fix(ptica): нет сделок за окно — цена не 0 ₽/м², а «нет данных»
All checks were successful
CI Trade-In / changes (pull_request) Successful in 10s
CI / changes (pull_request) Successful in 11s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Successful in 17m28s
CI / openapi-codegen-check (pull_request) Successful in 2m8s

deals_per_bucket оборачивал деление в COALESCE(...,0), поэтому пустая
комнатность получала среднюю цену 0 вместо NULL.

Схема УЖЕ обещала честный ответ (TopLayoutRow.avg_price_per_m2_rub:
float | None, комментарий «NULL если objective не покрывает obj»), и
Python-потребитель уже умеет None — пропускает такую строку во взвешенном
роллапе. COALESCE делал эту ветку недостижимой: обещание в контракте без
реализации.

Замер 13.08, окно 6 месяцев. Сработает ноль или нет — зависит от того,
сколько замапленных проектов попало в радиус, поэтому по слоям: 616
проектов, 2083 пары (проект × комнатность), пустых 635; хотя бы одна
пустая комнатность у 323 проектов, все пустые — у 80. При объединении по
два пустых 255 из 1267, по всему городу — ноль.

avg_area_m2 намеренно не трогаю: там контракт float (не Optional), нужна
правка схемы + типов фронта + решение по area_bin — отдельно, #2867.

Refs #2464
This commit is contained in:
bot-backend 2026-08-13 16:55:41 +05:00
parent 9e83eb4a53
commit c799dbf0ab

View file

@ -192,15 +192,30 @@ _INLINE_VELOCITY_SQL = text("""
SELECT
a.room_bucket,
SUM(a.deals_window) AS deals_window,
-- Здесь COALESCE(...,0) ОСТАЁТСЯ намеренно: TopLayoutRow.avg_area_m2
-- объявлен как float (не Optional), и NULL ронял бы контракт API.
-- Пустые комнатности получают площадь 0 м², и это тоже неправда но
-- честный NULL требует правки схемы + перегенерации типов фронта
-- и решения, что писать в area_bin. Отдельным заходом: #2867.
COALESCE(
SUM(a.area_weighted_sum)
/ NULLIF(SUM(a.deals_window), 0),
0
)::numeric(10, 2) AS avg_area_m2,
COALESCE(
-- #2464-B: БЕЗ COALESCE(...,0). Сделок за окно нет → делитель NULL →
-- средней цены нет, и это NULL, а не «0 /м²». Схема так и объявлена
-- (TopLayoutRow.avg_price_per_m2_rub: float | None), и Python ниже уже
-- умеет None (пропускает строку во взвешенном роллапе) но COALESCE
-- делал эту ветку недостижимой.
-- Замер 13.08 по проду, окно 6 месяцев. Сработает ноль или нет зависит
-- от того, сколько замапленных проектов попало в радиус, поэтому цифры
-- по слоям: у 616 проектов 2083 пары (проект × комнатность), пустых 635;
-- 323 проекта имеют хотя бы одну пустую комнатность, 80 пустые ВСЕ.
-- При объединении по два пустых остаётся 255 из 1267, по всему городу
-- ноль. То есть чем беднее окрестность участка, тем чаще выдумывался 0.
(
SUM(a.price_weighted_sum)
/ NULLIF(SUM(a.deals_window), 0),
0
/ NULLIF(SUM(a.deals_window), 0)
)::numeric(12, 2) * 1000.0 AS avg_price_per_m2_rub,
array_agg(DISTINCT a.project_name) AS matched_project_names,
MIN(a.window_start) AS window_start,