Files
mostovik-backend/docs/sro-membership-source.md
Aleksandr Meshchryakov 18971d33ec
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
feat: complete published registry contracts and gated SRO ingestion
2026-09-14 17:01:02 +02:00

140 lines
13 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.
# Проверка членства в СРО: эксплуатация
Источник `sro_membership_check`, группа/тип `sro_membership`, карточка
`sro-membership-check`. Upstream — сайт `reestr-sro.ru`; внутренний справочник
организаций используется для точной привязки UID и ОКПО. Другие внешние реестры
загрузчик не опрашивает.
## Доступ и пределы запросов
По умолчанию сбор закрыт. Для открытия необходимы одновременно
`SRO_UPSTREAM_ACCESS_APPROVED=true` и непустой
`SRO_UPSTREAM_APPROVAL_REFERENCE` — ссылка/номер документированного разрешения
владельца сайта на автоматизированные query/search запросы. Согласование разработки
внутри проекта не заменяет разрешение владельца upstream. Условия разрешения
должны быть совместимы с настройками загрузчика; более строгие пределы задаются
перед включением. Секреты в approval reference хранить нельзя.
Оба ручных entrypoint проверяют этот gate до enqueue; task и HTTP клиент повторяют
проверку перед внешним IO. Закрытый gate возвращает typed
`409 upstream_access_not_approved`. Если разрешение отозвано после постановки,
job завершается с ошибкой, без успешной пустой публикации.
| Настройка | По умолчанию | Значение |
| --- | --- | --- |
| `SRO_REQUEST_INTERVAL_SECONDS` | `3` | Минимум 3 секунды между запросами, включая redirects/retries; можно увеличить. |
| `SRO_HTTP_MAX_RESPONSE_BYTES` | `2097152` | Предел распакованного тела одного ответа. |
| `SRO_HTTP_TIMEOUT_SECONDS` | `30` | Read timeout; connect timeout 10 секунд. |
| `SRO_MAX_REQUESTS_PER_RUN` | `10000` | Общий предел запросов одного запуска, включая redirects/retries/sitemaps. |
HTTP клиент делает не более трёх попыток при транспортной ошибке и ответах
429/500/502/503/504. `Retry-After` учитывается как секунды или HTTP date, ожидание
свыше 300 секунд завершает запуск ошибкой. Цепочка redirects ограничена пятью
ответами; каждый адрес проверяется до обращения. Разрешён только HTTPS без
credentials/нестандартного порта, на `reestr-sro.ru` и его поддоменах. Переменные
окружения HTTP proxy автоматически не используются. HTML/query с идентификаторами
не выводится в application logs.
## Публикация и обновление
Celery task: `parsers.sro_membership_check.refresh`;
Python entrypoint: `apps.parsers.tasks_registry_snapshots.parse_sro_membership`,
keyword args `requested_by_id=None`, `mode="incremental"|"full"`.
Проверяются только организации текущего канонического справочника. Инкрементальный
запуск выбирает новые организации и те, у которых изменились name/full_name,
ИНН/ОГРН/ОКПО. Изменение определяется fingerprint; у модели Organization нет
универсального `updated_at`. Полный запуск проверяет весь справочник.
Lookup использует точный ОГРН, при его отсутствии/неподходящем формате — ИНН
с проверкой нормализованного имени. Если нет пригодного идентификатора, запуск
отклоняется с `sro_candidate_identifier_missing`, без удаления прежних записей.
Ответ дополнительно сопоставляется по ИНН+ОГРН через внутренний индекс. Отсутствие
ОКПО, конфликт идентификаторов, неоднозначная организация, неизвестный статус,
неполные обязательные поля или missing/unsafe SRO URL отправляют строку в quarantine.
Новые организации из внешнего ответа не создаются.
Одна запись соответствует паре canonical organization UID + SRO ID. Record UID
стабилен между загрузками, включая переход `active``excluded`. SRO ID/URL
берётся из same-site ссылки или точного ID, разрешённого по same-site sitemap;
по одному похожему названию URL не угадывается. Для даты допуска читается same-site
страница members по ИНН. Неизвестная дата остаётся `null` с причиной
`not_found`, `sro_page_unresolved` или `parse_error`. Наблюдаемая дата реестра
не подменяется датой загрузки.
Raw ответы сохраняются в приватном ZIP artifact с manifest URL→файл; отдельные
staged rows содержат source fields, нормализованный payload и причину quarantine.
Для SRO используется отдельный `FileSystemStorage`: `PARSER_PRIVATE_ARTIFACT_ROOT`
по умолчанию равен `<repository-root>/private-parser-artifacts`, вне `MEDIA_ROOT`.
Путь обязан быть абсолютным и не находиться внутри `MEDIA_ROOT`, в том числе после
разрешения symlink. Ошибка конфигурации запрещает файловую операцию. Приватный storage
не выдаёт публичный URL; файлы создаются с правами `0600`, каталоги `0700`.
Остальные источники сохраняют прежний `default_storage`.
В контейнерном окружении задайте `PARSER_PRIVATE_ARTIFACT_ROOT=/app/private-parser-artifacts`
и подключите к web/worker один постоянный volume по этому пути. Каталог должен быть
доступен пользователю приложения для записи и не должен монтироваться в public media,
static или reverse-proxy document root. Существующая retention-команда удаляет private
файлы через тот же `artifact.file.delete()`; удаление/пересоздание контейнера не должно
удалять volume. Миграция `0037` меняет storage в состоянии модели без перемещения данных.
До этой версии живой SRO сбор не запускался. Если оператор ранее создал SRO artifacts
в старом MEDIA_ROOT вручную, их требуется отдельно перенести в private root с сохранением
относительных имён и убрать публичные копии до открытия источника; автоматического
fallback на public raw нет.
Публикация записей, checkpoint организаций, load log и terminal job выполняется
в одной транзакции. Инкрементальная публикация заменяет данные только успешно
проверенного набора организаций; записи остальных организаций сохраняются.
Transport/schema failure или отменённая job откатывает публикацию и checkpoints.
Допустимый пустой ответ удаляет старые членства проверенной организации.
Любая семантически некорректная строка остаётся в quarantine и отклоняет весь
запуск с `sro_incomplete_membership_scan`. Это сохраняет прежние членства и
checkpoints: неизвестный статус или неразрешённая ссылка не доказывают, что
членство исчезло. Организация остаётся кандидатом следующего incremental запуска.
Если incremental не нашёл изменившихся организаций, batch имеет нулевую дельту
и наследует source registry date/version последней успешной публикации; ссылка
на неё сохраняется в `metadata.base_snapshot_artifact_id`. Данные не переписываются.
После commit безопасно ставится существующая задача обновления общих export
artifacts. Ошибка брокера после commit не превращает успешную публикацию в failure;
готовность выгрузки отражает собственный контракт export service.
## Счётчики и диагностика
`artifact.published_count`, `result.published_records_count` и `load_log.records_count`
содержат размер текущего опубликованного набора источника. Для incremental это
общий результат, включая неизменённые организации. `metadata.snapshot_records_count`
и `snapshot_organizations_count` дают такие же общие record/distinct organization
counts; `batch_published_records_count` и `updated_records_count` — число опубликованных
строк текущей дельты. `parsed_count`, quarantine и candidate/queried counts относятся
к текущей проверке; складывать их с общим published count нельзя.
Источник публикует весь собственный справочник; карточка/list/dashboard для трёх
новых источников используют весь published scope. Coverage ОПК показывается
отдельно. МСП и бюджет продолжают полную замену: новый scoped аргумент публикации
по умолчанию `None` сохраняет их поведение.
Для расследования используют job status/error, artifact status,
`metadata.rejection_reason`, `rejection_reasons` и staged disposition/reason.
Они содержат ограниченные коды ошибок, не raw HTML. Partial raw ZIP сохраняется
при ошибке уже начатого сбора; предыдущая публикация остаётся доступна.
Приватные raw artifacts удерживаются минимум 90 дней и минимум 10 последних
успешных SRO batches; неуспешные batches не вытесняют эти десять успешных.
## Расписания и граница проверки
Миграция `parsers.0036_sro_disabled_schedules` создаёт **выключенные** schedules:
daily incremental в 04:00 Europe/Moscow; monthly full 16-го числа в 04:00
Europe/Moscow — после наблюдаемого обновления реестра 15-го. Перед включением
нужно подтвердить фактическую периодичность/условия разрешения. Расписание само
не открывает gate; оба механизма по умолчанию выключены.
На 14.09.2026 query/search и живой SRO snapshot не запускались. Основная страница
без query доступна через read-only web; свежий robots.txt получить не удалось.
Указанные в source-first спецификации `Crawl-delay: 3` и query/search disallow
учтены консервативно. DOM selector/mapping проверены синтетическими fixtures формы
из `docs/source-integration/sources/sro-membership-check/backend-api.md`, а не
полным live scan. После разрешения владельца обязательна ограниченная проверка
фактической разметки перед первой полной загрузкой.