feat(site-finder): additive objective_lots price fallback for competitors (#307 OBJ-3) #1332

Merged
bot-backend merged 1 commit from feat/obj3-objective-price-fallback into main 2026-06-14 16:10:02 +00:00
Collaborator

Summary (OBJ-3, #307)

Site Finder competitors price-сигнал мёртв на проде: competitors._AVG_PRICE_SQL читает domrf_kn_flats.price_per_m2, но на последнем snapshot эта колонка = 0 строк (скрапер перестал отдавать per-flat цены ~2026-06-03) → avg_price_per_m2=None почти всем конкурентам → _price_similarity (вес 0.20) вырождается в нейтраль 0.5.

Additive fallback: где domrf-цены нет → медиана objective_lots.price_per_m2_rub через objective_complex_mapping (obj_id↔project, 1:1 мост). domrf-цены приоритетны (precedence domrf > objective; существующие непустые выводы байт-идентичны). Новое поле price_source (domrf/objective/null) — прозрачность провенанса для инвест-рекомендаций.

⚠️ User-facing: восстанавливает мёртвый price-сигнал → competitor relevance ordering снова учитывает цену для ~262 mapped ЕКБ obj_id (прод-median 136k руб/м², диапазон 80k-250k ✓). Веса/формула НЕ менялись; mv_layout_velocity не тронут (#295 defer). best_layouts price уже на objective — не трогал.

Test plan

  • 71 pytest (вкл. domrf-hit/objective-fallback/both-null/precedence), ruff clean
  • code-reviewer APPROVE (additive-инвариант байт-идентичен, fill-before-median ordering, no double-count, CAST clean)
  • прод-verify: 263 mapped obj_id → domrf 0 / objective fallback 262 (median 136k) / none 1
  • api-types.ts regenerated v7.13.0 (pinned) + prettier → openapi-codegen-check должен пройти
  • post-deploy: /competitors возвращает price_source=objective + avg_price_per_m2 для ЕКБ

Notes

  • objective_lots — current-state (unique objective_lot_id), медиана по текущим лотам, без per-snapshot дублей. No-snapshot-filter консистентно с уже-смерженным OBJ-3 bridge parcels.py:1603.
  • Медиана (не weighted-avg) — намеренно, избегаю #295-класса.
  • OBJ-1 (bank_name) остаётся (нужен re-scrape, proxy).

Refs #307

## Summary (OBJ-3, #307) Site Finder competitors price-сигнал **мёртв** на проде: `competitors._AVG_PRICE_SQL` читает `domrf_kn_flats.price_per_m2`, но на последнем snapshot эта колонка = **0 строк** (скрапер перестал отдавать per-flat цены ~2026-06-03) → `avg_price_per_m2=None` почти всем конкурентам → `_price_similarity` (вес 0.20) вырождается в нейтраль 0.5. **Additive fallback**: где domrf-цены нет → медиана `objective_lots.price_per_m2_rub` через `objective_complex_mapping` (obj_id↔project, 1:1 мост). domrf-цены **приоритетны** (precedence domrf > objective; существующие непустые выводы байт-идентичны). Новое поле `price_source` (`domrf`/`objective`/null) — прозрачность провенанса для инвест-рекомендаций. ⚠️ **User-facing**: восстанавливает мёртвый price-сигнал → competitor relevance ordering снова учитывает цену для ~262 mapped ЕКБ obj_id (прод-median 136k руб/м², диапазон 80k-250k ✓). Веса/формула НЕ менялись; `mv_layout_velocity` не тронут (#295 defer). best_layouts price уже на objective — не трогал. ## Test plan - [x] 71 pytest (вкл. domrf-hit/objective-fallback/both-null/precedence), ruff clean - [x] code-reviewer ✅ APPROVE (additive-инвариант байт-идентичен, fill-before-median ordering, no double-count, CAST clean) - [x] прод-verify: 263 mapped obj_id → domrf 0 / objective fallback 262 (median 136k) / none 1 - [x] api-types.ts regenerated v7.13.0 (pinned) + prettier → openapi-codegen-check должен пройти - [ ] post-deploy: /competitors возвращает price_source=objective + avg_price_per_m2 для ЕКБ ## Notes - objective_lots — current-state (unique `objective_lot_id`), медиана по текущим лотам, без per-snapshot дублей. No-snapshot-filter консистентно с уже-смерженным OBJ-3 bridge `parcels.py:1603`. - Медиана (не weighted-avg) — намеренно, избегаю #295-класса. - OBJ-1 (bank_name) остаётся (нужен re-scrape, proxy). Refs #307
bot-backend added 1 commit 2026-06-14 16:00:17 +00:00
feat(site-finder): additive objective_lots price fallback for competitors (#307 OBJ-3)
All checks were successful
CI / changes (push) Successful in 7s
CI / changes (pull_request) Successful in 8s
CI / frontend-tests (push) Successful in 1m5s
CI / frontend-tests (pull_request) Successful in 59s
CI / openapi-codegen-check (push) Successful in 2m10s
CI / openapi-codegen-check (pull_request) Successful in 1m39s
CI / backend-tests (push) Successful in 9m2s
CI / backend-tests (pull_request) Successful in 8m56s
0778af5d2c
competitors._AVG_PRICE_SQL читает domrf_kn_flats.price_per_m2, но на последнем
snapshot эта колонка пуста (0 строк, скрапер перестал отдавать per-flat цены
~2026-06-03) -> avg_price_per_m2=None почти всем конкурентам, price_similarity
вырождается в нейтраль 0.5. Добавлен additive fallback: где domrf-цены нет,
берём медиану objective_lots.price_per_m2_rub через objective_complex_mapping
(1:1 мост, ~80% покрытие, прод-median 136k руб/м²). domrf-цены приоритетны
(precedence domrf > objective); новое поле price_source ('domrf'/'objective')
для прозрачности инвест-рекомендаций. Веса и mv_layout_velocity не тронуты
(#295 defer). Медиана (не weighted-avg) устойчива к хвостам. api-types.ts
regenerated (v7.13.0 pinned + prettier).

Refs #307
bot-backend merged commit b405ae291d into main 2026-06-14 16:10:02 +00:00
bot-backend deleted branch feat/obj3-objective-price-fallback 2026-06-14 16:10:03 +00:00
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#1332
No description provided.