commercial: sell_through_pct (темп распродажи нежилого) мислейблится как «доля коммерции» (commercial_share_pct) #1635

Closed
opened 2026-06-16 18:44:29 +00:00 by bot-backend · 1 comment
Collaborator

Severity: low · Категория: numeric-correctness · Файл: backend/app/services/forecasting/recommendation.py:729-774

Файлы: backend/app/services/forecasting/recommendation.py:730,751,756,761 (_commercial_signal) → backend/app/services/forecasting/product_scoring.py:474-480 (_score_commercial) + docstring product_scoring.py:24.

Код: recommendation.py берёт sell_through = _as_float(getattr(metrics, 'sell_through_pct', None)), затем share_pct = round(sell_through, 1) кладёт в поле commercial_share_pct с reason «Коммерция (нежилое): реализованная доля ~X%». product_scoring делает value = _clamp01(float(share_pct)/100.0) с reason «Реализованная доля коммерции ~X% (§10.4, прокси спроса)».

Почему баг: sell_through_pct по определению MarketMetrics (backend/app/services/site_finder/market_metrics.py:106) = проданные ÷ (проданные+доступные) · 100 — ТЕМП РАСПРОДАЖИ выставленного нежилого стока (ликвидность/спрос), а НЕ доля нежилого в объёме застройки. Имя поля commercial_share_pct и module-docstring product_scoring.py:24 «доля коммерции» описывают структурную величину (сколько нежилого в продукте). Две разные величины конфлируются: «какую долю выставленного нежилого уже продали» vs. «какова доля нежилого в продукте». При высоком sell_through на малом числе лотов commercial-скор завышается, маскируя что нежилого почти нет (sell-through не учитывает объём n_lots).

Импакт ограничен: модуль ADVISORY, вес commercial 0.04 в overall; рядом величина честно помечена «прокси спроса на нежилые помещения» — sell-through как сигнал спроса/ликвидности интерпретируем валидно. Проблема в мислейбле имени поля и docstring «доля коммерции», плюс отсутствие учёта объёма. Severity low.

Фикс: переименовать поле/метку в commercial_sell_through_pct и привести reason+docstring product_scoring.py:24 к «темп распродажи нежилого (прокси ликвидности/спроса)», убрав слово «доля». Если действительно нужна доля коммерции в структуре — считать nonresidential_volume ÷ total_volume, а не sell-through.

Почему баг: Две разные величины конфланируются: «какую долю выставленного нежилого уже продали» (ликвидность) против «какова доля нежилого в продукте/спросе» (структура). Скор commercial и его reason утверждают «реализованная доля коммерции X%», хотя число отвечает на другой вопрос. При высоком sell_through (быстро распродали мало нежилых лотов) commercial-скор завышается, маскируя что нежилого почти нет. Это неверный численный сигнал в карту скоров (вес 0.04 в overall) и ложное объяснение в §16-reason.

Как чинить: Либо взять реальную долю нежилого (n_lots нежилого ÷ общий объём лотов сегмента) как commercial_share_pct, либо переименовать поле/микрокопию в «sell_through нежилого» и переинтерпретировать скор как ликвидность коммерции, а не долю. Сейчас имя поля (commercial_share_pct) и reason обещают долю, а считается темп распродажи.


deep-audit backend v2 (2-й проход, после PR #1543) · verify+harden, confidence 0.7 · unit B23

**Severity:** low · **Категория:** numeric-correctness · **Файл:** `backend/app/services/forecasting/recommendation.py:729-774` Файлы: backend/app/services/forecasting/recommendation.py:730,751,756,761 (_commercial_signal) → backend/app/services/forecasting/product_scoring.py:474-480 (_score_commercial) + docstring product_scoring.py:24. Код: recommendation.py берёт `sell_through = _as_float(getattr(metrics, 'sell_through_pct', None))`, затем `share_pct = round(sell_through, 1)` кладёт в поле `commercial_share_pct` с reason «Коммерция (нежилое): реализованная доля ~X%». product_scoring делает `value = _clamp01(float(share_pct)/100.0)` с reason «Реализованная доля коммерции ~X% (§10.4, прокси спроса)». Почему баг: sell_through_pct по определению MarketMetrics (backend/app/services/site_finder/market_metrics.py:106) = проданные ÷ (проданные+доступные) · 100 — ТЕМП РАСПРОДАЖИ выставленного нежилого стока (ликвидность/спрос), а НЕ доля нежилого в объёме застройки. Имя поля commercial_share_pct и module-docstring product_scoring.py:24 «доля коммерции» описывают структурную величину (сколько нежилого в продукте). Две разные величины конфлируются: «какую долю выставленного нежилого уже продали» vs. «какова доля нежилого в продукте». При высоком sell_through на малом числе лотов commercial-скор завышается, маскируя что нежилого почти нет (sell-through не учитывает объём n_lots). Импакт ограничен: модуль ADVISORY, вес commercial 0.04 в overall; рядом величина честно помечена «прокси спроса на нежилые помещения» — sell-through как сигнал спроса/ликвидности интерпретируем валидно. Проблема в мислейбле имени поля и docstring «доля коммерции», плюс отсутствие учёта объёма. Severity low. Фикс: переименовать поле/метку в commercial_sell_through_pct и привести reason+docstring product_scoring.py:24 к «темп распродажи нежилого (прокси ликвидности/спроса)», убрав слово «доля». Если действительно нужна доля коммерции в структуре — считать nonresidential_volume ÷ total_volume, а не sell-through. **Почему баг:** Две разные величины конфланируются: «какую долю выставленного нежилого уже продали» (ликвидность) против «какова доля нежилого в продукте/спросе» (структура). Скор commercial и его reason утверждают «реализованная доля коммерции X%», хотя число отвечает на другой вопрос. При высоком sell_through (быстро распродали мало нежилых лотов) commercial-скор завышается, маскируя что нежилого почти нет. Это неверный численный сигнал в карту скоров (вес 0.04 в overall) и ложное объяснение в §16-reason. **Как чинить:** Либо взять реальную долю нежилого (n_lots нежилого ÷ общий объём лотов сегмента) как commercial_share_pct, либо переименовать поле/микрокопию в «sell_through нежилого» и переинтерпретировать скор как ликвидность коммерции, а не долю. Сейчас имя поля (commercial_share_pct) и reason обещают долю, а считается темп распродажи. --- <sub>deep-audit backend v2 (2-й проход, после PR #1543) · verify+harden, confidence 0.7 · unit B23</sub>
bot-backend added the
week ревью 1
label 2026-06-16 18:44:29 +00:00
Author
Collaborator

Доделано (в PR #1662, commit c9ba595).

Cross-file остаток закрыт: внутренний контракт-ключ commercial_share_pctcommercial_sell_through_pct (producer recommendation.py + consumer product_scoring.py + 5 тест-сайтов). Reason/docstring/комментарии у _score_commercial исправлены: величина — темп распродажи нежилого (sell-through, прокси ликвидности/спроса), а НЕ доля нежилого в объёме застройки. Числовая логика не тронута. Ключ внутренний (нет frontend/schema/openapi-потребителей — проверено) → rename контракт-безопасен. pytest 171 passed.

**Доделано** ✅ (в PR #1662, commit c9ba595). Cross-file остаток закрыт: внутренний контракт-ключ `commercial_share_pct` → `commercial_sell_through_pct` (producer `recommendation.py` + consumer `product_scoring.py` + 5 тест-сайтов). Reason/docstring/комментарии у `_score_commercial` исправлены: величина — **темп распродажи нежилого** (sell-through, прокси ликвидности/спроса), а НЕ доля нежилого в объёме застройки. Числовая логика не тронута. Ключ внутренний (нет frontend/schema/openapi-потребителей — проверено) → rename контракт-безопасен. **pytest 171 passed.**
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#1635
No description provided.