Корень «−1.00 везде» (эпик #1953): compute_demand_supply_forecast брал district-wide unit_velocity (847.5/мес, ВСЕ классы/комнаты) как спрос и весь district-сток (~63k доступных) как предложение для КАЖДОЙ ячейки what_to_build → один и тот же ratio во всех ячейках → все deficit_index прижаты к −1.0. Плюс objective_lots — append-per-snapshot (~2.9× инфляция строк), что симметрично раздувало обе базы → даже сегментация без дедупа осталась бы вырожденной. Фикс (blast radius — ТОЛЬКО forecast/deficit calc; platform-wide dedup = #1964): - market_metrics.compute_market_metrics: +obj_class/+room_bucket (+cache key). _STOCK_SQL и _SALES_WINDOW_SQL дедуплят до ПОСЛЕДНЕГО снапшота на физлот (DISTINCT ON project_name,corpus_name,section,floor,lot_number ORDER BY … snapshot_date DESC,id DESC), затем агрегируют. Class-фильтр (LOWER=LOWER, class lowercase) + room-bucket (Source-B room_area-вокабуляр, зеркало sales_series.room_area_bucket_of → what_to_build фильтрует без перевода). ROLLUP/GROUPING сохранён; confidence считается на дедуплицированных counts. - demand_supply_forecast: base_pace и open-сток теперь ПОСЕГМЕНТНЫЕ (market_metrics(obj_class,room_bucket)). При заданном сегменте L2/L3 (hidden/future) ИСКЛЮЧЕНЫ из баланса — они класс/формат-агностичны, иначе двоились бы по всем ячейкам. +_market_room_bucket VOCAB-мост (валидирующий pass-through Source-B меток; неизвестное → None = без фильтра, не тихий 0-rows). - what_to_build/_DEFAULT_CLASSES и recommendation Economy-маппинг: «эконом»→ «стандарт» (в objective_lots эконома НЕТ, стандарт=483k → раньше ячейка матчила 0 строк и молча выпадала). - report_assembler honesty-guard: если ВСЯ сетка прижата к ±1.0 (degenerate-fallback) — не эмитим «строить»/«избегать», показываем «недостаточно гранулярных данных для посегментного вывода». - data/sql/173_objective_lots_physflat_idx.sql: partial index под DISTINCT ON (Index Only Scan + Unique, без Sort на 1.75M строк; idempotent, BEGIN/COMMIT). Prod-verify (parcel 66:41:0205010:287, Железнодорожный, h=24): ячейки ДИФФЕРЕНЦИРУЮТ (12 measured, 7 distinct) вместо all −1.0; MOI комфорт/студия 38.5 vs стандарт/студия 244.3 (точное совпадение с ожидаемым). Тесты: регрессия «ячейки различаются (не all −1.0)» + vocab-translation + honesty-guard + посегментное предложение. ruff clean; no :name::type.
38 lines
2.3 KiB
PL/PgSQL
38 lines
2.3 KiB
PL/PgSQL
-- Контекст: индекс дефицита #1959 (эпик #1953) — «−1.00 везде».
|
||
--
|
||
-- Прогнозный движок дефицита (compute_demand_supply_forecast) дедуплицирует
|
||
-- objective_lots до ПОСЛЕДНЕГО снапшота на каждый ФИЗИЧЕСКИЙ лот
|
||
-- (objective_lots — append-per-snapshot, ~2.9× инфляция строк; без дедупа спрос И
|
||
-- предложение оба раздуты симметрично → каждая ячейка what_to_build даёт один и тот
|
||
-- же ratio → все deficit_index = −1.0). Дедуп делается через
|
||
-- SELECT DISTINCT ON (project_name, corpus_name, section, floor, lot_number) …
|
||
-- ORDER BY project_name, corpus_name, section, floor, lot_number,
|
||
-- snapshot_date DESC, id DESC
|
||
-- по квартирам (premise_kind='квартира'). Этот partial index точно совпадает с
|
||
-- ключом DISTINCT ON и его направлениями сортировки → планировщик берёт Index-Only
|
||
-- Scan + Unique вместо сортировки 1.75M строк (EXPLAIN на prod: Sort исчезает,
|
||
-- cost ≈ 122k вместо полной сортировки).
|
||
--
|
||
-- DESC на snapshot_date/id — чтобы «последний снапшот первым» читался прямо из
|
||
-- индекса (DISTINCT ON берёт первую строку каждой группы). premise_kind в WHERE —
|
||
-- partial index только по квартирам (единственный сегмент дефицит-расчёта), меньше
|
||
-- и плотнее, чем полный.
|
||
--
|
||
-- Порядок применения: миграция ПЕРВОЙ (deploy first), затем backend-код, который
|
||
-- использует DISTINCT ON. Idempotent (CREATE INDEX IF NOT EXISTS) — безопасно при
|
||
-- ре-apply. Self-wrapped BEGIN/COMMIT.
|
||
BEGIN;
|
||
|
||
CREATE INDEX IF NOT EXISTS objective_lots_physflat_latest_idx
|
||
ON objective_lots (
|
||
project_name,
|
||
corpus_name,
|
||
section,
|
||
floor,
|
||
lot_number,
|
||
snapshot_date DESC,
|
||
id DESC
|
||
)
|
||
WHERE premise_kind = 'квартира';
|
||
|
||
COMMIT;
|