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 1a476d84..892374e7 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
@@ -132,6 +132,40 @@ describe("витрина лэндинга v3 без данных", () => {
expect(note.textContent).toContain(String(BACKTEST.priceError.sampleN));
});
+ /**
+ * УТВЕРЖДЕНИЕ ПРО ПОЛОСУ ПРОВЕРЯЕТ САМО СЕБЯ ПО ПОКАЗАННЫМ СТРОКАМ.
+ *
+ * Границы стоят в коде фронта, строки приходят из БД от последнего прогона
+ * задачи, а задача в расписании не стоит. Между выкатом фронта и пересчётом
+ * витрины на странице лежат СТАРЫЕ строки: на проде 12 из 20 вне полосы,
+ * худшая +75,71 %. Утверждение «показана полоса −5…+20 %» и строка +75,7 %
+ * под ним — хуже, чем отсутствие утверждения.
+ *
+ * Здесь ровно этот набор: одна строка вне полосы (значение с прода) — и про
+ * полосу не утверждается ничего. Ломать так: сделать `allWithinBand`
+ * всегда-true — тест покраснеет ПО ЗНАЧЕНИЮ, найдя «отобранная полоса» над
+ * строкой, которая в неё не входит.
+ */
+ it("«Точность»: строка вне полосы снимает утверждение про полосу, медиана сверки остаётся", () => {
+ const outOfBand: ShowcaseResponse = {
+ ...SHOWCASE,
+ deals: [
+ SHOWCASE.deals[0],
+ { ...SHOWCASE.deals[0], err_pct: 75.71 },
+ ],
+ };
+ render(
- {`Разброс показанных строк: медианное расхождение ${absPct(spread.medianAbsPct)} по ${spread.n} строкам, худшая ${absPct(spread.worstAbsPct)}. Это отобранная полоса расхождения от ${errPct(BAND_MIN_PCT)} до ${errPct(BAND_MAX_PCT)}, а не вся сверка: промахи крупнее полосы в данных есть, здесь их не видно. Медиана по всей сверке — ${BACKTEST.priceError.text} (${count(BACKTEST.priceError.sampleN)} сделок).`} + {`Разброс показанных строк: медианное расхождение ${absPct(spread.medianAbsPct)} по ${spread.n} строкам, худшая ${absPct(spread.worstAbsPct)}. ${ + allWithinBand(deals) + ? `Это отобранная полоса расхождения от ${errPct(BAND_MIN_PCT)} до ${errPct(BAND_MAX_PCT)}, а не вся сверка: промахи крупнее полосы в данных есть, здесь их не видно.` + : `Полосу расхождения от ${errPct(BAND_MIN_PCT)} до ${errPct(BAND_MAX_PCT)} эта подпись не обещает: среди показанных строк есть расхождения вне неё, то есть витрину собрал прогон с другим правилом — тем, что напечатано выше.` + } Медиана по всей сверке — ${BACKTEST.priceError.text} (${count(BACKTEST.priceError.sampleN)} сделок).`}
)}{deals[0].note}
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 2ee33a49..657dd8fb 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 @@ -16,8 +16,16 @@ * пишет только сделки с расхождением от −5 % до +20 % (`select_rows`/`BAND_*` * в app/tasks/landing_showcase_deals.py), внутри полосы порядок задают полнота * и свежесть. Лента берёт ТЕ ЖЕ строки, поэтому её содержимое отобрано ровно - * так же — и «худшая» в подписи ниже теперь ограничена полосой сверху, а не - * данными. + * так же. + * + * НО УТВЕРЖДАЕМ МЫ ЭТО НЕ НА ВЕРУ. Границы живут в коде фронта, а строки + * приезжают из БД от ПОСЛЕДНЕГО прогона задачи — и задача в расписании не + * стоит, её запускают руками. Значит между выкатом фронта и пересчётом + * витрины лента утверждала бы полосу над строками, собранными по прежнему + * правилу (на проде сейчас 12 из 20 таких, худшая +75,7 %), и тут же + * прокручивала бы опровержение мимо собственной подписи. Поэтому подпись + * спрашивает сами строки (`allWithinBand`) и говорит про полосу, только если + * ни одна из них этому не противоречит. * * ПОД лентой — подпись с разбросом ПОКАЗАННЫХ строк (`shownSpread`, та же * функция, что и под таблицей сверок): медиана модуля и худшая. До полосы она @@ -44,6 +52,7 @@ import styles from "../../landing-v3.module.css"; import { absPct, + allWithinBand, BAND_MAX_PCT, BAND_MIN_PCT, dealTitle, @@ -85,8 +94,16 @@ export function DealsTickerV3({ deals }: { deals: readonly ShowcaseDeal[] }) { {spread && ( + // Про полосу говорим, только если ЛЕНТА ей соответствует. Границы + // стоят в коде, строки приходят из БД от последнего прогона задачи, а + // задача в расписании не стоит: между выкатом и пересчётом лента + // утверждала бы полосу над строками, собранными до неё, — и тут же + // прокручивала +75,7 % мимо этого утверждения. Не соответствует — + // печатаем только то, что посчитано по самим строкам.- {`Показаны сделки с расхождением от ${errPct(BAND_MIN_PCT)} до ${errPct(BAND_MAX_PCT)} — отобранная полоса, не вся сверка. Медиана показанных строк — ${absPct(spread.medianAbsPct)}, худшая — ${absPct(spread.worstAbsPct)}`} + {allWithinBand(deals) + ? `Показаны сделки с расхождением от ${errPct(BAND_MIN_PCT)} до ${errPct(BAND_MAX_PCT)} — отобранная полоса, не вся сверка. Медиана показанных строк — ${absPct(spread.medianAbsPct)}, худшая — ${absPct(spread.worstAbsPct)}` + : `Медиана расхождения показанных строк — ${absPct(spread.medianAbsPct)}, худшая — ${absPct(spread.worstAbsPct)}`}
)} diff --git a/tradein-mvp/frontend/src/app/mera-public/_components/v3/deal-view.ts b/tradein-mvp/frontend/src/app/mera-public/_components/v3/deal-view.ts index 61653657..5bd0b185 100644 --- a/tradein-mvp/frontend/src/app/mera-public/_components/v3/deal-view.ts +++ b/tradein-mvp/frontend/src/app/mera-public/_components/v3/deal-view.ts @@ -206,6 +206,23 @@ export const WITHIN_PCT = 20; export const BAND_MIN_PCT = -5; export const BAND_MAX_PCT = 20; +/** + * Можно ли про ЭТИ строки утверждать, что они — полоса. + * + * Подпись про полосу собирается из констант, а строки приезжают из БД, из + * последнего прогона задачи. Прогон в расписании не стоит и запускается + * руками, поэтому между выкатом фронта и пересчётом витрины страница + * утверждала бы полосу над строками, собранными ДО неё: на проде 12 из 20 + * лежащих сейчас строк вне полосы, включая +75,7 % — утверждение и его + * опровержение в одном экране. + * + * Поэтому утверждение самопроверяемое: печатается, только если ни одна + * показанная строка ему не противоречит. Проверка идёт по тем же границам, + * что стоят в тексте, — соврать, не покраснев, подпись не может. + */ +export const allWithinBand = (deals: readonly ShowcaseDeal[]): boolean => + deals.every((d) => d.err_pct >= BAND_MIN_PCT && d.err_pct <= BAND_MAX_PCT); + export interface ShownSpread { readonly n: number; readonly medianAbsPct: number;