feat(site-finder): P2 cad_buildings соседи + overlap check (#46) #91
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#91
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/site-finder-neighbors"
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
Closes #46 (Geom P2 — соседи-высотки + overlap warning). Closes #21.
Backend — 2 новых запроса к
cad_buildings(GIST на geom):cad_num,building_name,floors(TEXT, через_parse_floors()верхняя граница диапазона "5-7"→7),year_built,cost_value,area,readable_address,distance_m. Top-30 ближайших.ST_Intersects+ST_Area(ST_Intersection)в UTM-32641 (для метров). Если хоть один building покрывает >50 м² нашего участка → hard warn.Aggregate в
neighbors_summary:avg_floors_100m,max_floors_100m,median_cost_per_m2_100m(с outlier filter 1k<x<500k ₽/м²),count_buildings_100mhas_existing_buildings: booleanoverlap_buildings[]с overlap_m2 каждогоneighbors[20]для UI таблицыFrontend —
NeighborsBlock.tsx:has_existing_buildings— с list overlap building names, m² пересечения, и текстом «Инвестиции невозможны без сноса»Acceptance (per issue #46)
Test plan
ruff+tsc+lintclean (✅ локально)⚠️ Auto-review: minor (non-blocking) — 0 блокеров, 7 рекомендаций (1 medium-severity, остальные мелкие).
Medium-severity (стоит починить до merge):
_neighbors_summary— unprotected aggregation loop (parcels.py). Внутренние два SQL блока в try/except, но Python aggregation loop (iterateneighbor_rows,float(r["cost_value"]), ...) — без защиты. Еслиcost_valueпридёт как non-numeric ("N/A"),float()бросит ValueError → весь endpoint 500. Низкая вероятность по schema, но дефенсивно стоит обернуть aggregation вtry/except Exceptionс fallbackdata_available: False.Backend mелочи:
Magic cost filter
1000 < cost_per_m2 < 500_000— undocumented constants. ВынестиCOST_PER_M2_MIN,COST_PER_M2_MAXрядом с существующимиSCORE_THRESHOLDSetc._parse_floorsдля multi-range — "1-2-3" вернёт max(1,2,3)=3 (неожиданно для тройного диапазона, но acceptable). Стоит добавить 1-строчный комментарий чтоisdigit()намеренно фильтрует malformed parts типа "5а-7".Overlap query
ORDER BY overlap_m2 DESC NULLS LAST+LIMIT 5— ST_Area вычисляется для всех qualifying rows ДО сортировки. В практике ЕКБ это 0-5 buildings (GIST scan наcad_buildings_geom_gistподтверждён в schema), но awareness flag.Frontend мелочи:
Русский plural неправильно (NeighborsBlock.tsx):
count === 1 ? "здание" : "зданий". Для 2-4 нужно "здания", для 5+ "зданий". Сейчас "3 зданий" вместо "3 здания". Использовать pluralize helper (3-line util).NeighborsSummaryinterface — все computed fields?(acceptable, FE гардит наdata_available). Можно ужесточить через discriminated union (data_available: truebranch с required fields), но это refactor а не bug.Organizational:
Что хорошо (✅):
:wkt,:our_cad) — no injection.::geographyдля ST_DWithin/ST_Distance +ST_Transform(..., 32641)для ST_Area — правильный pattern для ЕКБ.cad_buildings_geom_gistподтверждён вdata/sql/63_schema_nspd_runs.sql→ queries используют index._parse_floorsдля "5-7" → 7 (max) семантически правильно: "консервативный сосед-высотка" для shadow/density.has_existing_buildingsиз >50 м² post-filter, не из rawST_Intersectscount → no false positives от point-touch.building_name ?? readable_address ?? cad_numgraceful fallback.aria-expandedaccessibility.floors: stringvs parsedfloors_parsed: numberправильно разделены.Применено в
3706a79: aggregation loop защищён try/except (medium fix); cost magic numbers →_COST_PER_M2_MIN+_COST_PER_M2_MAX;_parse_floorsdocstring; русский pluralpluralBuildings(n)helper (правильно 1/2-4/5+). Перформанс ST_Area + discriminated union — defer. Vault entry — добавим batch'ем после merge.✅ Auto-review passed (LGTM) — все 4 fixable находки устранены чисто.
Resolved (✅):
try/except (ValueError, TypeError). KeyError защищён.get()на всех dict-доступах → comprehensive scope._COST_PER_M2_MIN/_COST_PER_M2_MAXmodule-level constants с rationale ("кадастровая стоимость иногда содержит 0/None или миллиарды")._parse_floorsdocstring объясняетisdigit()filter + multi-range degradation.pluralBuildings(n)корректно хендлит русский plural: 1→здание, 2-4→здания, 5+→зданий, плюс 11-14 teen exception (mod100 check ДО mod10).Cosmetic (cosmetic-only, не блокирует):
pluralBuildings: "zданий" — смесь Latinzс Cyrillic в комментарии. Не влияет на runtime, но артефакт чернового редактирования.#4 (overlap query awareness), #6 (discriminated union refactor), #7 (vault entry) — explicitly не addressed, acceptable per prior classification.
Auto-merge через ~3 мин (idle threshold для approve + commits ≤ 6).