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:
@@ -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,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user