fix(analytics): _district_market_saturation latest-snapshot filter (#1221)

domrf_kn_objects/sales_agg хранят ~3 snapshot'а на obj_id (weekly UPSERT,
retention=none). JOIN без latest-фильтра считал каждый ЖК N раз:
sold_median тянулся старыми perc → sat_factor занижен, гейт n>=5
проходил уже при 2 ЖК (n=6), sat_n завышен ~×N в scope. Сданные ЖК
просачивались через старые снапшоты со статусом 'Строящиеся'.

Patch: AND a.snapshot_date = (SELECT MAX(snapshot_date) FROM ...) для
обеих таблиц (зеркало #1210/#1212). COUNT(*) → COUNT(DISTINCT a.obj_id)
defensive. 29 recommend_mix + 8 saturation тестов зелёные.

Closes #1221
This commit is contained in:
Light1YT 2026-06-13 13:09:22 +05:00
parent d694e735cd
commit 780dd5640c

View file

@ -1414,12 +1414,21 @@ def _district_market_saturation(db: Session, *, district_name: str) -> tuple[flo
Возвращает (median_pct, n_objects). None если <5 ЖК с perc.
"""
# #1221: domrf_kn_objects/domrf_kn_sales_agg хранят ~3 snapshot'а на obj_id
# (UNIQUE(obj_id,[type,]snapshot_date), weekly UPSERT, ретенции нет). Без
# latest-фильтра один ЖК входил в медиану N раз → sold_median тянется старыми
# perc (sold% растёт во времени → sat_factor занижен), гейт n>=5 проходил уже
# при 2 ЖК, sat_n завышен ~×N. Фильтруем оба JOIN-партнёра MAX(snapshot_date)
# (зеркало #1210/#1212, ср. строки 1200/1208 и latest_obj-CTE стр. ~2780
# «иначе comparables дублируются»). COUNT(DISTINCT a.obj_id) — defensive:
# после latest-snapshot пары (obj_id, type='apartments') уникальны, но явный
# DISTINCT исключает регрессию, если кто-то ослабит UNIQUE-constraint.
row = (
db.execute(
text(
"""
SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY a.perc) AS sold_median,
COUNT(*) AS n
COUNT(DISTINCT a.obj_id) AS n
FROM domrf_kn_sales_agg a
JOIN domrf_kn_objects o
ON o.obj_id = a.obj_id
@ -1428,6 +1437,12 @@ def _district_market_saturation(db: Session, *, district_name: str) -> tuple[flo
AND a.perc IS NOT NULL
AND o.district_name = :dn
AND o.site_status = 'Строящиеся'
AND a.snapshot_date = (
SELECT MAX(snapshot_date) FROM domrf_kn_sales_agg
)
AND o.snapshot_date = (
SELECT MAX(snapshot_date) FROM domrf_kn_objects
)
"""
),
{"dn": district_name},