/** * Pure (DB-free) mapping helpers for tennis draw sync, separated so the * scoring-critical logic — winner/loser assignment and which rounds score — can * be unit-tested without a database. */ import { normalizeTeamName } from "~/lib/normalize-team-name"; import type { ResolvedDrawMatch } from "~/models/playoff-match"; import type { FetchedDraw, FetchedPlayer } from "./types"; /** Stable identity key for a drawn player (prefer the source's external id). */ export function playerKey(p: FetchedPlayer): string { return p.externalId ? `id:${p.externalId}` : `name:${normalizeTeamName(p.name)}`; } /** * Canonical participant name for a drawn player. Section brackets abbreviate the * display name ("J Sinner"); the wikilink target (externalId) is the full name, * so prefer it. Strips the trailing Wikipedia disambiguator (e.g. "Arthur Fils * (tennis)" → "Arthur Fils") so the stored name and fuzzy matching use the plain * name — the raw externalId (with the disambiguator) is kept as the stable key. */ export function canonicalPlayerName(p: FetchedPlayer): string { return (p.externalId ?? p.name).replace(/\s*\([^)]*\)\s*$/, "").trim(); } /** * Turn a fetched draw into bracket rows with participants/winner resolved to ids. * `winnerId`/`loserId` are set only when both players resolved and the source * reported a winner. `isScoring` comes from the bracket template's round config. */ export function buildResolvedMatches( draw: FetchedDraw, keyToParticipantId: Map, roundIsScoring: Map, ): ResolvedDrawMatch[] { const resolved: ResolvedDrawMatch[] = []; for (const round of draw.rounds) { for (const m of round.matches) { const p1 = m.player1 ? keyToParticipantId.get(playerKey(m.player1)) ?? null : null; const p2 = m.player2 ? keyToParticipantId.get(playerKey(m.player2)) ?? null : null; let winnerId: string | null = null; let loserId: string | null = null; if (m.winner === 1) { winnerId = p1; loserId = p2; } else if (m.winner === 2) { winnerId = p2; loserId = p1; } resolved.push({ externalMatchId: m.externalMatchId, round: m.round, matchNumber: m.matchNumber, participant1Id: p1, participant2Id: p2, winnerId, loserId, isScoring: roundIsScoring.get(m.round) ?? false, }); } } return resolved; }