// Shared interfaces and utilities for ESPN standings API adapters. // Used by MLB, NBA, WNBA, and MLS adapters. export interface EspnStat { name: string; displayName?: string; shortDisplayName?: string; description?: string; abbreviation?: string; type?: string; value?: number; displayValue?: string; } export interface EspnTeam { id: string; displayName: string; shortDisplayName?: string; abbreviation?: string; location?: string; name?: string; } export interface EspnStandingsEntry { team: EspnTeam; stats: EspnStat[]; } export interface EspnStandingsGroup { name?: string; standings?: { entries?: EspnStandingsEntry[] }; children?: EspnStandingsGroup[]; } export interface EspnStandingsResponse { children?: EspnStandingsGroup[]; } export function statsMap(stats: EspnStat[]): Map { const map = new Map(); for (const stat of stats) map.set(stat.name, stat); return map; } /** * Flatten an ESPN standings response into individual entries with their * conference and division context. * * Handles two layouts ESPN uses: * - conf → div → entries (NBA, MLB): division name is populated * - conf → entries (flat conferences): division is "" */ export function flattenEspnStandings( response: EspnStandingsResponse ): Array<{ entry: EspnStandingsEntry; conference: string; division: string }> { const results: Array<{ entry: EspnStandingsEntry; conference: string; division: string }> = []; for (const confGroup of response.children ?? []) { const conferenceName = confGroup.name ?? ""; if (confGroup.children && confGroup.children.length > 0) { for (const divGroup of confGroup.children) { const divisionName = divGroup.name ?? ""; for (const entry of divGroup.standings?.entries ?? []) { results.push({ entry, conference: conferenceName, division: divisionName }); } } } else { for (const entry of confGroup.standings?.entries ?? []) { results.push({ entry, conference: conferenceName, division: "" }); } } } return results; } /** * Extract the playoffSeed stat as a conference rank integer, or undefined if absent. * Only meaningful for team sports that use playoff seeding (MLB, NBA, WNBA). */ export function parseConferenceRank(sm: Map): number | undefined { const stat = sm.get("playoffSeed"); if (stat?.value !== undefined && stat.value !== null) return Math.round(stat.value); if (stat?.displayValue) { const parsed = parseInt(stat.displayValue, 10); return isNaN(parsed) ? undefined : parsed; } return undefined; } /** * Sort comparator for ESPN win-loss standings: wins descending, losses ascending, * alphabetical by team name as a stable tiebreaker. */ export function sortByWinLoss( a: { sm: Map; entry: EspnStandingsEntry }, b: { sm: Map; entry: EspnStandingsEntry } ): number { const winsA = a.sm.get("wins")?.value ?? 0; const winsB = b.sm.get("wins")?.value ?? 0; if (winsB !== winsA) return winsB - winsA; const lossA = a.sm.get("losses")?.value ?? 0; const lossB = b.sm.get("losses")?.value ?? 0; if (lossA !== lossB) return lossA - lossB; return a.entry.team.displayName.localeCompare(b.entry.team.displayName); }