/** * Pure function(s) for extracting canonical tournament identity * (name + year) from a `scoring_events` row. * * Used by the Phase 2 backfill to group per-window events into * canonical `tournaments` rows. */ export interface ScoringEventInput { name: string; eventDate: string | null; eventType: string; } export interface TournamentIdentity { /** Tournament name with any trailing 4-digit year stripped. */ name: string; year: number; } const TRAILING_YEAR_RE = / (\d{4})$/; /** * Extracts the canonical `(name, year)` identity for a tournament * from a scoring event. * * Resolution order for year: * 1. A trailing 4-digit year on the event name (e.g. "Wimbledon 2026"). * The year is stripped from the returned name. * 2. The first 4 characters of `eventDate` (format `YYYY-MM-DD`). * * Throws if neither source supplies a year. */ export function extractTournamentIdentity( ev: ScoringEventInput, ): TournamentIdentity { const trimmedName = ev.name.trim(); const match = trimmedName.match(TRAILING_YEAR_RE); if (match) { const yearFromName = Number(match[1]); const nameWithoutYear = trimmedName.slice(0, match.index).trim(); return { name: nameWithoutYear, year: yearFromName }; } if (ev.eventDate) { const yearStr = ev.eventDate.slice(0, 4); const yearFromDate = Number(yearStr); if (Number.isFinite(yearFromDate) && yearStr.length === 4) { return { name: trimmedName, year: yearFromDate }; } } throw new Error( `cannot determine year for scoring event "${ev.name}" (eventDate=${ev.eventDate ?? "null"})`, ); }