fix(forecasting): resolve admin district to micros в _query_artificial_demand (#1205) #1266

Merged
bot-backend merged 1 commit from fix/artificial-demand-admin-to-micros into main 2026-06-13 07:34:55 +00:00
Collaborator

Summary

Closes #1205 (P2 silent correctness, prod). Тот же класс бага что #1211 в _price_sensitivity: фильтр сырым admin-именем по микро-вокабуляру → 0 строк → §25.5 'unavailable' с ложной причиной.

Bug

objective_lots.district хранит микро-вокабуляр ('Уралмаш', 'ЖБИ', ...). _query_artificial_demand:821:

AND (CAST(:district AS text) IS NULL OR ol.district = CAST(:district AS text))

§22-путь forecast.py:123 → orchestrator → compute_special_indices:1656 передаёт admin 'Кировский'. ol.district='Кировский' = 0 строкn_sold=0 → §25.5 Artificial Demand 'unavailable' с ложной причиной «нет проданных лотов» в каждом district-scoped отчёте.

Все остальные потребители objective_lots (sales_series, market_metrics, supply_layers, future_supply) разворачивают через resolve_objective_districts — этот единственный SQL нет.

Fix

Импорт resolve_objective_districts + замена сырого фильтра на зеркальный паттерн sales_series._SOURCE_B_SQL / market_metrics._SALES_WINDOW_SQL:

(CAST(:has_district AS boolean) IS FALSE
 OR ol.district = ANY(CAST(:districts AS text[])))

Сигнатура _query_artificial_demand / _build_artificial_demand НЕ меняется — caller остаётся admin-aware.

Test plan

  • +5 новых тестов TestArtificialDemandDistrictResolution: резолвер вызван, микро попадают в :districts, n_sold>0 после фикса, None→EKB-wide.
  • 6 обновлённых SQL-тестов (новые binds, regression guard на удаление :district).
  • pytest -k "artificial_demand or ArtificialDemand"21 passed.
  • pytest tests/services/forecasting/1030 passed.
  • ruff check + psycopg v3 grep → clean.

Closes #1205

## Summary Closes #1205 (P2 silent correctness, prod). Тот же класс бага что **#1211** в `_price_sensitivity`: фильтр сырым admin-именем по микро-вокабуляру → 0 строк → §25.5 'unavailable' с ложной причиной. ## Bug `objective_lots.district` хранит **микро-вокабуляр** ('Уралмаш', 'ЖБИ', ...). `_query_artificial_demand:821`: ```sql AND (CAST(:district AS text) IS NULL OR ol.district = CAST(:district AS text)) ``` §22-путь `forecast.py:123 → orchestrator → compute_special_indices:1656` передаёт **admin** 'Кировский'. `ol.district='Кировский'` = **0 строк** → `n_sold=0` → §25.5 Artificial Demand `'unavailable'` с ложной причиной «нет проданных лотов» в каждом district-scoped отчёте. Все остальные потребители `objective_lots` (sales_series, market_metrics, supply_layers, future_supply) **разворачивают через `resolve_objective_districts`** — этот единственный SQL нет. ## Fix Импорт `resolve_objective_districts` + замена сырого фильтра на зеркальный паттерн `sales_series._SOURCE_B_SQL` / `market_metrics._SALES_WINDOW_SQL`: ```sql (CAST(:has_district AS boolean) IS FALSE OR ol.district = ANY(CAST(:districts AS text[]))) ``` Сигнатура `_query_artificial_demand` / `_build_artificial_demand` НЕ меняется — caller остаётся admin-aware. ## Test plan - [x] **+5 новых тестов** `TestArtificialDemandDistrictResolution`: резолвер вызван, микро попадают в `:districts`, n_sold>0 после фикса, None→EKB-wide. - [x] **6 обновлённых SQL-тестов** (новые binds, regression guard на удаление `:district`). - [x] `pytest -k "artificial_demand or ArtificialDemand"` → **21 passed**. - [x] `pytest tests/services/forecasting/` → **1030 passed**. - [x] `ruff check` + psycopg v3 grep → clean. Closes #1205
bot-backend added 1 commit 2026-06-13 06:17:11 +00:00
fix(forecasting): resolve admin district to micros в _query_artificial_demand (#1205)
All checks were successful
CI / changes (push) Successful in 7s
CI / changes (pull_request) Successful in 6s
CI / frontend-tests (push) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / backend-tests (push) Successful in 6m23s
CI / backend-tests (pull_request) Successful in 6m30s
a08cc317c3
objective_lots.district хранит МИКРО-вокабуляр ('Уралмаш', 'ЖБИ', ...).
_query_artificial_demand фильтровал сырым АДМИН-именем ('Кировский' с
forecast.py:123) → ol.district='Кировский' = 0 строк → n_sold=0 → §25.5
Artificial Demand 'unavailable' с ложной причиной «нет проданных лотов»
в каждом district-scoped отчёте. Тот же класс бага, что #1211 в
_price_sensitivity.

Patch: импорт resolve_objective_districts + замена сырого
`ol.district = CAST(:district AS text)` на зеркальный паттерн
sales_series._SOURCE_B_SQL / market_metrics._SALES_WINDOW_SQL:
  (CAST(:has_district AS boolean) IS FALSE
   OR ol.district = ANY(CAST(:districts AS text[])))

Сигнатура _query_artificial_demand / _build_artificial_demand НЕ меняется
— caller остаётся admin-aware на входе.

+5 новых тестов (TestArtificialDemandDistrictResolution: резолвер вызван,
микро в bind, n_sold>0 после фикса), 6 обновлённых SQL-тестов. 21 passed
artificial_demand + 1030 forecasting тестов зелёные. ruff clean.

Closes #1205
bot-backend merged commit c48f45c48f into main 2026-06-13 07:34:55 +00:00
bot-backend deleted branch fix/artificial-demand-admin-to-micros 2026-06-13 07:34:55 +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#1266
No description provided.