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).
|
// 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 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 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.
|
// Session 46 — MLB probable pitchers response.
|
||||||
interface PitcherSide { team?: string | null; pitcher?: string | null; era?: number | null }
|
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[]>([]);
|
const [games, setGames] = useState<SlateGame[]>([]);
|
||||||
// Session 45 — merged pre-graded snapshot across the loaded sports.
|
// Session 45 — merged pre-graded snapshot across the loaded sports.
|
||||||
const [snapGrades, setSnapGrades] = useState<SnapshotGrade[]>([]);
|
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 [snapDeltas, setSnapDeltas] = useState<SnapshotDelta[]>([]);
|
||||||
const [pitcherGames, setPitcherGames] = useState<PitcherGame[]>([]);
|
const [pitcherGames, setPitcherGames] = useState<PitcherGame[]>([]);
|
||||||
// Session 64 (A1-S5) — prop viability (lineup confirmation + injury wire).
|
// 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 oddsGames = groupByGame(oddsProps, sport);
|
||||||
const scheduleGames = schedule?.games || [];
|
const scheduleGames = schedule?.games || [];
|
||||||
const merged = mergeSlate(sport, scheduleGames, oddsGames, lines?.games, streaksRes?.streaks);
|
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);
|
setGames(allGames);
|
||||||
setSnapGrades(allSnapGrades);
|
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);
|
setSnapDeltas(allSnapDeltas);
|
||||||
setPitcherGames(allPitcherGames);
|
setPitcherGames(allPitcherGames);
|
||||||
setViability({ lineups: mergedLineups || undefined, injuries: Object.keys(mergedInjuries).length ? mergedInjuries : undefined });
|
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') && (
|
{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></>
|
<><span style={{ color: '#3A3A48' }}>·</span><span style={{ color: 'var(--g-a)', fontWeight: 700 }}>{games.filter((g) => g.status === 'in').length} LIVE</span></>
|
||||||
)}
|
)}
|
||||||
{lastRefreshed && (
|
{/* P1-6 — DATA age from the pipeline's refreshed_at (the same field the
|
||||||
<><span style={{ color: '#3A3A48' }}>·</span><span title="The slate auto-refreshes every 60 seconds">UPDATED {freshLabel(lastRefreshed, nowTick)}</span></>
|
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>
|
</div>
|
||||||
<input
|
<input
|
||||||
|
|||||||
Reference in New Issue
Block a user