feat(site-finder): gate verdict aggregator (#32 G5 sub-PR 1/2) #142
No reviewers
Labels
No labels
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
Fable 5 ревью
feedback/max
generative
GG-форсайт
needs-discussion
needs-human
observability
pause-bots
performance
priority/p0
priority/p1
priority/p2
priority/p3
scope/backend
scope/db
scope/devops
scope/frontend
scope/qa
scrapers
security
site-finder
stage/1
stage/2
status/blocked
status/done
status/needs-analysis
status/needs-fix
status/qa
status/ready
status/review
status/wip
tech-debt
tradein
ux
week ревью 1
wontfix
вторичка
ИРД
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#142
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/32-gate-verdict-aggregator"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
#32 G5 Gate Verdict — sub-PR 1 of 2 (backend aggregator).
Foundation уже shipped через Sprint 1.1 (PR #116):
Этот PR aggregates 4 поля в `gate_verdict` для UI banner (sub-PR 2/2).
Aggregator logic
Response shape
```json
"gate_verdict": {
"can_build_mkd": true | false | "unknown",
"verdict_label": "Можно" | "Нельзя" | "С ограничениями" | "Нужна проверка",
"blockers": [{"code": "PZZ_NOT_RESIDENTIAL", "detail": "..."}],
"warnings": [...],
"checks_performed": ["ПЗЗ зональность", "ЗОУИТ", "Инжен. сети 200м"],
"source": "nspd_dump" | "nspd_dump_partial" | "no_data"
}
```
Tests
15/15 pass — mock-based, no DB.
Sub-PR 2 (next)
GateVerdictBanner в OverviewTab — coloured banner + collapsible details.
Vault
`code/modules/Module_Gate_Verdict.md` NEW
Closes #140
Refs: #32, #94 (foundation)
Bot review (SHA
e644645, CI ✅)Чистый pure-function aggregator для G5. Inert до sub-PR 2/2 (UI banner).
Design
✅ Pure function без DB — consumes уже-fetched data из analyze_parcel, простая unit-test'абельность
✅ TypedDict response (Blocker/Warning/GateVerdict с Literal types) — strict contract для UI
✅ Constants на топе —
RESIDENTIAL_ZONE_PREFIXES,BLOCKER_SUBCATEGORIES,ENGINEERING_NEARBY_THRESHOLD_M=200. Easy to evolve.✅ Verdict ladder (No dump → Нельзя/Можно/С ограничениями/Нужна проверка) покрывает 4 UI banner state'а из issue
✅
sourcefield (nspd_dump/nspd_dump_partial/no_data) — transparency о data freshness для UI tooltipSafety
✅
if isinstance(sub, int) and sub in BLOCKER_SUBCATEGORIES— None/non-int subcategory не крашит✅
(nspd_dump or {}).get("available", False)— None-safe access✅
(nspd_engineering_nearby or [])— None list корректно треатится как emptyTests (15/15)
Полное покрытие веток:
is_residential_zone: Ж-2 / Ж5 prefix / "Жилая" fallback / П-1 reject / Р-2 rejectcompute_gate_verdict: no_dump / dump available=False / clean residential → Можно / Производственная → Нельзя / ЗОУИТ sub=17 блок / sub-other warning / no-engineering warning / stale → partial source / checks_performed list / PZZ_UNKNOWN warningparcels.py integration
Surgical (+8 строк): import + call + поле в response dict. Нет side-effects.
Минор (не блокеры — про future iterations sub-PR 3+)
BLOCKER_SUBCATEGORIES = {17: "..."}— пока только охранные ЛЭП/газа/трубопровода. Санитарно-защитные / водоохранные / особо охраняемые сейчас warnings. MVP acceptable; расширять по мере роста domain knowledge.RESIDENTIAL_KEYWORDS = ("жил",)substring match — теоретически false-positive на zone like "жилищно-коммунальная" (если такая встретится в данных). Acceptable initial heuristic; усложнять только если будут false positives.Russian labels в коде (
"Можно","Нельзя") — out-of-scope для i18n в текущем sprint.approve merge