P1-6 fix: one freshness source — Slate 'UPDATED' reads pipeline refreshed_at
Phone audit: the same screen showed green 'SIGNAL LIVE · UPDATED 0s ago' AND amber 'SYNC 46:03' — two components reading different fields. The Slate's 'UPDATED Xs ago' measured the CLIENT poll time (always ~0s, since it refetches every 60s), while the app-bar clock honestly measured the pipeline's data age (refreshed_at). '0s ago' claimed a freshness the data didn't have. Now the Slate captures snap.refreshed_at from the snapshot it already fetches (newest across sports) and 'UPDATED' shows THAT — the same source of truth as the clock. The clock still owns the amber/STALE reaction; the strip just states the honest data age. No more contradiction. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -175,7 +175,7 @@ interface StreaksResponse { streaks?: StreakApiRow[] }
|
||||
// Session 45 — pre-graded snapshot response (snapshot:{sport}:latest).
|
||||
interface SnapshotGrade { player?: string; player_name?: string; stat_type?: string; line?: number; direction?: string; grade?: string; archetype?: string | null; projection?: number | null; model_value?: number | null; gradedAt?: { line: number; odds?: number | null; timestamp?: string } | null }
|
||||
interface SnapshotDelta { player?: string; stat?: string; side?: string; delta?: number; direction?: string; currentLine?: number }
|
||||
interface SnapshotResponse { grades?: SnapshotGrade[]; deltas?: SnapshotDelta[] }
|
||||
interface SnapshotResponse { grades?: SnapshotGrade[]; deltas?: SnapshotDelta[]; updated_at?: string | null; refreshed_at?: string | null }
|
||||
|
||||
// Session 46 — MLB probable pitchers response.
|
||||
interface PitcherSide { team?: string | null; pitcher?: string | null; era?: number | null }
|
||||
@@ -583,6 +583,10 @@ export default function Slate({ initialTab = 'all', tier = 'free', preferredBook
|
||||
const [games, setGames] = useState<SlateGame[]>([]);
|
||||
// Session 45 — merged pre-graded snapshot across the loaded sports.
|
||||
const [snapGrades, setSnapGrades] = useState<SnapshotGrade[]>([]);
|
||||
// P1-6 — the PIPELINE's freshness (refreshed_at, the same field the app-bar
|
||||
// clock reads). "UPDATED Xs ago" must show the DATA age, not the client's poll
|
||||
// time — else the strip claims "0s ago" while the clock (honestly) says 46m.
|
||||
const [snapRefreshedAt, setSnapRefreshedAt] = useState<number | null>(null);
|
||||
const [snapDeltas, setSnapDeltas] = useState<SnapshotDelta[]>([]);
|
||||
const [pitcherGames, setPitcherGames] = useState<PitcherGame[]>([]);
|
||||
// Session 64 (A1-S5) — prop viability (lineup confirmation + injury wire).
|
||||
@@ -685,7 +689,7 @@ export default function Slate({ initialTab = 'all', tier = 'free', preferredBook
|
||||
const oddsGames = groupByGame(oddsProps, sport);
|
||||
const scheduleGames = schedule?.games || [];
|
||||
const merged = mergeSlate(sport, scheduleGames, oddsGames, lines?.games, streaksRes?.streaks);
|
||||
return { sport, merged, oddsOk, hadSchedule: scheduleGames.length > 0, snapGrades: snap?.grades || [], snapDeltas: snap?.deltas || [], pitcherGames: pitchersRes?.games || [], lineups: lineupsRes || null, injuries: injuriesRes?.byPlayer || null };
|
||||
return { sport, merged, oddsOk, hadSchedule: scheduleGames.length > 0, snapGrades: snap?.grades || [], snapDeltas: snap?.deltas || [], snapRefreshed: snap?.refreshed_at || snap?.updated_at || null, pitcherGames: pitchersRes?.games || [], lineups: lineupsRes || null, injuries: injuriesRes?.byPlayer || null };
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -717,6 +721,12 @@ export default function Slate({ initialTab = 'all', tier = 'free', preferredBook
|
||||
|
||||
setGames(allGames);
|
||||
setSnapGrades(allSnapGrades);
|
||||
// P1-6 — newest pipeline refreshed_at across the loaded sports (the real
|
||||
// data age, shared with the app-bar clock — one source of truth).
|
||||
{
|
||||
const stamps = perSport.map((s) => (s.snapRefreshed ? Date.parse(s.snapRefreshed) : NaN)).filter((t) => Number.isFinite(t));
|
||||
setSnapRefreshedAt(stamps.length ? Math.max(...stamps) : null);
|
||||
}
|
||||
setSnapDeltas(allSnapDeltas);
|
||||
setPitcherGames(allPitcherGames);
|
||||
setViability({ lineups: mergedLineups || undefined, injuries: Object.keys(mergedInjuries).length ? mergedInjuries : undefined });
|
||||
@@ -952,8 +962,12 @@ export default function Slate({ initialTab = 'all', tier = 'free', preferredBook
|
||||
{games.some((g) => g.status === 'in') && (
|
||||
<><span style={{ color: '#3A3A48' }}>·</span><span style={{ color: 'var(--g-a)', fontWeight: 700 }}>{games.filter((g) => g.status === 'in').length} LIVE</span></>
|
||||
)}
|
||||
{lastRefreshed && (
|
||||
<><span style={{ color: '#3A3A48' }}>·</span><span title="The slate auto-refreshes every 60 seconds">UPDATED {freshLabel(lastRefreshed, nowTick)}</span></>
|
||||
{/* P1-6 — DATA age from the pipeline's refreshed_at (the same field the
|
||||
app-bar clock reads), NOT the client poll time. "UPDATED 0s ago"
|
||||
while the clock said "SYNC 46:03" was the contradiction; both now
|
||||
read refreshed_at. The clock owns the amber/STALE reaction. */}
|
||||
{snapRefreshedAt && (
|
||||
<><span style={{ color: '#3A3A48' }}>·</span><span title="Data age — the pipeline's last refresh (one source of truth with the app-bar clock)">UPDATED {freshLabel(snapRefreshedAt, nowTick)}</span></>
|
||||
)}
|
||||
</div>
|
||||
<input
|
||||
|
||||
Reference in New Issue
Block a user