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
+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,
};