diff --git a/.env.example b/.env.example index 19e7993..9cb92d7 100644 --- a/.env.example +++ b/.env.example @@ -5,4 +5,5 @@ CLERK_PUBLISHABLE_KEY="" CLERK_WEBHOOK_SECRET="" CONTAINER_REGISTRY="" DEV_ADMIN_CLERK_ID="" -SENTRY_AUTH_TOKEN="" \ No newline at end of file +SENTRY_AUTH_TOKEN="" +SQUIGGLE_CONTACT_EMAIL="" diff --git a/app/routes/leagues/$leagueId.sports-seasons.$sportsSeasonId.tsx b/app/routes/leagues/$leagueId.sports-seasons.$sportsSeasonId.tsx index de9df24..a01d314 100644 --- a/app/routes/leagues/$leagueId.sports-seasons.$sportsSeasonId.tsx +++ b/app/routes/leagues/$leagueId.sports-seasons.$sportsSeasonId.tsx @@ -75,8 +75,10 @@ export default function SportSeasonDetail({ const standingsDisplayMode = simulatorType === "nhl_bracket" ? "nhl-divisions" : "flat"; // NBA: 10 teams make the postseason (6 auto-qualify + 4 play-in) + // AFL: top 10 advance to the finals series (Wildcard + QF/EF + SF + PF + GF) // NHL: handled by the nhl-divisions mode (3 per div + 2 wild cards) - const playoffSpots = simulatorType === "nba_bracket" ? 10 : 8; + const playoffSpots = + simulatorType === "nba_bracket" || simulatorType === "afl_bracket" ? 10 : 8; // Build ownership map for RegularSeasonStandings const ownershipMap = Object.fromEntries( diff --git a/app/services/simulations/__tests__/afl-simulator.test.ts b/app/services/simulations/__tests__/afl-simulator.test.ts new file mode 100644 index 0000000..c7bbff4 --- /dev/null +++ b/app/services/simulations/__tests__/afl-simulator.test.ts @@ -0,0 +1,288 @@ +import { describe, it, expect, vi, beforeEach, type MockInstance } from "vitest"; +import { normalizeTeamName } from "~/lib/normalize-team-name"; +import { getTeamData, eloWinProbability, AFLSimulator } from "../afl-simulator"; + +// ─── normalizeTeamName ──────────────────────────────────────────────────────── + +describe("normalizeTeamName", () => { + it("lowercases and trims", () => { + expect(normalizeTeamName(" Western Bulldogs ")).toBe("western bulldogs"); + }); + + it("collapses internal whitespace", () => { + expect(normalizeTeamName("Greater Western Sydney")).toBe("greater western sydney"); + }); + + it("is already-normalized identity", () => { + expect(normalizeTeamName("gold coast")).toBe("gold coast"); + }); +}); + +// ─── getTeamData ────────────────────────────────────────────────────────────── + +describe("getTeamData", () => { + it("returns data for an exact match", () => { + const d = getTeamData("Western Bulldogs"); + expect(d).toBeDefined(); + expect(d?.elo).toBe(1646); + }); + + it("is case-insensitive", () => { + expect(getTeamData("western bulldogs")).toEqual(getTeamData("Western Bulldogs")); + }); + + it("returns undefined for an unknown team", () => { + expect(getTeamData("Springfield Koalas")).toBeUndefined(); + }); + + it("all 18 AFL clubs are present", () => { + const allTeams = [ + "Western Bulldogs", "Gold Coast", "Hawthorn", "Geelong", + "Adelaide", "Sydney", "Fremantle", "Collingwood", + "Brisbane Lions", "Greater Western Sydney", "Carlton", "Port Adelaide", + "St Kilda", "North Melbourne", "Melbourne", "Essendon", + "Richmond", "West Coast", + ]; + for (const name of allTeams) { + expect(getTeamData(name), `missing team: ${name}`).toBeDefined(); + } + }); + + it("Western Bulldogs has the highest Elo", () => { + const bulldogs = getTeamData("Western Bulldogs")?.elo ?? 0; + const westCoast = getTeamData("West Coast")?.elo ?? 0; + expect(bulldogs).toBeGreaterThan(westCoast); + }); + + it("Elo ratings are in the expected range (1250–1750)", () => { + const allTeams = [ + "Western Bulldogs", "Gold Coast", "Hawthorn", "Geelong", + "Adelaide", "Sydney", "Fremantle", "Collingwood", + "Brisbane Lions", "Greater Western Sydney", "Carlton", "Port Adelaide", + "St Kilda", "North Melbourne", "Melbourne", "Essendon", + "Richmond", "West Coast", + ]; + for (const name of allTeams) { + const elo = getTeamData(name)?.elo ?? 0; + expect(elo, `${name} elo out of range`).toBeGreaterThanOrEqual(1250); + expect(elo, `${name} elo out of range`).toBeLessThanOrEqual(1750); + } + }); +}); + +// ─── eloWinProbability ──────────────────────────────────────────────────────── + +describe("eloWinProbability (PARITY_FACTOR = 450)", () => { + it("returns 0.5 for equal Elo ratings", () => { + expect(eloWinProbability(1500, 1500)).toBeCloseTo(0.5, 6); + }); + + it("favors the higher-rated team", () => { + expect(eloWinProbability(1706, 1500)).toBeGreaterThan(0.5); + expect(eloWinProbability(1295, 1500)).toBeLessThan(0.5); + }); + + it("is anti-symmetric: P(A>B) + P(B>A) = 1", () => { + const p = eloWinProbability(1706, 1295); + expect(p + eloWinProbability(1295, 1706)).toBeCloseTo(1.0, 10); + }); + + it("a 450-pt gap gives ~90.9% win probability", () => { + // P = 1 / (1 + 10^(-450/450)) = 1 / (1 + 10^-1) = 1/1.1 ≈ 0.909 + const p = eloWinProbability(1950, 1500); + expect(p).toBeCloseTo(1 / 1.1, 5); + }); + + it("Bulldogs (1646) vs Essendon (1342): strongly favors Bulldogs", () => { + // 304-pt gap at parity 450: P = 1/(1+10^(-304/450)) ≈ 0.826 + const p = eloWinProbability(1646, 1342); + expect(p).toBeGreaterThan(0.80); + }); +}); + +// ─── AFLSimulator.simulate() integration tests ─────────────────────────────── + +vi.mock("~/database/context", () => ({ + database: vi.fn(), +})); + +vi.mock("~/models/regular-season-standings", () => ({ + getRegularSeasonStandings: vi.fn(), +})); + +const AFL_TEAMS = [ + "Western Bulldogs", "Gold Coast", "Hawthorn", "Geelong", + "Adelaide", "Sydney", "Fremantle", "Collingwood", + "Brisbane Lions", "Greater Western Sydney", "Carlton", "Port Adelaide", + "St Kilda", "North Melbourne", "Melbourne", "Essendon", + "Richmond", "West Coast", +]; + +const PARTICIPANT_ROWS = AFL_TEAMS.map((name, i) => ({ + id: `team-${i + 1}`, + name, +})); + +const PARTICIPANT_IDS = PARTICIPANT_ROWS.map((r) => r.id); + +describe("AFLSimulator.simulate()", () => { + let mockDb: { select: MockInstance }; + + beforeEach(async () => { + const { database } = await import("~/database/context"); + const { getRegularSeasonStandings } = await import("~/models/regular-season-standings"); + + mockDb = { + select: vi.fn().mockReturnValue({ + from: vi.fn().mockReturnValue({ + where: vi.fn().mockResolvedValue(PARTICIPANT_ROWS), + }), + }), + }; + + (database as unknown as MockInstance).mockReturnValue(mockDb); + // Default: no standings (pre-season) + (getRegularSeasonStandings as unknown as MockInstance).mockResolvedValue([]); + }); + + it("throws if no participants found", async () => { + mockDb.select.mockReturnValue({ + from: vi.fn().mockReturnValue({ + where: vi.fn().mockResolvedValue([]), + }), + }); + const sim = new AFLSimulator(); + await expect(sim.simulate("season-1")).rejects.toThrow(/No participants found/); + }); + + it("returns 18 results — one per AFL club", async () => { + const sim = new AFLSimulator(); + const results = await sim.simulate("season-1"); + expect(results).toHaveLength(18); + }); + + it("all probability values are non-negative", async () => { + const sim = new AFLSimulator(); + const results = await sim.simulate("season-1"); + for (const r of results) { + for (const val of Object.values(r.probabilities)) { + expect(val).toBeGreaterThanOrEqual(0); + } + } + }); + + it("each column (probFirst through probEighth) sums to 1.0 across all participants", async () => { + const sim = new AFLSimulator(); + const results = await sim.simulate("season-1"); + + const keys = [ + "probFirst", "probSecond", "probThird", "probFourth", + "probFifth", "probSixth", "probSeventh", "probEighth", + ] as const; + for (const key of keys) { + const colSum = results.reduce((s, r) => s + r.probabilities[key], 0); + expect(colSum, `${key} column sum`).toBeCloseTo(1.0, 2); + } + }); + + it("P5/P6 and P7/P8 are distinct tiers (separate column sums, not a combined 5–8 pool)", async () => { + const sim = new AFLSimulator(); + const results = await sim.simulate("season-1"); + + // probFifth should sum to 1.0 (SF losers only) — NOT 2.0 (which would happen if EF losers were mixed in) + const fifthSum = results.reduce((s, r) => s + r.probabilities.probFifth, 0); + const seventhSum = results.reduce((s, r) => s + r.probabilities.probSeventh, 0); + expect(fifthSum).toBeCloseTo(1.0, 2); + expect(seventhSum).toBeCloseTo(1.0, 2); + }); + + it("probThird === probFourth for every participant (3rd/4th share same points in AFL)", async () => { + const sim = new AFLSimulator(); + const results = await sim.simulate("season-1"); + for (const r of results) { + expect(r.probabilities.probThird).toBeCloseTo(r.probabilities.probFourth, 10); + } + }); + + it("probFifth === probSixth for every participant (5th/6th share same points in AFL)", async () => { + const sim = new AFLSimulator(); + const results = await sim.simulate("season-1"); + for (const r of results) { + expect(r.probabilities.probFifth).toBeCloseTo(r.probabilities.probSixth, 10); + } + }); + + it("probSeventh === probEighth for every participant (7th/8th share same points in AFL)", async () => { + const sim = new AFLSimulator(); + const results = await sim.simulate("season-1"); + for (const r of results) { + expect(r.probabilities.probSeventh).toBeCloseTo(r.probabilities.probEighth, 10); + } + }); + + it("uses source: 'afl_bracket_monte_carlo' on all results", async () => { + const sim = new AFLSimulator(); + const results = await sim.simulate("season-1"); + for (const r of results) { + expect(r.source).toBe("afl_bracket_monte_carlo"); + } + }); + + it("all result participant IDs match input participant IDs", async () => { + const sim = new AFLSimulator(); + const results = await sim.simulate("season-1"); + const resultIds = new Set(results.map((r) => r.participantId)); + for (const id of PARTICIPANT_IDS) { + expect(resultIds.has(id), `missing participant: ${id}`).toBe(true); + } + }); + + it("Western Bulldogs (highest Elo) has the highest championship probability", async () => { + const sim = new AFLSimulator(); + const results = await sim.simulate("season-1"); + + const bulldogsResult = results.find((r) => r.participantId === "team-1"); // Western Bulldogs (highest Elo) + const westCoastResult = results.find((r) => r.participantId === "team-18"); // West Coast (near-lowest Elo) + if (!bulldogsResult || !westCoastResult) throw new Error("Expected results not found"); + + // The #1 Elo team should win the championship more often than the last-ranked team + expect(bulldogsResult.probabilities.probFirst).toBeGreaterThan(westCoastResult.probabilities.probFirst); + }); + + it("bottom-ranked teams rarely make finals (low combined probability)", async () => { + const sim = new AFLSimulator(); + const results = await sim.simulate("season-1"); + + // West Coast and Richmond (16th/17th Elo) should have very low combined finals probability + const westCoast = results.find((r) => r.participantId === "team-18"); + const richmond = results.find((r) => r.participantId === "team-17"); + if (!westCoast || !richmond) throw new Error("Expected results not found"); + + const wcTotal = Object.values(westCoast.probabilities).reduce((a, b) => a + b, 0); + const ricTotal = Object.values(richmond.probabilities).reduce((a, b) => a + b, 0); + + // Combined probability for a bottom team should be well below 1.0 + expect(wcTotal).toBeLessThan(0.5); + expect(ricTotal).toBeLessThan(0.5); + }); + + it("mid-season standings: team with most wins has elevated finals probability", async () => { + const { getRegularSeasonStandings } = await import("~/models/regular-season-standings"); + + // Give Western Bulldogs (team-1) 15 wins from 18 games — top of ladder + (getRegularSeasonStandings as unknown as MockInstance).mockResolvedValue([ + { participantId: "team-1", wins: 15, gamesPlayed: 18, losses: 3 }, + // All other teams have 5 wins + ...PARTICIPANT_IDS.slice(1).map((id) => ({ participantId: id, wins: 5, gamesPlayed: 18, losses: 13 })), + ]); + + const sim = new AFLSimulator(); + const results = await sim.simulate("season-1"); + + const leader = results.find((r) => r.participantId === "team-1"); + const bottom = results.find((r) => r.participantId === "team-18"); + if (!leader || !bottom) throw new Error("Expected results not found"); + + expect(leader.probabilities.probFirst).toBeGreaterThan(bottom.probabilities.probFirst); + }); +}); diff --git a/app/services/simulations/afl-simulator.ts b/app/services/simulations/afl-simulator.ts new file mode 100644 index 0000000..36dc54f --- /dev/null +++ b/app/services/simulations/afl-simulator.ts @@ -0,0 +1,423 @@ +/** + * AFL Season + Finals Simulator + * + * Monte Carlo simulation of the AFL regular season and finals for 2026. + * + * Algorithm: + * 1. Load all participants for the sports season from DB + * 2. Load current regular season standings (wins, gamesPlayed) — if available + * 3. Match participant names to hardcoded team data (Elo ratings) + * 4. For each simulation: + * a. For each team, simulate remaining regular season games (TOTAL_GAMES - gamesPlayed) + * using Elo win probability vs. an average opponent (Elo 1500) + * → projectedPoints = currentWins*4 + simulatedRemainingWins*4 + * b. Sort all 18 teams by projected points desc + random tiebreaker → final ladder + * → Top 10 advance to the AFL Finals Series + * c. Simulate AFL Finals Series (AFL_10 bracket): + * + * Wildcard Round: #7 vs #10, #8 vs #9 → losers exit (0 pts) + * Qualifying Finals: #1 vs #4, #2 vs #3 → winners → Prelim Finals (bye) + * losers → Semi-Finals (2nd chance) + * Elimination Finals: #5 vs WC2w, #6 vs WC1w → losers exit (7th/8th) + * Semi-Finals: QF1L vs EF2w, QF2L vs EF1w → losers exit (5th/6th) + * Preliminary Finals: QF1w vs SF2w, QF2w vs SF1w → losers exit (3rd/4th) + * Grand Final: PF1w vs PF2w → winner 1st, loser 2nd + * + * 5. Track placement counts per scoring tier + * 6. Convert counts to probability distributions + * + * Win probability (Elo, PARITY_FACTOR = 450): + * P(A beats B) = 1 / (1 + 10^((eloB - eloA) / 450)) + * A higher parity factor means more randomness per game. AFL uses 450, which is + * slightly above the NBA (400) — meaning AFL games are marginally less predictable + * than NBA games but far more predictable than NHL (1000). + * + * Regular season projection: + * Per-game win probability = eloWinProbability(teamElo, 1500) where 1500 = average opponent. + * If no standings exist in DB, defaults to 0 wins / TOTAL_GAMES remaining (seeding by Elo only). + * + * Elo ratings (see TEAMS_DATA below): + * Backsolved from Squiggle's projected season win totals using the inverse formula: + * elo = 1500 - 450 × log₁₀((1 − wins/23) / (wins/23)) + * Projected win counts were read from a screenshot of squiggle.com.au's season + * simulation table (as of Round 2, 2026). Update each round as projections shift. + * Source: https://squiggle.com.au + * + * Placement tiers → SimulationProbabilities mapping: + * probFirst = Grand Final winner (1 per sim) + * probSecond = Grand Final loser (1 per sim) + * probThird/Fourth = Preliminary Finals losers (2 per sim — split evenly) + * probFifth/Sixth = Semi-Finals losers (2 per sim — split evenly) + * probSeventh/Eighth = Elimination Finals losers (2 per sim — split evenly) + * Wildcard losers → all 0 (score 0 points, same as 9th/10th) + * Missed finals → all 0 + * + * NOTE: AFL uses the AFL_10 bracket template which splits the 5–8 tier into two + * separate pairs (5/6 and 7/8). This is already handled by scoring-rules.ts + * (SPLIT_5678_TEMPLATE_IDS); this simulator outputs the correct probabilities + * into the appropriate tiers. + */ + +import { database } from "~/database/context"; +import { eq } from "drizzle-orm"; +import * as schema from "~/database/schema"; +import type { Simulator, SimulationResult } from "./types"; +import { normalizeTeamName } from "~/lib/normalize-team-name"; +import { logger } from "~/lib/logger"; +import { getRegularSeasonStandings } from "~/models/regular-season-standings"; + +// ─── Simulation parameters ──────────────────────────────────────────────────── + +const NUM_SIMULATIONS = 10_000; + +/** + * Elo parity factor for AFL single-game win probability. + * 450 reflects moderate variance — lower than NHL (1000) to account for + * AFL's relatively predictable results vs. basketball's coin-flip tendencies. + */ +const PARITY_FACTOR = 450; + +/** Approximate total regular season games per AFL team (2026 season). */ +const AFL_REGULAR_SEASON_GAMES = 23; + +/** Average opponent Elo used for regular season projections. */ +const AVERAGE_OPPONENT_ELO = 1500; + +// ─── Team data (2026 AFL season, as of end of Round 2) ─────────────────────── +// +// Elo ratings are backsolved from Squiggle's projected season win totals. +// Process: we screenshotted squiggle.com.au's season simulation table (Round 2, +// 2026), read each team's projected wins, then applied the inverse formula: +// elo = 1500 - 450 × log₁₀((1 − wins/23) / (wins/23)) +// This calibrates each team so the simulator reproduces Squiggle's ladder +// projection when every game is played against an average opponent (Elo 1500). +// Update each round by re-reading the projected wins from Squiggle and recalculating. +// Source: https://squiggle.com.au + +interface AflTeamData { + elo: number; +} + +const TEAMS_DATA: Record = { + "Western Bulldogs": { elo: 1646 }, // 15.6 projected wins + "Hawthorn": { elo: 1604 }, // 14.5 + "Gold Coast": { elo: 1601 }, // 14.5 (3rd by %) + "Sydney": { elo: 1579 }, // 13.8 + "Adelaide": { elo: 1576 }, // 13.7 + "Geelong": { elo: 1572 }, // 13.6 + "Brisbane Lions": { elo: 1541 }, // 12.7 + "Fremantle": { elo: 1524 }, // 12.2 + "Collingwood": { elo: 1517 }, // 12.0 + "Greater Western Sydney":{ elo: 1500 }, // 11.5 + "GWS Giants": { elo: 1500 }, // alias + "Melbourne": { elo: 1473 }, // 10.7 + "St Kilda": { elo: 1466 }, // 10.5 + "North Melbourne": { elo: 1459 }, // 10.3 + "Carlton": { elo: 1449 }, // 10.0 + "Port Adelaide": { elo: 1435 }, // 9.6 + "Richmond": { elo: 1366 }, // 7.7 + "West Coast": { elo: 1362 }, // 7.6 + "Essendon": { elo: 1342 }, // 7.1 +}; + +// ─── Public helpers (exported for unit testing) ─────────────────────────────── + +/** + * Look up team data by participant name. + * + * Uses a two-step match so "Gold Coast Suns" → "Gold Coast", "Hawthorn Hawks" → "Hawthorn", etc. + * When multiple keys substring-match (e.g. "Adelaide" AND "Port Adelaide" both appear in + * "Port Adelaide Power"), the longest key wins — giving the more specific match priority. + * "GWS Giants" is an explicit alias since it won't substring-match "Greater Western Sydney". + */ +export function getTeamData(name: string): AflTeamData | undefined { + const normalized = normalizeTeamName(name); + const keys = Object.keys(TEAMS_DATA); + + // 1. Exact match (fast path) + for (const key of keys) { + if (normalizeTeamName(key) === normalized) return TEAMS_DATA[key]; + } + + // 2. Substring match — collect all candidates then pick the longest key so that + // "Port Adelaide" (13) beats "Adelaide" (8) for "Port Adelaide Power". + const candidates = keys.filter((key) => { + const normKey = normalizeTeamName(key); + return ( + normKey.length >= 4 && + normalized.length >= 4 && + (normalized.includes(normKey) || normKey.includes(normalized)) + ); + }); + + if (candidates.length === 0) return undefined; + candidates.sort((a, b) => b.length - a.length); + return TEAMS_DATA[candidates[0]]; +} + +/** + * Elo win probability for team A in a single game against team B. + * P(A) = 1 / (1 + 10^((eloB - eloA) / PARITY_FACTOR)) + * Exported for unit testing. + */ +export function eloWinProbability(eloA: number, eloB: number): number { + return 1 / (1 + Math.pow(10, (eloB - eloA) / PARITY_FACTOR)); +} + +// ─── Internal types ─────────────────────────────────────────────────────────── + +interface TeamEntry { + id: string; + name: string; + data: AflTeamData | undefined; + /** Actual wins from the standings table (0 if no standings loaded). */ + currentWins: number; + /** Remaining regular season games = TOTAL_GAMES - gamesPlayed (0 if season is complete). */ + remainingGames: number; + /** Elo win probability vs. average opponent — constant per team. */ + winProb: number; +} + +/** Get Elo for a team entry. + * Fallback 1400 = conservative below-average estimate for unknown/unrecognized teams. */ +function elo(entry: TeamEntry): number { + return entry.data?.elo ?? 1400; +} + +/** Simulate remaining regular season games for a team. + * Returns projected total wins for the season. */ +function simulateProjectedWins(entry: TeamEntry): number { + let extra = 0; + for (let g = 0; g < entry.remainingGames; g++) { + if (Math.random() < entry.winProb) extra++; + } + return entry.currentWins + extra; +} + +// ─── Simulator ──────────────────────────────────────────────────────────────── + +export class AFLSimulator implements Simulator { + async simulate(sportsSeasonId: string): Promise { + const db = database(); + + // 1. Load participants and standings in parallel. + const [participantRows, standings] = await Promise.all([ + db + .select({ id: schema.participants.id, name: schema.participants.name }) + .from(schema.participants) + .where(eq(schema.participants.sportsSeasonId, sportsSeasonId)), + getRegularSeasonStandings(sportsSeasonId), + ]); + + if (participantRows.length === 0) { + throw new Error( + `No participants found for sports season ${sportsSeasonId}. ` + + `Add all 18 AFL clubs as participants before running simulation.` + ); + } + + if (participantRows.length < 10) { + throw new Error( + `AFL simulation requires at least 10 participants to fill the finals bracket ` + + `(got ${participantRows.length}). Add all 18 AFL clubs before running simulation.` + ); + } + + // 2. Build standings lookup and construct team entries. + // currentWins, remainingGames, and per-game winProb are all resolved once + // here so nothing is recomputed inside the hot simulation loop. + const standingsMap = new Map(standings.map((s) => [s.participantId, s])); + const participantIds = participantRows.map((r) => r.id); + + const teams: TeamEntry[] = participantRows.map((r) => { + const standing = standingsMap.get(r.id); + const data = getTeamData(r.name); + if (!data) { + logger.warn( + { participantName: r.name, sportsSeasonId }, + `AFL simulator: no Elo found for participant "${r.name}" — falling back to 1400. ` + + `Add an entry to TEAMS_DATA or rename the participant to match an existing key.` + ); + } + const gamesPlayed = standing?.gamesPlayed ?? 0; + return { + id: r.id, + name: r.name, + data, + currentWins: standing?.wins ?? 0, + remainingGames: Math.max(0, AFL_REGULAR_SEASON_GAMES - gamesPlayed), + winProb: eloWinProbability(data?.elo ?? 1400, AVERAGE_OPPONENT_ELO), + }; + }); + + // ─── Helpers (defined once, outside the hot loop) ───────────────────────── + + /** Simulate a single AFL game. Returns the winner. */ + const simGame = (a: TeamEntry, b: TeamEntry): TeamEntry => + Math.random() < eloWinProbability(elo(a), elo(b)) ? a : b; + + /** + * Project end-of-season ladder and return the top 10 finalists seeded 1–10. + * + * Teams are sorted by projected ladder points (4 per win) descending. + * A small random tiebreaker simulates the percentage-based AFL tiebreaker + * without requiring actual scores. + */ + const buildFinalsList = (): TeamEntry[] => { + const projected = teams.map((t) => ({ + team: t, + points: simulateProjectedWins(t) * 4, + tiebreaker: Math.random(), + })); + projected.sort((a, b) => b.points - a.points || b.tiebreaker - a.tiebreaker); + return projected.slice(0, 10).map((x) => x.team); + }; + + /** + * Simulate the AFL Finals Series from a seeded list of 10 teams. + * + * Returns the placement for each team: + * "gf_winner" → 1st + * "gf_loser" → 2nd + * "pf_loser" → 3rd/4th (two teams per sim) + * "sf_loser" → 5th/6th (two teams per sim) + * "ef_loser" → 7th/8th (two teams per sim) + * "wc_loser" → 9th/10th (zero scoring points) + */ + const simAFLFinals = ( + finalists: TeamEntry[] + ): { + gfWinner: TeamEntry; + gfLoser: TeamEntry; + pfLosers: [TeamEntry, TeamEntry]; + sfLosers: [TeamEntry, TeamEntry]; + efLosers: [TeamEntry, TeamEntry]; + } => { + const [s1, s2, s3, s4, s5, s6, s7, s8, s9, s10] = finalists; + + // Wildcard Round: #7 vs #10, #8 vs #9 + const wc1Winner = simGame(s7, s10); + const wc2Winner = simGame(s8, s9); + + // Qualifying Finals: #1 vs #4, #2 vs #3 (double-chance: winners get bye to PF) + const qf1Winner = simGame(s1, s4); + const qf1Loser = qf1Winner === s1 ? s4 : s1; + const qf2Winner = simGame(s2, s3); + const qf2Loser = qf2Winner === s2 ? s3 : s2; + + // Elimination Finals: #5 vs WC2 winner, #6 vs WC1 winner + const ef1Winner = simGame(s5, wc2Winner); + const ef1Loser = ef1Winner === s5 ? wc2Winner : s5; + const ef2Winner = simGame(s6, wc1Winner); + const ef2Loser = ef2Winner === s6 ? wc1Winner : s6; + + // Semi-Finals: QF losers (2nd chance) vs EF winners + const sf1Winner = simGame(qf1Loser, ef2Winner); + const sf1Loser = sf1Winner === qf1Loser ? ef2Winner : qf1Loser; + const sf2Winner = simGame(qf2Loser, ef1Winner); + const sf2Loser = sf2Winner === qf2Loser ? ef1Winner : qf2Loser; + + // Preliminary Finals: QF winners vs SF winners + const pf1Winner = simGame(qf1Winner, sf2Winner); + const pf1Loser = pf1Winner === qf1Winner ? sf2Winner : qf1Winner; + const pf2Winner = simGame(qf2Winner, sf1Winner); + const pf2Loser = pf2Winner === qf2Winner ? sf1Winner : qf2Winner; + + // Grand Final + const gfWinner = simGame(pf1Winner, pf2Winner); + const gfLoser = gfWinner === pf1Winner ? pf2Winner : pf1Winner; + + return { + gfWinner, + gfLoser, + pfLosers: [pf1Loser, pf2Loser ], + sfLosers: [sf1Loser, sf2Loser ], + efLosers: [ef1Loser, ef2Loser ], + }; + }; + + // 3. Integer placement count maps — initialized to 0 for all participants. + // + // AFL scoring uses the AFL_10 bracket template which splits 5–8 into two + // separate pairs: Semi-Finals losers share 5th/6th (higher value), and + // Elimination Finals losers share 7th/8th (lower value). Both pairs get + // distinct point values so we track them in separate count maps. + const championCounts = new Map(participantIds.map((id) => [id, 0])); + const finalistCounts = new Map(participantIds.map((id) => [id, 0])); + const pfLoserCounts = new Map(participantIds.map((id) => [id, 0])); + const sfLoserCounts = new Map(participantIds.map((id) => [id, 0])); + const efLoserCounts = new Map(participantIds.map((id) => [id, 0])); + + // 4. Monte Carlo simulation loop. + for (let s = 0; s < NUM_SIMULATIONS; s++) { + const finalists = buildFinalsList(); + const { gfWinner, gfLoser, pfLosers, sfLosers, efLosers } = simAFLFinals(finalists); + + championCounts.set(gfWinner.id, (championCounts.get(gfWinner.id) ?? 0) + 1); + finalistCounts.set(gfLoser.id, (finalistCounts.get(gfLoser.id) ?? 0) + 1); + + for (const loser of pfLosers) { + pfLoserCounts.set(loser.id, (pfLoserCounts.get(loser.id) ?? 0) + 1); + } + for (const loser of sfLosers) { + sfLoserCounts.set(loser.id, (sfLoserCounts.get(loser.id) ?? 0) + 1); + } + for (const loser of efLosers) { + efLoserCounts.set(loser.id, (efLoserCounts.get(loser.id) ?? 0) + 1); + } + // Wildcard losers and non-finalists are not counted (0 points per scoring rules). + } + + // 5. Convert integer counts to probability distributions. + // + // Exact denominators guarantee column sums of 1.0 by construction: + // probFirst/Second → / NUM_SIMULATIONS (1 per sim) + // probThird/Fourth → / (2 * NUM_SIMULATIONS) (2 PF losers per sim) + // probFifth/Sixth → / (2 * NUM_SIMULATIONS) (2 SF losers per sim) + // probSeventh/Eighth → / (2 * NUM_SIMULATIONS) (2 EF losers per sim) + // + // Within each pair (3rd/4th, 5th/6th, 7th/8th), both positions receive the + // same probability — matching the AFL_10 bracket's averaged point values. + const N = NUM_SIMULATIONS; + const results: SimulationResult[] = participantIds.map((participantId) => { + const c = championCounts.get(participantId) ?? 0; + const f = finalistCounts.get(participantId) ?? 0; + const pf = pfLoserCounts.get(participantId) ?? 0; + const sf = sfLoserCounts.get(participantId) ?? 0; + const ef = efLoserCounts.get(participantId) ?? 0; + return { + participantId, + probabilities: { + probFirst: c / N, + probSecond: f / N, + probThird: pf / (2 * N), + probFourth: pf / (2 * N), + probFifth: sf / (2 * N), + probSixth: sf / (2 * N), + probSeventh: ef / (2 * N), + probEighth: ef / (2 * N), + }, + source: "afl_bracket_monte_carlo", + }; + }); + + // 6. Per-position normalization — belt-and-suspenders guard against floating-point + // division residuals. Columns are already near-exactly 1.0 after step 5. + const positionKeys: Array = [ + "probFirst", "probSecond", "probThird", "probFourth", + "probFifth", "probSixth", "probSeventh", "probEighth", + ]; + for (const key of positionKeys) { + const colSum = results.reduce((s, r) => s + r.probabilities[key], 0); + const residual = 1.0 - colSum; + if (residual !== 0) { + const maxResult = results.reduce((best, r) => + r.probabilities[key] > best.probabilities[key] ? r : best + ); + maxResult.probabilities[key] += residual; + } + } + + return results; + } +} diff --git a/app/services/simulations/registry.ts b/app/services/simulations/registry.ts index 98eb0b9..333013b 100644 --- a/app/services/simulations/registry.ts +++ b/app/services/simulations/registry.ts @@ -15,6 +15,7 @@ import { NCAAMSimulator } from "./ncaam-simulator"; import { NCAAWSimulator } from "./ncaaw-simulator"; import { NBASimulator } from "./nba-simulator"; import { NHLSimulator } from "./nhl-simulator"; +import { AFLSimulator } from "./afl-simulator"; export const SIMULATOR_TYPES = [ "f1_standings", @@ -26,6 +27,7 @@ export const SIMULATOR_TYPES = [ "ncaaw_bracket", "nba_bracket", "nhl_bracket", + "afl_bracket", ] as const; export type SimulatorType = typeof SIMULATOR_TYPES[number]; @@ -86,6 +88,10 @@ const REGISTRY: Record Simul info: { name: "NHL Playoff Monte Carlo", description: "Simulates NHL playoff seedings (divisional format, via seed probabilities) and full bracket (best-of-7 series) using Elo ratings" }, create: () => new NHLSimulator(), }, + afl_bracket: { + info: { name: "AFL Season + Finals Monte Carlo", description: "Projects AFL regular season standings via Elo, then simulates the 10-team finals series (Wildcard → QF/EF → SF → PF → Grand Final). Reads current standings from DB; falls back to full-season projection pre-season." }, + create: () => new AFLSimulator(), + }, }; export function getSimulator(simulatorType: SimulatorType): Simulator { diff --git a/app/services/standings-sync/afl.ts b/app/services/standings-sync/afl.ts new file mode 100644 index 0000000..d1290bf --- /dev/null +++ b/app/services/standings-sync/afl.ts @@ -0,0 +1,67 @@ +import type { FetchedStandingsRecord, StandingsSyncAdapter } from "./types"; + +const AFL_STANDINGS_URL = "https://api.squiggle.com.au/?q=standings;year=2026;format=json"; + +/** + * Squiggle API response shape for a single team's ladder entry. + * Docs: https://api.squiggle.com.au/ + */ +interface SquiggleStandingsEntry { + id: number; // Squiggle team ID (1–18) + name: string; // Full team name, e.g. "Western Bulldogs" + wins: number; + losses: number; + draws: number; + played: number; // Games played + for: number; // Points scored for + against: number; // Points scored against + percentage: number;// (for / (for + against)) * 100 + pts: number; // Ladder points (4=win, 2=draw, 0=loss) + rank: number; // Current ladder position (1-based) +} + +interface SquiggleStandingsResponse { + standings: SquiggleStandingsEntry[]; +} + +export class AflStandingsAdapter implements StandingsSyncAdapter { + async fetchStandings(): Promise { + const response = await fetch(AFL_STANDINGS_URL, { + headers: { + // Squiggle asks all clients to identify themselves for rate-limit contact. + // Set SQUIGGLE_CONTACT_EMAIL in your environment to identify this client. + "User-Agent": `Brackt.com AFL standings sync - ${process.env.SQUIGGLE_CONTACT_EMAIL ?? "admin@brackt.com"}`, + }, + }); + + if (!response.ok) { + throw new Error( + `AFL Squiggle standings API returned ${response.status}: ${response.statusText}` + ); + } + + const json = (await response.json()) as SquiggleStandingsResponse; + const entries = json.standings; + + if (!entries || entries.length === 0) { + throw new Error( + "AFL Squiggle standings API returned no entries — response shape may have changed" + ); + } + + // Squiggle already includes `rank` (ladder position), sorted by ladder position. + // Sort ascending by rank so leagueRank matches the AFL ladder order. + const sorted = [...entries].sort((a, b) => a.rank - b.rank); + + return sorted.map((entry): FetchedStandingsRecord => ({ + teamName: entry.name, + externalTeamId: String(entry.id), + wins: entry.wins, + losses: entry.losses, + ties: entry.draws, // AFL draws are stored in the `ties` column + winPct: entry.percentage / 100, // Squiggle returns e.g. 62.5; store as 0.625 + gamesPlayed: entry.played, + leagueRank: entry.rank, + })); + } +} diff --git a/app/services/standings-sync/index.ts b/app/services/standings-sync/index.ts index 5720e3d..ec264df 100644 --- a/app/services/standings-sync/index.ts +++ b/app/services/standings-sync/index.ts @@ -7,6 +7,7 @@ import { upsertPendingStandingsMappings } from "~/models/pending-standings-mappi import { findMatchingTeamName } from "~/lib/normalize-team-name"; import { NhlStandingsAdapter } from "./nhl"; import { NbaStandingsAdapter } from "./nba"; +import { AflStandingsAdapter } from "./afl"; import type { StandingsSyncAdapter, SyncResult, UnmatchedTeam } from "./types"; /** @@ -20,6 +21,8 @@ function getAdapter(simulatorType: string): StandingsSyncAdapter { return new NbaStandingsAdapter(); case "nhl_bracket": return new NhlStandingsAdapter(); + case "afl_bracket": + return new AflStandingsAdapter(); case "f1_standings": throw new Error( "F1 standings sync is not yet implemented. Use the manual standings page." diff --git a/database/schema.ts b/database/schema.ts index f334c30..152d9fc 100644 --- a/database/schema.ts +++ b/database/schema.ts @@ -92,6 +92,7 @@ export const simulatorTypeEnum = pgEnum("simulator_type", [ "ncaaw_bracket", "nba_bracket", "nhl_bracket", + "afl_bracket", ]); export const playoffMatchGameStatusEnum = pgEnum("playoff_match_game_status", [ diff --git a/drizzle/0056_jittery_the_fallen.sql b/drizzle/0056_jittery_the_fallen.sql new file mode 100644 index 0000000..7889093 --- /dev/null +++ b/drizzle/0056_jittery_the_fallen.sql @@ -0,0 +1 @@ +ALTER TYPE "public"."simulator_type" ADD VALUE 'afl_bracket'; \ No newline at end of file diff --git a/drizzle/meta/0056_snapshot.json b/drizzle/meta/0056_snapshot.json new file mode 100644 index 0000000..a07d7c2 --- /dev/null +++ b/drizzle/meta/0056_snapshot.json @@ -0,0 +1,3890 @@ +{ + "id": "7e7f5ecc-2e64-494d-bdd3-b97b5dd28cf1", + "prevId": "6463661d-575e-42d9-b20e-2b46e5edcfd5", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.autodraft_settings": { + "name": "autodraft_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "season_id": { + "name": "season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "mode": { + "name": "mode", + "type": "autodraft_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'next_pick'" + }, + "queue_only": { + "name": "queue_only", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "autodraft_settings_season_id_seasons_id_fk": { + "name": "autodraft_settings_season_id_seasons_id_fk", + "tableFrom": "autodraft_settings", + "tableTo": "seasons", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "autodraft_settings_team_id_teams_id_fk": { + "name": "autodraft_settings_team_id_teams_id_fk", + "tableFrom": "autodraft_settings", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.commissioners": { + "name": "commissioners", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "league_id": { + "name": "league_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "commissioners_league_id_leagues_id_fk": { + "name": "commissioners_league_id_leagues_id_fk", + "tableFrom": "commissioners", + "tableTo": "leagues", + "columnsFrom": [ + "league_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.draft_picks": { + "name": "draft_picks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "season_id": { + "name": "season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pick_number": { + "name": "pick_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "round": { + "name": "round", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "pick_in_round": { + "name": "pick_in_round", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "picked_by_user_id": { + "name": "picked_by_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "picked_by_type": { + "name": "picked_by_type", + "type": "picked_by_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "time_used": { + "name": "time_used", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "draft_picks_season_pick_unique": { + "name": "draft_picks_season_pick_unique", + "columns": [ + { + "expression": "season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pick_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "draft_picks_season_id_seasons_id_fk": { + "name": "draft_picks_season_id_seasons_id_fk", + "tableFrom": "draft_picks", + "tableTo": "seasons", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "draft_picks_team_id_teams_id_fk": { + "name": "draft_picks_team_id_teams_id_fk", + "tableFrom": "draft_picks", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "draft_picks_participant_id_participants_id_fk": { + "name": "draft_picks_participant_id_participants_id_fk", + "tableFrom": "draft_picks", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.draft_queue": { + "name": "draft_queue", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "season_id": { + "name": "season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "queue_position": { + "name": "queue_position", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "draft_queue_season_id_seasons_id_fk": { + "name": "draft_queue_season_id_seasons_id_fk", + "tableFrom": "draft_queue", + "tableTo": "seasons", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "draft_queue_team_id_teams_id_fk": { + "name": "draft_queue_team_id_teams_id_fk", + "tableFrom": "draft_queue", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "draft_queue_participant_id_participants_id_fk": { + "name": "draft_queue_participant_id_participants_id_fk", + "tableFrom": "draft_queue", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.draft_slots": { + "name": "draft_slots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "season_id": { + "name": "season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "draft_order": { + "name": "draft_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "draft_slots_season_id_seasons_id_fk": { + "name": "draft_slots_season_id_seasons_id_fk", + "tableFrom": "draft_slots", + "tableTo": "seasons", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "draft_slots_team_id_teams_id_fk": { + "name": "draft_slots_team_id_teams_id_fk", + "tableFrom": "draft_slots", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.draft_timers": { + "name": "draft_timers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "season_id": { + "name": "season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "time_remaining": { + "name": "time_remaining", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "draft_timers_season_id_seasons_id_fk": { + "name": "draft_timers_season_id_seasons_id_fk", + "tableFrom": "draft_timers", + "tableTo": "seasons", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "draft_timers_team_id_teams_id_fk": { + "name": "draft_timers_team_id_teams_id_fk", + "tableFrom": "draft_timers", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.event_results": { + "name": "event_results", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "scoring_event_id": { + "name": "scoring_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "placement": { + "name": "placement", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "qualifying_points_awarded": { + "name": "qualifying_points_awarded", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "eliminated": { + "name": "eliminated", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "raw_score": { + "name": "raw_score", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "event_results_scoring_event_id_scoring_events_id_fk": { + "name": "event_results_scoring_event_id_scoring_events_id_fk", + "tableFrom": "event_results", + "tableTo": "scoring_events", + "columnsFrom": [ + "scoring_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "event_results_participant_id_participants_id_fk": { + "name": "event_results_participant_id_participants_id_fk", + "tableFrom": "event_results", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.leagues": { + "name": "leagues", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "current_season_id": { + "name": "current_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "is_public_draft_board": { + "name": "is_public_draft_board", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "discord_webhook_url": { + "name": "discord_webhook_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.participant_ev_snapshots": { + "name": "participant_ev_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "snapshot_date": { + "name": "snapshot_date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "prob_first": { + "name": "prob_first", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_second": { + "name": "prob_second", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_third": { + "name": "prob_third", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_fourth": { + "name": "prob_fourth", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_fifth": { + "name": "prob_fifth", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_sixth": { + "name": "prob_sixth", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_seventh": { + "name": "prob_seventh", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_eighth": { + "name": "prob_eighth", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "calculated_ev": { + "name": "calculated_ev", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "source": { + "name": "source", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "participant_ev_snapshots_unique": { + "name": "participant_ev_snapshots_unique", + "columns": [ + { + "expression": "participant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sports_season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "snapshot_date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "participant_ev_snapshots_participant_id_participants_id_fk": { + "name": "participant_ev_snapshots_participant_id_participants_id_fk", + "tableFrom": "participant_ev_snapshots", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "participant_ev_snapshots_sports_season_id_sports_seasons_id_fk": { + "name": "participant_ev_snapshots_sports_season_id_sports_seasons_id_fk", + "tableFrom": "participant_ev_snapshots", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.participant_expected_values": { + "name": "participant_expected_values", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "prob_first": { + "name": "prob_first", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_second": { + "name": "prob_second", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_third": { + "name": "prob_third", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_fourth": { + "name": "prob_fourth", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_fifth": { + "name": "prob_fifth", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_sixth": { + "name": "prob_sixth", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_seventh": { + "name": "prob_seventh", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_eighth": { + "name": "prob_eighth", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "expected_value": { + "name": "expected_value", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "source": { + "name": "source", + "type": "probability_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'manual'" + }, + "source_odds": { + "name": "source_odds", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "calculated_at": { + "name": "calculated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "participant_expected_values_participant_id_participants_id_fk": { + "name": "participant_expected_values_participant_id_participants_id_fk", + "tableFrom": "participant_expected_values", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "participant_expected_values_sports_season_id_sports_seasons_id_fk": { + "name": "participant_expected_values_sports_season_id_sports_seasons_id_fk", + "tableFrom": "participant_expected_values", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.participant_qualifying_totals": { + "name": "participant_qualifying_totals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "total_qualifying_points": { + "name": "total_qualifying_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "events_scored": { + "name": "events_scored", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "final_ranking": { + "name": "final_ranking", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "participant_qualifying_totals_participant_id_participants_id_fk": { + "name": "participant_qualifying_totals_participant_id_participants_id_fk", + "tableFrom": "participant_qualifying_totals", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "participant_qualifying_totals_sports_season_id_sports_seasons_id_fk": { + "name": "participant_qualifying_totals_sports_season_id_sports_seasons_id_fk", + "tableFrom": "participant_qualifying_totals", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.participant_results": { + "name": "participant_results", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "final_position": { + "name": "final_position", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_partial_score": { + "name": "is_partial_score", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "qualifying_points": { + "name": "qualifying_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "participant_results_participant_id_participants_id_fk": { + "name": "participant_results_participant_id_participants_id_fk", + "tableFrom": "participant_results", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "participant_results_sports_season_id_sports_seasons_id_fk": { + "name": "participant_results_sports_season_id_sports_seasons_id_fk", + "tableFrom": "participant_results", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.participant_season_results": { + "name": "participant_season_results", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "current_points": { + "name": "current_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "current_position": { + "name": "current_position", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "participant_season_results_participant_id_participants_id_fk": { + "name": "participant_season_results_participant_id_participants_id_fk", + "tableFrom": "participant_season_results", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "participant_season_results_sports_season_id_sports_seasons_id_fk": { + "name": "participant_season_results_sports_season_id_sports_seasons_id_fk", + "tableFrom": "participant_season_results", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.participants": { + "name": "participants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "short_name": { + "name": "short_name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "expected_value": { + "name": "expected_value", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "participants_sports_season_id_sports_seasons_id_fk": { + "name": "participants_sports_season_id_sports_seasons_id_fk", + "tableFrom": "participants", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pending_standings_mappings": { + "name": "pending_standings_mappings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "external_team_id": { + "name": "external_team_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "team_name": { + "name": "team_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "standing_data": { + "name": "standing_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "psm_season_external_id_idx": { + "name": "psm_season_external_id_idx", + "columns": [ + { + "expression": "sports_season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pending_standings_mappings_sports_season_id_sports_seasons_id_fk": { + "name": "pending_standings_mappings_sports_season_id_sports_seasons_id_fk", + "tableFrom": "pending_standings_mappings", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.playoff_match_games": { + "name": "playoff_match_games", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "playoff_match_id": { + "name": "playoff_match_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "game_number": { + "name": "game_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "scheduled_at": { + "name": "scheduled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "playoff_match_game_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'scheduled'" + }, + "participant1_score": { + "name": "participant1_score", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "participant2_score": { + "name": "participant2_score", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "winner_id": { + "name": "winner_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "playoff_match_games_playoff_match_id_playoff_matches_id_fk": { + "name": "playoff_match_games_playoff_match_id_playoff_matches_id_fk", + "tableFrom": "playoff_match_games", + "tableTo": "playoff_matches", + "columnsFrom": [ + "playoff_match_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "playoff_match_games_winner_id_participants_id_fk": { + "name": "playoff_match_games_winner_id_participants_id_fk", + "tableFrom": "playoff_match_games", + "tableTo": "participants", + "columnsFrom": [ + "winner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.playoff_match_odds": { + "name": "playoff_match_odds", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "playoff_match_id": { + "name": "playoff_match_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "moneyline_odds": { + "name": "moneyline_odds", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "implied_probability": { + "name": "implied_probability", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": false + }, + "odds_source": { + "name": "odds_source", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "recorded_at": { + "name": "recorded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "playoff_match_odds_playoff_match_id_playoff_matches_id_fk": { + "name": "playoff_match_odds_playoff_match_id_playoff_matches_id_fk", + "tableFrom": "playoff_match_odds", + "tableTo": "playoff_matches", + "columnsFrom": [ + "playoff_match_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "playoff_match_odds_participant_id_participants_id_fk": { + "name": "playoff_match_odds_participant_id_participants_id_fk", + "tableFrom": "playoff_match_odds", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.playoff_matches": { + "name": "playoff_matches", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "scoring_event_id": { + "name": "scoring_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "round": { + "name": "round", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "match_number": { + "name": "match_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "participant1_id": { + "name": "participant1_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "participant2_id": { + "name": "participant2_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "winner_id": { + "name": "winner_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "loser_id": { + "name": "loser_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "is_complete": { + "name": "is_complete", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "participant1_score": { + "name": "participant1_score", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "participant2_score": { + "name": "participant2_score", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "is_scoring": { + "name": "is_scoring", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "template_round": { + "name": "template_round", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "seed_info": { + "name": "seed_info", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "playoff_matches_scoring_event_id_scoring_events_id_fk": { + "name": "playoff_matches_scoring_event_id_scoring_events_id_fk", + "tableFrom": "playoff_matches", + "tableTo": "scoring_events", + "columnsFrom": [ + "scoring_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "playoff_matches_participant1_id_participants_id_fk": { + "name": "playoff_matches_participant1_id_participants_id_fk", + "tableFrom": "playoff_matches", + "tableTo": "participants", + "columnsFrom": [ + "participant1_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "playoff_matches_participant2_id_participants_id_fk": { + "name": "playoff_matches_participant2_id_participants_id_fk", + "tableFrom": "playoff_matches", + "tableTo": "participants", + "columnsFrom": [ + "participant2_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "playoff_matches_winner_id_participants_id_fk": { + "name": "playoff_matches_winner_id_participants_id_fk", + "tableFrom": "playoff_matches", + "tableTo": "participants", + "columnsFrom": [ + "winner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "playoff_matches_loser_id_participants_id_fk": { + "name": "playoff_matches_loser_id_participants_id_fk", + "tableFrom": "playoff_matches", + "tableTo": "participants", + "columnsFrom": [ + "loser_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.qualifying_point_config": { + "name": "qualifying_point_config", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "placement": { + "name": "placement", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "points": { + "name": "points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "qualifying_point_config_sports_season_id_sports_seasons_id_fk": { + "name": "qualifying_point_config_sports_season_id_sports_seasons_id_fk", + "tableFrom": "qualifying_point_config", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.regular_season_standings": { + "name": "regular_season_standings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "wins": { + "name": "wins", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "losses": { + "name": "losses", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "ot_losses": { + "name": "ot_losses", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "ties": { + "name": "ties", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "win_pct": { + "name": "win_pct", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false + }, + "games_played": { + "name": "games_played", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "games_back": { + "name": "games_back", + "type": "numeric(5, 1)", + "primaryKey": false, + "notNull": false + }, + "conference": { + "name": "conference", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "division": { + "name": "division", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "conference_rank": { + "name": "conference_rank", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "division_rank": { + "name": "division_rank", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "league_rank": { + "name": "league_rank", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "streak": { + "name": "streak", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "last_ten": { + "name": "last_ten", + "type": "varchar(15)", + "primaryKey": false, + "notNull": false + }, + "home_record": { + "name": "home_record", + "type": "varchar(15)", + "primaryKey": false, + "notNull": false + }, + "away_record": { + "name": "away_record", + "type": "varchar(15)", + "primaryKey": false, + "notNull": false + }, + "external_team_id": { + "name": "external_team_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "synced_at": { + "name": "synced_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "rss_participant_season_idx": { + "name": "rss_participant_season_idx", + "columns": [ + { + "expression": "participant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sports_season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "regular_season_standings_participant_id_participants_id_fk": { + "name": "regular_season_standings_participant_id_participants_id_fk", + "tableFrom": "regular_season_standings", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "regular_season_standings_sports_season_id_sports_seasons_id_fk": { + "name": "regular_season_standings_sports_season_id_sports_seasons_id_fk", + "tableFrom": "regular_season_standings", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scoring_events": { + "name": "scoring_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "event_date": { + "name": "event_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "event_starts_at": { + "name": "event_starts_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "event_type": { + "name": "event_type", + "type": "event_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "playoff_round": { + "name": "playoff_round", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "is_qualifying_event": { + "name": "is_qualifying_event", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "bracket_template_id": { + "name": "bracket_template_id", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "scoring_starts_at_round": { + "name": "scoring_starts_at_round", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "bracket_region_config": { + "name": "bracket_region_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_complete": { + "name": "is_complete", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "scoring_events_sports_season_id_sports_seasons_id_fk": { + "name": "scoring_events_sports_season_id_sports_seasons_id_fk", + "tableFrom": "scoring_events", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.season_sports": { + "name": "season_sports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "season_id": { + "name": "season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "season_sports_season_id_seasons_id_fk": { + "name": "season_sports_season_id_seasons_id_fk", + "tableFrom": "season_sports", + "tableTo": "seasons", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "season_sports_sports_season_id_sports_seasons_id_fk": { + "name": "season_sports_sports_season_id_sports_seasons_id_fk", + "tableFrom": "season_sports", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.season_template_sports": { + "name": "season_template_sports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "template_id": { + "name": "template_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "season_template_sports_template_id_season_templates_id_fk": { + "name": "season_template_sports_template_id_season_templates_id_fk", + "tableFrom": "season_template_sports", + "tableTo": "season_templates", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "season_template_sports_sports_season_id_sports_seasons_id_fk": { + "name": "season_template_sports_sports_season_id_sports_seasons_id_fk", + "tableFrom": "season_template_sports", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.season_templates": { + "name": "season_templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "year": { + "name": "year", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.seasons": { + "name": "seasons", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "league_id": { + "name": "league_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "year": { + "name": "year", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "season_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pre_draft'" + }, + "template_id": { + "name": "template_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "draft_rounds": { + "name": "draft_rounds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 20 + }, + "flex_spots": { + "name": "flex_spots", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "draft_date_time": { + "name": "draft_date_time", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "draft_initial_time": { + "name": "draft_initial_time", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 120 + }, + "draft_increment_time": { + "name": "draft_increment_time", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 30 + }, + "draft_timer_mode": { + "name": "draft_timer_mode", + "type": "draft_timer_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'chess_clock'" + }, + "current_pick_number": { + "name": "current_pick_number", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1 + }, + "draft_started_at": { + "name": "draft_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "draft_paused": { + "name": "draft_paused", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "invite_code": { + "name": "invite_code", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "points_for_1st": { + "name": "points_for_1st", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 100 + }, + "points_for_2nd": { + "name": "points_for_2nd", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 70 + }, + "points_for_3rd": { + "name": "points_for_3rd", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 50 + }, + "points_for_4th": { + "name": "points_for_4th", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 40 + }, + "points_for_5th": { + "name": "points_for_5th", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 25 + }, + "points_for_6th": { + "name": "points_for_6th", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 25 + }, + "points_for_7th": { + "name": "points_for_7th", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 15 + }, + "points_for_8th": { + "name": "points_for_8th", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 15 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "seasons_league_id_leagues_id_fk": { + "name": "seasons_league_id_leagues_id_fk", + "tableFrom": "seasons", + "tableTo": "leagues", + "columnsFrom": [ + "league_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "seasons_template_id_season_templates_id_fk": { + "name": "seasons_template_id_season_templates_id_fk", + "tableFrom": "seasons", + "tableTo": "season_templates", + "columnsFrom": [ + "template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "seasons_invite_code_unique": { + "name": "seasons_invite_code_unique", + "nullsNotDistinct": false, + "columns": [ + "invite_code" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sports": { + "name": "sports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "sport_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon_url": { + "name": "icon_url", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "simulator_type": { + "name": "simulator_type", + "type": "simulator_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "sports_slug_unique": { + "name": "sports_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sports_seasons": { + "name": "sports_seasons", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "sport_id": { + "name": "sport_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "year": { + "name": "year", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "start_date": { + "name": "start_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "end_date": { + "name": "end_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "sports_season_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'upcoming'" + }, + "scoring_type": { + "name": "scoring_type", + "type": "scoring_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "scoring_pattern": { + "name": "scoring_pattern", + "type": "scoring_pattern", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "total_majors": { + "name": "total_majors", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "majors_completed": { + "name": "majors_completed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "qualifying_points_finalized": { + "name": "qualifying_points_finalized", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "elo_calibration_exponent": { + "name": "elo_calibration_exponent", + "type": "numeric(3, 2)", + "primaryKey": false, + "notNull": false + }, + "elo_min_rating": { + "name": "elo_min_rating", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1250 + }, + "elo_max_rating": { + "name": "elo_max_rating", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1750 + }, + "simulation_status": { + "name": "simulation_status", + "type": "simulation_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "is_draftable": { + "name": "is_draftable", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "sports_seasons_sport_id_sports_id_fk": { + "name": "sports_seasons_sport_id_sports_id_fk", + "tableFrom": "sports_seasons", + "tableTo": "sports", + "columnsFrom": [ + "sport_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_ev_snapshots": { + "name": "team_ev_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "snapshot_date": { + "name": "snapshot_date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "projected_points": { + "name": "projected_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "actual_points": { + "name": "actual_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "team_ev_snapshots_unique": { + "name": "team_ev_snapshots_unique", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "snapshot_date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "team_ev_snapshots_team_id_teams_id_fk": { + "name": "team_ev_snapshots_team_id_teams_id_fk", + "tableFrom": "team_ev_snapshots", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_ev_snapshots_season_id_seasons_id_fk": { + "name": "team_ev_snapshots_season_id_seasons_id_fk", + "tableFrom": "team_ev_snapshots", + "tableTo": "seasons", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_sport_scores": { + "name": "team_sport_scores", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "total_points": { + "name": "total_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "participants_completed": { + "name": "participants_completed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "participants_total": { + "name": "participants_total", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "calculated_at": { + "name": "calculated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "team_sport_scores_team_id_teams_id_fk": { + "name": "team_sport_scores_team_id_teams_id_fk", + "tableFrom": "team_sport_scores", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_sport_scores_sports_season_id_sports_seasons_id_fk": { + "name": "team_sport_scores_sports_season_id_sports_seasons_id_fk", + "tableFrom": "team_sport_scores", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_standings": { + "name": "team_standings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "total_points": { + "name": "total_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "current_rank": { + "name": "current_rank", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "previous_rank": { + "name": "previous_rank", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "first_place_count": { + "name": "first_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "second_place_count": { + "name": "second_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "third_place_count": { + "name": "third_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "fourth_place_count": { + "name": "fourth_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "fifth_place_count": { + "name": "fifth_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "sixth_place_count": { + "name": "sixth_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "seventh_place_count": { + "name": "seventh_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "eighth_place_count": { + "name": "eighth_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "participants_remaining": { + "name": "participants_remaining", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "actual_points": { + "name": "actual_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "projected_points": { + "name": "projected_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "participants_finished": { + "name": "participants_finished", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "calculated_at": { + "name": "calculated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "team_standings_team_id_teams_id_fk": { + "name": "team_standings_team_id_teams_id_fk", + "tableFrom": "team_standings", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_standings_season_id_seasons_id_fk": { + "name": "team_standings_season_id_seasons_id_fk", + "tableFrom": "team_standings", + "tableTo": "seasons", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.team_standings_snapshots": { + "name": "team_standings_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "season_id": { + "name": "season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "snapshot_date": { + "name": "snapshot_date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "total_points": { + "name": "total_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "rank": { + "name": "rank", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "first_place_count": { + "name": "first_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "second_place_count": { + "name": "second_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "third_place_count": { + "name": "third_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "fourth_place_count": { + "name": "fourth_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "fifth_place_count": { + "name": "fifth_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "sixth_place_count": { + "name": "sixth_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "seventh_place_count": { + "name": "seventh_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "eighth_place_count": { + "name": "eighth_place_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "participants_remaining": { + "name": "participants_remaining", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "actual_points": { + "name": "actual_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "projected_points": { + "name": "projected_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "participants_finished": { + "name": "participants_finished", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "team_standings_snapshots_unique": { + "name": "team_standings_snapshots_unique", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "snapshot_date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "team_standings_snapshots_team_id_teams_id_fk": { + "name": "team_standings_snapshots_team_id_teams_id_fk", + "tableFrom": "team_standings_snapshots", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_standings_snapshots_season_id_seasons_id_fk": { + "name": "team_standings_snapshots_season_id_seasons_id_fk", + "tableFrom": "team_standings_snapshots", + "tableTo": "seasons", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.teams": { + "name": "teams", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "season_id": { + "name": "season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "logo_url": { + "name": "logo_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "teams_season_id_seasons_id_fk": { + "name": "teams_season_id_seasons_id_fk", + "tableFrom": "teams", + "tableTo": "seasons", + "columnsFrom": [ + "season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tournament_group_members": { + "name": "tournament_group_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "tournament_group_id": { + "name": "tournament_group_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "eliminated": { + "name": "eliminated", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "tournament_group_members_tournament_group_id_tournament_groups_id_fk": { + "name": "tournament_group_members_tournament_group_id_tournament_groups_id_fk", + "tableFrom": "tournament_group_members", + "tableTo": "tournament_groups", + "columnsFrom": [ + "tournament_group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tournament_group_members_participant_id_participants_id_fk": { + "name": "tournament_group_members_participant_id_participants_id_fk", + "tableFrom": "tournament_group_members", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tournament_groups": { + "name": "tournament_groups", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "scoring_event_id": { + "name": "scoring_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "group_name": { + "name": "group_name", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "tournament_groups_scoring_event_id_scoring_events_id_fk": { + "name": "tournament_groups_scoring_event_id_scoring_events_id_fk", + "tableFrom": "tournament_groups", + "tableTo": "scoring_events", + "columnsFrom": [ + "scoring_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "clerk_id": { + "name": "clerk_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "display_name": { + "name": "display_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "first_name": { + "name": "first_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "last_name": { + "name": "last_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "image_url": { + "name": "image_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "is_admin": { + "name": "is_admin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_clerk_id_unique": { + "name": "users_clerk_id_unique", + "nullsNotDistinct": false, + "columns": [ + "clerk_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.autodraft_mode": { + "name": "autodraft_mode", + "schema": "public", + "values": [ + "next_pick", + "while_on" + ] + }, + "public.draft_timer_mode": { + "name": "draft_timer_mode", + "schema": "public", + "values": [ + "chess_clock", + "standard" + ] + }, + "public.event_type": { + "name": "event_type", + "schema": "public", + "values": [ + "playoff_game", + "major_tournament", + "final_standings", + "schedule_event" + ] + }, + "public.picked_by_type": { + "name": "picked_by_type", + "schema": "public", + "values": [ + "owner", + "commissioner", + "admin", + "auto" + ] + }, + "public.playoff_match_game_status": { + "name": "playoff_match_game_status", + "schema": "public", + "values": [ + "scheduled", + "complete", + "postponed" + ] + }, + "public.probability_source": { + "name": "probability_source", + "schema": "public", + "values": [ + "manual", + "futures_odds", + "elo_simulation", + "performance_model" + ] + }, + "public.scoring_pattern": { + "name": "scoring_pattern", + "schema": "public", + "values": [ + "playoff_bracket", + "season_standings", + "qualifying_points" + ] + }, + "public.scoring_type": { + "name": "scoring_type", + "schema": "public", + "values": [ + "playoffs", + "regular_season", + "majors" + ] + }, + "public.season_status": { + "name": "season_status", + "schema": "public", + "values": [ + "pre_draft", + "draft", + "active", + "completed" + ] + }, + "public.simulation_status": { + "name": "simulation_status", + "schema": "public", + "values": [ + "idle", + "running", + "failed" + ] + }, + "public.simulator_type": { + "name": "simulator_type", + "schema": "public", + "values": [ + "f1_standings", + "indycar_standings", + "golf_qualifying_points", + "playoff_bracket", + "ucl_bracket", + "ncaam_bracket", + "ncaaw_bracket", + "nba_bracket", + "nhl_bracket", + "afl_bracket" + ] + }, + "public.sport_type": { + "name": "sport_type", + "schema": "public", + "values": [ + "team", + "individual" + ] + }, + "public.sports_season_status": { + "name": "sports_season_status", + "schema": "public", + "values": [ + "upcoming", + "active", + "completed" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 87cfc65..35f96d8 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -393,6 +393,13 @@ "when": 1774072156237, "tag": "0055_special_vampiro", "breakpoints": true + }, + { + "idx": 56, + "version": "7", + "when": 1774167142673, + "tag": "0056_jittery_the_fallen", + "breakpoints": true } ] } \ No newline at end of file