brackt/app/lib/tournament-identity.ts
Chris Parsons f3937d0d84
feat: auto-provision canonical tournaments and participants on create
Creating new qualifying scoring_events or season_participants now
auto-upserts the matching canonical rows. Needed so the check constraint
doesn't reject qualifying events, and so new season participants
flow through syncTournamentResults.

- Extract tournament identity shared between backfill and event-creation
  into app/lib/tournament-identity.ts; scripts/backfill re-exports it.
- createScoringEvent + bulkCreateScoringEvents now accept tournamentId.
- Event creation routes auto-compute (name, year) and upsert tournament.
- createParticipant + createManyParticipants auto-link to canonical
  participants by (sportId, name).

Phase 3 Task 7 of canonical tournament layer migration.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-01 22:32:16 +00:00

50 lines
1.4 KiB
TypeScript

/**
* Extracts the canonical tournament identity — `(name, year)` — from a scoring
* event. Used both by the Phase 2 backfill and by the admin create-event flow
* that auto-provisions a canonical tournament for new qualifying events.
*
* Rules:
* - If the event name ends with a 4-digit year, strip it; that year wins.
* - Otherwise, fall back to the year portion of eventDate (YYYY-MM-DD).
* - If neither produces a year, throw.
*/
export interface ScoringEventIdentityInput {
name: string;
eventDate: string | Date | null | undefined;
eventType: string;
}
export interface TournamentIdentity {
name: string;
year: number;
}
const YEAR_SUFFIX = /\s+(\d{4})\s*$/;
export function extractTournamentIdentity(ev: ScoringEventIdentityInput): TournamentIdentity {
const trimmed = ev.name.trim();
const yearMatch = trimmed.match(YEAR_SUFFIX);
let cleanName = trimmed;
let year: number | null = null;
if (yearMatch) {
year = parseInt(yearMatch[1], 10);
cleanName = trimmed.replace(YEAR_SUFFIX, "").trim();
}
if (year === null && ev.eventDate != null) {
const iso = ev.eventDate instanceof Date
? ev.eventDate.toISOString()
: String(ev.eventDate);
year = parseInt(iso.slice(0, 4), 10);
}
if (year === null || Number.isNaN(year)) {
throw new Error(
`cannot determine year for event "${ev.name}" — no year in name and eventDate is null`,
);
}
return { name: cleanName, year };
}