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.
53 lines
3.6 KiB
PL/PgSQL
53 lines
3.6 KiB
PL/PgSQL
-- Контекст: #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;
|