feat(db): mv_quarter_price_index — quarter price-index из ДКП (Refs #760) #792
No reviewers
Labels
No labels
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
Fable 5 ревью
feedback/max
generative
GG-форсайт
needs-discussion
needs-human
observability
pause-bots
performance
priority/p0
priority/p1
priority/p2
priority/p3
scope/backend
scope/db
scope/devops
scope/frontend
scope/qa
scrapers
security
site-finder
stage/1
stage/2
status/blocked
status/done
status/needs-analysis
status/needs-fix
status/qa
status/ready
status/review
status/wip
tech-debt
tradein
ux
week ревью 1
wontfix
вторичка
ИРД
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#792
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/760-quarter-price-index"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Что (P0, #647-1 foundation)
Materialized view
mv_quarter_price_indexв main-БД:price_index = median_ppm2(quarter) / median_ppm2(city)по cad-кварталам66:%(Свердловская обл), нормировка к ЕКБ city median. Инфра-шаг — клиентскую цену НЕ меняет (estimator-интеграция = #647-3, за бэктест-гейтом #763).Методология
percentile_cont(0.5)по66:41:%кварталам сdeals_count>=5(equal-weight median-of-medians, в SQL, не хардкод).deals>=5→ quarter median / city; sparse<5→ district66:NNmedian / city; нет данных →1.0. Колонкаbasisмаркирует источник.quarter_cad_number(REFRESH CONCURRENTLY + O(1) lookup для estimator).mv_quarter_price_per_m2(95_mv_quarter_price.sql).Deploy-ordering (решено)
deploy.ymlприменяетls data/sql/*.sql | sort(лексикографически).99a_сортируется после99_nspd_entities_denorm.sql(_<a);100_+ушли бы перед99_(1<9) — поэтому99a_корректно применяется последним. Идемпотент (DROP MV IF EXISTS ... CASCADE), BEGIN/COMMIT,sql.md-конвенции.Валидация (live DB, 2026-05-30)
Spot-check: центр ЕКБ
66:41:0401018→3.98 (441k ₽/m²), периферия66:17:0806002→0.28 (31k), median-quarter66:41:0313118→1.00. Распределение правдоподобно (центр>1, окраина<1). ✅ sanity DoD.Зависимости
Блокирует #647-2 (#762 schema+cron-refresh) и #647-3 (estimator integration, за гейтом #763). Vault-док обновлю после merge.
Refs #760
#647-1 foundation (P0, инфра — клиентскую цену НЕ меняет). MV mv_quarter_price_index в main-БД: price_index = median_ppm2(quarter) / median_ppm2(city) по cad-кварталам 66:% (Свердловская обл), нормировка к ЕКБ city median (66:41, deals>=5). Методология: deals>=5 → quarter median; sparse (<5) → district(66:NN) median; нет данных → 1.0. basis-колонка маркирует источник. UNIQUE index на quarter_cad_number (REFRESH CONCURRENTLY + O(1) lookup estimator #647-3). Источник — существующая mv_quarter_price_per_m2 (95_mv_quarter_price.sql). Deploy-ordering: deploy.yml применяет 'ls data/sql/*.sql | sort' (лексикограф); 99a_ сортируется ПОСЛЕ 99_ ('_'<'a'), 100_+ ушли бы перед 99_ → 99a_ корректно применяется последним. Идемпотент (DROP IF EXISTS CASCADE), BEGIN/COMMIT. Валидация (live, 2026-05-30): city_median≈110 883; 1972 квартала (quarter 1708 / district 262 / city_fallback 2); 0 NULL; index 0.277–3.98; spot-check центр ЕКБ 66:41:0401018→3.98, периферия 66:17:0806002→0.28, median-quarter→1.00. Зависимости: блокирует #647-2 (#762 schema+refresh) и #647-3 (integration, за бэктест-гейтом #763).✅ APPROVE
MV
mv_quarter_price_index(#760, #647-1 foundation). EXPLAIN на live-DB — план разумный.Plan — seq scan по ~2.1k-строчному source MV (cost ~1.6k, тривиально), hash join для district-fallback, single-row city-median aggregate. Нет perf-проблем для периодического refresh; source MV существует, план чистый.
Correctness
99a_лексикографически сортируется последним (99_<99a_через_<a;100_–122_перед99_через1<9) — зависимость95_(source MV) применяется раньше. Reasoned верно; отражает pre-existing долг нумерации 100+<99 (advisory, не вина PR).DROP MV IF EXISTS CASCADE+_schema_migrations-guard), BEGIN/COMMIT,WITH NO DATA+ non-concurrent initial REFRESH (верно — пустой MV нельзя REFRESH CONCURRENTLY), UNIQUE index включает будущий CONCURRENTLY + O(1) lookup estimator'а.substring(... FOR 5)= верный district-prefix. Явные type-cast.Scope/security — чистый infra MV, нет estimator-интеграции (#647-3 за backtest-гейтом #763) → нулевой риск клиентской цены. Только новый файл, нет secrets, нет self-extending триггера.
Advisory —
99a_suffix-хак = pre-existing NN_-долг (100+<99 лексикографически); стоит vault-нота / будущая перенумерация.Verdict: ✅ APPROVE (P0 #760). MV-populated + sanity → qa.