fix(#243): cad_zouit fallback срабатывает когда nspd dump полностью отсутствует #248

Merged
lekss361 merged 1 commit from fix/243-cad-zouit-fallback-no-dump into main 2026-05-17 06:03:19 +00:00
Owner

Summary

Regression fix для PR #240 (G3 cad_zouit fallback). После deploy QA обнаружил что nspd_zouit_overlaps всегда [] на проде потому что nspd_quarter_dumps = 0 строк глобально → _get_zouit_overlaps возвращал make_empty_result() до fallback path.

Root cause

В backend/app/services/site_finder/quarter_dump_lookup.py в ветке if row is None:

  • Старая логика: return make_empty_result(harvest_triggered=...) — fallback недостижим
  • Fallback PR #240 срабатывал только когда dump_row exists AND zouit_count == 0

Fix

В ветке if row is None теперь вызываем _get_cad_zouit_overlaps(db, parcel_wkt) и включаем результат в nspd_zouit_overlaps перед возвратом:

if row is None:
    harvest_triggered = _trigger_harvest(quarter)
    cad_zouit_overlaps = _get_cad_zouit_overlaps(db, parcel_wkt) if parcel_wkt is not None else []
    result = make_empty_result(harvest_triggered=harvest_triggered)
    result["nspd_zouit_overlaps"] = cad_zouit_overlaps
    return result

Tests (+5, 34 total, all pass)

  • test_cad_zouit_fallback_fires_when_no_dump — fallback fires, source='cad_zouit'
  • test_cad_zouit_fallback_no_dump_no_parcel_wkt — wkt=None → empty, cad_zouit not queried
  • test_cad_zouit_fallback_empty_when_no_overlaps — cad_zouit returns 0 rows → empty list
  • test_get_cad_zouit_overlaps_returns_correct_schema — field contract verified
  • test_nspd_path_used_when_zouit_count_nonzero — NSPD path unchanged (backward compat)

Backward compat

NSPD path сохранён когда dump_row exists с zouit_count > 0 (после #244 fix). Source priority: NSPD > cad_zouit > empty.

Test plan

  • After deploy smoke:
    curl -s -X POST https://gendsgn.ru/api/v1/parcels/66:41:0603016:194/analyze \
      | jq '{count: (.nspd_zouit_overlaps | length), source: (.nspd_zouit_overlaps[0].source // "none")}'
    # expect: count > 0, source == "cad_zouit"
    
  • gate_verdict.blockers непустой когда «охранная зона трубопроводов» в radius
  • После merge #244 — NSPD path продолжает работать когда dump_row.zouit_count > 0

Closes #243.

## Summary Regression fix для PR #240 (G3 cad_zouit fallback). После deploy QA обнаружил что `nspd_zouit_overlaps` всегда `[]` на проде потому что `nspd_quarter_dumps = 0 строк` глобально → `_get_zouit_overlaps` возвращал `make_empty_result()` до fallback path. ## Root cause В `backend/app/services/site_finder/quarter_dump_lookup.py` в ветке `if row is None`: - Старая логика: `return make_empty_result(harvest_triggered=...)` — fallback недостижим - Fallback PR #240 срабатывал **только** когда `dump_row exists AND zouit_count == 0` ## Fix В ветке `if row is None` теперь вызываем `_get_cad_zouit_overlaps(db, parcel_wkt)` и включаем результат в `nspd_zouit_overlaps` перед возвратом: ```python if row is None: harvest_triggered = _trigger_harvest(quarter) cad_zouit_overlaps = _get_cad_zouit_overlaps(db, parcel_wkt) if parcel_wkt is not None else [] result = make_empty_result(harvest_triggered=harvest_triggered) result["nspd_zouit_overlaps"] = cad_zouit_overlaps return result ``` ## Tests (+5, 34 total, all pass) - `test_cad_zouit_fallback_fires_when_no_dump` — fallback fires, `source='cad_zouit'` - `test_cad_zouit_fallback_no_dump_no_parcel_wkt` — wkt=None → empty, cad_zouit not queried - `test_cad_zouit_fallback_empty_when_no_overlaps` — cad_zouit returns 0 rows → empty list - `test_get_cad_zouit_overlaps_returns_correct_schema` — field contract verified - `test_nspd_path_used_when_zouit_count_nonzero` — NSPD path unchanged (backward compat) ## Backward compat NSPD path сохранён когда `dump_row` exists с `zouit_count > 0` (после #244 fix). Source priority: NSPD > cad_zouit > empty. ## Test plan - [ ] After deploy smoke: ```bash curl -s -X POST https://gendsgn.ru/api/v1/parcels/66:41:0603016:194/analyze \ | jq '{count: (.nspd_zouit_overlaps | length), source: (.nspd_zouit_overlaps[0].source // "none")}' # expect: count > 0, source == "cad_zouit" ``` - [ ] `gate_verdict.blockers` непустой когда «охранная зона трубопроводов» в radius - [ ] После merge #244 — NSPD path продолжает работать когда `dump_row.zouit_count > 0` Closes #243.
lekss361 added 1 commit 2026-05-17 05:52:30 +00:00
Когда nspd_quarter_dumps пуст глобально (row is None) — ветка возвращала
make_empty_result() без вызова _get_cad_zouit_overlaps. Fallback из PR #240
был недостижим при отсутствии dump-строки.

Fix: в ветке `if row is None` вызываем _get_cad_zouit_overlaps(db, parcel_wkt)
и включаем результат в nspd_zouit_overlaps перед возвратом.
Backward compat: NSPD path сохранён когда dump exists с zouit_count > 0.

Tests: +5 (34 total) в test_quarter_dump_lookup.py.
lekss361 merged commit 10b79da2e9 into main 2026-05-17 06:03:19 +00:00
Author
Owner

Deep code review — verdict: APPROVE → merged

Squash-merged: 10b79da2.

Что проверено

Fix correctness:

  • Ветка if row is None теперь вызывает _get_cad_zouit_overlaps(db, parcel_wkt) с guard parcel_wkt is not None. Корректно.
  • Все 3 path-а покрыты:
    • row is None → cad_zouit (этот PR)
    • row exists + zouit_count == 0 → cad_zouit (PR #240, через _get_zouit_overlaps)
    • row exists + zouit_count > 0 → NSPD dump path (unchanged)

Backward compat:

  • Test test_nspd_path_used_when_zouit_count_nonzero явно валидирует что source='nspd-quarter-dump' при zouit_count=3. NSPD path не сломан.
  • make_empty_result() сохраняет схему — post-mutation result["nspd_zouit_overlaps"] = cad_zouit_overlaps не ломает контракт.

Cross-file impact:

  • parcels.py:2106 — потребляет nspd_dump_data["nspd_zouit_overlaps"] без изменений. Response shape стабилен.
  • gate_verdict.py:152-186 — keyword-классификатор для source='cad_zouit' (BLOCKER_TYPE_ZONE_KEYWORDS / WARNING_TYPE_ZONE_KEYWORDS) уже на месте с PR #240. Заработает автоматически.

Tests: +5 unit tests, mock-based с MagicMock, sequential db.execute() стороны корректно замоканы (включая порядок helper-вызовов в test 5). 34/34 PASS.

Conventions: ruff line ≤100 OK, psycopg v3 не trogal (используется существующий _get_cad_zouit_overlaps из PR #240). Нет SQL injection / N+1 / lock duration concerns.

Vault:

  • fixes/Fix_G3_Zouit_Fallback_NoDump_May17.md создан.
  • domains/api/endpoints/Endpoint_Parcel_Analyze.md обновлён секцией v3.8.1 fix (2026-05-17).

Lesson learned (мой miss в review PR #240)

PR #240 я approve'нул вчера без проверки branch if row is None (раннее завершение функции get_quarter_dump_data до того как _get_zouit_overlaps будет вызван). Тестировал только happy-fallback zouit_count == 0. На будущее: для branching logic нужно перечислять все возможные early-return пути в review checklist.

Production UX monitoring — критично

После deploy этот fix начнёт активно отдавать nspd_zouit_overlaps с source='cad_zouit' для всех кварталов где нет NSPD dump (= большинство прода, пока harvest #244 не отработает). Это приведёт к изменению UX:

  • gate_verdict теперь будет выдавать ZOUIT_CAD_BLOCKER (вместо unknown) для участков пересекающих type_zone с keyword'ами «охранная зона» / «трубопровод» / «электр» / «газ» в cad_zouit.
  • Участки, которые вчера показывались как «Нужна проверка», начнут показываться как «Не подходит» или «Внимание».
  • Это корректное поведение (раньше blocker'ы были скрыты багом), но пользователи увидят изменение verdict для уже просмотренных карточек.

Рекомендация:

  1. После deploy — smoke на 66:41:0603016:194 (test plan в PR body).
  2. Через 1-2 часа: SELECT count(*) FROM ... WHERE gate_verdict->>'can_build_mkd' = 'no' AND ... — посмотреть процент участков получивших новый BLOCKER.
  3. Если процент аномально высокий (>30% участков сразу blocker) — возможно BLOCKER_TYPE_ZONE_KEYWORDS слишком широкие, рассмотреть сужение в отдельном PR.
  4. Параллельно ждать merge #244 (harvest CAST fix) — после него NSPD path начнёт давать корректные nspd-quarter-dump overlaps для свежих кварталов, и cad_zouit fallback станет редким edge-case.

Ничего не блокирует

После merge — branch fix/243-cad-zouit-fallback-no-dump можно удалить (delete-branch-after-merge не активирован в репо, но руками — OK).

## Deep code review — verdict: APPROVE → merged Squash-merged: `10b79da2`. ### Что проверено **Fix correctness:** - Ветка `if row is None` теперь вызывает `_get_cad_zouit_overlaps(db, parcel_wkt)` с guard `parcel_wkt is not None`. Корректно. - Все 3 path-а покрыты: - `row is None` → cad_zouit (этот PR) - `row exists + zouit_count == 0` → cad_zouit (PR #240, через `_get_zouit_overlaps`) - `row exists + zouit_count > 0` → NSPD dump path (unchanged) **Backward compat:** - Test `test_nspd_path_used_when_zouit_count_nonzero` явно валидирует что `source='nspd-quarter-dump'` при `zouit_count=3`. NSPD path не сломан. - `make_empty_result()` сохраняет схему — post-mutation `result["nspd_zouit_overlaps"] = cad_zouit_overlaps` не ломает контракт. **Cross-file impact:** - `parcels.py:2106` — потребляет `nspd_dump_data["nspd_zouit_overlaps"]` без изменений. Response shape стабилен. - `gate_verdict.py:152-186` — keyword-классификатор для `source='cad_zouit'` (BLOCKER_TYPE_ZONE_KEYWORDS / WARNING_TYPE_ZONE_KEYWORDS) уже на месте с PR #240. Заработает автоматически. **Tests:** +5 unit tests, mock-based с `MagicMock`, sequential `db.execute()` стороны корректно замоканы (включая порядок helper-вызовов в test 5). 34/34 PASS. **Conventions:** ruff line ≤100 OK, psycopg v3 не trogal (используется существующий `_get_cad_zouit_overlaps` из PR #240). Нет SQL injection / N+1 / lock duration concerns. **Vault:** - `fixes/Fix_G3_Zouit_Fallback_NoDump_May17.md` создан. - `domains/api/endpoints/Endpoint_Parcel_Analyze.md` обновлён секцией `v3.8.1 fix (2026-05-17)`. ### Lesson learned (мой miss в review PR #240) PR #240 я approve'нул вчера без проверки branch `if row is None` (раннее завершение функции `get_quarter_dump_data` до того как `_get_zouit_overlaps` будет вызван). Тестировал только happy-fallback `zouit_count == 0`. На будущее: для branching logic нужно перечислять **все** возможные early-return пути в review checklist. ### Production UX monitoring — критично После deploy этот fix начнёт активно отдавать `nspd_zouit_overlaps` с `source='cad_zouit'` для **всех** кварталов где нет NSPD dump (= большинство прода, пока harvest #244 не отработает). Это приведёт к **изменению UX**: - `gate_verdict` теперь будет выдавать `ZOUIT_CAD_BLOCKER` (вместо unknown) для участков пересекающих type_zone с keyword'ами «охранная зона» / «трубопровод» / «электр» / «газ» в `cad_zouit`. - Участки, которые вчера показывались как «Нужна проверка», начнут показываться как «Не подходит» или «Внимание». - Это **корректное поведение** (раньше blocker'ы были скрыты багом), но пользователи увидят изменение verdict для уже просмотренных карточек. **Рекомендация:** 1. После deploy — smoke на `66:41:0603016:194` (test plan в PR body). 2. Через 1-2 часа: `SELECT count(*) FROM ... WHERE gate_verdict->>'can_build_mkd' = 'no' AND ...` — посмотреть процент участков получивших новый BLOCKER. 3. Если процент аномально высокий (>30% участков сразу blocker) — возможно `BLOCKER_TYPE_ZONE_KEYWORDS` слишком широкие, рассмотреть сужение в отдельном PR. 4. Параллельно ждать merge #244 (harvest CAST fix) — после него NSPD path начнёт давать корректные `nspd-quarter-dump` overlaps для свежих кварталов, и cad_zouit fallback станет редким edge-case. ### Ничего не блокирует После merge — branch `fix/243-cad-zouit-fallback-no-dump` можно удалить (delete-branch-after-merge не активирован в репо, но руками — OK).
Sign in to join this conversation.
No reviewers
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#248
No description provided.