[P2][tradein][frontend] Деление на median=0 → «Infinity%»/«NaN%» в ListingsCard/DealsCard CV и «ниже рынка» #741

Closed
opened 2026-05-30 15:26:00 +00:00 by bot-analyst · 2 comments
Collaborator

Worker: исполняемый спек. Делай ровно ниже. Конфликт с кодом → коммент, НЕ угадывай.
Финансовые проценты делятся на median_price_rub без guard — при пустой оценке (median=0, см. #697/#740) клиент видит «Infinity%» / «NaN%». Client-facing, демо 2026-06-01.

Задача

Защитить два деления на estimate.median_price_rub от нуля, чтобы вместо «Infinity%»/«NaN%» показывалось «—» (или блок скрывался).

Files (точные — проверено чтением)

  • tradein-mvp/frontend/src/components/trade-in/ListingsCard.tsx:
    • const m = estimate.median_price_rub; — строка 91.
    • CV-рендер: строка 153CV {(((estimate.range_high_rub - estimate.range_low_rub) / m) * 100).toFixed(1)}%. При m===0Infinity%.
  • tradein-mvp/frontend/src/components/trade-in/DealsCard.tsx:
    • «ниже рынка»: строки 88-91на {(((estimate.median_price_rub - m) / estimate.median_price_rub) * 100).toFixed(0)}% ниже рынка, где m — медиана сделок (стр. 85), делитель = estimate.median_price_rub. При median_price_rub===0NaN%/Infinity%. Доп: знак — если m > median_price_rub (сделки ВЫШЕ asking), текст «ниже рынка» неверен.

Контракт (точное толкование)

  1. ListingsCard.tsx:153: при m <= 0 НЕ вычислять CV — рендерить CV — (или скрыть .sub). Иначе как сейчас.
  2. DealsCard.tsx:88-91: при estimate.median_price_rub <= 0 НЕ вычислять процент — рендерить «—» (или скрыть блок «ниже рынка»). Дополнительно: подобрать слово по знаку — diff = (median_price_rub - m)/median_price_rub*100; если diff >= 0 → «ниже рынка», если < 0 → «выше рынка» (с Math.abs).

Definition of Done (бинарно)

  • ListingsCard.tsx:153: при median=0 нет подстроки «Infinity» в выводе. Проверка кода: деление обёрнуто в guard m > 0 ? ... : "—". grep -n 'range_high_rub - estimate.range_low_rub' tradein-mvp/frontend/src/components/trade-in/ListingsCard.tsx → строка теперь под условием.
  • DealsCard.tsx:88-91: при median_price_rub===0 нет «NaN»/«Infinity»; guard estimate.median_price_rub > 0 ? ... : "—".
  • Знак: при медиане сделок ВЫШЕ asking рендерится «выше рынка», не «ниже». grep -n 'выше рынка' tradein-mvp/frontend/src/components/trade-in/DealsCard.tsx → ≥1.
  • cd tradein-mvp/frontend && npx tsc --noEmit → 0 ошибок.
  • cd tradein-mvp/frontend && npx eslint src/components/trade-in/ListingsCard.tsx src/components/trade-in/DealsCard.tsx → clean.

Не делать (out of scope)

  • НЕ трогать backend, НЕ менять расчёт median.
  • НЕ чинить HeroSummary zero-state (это #740), n_analogs cap (finding #20), confidence-color (finding #21) — отдельные issue.
  • НЕ рефакторить общий calcCv в shared util (можно, но не обязательно для DoD — оставь на усмотрение, главное guard'ы).

Risk

  • Client-facing карточки результата. Guard'ы аддитивны, happy-path (median>0) не меняется.

Связано

  • Зависит концептуально от #697/#740 (пустая оценка). Источник: аудит inbox/2026-05-30-1545-tradein-hard-code-audit-backend-frontend (finding #19). Milestone Praktika demo 2026-06-01.
> Worker: исполняемый спек. Делай ровно ниже. Конфликт с кодом → коммент, НЕ угадывай. > Финансовые проценты делятся на `median_price_rub` без guard — при пустой оценке (median=0, см. #697/#740) клиент видит «Infinity%» / «NaN%». Client-facing, демо 2026-06-01. ## Задача Защитить два деления на `estimate.median_price_rub` от нуля, чтобы вместо «Infinity%»/«NaN%» показывалось «—» (или блок скрывался). ## Files (точные — проверено чтением) - `tradein-mvp/frontend/src/components/trade-in/ListingsCard.tsx`: - `const m = estimate.median_price_rub;` — строка **91**. - CV-рендер: строка **153** — `CV {(((estimate.range_high_rub - estimate.range_low_rub) / m) * 100).toFixed(1)}%`. При `m===0` → `Infinity%`. - `tradein-mvp/frontend/src/components/trade-in/DealsCard.tsx`: - «ниже рынка»: строки **88-91** — `на {(((estimate.median_price_rub - m) / estimate.median_price_rub) * 100).toFixed(0)}% ниже рынка`, где `m` — медиана сделок (стр. 85), делитель = `estimate.median_price_rub`. При `median_price_rub===0` → `NaN%`/`Infinity%`. Доп: знак — если `m > median_price_rub` (сделки ВЫШЕ asking), текст «ниже рынка» неверен. ## Контракт (точное толкование) 1. **ListingsCard.tsx:153**: при `m <= 0` НЕ вычислять CV — рендерить `CV —` (или скрыть `.sub`). Иначе как сейчас. 2. **DealsCard.tsx:88-91**: при `estimate.median_price_rub <= 0` НЕ вычислять процент — рендерить «—» (или скрыть блок «ниже рынка»). Дополнительно: подобрать слово по знаку — `diff = (median_price_rub - m)/median_price_rub*100`; если `diff >= 0` → «ниже рынка», если `< 0` → «выше рынка» (с `Math.abs`). ## Definition of Done (бинарно) - [ ] `ListingsCard.tsx:153`: при median=0 нет подстроки «Infinity» в выводе. Проверка кода: деление обёрнуто в guard `m > 0 ? ... : "—"`. `grep -n 'range_high_rub - estimate.range_low_rub' tradein-mvp/frontend/src/components/trade-in/ListingsCard.tsx` → строка теперь под условием. - [ ] `DealsCard.tsx:88-91`: при `median_price_rub===0` нет «NaN»/«Infinity»; guard `estimate.median_price_rub > 0 ? ... : "—"`. - [ ] Знак: при медиане сделок ВЫШЕ asking рендерится «выше рынка», не «ниже». `grep -n 'выше рынка' tradein-mvp/frontend/src/components/trade-in/DealsCard.tsx` → ≥1. - [ ] `cd tradein-mvp/frontend && npx tsc --noEmit` → 0 ошибок. - [ ] `cd tradein-mvp/frontend && npx eslint src/components/trade-in/ListingsCard.tsx src/components/trade-in/DealsCard.tsx` → clean. ## Не делать (out of scope) - НЕ трогать backend, НЕ менять расчёт median. - НЕ чинить HeroSummary zero-state (это #740), n_analogs cap (finding #20), confidence-color (finding #21) — отдельные issue. - НЕ рефакторить общий `calcCv` в shared util (можно, но не обязательно для DoD — оставь на усмотрение, главное guard'ы). ## Risk - Client-facing карточки результата. Guard'ы аддитивны, happy-path (median>0) не меняется. ## Связано - Зависит концептуально от #697/#740 (пустая оценка). Источник: аудит `inbox/2026-05-30-1545-tradein-hard-code-audit-backend-frontend` (finding #19). Milestone Praktika demo 2026-06-01.
bot-analyst added the
priority/p2
scope/frontend
status/ready
tradein
labels 2026-05-30 15:26:06 +00:00
bot-analyst added this to the Praktika demo — Mon 2026-06-01 milestone 2026-05-30 15:26:07 +00:00
bot-frontend self-assigned this 2026-05-30 15:34:03 +00:00
bot-frontend added
status/wip
and removed
status/ready
labels 2026-05-30 15:34:05 +00:00
bot-frontend added
status/review
and removed
status/wip
labels 2026-05-30 15:38:05 +00:00
Collaborator

Working on this in PR #744.

Working on this in PR #744.
bot-reviewer added
status/qa
and removed
status/review
labels 2026-05-30 15:40:06 +00:00
Collaborator

QA smoke PASS (PR #744, deployed)

Guards подтверждены в merged-коде (main) + runtime на insufficient_data-estimate (median=0):

  • ListingsCard.tsx CV: {m > 0 ? \${(((range_high - range_low)/m)*100).toFixed(1)}%` : "—"}` ✓ — нет «Infinity%».
  • DealsCard.tsx «рынок»: {median_price_rub > 0 ? \на ${Math.abs(...).toFixed(0)}% ${median_price_rub - m >= 0 ? "ниже" : "выше"} рынка` : "—"}` ✓ — guard от деления на 0 + корректный знак (ниже/выше через Math.abs). «выше рынка» присутствует.
  • Runtime (Челябинск, median=0, insufficient_data=true): на странице нет подстрок Infinity/NaN (regex-проверка → false). ✓

DoD выполнен. → status/done, closing.

✅ **QA smoke PASS** (PR #744, deployed) Guards подтверждены в merged-коде (`main`) + runtime на insufficient_data-estimate (median=0): - **ListingsCard.tsx** CV: `{m > 0 ? \`${(((range_high - range_low)/m)*100).toFixed(1)}%\` : "—"}` ✓ — нет «Infinity%». - **DealsCard.tsx** «рынок»: `{median_price_rub > 0 ? \`на ${Math.abs(...).toFixed(0)}% ${median_price_rub - m >= 0 ? "ниже" : "выше"} рынка\` : "—"}` ✓ — guard от деления на 0 + корректный знак (ниже/выше через Math.abs). «выше рынка» присутствует. - **Runtime** (Челябинск, median=0, insufficient_data=true): на странице **нет** подстрок `Infinity`/`NaN` (regex-проверка → false). ✓ DoD выполнен. → `status/done`, closing.
bot-qa added
status/done
and removed
status/qa
labels 2026-05-30 15:44:38 +00:00
Sign in to join this conversation.
No project
No assignees
3 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#741
No description provided.