▍ humdrum codex / soft

Sports: per-league fetch windows (sportsball parity), drop daysAhead

2d5ec4c83c25b0c36c8e2fcbae52bf9a482a81dc
humdrum <me@humdrum.me> · 2026-07-08 16:05

parent 59a52605

2 files changed

lib/schemas/sources/sports.ts +0 −2
@@ -12,8 +12,6 @@   leagues: z.array(z.string()).default(["basketball/nba"]),
   // Favorite teams — only these teams' games are added, even from leagues not
   // listed above. So you can keep all of one league + single teams from others.
   teams: z.array(SportsTeam).default([]),
-  // How many days ahead to fetch schedules (0 = today only).
-  daysAhead: z.number().int().min(0).max(14).default(7),
 });
 export type SportsConfig = z.infer<typeof SportsConfig>;
 
lib/sources/modules/sports.ts +28 −10
@@ -21,6 +21,32 @@ }
 
 const gameKey = (g: Game) => `${g.league}|${g.away}|${g.home}|${g.startTime ?? ""}`;
 
+// Per-league fetch windows (days back / forward), mirrored from the sportsball TUI's
+// league catalog (~/Developer/Home/sportsball internal/model/league.go). Tuned to each
+// sport's cadence so the last result and the next game both surface. Unknown leagues use
+// a modest default.
+const LEAGUE_WINDOWS: Record<string, { back: number; forward: number }> = {
+  "soccer/fifa.world": { back: 7, forward: 7 },
+  "baseball/mlb": { back: 2, forward: 3 },
+  "basketball/nba": { back: 3, forward: 4 },
+  "basketball/wnba": { back: 3, forward: 4 },
+  "hockey/nhl": { back: 3, forward: 4 },
+  "football/nfl": { back: 8, forward: 8 },
+};
+const DEFAULT_WINDOW = { back: 1, forward: 7 };
+
+// YYYYMMDD strings spanning one league's window, oldest first.
+function windowDates(league: string): string[] {
+  const w = LEAGUE_WINDOWS[league] ?? DEFAULT_WINDOW;
+  const out: string[] = [];
+  for (let off = -w.back; off <= w.forward; off++) {
+    const d = new Date();
+    d.setDate(d.getDate() + off);
+    out.push(d.toISOString().slice(0, 10).replace(/-/g, ""));
+  }
+  return out;
+}
+
 export const sportsModule: SourceModule<Config, Payload> = {
   kind: "sports",
   label: "Scores",
@@ -33,14 +59,6 @@   isConfigured: (config) => config.leagues.length > 0 || config.teams.length > 0,
 
   async fetch({ config, signal }) {
     const collected = new Map<string, Game>();
-
-    // Build YYYYMMDD strings for today + daysAhead days.
-    function dateStr(offsetDays: number): string {
-      const d = new Date();
-      d.setDate(d.getDate() + offsetDays);
-      return d.toISOString().slice(0, 10).replace(/-/g, "");
-    }
-    const dates = Array.from({ length: (config.daysAhead ?? 7) + 1 }, (_, i) => dateStr(i));
 
     // Fetch one league's scoreboard for a given date. If teamFilter is given,
     // keep only games whose home/away matches one of those team substrings.
@@ -84,8 +102,8 @@       byLeague.set(league, [...(byLeague.get(league) ?? []), team]);
     }
 
     await Promise.all([
-      ...config.leagues.flatMap((l) => dates.map((d) => collect(l, d))),
-      ...[...byLeague].flatMap(([l, teams]) => dates.map((d) => collect(l, d, teams))),
+      ...config.leagues.flatMap((l) => windowDates(l).map((d) => collect(l, d))),
+      ...[...byLeague].flatMap(([l, teams]) => windowDates(l).map((d) => collect(l, d, teams))),
     ]);
 
     const games = [...collected.values()].sort(