feat(site_finder): velocity coverage gap-fill (spatial+name) — #968 949-A #1076

Merged
bot-backend merged 1 commit from feat/competitor-velocity-spatial-fallback-968 into main 2026-06-06 17:07:40 +00:00
Collaborator

Что

Завершает #968 (949-A). Поднимает покрытие velocity-маппинга конкурентов.

Closes #968. Часть EPIC #949.

Состояние DoD

  • relevance_weight §9.1 (composite geo×class×price×stage + sort) — уже был («PR B»).
  • complex_id join fallback — этот PR.
  • поле в схеме — relevance_weight/relevance_breakdown уже в parcel.py (не дублирую).

Velocity gap-fill (этот PR)

Для domrf-конкурентов, КОТОРЫХ НЕТ в objective_complex_mapping (всего 148 строк, ~1.6%), мост к objective-velocity: ближайший data-bearing complex ≤200м с толерантным name-matchcomplexes.idobjective_lots.complex_idproject_nameobjective_corpus_room_month.

Замерено на проде (EXPLAIN+execute финального SQL): 148 → 329 distinct mapped конкурентов (2.2×).

Ключевой момент: матч ограничен complexes, у которых реально есть objective_lots.project_name — иначе наивный nearest-any complex давал лишь +37 (ближайший complex без objective-данных съедал матч); data-bearing nearest даёт +181.

Безопасность / blast-radius

  • Аддитивно, без регрессии: PRIMARY-ветка UNION byte-identical → velocity для 148 не меняется. Fallback — gap-fill-only (NOT IN существующий mapping). UNION дедупит, DISTINCT ON (obj_id) = ровно один complex на конкурента → нет double-count (prod: 329 objids / 2789 пар).
  • Узкий радиус влияния: velocity_per_month → stage_at_horizonrelevance_weight для ново-маппленных. §22 market-pulse velocity берётся из velocity.py (НЕ тронут). velocity.py оставлен намеренно (его has_mapping → coverage-gate rosreestr-fallback = отдельный концерн).
  • empty-comm_name guard (иначе LIKE '%%' → spatial-only).

Качество

  • deep-code-reviewer: ⚠️ MINOR (approved) — no regression / no double-count / SQL valid / psycopg v3 чисто. Учтены его пункты: empty-name guard, perf-комментарий, pre-prod валидация SQL (EXPLAIN+execute на проде, т.к. mock-тесты не гоняют реальный SQL).
  • ruff clean. Покрытие тестами реального SQL — follow-up (phantom-column gate сейчас self-skip в CI; вынесу задачу).

Тест-план (post-deploy)

  • 148 уже-маппленных конкурентов: velocity_per_month без изменений.
  • Sample ново-маппленных: velocity от правильного ЖК (comm_name ≈ canonical_name в пределах 200м).
## Что Завершает #968 (949-A). Поднимает покрытие velocity-маппинга конкурентов. Closes #968. Часть EPIC #949. ## Состояние DoD - ✅ `relevance_weight §9.1` (composite geo×class×price×stage + sort) — уже был («PR B»). - ✅ **complex_id join fallback** — этот PR. - ✅ поле в схеме — `relevance_weight`/`relevance_breakdown` уже в `parcel.py` (не дублирую). ## Velocity gap-fill (этот PR) Для domrf-конкурентов, КОТОРЫХ НЕТ в `objective_complex_mapping` (всего 148 строк, ~1.6%), мост к objective-velocity: **ближайший data-bearing complex ≤200м с толерантным name-match** → `complexes.id` → `objective_lots.complex_id` → `project_name` → `objective_corpus_room_month`. **Замерено на проде (EXPLAIN+execute финального SQL):** 148 → **329** distinct mapped конкурентов (**2.2×**). Ключевой момент: матч ограничен complexes, у которых **реально есть** `objective_lots.project_name` — иначе наивный nearest-any complex давал лишь +37 (ближайший complex без objective-данных съедал матч); data-bearing nearest даёт **+181**. ## Безопасность / blast-radius - **Аддитивно, без регрессии:** PRIMARY-ветка UNION byte-identical → velocity для 148 не меняется. Fallback — gap-fill-only (`NOT IN` существующий mapping). UNION дедупит, `DISTINCT ON (obj_id)` = ровно один complex на конкурента → нет double-count (prod: 329 objids / 2789 пар). - **Узкий радиус влияния:** velocity_per_month → `stage_at_horizon` → `relevance_weight` для ново-маппленных. §22 market-pulse velocity берётся из `velocity.py` (НЕ тронут). `velocity.py` оставлен намеренно (его has_mapping → coverage-gate rosreestr-fallback = отдельный концерн). - empty-`comm_name` guard (иначе `LIKE '%%'` → spatial-only). ## Качество - `deep-code-reviewer`: ⚠️ MINOR (approved) — no regression / no double-count / SQL valid / psycopg v3 чисто. Учтены его пункты: empty-name guard, perf-комментарий, **pre-prod валидация SQL** (EXPLAIN+execute на проде, т.к. mock-тесты не гоняют реальный SQL). - `ruff` clean. Покрытие тестами реального SQL — follow-up (phantom-column gate сейчас self-skip в CI; вынесу задачу). ## Тест-план (post-deploy) - 148 уже-маппленных конкурентов: velocity_per_month без изменений. - Sample ново-маппленных: velocity от правильного ЖК (comm_name ≈ canonical_name в пределах 200м).
bot-backend added the
analytics
scope/backend
GG-форсайт
site-finder
labels 2026-06-06 17:07:37 +00:00
bot-backend added 1 commit 2026-06-06 17:07:38 +00:00
feat(site_finder): velocity coverage gap-fill via spatial+name fallback (#968 949-A)
All checks were successful
CI / changes (push) Successful in 6s
CI / changes (pull_request) Successful in 5s
Deploy / changes (push) Successful in 6s
Deploy / build-backend (push) Successful in 1m34s
Deploy / build-frontend (push) Has been skipped
Deploy / build-worker (push) Successful in 2m57s
CI / backend-tests (push) Successful in 5m43s
Deploy / deploy (push) Successful in 1m9s
CI / backend-tests (pull_request) Successful in 5m56s
d9c2157d02
For competitors missing from objective_complex_mapping, bridge to objective
velocity: nearest objective-bearing complex within 200m whose name tolerantly
matches → complex_id → objective_lots.project_name → objective_corpus_room_month.
Prod-measured: 148 → 329 mapped competitors (2.2×), validated by EXPLAIN+execute.

- nearest_cx CTE UNION'd into mapped (gap-fill only; primary 148 byte-identical,
  no double-count — UNION dedups, DISTINCT ON = one complex per competitor)
- candidates restricted to complexes WITH objective_lots.project_name (data-bearing):
  naive nearest-any gave +37; data-bearing nearest gives +181 (the real win)
- empty-comm_name guard avoids LIKE '%%' spatial-only leak
- velocity.py unchanged (its has_mapping coverage-gate is a separate concern);
  parcel.py unchanged (relevance_weight already satisfies DoD)

Blast radius narrow: affects relevance_weight (via stage_at_horizon) for
newly-mapped competitors; §22 market-pulse velocity (velocity.py) untouched.
deep-code-reviewer ⚠️ minor (approved); review items addressed. Part of EPIC #949.
bot-backend merged commit d9c2157d02 into main 2026-06-06 17:07:40 +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#1076
No description provided.