gendesign/data/sql/175_v_objective_lots_latest.sql
Light1YT e67cb721bf fix(objective): physflat-dedup current-state consumers + honest n_sold window (#1964)
objective_lots — current-state UPSERT (UNIQUE objective_lot_id, 5 snapshot_date),
но Объектив присваивает ОДНОМУ физлоту (project,corpus,section,floor,lot_number)
несколько lot_id за пере-листинги → таблица раздута ~2.91× (прод: 1.75M квартир-
строк vs 603k физлотов). История — в отдельной objective_lots_history (не трогаем).

STEP 1: миграция 175 — VIEW v_objective_lots_latest (DISTINCT ON physflat-ключ,
последний снапшот). ol.* стабилен (51==51 колонок). DRY-RUN на проде: 603 049
квартир vs 1 753 283 raw.

STEP 2: репойнт current-state консьюмеров objective_lots → v_objective_lots_latest:
- supply_layers._L1_OPEN_SQL (L1 открытое предложение → дефицит-форсайт; прод:
  Юго-Западный комфорт 58 606 → 12 620)
- competitors._SOLD_COUNT_SQL (+ комментарий: COUNT(DISTINCT lot_id) СОХРАНЁН для
  fan-out-защиты маппинга, не COUNT(*) — view гарантирует physflat-дедуп, DISTINCT
  гарантирует mapping-fan-out-safety)
- parcels.py obj_pricing CTE (карточка конкурента units_sold/available)
- special_indices._ARTIFICIAL_DEMAND_SQL
- parcels.py district price block + geo-radius median (sample_size/n)
- concepts._OBJECTIVE_MEDIAN_SQL (гейт n≥10)
- landing KPI3 (% квартир с ценой)
- admin_scrape coverage: `lots` оставлен сырым (ETL-fidelity vs SQLite), добавлен
  `lots_physflat`
#1959 inline-дедуп в market_metrics НЕ рефакторим — добавлен комментарий об общем
physflat-ключе с view.

STEP 3: report_assembler._deal_count теперь = unit_velocity × window_months
(оконные продажи), НЕ кумулятивный n_sold. confidence_engine помечает фактор
«за 6 мес» и гейтит порогами окна (high≥50) — кумулятив (прод EKB ~380 921) делал
гейт бессмысленным и подпись лживой; оконное (~24 876 за 6 мес) честно.

Тесты: +guard'ы (L1/sold-count читают view, deal_count оконный). 963 passed.
2026-06-28 04:27:43 +05:00

53 lines
3.6 KiB
PL/PgSQL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- Контекст: #1964 (эпик #1953) — physflat-дедуп current-state потребителей
-- objective_lots через общий VIEW.
--
-- objective_lots — current-state UPSERT (UNIQUE objective_lot_id, ON CONFLICT DO
-- UPDATE), НЕ snapshot-append: всего 5 distinct snapshot_date. Инфляция строк
-- (~2.91×: прод 2026-06-28 — 1 753 283 сырых квартир-строки vs 603 049 физлотов)
-- возникает потому, что Объектив за время пере-листингов присваивает ОДНОМУ
-- физическому лоту (project_name, corpus_name, section, floor, lot_number)
-- НЕСКОЛЬКО objective_lot_id. История лотов живёт в ОТДЕЛЬНОЙ objective_lots_history
-- (её НЕ трогаем — это корректный time-series).
--
-- View отдаёт ТОТ ЖЕ набор колонок, что и objective_lots (ol.* — прод-проверка:
-- 51 base cols == 51 view cols), по ОДНОЙ строке на физлот — последний снапшот
-- (snapshot_date DESC, tie-break id DESC). objective_lot_id в результате уникален
-- (603 049 строк = 603 049 distinct objective_lot_id, 0 NULL) → потребители,
-- которым нужна fan-out-защита по маппингу (competitors._SOLD_COUNT_SQL), могут
-- использовать COUNT(DISTINCT objective_lot_id) поверх view.
--
-- Ключ/направления DISTINCT ON совпадают с partial-индексом
-- objective_lots_physflat_latest_idx (миграция 173, WHERE premise_kind='квартира')
-- → для квартир-scoped потребителей планировщик берёт Index Scan + Unique без
-- полной сортировки. Все current-state потребители #1964 — premise_kind='квартира'.
-- Новый индекс НЕ добавляем (173 покрывает).
--
-- #1959 уже устранил инфляцию ВНУТРИ compute_market_metrics (inline DISTINCT ON в
-- _STOCK_SQL/_SALES_WINDOW_SQL) и его 9 forecast/scoring-вызовов — НЕ дублируем.
-- Этот view — для ОСТАЛЬНЫХ current-state консьюмеров (supply_layers L1, конкурент-
-- карточки, district-цены, sample_size-гейты).
--
-- НЕ трогаем (time-series, корректны): sales_series._SOURCE_B_SQL,
-- objective_lots_history, mv_sales_tracker_*.
--
-- Порядок: миграция ПЕРВОЙ (deploy first), затем backend-код, читающий view.
-- Idempotent: CREATE OR REPLACE VIEW. Self-wrapped BEGIN/COMMIT.
-- Прод DRY-RUN (BEGIN; CREATE OR REPLACE VIEW; SELECT count; ROLLBACK):
-- view kvartira = 603 049 (vs 1 753 283 raw), колонки 51==51 ✓.
BEGIN;
CREATE OR REPLACE VIEW v_objective_lots_latest AS
SELECT DISTINCT ON (project_name, corpus_name, section, floor, lot_number) ol.*
FROM objective_lots ol
ORDER BY project_name, corpus_name, section, floor, lot_number,
snapshot_date DESC, id DESC;
COMMENT ON VIEW v_objective_lots_latest IS
'#1964: physflat-deduped current state of objective_lots — one row per '
'(project_name, corpus_name, section, floor, lot_number) at the latest '
'snapshot (snapshot_date DESC, id DESC). objective_lots stores ~2.91x '
'inflated rows (multiple objective_lot_id per physical flat across '
're-listings); this view collapses to ~603k physical flats. History lives '
'in objective_lots_history (do not use this view for time-series).';
COMMIT;