docs(forecast): исправить устаревшие комментарии #1959 (deep-review follow-up)
Doc-only, без изменений логики (deep-code-reviewer APPROVE): - market_metrics _STOCK_SQL/_SALES_WINDOW_SQL + docstring: убрать ложную отсылку «зеркало … из 100_*.sql» (индекс создаётся ТОЛЬКО в миграции 173). - _SALES_WINDOW_SQL комментарий + compute_market_metrics docstring: room_bucket это Source-B room_area-вокабуляр («Студии 15-30»/«2-к 45-60»/«80+ м²», зеркало room_area_bucket_of), НЕ Source-A «студия/1/2/3» — приводим к фактическому CASE. - market_metrics: добавить комментарий, что DISTINCT-ON дедуп БЕЗУСЛОВНЫЙ (все вызовы compute_market_metrics, не только форсайт-путь); deep-review подтвердил безопасность для ratio/saturated-velocity консьюмеров; дедуп сырой таблицы objective_lots для platform-wide остаётся #1964. - recommendation.map_class docstring: Economy → «стандарт» (код уже маппит так).
This commit is contained in:
parent
41804ed70e
commit
3cafe22c15
2 changed files with 34 additions and 18 deletions
|
|
@ -146,8 +146,9 @@ def map_class(target_class: str | None) -> str | None:
|
|||
"""Live target_class → forecast obj_class. PURE. None/неизвестное → None.
|
||||
|
||||
Comfort/Comfort+ сворачиваются в «комфорт», Business/Elite — в «бизнес»,
|
||||
Economy — в «эконом» (3 массовых класса §9.7). None → None = «без фильтра
|
||||
класса», вызывающий отдаёт движку дефолтную сетку классов.
|
||||
Economy — в «стандарт» (#1959: нижний массовый класс objective_lots —
|
||||
«стандарт», эконома в данных нет). 3 массовых класса §9.7. None → None =
|
||||
«без фильтра класса», вызывающий отдаёт движку дефолтную сетку классов.
|
||||
"""
|
||||
if target_class is None:
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -278,11 +278,20 @@ def _room_bucket(rooms_int: int | None) -> str:
|
|||
# objective_lots — append-per-snapshot (~2.9× инфляция строк), без дедупа сток
|
||||
# раздут и симметрично раздувает спрос И предложение → деградирует посегментный
|
||||
# дефицит. Ключ физлота = (project_name, corpus_name, section, floor, lot_number);
|
||||
# берём строку с MAX(snapshot_date), tie-break MAX(id) (зеркало
|
||||
# objective_lots_physflat_latest_idx из 100_*.sql). project_name NOT NULL;
|
||||
# corpus/section/floor/lot_number могут быть NULL (~2.5% лотов) — для таких лотов
|
||||
# DISTINCT ON схлопывает NULL=NULL в одну группу (неидентифицируемые физлоты, в
|
||||
# дефицит-расчёте безопасно), для остального — точный per-flat дедуп.
|
||||
# берём строку с MAX(snapshot_date), tie-break MAX(id). Поддерживается индексом
|
||||
# objective_lots_physflat_latest_idx (создаётся в миграции 173) — он совпадает с
|
||||
# ключом и направлениями этого DISTINCT ON → Index Only Scan + Unique без Sort.
|
||||
# project_name NOT NULL; corpus/section/floor/lot_number могут быть NULL (~2.5%
|
||||
# лотов) — для таких лотов DISTINCT ON схлопывает NULL=NULL в одну группу
|
||||
# (неидентифицируемые физлоты, в дефицит-расчёте безопасно), для остального —
|
||||
# точный per-flat дедуп.
|
||||
#
|
||||
# ⚠️ ДЕДУП БЕЗУСЛОВНЫЙ — применяется ко ВСЕМ вызовам compute_market_metrics, не
|
||||
# только к форсайт-пути. Deep-review (#1959) подтвердил: безопасно для остальных
|
||||
# потребителей (absorption/MoS/sell_through — отношения; velocity — saturated),
|
||||
# т.к. inflation симметрична в числителе/знаменателе и съедается шкалой. Дедуп
|
||||
# здесь касается ТОЛЬКО выборки внутри метрик — дедуп самой сырой таблицы
|
||||
# objective_lots (для всех platform-консьюмеров) остаётся отдельной задачей #1964.
|
||||
#
|
||||
# Сегмент-фильтры (#1959): obj_class (class в objective_lots — lowercase, матчим
|
||||
# LOWER=LOWER) + room_bucket. ROOM-bucket — Source-B room_area-вокабуляр
|
||||
|
|
@ -369,11 +378,15 @@ _STOCK_SQL = text(
|
|||
# → velocity/absorption раздуты ~2.9× (раздувают спрос симметрично раздутому
|
||||
# предложению → дефицит вырождается в −1.0). Дедупим в CTE `latest`, окно по
|
||||
# contract_date и сегмент-фильтры применяем уже к дедуплицированному набору.
|
||||
# Ключ/направления дедупа — зеркало _STOCK_SQL / objective_lots_physflat_latest_idx.
|
||||
# contract_date / rooms_int / area_pd берём из последнего снапшота физлота.
|
||||
# Ключ/направления дедупа — зеркало _STOCK_SQL (оба поддержаны индексом
|
||||
# objective_lots_physflat_latest_idx из миграции 173). contract_date / rooms_int /
|
||||
# area_pd берём из последнего снапшота физлота.
|
||||
# Сегмент-фильтры (#1959): obj_class (lowercase, LOWER=LOWER) + room_bucket
|
||||
# (Source-A вокабуляр студия/1/2/3/4/5+) — применяются ПОСЛЕ дедупа (объективны
|
||||
# по физлоту). ROLLUP остаётся: при room_bucket-фильтре отдаёт {bucket, total}.
|
||||
# (Source-B room_area-вокабуляр "Студии 15-30"/"1-к 30-45"/"2-к 45-60"/"3-к 60-80"/
|
||||
# "80+ м²" — ТОЧНОЕ зеркало sales_series.room_area_bucket_of, как в _STOCK_SQL) —
|
||||
# применяются ПОСЛЕ дедупа (объективны по физлоту: класс/комнатность/площадь не
|
||||
# меняются между снапшотами). ROLLUP остаётся: при room_bucket-фильтре отдаёт
|
||||
# {bucket, total}.
|
||||
_SALES_WINDOW_SQL = text(
|
||||
"""
|
||||
WITH latest AS (
|
||||
|
|
@ -452,13 +465,15 @@ def compute_market_metrics(
|
|||
(имеет смысл для ЕКБ-wide baseline).
|
||||
|
||||
#1959 (посегментный дефицит): `obj_class` (lowercase в objective_lots,
|
||||
матчим регистронезависимо) и `room_bucket` (Source-A вокабуляр —
|
||||
"студия"/"1"/"2"/"3"/"4"/"5+", зеркало `_room_bucket()`) сужают выборку до
|
||||
одного сегмента. Так base_pace (unit_velocity) и доступный сток становятся
|
||||
ПОСЕГМЕНТНЫМИ, а не district-wide (иначе все ячейки what_to_build делят один
|
||||
темп → дефицит вырождается в −1.0). None по обеим осям → прежнее district-wide
|
||||
поведение. ВАЖНО: SQL дедуплицирует objective_lots до последнего снапшота на
|
||||
физлот → counts (а значит и confidence) считаются на дедуплицированном наборе.
|
||||
матчим регистронезависимо) и `room_bucket` (Source-B room_area-вокабуляр —
|
||||
"Студии 15-30"/"1-к 30-45"/"2-к 45-60"/"3-к 60-80"/"80+ м²", зеркало
|
||||
`sales_series.room_area_bucket_of` → what_to_build фильтрует БЕЗ перевода)
|
||||
сужают выборку до одного сегмента. Так base_pace (unit_velocity) и доступный
|
||||
сток становятся ПОСЕГМЕНТНЫМИ, а не district-wide (иначе все ячейки
|
||||
what_to_build делят один темп → дефицит вырождается в −1.0). None по обеим осям
|
||||
→ прежнее district-wide поведение. ВАЖНО: SQL дедуплицирует objective_lots до
|
||||
последнего снапшота на физлот → counts (а значит и confidence) считаются на
|
||||
дедуплицированном наборе.
|
||||
|
||||
Возвращает MarketMetrics ВСЕГДА (даже на пустых данных): тогда метрики =
|
||||
None, confidence='low'. Никогда не бросает на отсутствии данных.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue