import type { FetchedStandingsRecord, StandingsSyncAdapter } from "./types"; const EPL_STANDINGS_URL = "https://api.football-data.org/v4/competitions/PL/standings"; interface FootballDataTeam { id: number; name: string; shortName?: string; tla?: string; } interface FootballDataTableEntry { position: number; team: FootballDataTeam; playedGames: number; form?: string | null; won: number; draw: number; lost: number; points: number; goalsFor: number; goalsAgainst: number; goalDifference: number; } interface FootballDataStanding { type: "TOTAL" | "HOME" | "AWAY" | string; table: FootballDataTableEntry[]; } interface FootballDataStandingsResponse { standings: FootballDataStanding[]; } /** * English Premier League standings adapter backed by football-data.org. * * Endpoint: GET /v4/competitions/PL/standings * Required env var: FOOTBALL_DATA_API_KEY */ export class EplStandingsAdapter implements StandingsSyncAdapter { constructor(private readonly apiKey = process.env.FOOTBALL_DATA_API_KEY) {} async fetchStandings(): Promise { if (!this.apiKey) { throw new Error( "FOOTBALL_DATA_API_KEY is required to sync EPL standings from football-data.org." ); } const response = await fetch(EPL_STANDINGS_URL, { headers: { "X-Auth-Token": this.apiKey, }, }); if (!response.ok) { throw new Error( `football-data.org EPL standings API returned ${response.status}: ${response.statusText}` ); } const json = (await response.json()) as FootballDataStandingsResponse; const total = json.standings?.find((standing) => standing.type === "TOTAL"); const table = total?.table ?? []; if (table.length === 0) { throw new Error( "football-data.org EPL standings API returned no TOTAL table entries — response shape may have changed" ); } return table .toSorted((a, b) => a.position - b.position) .map((entry): FetchedStandingsRecord => ({ teamName: entry.team.name, externalTeamId: String(entry.team.id), wins: entry.won, losses: entry.lost, ties: entry.draw, tablePoints: entry.points, goalsFor: entry.goalsFor, goalsAgainst: entry.goalsAgainst, goalDifference: entry.goalDifference, winPct: entry.playedGames > 0 ? entry.won / entry.playedGames : 0, gamesPlayed: entry.playedGames, leagueRank: entry.position, lastTen: entry.form ?? undefined, })); } }