DS2: Dashboard Slate Rebuild — one hero, pending collapse, never-empty hero

DESIGN-SPEC Parts 3 + 6 (audit #1, #13, #14). The founder's named #1 rebuild.

slateAdapter.js — the testable engine:
- selectTopGrades: rank tonight's grades by tier → confidence → edge so the
  row varies on a real signal, not identical-weight noise (#13).
- buildHeroReceipts: yesterday's PROVEN A-tier settled HITS (misses excluded),
  carrying the real result — the never-empty proof source (#1, Part 6).
- heroFallbackState: tonight wins, else receipts, else empty.
- pendingSummary: collapse an all-awaiting card's six "Grades post …" rows to
  ONE line count (#14).
- topReadForCard: the single best live graded read to promote (#2).

GameCard.tsx — ONE bold hero per card (large mono/tabular grade + player, rest
demoted); all-awaiting cards render one "N props pending · grade ~X ET" line via
nextRunLabelET instead of repeated filler. Real team logos + team-colored accent
already lead the card (DS0) — preserved.

dashboard/page.tsx — Top grades tonight ranked via selectTopGrades (+ % CONF the
varying signal); when tonight is empty, fetch /api/ledger/model and fall back to
yesterday's PROVEN A-tier receipts (✓ HIT + actual + CLV) so first paint always
proves the model. Honest nextRunLabelET copy kept for the truly-empty case (QA.22).

Tests: tests/unit/ds2Dashboard.test.js (21) — pure-fn + source assertions,
fail-before / pass-after. Full suite 237 suites / 2863 tests green (+21).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kev
2026-07-13 00:07:06 -04:00
parent cf91c04e90
commit fe294a5de3
4 changed files with 531 additions and 48 deletions
+194
View File
@@ -0,0 +1,194 @@
// DS2 (Design v2) — Dashboard Slate Rebuild. The founder's named #1 rebuild:
// real entities per card, ONE bold hero, collapse pending-filler (#14), and the
// NEVER-EMPTY hero (#1, Part 6) — first paint ALWAYS proves the model. The pure
// engine lives in web/src/lib/slateAdapter.js (unit-tested directly); the .tsx
// surfaces are asserted against source (plain-JS Jest) — same pattern as
// ds4Billboards / vyndrParityQA.
const fs = require('fs');
const path = require('path');
const ROOT = path.join(__dirname, '..', '..');
const WEB = path.join(ROOT, 'web', 'src');
const read = (rel) => fs.readFileSync(path.join(WEB, rel), 'utf8');
const adapter = require('../../web/src/lib/slateAdapter');
// ── 1. selectTopGrades — ranking VARIES on a real signal (#13) ─────────────
describe('selectTopGrades — tier first, then the varying signal (confidence/edge)', () => {
test('orders by grade tier, then confidence desc, then edge desc', () => {
const grades = [
{ player: 'C-low', grade: 'B', confidence: 40, edge: 1 },
{ player: 'A-hiconf', grade: 'A', confidence: 90, edge: 2 },
{ player: 'A-loconf', grade: 'A', confidence: 55, edge: 9 },
{ player: 'Aplus', grade: 'A+', confidence: 10, edge: 0 },
];
const out = adapter.selectTopGrades(grades, 10).map((g) => g.player);
expect(out).toEqual(['Aplus', 'A-hiconf', 'A-loconf', 'C-low']);
});
test('ties on grade break on confidence — rows are NOT identical-order noise', () => {
const grades = [
{ player: 'X', grade: 'B', confidence: 30 },
{ player: 'Y', grade: 'B', confidence: 80 },
{ player: 'Z', grade: 'B', confidence: 55 },
];
expect(adapter.selectTopGrades(grades, 10).map((g) => g.player)).toEqual(['Y', 'Z', 'X']);
});
test('honors the limit and drops gradeless rows; empty in → empty out', () => {
const grades = [{ grade: 'A', confidence: 1 }, { grade: 'A', confidence: 2 }, { confidence: 9 }];
expect(adapter.selectTopGrades(grades, 1)).toHaveLength(1);
expect(adapter.selectTopGrades([], 5)).toEqual([]);
expect(adapter.selectTopGrades(null, 5)).toEqual([]);
});
});
// ── 2. buildHeroReceipts — yesterday's A-tier settled HITS (#1) ────────────
describe('buildHeroReceipts — proven A-tier receipts, misses excluded', () => {
const rows = [
{ player_name: 'Aaron Judge', stat: 'total_bases', line: 1.5, side: 'over', grade: 'A+', outcome: 'hit', actual_value: 3, clv_result: 'beat', sport: 'mlb' },
{ player_name: 'Missed Guy', stat: 'hits', line: 1.5, side: 'over', grade: 'A', outcome: 'miss', actual_value: 0 },
{ player_name: 'B Guy', stat: 'runs', line: 0.5, side: 'over', grade: 'B', outcome: 'hit', actual_value: 1 },
{ player_name: 'Aminus Hit', stat: 'rbi', line: 0.5, side: 'over', grade: 'A-', outcome: 'hit', actual_value: 2 },
];
test('keeps only A-family grades that HIT (a miss never becomes proof)', () => {
const out = adapter.buildHeroReceipts(rows, 8);
const names = out.map((r) => r.player);
expect(names).toContain('Aaron Judge');
expect(names).toContain('Aminus Hit');
expect(names).not.toContain('Missed Guy'); // A-tier but a MISS
expect(names).not.toContain('B Guy'); // a hit but not A-tier
});
test('carries the real result (actual + grade) so the receipt is verifiable', () => {
const judge = adapter.buildHeroReceipts(rows, 8).find((r) => r.player === 'Aaron Judge');
expect(judge).toMatchObject({ grade: 'A+', outcome: 'hit', actual: 3 });
});
test('best-grade first, honors the limit, empty/absent → []', () => {
expect(adapter.buildHeroReceipts(rows, 8)[0].grade).toBe('A+');
expect(adapter.buildHeroReceipts(rows, 1)).toHaveLength(1);
expect(adapter.buildHeroReceipts([], 5)).toEqual([]);
expect(adapter.buildHeroReceipts(null, 5)).toEqual([]);
});
});
// ── 3. heroFallbackState — the NEVER-EMPTY hero engine (#1, Part 6) ────────
describe('heroFallbackState — tonight wins, else receipts, never empty when proof exists', () => {
const settled = [{ player_name: 'Judge', stat: 'total_bases', line: 1.5, side: 'over', grade: 'A', outcome: 'hit', actual_value: 3 }];
test('tonight has graded props → mode tonight', () => {
const s = adapter.heroFallbackState([{ player: 'P', grade: 'A', confidence: 70 }], settled, 10);
expect(s.mode).toBe('tonight');
expect(s.items).toHaveLength(1);
});
test('tonight EMPTY but yesterday has A-tier hits → mode receipts (proof, not empty)', () => {
const s = adapter.heroFallbackState([], settled, 10);
expect(s.mode).toBe('receipts');
expect(s.items[0].player).toBe('Judge');
});
test('only truly empty when BOTH are absent', () => {
expect(adapter.heroFallbackState([], [], 10).mode).toBe('empty');
expect(adapter.heroFallbackState(null, null, 10).mode).toBe('empty');
});
});
// ── 4. pendingSummary — collapse the dead "Grades post" repetition (#14) ──
describe('pendingSummary — six pending rows collapse to ONE line', () => {
test('all-awaiting card → one summary counting props + players', () => {
const strips = [
{ player: 'One', props: [{ stat: 'TB', line: 1.5, awaiting: true }, { stat: 'HR', line: 0.5, awaiting: true }] },
{ player: 'Two', props: [{ stat: 'Hits', line: 0.5, awaiting: true }] },
];
const s = adapter.pendingSummary(strips);
expect(s).not.toBeNull();
expect(s.count).toBe(3);
expect(s.players).toBe(2);
});
test('any graded prop present → no collapse (the card has real reads to show)', () => {
const strips = [
{ player: 'One', props: [{ stat: 'TB', line: 1.5, grade: 'A' }] },
{ player: 'Two', props: [{ stat: 'Hits', line: 0.5, awaiting: true }] },
];
expect(adapter.pendingSummary(strips)).toBeNull();
});
test('no awaiting props at all → null', () => {
expect(adapter.pendingSummary([{ player: 'One', props: [] }])).toBeNull();
expect(adapter.pendingSummary([])).toBeNull();
expect(adapter.pendingSummary(null)).toBeNull();
});
});
// ── 5. topReadForCard — the ONE bold hero per card (#2) ────────────────────
describe('topReadForCard — the single best graded prop leads the card', () => {
const strips = [
{ player: 'Mid', team: 'NYY', props: [{ stat: 'Hits', line: 0.5, side: 'O', grade: 'B' }] },
{ player: 'Best', team: 'BOS', archetype: { primary: 'BOMBER' }, props: [{ stat: 'TB', line: 1.5, side: 'O', grade: 'A+' }] },
{ player: 'Dead', team: 'LAD', props: [{ stat: 'HR', line: 0.5, side: 'O', grade: 'A+', dead: true }] },
];
test('picks the highest-tier live grade (dead reads never lead)', () => {
const top = adapter.topReadForCard(strips);
expect(top).not.toBeNull();
expect(top.player).toBe('Best');
expect(top.grade).toBe('A+');
expect(top.stat).toBe('TB');
});
test('no graded props → null (nothing to promote)', () => {
expect(adapter.topReadForCard([{ player: 'X', props: [{ stat: 'TB', line: 1, awaiting: true }] }])).toBeNull();
expect(adapter.topReadForCard([])).toBeNull();
});
});
// ── 6. GameCard.tsx — one hero + pending collapse + real entities ─────────
describe('GameCard.tsx wires the DS2 rebuild', () => {
const src = read('components/vyndr/GameCard.tsx');
test('leads with the real team entity (logo) + a team-colored accent edge', () => {
expect(src).toContain('<TeamLogo');
expect(src).toContain('accentColor(g.home.abbr');
expect(src).toContain('borderLeft');
});
test('promotes ONE bold hero read via topReadForCard (large mono/tabular grade)', () => {
expect(src).toContain('topReadForCard');
expect(src).toMatch(/fontVariantNumeric:\s*'tabular-nums'/);
// the hero grade badge is the largest on the card (strips use size 'sm').
expect(src).toMatch(/size=\{48\}|size="lg"/);
});
test('collapses all-awaiting cards to ONE pending line (kills the #14 repetition)', () => {
expect(src).toContain('pendingSummary');
expect(src).toContain('nextRunLabelET');
expect(src).toContain('props pending');
});
});
// ── 7. dashboard/page.tsx — the never-empty hero + varying rankings ────────
describe('dashboard Top grades tonight — proof-first, ranked, honest', () => {
const src = read('app/dashboard/page.tsx');
test('uses the never-empty engine (heroFallbackState / buildHeroReceipts)', () => {
expect(src).toMatch(/heroFallbackState|buildHeroReceipts/);
});
test('ranks tonight grades by the varying signal (selectTopGrades) and shows confidence', () => {
expect(src).toContain('selectTopGrades');
expect(src).toContain('confidence');
});
test('falls back to yesterday PROVEN A-tier receipts (with the settled result)', () => {
expect(src).toContain('/api/ledger/model');
expect(src).toMatch(/HIT|PROVEN|YESTERDAY/);
});
test('still keeps the honest schedule-derived waiting copy for the truly-empty case (QA.22)', () => {
expect(src).toContain('nextRunLabelET');
});
});
+159 -47
View File
@@ -15,6 +15,11 @@ import { AccuracyBadge, Skeleton, SkeletonList } from '@/components/vyndr';
import { emptyStateCopy } from '@/lib/emptyState';
// Session 59 (work-order 2.3) — the real pipeline schedule for waiting states.
import { nextRunLabelET } from '@/lib/pipelineSchedule';
// DS2 (Design v2) — the never-empty hero engine: rank tonight's grades on a
// varying signal, and fall back to yesterday's PROVEN A-tier receipts so first
// paint ALWAYS proves the model (#1, #13, Part 6).
import { selectTopGrades, buildHeroReceipts } from '@/lib/slateAdapter';
import GradeBadge from '@/components/vyndr/GradeBadge';
import { currentAccessToken } from '@/lib/authToken';
type Sport = 'NBA' | 'MLB' | 'WNBA';
@@ -61,6 +66,32 @@ interface ParlayLegStat {
parlay_count: number;
}
// DS2 — a PROVEN receipt (yesterday's settled A-tier hit) for the never-empty hero.
interface HeroReceipt {
player: string;
stat: string;
line: number;
side: string;
grade: string;
sport: string;
outcome: string;
actual: number | null;
clvResult: string | null;
}
// The /api/ledger/model settled-row shape (subset we read for receipts).
interface ModelEntry {
player_name?: string;
sport?: string;
stat?: string;
line?: number;
side?: string;
grade?: string;
outcome?: string | null;
actual_value?: number | null;
clv_result?: string | null;
}
interface RecentScan {
id: string;
player_name: string;
@@ -86,6 +117,9 @@ export default function DashboardPage() {
const [sport, setSport] = useState<Sport>('NBA');
const [games, setGames] = useState<Game[] | null>(null);
const [topGrades, setTopGrades] = useState<TopGrade[] | null>(null);
// DS2 — yesterday's PROVEN receipts, fetched only when tonight has no grades
// yet (the never-empty hero fallback). null = not yet loaded.
const [heroReceipts, setHeroReceipts] = useState<HeroReceipt[] | null>(null);
const [mostParlayed, setMostParlayed] = useState<ParlayLegStat[] | null>(null);
const [recentScans, setRecentScans] = useState<RecentScan[] | null>(null);
// Session 49 — the user's primary sport tab + preferred books from prefs.
@@ -127,6 +161,7 @@ export default function DashboardPage() {
let cancelled = false;
setGames(null);
setTopGrades(null);
setHeroReceipts(null);
Promise.all([
fetch(`/api/games/tonight?sport=${sport}`).then((r) => r.json()).catch(() => ({ games: [] })),
@@ -163,6 +198,24 @@ export default function DashboardPage() {
};
}, [sport]);
// DS2 (#1, Part 6) — the NEVER-EMPTY hero fallback. When tonight's slate has
// no graded props yet, pull the model's most-recent settled reads and keep
// only the PROVEN A-tier hits (buildHeroReceipts). First paint always proves
// the model works (the real 55-19 / 74% record). Best-effort, self-hiding.
useEffect(() => {
if (topGrades === null || topGrades.length > 0) return; // tonight has grades → no fallback needed
let cancelled = false;
fetch(`/api/ledger/model?sport=${sport.toLowerCase()}&limit=80`)
.then((r) => (r.ok ? r.json() : null))
.then((data) => {
if (cancelled) return;
const entries: ModelEntry[] = Array.isArray(data?.entries) ? data.entries : [];
setHeroReceipts(buildHeroReceipts(entries, 8) as HeroReceipt[]);
})
.catch(() => { if (!cancelled) setHeroReceipts([]); });
return () => { cancelled = true; };
}, [topGrades, sport]);
// Most parlayed + recent scans don't depend on sport
useEffect(() => {
fetch('/api/props/most-parlayed')
@@ -253,57 +306,116 @@ export default function DashboardPage() {
}}
/>
{/* Top grades horizontal scroll */}
<Section
title="Top grades tonight"
subtitle="The A-tier picks. Tap to read or add to parlay."
>
{topGrades === null ? (
<SkeletonRow />
) : topGrades.length === 0 ? (
// Session 59 (work-order 2.3) — the real schedule, not passive waiting.
<p style={emptyCopy}>No grades yet. Grades post {nextRunLabelET() || 'on the next pipeline run'}.</p>
) : (
<div
style={{
display: 'flex',
gap: 12,
overflowX: 'auto',
padding: '4px 4px 12px',
scrollSnapType: 'x mandatory',
}}
{/* Top grades tonight — DS2 (#1, #13, Part 6). Tonight's grades RANKED on
the varying signal (selectTopGrades: tier → confidence → edge) so the
row isn't identical-weight noise; when tonight is empty we fall back to
yesterday's PROVEN A-tier receipts so first paint ALWAYS proves the
model. Only the truly-empty case shows the honest schedule copy. */}
{(() => {
const ranked = topGrades ? selectTopGrades(topGrades, 10) as TopGrade[] : [];
const proofMode = topGrades !== null && ranked.length === 0 && (heroReceipts?.length ?? 0) > 0;
return (
<Section
title="Top grades tonight"
subtitle={proofMode ? 'No slate graded yet — the models most recent proven A-tier reads.' : 'The A-tier picks, ranked by confidence. Tap to read or add to parlay.'}
>
{topGrades.map((g, i) => (
<button
key={`${g.player}-${g.stat}-${i}`}
onClick={() => router.push(`/scan?sport=${g.sport}&player=${encodeURIComponent(g.player)}&stat=${g.stat}&line=${g.line}`)}
className="surface diagonal-cut surface-hover"
{topGrades === null ? (
<SkeletonRow />
) : ranked.length > 0 ? (
<div
style={{
minWidth: 200,
padding: 16,
textAlign: 'left',
scrollSnapAlign: 'start',
cursor: 'pointer',
border: '1px solid var(--border)',
borderRadius: 16,
background: 'var(--bg-surface)',
color: 'inherit',
fontFamily: 'inherit',
display: 'flex',
gap: 12,
overflowX: 'auto',
padding: '4px 4px 12px',
scrollSnapType: 'x mandatory',
}}
>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 12 }}>
<SportPill sport={g.sport} />
<GradePill grade={g.grade} />
</div>
<h3 style={{ fontSize: 14, fontWeight: 600, marginBottom: 4 }}>{g.player}</h3>
<p className="mono" style={{ fontSize: 12, color: 'var(--text-secondary)', textTransform: 'capitalize' }}>
{g.direction} {g.line} {g.stat.replace(/_/g, ' ')}
</p>
</button>
))}
</div>
)}
</Section>
{ranked.map((g, i) => (
<button
key={`${g.player}-${g.stat}-${i}`}
onClick={() => router.push(`/scan?sport=${g.sport}&player=${encodeURIComponent(g.player)}&stat=${g.stat}&line=${g.line}`)}
className="surface diagonal-cut surface-hover"
style={{
minWidth: 200,
padding: 16,
textAlign: 'left',
scrollSnapAlign: 'start',
cursor: 'pointer',
border: '1px solid var(--border)',
borderRadius: 16,
background: 'var(--bg-surface)',
color: 'inherit',
fontFamily: 'inherit',
}}
>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 12 }}>
<SportPill sport={g.sport} />
<GradePill grade={g.grade} />
</div>
<h3 style={{ fontSize: 14, fontWeight: 600, marginBottom: 4 }}>{g.player}</h3>
<p className="mono" style={{ fontSize: 12, color: 'var(--text-secondary)', textTransform: 'capitalize' }}>
{g.direction} {g.line} {g.stat.replace(/_/g, ' ')}
</p>
{/* #13 — surface the VARYING signal so ranked rows read as a
real ranking, not identical noise. */}
{g.confidence != null && (
<p className="mono" style={{ fontSize: 11, color: 'var(--text-tertiary)', letterSpacing: '0.04em', marginTop: 6, fontVariantNumeric: 'tabular-nums' }}>
{g.confidence}% CONF
</p>
)}
</button>
))}
</div>
) : proofMode ? (
// The never-empty proof: yesterday's PROVEN A-tier receipts.
<div
style={{
display: 'flex',
gap: 12,
overflowX: 'auto',
padding: '4px 4px 12px',
scrollSnapType: 'x mandatory',
}}
>
{(heroReceipts || []).map((r, i) => (
<div
key={`${r.player}-${r.stat}-${i}`}
className="surface diagonal-cut"
style={{
minWidth: 210,
padding: 16,
scrollSnapAlign: 'start',
border: '1px solid var(--grade-a)',
borderRadius: 16,
background: 'var(--bg-surface)',
}}
>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 }}>
<span className="mono" style={{ fontSize: 9, fontWeight: 800, letterSpacing: '0.1em', color: 'var(--grade-a)' }}>YESTERDAY · PROVEN</span>
<GradeBadge grade={r.grade} size={30} glow />
</div>
<h3 style={{ fontSize: 14, fontWeight: 700, marginBottom: 4 }}>{r.player}</h3>
<p className="mono" style={{ fontSize: 12, color: 'var(--text-secondary)', textTransform: 'capitalize', fontVariantNumeric: 'tabular-nums' }}>
{String(r.side).toUpperCase().startsWith('U') ? 'under' : 'over'} {r.line} {String(r.stat).replace(/_/g, ' ')}
</p>
<p className="mono" style={{ fontSize: 12, fontWeight: 800, color: 'var(--grade-a)', marginTop: 8, fontVariantNumeric: 'tabular-nums' }}>
&#10003; HIT{r.actual != null ? ` (${r.actual})` : ''}
{r.clvResult === 'beat' ? <span style={{ color: 'var(--text-tertiary)', fontWeight: 700 }}> · CLV BEAT</span> : null}
</p>
</div>
))}
</div>
) : heroReceipts === null ? (
// tonight empty, fallback still loading → skeleton, never a text wall.
<SkeletonRow />
) : (
// Truly nothing yet — the honest schedule-derived waiting copy (QA.22).
<p style={emptyCopy}>No grades yet. Grades post {nextRunLabelET() || 'on the next pipeline run'}.</p>
)}
</Section>
);
})()}
{/* Tonight's games */}
<Section title={`Today's ${sport} games`} subtitle={slateEmpty ? null : `${games?.length ?? 0} game${games?.length === 1 ? '' : 's'} today`}>
+38 -1
View File
@@ -10,6 +10,8 @@ import TeamLogo from '@/components/vyndr/TeamLogo';
import { accentColor } from '@/lib/teamMeta';
import { playerHref } from '@/lib/playerHref';
import { isPreferredBook } from '@/lib/books';
import { pendingSummary, topReadForCard } from '@/lib/slateAdapter';
import { nextRunLabelET } from '@/lib/pipelineSchedule';
import { useParlay, legKey } from '@/contexts/ParlayContext';
export interface GameLine {
@@ -182,6 +184,12 @@ export default function GameCard({ game: g, onAddParlay, onOpen, preferredBooks
const [linesExpanded, setLinesExpanded] = useState(false);
const collapsed = useMemo(() => collapseStrips(g.playerStrips || []), [g.playerStrips]);
const stripsToRender = showAllReads ? collapsed.sorted : collapsed.visible;
// DS2 (#2) — the ONE bold hero per card: the single best live graded read,
// promoted large + mono + tabular. Everything else in the strips below stays
// demoted context. (#14) — when the card has NO graded reads yet, the awaiting
// props collapse to ONE pending line instead of six identical rows.
const topRead = useMemo(() => topReadForCard(g.playerStrips || []), [g.playerStrips]);
const pending = useMemo(() => pendingSummary(g.playerStrips || []), [g.playerStrips]);
// A1 S11 — LIVE SLATE MODE: any strip prop carrying live tracking shows the
// once-per-card label. Grades locked pre-game NEVER change in-game.
const isTracking = useMemo(
@@ -321,7 +329,36 @@ export default function GameCard({ game: g, onAddParlay, onOpen, preferredBooks
</span>
)}
</SectionHead>
{g.playerStrips && g.playerStrips.length > 0 ? (
{/* DS2 (#2) — ONE bold hero: the card's best read, large + mono +
tabular. The failure mode was every value at identical weight; this
promotes the single number that matters and demotes the rest. */}
{topRead && !pending && (
<div
style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 11, padding: '10px 12px', borderRadius: 9, background: 'var(--bg-2)', border: '1px solid var(--border-hi)' }}
>
<GradeBadge grade={topRead.grade} size={48} glow />
<div style={{ minWidth: 0, flex: 1 }}>
<div style={{ fontSize: 15, fontWeight: 800, letterSpacing: '-0.01em', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{topRead.player}</div>
<div className="mono" style={{ fontSize: 12, color: 'var(--text-1)', marginTop: 3, fontVariantNumeric: 'tabular-nums' }}>
{topRead.stat} {String(topRead.side).toUpperCase().startsWith('U') ? 'u' : 'o'}{topRead.line}
{topRead.team ? <span style={{ color: 'var(--text-2)' }}> · {topRead.team}</span> : null}
</div>
</div>
<span className="mono" style={{ fontSize: 9, fontWeight: 800, letterSpacing: '0.1em', color: 'var(--text-2)', flexShrink: 0 }}>TOP READ</span>
</div>
)}
{/* DS2 (#14) — collapse the dead "Grades post …" repetition. An
all-awaiting card renders ONE compact line, not six identical rows. */}
{pending ? (
<div className="mono" style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 12, color: 'var(--text-1)', flexWrap: 'wrap' }}>
<span style={{ fontWeight: 800, color: 'var(--text-0)', fontVariantNumeric: 'tabular-nums' }}>{pending.count}</span>
<span>props pending</span>
<span style={{ color: 'var(--text-2)' }}>·</span>
<span style={{ color: 'var(--text-2)' }}>{pending.players} player{pending.players === 1 ? '' : 's'}</span>
<span style={{ color: 'var(--text-2)' }}>·</span>
<span style={{ color: 'var(--text-2)', fontStyle: 'italic' }}>{nextRunLabelET() ? `grade ${nextRunLabelET()}` : 'grade on the next pipeline run'}</span>
</div>
) : g.playerStrips && g.playerStrips.length > 0 ? (
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
{stripsToRender.map((ps, i) => (
<StatStrip
+140
View File
@@ -421,6 +421,139 @@ function buildPlayerStripsFromProps(gameProps, gradeIndex, deltaIndex, now = Dat
});
}
// ── DS2 (Design v2) — Dashboard Slate Rebuild engine ────────────────
// Pure, testable functions behind the founder's #1 rebuild: rank the top
// grades on a VARYING signal (#13), promote ONE bold hero per card (#2),
// collapse the dead "Grades post …" repetition (#14), and the NEVER-EMPTY
// hero (#1, Part 6) — first paint ALWAYS proves the model.
const DS2_GRADE_RANK = {
'A+': 0, A: 1, 'A-': 2, 'B+': 3, B: 4, 'B-': 5, 'C+': 6, C: 7, 'C-': 8, D: 9, F: 10,
};
/** Grade → sortable tier rank (lower = better). Unknown → 99. */
function gradeRankOf(g) {
const k = String(g == null ? '' : g).trim().toUpperCase();
return DS2_GRADE_RANK[k] !== undefined ? DS2_GRADE_RANK[k] : 99;
}
const numOr = (v, fallback) => {
const n = typeof v === 'number' ? v : parseFloat(v);
return Number.isFinite(n) ? n : fallback;
};
/**
* #13 — rank tonight's grades by TIER, then the VARYING signal so a leaderboard
* of near-identical rows stops being noise: confidence desc, then |edge| desc,
* stable by input order. Drops gradeless rows. Returns at most `limit`.
*/
function selectTopGrades(grades, limit = 10) {
const arr = (Array.isArray(grades) ? grades : []).filter((g) => g && g.grade);
const scored = arr.map((g, idx) => ({
g,
idx,
rank: gradeRankOf(g.grade),
conf: numOr(g.confidence, -1),
edge: Math.abs(numOr(g.edge, -Infinity)),
}));
scored.sort((a, b) => a.rank - b.rank || b.conf - a.conf || b.edge - a.edge || a.idx - b.idx);
return scored.slice(0, Math.max(0, limit)).map((s) => s.g);
}
/**
* #1 / Part 6 — build PROVEN receipts from settled ledger rows: only A-tier
* grades that HIT (a miss never becomes proof). Carries the real result so the
* receipt is verifiable. Best-grade first. `settledRows` = /api/ledger/model
* entries (player_name, stat, line, side, grade, outcome, actual_value, …).
*/
function buildHeroReceipts(settledRows, limit = 8) {
const rows = (Array.isArray(settledRows) ? settledRows : []).filter(
(r) => r && String(r.outcome || '').toLowerCase() === 'hit' && gradeRankOf(r.grade) <= 2,
);
const mapped = rows.map((r) => ({
player: displayName(r.player_name || r.player || ''),
stat: r.stat,
line: r.line,
side: String(r.side || 'over'),
grade: r.grade,
sport: String(r.sport || '').toUpperCase(),
outcome: 'hit',
actual: r.actual_value != null ? r.actual_value : null,
clvResult: r.clv_result || null,
}));
mapped.sort((a, b) => gradeRankOf(a.grade) - gradeRankOf(b.grade));
return mapped.slice(0, Math.max(0, limit));
}
/**
* #1 / Part 6 — the never-empty hero engine. Tonight's ranked top grades win;
* when tonight is empty, fall back to yesterday's PROVEN A-tier receipts; only
* `empty` when both are absent. First paint ALWAYS proves the model when any
* settled proof exists.
*/
function heroFallbackState(tonightGrades, settledRows, limit = 10) {
const tonight = selectTopGrades(tonightGrades, limit);
if (tonight.length > 0) return { mode: 'tonight', items: tonight };
const receipts = buildHeroReceipts(settledRows, Math.min(limit, 8));
if (receipts.length > 0) return { mode: 'receipts', items: receipts };
return { mode: 'empty', items: [] };
}
/**
* #14 — collapse the dead per-prop "Grades post …" repetition. When a card has
* NO graded reads yet (every prop awaiting), returns a single summary
* ({ count, players, statLabels }) so the UI renders ONE compact line instead
* of six identical rows. Any graded prop present → null (there are real reads
* to show). No awaiting props → null.
*/
function pendingSummary(strips) {
const list = Array.isArray(strips) ? strips : [];
let awaiting = 0;
let graded = 0;
const players = new Set();
const statLabels = [];
for (const s of list) {
for (const p of s.props || []) {
if (p.grade) graded += 1;
else if (p.awaiting) {
awaiting += 1;
players.add(s.player);
if (statLabels.length < 6) statLabels.push(`${p.stat} ${p.line}`);
}
}
}
if (graded > 0 || awaiting === 0) return null;
return { count: awaiting, players: players.size, statLabels };
}
/**
* #2 — the ONE bold hero per card: the single highest-tier LIVE graded prop
* (dead/not-in reads never lead). Returns { player, team, archetype, stat,
* line, side, grade } or null when the card has no graded reads.
*/
function topReadForCard(strips) {
let best = null;
for (const s of Array.isArray(strips) ? strips : []) {
for (const p of s.props || []) {
if (!p.grade || p.dead) continue;
const rank = gradeRankOf(p.grade);
if (!best || rank < best.rank) {
best = {
rank,
player: s.player,
team: s.team || '',
archetype: s.archetype || null,
stat: p.stat,
line: p.line,
side: p.side || 'O',
grade: p.grade,
};
}
}
}
if (!best) return null;
const { rank, ...rest } = best; // eslint-disable-line no-unused-vars
return rest;
}
// ── MLB probable pitchers (Session 46) ──────────────────────────────
const teamToken = (name) => String(name == null ? '' : name).toLowerCase().replace(/[^a-z0-9 ]/g, '').trim();
const teamMascot = (name) => { const t = teamToken(name).split(' ').filter(Boolean); return t.length ? t[t.length - 1] : ''; };
@@ -468,4 +601,11 @@ module.exports = {
buildPlayerStripsFromProps,
buildPitcherMap,
pitchersForGameTeams,
// DS2 — dashboard rebuild engine.
gradeRankOf,
selectTopGrades,
buildHeroReceipts,
heroFallbackState,
pendingSummary,
topReadForCard,
};