59 KiB
template_id, template_version, source_id, source_name, document_status
| template_id | template_version | source_id | source_name | document_status |
|---|---|---|---|---|
| source-backend-api | 1 | sme-support-recipients-registry | Реестр субъектов МСП — получателей поддержки | review |
Backend API источника «Реестр субъектов МСП — получателей поддержки»
Уточнение 14.09.2026: разделы подготовки выгрузки с
preparing/ HTTP 202 заменены общим runtime-контрактом: готовый ticket HTTP 201 → native form download HTTP 200; устаревшее поколение — HTTP 503source_export_not_ready, истёкший/использованный ticket — HTTP 410ticket_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:
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:
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 дополнительно содержит:
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 обязателен для
списков.
Стандартная ошибка:
{
"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:
GET /api/v1/sources/
Authorization: Bearer <token>
Response 200 application/json содержит среди data:
{
"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:
GET /api/v1/sources/sme-support-recipients-registry/
Authorization: Bearer <token>
Response 200 application/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:
GET /api/v1/parsers/dashboard/
Authorization: Bearer <token>
Response 200 содержит:
{
"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:
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:
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:
{
"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:
GET /api/v2/organization-source-records/2e5db996-e17d-4f5e-a898-bb29566927e4/
Authorization: Bearer <token>
Response 200 повторяет все top-level/list поля и заменяет payload на full detail:
{
"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:
POST /api/v1/sources/sme-support-recipients-registry/refresh/
Authorization: Bearer <token>
Content-Type: application/json
{}
Response 202:
{
"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:
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:
GET /api/v1/jobs/7b4d7911-5f1a-4fad-b2bb-67e9c170e4cc/
Authorization: Bearer <token>
Response 200:
{
"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 его не вызывает.
POST /api/v1/parsers/upload/fns_sme_support_recipients/
Response 405:
{
"success": false,
"errors": [
{
"code": "upload_not_supported",
"message": "Источник обновляется из официального Open Data URL.",
"field": null
}
]
}
11. История обновлений
11.1 GET /api/v1/system/logs/
Request:
GET /api/v1/system/logs/?source=fns_sme_support_recipients&page=1&page_size=20&ordering=-created_at
Authorization: Bearer <token>
Response 200:
{
"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:
GET /api/v1/system/logs/5102/
Authorization: Bearer <token>
Response 200 добавляет:
{
"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:
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:
{
"sources": ["government_support"],
"format": "xlsx"
}
Response 202:
{
"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:
{ "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-варианта:
{ "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:
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 typesme_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 выполняет:
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/XLSXQA 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 | Открыто |