import { Disclosure } from '../ui/Disclosure'; import type { BacktestRecommendation } from '../../lib/types'; /** * The verdict, ahead of the tuning detail. * * All eight findings used to render as equal-weight bullets, so "does this * strategy work" sat in the same visual register as "which cutoff scored best". * `topic` splits them: the three that answer the question stay inline, the rest * collapse. * * No topic chips — every backend string already self-prefixes ("Gate: …", * "Robustness: …"), so a chip would render "GATE │ Gate: …", and stripping the * prefix would drop real information ("(3y)" carries the lookback, "Legacy" * qualifies the diagnostic). */ const PRIMARY_TOPICS = new Set(['production', 'benchmark', 'robustness']); /** * Mirrors how the backend phrases a bad result — `_build_recommendation` emits * "Robustness WARNING: …" and "Book vs SPY: LAGS …". There is deliberately no * `severity` field on the payload; if that changes, this is the one place to fix. */ function isWarning(text: string): boolean { return text.includes('WARNING') || text.includes('LAGS'); } export function BacktestRecommendationCard({ recommendation, }: { recommendation: BacktestRecommendation; }) { const items = recommendation.items; if (items.length === 0) return null; // A warning is always visible, whatever its topic — burying "the edge // disappears without the top 5% of winners" behind a disclosure would defeat // the point of surfacing it at all. const primary = items.filter((i) => PRIMARY_TOPICS.has(i.topic) || isWarning(i.text)); const secondary = items.filter((i) => !PRIMARY_TOPICS.has(i.topic) && !isWarning(i.text)); const warningCount = items.filter((i) => isWarning(i.text)).length; return (
What this backtest recommends
{warningCount > 0 && ( ⚠ {warningCount} warning{warningCount > 1 ? 's' : ''} )}{recommendation.headline}
)} {primary.length > 0 && ({recommendation.note}
)}