diff --git a/backend/app/services/site_finder/supply_layers.py b/backend/app/services/site_finder/supply_layers.py index ab54dee5..f045dd58 100644 --- a/backend/app/services/site_finder/supply_layers.py +++ b/backend/app/services/site_finder/supply_layers.py @@ -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 """ )