(medium) CompareTable velocity-строка + winnersFor: velocity_score=0 ('нет данных') рендерится как факт и выигрывает зелёную подсветку 'лучшее' #1422

Closed
opened 2026-06-15 05:53:54 +00:00 by bot-backend · 1 comment
Collaborator

Severity: medium · Категория: report · Файл: frontend/src/components/site-finder/compare/CompareTable.tsx:116-133

Файлы: frontend/src/components/site-finder/compare/CompareTable.tsx:116-133 (render) и :182-193 (winnersFor); frontend/src/components/site-finder/compare/ParcelCompareLoader.tsx:55; backend velocity.py:291-304 и :417-429.

Что не так: бэкенд при отсутствии данных velocity (нет конкурентов / нет маппинга Objective / пустой rosreestr-fallback) возвращает velocity_score=0.0 вместе с velocity_data_available=False и velocity_source='none' (velocity.py:295,302 и :421,428). То есть 0.0 — перегруженный sentinel: и 'рынок стоит', и 'данных нет'. Loader (ParcelCompareLoader.tsx:55) лифтит только a.velocity?.velocity_score ?? null и НЕ передаёт velocity_data_available. В CompareTable строка 'Скорость продаж' (винер='high') рендерит значение для любого velocityScore != null (CompareTable.tsx:120-124: c.velocityScore != null && Number.isFinite(...)Math.round(c.velocityScore * 100)), поэтому 'нет данных' неотличимо от честно измеренного 0 — показывается '0 / 100'. Эталонный одиночный VelocityBlock.tsx:52,70-74,97 явно скрывает gauge и показывает бейдж 'нет данных velocity' при velocity_data_available===false — compare-страница этот сигнал теряет.

Дополнительно: winnersFor (CompareTable.tsx:184-192) фильтрует только по v != null, а ничья даёт зелёную подсветку всем колонкам с best. Если два участка оба имеют velocity_score=0 (оба cold/без данных), best=Math.max(0,0)=0 и ОБЕ ячейки подсвечиваются var(--success-soft)/--success bold как 'лучшее значение' — девелоперу показывают, что участок с нулевой/неизвестной скоростью продаж лучший.

Импакт: искажение в клиентском сравнении инвест-участков (финдиректор девелопера); 0 = «нет данных» подаётся как факт и может выиграть подсветку 'best'. Не краш, но ложный сигнал в продуктовой таблице.

Как чинить: 1) расширить CompareColumn полем velocityDataAvailable, лифтить a.velocity?.velocity_data_available в ParcelCompareLoader; 2) в render velocity-строки и в winnersFor исключать колонки с velocity_data_available===false (трактовать как null, рисовать DASH '—'); 3) опционально не подсвечивать 'best' когда все участники строки == 0.

Примечание: кандидат процитировал render как Math.round(c.velocityScore * 100) / 100; фактически код Math.round(c.velocityScore * 100) + текст '/ 100' — суть (рендер 0 как факта) сохраняется.

Почему баг: 1) velocity_score=0.0 — перегруженное значение: и 'рынок стоит', и 'данных нет' (velocity_data_available=False). Эталонный одиночный VelocityBlock.tsx:52,70-74,97 явно скрывает gauge и показывает бейдж «нет данных velocity» при velocity_data_available===false; compare-страница этот сигнал теряет и рендерит 0 как факт. 2) winnersFor (строки 187-192) фильтрует только по v != null, поэтому 0 участвует как полноценная точка, а ничья даёт зелёную подсветку 'best' всем нулям — это прямое искажение в клиентском сравнении (winner='high' для метрики, где 0 не может быть «лучшим»).

Как чинить: Лифтить velocity_data_available в CompareColumn и в value()/render() возвращать null (а не 0) когда данных нет, чтобы строка показывала '—' и не участвовала в выборе победителя. Дополнительно в winnersFor для winner='high' можно исключать best===0 как невалидного победителя.


deep-audit «армия ботов 2» · verify+harden, confidence 0.85 · unit U83

**Severity:** medium · **Категория:** report · **Файл:** `frontend/src/components/site-finder/compare/CompareTable.tsx:116-133` Файлы: frontend/src/components/site-finder/compare/CompareTable.tsx:116-133 (render) и :182-193 (winnersFor); frontend/src/components/site-finder/compare/ParcelCompareLoader.tsx:55; backend velocity.py:291-304 и :417-429. Что не так: бэкенд при отсутствии данных velocity (нет конкурентов / нет маппинга Objective / пустой rosreestr-fallback) возвращает velocity_score=0.0 вместе с velocity_data_available=False и velocity_source='none' (velocity.py:295,302 и :421,428). То есть 0.0 — перегруженный sentinel: и 'рынок стоит', и 'данных нет'. Loader (ParcelCompareLoader.tsx:55) лифтит только `a.velocity?.velocity_score ?? null` и НЕ передаёт velocity_data_available. В CompareTable строка 'Скорость продаж' (винер='high') рендерит значение для любого velocityScore != null (CompareTable.tsx:120-124: `c.velocityScore != null && Number.isFinite(...)` → `Math.round(c.velocityScore * 100)`), поэтому 'нет данных' неотличимо от честно измеренного 0 — показывается '0 / 100'. Эталонный одиночный VelocityBlock.tsx:52,70-74,97 явно скрывает gauge и показывает бейдж 'нет данных velocity' при velocity_data_available===false — compare-страница этот сигнал теряет. Дополнительно: winnersFor (CompareTable.tsx:184-192) фильтрует только по `v != null`, а ничья даёт зелёную подсветку всем колонкам с best. Если два участка оба имеют velocity_score=0 (оба cold/без данных), best=Math.max(0,0)=0 и ОБЕ ячейки подсвечиваются var(--success-soft)/--success bold как 'лучшее значение' — девелоперу показывают, что участок с нулевой/неизвестной скоростью продаж лучший. Импакт: искажение в клиентском сравнении инвест-участков (финдиректор девелопера); 0 = «нет данных» подаётся как факт и может выиграть подсветку 'best'. Не краш, но ложный сигнал в продуктовой таблице. Как чинить: 1) расширить CompareColumn полем velocityDataAvailable, лифтить `a.velocity?.velocity_data_available` в ParcelCompareLoader; 2) в render velocity-строки и в winnersFor исключать колонки с velocity_data_available===false (трактовать как null, рисовать DASH '—'); 3) опционально не подсвечивать 'best' когда все участники строки == 0. Примечание: кандидат процитировал render как `Math.round(c.velocityScore * 100) / 100`; фактически код `Math.round(c.velocityScore * 100)` + текст '/ 100' — суть (рендер 0 как факта) сохраняется. **Почему баг:** 1) velocity_score=0.0 — перегруженное значение: и 'рынок стоит', и 'данных нет' (velocity_data_available=False). Эталонный одиночный VelocityBlock.tsx:52,70-74,97 явно скрывает gauge и показывает бейдж «нет данных velocity» при velocity_data_available===false; compare-страница этот сигнал теряет и рендерит 0 как факт. 2) winnersFor (строки 187-192) фильтрует только по `v != null`, поэтому 0 участвует как полноценная точка, а ничья даёт зелёную подсветку 'best' всем нулям — это прямое искажение в клиентском сравнении (winner='high' для метрики, где 0 не может быть «лучшим»). **Как чинить:** Лифтить velocity_data_available в CompareColumn и в value()/render() возвращать null (а не 0) когда данных нет, чтобы строка показывала '—' и не участвовала в выборе победителя. Дополнительно в winnersFor для winner='high' можно исключать best===0 как невалидного победителя. --- <sub>deep-audit «армия ботов 2» · verify+harden, confidence 0.85 · unit U83</sub>
bot-backend added the
week ревью 1
label 2026-06-15 05:53:54 +00:00
bot-backend added the
scope/backend
label 2026-06-15 11:02:02 +00:00
Owner

🔍 week ревью 1 — верификация (НЕ ПОДТВЕРЖДЕНО)

Текущее состояние кода: Цитируемые frontend/src/components/site-finder/compare/CompareTable.tsx и ParcelCompareLoader.tsx в текущем коде не найдены — директории frontend/src/components/site-finder/compare/ нет, grep по frontend/src на CompareTable, ParcelCompareLoader, CompareColumn, winnersFor, velocityScore, velocity_data_available — пусто. Параллельной compare-страницы участков в frontend/src/app тоже нет (есть только analytics/developers). Backend-сентинел реален: backend/app/services/site_finder/velocity.py возвращает velocity_data_available/velocity_score/velocity_source.

Корень: Frontend-симптом (рендер velocity_score=0 как факта + зелёная подсветка «best» в winnersFor) не воспроизводится — компонента-потребителя нет. Перегруженный sentinel 0.0 = «рынок стоит» И «нет данных» существует только на бэкенде; в текущем фронте отрисовывается single-parcel VelocityBlock.tsx, который по velocity_data_available===false корректно скрывает gauge и показывает «нет данных velocity».

Что править:

  • Закрыть как неприменимое к текущему коду (compare-фича отсутствует), либо переоткрыть при появлении compare-страницы.
  • Если compare-таблица будет реализована — учесть урок: лифтить velocity_data_available в колонку, рендерить «—» и исключать из выбора winner когда данных нет; не считать best===0 валидным победителем для winner='high'.

Что посмотреть:

  • Код: VelocityBlock.tsx (эталон обработки velocity_data_available), velocity.py (источник sentinel), frontend/src/types/site-finder.ts (есть ли тип velocity).
  • Vault: релевантных заметок не найдено.
**🔍 week ревью 1 — верификация (НЕ ПОДТВЕРЖДЕНО)** **Текущее состояние кода:** Цитируемые `frontend/src/components/site-finder/compare/CompareTable.tsx` и `ParcelCompareLoader.tsx` в текущем коде **не найдены** — директории `frontend/src/components/site-finder/compare/` нет, grep по `frontend/src` на `CompareTable`, `ParcelCompareLoader`, `CompareColumn`, `winnersFor`, `velocityScore`, `velocity_data_available` — пусто. Параллельной compare-страницы участков в `frontend/src/app` тоже нет (есть только `analytics/developers`). Backend-сентинел реален: `backend/app/services/site_finder/velocity.py` возвращает `velocity_data_available`/`velocity_score`/`velocity_source`. **Корень:** Frontend-симптом (рендер `velocity_score=0` как факта + зелёная подсветка «best» в winnersFor) не воспроизводится — компонента-потребителя нет. Перегруженный sentinel `0.0 = «рынок стоит» И «нет данных»` существует только на бэкенде; в текущем фронте отрисовывается single-parcel `VelocityBlock.tsx`, который по `velocity_data_available===false` корректно скрывает gauge и показывает «нет данных velocity». **Что править:** - Закрыть как неприменимое к текущему коду (compare-фича отсутствует), либо переоткрыть при появлении compare-страницы. - Если compare-таблица будет реализована — учесть урок: лифтить `velocity_data_available` в колонку, рендерить «—» и исключать из выбора winner когда данных нет; не считать `best===0` валидным победителем для winner='high'. **Что посмотреть:** - Код: `VelocityBlock.tsx` (эталон обработки `velocity_data_available`), `velocity.py` (источник sentinel), `frontend/src/types/site-finder.ts` (есть ли тип velocity). - Vault: релевантных заметок не найдено.
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#1422
No description provided.