fix(mv): mv_layout_velocity unweighted AVG искажает площади/цены в 2-4 раза (P0) #295

Closed
opened 2026-05-17 14:04:54 +00:00 by lekss361 · 1 comment
Owner

Severity: P0-critical

mv_layout_velocity использует AVG(deals_total_avg_area_m2) без веса по count → нули из месяцев без сделок тянут среднее в 2-4 раза вниз. То же для price.

Симптомы (live на участке 66:41:0204016:10)

В блоке «Анализ планировок» Site Finder:

# Тип Площадь Средн. площадь Средн. цена ₽/м²
1 1-комн. <25 м² 19.1 81 031
2 Студия <25 м² 12.1 86 489
3 2-комн. 25-40 м² 27.2 64 234

Студия 12 м² физически невозможна (СНиП минимум 14). 3-к за 30 тыс ₽/м² — галлюцинация цен.

SQL evidence — unweighted vs weighted AVG

Район 66:41:0204016 (3 ЖК: Раута/Астон/Траектория, окно 24 мес):

room unweighted AVG area (UI) weighted AVG area (правильно) unweighted AVG price weighted AVG price
1-к 19.3 м² 38.9 м² (×2.0) 83 тыс ₽ 140 тыс ₽ (×1.7)
studio 11.5 м² 28.4 м² (×2.5) 83 тыс ₽ 182 тыс ₽ (×2.2)
2-к 26.9 м² 57.3 м² (×2.1) 64 тыс ₽ 130 тыс ₽ (×2.0)
3-к 21.8 м² 81.7 м² (×3.7) 30 тыс ₽ 111 тыс ₽ (×3.7)

Зависимость: чем больше zero_months в objective_corpus_room_month для конкретного (project, room), тем сильнее искажение (для 3-к — 60 из 82 месяцев нулевые → 73%).

Root cause

data/sql/94_mv_layout_velocity.sql:

SELECT cm.domrf_obj_id AS obj_id,
       l.room_bucket,
       SUM(l.deals_total_count)::integer AS total_deals_24mo,
       AVG(l.deals_total_avg_area_m2)::numeric(10,2) AS avg_area_m2,           -- BUG
       AVG(l.deals_total_avg_price_thousand_rub_per_m2)::numeric(12,2)         -- BUG
           AS avg_price_thousand_rub_per_m2,
       ...

objective_corpus_room_month имеет per-месяц-per-room строки даже при 0 сделок. Эти строки тянут AVG() вниз. Правильно — weighted:

(SUM(deals_total_avg_area_m2 * deals_total_count) / NULLIF(SUM(deals_total_count), 0))

Impact

  • BestLayoutsBlock — Топ планировок и Рекомендация ТЗ
  • /analytics/recommend — если использует тот же MV
  • Любой downstream (планируемый TradeIn Estimator, выручка-калькулятор)
  • Пользователь принимает trade-in решения на фейковых ценах

Fix

  1. Миграция data/sql/NN_fix_mv_layout_velocity_weighted_avg.sql — DROP + CREATE MV с weighted AVG (полный готовый DDL в vault fixes/Bug_MvLayoutVelocity_UnweightedAvg_May17.md).
  2. REFRESH MATERIALIZED VIEW CONCURRENTLY mv_layout_velocity после деплоя миграции.
  3. Регрессионный тест backend/tests/sql/test_mv_layout_velocity_weighted_avg.py.
  4. Обновить .claude/rules/sql.md — правило «AVG по агрегатам-полу-фабрикатам всегда weighted».

Verification

После фикса на том же 66:41:0204016:10:

  • Студия ~28 м², 1-к ~38 м², 2-к ~57 м², 3-к ~81 м²
  • Цены ~110-180 тыс ₽/м² (нормальный эконом-комфорт Екб 2026)

References

  • Полный bug-doc: vault fixes/Bug_MvLayoutVelocity_UnweightedAvg_May17.md
  • Связанный список фиксов: vault audits/SiteFinder_FixList_May17.md (этот баг — #21, новый top-1 priority)
  • Тех-аудит: vault audits/SiteFinder_TechAudit_May17.md

Discovered: 2026-05-17 live walkthrough by Ghost.

## Severity: P0-critical `mv_layout_velocity` использует `AVG(deals_total_avg_area_m2)` без веса по count → нули из месяцев без сделок тянут среднее в 2-4 раза вниз. То же для price. ## Симптомы (live на участке `66:41:0204016:10`) В блоке «Анализ планировок» Site Finder: | # | Тип | Площадь | Средн. площадь | Средн. цена ₽/м² | |---|---|---|---|---| | 1 | 1-комн. | <25 м² | **19.1** | **81 031** | | 2 | Студия | <25 м² | **12.1** | **86 489** | | 3 | 2-комн. | 25-40 м² | **27.2** | **64 234** | Студия 12 м² физически невозможна (СНиП минимум 14). 3-к за 30 тыс ₽/м² — галлюцинация цен. ## SQL evidence — unweighted vs weighted AVG Район 66:41:0204016 (3 ЖК: Раута/Астон/Траектория, окно 24 мес): | room | unweighted AVG area (UI) | weighted AVG area (правильно) | unweighted AVG price | weighted AVG price | |---|---|---|---|---| | 1-к | 19.3 м² | **38.9 м²** (×2.0) | 83 тыс ₽ | **140 тыс ₽** (×1.7) | | studio | 11.5 м² | **28.4 м²** (×2.5) | 83 тыс ₽ | **182 тыс ₽** (×2.2) | | 2-к | 26.9 м² | **57.3 м²** (×2.1) | 64 тыс ₽ | **130 тыс ₽** (×2.0) | | 3-к | 21.8 м² | **81.7 м²** (×3.7) | 30 тыс ₽ | **111 тыс ₽** (×3.7) | Зависимость: чем больше `zero_months` в `objective_corpus_room_month` для конкретного `(project, room)`, тем сильнее искажение (для 3-к — 60 из 82 месяцев нулевые → 73%). ## Root cause `data/sql/94_mv_layout_velocity.sql`: ```sql SELECT cm.domrf_obj_id AS obj_id, l.room_bucket, SUM(l.deals_total_count)::integer AS total_deals_24mo, AVG(l.deals_total_avg_area_m2)::numeric(10,2) AS avg_area_m2, -- BUG AVG(l.deals_total_avg_price_thousand_rub_per_m2)::numeric(12,2) -- BUG AS avg_price_thousand_rub_per_m2, ... ``` `objective_corpus_room_month` имеет per-месяц-per-room строки даже при 0 сделок. Эти строки тянут `AVG()` вниз. Правильно — weighted: ```sql (SUM(deals_total_avg_area_m2 * deals_total_count) / NULLIF(SUM(deals_total_count), 0)) ``` ## Impact - `BestLayoutsBlock` — Топ планировок и Рекомендация ТЗ - `/analytics/recommend` — если использует тот же MV - Любой downstream (планируемый TradeIn Estimator, выручка-калькулятор) - **Пользователь принимает trade-in решения на фейковых ценах** ## Fix 1. Миграция `data/sql/NN_fix_mv_layout_velocity_weighted_avg.sql` — DROP + CREATE MV с weighted AVG (полный готовый DDL в vault `fixes/Bug_MvLayoutVelocity_UnweightedAvg_May17.md`). 2. `REFRESH MATERIALIZED VIEW CONCURRENTLY mv_layout_velocity` после деплоя миграции. 3. Регрессионный тест `backend/tests/sql/test_mv_layout_velocity_weighted_avg.py`. 4. Обновить `.claude/rules/sql.md` — правило «AVG по агрегатам-полу-фабрикатам всегда weighted». ## Verification После фикса на том же `66:41:0204016:10`: - Студия `~28 м²`, 1-к `~38 м²`, 2-к `~57 м²`, 3-к `~81 м²` - Цены `~110-180 тыс ₽/м²` (нормальный эконом-комфорт Екб 2026) ## References - Полный bug-doc: vault `fixes/Bug_MvLayoutVelocity_UnweightedAvg_May17.md` - Связанный список фиксов: vault `audits/SiteFinder_FixList_May17.md` (этот баг — #21, новый top-1 priority) - Тех-аудит: vault `audits/SiteFinder_TechAudit_May17.md` Discovered: 2026-05-17 live walkthrough by Ghost.
lekss361 added the
site-finder
bug
labels 2026-05-17 14:04:54 +00:00
Owner

Fix already shipped in migration 100 (2026-05-17). Adding the regression test + sql rule in PR #626.

Fix already shipped in migration 100 (2026-05-17). Adding the regression test + sql rule in PR #626.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
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#295
No description provided.