[low] velocity: знаменатель score использует число MAPPED конкурентов (включая с нулевыми продажами) — систематическое занижение velocity_score #1382

Closed
opened 2026-06-14 18:10:07 +00:00 by bot-backend · 0 comments
Collaborator

Описание

Переменная n_with_sales (и комментарий) обещает «только mapped конкуренты», но фактически len(mapped_sales_rows) считает ВСЕ строки с has_mapping, включая mapped-конкурентов с нулём продаж в окне (LEFT JOIN на crm даёт строку с total_sqm=NULL→0, months_with_data=0). Числитель monthly_velocity суммирует только реальные продажи, а знаменатель n_with_sales * ekb_median * 2 раздувается за счёт неактивных mapped-конкурентов. Итог: горячий рынок с несколькими замаппленными-но-непродающими ЖК получает заниженный velocity_score.

Корень

Асимметрия числитель/знаменатель: числитель аккумулирует только активные м², знаменатель нормирует на число всех mapped (включая неактивных). SQL LEFT JOIN с условием deals_total_count > 0 в ON-клаузе возвращает строку с has_mapping=TRUE и нулём продаж; фильтр на строке 376 эти нулевые строки сохраняет.

Evidence

velocity.py:442-444:

# n_with_sales — только mapped конкуренты (у unmapped данных нет).
n_with_sales = len(mapped_sales_rows)
denominator = n_with_sales * ekb_median * 2.0 if n_with_sales > 0 else ekb_median * 2.0
velocity_score = min(1.0, max(0.0, monthly_velocity / denominator))

Граничный случай «все неактивны» обработан fallback'ом на строке 386 (if months_observed == 0 or total_sqm <= 0), но при хотя бы одном активном путь доходит до 442 с раздутым знаменателем.

Suggested fix

В знаменателе использовать число конкурентов с НЕНУЛЕВЫМИ продажами:

n_active = sum(1 for r in mapped_sales_rows if float(r['total_sqm'] or 0.0) > 0)
denominator = max(n_active, 1) * ekb_median * 2.0

Это согласует числитель (объём только активных) и знаменатель (норма на число активных).

Обоснование severity (low)

Влияние ограничено сценариями с частично неактивными mapped-конкурентами; граничный случай «все неактивны» обработан; score монотонно отражает рыночную активность, просто занижен — не инвертирован, логику не ломает. Confidence 0.82.


week ревью 1 · multi-agent аудит 2026-06-14 · severity=low · category=logic

Затронутые файлы:

  • /Users/anton/Птица/gendesign/backend/app/services/site_finder/velocity.py
## Описание Переменная `n_with_sales` (и комментарий) обещает «только mapped конкуренты», но фактически `len(mapped_sales_rows)` считает ВСЕ строки с `has_mapping`, включая mapped-конкурентов с нулём продаж в окне (LEFT JOIN на crm даёт строку с `total_sqm=NULL→0`, `months_with_data=0`). Числитель `monthly_velocity` суммирует только реальные продажи, а знаменатель `n_with_sales * ekb_median * 2` раздувается за счёт неактивных mapped-конкурентов. Итог: горячий рынок с несколькими замаппленными-но-непродающими ЖК получает заниженный `velocity_score`. ## Корень Асимметрия числитель/знаменатель: числитель аккумулирует только активные м², знаменатель нормирует на число всех mapped (включая неактивных). SQL LEFT JOIN с условием `deals_total_count > 0` в ON-клаузе возвращает строку с has_mapping=TRUE и нулём продаж; фильтр на строке 376 эти нулевые строки сохраняет. ## Evidence `velocity.py:442-444`: ```python # n_with_sales — только mapped конкуренты (у unmapped данных нет). n_with_sales = len(mapped_sales_rows) denominator = n_with_sales * ekb_median * 2.0 if n_with_sales > 0 else ekb_median * 2.0 velocity_score = min(1.0, max(0.0, monthly_velocity / denominator)) ``` Граничный случай «все неактивны» обработан fallback'ом на строке 386 (`if months_observed == 0 or total_sqm <= 0`), но при хотя бы одном активном путь доходит до 442 с раздутым знаменателем. ## Suggested fix В знаменателе использовать число конкурентов с НЕНУЛЕВЫМИ продажами: ```python n_active = sum(1 for r in mapped_sales_rows if float(r['total_sqm'] or 0.0) > 0) denominator = max(n_active, 1) * ekb_median * 2.0 ``` Это согласует числитель (объём только активных) и знаменатель (норма на число активных). ## Обоснование severity (low) Влияние ограничено сценариями с частично неактивными mapped-конкурентами; граничный случай «все неактивны» обработан; score монотонно отражает рыночную активность, просто занижен — не инвертирован, логику не ломает. Confidence 0.82. --- _week ревью 1 · multi-agent аудит 2026-06-14 · severity=**low** · category=`logic`_ Затронутые файлы: - `/Users/anton/Птица/gendesign/backend/app/services/site_finder/velocity.py`
bot-backend added the
week ревью 1
label 2026-06-14 18:10:07 +00:00
bot-backend added the
scope/backend
label 2026-06-15 11:01:55 +00:00
Sign in to join this conversation.
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#1382
No description provided.