feat(#29,#232): wire cad_parcels.permitted_use в analyze + cad_zouit fallback (G2+G3) #240

Merged
lekss361 merged 1 commit from feat/g2-g3-analyze-parcel-meta-zouit-fallback into main 2026-05-17 05:26:03 +00:00
Owner

Summary

Combined PR для двух Site Finder quick wins (оба меняют analyze endpoint).

TASK A — #29 G2: parcel_meta в analyze response

cad_parcels.permitted_use_established_by_document имеет 98.1% coverage (38559/39287). Раньше эти данные не отдавались в analyze — теперь добавлено как parcel_meta блок.

Изменения:

  • backend/app/schemas/parcel.py — новая ParcelMeta Pydantic model (5 полей: permitted_use, land_category, land_subtype, cad_cost, source)
  • backend/app/api/v1/parcels.py — SELECT из cad_parcels WHERE cad_num = CAST(:c AS text) на шаге 9f
  • backend/tests/api/v1/test_analyze_parcel_meta.py — 2 теста (found / not-found)

TASK B — #232 G3: cad_zouit fallback

cad_zouit имеет 3 483 строки + GIST index (cad_zouit_geom_gist, ~1ms на ST_Intersects). До этого _get_zouit_overlaps читал только из nspd_quarter_dumps (пуст из-за заморозки beat 2026-05-14). Теперь при zouit_count==0 или отсутствии dump → fallback на cad_zouit.

Важно: cad_zouit.subcategory 100% NULL → существующий BLOCKER_SUBCATEGORIES={17:...} не сработал бы. Добавлен BLOCKER_TYPE_ZONE_KEYWORDS / WARNING_TYPE_ZONE_KEYWORDS для классификации через type_zone substring match (охранные зоны / трубопроводы / электр / газ = blocker, СЗЗ = warning).

Изменения:

  • backend/app/services/site_finder/quarter_dump_lookup.py — новая функция _get_cad_zouit_overlaps, вызов из _get_zouit_overlaps при zouit_count==0
  • backend/app/services/site_finder/gate_verdict.py — keyword fallback для overlaps с source=="cad_zouit". NSPD subcategory path не тронут (backward compat)
  • backend/tests/test_gate_verdict.py — 6 новых тестов (все 20 зелёные)

Обновлены тесты с sequence shift

test_analyze_market_price.py, test_analyze_recent_permits.py, test_analyze_inline_weights.py — сдвиг +1 на позиции 10 из-за нового parcel_meta SELECT.

Test plan

  • Smoke after deploy:
    curl -s -X POST https://gendsgn.ru/api/v1/parcels/66:41:0204016:10/analyze \
      | jq '{parcel_meta, nspd_zouit_overlaps: (.nspd_zouit_overlaps | length), gate_verdict}'
    
  • parcel_meta.permitted_use не None для ЕКБ-cad
  • nspd_zouit_overlaps не пуст на проде даже при nspd_quarter_dumps=0
  • gate_verdict реагирует blocker'ом на «охранная зона трубопроводов» из cad_zouit
## Summary Combined PR для двух Site Finder quick wins (оба меняют analyze endpoint). ## TASK A — #29 G2: parcel_meta в analyze response `cad_parcels.permitted_use_established_by_document` имеет 98.1% coverage (38559/39287). Раньше эти данные не отдавались в analyze — теперь добавлено как `parcel_meta` блок. **Изменения:** - `backend/app/schemas/parcel.py` — новая `ParcelMeta` Pydantic model (5 полей: permitted_use, land_category, land_subtype, cad_cost, source) - `backend/app/api/v1/parcels.py` — SELECT из `cad_parcels WHERE cad_num = CAST(:c AS text)` на шаге 9f - `backend/tests/api/v1/test_analyze_parcel_meta.py` — 2 теста (found / not-found) ## TASK B — #232 G3: cad_zouit fallback `cad_zouit` имеет 3 483 строки + GIST index (`cad_zouit_geom_gist`, ~1ms на ST_Intersects). До этого `_get_zouit_overlaps` читал только из `nspd_quarter_dumps` (пуст из-за заморозки beat 2026-05-14). Теперь при `zouit_count==0` или отсутствии dump → fallback на cad_zouit. **Важно:** `cad_zouit.subcategory` 100% NULL → существующий `BLOCKER_SUBCATEGORIES={17:...}` не сработал бы. Добавлен `BLOCKER_TYPE_ZONE_KEYWORDS` / `WARNING_TYPE_ZONE_KEYWORDS` для классификации через `type_zone` substring match (охранные зоны / трубопроводы / электр / газ = blocker, СЗЗ = warning). **Изменения:** - `backend/app/services/site_finder/quarter_dump_lookup.py` — новая функция `_get_cad_zouit_overlaps`, вызов из `_get_zouit_overlaps` при `zouit_count==0` - `backend/app/services/site_finder/gate_verdict.py` — keyword fallback для overlaps с `source=="cad_zouit"`. NSPD subcategory path не тронут (backward compat) - `backend/tests/test_gate_verdict.py` — 6 новых тестов (все 20 зелёные) ## Обновлены тесты с sequence shift `test_analyze_market_price.py`, `test_analyze_recent_permits.py`, `test_analyze_inline_weights.py` — сдвиг +1 на позиции 10 из-за нового parcel_meta SELECT. ## Test plan - [ ] Smoke after deploy: ```bash curl -s -X POST https://gendsgn.ru/api/v1/parcels/66:41:0204016:10/analyze \ | jq '{parcel_meta, nspd_zouit_overlaps: (.nspd_zouit_overlaps | length), gate_verdict}' ``` - [ ] `parcel_meta.permitted_use` не None для ЕКБ-cad - [ ] `nspd_zouit_overlaps` не пуст на проде даже при `nspd_quarter_dumps=0` - [ ] gate_verdict реагирует blocker'ом на «охранная зона трубопроводов» из cad_zouit
lekss361 added 1 commit 2026-05-17 05:20:51 +00:00
TASK A (#29 G2): add parcel_meta to analyze response
- New ParcelMeta Pydantic schema in app/schemas/parcel.py
- SELECT from cad_parcels WHERE cad_num=:c in analyze_parcel() (step 9f)
- Returns permitted_use_established_by_document, land_record_category_type,
  land_record_subtype, cost_value; None when row absent
- Tests: test_analyze_parcel_meta.py (found + not-found cases)

TASK B (#232 G3): cad_zouit fallback in _get_zouit_overlaps
- When nspd_quarter_dumps has zouit_count==0, fall back to ST_Intersects
  query on cad_zouit (3483 rows, GIST indexed)
- Overlaps tagged with source='cad_zouit'; format compatible with NSPD path
- gate_verdict.py: BLOCKER_TYPE_ZONE_KEYWORDS tuple for keyword-based
  classification (охранная зона / трубопровод / электр / газ -> blocker;
  СЗЗ -> warning); NSPD subcategory path preserved backward-compat
- Tests: 6 new test cases in test_gate_verdict.py covering cad_zouit path
  and backward-compat for NSPD subcategory path

Updated db.execute call sequence in test_analyze_*.py (index shift +1 at pos 10).
lekss361 merged commit 7d0bc4bb6b into main 2026-05-17 05:26:03 +00:00
Author
Owner

Deep Code Review — APPROVE, merged

Verdict: APPROVE (LOW risk). Merged via deep-code-reviewer.

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

  • Source segregation в gate_verdict.py:155-202if src == "cad_zouit" корректно изолирует keyword path от NSPD subcategory path. Backward compat verified тестом test_nspd_subcategory_path_backward_compat.
  • psycopg v3 compliance — оба новых SQL используют CAST(:c AS text) и ST_GeomFromText(:wkt, 4326). Grep :[a-z_]+::[a-z] → 0 matches.
  • GIST index usedEXPLAIN на cad_zouit ST_Intersects для ЕКБ полигона: Bitmap Index Scan через cad_zouit_geom_gist, cost 230, ~14 rows. ~1ms.
  • Fallback trigger — активируется только при явном layer_counts.zouit_count == 0. Безопасно для текущего состояния (beat заморожен per Bug_NSPD_WMS_NotBulk_2026_May14).
  • Try/except + logger.warning — не маскируют ошибки, gracefully degrade.

Low-severity рекомендации (не блокирующие, можно отдельным PR)

  1. _get_cad_zouit_overlaps без LIMIT — для consistency с _get_engineering_nearby (LIMIT 20) добавить LIMIT 50. На сейчас не критично (3 483 строки в табл).
  2. Post-deploy monitoring keyword false-positive: через 1-2 дня выбрать ~100 production вердиктов и проверить распределение ZOUIT_CAD_BLOCKER / ZOUIT_CAD_SZZ / ZOUIT_CAD_OTHER codes против expected. type_zone это контролируемый Росреестром словарь, false-positive surface узкий — но проверить стоит.
  3. frontend/src/types/site-finder.ts не обновлён под parcel_meta. OK для merge (поле опционально, null-safe), но npm run codegen можно запустить отдельным PR.

Smoke test после deploy

curl -s -X POST https://gendsgn.ru/api/v1/parcels/66:41:0204016:10/analyze \
  | jq '{parcel_meta, nspd_zouit_overlaps: (.nspd_zouit_overlaps | length), gate_verdict}'

Ожидание: parcel_meta.permitted_use != null, nspd_zouit_overlaps не пуст, gate_verdict реагирует на cad_zouit overlaps.

## Deep Code Review — APPROVE, merged **Verdict:** APPROVE (LOW risk). Merged via deep-code-reviewer. ### Что проверено - **Source segregation** в `gate_verdict.py:155-202` — `if src == "cad_zouit"` корректно изолирует keyword path от NSPD subcategory path. Backward compat verified тестом `test_nspd_subcategory_path_backward_compat`. - **psycopg v3 compliance** — оба новых SQL используют `CAST(:c AS text)` и `ST_GeomFromText(:wkt, 4326)`. Grep `:[a-z_]+::[a-z]` → 0 matches. - **GIST index used** — `EXPLAIN` на `cad_zouit ST_Intersects` для ЕКБ полигона: Bitmap Index Scan через `cad_zouit_geom_gist`, cost 230, ~14 rows. ~1ms. - **Fallback trigger** — активируется только при явном `layer_counts.zouit_count == 0`. Безопасно для текущего состояния (beat заморожен per `Bug_NSPD_WMS_NotBulk_2026_May14`). - **Try/except + logger.warning** — не маскируют ошибки, gracefully degrade. ### Low-severity рекомендации (не блокирующие, можно отдельным PR) 1. `_get_cad_zouit_overlaps` без `LIMIT` — для consistency с `_get_engineering_nearby` (LIMIT 20) добавить `LIMIT 50`. На сейчас не критично (3 483 строки в табл). 2. **Post-deploy monitoring** keyword false-positive: через 1-2 дня выбрать ~100 production вердиктов и проверить распределение `ZOUIT_CAD_BLOCKER` / `ZOUIT_CAD_SZZ` / `ZOUIT_CAD_OTHER` codes против expected. `type_zone` это контролируемый Росреестром словарь, false-positive surface узкий — но проверить стоит. 3. `frontend/src/types/site-finder.ts` не обновлён под `parcel_meta`. OK для merge (поле опционально, null-safe), но `npm run codegen` можно запустить отдельным PR. ### Smoke test после deploy ```bash curl -s -X POST https://gendsgn.ru/api/v1/parcels/66:41:0204016:10/analyze \ | jq '{parcel_meta, nspd_zouit_overlaps: (.nspd_zouit_overlaps | length), gate_verdict}' ``` Ожидание: `parcel_meta.permitted_use != null`, `nspd_zouit_overlaps` не пуст, `gate_verdict` реагирует на cad_zouit overlaps.
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#240
No description provided.