All checks were successful
Mostovik Backend CI/CD / Tests and lint (push) Successful in 3m55s
Mostovik Backend CI/CD / Build linux/amd64 release images (push) Successful in 3m43s
Mostovik Backend CI/CD / Deploy and verify internal main (push) Has been skipped
Mostovik Backend CI/CD / Deploy customer main (push) Has been skipped
Mostovik Backend CI/CD / Deploy dev (push) Successful in 1m45s
1083 lines
59 KiB
Markdown
1083 lines
59 KiB
Markdown
---
|
||
template_id: source-backend-api
|
||
template_version: 1
|
||
source_id: 'sme-support-recipients-registry'
|
||
source_name: 'Реестр субъектов МСП — получателей поддержки'
|
||
document_status: review
|
||
---
|
||
|
||
# Backend API источника «Реестр субъектов МСП — получателей поддержки»
|
||
|
||
> Уточнение 14.09.2026: разделы подготовки выгрузки с `preparing` / HTTP 202
|
||
> заменены [общим runtime-контрактом](../../../source-records-public-runtime-contract.md):
|
||
> готовый ticket HTTP 201 → native form download HTTP 200; устаревшее поколение —
|
||
> HTTP 503 `source_export_not_ready`, истёкший/использованный ticket — HTTP 410 `ticket_expired`.
|
||
|
||
Назначение: backend-first спецификация загрузки официального набора ФНС, нормализации мер
|
||
поддержки, обогащения организаций и предоставления frontend полного typed list/detail API.
|
||
Frontend не обращается к `rmsp-pp.nalog.ru`, не скачивает ZIP/XML и не обходит внешний поиск.
|
||
|
||
## Принципы документа
|
||
|
||
- production-источник истины — ежемесячный XML ZIP из Open Data ФНС, а не внутренний JSON API;
|
||
- одна `СвПредПод` становится одной записью `sme_support_measure`;
|
||
- list payload лёгкий, detail payload полный и содержит вложенные массивы;
|
||
- Наименование, ИНН, ОГРН и ОКПО обязательны у каждой опубликованной организации;
|
||
- ОКПО и остальные отсутствующие реквизиты дообогащает backend;
|
||
- raw, staging, quarantine и published snapshot имеют раздельные счётчики;
|
||
- предыдущий published snapshot сохраняется до атомарного завершения нового импорта;
|
||
- универсальные endpoint расширяются, source-specific URL не создаются;
|
||
- идентификаторы и decimal передаются строками, даты — ISO 8601/date;
|
||
- `openapi.json`, runtime-response и contract tests должны совпадать до передачи frontend;
|
||
- display-строки, HTML и пять nullable-полей размера вместо массива backend не возвращает.
|
||
|
||
## 1. Назначение и область реализации
|
||
|
||
### Описание источника
|
||
|
||
| Параметр | Значение |
|
||
| ---------------------------- | ------------------------------------------------------------------------------------------------------------- |
|
||
| Наименование | Реестр субъектов МСП — получателей поддержки |
|
||
| Назначение | Сведения о получателе, решении, форме/виде/размере поддержки, поставщике, нормативных документах и нарушениях |
|
||
| Владелец backend | Команда backend/data Mostovik |
|
||
| Внешняя система | ФНС России: `nalog.gov.ru/opendata/7707329152-rsmppp` и `rmsp-pp.nalog.ru` |
|
||
| Способ получения | Discovery metadata → скачивание ZIP → потоковый XML parser → XSD/semantic validation |
|
||
| Периодичность | Ежемесячный snapshot; ежедневная проверка нового файла после 15-го числа |
|
||
| Наблюдаемый объём 15.08.2026 | 12 580 017 мер, 3 287 971 получатель, ZIP около 802 MiB |
|
||
| Ограничения web-поиска | До 30 000 ИНН/результатов, page size до 100, XLSX до 10 000; не production transport |
|
||
| Retention | Raw archive/checksum/provenance и минимум два последних snapshot; published переключается атомарно |
|
||
|
||
Официальные upstream URL:
|
||
|
||
```text
|
||
https://www.nalog.gov.ru/opendata/7707329152-rsmppp/
|
||
https://file.nalog.ru/opendata/7707329152-rsmppp/structure-20230615.xsd
|
||
https://file.nalog.ru/opendata/7707329152-rsmppp/VO_SVMSP_2_213_23_04_04.docx
|
||
https://rmsp-pp.nalog.ru/search.html?m=SupportList
|
||
https://rmsp-pp.nalog.ru/statistics.html
|
||
```
|
||
|
||
### Наблюдаемый upstream-контракт
|
||
|
||
XSD 4.04 задаёт `Файл → Документ → СвЮЛ/СвФЛ → СвПредПод[1..N]`.
|
||
|
||
Получатель-ЮЛ содержит обязательные `НаимОрг`, ИНН и ОГРН. Мера поддержки содержит:
|
||
|
||
- строковый реестровый номер до 36 символов;
|
||
- дату внесения сведений, тип получателя и категорию МСП на дату решения;
|
||
- название и ИНН поставщика;
|
||
- дату решения, срок поддержки и необязательную дату прекращения;
|
||
- форму и вид поддержки как код + название;
|
||
- `РазмПод[1..N]`: decimal(18,2) и единица `1..5`;
|
||
- признак и массив нарушений;
|
||
- массив нормативных документов;
|
||
- необязательную дату последнего изменения.
|
||
|
||
В проверенной web-выборке 200 ИНН найдено 46 получателей и 347 мер; на одного получателя
|
||
приходилось от 1 до 33 мер. Все 347 строк имели разные `dt_create` и `dt_insert`. Пример
|
||
`45316518`: JSON `dt_create=15.10.2024`, `dt_insert=04.10.2024 14:59:07`, а XLSX показывает
|
||
дату внесения `04.10.2024`. Поэтому web-поля служат QA fixture, а production date mapping
|
||
строится только от XML.
|
||
|
||
### Покрываемые frontend-сценарии
|
||
|
||
| Сценарий | Требуется | Страница / элемент | Endpoint |
|
||
| ---------------------------- | --------- | ------------------------------------------ | ----------------------------------------- |
|
||
| Каталог | Да | `/sources`, карточка | `/api/v1/sources/*` |
|
||
| Главная аналитика | Да | `/main`, source/coverage | `/api/v1/parsers/dashboard/` |
|
||
| Detail источника | Да | `/sources/sme-support-recipients-registry` | source detail + records |
|
||
| Таблица/карточка организации | Да | source detail и `/organizations/:uid` | v2 source records |
|
||
| Полный detail записи | Да | диалог записи | v2 record detail |
|
||
| Ручное обновление | Да | `/settings/scraping` | refresh/parser run/jobs |
|
||
| История | Да | `/update-history` | system logs |
|
||
| Выгрузка | Да | настройки источников | export ticket/download |
|
||
| File upload | Нет | upstream публикует URL | upload endpoint возвращает documented 405 |
|
||
|
||
## 2. Нормативные ссылки и аудит текущих контрактов
|
||
|
||
| Уровень | Репозиторный путь | Что изменить/проверить |
|
||
| -------------------- | ----------------------------------------------------- | -------------------------------------------------------- |
|
||
| Backend-first эталон | `docs/info/backend-endpoints-main-page-from-mocks.md` | envelope, statuses, jobs и counts |
|
||
| Машинная схема | `openapi.json` | новые enum, filters, ordering, named list/detail payload |
|
||
| Orval | `orval.config.ts` | стабильные operationId и generation |
|
||
| Generated TypeScript | `src/shared/api/generated-api/` | DTO/functions list/detail/dashboard/refresh/export |
|
||
| Generated Zod | `src/shared/model/generated-zod/` | required/nullable/enums/nested arrays |
|
||
| Runtime adapters | `src/pages/main/model/` | удалить временные DTO после генерации |
|
||
| Contract tests | `src/pages/main/model/__contract__/` | фактические responses стенда |
|
||
| Шаблоны источников | `docs/source-integration/templates/` | структура документов версии 1/1/2 |
|
||
|
||
### Точки сверки по каждому активному endpoint
|
||
|
||
| Endpoint | Текущее состояние | Целевое состояние |
|
||
| ------------------------------------------------ | ----------------------------------- | ---------------------------------------------------- |
|
||
| `GET /api/v1/sources/` | Универсальный каталог | Новая карточка и точные counts |
|
||
| `GET /api/v1/sources/{slug}/` | Универсальный detail | Source item, snapshot/raw/quarantine metrics |
|
||
| `GET /api/v1/parsers/dashboard/` | OpenAPI schema неполная | Полностью типизированный parser/dashboard snapshot |
|
||
| `GET /api/v2/organization-source-records/` | Generic payload и ограниченные enum | Light typed payload, filters/orderings для поддержки |
|
||
| `GET /api/v2/organization-source-records/{uid}/` | Generic detail schema | Full `SmeSupportRecordDetailPayload` |
|
||
| Refresh/jobs/logs/export | Универсальные процессы | Добавить идентификаторы источника и metrics |
|
||
|
||
### Реестр расхождений этого источника
|
||
|
||
| Расхождение | Решение |
|
||
| ------------------------------------------------------- | ------------------------------------------------------------------------------ |
|
||
| XLSX содержит 27 плоских колонок, XSD допускает массивы | В storage/API размеры, документы и нарушения остаются массивами |
|
||
| `dt_create` и `dt_insert` web API расходятся | Канонична `ДатаСвед` XML; web timestamps только diagnostic fixture |
|
||
| XML не содержит ОКПО, КПП, адрес, ОКВЭД и регион | Enrichment из канонического реестра, provenance `canonical_organization` |
|
||
| XML включает ИП/КФХ/НПД, текущий API требует ОГРН+ОКПО | Сохранить raw/staging; первая итерация публикует только ЮЛ |
|
||
| `id == support_regnum` в выборке | Не полагаться на числовой `id`; внешний ключ — строковый реестровый номер |
|
||
| `row_cnt` повторяет total query | Не хранить в записи |
|
||
| Код единицы 5 наблюдался со значением больше 1 | Не терять raw; semantic warning/quarantine по решению после production fixture |
|
||
| `amount` generic API не выражает часы/проценты/единицы | `amount` только RUB; UI использует `support_sizes[]` |
|
||
|
||
## 3. Идентификаторы источника
|
||
|
||
| Идентификатор | Значение | Использование |
|
||
| ---------------- | -------------------------------------------- | --------------------------------------- |
|
||
| `source_id` | `sme-support-recipients-registry` | Папка и спецификация |
|
||
| `parentSlug` | `sme-support-recipients-registry` | `/sources/*` |
|
||
| `routeSlug` | `sme-support-recipients-registry` | Frontend route |
|
||
| `parserSource` | `fns_sme_support_recipients` | dashboard, logs, source record `source` |
|
||
| `sourceGroup` | `government_support` | records и export enum |
|
||
| `recordType` | `sme_support_measure` | Классификация записи |
|
||
| `sourceItemCode` | `fns_sme_support_recipients` | `source_items[].code` |
|
||
| `refreshKey` | `fns_sme_support_recipients` | source refresh/parser run |
|
||
| `taskName` | `parsers.fns_sme_support_recipients.refresh` | Jobs metadata |
|
||
| `tableKey` | `sme-support-records` | Только frontend |
|
||
|
||
Допустимые aliases на период миграции: `sme-support-registry`, `fns-sme-support-recipients`.
|
||
Backend всегда отвечает каноническими значениями. `uid` source record, XML `ИдДок`, номер
|
||
поддержки и `organization.uid` — разные идентификаторы.
|
||
|
||
Стабильный ключ записи: `(parserSource, support_registry_number)`. При обнаружении одного
|
||
номера у разных получателей snapshot блокируется как integrity conflict; UUID записи при
|
||
повторной загрузке того же номера не меняется.
|
||
|
||
## 4. Обязательный контракт организации
|
||
|
||
| Пользовательское поле | API-поле | Тип | Required | Nullable | Правило |
|
||
| --------------------- | ------------------- | ------ | -------: | -------: | ------------------------------------ |
|
||
| Наименование | `organization.name` | string | Да | Нет | Каноническое непустое название |
|
||
| ИНН | `organization.inn` | string | Да | Нет | Ровно 10 цифр для опубликованного ЮЛ |
|
||
| ОГРН | `organization.ogrn` | string | Да | Нет | Ровно 13 цифр для опубликованного ЮЛ |
|
||
| ОКПО | `organization.okpo` | string | Да | Нет | 8/10 цифр по каноническому реестру |
|
||
|
||
Дополнительно detail может вернуть nullable `kpp`, `legal_address`, `business_activity`,
|
||
`region`, но эти поля не заменяют обязательную четвёрку.
|
||
|
||
### Происхождение и нормализация обязательных полей
|
||
|
||
| Поле | Upstream | Алгоритм | При отсутствии/конфликте |
|
||
| ------------------- | -------------- | -------------------------------------------------------- | ------------------------------------------------- |
|
||
| `organization.name` | `СвЮЛ.НаимОрг` | trim/Unicode/quote normalization, затем каноническое имя | `organization_not_found` либо `identity_conflict` |
|
||
| `organization.inn` | `СвЮЛ.ИННЮЛ` | строка, контрольный разряд, без numeric cast | `invalid_inn` |
|
||
| `organization.ogrn` | `СвЮЛ.ОГРН` | строка, контрольный разряд, основной exact lookup | `invalid_ogrn` |
|
||
| `organization.okpo` | отсутствует | exact ОГРН → проверка ИНН → канонический ОКПО | `okpo_missing` |
|
||
|
||
Порядок enrichment: exact ОГРН, подтверждение ИНН, fallback exact ИНН только при единственном
|
||
кандидате, проверка имени как сигнал, но не как самостоятельный ключ. Любое неоднозначное
|
||
совпадение уходит в quarantine. Поставщик поддержки обогащается отдельно и не блокирует
|
||
публикацию основной записи.
|
||
|
||
### Source-specific модель
|
||
|
||
List payload:
|
||
|
||
```text
|
||
support_registry_number, recipient_type, sme_category, region,
|
||
support_form, support_kind, decision_date, support_until, termination_date,
|
||
registry_entry_date, source_updated_date, support_sizes[], provider,
|
||
has_violation, violation_count, regulatory_documents_count, source_snapshot_date
|
||
```
|
||
|
||
Detail дополнительно содержит:
|
||
|
||
```text
|
||
source_document_id, organization_profile, provider enrichment,
|
||
regulatory_documents[], violations[], provenance
|
||
```
|
||
|
||
Dictionary value: `{ code: string, name: string }`. Decimal size: строка с максимум двумя
|
||
знаками после точки. `support_sizes` не бывает пустым. Единицы нормализуются:
|
||
|
||
| Код | `unit` | Название |
|
||
| --- | -------------- | --------------- |
|
||
| `1` | `RUB` | рубль |
|
||
| `2` | `square_meter` | квадратный метр |
|
||
| `3` | `hour` | час |
|
||
| `4` | `percent` | процент |
|
||
| `5` | `unit` | единица |
|
||
|
||
## 5. Матрица endpoint и решение по реализации
|
||
|
||
| № | Метод и endpoint | Статус | Изменение | Потребитель |
|
||
| --: | ----------------------------------------------------------- | --------------------- | -------------------------------------- | -------------------- |
|
||
| 1 | `GET /api/v1/sources/` | Нужно расширить | Карточка, counts, snapshot | `/sources`, `/main` |
|
||
| 2 | `GET /api/v1/sources/{slug}/` | Нужно расширить | Source item и source-specific metrics | Detail/scraping |
|
||
| 3 | `GET /api/v1/parsers/dashboard/` | Нужно расширить | Typed source/dashboard/coverage | Main/detail/scraping |
|
||
| 4 | `GET /api/v2/organization-source-records/` | Нужно расширить | Enum, filters, ordering, light payload | Таблицы |
|
||
| 5 | `GET /api/v2/organization-source-records/{uid}/` | Нужно расширить | Full nested detail | Диалог записи |
|
||
| 6 | `POST /api/v1/sources/{slug}/refresh/` | Нужно расширить | Одиночный refresh без params | Scraping |
|
||
| 7 | `POST /api/v1/parsers/run/{source_key}/` | Нужно расширить | Direct parser run | Internal/settings |
|
||
| 8 | `GET /api/v1/jobs/{task_id}/` | Существует и подходит | Source-specific progress meta | Polling |
|
||
| 9 | `GET /api/v1/system/logs/` | Нужно расширить | Фильтр/metrics parserSource | History |
|
||
| 10 | `GET /api/v1/system/logs/{id}/` | Нужно расширить | Snapshot/quarantine detail | History detail |
|
||
| 11 | `GET /api/v1/system/logs/export/` | Нужно расширить | История нового parserSource | CSV |
|
||
| 12 | `POST /api/v2/organization-source-records/export-ticket/` | Нужно расширить | Enum `government_support` | Export |
|
||
| 13 | `POST /api/v2/organization-source-records/export-download/` | Существует и подходит | Без source-specific логики | Export |
|
||
|
||
Существующие, но не обязательные endpoint:
|
||
|
||
- `GET /api/v1/sources/statuses/` — не использовать: карточки и dashboard уже содержат статус;
|
||
- `POST /api/v2/organization-source-records/export/` — синхронный export не использовать в UI,
|
||
но контракт сохранить документированным;
|
||
- `GET /api/v1/parsers/sources/` — не использовать, metadata приходит dashboard;
|
||
- `GET/POST /api/v1/parsers/schedules/*` — не вызывать frontend, schedule приходит dashboard;
|
||
- `GET /api/v1/parsers/load-logs/` — не использовать, история через system logs;
|
||
- `GET /api/v1/parsers/records/` — не использовать, таблица через v2 records;
|
||
- `GET /api/v1/parsers/results/{source_key}/*` — legacy, не добавлять;
|
||
- `POST /api/v1/parsers/upload/{source_key}/` — неприменим, источник URL-based;
|
||
- `GET /api/v1/jobs/` — неприменим, polling по detail job;
|
||
- `POST /api/v1/jobs/{task_id}/control/` и `GET /api/v1/jobs/{task_id}/stream/` — не требуются;
|
||
- legacy download `GET /api/v2/sources/<legacy-source>/.../download/` — не добавлять.
|
||
|
||
## 6. Общий паттерн описания endpoint
|
||
|
||
Все endpoint требуют bearer-auth. JSON responses используют проектный envelope, кроме
|
||
бинарного download. Date — `YYYY-MM-DD`, datetime — ISO 8601 с timezone. Пустые массивы — `[]`,
|
||
не `null`. Unknown counter — `null`, но известный ноль — `0`. `meta.pagination` обязателен для
|
||
списков.
|
||
|
||
Стандартная ошибка:
|
||
|
||
```json
|
||
{
|
||
"success": false,
|
||
"errors": [
|
||
{
|
||
"code": "validation_error",
|
||
"message": "Некорректный параметр source.",
|
||
"field": "source"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
Общие коды: `400` validation, `401` unauthenticated, `403` forbidden, `404` not found, `409`
|
||
conflict/already running, `422` unsupported filter combination, `429` throttled, `500` server.
|
||
GET можно повторять; POST refresh идемпотентен на время активной задачи.
|
||
|
||
## 7. Каталог источников
|
||
|
||
### 7.1 `GET /api/v1/sources/`
|
||
|
||
Request:
|
||
|
||
```http
|
||
GET /api/v1/sources/
|
||
Authorization: Bearer <token>
|
||
```
|
||
|
||
Response `200 application/json` содержит среди `data`:
|
||
|
||
```json
|
||
{
|
||
"slug": "sme-support-recipients-registry",
|
||
"title": "Реестр субъектов МСП — получателей поддержки",
|
||
"description": "Меры государственной поддержки юридических лиц из официального реестра ФНС.",
|
||
"order": 70,
|
||
"is_available": true,
|
||
"status": "success",
|
||
"status_label": "Обновлено",
|
||
"progress": 100,
|
||
"records_count": 8421050,
|
||
"organizations_count": 812340,
|
||
"last_updated_at": "2026-08-16T03:42:10+03:00",
|
||
"next_update_at": "2026-09-15T02:00:00+03:00",
|
||
"error_message": "",
|
||
"task_names": ["parsers.fns_sme_support_recipients.refresh"],
|
||
"refresh_requires_params": false,
|
||
"refresh_params": []
|
||
}
|
||
```
|
||
|
||
`records_count` — опубликованные меры ЮЛ; `organizations_count` — distinct canonical
|
||
`organization.uid`. Значения примера иллюстративны. `last_updated_at` — время публикации нашего
|
||
snapshot, не upstream `ДатаСост`. Сортировка каталога: `order`, затем `slug`.
|
||
|
||
Ошибки: `401` detail auth, `429` standard envelope, `500` standard envelope; повтор GET допустим.
|
||
|
||
### 7.2 `GET /api/v1/sources/{slug}/`
|
||
|
||
Request:
|
||
|
||
```http
|
||
GET /api/v1/sources/sme-support-recipients-registry/
|
||
Authorization: Bearer <token>
|
||
```
|
||
|
||
Response `200 application/json`:
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"errors": null,
|
||
"meta": {},
|
||
"data": {
|
||
"slug": "sme-support-recipients-registry",
|
||
"title": "Реестр субъектов МСП — получателей поддержки",
|
||
"description": "Меры государственной поддержки юридических лиц из официального реестра ФНС.",
|
||
"status": "success",
|
||
"status_label": "Обновлено",
|
||
"progress": 100,
|
||
"records_count": 8421050,
|
||
"organizations_count": 812340,
|
||
"last_updated_at": "2026-08-16T03:42:10+03:00",
|
||
"next_update_at": "2026-09-15T02:00:00+03:00",
|
||
"error_message": "",
|
||
"task_names": ["parsers.fns_sme_support_recipients.refresh"],
|
||
"refresh_requires_params": false,
|
||
"refresh_params": [],
|
||
"active_tasks": [],
|
||
"source_metrics": {
|
||
"source_snapshot_date": "2026-08-15",
|
||
"raw_support_records_count": 12580017,
|
||
"raw_recipients_count": 3287971,
|
||
"raw_legal_entities_count": 862467,
|
||
"published_records_count": 8421050,
|
||
"published_organizations_count": 812340,
|
||
"quarantined_records_count": 18420,
|
||
"unsupported_recipient_records_count": 4140547,
|
||
"violations_count": 11759
|
||
},
|
||
"source_items": [
|
||
{
|
||
"code": "fns_sme_support_recipients",
|
||
"refresh_key": "fns_sme_support_recipients",
|
||
"title": "Реестр субъектов МСП — получателей поддержки",
|
||
"parser_source": "fns_sme_support_recipients",
|
||
"parser_source_display": "Реестр субъектов МСП — получателей поддержки",
|
||
"records_count": 8421050,
|
||
"organizations_count": 812340,
|
||
"last_updated_at": "2026-08-16T03:42:10+03:00",
|
||
"latest_load": {
|
||
"batch_id": 5102,
|
||
"source": "fns_sme_support_recipients",
|
||
"records_count": 8421050,
|
||
"status": "success",
|
||
"error_message": "",
|
||
"created_at": "2026-08-16T01:10:00+03:00",
|
||
"updated_at": "2026-08-16T03:42:10+03:00"
|
||
},
|
||
"latest_success_load": {
|
||
"batch_id": 5102,
|
||
"source": "fns_sme_support_recipients",
|
||
"records_count": 8421050,
|
||
"status": "success",
|
||
"error_message": "",
|
||
"created_at": "2026-08-16T01:10:00+03:00",
|
||
"updated_at": "2026-08-16T03:42:10+03:00"
|
||
}
|
||
}
|
||
],
|
||
"latest_load": null,
|
||
"latest_success_load": null
|
||
}
|
||
}
|
||
```
|
||
|
||
`source_metrics` values are integer >=0. `unsupported_recipient_records_count` includes measures
|
||
ИП/КФХ/НПД excluded from organization API. `404` returns `source_not_found`. `active_tasks[]`
|
||
uses job fields `task_id`, `task_name`, `status`, `progress`, `progress_message`, `started_at`,
|
||
`created_at`, `meta`.
|
||
|
||
## 8. Dashboard парсеров
|
||
|
||
### 8.1 `GET /api/v1/parsers/dashboard/`
|
||
|
||
Request:
|
||
|
||
```http
|
||
GET /api/v1/parsers/dashboard/
|
||
Authorization: Bearer <token>
|
||
```
|
||
|
||
Response `200` содержит:
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"sources": [
|
||
{
|
||
"key": "fns_sme_support_recipients",
|
||
"source": "fns_sme_support_recipients",
|
||
"title": "Реестр субъектов МСП — получателей поддержки",
|
||
"agency": "ФНС России",
|
||
"task_name": "parsers.fns_sme_support_recipients.refresh",
|
||
"is_existing": true,
|
||
"requires_file_url": false,
|
||
"mode": "scheduled",
|
||
"status": "active",
|
||
"access_method": "open_data_xml_zip",
|
||
"parser_strategy": "atomic_snapshot",
|
||
"supports_file_upload": false,
|
||
"result_list_url": "/api/v2/organization-source-records/?source=fns_sme_support_recipients",
|
||
"result_detail_url": "/api/v2/organization-source-records/{uid}/"
|
||
}
|
||
],
|
||
"source_counts": { "fns_sme_support_recipients": 8421050 },
|
||
"load_logs": [],
|
||
"schedules": [
|
||
{
|
||
"key": "fns_sme_support_recipients",
|
||
"name": "parsers.fns_sme_support_recipients.refresh",
|
||
"enabled": true,
|
||
"schedule_type": "crontab",
|
||
"schedule": { "minute": "0", "hour": "2", "day_of_month": "15-31" }
|
||
}
|
||
],
|
||
"registry_enrichment_analytics": {
|
||
"population": { "active_registry_organizations": 1500000 },
|
||
"source_coverage": [
|
||
{
|
||
"source": "fns_sme_support_recipients",
|
||
"label": "Реестр субъектов МСП — получателей поддержки",
|
||
"records_count": 8421050,
|
||
"organizations_count": 125000,
|
||
"coverage_percent": 8.3333,
|
||
"last_updated_at": "2026-08-16T03:42:10+03:00"
|
||
}
|
||
],
|
||
"risk_signals": []
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
`source_coverage[].organizations_count` — пересечение опубликованных получателей с
|
||
организациями ОПК, а `/sources[].organizations_count` — все уникальные опубликованные
|
||
организации источника. Coverage не превышает
|
||
`population.active_registry_organizations`; процент вычисляется от числа организаций ОПК.
|
||
`source_counts` совпадает с published records. Полная OpenAPI schema обязательна, `data: void`
|
||
не допускается. Ошибки: `401`, `429`, `500`.
|
||
|
||
## 9. Записи источника
|
||
|
||
### 9.1 `GET /api/v2/organization-source-records/`
|
||
|
||
Request:
|
||
|
||
```http
|
||
GET /api/v2/organization-source-records/?source_group=government_support&source=fns_sme_support_recipients&record_type=sme_support_measure&page=1&page_size=50&ordering=-record_date
|
||
Authorization: Bearer <token>
|
||
```
|
||
|
||
Общие параметры: `organization` UUID, `search` max 200, `date_from/date_to` по decision date,
|
||
`page>=1`, `page_size` default 50/max 100.
|
||
|
||
Source-specific filters:
|
||
|
||
| Параметр | Тип | Правило |
|
||
| ------------------------ | -------------- | ----------------------------------------------------------------------------------------------- |
|
||
| `recipient_type` | enum | `legal_entity`, `individual_entrepreneur`, `farm`, `self_employed`; published MVP фактически ЮЛ |
|
||
| `sme_category` | enum | `micro`, `small`, `medium`, `not_sme` |
|
||
| `region_code` | string | Двузначный код обогащённой организации |
|
||
| `support_form_code` | string | Точный upstream-код |
|
||
| `support_kind_code` | string | Точный upstream-код |
|
||
| `provider_inn` | string | 10 цифр |
|
||
| `has_violation` | boolean | `true/false` |
|
||
| `support_unit` | enum | `RUB`, `square_meter`, `hour`, `percent`, `unit` |
|
||
| `amount_from/amount_to` | decimal string | Только RUB; включительно |
|
||
| `registry_entry_from/to` | date | Дата внесения XML |
|
||
| `support_until_from/to` | date | Срок поддержки |
|
||
| `termination_from/to` | date | Дата прекращения |
|
||
|
||
`search` ищет по name/ИНН/ОГРН/ОКПО, номеру поддержки, provider name/ИНН, form/kind. Ordering
|
||
whitelist:
|
||
|
||
```text
|
||
record_date, external_id, extension__organization__name,
|
||
extension__organization__inn, extension__organization__ogrn,
|
||
extension__organization__okpo, payload__support_form__name,
|
||
payload__support_kind__name, payload__support_until,
|
||
payload__provider__name, payload__sme_category__name,
|
||
payload__has_violation, amount
|
||
```
|
||
|
||
Каждое поле допускает `-`; default `-record_date,-external_id`, nulls last.
|
||
|
||
Response `200 application/json`:
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"errors": null,
|
||
"data": [
|
||
{
|
||
"uid": "2e5db996-e17d-4f5e-a898-bb29566927e4",
|
||
"extension_uid": "3e4c092e-81fb-4d41-9acb-f77922724339",
|
||
"source_group": "government_support",
|
||
"record_type": "sme_support_measure",
|
||
"source": "fns_sme_support_recipients",
|
||
"external_id": "45316518",
|
||
"title": "Иные консультационные услуги",
|
||
"record_date": "2024-09-27",
|
||
"amount": null,
|
||
"status": "published",
|
||
"url": "https://rmsp-pp.nalog.ru/subject.html?id=5030056754&id2=1075030001023#support=45316518",
|
||
"payload": {
|
||
"support_registry_number": "45316518",
|
||
"recipient_type": { "code": "1", "name": "Юридическое лицо" },
|
||
"sme_category": { "code": "1", "name": "Микропредприятие" },
|
||
"region": { "code": "77", "name": "Москва", "provenance": "canonical_organization" },
|
||
"support_form": { "code": "0400", "name": "Консультационная поддержка" },
|
||
"support_kind": { "code": "0401", "name": "Иные консультационные услуги" },
|
||
"decision_date": "2024-09-27",
|
||
"support_until": "2024-09-27",
|
||
"termination_date": null,
|
||
"registry_entry_date": "2024-10-04",
|
||
"source_updated_date": null,
|
||
"support_sizes": [{ "unit_code": "3", "unit": "hour", "value": "1.00" }],
|
||
"provider": {
|
||
"name": "АНО \"МОСКОВСКИЙ ЭКСПОРТНЫЙ ЦЕНТР\"",
|
||
"inn": "7710012211"
|
||
},
|
||
"has_violation": false,
|
||
"violation_count": 0,
|
||
"regulatory_documents_count": 1,
|
||
"source_snapshot_date": "2026-08-15"
|
||
},
|
||
"legacy_model": "",
|
||
"legacy_pk": "",
|
||
"load_batch": 5102,
|
||
"created_at": "2026-08-16T03:39:00+03:00",
|
||
"updated_at": "2026-08-16T03:39:00+03:00",
|
||
"financial_lines": [],
|
||
"organization": {
|
||
"uid": "0ebfa744-efc0-4e91-afaf-6b784b2bb22f",
|
||
"name": "АО \"90 ЭКСПЕРИМЕНТАЛЬНЫЙ ЗАВОД\"",
|
||
"full_name": "АКЦИОНЕРНОЕ ОБЩЕСТВО \"90 ЭКСПЕРИМЕНТАЛЬНЫЙ ЗАВОД\"",
|
||
"inn": "5030056754",
|
||
"kpp": "773001001",
|
||
"ogrn": "1075030001023",
|
||
"okpo": "07546844",
|
||
"legal_address": "г. Москва",
|
||
"business_activity": "Производство"
|
||
}
|
||
}
|
||
],
|
||
"meta": {
|
||
"pagination": {
|
||
"page": 1,
|
||
"page_size": 50,
|
||
"total_count": 2,
|
||
"total_pages": 1,
|
||
"has_next": false,
|
||
"has_previous": false
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
Примерные enriched КПП/ОКПО/адрес выше не являются утверждением о фактических реквизитах
|
||
организации; contract fixture backend обязан заменить их значениями тестового канонического
|
||
реестра. `amount=null`, потому что мера выражена в часах. Для RUB `amount` совпадает с суммой
|
||
RUB-элементов `support_sizes`.
|
||
|
||
### 9.2 `GET /api/v2/organization-source-records/{uid}/`
|
||
|
||
Request:
|
||
|
||
```http
|
||
GET /api/v2/organization-source-records/2e5db996-e17d-4f5e-a898-bb29566927e4/
|
||
Authorization: Bearer <token>
|
||
```
|
||
|
||
Response `200` повторяет все top-level/list поля и заменяет payload на full detail:
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"errors": null,
|
||
"meta": {},
|
||
"data": {
|
||
"uid": "2e5db996-e17d-4f5e-a898-bb29566927e4",
|
||
"source_group": "government_support",
|
||
"record_type": "sme_support_measure",
|
||
"source": "fns_sme_support_recipients",
|
||
"external_id": "45316518",
|
||
"title": "Иные консультационные услуги",
|
||
"record_date": "2024-09-27",
|
||
"amount": null,
|
||
"status": "published",
|
||
"url": "https://rmsp-pp.nalog.ru/subject.html?id=5030056754&id2=1075030001023#support=45316518",
|
||
"organization": {
|
||
"uid": "0ebfa744-efc0-4e91-afaf-6b784b2bb22f",
|
||
"name": "АО \"90 ЭКСПЕРИМЕНТАЛЬНЫЙ ЗАВОД\"",
|
||
"full_name": "АКЦИОНЕРНОЕ ОБЩЕСТВО \"90 ЭКСПЕРИМЕНТАЛЬНЫЙ ЗАВОД\"",
|
||
"inn": "5030056754",
|
||
"kpp": "773001001",
|
||
"ogrn": "1075030001023",
|
||
"okpo": "07546844",
|
||
"legal_address": "г. Москва",
|
||
"business_activity": "Производство"
|
||
},
|
||
"payload": {
|
||
"support_registry_number": "45316518",
|
||
"source_document_id": "VO-TEST-5030056754",
|
||
"recipient_type": { "code": "1", "name": "Юридическое лицо" },
|
||
"sme_category": { "code": "1", "name": "Микропредприятие" },
|
||
"region": { "code": "77", "name": "Москва", "provenance": "canonical_organization" },
|
||
"support_form": { "code": "0400", "name": "Консультационная поддержка" },
|
||
"support_kind": { "code": "0401", "name": "Иные консультационные услуги" },
|
||
"decision_date": "2024-09-27",
|
||
"support_until": "2024-09-27",
|
||
"termination_date": null,
|
||
"registry_entry_date": "2024-10-04",
|
||
"source_updated_date": null,
|
||
"support_sizes": [{ "unit_code": "3", "unit": "hour", "value": "1.00" }],
|
||
"provider": {
|
||
"name": "АНО \"МОСКОВСКИЙ ЭКСПОРТНЫЙ ЦЕНТР\"",
|
||
"inn": "7710012211",
|
||
"organization_uid": null,
|
||
"okpo": null,
|
||
"region": null,
|
||
"oktmo": null
|
||
},
|
||
"regulatory_documents": [
|
||
{
|
||
"kind": null,
|
||
"type": "Закон города Москвы",
|
||
"adopting_authority": "Московская городская Дума",
|
||
"date": "2008-11-26",
|
||
"number": "60",
|
||
"name": "О поддержке и развитии малого и среднего предпринимательства в городе Москве"
|
||
}
|
||
],
|
||
"has_violation": false,
|
||
"violations": [],
|
||
"provenance": {
|
||
"dataset_url": "https://www.nalog.gov.ru/opendata/7707329152-rsmppp/",
|
||
"archive_name": "data-20260815-structure-20230615.zip",
|
||
"archive_sha256": "00bc1d1ef97e1332f5e59fb04fd230f7ed0e0452ba7e90f6551d357ec891ce7a",
|
||
"source_snapshot_date": "2026-08-15",
|
||
"format_version": "4.04",
|
||
"xsd_name": "structure-20230615.xsd",
|
||
"load_batch": 5102,
|
||
"imported_at": "2026-08-16T03:39:00+03:00"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
`regulatory_documents` и `violations` всегда массивы. Violation object содержит `type` dictionary,
|
||
`recognized_date`, `remedy_deadline`, `remedied_date`. Provider enrichment nullable и не
|
||
подменяет upstream name/ИНН. `404 record_not_found`; запись из другого source возвращается по
|
||
UID корректно, а frontend проверяет `source/tableKey`.
|
||
|
||
## 10. Refresh и фоновые задачи
|
||
|
||
### 10.1 `POST /api/v1/sources/{slug}/refresh/`
|
||
|
||
Request:
|
||
|
||
```http
|
||
POST /api/v1/sources/sme-support-recipients-registry/refresh/
|
||
Authorization: Bearer <token>
|
||
Content-Type: application/json
|
||
|
||
{}
|
||
```
|
||
|
||
Response `202`:
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"errors": null,
|
||
"data": {
|
||
"task_id": "7b4d7911-5f1a-4fad-b2bb-67e9c170e4cc",
|
||
"task_ids": ["7b4d7911-5f1a-4fad-b2bb-67e9c170e4cc"],
|
||
"status": "queued"
|
||
}
|
||
}
|
||
```
|
||
|
||
Только роль с refresh permission. `409 already_running` возвращает существующий `task_id` в
|
||
`meta.active_task_id`. Параметры URL/file запрещены.
|
||
|
||
### 10.2 `POST /api/v1/parsers/run/{source_key}/`
|
||
|
||
Request:
|
||
|
||
```http
|
||
POST /api/v1/parsers/run/fns_sme_support_recipients/
|
||
Authorization: Bearer <token>
|
||
Content-Type: application/json
|
||
|
||
{}
|
||
```
|
||
|
||
Response `202` имеет тот же `task_id`/`task_ids` контракт. Неизвестный alias — `404
|
||
parser_source_not_found`; активный запуск — `409 already_running`. Endpoint не принимает путь
|
||
локального файла или произвольный upstream URL.
|
||
|
||
### 10.3 `GET /api/v1/jobs/{task_id}/`
|
||
|
||
Request:
|
||
|
||
```http
|
||
GET /api/v1/jobs/7b4d7911-5f1a-4fad-b2bb-67e9c170e4cc/
|
||
Authorization: Bearer <token>
|
||
```
|
||
|
||
Response `200`:
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"errors": null,
|
||
"data": {
|
||
"task_id": "7b4d7911-5f1a-4fad-b2bb-67e9c170e4cc",
|
||
"task_name": "parsers.fns_sme_support_recipients.refresh",
|
||
"status": "running",
|
||
"progress": 62,
|
||
"progress_message": "Обогащение организаций",
|
||
"created_at": "2026-08-16T01:10:00+03:00",
|
||
"started_at": "2026-08-16T01:10:04+03:00",
|
||
"finished_at": null,
|
||
"result": null,
|
||
"error": null,
|
||
"meta": {
|
||
"batch_id": 5102,
|
||
"source_snapshot_date": "2026-08-15",
|
||
"raw_records": 12580017,
|
||
"normalized_records": 8421050,
|
||
"quarantined_records": 18420
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
Terminal success result повторяет final counts/checksum; failed содержит structured error.
|
||
`progress` монотонен 0..100. `404 job_not_found`.
|
||
|
||
### 10.4 `POST /api/v1/parsers/upload/{source_key}/` (условный)
|
||
|
||
Request для источника неприменим: frontend его не вызывает.
|
||
|
||
```http
|
||
POST /api/v1/parsers/upload/fns_sme_support_recipients/
|
||
```
|
||
|
||
Response `405`:
|
||
|
||
```json
|
||
{
|
||
"success": false,
|
||
"errors": [
|
||
{
|
||
"code": "upload_not_supported",
|
||
"message": "Источник обновляется из официального Open Data URL.",
|
||
"field": null
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
## 11. История обновлений
|
||
|
||
### 11.1 `GET /api/v1/system/logs/`
|
||
|
||
Request:
|
||
|
||
```http
|
||
GET /api/v1/system/logs/?source=fns_sme_support_recipients&page=1&page_size=20&ordering=-created_at
|
||
Authorization: Bearer <token>
|
||
```
|
||
|
||
Response `200`:
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"errors": null,
|
||
"data": [
|
||
{
|
||
"id": 5102,
|
||
"source": "fns_sme_support_recipients",
|
||
"source_display": "Реестр субъектов МСП — получателей поддержки",
|
||
"status": "success",
|
||
"records_count": 8421050,
|
||
"organizations_count": 812340,
|
||
"error_message": "",
|
||
"created_at": "2026-08-16T01:10:00+03:00",
|
||
"updated_at": "2026-08-16T03:42:10+03:00"
|
||
}
|
||
],
|
||
"meta": { "pagination": { "page": 1, "page_size": 20, "total_count": 1, "total_pages": 1 } }
|
||
}
|
||
```
|
||
|
||
Filters: source, status, date_from/to; ordering created_at/updated_at/id. Response counts —
|
||
published, source-specific raw/quarantine находятся в detail.
|
||
|
||
### 11.2 `GET /api/v1/system/logs/{id}/`
|
||
|
||
Request:
|
||
|
||
```http
|
||
GET /api/v1/system/logs/5102/
|
||
Authorization: Bearer <token>
|
||
```
|
||
|
||
Response `200` добавляет:
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"errors": null,
|
||
"data": {
|
||
"id": 5102,
|
||
"source": "fns_sme_support_recipients",
|
||
"status": "success",
|
||
"records_count": 8421050,
|
||
"organizations_count": 812340,
|
||
"source_snapshot_date": "2026-08-15",
|
||
"archive_name": "data-20260815-structure-20230615.zip",
|
||
"archive_sha256": "00bc1d1ef97e1332f5e59fb04fd230f7ed0e0452ba7e90f6551d357ec891ce7a",
|
||
"raw_records_count": 12580017,
|
||
"quarantined_records_count": 18420,
|
||
"quarantine_by_reason": { "okpo_missing": 17400, "identity_conflict": 1020 },
|
||
"warnings": [],
|
||
"error_message": ""
|
||
}
|
||
}
|
||
```
|
||
|
||
`404 log_not_found`. Не возвращать raw XML с персональными данными.
|
||
|
||
### 11.3 `GET /api/v1/system/logs/export/`
|
||
|
||
Request:
|
||
|
||
```http
|
||
GET /api/v1/system/logs/export/?source=fns_sme_support_recipients&format=csv
|
||
Authorization: Bearer <token>
|
||
```
|
||
|
||
Response `200 text/csv; charset=utf-8` с `Content-Disposition`. Колонки включают batch,
|
||
source, status, snapshot, published/raw/quarantine counts, timestamps и error. Ошибки `400`,
|
||
`401`, `403`, `500` возвращаются JSON до начала streaming.
|
||
|
||
## 12. Выгрузка данных источника
|
||
|
||
### 12.1 `POST /api/v2/organization-source-records/export-ticket/`
|
||
|
||
Request:
|
||
|
||
```json
|
||
{
|
||
"sources": ["government_support"],
|
||
"format": "xlsx"
|
||
}
|
||
```
|
||
|
||
Response `202`:
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"errors": null,
|
||
"data": { "ticket": "exp_f743e84b", "status": "preparing", "expires_at": "2026-08-26T14:00:00Z" }
|
||
}
|
||
```
|
||
|
||
Те же filters records могут быть переданы документированным `filters` object. Ticket привязан
|
||
к пользователю и истекает.
|
||
|
||
### 12.2 `POST /api/v2/organization-source-records/export-download/`
|
||
|
||
Request:
|
||
|
||
```json
|
||
{ "ticket": "exp_f743e84b" }
|
||
```
|
||
|
||
Response `200 application/zip` или выбранный MIME с `Content-Disposition`. `202` означает ещё
|
||
не готово, `404` неизвестный ticket, `410` истёкший, `403` чужой ticket.
|
||
|
||
### 12.3 `POST /api/v2/organization-source-records/export/`
|
||
|
||
Request синхронного legacy-варианта:
|
||
|
||
```json
|
||
{ "sources": ["government_support"], "format": "json" }
|
||
```
|
||
|
||
Response `200` допустим только для малого результата; frontend не вызывает endpoint. Для
|
||
большого набора response `413 export_too_large` с рекомендацией ticket flow.
|
||
|
||
CSV/XLSX содержит обязательные organization fields и 27 бизнес-колонок, совместимых по смыслу
|
||
с выгрузкой ФНС. Если у меры несколько размеров/документов/нарушений, JSON сохраняет массивы,
|
||
а XLSX/CSV создаёт отдельные листы/таблицы с foreign key `support_registry_number`; данные не
|
||
склеиваются в неразбираемую строку.
|
||
|
||
## 13. Статусы и жизненный цикл
|
||
|
||
Operational job statuses:
|
||
|
||
```text
|
||
queued → pending → running → success
|
||
↘ retrying → running
|
||
↘ failed
|
||
↘ cancelled
|
||
```
|
||
|
||
UI: активные → `Выполняется`, терминальные → `Обновлено`; failure message показывается отдельно.
|
||
Business record status:
|
||
|
||
- `published` — мера есть в активном snapshot;
|
||
- `terminated` — XML содержит дату прекращения;
|
||
- запись, исчезнувшая из нового полного snapshot, архивируется и не входит в active list.
|
||
|
||
Не вычислять `active/completed` только по `support_until`: upstream не гарантирует такую
|
||
семантику. Violation — отдельный признак, не status.
|
||
|
||
## 14. Согласованность данных между endpoint
|
||
|
||
- `/sources.records_count = source_item.records_count = dashboard.source_counts = published
|
||
list meta.pagination.total_count` для одинакового snapshot/filters;
|
||
- `/sources.organizations_count` — distinct всех canonical organizations источника;
|
||
- `source_coverage[].organizations_count` — только организации ОПК и не обязано совпадать с
|
||
`/sources[].organizations_count`;
|
||
- `external_id = payload.support_registry_number`;
|
||
- `record_date = payload.decision_date`;
|
||
- `amount` равен RUB-size либо `null` и никогда не содержит часы/проценты;
|
||
- list/detail organization identity и core payload совпадают;
|
||
- `violation_count = detail.violations.length`;
|
||
- `regulatory_documents_count = detail.regulatory_documents.length`;
|
||
- `last_updated_at` всех metadata относится к одной публикации snapshot;
|
||
- raw, unsupported, quarantined и published counts не суммируются без документированной
|
||
формулы; official statistics используются только для reconciliation.
|
||
|
||
## 15. Ошибки, доступ, производительность и ограничения
|
||
|
||
| Операция | Read role | Refresh role | Admin |
|
||
| -------------------------------- | ----------------------------: | -----------: | ----: |
|
||
| Catalog/dashboard/records/detail | Да | Да | Да |
|
||
| Export | По отдельному permission | Да | Да |
|
||
| Refresh/parser run | Нет | Да | Да |
|
||
| Job/log detail | Да для разрешённых источников | Да | Да |
|
||
| Quarantine detail/raw archive | Нет | Нет | Да |
|
||
|
||
Source-specific ошибки: `archive_not_found`, `checksum_mismatch`, `zip_invalid`,
|
||
`xml_parse_error`, `xsd_validation_error`, `snapshot_older_than_active`, `duplicate_support_id`,
|
||
`invalid_inn`, `invalid_ogrn`, `organization_not_found`, `identity_conflict`, `okpo_missing`,
|
||
`invalid_support_size`, `reconciliation_failed`.
|
||
|
||
### Нефункциональные требования
|
||
|
||
- ZIP/XML читается потоково, без полного файла/DOM в памяти;
|
||
- download поддерживает timeout/retry/range, checksum проверяется до parse;
|
||
- staging writes bulk, индексы строятся/переключаются вне пользовательского list lock;
|
||
- атомарная публикация snapshot; partial snapshot никогда не виден frontend;
|
||
- индексы: source+record_type+record_date, external ID unique, organization UID, form/kind,
|
||
provider INN, violation, GIN/JSON indexes только при обосновании;
|
||
- list p95 <= 1.5 s на page 50 при типовом фильтре, detail p95 <= 500 ms после warm cache;
|
||
- search max 200 символов, page size max 100, rate limit документирован;
|
||
- raw archive и персональные данные ИП/НПД не выдаются в organization API;
|
||
- metrics/logs не содержат access token, cookie, XML FIO и локальные filesystem paths;
|
||
- parser повторно обрабатывает тот же checksum идемпотентно и не создаёт дубликаты;
|
||
- official stats/date/file metadata записываются для reconciliation, но не подменяют atomic
|
||
counts Mostovik.
|
||
|
||
## 16. Требования к OpenAPI и generated-клиенту
|
||
|
||
Добавить/уточнить:
|
||
|
||
- enum `government_support` во всех source group/export schemas;
|
||
- enum/source `fns_sme_support_recipients` и record type `sme_support_measure`;
|
||
- все source-specific query filters/orderings списка;
|
||
- отдельные `SmeSupportRecordListPayload` и `SmeSupportRecordDetailPayload`;
|
||
- schemas `SmeSupportDictionaryValue`, `SmeSupportSize`, `SmeSupportProvider`,
|
||
`SmeSupportRegulatoryDocument`, `SmeSupportViolation`, `SmeSupportProvenance`;
|
||
- `required` и `nullable` для каждого поля; arrays required/non-null;
|
||
- корректный list response и отдельный detail envelope;
|
||
- typed dashboard, refresh/job/log/export errors и binary content types;
|
||
- operationId, пригодные для Orval без дефисов: например
|
||
`v2_organization_source_records_retrieve`.
|
||
|
||
После обновления backend frontend выполняет:
|
||
|
||
```bash
|
||
bun run apigen
|
||
bun run type-check
|
||
bun run test:contract
|
||
```
|
||
|
||
Diff generated API проверяется: нельзя вручную редактировать `src/shared/api/generated-api/` и
|
||
`src/shared/model/generated-zod/`.
|
||
|
||
## 17. Backend-тесты и acceptance criteria
|
||
|
||
### Минимальная матрица backend/contract tests
|
||
|
||
- XML fixture: ЮЛ с одной и несколькими мерами;
|
||
- одна мера с несколькими размерами разных единиц;
|
||
- zero/one/many нормативных документов и нарушений;
|
||
- termination, update и nullable dates;
|
||
- ИП/КФХ/НПД остаются staging и не публикуются;
|
||
- exact ОГРН+ИНН enrichment, missing ОКПО, ambiguous/conflict;
|
||
- duplicate support number между получателями блокирует snapshot;
|
||
- `dt_create/dt_insert/XLSX` QA fixture не влияет на XML mapping;
|
||
- код единицы 5 со спорным значением сохраняет raw и создаёт warning/quarantine по политике;
|
||
- повторный import checksum идемпотентен;
|
||
- failed XSD/checksum/reconciliation сохраняет предыдущий snapshot;
|
||
- list filters, ordering, search, organization, date range и pagination;
|
||
- list payload не содержит detail arrays, detail содержит их полностью;
|
||
- counts/invariants между sources/dashboard/list/logs;
|
||
- refresh 202, duplicate 409, job terminal, history и ticket export;
|
||
- permissions, 401/403/404/410/413/422/429/500;
|
||
- performance test на объёме, сопоставимом с 12.6 млн записей.
|
||
|
||
### Backend acceptance criteria
|
||
|
||
- [ ] Все endpoint и response примеры реализованы и находятся в OpenAPI.
|
||
- [ ] Runtime contract tests подтверждают required/nullable/enums/content types.
|
||
- [ ] Каждая published запись содержит Наименование, ИНН, ОГРН и ОКПО.
|
||
- [ ] Full nested source data не теряется при нормализации и export.
|
||
- [ ] Snapshot публикуется атомарно и обратимо.
|
||
- [ ] Raw/published/quarantine/unsupported counters согласованы.
|
||
- [ ] Frontend может реализовать list/detail/refresh/history/export без прямого upstream-доступа.
|
||
|
||
## 18. Чего backend не должен возвращать
|
||
|
||
- HTML сайта ФНС, cookies, hash-параметры и токены временной XLSX-выгрузки;
|
||
- `row_cnt` как поле каждой записи;
|
||
- numeric ИНН/ОГРН/ОКПО/номер поддержки;
|
||
- только одно display-значение вместо `support_sizes[]`;
|
||
- пять nullable size fields в full detail вместо массива;
|
||
- склеенные документы/нарушения вместо typed arrays;
|
||
- `"—"`, `"нет данных"`, форматированные суммы и локализованные даты;
|
||
- raw XML/FIO ИП/НПД в organization endpoint;
|
||
- provider enrichment как замену upstream name/ИНН;
|
||
- live official statistics вместо counts активного snapshot;
|
||
- source-specific legacy endpoints, если универсальный API покрывает сценарий.
|
||
|
||
## 19. Решения и открытые вопросы
|
||
|
||
| Вопрос | Решение | Статус |
|
||
| --------------------------------------- | ----------------------------------------------------------------------------------------------------- | --------------------------- |
|
||
| Production transport | Официальный XML ZIP + XSD; web API только QA | Принято |
|
||
| Гранулярность | Одна мера поддержки = одна запись | Принято |
|
||
| ИП/КФХ/НПД | Raw/staging, не published в MVP | Принято для первой итерации |
|
||
| ОКПО | Канонический enrichment; missing/conflict → quarantine | Принято |
|
||
| Provider enrichment | Nullable, не блокирует публикацию | Принято |
|
||
| Дата внесения | `ДатаСвед` XML; fixture подтвердит mapping | Требует fixture |
|
||
| Единица 5 > 1 | Не терять raw; подтвердить production XSD/данные до strict rejection | Открыто |
|
||
| Срок хранения raw | Минимум два snapshot; окончательный срок согласовать с data owner | Открыто |
|
||
| Official stats reconciliation tolerance | Блокировать только на структурной ошибке; числовой threshold согласовать после первого полного import | Открыто |
|