feat: limit source exports to current year

This commit is contained in:
2026-08-04 23:25:26 +02:00
parent eaf8a18f8b
commit ef92cc4610
7 changed files with 308 additions and 25 deletions

View File

@@ -11,6 +11,11 @@ Frontend отправляет администраторский
JavaScript. Ticket передаётся в теле формы, не попадает в URL и после первого
запроса становится недействительным.
Имя скачиваемого архива формируется по выбранным источникам и времени создания
запроса: `<source-stem>_YYYYMMDD_HHMMSS.zip`, а для нескольких источников их
имена соединяются через `__`. Это же имя возвращается в `file_name` при выдаче
ticket и затем используется в `Content-Disposition`.
Совместимый администраторский
`POST /api/v2/organization-source-records/export/` по-прежнему сразу возвращает
тот же ZIP API-клиентам. Крупный XLSX может состоять из нескольких файлов
@@ -22,6 +27,13 @@ XLSX или JSON заново. Он упаковывает файлы после
архива. Поэтому `Content-Length` у ответа отсутствует. Если ни одного поколения
ещё нет, API отвечает `503` с кодом `source_export_not_ready`.
Каждое поколение содержит только текущий календарный год в timezone сервиса.
Для записей с предметной датой год определяется по ней, для записей без такой
даты — по `created_at`, для финансовых отчётов — по году `financial_lines`.
Вложенные финансовые строки других лет исключаются. После смены года поколение
прошлого года не раздаётся: до первой успешной сборки нового года API отвечает
`503 source_export_not_ready`.
## Матрица
| Группа API | Файл | CSV | XLSX | JSON |
@@ -43,9 +55,9 @@ XLSX или JSON заново. Он упаковывает файлы после
Все форматы начинают строку организации с полей `Наименование`, `ИНН`, `ОГРН`,
`КПП`, `ОКПО`, после которых следуют поля исходной записи и развёрнутого
`payload`. Все записи включаются в публичные файлы, а техническое наименование
внешнего поставщика нейтрализуется. Исходные значения в БД сохраняются для
работы интеграции и дедупликации.
`payload`. Все записи текущего года включаются в публичные файлы, а техническое
наименование внешнего поставщика нейтрализуется. Исходные значения в БД
сохраняются для работы интеграции и дедупликации.
## Ночная генерация
@@ -57,10 +69,12 @@ Celery Beat запускает
Генератор:
1. читает каждую группу из БД один раз без глобальной сортировки миллионов строк;
выборка текущего года использует функциональный индекс по году строковой
`record_date`, а записи без предметной даты — индекс `created_at`;
2. пишет compact JSON-массив на диск и использует его как готовый JSON без второй копии;
3. потоково создаёт CSV и XLSX без накопления всех строк в памяти;
4. разбивает XLSX по умолчанию по 100 000 строк на отдельные файлы, ограничивая временный XML и не превышая лимит Excel;
5. записывает размеры файлов и номера частей в manifest;
5. записывает размеры файлов, номера частей и календарный `export_year` в manifest;
6. атомарно переключает `current.json` только после готовности всей матрицы;
7. сохраняет текущее и предыдущее поколения по умолчанию.
@@ -72,10 +86,16 @@ Celery Beat запускает
Атомарная публикация требует одновременно хранить уже опубликованные поколения
и одно новое поколение в staging. Минимальный запас под артефакты рассчитывается
как `(SOURCE_RECORD_EXPORT_GENERATIONS_TO_KEEP + 1) * размер поколения`, плюс
рабочий запас файловой системы. На снимке dev от 2026-08-03 одно поколение
заняло 16,55 ГБ (54 физических файла), поэтому при значении `2` следует
выделить не менее 55 ГБ свободного места под каталог выгрузок. Временная копия
целого ZIP при скачивании не создаётся.
рабочий запас файловой системы. Исторический снимок dev от 2026-08-03 до
ограничения по году занимал 16,55 ГБ (54 физических файла); актуальный размер
годового поколения нужно брать из `total_size` результата команды сборки.
Временная копия целого ZIP при скачивании не создаётся.
На локальном снимке от 2026-08-04 поколение за 2026 год содержит 122 582 записи
и занимает 809 768 139 байт (772,3 MiB) для всей матрицы из 25 файлов. Архив со
всеми источниками оценивается в 410,1 MiB для JSON, 289,9 MiB для CSV и
72,3 MiB для XLSX; ZIP использует `ZIP_STORED`, поэтому к сумме файлов
добавляется только небольшой служебный overhead.
## Хранение и первый запуск