1. score_top_3_negatives — rewrite to unambiguously "most-negative first" via
explicit `sorted(negatives, key=contribution)[:3]` (ascending sort). Раньше
использовался trick `[-3:][::-1]` на desc-sorted — давал тот же результат
для N>=3 negatives, но неинтуитивно читать.
2. _compute_confidence(recent_deals_count) — guard `int(... or 0)` через
try/except (ValueError, TypeError). Защищает от non-numeric строки в
external/legacy market_trend payload.
3. Style: `import datetime as _dt` перенесён из function-scope в module-level.
Per auto-review on d0fa8a3.
PR #87 auto-review нашёл: два POI одной категории на одинаковом округлённом
расстоянии (например two аптеки 450м в плотном районе центра ЕКБ) давали
duplicate factor key "pharmacy_450m" → silent React reconciliation errors.
Fix: добавил enumerate index в slug: f"{cat}_{round(distance_m)}m_{idx}".
1 строка, backward compat (factor — opaque slug, не парсится клиентом).
Per auto-review on 97dc4ba.
Backend (parcels.py):
- Centrality factor: weight=1.0 вместо weight=center_bonus (semantic — decay
для bonus не применяется, contribution = bonus IS the value). Защищает
future PDF-export/UI который мог бы показать weight отдельно от contribution.
- group_totals type: dict[str, dict[str, float | int]] — count это int,
contribution и contribution_pct это float. Уточняет hint для future mypy.
- Top-3 sort convention — добавлен inline-комментарий: positives "[:3] от
descending" (most-positive first); negatives "[-3:][::-1]" (most-negative
first). Оба "dominant first".
Frontend (ScoreBreakdownPanel.tsx):
- Stacked bar legend orphan fix: positive groups идут в legend под баром (как
до того); negative groups показываются отдельной строкой ниже "Снижают балл —
Шум/трамвай: −0.46". Никаких swatch'ей без bar-сегмента.
Per auto-review on 1d1c169.
* feat(site-finder): P2 cad_buildings соседи + overlap check (#46)
Backend (parcels.py):
- _parse_floors() helper для TEXT column (cad_buildings.floors хранится как
строка, могут быть диапазоны "5-7"). Возвращает верхнюю границу.
- _neighbors_summary(db, geom_wkt, our_cad) — query соседей в 100м (GIST):
cad_num, building_name, floors, year_built, cost_value, area, address, distance.
Aggregate: avg_floors_100m, max_floors_100m, median_cost_per_m2_100m,
count_buildings_100m. Outliers cost/m² фильтруются (1k < x < 500k).
- Overlap check: ST_Intersects + ST_Area(ST_Intersection) > 50 m² (transformed
to UTM 32641 для метров). Если есть → has_existing_buildings: true +
overlap_buildings list.
- В response → neighbors_summary.
Frontend:
- Новый NeighborsBlock.tsx: hard red warn block для overlap (с building names +
overlap_m2 + "Инвестиции невозможны без сноса"); summary metrics (avg/max
floors, median price); toggle "Показать N ближайших" → таблица.
- Border меняется на красный при has_existing_buildings — visual cue.
- Добавлен в LandTab выше "Зонирование (ПЗЗ)".
- TS типы: NeighborBuilding, OverlapBuilding, NeighborsSummary.
Closes#46. Closes#21 (cad_buildings в Site Finder фильтрах).
* fix(site-finder): address PR #91 auto-review minor feedback
Backend (parcels.py):
1. (medium) Aggregation loop _neighbors_summary теперь обёрнут в try/except
(ValueError, TypeError) с fallback к data_available:False + log warning.
Защищает от non-numeric cost_value/area придёт в строке (e.g. "N/A") —
ранее весь endpoint падал 500.
2. Magic numbers вынесены: _COST_PER_M2_MIN=1000, _COST_PER_M2_MAX=500_000.
3. _parse_floors docstring + inline note про malformed parts ("5а-7" filter,
multi-range "1-2-3" max acceptable degradation).
Frontend (NeighborsBlock.tsx):
5. Русский plural fix: pluralBuildings(n) helper — 1 здание, 2-4 здания,
5+/11-14 зданий. Раньше "3 зданий" — теперь "3 здания".
Не сделано (defer):
4. ST_Area для overlap query — практически 0-5 buildings в ЕКБ, GIST scan OK.
6. Discriminated union для NeighborsSummary — refactor а не bug.
7. Vault entry для P2 — добавится batch'ем после merge всех текущих PR.
Per auto-review on 60d53bb.
---------
Co-authored-by: lekss361 <claudestars@proton.me>
Backend (parcels.py):
- _compute_confidence() composite score 0..1 from 7 subscores: poi_freshness,
geom_source (parcel vs quarter), district, market_trend (rosreestr_deals depth),
competitors, environment (noise/air/weather availability), zoning (placeholder
до G1).
- confidence_label: high (>0.75) / medium (0.4-0.75) / low (<0.4)
- confidence_caveats: list of конкретных проблем для UI
- confidence_breakdown: per-subscore 0..1 для прозрачности
Это stub-версия (полная — после G1/G2/D1/D2). Использует только текущие сигналы.
Frontend:
- Новый ConfidenceBadge.tsx — color-coded (green/yellow/red) badge с %
- Caveats для low — показываются сразу; для medium/high — под toggle
- Toggle "Подробнее" → breakdown per-subscore + полный список caveats
- Размещён в начале OverviewTab (выше "Район")
- TS типы расширены: confidence, confidence_label, confidence_breakdown, confidence_caveats
Closes#48.
Co-authored-by: lekss361 <claudestars@proton.me>
- nspd_geo: add _save_parcel() for thematic_id=1 → cad_parcels_geom (UPSERT,
ST_Transform from Web Mercator); _persist_target now handles 1/2/5
- parcels.py: analyze endpoint geom lookup extended with cad_parcels_geom as
3rd fallback source (after cad_quarters_geom, cad_buildings); both SELECT
and WKT subqueries updated
- parcels.py: POI score_breakdown items now include lat/lon for map markers
- poi_loader: OSM_CATEGORIES expanded — college+university→school,
hypermarket→shop_supermarket; coverage +3 tag pairs