Что было раньше
У нас 4 источника данных: kad.arbitr (определения суда), fedresurs (намерения о банкротстве), pb.nalog (налоговая задолженность через «Прозрачный Бизнес»), egrul (ЕГРЮЛ). Каждый когда-то писался отдельно — kad самый старый и боевой, egrul самый новый.
Проблема, которую ты сам не видишь, но которая ест время:
- У каждого источника свой стиль ошибок. kad может «лечь» из-за DDoS-Guard, fedresurs из-за Qrator, pb.nalog из-за CAPTCHA, egrul из-за лимита запросов. Каждый раз когда что-то падает, я лезу в логи и должен помнить, как именно этот конкретный источник обозначает «упал».
- Если по конкретному определению пришёл странный результат (например, СРО не та или ничего не нашли), нет простого способа «пересчитать с нуля». Нужно лезть в БД и крутить SQL.
- Не было видно, что вообще происходит. Все 4 шли через cron-скрипты, и единственный показатель здоровья — «есть ли свежие записи в БД». Если источник молчит — это потому что он сломался или потому что просто новых дел нет?
Это всё операционка, которая копится. Каждый раз когда мы добавляли новую функцию (вроде EN-cascade недавно), приходилось решать вопросы заново для каждого источника.
Что мы сделали
Большой рефакторинг — внутренний, тебя в день-в-день он не должен задевать. Но базу под следующие фичи положили.
1. Единый контракт для всех источников
Теперь все 4 источника живут под одним «договором»:
| Метод |
Что делает |
Когда срабатывает |
pull() |
Тащит сырые события из внешнего API |
Каждый раз когда cron запускается |
parse() |
Превращает сырое событие в нашу структуру |
Сразу после pull, без I/O |
persist() |
Записывает в БД |
Если parse не отбросил событие |
replay() |
Пересчитывает уже сохранённое событие через текущую логику |
По кнопке в UI или вручную |
health_check() |
Проверяет, жив ли источник |
По расписанию или по запросу |
Это значит — когда мы в следующий раз будем менять, например, регекспы для извлечения СРО, я не лезу в 4 разных файла, а правлю один. И тесты автоматически прогоняются через все 4 адаптера.
2. Replay-кнопка на /ops/sources/{name}
На страницах /ops/sources/kad.arbitr, /ops/sources/fedresurs_spa, /ops/sources/pb.nalog, /ops/sources/egrul.nalog теперь видны последние 50 событий — что было сделано, когда, с каким результатом. Рядом с каждым событием — кнопка «Replay».
Зачем это нужно: если ты когда-нибудь заметишь, что по конкретному определению пришёл странный результат (например, парсер посчитал что СРО ЦФОП АПК, а ты вручную посмотрел — там МСРО Содействие), нажми Replay. Система пересчитает событие с текущей версией логики. Если регекспы или dict обновились — увидишь новый результат сразу. Без меня и без SQL.
3. Цветные бейджи здоровья на /ops/sources
На общей странице /ops/sources теперь у каждого источника видна цветная плашка:
- 🟢 зелёный (
ok) — источник отвечает, латентность нормальная
- 🟡 амбер (
degraded) — отвечает, но медленно или с ошибками (часто = CAPTCHA, временный rate-limit)
- 🔴 красный (
failed) — упал, нужен оператор
- ⚪ серый (
unknown) — нет данных о состоянии (например, мы ещё не делали health-check, или у источника нет дешёвого ping-эндпоинта как у EGRUL)
Это полезно когда я не у компьютера: ты сам можешь зайти и увидеть, что красное — это уже сигнал «звони Юджину».
4. Canary на kad.arbitr в shadow-режиме
Самая аккуратная часть. Старый монитор по kad.arbitr продолжает работать как обычно — ничего не меняется. Параллельно с ним мы запустили новый адаптер в shadow-режиме: он каждые 15 минут делает ровно то же самое, что старый монитор, но в БД ничего не пишет. Только сравниваем результаты.
Цель: 24 часа наблюдать, не разъезжается ли новый адаптер со старым. Если разъезжается больше 5% циклов — откатываемся, разбираемся, чиним. Если в пределах нормы — следующим шагом промоутим новый адаптер на место старого. Это уже отдельная задача после 24-часового окна.
Этот же подход применим к fedresurs/pb.nalog/egrul, но мы их пока не canary'им — у них тестовое покрытие через сравнения и контракт-харнесс, а risk-budget для shadow-сравнения на проде мы тратим аккуратно, по одному.
Что ты увидишь
Сегодня вечером и завтра
- На
/ops/sources появятся цветные бейджи у kad.arbitr / fedresurs_spa / pb.nalog / egrul.nalog. На старте часть может быть серой — это норма, после первого health-цикла обновится.
- Зайдёшь на
/ops/sources/kad.arbitr — увидишь таблицу последних 50 событий и кнопки Replay. Можно тыкать — это безопасно, не сломает прод. На каждый клик в БД остаётся след «replay started → success/failed», я смогу разобрать ретроспективно если что-то пойдёт не так.
В ближайшие 24 часа
Каждые 15 минут срабатывает canary по kad.arbitr — пишет одну запись в pipeline_events с пометкой step='canary'. Через сутки я гляну логи и:
- Если расхождений со старым монитором < 5% → планируем замену старого монитора новым адаптером (это будет следующий пост).
- Если ≥ 5% → откатываю canary, разбираюсь почему.
По остальным трём источникам
fedresurs / pb.nalog / egrul пока работают по-старому — старые скриптовые цепочки никто не трогал. Новые адаптеры готовы и протестированы, но включим их в прод следующим этапом, по одному, через тот же shadow-режим, чтобы не рисковать.
Что дальше
Через 24 часа: ретро по kad.arbitr canary, решение по flip-to-primary.
Следующая неделя: если kad.arbitr canary прошёл — повторяем для fedresurs / pb.nalog / egrul в той же последовательности.
Параллельно: теперь когда платформа есть, проще будет добавлять новые источники когда они нам понадобятся (например, ЕФРСБ через платный API, если решим, что оно того стоит — отдельный разговор).
Технические детали
Что включает CP-5 эпик
10 sub-issues, итог:
- **CP-5-01:** BaseSource ABC + Pydantic v2 types + replay taxonomy preflight
- **CP-5-02:** Source registry auto-discovery + adapter-presence badge
- **CP-5-03:** Conformance test harness (общий тестовый контракт для всех адаптеров)
- **CP-5-04:** kad.arbitr adapter migration — самый pain-prone источник, под него ABC окончательно зафиксировали
- **CP-5-05:** Replay dispatcher endpoint с content negotiation (HTTP API + UI fallback)
- **CP-5-06:** fedresurs_spa adapter migration (SPA publications stream)
- **CP-5-07:** pb.nalog + egrul.nalog adapter migrations split (две лёгкие миграции одним тикетом)
- **CP-5-08:** Replay button UI на /ops/sources/{name} detail page
- **CP-5-09:** Health display + cross-adapter replay taxonomy validation
- **CP-5-10:** Canary cron + scope-boundary regression gate — закрытие эпика
**Тесты:** `pytest -m sources` 117 → 294 (+177 net new). `pytest -m sources_conformance` 31 → 199 (+168 — это conformance-харнесс, прогоняет общий контракт через все 4 адаптера). mypy --strict + ruff clean на всех CP-5 путях.
**Review chain:** 4 из 10 тикетов прошли через Codex review с REVISE → APPROVE циклом (5 substantive findings caught и зафиксированы pre-merge: run_step status semantics, частные методы провайдера, EGRUL health probe quota concern, taxonomy test vacuity, persist exception swallowing). Остальные 6 прошли APPROVE с первого захода.
**Architecture decisions locked:**
- Per-adapter drop-reason ownership: CP-5-01 владеет только `replay.started/.success/.failed`; каждый адаптер сам добавляет свои `.` коды в CP-2 taxonomy.
- CP-2/CP-3 schemas — LOCKED, новых ALTER нет.
- _CatalogScraperBase preserved untouched — fedresurs адаптер обёртывает FedresursPublicProvider + PublicationStreamScraper, не _CatalogScraperBase.
- Opt-in health probe pattern (EGRUL): default `unknown` со status detail вместо реального fetch, чтобы не палить квоту/CAPTCHA на cold-start. Оператор может явно включить live probe через `enable_live_health_probe=True`.
Push state + GitHub close-out
Все 10 commits на `origin/main`:
1361795 cp5-10 canary cron + scope-boundary gate
52c5d66 cp5-09 health-status display + replay taxonomy validation
0b4d3da cp5-07 pb.nalog + egrul.nalog adapter migrations
a90e984 cp5-06 fedresurs_spa adapter migration
e6cb42c cp5-08 replay button UI
b42a409 cp5-05 replay dispatcher endpoint
82b1976 cp5-04 kad.arbitr adapter migration
7f87772 cp5-03 conformance test harness
8f59031 cp5-02 source registry auto-discovery + badge
d1b72fb cp5-01 BaseSource ABC + Pydantic v2 types
GitHub Project #6: epic #188 + sub-issues #238-#247 закрыты как Done.