Self-healing results backfill: re-fetch past match days at boot
The persistence table shipped empty just as ESPN's default scoreboard window rolled past matchday 1 — prod forgot both results. Rather than patching the DB by hand, boot now re-fetches the date-scoped scoreboard for any past day whose fixtures still look scheduled and merges + persists the results. Heals fresh databases and any future data loss the same way. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -113,6 +113,31 @@ export function startScheduler(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Self-healing results backfill: any PAST match day whose fixtures still
|
||||||
|
// look 'scheduled' (fresh DB, or results lost before persistence existed)
|
||||||
|
// gets its date-scoped ESPN scoreboard re-fetched and merged.
|
||||||
|
const resultsBackfill = async (): Promise<void> => {
|
||||||
|
const today = dateUTC(new Date().toISOString());
|
||||||
|
const dates = new Set<string>();
|
||||||
|
for (const f of state.allFixtures()) {
|
||||||
|
const d = dateUTC(f.kickoff);
|
||||||
|
if (d < today && f.status === 'scheduled' && f.home.team && f.away.team) dates.add(d);
|
||||||
|
}
|
||||||
|
for (const date of dates) {
|
||||||
|
if (stopped) return;
|
||||||
|
const t0 = Date.now();
|
||||||
|
try {
|
||||||
|
const matches = await fetchEspnScoreboard(date);
|
||||||
|
const changed = state.mergeLive(matches, 'espn');
|
||||||
|
for (const f of changed) saveFixtureResult(f.num, f.homeScore, f.awayScore, f.status);
|
||||||
|
if (changed.length) onChange();
|
||||||
|
logIngest('espn', 'results-backfill', true, Date.now() - t0, `${date}: ${changed.length} restored`);
|
||||||
|
} catch (e) {
|
||||||
|
logIngest('espn', 'results-backfill', false, Date.now() - t0, e instanceof Error ? e.message : String(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// One-time backfill: re-store summaries saved by an older normalizer (e.g.
|
// One-time backfill: re-store summaries saved by an older normalizer (e.g.
|
||||||
// before structured scorers existed), so started matches stay fully usable.
|
// before structured scorers existed), so started matches stay fully usable.
|
||||||
const backfillExt = async (): Promise<void> => {
|
const backfillExt = async (): Promise<void> => {
|
||||||
@@ -339,6 +364,7 @@ export function startScheduler(
|
|||||||
await mapAllFixtures().catch(() => {});
|
await mapAllFixtures().catch(() => {});
|
||||||
await liveTick().catch(() => {});
|
await liveTick().catch(() => {});
|
||||||
scheduleLive(); // live cadence starts now; slow one-time sweeps follow
|
scheduleLive(); // live cadence starts now; slow one-time sweeps follow
|
||||||
|
await resultsBackfill().catch(() => {}); // heal any forgotten past match days
|
||||||
// persist whatever the current scoreboard window knows — results learned
|
// persist whatever the current scoreboard window knows — results learned
|
||||||
// before this table existed would otherwise never be written
|
// before this table existed would otherwise never be written
|
||||||
for (const f of state.allFixtures()) {
|
for (const f of state.allFixtures()) {
|
||||||
|
|||||||
Reference in New Issue
Block a user