EPIC: аудит кода ПТИЦЫ (мульти-агентный code review 2026-07-05) — 18 подтверждённых находок #2445

Closed
opened 2026-07-05 18:22:10 +00:00 by bot-backend · 5 comments
Collaborator

Мульти-агентный code review site-finder (ПТИЦА, без tradein-mvp): 9 измерений × Sonnet 5, каждая находка независимо проверена 2 скептиками (adversarial verify). Два прогона (LLM недетерминированы) → объединённый дедуп-пул 18 подтверждённых находок (из ~40 кандидатов, ~20 отсеяно скептиками как non-issue).

Кластер A — Честность цифр (silent caps / None-to-0) — HIGH приоритет

Прямо портит доверие: девелопер видит неверные числа как достоверные.

  • A1 quarter_dump_lookup.py:1182/1181total_structures_in_radius=len() от LIMIT 50, protection_zones_intersecting=len() от LIMIT 100 (+ нет ORDER BY → недетерминированные 100). Показывает усечение как полный итог. Паттерн items_truncated уже есть в permits_nearby.py.
  • A2 utility_infrastructure_loader.py:712total_features=len() от LIMIT 200. Та же болезнь. (прогон 1)
  • A3 supply_layers.py:272 — L2 SQL COALESCE(flat_count,0) - free_flats обнуляет неизвестный flat_count, противоречит собственному Python-хел邁еру _hidden_units() (возвращает None). Инфлейтит L2-покрытие + скрытые единицы.
  • A4 velocity.py:710_get_ekb_median имеет COALESCE(deals_total_vol_m2, deals_total_count*45.0) proxy-fallback без disclosure, засоряет знаменатель нормализации velocity_score.
  • A5 competitors.py:452_AVG_PRICE_SQL фильтрует по глобальной MAX(snapshot_date) вместо per-object. Тот же баг, что чинили в best_layouts.py (#1956), не портирован. Цена→NULL у большинства конкурентов. (прогон 1)

Кластер B — Robustness / необработанные краши — HIGH/MEDIUM

  • B1 geometry.py:180CRSError не ловится: параметр с широтой вне [-90,90] (спроецированные метры вместо WGS84) крашит запрос 500 вместо 422 ParcelGeometryError.
  • B2 GateVerdictBanner.tsx:142COLOR_BY_LABEL[verdict_label] без fallback: при schema-drift/partial-deploy color становится undefined → краш рендера. Соседние lookup'ы (ParcelDrawer.tsx) имеют ?? '#73767E'.
  • B3 VelocityBlock.tsx:113CONFIDENCE_COLOR[confidence] без fallback, тот же класс.
  • B4 AnalysisPageContent.tsx:254 — нет key={cad} → при клиентской навигации между участками инстанс §7Concept переиспользуется, показывает stale-state от предыдущего участка.

Кластер C — Паритет/disclosure экспортёров — HIGH/MEDIUM

  • C1 generative/exporters/pdf.py:191 — footer PDF ложно заявляет «коммерческие/офисные площади не учитываются», хотя таблица двумя строками выше показывает офисную выручку + НДС. Stale-caveat после добавления office-support.
  • C2 exporters/report_pdf.py:645 — нет секции «Уверенность» (§13.7), хотя report_docx.py/report_md.py её имеют. Паритет-дрейф + при dict-факторе PDF может крашить.

Кластер D — Устойчивость скраперов / WAF-гигиена — MEDIUM/LOW

Связано с #2443 (DOM.РФ WAF-бан). Фиксы профилактические (добавление backoff/guard безопасно).

  • D1 admin_scrape.py:1803/kn-catalog-objects всё ещё даёт дёрнуть таск, чей beat отключён из-за WAF-бана, с max_objects=2000, force=True, без server-side cooldown-гейта.
  • D2 domrf_catalog_object.py:439BrowserSession без override → наследует дефолт concurrency=8, jitter 600-1500ms вместо throttled concurrency=2 (как в scrape_kn после #1945). Тот же /сервисы/* путь, что вызвал бан.
  • D3 okn_objects_sync.py:143 + okn_egrkn_client.py — до 1500 последовательных GET к okn-mk.mkrf.ru без sleep/backoff/circuit-breaker. Соседние скраперы throttled, этот выпадает. (оба прогона)
  • D4 reservation_ingest.py:187 — external PDF-fetch в цикле при открытой DB-сессии + без пейсинга к pravo.gov66.ru. (прогон 1)
  • D5 izyatie_ocr_ingest.py:147 — только fetch_pdf() в try/except; ocr_pdf_text()/extract_izyatie_records()/_upsert не защищены → исключение убивает весь таск вместо skip одного документа.
  • D6 etl_newbuilding_crossload.py:19 — нет своего error-handling/breadcrumb-лога, в отличие от всех соседних тасков. Падение невидимо оператору.

Отсеяно скептиками (не чинить)

SP 42.13330 setback/insolation в генеративном движке (осознанная MVP-эвристика, не выдаётся за норматив), gisogd WFS-пагинация (реальные числа не тянут на hammering), forecast-headline без confidence (verify: не критично), и др.


План: волнами по кластерам. A (honesty) — первая, самая ценная. Каждый фикс → отдельный PR с Refs на этот epic. Метод: worktree-агент → code-reviewer → CI → self-merge → deploy → prod-verify.

Мульти-агентный code review site-finder (ПТИЦА, без tradein-mvp): 9 измерений × Sonnet 5, каждая находка независимо проверена 2 скептиками (adversarial verify). Два прогона (LLM недетерминированы) → объединённый дедуп-пул **18 подтверждённых** находок (из ~40 кандидатов, ~20 отсеяно скептиками как non-issue). ## Кластер A — Честность цифр (silent caps / None-to-0) — HIGH приоритет Прямо портит доверие: девелопер видит неверные числа как достоверные. - [ ] **A1** `quarter_dump_lookup.py:1182/1181` — `total_structures_in_radius`=`len()` от `LIMIT 50`, `protection_zones_intersecting`=`len()` от `LIMIT 100` (+ нет ORDER BY → недетерминированные 100). Показывает усечение как полный итог. Паттерн `items_truncated` уже есть в `permits_nearby.py`. - [ ] **A2** `utility_infrastructure_loader.py:712` — `total_features`=`len()` от `LIMIT 200`. Та же болезнь. *(прогон 1)* - [ ] **A3** `supply_layers.py:272` — L2 SQL `COALESCE(flat_count,0) - free_flats` обнуляет неизвестный `flat_count`, противоречит собственному Python-хел邁еру `_hidden_units()` (возвращает None). Инфлейтит L2-покрытие + скрытые единицы. - [ ] **A4** `velocity.py:710` — `_get_ekb_median` имеет `COALESCE(deals_total_vol_m2, deals_total_count*45.0)` proxy-fallback без disclosure, засоряет знаменатель нормализации velocity_score. - [ ] **A5** `competitors.py:452` — `_AVG_PRICE_SQL` фильтрует по глобальной `MAX(snapshot_date)` вместо per-object. Тот же баг, что чинили в `best_layouts.py` (#1956), не портирован. Цена→NULL у большинства конкурентов. *(прогон 1)* ## Кластер B — Robustness / необработанные краши — HIGH/MEDIUM - [ ] **B1** `geometry.py:180` — `CRSError` не ловится: параметр с широтой вне [-90,90] (спроецированные метры вместо WGS84) крашит запрос 500 вместо 422 `ParcelGeometryError`. - [ ] **B2** `GateVerdictBanner.tsx:142` — `COLOR_BY_LABEL[verdict_label]` без fallback: при schema-drift/partial-deploy `color` становится undefined → краш рендера. Соседние lookup'ы (`ParcelDrawer.tsx`) имеют `?? '#73767E'`. - [ ] **B3** `VelocityBlock.tsx:113` — `CONFIDENCE_COLOR[confidence]` без fallback, тот же класс. - [ ] **B4** `AnalysisPageContent.tsx:254` — нет `key={cad}` → при клиентской навигации между участками инстанс §7Concept переиспользуется, показывает stale-state от предыдущего участка. ## Кластер C — Паритет/disclosure экспортёров — HIGH/MEDIUM - [ ] **C1** `generative/exporters/pdf.py:191` — footer PDF ложно заявляет «коммерческие/офисные площади не учитываются», хотя таблица двумя строками выше показывает офисную выручку + НДС. Stale-caveat после добавления office-support. - [ ] **C2** `exporters/report_pdf.py:645` — нет секции «Уверенность» (§13.7), хотя `report_docx.py`/`report_md.py` её имеют. Паритет-дрейф + при dict-факторе PDF может крашить. ## Кластер D — Устойчивость скраперов / WAF-гигиена — MEDIUM/LOW Связано с #2443 (DOM.РФ WAF-бан). Фиксы профилактические (добавление backoff/guard безопасно). - [ ] **D1** `admin_scrape.py:1803` — `/kn-catalog-objects` всё ещё даёт дёрнуть таск, чей beat отключён из-за WAF-бана, с `max_objects=2000, force=True`, без server-side cooldown-гейта. - [ ] **D2** `domrf_catalog_object.py:439` — `BrowserSession` без override → наследует дефолт `concurrency=8, jitter 600-1500ms` вместо throttled `concurrency=2` (как в scrape_kn после #1945). Тот же `/сервисы/*` путь, что вызвал бан. - [ ] **D3** `okn_objects_sync.py:143` + `okn_egrkn_client.py` — до 1500 последовательных GET к `okn-mk.mkrf.ru` без sleep/backoff/circuit-breaker. Соседние скраперы throttled, этот выпадает. *(оба прогона)* - [ ] **D4** `reservation_ingest.py:187` — external PDF-fetch в цикле при открытой DB-сессии + без пейсинга к `pravo.gov66.ru`. *(прогон 1)* - [ ] **D5** `izyatie_ocr_ingest.py:147` — только `fetch_pdf()` в try/except; `ocr_pdf_text()`/`extract_izyatie_records()`/`_upsert` не защищены → исключение убивает весь таск вместо skip одного документа. - [ ] **D6** `etl_newbuilding_crossload.py:19` — нет своего error-handling/breadcrumb-лога, в отличие от всех соседних тасков. Падение невидимо оператору. ## Отсеяно скептиками (не чинить) SP 42.13330 setback/insolation в генеративном движке (осознанная MVP-эвристика, не выдаётся за норматив), gisogd WFS-пагинация (реальные числа не тянут на hammering), forecast-headline без confidence (verify: не критично), и др. --- **План:** волнами по кластерам. A (honesty) — первая, самая ценная. Каждый фикс → отдельный PR с Refs на этот epic. Метод: worktree-агент → code-reviewer → CI → self-merge → deploy → prod-verify.
Author
Collaborator

Кластер A (честность цифр) — ЗАКРЫТ

Все 5 находок исправлены, ревью (2 скептика на находку + code-reviewer на каждый дифф), CI зелёный, замержены:

  • A1+A2 silent-caps → PR #2450 (true COUNT + *_truncated флаги)
  • A3 supply_layers None-to-0 → PR #2446
  • A4 velocity proxy-disclosure → PR #2448 (+ follow-up #2449 для 2 остаточных сайтов)
  • A5 competitors per-object snapshot → PR #2447

Deploy + prod-verify в процессе. Далее: волна B (robustness-краши).

## Кластер A (честность цифр) — ЗАКРЫТ ✅ Все 5 находок исправлены, ревью (2 скептика на находку + code-reviewer на каждый дифф), CI зелёный, замержены: - **A1+A2** silent-caps → PR #2450 (true COUNT + `*_truncated` флаги) - **A3** supply_layers None-to-0 → PR #2446 - **A4** velocity proxy-disclosure → PR #2448 (+ follow-up #2449 для 2 остаточных сайтов) - **A5** competitors per-object snapshot → PR #2447 Deploy + prod-verify в процессе. Далее: волна B (robustness-краши).
Author
Collaborator

Кластер A — prod-verified (release 82e0b8b0)

qa-tester (rule #7) — PASS по всем 5 целям с реальными прод-сигналами:

  • Regression smoke: analyze 200, все ~80 блоков, 0×500 на connection-points/utility/competitors
  • A1/A2: structures_truncated/protection_zones_truncated/features_truncated присутствуют в API-ответе, default false, ответ цел
  • A5: положительный witness — участок 66:41:0108086:217, obj 63449 → price_source="domrf", avg_price_per_m2=207657. Per-object-latest путь срабатывает когда есть цены
  • A4: velocity.ekb_median_proxy_fraction присутствует
  • A5 индекс: idx_kn_flats_obj_snap (obj_id, snapshot_date DESC) подтверждён на проде

Caveat (НЕ регрессия, связь с #2443): практический yield A5 сейчас тонкий — latest domrf-снапшот (2026-06-28) имеет price_per_m2=NULL у большинства region-66 объектов (291 из ~тысяч с ценами). Цены грузятся тем же catalog-скрапером (/сервисы/*), что забанен WAF (#2443) — вот почему свежие снапшоты без цен. Фикс A5 корректен, но раскроется в полную силу только после разблокировки domrf-скрапинга. Ещё один довод в пользу решения по #2443.

Кластер A закрыт полностью. Волна B (robustness) — в процессе merge.

## Кластер A — prod-verified ✅ (release `82e0b8b0`) qa-tester (rule #7) — PASS по всем 5 целям с реальными прод-сигналами: - **Regression smoke**: `analyze` 200, все ~80 блоков, 0×500 на connection-points/utility/competitors - **A1/A2**: `structures_truncated`/`protection_zones_truncated`/`features_truncated` присутствуют в API-ответе, default false, ответ цел - **A5**: положительный witness — участок `66:41:0108086:217`, obj 63449 → `price_source="domrf"`, `avg_price_per_m2=207657`. Per-object-latest путь срабатывает когда есть цены - **A4**: `velocity.ekb_median_proxy_fraction` присутствует - **A5 индекс**: `idx_kn_flats_obj_snap (obj_id, snapshot_date DESC)` подтверждён на проде **Caveat (НЕ регрессия, связь с #2443):** практический yield A5 сейчас тонкий — latest domrf-снапшот (2026-06-28) имеет `price_per_m2=NULL` у большинства region-66 объектов (291 из ~тысяч с ценами). Цены грузятся тем же catalog-скрапером (`/сервисы/*`), что **забанен WAF** (#2443) — вот почему свежие снапшоты без цен. Фикс A5 корректен, но раскроется в полную силу только после разблокировки domrf-скрапинга. Ещё один довод в пользу решения по #2443. Кластер A закрыт полностью. Волна B (robustness) — в процессе merge.
Author
Collaborator

Волна B (robustness-краши) — ЗАКРЫТА

Все 4 находки исправлены, code-reviewer на каждый дифф, CI зелёный, замержены:

  • B1 geometry CRSError→422 → PR #2453 (root-cause WGS84 bbox-check + defensive wrap)
  • B2+B3 frontend enum-fallbacks → PR #2451 (+ follow-up #2452 для 4 sibling-мест)
  • B4 analysis remount on cad change → PR #2454 (нашли, что баг шире §7: §3+ChatPanel тоже утекали)

Deploy идёт. Далее: волна C (экспортёры) — C1 #2455 уже открыт, C2 на ревью.

## Волна B (robustness-краши) — ЗАКРЫТА ✅ Все 4 находки исправлены, code-reviewer на каждый дифф, CI зелёный, замержены: - **B1** geometry CRSError→422 → PR #2453 (root-cause WGS84 bbox-check + defensive wrap) - **B2+B3** frontend enum-fallbacks → PR #2451 (+ follow-up #2452 для 4 sibling-мест) - **B4** analysis remount on cad change → PR #2454 (нашли, что баг шире §7: §3+ChatPanel тоже утекали) Deploy идёт. Далее: волна C (экспортёры) — C1 #2455 уже открыт, C2 на ревью.
Author
Collaborator

Волны C (экспортёры) + D (WAF-гигиена) — ЗАКРЫТЫ

Волна C (2 находки):

  • C1 stale VAT/office caveat в concept-PDF → PR #2455 (+ follow-up #2457 для full_report row-label)
  • C2 §13.7 «Уверенность» паритет PDF-экспортёра → PR #2456

Волна D (6 находок, WAF-гигиена #2443):

  • D1+D2 admin WAF-guard + throttled catalog BrowserSession → PR #2459
  • D3 okn rate-limit/backoff/circuit-breaker → PR #2461
  • D4 reservation pacing + короткоживущая DB-сессия → PR #2458
  • D5+D6 izyatie per-doc isolation + newbuilding breadcrumb → PR #2460

Все с code-reviewer + CI зелёный + squash-merged.

Deploy-инцидент (важно — задокументировано)

qa-tester поймал, что B1 (geometry 422) не доехал на прод после мержа волны B, хотя код в main верен. Корень: deploy.yml concurrency: cancel-in-progress: false → раны строго последовательны; при множестве быстрых мержей (волны из 3-4 PR за минуту) работающий контейнер отстаёт от :latest образа на десятки минут. Мой первичный deploy-монитор ошибочно проверял env-переменную SENTRY_RELEASE (её ставит sed в .env.runtime независимо от кода образа) → ложный «зелёный». Урок зафиксирован в память + будет отдельный инфра-issue.

Сейчас весь аудит (A+B+C+D) в main; финальный backend-deploy подтягивает B1+D в контейнер — проверяю по коду в контейнере, не по env-флагу. Прод-верификация B1 напрямую (POST projected coords → 422) — следом.

Итог аудита #2445

18 находок (мульти-агентный review, 2 скептика на каждую + code-reviewer на дифф), сгруппированы в 12 PR по 4 волнам, + 5 follow-up issues (#2449, #2452, #2457 + 2 в теле). Кластер A prod-verified; B/C/D — merge complete, prod-verify B1 в процессе.

## Волны C (экспортёры) + D (WAF-гигиена) — ЗАКРЫТЫ ✅ **Волна C** (2 находки): - **C1** stale VAT/office caveat в concept-PDF → PR #2455 (+ follow-up #2457 для full_report row-label) - **C2** §13.7 «Уверенность» паритет PDF-экспортёра → PR #2456 **Волна D** (6 находок, WAF-гигиена #2443): - **D1+D2** admin WAF-guard + throttled catalog BrowserSession → PR #2459 - **D3** okn rate-limit/backoff/circuit-breaker → PR #2461 - **D4** reservation pacing + короткоживущая DB-сессия → PR #2458 - **D5+D6** izyatie per-doc isolation + newbuilding breadcrumb → PR #2460 Все с code-reviewer ✅ + CI зелёный + squash-merged. ## Deploy-инцидент (важно — задокументировано) qa-tester поймал, что **B1 (geometry 422) не доехал на прод** после мержа волны B, хотя код в main верен. Корень: `deploy.yml` `concurrency: cancel-in-progress: false` → раны строго последовательны; при множестве быстрых мержей (волны из 3-4 PR за минуту) **работающий контейнер отстаёт от `:latest` образа на десятки минут**. Мой первичный deploy-монитор ошибочно проверял env-переменную `SENTRY_RELEASE` (её ставит `sed` в `.env.runtime` независимо от кода образа) → ложный «зелёный». Урок зафиксирован в память + будет отдельный инфра-issue. Сейчас весь аудит (A+B+C+D) в main; финальный backend-deploy подтягивает B1+D в контейнер — проверяю по **коду в контейнере**, не по env-флагу. Прод-верификация B1 напрямую (POST projected coords → 422) — следом. ## Итог аудита #2445 **18 находок** (мульти-агентный review, 2 скептика на каждую + code-reviewer на дифф), сгруппированы в **12 PR** по 4 волнам, + **5 follow-up issues** (#2449, #2452, #2457 + 2 в теле). Кластер A prod-verified; B/C/D — merge complete, prod-verify B1 в процессе.
Author
Collaborator

Аудит #2445 — ЗАКРЫТ (весь на проде)

Финальная прод-верификация (по коду в работающем контейнере, не по env-флагу):

  • B1 (geometry 422): _assert_wgs84_bounds(projected)ParcelGeometryError, _metric_transformers → CRSError обёрнут → 422 не 500.
  • Волна D маркеры в контейнере: D1 waf-guard=7, D2 catalog-throttle=1, D3 okn rate-limit=5, D4 reservation pacing=5, D5 izyatie rollback=5.
  • health 200, backend healthy.

Итог: 18 находок → 12 PR (#2446-2448, #2450-2451, #2453-2456, #2458-2461) по 4 волнам, + 5 follow-up (#2449, #2452, #2457) + deploy-инфра #2462. Кластер A qa-verified (5 целей PASS); B1 prod-verified по коду; C/D merged+deployed, маркеры в контейнере.

Deploy-инцидент (контейнер отставал от образа) вынесен в #2462. Закрываю epic.

## Аудит #2445 — ЗАКРЫТ ✅ (весь на проде) Финальная прод-верификация (по **коду в работающем контейнере**, не по env-флагу): - **B1** (geometry 422): `_assert_wgs84_bounds(projected)` → `ParcelGeometryError`, `_metric_transformers` → CRSError обёрнут → 422 не 500. ✅ - **Волна D** маркеры в контейнере: D1 waf-guard=7, D2 catalog-throttle=1, D3 okn rate-limit=5, D4 reservation pacing=5, D5 izyatie rollback=5. ✅ - health 200, backend healthy. **Итог:** 18 находок → 12 PR (#2446-2448, #2450-2451, #2453-2456, #2458-2461) по 4 волнам, + 5 follow-up (#2449, #2452, #2457) + deploy-инфра #2462. Кластер A qa-verified (5 целей PASS); B1 prod-verified по коду; C/D merged+deployed, маркеры в контейнере. Deploy-инцидент (контейнер отставал от образа) вынесен в #2462. Закрываю epic.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#2445
No description provided.