schema: add canonical tournament & participant tables

Adds tournaments, participants (canonical), tournament_results, and
participant_surface_elos (canonical). Adds nullable tournament_id to
scoring_events and nullable participant_id to season_participants.
Phase 1b of canonical tournament layer migration.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Chris Parsons 2026-05-01 19:56:42 +00:00
parent ec10c9006c
commit 775b905069
No known key found for this signature in database

View file

@ -150,6 +150,12 @@ export const simulatorTypeEnum = pgEnum("simulator_type", [
"llws_bracket", "llws_bracket",
]); ]);
export const tournamentStatusEnum = pgEnum("tournament_status", [
"scheduled",
"in_progress",
"completed",
]);
export const playoffMatchGameStatusEnum = pgEnum("playoff_match_game_status", [ export const playoffMatchGameStatusEnum = pgEnum("playoff_match_game_status", [
"scheduled", "scheduled",
"complete", "complete",
@ -378,6 +384,9 @@ export const seasonParticipants = pgTable("season_participants", {
sportsSeasonId: uuid("sports_season_id") sportsSeasonId: uuid("sports_season_id")
.notNull() .notNull()
.references(() => sportsSeasons.id, { onDelete: "cascade" }), .references(() => sportsSeasons.id, { onDelete: "cascade" }),
participantId: uuid("participant_id").references(() => participants.id, {
onDelete: "restrict",
}),
name: varchar("name", { length: 255 }).notNull(), name: varchar("name", { length: 255 }).notNull(),
shortName: varchar("short_name", { length: 100 }), shortName: varchar("short_name", { length: 100 }),
externalId: varchar("external_id", { length: 255 }), externalId: varchar("external_id", { length: 255 }),
@ -389,6 +398,96 @@ export const seasonParticipants = pgTable("season_participants", {
uniqueIndex("participants_sports_season_name_unique").on(table.sportsSeasonId, table.name), uniqueIndex("participants_sports_season_name_unique").on(table.sportsSeasonId, table.name),
]); ]);
export const participants = pgTable(
"participants",
{
id: uuid("id").primaryKey().defaultRandom(),
sportId: uuid("sport_id")
.notNull()
.references(() => sports.id, { onDelete: "cascade" }),
name: varchar("name", { length: 255 }).notNull(),
externalKey: varchar("external_key", { length: 255 }),
metadata: jsonb("metadata"),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").defaultNow().notNull(),
},
(t) => ({
uniqueSportName: uniqueIndex("participants_sport_name_unique").on(
t.sportId,
t.name,
),
}),
);
export const tournaments = pgTable(
"tournaments",
{
id: uuid("id").primaryKey().defaultRandom(),
sportId: uuid("sport_id")
.notNull()
.references(() => sports.id, { onDelete: "cascade" }),
name: varchar("name", { length: 255 }).notNull(),
year: integer("year").notNull(),
startsAt: timestamp("starts_at"),
endsAt: timestamp("ends_at"),
surface: varchar("surface", { length: 50 }), // tennis only: hard | clay | grass
location: varchar("location", { length: 255 }),
status: tournamentStatusEnum("status").notNull().default("scheduled"),
externalKey: varchar("external_key", { length: 255 }),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").defaultNow().notNull(),
},
(t) => ({
uniqueSportNameYear: uniqueIndex("tournaments_sport_name_year_unique").on(
t.sportId,
t.name,
t.year,
),
}),
);
export const tournamentResults = pgTable(
"tournament_results",
{
id: uuid("id").primaryKey().defaultRandom(),
tournamentId: uuid("tournament_id")
.notNull()
.references(() => tournaments.id, { onDelete: "cascade" }),
participantId: uuid("participant_id")
.notNull()
.references(() => participants.id, { onDelete: "cascade" }),
placement: integer("placement"),
rawScore: decimal("raw_score", { precision: 10, scale: 2 }),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").defaultNow().notNull(),
},
(t) => ({
uniqueTournamentParticipant: uniqueIndex(
"tournament_results_tournament_participant_unique",
).on(t.tournamentId, t.participantId),
}),
);
export const participantSurfaceElos = pgTable(
"participant_surface_elos",
{
id: uuid("id").primaryKey().defaultRandom(),
participantId: uuid("participant_id")
.notNull()
.references(() => participants.id, { onDelete: "cascade" }),
worldRanking: integer("world_ranking"),
eloHard: integer("elo_hard"),
eloClay: integer("elo_clay"),
eloGrass: integer("elo_grass"),
updatedAt: timestamp("updated_at").defaultNow().notNull(),
},
(t) => ({
uniqueParticipant: uniqueIndex("participant_surface_elos_participant_unique").on(
t.participantId,
),
}),
);
export const seasonTemplateSports = pgTable("season_template_sports", { export const seasonTemplateSports = pgTable("season_template_sports", {
id: uuid("id").primaryKey().defaultRandom(), id: uuid("id").primaryKey().defaultRandom(),
templateId: uuid("template_id") templateId: uuid("template_id")
@ -435,6 +534,9 @@ export const scoringEvents = pgTable("scoring_events", {
sportsSeasonId: uuid("sports_season_id") sportsSeasonId: uuid("sports_season_id")
.notNull() .notNull()
.references(() => sportsSeasons.id, { onDelete: "cascade" }), .references(() => sportsSeasons.id, { onDelete: "cascade" }),
tournamentId: uuid("tournament_id").references(() => tournaments.id, {
onDelete: "set null",
}),
name: varchar("name", { length: 255 }).notNull(), // "2024 Masters", "Super Bowl LIX", etc. name: varchar("name", { length: 255 }).notNull(), // "2024 Masters", "Super Bowl LIX", etc.
eventDate: date("event_date"), eventDate: date("event_date"),
eventStartsAt: timestamp("event_starts_at", { withTimezone: true }), eventStartsAt: timestamp("event_starts_at", { withTimezone: true }),
@ -783,6 +885,8 @@ export const teamEvSnapshots = pgTable("team_ev_snapshots", {
// Relations // Relations
export const sportsRelations = relations(sports, ({ many }) => ({ export const sportsRelations = relations(sports, ({ many }) => ({
sportsSeasons: many(sportsSeasons), sportsSeasons: many(sportsSeasons),
tournaments: many(tournaments),
participants: many(participants),
})); }));
export const sportsSeasonsRelations = relations(sportsSeasons, ({ one, many }) => ({ export const sportsSeasonsRelations = relations(sportsSeasons, ({ one, many }) => ({
@ -803,10 +907,57 @@ export const seasonParticipantsRelations = relations(seasonParticipants, ({ one,
fields: [seasonParticipants.sportsSeasonId], fields: [seasonParticipants.sportsSeasonId],
references: [sportsSeasons.id], references: [sportsSeasons.id],
}), }),
canonicalParticipant: one(participants, {
fields: [seasonParticipants.participantId],
references: [participants.id],
}),
results: many(seasonParticipantResults), results: many(seasonParticipantResults),
regularSeasonStandings: many(regularSeasonStandings), regularSeasonStandings: many(regularSeasonStandings),
})); }));
export const participantsRelations = relations(participants, ({ one, many }) => ({
sport: one(sports, {
fields: [participants.sportId],
references: [sports.id],
}),
seasonParticipants: many(seasonParticipants),
tournamentResults: many(tournamentResults),
surfaceElo: one(participantSurfaceElos, {
fields: [participants.id],
references: [participantSurfaceElos.participantId],
}),
}));
export const tournamentsRelations = relations(tournaments, ({ one, many }) => ({
sport: one(sports, {
fields: [tournaments.sportId],
references: [sports.id],
}),
scoringEvents: many(scoringEvents),
tournamentResults: many(tournamentResults),
}));
export const tournamentResultsRelations = relations(tournamentResults, ({ one }) => ({
tournament: one(tournaments, {
fields: [tournamentResults.tournamentId],
references: [tournaments.id],
}),
participant: one(participants, {
fields: [tournamentResults.participantId],
references: [participants.id],
}),
}));
export const participantSurfaceElosRelations = relations(
participantSurfaceElos,
({ one }) => ({
participant: one(participants, {
fields: [participantSurfaceElos.participantId],
references: [participants.id],
}),
}),
);
export const seasonTemplatesRelations = relations(seasonTemplates, ({ many }) => ({ export const seasonTemplatesRelations = relations(seasonTemplates, ({ many }) => ({
seasonTemplateSports: many(seasonTemplateSports), seasonTemplateSports: many(seasonTemplateSports),
seasons: many(seasons), seasons: many(seasons),
@ -943,6 +1094,10 @@ export const scoringEventsRelations = relations(scoringEvents, ({ one, many }) =
fields: [scoringEvents.sportsSeasonId], fields: [scoringEvents.sportsSeasonId],
references: [sportsSeasons.id], references: [sportsSeasons.id],
}), }),
tournament: one(tournaments, {
fields: [scoringEvents.tournamentId],
references: [tournaments.id],
}),
eventResults: many(eventResults), eventResults: many(eventResults),
playoffMatches: many(playoffMatches), playoffMatches: many(playoffMatches),
tournamentGroups: many(tournamentGroups), tournamentGroups: many(tournamentGroups),