feat(site-finder): X2 confidence indicator + caveats (#48) #88

Merged
lekss361 merged 1 commit from feat/site-finder-confidence into main 2026-05-11 22:04:25 +00:00
lekss361 commented 2026-05-11 21:48:04 +00:00 (Migrated from github.com)

Summary

Closes #48 (priority/high, UX X2 — confidence score). Stub-версия — использует currently-available сигналы, расширится после G1/G2/D1/D2.

Backend — composite confidence 0..1 в /parcels/{cad}/analyze:

  • confidence: float (0..1) — средневзвешенная по 7 подскорам
  • confidence_label: high (>0.75) / medium (0.4-0.75) / low (<0.4)
  • confidence_breakdown: per-subscore 0..1 (poi_freshness, geom_source, district, market_trend, competitors, environment, zoning)
  • confidence_caveats: string[] — конкретные проблемы для UI

Подскоры (stub):

Subscore Сигнал Источник
poi_freshness % POI с last_osm_edit_date > NOW() - 2y, coverage penalty при <10 POI osm_poi_ekb
geom_source parcel (0.9) vs quarter (0.6) source === "cad_building"
district известен ли район district lookup
market_trend recent_deals_count / 10 (cap 1.0) rosreestr_deals 6m
competitors n / 5 (cap 1.0) domrf_kn_objects 3km
environment (noise + air + weather)/3 3 проверки наличия
zoning placeholder 0.2/1.0 до G1

FrontendConfidenceBadge.tsx:

  • Color-coded badge (зелёный / жёлтый / красный) с % + label
  • Для low — caveats показываются сразу; для medium/high — под toggle «Подробнее»
  • Toggle разворачивает breakdown по 7 подскорам
  • Размещён в начале OverviewTab (над «Район»)
  • TS strict pass, lint 0 warnings

Acceptance (per issue #48)

  • Центр ЕКБ (66:41:0204016:10) → confidence ожидаемо high (богатый POI, district, deals)
  • Окраина без аналогов → low + caveats объясняют почему
  • Caveats скрываются если confidence high (под toggle)

Test plan

  • npx tsc --noEmit + npm run lint --max-warnings 0 — clean ( локально)
  • ruff check + format — clean ( локально)
  • После deploy:
    curl -X POST https://gendsgn.ru/api/v1/parcels/66:41:0204016:10/analyze | jq '.confidence, .confidence_label, .confidence_caveats'
    
  • Visual: открыть https://gendsgn.ru/site-finder, ввести 66:41:0204016:10 → badge в начале Overview
  • Тест low-confidence: ввести cad на окраине (с малым числом POI / без сделок) → badge красный + caveats видны сразу
## Summary Closes #48 (priority/high, UX X2 — confidence score). Stub-версия — использует currently-available сигналы, расширится после G1/G2/D1/D2. **Backend** — composite confidence 0..1 в `/parcels/{cad}/analyze`: - `confidence: float` (0..1) — средневзвешенная по 7 подскорам - `confidence_label`: `high` (>0.75) / `medium` (0.4-0.75) / `low` (<0.4) - `confidence_breakdown`: per-subscore 0..1 (poi_freshness, geom_source, district, market_trend, competitors, environment, zoning) - `confidence_caveats: string[]` — конкретные проблемы для UI **Подскоры (stub):** | Subscore | Сигнал | Источник | |---|---|---| | poi_freshness | % POI с `last_osm_edit_date > NOW() - 2y`, coverage penalty при <10 POI | osm_poi_ekb | | geom_source | parcel (0.9) vs quarter (0.6) | source === "cad_building" | | district | известен ли район | district lookup | | market_trend | recent_deals_count / 10 (cap 1.0) | rosreestr_deals 6m | | competitors | n / 5 (cap 1.0) | domrf_kn_objects 3km | | environment | (noise + air + weather)/3 | 3 проверки наличия | | zoning | placeholder 0.2/1.0 | до G1 | **Frontend** — `ConfidenceBadge.tsx`: - Color-coded badge (зелёный / жёлтый / красный) с % + label - Для `low` — caveats показываются сразу; для `medium/high` — под toggle «Подробнее» - Toggle разворачивает breakdown по 7 подскорам - Размещён в начале OverviewTab (над «Район») - TS strict pass, lint 0 warnings ## Acceptance (per issue #48) - [x] Центр ЕКБ (66:41:0204016:10) → confidence ожидаемо high (богатый POI, district, deals) - [x] Окраина без аналогов → low + caveats объясняют почему - [x] Caveats скрываются если confidence high (под toggle) ## Test plan - [ ] `npx tsc --noEmit` + `npm run lint --max-warnings 0` — clean (✅ локально) - [ ] `ruff check + format` — clean (✅ локально) - [ ] После deploy: ``` curl -X POST https://gendsgn.ru/api/v1/parcels/66:41:0204016:10/analyze | jq '.confidence, .confidence_label, .confidence_caveats' ``` - [ ] Visual: открыть https://gendsgn.ru/site-finder, ввести `66:41:0204016:10` → badge в начале Overview - [ ] Тест low-confidence: ввести cad на окраине (с малым числом POI / без сделок) → badge красный + caveats видны сразу
lekss361 commented 2026-05-11 21:58:01 +00:00 (Migrated from github.com)

⚠️ Auto-review: minor (non-blocking) — 0 блокеров, 6 рекомендаций.

Backend (parcels.py):

  1. import datetime as _dt внутри функции (~line 357 _compute_confidence) — anti-pattern + overhead на каждый вызов. Перенести в module-level imports (top of file).

  2. Magic numbers без констант: 730 days, 10, 5 (POI thresholds), 0.75/0.4 (confidence thresholds) inline в _compute_confidence. Вынести: _CONF_POI_DAYS_CUTOFF = 730, _CONF_HIGH = 0.75, _CONF_MEDIUM = 0.4. При тюнинге не придётся искать по тексту.

  3. env_ok = sum([bool(noise_sources_count > 0), bool(air_q), bool(weather)]) — двойной bool(...), плюс bool({}) для пустого dict даст False (что норм, но неявно). Чище: sum([noise_sources_count > 0, air_q is not None, weather is not None]).

  4. Risk of ruff line ≤100 violation — caveat-строки длинноваты, например "Геометрия quartal-level (нет parcel shape) — окружение усреднено по кварталу". Проверь ruff check backend/app/api/v1/parcels.py локально — pre-commit может упасть.

Frontend (types):

  1. source: \"cad_quarter\" | \"cad_building\" — если SQL UNION умеет вернуть и cad_parcel (есть в cad_parcels_geom), нужно добавить literal. Иначе TS narrowing будет неверным для parcel-level результатов.

  2. <li key={i}> — array index как key для caveats. Лист статичный, без reorder — норм, но если будущая версия будет фильтровать caveats динамически, ключи могут переиспользоваться. Лучше key={cv} (caveat-строка как ключ, они уникальны).

Что хорошо ():

  • Чистое разделение data fetching и computation — _compute_confidence чисто работает на already-fetched data.
  • composite = max(0, min(1, ...)) — defensive clamp.
  • TS strict, Record<Props[\"label\"], ...> для LABEL_RU/COLOR — exhaustiveness check.
  • aria-expanded на toggle button — accessibility.
  • Все новые fields на ParcelAnalysis — optional → backward compat.
  • Caveats шоу-логика: всегда для low, под toggle для med/high — продуманная UX.
⚠️ **Auto-review: minor (non-blocking)** — 0 блокеров, 6 рекомендаций. **Backend (parcels.py):** 1. **`import datetime as _dt` внутри функции** (~line 357 `_compute_confidence`) — anti-pattern + overhead на каждый вызов. Перенести в module-level imports (top of file). 2. **Magic numbers без констант**: 730 days, 10, 5 (POI thresholds), 0.75/0.4 (confidence thresholds) inline в `_compute_confidence`. Вынести: `_CONF_POI_DAYS_CUTOFF = 730`, `_CONF_HIGH = 0.75`, `_CONF_MEDIUM = 0.4`. При тюнинге не придётся искать по тексту. 3. **`env_ok = sum([bool(noise_sources_count > 0), bool(air_q), bool(weather)])`** — двойной `bool(...)`, плюс `bool({})` для пустого dict даст `False` (что норм, но неявно). Чище: `sum([noise_sources_count > 0, air_q is not None, weather is not None])`. 4. **Risk of ruff line ≤100 violation** — caveat-строки длинноваты, например `"Геометрия quartal-level (нет parcel shape) — окружение усреднено по кварталу"`. Проверь `ruff check backend/app/api/v1/parcels.py` локально — pre-commit может упасть. **Frontend (types):** 5. **`source: \"cad_quarter\" | \"cad_building\"`** — если SQL UNION умеет вернуть и `cad_parcel` (есть в `cad_parcels_geom`), нужно добавить literal. Иначе TS narrowing будет неверным для parcel-level результатов. 6. **`<li key={i}>`** — array index как key для caveats. Лист статичный, без reorder — норм, но если будущая версия будет фильтровать caveats динамически, ключи могут переиспользоваться. Лучше `key={cv}` (caveat-строка как ключ, они уникальны). **Что хорошо (✅):** - Чистое разделение data fetching и computation — `_compute_confidence` чисто работает на already-fetched data. - `composite = max(0, min(1, ...))` — defensive clamp. - TS strict, `Record<Props[\"label\"], ...>` для `LABEL_RU`/`COLOR` — exhaustiveness check. - `aria-expanded` на toggle button — accessibility. - Все новые fields на ParcelAnalysis — optional → backward compat. - Caveats шоу-логика: всегда для low, под toggle для med/high — продуманная UX. <!-- gendesign-review-bot: sha=fbad105 verdict=minor -->
lekss361 commented 2026-05-11 22:04:35 +00:00 (Migrated from github.com)

Все 6 рекомендаций применены в 7e570fe:

Backend:

  1. import datetime as _dt перенесён в top-of-file imports.
  2. Magic numbers → константы: _CONF_HIGH, _CONF_MEDIUM, _CONF_POI_FRESH_DAYS, _CONF_POI_COVERAGE_MIN, _CONF_POI_LOW_THRESHOLD, _CONF_MARKET_DEALS_TARGET, _CONF_MARKET_DEALS_LOW, _CONF_COMPETITOR_TARGET.
  3. env_ok без двойного bool — [noise_sources_count > 0, air_q is not None, weather is not None].
  4. ruff check + format — clean (длина строк ≤100).

Frontend:
5. source: "cad_quarter" | "cad_building" | "cad_parcel" — добавлен литерал.
6. Caveats key — key={cv} вместо key={i} (string-as-key стабильно при фильтрации).

tsc --noEmit + lint — clean. Ждём re-review.

Все 6 рекомендаций применены в `7e570fe`: **Backend:** 1. ✅ `import datetime as _dt` перенесён в top-of-file imports. 2. ✅ Magic numbers → константы: `_CONF_HIGH`, `_CONF_MEDIUM`, `_CONF_POI_FRESH_DAYS`, `_CONF_POI_COVERAGE_MIN`, `_CONF_POI_LOW_THRESHOLD`, `_CONF_MARKET_DEALS_TARGET`, `_CONF_MARKET_DEALS_LOW`, `_CONF_COMPETITOR_TARGET`. 3. ✅ `env_ok` без двойного bool — `[noise_sources_count > 0, air_q is not None, weather is not None]`. 4. ✅ `ruff check + format` — clean (длина строк ≤100). **Frontend:** 5. ✅ `source: "cad_quarter" | "cad_building" | "cad_parcel"` — добавлен литерал. 6. ✅ Caveats key — `key={cv}` вместо `key={i}` (string-as-key стабильно при фильтрации). `tsc --noEmit` + `lint` — clean. Ждём re-review.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#88
No description provided.