fix(best-layouts): per-object latest snapshot для supply → срок продажи/% продано (#1956) #2020

Merged
bot-backend merged 1 commit from fix/best-layouts-per-object-snapshot-1956 into main 2026-06-27 18:43:19 +00:00
Collaborator

Батч аудита (эпик #1953). Регрессия от моего же #1944. code-reviewer APPROVE, прод-verified.

Проблема

_SUPPLY_BATCH_SQL (best_layouts.py) джойнил domrf_kn_flats по ОДНОЙ глобальной дате f.snapshot_date = MAX(snapshot_date) (по всей таблице). Но domrf_kn_flatsper-object time-series (каждый ЖК скрейпится в свой день). На единственной глобал-max дате почти ни у одного соседнего объекта нет flats → supply_units_in_radius=0 → фронт salesPeriodMonths(0,v)=0 → «0 мес», sold_pct=null → «—».

Фикс

flats_latest CTE — per-object latest snapshot:

flats_latest AS (
  SELECT DISTINCT ON (f.obj_id) f.obj_id, f.snapshot_date
  FROM domrf_kn_flats f JOIN nearby n ON n.obj_id=f.obj_id
  ORDER BY f.obj_id, f.snapshot_date DESC, f.id DESC
)
... JOIN domrf_kn_flats f ON f.obj_id=fl.obj_id AND f.snapshot_date=fl.snapshot_date

#1944 objects-first дедуп (latest_obj DISTINCT ON + nearby MATERIALIZED) сохранён — ×8.5 fan-out не возвращается (flats никогда не джойнятся к объектам напрямую).

Прод-verify (66:41:0205010:287, r=1км)

9 объектов; глобал-max=2026-06-22. supply: было 0 → стало 2675. Дымящийся ствол: только 4 из 9 объектов имеют flats, на датах 05-17/05-05, ни одного на 06-22 → старый JOIN давал 0. Per-bucket supply теперь ненулевой для студия/1/2/3/4+ → срок продажи ненулевой, % продано не «—».

Качество

  • ruff ; no :name::type; 376 passed (+ guard test_supply_joins_flats_per_object_latest_snapshot — упадёт при реверте на глобал-MAX).
  • Scope: меняется только источник supply-числителя; velocity/sold_pct/is_oversold формулы не тронуты.

⚠️ Coupling #1945: данные flats частично устаревшие (последние per-object снапшоты — май, loader сломан) → supply ниже здорового, но фикс корректен (0→реальные per-object). Loader не трогаю.
Follow-up (perf, минор): композитный индекс (obj_id, snapshot_date) на domrf_kn_flats (сейчас запрос ограничен nearby → приемлемо).

Closes #1956.

Батч аудита (эпик #1953). **Регрессия от моего же #1944.** code-reviewer ✅ APPROVE, прод-verified. ## Проблема `_SUPPLY_BATCH_SQL` (best_layouts.py) джойнил `domrf_kn_flats` по ОДНОЙ глобальной дате `f.snapshot_date = MAX(snapshot_date)` (по всей таблице). Но `domrf_kn_flats` — **per-object time-series** (каждый ЖК скрейпится в свой день). На единственной глобал-max дате почти ни у одного соседнего объекта нет flats → `supply_units_in_radius=0` → фронт `salesPeriodMonths(0,v)=0` → «0 мес», `sold_pct=null` → «—». ## Фикс `flats_latest` CTE — per-object latest snapshot: ```sql flats_latest AS ( SELECT DISTINCT ON (f.obj_id) f.obj_id, f.snapshot_date FROM domrf_kn_flats f JOIN nearby n ON n.obj_id=f.obj_id ORDER BY f.obj_id, f.snapshot_date DESC, f.id DESC ) ... JOIN domrf_kn_flats f ON f.obj_id=fl.obj_id AND f.snapshot_date=fl.snapshot_date ``` #1944 objects-first дедуп (`latest_obj` DISTINCT ON + `nearby MATERIALIZED`) сохранён — ×8.5 fan-out не возвращается (flats никогда не джойнятся к объектам напрямую). ## Прод-verify (66:41:0205010:287, r=1км) 9 объектов; глобал-max=2026-06-22. **supply: было 0 → стало 2675.** Дымящийся ствол: только 4 из 9 объектов имеют flats, на датах 05-17/05-05, ни одного на 06-22 → старый JOIN давал 0. Per-bucket supply теперь ненулевой для студия/1/2/3/4+ → срок продажи ненулевой, % продано не «—». ## Качество - ruff ✅; no `:name::type`; **376 passed** (+ guard `test_supply_joins_flats_per_object_latest_snapshot` — упадёт при реверте на глобал-MAX). - Scope: меняется только источник supply-числителя; velocity/sold_pct/is_oversold формулы не тронуты. ⚠️ Coupling #1945: данные flats частично устаревшие (последние per-object снапшоты — май, loader сломан) → supply ниже здорового, но фикс корректен (0→реальные per-object). Loader не трогаю. Follow-up (perf, минор): композитный индекс `(obj_id, snapshot_date)` на domrf_kn_flats (сейчас запрос ограничен `nearby` → приемлемо). Closes #1956.
bot-backend added 1 commit 2026-06-27 18:29:03 +00:00
fix(best-layouts): per-object latest snapshot for supply (#1956)
All checks were successful
CI / changes (pull_request) Successful in 7s
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 1m53s
CI / backend-tests (pull_request) Successful in 13m45s
0acd72a325
_SUPPLY_BATCH_SQL джойнил domrf_kn_flats по ОДНОЙ глобальной дате
(f.snapshot_date = MAX(snapshot_date) по всей таблице). Но domrf_kn_flats —
ПО-ОБЪЕКТНЫЙ time-series: каждый ЖК скрейпится в свой день. На единственной
глобал-max дате присутствует обычно 1 объект → у остальных 0 квартир →
supply_units_in_radius=0 для всех строк 4.2 Планировки → frontend показывал
«Срок продажи 0 мес» и «% продано —». Регрессия от #1944 (objects-first
дедуп snapshot'ов объектов, который сам по себе корректен).

Фикс: flats_latest CTE (DISTINCT ON (obj_id) ... ORDER BY obj_id,
snapshot_date DESC, id DESC) берёт для КАЖДОГО obj_id его собственный
последний снимок и джойнится к nearby. objects-first MATERIALIZED дедуп
(#1944) сохранён → fan-out по снимкам не возвращается. Глобальный
db.scalar(MAX(snapshot_date)) + :latest_snap bind удалены.

Прод (66:41:0205010:287, r=1км, 9 объектов): supply 0 (global-max) → 2675
(per-object, 4 объекта имеют flats на разных датах 2026-05-17/05-05; ни один
не на глобал-max 2026-06-22). Данные flats частично сломаны (#1945, отдельно),
но фикс корректно двигает supply с 0 к реальным per-object числам.

Тесты: новый guard test_supply_joins_flats_per_object_latest_snapshot;
обновлены mock-фабрики (db.scalar больше не вызывается).
bot-backend merged commit 96d5794b5c into main 2026-06-27 18:43:19 +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#2020
No description provided.