diff --git a/.env.example b/.env.example index d2e999b..2fb83aa 100644 --- a/.env.example +++ b/.env.example @@ -4,3 +4,4 @@ CLERK_SECRET_KEY="" CLERK_PUBLISHABLE_KEY="" CLERK_WEBHOOK_SECRET="" CONTAINER_REGISTRY="" +DEV_ADMIN_CLERK_ID="" \ No newline at end of file diff --git a/app/components/scoring/PlayoffBracket.tsx b/app/components/scoring/PlayoffBracket.tsx index fc45525..d5c0994 100644 --- a/app/components/scoring/PlayoffBracket.tsx +++ b/app/components/scoring/PlayoffBracket.tsx @@ -45,6 +45,7 @@ interface PlayoffBracketProps { rounds: string[]; // Ordered list of round names (earliest first) preEliminatedParticipants?: { id: string; name: string }[]; // Eliminated before bracket (e.g. group stage) participantPoints?: { participantId: string; points: number }[]; // Computed fantasy points per participant + partialScoreParticipantIds?: string[]; // Still-competing participants with provisional floor scores teamOwnerships?: TeamOwnership[]; userParticipantIds?: string[]; showOwnership?: boolean; @@ -114,6 +115,7 @@ export function PlayoffBracket({ rounds, preEliminatedParticipants = [], participantPoints = [], + partialScoreParticipantIds = [], teamOwnerships = [], userParticipantIds = [], showOwnership = true, @@ -135,7 +137,9 @@ export function PlayoffBracket({ return `Winner of ${feeder.round} M${feeder.matchNumber}`; }; - // Build elimination rankings: collect losers per round, then assign rank labels + // Build elimination rankings: collect losers per round, then assign rank labels. + // In double-chance brackets (e.g. AFL), a participant may lose one round but + // win a later one — only count them as eliminated at their FINAL losing match. const losersByRound = new Map>(); let hasScore = false; let bracketWinner: Participant | null = null; @@ -146,8 +150,17 @@ export function PlayoffBracket({ : null; if (finalMatch?.winner) bracketWinner = finalMatch.winner; + // Build the set of all participants who won at least one completed match. + // If a participant won any match, their earlier loss was not their final elimination. + const participantWinIds = new Set(); + for (const match of matches) { + if (match.isComplete && match.winnerId) participantWinIds.add(match.winnerId); + } + for (const match of matches) { if (!match.isComplete || !match.loser) continue; + // Skip if this loser went on to win another match (double-chance bracket) + if (participantWinIds.has(match.loser.id)) continue; const loserScore = match.loserId === match.participant1Id ? match.participant1Score @@ -162,21 +175,49 @@ export function PlayoffBracket({ } // Walk rounds latest→earliest to assign rank labels (no mutation) + // nextRank must start after the still-alive participants (not always 2) + const allBracketParticipantIds = new Set(); + for (const match of matches) { + if (match.participant1Id) allBracketParticipantIds.add(match.participant1Id); + if (match.participant2Id) allBracketParticipantIds.add(match.participant2Id); + } + const totalEliminatedInBracket = [...losersByRound.values()].reduce( + (sum, losers) => sum + losers.length, + 0 + ); + const stillAlive = + allBracketParticipantIds.size - totalEliminatedInBracket - (bracketWinner ? 1 : 0); const rankedEntries: EliminatedEntry[] = []; - let nextRank = 2; + let nextRank = stillAlive + (bracketWinner ? 2 : 1); for (let ri = rounds.length - 1; ri >= 0; ri--) { const roundLosers = losersByRound.get(rounds[ri]) || []; if (roundLosers.length === 0) continue; - const rankEnd = nextRank + roundLosers.length - 1; - const rankLabel = - nextRank === rankEnd ? `#${nextRank}` : `#${nextRank}–${rankEnd}`; + const rankLabel = `T${nextRank}`; for (const loser of roundLosers) { rankedEntries.push({ ...loser, rankLabel }); } nextRank += roundLosers.length; } - const showRankings = rankedEntries.length > 0 || bracketWinner !== null || preEliminatedParticipants.length > 0; + // Exclude pre-eliminated participants already ranked via bracket match losers + const rankedParticipantIds = new Set(rankedEntries.map((e) => e.participant.id)); + if (bracketWinner) rankedParticipantIds.add(bracketWinner.id); + const filteredPreEliminated = preEliminatedParticipants.filter( + (p) => !rankedParticipantIds.has(p.id) + ); + + const showRankings = rankedEntries.length > 0 || bracketWinner !== null || filteredPreEliminated.length > 0; + + // Build participant lookup from match data for the "In Contention" table + const participantMap = new Map(); + for (const match of matches) { + if (match.participant1) participantMap.set(match.participant1.id, match.participant1); + if (match.participant2) participantMap.set(match.participant2.id, match.participant2); + } + const activeParticipants = [...allBracketParticipantIds] + .filter((id) => !rankedParticipantIds.has(id)) + .map((id) => participantMap.get(id)) + .filter((p): p is Participant => p !== undefined); // Hoist winner row lookups so we don't need an IIFE in JSX const winnerIsOwned = bracketWinner ? userParticipantSet.has(bracketWinner.id) : false; @@ -370,7 +411,68 @@ export function PlayoffBracket({ ); })} - {/* Final Rankings */} + {/* In Contention */} + {activeParticipants.length > 0 && ( + + + + + In Contention + + + + + + + Participant + {showOwnership && ( + Drafted By + )} + {pointsMap.size > 0 && ( + Pts + )} + + + + {activeParticipants.map((p) => { + const isOwned = userParticipantSet.has(p.id); + const ownership = showOwnership ? ownershipMap.get(p.id) || null : null; + const pts = pointsMap.get(p.id); + return ( + + + {p.name} + {isOwned && ( + + )} + + {showOwnership && ( + + {ownership ? ( + + ) : ( + - + )} + + )} + {pointsMap.size > 0 && ( + + {pts !== undefined ? pts : "–"} + + )} + + ); + })} + +
+
+
+ )} + + {/* Final Rankings / Eliminated Teams */} {showRankings && ( @@ -463,7 +565,7 @@ export function PlayoffBracket({ ); })} - {preEliminatedParticipants.map((p) => { + {filteredPreEliminated.map((p) => { const isOwned = userParticipantSet.has(p.id); const ownership = showOwnership ? ownershipMap.get(p.id) || null : null; const pts = pointsMap.get(p.id); diff --git a/app/components/scoring/SportSeasonDisplay.tsx b/app/components/scoring/SportSeasonDisplay.tsx index 451c861..2cc4df5 100644 --- a/app/components/scoring/SportSeasonDisplay.tsx +++ b/app/components/scoring/SportSeasonDisplay.tsx @@ -98,6 +98,7 @@ interface SportSeasonDisplayProps { playoffRounds?: string[]; preEliminatedParticipants?: { id: string; name: string }[]; participantPoints?: { participantId: string; points: number }[]; + partialScoreParticipantIds?: string[]; // Season standings data (F1) seasonStandings?: SeasonStanding[]; @@ -125,6 +126,7 @@ export function SportSeasonDisplay({ playoffRounds = [], preEliminatedParticipants = [], participantPoints = [], + partialScoreParticipantIds = [], seasonStandings = [], seasonIsFinalized = false, qpStandings = [], @@ -147,6 +149,7 @@ export function SportSeasonDisplay({ rounds={playoffRounds} preEliminatedParticipants={preEliminatedParticipants} participantPoints={participantPoints} + partialScoreParticipantIds={partialScoreParticipantIds} teamOwnerships={teamOwnerships} userParticipantIds={userParticipantIds} showOwnership={showOwnership} diff --git a/app/models/__tests__/progressive-floor-scoring.test.ts b/app/models/__tests__/progressive-floor-scoring.test.ts new file mode 100644 index 0000000..d62068f --- /dev/null +++ b/app/models/__tests__/progressive-floor-scoring.test.ts @@ -0,0 +1,170 @@ +import { describe, it, expect } from "vitest"; +import { getGuaranteedMinimumPosition } from "../scoring-calculator"; +import { calculateAveragedPoints, calculateBracketPoints, calculateFantasyPoints, type ScoringRules } from "../scoring-rules"; + +const DEFAULT_SCORING: ScoringRules = { + pointsFor1st: 100, + pointsFor2nd: 70, + pointsFor3rd: 50, + pointsFor4th: 40, + pointsFor5th: 25, + pointsFor6th: 25, + pointsFor7th: 15, + pointsFor8th: 15, +}; + +describe("Progressive Floor Scoring", () => { + describe("getGuaranteedMinimumPosition", () => { + describe("Standard bracket rounds", () => { + it("returns null for Finals — winner is finalized as 1st", () => { + expect(getGuaranteedMinimumPosition("Finals", null, true)).toBeNull(); + expect(getGuaranteedMinimumPosition("Championship", null, true)).toBeNull(); + expect(getGuaranteedMinimumPosition("Super Bowl", null, true)).toBeNull(); + expect(getGuaranteedMinimumPosition("NBA Finals", null, true)).toBeNull(); + expect(getGuaranteedMinimumPosition("Grand Final", "afl_10", true)).toBeNull(); + }); + + it("returns 2 for Semifinals — winners go to Final, guaranteed at worst 2nd", () => { + expect(getGuaranteedMinimumPosition("Semifinals", null, true)).toBe(2); + expect(getGuaranteedMinimumPosition("Final Four", null, true)).toBe(2); + expect(getGuaranteedMinimumPosition("Conference Finals", null, true)).toBe(2); + expect(getGuaranteedMinimumPosition("Conference Championship", null, true)).toBe(2); + }); + + it("returns 3 for Quarterfinals — winners go to Semis, guaranteed at worst 3rd-4th", () => { + expect(getGuaranteedMinimumPosition("Quarterfinals", null, true)).toBe(3); + expect(getGuaranteedMinimumPosition("Elite Eight", null, true)).toBe(3); + expect(getGuaranteedMinimumPosition("Divisional", null, true)).toBe(3); + expect(getGuaranteedMinimumPosition("Conference Semifinals", null, true)).toBe(3); + }); + + it("returns 5 for earlier scoring rounds — winners are guaranteed top-8", () => { + expect(getGuaranteedMinimumPosition("Round of 16", null, true)).toBe(5); + expect(getGuaranteedMinimumPosition("Round of 32", null, true)).toBe(5); + expect(getGuaranteedMinimumPosition("Wild Card Round", null, true)).toBe(5); + }); + + it("returns null for non-scoring rounds — winning doesn't guarantee points yet", () => { + expect(getGuaranteedMinimumPosition("Round of 16", null, false)).toBeNull(); + expect(getGuaranteedMinimumPosition("First Four", null, false)).toBeNull(); + expect(getGuaranteedMinimumPosition("Quarterfinals", null, false)).toBeNull(); + }); + }); + + describe("AFL bracket (afl_10)", () => { + it("returns 3 for Qualifying Finals winners — advance to Preliminary Finals", () => { + expect(getGuaranteedMinimumPosition("Qualifying Finals", "afl_10", true)).toBe(3); + }); + + it("returns 5 for Elimination Finals winners — advance to Semi-Finals", () => { + expect(getGuaranteedMinimumPosition("Elimination Finals", "afl_10", true)).toBe(5); + }); + + it("returns 3 for AFL Semi-Finals winners — advance to Preliminary Finals", () => { + expect(getGuaranteedMinimumPosition("Semi-Finals", "afl_10", true)).toBe(3); + }); + + it("returns 2 for Preliminary Finals winners — advance to Grand Final", () => { + expect(getGuaranteedMinimumPosition("Preliminary Finals", "afl_10", true)).toBe(2); + }); + }); + }); + + describe("Progressive point values (guaranteed minimum points)", () => { + it("standard bracket: 8-team path to champion earns correct floor at each stage", () => { + // Win Quarterfinals → guaranteed at worst 3rd-4th + const afterQF = calculateAveragedPoints([3, 4], DEFAULT_SCORING); + expect(afterQF).toBe(45); // (50 + 40) / 2 + + // Win Semifinals → guaranteed at worst 2nd + const afterSF = calculateFantasyPoints(2, DEFAULT_SCORING); + expect(afterSF).toBe(70); + + // Win Finals → 1st place + const champion = calculateFantasyPoints(1, DEFAULT_SCORING); + expect(champion).toBe(100); + }); + + it("standard bracket: losing in Round of 16 earns 0 pts (non-scoring)", () => { + // Non-scoring round losers get eliminated with 0 pts + expect(calculateFantasyPoints(0, DEFAULT_SCORING)).toBe(0); + }); + + it("standard bracket: Quarterfinal survivors start at 20 pts floor (avg of 5-8)", () => { + // 8 winners from R16 advance to QF. If all 8 lose QF → tied 5th-8th → 20 pts each. + // But only 4 lose QF; the 4 winners advance. Their guaranteed minimum = avg(3, 4). + // The R16 winners' guaranteed minimum (before QF is played) would be avg(5,6,7,8) = 20. + const r16WinnersFloor = calculateAveragedPoints([5, 6, 7, 8], DEFAULT_SCORING); + expect(r16WinnersFloor).toBe(20); // (25+25+15+15)/4 + }); + + it("AFL: Elimination Finals winners get 5th-6th floor (avg)", () => { + // EF winners advance to Semi-Finals. If they lose Semi-Finals → 5th-6th. + const efWinnersFloor = calculateAveragedPoints([5, 6], DEFAULT_SCORING); + expect(efWinnersFloor).toBe(25); // (25+25)/2 = 25 + }); + + it("AFL: Qualifying Finals winners get 3rd-4th floor (avg)", () => { + // QF winners advance to Preliminary Finals. If they lose → 3rd-4th. + const qfWinnersFloor = calculateAveragedPoints([3, 4], DEFAULT_SCORING); + expect(qfWinnersFloor).toBe(45); // (50+40)/2 + }); + + it("AFL: Qualifying Finals losers (second chance) get 5th-6th floor", () => { + // QF losers drop to Semi-Finals. If they lose Semi-Finals → 5th-6th. + const qfLosersFloor = calculateAveragedPoints([5, 6], DEFAULT_SCORING); + expect(qfLosersFloor).toBe(25); // (25+25)/2 = 25 + }); + }); + + describe("Non-scoring round provisional floors", () => { + it("R16 winners (non-scoring) get T5 floor = 20 pts", () => { + // After winning a non-scoring R16, participants are guaranteed top-8. + // processPlayoffEvent assigns finalPosition=5 with isPartialScore=true. + // calculateBracketPoints maps position 5 → avg(5-8) = 20 pts. + expect(calculateBracketPoints(5, DEFAULT_SCORING)).toBe(20); + }); + + it("non-scoring floor (pos 5) is less than QF winner floor (pos 3)", () => { + // Advancing past QF increases the floor from 20 → 45 pts. + const nonScoringFloor = calculateBracketPoints(5, DEFAULT_SCORING); + const afterQFFloor = calculateBracketPoints(3, DEFAULT_SCORING); + expect(afterQFFloor).toBeGreaterThan(nonScoringFloor); + }); + + it("position 5 stored for non-scoring winner gives correct 20 pts via calculateBracketPoints", () => { + // The non-scoring branch stores finalPosition=5 (fixed). + // calculateBracketPoints correctly averages [5,6,7,8] regardless of how many + // winners the non-scoring round produced. + expect(calculateBracketPoints(5, DEFAULT_SCORING)).toBe(20); // (25+25+15+15)/4 + }); + }); + + describe("Progressive scoring progression", () => { + it("score increases as participant advances through bracket", () => { + // A participant's guaranteed minimum only increases, never decreases. + // After each round, verify new floor > previous floor. + const afterR16Floor = calculateAveragedPoints([5, 6, 7, 8], DEFAULT_SCORING); // 20 + const afterQFFloor = calculateAveragedPoints([3, 4], DEFAULT_SCORING); // 45 + const afterSFFloor = calculateFantasyPoints(2, DEFAULT_SCORING); // 70 + const champion = calculateFantasyPoints(1, DEFAULT_SCORING); // 100 + + expect(afterQFFloor).toBeGreaterThan(afterR16Floor); + expect(afterSFFloor).toBeGreaterThan(afterQFFloor); + expect(champion).toBeGreaterThan(afterSFFloor); + }); + + it("final winner's partial score (2nd) is replaced by finalized 1st place score", () => { + // Before Finals: finalist has guaranteed minimum of 2nd place + const finalistFloor = calculateFantasyPoints(2, DEFAULT_SCORING); + expect(finalistFloor).toBe(70); + + // After winning Finals: finalized to 1st place + const championScore = calculateFantasyPoints(1, DEFAULT_SCORING); + expect(championScore).toBe(100); + + // Score increased when finalized + expect(championScore).toBeGreaterThan(finalistFloor); + }); + }); +}); diff --git a/app/models/scoring-calculator.ts b/app/models/scoring-calculator.ts index a991076..7d2eaf1 100644 --- a/app/models/scoring-calculator.ts +++ b/app/models/scoring-calculator.ts @@ -1,7 +1,7 @@ import { database } from "~/database/context"; import * as schema from "~/database/schema"; import { eq, and, inArray } from "drizzle-orm"; -import { getScoringRules, calculateFantasyPoints, calculateAveragedPoints } from "./scoring-rules"; +import { getScoringRules, calculateFantasyPoints, calculateAveragedPoints, calculateBracketPoints } from "./scoring-rules"; import { getSeasonResults } from "./participant-season-result"; import { updateProbabilitiesAfterResult } from "~/services/probability-updater"; @@ -16,14 +16,16 @@ export type ScoringPattern = | "qualifying_points"; /** - * Process a playoff event completion and assign final placements + * Process a playoff event completion and assign final placements. * - * Q19: Admin enters the same placement for all tied participants, - * system automatically shares positions + * Loser placement rules per round type: + * Non-scoring rounds: losers get 0 pts (pre-bracket elimination) + * Quarterfinals / equivalent: losers share T5–T8 + * Semifinals / equivalent: losers share T3–T4 + * Finals / equivalent: loser gets 2nd, winner gets 1st * - * Q20: Participants eliminated before Elite Eight get 0 points - * - * Phase 2.7: Updated to support template-based brackets with non-scoring rounds + * Progressive floor scoring: winners immediately earn a provisional placement + * reflecting their worst-case finish from this point forward. */ export async function processPlayoffEvent( eventId: string, @@ -124,21 +126,49 @@ export async function processPlayoffEvent( throw new Error(`Scoring rules not found for season ${seasonSport.seasonId}`); } - // If this is a non-scoring round, award 0 points (Q20) + // Non-scoring rounds: losers are pre-bracket eliminated (0 pts). + // Winners have cleared the qualifier and are guaranteed a top-8 finish, + // so we bank position 5 (T5–T8 floor) as their provisional score immediately. + // Using a fixed value of 5 is correct for standard brackets (R16 winners → QF → T5 worst case) + // and conservatively safe for non-standard cases (never over-promises points). if (!isScoring) { for (const match of matches) { if (match.loserId) { await upsertParticipantResult( match.loserId, event.sportsSeasonId, - 0, // 0 placement = 0 points for early elimination + 0, // 0 = pre-bracket elimination, earns no points db ); } + if (match.winnerId) { + await upsertParticipantResult( + match.winnerId, + event.sportsSeasonId, + 5, // provisional T5–T8 floor: guaranteed top-8 finish + db, + true + ); + } } } - // AFL-specific rounds (Phase 3.3) - else if (round === "Elimination Finals") { + // AFL-specific rounds (afl_10 bracket template) + else if (round === "Qualifying Finals") { + // AFL: Qualifying Finals losers get a second chance via Semi-Finals. + // They are not eliminated here, but earn a guaranteed minimum of 5th-6th. + // Winners are handled by the general guaranteed-minimum loop below (3rd-4th). + for (const match of matches) { + if (match.loserId) { + await upsertParticipantResult( + match.loserId, + event.sportsSeasonId, + 5, // Guaranteed minimum: at worst 5th-6th via Semi-Finals + db, + true // isPartialScore: still competing + ); + } + } + } else if (round === "Elimination Finals") { // AFL: Elimination Finals losers share 7th-8th for (const match of matches) { if (match.loserId) { @@ -171,7 +201,7 @@ export async function processPlayoffEvent( throw new Error("Finals match is not complete"); } - // Update or create participant results for winner + // Update or create participant results for winner (finalized, isPartialScore=false) await upsertParticipantResult( finalMatch.winnerId, event.sportsSeasonId, @@ -179,7 +209,7 @@ export async function processPlayoffEvent( db ); - // Update or create participant results for loser + // Update or create participant results for loser (finalized, isPartialScore=false) await upsertParticipantResult( finalMatch.loserId, event.sportsSeasonId, @@ -210,12 +240,52 @@ export async function processPlayoffEvent( ); } } + } else { + // Unrecognized scoring round: losers are outside the top-8 and earn 0 pts. + // This handles rounds like "Round of 16", "Round of 32" when isScoring=true, + // or any custom round name not explicitly mapped above. + console.warn( + `[ScoringCalculator] Unrecognized scoring round "${round}" for event ${eventId}. Losers receive 0 pts.` + ); + for (const match of matches) { + if (match.loserId) { + await upsertParticipantResult( + match.loserId, + event.sportsSeasonId, + 0, + db + ); + } + } + } + + // Progressive floor scoring: assign guaranteed minimum points to winners. + // Winners of any scoring round (except Finals, which already finalize both + // participants above) earn provisional points reflecting the worst-case + // placement they can achieve from here. These update as they advance. + const guaranteedMinimum = getGuaranteedMinimumPosition( + round, + event.bracketTemplateId, + isScoring + ); + if (guaranteedMinimum !== null) { + for (const match of matches) { + if (match.winnerId) { + await upsertParticipantResult( + match.winnerId, + event.sportsSeasonId, + guaranteedMinimum, + db, + true // isPartialScore: still competing, score will increase as they advance + ); + } + } } // Recalculate standings for all affected leagues await recalculateAffectedLeagues(event.sportsSeasonId, db); - // Auto-trigger probability recalculation (Phase 5.3) + // Auto-trigger probability recalculation after result try { await updateProbabilitiesAfterResult(event.sportsSeasonId, true); console.log( @@ -231,12 +301,17 @@ export async function processPlayoffEvent( /** * Helper to upsert participant result + * + * isPartialScore=true means the participant is still alive and this placement + * is their guaranteed minimum floor — it will be replaced as they advance or + * when they are finally eliminated. */ async function upsertParticipantResult( participantId: string, sportsSeasonId: string, finalPosition: number, - db: ReturnType + db: ReturnType, + isPartialScore = false ): Promise { const existing = await db.query.participantResults.findFirst({ where: and( @@ -246,10 +321,16 @@ async function upsertParticipantResult( }); if (existing) { + // Never un-finalize: if the row is already finalized (isPartialScore=false), + // a call with isPartialScore=true must not overwrite it (e.g. a re-run of + // processPlayoffEvent after a manual finalization). + if (!existing.isPartialScore && isPartialScore) return; + await db .update(schema.participantResults) .set({ finalPosition, + isPartialScore, updatedAt: new Date(), }) .where(eq(schema.participantResults.id, existing.id)); @@ -258,16 +339,78 @@ async function upsertParticipantResult( participantId, sportsSeasonId, finalPosition, + isPartialScore, }); } } /** - * Process a qualifying event completion and update QP totals - * Phase 3.2 Implementation + * Returns the guaranteed minimum final position for winners of a given round. * - * Q4: Manual finalization after all majors complete - * Q5: Ties in QP handled by sharing placements + * This is the worst-case placement a participant can achieve if they lose + * every remaining match from this point forward. Returns null when: + * - The round is non-scoring (winners haven't reached a points-paying zone) + * - The round is the Finals (winners are already being finalized as 1st place) + */ +export function getGuaranteedMinimumPosition( + round: string, + bracketTemplateId: string | null | undefined, + isScoring: boolean +): number | null { + // Non-scoring rounds: winning doesn't guarantee any points yet + if (!isScoring) return null; + + // Finals: winner is finalized as 1st — handled inline, not via this helper + if ( + round === "Finals" || + round === "Championship" || + round === "Super Bowl" || + round === "NBA Finals" || + round === "Grand Final" + ) { + return null; + } + + // AFL-specific routing (afl_10 bracket template) + if (bracketTemplateId === "afl_10") { + // Qualifying Finals: winners → Prelim Finals (losers there get 3rd-4th) + if (round === "Qualifying Finals") return 3; + // Elimination Finals: winners → Semi-Finals (losers there get 5th-6th) + if (round === "Elimination Finals") return 5; + // Semi-Finals: winners → Prelim Finals (losers there get 3rd-4th) + if (round === "Semi-Finals") return 3; + } + + // Standard bracket rounds + // Semi-final winners advance to the Final — guaranteed at worst 2nd place + if ( + round === "Semifinals" || + round === "Final Four" || + round === "Conference Finals" || + round === "Conference Championship" || + round === "Preliminary Finals" + ) { + return 2; + } + + // Quarterfinal winners advance to Semis — guaranteed at worst 3rd-4th + if ( + round === "Quarterfinals" || + round === "Elite Eight" || + round === "Divisional" || + round === "Conference Semifinals" + ) { + return 3; + } + + // All other scoring rounds (e.g. Round of 16, Round of 32, etc.) — + // winners are now guaranteed a top-8 finish at worst (position 5) + return 5; +} + +/** + * Process a qualifying event completion and update QP totals. + * Ties in QP are handled by sharing placements (averaged points). */ export async function processQualifyingEvent( eventId: string, @@ -363,13 +506,9 @@ export async function processQualifyingEvent( } /** - * Finalize qualifying points and convert to fantasy placements - * Called manually by admin when all majors are complete - * Phase 3.2 Implementation - * - * Q4: Manual finalization (admin button) - * Q5: QP ties handled by sharing placements - * Q19: Tie entry uses same placement, system auto-calculates shared positions + * Finalize qualifying points and convert to fantasy placements. + * Called manually by admin when all events are complete. + * Tied QP totals result in shared placements (averaged points). */ export async function finalizeQualifyingPoints( sportsSeasonId: string, @@ -478,7 +617,7 @@ export async function finalizeQualifyingPoints( // Trigger recalculation for all affected leagues await recalculateAffectedLeagues(sportsSeasonId, db); - // Auto-trigger probability recalculation (Phase 5.3) + // Auto-trigger probability recalculation after result try { await updateProbabilitiesAfterResult(sportsSeasonId, true); console.log( @@ -497,12 +636,9 @@ export async function finalizeQualifyingPoints( } /** - * Process season standings (F1) and assign final placements - * - * Q7: For F1, we show current F1 points during the season, then assign fantasy points at the end - * Q8: Just final standings, not individual races - * Q14: Store current running total, manual update with API future - * Q13/Q19: Ties handled by admin entering same position, system auto-shares placements + * Process season standings (F1/IndyCar) and assign final placements. + * Reads from participant_season_results (championship points) and converts + * final standings positions to fantasy placements. Ties share placements. */ export async function processSeasonStandings( sportsSeasonId: string, @@ -591,7 +727,7 @@ export async function processSeasonStandings( // Trigger recalculation for all affected leagues await recalculateAffectedLeagues(sportsSeasonId, db); - // Auto-trigger probability recalculation (Phase 5.3) + // Auto-trigger probability recalculation after result try { await updateProbabilitiesAfterResult(sportsSeasonId, true); console.log( @@ -641,6 +777,7 @@ export async function calculateTeamScore( participant: { with: { results: true, + sportsSeason: true, }, }, }, @@ -660,10 +797,15 @@ export async function calculateTeamScore( let participantsCompleted = 0; for (const pick of picks) { - const result = pick.participant.results[0]; // Should only be one result per participant + // One result per participant per sports season (enforced by upsertParticipantResult). + // If duplicates exist due to data corruption, the first row wins — acceptable trade-off. + const result = pick.participant.results[0]; if (result && result.finalPosition) { - const points = calculateFantasyPoints(result.finalPosition, scoringRules); + const isBracket = pick.participant.sportsSeason?.scoringPattern === "playoff_bracket"; + const points = isBracket + ? calculateBracketPoints(result.finalPosition, scoringRules) + : calculateFantasyPoints(result.finalPosition, scoringRules); totalPoints += points; participantsCompleted++; @@ -683,8 +825,8 @@ export async function calculateTeamScore( } /** - * Calculate projected total points for a team - * Phase 5.4: Includes actual points from finished participants + EVs from unfinished + * Calculate projected total points for a team. + * Combines actual points from finished participants with EV projections for the rest. */ export async function calculateTeamProjectedScore( teamId: string, @@ -861,10 +1003,9 @@ function assignRanks( } /** - * Recalculate standings for all teams in a season - * Called after any scoring event completes or participant results change - * - * Phase 4.1: Implements tiebreaker logic and ranking + * Recalculate standings for all teams in a season. + * Called after any scoring event completes or participant results change. + * Implements tiebreaker logic: total points, then placement counts (1st, 2nd, …). */ export async function recalculateStandings( seasonId: string, @@ -923,7 +1064,6 @@ export async function recalculateStandings( eighthPlaceCount: teamScore.placementCounts[8], participantsRemaining: teamScore.participantsTotal - teamScore.participantsCompleted, - // Phase 5.4: Projected points tracking actualPoints: teamScore.actualPoints?.toString() ?? null, projectedPoints: teamScore.projectedPoints?.toString() ?? null, participantsFinished: teamScore.participantsFinished ?? null, diff --git a/app/models/scoring-rules.ts b/app/models/scoring-rules.ts index 26fb39c..dc590ab 100644 --- a/app/models/scoring-rules.ts +++ b/app/models/scoring-rules.ts @@ -104,6 +104,34 @@ export function calculateAveragedPoints( return total / placements.length; } +/** + * Calculate fantasy points for a bracket placement, averaging tied positions. + * + * Standard single-elimination bracket tiers: + * 1st: solo winner + * 2nd: solo finalist + * 3rd-4th: two SF losers share these positions → averaged + * 5th-8th: four QF losers share these positions → averaged + * + * Use this instead of calculateFantasyPoints for playoff_bracket scoring. + * + * TODO: This hardcodes 8-team bracket tiers. Future support for larger brackets + * (e.g. 16-team with 9th-16th scoring) will require parameterised tier config. + */ +export function calculateBracketPoints( + finalPosition: number, + rules: ScoringRules +): number { + if (finalPosition <= 0) return 0; + if (finalPosition === 1) return rules.pointsFor1st; + if (finalPosition === 2) return rules.pointsFor2nd; + if (finalPosition === 3 || finalPosition === 4) + return calculateAveragedPoints([3, 4], rules); + if (finalPosition >= 5 && finalPosition <= 8) + return calculateAveragedPoints([5, 6, 7, 8], rules); + return 0; +} + /** * Get points array as a simple ordered list [1st, 2nd, 3rd, ..., 8th] * Useful for display purposes diff --git a/app/models/user.ts b/app/models/user.ts index 3b3467e..fea35d2 100644 --- a/app/models/user.ts +++ b/app/models/user.ts @@ -124,6 +124,9 @@ export async function isUserAdmin(userId: string): Promise { } export async function isUserAdminByClerkId(clerkId: string): Promise { + if (process.env.NODE_ENV === "development" && process.env.DEV_ADMIN_CLERK_ID === clerkId) { + return true; + } const user = await findUserByClerkId(clerkId); return user?.isAdmin ?? false; } diff --git a/app/routes/leagues/$leagueId.sports-seasons.$sportsSeasonId.server.ts b/app/routes/leagues/$leagueId.sports-seasons.$sportsSeasonId.server.ts index d94c64b..0f0df0d 100644 --- a/app/routes/leagues/$leagueId.sports-seasons.$sportsSeasonId.server.ts +++ b/app/routes/leagues/$leagueId.sports-seasons.$sportsSeasonId.server.ts @@ -5,10 +5,10 @@ import { isUserLeagueMember, isCommissioner, findTeamsBySeasonId, - findUserByClerkId, } from "~/models"; import { getDraftPicks } from "~/models/draft-pick"; import { getSeasonResults } from "~/models/participant-season-result"; +import { calculateBracketPoints } from "~/models/scoring-rules"; import { getQPStandings } from "~/models/qualifying-points"; import { getUpcomingScoringEvents, @@ -16,7 +16,7 @@ import { } from "~/models/scoring-event"; import { database } from "~/database/context"; import * as schema from "~/database/schema"; -import { eq, and } from "drizzle-orm"; +import { eq, and, inArray } from "drizzle-orm"; export async function loader(args: Route.LoaderArgs) { const { userId } = await getAuth(args); @@ -88,18 +88,17 @@ export async function loader(args: Route.LoaderArgs) { { teamName: string; teamId: string; ownerName?: string } >(); - // Get unique owner IDs for user lookups - const ownerIds = [...new Set(teams.map((t) => t.ownerId).filter(Boolean))]; - const ownerUsers = await Promise.all( - ownerIds.map(async (ownerId) => { - const user = await findUserByClerkId(ownerId!); - return { - ownerId, - name: user?.username || user?.displayName || "Unknown", - }; - }) + // Get unique owner IDs and batch-fetch their user records in a single query + const ownerIds = [...new Set(teams.map((t) => t.ownerId).filter(Boolean))] as string[]; + const ownerUserRows = ownerIds.length > 0 + ? await db.query.users.findMany({ + where: inArray(schema.users.clerkId, ownerIds), + columns: { clerkId: true, username: true, displayName: true }, + }) + : []; + const ownerMap = new Map( + ownerUserRows.map((u) => [u.clerkId, u.username || u.displayName || "Unknown"]) ); - const ownerMap = new Map(ownerUsers.map((o) => [o.ownerId, o.name])); // Map draft picks to ownership for (const pick of draftPicks) { @@ -137,6 +136,7 @@ export async function loader(args: Route.LoaderArgs) { let playoffRounds: string[] = []; let preEliminatedParticipants: { id: string; name: string }[] = []; let participantPoints: { participantId: string; points: number }[] = []; + let partialScoreParticipantIds: string[] = []; let seasonStandings: SeasonStanding[] = []; let qpStandings: any[] = []; @@ -169,8 +169,8 @@ export async function loader(args: Route.LoaderArgs) { ); } - // Fetch group-stage losers and fantasy points in parallel - const [eliminatedResults, seasonResults] = await Promise.all([ + // Fetch group-stage losers and all participant results for bracket scoring + const [eliminatedResults, allResults] = await Promise.all([ db.query.participantResults.findMany({ where: and( eq(schema.participantResults.sportsSeasonId, sportsSeasonId), @@ -178,16 +178,44 @@ export async function loader(args: Route.LoaderArgs) { ), with: { participant: true }, }), - getSeasonResults(sportsSeasonId), + db.query.participantResults.findMany({ + where: eq(schema.participantResults.sportsSeasonId, sportsSeasonId), + }), ]); preEliminatedParticipants = eliminatedResults .filter((r) => r.participant) .map((r) => ({ id: (r as any).participant.id, name: (r as any).participant.name })); - participantPoints = seasonResults - .map((r) => ({ participantId: r.participantId, points: parseFloat(r.currentPoints || "0") })) - .filter((r) => r.points > 0); + // Compute per-participant fantasy points directly from participant_results. + // This covers both finalized placements and progressive floor scores for still-alive participants. + const scoringRules = { + pointsFor1st: season.pointsFor1st, + pointsFor2nd: season.pointsFor2nd, + pointsFor3rd: season.pointsFor3rd, + pointsFor4th: season.pointsFor4th, + pointsFor5th: season.pointsFor5th, + pointsFor6th: season.pointsFor6th, + pointsFor7th: season.pointsFor7th, + pointsFor8th: season.pointsFor8th, + }; + const seenParticipantIds = new Set(); + participantPoints = allResults + .filter((r) => r.finalPosition !== null && r.finalPosition > 0) + .filter((r) => { + if (seenParticipantIds.has(r.participantId)) return false; + seenParticipantIds.add(r.participantId); + return true; + }) + .map((r) => ({ + participantId: r.participantId, + points: calculateBracketPoints(r.finalPosition!, scoringRules), + })); + + // Track which participants have provisional (floor) scores — still competing + partialScoreParticipantIds = allResults + .filter((r) => r.isPartialScore) + .map((r) => r.participantId); } else if (scoringPattern === "season_standings") { // Fetch F1-style championship standings and map to SeasonStanding shape const results = await getSeasonResults(sportsSeasonId); @@ -226,6 +254,7 @@ export async function loader(args: Route.LoaderArgs) { playoffRounds, preEliminatedParticipants, participantPoints, + partialScoreParticipantIds, seasonStandings, qpStandings, teamOwnerships, diff --git a/app/routes/leagues/$leagueId.sports-seasons.$sportsSeasonId.tsx b/app/routes/leagues/$leagueId.sports-seasons.$sportsSeasonId.tsx index 5a5e34f..79e86ea 100644 --- a/app/routes/leagues/$leagueId.sports-seasons.$sportsSeasonId.tsx +++ b/app/routes/leagues/$leagueId.sports-seasons.$sportsSeasonId.tsx @@ -49,6 +49,7 @@ export default function SportSeasonDetail({ playoffRounds, preEliminatedParticipants, participantPoints, + partialScoreParticipantIds, seasonStandings, qpStandings, teamOwnerships, @@ -100,6 +101,7 @@ export default function SportSeasonDetail({ playoffRounds={playoffRounds} preEliminatedParticipants={preEliminatedParticipants} participantPoints={participantPoints} + partialScoreParticipantIds={partialScoreParticipantIds} seasonStandings={seasonStandings as any} seasonIsFinalized={seasonIsFinalized} qpStandings={qpStandings as any} diff --git a/app/services/__tests__/probability-updater.test.ts b/app/services/__tests__/probability-updater.test.ts index 4841b86..c1388ed 100644 --- a/app/services/__tests__/probability-updater.test.ts +++ b/app/services/__tests__/probability-updater.test.ts @@ -32,6 +32,7 @@ describe("probability-updater", () => { participantId: "participant-1", sportsSeasonId: "season-1", finalPosition: 1, + isPartialScore: false, qualifyingPoints: null, notes: null, createdAt: new Date(), @@ -102,6 +103,7 @@ describe("probability-updater", () => { participantId: "participant-1", sportsSeasonId: "season-1", finalPosition: 1, + isPartialScore: false, qualifyingPoints: null, notes: null, createdAt: new Date(), @@ -112,6 +114,7 @@ describe("probability-updater", () => { participantId: "participant-2", sportsSeasonId: "season-1", finalPosition: 2, + isPartialScore: false, qualifyingPoints: null, notes: null, createdAt: new Date(), @@ -187,6 +190,7 @@ describe("probability-updater", () => { participantId: "participant-1", sportsSeasonId: "season-1", finalPosition: null, // No position set yet + isPartialScore: false, qualifyingPoints: "50.00", notes: null, createdAt: new Date(), @@ -210,6 +214,7 @@ describe("probability-updater", () => { participantId: "participant-1", sportsSeasonId: "season-1", finalPosition: 0, // Eliminated + isPartialScore: false, qualifyingPoints: null, notes: null, createdAt: new Date(), diff --git a/database/schema.ts b/database/schema.ts index d93748b..74f230b 100644 --- a/database/schema.ts +++ b/database/schema.ts @@ -318,6 +318,7 @@ export const participantResults = pgTable("participant_results", { .notNull() .references(() => sportsSeasons.id, { onDelete: "cascade" }), finalPosition: integer("final_position"), + isPartialScore: boolean("is_partial_score").notNull().default(false), qualifyingPoints: decimal("qualifying_points", { precision: 10, scale: 2 }), notes: text("notes"), createdAt: timestamp("created_at").defaultNow().notNull(), diff --git a/drizzle/0038_sad_wilson_fisk.sql b/drizzle/0038_sad_wilson_fisk.sql new file mode 100644 index 0000000..15a9605 --- /dev/null +++ b/drizzle/0038_sad_wilson_fisk.sql @@ -0,0 +1,2 @@ +-- Progressive floor scoring: track whether a participant result is provisional (still alive) +ALTER TABLE "participant_results" ADD COLUMN "is_partial_score" boolean DEFAULT false NOT NULL; diff --git a/drizzle/meta/0038_snapshot.json b/drizzle/meta/0038_snapshot.json new file mode 100644 index 0000000..8fd7e1d --- /dev/null +++ b/drizzle/meta/0038_snapshot.json @@ -0,0 +1,3303 @@ +{ + "id": "c33ec2dc-da57-4f9d-b094-84b423f95f2a", + "prevId": "5a5b7ce7-0459-461f-8ff0-09f6ce367bd8", + "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 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.participant_ev_snapshots": { + "name": "participant_ev_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "snapshot_date": { + "name": "snapshot_date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "prob_first": { + "name": "prob_first", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_second": { + "name": "prob_second", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_third": { + "name": "prob_third", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_fourth": { + "name": "prob_fourth", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_fifth": { + "name": "prob_fifth", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_sixth": { + "name": "prob_sixth", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_seventh": { + "name": "prob_seventh", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_eighth": { + "name": "prob_eighth", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "calculated_ev": { + "name": "calculated_ev", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "source": { + "name": "source", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "participant_ev_snapshots_unique": { + "name": "participant_ev_snapshots_unique", + "columns": [ + { + "expression": "participant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sports_season_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "snapshot_date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "participant_ev_snapshots_participant_id_participants_id_fk": { + "name": "participant_ev_snapshots_participant_id_participants_id_fk", + "tableFrom": "participant_ev_snapshots", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "participant_ev_snapshots_sports_season_id_sports_seasons_id_fk": { + "name": "participant_ev_snapshots_sports_season_id_sports_seasons_id_fk", + "tableFrom": "participant_ev_snapshots", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.participant_expected_values": { + "name": "participant_expected_values", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "prob_first": { + "name": "prob_first", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_second": { + "name": "prob_second", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_third": { + "name": "prob_third", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_fourth": { + "name": "prob_fourth", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_fifth": { + "name": "prob_fifth", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_sixth": { + "name": "prob_sixth", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_seventh": { + "name": "prob_seventh", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "prob_eighth": { + "name": "prob_eighth", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "expected_value": { + "name": "expected_value", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "source": { + "name": "source", + "type": "probability_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'manual'" + }, + "source_odds": { + "name": "source_odds", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "calculated_at": { + "name": "calculated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "participant_expected_values_participant_id_participants_id_fk": { + "name": "participant_expected_values_participant_id_participants_id_fk", + "tableFrom": "participant_expected_values", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "participant_expected_values_sports_season_id_sports_seasons_id_fk": { + "name": "participant_expected_values_sports_season_id_sports_seasons_id_fk", + "tableFrom": "participant_expected_values", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.participant_qualifying_totals": { + "name": "participant_qualifying_totals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "total_qualifying_points": { + "name": "total_qualifying_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "events_scored": { + "name": "events_scored", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "final_ranking": { + "name": "final_ranking", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "participant_qualifying_totals_participant_id_participants_id_fk": { + "name": "participant_qualifying_totals_participant_id_participants_id_fk", + "tableFrom": "participant_qualifying_totals", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "participant_qualifying_totals_sports_season_id_sports_seasons_id_fk": { + "name": "participant_qualifying_totals_sports_season_id_sports_seasons_id_fk", + "tableFrom": "participant_qualifying_totals", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.participant_results": { + "name": "participant_results", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "final_position": { + "name": "final_position", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_partial_score": { + "name": "is_partial_score", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "qualifying_points": { + "name": "qualifying_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "participant_results_participant_id_participants_id_fk": { + "name": "participant_results_participant_id_participants_id_fk", + "tableFrom": "participant_results", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "participant_results_sports_season_id_sports_seasons_id_fk": { + "name": "participant_results_sports_season_id_sports_seasons_id_fk", + "tableFrom": "participant_results", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.participant_season_results": { + "name": "participant_season_results", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "current_points": { + "name": "current_points", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "current_position": { + "name": "current_position", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "participant_season_results_participant_id_participants_id_fk": { + "name": "participant_season_results_participant_id_participants_id_fk", + "tableFrom": "participant_season_results", + "tableTo": "participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "participant_season_results_sports_season_id_sports_seasons_id_fk": { + "name": "participant_season_results_sports_season_id_sports_seasons_id_fk", + "tableFrom": "participant_season_results", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.participants": { + "name": "participants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "sports_season_id": { + "name": "sports_season_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "short_name": { + "name": "short_name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "expected_value": { + "name": "expected_value", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "participants_sports_season_id_sports_seasons_id_fk": { + "name": "participants_sports_season_id_sports_seasons_id_fk", + "tableFrom": "participants", + "tableTo": "sports_seasons", + "columnsFrom": [ + "sports_season_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.playoff_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.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_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 + }, + "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 + }, + "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'" + }, + "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": {}, + "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.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", + "auto" + ] + }, + "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" + ] + }, + "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 b799f5c..e027ad9 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -267,6 +267,13 @@ "when": 1763289000000, "tag": "0037_add_simulator_type_to_sports", "breakpoints": true + }, + { + "idx": 38, + "version": "7", + "when": 1773122866361, + "tag": "0038_sad_wilson_fisk", + "breakpoints": true } ] } \ No newline at end of file