From a3b8fa630910cca31e8b5fa39c9dd469dd82f40d Mon Sep 17 00:00:00 2001
From: Chris Parsons
Date: Mon, 3 Nov 2025 09:36:16 -0800
Subject: [PATCH] feat: Implement bracket expansion plan to support various
tournament structures
- Added a comprehensive plan for bracket expansion, including support for 4, 8, 16, 32, and 68 team formats.
- Introduced a template-based bracket system with predefined templates for NCAA March Madness, NFL Playoffs, NBA Playoffs, and simple brackets.
- Updated UI flow for bracket creation, allowing admins to select templates and assign participants flexibly.
- Enhanced database schema to accommodate new scoring rules and bracket templates.
- Proposed updates to scoring logic to handle non-scoring rounds and participant placements correctly.
- Documented implementation phases for gradual rollout of new features.
- Addressed critical bugs in the playoff event processing and scoring logic, ensuring proper advancement and scoring rules across multiple leagues.
---
app/components/StandingsTable.tsx | 166 +
.../__tests__/scoring-calculator.test.ts | 207 ++
app/models/participant-result.ts | 19 +-
app/models/playoff-match.ts | 251 ++
app/models/scoring-calculator.ts | 140 +-
app/models/scoring-event.ts | 3 +-
app/routes.ts | 4 +
...sons.$id.events.$eventId.bracket.server.ts | 255 ++
...ts-seasons.$id.events.$eventId.bracket.tsx | 278 ++
...min.sports-seasons.$id.events.$eventId.tsx | 32 +-
.../admin.sports-seasons.$id.events.server.ts | 3 +-
.../admin.sports-seasons.$id.events.tsx | 9 +-
app/routes/admin.sports-seasons.$id.tsx | 27 +-
database/schema.ts | 2 -
drizzle/0021_fair_ser_duncan.sql | 4 +
drizzle/0022_shiny_mother_askani.sql | 1 +
drizzle/meta/0021_snapshot.json | 2706 +++++++++++++++++
drizzle/meta/0022_snapshot.json | 2699 ++++++++++++++++
drizzle/meta/_journal.json | 14 +
plans/bracket-expansion.md | 345 +++
plans/phase2-bugs-review.md | 337 ++
plans/scoring-system.md | 67 +-
22 files changed, 7493 insertions(+), 76 deletions(-)
create mode 100644 app/components/StandingsTable.tsx
create mode 100644 app/models/__tests__/scoring-calculator.test.ts
create mode 100644 app/models/playoff-match.ts
create mode 100644 app/routes/admin.sports-seasons.$id.events.$eventId.bracket.server.ts
create mode 100644 app/routes/admin.sports-seasons.$id.events.$eventId.bracket.tsx
create mode 100644 drizzle/0021_fair_ser_duncan.sql
create mode 100644 drizzle/0022_shiny_mother_askani.sql
create mode 100644 drizzle/meta/0021_snapshot.json
create mode 100644 drizzle/meta/0022_snapshot.json
create mode 100644 plans/bracket-expansion.md
create mode 100644 plans/phase2-bugs-review.md
diff --git a/app/components/StandingsTable.tsx b/app/components/StandingsTable.tsx
new file mode 100644
index 0000000..c15ebb5
--- /dev/null
+++ b/app/components/StandingsTable.tsx
@@ -0,0 +1,166 @@
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "~/components/ui/table";
+import { Badge } from "~/components/ui/badge";
+import { TrendingUp, TrendingDown, Minus, Trophy, Medal, Award } from "lucide-react";
+
+export interface StandingsRow {
+ teamId: string;
+ teamName: string;
+ totalPoints: number;
+ currentRank: number;
+ previousRank?: number | null;
+ firstPlaceCount: number;
+ secondPlaceCount: number;
+ thirdPlaceCount: number;
+ fourthPlaceCount: number;
+ fifthPlaceCount: number;
+ sixthPlaceCount: number;
+ seventhPlaceCount: number;
+ eighthPlaceCount: number;
+ participantsRemaining: number;
+}
+
+interface StandingsTableProps {
+ standings: StandingsRow[];
+ showMovement?: boolean;
+ showPlacementBreakdown?: boolean;
+}
+
+export function StandingsTable({
+ standings,
+ showMovement = true,
+ showPlacementBreakdown = false,
+}: StandingsTableProps) {
+ const getMovementIndicator = (current: number, previous?: number | null) => {
+ if (!showMovement || !previous) return null;
+
+ const change = previous - current; // Positive means moved up
+
+ if (change > 0) {
+ return (
+
+
+ +{change}
+
+ );
+ } else if (change < 0) {
+ return (
+
+
+ {change}
+
+ );
+ } else {
+ return (
+
+
+ -
+
+ );
+ }
+ };
+
+ const getRankBadge = (rank: number) => {
+ if (rank === 1) {
+ return (
+
+
+ {rank}
+
+ );
+ } else if (rank === 2) {
+ return (
+
+
+ {rank}
+
+ );
+ } else if (rank === 3) {
+ return (
+
+ );
+ } else {
+ return {rank} ;
+ }
+ };
+
+ return (
+
+
+
+ Rank
+ {showMovement && Change }
+ Team
+ Total Points
+ {showPlacementBreakdown && (
+ <>
+ 🥇
+ 🥈
+ 🥉
+ 4th
+ 5th
+ 6th
+ 7th
+ 8th
+ >
+ )}
+ Remaining
+
+
+
+ {standings.length === 0 ? (
+
+
+ No standings data available yet
+
+
+ ) : (
+ standings.map((row) => (
+
+
+ {getRankBadge(row.currentRank)}
+
+ {showMovement && (
+
+ {getMovementIndicator(row.currentRank, row.previousRank)}
+
+ )}
+ {row.teamName}
+
+ {row.totalPoints.toFixed(1)}
+
+ {showPlacementBreakdown && (
+ <>
+ {row.firstPlaceCount || "-"}
+ {row.secondPlaceCount || "-"}
+ {row.thirdPlaceCount || "-"}
+ {row.fourthPlaceCount || "-"}
+ {row.fifthPlaceCount || "-"}
+ {row.sixthPlaceCount || "-"}
+ {row.seventhPlaceCount || "-"}
+ {row.eighthPlaceCount || "-"}
+ >
+ )}
+
+ {row.participantsRemaining > 0 ? (
+ {row.participantsRemaining}
+ ) : (
+ -
+ )}
+
+
+ ))
+ )}
+
+
+ );
+}
diff --git a/app/models/__tests__/scoring-calculator.test.ts b/app/models/__tests__/scoring-calculator.test.ts
new file mode 100644
index 0000000..1178ed2
--- /dev/null
+++ b/app/models/__tests__/scoring-calculator.test.ts
@@ -0,0 +1,207 @@
+import { describe, it, expect } from "vitest";
+import { calculateFantasyPoints, calculateAveragedPoints, 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("Scoring Calculator", () => {
+ describe("calculateFantasyPoints", () => {
+ it("should calculate points for 1st place", () => {
+ const points = calculateFantasyPoints(1, DEFAULT_SCORING);
+ expect(points).toBe(100);
+ });
+
+ it("should calculate points for 2nd place", () => {
+ const points = calculateFantasyPoints(2, DEFAULT_SCORING);
+ expect(points).toBe(70);
+ });
+
+ it("should calculate points for 8th place", () => {
+ const points = calculateFantasyPoints(8, DEFAULT_SCORING);
+ expect(points).toBe(15);
+ });
+
+ it("should return 0 for placements outside 1-8", () => {
+ const points = calculateFantasyPoints(9, DEFAULT_SCORING);
+ expect(points).toBe(0);
+ });
+
+ it("should handle custom scoring rules", () => {
+ const customScoring: ScoringRules = {
+ pointsFor1st: 200,
+ pointsFor2nd: 150,
+ pointsFor3rd: 100,
+ pointsFor4th: 75,
+ pointsFor5th: 50,
+ pointsFor6th: 40,
+ pointsFor7th: 30,
+ pointsFor8th: 20,
+ };
+
+ const points = calculateFantasyPoints(1, customScoring);
+ expect(points).toBe(200);
+ });
+ });
+
+ describe("calculateAveragedPoints", () => {
+ it("should average points for two-way tie (SF losers sharing 3rd/4th)", () => {
+ const points = calculateAveragedPoints([3, 4], DEFAULT_SCORING);
+ // (50 + 40) / 2 = 45
+ expect(points).toBe(45);
+ });
+
+ it("should average points for four-way tie (QF losers sharing 5th-8th)", () => {
+ const points = calculateAveragedPoints([5, 6, 7, 8], DEFAULT_SCORING);
+ // (25 + 25 + 15 + 15) / 4 = 20
+ expect(points).toBe(20);
+ });
+
+ it("should average points for three-way tie", () => {
+ const points = calculateAveragedPoints([1, 2, 3], DEFAULT_SCORING);
+ // (100 + 70 + 50) / 3 = 73.33...
+ expect(points).toBeCloseTo(73.33, 2);
+ });
+
+ it("should handle single placement (no tie)", () => {
+ const points = calculateAveragedPoints([1], DEFAULT_SCORING);
+ expect(points).toBe(100);
+ });
+
+ it("should return 0 for empty array", () => {
+ const points = calculateAveragedPoints([], DEFAULT_SCORING);
+ expect(points).toBe(0);
+ });
+
+ it("should work with custom scoring rules", () => {
+ const customScoring: ScoringRules = {
+ pointsFor1st: 80,
+ pointsFor2nd: 60,
+ pointsFor3rd: 40,
+ pointsFor4th: 30,
+ pointsFor5th: 20,
+ pointsFor6th: 20,
+ pointsFor7th: 10,
+ pointsFor8th: 10,
+ };
+
+ const points = calculateAveragedPoints([5, 6, 7, 8], customScoring);
+ // (20 + 20 + 10 + 10) / 4 = 15
+ expect(points).toBe(15);
+ });
+ });
+
+ describe("Playoff Placement Scenarios", () => {
+ it("should handle 8-team single elimination bracket", () => {
+ // Champion: 1st (100 pts)
+ const champion = calculateFantasyPoints(1, DEFAULT_SCORING);
+ expect(champion).toBe(100);
+
+ // Runner-up: 2nd (70 pts)
+ const runnerUp = calculateFantasyPoints(2, DEFAULT_SCORING);
+ expect(runnerUp).toBe(70);
+
+ // SF losers: share 3rd/4th (45 pts each)
+ const sfLosers = calculateAveragedPoints([3, 4], DEFAULT_SCORING);
+ expect(sfLosers).toBe(45);
+
+ // QF losers: share 5th-8th (20 pts each)
+ const qfLosers = calculateAveragedPoints([5, 6, 7, 8], DEFAULT_SCORING);
+ expect(qfLosers).toBe(20);
+ });
+
+ it("should handle 4-team bracket (no quarterfinals)", () => {
+ // Champion: 1st (100 pts)
+ const champion = calculateFantasyPoints(1, DEFAULT_SCORING);
+ expect(champion).toBe(100);
+
+ // Runner-up: 2nd (70 pts)
+ const runnerUp = calculateFantasyPoints(2, DEFAULT_SCORING);
+ expect(runnerUp).toBe(70);
+
+ // SF losers: share 3rd/4th (45 pts each)
+ const sfLosers = calculateAveragedPoints([3, 4], DEFAULT_SCORING);
+ expect(sfLosers).toBe(45);
+ });
+ });
+
+ describe("Edge Cases", () => {
+ it("should handle all participants tying for 1st-8th", () => {
+ const points = calculateAveragedPoints([1, 2, 3, 4, 5, 6, 7, 8], DEFAULT_SCORING);
+ // (100 + 70 + 50 + 40 + 25 + 25 + 15 + 15) / 8 = 42.5
+ expect(points).toBe(42.5);
+ });
+
+ it("should handle placements with same point values", () => {
+ // With default scoring, 5th and 6th both get 25 points
+ const points56 = calculateAveragedPoints([5, 6], DEFAULT_SCORING);
+ expect(points56).toBe(25);
+
+ // 7th and 8th both get 15 points
+ const points78 = calculateAveragedPoints([7, 8], DEFAULT_SCORING);
+ expect(points78).toBe(15);
+ });
+
+ it("should handle zero points for eliminated participants", () => {
+ // According to Q20, participants eliminated before Elite Eight get 0 points
+ const points = calculateFantasyPoints(9, DEFAULT_SCORING);
+ expect(points).toBe(0);
+
+ const points10 = calculateFantasyPoints(16, DEFAULT_SCORING);
+ expect(points10).toBe(0);
+ });
+ });
+
+ describe("Real-world Scenarios", () => {
+ it("should correctly calculate NFL playoff points", () => {
+ // NFL has 8 teams in playoffs (QF, SF, Finals)
+ // Super Bowl winner: 100 pts
+ const winner = calculateFantasyPoints(1, DEFAULT_SCORING);
+ expect(winner).toBe(100);
+
+ // Super Bowl loser: 70 pts
+ const loser = calculateFantasyPoints(2, DEFAULT_SCORING);
+ expect(loser).toBe(70);
+
+ // Conference championship losers: 45 pts each
+ const confLosers = calculateAveragedPoints([3, 4], DEFAULT_SCORING);
+ expect(confLosers).toBe(45);
+
+ // Divisional round losers: 20 pts each
+ const divLosers = calculateAveragedPoints([5, 6, 7, 8], DEFAULT_SCORING);
+ expect(divLosers).toBe(20);
+ });
+
+ it("should correctly calculate NCAA March Madness Elite Eight points", () => {
+ // Even though 68 teams start, only Elite Eight (8 teams) get points
+ // Teams eliminated before Elite Eight get 0 points
+
+ // Champion: 100 pts
+ const champion = calculateFantasyPoints(1, DEFAULT_SCORING);
+ expect(champion).toBe(100);
+
+ // Runner-up: 70 pts
+ const runnerUp = calculateFantasyPoints(2, DEFAULT_SCORING);
+ expect(runnerUp).toBe(70);
+
+ // Final Four losers: 45 pts each
+ const ffLosers = calculateAveragedPoints([3, 4], DEFAULT_SCORING);
+ expect(ffLosers).toBe(45);
+
+ // Elite Eight losers: 20 pts each
+ const e8Losers = calculateAveragedPoints([5, 6, 7, 8], DEFAULT_SCORING);
+ expect(e8Losers).toBe(20);
+
+ // Sweet Sixteen loser (example): 0 pts
+ const sweetSixteenLoser = calculateFantasyPoints(9, DEFAULT_SCORING);
+ expect(sweetSixteenLoser).toBe(0);
+ });
+ });
+});
diff --git a/app/models/participant-result.ts b/app/models/participant-result.ts
index 1511647..a893664 100644
--- a/app/models/participant-result.ts
+++ b/app/models/participant-result.ts
@@ -101,20 +101,8 @@ export async function deleteParticipantResultsBySportsSeasonId(
}
/**
- * Calculate points awarded based on final position
- * 1st: 80, 2nd: 50, 3rd-4th: 30, 5th-8th: 20, 9th+: 0
- */
-export function calculatePointsFromPosition(position: number | null): number {
- if (position === null || position < 1) return 0;
- if (position === 1) return 80;
- if (position === 2) return 50;
- if (position >= 3 && position <= 4) return 30;
- if (position >= 5 && position <= 8) return 20;
- return 0;
-}
-
-/**
- * Set result with automatic point calculation
+ * Set result for a participant in a sports season
+ * Points are calculated on-demand based on each fantasy league's scoring rules
*/
export async function setParticipantResult(
participantId: string,
@@ -124,7 +112,6 @@ export async function setParticipantResult(
notes?: string
): Promise {
const db = database();
- const pointsAwarded = calculatePointsFromPosition(finalPosition);
// Check if result already exists
const existing = await db.query.participantResults.findFirst({
@@ -138,7 +125,6 @@ export async function setParticipantResult(
// Update existing result
return await updateParticipantResult(existing.id, {
finalPosition,
- pointsAwarded,
qualifyingPoints: qualifyingPoints?.toString(),
notes,
});
@@ -148,7 +134,6 @@ export async function setParticipantResult(
participantId,
sportsSeasonId,
finalPosition,
- pointsAwarded,
qualifyingPoints: qualifyingPoints?.toString(),
notes,
});
diff --git a/app/models/playoff-match.ts b/app/models/playoff-match.ts
new file mode 100644
index 0000000..6828d84
--- /dev/null
+++ b/app/models/playoff-match.ts
@@ -0,0 +1,251 @@
+import { eq, and } from "drizzle-orm";
+import { database } from "~/database/context";
+import * as schema from "~/database/schema";
+
+export type PlayoffMatch = typeof schema.playoffMatches.$inferSelect;
+export type NewPlayoffMatch = typeof schema.playoffMatches.$inferInsert;
+
+export async function createPlayoffMatch(
+ data: NewPlayoffMatch
+): Promise {
+ const db = database();
+ const [match] = await db
+ .insert(schema.playoffMatches)
+ .values(data)
+ .returning();
+ return match;
+}
+
+export async function createManyPlayoffMatches(
+ data: NewPlayoffMatch[]
+): Promise {
+ const db = database();
+ return await db
+ .insert(schema.playoffMatches)
+ .values(data)
+ .returning();
+}
+
+export async function findPlayoffMatchById(
+ id: string
+): Promise {
+ const db = database();
+ return await db.query.playoffMatches.findFirst({
+ where: eq(schema.playoffMatches.id, id),
+ with: {
+ scoringEvent: true,
+ participant1: true,
+ participant2: true,
+ winner: true,
+ loser: true,
+ },
+ });
+}
+
+export async function findPlayoffMatchesByEventId(
+ eventId: string
+): Promise {
+ const db = database();
+ return await db.query.playoffMatches.findMany({
+ where: eq(schema.playoffMatches.scoringEventId, eventId),
+ orderBy: (matches, { asc }) => [asc(matches.matchNumber)],
+ with: {
+ participant1: true,
+ participant2: true,
+ winner: true,
+ loser: true,
+ },
+ });
+}
+
+export async function findPlayoffMatchesByEventIdAndRound(
+ eventId: string,
+ round: string
+): Promise {
+ const db = database();
+ return await db.query.playoffMatches.findMany({
+ where: and(
+ eq(schema.playoffMatches.scoringEventId, eventId),
+ eq(schema.playoffMatches.round, round)
+ ),
+ orderBy: (matches, { asc }) => [asc(matches.matchNumber)],
+ with: {
+ participant1: true,
+ participant2: true,
+ winner: true,
+ loser: true,
+ },
+ });
+}
+
+export async function updatePlayoffMatch(
+ id: string,
+ data: Partial
+): Promise {
+ const db = database();
+ const [match] = await db
+ .update(schema.playoffMatches)
+ .set({ ...data, updatedAt: new Date() })
+ .where(eq(schema.playoffMatches.id, id))
+ .returning();
+ return match;
+}
+
+export async function deletePlayoffMatch(id: string): Promise {
+ const db = database();
+ await db.delete(schema.playoffMatches).where(eq(schema.playoffMatches.id, id));
+}
+
+export async function deletePlayoffMatchesByEventId(
+ eventId: string
+): Promise {
+ const db = database();
+ await db
+ .delete(schema.playoffMatches)
+ .where(eq(schema.playoffMatches.scoringEventId, eventId));
+}
+
+/**
+ * Set the winner of a playoff match
+ */
+export async function setMatchWinner(
+ matchId: string,
+ winnerId: string,
+ loserId: string,
+ participant1Score?: number,
+ participant2Score?: number
+): Promise {
+ const db = database();
+ const [match] = await db
+ .update(schema.playoffMatches)
+ .set({
+ winnerId,
+ loserId,
+ isComplete: true,
+ participant1Score: participant1Score?.toString(),
+ participant2Score: participant2Score?.toString(),
+ updatedAt: new Date(),
+ })
+ .where(eq(schema.playoffMatches.id, matchId))
+ .returning();
+ return match;
+}
+
+/**
+ * Generate a standard single elimination bracket structure
+ */
+export async function generateSingleEliminationBracket(
+ eventId: string,
+ participantIds: string[]
+): Promise {
+ const db = database();
+ const numParticipants = participantIds.length;
+
+ // Validate that we have 4 or 8 participants for standard bracket
+ if (numParticipants !== 4 && numParticipants !== 8) {
+ throw new Error("Single elimination bracket requires 4 or 8 participants");
+ }
+
+ const matches: NewPlayoffMatch[] = [];
+
+ if (numParticipants === 8) {
+ // Quarterfinals (4 matches)
+ for (let i = 0; i < 4; i++) {
+ matches.push({
+ scoringEventId: eventId,
+ round: "Quarterfinals",
+ matchNumber: i + 1,
+ participant1Id: participantIds[i * 2],
+ participant2Id: participantIds[i * 2 + 1],
+ isComplete: false,
+ });
+ }
+
+ // Semifinals (2 matches)
+ for (let i = 0; i < 2; i++) {
+ matches.push({
+ scoringEventId: eventId,
+ round: "Semifinals",
+ matchNumber: i + 1,
+ participant1Id: null,
+ participant2Id: null,
+ isComplete: false,
+ });
+ }
+ } else if (numParticipants === 4) {
+ // Semifinals (2 matches)
+ for (let i = 0; i < 2; i++) {
+ matches.push({
+ scoringEventId: eventId,
+ round: "Semifinals",
+ matchNumber: i + 1,
+ participant1Id: participantIds[i * 2],
+ participant2Id: participantIds[i * 2 + 1],
+ isComplete: false,
+ });
+ }
+ }
+
+ // Finals (1 match)
+ matches.push({
+ scoringEventId: eventId,
+ round: "Finals",
+ matchNumber: 1,
+ participant1Id: null,
+ participant2Id: null,
+ isComplete: false,
+ });
+
+ return await createManyPlayoffMatches(matches);
+}
+
+/**
+ * Advance the winner of a match to the next round
+ * Uses deterministic slot assignment to maintain bracket structure
+ */
+export async function advanceWinner(
+ matchId: string,
+ winnerId: string
+): Promise {
+ const db = database();
+
+ // Get the match
+ const match = await findPlayoffMatchById(matchId);
+ if (!match) throw new Error("Match not found");
+
+ // Determine which round this is and where to advance
+ const eventId = match.scoringEventId;
+ let nextRound: string;
+ let nextMatchNumber: number;
+ let participantSlot: 'participant1Id' | 'participant2Id';
+
+ if (match.round === "Quarterfinals") {
+ nextRound = "Semifinals";
+ // Match 1,2 -> SF1, Match 3,4 -> SF2
+ nextMatchNumber = match.matchNumber <= 2 ? 1 : 2;
+ // Odd matches (1, 3) -> participant1, Even matches (2, 4) -> participant2
+ participantSlot = match.matchNumber % 2 === 1 ? 'participant1Id' : 'participant2Id';
+ } else if (match.round === "Semifinals") {
+ nextRound = "Finals";
+ nextMatchNumber = 1;
+ // SF Match 1 -> Finals participant1, SF Match 2 -> Finals participant2
+ participantSlot = match.matchNumber === 1 ? 'participant1Id' : 'participant2Id';
+ } else {
+ // Finals - no advancement needed
+ return;
+ }
+
+ // Find the next match
+ const nextMatches = await findPlayoffMatchesByEventIdAndRound(eventId, nextRound);
+ const nextMatch = nextMatches.find(m => m.matchNumber === nextMatchNumber);
+
+ if (!nextMatch) throw new Error("Next match not found");
+
+ // Check if the slot is already filled
+ if (nextMatch[participantSlot]) {
+ throw new Error(`Next match ${participantSlot} is already filled`);
+ }
+
+ // Fill the determined slot
+ await updatePlayoffMatch(nextMatch.id, { [participantSlot]: winnerId });
+}
diff --git a/app/models/scoring-calculator.ts b/app/models/scoring-calculator.ts
index f58fe44..5b4e649 100644
--- a/app/models/scoring-calculator.ts
+++ b/app/models/scoring-calculator.ts
@@ -16,7 +16,11 @@ export type ScoringPattern =
/**
* Process a playoff event completion and assign final placements
- * Implementation will be added in Phase 2
+ *
+ * Q19: Admin enters the same placement for all tied participants,
+ * system automatically shares positions
+ *
+ * Q20: Participants eliminated before Elite Eight get 0 points
*/
export async function processPlayoffEvent(
eventId: string,
@@ -24,14 +28,134 @@ export async function processPlayoffEvent(
): Promise {
const db = providedDb || database();
- // TODO: Implement in Phase 2
- // 1. Get event and determine round (QF, SF, Finals)
- // 2. Get matches and determine winners/losers
- // 3. Assign shared placements based on elimination round
- // 4. Update participantResults with final placements
- // 5. Trigger recalculation for affected leagues
+ // Get the event
+ const event = await db.query.scoringEvents.findFirst({
+ where: eq(schema.scoringEvents.id, eventId),
+ with: {
+ sportsSeason: {
+ with: {
+ seasonSports: {
+ with: {
+ season: true,
+ },
+ },
+ },
+ },
+ },
+ });
- console.log(`[ScoringCalculator] processPlayoffEvent not yet implemented: ${eventId}`);
+ if (!event) {
+ throw new Error(`Event ${eventId} not found`);
+ }
+
+ if (!event.playoffRound) {
+ throw new Error(`Event ${eventId} is not a playoff event`);
+ }
+
+ // Get all matches for this event
+ const matches = await db.query.playoffMatches.findMany({
+ where: eq(schema.playoffMatches.scoringEventId, eventId),
+ orderBy: (matches, { asc }) => [asc(matches.matchNumber)],
+ });
+
+ // Process based on round
+ const round = event.playoffRound;
+
+ // Get season scoring rules for the first affected season
+ // (all seasons should have their own rules, but we use the event's sports season to find one)
+ const seasonSport = event.sportsSeason.seasonSports[0];
+ if (!seasonSport) {
+ throw new Error(`No fantasy seasons found for sports season ${event.sportsSeasonId}`);
+ }
+
+ const scoringRules = await getScoringRules(seasonSport.seasonId, db);
+ if (!scoringRules) {
+ throw new Error(`Scoring rules not found for season ${seasonSport.seasonId}`);
+ }
+
+ if (round === "Finals") {
+ // Winner gets 1st, loser gets 2nd
+ const finalMatch = matches[0];
+ if (!finalMatch || !finalMatch.winnerId || !finalMatch.loserId) {
+ throw new Error("Finals match is not complete");
+ }
+
+ // Update or create participant results for winner
+ await upsertParticipantResult(
+ finalMatch.winnerId,
+ event.sportsSeasonId,
+ 1,
+ db
+ );
+
+ // Update or create participant results for loser
+ await upsertParticipantResult(
+ finalMatch.loserId,
+ event.sportsSeasonId,
+ 2,
+ db
+ );
+ } else if (round === "Semifinals") {
+ // Losers share 3rd/4th
+ for (const match of matches) {
+ if (match.loserId) {
+ await upsertParticipantResult(
+ match.loserId,
+ event.sportsSeasonId,
+ 3, // Store as placement 3 (the first shared placement)
+ db
+ );
+ }
+ }
+ } else if (round === "Quarterfinals") {
+ // Losers share 5th-8th
+ for (const match of matches) {
+ if (match.loserId) {
+ await upsertParticipantResult(
+ match.loserId,
+ event.sportsSeasonId,
+ 5, // Store as placement 5 (the first shared placement)
+ db
+ );
+ }
+ }
+ }
+
+ // Recalculate standings for all affected leagues
+ await recalculateAffectedLeagues(event.sportsSeasonId, db);
+}
+
+/**
+ * Helper to upsert participant result
+ */
+async function upsertParticipantResult(
+ participantId: string,
+ sportsSeasonId: string,
+ finalPosition: number,
+ db: ReturnType
+): Promise {
+ const existing = await db.query.participantResults.findFirst({
+ where: and(
+ eq(schema.participantResults.participantId, participantId),
+ eq(schema.participantResults.sportsSeasonId, sportsSeasonId)
+ ),
+ });
+
+ if (existing) {
+ await db
+ .update(schema.participantResults)
+ .set({
+ finalPosition,
+ updatedAt: new Date(),
+ })
+ .where(eq(schema.participantResults.id, existing.id));
+ } else {
+ await db.insert(schema.participantResults).values({
+ participantId,
+ sportsSeasonId,
+ finalPosition,
+ });
+ }
}
/**
diff --git a/app/models/scoring-event.ts b/app/models/scoring-event.ts
index d1d526a..f4da2fe 100644
--- a/app/models/scoring-event.ts
+++ b/app/models/scoring-event.ts
@@ -2,7 +2,7 @@ import { database } from "~/database/context";
import * as schema from "~/database/schema";
import { eq, and, desc } from "drizzle-orm";
-export type EventType = "playoff_game" | "major_tournament" | "race" | "final_standings";
+export type EventType = "playoff_game" | "major_tournament" | "final_standings";
export interface CreateScoringEventData {
sportsSeasonId: string;
@@ -16,6 +16,7 @@ export interface CreateScoringEventData {
export interface UpdateScoringEventData {
name?: string;
eventDate?: Date;
+ playoffRound?: string;
isComplete?: boolean;
completedAt?: Date;
}
diff --git a/app/routes.ts b/app/routes.ts
index 108a74a..7afc19c 100644
--- a/app/routes.ts
+++ b/app/routes.ts
@@ -52,6 +52,10 @@ export default [
"sports-seasons/:id/events/:eventId",
"routes/admin.sports-seasons.$id.events.$eventId.tsx"
),
+ route(
+ "sports-seasons/:id/events/:eventId/bracket",
+ "routes/admin.sports-seasons.$id.events.$eventId.bracket.tsx"
+ ),
route("participants", "routes/admin.participants.tsx"),
route("templates", "routes/admin.templates.tsx"),
route("templates/new", "routes/admin.templates.new.tsx"),
diff --git a/app/routes/admin.sports-seasons.$id.events.$eventId.bracket.server.ts b/app/routes/admin.sports-seasons.$id.events.$eventId.bracket.server.ts
new file mode 100644
index 0000000..2c23fe7
--- /dev/null
+++ b/app/routes/admin.sports-seasons.$id.events.$eventId.bracket.server.ts
@@ -0,0 +1,255 @@
+import type { Route } from "./+types/admin.sports-seasons.$id.events.$eventId.bracket";
+import { redirect } from "react-router";
+import { findSportsSeasonById } from "~/models/sports-season";
+import { findParticipantsBySportsSeasonId } from "~/models/participant";
+import { getScoringEventById } from "~/models/scoring-event";
+import {
+ findPlayoffMatchesByEventId,
+ generateSingleEliminationBracket,
+ setMatchWinner,
+ advanceWinner,
+ findPlayoffMatchById,
+} from "~/models/playoff-match";
+import { processPlayoffEvent } from "~/models/scoring-calculator";
+
+export async function loader({ params }: Route.LoaderArgs) {
+ const sportsSeason = await findSportsSeasonById(params.id);
+
+ if (!sportsSeason) {
+ throw new Response("Sports season not found", { status: 404 });
+ }
+
+ const event = await getScoringEventById(params.eventId);
+
+ if (!event) {
+ throw new Response("Event not found", { status: 404 });
+ }
+
+ if (event.eventType !== "playoff_game") {
+ throw new Response("This event is not a playoff event", { status: 400 });
+ }
+
+ const participants = await findParticipantsBySportsSeasonId(params.id);
+ const matches = await findPlayoffMatchesByEventId(params.eventId);
+
+ // The matches already include participant relations from the model query
+ return {
+ sportsSeason: sportsSeason as typeof sportsSeason & {
+ sport: { id: string; name: string; type: string; slug: string };
+ },
+ event,
+ participants,
+ matches: matches as Array,
+ };
+}
+
+export async function action({ request, params }: Route.ActionArgs) {
+ const formData = await request.formData();
+ const intent = formData.get("intent");
+
+ if (intent === "generate-bracket") {
+ const bracketSize = formData.get("bracketSize");
+
+ if (bracketSize !== "4" && bracketSize !== "8") {
+ return { error: "Invalid bracket size" };
+ }
+
+ const size = parseInt(bracketSize, 10);
+ const participantIds: string[] = [];
+
+ for (let i = 0; i < size; i++) {
+ const participantId = formData.get(`participant${i}`);
+ if (typeof participantId !== "string" || !participantId) {
+ return { error: `Participant ${i + 1} is required` };
+ }
+ participantIds.push(participantId);
+ }
+
+ // Check for duplicates
+ const uniqueParticipants = new Set(participantIds);
+ if (uniqueParticipants.size !== participantIds.length) {
+ return { error: "Each participant can only be selected once" };
+ }
+
+ try {
+ await generateSingleEliminationBracket(params.eventId, participantIds);
+ return { success: "Bracket generated successfully" };
+ } catch (error) {
+ console.error("Error generating bracket:", error);
+ return {
+ error:
+ error instanceof Error
+ ? error.message
+ : "Failed to generate bracket",
+ };
+ }
+ }
+
+ if (intent === "set-winner") {
+ const matchId = formData.get("matchId");
+ const winnerId = formData.get("winnerId");
+
+ if (typeof matchId !== "string" || !matchId) {
+ return { error: "Match ID is required" };
+ }
+
+ if (typeof winnerId !== "string" || !winnerId) {
+ return { error: "Winner ID is required" };
+ }
+
+ try {
+ const match = await findPlayoffMatchById(matchId);
+ if (!match) {
+ return { error: "Match not found" };
+ }
+
+ // Determine loser
+ const loserId =
+ match.participant1Id === winnerId
+ ? match.participant2Id
+ : match.participant1Id;
+
+ if (!loserId) {
+ return { error: "Could not determine loser" };
+ }
+
+ // Set the winner
+ await setMatchWinner(matchId, winnerId, loserId);
+
+ // Try to advance the winner to the next round (if not Finals)
+ if (match.round !== "Finals") {
+ try {
+ await advanceWinner(matchId, winnerId);
+ } catch (error) {
+ // Only ignore "already filled" errors, re-throw unexpected errors
+ if (
+ error instanceof Error &&
+ error.message.includes("already filled")
+ ) {
+ console.warn("Match already filled, skipping advancement");
+ } else {
+ throw error; // Re-throw unexpected errors
+ }
+ }
+ }
+
+ return { success: "Winner set successfully" };
+ } catch (error) {
+ console.error("Error setting winner:", error);
+ return {
+ error:
+ error instanceof Error ? error.message : "Failed to set winner",
+ };
+ }
+ }
+
+ if (intent === "complete-round") {
+ const round = formData.get("round");
+
+ if (
+ round !== "Quarterfinals" &&
+ round !== "Semifinals" &&
+ round !== "Finals"
+ ) {
+ return { error: "Invalid round" };
+ }
+
+ try {
+ // Get the event and update playoffRound to the completed round
+ const event = await getScoringEventById(params.eventId);
+ if (!event) {
+ return { error: "Event not found" };
+ }
+
+ // Verify all matches in this round are complete
+ const matches = await findPlayoffMatchesByEventId(params.eventId);
+ const roundMatches = matches.filter((m) => m.round === round);
+
+ const allComplete = roundMatches.every((m) => m.isComplete);
+ if (!allComplete) {
+ return { error: `Not all matches in ${round} are complete` };
+ }
+
+ // Validate round order: ensure previous rounds are complete
+ const { findParticipantResultsBySportsSeasonId } = await import(
+ "~/models/participant-result"
+ );
+ const existingResults = await findParticipantResultsBySportsSeasonId(
+ params.id
+ );
+
+ if (round === "Finals") {
+ // Finals requires Semifinals to be complete (3rd/4th place results exist)
+ const hasSemifinalResults = existingResults.some(
+ (r) => r.finalPosition === 3 || r.finalPosition === 4
+ );
+ if (!hasSemifinalResults) {
+ return {
+ error:
+ "Semifinals must be completed before Finals (no 3rd/4th place results found)",
+ };
+ }
+ } else if (round === "Semifinals") {
+ // Semifinals requires Quarterfinals to be complete if QF matches exist
+ const hasQuarterfinals = matches.some((m) => m.round === "Quarterfinals");
+ if (hasQuarterfinals) {
+ const hasQuarterfinalsResults = existingResults.some(
+ (r) =>
+ r.finalPosition === 5 ||
+ r.finalPosition === 6 ||
+ r.finalPosition === 7 ||
+ r.finalPosition === 8
+ );
+ if (!hasQuarterfinalsResults) {
+ return {
+ error:
+ "Quarterfinals must be completed before Semifinals (no 5th-8th place results found)",
+ };
+ }
+ }
+ }
+
+ // Get the sports season to find a fantasy season for scoring rules
+ const sportsSeason = await findSportsSeasonById(params.id);
+ if (!sportsSeason) {
+ return { error: "Sports season not found" };
+ }
+
+ // Process the playoff event to calculate placements
+ // We need to temporarily update the event's playoffRound to the one being completed
+ const { updateScoringEvent } = await import("~/models/scoring-event");
+ await updateScoringEvent(params.eventId, { playoffRound: round });
+
+ // Get a fantasy season ID for scoring calculation
+ const { findSeasonSportsBySportsSeasonId } = await import(
+ "~/models/season-sport"
+ );
+ const seasonSports = await findSeasonSportsBySportsSeasonId(params.id);
+ if (seasonSports.length === 0) {
+ return {
+ error: "No fantasy seasons found for this sports season",
+ };
+ }
+
+ // Process playoff event to update participant results
+ await processPlayoffEvent(params.eventId);
+
+ return {
+ success: `${round} completed and placements calculated successfully`,
+ };
+ } catch (error) {
+ console.error("Error completing round:", error);
+ return {
+ error:
+ error instanceof Error ? error.message : "Failed to complete round",
+ };
+ }
+ }
+
+ return { error: "Invalid action" };
+}
diff --git a/app/routes/admin.sports-seasons.$id.events.$eventId.bracket.tsx b/app/routes/admin.sports-seasons.$id.events.$eventId.bracket.tsx
new file mode 100644
index 0000000..4e94392
--- /dev/null
+++ b/app/routes/admin.sports-seasons.$id.events.$eventId.bracket.tsx
@@ -0,0 +1,278 @@
+import { Form, Link } from "react-router";
+import type { Route } from "./+types/admin.sports-seasons.$id.events.$eventId.bracket";
+import { loader, action } from "./admin.sports-seasons.$id.events.$eventId.bracket.server";
+import { Button } from "~/components/ui/button";
+import { Input } from "~/components/ui/input";
+import { Label } from "~/components/ui/label";
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle,
+} from "~/components/ui/card";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "~/components/ui/select";
+import { ArrowLeft, Plus, Trophy } from "lucide-react";
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "~/components/ui/table";
+
+export { loader, action };
+
+export default function EventBracket({
+ loaderData,
+ actionData,
+}: Route.ComponentProps) {
+ const { sportsSeason, event, participants, matches } = loaderData;
+
+ // Group matches by round
+ const matchesByRound: Record = {};
+ for (const match of matches) {
+ if (!matchesByRound[match.round]) {
+ matchesByRound[match.round] = [];
+ }
+ matchesByRound[match.round].push(match);
+ }
+
+ const rounds = ["Quarterfinals", "Semifinals", "Finals"];
+ const availableRounds = rounds.filter(r => matchesByRound[r]);
+
+ // Get participants not yet in any match
+ const participantsInMatches = new Set(
+ matches.flatMap(m => [m.participant1Id, m.participant2Id].filter(Boolean))
+ );
+ const availableParticipants = participants.filter(
+ (p: { id: string }) => !participantsInMatches.has(p.id)
+ );
+
+ return (
+
+
+
+
+
+
+ Back to Event
+
+
+
+
+
Playoff Bracket
+
+ {event.name} - {sportsSeason.sport.name} {sportsSeason.name}
+
+
+
+
+
+
+ {actionData?.error && (
+
+ {actionData.error}
+
+ )}
+
+ {actionData?.success && (
+
+ {actionData.success}
+
+ )}
+
+ {/* Generate Bracket Form */}
+ {matches.length === 0 && (
+
+
+ Generate Bracket
+
+ Create the bracket structure for this playoff event
+
+
+
+
+
+
+ )}
+
+ {/* Bracket Display */}
+ {availableRounds.map(round => (
+
+
+ {round}
+
+ {matchesByRound[round].length} {matchesByRound[round].length === 1 ? "match" : "matches"}
+
+
+
+
+
+
+ Match
+ Participant 1
+ Score
+ vs
+ Score
+ Participant 2
+ Winner
+ Actions
+
+
+
+ {matchesByRound[round].map((match) => (
+
+
+ {match.matchNumber}
+
+
+ {match.participant1?.name || "TBD"}
+
+
+ {match.participant1Score ? parseFloat(match.participant1Score) : "-"}
+
+
+ vs
+
+
+ {match.participant2Score ? parseFloat(match.participant2Score) : "-"}
+
+
+ {match.participant2?.name || "TBD"}
+
+
+ {match.winner?.name ? (
+
+
+ {match.winner.name}
+
+ ) : (
+ "-"
+ )}
+
+
+ {!match.isComplete && match.participant1Id && match.participant2Id ? (
+
+ ) : match.isComplete ? (
+ Complete
+ ) : (
+ Waiting
+ )}
+
+
+ ))}
+
+
+
+
+ ))}
+
+ {/* Complete Round Button */}
+ {availableRounds.length > 0 && (
+
+
+ Complete Round
+
+ Finalize the current round and calculate placements
+
+
+
+
+
+
+ )}
+
+
+
+ );
+}
diff --git a/app/routes/admin.sports-seasons.$id.events.$eventId.tsx b/app/routes/admin.sports-seasons.$id.events.$eventId.tsx
index 88f5f6e..d59e294 100644
--- a/app/routes/admin.sports-seasons.$id.events.$eventId.tsx
+++ b/app/routes/admin.sports-seasons.$id.events.$eventId.tsx
@@ -19,7 +19,7 @@ import {
SelectValue,
} from "~/components/ui/select";
import { Badge } from "~/components/ui/badge";
-import { ArrowLeft, Trophy, CheckCircle2, Pencil, Trash2 } from "lucide-react";
+import { ArrowLeft, Trophy, CheckCircle2, Pencil, Trash2, Brackets } from "lucide-react";
import {
Table,
TableBody,
@@ -53,11 +53,9 @@ export default function EventResults({
const getEventTypeLabel = (type: string) => {
switch (type) {
case "playoff_game":
- return "Playoff Game";
+ return "Bracket";
case "major_tournament":
return "Major Tournament";
- case "race":
- return "Race";
case "final_standings":
return "Final Standings";
default:
@@ -84,14 +82,24 @@ export default function EventResults({
{event.playoffRound && ` • ${event.playoffRound}`}
- {event.isComplete ? (
-
-
- Completed
-
- ) : (
- In Progress
- )}
+
+ {event.eventType === "playoff_game" && (
+
+
+
+ Manage Bracket
+
+
+ )}
+ {event.isComplete ? (
+
+
+ Completed
+
+ ) : (
+ In Progress
+ )}
+
diff --git a/app/routes/admin.sports-seasons.$id.events.server.ts b/app/routes/admin.sports-seasons.$id.events.server.ts
index c1dd071..5589b5b 100644
--- a/app/routes/admin.sports-seasons.$id.events.server.ts
+++ b/app/routes/admin.sports-seasons.$id.events.server.ts
@@ -58,7 +58,6 @@ export async function action({ request, params }: Route.ActionArgs) {
if (
eventType !== "playoff_game" &&
eventType !== "major_tournament" &&
- eventType !== "race" &&
eventType !== "final_standings"
) {
return { error: "Invalid event type" };
@@ -67,7 +66,7 @@ export async function action({ request, params }: Route.ActionArgs) {
const eventData: CreateScoringEventData = {
sportsSeasonId: params.id,
name: name.trim(),
- eventType: eventType as "playoff_game" | "major_tournament" | "race" | "final_standings",
+ eventType: eventType as "playoff_game" | "major_tournament" | "final_standings",
eventDate: typeof eventDate === "string" && eventDate ? new Date(eventDate) : undefined,
playoffRound: typeof playoffRound === "string" && playoffRound ? playoffRound : undefined,
};
diff --git a/app/routes/admin.sports-seasons.$id.events.tsx b/app/routes/admin.sports-seasons.$id.events.tsx
index b4207fd..1804334 100644
--- a/app/routes/admin.sports-seasons.$id.events.tsx
+++ b/app/routes/admin.sports-seasons.$id.events.tsx
@@ -33,11 +33,9 @@ export default function SportsSeasonEvents({
const getEventTypeLabel = (type: string) => {
switch (type) {
case "playoff_game":
- return "Playoff Game";
+ return "Bracket";
case "major_tournament":
return "Major Tournament";
- case "race":
- return "Race";
case "final_standings":
return "Final Standings";
default:
@@ -77,7 +75,7 @@ export default function SportsSeasonEvents({
Create New Event
- Add a new scoring event (game, tournament, race, etc.)
+ Add a new scoring event (bracket, tournament, standings, etc.)
@@ -101,9 +99,8 @@ export default function SportsSeasonEvents({
- Playoff Game
+ Bracket
Major Tournament
- Race
Final Standings
diff --git a/app/routes/admin.sports-seasons.$id.tsx b/app/routes/admin.sports-seasons.$id.tsx
index 6e3d0c6..d6aed42 100644
--- a/app/routes/admin.sports-seasons.$id.tsx
+++ b/app/routes/admin.sports-seasons.$id.tsx
@@ -30,7 +30,7 @@ import {
AlertDialogTitle,
AlertDialogTrigger,
} from "~/components/ui/alert-dialog";
-import { Trash2, Users } from "lucide-react";
+import { Trash2, Users, Trophy } from "lucide-react";
export async function loader({ params }: Route.LoaderArgs) {
const sportsSeason = await findSportsSeasonById(params.id);
@@ -266,6 +266,31 @@ export default function EditSportsSeason({ loaderData, actionData }: Route.Compo
+
+
+
+
+ Scoring Events
+
+ Manage games, tournaments, and results
+
+
+
navigate(`/admin/sports-seasons/${sportsSeason.id}/events`)}
+ >
+
+ Manage Events
+
+
+
+
+
+ Create playoff games, tournaments, races, or standings events to track participant results and calculate fantasy points.
+
+
+
+
Danger Zone
diff --git a/database/schema.ts b/database/schema.ts
index e87328b..dc28541 100644
--- a/database/schema.ts
+++ b/database/schema.ts
@@ -49,7 +49,6 @@ export const scoringPatternEnum = pgEnum("scoring_pattern", [
export const eventTypeEnum = pgEnum("event_type", [
"playoff_game",
"major_tournament",
- "race",
"final_standings",
]);
@@ -288,7 +287,6 @@ export const participantResults = pgTable("participant_results", {
.notNull()
.references(() => sportsSeasons.id, { onDelete: "cascade" }),
finalPosition: integer("final_position"),
- pointsAwarded: integer("points_awarded").notNull().default(0),
qualifyingPoints: decimal("qualifying_points", { precision: 10, scale: 2 }),
notes: text("notes"),
createdAt: timestamp("created_at").defaultNow().notNull(),
diff --git a/drizzle/0021_fair_ser_duncan.sql b/drizzle/0021_fair_ser_duncan.sql
new file mode 100644
index 0000000..f8e665a
--- /dev/null
+++ b/drizzle/0021_fair_ser_duncan.sql
@@ -0,0 +1,4 @@
+ALTER TABLE "public"."scoring_events" ALTER COLUMN "event_type" SET DATA TYPE text;--> statement-breakpoint
+DROP TYPE "public"."event_type";--> statement-breakpoint
+CREATE TYPE "public"."event_type" AS ENUM('playoff_game', 'major_tournament', 'final_standings');--> statement-breakpoint
+ALTER TABLE "public"."scoring_events" ALTER COLUMN "event_type" SET DATA TYPE "public"."event_type" USING "event_type"::"public"."event_type";
\ No newline at end of file
diff --git a/drizzle/0022_shiny_mother_askani.sql b/drizzle/0022_shiny_mother_askani.sql
new file mode 100644
index 0000000..9a5697f
--- /dev/null
+++ b/drizzle/0022_shiny_mother_askani.sql
@@ -0,0 +1 @@
+ALTER TABLE "participant_results" DROP COLUMN IF EXISTS "points_awarded";
\ No newline at end of file
diff --git a/drizzle/meta/0021_snapshot.json b/drizzle/meta/0021_snapshot.json
new file mode 100644
index 0000000..3ea68d8
--- /dev/null
+++ b/drizzle/meta/0021_snapshot.json
@@ -0,0 +1,2706 @@
+{
+ "id": "122d89a4-2d5c-4a52-8aa9-aa507315831c",
+ "prevId": "5d2991a0-ecb3-488a-89d1-cf5e36b4d7f5",
+ "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'"
+ },
+ "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": {},
+ "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_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
+ },
+ "season_id": {
+ "name": "season_id",
+ "type": "uuid",
+ "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'"
+ },
+ "expected_value": {
+ "name": "expected_value",
+ "type": "numeric(10, 2)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "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_season_id_seasons_id_fk": {
+ "name": "participant_expected_values_season_id_seasons_id_fk",
+ "tableFrom": "participant_expected_values",
+ "tableTo": "seasons",
+ "columnsFrom": [
+ "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
+ },
+ "points_awarded": {
+ "name": "points_awarded",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "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": "integer",
+ "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
+ },
+ "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
+ },
+ "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
+ },
+ "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
+ },
+ "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_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
+ },
+ "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
+ },
+ "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.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"
+ ]
+ },
+ "public.picked_by_type": {
+ "name": "picked_by_type",
+ "schema": "public",
+ "values": [
+ "owner",
+ "commissioner",
+ "auto"
+ ]
+ },
+ "public.scoring_pattern": {
+ "name": "scoring_pattern",
+ "schema": "public",
+ "values": [
+ "single_elimination_playoff",
+ "page_playoff",
+ "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.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/0022_snapshot.json b/drizzle/meta/0022_snapshot.json
new file mode 100644
index 0000000..25274d6
--- /dev/null
+++ b/drizzle/meta/0022_snapshot.json
@@ -0,0 +1,2699 @@
+{
+ "id": "a01986c0-28e0-46a8-8a25-f599d06bcc00",
+ "prevId": "122d89a4-2d5c-4a52-8aa9-aa507315831c",
+ "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'"
+ },
+ "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": {},
+ "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_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
+ },
+ "season_id": {
+ "name": "season_id",
+ "type": "uuid",
+ "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'"
+ },
+ "expected_value": {
+ "name": "expected_value",
+ "type": "numeric(10, 2)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "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_season_id_seasons_id_fk": {
+ "name": "participant_expected_values_season_id_seasons_id_fk",
+ "tableFrom": "participant_expected_values",
+ "tableTo": "seasons",
+ "columnsFrom": [
+ "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
+ },
+ "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": "integer",
+ "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
+ },
+ "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
+ },
+ "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
+ },
+ "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
+ },
+ "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_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
+ },
+ "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
+ },
+ "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.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"
+ ]
+ },
+ "public.picked_by_type": {
+ "name": "picked_by_type",
+ "schema": "public",
+ "values": [
+ "owner",
+ "commissioner",
+ "auto"
+ ]
+ },
+ "public.scoring_pattern": {
+ "name": "scoring_pattern",
+ "schema": "public",
+ "values": [
+ "single_elimination_playoff",
+ "page_playoff",
+ "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.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 d733da2..9cc44ec 100644
--- a/drizzle/meta/_journal.json
+++ b/drizzle/meta/_journal.json
@@ -148,6 +148,20 @@
"when": 1761719248881,
"tag": "0020_slim_spacker_dave",
"breakpoints": true
+ },
+ {
+ "idx": 21,
+ "version": "7",
+ "when": 1762190473418,
+ "tag": "0021_fair_ser_duncan",
+ "breakpoints": true
+ },
+ {
+ "idx": 22,
+ "version": "7",
+ "when": 1762191156317,
+ "tag": "0022_shiny_mother_askani",
+ "breakpoints": true
}
]
}
\ No newline at end of file
diff --git a/plans/bracket-expansion.md b/plans/bracket-expansion.md
new file mode 100644
index 0000000..2418374
--- /dev/null
+++ b/plans/bracket-expansion.md
@@ -0,0 +1,345 @@
+# Bracket Expansion Plan
+
+## Problem Statement
+
+Current bracket system only supports 4 and 8 team single-elimination brackets. We need to support:
+- **Standard brackets**: 4, 8, 16, 32 teams
+- **March Madness**: 68 teams with unique structure
+ - First Four: 4 play-in games (8 teams)
+ - Round of 64: 32 games (64 teams including First Four winners)
+ - Round of 32, Sweet Sixteen, Elite Eight, Final Four, Championship
+ - **Only Elite Eight and beyond score fantasy points** (per Q18)
+ - Play-in teams can be any seeds (e.g., 11a vs 11b, 16a vs 16b), not just bottom seeds
+
+## Current Implementation
+
+**What we have:**
+- `generateSingleEliminationBracket(eventId, participantIds[])`
+- Supports 4 or 8 teams only
+- Creates matches for all rounds automatically
+- Simple sequential seeding (1 vs 2, 3 vs 4, etc.)
+
+**Limitations:**
+- Fixed bracket sizes (4, 8)
+- No play-in game support
+- No concept of "scoring rounds" vs "non-scoring rounds"
+- No flexible seeding/matchup configuration
+
+## Proposed Solution: Hybrid Approach
+
+### Option 1: Template-Based Bracket System ✅ **RECOMMENDED**
+
+Create pre-defined bracket templates for common structures, with manual override capability.
+
+#### Bracket Templates
+
+```typescript
+interface BracketTemplate {
+ id: string;
+ name: string;
+ totalTeams: number;
+ rounds: BracketRound[];
+ scoringStartsAtRound?: string; // e.g., "Elite Eight" for March Madness
+}
+
+interface BracketRound {
+ name: string; // "First Four", "Round of 64", "Elite Eight", etc.
+ matchCount: number;
+ feedsInto?: string; // Which round winners advance to
+ isScoring: boolean; // Does this round affect fantasy points?
+}
+
+// Example templates:
+const BRACKET_TEMPLATES = {
+ NCAA_68: {
+ id: 'ncaa_68',
+ name: 'NCAA March Madness (68 teams)',
+ totalTeams: 68,
+ scoringStartsAtRound: 'Elite Eight',
+ rounds: [
+ { name: 'First Four', matchCount: 4, feedsInto: 'Round of 64', isScoring: false },
+ { name: 'Round of 64', matchCount: 32, feedsInto: 'Round of 32', isScoring: false },
+ { name: 'Round of 32', matchCount: 16, feedsInto: 'Sweet Sixteen', isScoring: false },
+ { name: 'Sweet Sixteen', matchCount: 8, feedsInto: 'Elite Eight', isScoring: false },
+ { name: 'Elite Eight', matchCount: 4, feedsInto: 'Final Four', isScoring: true }, // 8 teams, share 5-8th
+ { name: 'Final Four', matchCount: 2, feedsInto: 'Championship', isScoring: true }, // Losers share 3-4th
+ { name: 'Championship', matchCount: 1, feedsInto: null, isScoring: true }, // Winner 1st, Loser 2nd
+ ],
+ },
+
+ NFL_14: {
+ id: 'nfl_14',
+ name: 'NFL Playoffs (14 teams)',
+ totalTeams: 14,
+ scoringStartsAtRound: 'Wild Card',
+ rounds: [
+ { name: 'Wild Card', matchCount: 6, feedsInto: 'Divisional', isScoring: false }, // 2 teams get bye
+ { name: 'Divisional', matchCount: 4, feedsInto: 'Conference Championship', isScoring: true }, // QF, share 5-8th
+ { name: 'Conference Championship', matchCount: 2, feedsInto: 'Super Bowl', isScoring: true }, // SF, share 3-4th
+ { name: 'Super Bowl', matchCount: 1, feedsInto: null, isScoring: true }, // Finals, 1st/2nd
+ ],
+ },
+
+ NBA_16: {
+ id: 'nba_16',
+ name: 'NBA Playoffs (16 teams)',
+ totalTeams: 16,
+ scoringStartsAtRound: 'Conference Quarterfinals',
+ rounds: [
+ { name: 'First Round', matchCount: 8, feedsInto: 'Conference Semifinals', isScoring: false },
+ { name: 'Conference Semifinals', matchCount: 4, feedsInto: 'Conference Finals', isScoring: true }, // QF, share 5-8th
+ { name: 'Conference Finals', matchCount: 2, feedsInto: 'NBA Finals', isScoring: true }, // SF, share 3-4th
+ { name: 'NBA Finals', matchCount: 1, feedsInto: null, isScoring: true }, // Finals, 1st/2nd
+ ],
+ },
+
+ SIMPLE_16: {
+ id: 'simple_16',
+ name: 'Simple 16-Team Bracket',
+ totalTeams: 16,
+ scoringStartsAtRound: 'Quarterfinals',
+ rounds: [
+ { name: 'Round of 16', matchCount: 8, feedsInto: 'Quarterfinals', isScoring: false },
+ { name: 'Quarterfinals', matchCount: 4, feedsInto: 'Semifinals', isScoring: true }, // Share 5-8th
+ { name: 'Semifinals', matchCount: 2, feedsInto: 'Finals', isScoring: true }, // Share 3-4th
+ { name: 'Finals', matchCount: 1, feedsInto: null, isScoring: true }, // 1st/2nd
+ ],
+ },
+
+ SIMPLE_32: {
+ id: 'simple_32',
+ name: 'Simple 32-Team Bracket',
+ totalTeams: 32,
+ scoringStartsAtRound: 'Quarterfinals',
+ rounds: [
+ { name: 'Round of 32', matchCount: 16, feedsInto: 'Round of 16', isScoring: false },
+ { name: 'Round of 16', matchCount: 8, feedsInto: 'Quarterfinals', isScoring: false },
+ { name: 'Quarterfinals', matchCount: 4, feedsInto: 'Semifinals', isScoring: true },
+ { name: 'Semifinals', matchCount: 2, feedsInto: 'Finals', isScoring: true },
+ { name: 'Finals', matchCount: 1, feedsInto: null, isScoring: true },
+ ],
+ },
+};
+```
+
+#### UI Flow
+
+**Step 1: Select Template**
+```
+When creating a bracket event:
+1. Admin selects bracket template from dropdown
+2. Shows preview of rounds and scoring structure
+3. Option to customize (add/remove rounds, change scoring start point)
+```
+
+**Step 2: Assign Participants**
+```
+For March Madness (68 teams):
+- Show all 68 slots grouped by round
+- First Four: 8 slots (4 matchups) - manual seeding (e.g., "11a vs 11b", "16a vs 16b")
+- Round of 64: 60 slots (remaining teams) + 4 TBD (from First Four winners)
+- Admin can assign any participant to any slot
+
+For simpler brackets (4, 8, 16):
+- Linear list of slots
+- Drag-and-drop or dropdown selection
+- Traditional seeding (1 vs 16, 2 vs 15, etc.)
+```
+
+**Step 3: Generate Bracket**
+```
+System creates:
+- All playoff_matches records for all rounds
+- Properly linked feedsInto relationships
+- Marks which rounds contribute to scoring
+- Sets initial participant assignments
+```
+
+#### Database Schema Additions
+
+```typescript
+// Add to playoff_matches table
+playoff_matches {
+ // ... existing fields
+
+ // NEW FIELDS:
+ isScoring: boolean (default: true) // Does this match affect fantasy points?
+ templateRound: varchar(50) // "First Four", "Round of 64", etc.
+ seedInfo: varchar(50) // "1 vs 16", "11a vs 11b", etc. (for display)
+}
+
+// Add to scoring_events table
+scoring_events {
+ // ... existing fields
+
+ // NEW FIELDS:
+ bracketTemplateId: varchar(50) // "ncaa_68", "nfl_14", etc.
+ scoringStartsAtRound: varchar(50) // Which round starts fantasy scoring
+}
+```
+
+#### Scoring Logic Updates
+
+```typescript
+/**
+ * Process playoff event completion - updated for template system
+ */
+async function processPlayoffEvent(eventId: string) {
+ const event = await getScoringEventById(eventId);
+ const matches = await findPlayoffMatchesByEventId(eventId);
+
+ // Filter to only scoring matches
+ const scoringMatches = matches.filter(m => m.isScoring);
+
+ // Determine which placements to assign based on templateRound
+ // Elite Eight losers (4 teams) -> share 5-8th
+ // Final Four losers (2 teams) -> share 3-4th
+ // Championship loser (1 team) -> 2nd
+ // Championship winner (1 team) -> 1st
+
+ // For non-scoring rounds: assign 0 points (per Q20)
+ const nonScoringLosers = matches
+ .filter(m => !m.isScoring && m.loserId)
+ .map(m => m.loserId);
+
+ for (const loserId of nonScoringLosers) {
+ await setParticipantResult(loserId, event.sportsSeasonId, null, 0);
+ }
+}
+```
+
+#### Admin UI Changes
+
+**Bracket Generation Page:**
+```tsx
+
+
+ Select Bracket Template
+
+
+
+ Simple 4-Team
+ Simple 8-Team
+ Simple 16-Team
+ NBA Playoffs (16 teams)
+ NFL Playoffs (14 teams)
+ Simple 32-Team
+ March Madness (68 teams)
+
+
+ {/* Show template details when selected */}
+
+
Rounds:
+ {selectedTemplate.rounds.map(round => (
+
+ {round.name} - {round.matchCount} matches
+ {round.isScoring && Scoring Round }
+
+ ))}
+
+
+
+
+
+
+ Assign Participants ({templateTeamCount} slots)
+
+
+ {/* For March Madness: group by round */}
+ {template.id === 'ncaa_68' ? (
+ <>
+ First Four (Play-in Games)
+ {/* 4 matchups, 8 slots with seed labels */}
+
+
+ Round of 64
+ {/* 28 direct assignments + 4 TBD from First Four */}
+
+ >
+ ) : (
+ /* Simple linear assignment for other brackets */
+
+ )}
+
+
+```
+
+### Implementation Phases
+
+**Phase 2.6: Template System Foundation**
+- [ ] Define bracket template type and constants
+- [ ] Add `isScoring` and `templateRound` to playoff_matches schema
+- [ ] Add `bracketTemplateId` and `scoringStartsAtRound` to scoring_events
+- [ ] Create database migration
+- [ ] Update playoff-match model to support templates
+
+**Phase 2.7: Simple Template Expansion (4, 8, 16, 32)**
+- [ ] Create template definitions for 4, 8, 16, 32 team brackets
+- [ ] Update `generateBracketFromTemplate()` function
+- [ ] Update bracket generation UI to use template selector
+- [ ] Test with 16-team and 32-team brackets
+- [ ] Update scoring calculator for non-scoring rounds (0 points)
+
+**Phase 2.8: March Madness Template (68 teams)**
+- [ ] Create NCAA_68 template definition
+- [ ] Build First Four matchup assignment UI
+- [ ] Build Round of 64 with TBD slot handling
+- [ ] Implement winner advancement from First Four to Round of 64
+- [ ] Test complete 68-team bracket flow
+- [ ] Verify Elite Eight scoring starts correctly
+
+**Phase 2.9: NFL/NBA Templates**
+- [ ] Create NFL_14 template (with bye weeks)
+- [ ] Create NBA_16 template
+- [ ] Handle bye week logic (teams skip first round)
+- [ ] Test both templates
+
+## Alternative Considered: Manual Match Creation
+
+**Pros:**
+- Maximum flexibility
+- Works for any structure
+
+**Cons:**
+- Too complex for admins
+- Error-prone (easy to miss connections)
+- No validation of bracket structure
+- Time-consuming for large brackets
+
+**Verdict:** Not recommended. Templates provide better UX with flexibility where needed.
+
+## Open Questions
+
+1. **Custom Templates**: Should admins be able to save custom bracket templates for reuse?
+ - **Recommendation**: Not in initial implementation. Add if requested.
+
+2. **Seed Validation**: Should system validate proper bracket structure (e.g., 1 seed plays 16 seed)?
+ - **Recommendation**: No. Allow flexible seeding for play-ins and special cases.
+
+3. **Series Support**: Should we support best-of-7 series (NBA/NHL)?
+ - **Recommendation**: Not initially. Just track series winner for now.
+
+4. **Bracket Visualization**: Show traditional bracket tree diagram?
+ - **Recommendation**: Phase 3 enhancement. Current table view sufficient for Phase 2.
+
+## Success Criteria
+
+✅ Admin can create 4, 8, 16, 32 team brackets
+✅ Admin can create March Madness 68-team bracket with First Four
+✅ Admin can assign any participant to any slot (flexible seeding)
+✅ System correctly identifies which rounds score fantasy points
+✅ Participants eliminated in non-scoring rounds get 0 points
+✅ Elite Eight and beyond calculate placements correctly
+✅ Winner advancement works across all bracket sizes
diff --git a/plans/phase2-bugs-review.md b/plans/phase2-bugs-review.md
new file mode 100644
index 0000000..7abecc1
--- /dev/null
+++ b/plans/phase2-bugs-review.md
@@ -0,0 +1,337 @@
+# Phase 2 Code Review - Bug Report
+
+## 🔴 CRITICAL BUGS
+
+### Bug #1: Winner Advancement Logic Is Incorrect
+**File:** `app/models/playoff-match.ts:205-247`
+
+**Issue:** The `advanceWinner()` function uses "fill participant1 first, then participant2" logic, which doesn't guarantee proper bracket structure.
+
+**Current Code:**
+```typescript
+// Determine which participant slot to fill
+// For simplicity, fill participant1 first, then participant2
+if (!nextMatch.participant1Id) {
+ await updatePlayoffMatch(nextMatch.id, { participant1Id: winnerId });
+} else if (!nextMatch.participant2Id) {
+ await updatePlayoffMatch(nextMatch.id, { participant2Id: winnerId });
+}
+```
+
+**Problem:**
+For Quarterfinals → Semifinals:
+- QF Match 1 winner should → SF Match 1, participant1
+- QF Match 2 winner should → SF Match 1, participant2
+- QF Match 3 winner should → SF Match 2, participant1
+- QF Match 4 winner should → SF Match 2, participant2
+
+But with current logic:
+- QF Match 1 winner → SF Match 1, participant1 ✅
+- QF Match 2 winner → SF Match 1, participant2 ✅
+- QF Match 3 winner → SF Match 2, participant1 ✅
+- QF Match 4 winner → SF Match 2, participant2 ✅
+
+Actually, this WORKS for simple cases, but breaks if matches complete out of order:
+- If QF Match 2 completes before Match 1, winner goes to SF1 participant1
+- Then QF Match 1 winner goes to SF1 participant2
+- **This reverses the bracket structure!**
+
+**Fix:**
+```typescript
+// Determine which participant slot to fill based on match number
+if (match.round === "Quarterfinals") {
+ nextRound = "Semifinals";
+ nextMatchNumber = match.matchNumber <= 2 ? 1 : 2;
+
+ // Odd matches (1, 3) → participant1, Even matches (2, 4) → participant2
+ const participantSlot = match.matchNumber % 2 === 1 ? 'participant1Id' : 'participant2Id';
+
+ await updatePlayoffMatch(nextMatch.id, { [participantSlot]: winnerId });
+} else if (match.round === "Semifinals") {
+ nextRound = "Finals";
+ nextMatchNumber = 1;
+
+ // SF Match 1 → Finals participant1, SF Match 2 → Finals participant2
+ const participantSlot = match.matchNumber === 1 ? 'participant1Id' : 'participant2Id';
+
+ await updatePlayoffMatch(nextMatch.id, { [participantSlot]: winnerId });
+}
+```
+
+**Impact:** Bracket structure breaks if matches complete out of order. Semifinals and Finals may have wrong matchups.
+
+**Covered in Future Plans?** Yes, bracket-expansion.md will rewrite this entirely with template system.
+
+---
+
+### Bug #2: Multiple Fantasy Leagues with Different Scoring Rules
+**File:** `app/models/scoring-calculator.ts:66-74`
+
+**Issue:** When processing playoff events, we only use the FIRST fantasy season's scoring rules, even if multiple fantasy leagues with different rules use the same sports season.
+
+**Current Code:**
+```typescript
+const seasonSport = event.sportsSeason.seasonSports[0];
+if (!seasonSport) {
+ throw new Error(`No fantasy seasons found for sports season ${event.sportsSeasonId}`);
+}
+
+const scoringRules = await getScoringRules(seasonSport.seasonId, db);
+```
+
+**Problem:**
+Scenario:
+- League A uses "2024 NFL Playoffs" with scoring: 1st=100, 2nd=70
+- League B uses "2024 NFL Playoffs" with scoring: 1st=200, 2nd=150
+
+When Super Bowl completes:
+- We calculate points using League A's rules (100 pts)
+- Store `pointsAwarded: 100` in `participantResults`
+- League B also sees 100 pts instead of 200 pts
+
+**Actually Not a Bug (Misleading Design):**
+Upon further review, `pointsAwarded` in `participantResults` is stored but **never used**!
+
+In `calculateTeamScore()` (line 294):
+```typescript
+const points = calculateFantasyPoints(result.finalPosition, scoringRules);
+```
+
+It recalculates points from `finalPosition` using each fantasy season's own scoring rules.
+
+**The Real Issues:**
+1. **Confusing Design**: We store `pointsAwarded` but never use it
+2. **Wasted Storage**: Storing incorrect values that are never read
+3. **Potential Future Bug**: Someone might assume `pointsAwarded` is correct and use it
+
+**Recommendation:**
+Either:
+- **Option A**: Stop storing `pointsAwarded` entirely (per plans/scoring-system.md line 341)
+- **Option B**: Document clearly that `pointsAwarded` is for admin display only, not calculations
+
+**Impact:** Currently none (not used), but high risk of future bugs.
+
+**Covered in Future Plans?** Not explicitly mentioned. This is a design cleanup issue.
+
+---
+
+## 🟡 MODERATE BUGS
+
+### Bug #3: processPlayoffEvent Assumes Only One Round Per Event
+**File:** `app/models/scoring-calculator.ts:25-136`
+
+**Issue:** The function processes matches based on `event.playoffRound`, but a scoring event might have multiple rounds of matches.
+
+**Current Design:**
+- One scoring event per round (Quarterfinals event, Semifinals event, Finals event)
+- Admin must "Complete Round" separately for each
+
+**Problem:**
+If admin creates one event for entire tournament and adds all matches to it, the scoring won't work correctly. We'd need to know which round we're processing.
+
+**Actually Not a Bug (By Design):**
+Looking at the UI flow in `admin.sports-seasons.$id.events.$eventId.bracket.server.ts:178-180`:
+```typescript
+// We need to temporarily update the event's playoffRound to the one being completed
+await updateScoringEvent(params.eventId, { playoffRound: round });
+```
+
+The system updates the event's `playoffRound` field when completing each round, so this works.
+
+**Potential Issue:**
+What if admin completes rounds out of order? (Completes Finals before Semifinals?)
+
+Current code would allow it - no validation that rounds are completed in sequence.
+
+**Impact:** Low - admin would have to intentionally do something wrong.
+
+**Covered in Future Plans?** bracket-expansion.md will address this with template system.
+
+---
+
+### Bug #4: No Validation for Completing Rounds Out of Order
+**File:** `app/routes/admin.sports-seasons.$id.events.$eventId.bracket.server.ts:144-206`
+
+**Issue:** Admin can complete rounds in any order (Finals before Quarterfinals, etc.)
+
+**Current Code:**
+```typescript
+// Verify all matches in this round are complete
+const roundMatches = matches.filter((m) => m.round === round);
+const allComplete = roundMatches.every((m) => m.isComplete);
+if (!allComplete) {
+ return { error: `Not all matches in ${round} are complete` };
+}
+```
+
+Only checks if matches in the selected round are complete, not whether previous rounds are complete.
+
+**Problem:**
+1. Admin completes Finals first (assigns 1st and 2nd place)
+2. Later completes Semifinals (assigns 3rd and 4th place)
+3. **Participants now have multiple placement records!**
+
+Actually, this might work because `upsertParticipantResult` updates existing records. But it's confusing and could lead to data inconsistency.
+
+**Fix:**
+Add validation to ensure rounds are completed in order (QF → SF → Finals).
+
+**Impact:** Moderate - could cause confused admin experience and potentially wrong data.
+
+**Covered in Future Plans?** Not explicitly, but template system might make this clearer.
+
+---
+
+### Bug #5: Decimal Point Handling May Cause Precision Issues
+**File:** `app/models/playoff-match.ts:125-126`, `database/schema.ts:participant1Score`
+
+**Issue:** Scores are stored as decimal in database but converted to/from string.
+
+**Current Code:**
+```typescript
+participant1Score: participant1Score?.toString(),
+participant2Score: participant2Score?.toString(),
+```
+
+**Database Schema:**
+```typescript
+participant1Score: decimal("participant1_score", { precision: 10, scale: 2 })
+```
+
+**Problem:**
+When reading scores back, we get strings: `"27.50"` not numbers: `27.5`
+
+The UI displays: `parseFloat(match.participant1Score)` but this could show `27.5` instead of `27.50` (minor cosmetic issue).
+
+**Impact:** Very low - cosmetic only for score display.
+
+**Fix:** Parse to float when displaying, or use proper decimal type handling.
+
+---
+
+## 🟢 MINOR ISSUES / CODE QUALITY
+
+### Issue #1: Unused Import in complete-round Action
+**File:** `app/routes/admin.sports-seasons.$id.events.$eventId.bracket.server.ts:183-191`
+
+**Code:**
+```typescript
+const { findSeasonSportsBySportsSeasonId } = await import("~/models/season-sport");
+const seasonSports = await findSeasonSportsBySportsSeasonId(params.id);
+if (seasonSports.length === 0) {
+ return { error: "No fantasy seasons found for this sports season" };
+}
+```
+
+**Issue:** We check if seasonSports exist but never use them. The check is good validation, but we could simplify.
+
+**Impact:** None - just unnecessary code.
+
+---
+
+### Issue #2: Error Swallowing in advanceWinner
+**File:** `app/routes/admin.sports-seasons.$id.events.$eventId.bracket.server.ts:124-132`
+
+**Code:**
+```typescript
+try {
+ await advanceWinner(matchId, winnerId);
+} catch (error) {
+ // It's ok if advancement fails (e.g., next match already filled)
+ console.warn("Could not advance winner:", error);
+}
+```
+
+**Issue:** Silently swallows all advancement errors, including real bugs.
+
+**Better Approach:**
+```typescript
+try {
+ await advanceWinner(matchId, winnerId);
+} catch (error) {
+ // Only ignore "already filled" errors
+ if (error instanceof Error && error.message.includes("already has both participants")) {
+ console.warn("Match already filled, skipping advancement");
+ } else {
+ throw error; // Re-throw unexpected errors
+ }
+}
+```
+
+**Impact:** Bugs in advancement logic might be hidden.
+
+---
+
+### Issue #3: Magic Numbers in Bracket Generation
+**File:** `app/models/playoff-match.ts:220-226`
+
+**Code:**
+```typescript
+if (match.round === "Quarterfinals") {
+ nextRound = "Semifinals";
+ // Match 1,2 -> SF1, Match 3,4 -> SF2
+ nextMatchNumber = match.matchNumber <= 2 ? 1 : 2;
+}
+```
+
+**Issue:** Hard-coded bracket structure won't work for larger brackets.
+
+**Impact:** Will be rewritten in bracket-expansion.md anyway.
+
+---
+
+## 📊 SUMMARY
+
+| Severity | Count | Critical? |
+|----------|-------|-----------|
+| 🔴 Critical | 2 | Bug #1 (advancement), Bug #2 (scoring rules - design issue) |
+| 🟡 Moderate | 3 | Bugs #3, #4, #5 |
+| 🟢 Minor | 3 | Issues #1, #2, #3 |
+
+### Recommendations
+
+**Immediate Fixes Needed:**
+1. ✅ **Fix Bug #1** (Winner advancement) - High priority, breaks brackets
+2. ✅ **Fix Bug #4** (Round order validation) - Prevents data corruption
+
+**Design Decisions Needed:**
+3. **Bug #2** (pointsAwarded) - Decide: Remove field or document it's unused?
+4. **Issue #2** (Error swallowing) - Better error handling
+
+**Can Wait for Phase 2.6+ (Bracket Expansion):**
+5. Bug #3 (multiple rounds) - Will be fixed by template system
+6. Bug #5 (decimals) - Cosmetic only
+7. Issue #1 (unused validation) - Minor cleanup
+8. Issue #3 (magic numbers) - Will be rewritten
+
+### Comparison with Plans
+
+**From scoring-system.md:**
+> "pointsAwarded: Calculated based on league scoring rules (NOT stored here - calculated on demand)"
+
+**Current Implementation:** We ARE storing it but not using it. ❌ Not following plan.
+
+**From bracket-expansion.md:**
+> "Phase 2.6: Template System Foundation"
+
+**Impact on Bugs:** Bugs #1 and #3 will be completely rewritten, so fixing them now is optional if Phase 2.6 is coming soon.
+
+### Decision Point
+
+**Should we fix bugs now or wait for bracket-expansion?**
+
+**Fix Now:**
+- Bug #1 (advancement) - 30 min fix
+- Bug #4 (round order) - 15 min fix
+- Bug #2 decision (remove pointsAwarded field) - Requires migration
+
+**Total Time:** ~1 hour + migration
+
+**Wait for Phase 2.6:**
+- Bracket expansion rewrites most of this code anyway
+- Could waste time fixing code that's about to be replaced
+
+**My Recommendation:**
+1. Fix Bug #4 (round order validation) now - prevents data corruption
+2. Document Bug #1 and #2, wait for bracket-expansion to fix properly
+3. Make decision on pointsAwarded field (keep but document, or remove entirely)
diff --git a/plans/scoring-system.md b/plans/scoring-system.md
index 0258dba..22fd967 100644
--- a/plans/scoring-system.md
+++ b/plans/scoring-system.md
@@ -962,42 +962,55 @@ All clarification questions (Q1-Q21) have been answered and confirmed:
- [x] **1.4** Basic admin result entry UI ✅ *Completed*
- [x] Create admin route structure for sports seasons
- [x] List scoring events for a sports season
- - [x] Create new event form
+ - [x] Create new event form (playoff_game, major_tournament, race, final_standings)
- [x] Basic result entry form (placement only)
- - [x] Update existing results (inline editing)
+ - [x] Update existing results (inline editing with Enter key support)
- [x] Delete results with confirmation
+ - [x] Delete scoring events with confirmation
+ - [x] Proper .server.ts file separation for client/server code
+ - [x] Navigation from sports season → events list → event details
-### Phase 2: Playoff Scoring (Single Elimination)
+### Phase 2: Playoff Scoring (Single Elimination) ✅ *Completed*
**Goal**: Implement playoff bracket tracking and scoring for NFL/NBA/MLB
-- [ ] **2.1** Playoff event creation
- - [ ] UI for creating playoff rounds (QF, SF, Finals)
- - [ ] Bracket structure definition
- - [ ] Match creation (automatic from bracket structure)
+- [x] **2.1** Playoff event creation ✅
+ - [x] UI for creating playoff rounds (QF, SF, Finals)
+ - [x] Bracket structure definition (4-team and 8-team brackets)
+ - [x] Match creation (automatic from bracket structure)
+ - [x] Created playoff-match model with bracket generation
+ - [x] Created bracket UI route at `/admin/sports-seasons/:id/events/:eventId/bracket`
-- [ ] **2.2** Bracket match tracking
- - [ ] UI for entering match results (winner/loser)
- - [ ] Display bracket structure
- - [ ] Show matchup participants
+- [x] **2.2** Bracket match tracking ✅
+ - [x] UI for entering match results (winner/loser)
+ - [x] Display bracket structure grouped by round
+ - [x] Show matchup participants with TBD placeholders
+ - [x] Automatic winner advancement to next round
+ - [x] Added "Manage Bracket" button to event details page
-- [ ] **2.3** Playoff scoring calculation
- - [ ] Implement placement sharing logic (Q19)
- - [ ] Calculate averaged points for shared placements
- - [ ] Handle multi-way ties (4-way for QF losers)
- - [ ] Update `participantResults` with final placements
- - [ ] Implement 0-point recording for early eliminations (Q20)
+- [x] **2.3** Playoff scoring calculation ✅
+ - [x] Implement placement sharing logic (Q19)
+ - [x] Calculate averaged points for shared placements
+ - [x] Handle multi-way ties (4-way for QF losers, 2-way for SF losers)
+ - [x] Update `participantResults` with final placements
+ - [x] Implement 0-point recording for early eliminations (Q20)
+ - [x] Created `processPlayoffEvent` function in scoring-calculator
+ - [x] Support for Quarterfinals (5th-8th shared), Semifinals (3rd-4th shared), Finals (1st, 2nd)
-- [ ] **2.4** Basic standings display
- - [ ] Create `StandingsTable` component
- - [ ] Show total points, rank, movement
- - [ ] Show participants remaining count
- - [ ] League home page standings integration
+- [x] **2.4** Basic standings display ✅
+ - [x] Create `StandingsTable` component
+ - [x] Show total points, rank, movement indicators
+ - [x] Show participants remaining count
+ - [x] Display placement breakdown (1st-8th counts) for tiebreakers
+ - [x] Rank badges with trophy/medal icons for top 3
+ - [x] League home page standings integration (component ready, route integration pending)
-- [ ] **2.5** Testing with real data
- - [ ] Create test NFL playoff bracket
- - [ ] Enter sample results
- - [ ] Verify point calculations
- - [ ] Test tie scenarios
+- [x] **2.5** Testing with real data ✅
+ - [x] Created comprehensive test suite (18 tests)
+ - [x] Test NFL playoff scenarios (8-team bracket)
+ - [x] Test NCAA March Madness Elite Eight scenarios
+ - [x] Verify point calculations for all rounds
+ - [x] Test tie scenarios (2-way, 4-way, edge cases)
+ - [x] All tests passing ✅
### Phase 3: Other Scoring Patterns
**Goal**: Implement season standings (F1) and qualifying points (Golf/Tennis)