fix(supply_layers): L3 future-supply берёт ПОСЛЕДНИЙ снапшот → потом фильтр (#1212)
All checks were successful
CI / changes (push) Successful in 7s
CI / changes (pull_request) Successful in 5s
CI / frontend-tests (push) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / backend-tests (push) Successful in 6m30s
CI / backend-tests (pull_request) Successful in 6m24s
All checks were successful
CI / changes (push) Successful in 7s
CI / changes (pull_request) Successful in 5s
CI / frontend-tests (push) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / backend-tests (push) Successful in 6m30s
CI / backend-tests (pull_request) Successful in 6m24s
_L3_FUTURE_SQL применял volatile-фильтры (ready_dt > horizon, free_flats <= threshold) в WHERE ВНУТРИ CTE с DISTINCT ON (obj_id) ORDER BY snapshot_date DESC. Это значит фильтр применялся ДО DISTINCT ON → бралась «последний снапшот, ПРОШЕДШИЙ фильтр», а не последний в принципе. Эффект на проде: объект, когда-то бывший «объявлен, не продаётся» (free_flats=0/NULL), остаётся в L3 future-supply даже после открытия продаж — свежий снапшот с free_flats=180 отфильтрован, взят старый с free_flats=0. Двойной счёт с L1/L2 (open + future одного объекта), stale flat_count/ready_dt в supply_layers и форсайте. Patch: разделил фильтры на стабильные (в CTE: region_cd, district_name) и volatile (во внешнем WHERE после DISTINCT ON: ready_dt, free_flats). Зеркало паттерна L2-CTE (там volatile уже снаружи). Семантика теперь матчит docstring: «свежий снапшот, затем фильтр». 62/62 supply_layers тестов зелёные. ruff clean. SQL psycopg v3 (CAST(:x AS interval)) уже корректен. Closes #1212
This commit is contained in:
parent
b00e338667
commit
39094ba644
1 changed files with 12 additions and 3 deletions
|
|
@ -252,6 +252,15 @@ _L2_HIDDEN_SQL = text(
|
|||
_L3_FUTURE_SQL = text(
|
||||
"""
|
||||
WITH latest AS (
|
||||
-- #1212: сначала ВСЕГДА берём последний снапшот на obj_id
|
||||
-- (DISTINCT ON + ORDER BY snapshot_date DESC). Внутри CTE — только
|
||||
-- стабильные фильтры (region_cd / админ-район). Volatile-фильтры
|
||||
-- (ready_dt, free_flats) применяются ПОСЛЕ DISTINCT ON во внешнем
|
||||
-- WHERE. Иначе WHERE применяется ДО DISTINCT ON и берётся «последний
|
||||
-- снапшот, прошедший фильтр»: проект, когда-то бывший «объявлен,
|
||||
-- не продаётся», остаётся в L3 и после открытия продаж (свежий
|
||||
-- снапшот с большим free_flats отфильтрован, взят старый) →
|
||||
-- двойной счёт с L1/L2, stale flat_count/ready_dt.
|
||||
SELECT DISTINCT ON (o.obj_id)
|
||||
o.obj_id,
|
||||
o.district_name,
|
||||
|
|
@ -264,9 +273,6 @@ _L3_FUTURE_SQL = text(
|
|||
FROM domrf_kn_objects o
|
||||
WHERE o.region_cd = :region_cd
|
||||
AND o.district_name IS NOT NULL
|
||||
AND o.ready_dt IS NOT NULL
|
||||
AND o.ready_dt > CURRENT_DATE + CAST(:horizon AS interval)
|
||||
AND COALESCE(o.free_flats, 0) <= :max_free_flats
|
||||
AND (
|
||||
CAST(:district AS text) IS NULL
|
||||
OR o.district_name = CAST(:district AS text)
|
||||
|
|
@ -281,6 +287,9 @@ _L3_FUTURE_SQL = text(
|
|||
COALESCE(SUM(square_living), 0) AS area_future,
|
||||
MIN(ready_dt) AS expected_online_date
|
||||
FROM latest
|
||||
WHERE ready_dt IS NOT NULL
|
||||
AND ready_dt > CURRENT_DATE + CAST(:horizon AS interval)
|
||||
AND COALESCE(free_flats, 0) <= :max_free_flats
|
||||
GROUP BY district_name, dev_group_name
|
||||
"""
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue