S6 (a1): display — the full picture under the grammar
- specs/ROW-GRAMMAR.md: the row grammar law (slot order, color law, mark
law, mobile stacking, no-truncation), locked by tests/unit/rowGrammar.
StatStrip violations fixed: MovementChip before the grade (market
context before model output); ViabilityChips after the archetype
(identity is one contiguous run).
- Line-movement sparklines: intradayRefreshService.trackHistory captures
real {t,line} points per grade (seeded with the lock, deduped when
flat, capped 24) inside the snapshot write-back; StatStrip.LineSparkline
renders at >=3 points (green toward / amber against / dim flat).
- Last-10 dot strips: services/last10Dots (streaksService accessors) ->
/api/snapshot/:sport attaches last10_dots from rosterlogs:{sport};
StatStrip.DotStrip renders vs the LOCKED line, newest first.
- CLV distribution: getModelAggregate emits clv_distribution (7 signed
buckets, outliers clamped) only past the centralized n>=20 gate;
ledger MODEL header renders the bar strip.
- Global search: SearchModal (cmd-K via GlobalHosts + window.__search),
players via /api/players/search per sport + static lib/teams.js
(soccer deliberately absent); Nav search icon + Search first in the
mobile More sheet. Explore tab untouched.
- Landing LCP: fade-up floored at opacity .6 (hero h1 contentful on
first frame, S33 visible-floor rule) + IBM Plex Mono preload:false
(4 decorative font files off the slow-4G critical path).
2654 -> 2698 tests (226 suites) green; web build exit 0.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -36,6 +36,7 @@ const STEAM_NOISE = 0.5; // ignore movement below this (both directions)
|
||||
const REGRADE_TRIGGER = 1.0; // moved-against threshold that triggers a re-grade
|
||||
const SNAP_TTL = 24 * 3600; // keep in sync with snapshotService
|
||||
const TICKER_MOVE_CAP = 6;
|
||||
const HISTORY_CAP = 24; // {t, line} points per grade (S6 sparklines)
|
||||
|
||||
const GRADE_RANK = { 'A+': 0, A: 1, 'A-': 2, 'B+': 3, B: 4, 'B-': 5, 'C+': 6, C: 7, 'C-': 8, D: 9, F: 10 };
|
||||
const rank = (g) => (g && GRADE_RANK[g] !== undefined ? GRADE_RANK[g] : 99);
|
||||
@@ -52,6 +53,36 @@ function indexOddsProps(props) {
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* S6 (A1 board) — line-history capture for the row sparklines. Appends the
|
||||
* CURRENT real feed line as a {t, line} point on the grade's movement
|
||||
* tracking, persisted in the snapshot this refresh already writes back
|
||||
* (zero new keys). Rules:
|
||||
* - real points only: an unparseable current line appends nothing;
|
||||
* - seed: an empty history first records the LOCKED line at its own
|
||||
* graded timestamp (a real captured value);
|
||||
* - dedupe: consecutive identical lines don't append — a point means the
|
||||
* line MOVED, so ≥3 points = a real movement story, not a flat pulse;
|
||||
* - cap: last HISTORY_CAP (24) points.
|
||||
*/
|
||||
function trackHistory(g, currentLine, ts) {
|
||||
const prev = Array.isArray(g.history) ? g.history : [];
|
||||
// Strict: Number(null) is 0 — a fabricated line (Data Semantics Rule).
|
||||
const current = currentLine == null ? NaN : Number(currentLine);
|
||||
if (!Number.isFinite(current)) return prev.length > 0 ? prev : undefined;
|
||||
let hist = prev;
|
||||
if (hist.length === 0) {
|
||||
const locked = g.gradedAt && g.gradedAt.line != null ? g.gradedAt.line : g.line;
|
||||
const lockedNum = Number(locked);
|
||||
if (locked != null && Number.isFinite(lockedNum)) {
|
||||
hist = [{ t: (g.gradedAt && g.gradedAt.timestamp) || ts, line: lockedNum }];
|
||||
}
|
||||
}
|
||||
const last = hist[hist.length - 1];
|
||||
if (!last || last.line !== current) hist = [...hist, { t: ts, line: current }];
|
||||
return hist.slice(-HISTORY_CAP);
|
||||
}
|
||||
|
||||
/** Signed movement RELATIVE TO THE GRADED SIDE: positive = toward (with).
|
||||
* Strict null-safe parse — Number(null) is 0, a fabricated line. */
|
||||
function signedDelta(side, lockedLine, currentLine) {
|
||||
@@ -110,9 +141,14 @@ async function runIntradayRefresh(sport, opts = {}) {
|
||||
if (!prop || prop.line == null || locked == null) { grades.push(g); continue; }
|
||||
checked += 1;
|
||||
|
||||
// S6 — capture the real current line into the grade's {t, line} history
|
||||
// (sparkline fuel). Rides inside the snapshot write below — no new keys.
|
||||
const history = trackHistory(g, prop.line, ts);
|
||||
const withHist = (obj) => (history ? { ...obj, history } : obj);
|
||||
|
||||
const delta = signedDelta(g.direction, locked, prop.line);
|
||||
if (delta == null || Math.abs(delta) < STEAM_NOISE) {
|
||||
grades.push({ ...g, movement: null });
|
||||
grades.push(withHist({ ...g, movement: null }));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -120,7 +156,7 @@ async function runIntradayRefresh(sport, opts = {}) {
|
||||
if (delta > 0) {
|
||||
// Moved WITH the grade — the market is chasing our number.
|
||||
steam += 1;
|
||||
grades.push({ ...g, movement: { kind: 'steam', delta, currentLine: current, at: ts } });
|
||||
grades.push(withHist({ ...g, movement: { kind: 'steam', delta, currentLine: current, at: ts } }));
|
||||
if (Math.abs(delta) >= REGRADE_TRIGGER) {
|
||||
moveEvents.push(moveEvent(sp, g, locked, current, ts));
|
||||
}
|
||||
@@ -129,7 +165,7 @@ async function runIntradayRefresh(sport, opts = {}) {
|
||||
|
||||
// Moved AGAINST the grade.
|
||||
if (Math.abs(delta) < REGRADE_TRIGGER) {
|
||||
grades.push({ ...g, movement: { kind: 'against', delta, currentLine: current, at: ts } });
|
||||
grades.push(withHist({ ...g, movement: { kind: 'against', delta, currentLine: current, at: ts } }));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -146,19 +182,19 @@ async function runIntradayRefresh(sport, opts = {}) {
|
||||
if (!res || !res.grade || res.insufficient_data || rank(res.grade) <= rank(g.grade)) {
|
||||
// Grade holds (or the model refuses to re-read) → better entry, same read.
|
||||
value += 1;
|
||||
grades.push({ ...g, movement: { kind: 'value', delta, currentLine: current, at: ts } });
|
||||
grades.push(withHist({ ...g, movement: { kind: 'value', delta, currentLine: current, at: ts } }));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Grade DROPS → public revision. Original grade preserved once, forever.
|
||||
revised += 1;
|
||||
const fromGrade = g.revised_from_grade || g.grade;
|
||||
grades.push({
|
||||
grades.push(withHist({
|
||||
...g,
|
||||
grade: res.grade,
|
||||
revised_from_grade: fromGrade,
|
||||
movement: { kind: 'revised', delta, currentLine: current, at: ts },
|
||||
});
|
||||
}));
|
||||
try {
|
||||
await deps.ledger.applyRevision(sp, {
|
||||
playerKey: nameKey(player), stat, line: Number(locked),
|
||||
@@ -213,5 +249,5 @@ module.exports = {
|
||||
runIntradayRefresh,
|
||||
runAllIntradayRefreshes,
|
||||
inSlateHours,
|
||||
__internals: { signedDelta, indexOddsProps, moveEvent, rank, STEAM_NOISE, REGRADE_TRIGGER },
|
||||
__internals: { signedDelta, indexOddsProps, moveEvent, rank, trackHistory, STEAM_NOISE, REGRADE_TRIGGER, HISTORY_CAP },
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user