diff --git a/tradein-mvp/frontend/src/app/mera-public/__tests__/landing-v3-render.test.tsx b/tradein-mvp/frontend/src/app/mera-public/__tests__/landing-v3-render.test.tsx index 2fdbbac7..4a3c7aca 100644 --- a/tradein-mvp/frontend/src/app/mera-public/__tests__/landing-v3-render.test.tsx +++ b/tradein-mvp/frontend/src/app/mera-public/__tests__/landing-v3-render.test.tsx @@ -10,6 +10,7 @@ import { render, screen } from "@testing-library/react"; import { describe, expect, it } from "vitest"; import { AccuracyV3 } from "../_components/v3/AccuracyV3"; +import { DealsTickerV3 } from "../_components/v3/DealsTickerV3"; import { CostOfErrorV3 } from "../_components/v3/CostOfErrorV3"; import { HeroV3 } from "../_components/v3/HeroV3"; import type { LandingStat, ShowcaseResponse } from "../public-api"; @@ -136,3 +137,31 @@ describe("витрина лэндинга v3 без данных", () => { expect(screen.queryByText(/По всем расчётам МЕРЫ/)).toBeNull(); }); }); + +/** + * Лента висит НАД первым экраном, и её строки отобраны по полноте и свежести, + * а не по величине ошибки, — крупный промах в ней штатен. Проверяется, что + * рядом с промахом стоит его контекст и что оба числа сосчитаны ПО ПОКАЗАННЫМ + * строкам: подпись с вписанными руками величинами тут же разошлась бы с лентой. + */ +describe("лента сделок: контекст разброса", () => { + const tickerDeal = (err_pct: number) => ({ + ...SHOWCASE.deals[0], + err_pct, + predicted_rub: Math.round(SHOWCASE.deals[0].fact_rub * (1 + err_pct / 100)), + }); + + it("подпись печатает медиану и худшую ровно тех строк, что показаны", () => { + render(); + const note = screen.getByText(/Медиана расхождения показанных строк/u); + expect(note.textContent).toContain("11,5 %"); + expect(note.textContent).toContain("75,7 %"); + }); + + it("та же лента без худшей строки печатает ДРУГИЕ числа — они не константы", () => { + render(); + const note = screen.getByText(/Медиана расхождения показанных строк/u); + expect(note.textContent).toContain("7,8 %"); + expect(note.textContent).not.toContain("75,7 %"); + }); +}); diff --git a/tradein-mvp/frontend/src/app/mera-public/_components/v3/DealsTickerV3.tsx b/tradein-mvp/frontend/src/app/mera-public/_components/v3/DealsTickerV3.tsx index 7f56ef50..4e0a957b 100644 --- a/tradein-mvp/frontend/src/app/mera-public/_components/v3/DealsTickerV3.tsx +++ b/tradein-mvp/frontend/src/app/mera-public/_components/v3/DealsTickerV3.tsx @@ -12,6 +12,15 @@ * не из чего. Адреса тоже нет: улица и дом известны у 2.7% сделок, поэтому * объект описан тем, что есть — комнаты, площадь, район, этаж. * + * ПОД лентой — подпись с разбросом ПОКАЗАННЫХ строк (`shownSpread`, та же + * функция, что и под таблицей сверок): медиана модуля и худшая. Без неё первое, + * что видит посетитель страницы про точность, — промах в семьдесят процентов + * без единой цифры контекста, опровергающий её же заголовок. Прятать промахи + * нельзя: строки отобраны по ПОЛНОТЕ и СВЕЖЕСТИ (`_sort_key` в + * app/tasks/landing_showcase_deals.py), поэтому лечится контекстом, а не + * отбором. Вывода («зато обычно точно») в подписи нет намеренно: он протух бы + * на первом же пересчёте витрины, а два числа рядом не протухают. + * * Список дублируется дважды подряд — стандартный приём бесшовного CSS-marquee * (анимация уводит ровно на −50%). * @@ -24,34 +33,45 @@ import type { ShowcaseDeal } from "../../public-api"; import styles from "../../landing-v3.module.css"; -import { dealTitle, errPct, rub } from "./deal-view"; +import { absPct, dealTitle, errPct, rub, shownSpread } from "./deal-view"; export function DealsTickerV3({ deals }: { deals: readonly ShowcaseDeal[] }) { const items = [...deals, ...deals]; + // Разброс считается по `deals`, а не по `items`: `items` — та же двадцатка, + // склеенная сама с собой ради бесшовного marquee. Медиана и худшая на ней + // совпали бы, а `n` — нет, и подпись начала бы врать при первой же правке. + const spread = shownSpread(deals); return (
-
-
- ); } diff --git a/tradein-mvp/frontend/src/app/mera-public/landing-v3.module.css b/tradein-mvp/frontend/src/app/mera-public/landing-v3.module.css index c3705e01..5baa8c3f 100644 --- a/tradein-mvp/frontend/src/app/mera-public/landing-v3.module.css +++ b/tradein-mvp/frontend/src/app/mera-public/landing-v3.module.css @@ -1397,9 +1397,22 @@ /* --- Лента «ПРОГНОЗ → ФАКТ» ---------------------------------------------- */ .tickerRoot { + background: var(--b2c-surface-dark); +} +/* Бегущая часть отделена от подписи: `overflow: hidden` и точка отсчёта для + абсолютного бейджа нужны ТОЛЬКО ей, иначе бейдж растянулся бы на всю высоту + вместе с подписью. */ +.tickerStrip { position: relative; overflow: hidden; - background: var(--b2c-surface-dark); +} +.tickerNote { + margin: 0; + padding: 0 clamp(20px, 5vw, 56px) 10px; + font-family: var(--b2c-font-mono); + font-size: 10px; + letter-spacing: 0.06em; + color: var(--b2c-muted); } .tickerBadge { position: absolute;