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
199 lines
20 KiB
Markdown
199 lines
20 KiB
Markdown
# Проверка членства в СРО: эксплуатация
|
||
|
||
Источник `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 контракт](source-integration/sources/sro-membership-check/backend-api.md)
|
||
с ограничением production refresh и
|
||
[документ замечаний](source-integration/source-records-backend-improvements.md)
|
||
с исходным ответом `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
|
||
стабилен между загрузками, включая переход `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 целиком.
|
||
Незавершённый 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. После разрешения владельца обязательна ограниченная проверка
|
||
фактической разметки перед первой полной загрузкой.
|