fix(sf-17): rosreestr velocity fallback via cad_quarter (~100% EKB coverage) #289

Merged
lekss361 merged 1 commit from fix/sf-17-rosreestr-velocity-fallback into main 2026-05-17 13:55:20 +00:00
Owner

Summary

Task #44 Part D = SF FixList #17 (Wave 3). После Part A (PR #286) mapping coverage поднялась до ~20%, но 80% ЖК ЕКБ всё равно без Objective data → их velocity = 0 (правильно но не useful). Этот PR добавляет independent fallback через rosreestr_deals JOIN по cad_quarter — даёт 100% coverage по кварталам.

Audit (postgres MCP)

  • rosreestr_deals EKB: 244,970 сделок, 11,445 уникальных кварталов
  • domrf_kn_objects EKB: 237 уникальных cad_quarter
  • Coverage: 237/237 (100%) кварталов имеют rosreestr данные за последние 12 месяцев
  • Test quarter 66:41:0702048 (для test_cad 66:41:0702048:27): 10 сделок за 12 мес

Изменения

  • Backend backend/app/services/site_finder/velocity.py:
    • VelocityResult.velocity_source: Literal['objective', 'rosreestr_fallback', 'none']
    • _OBJECTIVE_COVERAGE_MIN_RATIO = 0.50 — порог переключения на fallback
    • _compute_rosreestr_fallback()SELECT COUNT(*) FROM rosreestr_deals WHERE quarter_cad_number = :cad_quarter AND doc_type IN ('ДДУ', 'ДКП')
    • compute_velocity(cad_quarter, ...) — новый param, проверка mapped_ratio, fallback при <50% mapped ИЛИ no data
  • Backend backend/app/api/v1/parcels.py:
    • Извлечение _cad_quarter (первые 3 сегмента cad_num), передача в compute_velocity
  • Frontend frontend/src/types/site-finder.ts:
    • Velocity.velocity_source?: 'objective' | 'rosreestr_fallback' | 'none'
  • Frontend frontend/src/components/site-finder/VelocityBlock.tsx:
    • Amber badge "Источник: квартальные сделки" (bg-amber-50 text-amber-800) когда velocity_source === 'rosreestr_fallback'
  • Vault: fixes/Fix_SF_Velocity_RosreestrFallback_May17.md

Test plan

  • POST /api/v1/parcels/66:41:0702048:27/analyzevelocity.velocity_source присутствует
  • Участок где <50% ЖК в радиусе маппированы → velocity_source = 'rosreestr_fallback', velocity_data_available = true, amber badge виден
  • Участок с >=50% mapped (test_cad после Part A run) → velocity_source = 'objective', badge не показывается
  • sum=100 invariant в Recommendation не сломан (rosreestr velocity не должно влиять на mix)

Related: epic #271 item #17 · task #44 Part D · завершает A+D combo

Combined coverage после deploy:

  • Part A: Objective mapping 8.5% → 20% (трigger ?v2=true curl after deploy)
  • Part D: ~100% via rosreestr fallback для остальных участков
## Summary Task #44 Part D = SF FixList #17 (Wave 3). После Part A (PR #286) mapping coverage поднялась до ~20%, но 80% ЖК ЕКБ всё равно без Objective data → их velocity = 0 (правильно но не useful). Этот PR добавляет independent fallback через `rosreestr_deals` JOIN по `cad_quarter` — даёт **100% coverage по кварталам**. ## Audit (postgres MCP) - `rosreestr_deals` EKB: **244,970 сделок**, **11,445 уникальных кварталов** - `domrf_kn_objects` EKB: **237 уникальных cad_quarter** - **Coverage: 237/237 (100%)** кварталов имеют rosreestr данные за последние 12 месяцев - Test quarter `66:41:0702048` (для test_cad `66:41:0702048:27`): 10 сделок за 12 мес ## Изменения - **Backend** `backend/app/services/site_finder/velocity.py`: - `VelocityResult.velocity_source: Literal['objective', 'rosreestr_fallback', 'none']` - `_OBJECTIVE_COVERAGE_MIN_RATIO = 0.50` — порог переключения на fallback - `_compute_rosreestr_fallback()` — `SELECT COUNT(*) FROM rosreestr_deals WHERE quarter_cad_number = :cad_quarter AND doc_type IN ('ДДУ', 'ДКП')` - `compute_velocity(cad_quarter, ...)` — новый param, проверка mapped_ratio, fallback при <50% mapped ИЛИ no data - **Backend** `backend/app/api/v1/parcels.py`: - Извлечение `_cad_quarter` (первые 3 сегмента `cad_num`), передача в `compute_velocity` - **Frontend** `frontend/src/types/site-finder.ts`: - `Velocity.velocity_source?: 'objective' | 'rosreestr_fallback' | 'none'` - **Frontend** `frontend/src/components/site-finder/VelocityBlock.tsx`: - Amber badge "Источник: квартальные сделки" (`bg-amber-50 text-amber-800`) когда `velocity_source === 'rosreestr_fallback'` - Vault: `fixes/Fix_SF_Velocity_RosreestrFallback_May17.md` ## Test plan - [ ] `POST /api/v1/parcels/66:41:0702048:27/analyze` → `velocity.velocity_source` присутствует - [ ] Участок где <50% ЖК в радиусе маппированы → `velocity_source = 'rosreestr_fallback'`, `velocity_data_available = true`, amber badge виден - [ ] Участок с >=50% mapped (test_cad после Part A run) → `velocity_source = 'objective'`, badge не показывается - [ ] sum=100 invariant в Recommendation не сломан (rosreestr velocity не должно влиять на mix) Related: epic #271 item #17 · task #44 Part D · **завершает A+D combo** Combined coverage после deploy: - Part A: Objective mapping 8.5% → 20% (трigger `?v2=true` curl after deploy) - Part D: ~100% via rosreestr fallback для остальных участков
lekss361 added 1 commit 2026-05-17 13:52:18 +00:00
When Objective mapping coverage falls below 50% of competitors in the radius,
fall back to rosreestr_deals JOIN on the parcel's cadastral quarter. Audit shows
237/237 EKB quarters (100%) have rosreestr data for the last 12 months, compared
to <20% Objective coverage before bulk mapping.

- velocity.py: add _compute_rosreestr_fallback(), _OBJECTIVE_COVERAGE_MIN_RATIO
  constant, velocity_source field on VelocityResult (objective/rosreestr_fallback/none)
- parcels.py: extract cad_quarter from cad_num, pass to compute_velocity
- site-finder.ts: add velocity_source field to Velocity interface
- VelocityBlock.tsx: badge "Источник: квартальные сделки" when rosreestr_fallback

Epic #271 item #17
lekss361 merged commit f0589bcdff into main 2026-05-17 13:55:20 +00:00
Author
Owner

Merged via deep-code-reviewer — APPROVE.

Проверено:

  • psycopg v3 + CAST(:window_interval AS interval) корректно
  • EXPLAIN ANALYZE на 66:41:0702048 (6mo): 52ms, partition prune работает (только 2025q4 + 2026q1), индекс rosreestr_deals_region_cq_idx использован
  • Backward compat: cad_quarter: str | None = None — 12 callers в test_velocity.py + старый parcels.py не сломались
  • Guards: len(_cad_parts) >= 3 else None, n_total_comps > 0 else 0.0, db.begin_nested() SAVEPOINT
  • Vault fixes/Fix_SF_Velocity_RosreestrFallback_May17.md присутствует

Smoke post-deploy:

  • POST /api/v1/parcels/66:41:0702048:27/analyzevelocity.velocity_source присутствует в JSON
  • Участок с <50% mapped → velocity_source='rosreestr_fallback' + amber badge "Источник: квартальные сделки"
  • Combined coverage: Part A (?v2=true mapping run) + Part D (auto-active) → ~100% velocity coverage по EKB

Nit (отдельным PR, не блокер):

  • Можно добавить region_code = 66 в WHERE для full index использования (region_code, quarter_cad_number) — сейчас 52ms ок, но prefix-only scan.
Merged via deep-code-reviewer — APPROVE. **Проверено:** - psycopg v3 + `CAST(:window_interval AS interval)` корректно - EXPLAIN ANALYZE на `66:41:0702048` (6mo): 52ms, partition prune работает (только `2025q4` + `2026q1`), индекс `rosreestr_deals_region_cq_idx` использован - Backward compat: `cad_quarter: str | None = None` — 12 callers в `test_velocity.py` + старый `parcels.py` не сломались - Guards: `len(_cad_parts) >= 3 else None`, `n_total_comps > 0 else 0.0`, `db.begin_nested()` SAVEPOINT - Vault `fixes/Fix_SF_Velocity_RosreestrFallback_May17.md` присутствует **Smoke post-deploy:** - `POST /api/v1/parcels/66:41:0702048:27/analyze` → `velocity.velocity_source` присутствует в JSON - Участок с <50% mapped → `velocity_source='rosreestr_fallback'` + amber badge "Источник: квартальные сделки" - Combined coverage: Part A (`?v2=true` mapping run) + Part D (auto-active) → ~100% velocity coverage по EKB **Nit (отдельным PR, не блокер):** - Можно добавить `region_code = 66` в WHERE для full index использования `(region_code, quarter_cad_number)` — сейчас 52ms ок, но prefix-only scan.
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#289
No description provided.