From 5b261bf25874d1886f00803b51caff3acce503b5 Mon Sep 17 00:00:00 2001 From: Chris Parsons <438676+chrisparsons83@users.noreply.github.com> Date: Tue, 12 May 2026 14:22:34 -0700 Subject: [PATCH] Add NLL box lacrosse simulator implementation plan and preseason-draftability guidance (#417) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs: plan NLL preseason simulator * Add NLL Season + Playoffs Monte Carlo simulator 14-team regular season (18 games) projects top-8 via Elo + decaying projectedWins prior (adjusts for games already played). Playoff bracket: QF single-game (1v8, 2v7, 3v6, 4v5), SF and Finals best-of-3. Three runtime modes: bracket-aware → known-seed → regular-season projection. Co-Authored-By: Claude Sonnet 4.6 --------- Co-authored-by: Claude Sonnet 4.6 --- .../__tests__/nll-simulator.test.ts | 506 ++ app/services/simulations/manifest.ts | 17 + app/services/simulations/nll-simulator.ts | 644 ++ app/services/simulations/registry.ts | 9 + app/services/simulations/simulator-config.ts | 5 + database/schema.ts | 1 + .../nll-simulator-implementation-plan.md | 272 + docs/agents/simulators.md | 12 + drizzle/0103_salty_runaways.sql | 1 + drizzle/meta/0103_snapshot.json | 6112 +++++++++++++++++ drizzle/meta/_journal.json | 7 + 11 files changed, 7586 insertions(+) create mode 100644 app/services/simulations/__tests__/nll-simulator.test.ts create mode 100644 app/services/simulations/nll-simulator.ts create mode 100644 docs/agents/nll-simulator-implementation-plan.md create mode 100644 drizzle/0103_salty_runaways.sql create mode 100644 drizzle/meta/0103_snapshot.json diff --git a/app/services/simulations/__tests__/nll-simulator.test.ts b/app/services/simulations/__tests__/nll-simulator.test.ts new file mode 100644 index 0000000..641449f --- /dev/null +++ b/app/services/simulations/__tests__/nll-simulator.test.ts @@ -0,0 +1,506 @@ +import { describe, it, expect } from "vitest"; +import { + nllGameWinProbability, + simulateNllGame, + simulateBestOfThree, + buildNllBracketFromSeeds, + simulateNllPlayoffs, + simulateRegularSeasonSeeds, + resolveQfMatch, + resolveBo3Match, + type SeedEntry, + type SimTeam, + type MatchSnapshot, + type GameSnapshot, +} from "../nll-simulator"; +import { SIMULATOR_TYPES } from "../registry"; +import { SIMULATOR_MANIFEST } from "../manifest"; + +// ─── Helpers ────────────────────────────────────────────────────────────────── + +function makeSeed(participantId: string, seed: number, elo = 1500): SeedEntry { + return { participantId, elo, seed }; +} + +function make14Teams(eloFn = (_i: number) => 1500) { + return Array.from({ length: 14 }, (_, i) => ({ + participantId: `team-${i + 1}`, + elo: eloFn(i), + currentWins: 0, + gamesPlayed: 0, + projectedWins: null as number | null, + })); +} + +// ─── nllGameWinProbability ───────────────────────────────────────────────────── + +describe("nllGameWinProbability", () => { + it("returns 0.5 for equal Elo", () => { + expect(nllGameWinProbability(1500, 1500)).toBeCloseTo(0.5); + }); + + it("favours the higher-Elo team", () => { + expect(nllGameWinProbability(1600, 1400)).toBeGreaterThan(0.5); + }); + + it("returns a probability in [0, 1]", () => { + const p = nllGameWinProbability(2000, 1000); + expect(p).toBeGreaterThan(0); + expect(p).toBeLessThan(1); + }); + + it("uses the supplied parityFactor", () => { + const pLoose = nllGameWinProbability(1600, 1400, 1000); + const pTight = nllGameWinProbability(1600, 1400, 200); + expect(pLoose).toBeLessThan(pTight); + }); +}); + +// ─── simulateNllGame ────────────────────────────────────────────────────────── + +describe("simulateNllGame", () => { + it("returns one winner and one loser from the two teams", () => { + const a: SimTeam = { participantId: "A", elo: 1500 }; + const b: SimTeam = { participantId: "B", elo: 1500 }; + const result = simulateNllGame(a, b); + const ids = [result.winner.participantId, result.loser.participantId].toSorted(); + expect(ids).toEqual(["A", "B"]); + }); + + it("winner and loser are different", () => { + const a: SimTeam = { participantId: "A", elo: 1500 }; + const b: SimTeam = { participantId: "B", elo: 1500 }; + const result = simulateNllGame(a, b); + expect(result.winner.participantId).not.toBe(result.loser.participantId); + }); + + it("over many trials, higher-Elo team wins more often", () => { + const strong: SimTeam = { participantId: "strong", elo: 1700 }; + const weak: SimTeam = { participantId: "weak", elo: 1300 }; + let wins = 0; + for (let i = 0; i < 10_000; i++) { + if (simulateNllGame(strong, weak).winner.participantId === "strong") wins++; + } + expect(wins).toBeGreaterThan(7000); + }); +}); + +// ─── simulateBestOfThree ────────────────────────────────────────────────────── + +describe("simulateBestOfThree", () => { + it("returns one winner and one loser", () => { + const a: SimTeam = { participantId: "A", elo: 1500 }; + const b: SimTeam = { participantId: "B", elo: 1500 }; + const result = simulateBestOfThree(a, b); + expect(result.winner.participantId).not.toBe(result.loser.participantId); + const ids = [result.winner.participantId, result.loser.participantId].toSorted(); + expect(ids).toEqual(["A", "B"]); + }); + + it("gamesPlayed is between 2 and 3", () => { + const a: SimTeam = { participantId: "A", elo: 1500 }; + const b: SimTeam = { participantId: "B", elo: 1500 }; + for (let i = 0; i < 1000; i++) { + const { gamesPlayed } = simulateBestOfThree(a, b); + expect(gamesPlayed).toBeGreaterThanOrEqual(2); + expect(gamesPlayed).toBeLessThanOrEqual(3); + } + }); + + it("from a 1-1 state always plays exactly 1 more game", () => { + const a: SimTeam = { participantId: "A", elo: 1500 }; + const b: SimTeam = { participantId: "B", elo: 1500 }; + for (let i = 0; i < 1000; i++) { + const { gamesPlayed } = simulateBestOfThree(a, b, 400, { winsA: 1, winsB: 1 }); + expect(gamesPlayed).toBe(1); + } + }); + + it("respects existing series state: team with 2 wins is declared winner immediately", () => { + const a: SimTeam = { participantId: "A", elo: 1500 }; + const b: SimTeam = { participantId: "B", elo: 1500 }; + const result = simulateBestOfThree(a, b, 400, { winsA: 2, winsB: 0 }); + expect(result.winner.participantId).toBe("A"); + }); + + it("simulates from a 1-0 state correctly", () => { + const a: SimTeam = { participantId: "A", elo: 1900 }; + const b: SimTeam = { participantId: "B", elo: 1100 }; + let aWins = 0; + for (let i = 0; i < 5_000; i++) { + const r = simulateBestOfThree(a, b, 400, { winsA: 1, winsB: 0 }); + if (r.winner.participantId === "A") aWins++; + } + // A is heavily favoured and already leads 1-0 + expect(aWins).toBeGreaterThan(4000); + }); +}); + +// ─── buildNllBracketFromSeeds ───────────────────────────────────────────────── + +describe("buildNllBracketFromSeeds", () => { + const seeds = Array.from({ length: 8 }, (_, i) => makeSeed(`t${i + 1}`, i + 1)); + + it("produces 4 matchups", () => { + const bracket = buildNllBracketFromSeeds(seeds); + expect(bracket).toHaveLength(4); + }); + + it("QF1 is seed 1 vs seed 8", () => { + const [[a, b]] = buildNllBracketFromSeeds(seeds); + expect(a.seed).toBe(1); + expect(b.seed).toBe(8); + }); + + it("QF2 is seed 4 vs seed 5", () => { + const [, [a, b]] = buildNllBracketFromSeeds(seeds); + expect(a.seed).toBe(4); + expect(b.seed).toBe(5); + }); + + it("QF3 is seed 2 vs seed 7", () => { + const [, , [a, b]] = buildNllBracketFromSeeds(seeds); + expect(a.seed).toBe(2); + expect(b.seed).toBe(7); + }); + + it("QF4 is seed 3 vs seed 6", () => { + const [, , , [a, b]] = buildNllBracketFromSeeds(seeds); + expect(a.seed).toBe(3); + expect(b.seed).toBe(6); + }); + + it("throws if a required seed is missing", () => { + const incomplete = seeds.slice(0, 7); // missing seed 8 + expect(() => buildNllBracketFromSeeds(incomplete)).toThrow(); + }); +}); + +// ─── simulateNllPlayoffs ────────────────────────────────────────────────────── + +describe("simulateNllPlayoffs", () => { + const seeds = Array.from({ length: 8 }, (_, i) => makeSeed(`t${i + 1}`, i + 1)); + + it("returns exactly one champion", () => { + const result = simulateNllPlayoffs(seeds); + expect(typeof result.champion).toBe("string"); + }); + + it("returns exactly one finalist", () => { + const result = simulateNllPlayoffs(seeds); + expect(typeof result.finalist).toBe("string"); + }); + + it("champion and finalist are different", () => { + const result = simulateNllPlayoffs(seeds); + expect(result.champion).not.toBe(result.finalist); + }); + + it("returns 2 SF losers", () => { + const result = simulateNllPlayoffs(seeds); + expect(result.sfLosers).toHaveLength(2); + }); + + it("returns 4 QF losers", () => { + const result = simulateNllPlayoffs(seeds); + expect(result.qfLosers).toHaveLength(4); + }); + + it("champion comes from the playoff field (seeds 1–8)", () => { + const ids = new Set(seeds.map((s) => s.participantId)); + const result = simulateNllPlayoffs(seeds); + expect(ids.has(result.champion)).toBe(true); + }); + + it("all 8 participants appear exactly once across all placements", () => { + const result = simulateNllPlayoffs(seeds); + const all = [ + result.champion, + result.finalist, + ...result.sfLosers, + ...result.qfLosers, + ]; + expect(all).toHaveLength(8); + expect(new Set(all).size).toBe(8); + }); + + it("higher-Elo team (seed 1) wins more often than lower-Elo team (seed 8)", () => { + const strongSeeds = Array.from({ length: 8 }, (_, i) => + makeSeed(`t${i + 1}`, i + 1, 1800 - i * 50) + ); + let seed1Wins = 0; + for (let i = 0; i < 5_000; i++) { + if (simulateNllPlayoffs(strongSeeds).champion === "t1") seed1Wins++; + } + expect(seed1Wins).toBeGreaterThan(1500); + }); +}); + +// ─── simulateRegularSeasonSeeds ─────────────────────────────────────────────── + +describe("simulateRegularSeasonSeeds", () => { + it("returns exactly 8 seeds from a 14-team field", () => { + const teams = make14Teams(); + const seeds = simulateRegularSeasonSeeds(teams); + expect(seeds).toHaveLength(8); + }); + + it("seeds are numbered 1–8 with no duplicates", () => { + const teams = make14Teams(); + const seeds = simulateRegularSeasonSeeds(teams); + const seedNums = seeds.map((s) => s.seed).toSorted((a, b) => a - b); + expect(seedNums).toEqual([1, 2, 3, 4, 5, 6, 7, 8]); + }); + + it("all returned participant IDs are from the input teams", () => { + const teams = make14Teams(); + const ids = new Set(teams.map((t) => t.participantId)); + const seeds = simulateRegularSeasonSeeds(teams); + for (const s of seeds) { + expect(ids.has(s.participantId)).toBe(true); + } + }); + + it("higher-Elo teams qualify for playoffs more often", () => { + const teams = make14Teams((i) => i === 0 ? 1900 : 1400); + let qualifiedCount = 0; + for (let i = 0; i < 1000; i++) { + const seeds = simulateRegularSeasonSeeds(teams); + if (seeds.some((s) => s.participantId === "team-1")) qualifiedCount++; + } + expect(qualifiedCount).toBeGreaterThan(900); + }); + + it("team with higher projectedWins qualifies more often", () => { + const base = make14Teams(() => 1500); + base[0] = { ...base[0], projectedWins: 17 }; // 17/18 projected wins + base[1] = { ...base[1], projectedWins: 2 }; // 2/18 projected wins + + let t1Qualifies = 0; + let t2Qualifies = 0; + for (let i = 0; i < 1000; i++) { + const seeds = simulateRegularSeasonSeeds(base); + const ids = seeds.map((s) => s.participantId); + if (ids.includes("team-1")) t1Qualifies++; + if (ids.includes("team-2")) t2Qualifies++; + } + expect(t1Qualifies).toBeGreaterThan(t2Qualifies); + }); + + it("mid-season: prior adjusts for remaining wins — team with more projected wins still ahead qualifies more", () => { + // All teams are at the halfway point with 0 currentWins so currentWins don't confound. + // team-1 has a very high remaining projection; team-2 has a very low one. + // With equal Elo, the prior blend should make team-1 qualify more. + const teams = make14Teams(() => 1500).map((t) => ({ + ...t, currentWins: 0, gamesPlayed: 9, + })); + teams[0] = { ...teams[0], projectedWins: 17 }; // priorRate = min(1, 17/9) = 1.0 + teams[1] = { ...teams[1], projectedWins: 2 }; // priorRate = 2/9 ≈ 0.22 + + let t1Qualifies = 0; + let t2Qualifies = 0; + for (let i = 0; i < 1000; i++) { + const seeds = simulateRegularSeasonSeeds(teams); + const ids = seeds.map((s) => s.participantId); + if (ids.includes("team-1")) t1Qualifies++; + if (ids.includes("team-2")) t2Qualifies++; + } + expect(t1Qualifies).toBeGreaterThan(t2Qualifies); + }); + + it("works with a full pre-season 0-0 state", () => { + const teams = make14Teams(() => 1500); + expect(() => simulateRegularSeasonSeeds(teams)).not.toThrow(); + }); + + it("respects existing wins as a floor (current wins carry over)", () => { + // Team with 16 wins already can't go below 16. + const teams = make14Teams(() => 1500); + teams[0] = { ...teams[0], currentWins: 16, gamesPlayed: 17 }; + for (let i = 0; i < 100; i++) { + const seeds = simulateRegularSeasonSeeds(teams); + // team-1 must be seeded (has ≥16 wins, max 18 total) + expect(seeds.some((s) => s.participantId === "team-1")).toBe(true); + } + }); +}); + +// ─── Output probability column sums ────────────────────────────────────────── + +describe("simulateNllPlayoffs probability column sums", () => { + it("probFirst sums to 1 across all participants", () => { + const N = 1000; + const seeds = Array.from({ length: 8 }, (_, i) => + makeSeed(`t${i + 1}`, i + 1, 1500) + ); + const counts = new Map(seeds.map((s) => [s.participantId, 0])); + for (let i = 0; i < N; i++) { + const r = simulateNllPlayoffs(seeds); + counts.set(r.champion, (counts.get(r.champion) ?? 0) + 1); + } + const total = [...counts.values()].reduce((s, c) => s + c, 0); + expect(total).toBe(N); + }); + + it("probThird+Fourth sums: 2 SF losers per sim → total = 2*N", () => { + const N = 1000; + const seeds = Array.from({ length: 8 }, (_, i) => + makeSeed(`t${i + 1}`, i + 1, 1500) + ); + let total = 0; + for (let i = 0; i < N; i++) { + total += simulateNllPlayoffs(seeds).sfLosers.length; + } + expect(total).toBe(2 * N); + }); + + it("probFifth–Eighth sums: 4 QF losers per sim → total = 4*N", () => { + const N = 1000; + const seeds = Array.from({ length: 8 }, (_, i) => + makeSeed(`t${i + 1}`, i + 1, 1500) + ); + let total = 0; + for (let i = 0; i < N; i++) { + total += simulateNllPlayoffs(seeds).qfLosers.length; + } + expect(total).toBe(4 * N); + }); +}); + +// ─── resolveQfMatch ─────────────────────────────────────────────────────────── + +function makeMatch(overrides: Partial = {}): MatchSnapshot { + return { + id: "match-1", + isComplete: false, + winnerId: null, + loserId: null, + participant1Id: "A", + participant2Id: "B", + ...overrides, + }; +} + +describe("resolveQfMatch", () => { + it("returns stored result deterministically when match is complete", () => { + const match = makeMatch({ isComplete: true, winnerId: "A", loserId: "B" }); + const eloMap = new Map([["A", 1500], ["B", 1500]]); + for (let i = 0; i < 100; i++) { + const result = resolveQfMatch(match, eloMap); + expect(result.winner).toBe("A"); + expect(result.loser).toBe("B"); + } + }); + + it("simulates a game when match is incomplete", () => { + const match = makeMatch(); + const eloMap = new Map([["A", 1500], ["B", 1500]]); + const result = resolveQfMatch(match, eloMap); + const ids = [result.winner, result.loser].toSorted(); + expect(ids).toEqual(["A", "B"]); + }); + + it("throws when participant slots are missing", () => { + const match = makeMatch({ participant1Id: null }); + const eloMap = new Map(); + expect(() => resolveQfMatch(match, eloMap)).toThrow(); + }); + + it("throws when match is undefined", () => { + const eloMap = new Map(); + expect(() => resolveQfMatch(undefined, eloMap)).toThrow(); + }); +}); + +// ─── resolveBo3Match ────────────────────────────────────────────────────────── + +function noGames(): GameSnapshot[] { + return []; +} + +describe("resolveBo3Match", () => { + it("returns stored result deterministically when match is complete", () => { + const match = makeMatch({ isComplete: true, winnerId: "B", loserId: "A" }); + const eloMap = new Map([["A", 1500], ["B", 1500]]); + for (let i = 0; i < 100; i++) { + const result = resolveBo3Match(match, noGames(), eloMap); + expect(result.winner).toBe("B"); + expect(result.loser).toBe("A"); + } + }); + + it("simulates from scratch when no games played", () => { + const match = makeMatch(); + const eloMap = new Map([["A", 1500], ["B", 1500]]); + const result = resolveBo3Match(match, noGames(), eloMap); + const ids = [result.winner, result.loser].toSorted(); + expect(ids).toEqual(["A", "B"]); + }); + + it("returns winner deterministically when game rows show 2 wins for p1", () => { + const match = makeMatch(); + const games: GameSnapshot[] = [{ winnerId: "A" }, { winnerId: "A" }]; + const eloMap = new Map([["A", 1500], ["B", 1500]]); + for (let i = 0; i < 100; i++) { + const result = resolveBo3Match(match, games, eloMap); + expect(result.winner).toBe("A"); + expect(result.loser).toBe("B"); + } + }); + + it("simulates correctly from a 1-1 game state", () => { + const match = makeMatch(); + const games: GameSnapshot[] = [{ winnerId: "A" }, { winnerId: "B" }]; + const eloMap = new Map([["A", 1900], ["B", 1100]]); + let aWins = 0; + for (let i = 0; i < 5_000; i++) { + if (resolveBo3Match(match, games, eloMap).winner === "A") aWins++; + } + expect(aWins).toBeGreaterThan(4000); + }); + + it("accepts p1/p2 overrides when match slots are null", () => { + const match = makeMatch({ participant1Id: null, participant2Id: null }); + const eloMap = new Map([["X", 1500], ["Y", 1500]]); + const result = resolveBo3Match(match, noGames(), eloMap, "X", "Y"); + const ids = [result.winner, result.loser].toSorted(); + expect(ids).toEqual(["X", "Y"]); + }); + + it("throws when participant slots are missing and no overrides given", () => { + const match = makeMatch({ participant1Id: null }); + const eloMap = new Map(); + expect(() => resolveBo3Match(match, noGames(), eloMap)).toThrow(); + }); +}); + +// ─── Manifest, registry, and schema enum sync ──────────────────────────────── + +describe("nll_bracket simulator sync", () => { + it("nll_bracket is in SIMULATOR_TYPES", () => { + expect(SIMULATOR_TYPES).toContain("nll_bracket"); + }); + + it("nll_bracket has a manifest profile", () => { + expect(SIMULATOR_MANIFEST["nll_bracket"]).toBeDefined(); + }); + + it("manifest profile requires sourceElo", () => { + expect(SIMULATOR_MANIFEST["nll_bracket"].requiredInputs).toContain("sourceElo"); + }); + + it("manifest profile has projectedWins as optional input", () => { + expect(SIMULATOR_MANIFEST["nll_bracket"].optionalInputs).toContain("projectedWins"); + }); + + it("manifest profile lists regularStandings and bracket setup sections", () => { + const sections = SIMULATOR_MANIFEST["nll_bracket"].setupSections; + expect(sections).toContain("regularStandings"); + expect(sections).toContain("bracket"); + }); + + it("manifest profile has sourceElo derivable from projectedWins", () => { + const derivable = SIMULATOR_MANIFEST["nll_bracket"].derivableInputs; + expect(derivable?.sourceElo).toContain("projectedWins"); + }); +}); diff --git a/app/services/simulations/manifest.ts b/app/services/simulations/manifest.ts index 5bf1c16..28a7e21 100644 --- a/app/services/simulations/manifest.ts +++ b/app/services/simulations/manifest.ts @@ -191,6 +191,23 @@ const PROFILES: Record = diff --git a/app/services/simulations/nll-simulator.ts b/app/services/simulations/nll-simulator.ts new file mode 100644 index 0000000..47c134f --- /dev/null +++ b/app/services/simulations/nll-simulator.ts @@ -0,0 +1,644 @@ +/** + * NLL Season + Playoffs Simulator + * + * Monte Carlo simulation of the NLL regular season and playoffs. + * + * Three modes, auto-detected at runtime: + * + * ── Mode 1: Bracket-Aware ───────────────────────────────────────────────────── + * Used when a playoff_game scoring event with generated matches exists. + * Reads completed QF/SF/Finals results; simulates remaining games and series. + * Best-of-3 series respect completed playoff_match_games rows. + * + * ── Mode 2: Known-Seed ──────────────────────────────────────────────────────── + * Used when no bracket exists but all 8 seeds 1-8 are set via the seed input. + * Simulates the playoff bracket directly from those seeds. + * + * ── Mode 3: Regular-Season Projection (default) ─────────────────────────────── + * Simulates remaining regular season games per team using Elo vs average (1500), + * blending with projectedWins when provided. Top 8 teams qualify. Then simulates + * the NLL playoff bracket. + * + * NLL playoff bracket format: + * Quarterfinals (single game): 1v8, 2v7, 3v6, 4v5 + * Semifinal arms: (1/8 winner vs 4/5 winner), (2/7 winner vs 3/6 winner) + * Semifinals (best-of-3) and Finals (best-of-3) + * + * Probability mapping: + * probFirst = NLL champion (1 per sim) + * probSecond = Finals loser (1 per sim) + * probThird/Fourth = Semifinal losers (2 per sim — split evenly) + * probFifth–Eighth = Quarterfinal losers (4 per sim — split evenly) + * Missed playoffs → all 0 + * + * Round name constants (ROUND_*) must match the bracket template used to + * generate playoff matches. If the template changes round names, update these. + */ + +import { database } from "~/database/context"; +import { eq, and } from "drizzle-orm"; +import * as schema from "~/database/schema"; +import type { Simulator, SimulationResult } from "./types"; +import { eloWinProbabilityWithParity } from "~/services/probability-engine"; +import { getRegularSeasonStandings } from "~/models/regular-season-standings"; +import { getParticipantSimulatorInputs } from "~/models/simulator"; +import { logger } from "~/lib/logger"; + +// ─── Simulation parameters ──────────────────────────────────────────────────── + +const NUM_SIMULATIONS = 50_000; +const NLL_REGULAR_SEASON_GAMES = 18; +const NLL_PLAYOFF_TEAMS = 8; +const PARITY_FACTOR = 400; +const AVERAGE_OPPONENT_ELO = 1500; + +/** Maximum preseason weight given to projectedWins in the blend. Decays to 0 by season end. */ +const PROJECTED_WINS_WEIGHT = 0.65; + +/** Fallback Elo used when a bracket participant has no sourceElo on record. */ +const FALLBACK_ELO = 1400; + +// ─── Round name constants ───────────────────────────────────────────────────── +// These must match the round strings generated by the nll_bracket bracket template. + +export const ROUND_QUARTERFINALS = "Quarterfinals" as const; +export const ROUND_SEMIFINALS = "Semifinals" as const; +export const ROUND_FINALS = "Finals" as const; + +// ─── Structural types used by resolve helpers ───────────────────────────────── + +/** Minimal match fields needed by the resolve helpers. */ +export interface MatchSnapshot { + id: string; + isComplete: boolean; + winnerId: string | null; + loserId: string | null; + participant1Id: string | null; + participant2Id: string | null; +} + +/** Minimal game result needed by the resolve helpers. */ +export interface GameSnapshot { + winnerId: string | null; +} + +// ─── Public helpers (exported for testability) ──────────────────────────────── + +export function nllGameWinProbability( + eloA: number, + eloB: number, + parityFactor = PARITY_FACTOR +): number { + return eloWinProbabilityWithParity(eloA, eloB, parityFactor); +} + +export interface SimTeam { + participantId: string; + elo: number; + seed?: number; +} + +export function simulateNllGame( + teamA: SimTeam, + teamB: SimTeam, + parityFactor = PARITY_FACTOR +): { winner: SimTeam; loser: SimTeam } { + const pA = nllGameWinProbability(teamA.elo, teamB.elo, parityFactor); + if (Math.random() < pA) return { winner: teamA, loser: teamB }; + return { winner: teamB, loser: teamA }; +} + +export interface BestOfThreeState { + winsA: number; + winsB: number; +} + +/** Simulate a best-of-3 series from an optional existing state. First to 2 wins. */ +export function simulateBestOfThree( + teamA: SimTeam, + teamB: SimTeam, + parityFactor = PARITY_FACTOR, + existing: BestOfThreeState = { winsA: 0, winsB: 0 } +): { winner: SimTeam; loser: SimTeam; gamesPlayed: number } { + let wA = existing.winsA; + let wB = existing.winsB; + let gamesPlayed = 0; + const pA = nllGameWinProbability(teamA.elo, teamB.elo, parityFactor); + while (wA < 2 && wB < 2) { + if (Math.random() < pA) wA++; else wB++; + gamesPlayed++; + } + return wA === 2 + ? { winner: teamA, loser: teamB, gamesPlayed } + : { winner: teamB, loser: teamA, gamesPlayed }; +} + +export interface SeedEntry { + participantId: string; + elo: number; + seed: number; +} + +/** + * Build the 4 NLL Quarterfinal matchups from seeds 1-8. + * Bracket arms are fixed: + * QF1: 1 vs 8 → SF arm A + * QF2: 4 vs 5 → SF arm A + * QF3: 2 vs 7 → SF arm B + * QF4: 3 vs 6 → SF arm B + */ +export function buildNllBracketFromSeeds(seeds: SeedEntry[]): [ + [SeedEntry, SeedEntry], // QF1: 1 vs 8 + [SeedEntry, SeedEntry], // QF2: 4 vs 5 + [SeedEntry, SeedEntry], // QF3: 2 vs 7 + [SeedEntry, SeedEntry], // QF4: 3 vs 6 +] { + const bySeeds = new Map(seeds.map((s) => [s.seed, s])); + const s = (n: number) => { + const entry = bySeeds.get(n); + if (!entry) throw new Error(`Seed ${n} not found in bracket seeds.`); + return entry; + }; + return [ + [s(1), s(8)], + [s(4), s(5)], + [s(2), s(7)], + [s(3), s(6)], + ]; +} + +export interface NllPlayoffResult { + champion: string; + finalist: string; + sfLosers: [string, string]; + qfLosers: [string, string, string, string]; +} + +/** Simulate the full NLL playoff bracket from 8 seeded teams. */ +export function simulateNllPlayoffs( + seeds: SeedEntry[], + parityFactor = PARITY_FACTOR +): NllPlayoffResult { + const [[qf1a, qf1b], [qf2a, qf2b], [qf3a, qf3b], [qf4a, qf4b]] = + buildNllBracketFromSeeds(seeds); + + const qf1 = simulateNllGame(qf1a, qf1b, parityFactor); + const qf2 = simulateNllGame(qf2a, qf2b, parityFactor); + const qf3 = simulateNllGame(qf3a, qf3b, parityFactor); + const qf4 = simulateNllGame(qf4a, qf4b, parityFactor); + + // SF arm A: QF1 winner vs QF2 winner (1/8 vs 4/5) + const sfA = simulateBestOfThree(qf1.winner, qf2.winner, parityFactor); + // SF arm B: QF3 winner vs QF4 winner (2/7 vs 3/6) + const sfB = simulateBestOfThree(qf3.winner, qf4.winner, parityFactor); + + const final = simulateBestOfThree(sfA.winner, sfB.winner, parityFactor); + + return { + champion: final.winner.participantId, + finalist: final.loser.participantId, + sfLosers: [sfA.loser.participantId, sfB.loser.participantId], + qfLosers: [ + qf1.loser.participantId, + qf2.loser.participantId, + qf3.loser.participantId, + qf4.loser.participantId, + ], + }; +} + +// ─── Bracket-aware resolve helpers (exported for testability) ───────────────── + +/** + * Resolve a single-game QF match. + * Returns the stored result deterministically when the match is complete; + * simulates a single game otherwise. + */ +export function resolveQfMatch( + match: MatchSnapshot | undefined, + eloMap: Map +): { winner: string; loser: string } { + if (match?.isComplete && match.winnerId && match.loserId) { + return { winner: match.winnerId, loser: match.loserId }; + } + const p1 = match?.participant1Id; + const p2 = match?.participant2Id; + if (!p1 || !p2) { + throw new Error( + `NLL QF match ${match?.id ?? "(undefined)"} is missing participant slots. ` + + `Ensure the bracket is fully generated before simulating.` + ); + } + const result = simulateNllGame( + { participantId: p1, elo: eloMap.get(p1) ?? FALLBACK_ELO }, + { participantId: p2, elo: eloMap.get(p2) ?? FALLBACK_ELO } + ); + return { winner: result.winner.participantId, loser: result.loser.participantId }; +} + +/** + * Resolve a best-of-3 SF or Finals match. + * Returns the stored result deterministically when the match is complete. + * Counts completed game rows (GameSnapshot[]) for partial series state; if a + * team already has 2 wins in the game rows, treats the series as done even if + * playoffMatches.isComplete has not been toggled yet. + * Simulates remaining games from the current series score otherwise. + */ +export function resolveBo3Match( + match: MatchSnapshot | undefined, + games: GameSnapshot[], + eloMap: Map, + p1Override?: string, + p2Override?: string +): { winner: string; loser: string } { + if (match?.isComplete && match.winnerId && match.loserId) { + return { winner: match.winnerId, loser: match.loserId }; + } + const p1 = match?.participant1Id ?? p1Override; + const p2 = match?.participant2Id ?? p2Override; + if (!p1 || !p2) { + throw new Error( + `NLL series match ${match?.id ?? "(undefined)"} is missing participant slots. ` + + `Ensure the bracket is fully generated before simulating.` + ); + } + + let winsP1 = 0; + let winsP2 = 0; + for (const g of games) { + if (g.winnerId === null) continue; + if (g.winnerId === p1) winsP1++; + else if (g.winnerId === p2) winsP2++; + } + + // A team with 2 game wins has clinched the series. + if (winsP1 >= 2) return { winner: p1, loser: p2 }; + if (winsP2 >= 2) return { winner: p2, loser: p1 }; + + const { winner, loser } = simulateBestOfThree( + { participantId: p1, elo: eloMap.get(p1) ?? FALLBACK_ELO }, + { participantId: p2, elo: eloMap.get(p2) ?? FALLBACK_ELO }, + PARITY_FACTOR, + { winsA: winsP1, winsB: winsP2 } + ); + return { winner: winner.participantId, loser: loser.participantId }; +} + +// ─── Regular-season projection helper ──────────────────────────────────────── + +export interface TeamProjection { + participantId: string; + elo: number; + currentWins: number; + gamesPlayed: number; + projectedWins: number | null; +} + +/** Simulate remaining regular season games and return the top-8 seeds. */ +export function simulateRegularSeasonSeeds( + teams: TeamProjection[], + parityFactor = PARITY_FACTOR +): SeedEntry[] { + // Pre-compute tiebreaker jitter once per simulation for sort stability. + const jitter = new Map(teams.map((t) => [t.participantId, Math.random()])); + + const projected = teams.map((t) => { + const remaining = Math.max(0, NLL_REGULAR_SEASON_GAMES - t.gamesPlayed); + const eloRate = eloWinProbabilityWithParity(t.elo, AVERAGE_OPPONENT_ELO, parityFactor); + + // Decay the preseason projectedWins prior linearly as the season progresses. + // At gamesPlayed=0 it carries PROJECTED_WINS_WEIGHT (65%); by gamesPlayed=18 + // it carries 0% so late-season projections rely purely on Elo. This prevents + // a stale preseason estimate from dominating when actual standings are available. + // + // The prior rate is computed over *remaining* games (not the full season) so + // that wins already accumulated are subtracted from the preseason expectation. + // If currentWins already meets or exceeds the projection, the prior is clamped + // to 0 and Elo takes over fully. + const completionFraction = t.gamesPlayed / NLL_REGULAR_SEASON_GAMES; + const projectedBlend = + t.projectedWins !== null ? PROJECTED_WINS_WEIGHT * (1 - completionFraction) : 0; + const priorRate = + t.projectedWins !== null && remaining > 0 + ? Math.min(1, Math.max(0, t.projectedWins - t.currentWins) / remaining) + : 0; + const winRate = + projectedBlend > 0 && priorRate > 0 + ? projectedBlend * priorRate + (1 - projectedBlend) * eloRate + : eloRate; + + let additionalWins = 0; + for (let g = 0; g < remaining; g++) { + if (Math.random() < winRate) additionalWins++; + } + return { + participantId: t.participantId, + elo: t.elo, + finalWins: t.currentWins + additionalWins, + }; + }); + + const sorted = projected.toSorted( + (a, b) => + b.finalWins - a.finalWins || + (jitter.get(b.participantId) ?? 0) - (jitter.get(a.participantId) ?? 0) + ); + + return sorted.slice(0, NLL_PLAYOFF_TEAMS).map((t, i) => ({ + participantId: t.participantId, + elo: t.elo, + seed: i + 1, + })); +} + +// ─── Simulator class ────────────────────────────────────────────────────────── + +export class NLLSimulator implements Simulator { + async simulate(sportsSeasonId: string): Promise { + const db = database(); + + // ── Load participants ───────────────────────────────────────────────────── + const participants = await db.query.seasonParticipants.findMany({ + where: eq(schema.seasonParticipants.sportsSeasonId, sportsSeasonId), + }); + + if (participants.length === 0) { + throw new Error(`No participants found for sports season ${sportsSeasonId}.`); + } + + // ── Load Elo ratings from EV table (populated by prepareSimulatorInputsForRun) ── + const evRows = await db + .select({ + participantId: schema.seasonParticipantExpectedValues.participantId, + sourceElo: schema.seasonParticipantExpectedValues.sourceElo, + }) + .from(schema.seasonParticipantExpectedValues) + .where(eq(schema.seasonParticipantExpectedValues.sportsSeasonId, sportsSeasonId)); + + const eloMap = new Map(); + for (const r of evRows) { + if (r.sourceElo !== null && r.sourceElo !== undefined) { + eloMap.set(r.participantId, r.sourceElo); + } + } + + if (eloMap.size === 0) { + throw new Error( + `No Elo ratings found for sports season ${sportsSeasonId}. ` + + `Enter sourceElo or projectedWins via Admin → Elo Ratings before simulating.` + ); + } + + // ── Load simulator inputs (projectedWins + seed) ────────────────────────── + const simInputs = await getParticipantSimulatorInputs(sportsSeasonId); + const projectedWinsMap = new Map(); + const seedMap = new Map(); + + for (const input of simInputs) { + projectedWinsMap.set(input.participantId, input.projectedWins); + if (input.seed !== null && input.seed !== undefined) { + seedMap.set(input.participantId, input.seed); + } + } + + // ── All participant IDs for counting ───────────────────────────────────── + const allIds = participants.map((p) => p.id); + + // ── Mode detection ──────────────────────────────────────────────────────── + + // Mode 1: bracket-aware — a playoff_game scoring event with matches exists. + const bracketEvent = await db.query.scoringEvents.findFirst({ + where: and( + eq(schema.scoringEvents.sportsSeasonId, sportsSeasonId), + eq(schema.scoringEvents.eventType, "playoff_game") + ), + }); + + if (bracketEvent) { + const bracketMatches = await db.query.playoffMatches.findMany({ + where: eq(schema.playoffMatches.scoringEventId, bracketEvent.id), + orderBy: (m, { asc }) => [asc(m.matchNumber)], + }); + + if (bracketMatches.length > 0) { + return this.simulateBracketAware(allIds, eloMap, bracketMatches); + } + } + + // Mode 2: known-seed — all 8 seeds 1-8 are explicitly set. + const knownSeeds: SeedEntry[] = []; + for (const p of participants) { + const elo = eloMap.get(p.id); + const seed = seedMap.get(p.id); + if (elo !== undefined && seed !== undefined && seed >= 1 && seed <= 8) { + knownSeeds.push({ participantId: p.id, elo, seed }); + } + } + + if ( + knownSeeds.length === NLL_PLAYOFF_TEAMS && + new Set(knownSeeds.map((s) => s.seed)).size === NLL_PLAYOFF_TEAMS + ) { + return this.simulateKnownSeeds(allIds, knownSeeds); + } + + // Mode 3: regular-season projection (default, including preseason). + const standingsRows = await getRegularSeasonStandings(sportsSeasonId, db); + const standingsMap = new Map(standingsRows.map((r) => [r.participantId, r])); + + const teams: TeamProjection[] = []; + for (const p of participants) { + const elo = eloMap.get(p.id); + if (elo === undefined) continue; + const standing = standingsMap.get(p.id); + teams.push({ + participantId: p.id, + elo, + currentWins: standing?.wins ?? 0, + gamesPlayed: standing?.gamesPlayed ?? 0, + projectedWins: projectedWinsMap.get(p.id) ?? null, + }); + } + + if (teams.length === 0) { + throw new Error( + `No participants with Elo ratings found for season ${sportsSeasonId}.` + ); + } + + return this.simulateRegularSeason(allIds, teams); + } + + // ── Mode 1: Bracket-Aware ───────────────────────────────────────────────── + + private async simulateBracketAware( + allIds: string[], + eloMap: Map, + allMatches: typeof schema.playoffMatches.$inferSelect[] + ): Promise { + const db = database(); + + const qfMatches = allMatches + .filter((m) => m.round === ROUND_QUARTERFINALS) + .toSorted((a, b) => a.matchNumber - b.matchNumber); + const sfMatches = allMatches + .filter((m) => m.round === ROUND_SEMIFINALS) + .toSorted((a, b) => a.matchNumber - b.matchNumber); + const finalMatches = allMatches.filter((m) => m.round === ROUND_FINALS); + + if (qfMatches.length !== 4 || sfMatches.length !== 2 || finalMatches.length !== 1) { + throw new Error( + `NLL bracket has unexpected structure. Expected QF×4, SF×2, Finals×1. ` + + `Got QF×${qfMatches.length}, SF×${sfMatches.length}, Finals×${finalMatches.length}. ` + + `Ensure the bracket was generated with the nll_bracket template.` + ); + } + + // Warn about any bracket participants without Elo ratings before the hot loop. + const bracketParticipantIds = new Set(); + for (const m of allMatches) { + if (m.participant1Id) bracketParticipantIds.add(m.participant1Id); + if (m.participant2Id) bracketParticipantIds.add(m.participant2Id); + if (m.winnerId) bracketParticipantIds.add(m.winnerId); + if (m.loserId) bracketParticipantIds.add(m.loserId); + } + for (const id of bracketParticipantIds) { + if (!eloMap.has(id)) { + logger.warn( + `NLL bracket simulator: no Elo rating for participant ${id}. ` + + `Using fallback Elo ${FALLBACK_ELO}. Enter sourceElo via Admin → Elo Ratings.` + ); + } + } + + // Load playoff_match_games for best-of-3 series state. + const sfAndFinalIds = [...sfMatches, ...finalMatches].map((m) => m.id); + const allGames = sfAndFinalIds.length > 0 + ? await db.query.playoffMatchGames.findMany({ + where: (g, { inArray }) => inArray(g.playoffMatchId, sfAndFinalIds), + }) + : []; + + const gamesByMatch = new Map(); + for (const game of allGames) { + const list = gamesByMatch.get(game.playoffMatchId) ?? []; + list.push(game); + gamesByMatch.set(game.playoffMatchId, list); + } + + const championCounts = new Map(allIds.map((id) => [id, 0])); + const finalistCounts = new Map(allIds.map((id) => [id, 0])); + const sfLoserCounts = new Map(allIds.map((id) => [id, 0])); + const qfLoserCounts = new Map(allIds.map((id) => [id, 0])); + + const [qf1, qf2, qf3, qf4] = qfMatches; + const [sf1, sf2] = sfMatches; + const finalMatch = finalMatches[0]; + + for (let s = 0; s < NUM_SIMULATIONS; s++) { + const r_qf1 = resolveQfMatch(qf1, eloMap); + const r_qf2 = resolveQfMatch(qf2, eloMap); + const r_qf3 = resolveQfMatch(qf3, eloMap); + const r_qf4 = resolveQfMatch(qf4, eloMap); + + // SF arm A: QF1 winner vs QF2 winner (1/8 vs 4/5 bracket arm) + const r_sf1 = resolveBo3Match(sf1, gamesByMatch.get(sf1.id) ?? [], eloMap, r_qf1.winner, r_qf2.winner); + // SF arm B: QF3 winner vs QF4 winner (2/7 vs 3/6 bracket arm) + const r_sf2 = resolveBo3Match(sf2, gamesByMatch.get(sf2.id) ?? [], eloMap, r_qf3.winner, r_qf4.winner); + + const r_final = resolveBo3Match(finalMatch, gamesByMatch.get(finalMatch.id) ?? [], eloMap, r_sf1.winner, r_sf2.winner); + + championCounts.set(r_final.winner, (championCounts.get(r_final.winner) ?? 0) + 1); + finalistCounts.set(r_final.loser, (finalistCounts.get(r_final.loser) ?? 0) + 1); + sfLoserCounts.set(r_sf1.loser, (sfLoserCounts.get(r_sf1.loser) ?? 0) + 1); + sfLoserCounts.set(r_sf2.loser, (sfLoserCounts.get(r_sf2.loser) ?? 0) + 1); + qfLoserCounts.set(r_qf1.loser, (qfLoserCounts.get(r_qf1.loser) ?? 0) + 1); + qfLoserCounts.set(r_qf2.loser, (qfLoserCounts.get(r_qf2.loser) ?? 0) + 1); + qfLoserCounts.set(r_qf3.loser, (qfLoserCounts.get(r_qf3.loser) ?? 0) + 1); + qfLoserCounts.set(r_qf4.loser, (qfLoserCounts.get(r_qf4.loser) ?? 0) + 1); + } + + return this.buildResults(allIds, championCounts, finalistCounts, sfLoserCounts, qfLoserCounts); + } + + // ── Mode 2: Known-Seed ──────────────────────────────────────────────────── + + private simulateKnownSeeds( + allIds: string[], + seeds: SeedEntry[] + ): SimulationResult[] { + const championCounts = new Map(allIds.map((id) => [id, 0])); + const finalistCounts = new Map(allIds.map((id) => [id, 0])); + const sfLoserCounts = new Map(allIds.map((id) => [id, 0])); + const qfLoserCounts = new Map(allIds.map((id) => [id, 0])); + + for (let s = 0; s < NUM_SIMULATIONS; s++) { + const result = simulateNllPlayoffs(seeds); + championCounts.set(result.champion, (championCounts.get(result.champion) ?? 0) + 1); + finalistCounts.set(result.finalist, (finalistCounts.get(result.finalist) ?? 0) + 1); + for (const id of result.sfLosers) { + sfLoserCounts.set(id, (sfLoserCounts.get(id) ?? 0) + 1); + } + for (const id of result.qfLosers) { + qfLoserCounts.set(id, (qfLoserCounts.get(id) ?? 0) + 1); + } + } + + return this.buildResults(allIds, championCounts, finalistCounts, sfLoserCounts, qfLoserCounts); + } + + // ── Mode 3: Regular-Season Projection ──────────────────────────────────── + + private simulateRegularSeason( + allIds: string[], + teams: TeamProjection[] + ): SimulationResult[] { + const championCounts = new Map(allIds.map((id) => [id, 0])); + const finalistCounts = new Map(allIds.map((id) => [id, 0])); + const sfLoserCounts = new Map(allIds.map((id) => [id, 0])); + const qfLoserCounts = new Map(allIds.map((id) => [id, 0])); + + for (let s = 0; s < NUM_SIMULATIONS; s++) { + const seeds = simulateRegularSeasonSeeds(teams); + const result = simulateNllPlayoffs(seeds); + championCounts.set(result.champion, (championCounts.get(result.champion) ?? 0) + 1); + finalistCounts.set(result.finalist, (finalistCounts.get(result.finalist) ?? 0) + 1); + for (const id of result.sfLosers) { + sfLoserCounts.set(id, (sfLoserCounts.get(id) ?? 0) + 1); + } + for (const id of result.qfLosers) { + qfLoserCounts.set(id, (qfLoserCounts.get(id) ?? 0) + 1); + } + } + + return this.buildResults(allIds, championCounts, finalistCounts, sfLoserCounts, qfLoserCounts); + } + + // ── Shared result builder ───────────────────────────────────────────────── + + private buildResults( + allIds: string[], + championCounts: Map, + finalistCounts: Map, + sfLoserCounts: Map, + qfLoserCounts: Map + ): SimulationResult[] { + const N = NUM_SIMULATIONS; + return allIds.map((id) => ({ + participantId: id, + probabilities: { + probFirst: (championCounts.get(id) ?? 0) / N, + probSecond: (finalistCounts.get(id) ?? 0) / N, + // 2 SF losers per sim — split evenly across 3rd/4th + probThird: (sfLoserCounts.get(id) ?? 0) / N / 2, + probFourth: (sfLoserCounts.get(id) ?? 0) / N / 2, + // 4 QF losers per sim — split evenly across 5th–8th + probFifth: (qfLoserCounts.get(id) ?? 0) / N / 4, + probSixth: (qfLoserCounts.get(id) ?? 0) / N / 4, + probSeventh: (qfLoserCounts.get(id) ?? 0) / N / 4, + probEighth: (qfLoserCounts.get(id) ?? 0) / N / 4, + }, + source: "nll_bracket_monte_carlo", + })); + } +} diff --git a/app/services/simulations/registry.ts b/app/services/simulations/registry.ts index 95d2769..8fd06b0 100644 --- a/app/services/simulations/registry.ts +++ b/app/services/simulations/registry.ts @@ -29,6 +29,7 @@ import { NCAAFootballSimulator } from "./ncaa-football-simulator"; import { LLWSSimulator } from "./llws-simulator"; import { CollegeHockeySimulator } from "./college-hockey-simulator"; import { BracktSimulator } from "./brackt-simulator"; +import { NLLSimulator } from "./nll-simulator"; export const SIMULATOR_TYPES = [ "f1_standings", @@ -54,6 +55,7 @@ export const SIMULATOR_TYPES = [ "llws_bracket", "college_hockey_bracket", "brackt", + "nll_bracket", ] as const; export type SimulatorType = typeof SIMULATOR_TYPES[number]; @@ -182,6 +184,13 @@ const REGISTRY: Record Simul }, create: () => new BracktSimulator(), }, + nll_bracket: { + info: { + name: "NLL Season + Playoffs Monte Carlo", + description: "Projects NLL regular season standings via Elo (14 teams, 18 games) to determine the top-8 playoff field, then simulates the bracket (QF single-game; SF and Finals best-of-3). Reads current standings from DB; falls back to full-season projection pre-season.", + }, + create: () => new NLLSimulator(), + }, }; export function getSimulator(simulatorType: SimulatorType): Simulator { diff --git a/app/services/simulations/simulator-config.ts b/app/services/simulations/simulator-config.ts index 962f21e..ba44354 100644 --- a/app/services/simulations/simulator-config.ts +++ b/app/services/simulations/simulator-config.ts @@ -63,6 +63,11 @@ const CONFIG: Record = { parityFactor: 400, averageOpponentElo: 1500, }, + nll_bracket: { + seasonGames: 18, + parityFactor: 400, + averageOpponentElo: 1500, + }, // Simulators below don't use projected-wins → Elo conversion. // They can be populated later if needed. f1_standings: null, diff --git a/database/schema.ts b/database/schema.ts index d1f3a95..06f335a 100644 --- a/database/schema.ts +++ b/database/schema.ts @@ -154,6 +154,7 @@ export const simulatorTypeEnum = pgEnum("simulator_type", [ "llws_bracket", "college_hockey_bracket", "brackt", + "nll_bracket", ]); export const tournamentStatusEnum = pgEnum("tournament_status", [ diff --git a/docs/agents/nll-simulator-implementation-plan.md b/docs/agents/nll-simulator-implementation-plan.md new file mode 100644 index 0000000..3dfa70a --- /dev/null +++ b/docs/agents/nll-simulator-implementation-plan.md @@ -0,0 +1,272 @@ +# NLL Box Lacrosse Simulator Implementation Plan + +## Goal + +Implement National Lacrosse League (NLL) box lacrosse as a preseason-draftable sport. Brackt leagues should be able to draft NLL teams before the regular season starts, while the simulator projects both: + +1. the 18-game regular season and top-eight playoff qualification; and +2. the NLL playoff bracket: single-elimination Quarterfinals followed by best-of-three Semifinals and best-of-three NLL Finals. + +This is **not** a playoff-only MVP. The simulator must support all NLL teams in preseason and in-season states, then converge naturally to bracket-only behavior once the playoff field is known. + +## Current NLL format assumptions + +Use these assumptions until the league changes format or an admin overrides season config: + +- NLL uses unified single-table standings. +- The league has 14 franchises. +- Each team plays an 18-game regular season. +- Top eight teams qualify for the playoffs. +- Quarterfinal matchups are `1 vs 8`, `2 vs 7`, `3 vs 6`, and `4 vs 5`. +- Quarterfinals are single elimination. +- Semifinals and NLL Finals are best-of-three series. +- Bracket arms should be fixed as `(1 vs 8) winner vs (4 vs 5) winner` and `(2 vs 7) winner vs (3 vs 6) winner`, matching the published NLL bracket layout. + +References checked May 12, 2026: + +- Official NLL playoff bracket page: +- Official 2026 playoff schedule/format announcement: +- Official unified standings/playoff structure announcement: +- Official NLL standings page: + +## Architecture fit + +Follow the simulator guide: + +- Add a dedicated `nll_bracket` `simulatorType`. +- Keep mutable season inputs in `season_participant_simulator_inputs`; do not hardcode current teams, current standings, odds, seeds, or ratings in the simulator. +- Query simulator config and inputs through the simulator model/runner pathways already used by admin routes. +- Use the existing input-policy system so direct Elo can be derived from projected wins or futures odds. +- Return the standard top-eight probability columns used by expected values. + +## Data model and config changes + +### Simulator type + +Add `nll_bracket` in all simulator type locations: + +- `database/schema.ts` `simulatorTypeEnum` +- `app/services/simulations/registry.ts` `SIMULATOR_TYPES` +- `app/services/simulations/registry.ts` registry entry +- `app/services/simulations/manifest.ts` profile map +- `app/services/simulations/simulator-config.ts` projected-wins config + +Generate the enum migration with `npm run db:generate`; never hand-write migration SQL. + +### Manifest profile + +Recommended profile: + +```ts +nll_bracket: { + defaultConfig: { + iterations: 50_000, + seasonGames: 18, + parityFactor: 400, + bracketSize: 8, + playoffTeams: 8, + regularSeasonTeamCount: 14, + regularSeasonMode: "project_remaining_games", + regularSeasonNoise: 0.9, + homeFieldElo: 0, + }, + requiredInputs: ["sourceElo"], + optionalInputs: ["projectedWins", "sourceOdds", "seed"], + derivableInputs: { sourceElo: ["projectedWins", "sourceOdds"] }, + setupSections: ["participants", "eloRatings", "regularStandings", "bracket"], +} +``` + +`projectedWins` is the preferred preseason admin input. Direct `sourceElo` should override it, and futures odds can remain a fallback/alternative. + +### Simulator config + +Add `nll_bracket` to `app/services/simulations/simulator-config.ts`: + +```ts +nll_bracket: { + seasonGames: 18, + parityFactor: 400, + averageOpponentElo: 1500, +} +``` + +This enables generic projected-wins-to-Elo handling in admin setup. + +## Simulator algorithm + +Create `app/services/simulations/nll-simulator.ts` with an `NLLSimulator implements Simulator`. + +### Inputs loaded per run + +Load: + +- all `seasonParticipants` for the sports season; +- resolved `sourceElo` values from simulator inputs / EV compatibility bridge; +- optional `projectedWins` for preseason regular-season projection; +- optional `seed` values for known playoff seeds; +- regular-season standings from the existing regular standings table when available; +- playoff bracket event/matches/games when available. + +Fail readiness through the manifest/input policy if required Elo cannot be resolved for every participant that can affect the playoff field. Do not silently assign Elo unless the season config explicitly enables an input-policy fallback. + +### Mode selection + +The simulator should choose the most complete available state: + +1. **Bracket-aware mode**: if a populated NLL playoff bracket exists, honor it. + - Use completed match winners/losers. + - Use completed game rows inside best-of-three matches when present. + - Simulate unresolved games/matches. +2. **Known-seed mode**: if no bracket exists but eight teams have seeds 1-8, simulate the playoff bracket directly from those seeds. +3. **Regular-season projection mode**: otherwise, simulate the full regular season each iteration, derive top-eight seeds, then simulate playoffs. + +Regular-season projection mode is required for preseason draftability. + +### Regular-season projection mode + +For each Monte Carlo iteration: + +1. Start each team from current standings if present: + - `wins`, `losses`, and `gamesPlayed` from the regular standings table; + - if standings are absent, start all teams at `0-0`. +2. Determine remaining games: + - `remainingGames = max(0, seasonGames - gamesPlayed)`. +3. Convert Elo to game win probability against an average opponent: + - `p = 1 / (1 + 10 ** ((averageOpponentElo - teamElo) / parityFactor))`. +4. Project additional wins: + - preseason/default option: sample `remainingGames` Bernoulli trials at `p`; + - if `projectedWins` exists, calibrate the team's mean final wins toward that projection by blending Elo-derived win rate with projected-win rate; + - preserve current wins as a floor, so projected final wins cannot fall below already-earned wins. +5. Rank all teams by simulated final wins, with random tiebreaker for teams tied on wins. +6. Take the top eight as seeds 1-8. +7. Simulate the playoff bracket for those seeds. + +Recommended projection blend for phase one: + +```ts +projectedRate = projectedWins / seasonGames +eloRate = eloGameWinProbability(teamElo, averageOpponentElo, parityFactor) +regularSeasonWinRate = projectedWins == null + ? eloRate + : 0.65 * projectedRate + 0.35 * eloRate +``` + +Keep `0.65` configurable as `projectedWinsWeight` if added to `defaultConfig`. + +### Playoff simulation + +For playoff games, use team-vs-team Elo probability: + +```ts +pA = 1 / (1 + 10 ** ((eloB - eloA) / parityFactor)) +``` + +Then simulate: + +- Quarterfinals: one game. +- Semifinals: best of three, first team to two wins. +- Finals: best of three, first team to two wins. + +Export small helpers for testability: + +- `nllGameWinProbability(eloA, eloB, parityFactor)` +- `simulateNllGame(teamA, teamB, parityFactor)` +- `simulateBestOfThree(teamA, teamB, parityFactor, existingWins?)` +- `buildNllBracketFromSeeds(seedEntries)` +- `simulateNllPlayoffs(seedEntries, options)` +- `simulateRegularSeasonSeeds(teams, options)` + +### Completed results and partial series + +When a playoff bracket exists: + +- A completed match with `winnerId` should be deterministic. +- For best-of-three matches, completed `playoff_match_games` rows should set current series wins. +- If a team already has two series wins, treat the series as complete even if `playoffMatches.isComplete` has not yet been toggled. +- If only one game is complete, simulate the remaining game(s) from the current 1-0 state. + +### Probability mapping + +Map each iteration to Brackt's standard top-eight shape: + +- champion: `probFirst` +- finalist: `probSecond` +- two semifinal losers: split evenly across `probThird` and `probFourth` +- four quarterfinal losers: split evenly across `probFifth`, `probSixth`, `probSeventh`, `probEighth` +- non-playoff teams in a given iteration receive no placement count for that iteration + +Across all participants: + +- `probFirst` sums to 1; +- `probSecond` sums to 1; +- `probThird` and `probFourth` each sum to 1; +- `probFifth` through `probEighth` each sum to 1. + +## Admin and data setup + +### Preseason setup workflow + +1. Create the NLL sport with `simulatorType = "nll_bracket"`. +2. Create the NLL sports season with all 14 teams as participants. +3. Import preseason `projectedWins` for every team through the generic simulator CSV importer. +4. Optionally import championship futures odds. +5. Run the simulator before draft rooms open; all 14 teams should receive EV based on playoff qualification and bracket outcomes. + +### In-season workflow + +1. Sync or manually update regular standings. +2. Re-run the simulator as standings change. +3. `projectedWins` can remain a preseason prior, but current standings should increasingly dominate as games are played. + +### Playoff workflow + +1. Once seeds are final, enter seeds or create the bracket. +2. If bracket exists, the simulator should stop projecting regular-season qualification and use bracket-aware mode. +3. As playoff games complete, update match/game results and re-run the simulator. + +## Testing plan + +Add `app/services/simulations/__tests__/nll-simulator.test.ts`. + +Required tests: + +- projected-wins-to-Elo readiness works for `nll_bracket`; +- regular-season projection can run from a preseason `0-0` state with 14 teams; +- higher projected wins increase playoff qualification and title probability; +- only eight teams qualify in each simulated iteration; +- seed ordering builds fixed bracket arms correctly: `(1,8)/(4,5)` and `(2,7)/(3,6)`; +- Quarterfinals are single-game; +- Semifinals and Finals are best-of-three; +- partial best-of-three state is honored; +- completed matches are deterministic; +- all output probability columns sum correctly; +- manifest, registry, and schema enum stay in sync. + +Run at minimum: + +```bash +npm run typecheck +npm run lint +npm run test:run +``` + +## Implementation task breakdown + +1. Add schema enum and migration for `nll_bracket`. +2. Add manifest/config/registry entries. +3. Implement pure NLL simulator helpers. +4. Implement regular-season projection mode. +5. Implement known-seed playoff mode. +6. Implement bracket-aware mode with completed match/game support. +7. Add unit tests and readiness/manifest regression coverage. +8. Seed/create admin data for the NLL sport and current season outside of production code. +9. Run required checks. +10. Verify `/admin/simulators` shows actionable setup state and can run preseason NLL seasons. + +## Explicit non-goals + +- Do not hardcode current NLL standings, team Elo values, futures odds, or playoff seeds into production simulator code. +- Do not make NLL playoff-only; preseason draftability requires regular-season simulation. +- Do not add route-level Drizzle queries for simulator setup; use model/service layers. +- Do not create migration SQL manually. diff --git a/docs/agents/simulators.md b/docs/agents/simulators.md index 2d6e456..2da6d03 100644 --- a/docs/agents/simulators.md +++ b/docs/agents/simulators.md @@ -23,6 +23,18 @@ Before running older Elo-based simulators, the runner materializes resolved Elo The runner also materializes derived `rating` values for rating-based simulators that declare that support, such as preseason NCAAM/NCAAW using futures odds before KenPom/Barttorvik-style ratings are available. +## Preseason Draftability + +Brackt sports are expected to be draftable before their real-world season starts. When adding or revising a team-sport simulator, design for preseason and in-season runs first: + +- Include all draftable teams/participants, not only the eventual playoff field. +- Simulate regular-season qualification or standings when playoff participation is not known yet. +- Let current standings override or blend with preseason projections as the season progresses. +- Use projected wins, projected table points, futures odds, direct ratings, or sport-specific ratings as season-scoped inputs that can drive preseason EVs. +- Switch to bracket-aware simulation only when a real bracket or finalized seeds exist. + +A playoff-only simulator is not sufficient for team sports that Brackt drafts in preseason. If a sport ever has a true exception, that exception must be explicit in the sport-specific implementation plan and product requirements. + ## Season Scoping Never store mutable simulator state on `sports`. A sport can have multiple active seasons at once, such as NHL playoffs for one season and preseason drafting for the next. Each sports season needs independent: diff --git a/drizzle/0103_salty_runaways.sql b/drizzle/0103_salty_runaways.sql new file mode 100644 index 0000000..0e2045c --- /dev/null +++ b/drizzle/0103_salty_runaways.sql @@ -0,0 +1 @@ +ALTER TYPE "public"."simulator_type" ADD VALUE 'nll_bracket'; \ No newline at end of file diff --git a/drizzle/meta/0103_snapshot.json b/drizzle/meta/0103_snapshot.json new file mode 100644 index 0000000..c6dc833 --- /dev/null +++ b/drizzle/meta/0103_snapshot.json @@ -0,0 +1,6112 @@ +{ + "id": "7839dbaa-6619-44f8-90f9-cdc169c4c757", + "prevId": "1135ac5a-94a2-48bc-acca-2457f2d4d089", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.accounts": { + "name": "accounts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "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": { + "accounts_user_id_users_id_fk": { + "name": "accounts_user_id_users_id_fk", + "tableFrom": "accounts", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "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.commissioner_audit_log": { + "name": "commissioner_audit_log", + "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 + }, + "league_id": { + "name": "league_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "actor_display_name": { + "name": "actor_display_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "audit_action", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "affected_team_ids": { + "name": "affected_team_ids", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "audit_log_season_id_idx": { + "name": "audit_log_season_id_idx", + "columns": [ + { + "expression": "season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "commissioner_audit_log_season_id_seasons_id_fk": { + "name": "commissioner_audit_log_season_id_seasons_id_fk", + "tableFrom": "commissioner_audit_log", + "tableTo": "seasons", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "commissioner_audit_log_league_id_leagues_id_fk": { + "name": "commissioner_audit_log_league_id_leagues_id_fk", + "tableFrom": "commissioner_audit_log", + "tableTo": "leagues", + "columnsFrom": [ + "league_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.cs2_major_stage_results": { + "name": "cs2_major_stage_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 + }, + "stage_entry": { + "name": "stage_entry", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "stage_eliminated": { + "name": "stage_eliminated", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "stage_eliminated_wins": { + "name": "stage_eliminated_wins", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "final_placement": { + "name": "final_placement", + "type": "integer", + "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": { + "cs2_major_stage_results_unique": { + "name": "cs2_major_stage_results_unique", + "columns": [ + { + "expression": "scoring_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "participant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cs2_major_stage_results_scoring_event_id_scoring_events_id_fk": { + "name": "cs2_major_stage_results_scoring_event_id_scoring_events_id_fk", + "tableFrom": "cs2_major_stage_results", + "tableTo": "scoring_events", + "columnsFrom": [ + "scoring_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cs2_major_stage_results_participant_id_season_participants_id_fk": { + "name": "cs2_major_stage_results_participant_id_season_participants_id_fk", + "tableFrom": "cs2_major_stage_results", + "tableTo": "season_participants", + "columnsFrom": [ + "participant_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_season_participants_id_fk": { + "name": "draft_picks_participant_id_season_participants_id_fk", + "tableFrom": "draft_picks", + "tableTo": "season_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_season_participants_id_fk": { + "name": "draft_queue_participant_id_season_participants_id_fk", + "tableFrom": "draft_queue", + "tableTo": "season_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 + }, + "season_participant_id": { + "name": "season_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": { + "event_results_event_participant_unique": { + "name": "event_results_event_participant_unique", + "columns": [ + { + "expression": "scoring_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "season_participant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "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_season_participant_id_season_participants_id_fk": { + "name": "event_results_season_participant_id_season_participants_id_fk", + "tableFrom": "event_results", + "tableTo": "season_participants", + "columnsFrom": [ + "season_participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.group_stage_matches": { + "name": "group_stage_matches", + "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 + }, + "participant1_id": { + "name": "participant1_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "participant2_id": { + "name": "participant2_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "participant1_score": { + "name": "participant1_score", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "participant2_score": { + "name": "participant2_score", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_complete": { + "name": "is_complete", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "matchday": { + "name": "matchday", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "scheduled_at": { + "name": "scheduled_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": { + "group_stage_matches_group_pair_unique": { + "name": "group_stage_matches_group_pair_unique", + "columns": [ + { + "expression": "tournament_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "participant1_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "participant2_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "group_stage_matches_tournament_group_id_tournament_groups_id_fk": { + "name": "group_stage_matches_tournament_group_id_tournament_groups_id_fk", + "tableFrom": "group_stage_matches", + "tableTo": "tournament_groups", + "columnsFrom": [ + "tournament_group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "group_stage_matches_participant1_id_season_participants_id_fk": { + "name": "group_stage_matches_participant1_id_season_participants_id_fk", + "tableFrom": "group_stage_matches", + "tableTo": "season_participants", + "columnsFrom": [ + "participant1_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "group_stage_matches_participant2_id_season_participants_id_fk": { + "name": "group_stage_matches_participant2_id_season_participants_id_fk", + "tableFrom": "group_stage_matches", + "tableTo": "season_participants", + "columnsFrom": [ + "participant2_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "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_season_participants_id_fk": { + "name": "participant_ev_snapshots_participant_id_season_participants_id_fk", + "tableFrom": "participant_ev_snapshots", + "tableTo": "season_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_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 + }, + "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_participant_unique": { + "name": "participant_golf_skills_participant_unique", + "columns": [ + { + "expression": "participant_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" + } + }, + "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_season_participants_id_fk": { + "name": "participant_season_results_participant_id_season_participants_id_fk", + "tableFrom": "participant_season_results", + "tableTo": "season_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 + }, + "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_participant_unique": { + "name": "participant_surface_elos_participant_unique", + "columns": [ + { + "expression": "participant_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" + } + }, + "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()" + }, + "sport_id": { + "name": "sport_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "external_key": { + "name": "external_key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "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": { + "participants_sport_name_unique": { + "name": "participants_sport_name_unique", + "columns": [ + { + "expression": "sport_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "participants_sport_id_sports_id_fk": { + "name": "participants_sport_id_sports_id_fk", + "tableFrom": "participants", + "tableTo": "sports", + "columnsFrom": [ + "sport_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_season_participants_id_fk": { + "name": "playoff_match_games_winner_id_season_participants_id_fk", + "tableFrom": "playoff_match_games", + "tableTo": "season_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_season_participants_id_fk": { + "name": "playoff_match_odds_participant_id_season_participants_id_fk", + "tableFrom": "playoff_match_odds", + "tableTo": "season_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_season_participants_id_fk": { + "name": "playoff_matches_participant1_id_season_participants_id_fk", + "tableFrom": "playoff_matches", + "tableTo": "season_participants", + "columnsFrom": [ + "participant1_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "playoff_matches_participant2_id_season_participants_id_fk": { + "name": "playoff_matches_participant2_id_season_participants_id_fk", + "tableFrom": "playoff_matches", + "tableTo": "season_participants", + "columnsFrom": [ + "participant2_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "playoff_matches_winner_id_season_participants_id_fk": { + "name": "playoff_matches_winner_id_season_participants_id_fk", + "tableFrom": "playoff_matches", + "tableTo": "season_participants", + "columnsFrom": [ + "winner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "playoff_matches_loser_id_season_participants_id_fk": { + "name": "playoff_matches_loser_id_season_participants_id_fk", + "tableFrom": "playoff_matches", + "tableTo": "season_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 + }, + "table_points": { + "name": "table_points", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "goals_for": { + "name": "goals_for", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "goals_against": { + "name": "goals_against", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "goal_difference": { + "name": "goal_difference", + "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_season_participants_id_fk": { + "name": "regular_season_standings_participant_id_season_participants_id_fk", + "tableFrom": "regular_season_standings", + "tableTo": "season_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 + }, + "tournament_id": { + "name": "tournament_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "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" + }, + "scoring_events_tournament_id_tournaments_id_fk": { + "name": "scoring_events_tournament_id_tournaments_id_fk", + "tableFrom": "scoring_events", + "tableTo": "tournaments", + "columnsFrom": [ + "tournament_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "scoring_events_qualifying_require_tournament": { + "name": "scoring_events_qualifying_require_tournament", + "value": "NOT \"scoring_events\".\"is_qualifying_event\" OR \"scoring_events\".\"tournament_id\" IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.season_participant_expected_values": { + "name": "season_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 + }, + "world_ranking": { + "name": "world_ranking", + "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": { + "season_participant_expected_values_participant_id_season_participants_id_fk": { + "name": "season_participant_expected_values_participant_id_season_participants_id_fk", + "tableFrom": "season_participant_expected_values", + "tableTo": "season_participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "season_participant_expected_values_sports_season_id_sports_seasons_id_fk": { + "name": "season_participant_expected_values_sports_season_id_sports_seasons_id_fk", + "tableFrom": "season_participant_expected_values", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.season_participant_golf_skills": { + "name": "season_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": { + "season_participant_golf_skills_unique": { + "name": "season_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": { + "season_participant_golf_skills_participant_id_season_participants_id_fk": { + "name": "season_participant_golf_skills_participant_id_season_participants_id_fk", + "tableFrom": "season_participant_golf_skills", + "tableTo": "season_participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "season_participant_golf_skills_sports_season_id_sports_seasons_id_fk": { + "name": "season_participant_golf_skills_sports_season_id_sports_seasons_id_fk", + "tableFrom": "season_participant_golf_skills", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.season_participant_qualifying_totals": { + "name": "season_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": { + "season_participant_qualifying_totals_participant_id_season_participants_id_fk": { + "name": "season_participant_qualifying_totals_participant_id_season_participants_id_fk", + "tableFrom": "season_participant_qualifying_totals", + "tableTo": "season_participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "season_participant_qualifying_totals_sports_season_id_sports_seasons_id_fk": { + "name": "season_participant_qualifying_totals_sports_season_id_sports_seasons_id_fk", + "tableFrom": "season_participant_qualifying_totals", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.season_participant_results": { + "name": "season_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": { + "season_participant_results_participant_id_season_participants_id_fk": { + "name": "season_participant_results_participant_id_season_participants_id_fk", + "tableFrom": "season_participant_results", + "tableTo": "season_participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "season_participant_results_sports_season_id_sports_seasons_id_fk": { + "name": "season_participant_results_sports_season_id_sports_seasons_id_fk", + "tableFrom": "season_participant_results", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.season_participant_simulator_inputs": { + "name": "season_participant_simulator_inputs", + "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 + }, + "source_odds": { + "name": "source_odds", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "source_elo": { + "name": "source_elo", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "world_ranking": { + "name": "world_ranking", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "rating": { + "name": "rating", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "projected_wins": { + "name": "projected_wins", + "type": "numeric(6, 2)", + "primaryKey": false, + "notNull": false + }, + "projected_table_points": { + "name": "projected_table_points", + "type": "numeric(6, 2)", + "primaryKey": false, + "notNull": false + }, + "seed": { + "name": "seed", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "region": { + "name": "region", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "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": { + "season_participant_simulator_inputs_unique": { + "name": "season_participant_simulator_inputs_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": {} + }, + "season_participant_simulator_inputs_season_idx": { + "name": "season_participant_simulator_inputs_season_idx", + "columns": [ + { + "expression": "sports_season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "season_participant_simulator_inputs_participant_id_season_participants_id_fk": { + "name": "season_participant_simulator_inputs_participant_id_season_participants_id_fk", + "tableFrom": "season_participant_simulator_inputs", + "tableTo": "season_participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "season_participant_simulator_inputs_sports_season_id_sports_seasons_id_fk": { + "name": "season_participant_simulator_inputs_sports_season_id_sports_seasons_id_fk", + "tableFrom": "season_participant_simulator_inputs", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.season_participants": { + "name": "season_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 + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "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'" + }, + "vorp_value": { + "name": "vorp_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": { + "participants_sports_season_name_unique": { + "name": "participants_sports_season_name_unique", + "columns": [ + { + "expression": "sports_season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "season_participants_sports_season_id_sports_seasons_id_fk": { + "name": "season_participants_sports_season_id_sports_seasons_id_fk", + "tableFrom": "season_participants", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "season_participants_participant_id_participants_id_fk": { + "name": "season_participants_participant_id_participants_id_fk", + "tableFrom": "season_participants", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "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_completed_at": { + "name": "draft_completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "draft_paused": { + "name": "draft_paused", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "overnight_pause_mode": { + "name": "overnight_pause_mode", + "type": "overnight_pause_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "overnight_pause_start": { + "name": "overnight_pause_start", + "type": "varchar(5)", + "primaryKey": false, + "notNull": false + }, + "overnight_pause_end": { + "name": "overnight_pause_end", + "type": "varchar(5)", + "primaryKey": false, + "notNull": false + }, + "overnight_pause_timezone": { + "name": "overnight_pause_timezone", + "type": "varchar(50)", + "primaryKey": false, + "notNull": 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.sessions": { + "name": "sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "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()" + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "sessions_token_unique": { + "name": "sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.simulator_profiles": { + "name": "simulator_profiles", + "schema": "", + "columns": { + "simulator_type": { + "name": "simulator_type", + "type": "simulator_type", + "typeSchema": "public", + "primaryKey": true, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "default_config": { + "name": "default_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "input_schema": { + "name": "input_schema", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "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.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(512)", + "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_season_simulator_configs": { + "name": "sports_season_simulator_configs", + "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 + }, + "simulator_type": { + "name": "simulator_type", + "type": "simulator_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "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": { + "sports_season_simulator_config_unique": { + "name": "sports_season_simulator_config_unique", + "columns": [ + { + "expression": "sports_season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sports_season_simulator_config_type_idx": { + "name": "sports_season_simulator_config_type_idx", + "columns": [ + { + "expression": "simulator_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sports_season_simulator_configs_sports_season_id_sports_seasons_id_fk": { + "name": "sports_season_simulator_configs_sports_season_id_sports_seasons_id_fk", + "tableFrom": "sports_season_simulator_configs", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "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 + }, + "fantasy_season_id": { + "name": "fantasy_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "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'" + }, + "draft_on": { + "name": "draft_on", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "draft_off": { + "name": "draft_off", + "type": "date", + "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": { + "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" + }, + "sports_seasons_fantasy_season_id_seasons_id_fk": { + "name": "sports_seasons_fantasy_season_id_seasons_id_fk", + "tableFrom": "sports_seasons", + "tableTo": "seasons", + "columnsFrom": [ + "fantasy_season_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_score_events": { + "name": "team_score_events", + "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 + }, + "scoring_event_id": { + "name": "scoring_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "scoring_event_name": { + "name": "scoring_event_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "sport_name": { + "name": "sport_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "match_id": { + "name": "match_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "participant_ids": { + "name": "participant_ids", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "points_delta": { + "name": "points_delta", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "team_score_events_match_unique": { + "name": "team_score_events_match_unique", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "match_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "match_id IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "team_score_events_event_unique": { + "name": "team_score_events_event_unique", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scoring_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "match_id IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "team_score_events_team_id_teams_id_fk": { + "name": "team_score_events_team_id_teams_id_fk", + "tableFrom": "team_score_events", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_score_events_season_id_seasons_id_fk": { + "name": "team_score_events_season_id_seasons_id_fk", + "tableFrom": "team_score_events", + "tableTo": "seasons", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_score_events_scoring_event_id_scoring_events_id_fk": { + "name": "team_score_events_scoring_event_id_scoring_events_id_fk", + "tableFrom": "team_score_events", + "tableTo": "scoring_events", + "columnsFrom": [ + "scoring_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "team_score_events_match_id_playoff_matches_id_fk": { + "name": "team_score_events_match_id_playoff_matches_id_fk", + "tableFrom": "team_score_events", + "tableTo": "playoff_matches", + "columnsFrom": [ + "match_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "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 + }, + "flag_config": { + "name": "flag_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "avatar_type": { + "name": "avatar_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'owner'" + }, + "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_season_participants_id_fk": { + "name": "tournament_group_members_participant_id_season_participants_id_fk", + "tableFrom": "tournament_group_members", + "tableTo": "season_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.tournament_results": { + "name": "tournament_results", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "tournament_id": { + "name": "tournament_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 + }, + "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": { + "tournament_results_tournament_participant_unique": { + "name": "tournament_results_tournament_participant_unique", + "columns": [ + { + "expression": "tournament_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "participant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tournament_results_tournament_id_tournaments_id_fk": { + "name": "tournament_results_tournament_id_tournaments_id_fk", + "tableFrom": "tournament_results", + "tableTo": "tournaments", + "columnsFrom": [ + "tournament_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tournament_results_participant_id_participants_id_fk": { + "name": "tournament_results_participant_id_participants_id_fk", + "tableFrom": "tournament_results", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tournaments": { + "name": "tournaments", + "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 + }, + "starts_at": { + "name": "starts_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "ends_at": { + "name": "ends_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "surface": { + "name": "surface", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "location": { + "name": "location", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "tournament_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'scheduled'" + }, + "external_key": { + "name": "external_key", + "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": { + "tournaments_sport_name_year_unique": { + "name": "tournaments_sport_name_year_unique", + "columns": [ + { + "expression": "sport_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "year", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tournaments_sport_id_sports_id_fk": { + "name": "tournaments_sport_id_sports_id_fk", + "tableFrom": "tournaments", + "tableTo": "sports", + "columnsFrom": [ + "sport_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": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "username": { + "name": "username", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "display_name": { + "name": "display_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "image_url": { + "name": "image_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "flag_config": { + "name": "flag_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "custom_avatar_url": { + "name": "custom_avatar_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "avatar_type": { + "name": "avatar_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'flag'" + }, + "is_admin": { + "name": "is_admin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "timezone": { + "name": "timezone", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "last_data_request_at": { + "name": "last_data_request_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_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": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_clerk_id_unique": { + "name": "users_clerk_id_unique", + "nullsNotDistinct": false, + "columns": [ + "clerk_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verifications": { + "name": "verifications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "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": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.watchlist": { + "name": "watchlist", + "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 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "watchlist_season_team_participant_unique": { + "name": "watchlist_season_team_participant_unique", + "columns": [ + { + "expression": "season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "participant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "watchlist_season_id_seasons_id_fk": { + "name": "watchlist_season_id_seasons_id_fk", + "tableFrom": "watchlist", + "tableTo": "seasons", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "watchlist_team_id_teams_id_fk": { + "name": "watchlist_team_id_teams_id_fk", + "tableFrom": "watchlist", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "watchlist_participant_id_season_participants_id_fk": { + "name": "watchlist_participant_id_season_participants_id_fk", + "tableFrom": "watchlist", + "tableTo": "season_participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.audit_action": { + "name": "audit_action", + "schema": "public", + "values": [ + "league_settings_changed", + "draft_settings_changed", + "scoring_rules_changed", + "sports_changed", + "draft_order_set", + "draft_order_randomized", + "draft_started", + "draft_paused", + "draft_resumed", + "draft_reset", + "draft_rollback", + "force_autopick", + "force_manual_pick", + "draft_pick_changed", + "time_bank_edited", + "brackt_resolved" + ] + }, + "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.overnight_pause_mode": { + "name": "overnight_pause_mode", + "schema": "public", + "values": [ + "none", + "league", + "per_user" + ] + }, + "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", + "nfl_bracket", + "afl_bracket", + "epl_standings", + "snooker_bracket", + "tennis_qualifying_points", + "mlb_bracket", + "wnba_bracket", + "world_cup", + "darts_bracket", + "cs2_major_qualifying_points", + "ncaa_football_bracket", + "llws_bracket", + "college_hockey_bracket", + "brackt", + "nll_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" + ] + }, + "public.tournament_status": { + "name": "tournament_status", + "schema": "public", + "values": [ + "scheduled", + "in_progress", + "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 5228b51..f419ef6 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -722,6 +722,13 @@ "when": 1778539362096, "tag": "0102_wild_wiccan", "breakpoints": true + }, + { + "idx": 103, + "version": "7", + "when": 1778611567910, + "tag": "0103_salty_runaways", + "breakpoints": true } ] } \ No newline at end of file