feat(site-finder): P1 geometry suitability score (#45) #89
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#89
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/site-finder-polygon-suitability"
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 #45 (priority/normal, Geom P1 — polygon suitability). Backend Shapely-only feature + UI badge.
Backend —
_polygon_suitability(geom_wkt):Composite score 0..1:
Labels: микро (<0.05 ha) / подходящий (≥0.7) / сложная форма (≥0.4) / слабо подходит (<0.4)
Frontend —
GeometrySuitabilityBlock.tsx:Acceptance (per issue #45)
Test plan
ruff check + format+tsc --noEmit+lint— clean (✅ локально)curl -X POST https://gendsgn.ru/api/v1/parcels/66:41:0204016:10/analyze | jq '.geometry_suitability'⚠️ Auto-review: minor (non-blocking) — 0 блокеров, 6 рекомендаций (1 фактическая ошибка в тексте).
Фактическая ошибка (стоит поправить):
recommendationvs scoring penalty — несовместимые пороги. Текст рекомендации:"Для МКД 16+ этажей нужно >0.3 ha + минимальная ширина 40м.", а штрафshort_side < 30срабатывает при <30м. Пользователь увидит "нужно 40м", но получит "узкий" только при <30. Сделай одно из:Backend (parcels.py):
Lazy imports внутри функции (
from shapely import wkt as _wkt,from shapely.geometry import Polygon). Shapely — prod-dep, не optional. Перенести в module-level imports.Bare
except Exceptionв inner MABR блоке без логирования — еслиminimum_rotated_rectangleупадёт по неожиданной причине, fallback тихий. Добавитьlogger.debug("MABR failed: %s", e).Magic numbers без констант:
0.2,0.5,0.3,0.65,5,30,0.05,0.7,0.4. Особенно критично вынести:MIN_WIDTH_M = 30,MIN_AREA_HA = 0.2,AREA_SCORE_FULL_HA = 0.5. Это строительные нормы — будут менять.Label "микро" диспатчится до score, маскируя penalties. Участок 0.04 ha с
short_side = 5мполучит label="микро", а penalties будут в ответе. UI покажет "микро" без сигнала о форме. Варианты: убрать ранний return для "микро" и давать после score, либо комбинировать: "микро, узкий".Frontend (GeometrySuitabilityBlock.tsx):
⚠emoji (U+26A0) в продовом UI — рендерится по-разному на iOS/Android/Windows, в PDF-экспорте (WeasyPrint, X3) может выпасть. Остальные компоненты (ConfidenceBadge,GeotechRiskBlock) используют текстовые маркеры/CSS-иконки. Заменить на "Проблемы формы:" или span с CSS-стилем.Что хорошо (✅):
try/exceptсdata_available: False+logger.warning— graceful degradation.mabr_coords[i+1]для i=3 → 4-я точка).Record<NonNullable<...>, ...>для exhaustiveness.geometry_suitabilityoptional в ParcelAnalysis.Все 6 пунктов применены в
ae5e8de:Фактическая ошибка:
recommendationтеперь явно разделяет 2 порога: "строительный минимум короткой стороны — 30м, комфорт типового МКД 12-16 этажей — от 40м и площадь от 0.3 га." Penalty triggered при <30м matches_GEOM_MIN_WIDTH_PHYSICAL_M; 40м идёт как separate recommendation.Backend:
2. ✅ Lazy imports → top-of-file:
from shapely import wkt as _shp_wkt,from shapely.geometry import Polygon.3. ✅ MABR inner exception:
logger.debug("MABR computation failed, falling back to sqrt(area): %s", mabr_err).4. ✅ Magic numbers → константы:
_GEOM_MIN_AREA_HA,_GEOM_AREA_SCORE_FULL_HA,_GEOM_ASPECT_PENALTY_THRESHOLD,_GEOM_CONVEX_PENALTY_THRESHOLD,_GEOM_MIN_WIDTH_PHYSICAL_M,_GEOM_MIN_WIDTH_COMFORT_M,_GEOM_LABEL_MICRO_HA,_GEOM_LABEL_GOOD,_GEOM_LABEL_MEDIUM, + penalty values.5. ✅ Label комбинирует "микро" с первым penalty: "микро, узкий" / "микро, вытянутый". Pure "микро" остаётся когда нет penalty.
Frontend:
6. ✅ Emoji ⚠ заменён на
<strong>Проблемы формы:</strong>— стабильнее в WeasyPrint/cross-platform.GeometrySuitabilityBaseLabelexported; label широчен доstringдля combo'в; новый helpercolorForLabel()парсит base часть label.ruff+tsc+lint— clean. Ждём re-review.⚠️ Auto-review: minor (non-blocking) — все 6 прошлых исправлений сделаны чисто, 1 новая мелочь.
Исправлено (✅):
_GEOM_MIN_WIDTH_PHYSICAL_M = 30и_GEOM_MIN_WIDTH_COMFORT_M = 40явно разделены, recommendation теперь explicit: "Строительный минимум — 30м, комфорт типового МКД 12-16 этажей — от 40м".from shapely import wkt as _shp_wktиfrom shapely.geometry import Polygonна module level (lines 8-9).logger.debug("MABR computation failed, falling back to sqrt(area): %s", mabr_err)._GEOM_*(lines 281-294). Сам по себе хорошо документирован.is_microbranch теперьf"микро, {penalties[0].split(' (')[0]}"— "микро, узкий" вместо bare "микро".⚠→<strong>Проблемы формы:</strong>(line 195 в TS).Новая мелочь:
parcels.py:422—0.3 гав recommendation как magic number. Текст:"...и площадь от 0.3 га.". Но_GEOM_MIN_AREA_HA = 0.2и_GEOM_AREA_SCORE_FULL_HA = 0.5— ни одного0.3. Это та же категория проблемы что #1 в прошлом review (text vs threshold mismatch), только в другом поле. Пользователь увидит "от 0.3 га" в рекомендации, но scoring модель использует 0.2/0.5.Fix options:
_GEOM_AREA_COMFORT_HA = 0.3с комментарием "третий порог: комфортная площадь МКД"._GEOM_AREA_SCORE_FULL_HA(0.5) если это правильное значение для recommendation.Дополнительная заметка:
label?: string(вместо строгого union) — acceptable trade-off для backend flexibility. Если хочется тигер:GeometrySuitabilityBaseLabel | \микро, ${string}`(template literal union) даст точный тип combo'шек. Можно как// TODO` если формат стабилизируется.Что хорошо (✅):
colorForLabeldefensive fallback корректно хендлит combo prefix.sqrt(area_m2)+aspect_ratio=1.0— reasonable degraded path.Применено в
5832dfa: добавлена константа_GEOM_AREA_COMFORT_HA = 0.3(третий semantic threshold между_GEOM_MIN_AREA_HAи_GEOM_AREA_SCORE_FULL_HA); recommendation f-string подставляет константу. Magic literal "0.3" удалён.✅ Auto-review passed (LGTM) — все 7 прошлых рекомендаций устранены чисто.
_GEOM_AREA_COMFORT_HA = 0.3теперь named constant с явным комментарием, recommendation интерполирует константу. Naming consistent с_GEOM_*prefix pattern. Role distinction (0.2 physical / 0.3 comfort / 0.5 premium) auditable.Auto-merge через ~3 мин (idle threshold для approve verdict + commits ≤ 6).
🛑 Auto-merge halted: merge conflict — после ранее-merged PR (#88, X2 confidence) PR конфликтует с main в
backend/app/api/v1/parcels.py.Что нужно сделать:
Bot re-review автоматически после rebase-push.
Merge conflict resolved в `bab2b95` (merge main into branch).
Что слилось:
`ruff check + format` + `tsc --noEmit` + `next lint` — clean.
PR теперь MERGEABLE. Ждём bot re-review.
⚠️ Auto-review: minor (non-blocking) — clean rebase, core logic корректна.
Findings (все non-blocking):
`frontend/src/components/site-finder/GeometrySuitabilityBlock.tsx` (весь файл): Inline styles используются для static layout (grid, font sizes, padding, margins). По CLAUDE.md inline styles нужны только для динамики. Динамические `c.bg / c.fg / c.border` оправданы, но окружающий static styling должен быть Tailwind. Не блокер — сиблинги (NeighborsBlock, ConfidenceBadge) используют тот же pattern; стоит вынести в convention-cleanup ticket.
`backend/app/api/v1/parcels.py` (_polygon_suitability outer except): `str(e)` конкатенируется в `note` поле response: `f"Не удалось проанализировать геометрию: {e}"`. Для internal B2B tool OK, но raw Shapely/Python exception messages могут leak internal path info. Non-blocking; для future hardening pass — обернуть в generic message.
`backend/app/api/v1/parcels.py` (MABR block): `assert isinstance(poly, Polygon)` после MultiPolygon branch — strip-нется под `python -O`, no-op в проде. Misleading. Лучше явный type guard или просто полагаться на preceding `if`.
Positives:
Auto-merge через ~5 мин (idle threshold для minor + commits=4 ≤ 6).