Files
state-corp-backend/docs/source-record-export-matrix-ru.md
Aleksandr Meshchriakov 2b9ecc2681
All checks were successful
CI/CD Pipeline / Code Quality Checks (push) Successful in 3m16s
CI/CD Pipeline / Run Tests (push) Successful in 4m58s
CI/CD Pipeline / Build and Push Dev Images (push) Successful in 32s
CI/CD Pipeline / Deploy Dev via Compose (push) Successful in 33s
feat: limit source exports to current year
2026-08-04 23:25:36 +02:00

103 lines
7.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Матрица файловых выгрузок внешних данных 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/`.
Имя скачиваемого архива формируется по выбранным источникам и времени создания
запроса: `<source-stem>_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.