Commit graph

8 commits

Author SHA1 Message Date
bc1da8eac9 fix(gate): area-gate ЗОУИТ utility-easement blocker (sub-17) — снять over-block
All checks were successful
CI / changes (pull_request) Successful in 7s
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 1m52s
CI / backend-tests (pull_request) Successful in 11m34s
ZOUIT_OVERLAP_SUB17 (единственный subcategory-blocker = охранная зона
ЛЭП/газа/трубопровода) был ХАРД-блокером на ЛЮБОЕ пересечение → блокировал
434/574 (75.6%) участков как «нельзя строить МКД». Прод-замер: реальное
покрытие участка этими зонами median=6%, p90=47%, только 1/17 >60% — тонкие
инж-коридоры. По РФ охранная зона ограничивает застройку ВНУТРИ полосы, а не
весь участок (МКД сажается на незанятой части). Хард-блок на 6%-покрытии неверен
и (после фикса резолва зон #1891) прятал financial_estimate у ~90% участков.

Фикс: area-gate.
- quarter_dump_lookup._get_zouit_overlaps + _get_cad_zouit_overlaps: добавлен
  coverage_pct (доля площади участка под зоной, в geography м², NULLIF-guard).
- gate_verdict.compute_gate_verdict: sub-17 (и cad network/blocker) АГРЕГИРУЮТСЯ
  (sum-capped, консервативно к блоку) → блокер ТОЛЬКО если покрытие >
  settings.gate_zouit_engineering_blocker_min_coverage (0.6, env-tunable), иначе
  WARNING ZOUIT_ENGINEERING_PARTIAL с % покрытия. Больше нет 4× дубль-блокеров.
  СЗЗ/OKN/прочие ЗОУИТ — без изменений (warnings). main_vri/резидентность — без
  изменений.
- coverage_pct отсутствует (legacy/synthetic) → трактуем как 0 → warning
  (документировано: дамп всегда даёт coverage; ложный хард-блок дороже).

Тесты: +помесь gate/aggregation/coverage (131 combined passed); правка
_make_zouit_row (7-я колонка coverage_pct) в tests/test_quarter_dump_lookup.py.
Полный бэкенд локально: 2 failed → fixed → перепроверка. ruff чисто. Схема не
менялась (gate_verdict/overlaps — dict[str,Any], api-types regen не нужен).

Refs #1881
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 14:45:59 +05:00
de13bfc64c fix(gate): main_vri-authoritative детекция МКД (убрать Ж-1/Ж-2 ИЖС false-positive, включить Ц-2)
Some checks failed
CI / changes (pull_request) Successful in 6s
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 1m51s
CI / backend-tests (pull_request) Failing after 12m21s
PR-A синтезировал zone_code = geoportal-индекс ("Ж-2") → gate.is_residential_zone
матчил ^Ж и считал ЛЮБУЮ Ж-* зону жилой-МКД. Но по zone_regulation_cache.main_vri
(authoritative ВРИ): Ж-1/Ж-2 = только ИЖС (нет кодов МКД) → ^Ж давал FALSE POSITIVE
("можно МКД" на ИЖС-участке — реальный дефект для девелопера); Ц-2 = разрешает МКД
(коды 2.5/2.6) но без Ж-префикса → FALSE NEGATIVE.

Фикс: is_residential_zone получил приоритет-0 — при наличии main_vri решает по нему
авторитетно (mkd_permitted_from_vri: коды 2.1.1/2.5/2.6 = МКД, точное совпадение
токена, "2.50"/"2.59"/"12.0" не матчат), переопределяя ^Ж/subcategory/keyword.
Эвристики остаются ТОЛЬКО как fallback при main_vri=None (dump-путь без geoportal).
parcels.py прокидывает _regulation.main_vri в nspd_zoning на ОБОИХ путях (synth +
dump-augment) → dump-зоны (Ц-2 и пр.) тоже апгрейдятся на main_vri-детекцию.
Унифицирует PR-A+PR-B. Обратная совместимость: main_vri default None.

Проверено по зонам: Ж-3/4/5 permit_mkd=True, Ж-1/2=False(ИЖС), Ц-2=True,
Ц-1/3/4+ЦС-*=False.

Тесты: +22 (mkd_permitted_from_vri true/false/none + no-false-prefix; override
suppress ИЖС / enable Ц-2 / overrides subcategory; fallback при None unchanged;
synth ИЖС-кейс не даёт residential). Полный бэкенд локально: 3442 passed, 0 failed,
coverage 70.3% (gate 65%). ruff чисто. Схема не менялась. Code-review 2×approve 0 major.

Refs #1881 #1891

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 03:59:31 +05:00
46f021e1c8 fix(site-finder): разгейтить geoportal зон-резолвер от gappy NSPD-дампа
Some checks failed
CI / changes (pull_request) Successful in 7s
CI / openapi-codegen-check (pull_request) Successful in 1m51s
CI / backend-tests (pull_request) Failing after 11m59s
CI / frontend-tests (pull_request) Has been skipped
financial_estimate фаерил ~0% в проде (0/542 анализов, 90д): резолв ПЗЗ-зоны на
участок проваливался на 94%. Причина — в analyze ДВА резолвера зоны:
(1) get_quarter_dump_data/_get_zoning (nspd_quarter_dumps, точный ST_Intersects) —
терзоны НЕ замощают квартал плотно, центроид участка падает в 45-86м ЗАЗОР между
валидными зонами → nspd_zoning=None у 512/542; (2) get_or_fetch_zone_regulation →
EKBGeoportalClient.zone_index_at (живой геопортал, cache-first) — РАБОТАЕТ, резолвит
gap-участки. Но (2), дающий max_far, был ЗАГЕЙЧЕН за `_nspd_zoning is not None`, т.е.
зазор дампа глушил рабочий резолвер. Плюс: dump кладёт кадастровый рег-номер
("66:41-7.14") в zone_code, а gate.is_residential_zone матчит ^Ж → не срабатывал.

Фикс (PR-A): убран `_nspd_zoning is not None` из условия — резолвер геопортала
работает при наличии centroid (region-guard: только КН 66:41, геопортал ЕКБ-only).
Когда dump зону не дал — синтезируем минимальный nspd_zoning из geoportal:
zone_code = индекс зоны ("Ж-2") → is_residential_zone ^Ж срабатывает → can_build_mkd
резолвится для Ж-* зон. Запись обратно в nspd_dump_data["nspd_zoning"] доходит до
gate / финмоста / ответа (читают по ключу). Когда dump зону ДАЛ — zone_code не
перетираем (raw_props.subcategory детектит жильё), только добавляем regulation-поля.
Геопортал — authoritative источник (point-in-zone по ПОЛНОМУ слою ПЗЗ); «зазор»
только в нашем кэше-дампе, не в реальности. Хот-path-safe (try/except, timeout 3с,
cache-first). Расширение dump-резидентности на Ц-*/ЦС-* → PR-B.

Тесты: +7 (синтез при dump=None+geoportal; нет синтеза при обоих None; dump zone_code
не перетёрт; геопортал падает→деградация; is_residential_zone Ж-2=True, ПК-1/ЦС-3=False).
Кейс синтеза ассертит РЕАЛЬНЫЙ gate can_build_mkd=True. 47 passed (вкл. pre-existing
zoning+gate), ruff+mypy чисто. Схема не менялась (api-types regen не нужен).

Прод-замер: 66:41:0303006:930 (Ж-5 gap) сейчас zone_code/max_far/financial=None,
can_build=False → после деплоя ожидаем Ж-5/max_far=4/can_build=True/financial=PRESENT.

Refs #1881

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 03:25:06 +05:00
827459e72a test(site-finder): cover is_residential_zone raw_props/subcategory branch (#1353 follow-up)
Some checks failed
CI / changes (push) Has been cancelled
CI / backend-tests (push) Has been cancelled
CI / frontend-tests (push) Has been cancelled
CI / openapi-codegen-check (push) Has been cancelled
CI / changes (pull_request) Successful in 5s
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Failing after 1m46s
CI / backend-tests (pull_request) Failing after 8m53s
2026-06-17 21:22:04 +03:00
ac19ef4823 fix(site-finder): NO_ENGINEERING_NEARBY is a warning, not a verdict blocker (#1617)
Some checks failed
CI / changes (push) Successful in 9s
CI / changes (pull_request) Successful in 7s
CI / frontend-tests (push) Has been skipped
CI / backend-tests (pull_request) Has been cancelled
CI / openapi-codegen-check (push) Failing after 1m40s
CI / frontend-tests (pull_request) Has been cancelled
CI / openapi-codegen-check (pull_request) Has been cancelled
CI / backend-tests (push) Failing after 8m47s
Introduce INFORMATIONAL_WARNING_CODES frozenset; verdict assembly now only
downgrades to «С ограничениями» when verdict-relevant warnings exist.
NO_ENGINEERING_NEARBY still appears in warnings[] for display but a clean
residential parcel (Ж-zone, no ЗОУИТ) correctly receives verdict_label=«Можно».
2026-06-17 20:59:32 +03:00
e5e0ccb283 feat(site-finder): сетевые обременения из ЗОУИТ (#1070, Refs #103) (#1328)
All checks were successful
Deploy / changes (push) Successful in 6s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 1m36s
Deploy / build-worker (push) Successful in 2m40s
Deploy / deploy (push) Successful in 1m12s
2026-06-14 14:49:32 +00:00
lekss361
f70be684da feat(#29,#232): wire cad_parcels.permitted_use in analyze + cad_zouit fallback (G2+G3)
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).
2026-05-17 08:17:22 +03:00
lekss361
76a88b2827
feat(site-finder): gate verdict aggregator — can-build-MKD signal (#32 G5 sub-PR 1/2) (#142)
Pure-function aggregator collapses nspd_zoning / nspd_zouit_overlaps /
nspd_engineering_nearby / nspd_dump в один GateVerdict TypedDict.

Logic:
- ПЗЗ не Ж-* → BLOCKER
- ЗОУИТ sub=17 (инжен. охранная) → BLOCKER
- Другие ЗОУИТ → WARNING
- Нет инжен. сетей в 200м → WARNING
- nspd_dump stale → source: nspd_dump_partial
- Нет dump → can_build_mkd: 'unknown'

Integration: новое поле gate_verdict в analyze_parcel response.

Tests: 15/15 pass (mock-based).

Vault: code/modules/Module_Gate_Verdict.md NEW.

Closes #140 (sub-PR 2 frontend закроет #32)

Co-authored-by: lekss361 <claudestars@proton.me>
2026-05-15 00:59:53 +03:00