Files
mostovik-backend/docs/source-record-export-matrix-ru.md
Aleksandr Meshchriakov eaf8a18f8b
All checks were successful
CI/CD Pipeline / Quality Gate (push) Successful in 30s
CI/CD Pipeline / Build and Push Images (push) Successful in 34s
CI/CD Pipeline / Internal Notify (push) Successful in 0s
CI/CD Pipeline / Deploy Dev via Compose (push) Successful in 32s
fix: preserve source export record counts
2026-08-04 12:24:09 +02:00

102 lines
7.2 KiB
Markdown
Raw 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.
# Матрица файловых выгрузок источников
## Пользовательский контракт
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 и после первого
запроса становится недействительным.
Совместимый администраторский
`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`.
## Матрица
| Группа 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. читает каждую группу из БД один раз без глобальной сортировки миллионов строк;
2. пишет compact JSON-массив на диск и использует его как готовый JSON без второй копии;
3. потоково создаёт CSV и XLSX без накопления всех строк в памяти;
4. разбивает XLSX по умолчанию по 100 000 строк на отдельные файлы, ограничивая временный XML и не превышая лимит Excel;
5. записывает размеры файлов и номера частей в manifest;
6. атомарно переключает `current.json` только после готовности всей матрицы;
7. сохраняет текущее и предыдущее поколения по умолчанию.
При ошибке незавершённое поколение удаляется, а download endpoint продолжает
отдавать предыдущую успешную версию.
### Расчёт диска
Атомарная публикация требует одновременно хранить уже опубликованные поколения
и одно новое поколение в staging. Минимальный запас под артефакты рассчитывается
как `(SOURCE_RECORD_EXPORT_GENERATIONS_TO_KEEP + 1) * размер поколения`, плюс
рабочий запас файловой системы. На снимке dev от 2026-08-03 одно поколение
заняло 16,55 ГБ (54 физических файла), поэтому при значении `2` следует
выделить не менее 55 ГБ свободного места под каталог выгрузок. Временная копия
целого ZIP при скачивании не создаётся.
## Хранение и первый запуск
Каталог задаётся через `SOURCE_RECORD_EXPORT_DIRECTORY`, по умолчанию —
`media/source-record-exports`. Он должен быть общим read-write volume для web и
Celery worker. В Docker Compose используется `./media:/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 распределённой блокировки Celery |
| `SOURCE_RECORD_EXPORT_XLSX_ROWS_PER_FILE` | `100000` | Максимум строк данных в одной XLSX-части |
| `SOURCE_RECORD_EXPORT_DOWNLOAD_TICKET_TTL_SECONDS` | `300` | Срок действия одноразового browser-download ticket |