Files
mostovik-backend/docs/sro-membership-source.md
Aleksandr Meshchryakov 2cfc057e34
All checks were successful
Mostovik Backend CI/CD / Tests and lint (push) Successful in 2m55s
Mostovik Backend CI/CD / Build linux/amd64 release images (push) Successful in 3m48s
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 1m54s
fix: publish valid SRO memberships while preserving quarantined history
2026-09-15 13:25:43 +02:00

20 KiB
Raw Blame History

Проверка членства в СРО: эксплуатация

Источник sro_membership_check, группа/тип sro_membership, карточка sro-membership-check. Upstream — сайт reestr-sro.ru; внутренний справочник организаций используется для точной привязки UID и ОКПО. Другие внешние реестры загрузчик не опрашивает.

Доступ и пределы запросов

По умолчанию сбор закрыт. Для production необходимы одновременно SRO_UPSTREAM_ACCESS_APPROVED=true и непустой SRO_UPSTREAM_APPROVAL_REFERENCE — ссылка/номер документированного разрешения владельца сайта на автоматизированные query/search запросы. Согласование разработки внутри проекта не заменяет разрешение владельца upstream. Условия разрешения должны быть совместимы с настройками загрузчика; более строгие пределы задаются перед включением. Секреты в approval reference хранить нельзя.

15.09.2026 пользователь явно изменил требование: «Изменить требование и включить сбор на dev». Только для dev предусмотрен отдельный opt-in SRO_DEV_COLLECTION_ENABLED=true. Он разрешает сбор без изменения SRO_UPSTREAM_ACCESS_APPROVED и SRO_UPSTREAM_APPROVAL_REFERENCE и не обозначает разрешение владельца upstream. По умолчанию флаг false; на production он должен оставаться выключенным. Код разрешает запуск при включённом dev-флаге или при наличии обоих параметров согласованного доступа. Для отключения dev-исключения верните флаг в false и примените конфигурацию web/worker.

Исходный source-first контракт с ограничением production refresh и документ замечаний с исходным ответом 409 сохранены как происхождение требования. Настоящее изменение относится только к включению сбора на dev; ограничения запросов, привязки организаций и публикации данных сохраняются.

Оба ручных entrypoint проверяют этот gate до enqueue; task и HTTP клиент повторяют проверку перед внешним IO. Закрытый gate возвращает typed 409 upstream_access_not_approved. Если gate закрыт после постановки, job завершается с ошибкой, без успешной пустой публикации.

Настройка По умолчанию Значение
SRO_DEV_COLLECTION_ENABLED false Явное включение сбора только на dev, отдельно от подтверждения разрешения upstream.
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. User-Agent — нейтральный Mostovik SRO integration, без утверждения о разрешении upstream.

Наблюдаемый 15.09.2026 ответ lookup 404 учитывается как отсутствие членства только для /proverka_dopuska/ с одним q из 10 или 13 цифр, при точном title «Проверка членства в реестре СРО, проверить допуск организации в СРО по ИНН», единственном h1 «Запрашиваемая страница на сайте отсутствует.» и отсутствии table.sro-members. Конечный q после redirects должен совпадать с запрошенным идентификатором организации; иначе запуск отклоняется с sro_lookup_identifier_mismatch. Другой HTML при lookup 404 отклоняется с sro_lookup_response_unrecognized. На остальных путях 404 сохраняет прежнее значение sro_page_not_found, в том числе на страницах даты допуска.

Этот шаблон 404 неоднозначен: он сам по себе не доказывает отсутствие членства. Поэтому если все кандидаты получили такой ответ и ни один lookup не вернул успешно разобранный 200, весь запуск отклоняется с sro_ambiguous_empty_scan до публикации и изменения checkpoints; прежние записи сохраняются. Смешанный запуск с корректным 200 может учитывать распознанные отрицательные ответы. Приватный raw manifest сохраняет status_code; artifact metadata содержит recognized_lookup_404_count и successful_lookup_200_count. Предел размера ответа действует и для lookup 404. Дата реестра из error-шаблона не извлекается.

Публикация и обновление

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 стабилен между загрузками, включая переход activeexcluded. 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 целиком. Незавершённый batch остаётся диагностическим и не становится current snapshot.

Завершённый сбор может закончиться success с quarantine, как в примере истории исходного контракта. Валидные строки публикуются, включая валидные членства организации, у которой другая строка оказалась некорректной. Неизвестный статус, отсутствующий SRO ID или URL, конфликт идентификаторов не исправляются догадками: исходная строка остаётся в quarantine с причиной. У таких организаций прежние членства, не обновлённые валидной строкой этого batch, сохраняются с прежними UID, payload и load batch. Ошибка в строке не доказывает прекращение других членств. Для полностью проверенных организаций исчезнувшие членства удаляются; допустимый пустой ответ удаляет их все. При incremental неизменённые, не опрошенные организации также сохраняются.

Checkpoint полностью проверенной организации обновляется. У организации с quarantine прежний checkpoint удаляется в той же транзакции, чтобы следующий incremental повторил lookup даже при неизменившемся fingerprint. Raw artifact и staged audit сохраняются. Если нет ни одной валидной строки и ни одного подтверждённого пустого lookup 200, quarantine отклоняет запуск с sro_incomplete_membership_scan; смесь invalid rows и неоднозначных 404 не считается подтверждённым отсутствием. Для полностью состоящего из 404 запуска сохраняется отдельный sro_ambiguous_empty_scan. В обоих случаях прежние записи и checkpoints остаются без изменений.

Если 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 — число опубликованных строк текущей дельты. При full с quarantine общий count также включает сохранённые старые членства. retained_records_count — число старых строк организаций с quarantine, которые не были обновлены валидным upsert и сохранены от удаления; quarantined_organizations_count — число организаций хотя бы с одной строкой quarantine; completed_organizations_count — число полностью проверенных организаций без quarantine, включая допустимые пустые результаты. Эти три поля доступны в metadata API. В приватном artifact confirmed_empty_lookup_200_count отдельно фиксирует подтверждённые пустые 200 для проверки допустимости публикации. parsed_count, quarantine и candidate/queried counts относятся к текущей проверке; складывать их с общим published count нельзя. Новая дата публикации не изменяет прежнюю lineage сохранённых записей.

Источник публикует весь собственный справочник; карточка/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. После разрешения владельца обязательна ограниченная проверка фактической разметки перед первой полной загрузкой.