Files
mostovik-backend/docs/source-record-export-matrix-ru.md

9.2 KiB
Raw Blame History

Матрица файловых выгрузок источников

Пользовательский контракт

Frontend отправляет администраторский POST /api/v2/organization-source-records/export-ticket/ с массивом sources и выбранным format. В ответ он получает короткоживущий одноразовый ticket и передаёт его обычной HTML-формой в POST /api/v2/organization-source-records/export-download/. Поэтому браузер сохраняет потоковый ZIP напрямую на диск, не удерживая весь архив как Blob в JavaScript. Ticket передаётся в теле формы, не попадает в URL и после первого запроса становится недействительным.

Имя скачиваемого архива формируется по выбранным источникам и времени создания запроса: <source-stem>_YYYYMMDD_HHMMSS.zip, а для нескольких источников их имена соединяются через __. Это же имя возвращается в file_name при выдаче ticket и затем используется в Content-Disposition.

Совместимый администраторский POST /api/v2/organization-source-records/export/ по-прежнему сразу возвращает тот же ZIP API-клиентам. Крупный XLSX может состоять из нескольких файлов *-part-001.xlsx, *-part-002.xlsx и далее.

При скачивании endpoint не читает таблицы записей источников и не строит CSV, XLSX или JSON заново. Он упаковывает файлы последнего полностью опубликованного ночного поколения и сразу потоково отправляет ZIP без временной копии всего архива. Поэтому Content-Length у ответа отсутствует. Если ни одного поколения ещё нет, API отвечает 503 с кодом source_export_not_ready.

Каждое поколение содержит только текущий календарный год в timezone сервиса. Для записей с предметной датой год определяется по ней, для записей без такой даты — по created_at, для финансовых отчётов — по году financial_lines. Вложенные финансовые строки других лет исключаются. После смены года поколение прошлого года не раздаётся: до первой успешной сборки нового года API отвечает 503 source_export_not_ready.

Матрица

Группа API Файл CSV XLSX JSON
financial_indicators financial-indicators да
government_procurements public-procurements да да да
industrial_production manufacturers-and-products да да да
planned_inspections planned-inspections да да да
bankruptcy bankruptcy-procedures да да да
defense_suppliers defense-unreliable-suppliers да да да
arbitration arbitration-cases да да да
security_registries information-security-registries да да да
vacancies labor-vacancies да да да

Итого формируется 25 логических артефактов: один JSON для финансовых показателей и по три формата для остальных восьми групп. Физических файлов может быть больше из-за разбиения крупных XLSX. Если финансовые показатели выбраны вместе с другим форматом, в ZIP для них всё равно включается JSON.

Все форматы начинают строку организации с полей Наименование, ИНН, ОГРН, КПП, ОКПО, после которых следуют поля исходной записи и развёрнутого payload. Все записи текущего года включаются в публичные файлы, а техническое наименование внешнего поставщика нейтрализуется. Исходные значения в БД сохраняются для работы интеграции и дедупликации.

Ночная генерация

Celery Beat запускает organizations.tasks.refresh_source_record_export_artifacts ежедневно в 05:30 Europe/Moscow, после ежедневного обновления organization sources в 04:30.

Генератор:

  1. читает каждую группу из БД один раз без глобальной сортировки миллионов строк; выборка текущего года использует функциональный индекс по году строковой record_date, а записи без предметной даты — индекс created_at;
  2. пишет compact JSON-массив на диск и использует его как готовый JSON без второй копии;
  3. потоково создаёт CSV и XLSX без накопления всех строк в памяти;
  4. разбивает XLSX по умолчанию по 100 000 строк на отдельные файлы, ограничивая временный XML и не превышая лимит Excel;
  5. записывает размеры файлов, номера частей и календарный export_year в manifest;
  6. атомарно переключает current.json только после готовности всей матрицы;
  7. сохраняет текущее и предыдущее поколения по умолчанию.

При ошибке незавершённое поколение удаляется, а download endpoint продолжает отдавать предыдущую успешную версию.

Расчёт диска

Атомарная публикация требует одновременно хранить уже опубликованные поколения и одно новое поколение в staging. Минимальный запас под артефакты рассчитывается как (SOURCE_RECORD_EXPORT_GENERATIONS_TO_KEEP + 1) * размер поколения, плюс рабочий запас файловой системы. Исторический снимок 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.

Хранение и первый запуск

Каталог задаётся через SOURCE_RECORD_EXPORT_DIRECTORY, по умолчанию — media/source-record-exports. Он должен быть общим read-write volume для web и Celery worker. В Docker Compose используется ./media:/app/media.

После первого развёртывания готовое поколение можно создать сразу, не ожидая ночного расписания:

PYTHONPATH=src uv run python src/manage.py build_source_record_exports

Доступные настройки:

Настройка Значение по умолчанию Назначение
SOURCE_RECORD_EXPORT_DIRECTORY media/source-record-exports Общий каталог артефактов
SOURCE_RECORD_EXPORT_GENERATIONS_TO_KEEP 2 Число сохраняемых успешных поколений
SOURCE_RECORD_EXPORT_LOCK_TTL_SECONDS 21600 TTL распределённой блокировки Celery
SOURCE_RECORD_EXPORT_XLSX_ROWS_PER_FILE 100000 Максимум строк данных в одной XLSX-части
SOURCE_RECORD_EXPORT_DOWNLOAD_TICKET_TTL_SECONDS 300 Срок действия одноразового browser-download ticket