From bde1e6e5f09b74e3beef87730d0ae684b9741187 Mon Sep 17 00:00:00 2001 From: Chris Parsons <438676+chrisparsons83@users.noreply.github.com> Date: Thu, 26 Mar 2026 00:34:51 -0700 Subject: [PATCH] Add WNBA playoff simulator with SRS-based Elo ratings, fixes #125 (#231) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add WNBA playoff simulator with SRS-based Elo ratings, fixes #125 - New WNBASimulator: Monte Carlo (50k sims) projecting remaining regular season games → seeding → R1 Bo3 / Semis Bo5 / Finals Bo7 bracket - Hybrid Elo sourcing: pre-season uses futures odds (ICM); once avg gamesPlayed ≥ 5, switches automatically to SRS-derived Elo (elo = 1500 + srs × 20) - New WnbaStandingsAdapter: fetches ESPN standings + teams endpoints in parallel; includes zero-records for 2026 expansion teams (Portland Fire, Toronto Tempo) not yet in standings - Added srs column to regular_season_standings (migration 0063); stored as net rating proxy (avgPointsFor − avgPointsAgainst) - Added wnba_bracket to simulatorTypeEnum Co-Authored-By: Claude Sonnet 4.6 * Fix missing afterEach import in wnba standings test Co-Authored-By: Claude Sonnet 4.6 --------- Co-authored-by: Claude Sonnet 4.6 --- app/models/regular-season-standings.ts | 3 + .../__tests__/wnba-simulator.test.ts | 149 + app/services/simulations/registry.ts | 6 + app/services/simulations/wnba-simulator.ts | 317 ++ .../standings-sync/__tests__/nba.test.ts | 6 +- .../standings-sync/__tests__/wnba.test.ts | 276 ++ app/services/standings-sync/index.ts | 4 + app/services/standings-sync/types.ts | 1 + app/services/standings-sync/wnba.ts | 269 ++ database/schema.ts | 2 + drizzle/0063_bored_ultimo.sql | 3 + drizzle/meta/0063_snapshot.json | 4164 +++++++++++++++++ drizzle/meta/_journal.json | 7 + 13 files changed, 5206 insertions(+), 1 deletion(-) create mode 100644 app/services/simulations/__tests__/wnba-simulator.test.ts create mode 100644 app/services/simulations/wnba-simulator.ts create mode 100644 app/services/standings-sync/__tests__/wnba.test.ts create mode 100644 app/services/standings-sync/wnba.ts create mode 100644 drizzle/0063_bored_ultimo.sql create mode 100644 drizzle/meta/0063_snapshot.json diff --git a/app/models/regular-season-standings.ts b/app/models/regular-season-standings.ts index 3b867ee..9dbf8f7 100644 --- a/app/models/regular-season-standings.ts +++ b/app/models/regular-season-standings.ts @@ -22,6 +22,7 @@ export interface UpsertRegularSeasonStandingData { homeRecord?: string | null; awayRecord?: string | null; externalTeamId?: string | null; + srs?: number | null; syncedAt?: Date | null; } @@ -59,6 +60,7 @@ export async function upsertRegularSeasonStandings( homeRecord: r.homeRecord ?? null, awayRecord: r.awayRecord ?? null, externalTeamId: r.externalTeamId ?? null, + srs: r.srs !== null && r.srs !== undefined ? r.srs.toString() : null, syncedAt: r.syncedAt !== undefined ? r.syncedAt : now, updatedAt: now, })); @@ -90,6 +92,7 @@ export async function upsertRegularSeasonStandings( homeRecord: sql`excluded.home_record`, awayRecord: sql`excluded.away_record`, externalTeamId: sql`excluded.external_team_id`, + srs: sql`excluded.srs`, syncedAt: sql`excluded.synced_at`, updatedAt: sql`excluded.updated_at`, }, diff --git a/app/services/simulations/__tests__/wnba-simulator.test.ts b/app/services/simulations/__tests__/wnba-simulator.test.ts new file mode 100644 index 0000000..1d6a20a --- /dev/null +++ b/app/services/simulations/__tests__/wnba-simulator.test.ts @@ -0,0 +1,149 @@ +import { describe, it, expect } from "vitest"; +import { srsToElo, simSeriesN, resolveElo } from "../wnba-simulator"; +import { eloWinProbability } from "~/services/probability-engine"; + +// ─── srsToElo ───────────────────────────────────────────────────────────────── + +describe("srsToElo", () => { + it("maps SRS 0 to league-average Elo 1500", () => { + expect(srsToElo(0)).toBe(1500); + }); + + it("maps positive SRS above 1500", () => { + expect(srsToElo(5)).toBe(1600); + expect(srsToElo(10)).toBe(1700); + }); + + it("maps negative SRS below 1500", () => { + expect(srsToElo(-5)).toBe(1400); + expect(srsToElo(-10)).toBe(1300); + }); + + it("is linear with scale 20", () => { + expect(srsToElo(3)).toBe(srsToElo(1) + 40); + }); +}); + +// ─── eloWinProbability ──────────────────────────────────────────────────────── + +describe("eloWinProbability", () => { + it("returns 0.5 for equal Elo ratings", () => { + expect(eloWinProbability(1500, 1500)).toBeCloseTo(0.5, 6); + }); + + it("favors the higher-rated team", () => { + expect(eloWinProbability(1600, 1500)).toBeGreaterThan(0.5); + expect(eloWinProbability(1500, 1600)).toBeLessThan(0.5); + }); + + it("is anti-symmetric: P(A>B) + P(B>A) = 1", () => { + const p = eloWinProbability(1640, 1430); + expect(p + eloWinProbability(1430, 1640)).toBeCloseTo(1.0, 10); + }); + + it("SRS +10 vs SRS -10 gives ~76% win probability", () => { + // srsToElo(+10) = 1700, srsToElo(-10) = 1300 → gap 400 → P = 1/(1+10^(-1)) ≈ 0.909 + // Actually gap of 400 → P ≈ 0.909. Let's verify the ±10 SRS case: + // srsToElo(+10) = 1700, srsToElo(-10) = 1300 → gap = 400 → P ≈ 0.909 + const p = eloWinProbability(srsToElo(10), srsToElo(-10)); + expect(p).toBeGreaterThan(0.9); + expect(p).toBeLessThan(0.92); + }); + + it("SRS +5 vs SRS 0 gives ~64% win probability", () => { + // srsToElo(+5) = 1600, srsToElo(0) = 1500 → gap = 100 → P = 1/(1+10^(-0.25)) ≈ 0.640 + const p = eloWinProbability(srsToElo(5), srsToElo(0)); + expect(p).toBeGreaterThan(0.63); + expect(p).toBeLessThan(0.66); + }); +}); + +// ─── resolveElo ─────────────────────────────────────────────────────────────── + +describe("resolveElo", () => { + it("SRS mode with SRS present: uses SRS-derived Elo", () => { + expect(resolveElo(5, 1600, true)).toBe(srsToElo(5)); // 1600 + }); + + it("SRS mode with no SRS: falls back to futures Elo", () => { + expect(resolveElo(null, 1620, true)).toBe(1620); + }); + + it("SRS mode with no SRS and no futures: falls back to 1500", () => { + expect(resolveElo(null, null, true)).toBe(1500); + }); + + it("futures mode: uses futures Elo regardless of SRS", () => { + expect(resolveElo(8, 1650, false)).toBe(1650); + }); + + it("futures mode with no futures: falls back to 1500", () => { + expect(resolveElo(8, null, false)).toBe(1500); + }); +}); + +// ─── simSeriesN ─────────────────────────────────────────────────────────────── + +const makeTeam = (id: string, elo: number) => ({ + id, + name: id, + elo, + currentWins: 0, + remainingGames: 0, + winProb: 0.5, +}); + +describe("simSeriesN", () => { + it("best-of-3: winner reaches exactly 2 wins", () => { + const a = makeTeam("A", 1600); + const b = makeTeam("B", 1400); + for (let i = 0; i < 100; i++) { + const { winner, loser } = simSeriesN(a, b, 2); + expect(winner === a || winner === b).toBe(true); + expect(loser === a || loser === b).toBe(true); + expect(winner).not.toBe(loser); + } + }); + + it("best-of-5: winner and loser are always different teams", () => { + const a = makeTeam("A", 1500); + const b = makeTeam("B", 1500); + for (let i = 0; i < 100; i++) { + const { winner, loser } = simSeriesN(a, b, 3); + expect(winner).not.toBe(loser); + } + }); + + it("best-of-7: returns the input team objects (referential identity)", () => { + const a = makeTeam("TeamA", 1550); + const b = makeTeam("TeamB", 1450); + const { winner, loser } = simSeriesN(a, b, 4); + expect(winner === a || winner === b).toBe(true); + expect(loser === a || loser === b).toBe(true); + }); + + it("heavily favored team wins most best-of-3 series", () => { + const strong = makeTeam("Strong", 1800); + const weak = makeTeam("Weak", 1200); + let strongWins = 0; + const N = 1000; + for (let i = 0; i < N; i++) { + if (simSeriesN(strong, weak, 2).winner === strong) strongWins++; + } + // P(game) ≈ 0.994, P(series) should be > 0.99 + expect(strongWins / N).toBeGreaterThan(0.97); + }); + + it("even matchup produces roughly 50% win rate in a large sample", () => { + const a = makeTeam("A", 1500); + const b = makeTeam("B", 1500); + let aWins = 0; + const N = 2000; + for (let i = 0; i < N; i++) { + if (simSeriesN(a, b, 3).winner === a) aWins++; + } + // Should be close to 50% — allow ±5% + expect(aWins / N).toBeGreaterThan(0.45); + expect(aWins / N).toBeLessThan(0.55); + }); +}); diff --git a/app/services/simulations/registry.ts b/app/services/simulations/registry.ts index 5129cb4..a02254f 100644 --- a/app/services/simulations/registry.ts +++ b/app/services/simulations/registry.ts @@ -19,6 +19,7 @@ import { AFLSimulator } from "./afl-simulator"; import { SnookerSimulator } from "./snooker-simulator"; import { TennisSimulator } from "./tennis-simulator"; import { MLBSimulator } from "./mlb-simulator"; +import { WNBASimulator } from "./wnba-simulator"; export const SIMULATOR_TYPES = [ "f1_standings", @@ -34,6 +35,7 @@ export const SIMULATOR_TYPES = [ "snooker_bracket", "tennis_qualifying_points", "mlb_bracket", + "wnba_bracket", ] as const; export type SimulatorType = typeof SIMULATOR_TYPES[number]; @@ -110,6 +112,10 @@ const REGISTRY: Record Simul info: { name: "MLB Playoff Monte Carlo", description: "Simulates MLB division races + full playoff bracket (WC best-of-3, DS best-of-5, LCS/WS best-of-7) using Elo ratings calibrated from FanGraphs projected wins" }, create: () => new MLBSimulator(), }, + wnba_bracket: { + info: { name: "WNBA Playoff Monte Carlo", description: "Projects WNBA regular season seedings and simulates full playoff bracket (R1 best-of-3, Semis best-of-5, Finals best-of-7) using SRS-derived Elo ratings. SRS values sourced from basketball-reference.com." }, + create: () => new WNBASimulator(), + }, }; export function getSimulator(simulatorType: SimulatorType): Simulator { diff --git a/app/services/simulations/wnba-simulator.ts b/app/services/simulations/wnba-simulator.ts new file mode 100644 index 0000000..d8b5c0b --- /dev/null +++ b/app/services/simulations/wnba-simulator.ts @@ -0,0 +1,317 @@ +/** + * WNBA Playoff Simulator + * + * Monte Carlo simulation of the WNBA playoffs including regular season + * remainder projection and seeding. + * + * Algorithm: + * 1. Load participants, regular season standings, and futures odds in parallel. + * 2. Choose Elo source automatically based on season progress: + * Pre-season (avg gamesPlayed < SRS_GAMES_THRESHOLD): + * → futures odds via convertFuturesToElo() from participantExpectedValues + * → falls back to 1500 for teams without odds + * In-season (avg gamesPlayed >= SRS_GAMES_THRESHOLD): + * → SRS from regularSeasonStandings: elo = 1500 + srs * SRS_ELO_SCALE + * → falls back to futures Elo for teams without SRS, then 1500 + * 3. For each simulation: + * a. For each team, simulate remaining regular season games (40 - gamesPlayed) + * using Elo win probability vs. an average opponent (Elo 1500) + * → projectedWins = currentWins + simulatedRemainingWins + * b. Sort all teams by projected wins (desc) + random tiebreaker + * → Top 8 qualify for playoffs + * c. Simulate WNBA playoff bracket (no byes, no play-in): + * Round 1 (best-of-3): 1v8, 4v5, 2v7, 3v6 + * Semifinals (best-of-5): winner(1/8) vs winner(4/5), winner(2/7) vs winner(3/6) + * Finals (best-of-7): Semifinal winners + * 4. Track placement counts per scoring tier + * 5. Convert counts to probability distributions + * + * Win probability (Elo, PARITY_FACTOR = 400): + * P(A beats B) = 1 / (1 + 10^((eloB - eloA) / 400)) + * + * SRS → Elo conversion (in-season): + * elo = 1500 + srs * 20 + * Calibration: WNBA SRS typically ranges ±8–10. A ±10 SRS gap → ±200 Elo → ~76% win + * probability for the stronger team, which is appropriate for single-game WNBA matchups. + * Source: basketball-reference.com/wnba/years/YYYY.html (SRS column in team standings) + * + * Futures → Elo conversion (pre-season): + * Uses convertFuturesToElo() from probability-engine (same as BracketSimulator / UCLSimulator). + * Reads sourceOdds (American format) from participantExpectedValues. + * + * Placement tiers → SimulationProbabilities mapping: + * probFirst = WNBA champion (1 per sim) + * probSecond = Finals loser (1 per sim) + * probThird/Fourth = Semifinal losers (2 per sim) + * probFifth–Eighth = Round 1 losers (4 per sim) + * Missed playoffs → all 0 + */ + +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"; +import { getRegularSeasonStandings } from "~/models/regular-season-standings"; +import { convertFuturesToElo, eloWinProbability } from "~/services/probability-engine"; + +// ─── Simulation parameters ──────────────────────────────────────────────────── + +const NUM_SIMULATIONS = 50_000; + +/** WNBA regular season games per team. */ +const WNBA_REGULAR_SEASON_GAMES = 40; + +/** + * Multiplier converting SRS to Elo offset from 1500. + * elo = 1500 + srs * SRS_ELO_SCALE + * + * At scale 20: + * SRS +10 → Elo 1700, SRS -10 → Elo 1300 → P(+10 vs -10) ≈ 76% + * SRS +5 → Elo 1600, SRS 0 → Elo 1500 → P(+5 vs 0) ≈ 64% + */ +const SRS_ELO_SCALE = 20; + +/** + * Average games played threshold to switch from futures-derived Elo to SRS-derived Elo. + * Once most teams have played this many games, SRS is meaningful enough to use. + */ +const SRS_GAMES_THRESHOLD = 5; + +/** Number of playoff teams. */ +const PLAYOFF_TEAMS = 8; + +// ─── Public helpers (exported for unit testing) ─────────────────────────────── + +export { normalizeTeamName }; + +/** + * Convert an SRS rating to an Elo rating. + * An SRS of 0 maps to 1500 (league average). + */ +export function srsToElo(srs: number): number { + return 1500 + srs * SRS_ELO_SCALE; +} + +/** + * Resolve the Elo to use for a team given available signals and the current mode. + * + * In SRS mode (in-season): use SRS-derived Elo, fall back to futuresElo, then 1500. + * In futures mode (pre-season): use futuresElo, fall back to 1500. + */ +export function resolveElo( + srs: number | null, + futuresElo: number | null, + useSRS: boolean +): number { + if (useSRS) { + if (srs !== null) return srsToElo(srs); + return futuresElo ?? 1500; + } + return futuresElo ?? 1500; +} + +// ─── Internal types ─────────────────────────────────────────────────────────── + +interface TeamEntry { + id: string; + name: string; + elo: number; + currentWins: number; + remainingGames: number; + /** Elo win probability vs. average opponent (1500) — constant per team. */ + winProb: number; +} + +/** Simulate a best-of-N series. + * @param winTarget wins needed to win the series (2 for BoX3, 3 for BoX5, 4 for BoX7) */ +export function simSeriesN( + a: TeamEntry, + b: TeamEntry, + winTarget: number +): { winner: TeamEntry; loser: TeamEntry } { + const winProb = eloWinProbability(a.elo, b.elo); + let winsA = 0; + let winsB = 0; + while (winsA < winTarget && winsB < winTarget) { + if (Math.random() < winProb) winsA++; + else winsB++; + } + return winsA === winTarget ? { winner: a, loser: b } : { winner: b, loser: a }; +} + +/** Simulate remaining regular season games for a team. Returns projected total wins. */ +function simulateProjectedWins(team: TeamEntry): number { + let extra = 0; + for (let g = 0; g < team.remainingGames; g++) { + if (Math.random() < team.winProb) extra++; + } + return team.currentWins + extra; +} + +// ─── Simulator ──────────────────────────────────────────────────────────────── + +export class WNBASimulator implements Simulator { + async simulate(sportsSeasonId: string): Promise { + const db = database(); + + // 1. Load participants, standings, and futures odds in parallel. + const [participantRows, standings, evRows] = await Promise.all([ + db + .select({ id: schema.participants.id, name: schema.participants.name }) + .from(schema.participants) + .where(eq(schema.participants.sportsSeasonId, sportsSeasonId)), + getRegularSeasonStandings(sportsSeasonId), + db + .select({ + participantId: schema.participantExpectedValues.participantId, + sourceOdds: schema.participantExpectedValues.sourceOdds, + }) + .from(schema.participantExpectedValues) + .where(eq(schema.participantExpectedValues.sportsSeasonId, sportsSeasonId)), + ]); + + if (participantRows.length === 0) { + throw new Error( + `No participants found for sports season ${sportsSeasonId}. ` + + `Add WNBA teams as participants before running simulation.` + ); + } + + if (participantRows.length < PLAYOFF_TEAMS) { + throw new Error( + `WNBA simulation requires at least ${PLAYOFF_TEAMS} participants ` + + `(got ${participantRows.length}). Add all WNBA teams before running simulation.` + ); + } + + // 2. Build futures Elo map from championship odds. + const oddsInput = evRows + .filter((r) => r.sourceOdds !== null) + .map((r) => ({ participantId: r.participantId, odds: r.sourceOdds ?? 0 })); + const futuresEloMap: Map = + oddsInput.length > 0 + ? convertFuturesToElo(oddsInput, "american") + : new Map(); + + // 3. Build standings lookup and determine simulation mode. + const standingsMap = new Map(standings.map((s) => [s.participantId, s])); + const participantIds = participantRows.map((r) => r.id); + + const totalGamesPlayed = participantRows.reduce((sum, r) => { + return sum + (standingsMap.get(r.id)?.gamesPlayed ?? 0); + }, 0); + const avgGamesPlayed = totalGamesPlayed / participantRows.length; + const useSRS = avgGamesPlayed >= SRS_GAMES_THRESHOLD; + + // 4. Construct team entries with resolved Elo. + const teams: TeamEntry[] = participantRows.map((r) => { + const standing = standingsMap.get(r.id); + const srs = + standing?.srs !== null && standing?.srs !== undefined + ? parseFloat(standing.srs) + : null; + const futuresElo = futuresEloMap.get(r.id) ?? null; + const elo = resolveElo(srs, futuresElo, useSRS); + const gamesPlayed = standing?.gamesPlayed ?? 0; + return { + id: r.id, + name: r.name, + elo, + currentWins: standing?.wins ?? 0, + remainingGames: Math.max(0, WNBA_REGULAR_SEASON_GAMES - gamesPlayed), + winProb: eloWinProbability(elo, 1500), + }; + }); + + const source = useSRS + ? "wnba_bracket_monte_carlo_srs" + : "wnba_bracket_monte_carlo_futures"; + + /** Seed teams 1–8 by projected wins for this simulation. */ + const buildSeededBracket = (): TeamEntry[] => { + const projected = teams.map((t) => ({ + team: t, + projectedWins: simulateProjectedWins(t), + tiebreaker: Math.random(), + })); + projected.sort((a, b) => b.projectedWins - a.projectedWins || b.tiebreaker - a.tiebreaker); + return projected.slice(0, PLAYOFF_TEAMS).map((x) => x.team); + }; + + // 5. Integer placement count maps — initialized to 0 for all participants. + const championCounts = new Map(participantIds.map((id) => [id, 0])); + const finalistCounts = new Map(participantIds.map((id) => [id, 0])); + const semiLoserCounts = new Map(participantIds.map((id) => [id, 0])); + const r1LoserCounts = new Map(participantIds.map((id) => [id, 0])); + + // 6. Monte Carlo simulation loop. + for (let s = 0; s < NUM_SIMULATIONS; s++) { + const [s1, s2, s3, s4, s5, s6, s7, s8] = buildSeededBracket(); + + // Round 1 (best-of-3): 1v8, 4v5, 2v7, 3v6 + const r1_a = simSeriesN(s1, s8, 2); + const r1_b = simSeriesN(s4, s5, 2); + const r1_c = simSeriesN(s2, s7, 2); + const r1_d = simSeriesN(s3, s6, 2); + + r1LoserCounts.set(r1_a.loser.id, (r1LoserCounts.get(r1_a.loser.id) ?? 0) + 1); + r1LoserCounts.set(r1_b.loser.id, (r1LoserCounts.get(r1_b.loser.id) ?? 0) + 1); + r1LoserCounts.set(r1_c.loser.id, (r1LoserCounts.get(r1_c.loser.id) ?? 0) + 1); + r1LoserCounts.set(r1_d.loser.id, (r1LoserCounts.get(r1_d.loser.id) ?? 0) + 1); + + // Semifinals (best-of-5): winner(1/8) vs winner(4/5), winner(2/7) vs winner(3/6) + const sf_a = simSeriesN(r1_a.winner, r1_b.winner, 3); + const sf_b = simSeriesN(r1_c.winner, r1_d.winner, 3); + + semiLoserCounts.set(sf_a.loser.id, (semiLoserCounts.get(sf_a.loser.id) ?? 0) + 1); + semiLoserCounts.set(sf_b.loser.id, (semiLoserCounts.get(sf_b.loser.id) ?? 0) + 1); + + // Finals (best-of-7) + const final = simSeriesN(sf_a.winner, sf_b.winner, 4); + + championCounts.set(final.winner.id, (championCounts.get(final.winner.id) ?? 0) + 1); + finalistCounts.set(final.loser.id, (finalistCounts.get(final.loser.id) ?? 0) + 1); + } + + // 7. Convert integer counts to probability distributions. + const results: SimulationResult[] = participantIds.map((participantId) => { + const c = championCounts.get(participantId) ?? 0; + const f = finalistCounts.get(participantId) ?? 0; + const sl = semiLoserCounts.get(participantId) ?? 0; + const r1 = r1LoserCounts.get(participantId) ?? 0; + return { + participantId, + probabilities: { + probFirst: c / NUM_SIMULATIONS, + probSecond: f / NUM_SIMULATIONS, + probThird: sl / (2 * NUM_SIMULATIONS), + probFourth: sl / (2 * NUM_SIMULATIONS), + probFifth: r1 / (4 * NUM_SIMULATIONS), + probSixth: r1 / (4 * NUM_SIMULATIONS), + probSeventh: r1 / (4 * NUM_SIMULATIONS), + probEighth: r1 / (4 * NUM_SIMULATIONS), + }, + source, + }; + }); + + // 8. Per-position normalization — belt-and-suspenders guard against floating-point residuals. + const positionKeys: Array = [ + "probFirst", "probSecond", "probThird", "probFourth", + "probFifth", "probSixth", "probSeventh", "probEighth", + ]; + for (const key of positionKeys) { + const colSum = results.reduce((s, r) => s + r.probabilities[key], 0); + const residual = 1.0 - colSum; + if (residual !== 0) { + const maxResult = results.reduce((best, r) => + r.probabilities[key] > best.probabilities[key] ? r : best + ); + maxResult.probabilities[key] += residual; + } + } + + return results; + } +} diff --git a/app/services/standings-sync/__tests__/nba.test.ts b/app/services/standings-sync/__tests__/nba.test.ts index ad8aef0..3c540c4 100644 --- a/app/services/standings-sync/__tests__/nba.test.ts +++ b/app/services/standings-sync/__tests__/nba.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect, vi, beforeEach } from "vitest"; +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { NbaStandingsAdapter } from "../nba"; function makeStat(name: string, value: number, displayValue?: string) { @@ -84,6 +84,10 @@ describe("NbaStandingsAdapter", () => { vi.stubGlobal("fetch", vi.fn()); }); + afterEach(() => { + vi.restoreAllMocks(); + }); + it("maps ESPN API response to FetchedStandingsRecord[]", async () => { vi.mocked(fetch).mockResolvedValueOnce({ ok: true, diff --git a/app/services/standings-sync/__tests__/wnba.test.ts b/app/services/standings-sync/__tests__/wnba.test.ts new file mode 100644 index 0000000..11a5621 --- /dev/null +++ b/app/services/standings-sync/__tests__/wnba.test.ts @@ -0,0 +1,276 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import { WnbaStandingsAdapter } from "../wnba"; + +function makeStat(name: string, value: number, displayValue?: string) { + return { name, value, displayValue: displayValue ?? String(value) }; +} + +// WNBA standings response (13 active teams — flat conference structure, no divisions). +const SAMPLE_STANDINGS_RESPONSE = { + children: [ + { + name: "Eastern Conference", + standings: { + entries: [ + { + team: { id: "5", displayName: "New York Liberty", abbreviation: "NY" }, + stats: [ + makeStat("wins", 28), + makeStat("losses", 12), + makeStat("winPercent", 0.7), + makeStat("gamesBehind", 0), + makeStat("playoffSeed", 1), + { name: "streak", value: 3, displayValue: "W3" }, + { name: "Last Ten Games", value: 0, displayValue: "8-2" }, + { name: "Home", value: 0, displayValue: "15-5" }, + { name: "Road", value: 0, displayValue: "13-7" }, + makeStat("avgPointsFor", 89.5), + makeStat("avgPointsAgainst", 82.1), + ], + }, + { + team: { id: "3", displayName: "Connecticut Sun", abbreviation: "CONN" }, + stats: [ + makeStat("wins", 20), + makeStat("losses", 20), + makeStat("winPercent", 0.5), + makeStat("gamesBehind", 8), + makeStat("playoffSeed", 4), + { name: "streak", value: 1, displayValue: "L1" }, + { name: "Last Ten Games", value: 0, displayValue: "5-5" }, + { name: "Home", value: 0, displayValue: "11-9" }, + { name: "Road", value: 0, displayValue: "9-11" }, + makeStat("avgPointsFor", 78.0), + makeStat("avgPointsAgainst", 78.0), + ], + }, + ], + }, + }, + { + name: "Western Conference", + standings: { + entries: [ + { + team: { id: "20", displayName: "Las Vegas Aces", abbreviation: "LV" }, + stats: [ + makeStat("wins", 26), + makeStat("losses", 14), + makeStat("winPercent", 0.65), + makeStat("gamesBehind", 2), + makeStat("playoffSeed", 1), + { name: "streak", value: 2, displayValue: "W2" }, + { name: "Last Ten Games", value: 0, displayValue: "7-3" }, + { name: "Home", value: 0, displayValue: "14-6" }, + { name: "Road", value: 0, displayValue: "12-8" }, + makeStat("avgPointsFor", 91.2), + makeStat("avgPointsAgainst", 85.0), + ], + }, + ], + }, + }, + ], +}; + +// Teams endpoint response — includes 2 expansion teams not yet in standings. +const SAMPLE_TEAMS_RESPONSE = { + sports: [ + { + leagues: [ + { + teams: [ + { team: { id: "5", displayName: "New York Liberty", isActive: true } }, + { team: { id: "3", displayName: "Connecticut Sun", isActive: true } }, + { team: { id: "20", displayName: "Las Vegas Aces", isActive: true } }, + { team: { id: "99001", displayName: "Portland Fire", isActive: true } }, + { team: { id: "99002", displayName: "Toronto Tempo", isActive: true } }, + ], + }, + ], + }, + ], +}; + +// Teams response with no expansion teams beyond what's in standings. +const TEAMS_RESPONSE_NO_EXTRAS = { + sports: [{ leagues: [{ teams: [ + { team: { id: "5", displayName: "New York Liberty", isActive: true } }, + { team: { id: "3", displayName: "Connecticut Sun", isActive: true } }, + { team: { id: "20", displayName: "Las Vegas Aces", isActive: true } }, + ] }] }], +}; + +function mockFetch(standingsBody: unknown, teamsBody: unknown) { + vi.mocked(fetch) + .mockResolvedValueOnce({ ok: true, json: async () => standingsBody } as Response) + .mockResolvedValueOnce({ ok: true, json: async () => teamsBody } as Response); +} + +describe("WnbaStandingsAdapter", () => { + beforeEach(() => { + vi.stubGlobal("fetch", vi.fn()); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it("returns standings teams + zero-record expansion teams", async () => { + mockFetch(SAMPLE_STANDINGS_RESPONSE, SAMPLE_TEAMS_RESPONSE); + + const adapter = new WnbaStandingsAdapter(); + const records = await adapter.fetchStandings(); + + // 3 from standings + 2 expansion + expect(records).toHaveLength(5); + }); + + it("expansion teams have wins=0, losses=0, gamesPlayed=0, srs=null", async () => { + mockFetch(SAMPLE_STANDINGS_RESPONSE, SAMPLE_TEAMS_RESPONSE); + + const adapter = new WnbaStandingsAdapter(); + const records = await adapter.fetchStandings(); + + const portland = records.find((r) => r.teamName === "Portland Fire"); + if (!portland) throw new Error("Portland Fire not found"); + expect(portland.wins).toBe(0); + expect(portland.losses).toBe(0); + expect(portland.gamesPlayed).toBe(0); + expect(portland.srs).toBeNull(); + + const toronto = records.find((r) => r.teamName === "Toronto Tempo"); + if (!toronto) throw new Error("Toronto Tempo not found"); + expect(toronto.wins).toBe(0); + }); + + it("expansion teams rank below all active teams", async () => { + mockFetch(SAMPLE_STANDINGS_RESPONSE, SAMPLE_TEAMS_RESPONSE); + + const adapter = new WnbaStandingsAdapter(); + const records = await adapter.fetchStandings(); + + const maxActiveRank = Math.max( + ...records + .filter((r) => r.wins > 0 || r.gamesPlayed > 0) + .map((r) => r.leagueRank) + ); + const portland = records.find((r) => r.teamName === "Portland Fire"); + const toronto = records.find((r) => r.teamName === "Toronto Tempo"); + if (!portland) throw new Error("Portland Fire not found"); + if (!toronto) throw new Error("Toronto Tempo not found"); + expect(portland.leagueRank).toBeGreaterThan(maxActiveRank); + expect(toronto.leagueRank).toBeGreaterThan(maxActiveRank); + }); + + it("parses wins, losses, and gamesPlayed correctly", async () => { + mockFetch(SAMPLE_STANDINGS_RESPONSE, SAMPLE_TEAMS_RESPONSE); + + const adapter = new WnbaStandingsAdapter(); + const records = await adapter.fetchStandings(); + + const ny = records.find((r) => r.teamName === "New York Liberty"); + if (!ny) throw new Error("New York Liberty not found"); + expect(ny.wins).toBe(28); + expect(ny.losses).toBe(12); + expect(ny.gamesPlayed).toBe(40); + }); + + it("assigns leagueRank 1 to the team with most wins", async () => { + mockFetch(SAMPLE_STANDINGS_RESPONSE, SAMPLE_TEAMS_RESPONSE); + + const adapter = new WnbaStandingsAdapter(); + const records = await adapter.fetchStandings(); + + const rank1 = records.find((r) => r.leagueRank === 1); + expect(rank1?.teamName).toBe("New York Liberty"); + }); + + it("assigns conference from the standings response", async () => { + mockFetch(SAMPLE_STANDINGS_RESPONSE, SAMPLE_TEAMS_RESPONSE); + + const adapter = new WnbaStandingsAdapter(); + const records = await adapter.fetchStandings(); + + const ny = records.find((r) => r.teamName === "New York Liberty"); + expect(ny?.conference).toBe("Eastern Conference"); + + const lv = records.find((r) => r.teamName === "Las Vegas Aces"); + expect(lv?.conference).toBe("Western Conference"); + }); + + it("expansion teams have no division (WNBA has no divisions)", async () => { + mockFetch(SAMPLE_STANDINGS_RESPONSE, SAMPLE_TEAMS_RESPONSE); + + const adapter = new WnbaStandingsAdapter(); + const records = await adapter.fetchStandings(); + + for (const record of records) { + expect(record.division).toBeUndefined(); + } + }); + + it("computes srs as net rating (avgPointsFor - avgPointsAgainst)", async () => { + mockFetch(SAMPLE_STANDINGS_RESPONSE, SAMPLE_TEAMS_RESPONSE); + + const adapter = new WnbaStandingsAdapter(); + const records = await adapter.fetchStandings(); + + const ny = records.find((r) => r.teamName === "New York Liberty"); + expect(ny?.srs).toBeCloseTo(7.4, 1); // 89.5 - 82.1 + + const conn = records.find((r) => r.teamName === "Connecticut Sun"); + expect(conn?.srs).toBeCloseTo(0, 1); // 78.0 - 78.0 + }); + + it("does not duplicate teams that appear in both standings and teams list", async () => { + mockFetch(SAMPLE_STANDINGS_RESPONSE, TEAMS_RESPONSE_NO_EXTRAS); + + const adapter = new WnbaStandingsAdapter(); + const records = await adapter.fetchStandings(); + + expect(records).toHaveLength(3); + const names = records.map((r) => r.teamName); + expect(new Set(names).size).toBe(3); + }); + + it("extracts streak and lastTen from standings", async () => { + mockFetch(SAMPLE_STANDINGS_RESPONSE, SAMPLE_TEAMS_RESPONSE); + + const adapter = new WnbaStandingsAdapter(); + const records = await adapter.fetchStandings(); + + const ny = records.find((r) => r.teamName === "New York Liberty"); + expect(ny?.streak).toBe("W3"); + expect(ny?.lastTen).toBe("8-2"); + }); + + it("extracts home and away records", async () => { + mockFetch(SAMPLE_STANDINGS_RESPONSE, SAMPLE_TEAMS_RESPONSE); + + const adapter = new WnbaStandingsAdapter(); + const records = await adapter.fetchStandings(); + + const ny = records.find((r) => r.teamName === "New York Liberty"); + expect(ny?.homeRecord).toBe("15-5"); + expect(ny?.awayRecord).toBe("13-7"); + }); + + it("throws on non-ok standings response", async () => { + vi.mocked(fetch) + .mockResolvedValueOnce({ ok: false, status: 503, statusText: "Service Unavailable" } as Response) + .mockResolvedValueOnce({ ok: true, json: async () => SAMPLE_TEAMS_RESPONSE } as Response); + + const adapter = new WnbaStandingsAdapter(); + await expect(adapter.fetchStandings()).rejects.toThrow("503"); + }); + + it("throws on non-ok teams response", async () => { + vi.mocked(fetch) + .mockResolvedValueOnce({ ok: true, json: async () => SAMPLE_STANDINGS_RESPONSE } as Response) + .mockResolvedValueOnce({ ok: false, status: 429, statusText: "Too Many Requests" } as Response); + + const adapter = new WnbaStandingsAdapter(); + await expect(adapter.fetchStandings()).rejects.toThrow("429"); + }); +}); diff --git a/app/services/standings-sync/index.ts b/app/services/standings-sync/index.ts index 741c18f..1c272a4 100644 --- a/app/services/standings-sync/index.ts +++ b/app/services/standings-sync/index.ts @@ -9,6 +9,7 @@ import { NhlStandingsAdapter } from "./nhl"; import { NbaStandingsAdapter } from "./nba"; import { AflStandingsAdapter } from "./afl"; import { MlbStandingsAdapter } from "./mlb"; +import { WnbaStandingsAdapter } from "./wnba"; import type { StandingsSyncAdapter, SyncResult, UnmatchedTeam } from "./types"; /** @@ -26,6 +27,8 @@ function getAdapter(simulatorType: string): StandingsSyncAdapter { return new AflStandingsAdapter(); case "mlb_bracket": return new MlbStandingsAdapter(); + case "wnba_bracket": + return new WnbaStandingsAdapter(); case "f1_standings": throw new Error( "F1 standings sync is not yet implemented. Use the manual standings page." @@ -123,6 +126,7 @@ export async function syncStandings(sportsSeasonId: string): Promise homeRecord: record.homeRecord ?? null, awayRecord: record.awayRecord ?? null, externalTeamId: record.externalTeamId, + srs: record.srs ?? null, syncedAt: new Date(), }); } diff --git a/app/services/standings-sync/types.ts b/app/services/standings-sync/types.ts index 5991d2e..ae5274d 100644 --- a/app/services/standings-sync/types.ts +++ b/app/services/standings-sync/types.ts @@ -17,6 +17,7 @@ export interface FetchedStandingsRecord { lastTen?: string; homeRecord?: string; awayRecord?: string; + srs?: number | null; // Net rating or SRS proxy (pointsFor - pointsAgainst per game) } export interface StandingsSyncAdapter { diff --git a/app/services/standings-sync/wnba.ts b/app/services/standings-sync/wnba.ts new file mode 100644 index 0000000..10b6a35 --- /dev/null +++ b/app/services/standings-sync/wnba.ts @@ -0,0 +1,269 @@ +import type { FetchedStandingsRecord, StandingsSyncAdapter } from "./types"; + +const WNBA_STANDINGS_URL = + "https://site.api.espn.com/apis/v2/sports/basketball/wnba/standings"; + +/** + * Returns all registered WNBA teams (including pre-season expansion teams not yet + * in standings). Used to supplement the standings response so every team gets a record. + */ +const WNBA_TEAMS_URL = + "https://site.api.espn.com/apis/site/v2/sports/basketball/wnba/teams"; + +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[]; +} + +interface EspnTeamItem { + team: { + id: string; + displayName: string; + isActive?: boolean; + }; +} + +interface EspnTeamsResponse { + sports?: Array<{ + leagues?: Array<{ + teams?: EspnTeamItem[]; + }>; + }>; +} + +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. + * WNBA has conferences (East/West) but no divisions — entries are either directly + * under the conference group or nested one level deeper (ESPN sometimes wraps + * conferences in a single top-level group). Handles both layouts. + */ +function flattenEspnStandings( + response: EspnStandingsResponse +): Array<{ entry: EspnStandingsEntry; conference: string }> { + const results: Array<{ entry: EspnStandingsEntry; conference: string }> = []; + + for (const topGroup of response.children ?? []) { + if (topGroup.children && topGroup.children.length > 0) { + // Top-level group has sub-groups — each sub-group is a conference (no divisions). + for (const confGroup of topGroup.children) { + const conferenceName = confGroup.name ?? topGroup.name ?? ""; + for (const entry of confGroup.standings?.entries ?? []) { + results.push({ entry, conference: conferenceName }); + } + } + } else { + // Top-level group is the conference itself. + const conferenceName = topGroup.name ?? ""; + for (const entry of topGroup.standings?.entries ?? []) { + results.push({ entry, conference: conferenceName }); + } + } + } + + return results; +} + +/** Parse a standings entry into a FetchedStandingsRecord (rank assigned by caller). */ +function parseEntry( + entry: EspnStandingsEntry, + conference: string, + leagueRank: number +): 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; + const lastTen = + sm.get("Last Ten Games")?.displayValue ?? + sm.get("L10")?.displayValue ?? + undefined; + const homeRecord = sm.get("Home")?.displayValue ?? undefined; + const awayRecord = sm.get("Road")?.displayValue ?? undefined; + + const playoffSeedStat = sm.get("playoffSeed"); + const conferenceRank = + playoffSeedStat?.value !== undefined && playoffSeedStat.value !== null + ? Math.round(playoffSeedStat.value) + : playoffSeedStat?.displayValue + ? parseInt(playoffSeedStat.displayValue, 10) || undefined + : undefined; + + // SRS proxy: average point differential per game. + // ESPN exposes "avgPointsFor" and "avgPointsAgainst" (or "pointsFor"/"pointsAgainst"). + // If unavailable, fall back to null so the simulator uses the league-average Elo (1500). + const ptFor = + sm.get("avgPointsFor")?.value ?? + sm.get("pointsFor")?.value ?? + null; + const ptAgainst = + sm.get("avgPointsAgainst")?.value ?? + sm.get("pointsAgainst")?.value ?? + null; + const srs = + ptFor !== null && ptFor !== undefined && + ptAgainst !== null && ptAgainst !== undefined + ? Math.round((ptFor - ptAgainst) * 100) / 100 + : null; + + return { + teamName: entry.team.displayName, + externalTeamId: entry.team.id, + wins: Math.round(wins), + losses: Math.round(losses), + winPct: winPercent, + gamesPlayed: Math.round(wins) + Math.round(losses), + gamesBack: gamesBehind, + conference: conference || undefined, + conferenceRank, + leagueRank, + streak, + lastTen, + homeRecord, + awayRecord, + srs, + }; +} + +export class WnbaStandingsAdapter implements StandingsSyncAdapter { + async fetchStandings(): Promise { + // Fetch both endpoints in parallel. + const [standingsResponse, teamsResponse] = await Promise.all([ + fetch(WNBA_STANDINGS_URL), + fetch(WNBA_TEAMS_URL), + ]); + + if (!standingsResponse.ok) { + throw new Error( + `WNBA standings API returned ${standingsResponse.status}: ${standingsResponse.statusText}` + ); + } + if (!teamsResponse.ok) { + throw new Error( + `WNBA teams API returned ${teamsResponse.status}: ${teamsResponse.statusText}` + ); + } + + const [standingsJson, teamsJson] = await Promise.all([ + standingsResponse.json() as Promise, + teamsResponse.json() as Promise, + ]); + + // Build standings records from the standings endpoint, sorted by wins desc. + // Pre-parse each entry once so statsMap isn't rebuilt multiple times per entry. + const flattened = flattenEspnStandings(standingsJson).map(({ entry, conference }) => ({ + entry, + conference, + sm: statsMap(entry.stats), + })); + const sortedEntries = [...flattened].toSorted((a, b) => { + const winsA = a.sm.get("wins")?.value ?? 0; + const winsB = b.sm.get("wins")?.value ?? 0; + if (winsB !== winsA) return winsB - winsA; + const lossA = a.sm.get("losses")?.value ?? 0; + const lossB = b.sm.get("losses")?.value ?? 0; + return lossA - lossB; + }); + + // Map externalTeamId → parsed record (so we can detect which teams are missing). + const recordsByTeamId = new Map(); + sortedEntries.forEach(({ entry, conference }, idx) => { + recordsByTeamId.set( + entry.team.id, + parseEntry(entry, conference, idx + 1) + ); + }); + + // Extract the full team list from the teams endpoint. + const allTeams: Array<{ id: string; displayName: string }> = ( + teamsJson.sports?.[0]?.leagues?.[0]?.teams ?? [] + ) + .filter((t) => t.team.isActive !== false) + .map((t) => ({ id: t.team.id, displayName: t.team.displayName })); + + if (allTeams.length === 0 && recordsByTeamId.size === 0) { + throw new Error( + "WNBA standings API returned no entries — response shape may have changed" + ); + } + + // Merge: use standing records where available; fill in zero-records for + // expansion teams not yet in the standings (pre-season or mid-season addition). + const results: FetchedStandingsRecord[] = []; + const seenIds = new Set(); + + // First, add all teams that have standings data (preserving their rank). + for (const record of recordsByTeamId.values()) { + results.push(record); + seenIds.add(record.externalTeamId); + } + + // Then, append zero-records for any team in the teams list but not in standings. + let expansionRank = results.length + 1; + for (const team of allTeams) { + if (!seenIds.has(team.id)) { + results.push({ + teamName: team.displayName, + externalTeamId: team.id, + wins: 0, + losses: 0, + winPct: 0, + gamesPlayed: 0, + leagueRank: expansionRank++, + srs: null, + }); + } + } + + // If the teams endpoint returned nothing (API change), fall back to standings only. + if (results.length === 0) { + throw new Error( + "WNBA standings API returned no entries — response shape may have changed" + ); + } + + return results; + } +} diff --git a/database/schema.ts b/database/schema.ts index f7b9212..794eb39 100644 --- a/database/schema.ts +++ b/database/schema.ts @@ -96,6 +96,7 @@ export const simulatorTypeEnum = pgEnum("simulator_type", [ "snooker_bracket", "tennis_qualifying_points", "mlb_bracket", + "wnba_bracket", ]); export const playoffMatchGameStatusEnum = pgEnum("playoff_match_game_status", [ @@ -1031,6 +1032,7 @@ export const regularSeasonStandings = pgTable("regular_season_standings", { homeRecord: varchar("home_record", { length: 15 }), awayRecord: varchar("away_record", { length: 15 }), externalTeamId: varchar("external_team_id", { length: 255 }), + srs: decimal("srs", { precision: 6, scale: 2 }), // Simple Rating System (point diff adjusted for SOS) syncedAt: timestamp("synced_at"), // null = manually entered createdAt: timestamp("created_at").defaultNow().notNull(), updatedAt: timestamp("updated_at").defaultNow().notNull(), diff --git a/drizzle/0063_bored_ultimo.sql b/drizzle/0063_bored_ultimo.sql new file mode 100644 index 0000000..d5e18c2 --- /dev/null +++ b/drizzle/0063_bored_ultimo.sql @@ -0,0 +1,3 @@ +ALTER TYPE "public"."simulator_type" ADD VALUE IF NOT EXISTS 'mlb_bracket';--> statement-breakpoint +ALTER TYPE "public"."simulator_type" ADD VALUE 'wnba_bracket';--> statement-breakpoint +ALTER TABLE "regular_season_standings" ADD COLUMN "srs" numeric(6, 2); diff --git a/drizzle/meta/0063_snapshot.json b/drizzle/meta/0063_snapshot.json new file mode 100644 index 0000000..a30834c --- /dev/null +++ b/drizzle/meta/0063_snapshot.json @@ -0,0 +1,4164 @@ +{ + "id": "30dea482-ef4b-44bb-9ca4-c3015982bc4d", + "prevId": "ba4f68ce-4b0d-49f8-aaaf-2ab318c346c3", + "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 + }, + "source_elo": { + "name": "source_elo", + "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": { + "participant_ev_participant_season_unique": { + "name": "participant_ev_participant_season_unique", + "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": { + "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_golf_skills": { + "name": "participant_golf_skills", + "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 + }, + "sg_total": { + "name": "sg_total", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": false + }, + "datagolf_rank": { + "name": "datagolf_rank", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "masters_odds": { + "name": "masters_odds", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "us_open_odds": { + "name": "us_open_odds", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "open_championship_odds": { + "name": "open_championship_odds", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "pga_championship_odds": { + "name": "pga_championship_odds", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "participant_golf_skills_unique": { + "name": "participant_golf_skills_unique", + "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": { + "participant_golf_skills_participant_id_participants_id_fk": { + "name": "participant_golf_skills_participant_id_participants_id_fk", + "tableFrom": "participant_golf_skills", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "participant_golf_skills_sports_season_id_sports_seasons_id_fk": { + "name": "participant_golf_skills_sports_season_id_sports_seasons_id_fk", + "tableFrom": "participant_golf_skills", + "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.participant_surface_elos": { + "name": "participant_surface_elos", + "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 + }, + "world_ranking": { + "name": "world_ranking", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "elo_hard": { + "name": "elo_hard", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "elo_clay": { + "name": "elo_clay", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "elo_grass": { + "name": "elo_grass", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "participant_surface_elos_unique": { + "name": "participant_surface_elos_unique", + "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": { + "participant_surface_elos_participant_id_participants_id_fk": { + "name": "participant_surface_elos_participant_id_participants_id_fk", + "tableFrom": "participant_surface_elos", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "participant_surface_elos_sports_season_id_sports_seasons_id_fk": { + "name": "participant_surface_elos_sports_season_id_sports_seasons_id_fk", + "tableFrom": "participant_surface_elos", + "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 + }, + "srs": { + "name": "srs", + "type": "numeric(6, 2)", + "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", + "afl_bracket", + "snooker_bracket", + "tennis_qualifying_points", + "mlb_bracket", + "wnba_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 3e428c0..de78bc7 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -442,6 +442,13 @@ "when": 1774500000000, "tag": "0062_add_mlb_bracket_simulator_type", "breakpoints": true + }, + { + "idx": 63, + "version": "7", + "when": 1774504559479, + "tag": "0063_bored_ultimo", + "breakpoints": true } ] } \ No newline at end of file