64 lines
2.4 KiB
TypeScript
64 lines
2.4 KiB
TypeScript
|
|
/**
|
||
|
|
* 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<string, string>,
|
||
|
|
roundIsScoring: Map<string, boolean>,
|
||
|
|
): 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;
|
||
|
|
}
|