fix(supply_layers): L3 future-supply берёт ПОСЛЕДНИЙ снапшот → потом фильтр (#1212) #1256

Merged
bot-backend merged 1 commit from fix/supply-layers-l3-volatile-filter-order into main 2026-06-13 05:53:01 +00:00
Collaborator

Summary

Closes #1212 (P2 correctness). _L3_FUTURE_SQL применял volatile-фильтры ДО DISTINCT ON → брал «последний снапшот, прошедший фильтр», не последний.

Bug

domrf_kn_objects версионируется (UNIQUE(obj_id, snapshot_date), weekly scraper). CTE:

SELECT DISTINCT ON (o.obj_id) ...
  FROM domrf_kn_objects o
 WHERE o.region_cd = :region_cd
   AND o.ready_dt > CURRENT_DATE + ...     -- ❌ volatile, ДО DISTINCT ON
   AND COALESCE(o.free_flats, 0) <= :max  -- ❌ volatile
 ORDER BY o.obj_id, o.snapshot_date DESC NULLS LAST

WHERE применяется до 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 и форсайте. Docstring обещает «свежий снапшот, затем фильтр» — нарушено.

Fix

Разделил фильтры:

  • В CTE остаются только стабильные: region_cd, district_name.
  • Volatile (ready_dt, free_flats) перенесены во внешний WHERE после DISTINCT ON.

Зеркало паттерна L2-CTE в этом же модуле (там volatile уже снаружи). Семантика теперь матчит docstring.

Test plan

  • pytest -k supply_layers62 passed, 1 skipped, 0 failed.
  • ruff check → clean.
  • psycopg v3 SQL pattern сохранён (CAST(:x AS type)).
  • Post-deploy: smoke на районе с известным «открытым после паузы» проектом → должен исчезнуть из L3 future.

Closes #1212

## Summary Closes #1212 (P2 correctness). `_L3_FUTURE_SQL` применял volatile-фильтры **ДО** `DISTINCT ON` → брал «последний снапшот, прошедший фильтр», не последний. ## Bug `domrf_kn_objects` версионируется (`UNIQUE(obj_id, snapshot_date)`, weekly scraper). CTE: ```sql SELECT DISTINCT ON (o.obj_id) ... FROM domrf_kn_objects o WHERE o.region_cd = :region_cd AND o.ready_dt > CURRENT_DATE + ... -- ❌ volatile, ДО DISTINCT ON AND COALESCE(o.free_flats, 0) <= :max -- ❌ volatile ORDER BY o.obj_id, o.snapshot_date DESC NULLS LAST ``` WHERE применяется до 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` и форсайте. Docstring обещает «свежий снапшот, затем фильтр» — нарушено. ## Fix Разделил фильтры: - В CTE остаются только **стабильные**: `region_cd`, `district_name`. - Volatile (`ready_dt`, `free_flats`) перенесены во **внешний WHERE** после DISTINCT ON. Зеркало паттерна L2-CTE в этом же модуле (там volatile уже снаружи). Семантика теперь матчит docstring. ## Test plan - [x] `pytest -k supply_layers` → **62 passed, 1 skipped, 0 failed**. - [x] `ruff check` → clean. - [x] psycopg v3 SQL pattern сохранён (`CAST(:x AS type)`). - [ ] Post-deploy: smoke на районе с известным «открытым после паузы» проектом → должен исчезнуть из L3 future. Closes #1212
bot-backend added 1 commit 2026-06-13 05:38:55 +00:00
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
39094ba644
_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
bot-backend merged commit d587b9e199 into main 2026-06-13 05:53:01 +00:00
bot-backend deleted branch fix/supply-layers-l3-volatile-filter-order 2026-06-13 05:53:02 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#1256
No description provided.