From bcca8b76fa93be216efa2b785c531be7b160c126 Mon Sep 17 00:00:00 2001 From: Chris Parsons <438676+chrisparsons83@users.noreply.github.com> Date: Sat, 21 Mar 2026 00:12:01 -0700 Subject: [PATCH] Add regular season standings for NBA/NHL (fixes #89) (#192) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds live standings sync and display for bracket-based sports (NBA/NHL), so league members can see W/L tables and which teams their opponents drafted during the regular season — not just after the playoff bracket is set. - New `regular_season_standings` table with upsert-on-conflict sync - Standings sync service with NHL (api-web.nhle.com) and NBA (ESPN) adapters, externalId write-back for future syncs, and unmatched-team resolution UI - `RegularSeasonStandings` component: flat (NBA) + division/wild-card (NHL) modes, playoff line, TeamOwnerBadge, projected Brackt points (EV), mobile horizontal scroll - Admin "Sync Standings" card + "Resolve Unmatched" UI on sports season page - Admin manual standings edit hatch at /admin/sports-seasons/:id/regular-standings - Show standings above bracket until matches exist; below once bracket is set - `normalize-team-name` utility extracted to shared lib Co-authored-by: Claude Sonnet 4.6 --- .../sport-season/RegularSeasonStandings.tsx | 430 ++ .../__tests__/RegularSeasonStandings.test.tsx | 182 + app/lib/__tests__/normalize-team-name.test.ts | 48 + app/lib/normalize-team-name.ts | 49 + app/models/pending-standings-mappings.ts | 100 + app/models/regular-season-standings.ts | 200 + app/routes.ts | 4 + ...n.sports-seasons.$id.regular-standings.tsx | 253 ++ app/routes/admin.sports-seasons.$id.tsx | 228 +- ...d.sports-seasons.$sportsSeasonId.server.ts | 19 + ...eagueId.sports-seasons.$sportsSeasonId.tsx | 57 +- app/services/simulations/nba-simulator.ts | 6 +- .../standings-sync/__tests__/nba.test.ts | 171 + .../standings-sync/__tests__/nhl.test.ts | 141 + .../standings-sync/__tests__/sync.test.ts | 48 + app/services/standings-sync/f1.ts | 31 + app/services/standings-sync/index.ts | 139 + app/services/standings-sync/nba.ts | 156 + app/services/standings-sync/nhl.ts | 78 + app/services/standings-sync/thesportsdb.ts | 25 + app/services/standings-sync/types.ts | 34 + database/schema.ts | 74 + drizzle/0054_massive_vulcan.sql | 39 + drizzle/0055_special_vampiro.sql | 16 + drizzle/meta/0054_snapshot.json | 3802 ++++++++++++++++ drizzle/meta/0055_snapshot.json | 3889 +++++++++++++++++ drizzle/meta/_journal.json | 14 + 27 files changed, 10224 insertions(+), 9 deletions(-) create mode 100644 app/components/sport-season/RegularSeasonStandings.tsx create mode 100644 app/components/sport-season/__tests__/RegularSeasonStandings.test.tsx create mode 100644 app/lib/__tests__/normalize-team-name.test.ts create mode 100644 app/lib/normalize-team-name.ts create mode 100644 app/models/pending-standings-mappings.ts create mode 100644 app/models/regular-season-standings.ts create mode 100644 app/routes/admin.sports-seasons.$id.regular-standings.tsx create mode 100644 app/services/standings-sync/__tests__/nba.test.ts create mode 100644 app/services/standings-sync/__tests__/nhl.test.ts create mode 100644 app/services/standings-sync/__tests__/sync.test.ts create mode 100644 app/services/standings-sync/f1.ts create mode 100644 app/services/standings-sync/index.ts create mode 100644 app/services/standings-sync/nba.ts create mode 100644 app/services/standings-sync/nhl.ts create mode 100644 app/services/standings-sync/thesportsdb.ts create mode 100644 app/services/standings-sync/types.ts create mode 100644 drizzle/0054_massive_vulcan.sql create mode 100644 drizzle/0055_special_vampiro.sql create mode 100644 drizzle/meta/0054_snapshot.json create mode 100644 drizzle/meta/0055_snapshot.json diff --git a/app/components/sport-season/RegularSeasonStandings.tsx b/app/components/sport-season/RegularSeasonStandings.tsx new file mode 100644 index 0000000..eaa8636 --- /dev/null +++ b/app/components/sport-season/RegularSeasonStandings.tsx @@ -0,0 +1,430 @@ +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "~/components/ui/card"; +import { BarChart3 } from "lucide-react"; +import { TeamOwnerBadge } from "~/components/ui/team-owner-badge"; + +interface StandingRow { + id: string; + participantId: string; + wins: number; + losses: number; + otLosses: number | null; + winPct: string | null; + gamesPlayed: number; + gamesBack: string | null; + conference: string | null; + division: string | null; + conferenceRank: number | null; + divisionRank: number | null; + leagueRank: number | null; + streak: string | null; + lastTen: string | null; + homeRecord: string | null; + awayRecord: string | null; + syncedAt: string | null; + participant: { id: string; name: string; shortName?: string | null }; +} + +interface TeamOwnership { + teamName: string; + ownerName: string; + teamId: string; +} + +interface Props { + standings: StandingRow[]; + teamOwnerships: Record; + userParticipantIds: string[]; + showOtLosses?: boolean; + participantEvs?: Record; + /** How many teams per conference qualify for playoffs (flat mode only). 0 = no line. */ + playoffSpots?: number; + /** "flat" = single ranked list per conference (NBA). "nhl-divisions" = div top-3 + wild card. */ + displayMode?: "flat" | "nhl-divisions"; +} + +type RankMode = "conference" | "division" | "index"; + +interface TableSection { + heading?: string; + rows: StandingRow[]; + rankMode: RankMode; + playoffSpots: number; + showDivisionLabel?: boolean; +} + +// ─── Formatting ─────────────────────────────────────────────────────────────── + +function formatWinPct(winPct: string | null, wins: number, gamesPlayed: number): string { + if (winPct != null) { + const pct = parseFloat(winPct); + if (!isNaN(pct)) return pct.toFixed(3); + } + if (gamesPlayed > 0) return (wins / gamesPlayed).toFixed(3); + return "—"; +} + +function formatGB(gamesBack: string | null): string { + if (gamesBack == null) return "—"; + const gb = parseFloat(gamesBack); + if (isNaN(gb) || gb === 0) return "—"; + return gb % 1 === 0 ? gb.toString() : gb.toFixed(1); +} + +function getRank(row: StandingRow, idx: number, mode: RankMode): number { + if (mode === "division") return row.divisionRank ?? idx + 1; + if (mode === "index") return idx + 1; + return row.conferenceRank ?? idx + 1; +} + +// ─── Grouping ───────────────────────────────────────────────────────────────── + +function buildFlatSections( + standings: StandingRow[], + playoffSpots: number +): Array<{ conference: string; sections: TableSection[] }> { + const hasConferences = standings.some((s) => s.conference); + + if (!hasConferences) { + return [ + { + conference: "", + sections: [ + { + rows: [...standings].sort( + (a, b) => (a.leagueRank ?? 99) - (b.leagueRank ?? 99) + ), + rankMode: "conference", + playoffSpots, + }, + ], + }, + ]; + } + + const confMap = new Map(); + for (const row of standings) { + const conf = row.conference ?? "Other"; + if (!confMap.has(conf)) confMap.set(conf, []); + confMap.get(conf)!.push(row); + } + + return Array.from(confMap.entries()).map(([conference, rows]) => ({ + conference, + sections: [ + { + rows: [...rows].sort( + (a, b) => + (a.conferenceRank ?? a.leagueRank ?? 99) - + (b.conferenceRank ?? b.leagueRank ?? 99) + ), + rankMode: "conference" as RankMode, + playoffSpots, + showDivisionLabel: true, + }, + ], + })); +} + +function buildNhlSections( + standings: StandingRow[] +): Array<{ conference: string; sections: TableSection[] }> { + const confMap = new Map(); + for (const row of standings) { + const conf = row.conference ?? "Other"; + if (!confMap.has(conf)) confMap.set(conf, []); + confMap.get(conf)!.push(row); + } + + return Array.from(confMap.entries()).map(([conference, rows]) => { + const divMap = new Map(); + for (const row of rows) { + const div = row.division ?? ""; + if (!divMap.has(div)) divMap.set(div, []); + divMap.get(div)!.push(row); + } + + const divisions = Array.from(divMap.entries()) + .map(([name, divRows]) => ({ + name, + qualifiers: divRows + .filter((r) => (r.divisionRank ?? 99) <= 3) + .sort((a, b) => (a.divisionRank ?? 99) - (b.divisionRank ?? 99)), + })) + .sort( + (a, b) => + (a.qualifiers[0]?.conferenceRank ?? 99) - + (b.qualifiers[0]?.conferenceRank ?? 99) + ); + + const wildCard = rows + .filter((r) => (r.divisionRank ?? 99) > 3) + .sort( + (a, b) => + (a.conferenceRank ?? a.leagueRank ?? 99) - + (b.conferenceRank ?? b.leagueRank ?? 99) + ); + + const sections: TableSection[] = [ + ...divisions.map((div) => ({ + heading: `${div.name} Division`, + rows: div.qualifiers, + rankMode: "division" as RankMode, + playoffSpots: 0, + showDivisionLabel: false, + })), + ...(wildCard.length > 0 + ? [ + { + heading: "Wild Card", + rows: wildCard, + rankMode: "index" as RankMode, + playoffSpots: 2, + showDivisionLabel: true, + }, + ] + : []), + ]; + + return { conference, sections }; + }); +} + +// ─── Single table that renders all sections ─────────────────────────────────── + +function StandingsTable({ + sections, + teamOwnerships, + userParticipantIds, + showOtLosses, + participantEvs, + hasEvs, +}: { + sections: TableSection[]; + teamOwnerships: Record; + userParticipantIds: string[]; + showOtLosses: boolean; + participantEvs: Record; + hasEvs: boolean; +}) { + // # Team GP W L [OTL] [PTS] PCT GB L10 STK Mgr [PROJ] + // OTL and PTS are both shown for hockey (showOtLosses = true) + const totalCols = 10 + (showOtLosses ? 2 : 0) + (hasEvs ? 1 : 0); + + return ( +
+ + + + + + + + + {showOtLosses && } + {showOtLosses && } + + + + + + {hasEvs && } + + + + {sections.flatMap((section, sIdx) => { + const sectionRows: React.ReactNode[] = []; + let playoffLineShown = false; + + if (section.heading) { + sectionRows.push( + + + + ); + } + + section.rows.forEach((row, i) => { + const rank = getRank(row, i, section.rankMode); + + if (!playoffLineShown && section.playoffSpots > 0 && rank > section.playoffSpots) { + playoffLineShown = true; + sectionRows.push( + + + + ); + } + + const isUserTeam = userParticipantIds.includes(row.participantId); + const ownership = teamOwnerships[row.participantId]; + const ev = participantEvs[row.participantId]; + + sectionRows.push( + + + + + + + {showOtLosses && ( + + )} + {showOtLosses && ( + + )} + + + + + + {hasEvs && ( + + )} + + ); + }); + + return sectionRows; + })} + +
#TeamGPWLOTLPTSPCTGBL10STKMgrPROJ
0 ? "pt-5" : "pt-2" + }`} + > + {section.heading} +
+
+
+ + Playoff Line + +
+
+
{rank} + + {row.participant.shortName ?? row.participant.name} + + {section.showDivisionLabel && row.division && ( + + {row.division} + + )} + + {row.gamesPlayed} + {row.wins}{row.losses}{row.otLosses ?? 0} + {row.wins * 2 + (row.otLosses ?? 0)} + + {formatWinPct(row.winPct, row.wins, row.gamesPlayed)} + + {formatGB(row.gamesBack)} + + {row.lastTen ?? "—"} + + {row.streak ? ( + + {row.streak} + + ) : ( + + )} + + {ownership ? ( +
+ +
+ ) : ( + + )} +
+ {ev != null ? ( + + {parseFloat(ev).toFixed(1)} + + ) : ( + + )} +
+
+ ); +} + +// ─── Main export ────────────────────────────────────────────────────────────── + +export function RegularSeasonStandings({ + standings, + teamOwnerships, + userParticipantIds, + showOtLosses = false, + participantEvs = {}, + playoffSpots = 8, + displayMode = "flat", +}: Props) { + if (standings.length === 0) return null; + + const hasEvs = Object.keys(participantEvs).length > 0; + const lastSyncedAt = standings + .map((s) => s.syncedAt) + .filter(Boolean) + .sort() + .at(-1); + + const groups = + displayMode === "nhl-divisions" + ? buildNhlSections(standings) + : buildFlatSections(standings, playoffSpots); + + const tableProps = { teamOwnerships, userParticipantIds, showOtLosses, participantEvs, hasEvs }; + + return ( + + +
+ + + Regular Season Standings + + {lastSyncedAt && ( + + Updated {new Date(lastSyncedAt).toLocaleDateString()} + + )} +
+
+ + {groups.map((group) => ( +
+ {group.conference && ( +

+ {group.conference} Conference +

+ )} + +
+ ))} +
+
+ ); +} diff --git a/app/components/sport-season/__tests__/RegularSeasonStandings.test.tsx b/app/components/sport-season/__tests__/RegularSeasonStandings.test.tsx new file mode 100644 index 0000000..ddb6d71 --- /dev/null +++ b/app/components/sport-season/__tests__/RegularSeasonStandings.test.tsx @@ -0,0 +1,182 @@ +import { describe, it, expect } from "vitest"; +import { render, screen } from "@testing-library/react"; +import { RegularSeasonStandings } from "../RegularSeasonStandings"; + +function makeRow(overrides: Partial<{ + id: string; + participantId: string; + wins: number; + losses: number; + otLosses: number | null; + gamesPlayed: number; + winPct: string | null; + gamesBack: string | null; + conference: string | null; + division: string | null; + conferenceRank: number | null; + divisionRank: number | null; + leagueRank: number | null; + streak: string | null; + lastTen: string | null; + homeRecord: string | null; + awayRecord: string | null; + syncedAt: string | null; + participant: { id: string; name: string; shortName?: string | null }; +}> = {}) { + return { + id: overrides.id ?? "row-1", + participantId: overrides.participantId ?? "p-1", + wins: overrides.wins ?? 40, + losses: overrides.losses ?? 28, + otLosses: overrides.otLosses ?? null, + gamesPlayed: overrides.gamesPlayed ?? 68, + winPct: overrides.winPct ?? "0.5882", + gamesBack: overrides.gamesBack ?? null, + conference: overrides.conference ?? null, + division: overrides.division ?? null, + conferenceRank: overrides.conferenceRank ?? null, + divisionRank: overrides.divisionRank ?? null, + leagueRank: overrides.leagueRank ?? 1, + streak: overrides.streak ?? null, + lastTen: overrides.lastTen ?? null, + homeRecord: overrides.homeRecord ?? null, + awayRecord: overrides.awayRecord ?? null, + syncedAt: overrides.syncedAt ?? null, + participant: overrides.participant ?? { id: "p-1", name: "Boston Celtics" }, + }; +} + +const NO_OWNERSHIPS = {}; +const NO_USER_IDS: string[] = []; + +describe("RegularSeasonStandings", () => { + it("renders nothing when standings array is empty", () => { + const { container } = render( + + ); + expect(container.firstChild).toBeNull(); + }); + + it("renders team names", () => { + render( + + ); + expect(screen.getByText("Boston Celtics")).toBeInTheDocument(); + expect(screen.getByText("Toronto Raptors")).toBeInTheDocument(); + }); + + it("shows OTL column when showOtLosses=true", () => { + render( + + ); + expect(screen.getByText("OTL")).toBeInTheDocument(); + }); + + it("hides OTL column when showOtLosses=false (default)", () => { + render( + + ); + expect(screen.queryByText("OTL")).not.toBeInTheDocument(); + }); + + it("renders conference headings and inline division labels when present", () => { + render( + + ); + // Conference group headings are rendered as

+ expect(screen.getByText(/Eastern Conference/i)).toBeInTheDocument(); + expect(screen.getByText(/Western Conference/i)).toBeInTheDocument(); + // Division is now shown as an inline label per row (not a section header) + expect(screen.getAllByText(/Atlantic/i).length).toBeGreaterThan(0); + expect(screen.getAllByText(/Pacific/i).length).toBeGreaterThan(0); + }); + + it("shows ownership badge for drafted teams", () => { + render( + + ); + expect(screen.getByText("Alice")).toBeInTheDocument(); + }); + + it("does not show badge for undrafted teams", () => { + render( + + ); + // No owner badge should be present + expect(screen.queryByText(/Alice/i)).not.toBeInTheDocument(); + }); + + it("applies different styling for user's own team", () => { + const { container } = render( + + ); + // The user's row should have a primary styling class + const userRow = container.querySelector("tr.bg-primary\\/5"); + expect(userRow).not.toBeNull(); + }); + + it("renders streak with correct color class for wins", () => { + render( + + ); + const streakEl = screen.getByText("W5"); + expect(streakEl.className).toContain("emerald"); + }); + + it("renders streak with destructive class for losses", () => { + render( + + ); + const streakEl = screen.getByText("L3"); + expect(streakEl.className).toContain("destructive"); + }); +}); diff --git a/app/lib/__tests__/normalize-team-name.test.ts b/app/lib/__tests__/normalize-team-name.test.ts new file mode 100644 index 0000000..cf1c8d6 --- /dev/null +++ b/app/lib/__tests__/normalize-team-name.test.ts @@ -0,0 +1,48 @@ +import { describe, it, expect } from "vitest"; +import { normalizeTeamName, findMatchingTeamName } from "../normalize-team-name"; + +describe("normalizeTeamName", () => { + it("lowercases and trims", () => { + expect(normalizeTeamName(" Boston Celtics ")).toBe("boston celtics"); + }); + + it("collapses multiple spaces", () => { + expect(normalizeTeamName("Los Angeles Lakers")).toBe("los angeles lakers"); + }); + + it("is idempotent", () => { + const name = "oklahoma city thunder"; + expect(normalizeTeamName(name)).toBe(name); + }); +}); + +describe("findMatchingTeamName", () => { + const participants = [ + "Boston Celtics", + "Los Angeles Lakers", + "Oklahoma City Thunder", + "Golden State Warriors", + "Toronto Raptors", + ]; + + it("returns exact case-insensitive match", () => { + expect(findMatchingTeamName("Boston Celtics", participants)).toBe("Boston Celtics"); + expect(findMatchingTeamName("boston celtics", participants)).toBe("Boston Celtics"); + expect(findMatchingTeamName("TORONTO RAPTORS", participants)).toBe("Toronto Raptors"); + }); + + it("matches partial city+team via substring (e.g. API returns city only)", () => { + // "Golden State" is a substring of "Golden State Warriors" + expect(findMatchingTeamName("Golden State", participants)).toBe("Golden State Warriors"); + // "Oklahoma City" is a substring of "Oklahoma City Thunder" + expect(findMatchingTeamName("Oklahoma City", participants)).toBe("Oklahoma City Thunder"); + }); + + it("returns null for unmatched name", () => { + expect(findMatchingTeamName("Phoenix Suns", participants)).toBeNull(); + }); + + it("returns null for empty participants list", () => { + expect(findMatchingTeamName("Boston Celtics", [])).toBeNull(); + }); +}); diff --git a/app/lib/normalize-team-name.ts b/app/lib/normalize-team-name.ts new file mode 100644 index 0000000..39d17f8 --- /dev/null +++ b/app/lib/normalize-team-name.ts @@ -0,0 +1,49 @@ +/** + * Normalize a team name for fuzzy matching across data sources. + * Lowercases, trims, and collapses whitespace. + */ +export function normalizeTeamName(name: string): string { + return name.toLowerCase().trim().replace(/\s+/g, " "); +} + +/** + * Try to match `apiName` to one of the participant names. + * Returns the matched participant name, or null if no match found. + * + * Strategy: + * 1. Exact normalized match (case-insensitive) + * 2. Substring: one contains the other (handles "Golden State" vs "Golden State Warriors") + * + * Note: City abbreviations like "LA" ↔ "Los Angeles" are NOT handled here — the + * NBA/NHL APIs typically return full city names so the admin should enter full names + * in the participant list to ensure reliable matching. + */ +export function findMatchingTeamName( + apiName: string, + participantNames: string[] +): string | null { + const normalizedApi = normalizeTeamName(apiName); + + // Guard: empty API name should never match anything + if (!normalizedApi) return null; + + // 1. Exact match + for (const name of participantNames) { + if (normalizeTeamName(name) === normalizedApi) return name; + } + + // 2. Substring match (only when the shorter string is at least 4 chars to avoid false positives) + for (const name of participantNames) { + const normalizedParticipant = normalizeTeamName(name); + if ( + normalizedApi.length >= 4 && + normalizedParticipant.length >= 4 && + (normalizedApi.includes(normalizedParticipant) || + normalizedParticipant.includes(normalizedApi)) + ) { + return name; + } + } + + return null; +} diff --git a/app/models/pending-standings-mappings.ts b/app/models/pending-standings-mappings.ts new file mode 100644 index 0000000..359624e --- /dev/null +++ b/app/models/pending-standings-mappings.ts @@ -0,0 +1,100 @@ +import { database } from "~/database/context"; +import * as schema from "~/database/schema"; +import { eq, and } from "drizzle-orm"; +import type { FetchedStandingsRecord } from "~/services/standings-sync/types"; + +/** + * Upsert a pending mapping for an unmatched team from a standings sync. + * Uses onConflictDoUpdate so re-syncing refreshes the standing data. + */ +export async function upsertPendingStandingsMapping( + sportsSeasonId: string, + record: FetchedStandingsRecord, + providedDb?: ReturnType +) { + const db = providedDb || database(); + + const [row] = await db + .insert(schema.pendingStandingsMappings) + .values({ + sportsSeasonId, + externalTeamId: record.externalTeamId, + teamName: record.teamName, + standingData: record as unknown as Record, + }) + .onConflictDoUpdate({ + target: [ + schema.pendingStandingsMappings.sportsSeasonId, + schema.pendingStandingsMappings.externalTeamId, + ], + set: { + teamName: record.teamName, + standingData: record as unknown as Record, + }, + }) + .returning(); + + return row; +} + +/** + * Bulk upsert pending mappings for all unmatched teams in a sync. + */ +export async function upsertPendingStandingsMappings( + sportsSeasonId: string, + records: FetchedStandingsRecord[], + providedDb?: ReturnType +) { + const db = providedDb || database(); + return Promise.all(records.map((r) => upsertPendingStandingsMapping(sportsSeasonId, r, db))); +} + +/** + * Get all pending mappings for a sports season (awaiting admin resolution). + */ +export async function getPendingStandingsMappings( + sportsSeasonId: string, + providedDb?: ReturnType +) { + const db = providedDb || database(); + + return db.query.pendingStandingsMappings.findMany({ + where: eq(schema.pendingStandingsMappings.sportsSeasonId, sportsSeasonId), + orderBy: schema.pendingStandingsMappings.teamName, + }); +} + +/** + * Delete a resolved pending mapping by its externalTeamId. + */ +export async function deletePendingStandingsMapping( + sportsSeasonId: string, + externalTeamId: string, + providedDb?: ReturnType +) { + const db = providedDb || database(); + + await db + .delete(schema.pendingStandingsMappings) + .where( + and( + eq(schema.pendingStandingsMappings.sportsSeasonId, sportsSeasonId), + eq(schema.pendingStandingsMappings.externalTeamId, externalTeamId) + ) + ); +} + +/** + * Delete all pending mappings for a sports season. + * Used when manually clearing the queue. + */ +export async function deleteAllPendingStandingsMappings( + sportsSeasonId: string, + providedDb?: ReturnType +) { + const db = providedDb || database(); + + await db + .delete(schema.pendingStandingsMappings) + .where(eq(schema.pendingStandingsMappings.sportsSeasonId, sportsSeasonId)); +} diff --git a/app/models/regular-season-standings.ts b/app/models/regular-season-standings.ts new file mode 100644 index 0000000..78fc7c1 --- /dev/null +++ b/app/models/regular-season-standings.ts @@ -0,0 +1,200 @@ +import { database } from "~/database/context"; +import * as schema from "~/database/schema"; +import { eq, and, max } from "drizzle-orm"; + +export interface UpsertRegularSeasonStandingData { + participantId: string; + sportsSeasonId: string; + wins: number; + losses: number; + otLosses?: number | null; + ties?: number | null; + winPct?: number | null; + gamesPlayed: number; + gamesBack?: number | null; + conference?: string | null; + division?: string | null; + conferenceRank?: number | null; + divisionRank?: number | null; + leagueRank?: number | null; + streak?: string | null; + lastTen?: string | null; + homeRecord?: string | null; + awayRecord?: string | null; + externalTeamId?: string | null; + syncedAt?: Date | null; +} + +/** + * Bulk upsert regular season standings records. + * Uses onConflictDoUpdate on the (participantId, sportsSeasonId) unique index. + * Sets syncedAt = now() for auto-synced records; null means manually entered. + */ +export async function upsertRegularSeasonStandings( + records: UpsertRegularSeasonStandingData[], + providedDb?: ReturnType +) { + const db = providedDb || database(); + + if (records.length === 0) return []; + + const now = new Date(); + const values = records.map((r) => ({ + participantId: r.participantId, + sportsSeasonId: r.sportsSeasonId, + wins: r.wins, + losses: r.losses, + otLosses: r.otLosses ?? null, + ties: r.ties ?? null, + winPct: r.winPct != null ? r.winPct.toString() : null, + gamesPlayed: r.gamesPlayed, + gamesBack: r.gamesBack != null ? r.gamesBack.toString() : null, + conference: r.conference ?? null, + division: r.division ?? null, + conferenceRank: r.conferenceRank ?? null, + divisionRank: r.divisionRank ?? null, + leagueRank: r.leagueRank ?? null, + streak: r.streak ?? null, + lastTen: r.lastTen ?? null, + homeRecord: r.homeRecord ?? null, + awayRecord: r.awayRecord ?? null, + externalTeamId: r.externalTeamId ?? null, + syncedAt: r.syncedAt !== undefined ? r.syncedAt : now, + updatedAt: now, + })); + + return await db.transaction(async (tx) => { + return tx + .insert(schema.regularSeasonStandings) + .values(values) + .onConflictDoUpdate({ + target: [ + schema.regularSeasonStandings.participantId, + schema.regularSeasonStandings.sportsSeasonId, + ], + set: { + wins: schema.regularSeasonStandings.wins, + losses: schema.regularSeasonStandings.losses, + otLosses: schema.regularSeasonStandings.otLosses, + ties: schema.regularSeasonStandings.ties, + winPct: schema.regularSeasonStandings.winPct, + gamesPlayed: schema.regularSeasonStandings.gamesPlayed, + gamesBack: schema.regularSeasonStandings.gamesBack, + conference: schema.regularSeasonStandings.conference, + division: schema.regularSeasonStandings.division, + conferenceRank: schema.regularSeasonStandings.conferenceRank, + divisionRank: schema.regularSeasonStandings.divisionRank, + leagueRank: schema.regularSeasonStandings.leagueRank, + streak: schema.regularSeasonStandings.streak, + lastTen: schema.regularSeasonStandings.lastTen, + homeRecord: schema.regularSeasonStandings.homeRecord, + awayRecord: schema.regularSeasonStandings.awayRecord, + externalTeamId: schema.regularSeasonStandings.externalTeamId, + syncedAt: schema.regularSeasonStandings.syncedAt, + updatedAt: schema.regularSeasonStandings.updatedAt, + }, + }) + .returning(); + }); +} + +/** + * Upsert a single standing record as a manual admin override. + * Sets syncedAt = null to indicate manual entry (won't conflict with auto-sync). + */ +export async function upsertManualStanding( + participantId: string, + sportsSeasonId: string, + data: Omit, + providedDb?: ReturnType +) { + const results = await upsertRegularSeasonStandings( + [{ ...data, participantId, sportsSeasonId, syncedAt: null }], + providedDb + ); + return results[0]; +} + +/** + * Get all regular season standings for a sports season. + * Ordered by conference, division, then divisionRank (or leagueRank as fallback). + */ +export async function getRegularSeasonStandings( + sportsSeasonId: string, + providedDb?: ReturnType +) { + const db = providedDb || database(); + + const rows = await db.query.regularSeasonStandings.findMany({ + where: eq(schema.regularSeasonStandings.sportsSeasonId, sportsSeasonId), + with: { + participant: true, + }, + }); + + // Sort: conference → division → divisionRank ?? leagueRank ?? leagueRank, nulls last + return rows.sort((a, b) => { + const confA = a.conference ?? "ZZZ"; + const confB = b.conference ?? "ZZZ"; + if (confA !== confB) return confA.localeCompare(confB); + + const divA = a.division ?? "ZZZ"; + const divB = b.division ?? "ZZZ"; + if (divA !== divB) return divA.localeCompare(divB); + + const rankA = a.divisionRank ?? a.leagueRank ?? 999; + const rankB = b.divisionRank ?? b.leagueRank ?? 999; + return rankA - rankB; + }); +} + +/** + * Returns the most recent syncedAt timestamp across all records for a season. + * Returns null if no auto-synced records exist. + */ +export async function getLastSyncedAt( + sportsSeasonId: string, + providedDb?: ReturnType +): Promise { + const db = providedDb || database(); + + const result = await db + .select({ lastSync: max(schema.regularSeasonStandings.syncedAt) }) + .from(schema.regularSeasonStandings) + .where(eq(schema.regularSeasonStandings.sportsSeasonId, sportsSeasonId)); + + return result[0]?.lastSync ?? null; +} + +/** + * Delete all regular season standings for a sports season. + */ +export async function deleteRegularSeasonStandings( + sportsSeasonId: string, + providedDb?: ReturnType +) { + const db = providedDb || database(); + + await db + .delete(schema.regularSeasonStandings) + .where(eq(schema.regularSeasonStandings.sportsSeasonId, sportsSeasonId)); +} + +/** + * Get a single standing record for a participant in a season. + */ +export async function getParticipantStanding( + participantId: string, + sportsSeasonId: string, + providedDb?: ReturnType +) { + const db = providedDb || database(); + + return db.query.regularSeasonStandings.findFirst({ + where: and( + eq(schema.regularSeasonStandings.participantId, participantId), + eq(schema.regularSeasonStandings.sportsSeasonId, sportsSeasonId) + ), + with: { participant: true }, + }); +} diff --git a/app/routes.ts b/app/routes.ts index 74782c6..9ad2b36 100644 --- a/app/routes.ts +++ b/app/routes.ts @@ -85,6 +85,10 @@ export default [ "sports-seasons/:id/standings", "routes/admin.sports-seasons.$id.standings.tsx" ), + route( + "sports-seasons/:id/regular-standings", + "routes/admin.sports-seasons.$id.regular-standings.tsx" + ), route( "sports-seasons/:id/simulate", "routes/admin.sports-seasons.$id.simulate.tsx" diff --git a/app/routes/admin.sports-seasons.$id.regular-standings.tsx b/app/routes/admin.sports-seasons.$id.regular-standings.tsx new file mode 100644 index 0000000..183a0b4 --- /dev/null +++ b/app/routes/admin.sports-seasons.$id.regular-standings.tsx @@ -0,0 +1,253 @@ +import { Form, Link } from "react-router"; +import type { Route } from "./+types/admin.sports-seasons.$id.regular-standings"; + +import { findSportsSeasonById } from "~/models/sports-season"; +import { findParticipantsBySportsSeasonId } from "~/models/participant"; +import { getRegularSeasonStandings, upsertManualStanding } from "~/models/regular-season-standings"; +import { Button } from "~/components/ui/button"; +import { Input } from "~/components/ui/input"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "~/components/ui/card"; +import { ArrowLeft, Save, BarChart3 } from "lucide-react"; + +export function meta({ data }: Route.MetaArgs): Route.MetaDescriptors { + return [{ title: `Regular Season Standings — ${data?.sportsSeason?.name ?? "Sports Season"} - Brackt Admin` }]; +} + +export async function loader({ params }: Route.LoaderArgs) { + const sportsSeason = await findSportsSeasonById(params.id); + if (!sportsSeason) { + throw new Response("Sports season not found", { status: 404 }); + } + + const participants = await findParticipantsBySportsSeasonId(params.id); + const standingsRecords = await getRegularSeasonStandings(params.id); + + // Build a map of existing records keyed by participantId + const standingsMap = Object.fromEntries( + standingsRecords.map((r) => [r.participantId, r]) + ); + + return { + sportsSeason: sportsSeason as typeof sportsSeason & { sport: { id: string; name: string; type: string } }, + participants, + standingsMap, + }; +} + +export async function action({ request, params }: Route.ActionArgs) { + const formData = await request.formData(); + + const isNhl = formData.get("_isNhl") === "true"; + + // Parse updates from form fields: wins_, losses_, otLosses_, conference_, division_ + const byId = new Map< + string, + { + wins?: number; + losses?: number; + otLosses?: number | null; + conference?: string | null; + division?: string | null; + } + >(); + + for (const [key, value] of formData.entries()) { + const winsMatch = key.match(/^wins_(.+)$/); + const lossesMatch = key.match(/^losses_(.+)$/); + const otMatch = key.match(/^otLosses_(.+)$/); + const confMatch = key.match(/^conference_(.+)$/); + const divMatch = key.match(/^division_(.+)$/); + + if (winsMatch) { + const id = winsMatch[1]; + const wins = parseInt(value as string, 10); + if (!isNaN(wins)) byId.set(id, { ...byId.get(id), wins }); + } else if (lossesMatch) { + const id = lossesMatch[1]; + const losses = parseInt(value as string, 10); + if (!isNaN(losses)) byId.set(id, { ...byId.get(id), losses }); + } else if (otMatch) { + const id = otMatch[1]; + const otLosses = value === "" ? null : parseInt(value as string, 10); + byId.set(id, { ...byId.get(id), otLosses: isNaN(otLosses as number) ? null : otLosses }); + } else if (confMatch) { + const id = confMatch[1]; + byId.set(id, { ...byId.get(id), conference: (value as string).trim() || null }); + } else if (divMatch) { + const id = divMatch[1]; + byId.set(id, { ...byId.get(id), division: (value as string).trim() || null }); + } + } + + try { + for (const [participantId, data] of byId.entries()) { + if (data.wins == null && data.losses == null) continue; + const wins = data.wins ?? 0; + const losses = data.losses ?? 0; + const gamesPlayed = wins + losses + (data.otLosses ?? 0); + const winPct = gamesPlayed > 0 ? wins / gamesPlayed : 0; + + await upsertManualStanding(participantId, params.id, { + wins, + losses, + otLosses: data.otLosses ?? null, + gamesPlayed, + winPct, + conference: data.conference ?? null, + division: data.division ?? null, + }); + } + return { success: true }; + } catch (error) { + console.error("Error saving regular season standings:", error); + return { error: "Failed to save standings. Please try again." }; + } +} + +export default function ManageRegularStandings({ loaderData, actionData }: Route.ComponentProps) { + const { sportsSeason, participants, standingsMap } = loaderData; + const isNhl = sportsSeason.sport?.name?.toLowerCase().includes("nhl") || + sportsSeason.sport?.name?.toLowerCase().includes("hockey"); + + // Sort participants: those with records first (by wins desc), then unrecorded + const sorted = [...participants].sort((a, b) => { + const recA = standingsMap[a.id]; + const recB = standingsMap[b.id]; + if (recA && recB) return (recB.wins ?? 0) - (recA.wins ?? 0); + if (recA) return -1; + if (recB) return 1; + return a.name.localeCompare(b.name); + }); + + return ( +
+
+
+ +

Regular Season Standings

+

+ {sportsSeason.sport.name} — {sportsSeason.name} +

+

+ Manually enter W/L records. These will be overwritten on the next auto-sync unless you use this page again afterward. +

+
+ +
+ {actionData?.error && ( +
+ {actionData.error} +
+ )} + {actionData?.success && ( +
+ Standings saved. +
+ )} + + + + + + W/L Records + + + Enter wins, losses{isNhl ? ", and OT losses" : ""} for each team. Conference and division are optional but improve the standings display. + + + +
+ + +
+
+ Team + W + L + {isNhl && OTL} + Conference + Division +
+ + {sorted.map((participant) => { + const record = standingsMap[participant.id]; + const isSynced = record && record.syncedAt !== null; + return ( +
+ + {participant.name} + {isSynced && ( + (synced) + )} + + + + {isNhl && ( + + )} + + +
+ ); + })} +
+ + +
+
+
+
+
+
+ ); +} diff --git a/app/routes/admin.sports-seasons.$id.tsx b/app/routes/admin.sports-seasons.$id.tsx index 273c612..e37358d 100644 --- a/app/routes/admin.sports-seasons.$id.tsx +++ b/app/routes/admin.sports-seasons.$id.tsx @@ -1,4 +1,4 @@ -import { Form, Link, redirect, useNavigate } from "react-router"; +import { Form, Link, redirect, useNavigate, useNavigation } from "react-router"; import { getAuth } from "@clerk/react-router/server"; import { isUserAdminByClerkId } from "~/models/user"; import type { Route } from "./+types/admin.sports-seasons.$id"; @@ -11,6 +11,14 @@ import { database } from "~/database/context"; import { participantEvSnapshots, seasonSports } from "~/database/schema"; import { eq, desc } from "drizzle-orm"; import { getSimulatorInfo, type SimulatorType } from "~/services/simulations/registry"; +import { syncStandings } from "~/services/standings-sync/index"; +import { getLastSyncedAt } from "~/models/regular-season-standings"; +import { + getPendingStandingsMappings, + deletePendingStandingsMapping, +} from "~/models/pending-standings-mappings"; +import { updateParticipant } from "~/models/participant"; +import { upsertRegularSeasonStandings } from "~/models/regular-season-standings"; import { Button } from "~/components/ui/button"; import { Input } from "~/components/ui/input"; import { Label } from "~/components/ui/label"; @@ -41,7 +49,7 @@ import { } from "~/components/ui/alert-dialog"; import { Badge } from "~/components/ui/badge"; import { Checkbox } from "~/components/ui/checkbox"; -import { Trash2, Users, Trophy, Calculator, CheckCircle2, Zap, AlertTriangle, Loader2 } from "lucide-react"; +import { Trash2, Users, Trophy, Calculator, CheckCircle2, Zap, AlertTriangle, Loader2, RefreshCw } from "lucide-react"; import { useState } from "react"; export function meta({ data }: Route.MetaArgs): Route.MetaDescriptors { @@ -72,11 +80,23 @@ export async function loader({ params }: Route.LoaderArgs) { ? getSimulatorInfo(sportsSeason.sport.simulatorType as SimulatorType) : null; + const lastStandingsSyncedAt = + sportsSeason.sport?.type === "team" + ? await getLastSyncedAt(params.id) + : null; + + const pendingMappings = + sportsSeason.sport?.type === "team" + ? await getPendingStandingsMappings(params.id) + : []; + return { sportsSeason, participants, lastSimulatedDate, simulatorInfo, + lastStandingsSyncedAt: lastStandingsSyncedAt?.toISOString() ?? null, + pendingMappings, }; } @@ -114,6 +134,76 @@ export async function action(args: Route.ActionArgs) { } } + if (intent === "sync-standings") { + try { + const result = await syncStandings(params.id); + return { success: true, intent: "sync-standings", syncResult: result }; + } catch (error) { + console.error("Error syncing standings:", error); + return { + syncError: + error instanceof Error ? error.message : "Failed to sync standings. Please try again.", + }; + } + } + + if (intent === "resolve-mapping") { + const externalTeamId = formData.get("externalTeamId"); + const participantId = formData.get("participantId"); + const standingDataRaw = formData.get("standingData"); + + if ( + typeof externalTeamId !== "string" || + !externalTeamId.trim() || + typeof participantId !== "string" || + !participantId.trim() || + typeof standingDataRaw !== "string" + ) { + return { error: "Invalid resolve-mapping payload." }; + } + + try { + const standingData = JSON.parse(standingDataRaw) as Record; + + // Write externalId onto the participant for future ID-first matching + await updateParticipant(participantId, { externalId: externalTeamId }); + + // Upsert the standing record from the stored standingData + await upsertRegularSeasonStandings([ + { + participantId, + sportsSeasonId: params.id, + wins: (standingData.wins as number) ?? 0, + losses: (standingData.losses as number) ?? 0, + otLosses: (standingData.otLosses as number | null) ?? null, + ties: (standingData.ties as number | null) ?? null, + winPct: (standingData.winPct as number) ?? 0, + gamesPlayed: (standingData.gamesPlayed as number) ?? 0, + gamesBack: (standingData.gamesBack as number | null) ?? null, + conference: (standingData.conference as string | null) ?? null, + division: (standingData.division as string | null) ?? null, + conferenceRank: (standingData.conferenceRank as number | null) ?? null, + divisionRank: (standingData.divisionRank as number | null) ?? null, + leagueRank: (standingData.leagueRank as number) ?? 0, + streak: (standingData.streak as string | null) ?? null, + lastTen: (standingData.lastTen as string | null) ?? null, + homeRecord: (standingData.homeRecord as string | null) ?? null, + awayRecord: (standingData.awayRecord as string | null) ?? null, + externalTeamId, + syncedAt: new Date(), + }, + ]); + + // Remove from pending queue + await deletePendingStandingsMapping(params.id, externalTeamId); + + return { success: true, intent: "resolve-mapping", resolvedTeam: String(standingData.teamName ?? "") }; + } catch (error) { + console.error("Error resolving mapping:", error); + return { error: "Failed to resolve mapping. Please try again." }; + } + } + if (intent === "finalize-standings") { try { await processSeasonStandings(params.id); @@ -195,8 +285,12 @@ export async function action(args: Route.ActionArgs) { } export default function EditSportsSeason({ loaderData, actionData }: Route.ComponentProps) { - const { sportsSeason, participants, lastSimulatedDate, simulatorInfo } = loaderData; + const { sportsSeason, participants, lastSimulatedDate, simulatorInfo, lastStandingsSyncedAt, pendingMappings } = loaderData; const navigate = useNavigate(); + const navigation = useNavigation(); + const isSyncingStandings = + navigation.state === "submitting" && + (navigation.formData?.get("intent") as string) === "sync-standings"; const [scoringPattern, setScoringPattern] = useState(sportsSeason.scoringPattern || ""); return ( @@ -498,6 +592,134 @@ export default function EditSportsSeason({ loaderData, actionData }: Route.Compo + {sportsSeason.sport?.type === "team" && ( + + +
+
+ Regular Season Standings + + Sync current W/L standings from the official API, or edit manually. + {lastStandingsSyncedAt && ( + + Last synced: {new Date(lastStandingsSyncedAt).toLocaleString()}. + + )} + +
+
+ +
+ + +
+
+
+
+ {(actionData?.success && actionData.intent === "sync-standings") || actionData?.syncError ? ( + + {actionData?.success && actionData.intent === "sync-standings" && actionData.syncResult && ( +
+
+ Synced {actionData.syncResult.synced} team{actionData.syncResult.synced !== 1 ? "s" : ""} successfully. +
+ {actionData.syncResult.unmatched.length > 0 && ( +
+

+ {actionData.syncResult.unmatched.length} team{actionData.syncResult.unmatched.length !== 1 ? "s" : ""} could not be matched to participants: +

+
    + {actionData.syncResult.unmatched.map((u: { teamName: string; externalTeamId: string }) => ( +
  • {u.teamName}
  • + ))} +
+

+ Use the "Unmatched Teams" card below to assign these to participants. Future syncs will use the saved ID. +

+
+ )} +
+ )} + {actionData?.syncError && ( +
+ {actionData.syncError} +
+ )} +
+ ) : null} +
+ )} + + {sportsSeason.sport?.type === "team" && pendingMappings.length > 0 && ( + + + + + Unmatched Teams ({pendingMappings.length}) + + + These teams from the last sync could not be automatically matched to a participant. + Assign each one to the correct participant to resolve. + + + + {actionData?.success && actionData.intent === "resolve-mapping" && ( +
+ Resolved: {actionData.resolvedTeam} +
+ )} + {pendingMappings.map((mapping) => ( +
+ + + +
+

{mapping.teamName}

+

ID: {mapping.externalTeamId}

+
+ + +
+ ))} +
+
+ )} + {sportsSeason.scoringPattern === "season_standings" && ( diff --git a/app/routes/leagues/$leagueId.sports-seasons.$sportsSeasonId.server.ts b/app/routes/leagues/$leagueId.sports-seasons.$sportsSeasonId.server.ts index 9f9b020..05ba516 100644 --- a/app/routes/leagues/$leagueId.sports-seasons.$sportsSeasonId.server.ts +++ b/app/routes/leagues/$leagueId.sports-seasons.$sportsSeasonId.server.ts @@ -15,6 +15,8 @@ import { getUpcomingScoringEvents, getRecentCompletedEvents, } from "~/models/scoring-event"; +import { getRegularSeasonStandings } from "~/models/regular-season-standings"; +import { getAllParticipantEVsForSeason } from "~/models/participant-expected-value"; import { getBracketTemplate, getOrderedRoundsFromMatches } from "~/lib/bracket-templates"; import { database } from "~/database/context"; import * as schema from "~/database/schema"; @@ -242,9 +244,24 @@ export async function loader(args: Route.LoaderArgs) { getRecentCompletedEvents(sportsSeasonId, 3), ]); + // Fetch regular season standings for team-sport playoff_bracket seasons (NBA, NHL) + const isTeamBracket = + scoringPattern === "playoff_bracket" && sportsSeason.sport?.type === "team"; + const [regularSeasonStandings, regularSeasonEvs] = isTeamBracket + ? await Promise.all([ + getRegularSeasonStandings(sportsSeasonId), + getAllParticipantEVsForSeason(sportsSeasonId), + ]) + : [[], []]; + // Derive seasonIsFinalized from status const seasonIsFinalized = sportsSeason.status === "completed"; + // Build participantId → expectedValue map for display + const participantEvs = Object.fromEntries( + regularSeasonEvs.map((ev) => [ev.participantId, ev.expectedValue]) + ); + return { league, season, @@ -262,5 +279,7 @@ export async function loader(args: Route.LoaderArgs) { upcomingEvents, recentEvents, seasonIsFinalized, + regularSeasonStandings, + participantEvs, }; } diff --git a/app/routes/leagues/$leagueId.sports-seasons.$sportsSeasonId.tsx b/app/routes/leagues/$leagueId.sports-seasons.$sportsSeasonId.tsx index a968a6c..cc3df58 100644 --- a/app/routes/leagues/$leagueId.sports-seasons.$sportsSeasonId.tsx +++ b/app/routes/leagues/$leagueId.sports-seasons.$sportsSeasonId.tsx @@ -4,6 +4,7 @@ import type { Route } from "./+types/$leagueId.sports-seasons.$sportsSeasonId"; import { loader } from "./$leagueId.sports-seasons.$sportsSeasonId.server"; import { SportSeasonDisplay } from "~/components/scoring/SportSeasonDisplay"; import { EventSchedule } from "~/components/sport-season/EventSchedule"; +import { RegularSeasonStandings } from "~/components/sport-season/RegularSeasonStandings"; import { Button } from "~/components/ui/button"; import { Badge } from "~/components/ui/badge"; import { ArrowLeft, CheckCircle2, Clock, Zap } from "lucide-react"; @@ -62,8 +63,29 @@ export default function SportSeasonDetail({ upcomingEvents, recentEvents, seasonIsFinalized, + regularSeasonStandings, + participantEvs, } = loaderData; + const hasBracket = playoffMatches && playoffMatches.length > 0; + const hasStandings = regularSeasonStandings && regularSeasonStandings.length > 0; + const showOtLosses = hasStandings && regularSeasonStandings.some((s: any) => s.otLosses != null); + + const simulatorType = sportsSeason.sport?.simulatorType; + const standingsDisplayMode = + simulatorType === "nhl_bracket" ? "nhl-divisions" : "flat"; + // NBA: 10 teams make the postseason (6 auto-qualify + 4 play-in) + // NHL: handled by the nhl-divisions mode (3 per div + 2 wild cards) + const playoffSpots = simulatorType === "nba_bracket" ? 10 : 8; + + // Build ownership map for RegularSeasonStandings + const ownershipMap = Object.fromEntries( + teamOwnerships.map((o: any) => [ + o.participantId, + { teamName: o.teamName, ownerName: o.ownerName ?? "", teamId: o.teamId }, + ]) + ); + return (
@@ -87,6 +109,21 @@ export default function SportSeasonDetail({
+ {/* Regular season standings — show above bracket when no bracket exists yet */} + {hasStandings && !hasBracket && ( +
+ +
+ )} + {/* Event schedule - hidden for bracket sports (the bracket itself is the event) */} {scoringPattern !== "playoff_bracket" && (upcomingEvents.length > 0 || recentEvents.length > 0) && ( @@ -98,7 +135,8 @@ export default function SportSeasonDetail({ )} - + />} + + {/* Regular season standings — show below bracket once bracket exists */} + {hasStandings && hasBracket && ( +
+ +
+ )} ); } diff --git a/app/services/simulations/nba-simulator.ts b/app/services/simulations/nba-simulator.ts index 9ea1e8f..147ecfc 100644 --- a/app/services/simulations/nba-simulator.ts +++ b/app/services/simulations/nba-simulator.ts @@ -44,6 +44,7 @@ import { database } from "~/database/context"; import { eq } from "drizzle-orm"; import * as schema from "~/database/schema"; import type { Simulator, SimulationResult } from "./types"; +import { normalizeTeamName } from "~/lib/normalize-team-name"; // ─── Simulation parameters ──────────────────────────────────────────────────── @@ -211,10 +212,7 @@ const TEAMS_DATA: Record = { // ─── Public helpers (exported for unit testing) ─────────────────────────────── -/** Normalize a team name for lookup (lowercase, trimmed, collapsed whitespace). */ -export function normalizeTeamName(name: string): string { - return name.toLowerCase().trim().replace(/\s+/g, " "); -} +export { normalizeTeamName }; /** Look up team data by participant name (case-insensitive). */ export function getTeamData(name: string): NbaTeamData | undefined { diff --git a/app/services/standings-sync/__tests__/nba.test.ts b/app/services/standings-sync/__tests__/nba.test.ts new file mode 100644 index 0000000..39a5e70 --- /dev/null +++ b/app/services/standings-sync/__tests__/nba.test.ts @@ -0,0 +1,171 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { NbaStandingsAdapter } from "../nba"; + +function makeStat(name: string, value: number, displayValue?: string) { + return { name, value, displayValue: displayValue ?? String(value) }; +} + +const SAMPLE_NBA_RESPONSE = { + children: [ + { + name: "Eastern Conference", + children: [ + { + name: "Atlantic Division", + standings: { + entries: [ + { + team: { id: "2", displayName: "Boston Celtics", abbreviation: "BOS" }, + stats: [ + makeStat("wins", 52), + makeStat("losses", 16), + makeStat("winPercent", 0.765), + makeStat("gamesBehind", 0), + makeStat("playoffSeed", 1), + { name: "streak", value: 5, displayValue: "W5" }, + makeStat("homeWins", 28), + makeStat("homeLosses", 7), + makeStat("awayWins", 24), + makeStat("awayLosses", 9), + ], + }, + { + team: { id: "7", displayName: "Toronto Raptors", abbreviation: "TOR" }, + stats: [ + makeStat("wins", 22), + makeStat("losses", 46), + makeStat("winPercent", 0.324), + makeStat("gamesBehind", 30), + makeStat("playoffSeed", 12), + { name: "streak", value: 2, displayValue: "L2" }, + makeStat("homeWins", 12), + makeStat("homeLosses", 22), + makeStat("awayWins", 10), + makeStat("awayLosses", 24), + ], + }, + ], + }, + }, + ], + }, + { + name: "Western Conference", + children: [ + { + name: "Northwest Division", + standings: { + entries: [ + { + team: { id: "21", displayName: "Oklahoma City Thunder", abbreviation: "OKC" }, + stats: [ + makeStat("wins", 58), + makeStat("losses", 10), + makeStat("winPercent", 0.853), + makeStat("gamesBehind", 0), + makeStat("playoffSeed", 1), + { name: "streak", value: 4, displayValue: "W4" }, + makeStat("homeWins", 30), + makeStat("homeLosses", 4), + makeStat("awayWins", 28), + makeStat("awayLosses", 6), + ], + }, + ], + }, + }, + ], + }, + ], +}; + +describe("NbaStandingsAdapter", () => { + beforeEach(() => { + vi.stubGlobal("fetch", vi.fn()); + }); + + it("maps ESPN API response to FetchedStandingsRecord[]", async () => { + vi.mocked(fetch).mockResolvedValueOnce({ + ok: true, + json: async () => SAMPLE_NBA_RESPONSE, + } as Response); + + const adapter = new NbaStandingsAdapter(); + const records = await adapter.fetchStandings(); + + expect(records).toHaveLength(3); + + const okc = records.find((r) => r.teamName === "Oklahoma City Thunder")!; + expect(okc).toBeDefined(); + expect(okc.wins).toBe(58); + expect(okc.losses).toBe(10); + expect(okc.conference).toBe("Western Conference"); + expect(okc.division).toBe("Northwest Division"); + expect(okc.otLosses).toBeUndefined(); + }); + + it("assigns conference correctly", async () => { + vi.mocked(fetch).mockResolvedValueOnce({ + ok: true, + json: async () => SAMPLE_NBA_RESPONSE, + } as Response); + + const adapter = new NbaStandingsAdapter(); + const records = await adapter.fetchStandings(); + + const bos = records.find((r) => r.teamName === "Boston Celtics")!; + expect(bos.conference).toBe("Eastern Conference"); + expect(bos.division).toBe("Atlantic Division"); + }); + + it("extracts streak from stats array", async () => { + vi.mocked(fetch).mockResolvedValueOnce({ + ok: true, + json: async () => SAMPLE_NBA_RESPONSE, + } as Response); + + const adapter = new NbaStandingsAdapter(); + const records = await adapter.fetchStandings(); + + const bos = records.find((r) => r.teamName === "Boston Celtics")!; + expect(bos.streak).toBe("W5"); + + const tor = records.find((r) => r.teamName === "Toronto Raptors")!; + expect(tor.streak).toBe("L2"); + }); + + it("does not set otLosses (NBA has no OT losses)", async () => { + vi.mocked(fetch).mockResolvedValueOnce({ + ok: true, + json: async () => SAMPLE_NBA_RESPONSE, + } as Response); + + const adapter = new NbaStandingsAdapter(); + const records = await adapter.fetchStandings(); + + for (const record of records) { + expect(record.otLosses).toBeUndefined(); + } + }); + + it("throws on non-ok response", async () => { + vi.mocked(fetch).mockResolvedValueOnce({ + ok: false, + status: 429, + statusText: "Too Many Requests", + } as Response); + + const adapter = new NbaStandingsAdapter(); + await expect(adapter.fetchStandings()).rejects.toThrow("429"); + }); + + it("throws when no entries returned", async () => { + vi.mocked(fetch).mockResolvedValueOnce({ + ok: true, + json: async () => ({ children: [] }), + } as Response); + + const adapter = new NbaStandingsAdapter(); + await expect(adapter.fetchStandings()).rejects.toThrow("no entries"); + }); +}); diff --git a/app/services/standings-sync/__tests__/nhl.test.ts b/app/services/standings-sync/__tests__/nhl.test.ts new file mode 100644 index 0000000..6fec933 --- /dev/null +++ b/app/services/standings-sync/__tests__/nhl.test.ts @@ -0,0 +1,141 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { NhlStandingsAdapter } from "../nhl"; + +const SAMPLE_NHL_RESPONSE = { + standings: [ + { + teamName: { default: "Boston Bruins" }, + teamAbbrev: { default: "BOS" }, + conferenceName: "Eastern", + divisionName: "Atlantic", + gamesPlayed: 68, + wins: 42, + losses: 20, + otLosses: 6, + points: 90, + pointPctg: 0.662, + winPctg: 0.618, + l10Wins: 7, + l10Losses: 2, + l10OtLosses: 1, + streakCode: "W", + streakCount: 3, + homeWins: 24, + homeLosses: 9, + homeOtLosses: 2, + roadWins: 18, + roadLosses: 11, + roadOtLosses: 4, + conferenceSequence: 1, + divisionSequence: 1, + leagueSequence: 1, + }, + { + teamName: { default: "Toronto Maple Leafs" }, + teamAbbrev: { default: "TOR" }, + conferenceName: "Eastern", + divisionName: "Atlantic", + gamesPlayed: 70, + wins: 38, + losses: 25, + otLosses: 7, + points: 83, + pointPctg: 0.593, + winPctg: 0.543, + l10Wins: 5, + l10Losses: 4, + l10OtLosses: 1, + streakCode: "L", + streakCount: 2, + homeWins: 20, + homeLosses: 12, + homeOtLosses: 3, + roadWins: 18, + roadLosses: 13, + roadOtLosses: 4, + conferenceSequence: 3, + divisionSequence: 2, + leagueSequence: 5, + }, + ], +}; + +describe("NhlStandingsAdapter", () => { + beforeEach(() => { + vi.stubGlobal("fetch", vi.fn()); + }); + + it("maps NHL API response to FetchedStandingsRecord[]", async () => { + vi.mocked(fetch).mockResolvedValueOnce({ + ok: true, + json: async () => SAMPLE_NHL_RESPONSE, + } as Response); + + const adapter = new NhlStandingsAdapter(); + const records = await adapter.fetchStandings(); + + expect(records).toHaveLength(2); + + const bos = records[0]; + expect(bos.teamName).toBe("Boston Bruins"); + expect(bos.externalTeamId).toBe("BOS"); + expect(bos.wins).toBe(42); + expect(bos.losses).toBe(20); + expect(bos.otLosses).toBe(6); + expect(bos.gamesPlayed).toBe(68); + expect(bos.conference).toBe("Eastern"); + expect(bos.division).toBe("Atlantic"); + expect(bos.conferenceRank).toBe(1); + expect(bos.divisionRank).toBe(1); + expect(bos.leagueRank).toBe(1); + }); + + it("formats streak correctly", async () => { + vi.mocked(fetch).mockResolvedValueOnce({ + ok: true, + json: async () => SAMPLE_NHL_RESPONSE, + } as Response); + + const adapter = new NhlStandingsAdapter(); + const records = await adapter.fetchStandings(); + + expect(records[0].streak).toBe("W3"); + expect(records[1].streak).toBe("L2"); + }); + + it("formats lastTen as W-L-OTL", async () => { + vi.mocked(fetch).mockResolvedValueOnce({ + ok: true, + json: async () => SAMPLE_NHL_RESPONSE, + } as Response); + + const adapter = new NhlStandingsAdapter(); + const records = await adapter.fetchStandings(); + + expect(records[0].lastTen).toBe("7-2-1"); + }); + + it("formats home and away records", async () => { + vi.mocked(fetch).mockResolvedValueOnce({ + ok: true, + json: async () => SAMPLE_NHL_RESPONSE, + } as Response); + + const adapter = new NhlStandingsAdapter(); + const records = await adapter.fetchStandings(); + + expect(records[0].homeRecord).toBe("24-9-2"); + expect(records[0].awayRecord).toBe("18-11-4"); + }); + + it("throws on non-ok response", async () => { + vi.mocked(fetch).mockResolvedValueOnce({ + ok: false, + status: 503, + statusText: "Service Unavailable", + } as Response); + + const adapter = new NhlStandingsAdapter(); + await expect(adapter.fetchStandings()).rejects.toThrow("503"); + }); +}); diff --git a/app/services/standings-sync/__tests__/sync.test.ts b/app/services/standings-sync/__tests__/sync.test.ts new file mode 100644 index 0000000..d36162d --- /dev/null +++ b/app/services/standings-sync/__tests__/sync.test.ts @@ -0,0 +1,48 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { findMatchingTeamName } from "~/lib/normalize-team-name"; + +// Pure unit tests for the name-matching logic used by syncStandings. +// The full orchestrator requires DB context; those are covered by integration tests. + +describe("findMatchingTeamName (sync name-matching logic)", () => { + const participants = [ + "Boston Celtics", + "Los Angeles Lakers", + "Golden State Warriors", + "Oklahoma City Thunder", + "San Antonio Spurs", + "New York Knicks", + ]; + + it("matches exact names", () => { + expect(findMatchingTeamName("Boston Celtics", participants)).toBe("Boston Celtics"); + expect(findMatchingTeamName("New York Knicks", participants)).toBe("New York Knicks"); + }); + + it("is case-insensitive", () => { + expect(findMatchingTeamName("golden state warriors", participants)).toBe( + "Golden State Warriors" + ); + expect(findMatchingTeamName("SAN ANTONIO SPURS", participants)).toBe("San Antonio Spurs"); + }); + + it("matches partial city+team via substring", () => { + expect(findMatchingTeamName("Golden State", participants)).toBe("Golden State Warriors"); + expect(findMatchingTeamName("Oklahoma City", participants)).toBe("Oklahoma City Thunder"); + // "New York" is a substring of "New York Knicks" + expect(findMatchingTeamName("New York", participants)).toBe("New York Knicks"); + }); + + it("returns null for truly unmatched names", () => { + expect(findMatchingTeamName("Phoenix Suns", participants)).toBeNull(); + expect(findMatchingTeamName("Charlotte Hornets", participants)).toBeNull(); + }); + + it("returns null for empty string", () => { + expect(findMatchingTeamName("", participants)).toBeNull(); + }); + + it("handles extra whitespace gracefully", () => { + expect(findMatchingTeamName(" Boston Celtics ", participants)).toBe("Boston Celtics"); + }); +}); diff --git a/app/services/standings-sync/f1.ts b/app/services/standings-sync/f1.ts new file mode 100644 index 0000000..a06725f --- /dev/null +++ b/app/services/standings-sync/f1.ts @@ -0,0 +1,31 @@ +import type { FetchedStandingsRecord, StandingsSyncAdapter } from "./types"; + +/** + * F1 / IndyCar championship standings adapter. + * + * TODO: Implement using one of: + * - OpenF1 API: https://openf1.org/ (free, real-time, no key required) + * - Ergast API: https://ergast.com/mrd/ (free, historical, being deprecated) + * - Official F1 API: requires a key but is comprehensive + * + * NOTE: For season_standings sports, the sync result should upsert into + * participantSeasonResults (currentPoints + currentPosition) rather than + * regularSeasonStandings. The orchestrator in index.ts handles the routing. + */ +export class F1StandingsAdapter implements StandingsSyncAdapter { + async fetchStandings(): Promise { + throw new Error( + "F1/IndyCar standings sync not yet implemented. " + + "Implement using OpenF1 API (https://openf1.org/) or Ergast API." + ); + } +} + +export class IndyCarStandingsAdapter implements StandingsSyncAdapter { + async fetchStandings(): Promise { + throw new Error( + "IndyCar standings sync not yet implemented. " + + "Implement using the IndyCar official results API." + ); + } +} diff --git a/app/services/standings-sync/index.ts b/app/services/standings-sync/index.ts new file mode 100644 index 0000000..9ee3093 --- /dev/null +++ b/app/services/standings-sync/index.ts @@ -0,0 +1,139 @@ +import { database } from "~/database/context"; +import { eq } from "drizzle-orm"; +import * as schema from "~/database/schema"; +import { findParticipantsBySportsSeasonId, updateParticipant } from "~/models/participant"; +import { upsertRegularSeasonStandings } from "~/models/regular-season-standings"; +import { upsertPendingStandingsMappings } from "~/models/pending-standings-mappings"; +import { findMatchingTeamName } from "~/lib/normalize-team-name"; +import { NhlStandingsAdapter } from "./nhl"; +import { NbaStandingsAdapter } from "./nba"; +import type { StandingsSyncAdapter, SyncResult, UnmatchedTeam } from "./types"; + +/** + * Map a simulator type to its standings sync adapter. + * Returns null for sports that don't use regularSeasonStandings + * (e.g. season_standings sports like F1 will use a different path when implemented). + */ +function getAdapter(simulatorType: string): StandingsSyncAdapter { + switch (simulatorType) { + case "nba_bracket": + return new NbaStandingsAdapter(); + case "nhl_bracket": + return new NhlStandingsAdapter(); + case "f1_standings": + throw new Error( + "F1 standings sync is not yet implemented. Use the manual standings page." + ); + case "indycar_standings": + throw new Error( + "IndyCar standings sync is not yet implemented. Use the manual standings page." + ); + default: + throw new Error( + `No standings sync adapter available for simulator type "${simulatorType}". ` + + "Only NBA and NHL are currently supported." + ); + } +} + +/** + * Sync current standings from the appropriate external API for a sports season. + * Matches API team names to participants by name and bulk-upserts the results. + * + * Returns the count of synced teams and any API team names that couldn't be matched. + */ +export async function syncStandings(sportsSeasonId: string): Promise { + const db = database(); + + // Load sports season with sport relation + const sportsSeason = await db.query.sportsSeasons.findFirst({ + where: eq(schema.sportsSeasons.id, sportsSeasonId), + with: { sport: true }, + }); + + if (!sportsSeason) { + throw new Error(`Sports season ${sportsSeasonId} not found`); + } + + const simulatorType = sportsSeason.sport?.simulatorType; + if (!simulatorType) { + const sportName = sportsSeason.sport?.name ?? "(no sport linked)"; + throw new Error(`Sport "${sportName}" has no simulator type configured`); + } + + const adapter = getAdapter(simulatorType); + const fetchedRecords = await adapter.fetchStandings(); + + // Load all participants for this season + const participants = await findParticipantsBySportsSeasonId(sportsSeasonId); + const participantNames = participants.map((p) => p.name); + const participantByName = new Map(participants.map((p) => [p.name, p])); + // Build a map of externalId → participant for ID-first matching + const participantByExternalId = new Map( + participants.filter((p) => p.externalId).map((p) => [p.externalId!, p]) + ); + + const toUpsert: Parameters[0] = []; + const unmatchedRecords: typeof fetchedRecords = []; + + for (const record of fetchedRecords) { + // Strategy A: try externalId-first (bypasses name matching entirely after first sync) + let participant = participantByExternalId.get(record.externalTeamId) ?? null; + + if (!participant) { + // Fall back to name matching + const matchedName = findMatchingTeamName(record.teamName, participantNames); + if (matchedName) { + participant = participantByName.get(matchedName)!; + // Write-back: persist externalId so future syncs skip name matching for this team + if (!participant.externalId) { + await updateParticipant(participant.id, { externalId: record.externalTeamId }); + } + } + } + + if (!participant) { + unmatchedRecords.push(record); + continue; + } + + toUpsert.push({ + participantId: participant.id, + sportsSeasonId, + wins: record.wins, + losses: record.losses, + otLosses: record.otLosses ?? null, + ties: record.ties ?? null, + winPct: record.winPct, + gamesPlayed: record.gamesPlayed, + gamesBack: record.gamesBack ?? null, + conference: record.conference ?? null, + division: record.division ?? null, + conferenceRank: record.conferenceRank ?? null, + divisionRank: record.divisionRank ?? null, + leagueRank: record.leagueRank, + streak: record.streak ?? null, + lastTen: record.lastTen ?? null, + homeRecord: record.homeRecord ?? null, + awayRecord: record.awayRecord ?? null, + externalTeamId: record.externalTeamId, + syncedAt: new Date(), + }); + } + + if (toUpsert.length > 0) { + await upsertRegularSeasonStandings(toUpsert); + } + + // Persist unmatched records so admin can resolve them without losing data on page reload + if (unmatchedRecords.length > 0) { + await upsertPendingStandingsMappings(sportsSeasonId, unmatchedRecords); + } + + const unmatched: UnmatchedTeam[] = unmatchedRecords.map((r) => ({ + teamName: r.teamName, + externalTeamId: r.externalTeamId, + })); + + return { synced: toUpsert.length, unmatched }; +} diff --git a/app/services/standings-sync/nba.ts b/app/services/standings-sync/nba.ts new file mode 100644 index 0000000..0ebf52d --- /dev/null +++ b/app/services/standings-sync/nba.ts @@ -0,0 +1,156 @@ +import type { FetchedStandingsRecord, StandingsSyncAdapter } from "./types"; + +const NBA_STANDINGS_URL = + "https://site.api.espn.com/apis/v2/sports/basketball/nba/standings"; + +interface EspnStat { + name: string; + displayName?: string; + shortDisplayName?: string; + description?: string; + abbreviation?: string; + type?: string; + value?: number; + displayValue?: string; +} + +interface EspnTeam { + id: string; + displayName: string; + shortDisplayName?: string; + abbreviation?: string; + location?: string; + name?: string; +} + +interface EspnStandingsEntry { + team: EspnTeam; + stats: EspnStat[]; +} + +interface EspnStandingsGroup { + name?: string; + standings?: { entries?: EspnStandingsEntry[] }; + children?: EspnStandingsGroup[]; +} + +interface EspnStandingsResponse { + children?: EspnStandingsGroup[]; +} + +function statsMap(stats: EspnStat[]): Map { + const map = new Map(); + for (const stat of stats) { + map.set(stat.name, stat); + } + return map; +} + +/** + * Flatten the ESPN standings response (conference → division → entries). + * Returns enriched entries with conference and division names attached. + */ +function flattenEspnStandings( + response: EspnStandingsResponse +): Array<{ entry: EspnStandingsEntry; conference: string; division: string }> { + const results: Array<{ + entry: EspnStandingsEntry; + conference: string; + division: string; + }> = []; + + for (const confGroup of response.children ?? []) { + const conferenceName = confGroup.name ?? ""; + + // Some ESPN responses have nested division children + if (confGroup.children && confGroup.children.length > 0) { + for (const divGroup of confGroup.children) { + const divisionName = divGroup.name ?? ""; + for (const entry of divGroup.standings?.entries ?? []) { + results.push({ entry, conference: conferenceName, division: divisionName }); + } + } + } else { + // Flat conference without division sub-groups + for (const entry of confGroup.standings?.entries ?? []) { + results.push({ entry, conference: conferenceName, division: "" }); + } + } + } + + return results; +} + +export class NbaStandingsAdapter implements StandingsSyncAdapter { + async fetchStandings(): Promise { + const response = await fetch(NBA_STANDINGS_URL); + if (!response.ok) { + throw new Error(`NBA standings API returned ${response.status}: ${response.statusText}`); + } + + const json = (await response.json()) as EspnStandingsResponse; + const flattened = flattenEspnStandings(json); + + if (flattened.length === 0) { + throw new Error("NBA standings API returned no entries — response shape may have changed"); + } + + // Assign league rank by sorting on wins desc, then losses asc + const sorted = [...flattened].sort((a, b) => { + const winsA = statsMap(a.entry.stats).get("wins")?.value ?? 0; + const winsB = statsMap(b.entry.stats).get("wins")?.value ?? 0; + if (winsB !== winsA) return winsB - winsA; + const lossA = statsMap(a.entry.stats).get("losses")?.value ?? 0; + const lossB = statsMap(b.entry.stats).get("losses")?.value ?? 0; + return lossA - lossB; + }); + + return sorted.map(({ entry, conference, division }, leagueIdx): FetchedStandingsRecord => { + const sm = statsMap(entry.stats); + const wins = sm.get("wins")?.value ?? 0; + const losses = sm.get("losses")?.value ?? 0; + const winPercent = sm.get("winPercent")?.value ?? sm.get("winPct")?.value ?? 0; + const gamesBehind = sm.get("gamesBehind")?.value; + const streak = sm.get("streak")?.displayValue ?? sm.get("streakSummary")?.displayValue; + + // ESPN returns last-10 as "Last Ten Games" with a displayValue like "7-3" + const lastTen = + sm.get("Last Ten Games")?.displayValue ?? + sm.get("L10")?.displayValue ?? + undefined; + + // Seed within conference — ESPN key is "playoffSeed", value is a float (1.0, 2.0 ...) + const playoffSeedStat = sm.get("playoffSeed"); + const conferenceRank = + playoffSeedStat?.value != null + ? Math.round(playoffSeedStat.value) + : playoffSeedStat?.displayValue + ? parseInt(playoffSeedStat.displayValue, 10) || undefined + : undefined; + + const gamesPlayed = wins + losses; + + // Home/road records — ESPN returns these as displayValue strings (e.g. "24-17") + const homeRecord = sm.get("Home")?.displayValue ?? undefined; + const awayRecord = sm.get("Road")?.displayValue ?? undefined; + + return { + teamName: entry.team.displayName, + externalTeamId: entry.team.id, + wins: Math.round(wins), + losses: Math.round(losses), + winPct: winPercent, + gamesPlayed, + gamesBack: gamesBehind, + conference, + division: division || undefined, + conferenceRank, + leagueRank: leagueIdx + 1, + streak, + lastTen, + homeRecord, + awayRecord, + }; + }); + } +} diff --git a/app/services/standings-sync/nhl.ts b/app/services/standings-sync/nhl.ts new file mode 100644 index 0000000..22db0a0 --- /dev/null +++ b/app/services/standings-sync/nhl.ts @@ -0,0 +1,78 @@ +import type { FetchedStandingsRecord, StandingsSyncAdapter } from "./types"; + +const NHL_STANDINGS_URL = "https://api-web.nhle.com/v1/standings/now"; + +interface NhlTeamRecord { + teamName: { default: string }; + teamAbbrev: { default: string }; + teamLogo?: string; + conferenceName: string; + divisionName: string; + gamesPlayed: number; + wins: number; + losses: number; + otLosses: number; + points: number; + pointPctg: number; + winPctg: number; + l10Wins: number; + l10Losses: number; + l10OtLosses: number; + streakCode: string; // "W" or "L" + streakCount: number; + homeWins: number; + homeLosses: number; + homeOtLosses: number; + roadWins: number; + roadLosses: number; + roadOtLosses: number; + conferenceSequence: number; + divisionSequence: number; + leagueSequence: number; + teamId?: number; +} + +interface NhlStandingsResponse { + standings: NhlTeamRecord[]; +} + +export class NhlStandingsAdapter implements StandingsSyncAdapter { + async fetchStandings(): Promise { + const response = await fetch(NHL_STANDINGS_URL); + if (!response.ok) { + throw new Error(`NHL standings API returned ${response.status}: ${response.statusText}`); + } + + const json = (await response.json()) as NhlStandingsResponse; + + if (!json.standings || !Array.isArray(json.standings)) { + throw new Error("Unexpected NHL standings API response shape"); + } + + return json.standings.map((team, index): FetchedStandingsRecord => { + const homeRecord = `${team.homeWins}-${team.homeLosses}-${team.homeOtLosses}`; + const awayRecord = `${team.roadWins}-${team.roadLosses}-${team.roadOtLosses}`; + const streak = `${team.streakCode}${team.streakCount}`; + const lastTen = `${team.l10Wins}-${team.l10Losses}-${team.l10OtLosses}`; + + return { + teamName: team.teamName.default, + externalTeamId: team.teamAbbrev?.default ?? String(index), + wins: team.wins, + losses: team.losses, + otLosses: team.otLosses, + winPct: team.winPctg ?? 0, + gamesPlayed: team.gamesPlayed, + conference: team.conferenceName, + division: team.divisionName, + conferenceRank: team.conferenceSequence, + divisionRank: team.divisionSequence, + leagueRank: team.leagueSequence, + streak, + lastTen, + homeRecord, + awayRecord, + }; + }); + } +} diff --git a/app/services/standings-sync/thesportsdb.ts b/app/services/standings-sync/thesportsdb.ts new file mode 100644 index 0000000..a0897a1 --- /dev/null +++ b/app/services/standings-sync/thesportsdb.ts @@ -0,0 +1,25 @@ +import type { FetchedStandingsRecord, StandingsSyncAdapter } from "./types"; + +/** + * TheSportsDB standings adapter. + * + * TODO: Implement when TheSportsDB adds reliable NBA/NHL standings support. + * + * Status (as of 2026-03): The free-tier lookuptable.php endpoint is documented + * as "soccer only" and returns empty results for NBA (4387) and NHL (4380). + * A premium v2 API key ($9/mo) may unlock broader support — test with: + * GET https://www.thesportsdb.com/api/v2/json/lookuptable.php?l={leagueId} + * Headers: { "X-API-KEY": process.env.THESPORTSDB_API_KEY } + * + * Reference: https://www.thesportsdb.com/forum_topic.php?t=5772 + */ +export class TheSportsDbAdapter implements StandingsSyncAdapter { + constructor(private readonly leagueId: string) {} + + async fetchStandings(): Promise { + throw new Error( + `TheSportsDB standings sync not yet implemented (league ${this.leagueId}). ` + + "Use NhlStandingsAdapter or NbaStandingsAdapter instead." + ); + } +} diff --git a/app/services/standings-sync/types.ts b/app/services/standings-sync/types.ts new file mode 100644 index 0000000..5991d2e --- /dev/null +++ b/app/services/standings-sync/types.ts @@ -0,0 +1,34 @@ +export interface FetchedStandingsRecord { + teamName: string; // matched against participants.name + externalTeamId: string; + wins: number; + losses: number; + otLosses?: number; + ties?: number; + winPct: number; + gamesPlayed: number; + gamesBack?: number; + conference?: string; + division?: string; + conferenceRank?: number; + divisionRank?: number; + leagueRank: number; + streak?: string; + lastTen?: string; + homeRecord?: string; + awayRecord?: string; +} + +export interface StandingsSyncAdapter { + fetchStandings(): Promise; +} + +export interface UnmatchedTeam { + teamName: string; + externalTeamId: string; +} + +export interface SyncResult { + synced: number; + unmatched: UnmatchedTeam[]; +} diff --git a/database/schema.ts b/database/schema.ts index 8a50d02..f334c30 100644 --- a/database/schema.ts +++ b/database/schema.ts @@ -683,6 +683,8 @@ export const sportsSeasonsRelations = relations(sportsSeasons, ({ one, many }) = seasonTemplateSports: many(seasonTemplateSports), seasonSports: many(seasonSports), participantResults: many(participantResults), + regularSeasonStandings: many(regularSeasonStandings), + pendingStandingsMappings: many(pendingStandingsMappings), })); export const participantsRelations = relations(participants, ({ one, many }) => ({ @@ -691,6 +693,7 @@ export const participantsRelations = relations(participants, ({ one, many }) => references: [sportsSeasons.id], }), results: many(participantResults), + regularSeasonStandings: many(regularSeasonStandings), })); export const seasonTemplatesRelations = relations(seasonTemplates, ({ many }) => ({ @@ -991,3 +994,74 @@ export const teamEvSnapshotsRelations = relations(teamEvSnapshots, ({ one }) => references: [seasons.id], }), })); + +// Regular season W/L standings for team-sport playoff_bracket seasons (NBA, NHL, etc.) +// Populated by the standings sync service; syncedAt = null means manually entered +export const regularSeasonStandings = pgTable("regular_season_standings", { + id: uuid("id").primaryKey().defaultRandom(), + participantId: uuid("participant_id") + .notNull() + .references(() => participants.id, { onDelete: "cascade" }), + sportsSeasonId: uuid("sports_season_id") + .notNull() + .references(() => sportsSeasons.id, { onDelete: "cascade" }), + wins: integer("wins").notNull().default(0), + losses: integer("losses").notNull().default(0), + otLosses: integer("ot_losses"), // NHL overtime losses; null for NBA + ties: integer("ties"), // future sports (e.g. soccer) + winPct: decimal("win_pct", { precision: 5, scale: 4 }), + gamesPlayed: integer("games_played").notNull().default(0), + gamesBack: decimal("games_back", { precision: 5, scale: 1 }), + conference: varchar("conference", { length: 100 }), + division: varchar("division", { length: 100 }), + conferenceRank: integer("conference_rank"), + divisionRank: integer("division_rank"), + leagueRank: integer("league_rank"), + streak: varchar("streak", { length: 20 }), // e.g. "W3", "L2" + lastTen: varchar("last_ten", { length: 15 }), // e.g. "7-2-1" + homeRecord: varchar("home_record", { length: 15 }), + awayRecord: varchar("away_record", { length: 15 }), + externalTeamId: varchar("external_team_id", { length: 255 }), + syncedAt: timestamp("synced_at"), // null = manually entered + createdAt: timestamp("created_at").defaultNow().notNull(), + updatedAt: timestamp("updated_at").defaultNow().notNull(), +}, (table) => ({ + uniqueParticipantSeason: uniqueIndex("rss_participant_season_idx") + .on(table.participantId, table.sportsSeasonId), +})); + +export const regularSeasonStandingsRelations = relations(regularSeasonStandings, ({ one }) => ({ + participant: one(participants, { + fields: [regularSeasonStandings.participantId], + references: [participants.id], + }), + sportsSeason: one(sportsSeasons, { + fields: [regularSeasonStandings.sportsSeasonId], + references: [sportsSeasons.id], + }), +})); + +// Unmatched teams from a standings sync — awaiting admin resolution. +// Once the admin maps an external team to a participant, this record is deleted +// and the standing + participant.externalId are written. +export const pendingStandingsMappings = pgTable("pending_standings_mappings", { + id: uuid("id").primaryKey().defaultRandom(), + sportsSeasonId: uuid("sports_season_id") + .notNull() + .references(() => sportsSeasons.id, { onDelete: "cascade" }), + externalTeamId: varchar("external_team_id", { length: 255 }).notNull(), + teamName: varchar("team_name", { length: 255 }).notNull(), + // Full standing data from the API, stored as JSON for use when resolving + standingData: jsonb("standing_data").notNull(), + createdAt: timestamp("created_at").defaultNow().notNull(), +}, (table) => ({ + uniqueSeasonExternalId: uniqueIndex("psm_season_external_id_idx") + .on(table.sportsSeasonId, table.externalTeamId), +})); + +export const pendingStandingsMappingsRelations = relations(pendingStandingsMappings, ({ one }) => ({ + sportsSeason: one(sportsSeasons, { + fields: [pendingStandingsMappings.sportsSeasonId], + references: [sportsSeasons.id], + }), +})); diff --git a/drizzle/0054_massive_vulcan.sql b/drizzle/0054_massive_vulcan.sql new file mode 100644 index 0000000..2b28f7a --- /dev/null +++ b/drizzle/0054_massive_vulcan.sql @@ -0,0 +1,39 @@ +CREATE TABLE IF NOT EXISTS "regular_season_standings" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "participant_id" uuid NOT NULL, + "sports_season_id" uuid NOT NULL, + "wins" integer DEFAULT 0 NOT NULL, + "losses" integer DEFAULT 0 NOT NULL, + "ot_losses" integer, + "ties" integer, + "win_pct" numeric(5, 4), + "games_played" integer DEFAULT 0 NOT NULL, + "games_back" numeric(5, 1), + "conference" varchar(100), + "division" varchar(100), + "conference_rank" integer, + "division_rank" integer, + "league_rank" integer, + "streak" varchar(20), + "last_ten" varchar(15), + "home_record" varchar(15), + "away_record" varchar(15), + "external_team_id" varchar(255), + "synced_at" timestamp, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "regular_season_standings" ADD CONSTRAINT "regular_season_standings_participant_id_participants_id_fk" FOREIGN KEY ("participant_id") REFERENCES "public"."participants"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "regular_season_standings" ADD CONSTRAINT "regular_season_standings_sports_season_id_sports_seasons_id_fk" FOREIGN KEY ("sports_season_id") REFERENCES "public"."sports_seasons"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +CREATE UNIQUE INDEX IF NOT EXISTS "rss_participant_season_idx" ON "regular_season_standings" USING btree ("participant_id","sports_season_id"); \ No newline at end of file diff --git a/drizzle/0055_special_vampiro.sql b/drizzle/0055_special_vampiro.sql new file mode 100644 index 0000000..3e89fca --- /dev/null +++ b/drizzle/0055_special_vampiro.sql @@ -0,0 +1,16 @@ +CREATE TABLE IF NOT EXISTS "pending_standings_mappings" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "sports_season_id" uuid NOT NULL, + "external_team_id" varchar(255) NOT NULL, + "team_name" varchar(255) NOT NULL, + "standing_data" jsonb NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "pending_standings_mappings" ADD CONSTRAINT "pending_standings_mappings_sports_season_id_sports_seasons_id_fk" FOREIGN KEY ("sports_season_id") REFERENCES "public"."sports_seasons"("id") ON DELETE cascade ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +CREATE UNIQUE INDEX IF NOT EXISTS "psm_season_external_id_idx" ON "pending_standings_mappings" USING btree ("sports_season_id","external_team_id"); \ No newline at end of file diff --git a/drizzle/meta/0054_snapshot.json b/drizzle/meta/0054_snapshot.json new file mode 100644 index 0000000..3ee9639 --- /dev/null +++ b/drizzle/meta/0054_snapshot.json @@ -0,0 +1,3802 @@ +{ + "id": "f4a79251-f513-413c-9270-8f44c42eed03", + "prevId": "a84e2784-1121-4715-aa97-79e3780ebf1f", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.autodraft_settings": { + "name": "autodraft_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "season_id": { + "name": "season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "mode": { + "name": "mode", + "type": "autodraft_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'next_pick'" + }, + "queue_only": { + "name": "queue_only", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "autodraft_settings_season_id_seasons_id_fk": { + "name": "autodraft_settings_season_id_seasons_id_fk", + "tableFrom": "autodraft_settings", + "tableTo": "seasons", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "autodraft_settings_team_id_teams_id_fk": { + "name": "autodraft_settings_team_id_teams_id_fk", + "tableFrom": "autodraft_settings", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.commissioners": { + "name": "commissioners", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "league_id": { + "name": "league_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "commissioners_league_id_leagues_id_fk": { + "name": "commissioners_league_id_leagues_id_fk", + "tableFrom": "commissioners", + "tableTo": "leagues", + "columnsFrom": [ + "league_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.draft_picks": { + "name": "draft_picks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "season_id": { + "name": "season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pick_number": { + "name": "pick_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "round": { + "name": "round", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "pick_in_round": { + "name": "pick_in_round", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "picked_by_user_id": { + "name": "picked_by_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "picked_by_type": { + "name": "picked_by_type", + "type": "picked_by_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "time_used": { + "name": "time_used", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "draft_picks_season_pick_unique": { + "name": "draft_picks_season_pick_unique", + "columns": [ + { + "expression": "season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pick_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "draft_picks_season_id_seasons_id_fk": { + "name": "draft_picks_season_id_seasons_id_fk", + "tableFrom": "draft_picks", + "tableTo": "seasons", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "draft_picks_team_id_teams_id_fk": { + "name": "draft_picks_team_id_teams_id_fk", + "tableFrom": "draft_picks", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "draft_picks_participant_id_participants_id_fk": { + "name": "draft_picks_participant_id_participants_id_fk", + "tableFrom": "draft_picks", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.draft_queue": { + "name": "draft_queue", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "season_id": { + "name": "season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "queue_position": { + "name": "queue_position", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "draft_queue_season_id_seasons_id_fk": { + "name": "draft_queue_season_id_seasons_id_fk", + "tableFrom": "draft_queue", + "tableTo": "seasons", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "draft_queue_team_id_teams_id_fk": { + "name": "draft_queue_team_id_teams_id_fk", + "tableFrom": "draft_queue", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "draft_queue_participant_id_participants_id_fk": { + "name": "draft_queue_participant_id_participants_id_fk", + "tableFrom": "draft_queue", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.draft_slots": { + "name": "draft_slots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "season_id": { + "name": "season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "draft_order": { + "name": "draft_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "draft_slots_season_id_seasons_id_fk": { + "name": "draft_slots_season_id_seasons_id_fk", + "tableFrom": "draft_slots", + "tableTo": "seasons", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "draft_slots_team_id_teams_id_fk": { + "name": "draft_slots_team_id_teams_id_fk", + "tableFrom": "draft_slots", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.draft_timers": { + "name": "draft_timers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "season_id": { + "name": "season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "time_remaining": { + "name": "time_remaining", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "draft_timers_season_id_seasons_id_fk": { + "name": "draft_timers_season_id_seasons_id_fk", + "tableFrom": "draft_timers", + "tableTo": "seasons", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "draft_timers_team_id_teams_id_fk": { + "name": "draft_timers_team_id_teams_id_fk", + "tableFrom": "draft_timers", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.event_results": { + "name": "event_results", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "scoring_event_id": { + "name": "scoring_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "placement": { + "name": "placement", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "qualifying_points_awarded": { + "name": "qualifying_points_awarded", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "eliminated": { + "name": "eliminated", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "raw_score": { + "name": "raw_score", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "event_results_scoring_event_id_scoring_events_id_fk": { + "name": "event_results_scoring_event_id_scoring_events_id_fk", + "tableFrom": "event_results", + "tableTo": "scoring_events", + "columnsFrom": [ + "scoring_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "event_results_participant_id_participants_id_fk": { + "name": "event_results_participant_id_participants_id_fk", + "tableFrom": "event_results", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.leagues": { + "name": "leagues", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "current_season_id": { + "name": "current_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "is_public_draft_board": { + "name": "is_public_draft_board", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "discord_webhook_url": { + "name": "discord_webhook_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.participant_ev_snapshots": { + "name": "participant_ev_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "snapshot_date": { + "name": "snapshot_date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "prob_first": { + "name": "prob_first", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_second": { + "name": "prob_second", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_third": { + "name": "prob_third", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_fourth": { + "name": "prob_fourth", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_fifth": { + "name": "prob_fifth", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_sixth": { + "name": "prob_sixth", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_seventh": { + "name": "prob_seventh", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_eighth": { + "name": "prob_eighth", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "calculated_ev": { + "name": "calculated_ev", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "source": { + "name": "source", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "participant_ev_snapshots_unique": { + "name": "participant_ev_snapshots_unique", + "columns": [ + { + "expression": "participant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sports_season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "snapshot_date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "participant_ev_snapshots_participant_id_participants_id_fk": { + "name": "participant_ev_snapshots_participant_id_participants_id_fk", + "tableFrom": "participant_ev_snapshots", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "participant_ev_snapshots_sports_season_id_sports_seasons_id_fk": { + "name": "participant_ev_snapshots_sports_season_id_sports_seasons_id_fk", + "tableFrom": "participant_ev_snapshots", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.participant_expected_values": { + "name": "participant_expected_values", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "prob_first": { + "name": "prob_first", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_second": { + "name": "prob_second", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_third": { + "name": "prob_third", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_fourth": { + "name": "prob_fourth", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_fifth": { + "name": "prob_fifth", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_sixth": { + "name": "prob_sixth", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_seventh": { + "name": "prob_seventh", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_eighth": { + "name": "prob_eighth", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "expected_value": { + "name": "expected_value", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "source": { + "name": "source", + "type": "probability_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'manual'" + }, + "source_odds": { + "name": "source_odds", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "calculated_at": { + "name": "calculated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "participant_expected_values_participant_id_participants_id_fk": { + "name": "participant_expected_values_participant_id_participants_id_fk", + "tableFrom": "participant_expected_values", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "participant_expected_values_sports_season_id_sports_seasons_id_fk": { + "name": "participant_expected_values_sports_season_id_sports_seasons_id_fk", + "tableFrom": "participant_expected_values", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.participant_qualifying_totals": { + "name": "participant_qualifying_totals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "total_qualifying_points": { + "name": "total_qualifying_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "events_scored": { + "name": "events_scored", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "final_ranking": { + "name": "final_ranking", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "participant_qualifying_totals_participant_id_participants_id_fk": { + "name": "participant_qualifying_totals_participant_id_participants_id_fk", + "tableFrom": "participant_qualifying_totals", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "participant_qualifying_totals_sports_season_id_sports_seasons_id_fk": { + "name": "participant_qualifying_totals_sports_season_id_sports_seasons_id_fk", + "tableFrom": "participant_qualifying_totals", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.participant_results": { + "name": "participant_results", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "final_position": { + "name": "final_position", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_partial_score": { + "name": "is_partial_score", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "qualifying_points": { + "name": "qualifying_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "participant_results_participant_id_participants_id_fk": { + "name": "participant_results_participant_id_participants_id_fk", + "tableFrom": "participant_results", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "participant_results_sports_season_id_sports_seasons_id_fk": { + "name": "participant_results_sports_season_id_sports_seasons_id_fk", + "tableFrom": "participant_results", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.participant_season_results": { + "name": "participant_season_results", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "current_points": { + "name": "current_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "current_position": { + "name": "current_position", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "participant_season_results_participant_id_participants_id_fk": { + "name": "participant_season_results_participant_id_participants_id_fk", + "tableFrom": "participant_season_results", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "participant_season_results_sports_season_id_sports_seasons_id_fk": { + "name": "participant_season_results_sports_season_id_sports_seasons_id_fk", + "tableFrom": "participant_season_results", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.participants": { + "name": "participants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "short_name": { + "name": "short_name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "expected_value": { + "name": "expected_value", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "participants_sports_season_id_sports_seasons_id_fk": { + "name": "participants_sports_season_id_sports_seasons_id_fk", + "tableFrom": "participants", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.playoff_match_games": { + "name": "playoff_match_games", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "playoff_match_id": { + "name": "playoff_match_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_number": { + "name": "game_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "scheduled_at": { + "name": "scheduled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "playoff_match_game_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'scheduled'" + }, + "participant1_score": { + "name": "participant1_score", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "participant2_score": { + "name": "participant2_score", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "winner_id": { + "name": "winner_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "playoff_match_games_playoff_match_id_playoff_matches_id_fk": { + "name": "playoff_match_games_playoff_match_id_playoff_matches_id_fk", + "tableFrom": "playoff_match_games", + "tableTo": "playoff_matches", + "columnsFrom": [ + "playoff_match_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "playoff_match_games_winner_id_participants_id_fk": { + "name": "playoff_match_games_winner_id_participants_id_fk", + "tableFrom": "playoff_match_games", + "tableTo": "participants", + "columnsFrom": [ + "winner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.playoff_match_odds": { + "name": "playoff_match_odds", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "playoff_match_id": { + "name": "playoff_match_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "moneyline_odds": { + "name": "moneyline_odds", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "implied_probability": { + "name": "implied_probability", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": false + }, + "odds_source": { + "name": "odds_source", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "recorded_at": { + "name": "recorded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "playoff_match_odds_playoff_match_id_playoff_matches_id_fk": { + "name": "playoff_match_odds_playoff_match_id_playoff_matches_id_fk", + "tableFrom": "playoff_match_odds", + "tableTo": "playoff_matches", + "columnsFrom": [ + "playoff_match_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "playoff_match_odds_participant_id_participants_id_fk": { + "name": "playoff_match_odds_participant_id_participants_id_fk", + "tableFrom": "playoff_match_odds", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.playoff_matches": { + "name": "playoff_matches", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "scoring_event_id": { + "name": "scoring_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "round": { + "name": "round", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "match_number": { + "name": "match_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "participant1_id": { + "name": "participant1_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "participant2_id": { + "name": "participant2_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "winner_id": { + "name": "winner_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "loser_id": { + "name": "loser_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "is_complete": { + "name": "is_complete", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "participant1_score": { + "name": "participant1_score", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "participant2_score": { + "name": "participant2_score", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "is_scoring": { + "name": "is_scoring", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "template_round": { + "name": "template_round", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "seed_info": { + "name": "seed_info", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "playoff_matches_scoring_event_id_scoring_events_id_fk": { + "name": "playoff_matches_scoring_event_id_scoring_events_id_fk", + "tableFrom": "playoff_matches", + "tableTo": "scoring_events", + "columnsFrom": [ + "scoring_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "playoff_matches_participant1_id_participants_id_fk": { + "name": "playoff_matches_participant1_id_participants_id_fk", + "tableFrom": "playoff_matches", + "tableTo": "participants", + "columnsFrom": [ + "participant1_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "playoff_matches_participant2_id_participants_id_fk": { + "name": "playoff_matches_participant2_id_participants_id_fk", + "tableFrom": "playoff_matches", + "tableTo": "participants", + "columnsFrom": [ + "participant2_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "playoff_matches_winner_id_participants_id_fk": { + "name": "playoff_matches_winner_id_participants_id_fk", + "tableFrom": "playoff_matches", + "tableTo": "participants", + "columnsFrom": [ + "winner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "playoff_matches_loser_id_participants_id_fk": { + "name": "playoff_matches_loser_id_participants_id_fk", + "tableFrom": "playoff_matches", + "tableTo": "participants", + "columnsFrom": [ + "loser_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.qualifying_point_config": { + "name": "qualifying_point_config", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "placement": { + "name": "placement", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "points": { + "name": "points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "qualifying_point_config_sports_season_id_sports_seasons_id_fk": { + "name": "qualifying_point_config_sports_season_id_sports_seasons_id_fk", + "tableFrom": "qualifying_point_config", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.regular_season_standings": { + "name": "regular_season_standings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "wins": { + "name": "wins", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "losses": { + "name": "losses", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "ot_losses": { + "name": "ot_losses", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "ties": { + "name": "ties", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "win_pct": { + "name": "win_pct", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false + }, + "games_played": { + "name": "games_played", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "games_back": { + "name": "games_back", + "type": "numeric(5, 1)", + "primaryKey": false, + "notNull": false + }, + "conference": { + "name": "conference", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "division": { + "name": "division", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "conference_rank": { + "name": "conference_rank", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "division_rank": { + "name": "division_rank", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "league_rank": { + "name": "league_rank", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "streak": { + "name": "streak", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "last_ten": { + "name": "last_ten", + "type": "varchar(15)", + "primaryKey": false, + "notNull": false + }, + "home_record": { + "name": "home_record", + "type": "varchar(15)", + "primaryKey": false, + "notNull": false + }, + "away_record": { + "name": "away_record", + "type": "varchar(15)", + "primaryKey": false, + "notNull": false + }, + "external_team_id": { + "name": "external_team_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "synced_at": { + "name": "synced_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "rss_participant_season_idx": { + "name": "rss_participant_season_idx", + "columns": [ + { + "expression": "participant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sports_season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "regular_season_standings_participant_id_participants_id_fk": { + "name": "regular_season_standings_participant_id_participants_id_fk", + "tableFrom": "regular_season_standings", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "regular_season_standings_sports_season_id_sports_seasons_id_fk": { + "name": "regular_season_standings_sports_season_id_sports_seasons_id_fk", + "tableFrom": "regular_season_standings", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scoring_events": { + "name": "scoring_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "event_date": { + "name": "event_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "event_starts_at": { + "name": "event_starts_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "event_type": { + "name": "event_type", + "type": "event_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "playoff_round": { + "name": "playoff_round", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "is_qualifying_event": { + "name": "is_qualifying_event", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "bracket_template_id": { + "name": "bracket_template_id", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "scoring_starts_at_round": { + "name": "scoring_starts_at_round", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "bracket_region_config": { + "name": "bracket_region_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_complete": { + "name": "is_complete", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "scoring_events_sports_season_id_sports_seasons_id_fk": { + "name": "scoring_events_sports_season_id_sports_seasons_id_fk", + "tableFrom": "scoring_events", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.season_sports": { + "name": "season_sports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "season_id": { + "name": "season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "season_sports_season_id_seasons_id_fk": { + "name": "season_sports_season_id_seasons_id_fk", + "tableFrom": "season_sports", + "tableTo": "seasons", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "season_sports_sports_season_id_sports_seasons_id_fk": { + "name": "season_sports_sports_season_id_sports_seasons_id_fk", + "tableFrom": "season_sports", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.season_template_sports": { + "name": "season_template_sports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "template_id": { + "name": "template_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "season_template_sports_template_id_season_templates_id_fk": { + "name": "season_template_sports_template_id_season_templates_id_fk", + "tableFrom": "season_template_sports", + "tableTo": "season_templates", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "season_template_sports_sports_season_id_sports_seasons_id_fk": { + "name": "season_template_sports_sports_season_id_sports_seasons_id_fk", + "tableFrom": "season_template_sports", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.season_templates": { + "name": "season_templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "year": { + "name": "year", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.seasons": { + "name": "seasons", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "league_id": { + "name": "league_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "year": { + "name": "year", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "season_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pre_draft'" + }, + "template_id": { + "name": "template_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "draft_rounds": { + "name": "draft_rounds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 20 + }, + "flex_spots": { + "name": "flex_spots", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "draft_date_time": { + "name": "draft_date_time", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "draft_initial_time": { + "name": "draft_initial_time", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 120 + }, + "draft_increment_time": { + "name": "draft_increment_time", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 30 + }, + "draft_timer_mode": { + "name": "draft_timer_mode", + "type": "draft_timer_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'chess_clock'" + }, + "current_pick_number": { + "name": "current_pick_number", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1 + }, + "draft_started_at": { + "name": "draft_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "draft_paused": { + "name": "draft_paused", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "invite_code": { + "name": "invite_code", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "points_for_1st": { + "name": "points_for_1st", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 100 + }, + "points_for_2nd": { + "name": "points_for_2nd", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 70 + }, + "points_for_3rd": { + "name": "points_for_3rd", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 50 + }, + "points_for_4th": { + "name": "points_for_4th", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 40 + }, + "points_for_5th": { + "name": "points_for_5th", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 25 + }, + "points_for_6th": { + "name": "points_for_6th", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 25 + }, + "points_for_7th": { + "name": "points_for_7th", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 15 + }, + "points_for_8th": { + "name": "points_for_8th", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 15 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "seasons_league_id_leagues_id_fk": { + "name": "seasons_league_id_leagues_id_fk", + "tableFrom": "seasons", + "tableTo": "leagues", + "columnsFrom": [ + "league_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "seasons_template_id_season_templates_id_fk": { + "name": "seasons_template_id_season_templates_id_fk", + "tableFrom": "seasons", + "tableTo": "season_templates", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "seasons_invite_code_unique": { + "name": "seasons_invite_code_unique", + "nullsNotDistinct": false, + "columns": [ + "invite_code" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sports": { + "name": "sports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "sport_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon_url": { + "name": "icon_url", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "simulator_type": { + "name": "simulator_type", + "type": "simulator_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "sports_slug_unique": { + "name": "sports_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sports_seasons": { + "name": "sports_seasons", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "sport_id": { + "name": "sport_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "year": { + "name": "year", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "start_date": { + "name": "start_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "end_date": { + "name": "end_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "sports_season_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'upcoming'" + }, + "scoring_type": { + "name": "scoring_type", + "type": "scoring_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "scoring_pattern": { + "name": "scoring_pattern", + "type": "scoring_pattern", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "total_majors": { + "name": "total_majors", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "majors_completed": { + "name": "majors_completed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "qualifying_points_finalized": { + "name": "qualifying_points_finalized", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "elo_calibration_exponent": { + "name": "elo_calibration_exponent", + "type": "numeric(3, 2)", + "primaryKey": false, + "notNull": false + }, + "elo_min_rating": { + "name": "elo_min_rating", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1250 + }, + "elo_max_rating": { + "name": "elo_max_rating", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1750 + }, + "simulation_status": { + "name": "simulation_status", + "type": "simulation_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "is_draftable": { + "name": "is_draftable", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "sports_seasons_sport_id_sports_id_fk": { + "name": "sports_seasons_sport_id_sports_id_fk", + "tableFrom": "sports_seasons", + "tableTo": "sports", + "columnsFrom": [ + "sport_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_ev_snapshots": { + "name": "team_ev_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "snapshot_date": { + "name": "snapshot_date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "projected_points": { + "name": "projected_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "actual_points": { + "name": "actual_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "team_ev_snapshots_unique": { + "name": "team_ev_snapshots_unique", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "snapshot_date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "team_ev_snapshots_team_id_teams_id_fk": { + "name": "team_ev_snapshots_team_id_teams_id_fk", + "tableFrom": "team_ev_snapshots", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_ev_snapshots_season_id_seasons_id_fk": { + "name": "team_ev_snapshots_season_id_seasons_id_fk", + "tableFrom": "team_ev_snapshots", + "tableTo": "seasons", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_sport_scores": { + "name": "team_sport_scores", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "total_points": { + "name": "total_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "participants_completed": { + "name": "participants_completed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "participants_total": { + "name": "participants_total", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "calculated_at": { + "name": "calculated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "team_sport_scores_team_id_teams_id_fk": { + "name": "team_sport_scores_team_id_teams_id_fk", + "tableFrom": "team_sport_scores", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_sport_scores_sports_season_id_sports_seasons_id_fk": { + "name": "team_sport_scores_sports_season_id_sports_seasons_id_fk", + "tableFrom": "team_sport_scores", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_standings": { + "name": "team_standings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "total_points": { + "name": "total_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "current_rank": { + "name": "current_rank", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "previous_rank": { + "name": "previous_rank", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "first_place_count": { + "name": "first_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "second_place_count": { + "name": "second_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "third_place_count": { + "name": "third_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "fourth_place_count": { + "name": "fourth_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "fifth_place_count": { + "name": "fifth_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "sixth_place_count": { + "name": "sixth_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "seventh_place_count": { + "name": "seventh_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "eighth_place_count": { + "name": "eighth_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "participants_remaining": { + "name": "participants_remaining", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "actual_points": { + "name": "actual_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "projected_points": { + "name": "projected_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "participants_finished": { + "name": "participants_finished", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "calculated_at": { + "name": "calculated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "team_standings_team_id_teams_id_fk": { + "name": "team_standings_team_id_teams_id_fk", + "tableFrom": "team_standings", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_standings_season_id_seasons_id_fk": { + "name": "team_standings_season_id_seasons_id_fk", + "tableFrom": "team_standings", + "tableTo": "seasons", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_standings_snapshots": { + "name": "team_standings_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "snapshot_date": { + "name": "snapshot_date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "total_points": { + "name": "total_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "rank": { + "name": "rank", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "first_place_count": { + "name": "first_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "second_place_count": { + "name": "second_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "third_place_count": { + "name": "third_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "fourth_place_count": { + "name": "fourth_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "fifth_place_count": { + "name": "fifth_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "sixth_place_count": { + "name": "sixth_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "seventh_place_count": { + "name": "seventh_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "eighth_place_count": { + "name": "eighth_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "participants_remaining": { + "name": "participants_remaining", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "actual_points": { + "name": "actual_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "projected_points": { + "name": "projected_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "participants_finished": { + "name": "participants_finished", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "team_standings_snapshots_unique": { + "name": "team_standings_snapshots_unique", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "snapshot_date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "team_standings_snapshots_team_id_teams_id_fk": { + "name": "team_standings_snapshots_team_id_teams_id_fk", + "tableFrom": "team_standings_snapshots", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_standings_snapshots_season_id_seasons_id_fk": { + "name": "team_standings_snapshots_season_id_seasons_id_fk", + "tableFrom": "team_standings_snapshots", + "tableTo": "seasons", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.teams": { + "name": "teams", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "season_id": { + "name": "season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "logo_url": { + "name": "logo_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "teams_season_id_seasons_id_fk": { + "name": "teams_season_id_seasons_id_fk", + "tableFrom": "teams", + "tableTo": "seasons", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tournament_group_members": { + "name": "tournament_group_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "tournament_group_id": { + "name": "tournament_group_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "eliminated": { + "name": "eliminated", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "tournament_group_members_tournament_group_id_tournament_groups_id_fk": { + "name": "tournament_group_members_tournament_group_id_tournament_groups_id_fk", + "tableFrom": "tournament_group_members", + "tableTo": "tournament_groups", + "columnsFrom": [ + "tournament_group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tournament_group_members_participant_id_participants_id_fk": { + "name": "tournament_group_members_participant_id_participants_id_fk", + "tableFrom": "tournament_group_members", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tournament_groups": { + "name": "tournament_groups", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "scoring_event_id": { + "name": "scoring_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "group_name": { + "name": "group_name", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "tournament_groups_scoring_event_id_scoring_events_id_fk": { + "name": "tournament_groups_scoring_event_id_scoring_events_id_fk", + "tableFrom": "tournament_groups", + "tableTo": "scoring_events", + "columnsFrom": [ + "scoring_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "clerk_id": { + "name": "clerk_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "display_name": { + "name": "display_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "first_name": { + "name": "first_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "last_name": { + "name": "last_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "image_url": { + "name": "image_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "is_admin": { + "name": "is_admin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_clerk_id_unique": { + "name": "users_clerk_id_unique", + "nullsNotDistinct": false, + "columns": [ + "clerk_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.autodraft_mode": { + "name": "autodraft_mode", + "schema": "public", + "values": [ + "next_pick", + "while_on" + ] + }, + "public.draft_timer_mode": { + "name": "draft_timer_mode", + "schema": "public", + "values": [ + "chess_clock", + "standard" + ] + }, + "public.event_type": { + "name": "event_type", + "schema": "public", + "values": [ + "playoff_game", + "major_tournament", + "final_standings", + "schedule_event" + ] + }, + "public.picked_by_type": { + "name": "picked_by_type", + "schema": "public", + "values": [ + "owner", + "commissioner", + "admin", + "auto" + ] + }, + "public.playoff_match_game_status": { + "name": "playoff_match_game_status", + "schema": "public", + "values": [ + "scheduled", + "complete", + "postponed" + ] + }, + "public.probability_source": { + "name": "probability_source", + "schema": "public", + "values": [ + "manual", + "futures_odds", + "elo_simulation", + "performance_model" + ] + }, + "public.scoring_pattern": { + "name": "scoring_pattern", + "schema": "public", + "values": [ + "playoff_bracket", + "season_standings", + "qualifying_points" + ] + }, + "public.scoring_type": { + "name": "scoring_type", + "schema": "public", + "values": [ + "playoffs", + "regular_season", + "majors" + ] + }, + "public.season_status": { + "name": "season_status", + "schema": "public", + "values": [ + "pre_draft", + "draft", + "active", + "completed" + ] + }, + "public.simulation_status": { + "name": "simulation_status", + "schema": "public", + "values": [ + "idle", + "running", + "failed" + ] + }, + "public.simulator_type": { + "name": "simulator_type", + "schema": "public", + "values": [ + "f1_standings", + "indycar_standings", + "golf_qualifying_points", + "playoff_bracket", + "ucl_bracket", + "ncaam_bracket", + "ncaaw_bracket", + "nba_bracket", + "nhl_bracket" + ] + }, + "public.sport_type": { + "name": "sport_type", + "schema": "public", + "values": [ + "team", + "individual" + ] + }, + "public.sports_season_status": { + "name": "sports_season_status", + "schema": "public", + "values": [ + "upcoming", + "active", + "completed" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/0055_snapshot.json b/drizzle/meta/0055_snapshot.json new file mode 100644 index 0000000..a96e4d5 --- /dev/null +++ b/drizzle/meta/0055_snapshot.json @@ -0,0 +1,3889 @@ +{ + "id": "6463661d-575e-42d9-b20e-2b46e5edcfd5", + "prevId": "f4a79251-f513-413c-9270-8f44c42eed03", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.autodraft_settings": { + "name": "autodraft_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "season_id": { + "name": "season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "mode": { + "name": "mode", + "type": "autodraft_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'next_pick'" + }, + "queue_only": { + "name": "queue_only", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "autodraft_settings_season_id_seasons_id_fk": { + "name": "autodraft_settings_season_id_seasons_id_fk", + "tableFrom": "autodraft_settings", + "tableTo": "seasons", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "autodraft_settings_team_id_teams_id_fk": { + "name": "autodraft_settings_team_id_teams_id_fk", + "tableFrom": "autodraft_settings", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.commissioners": { + "name": "commissioners", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "league_id": { + "name": "league_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "commissioners_league_id_leagues_id_fk": { + "name": "commissioners_league_id_leagues_id_fk", + "tableFrom": "commissioners", + "tableTo": "leagues", + "columnsFrom": [ + "league_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.draft_picks": { + "name": "draft_picks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "season_id": { + "name": "season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pick_number": { + "name": "pick_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "round": { + "name": "round", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "pick_in_round": { + "name": "pick_in_round", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "picked_by_user_id": { + "name": "picked_by_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "picked_by_type": { + "name": "picked_by_type", + "type": "picked_by_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "time_used": { + "name": "time_used", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "draft_picks_season_pick_unique": { + "name": "draft_picks_season_pick_unique", + "columns": [ + { + "expression": "season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pick_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "draft_picks_season_id_seasons_id_fk": { + "name": "draft_picks_season_id_seasons_id_fk", + "tableFrom": "draft_picks", + "tableTo": "seasons", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "draft_picks_team_id_teams_id_fk": { + "name": "draft_picks_team_id_teams_id_fk", + "tableFrom": "draft_picks", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "draft_picks_participant_id_participants_id_fk": { + "name": "draft_picks_participant_id_participants_id_fk", + "tableFrom": "draft_picks", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.draft_queue": { + "name": "draft_queue", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "season_id": { + "name": "season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "queue_position": { + "name": "queue_position", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "draft_queue_season_id_seasons_id_fk": { + "name": "draft_queue_season_id_seasons_id_fk", + "tableFrom": "draft_queue", + "tableTo": "seasons", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "draft_queue_team_id_teams_id_fk": { + "name": "draft_queue_team_id_teams_id_fk", + "tableFrom": "draft_queue", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "draft_queue_participant_id_participants_id_fk": { + "name": "draft_queue_participant_id_participants_id_fk", + "tableFrom": "draft_queue", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.draft_slots": { + "name": "draft_slots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "season_id": { + "name": "season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "draft_order": { + "name": "draft_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "draft_slots_season_id_seasons_id_fk": { + "name": "draft_slots_season_id_seasons_id_fk", + "tableFrom": "draft_slots", + "tableTo": "seasons", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "draft_slots_team_id_teams_id_fk": { + "name": "draft_slots_team_id_teams_id_fk", + "tableFrom": "draft_slots", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.draft_timers": { + "name": "draft_timers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "season_id": { + "name": "season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "time_remaining": { + "name": "time_remaining", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "draft_timers_season_id_seasons_id_fk": { + "name": "draft_timers_season_id_seasons_id_fk", + "tableFrom": "draft_timers", + "tableTo": "seasons", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "draft_timers_team_id_teams_id_fk": { + "name": "draft_timers_team_id_teams_id_fk", + "tableFrom": "draft_timers", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.event_results": { + "name": "event_results", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "scoring_event_id": { + "name": "scoring_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "placement": { + "name": "placement", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "qualifying_points_awarded": { + "name": "qualifying_points_awarded", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "eliminated": { + "name": "eliminated", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "raw_score": { + "name": "raw_score", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "event_results_scoring_event_id_scoring_events_id_fk": { + "name": "event_results_scoring_event_id_scoring_events_id_fk", + "tableFrom": "event_results", + "tableTo": "scoring_events", + "columnsFrom": [ + "scoring_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "event_results_participant_id_participants_id_fk": { + "name": "event_results_participant_id_participants_id_fk", + "tableFrom": "event_results", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.leagues": { + "name": "leagues", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "current_season_id": { + "name": "current_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "is_public_draft_board": { + "name": "is_public_draft_board", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "discord_webhook_url": { + "name": "discord_webhook_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.participant_ev_snapshots": { + "name": "participant_ev_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "snapshot_date": { + "name": "snapshot_date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "prob_first": { + "name": "prob_first", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_second": { + "name": "prob_second", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_third": { + "name": "prob_third", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_fourth": { + "name": "prob_fourth", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_fifth": { + "name": "prob_fifth", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_sixth": { + "name": "prob_sixth", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_seventh": { + "name": "prob_seventh", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_eighth": { + "name": "prob_eighth", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "calculated_ev": { + "name": "calculated_ev", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "source": { + "name": "source", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "participant_ev_snapshots_unique": { + "name": "participant_ev_snapshots_unique", + "columns": [ + { + "expression": "participant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sports_season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "snapshot_date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "participant_ev_snapshots_participant_id_participants_id_fk": { + "name": "participant_ev_snapshots_participant_id_participants_id_fk", + "tableFrom": "participant_ev_snapshots", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "participant_ev_snapshots_sports_season_id_sports_seasons_id_fk": { + "name": "participant_ev_snapshots_sports_season_id_sports_seasons_id_fk", + "tableFrom": "participant_ev_snapshots", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.participant_expected_values": { + "name": "participant_expected_values", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "prob_first": { + "name": "prob_first", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_second": { + "name": "prob_second", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_third": { + "name": "prob_third", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_fourth": { + "name": "prob_fourth", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_fifth": { + "name": "prob_fifth", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_sixth": { + "name": "prob_sixth", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_seventh": { + "name": "prob_seventh", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_eighth": { + "name": "prob_eighth", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "expected_value": { + "name": "expected_value", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "source": { + "name": "source", + "type": "probability_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'manual'" + }, + "source_odds": { + "name": "source_odds", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "calculated_at": { + "name": "calculated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "participant_expected_values_participant_id_participants_id_fk": { + "name": "participant_expected_values_participant_id_participants_id_fk", + "tableFrom": "participant_expected_values", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "participant_expected_values_sports_season_id_sports_seasons_id_fk": { + "name": "participant_expected_values_sports_season_id_sports_seasons_id_fk", + "tableFrom": "participant_expected_values", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.participant_qualifying_totals": { + "name": "participant_qualifying_totals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "total_qualifying_points": { + "name": "total_qualifying_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "events_scored": { + "name": "events_scored", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "final_ranking": { + "name": "final_ranking", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "participant_qualifying_totals_participant_id_participants_id_fk": { + "name": "participant_qualifying_totals_participant_id_participants_id_fk", + "tableFrom": "participant_qualifying_totals", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "participant_qualifying_totals_sports_season_id_sports_seasons_id_fk": { + "name": "participant_qualifying_totals_sports_season_id_sports_seasons_id_fk", + "tableFrom": "participant_qualifying_totals", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.participant_results": { + "name": "participant_results", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "final_position": { + "name": "final_position", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_partial_score": { + "name": "is_partial_score", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "qualifying_points": { + "name": "qualifying_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "participant_results_participant_id_participants_id_fk": { + "name": "participant_results_participant_id_participants_id_fk", + "tableFrom": "participant_results", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "participant_results_sports_season_id_sports_seasons_id_fk": { + "name": "participant_results_sports_season_id_sports_seasons_id_fk", + "tableFrom": "participant_results", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.participant_season_results": { + "name": "participant_season_results", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "current_points": { + "name": "current_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "current_position": { + "name": "current_position", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "participant_season_results_participant_id_participants_id_fk": { + "name": "participant_season_results_participant_id_participants_id_fk", + "tableFrom": "participant_season_results", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "participant_season_results_sports_season_id_sports_seasons_id_fk": { + "name": "participant_season_results_sports_season_id_sports_seasons_id_fk", + "tableFrom": "participant_season_results", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.participants": { + "name": "participants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "short_name": { + "name": "short_name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "expected_value": { + "name": "expected_value", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "participants_sports_season_id_sports_seasons_id_fk": { + "name": "participants_sports_season_id_sports_seasons_id_fk", + "tableFrom": "participants", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pending_standings_mappings": { + "name": "pending_standings_mappings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "external_team_id": { + "name": "external_team_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "team_name": { + "name": "team_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "standing_data": { + "name": "standing_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "psm_season_external_id_idx": { + "name": "psm_season_external_id_idx", + "columns": [ + { + "expression": "sports_season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pending_standings_mappings_sports_season_id_sports_seasons_id_fk": { + "name": "pending_standings_mappings_sports_season_id_sports_seasons_id_fk", + "tableFrom": "pending_standings_mappings", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.playoff_match_games": { + "name": "playoff_match_games", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "playoff_match_id": { + "name": "playoff_match_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_number": { + "name": "game_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "scheduled_at": { + "name": "scheduled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "playoff_match_game_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'scheduled'" + }, + "participant1_score": { + "name": "participant1_score", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "participant2_score": { + "name": "participant2_score", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "winner_id": { + "name": "winner_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "playoff_match_games_playoff_match_id_playoff_matches_id_fk": { + "name": "playoff_match_games_playoff_match_id_playoff_matches_id_fk", + "tableFrom": "playoff_match_games", + "tableTo": "playoff_matches", + "columnsFrom": [ + "playoff_match_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "playoff_match_games_winner_id_participants_id_fk": { + "name": "playoff_match_games_winner_id_participants_id_fk", + "tableFrom": "playoff_match_games", + "tableTo": "participants", + "columnsFrom": [ + "winner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.playoff_match_odds": { + "name": "playoff_match_odds", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "playoff_match_id": { + "name": "playoff_match_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "moneyline_odds": { + "name": "moneyline_odds", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "implied_probability": { + "name": "implied_probability", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": false + }, + "odds_source": { + "name": "odds_source", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "recorded_at": { + "name": "recorded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "playoff_match_odds_playoff_match_id_playoff_matches_id_fk": { + "name": "playoff_match_odds_playoff_match_id_playoff_matches_id_fk", + "tableFrom": "playoff_match_odds", + "tableTo": "playoff_matches", + "columnsFrom": [ + "playoff_match_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "playoff_match_odds_participant_id_participants_id_fk": { + "name": "playoff_match_odds_participant_id_participants_id_fk", + "tableFrom": "playoff_match_odds", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.playoff_matches": { + "name": "playoff_matches", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "scoring_event_id": { + "name": "scoring_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "round": { + "name": "round", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "match_number": { + "name": "match_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "participant1_id": { + "name": "participant1_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "participant2_id": { + "name": "participant2_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "winner_id": { + "name": "winner_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "loser_id": { + "name": "loser_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "is_complete": { + "name": "is_complete", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "participant1_score": { + "name": "participant1_score", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "participant2_score": { + "name": "participant2_score", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "is_scoring": { + "name": "is_scoring", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "template_round": { + "name": "template_round", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "seed_info": { + "name": "seed_info", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "playoff_matches_scoring_event_id_scoring_events_id_fk": { + "name": "playoff_matches_scoring_event_id_scoring_events_id_fk", + "tableFrom": "playoff_matches", + "tableTo": "scoring_events", + "columnsFrom": [ + "scoring_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "playoff_matches_participant1_id_participants_id_fk": { + "name": "playoff_matches_participant1_id_participants_id_fk", + "tableFrom": "playoff_matches", + "tableTo": "participants", + "columnsFrom": [ + "participant1_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "playoff_matches_participant2_id_participants_id_fk": { + "name": "playoff_matches_participant2_id_participants_id_fk", + "tableFrom": "playoff_matches", + "tableTo": "participants", + "columnsFrom": [ + "participant2_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "playoff_matches_winner_id_participants_id_fk": { + "name": "playoff_matches_winner_id_participants_id_fk", + "tableFrom": "playoff_matches", + "tableTo": "participants", + "columnsFrom": [ + "winner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "playoff_matches_loser_id_participants_id_fk": { + "name": "playoff_matches_loser_id_participants_id_fk", + "tableFrom": "playoff_matches", + "tableTo": "participants", + "columnsFrom": [ + "loser_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.qualifying_point_config": { + "name": "qualifying_point_config", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "placement": { + "name": "placement", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "points": { + "name": "points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "qualifying_point_config_sports_season_id_sports_seasons_id_fk": { + "name": "qualifying_point_config_sports_season_id_sports_seasons_id_fk", + "tableFrom": "qualifying_point_config", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.regular_season_standings": { + "name": "regular_season_standings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "wins": { + "name": "wins", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "losses": { + "name": "losses", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "ot_losses": { + "name": "ot_losses", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "ties": { + "name": "ties", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "win_pct": { + "name": "win_pct", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false + }, + "games_played": { + "name": "games_played", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "games_back": { + "name": "games_back", + "type": "numeric(5, 1)", + "primaryKey": false, + "notNull": false + }, + "conference": { + "name": "conference", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "division": { + "name": "division", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "conference_rank": { + "name": "conference_rank", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "division_rank": { + "name": "division_rank", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "league_rank": { + "name": "league_rank", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "streak": { + "name": "streak", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "last_ten": { + "name": "last_ten", + "type": "varchar(15)", + "primaryKey": false, + "notNull": false + }, + "home_record": { + "name": "home_record", + "type": "varchar(15)", + "primaryKey": false, + "notNull": false + }, + "away_record": { + "name": "away_record", + "type": "varchar(15)", + "primaryKey": false, + "notNull": false + }, + "external_team_id": { + "name": "external_team_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "synced_at": { + "name": "synced_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "rss_participant_season_idx": { + "name": "rss_participant_season_idx", + "columns": [ + { + "expression": "participant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sports_season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "regular_season_standings_participant_id_participants_id_fk": { + "name": "regular_season_standings_participant_id_participants_id_fk", + "tableFrom": "regular_season_standings", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "regular_season_standings_sports_season_id_sports_seasons_id_fk": { + "name": "regular_season_standings_sports_season_id_sports_seasons_id_fk", + "tableFrom": "regular_season_standings", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scoring_events": { + "name": "scoring_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "event_date": { + "name": "event_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "event_starts_at": { + "name": "event_starts_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "event_type": { + "name": "event_type", + "type": "event_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "playoff_round": { + "name": "playoff_round", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "is_qualifying_event": { + "name": "is_qualifying_event", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "bracket_template_id": { + "name": "bracket_template_id", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "scoring_starts_at_round": { + "name": "scoring_starts_at_round", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "bracket_region_config": { + "name": "bracket_region_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_complete": { + "name": "is_complete", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "scoring_events_sports_season_id_sports_seasons_id_fk": { + "name": "scoring_events_sports_season_id_sports_seasons_id_fk", + "tableFrom": "scoring_events", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.season_sports": { + "name": "season_sports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "season_id": { + "name": "season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "season_sports_season_id_seasons_id_fk": { + "name": "season_sports_season_id_seasons_id_fk", + "tableFrom": "season_sports", + "tableTo": "seasons", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "season_sports_sports_season_id_sports_seasons_id_fk": { + "name": "season_sports_sports_season_id_sports_seasons_id_fk", + "tableFrom": "season_sports", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.season_template_sports": { + "name": "season_template_sports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "template_id": { + "name": "template_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "season_template_sports_template_id_season_templates_id_fk": { + "name": "season_template_sports_template_id_season_templates_id_fk", + "tableFrom": "season_template_sports", + "tableTo": "season_templates", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "season_template_sports_sports_season_id_sports_seasons_id_fk": { + "name": "season_template_sports_sports_season_id_sports_seasons_id_fk", + "tableFrom": "season_template_sports", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.season_templates": { + "name": "season_templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "year": { + "name": "year", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.seasons": { + "name": "seasons", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "league_id": { + "name": "league_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "year": { + "name": "year", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "season_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pre_draft'" + }, + "template_id": { + "name": "template_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "draft_rounds": { + "name": "draft_rounds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 20 + }, + "flex_spots": { + "name": "flex_spots", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "draft_date_time": { + "name": "draft_date_time", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "draft_initial_time": { + "name": "draft_initial_time", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 120 + }, + "draft_increment_time": { + "name": "draft_increment_time", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 30 + }, + "draft_timer_mode": { + "name": "draft_timer_mode", + "type": "draft_timer_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'chess_clock'" + }, + "current_pick_number": { + "name": "current_pick_number", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1 + }, + "draft_started_at": { + "name": "draft_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "draft_paused": { + "name": "draft_paused", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "invite_code": { + "name": "invite_code", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "points_for_1st": { + "name": "points_for_1st", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 100 + }, + "points_for_2nd": { + "name": "points_for_2nd", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 70 + }, + "points_for_3rd": { + "name": "points_for_3rd", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 50 + }, + "points_for_4th": { + "name": "points_for_4th", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 40 + }, + "points_for_5th": { + "name": "points_for_5th", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 25 + }, + "points_for_6th": { + "name": "points_for_6th", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 25 + }, + "points_for_7th": { + "name": "points_for_7th", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 15 + }, + "points_for_8th": { + "name": "points_for_8th", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 15 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "seasons_league_id_leagues_id_fk": { + "name": "seasons_league_id_leagues_id_fk", + "tableFrom": "seasons", + "tableTo": "leagues", + "columnsFrom": [ + "league_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "seasons_template_id_season_templates_id_fk": { + "name": "seasons_template_id_season_templates_id_fk", + "tableFrom": "seasons", + "tableTo": "season_templates", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "seasons_invite_code_unique": { + "name": "seasons_invite_code_unique", + "nullsNotDistinct": false, + "columns": [ + "invite_code" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sports": { + "name": "sports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "sport_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon_url": { + "name": "icon_url", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "simulator_type": { + "name": "simulator_type", + "type": "simulator_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "sports_slug_unique": { + "name": "sports_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sports_seasons": { + "name": "sports_seasons", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "sport_id": { + "name": "sport_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "year": { + "name": "year", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "start_date": { + "name": "start_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "end_date": { + "name": "end_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "sports_season_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'upcoming'" + }, + "scoring_type": { + "name": "scoring_type", + "type": "scoring_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "scoring_pattern": { + "name": "scoring_pattern", + "type": "scoring_pattern", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "total_majors": { + "name": "total_majors", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "majors_completed": { + "name": "majors_completed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "qualifying_points_finalized": { + "name": "qualifying_points_finalized", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "elo_calibration_exponent": { + "name": "elo_calibration_exponent", + "type": "numeric(3, 2)", + "primaryKey": false, + "notNull": false + }, + "elo_min_rating": { + "name": "elo_min_rating", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1250 + }, + "elo_max_rating": { + "name": "elo_max_rating", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1750 + }, + "simulation_status": { + "name": "simulation_status", + "type": "simulation_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "is_draftable": { + "name": "is_draftable", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "sports_seasons_sport_id_sports_id_fk": { + "name": "sports_seasons_sport_id_sports_id_fk", + "tableFrom": "sports_seasons", + "tableTo": "sports", + "columnsFrom": [ + "sport_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_ev_snapshots": { + "name": "team_ev_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "snapshot_date": { + "name": "snapshot_date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "projected_points": { + "name": "projected_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "actual_points": { + "name": "actual_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "team_ev_snapshots_unique": { + "name": "team_ev_snapshots_unique", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "snapshot_date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "team_ev_snapshots_team_id_teams_id_fk": { + "name": "team_ev_snapshots_team_id_teams_id_fk", + "tableFrom": "team_ev_snapshots", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_ev_snapshots_season_id_seasons_id_fk": { + "name": "team_ev_snapshots_season_id_seasons_id_fk", + "tableFrom": "team_ev_snapshots", + "tableTo": "seasons", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_sport_scores": { + "name": "team_sport_scores", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "total_points": { + "name": "total_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "participants_completed": { + "name": "participants_completed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "participants_total": { + "name": "participants_total", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "calculated_at": { + "name": "calculated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "team_sport_scores_team_id_teams_id_fk": { + "name": "team_sport_scores_team_id_teams_id_fk", + "tableFrom": "team_sport_scores", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_sport_scores_sports_season_id_sports_seasons_id_fk": { + "name": "team_sport_scores_sports_season_id_sports_seasons_id_fk", + "tableFrom": "team_sport_scores", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_standings": { + "name": "team_standings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "total_points": { + "name": "total_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "current_rank": { + "name": "current_rank", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "previous_rank": { + "name": "previous_rank", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "first_place_count": { + "name": "first_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "second_place_count": { + "name": "second_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "third_place_count": { + "name": "third_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "fourth_place_count": { + "name": "fourth_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "fifth_place_count": { + "name": "fifth_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "sixth_place_count": { + "name": "sixth_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "seventh_place_count": { + "name": "seventh_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "eighth_place_count": { + "name": "eighth_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "participants_remaining": { + "name": "participants_remaining", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "actual_points": { + "name": "actual_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "projected_points": { + "name": "projected_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "participants_finished": { + "name": "participants_finished", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "calculated_at": { + "name": "calculated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "team_standings_team_id_teams_id_fk": { + "name": "team_standings_team_id_teams_id_fk", + "tableFrom": "team_standings", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_standings_season_id_seasons_id_fk": { + "name": "team_standings_season_id_seasons_id_fk", + "tableFrom": "team_standings", + "tableTo": "seasons", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_standings_snapshots": { + "name": "team_standings_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "snapshot_date": { + "name": "snapshot_date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "total_points": { + "name": "total_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "rank": { + "name": "rank", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "first_place_count": { + "name": "first_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "second_place_count": { + "name": "second_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "third_place_count": { + "name": "third_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "fourth_place_count": { + "name": "fourth_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "fifth_place_count": { + "name": "fifth_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "sixth_place_count": { + "name": "sixth_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "seventh_place_count": { + "name": "seventh_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "eighth_place_count": { + "name": "eighth_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "participants_remaining": { + "name": "participants_remaining", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "actual_points": { + "name": "actual_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "projected_points": { + "name": "projected_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "participants_finished": { + "name": "participants_finished", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "team_standings_snapshots_unique": { + "name": "team_standings_snapshots_unique", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "snapshot_date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "team_standings_snapshots_team_id_teams_id_fk": { + "name": "team_standings_snapshots_team_id_teams_id_fk", + "tableFrom": "team_standings_snapshots", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_standings_snapshots_season_id_seasons_id_fk": { + "name": "team_standings_snapshots_season_id_seasons_id_fk", + "tableFrom": "team_standings_snapshots", + "tableTo": "seasons", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.teams": { + "name": "teams", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "season_id": { + "name": "season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "logo_url": { + "name": "logo_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "teams_season_id_seasons_id_fk": { + "name": "teams_season_id_seasons_id_fk", + "tableFrom": "teams", + "tableTo": "seasons", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tournament_group_members": { + "name": "tournament_group_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "tournament_group_id": { + "name": "tournament_group_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "eliminated": { + "name": "eliminated", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "tournament_group_members_tournament_group_id_tournament_groups_id_fk": { + "name": "tournament_group_members_tournament_group_id_tournament_groups_id_fk", + "tableFrom": "tournament_group_members", + "tableTo": "tournament_groups", + "columnsFrom": [ + "tournament_group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tournament_group_members_participant_id_participants_id_fk": { + "name": "tournament_group_members_participant_id_participants_id_fk", + "tableFrom": "tournament_group_members", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tournament_groups": { + "name": "tournament_groups", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "scoring_event_id": { + "name": "scoring_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "group_name": { + "name": "group_name", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "tournament_groups_scoring_event_id_scoring_events_id_fk": { + "name": "tournament_groups_scoring_event_id_scoring_events_id_fk", + "tableFrom": "tournament_groups", + "tableTo": "scoring_events", + "columnsFrom": [ + "scoring_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "clerk_id": { + "name": "clerk_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "display_name": { + "name": "display_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "first_name": { + "name": "first_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "last_name": { + "name": "last_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "image_url": { + "name": "image_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "is_admin": { + "name": "is_admin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_clerk_id_unique": { + "name": "users_clerk_id_unique", + "nullsNotDistinct": false, + "columns": [ + "clerk_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.autodraft_mode": { + "name": "autodraft_mode", + "schema": "public", + "values": [ + "next_pick", + "while_on" + ] + }, + "public.draft_timer_mode": { + "name": "draft_timer_mode", + "schema": "public", + "values": [ + "chess_clock", + "standard" + ] + }, + "public.event_type": { + "name": "event_type", + "schema": "public", + "values": [ + "playoff_game", + "major_tournament", + "final_standings", + "schedule_event" + ] + }, + "public.picked_by_type": { + "name": "picked_by_type", + "schema": "public", + "values": [ + "owner", + "commissioner", + "admin", + "auto" + ] + }, + "public.playoff_match_game_status": { + "name": "playoff_match_game_status", + "schema": "public", + "values": [ + "scheduled", + "complete", + "postponed" + ] + }, + "public.probability_source": { + "name": "probability_source", + "schema": "public", + "values": [ + "manual", + "futures_odds", + "elo_simulation", + "performance_model" + ] + }, + "public.scoring_pattern": { + "name": "scoring_pattern", + "schema": "public", + "values": [ + "playoff_bracket", + "season_standings", + "qualifying_points" + ] + }, + "public.scoring_type": { + "name": "scoring_type", + "schema": "public", + "values": [ + "playoffs", + "regular_season", + "majors" + ] + }, + "public.season_status": { + "name": "season_status", + "schema": "public", + "values": [ + "pre_draft", + "draft", + "active", + "completed" + ] + }, + "public.simulation_status": { + "name": "simulation_status", + "schema": "public", + "values": [ + "idle", + "running", + "failed" + ] + }, + "public.simulator_type": { + "name": "simulator_type", + "schema": "public", + "values": [ + "f1_standings", + "indycar_standings", + "golf_qualifying_points", + "playoff_bracket", + "ucl_bracket", + "ncaam_bracket", + "ncaaw_bracket", + "nba_bracket", + "nhl_bracket" + ] + }, + "public.sport_type": { + "name": "sport_type", + "schema": "public", + "values": [ + "team", + "individual" + ] + }, + "public.sports_season_status": { + "name": "sports_season_status", + "schema": "public", + "values": [ + "upcoming", + "active", + "completed" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index fd4b097..87cfc65 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -379,6 +379,20 @@ "when": 1773903201189, "tag": "0053_smooth_kingpin", "breakpoints": true + }, + { + "idx": 54, + "version": "7", + "when": 1774070769112, + "tag": "0054_massive_vulcan", + "breakpoints": true + }, + { + "idx": 55, + "version": "7", + "when": 1774072156237, + "tag": "0055_special_vampiro", + "breakpoints": true } ] } \ No newline at end of file