# Матрица файловых выгрузок внешних данных State Corp ## Пользовательский контракт Администраторский frontend отправляет `POST /api/v2/organization-source-records/export-ticket/` с массивом `sources` и форматом. Backend проверяет последнее полностью опубликованное поколение и возвращает короткоживущий одноразовый ticket. Затем frontend передаёт ticket в теле обычной HTML-формы на `POST /api/v2/organization-source-records/export-download/`. Имя скачиваемого архива формируется по выбранным источникам и времени создания запроса: `_YYYYMMDD_HHMMSS.zip`, а для нескольких источников их имена соединяются через `__`. Значение `file_name` из ticket и итоговый `Content-Disposition` совпадают. Браузер получает потоковый ZIP напрямую, без многогигабайтного `Blob` в JavaScript. Ticket не попадает в URL и после первого запроса становится недействительным. Совместимый администраторский endpoint `POST /api/v2/organization-source-records/export/` сразу возвращает тот же ZIP для API-клиентов. Во время скачивания таблицы `external_data` не читаются: endpoint упаковывает готовые файлы последнего ночного поколения. При отсутствии поколения API возвращает `503` с кодом `source_export_not_ready`. Каждое поколение содержит только текущий календарный год в timezone сервиса. Для моделей с предметной датой год определяется по ней, для моделей без такой даты — по `created_at`, для финансовых отчётов — по году строк отчёта. Строки других лет из финансового отчёта не выгружаются. После смены года старое поколение не раздаётся: до успешной сборки нового года API отвечает `503 source_export_not_ready`. ## Матрица | Группа API | Таблицы State Corp | Файл | CSV | XLSX | JSON | |---|---|---|:---:|:---:|:---:| | `financial_indicators` | `FinancialReport`, `FinancialReportLine` | `financial-indicators` | — | — | да | | `government_procurements` | `PublicProcurement` | `public-procurements` | да | да | да | | `industrial_production` | `IndustrialProduct`, `IndustrialCertificate`, `ManufacturerRegistryEntry` | `manufacturers-and-products` | да | да | да | | `planned_inspections` | `ProsecutorCheck` | `planned-inspections` | да | да | да | | `bankruptcy` | `BankruptcyProcedure` | `bankruptcy-procedures` | да | да | да | | `defense_suppliers` | `DefenseUnreliableSupplier` | `defense-unreliable-suppliers` | да | да | да | | `arbitration` | `ArbitrationCase` | `arbitration-cases` | да | да | да | | `security_registries` | `InformationSecurityRegistryEntry` | `information-security-registries` | да | да | да | | `vacancies` | `LaborVacancy` | `labor-vacancies` | да | да | да | Итого формируется 25 логических артефактов. Финансовые показатели всегда выгружаются в JSON с вложенным массивом `financial_lines`. Промышленная группа объединяет три таблицы, а поле `record_type` различает тип строки. Все строки используют тот же контракт, что и Mostovik: реквизиты организации, включая ОКПО, общие поля записи источника и специфичные поля в `payload.*`. Все записи текущего года включаются в публичные файлы, а техническое наименование внешнего поставщика нейтрализуется. Исходные значения в БД сохраняются для работы интеграции и дедупликации. Физических XLSX-файлов может быть больше: по умолчанию один файл содержит не более 100 000 строк данных и получает суффикс `-part-001`, `-part-002` и далее. ## Ночная генерация Celery Beat запускает `apps.external_data.tasks.refresh_source_record_export_artifacts` ежедневно в `05:30 Europe/Moscow`. Генератор: 1. читает каждую нормализованную таблицу один раз без model-level сортировки; предметные даты и fallback по `created_at` индексированы; 2. создаёт компактный JSON-массив и переиспользует его как готовый JSON; 3. потоково формирует CSV и write-only XLSX; 4. записывает календарный `export_year` в manifest; 5. атомарно публикует `current.json` только после готовности всей матрицы; 6. при ошибке удаляет staging и продолжает отдавать предыдущее поколение того же года; 7. сохраняет текущее и предыдущее поколения по умолчанию. Web и Celery worker должны использовать общий read-write volume `/app/media`. ## Первый запуск и настройки Первое поколение можно сформировать вручную: ```bash 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 распределённой блокировки | | `SOURCE_RECORD_EXPORT_XLSX_ROWS_PER_FILE` | `100000` | Строк данных в одной XLSX-части | | `SOURCE_RECORD_EXPORT_DOWNLOAD_TICKET_TTL_SECONDS` | `300` | Срок действия download-ticket | Для атомарной генерации требуется свободное место не меньше `(GENERATIONS_TO_KEEP + 1) * размер поколения` плюс запас файловой системы. На локальном снимке от 2026-08-04 поколение за 2026 год содержит 1 962 записи и занимает 4 738 872 байта (4,52 MiB) для всей матрицы. Архив со всеми источниками оценивается в 2,49 MiB для JSON, 1,59 MiB для CSV и 0,44 MiB для XLSX; к сумме добавляется небольшой служебный overhead ZIP.