diff --git a/app/lib/__tests__/normalize-team-name.test.ts b/app/lib/__tests__/normalize-team-name.test.ts
index cf1c8d6..881f7d6 100644
--- a/app/lib/__tests__/normalize-team-name.test.ts
+++ b/app/lib/__tests__/normalize-team-name.test.ts
@@ -14,6 +14,11 @@ describe("normalizeTeamName", () => {
const name = "oklahoma city thunder";
expect(normalizeTeamName(name)).toBe(name);
});
+
+ it("folds accents so diacritics don't block a match", () => {
+ expect(normalizeTeamName("Stéfanos Tsitsipás")).toBe("stefanos tsitsipas");
+ expect(normalizeTeamName("Félix Auger-Aliassime")).toBe("felix auger-aliassime");
+ });
});
describe("findMatchingTeamName", () => {
@@ -38,6 +43,12 @@ describe("findMatchingTeamName", () => {
expect(findMatchingTeamName("Oklahoma City", participants)).toBe("Oklahoma City Thunder");
});
+ it("matches across accents (API has them, our list doesn't)", () => {
+ expect(findMatchingTeamName("Stéfanos Tsitsipás", ["Stefanos Tsitsipas"])).toBe(
+ "Stefanos Tsitsipas",
+ );
+ });
+
it("returns null for unmatched name", () => {
expect(findMatchingTeamName("Phoenix Suns", participants)).toBeNull();
});
diff --git a/app/lib/normalize-team-name.ts b/app/lib/normalize-team-name.ts
index 39d17f8..53a9547 100644
--- a/app/lib/normalize-team-name.ts
+++ b/app/lib/normalize-team-name.ts
@@ -1,9 +1,15 @@
/**
- * Normalize a team name for fuzzy matching across data sources.
- * Lowercases, trims, and collapses whitespace.
+ * Normalize a team/participant name for fuzzy matching across data sources.
+ * Folds accents (so "Stéfanos Tsitsipás" matches a plain "Stefanos Tsitsipas"),
+ * lowercases, trims, and collapses whitespace.
*/
export function normalizeTeamName(name: string): string {
- return name.toLowerCase().trim().replace(/\s+/g, " ");
+ return name
+ .normalize("NFKD")
+ .replace(/[̀-ͯ]/g, "") // strip combining diacritical marks
+ .toLowerCase()
+ .trim()
+ .replace(/\s+/g, " ");
}
/**
diff --git a/app/models/__tests__/qualifying-bracket-scoring.test.ts b/app/models/__tests__/qualifying-bracket-scoring.test.ts
index 7e479a1..abb2935 100644
--- a/app/models/__tests__/qualifying-bracket-scoring.test.ts
+++ b/app/models/__tests__/qualifying-bracket-scoring.test.ts
@@ -146,6 +146,57 @@ describe("deriveBracketQualifyingStates (simple_8)", () => {
});
});
+// ── tennis_128: scoring starts deep (Round of 16), not at round 1 ─────────────
+
+describe("deriveBracketQualifyingStates (tennis_128 — deep scoring start)", () => {
+ const TENNIS = BRACKET_TEMPLATES["tennis_128"];
+ const tStates = (matches: BracketMatchInput[]) =>
+ deriveBracketQualifyingStates(matches, TENNIS.rounds, (round) =>
+ getRoundConfig(round, "tennis_128"),
+ );
+
+ it("awards NO QP to a player who lost before the Round of 16", () => {
+ const s = tStates([
+ played("Round of 128", "winner", "earlyLoser"),
+ ]);
+ expect(s.has("earlyLoser")).toBe(false); // 0 QP — didn't reach scoring
+ });
+
+ it("awards NO QP just for winning a pre-scoring round (still short of R16)", () => {
+ // Won R128, next match (R64) not played → not yet in the Round of 16.
+ const s = tStates([played("Round of 128", "p", "x")]);
+ expect(s.has("p")).toBe(false);
+ });
+
+ it("gives NO entry floor to undrawn/unplayed players (the 9th-place bug)", () => {
+ const s = tStates([
+ pending("Round of 128", "a", "b"),
+ pending("Round of 128", "c", "d"),
+ ]);
+ expect(s.size).toBe(0); // nobody has earned anything yet
+ });
+
+ it("floors a player who reached the Round of 16 (won R32) at T9–16", () => {
+ // a wins R128 → R64 → R32, so a is now IN the Round of 16 (not yet played).
+ const s = tStates([
+ played("Round of 128", "a", "x1"),
+ played("Round of 64", "a", "x2"),
+ played("Round of 32", "a", "x3"),
+ ]);
+ expect(s.get("a")).toEqual({ placement: 9, tieCount: 8 });
+ // The players a beat in non-scoring rounds earn nothing.
+ expect(s.has("x1")).toBe(false);
+ });
+
+ it("locks an actual Round of 16 loser at T9–16 and floors the winner at T5–8", () => {
+ const s = tStates([
+ played("Round of 16", "r16winner", "r16loser"),
+ ]);
+ expect(s.get("r16loser")).toEqual({ placement: 9, tieCount: 8 });
+ expect(s.get("r16winner")).toEqual({ placement: 5, tieCount: 4 }); // QF floor
+ });
+});
+
// ── QP values (the worked example) ────────────────────────────────────────────
describe("guaranteed-minimum QP per outcome (default config)", () => {
diff --git a/app/models/playoff-match.ts b/app/models/playoff-match.ts
index fe436ea..81a8057 100644
--- a/app/models/playoff-match.ts
+++ b/app/models/playoff-match.ts
@@ -142,6 +142,96 @@ export async function setMatchWinner(
return match;
}
+/** A draw match with participants/winner already resolved to ids. */
+export interface ResolvedDrawMatch {
+ externalMatchId: string;
+ round: string;
+ matchNumber: number;
+ participant1Id: string | null;
+ participant2Id: string | null;
+ winnerId: string | null;
+ loserId: string | null;
+ isScoring: boolean;
+}
+
+/**
+ * Populate (or advance) a bracket from a fully-resolved external draw, keyed on
+ * `externalMatchId` so repeated syncs update in place rather than duplicating.
+ *
+ * Unlike `generateBracketFromTemplate` (which seeds only round 1 and leaves later
+ * rounds empty for `advanceWinnerTemplate`), this writes every round's actual
+ * matchups and known winners directly from the source — appropriate for a feed
+ * (e.g. Wikipedia) that reports the full draw including completed rounds. A row
+ * is marked complete when both its winner and loser are known.
+ *
+ * @returns counts of rows written (inserted or updated) and those carrying a result.
+ */
+export async function populateBracketFromDraw(
+ eventId: string,
+ matches: ResolvedDrawMatch[]
+): Promise<{ written: number; completed: number }> {
+ const db = database();
+
+ const existing = await db.query.playoffMatches.findMany({
+ where: eq(schema.playoffMatches.scoringEventId, eventId),
+ });
+ const byExternalId = new Map(
+ existing
+ .filter((m) => m.externalMatchId)
+ .map((m) => [m.externalMatchId as string, m])
+ );
+
+ const toInsert: NewPlayoffMatch[] = [];
+ let written = 0;
+ let completed = 0;
+
+ for (const m of matches) {
+ const isComplete = m.winnerId !== null && m.loserId !== null;
+ if (isComplete) completed++;
+
+ const existingRow = byExternalId.get(m.externalMatchId);
+ if (existingRow) {
+ await db
+ .update(schema.playoffMatches)
+ .set({
+ round: m.round,
+ matchNumber: m.matchNumber,
+ participant1Id: m.participant1Id,
+ participant2Id: m.participant2Id,
+ winnerId: m.winnerId,
+ loserId: m.loserId,
+ isComplete,
+ isScoring: m.isScoring,
+ templateRound: m.round,
+ updatedAt: new Date(),
+ })
+ .where(eq(schema.playoffMatches.id, existingRow.id));
+ written++;
+ } else {
+ toInsert.push({
+ scoringEventId: eventId,
+ round: m.round,
+ matchNumber: m.matchNumber,
+ participant1Id: m.participant1Id,
+ participant2Id: m.participant2Id,
+ winnerId: m.winnerId,
+ loserId: m.loserId,
+ isComplete,
+ isScoring: m.isScoring,
+ templateRound: m.round,
+ externalMatchId: m.externalMatchId,
+ });
+ }
+ }
+
+ if (toInsert.length > 0) {
+ await createManyPlayoffMatches(toInsert);
+ written += toInsert.length;
+ }
+
+ return { written, completed };
+}
+
/**
* Generate a standard single elimination bracket structure
*/
diff --git a/app/models/scoring-calculator.ts b/app/models/scoring-calculator.ts
index 6e6b556..bb28973 100644
--- a/app/models/scoring-calculator.ts
+++ b/app/models/scoring-calculator.ts
@@ -727,15 +727,38 @@ export function deriveBracketQualifyingStates(
}
if (!highest) {
- // In the bracket but no match resolved yet → entry floor.
- const cfg = firstScoringRound ? getConfig(firstScoringRound.name) : null;
+ // In the bracket but no match resolved yet → sitting in the first round.
+ // Only grant an entry floor if that first round is itself a scoring round
+ // (e.g. simple_8 / CS2 Champions Stage, where every entrant is already in
+ // the scoring stage). For deep brackets whose scoring starts later
+ // (tennis_128: Round of 128 → … → Round of 16), merely being drawn earns
+ // nothing — no QP until the player reaches the first scoring round.
+ if (!rounds[0]?.isScoring || !firstScoringRound) continue;
+ const cfg = getConfig(firstScoringRound.name);
if (!cfg) continue;
result.set(id, { placement: cfg.loserPosition, tieCount: firstScoringRound.matchCount });
continue;
}
const cfg = getConfig(highest);
- if (!cfg) continue;
+ if (!cfg) {
+ // Won a non-scoring round. If that win advanced them INTO a scoring round,
+ // they've guaranteed that round's loser floor (e.g. a tennis R32 win → in
+ // the Round of 16 → guaranteed T9–16). Otherwise (won an earlier
+ // non-scoring round) they've earned no QP yet.
+ const feedsInto = feedsIntoByRound.get(highest) ?? null;
+ const nextRound = feedsInto ? rounds.find((r) => r.name === feedsInto) : null;
+ if (nextRound?.isScoring) {
+ const floorCfg = getConfig(nextRound.name);
+ if (floorCfg) {
+ result.set(id, {
+ placement: floorCfg.loserPosition,
+ tieCount: nextRound.matchCount,
+ });
+ }
+ }
+ continue;
+ }
if (cfg.winnerFloor === null) {
// Won the finalization round → champion (or 3rd-place-game winner).
result.set(id, { placement: cfg.winnerPosition ?? 1, tieCount: 1 });
diff --git a/app/models/scoring-event.ts b/app/models/scoring-event.ts
index 1525e58..4a850a3 100644
--- a/app/models/scoring-event.ts
+++ b/app/models/scoring-event.ts
@@ -42,6 +42,8 @@ export interface UpdateScoringEventData {
scoringStartsAtRound?: string;
/** Per-event region config for NCAA-style brackets (overrides template defaults) */
bracketRegionConfig?: BracketRegion[] | null;
+ /** External data-source locator (e.g. Wikipedia article title for tennis draws) */
+ externalSourceKey?: string | null;
}
/**
@@ -173,6 +175,7 @@ export async function updateScoringEvent(
if (data.bracketTemplateId !== undefined) updateData.bracketTemplateId = data.bracketTemplateId;
if (data.scoringStartsAtRound !== undefined) updateData.scoringStartsAtRound = data.scoringStartsAtRound;
if (data.bracketRegionConfig !== undefined) updateData.bracketRegionConfig = data.bracketRegionConfig;
+ if (data.externalSourceKey !== undefined) updateData.externalSourceKey = data.externalSourceKey;
const [updated] = await db
.update(schema.scoringEvents)
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
index dd15217..59b66a2 100644
--- a/app/routes/admin.sports-seasons.$id.events.$eventId.bracket.server.ts
+++ b/app/routes/admin.sports-seasons.$id.events.$eventId.bracket.server.ts
@@ -1,7 +1,11 @@
import type { Route } from "./+types/admin.sports-seasons.$id.events.$eventId.bracket";
import { findSportsSeasonById } from "~/models/sports-season";
-import { findParticipantsBySportsSeasonId } from "~/models/season-participant";
+import {
+ findParticipantsBySportsSeasonId,
+ createParticipant,
+ updateParticipant,
+} from "~/models/season-participant";
import { getScoringEventById, updateScoringEvent, isReadOnlySibling } from "~/models/scoring-event";
import {
findPlayoffMatchesByEventId,
@@ -62,6 +66,8 @@ import * as schema from "~/database/schema";
import { eq } from "drizzle-orm";
import { maybeResolveCompletedBracktForSportsSeason } from "~/services/brackt.server";
import { fanOutMajorIfPrimary } from "~/services/sync-tournament-results";
+import { syncTennisDraw, previewTennisDraw } from "~/services/match-sync";
+import { articleTitleFromInput } from "~/services/match-sync/wikipedia-tennis";
export async function loader({ params }: Route.LoaderArgs) {
const sportsSeason = await findSportsSeasonById(params.id);
@@ -159,6 +165,72 @@ export async function action({ request, params }: Route.ActionArgs) {
}
}
+ if (intent === "create-draw-participant") {
+ const name = (formData.get("name") as string | null)?.trim();
+ const externalId = (formData.get("externalId") as string | null)?.trim() || null;
+ if (!name) return { error: "Participant name is required" };
+ try {
+ await createParticipant({ sportsSeasonId: params.id, name, externalId });
+ return { success: `Created "${name}". Re-run Preview or Sync Draw to apply.` };
+ } catch (error) {
+ return { error: error instanceof Error ? error.message : "Failed to create participant" };
+ }
+ }
+
+ if (intent === "relink-draw-participant") {
+ const participantId = formData.get("participantId") as string | null;
+ const name = (formData.get("name") as string | null)?.trim();
+ const externalId = (formData.get("externalId") as string | null)?.trim() || null;
+ if (!participantId || !name) return { error: "Participant and name are required" };
+ try {
+ await updateParticipant(participantId, { name, externalId });
+ return { success: `Renamed and linked to "${name}". Re-run Preview or Sync Draw to apply.` };
+ } catch (error) {
+ return { error: error instanceof Error ? error.message : "Failed to update participant" };
+ }
+ }
+
+ if (intent === "preview-draw") {
+ const rawInput = (formData.get("externalSourceKey") as string | null)?.trim();
+ const articleTitle = rawInput ? articleTitleFromInput(rawInput) : "";
+ // Persist the article so the user can preview, then sync, without re-entering.
+ if (articleTitle) {
+ await updateScoringEvent(params.eventId, { externalSourceKey: articleTitle });
+ }
+ try {
+ const preview = await previewTennisDraw(params.eventId);
+ return { drawPreview: preview };
+ } catch (error) {
+ logger.error("[preview-draw] error:", error);
+ return { error: error instanceof Error ? error.message : "Failed to preview draw" };
+ }
+ }
+
+ if (intent === "sync-draw") {
+ const rawInput = (formData.get("externalSourceKey") as string | null)?.trim();
+ // Accept a pasted Wikipedia URL or a plain article title; store the title.
+ const articleTitle = rawInput ? articleTitleFromInput(rawInput) : "";
+ if (articleTitle) {
+ await updateScoringEvent(params.eventId, { externalSourceKey: articleTitle });
+ }
+ try {
+ const result = await syncTennisDraw(params.eventId);
+ const reviewNote =
+ result.unmatched.length > 0
+ ? ` ${result.unmatched.length} auto-created player(s) need a quick review for possible duplicates.`
+ : "";
+ return {
+ success:
+ `Synced draw: ${result.matchesWritten} matches written ` +
+ `(${result.completed} completed), ${result.participantsCreated} participant(s) added.${reviewNote}`,
+ drawSyncResult: result,
+ };
+ } catch (error) {
+ logger.error("[sync-draw] error:", error);
+ return { error: error instanceof Error ? error.message : "Failed to sync draw" };
+ }
+ }
+
if (intent === "generate-bracket") {
const templateId = formData.get("templateId");
diff --git a/app/routes/admin.sports-seasons.$id.events.$eventId.bracket.tsx b/app/routes/admin.sports-seasons.$id.events.$eventId.bracket.tsx
index 3cbf772..5569fe0 100644
--- a/app/routes/admin.sports-seasons.$id.events.$eventId.bracket.tsx
+++ b/app/routes/admin.sports-seasons.$id.events.$eventId.bracket.tsx
@@ -1,4 +1,4 @@
-import { Form, Link } from "react-router";
+import { Form, Link, useFetcher } from "react-router";
import { Fragment, useState, useEffect, useMemo, useRef } from "react";
import { localDateTimeToUtcIso, utcIsoToLocalDateTime } from "~/lib/date-utils";
import type { Route } from "./+types/admin.sports-seasons.$id.events.$eventId.bracket";
@@ -93,6 +93,62 @@ function GroupMatchScheduleForm({
export { loader, action };
+/**
+ * One "possible duplicate" row in the draw preview. Uses a fetcher so resolving
+ * it (rename existing / create as new) submits in the background — the preview
+ * stays on screen instead of reloading the page and forcing a fresh dry run.
+ */
+function DuplicateRow({
+ p,
+}: {
+ p: { name: string; externalId: string | null; suggestion: string; suggestionId: string };
+}) {
+ const fetcher = useFetcher<{ success?: string; error?: string }>();
+ const busy = fetcher.state !== "idle";
+
+ return (
+
+
+ {p.name}
+ → looks like
+ {p.suggestion}
+
+ {fetcher.data?.success ? (
+ ✓ {fetcher.data.success}
+ ) : (
+
+
+
+
+
+ Rename “{p.suggestion}” → “{p.name}”
+
+
+ Create as new
+
+
+ )}
+ {fetcher.data?.error && (
+ {fetcher.data.error}
+ )}
+
+ );
+}
+
export default function EventBracket({
loaderData,
actionData,
@@ -375,6 +431,164 @@ export default function EventBracket({
)}
+ {/* Possible-duplicate review after a draw sync */}
+ {actionData?.drawSyncResult && actionData.drawSyncResult.unmatched.length > 0 && (
+
+
+
+ Review {actionData.drawSyncResult.unmatched.length} possible duplicate
+ {actionData.drawSyncResult.unmatched.length === 1 ? "" : "s"}
+
+
+ These players were auto-created but closely resemble an existing participant —
+ if a duplicate, results won't reach the drafted copy. To fix: on the{" "}
+
+ participants page
+
+ , set the correct participant's external ID to the Wikipedia name, delete the
+ duplicate, then click Sync Draw again.
+
+
+
+
+ {actionData.drawSyncResult.unmatched.map((p) => (
+
+ {p.name}
+ {p.externalId && p.externalId !== p.name && (
+ ({p.externalId})
+ )}
+
+ ))}
+
+
+
+ )}
+
+ {/* Dry-run preview of a draw sync */}
+ {actionData?.drawPreview && (
+
+
+ Draw preview (no changes made)
+ {actionData.drawPreview.article}
+
+
+
+
+ Players: {actionData.drawPreview.totalPlayers}
+
+
+ Matched:{" "}
+
+ {actionData.drawPreview.matched}
+
+
+
+ Will create:{" "}
+ {actionData.drawPreview.willCreate.length}
+
+
+ Matches:{" "}
+
+ {actionData.drawPreview.completedMatches}/{actionData.drawPreview.totalMatches}{" "}
+ done
+
+
+
+ Unfilled R1 slots:{" "}
+ {actionData.drawPreview.tbdFirstRound}
+
+
+
+ {actionData.drawPreview.possibleDuplicates.length > 0 && (
+
+
+ Possible duplicates ({actionData.drawPreview.possibleDuplicates.length}) — would
+ be created but resemble an existing participant:
+
+
+ {actionData.drawPreview.possibleDuplicates.map((p) => (
+
+ ))}
+
+
+ Rename if it's the same player (links
+ the existing participant so it matches);{" "}
+ Create as new if they're different
+ people. Then re-run Preview or Sync Draw.
+
+
+ )}
+
+ {actionData.drawPreview.willCreate.length > 0 && (
+
+
+ Show all {actionData.drawPreview.willCreate.length} players that would be created
+
+
+ {actionData.drawPreview.willCreate.map((p) => (
+ {p.name}
+ ))}
+
+
+ )}
+
+
+ Fix any duplicates on the{" "}
+
+ participants page
+ {" "}
+ first, then click Sync Draw .
+
+
+
+ )}
+
+ {/* Sync Draw from Wikipedia — tennis Grand Slam auto-populate + auto-score */}
+ {event.isQualifyingEvent &&
+ sportsSeason.sport.simulatorType === "tennis_qualifying_points" && (
+
+
+ Sync Draw from Wikipedia
+
+ Auto-populate and score this Grand Slam bracket from its Wikipedia draw
+ article. Re-run during the tournament to pull in completed matches.
+
+
+
+
+
+
+ )}
+
{/* Reprocess Bracket - Full rebuild of participant results.
Hidden once every match is complete — at that point all placements are final
and "finalize bracket" should be used instead. */}
diff --git a/app/routes/admin/jobs.sync-matches.ts b/app/routes/admin/jobs.sync-matches.ts
index 23c7870..9921cac 100644
--- a/app/routes/admin/jobs.sync-matches.ts
+++ b/app/routes/admin/jobs.sync-matches.ts
@@ -2,7 +2,7 @@ import { and, eq, isNotNull } from "drizzle-orm";
import { database } from "~/database/context";
import * as schema from "~/database/schema";
import { requireCronSecret } from "~/lib/cron-auth";
-import { syncMatches } from "~/services/match-sync";
+import { syncMatches, syncTennisDraw } from "~/services/match-sync";
export async function action({ request }: { request: Request }) {
requireCronSecret(request);
@@ -37,5 +37,39 @@ export async function action({ request }: { request: Request }) {
}
}
- return Response.json({ synced, errors }, { status: errors.length > 0 && synced.length === 0 ? 500 : 200 });
+ // Tennis Grand Slam draws: sync each active tennis major's primary window from
+ // its configured Wikipedia article. (Tennis uses a per-event externalSourceKey,
+ // not a per-season externalSeasonId, so it isn't covered by the loop above.)
+ const tennisEvents = await db.query.scoringEvents.findMany({
+ where: and(
+ isNotNull(schema.scoringEvents.externalSourceKey),
+ eq(schema.scoringEvents.isQualifyingEvent, true),
+ eq(schema.scoringEvents.isComplete, false),
+ ),
+ with: { sportsSeason: { with: { sport: true } } },
+ });
+
+ const drawSynced: string[] = [];
+ for (const ev of tennisEvents) {
+ if (ev.sportsSeason?.status !== "active") continue;
+ if (ev.sportsSeason?.sport?.simulatorType !== "tennis_qualifying_points") continue;
+ // Skip read-only siblings of a shared major — results fan out from the primary.
+ if (ev.tournamentId && !ev.isPrimary) continue;
+ try {
+ await syncTennisDraw(ev.id);
+ drawSynced.push(ev.id);
+ } catch (err) {
+ errors.push({
+ id: ev.id,
+ name: ev.name ?? "(tennis draw)",
+ error: err instanceof Error ? err.message : String(err),
+ });
+ }
+ }
+
+ const okCount = synced.length + drawSynced.length;
+ return Response.json(
+ { synced, drawSynced, errors },
+ { status: errors.length > 0 && okCount === 0 ? 500 : 200 },
+ );
}
diff --git a/app/services/match-sync/__tests__/fixtures/wimbledon-2025-mens-singles.wikitext b/app/services/match-sync/__tests__/fixtures/wimbledon-2025-mens-singles.wikitext
new file mode 100644
index 0000000..1dbd563
--- /dev/null
+++ b/app/services/match-sync/__tests__/fixtures/wimbledon-2025-mens-singles.wikitext
@@ -0,0 +1,2181 @@
+{{short description|Tennis championship}}
+{{use dmy dates|date=May 2025}}
+{{Further|2025 Wimbledon Championships}}
+{{Infobox tennis tournament event|2025|Wimbledon Championships|
+| type = wimbledon2022
+| date = 13 July 2025
+| champ = {{flagicon|ITA}} [[Jannik Sinner]]
+| runner = {{flagicon|ESP}} [[Carlos Alcaraz]]
+| score = 4–6, 6–4, 6–4, 6–4
+| draw = 128 (16{{Tooltip| Q | Qualifiers}} / 8{{Tooltip| WC | Wild Cards}})
+| seeds = 32
+| event = Gentlemen's singles
+| before_name = Wimbledon Championships – Men's singles
+| after_name = Wimbledon Championships – Men's singles
+| qual_type = qualmenswomenssingles
+}}
+
+[[Jannik Sinner]] defeated two-time defending champion [[Carlos Alcaraz]][{{cite web|url=https://www.bbc.co.uk/sport/tennis/articles/c8864j5815go|title=Alcaraz crushes Djokovic to retain Wimbledon title|date=14 July 2024 |work=BBC Sport|access-date=14 July 2024}}] in the final, 4–6, 6–4, 6–4, 6–4 to win the gentlemen's singles tennis title at the [[2025 Wimbledon Championships]].[{{cite web|url=https://www.bbc.co.uk/sport/tennis/articles/c5ykw5n0p7no|title=Sinner beats Alcaraz to win first Wimbledon title|date=13 July 2025 |publisher=BBC Sport|accessdate=13 July 2025}}][{{cite web|last1=Sridhar |first1=Shrivathsa |url=https://www.reuters.com/sports/tennis/sinner-beats-alcaraz-win-wimbledon-mens-title-2025-07-13/|title=Sinner dethrones Alcaraz to capture maiden Wimbledon crown|work=Reuters |date=13 July 2025 |accessdate=13 July 2025}}] It was his first [[Wimbledon Championships|Wimbledon]] title and fourth [[Grand Slam (tennis)#Tournaments|major]] title overall.[{{cite web|url=https://www.atptour.com/en/news/alcaraz-sinner-wimbledon-2025-final-sunday|title=Sinner gains Alcaraz revenge, wins first Wimbledon crown|publisher=ATPTour|accessdate=13 July 2025}}] With the win, Sinner ended Alcaraz's undefeated 5–0 record in major finals,[ and became the first Italian to win a Wimbledon singles title.][{{cite web|url=https://www.ubitennis.net/2025/07/jannik-sinner-ends-alcarazs-reign-to-become-italys-first-wimbledon-champion/|title=Jannik Sinner Ends Alcaraz's Reign To Become Italy's First Wimbledon champion|date=13 July 2025 |publisher=Ubitennis|accessdate=13 July 2025}}]
+
+At {{ayd|2001|8|16|2025|7|13}} old, Sinner was the second-youngest man to reach the final at all four majors after [[Jim Courier]] at {{ayd|1970|8|17|1993|7|4}} old, a record that was later broken by Alcaraz at the [[2026 Australian Open – Men's singles|2026 Australian Open]] at {{ayd|2003|5|5|2026|2|1}} old.[ {{cite web|url=https://lastwordonsports.com/tennis/2025/07/12/sinner-joins-elites-of-tennis-ahead/|title=Jannik Sinner Joins Elite Lists of Tennis Greats Ahead of Wimbledon Final|website=LastWordOnSports|date=12 July 2025}}][{{cite web|url=https://clutchpoints.com/tennis/wimbledon-news-jannik-sinner-roger-federer-record-novak-djokovic-takedown|title= Jannik Sinner breaks Roger Federer's record with Novak Djokovic Wimbledon takedown|publisher=Clutchpoints|date=11 July 2025}} ] Alcaraz was the tenth man in the Open Era to reach three consecutive Wimbledon finals, and the first player born in 1990 or later to do so.[ {{cite web|url=https://www.tennis.com/news/articles/carlos-alcaraz-reaches-third-straight-wimbledon-final-joins-federer-nadal-and-djokovic-on-exclusive-list|title= Carlos Alcaraz reaches third straight Wimbledon final, joins Federer, Nadal and Djokovic on exclusive list|date=11 July 2025|website=tennis.com}}] Sinner and Alcaraz joined [[Roger Federer]] and [[Rafael Nadal]] as the only men's pair to contest both the [[French Open]] and Wimbledon finals in the same season.[{{Cite web |date=2025-07-11 |title=Alcaraz & Sinner join Federer & Nadal as only rivals to do this |url=http://www.atptour.com/en/news/alcaraz-sinner-federer-nadal-roland-garros-wimbledon-finals |access-date=2025-07-12|website=ATPTour|language=en}}]
+
+With his third round win, [[Novak Djokovic]] recorded his 100th match win at Wimbledon and became the second man to record 100 or more wins at two majors (after Federer), in addition to the French Open.[{{cite web|url=https://www.atptour.com/en/news/djokovic-wimbledon-2025-100-wins|title=Djokovic joins Federer, earns 100th Wimbledon match win|publisher=ATPTour|accessdate=5 July 2025}}] Djokovic made his 19th career third round appearance and 14th semifinal appearance at Wimbledon, the most by any man in the Open Era.[ {{cite web|url=https://www.atptour.com/en/news/djokovic-evans-wimbledon-2025-thursday|title=Djokovic sweeps aside Evans, keeps Wimbledon bid on track|date=3 July 2025|website=ATPTour}}][{{cite web|url=https://www.espn.ph/tennis/story/_/id/45703099/novak-djokovic-defeats-flavio-cobolli-reach-wimbledon-2025-semifinals|title=Novak Djokovic reaches men's-record 14th Wimbledon semifinal|publisher=[[ESPN]]|date=9 July 2025|accessdate=9 July 2025}}] His defeat to Sinner in the semifinals marked the first time since [[2017 Wimbledon Championships – Men's singles|2017]] that Djokovic did not reach the final, ending a streak of six consecutive final appearances.[{{Cite web |title=MSN |url=https://www.msn.com/en-gb/sport/tennis/federer-record-safe-as-sinner-smashes-injured-djokovic/ar-AA1IqUkN |access-date=2025-07-11 |website=www.msn.com}}] It also marked the first instance where none of the "[[Big Four career statistics|Big Four]]" were present in a Wimbledon final since [[2002 Wimbledon Championships – Men's singles|2002]], ending their streak at 21-consecutive finals, of which 9 featured both finalists from the Big Four.[{{cite web|url=https://www.foxsports.com.au/tennis/resilient-sinner-creates-history-as-the-first-italian-to-win-wimbledon-when-downing-alcaraz-in-a-stunning-decider/news-story/fad9efeb2b670a29985436e2cfb99726|title=Resilient Sinner creates history as the first Italian to win Wimbledon when downing Alcaraz in a stunning decider|work=Fox Sports|date=13 July 2025 |accessdate=13 July 2025}}]
+
+13 out of the 32 seeds lost in the first round, the most to do so at Wimbledon in the Open Era,[{{cite web|title=Zverev, Gauff among record Wimbledon seeds exodus|url=https://www.atptour.com/en/news/wimbledon-2025-seeds-upset-record|date=1 July 2025 |publisher=ATPTour|website=ATPTour.com|access-date=2 July 2025}}] and tied the record set for any major at the [[2004 Australian Open – Men's singles|2004 Australian Open]].[ {{cite web|url=https://www.si.com/tennis/si-am-record-setting-carnage-at-wimbledon-as-numerous-top-seeds-exit-early|title=Record-Setting Carnage at Wimbledon as Numerous Top Seeds Exit Early|date=2 July 2025|website=SI.com}}]
+
+The event marked the final professional appearance of former world No. 9 [[Fabio Fognini]]. He lost in the first round to Alcaraz.[ {{cite web|url=https://www.atptour.com/en/news/fognini-retirement-announcement-2025 |title=Fognini announces retirement |website=ATPTour|date=9 July 2025}} ]
+
+
+[[File:2025 singles championships scoreboards 2026-01-23.jpg | thumb | right | 2025 Ladies' and Gentlemen's singles Wimbledon Championships draws and scoreboards]]
+
+== Seeds ==
+{{columns-list|
+{{seeds|1|1}} '''{{flagicon|ITA}} [[Jannik Sinner]] (champion)'''
+{{seeds|2|8}} {{flagicon|ESP}} [[Carlos Alcaraz]] ''(final)''
+{{seeds|3|6}} {{flagicon|GER}} [[Alexander Zverev]] ''(first round)''
+{{seeds|4|3}} {{flagicon|GBR}} [[Jack Draper]] ''(second round)''
+{{seeds|5|5}} {{flagicon|USA}} [[Taylor Fritz]] ''(semifinals)''
+{{seeds|6|4}} {{flagicon|SRB}} [[Novak Djokovic]] ''(semifinals)''
+{{seeds|7|2}} {{flagicon|ITA}} [[Lorenzo Musetti]] ''(first round)''
+{{seeds|8|7}} {{flagicon|DEN}} [[Holger Rune]] ''(first round)''
+{{seeds|9|5}} {{flagicon|}} [[Daniil Medvedev]] ''(first round)''
+{{seeds|10|2}} {{flagicon|USA}} [[Ben Shelton]] ''(quarterfinals)''
+{{seeds|11|4}} {{flagicon|AUS}} [[Alex de Minaur]] ''(fourth round)''
+{{seeds|12|7}} {{flagicon|USA}} [[Frances Tiafoe]] ''(second round)''
+{{seeds|13|1}} {{flagicon|USA}} [[Tommy Paul (tennis)|Tommy Paul]] ''(second round)''
+{{seeds|14|8}} {{flagicon|}} [[Andrey Rublev]] ''(fourth round)''
+{{seeds|15|3}} {{flagicon|CZE}} [[Jakub Menšík]] ''(third round)''
+{{seeds|16|6}} {{flagicon|ARG}} [[Francisco Cerúndolo]] ''(first round)''
+{{seeds|17|6}} {{flagicon|}} [[Karen Khachanov]] ''(quarterfinals)''
+{{seeds|18|2}} {{flagicon|FRA}} [[Ugo Humbert]] ''(first round)''
+{{seeds|19|1}} {{flagicon|BUL}} [[Grigor Dimitrov]] ''(fourth round, retired)''
+{{seeds|20|5}} {{flagicon|AUS}} [[Alexei Popyrin]] ''(first round)''
+{{seeds|21|4}} {{flagicon|CZE}} [[Tomáš Macháč]] ''(second round)''
+{{seeds|22|3}} {{flagicon|ITA}} [[Flavio Cobolli]] ''(quarterfinals)''
+{{seeds|23|7}} {{flagicon|CZE}} [[Jiří Lehečka]] ''(second round)''
+{{seeds|24|8}} {{flagicon|GRE}} [[Stefanos Tsitsipas]] ''(first round, retired)''
+{{seeds|25|8}} {{flagicon|CAN}} [[Félix Auger-Aliassime]] ''(second round)''
+{{seeds|26|5}} {{flagicon|ESP}} [[Alejandro Davidovich Fokina]] ''(third round)''
+{{seeds|27|1}} {{flagicon|CAN}} [[Denis Shapovalov]] ''(first round)''
+{{seeds|28|3}} {{flagicon|KAZ}} [[Alexander Bublik]] ''(first round)''
+{{seeds|29|2}} {{flagicon|USA}} [[Brandon Nakashima]] ''(third round)''
+{{seeds|30|4}} {{flagicon|USA}} [[Alex Michelsen]] ''(first round)''
+{{seeds|31|7}} {{flagicon|NED}} [[Tallon Griekspoor]] ''(first round)''
+{{seeds|32|6}} {{flagicon|ITA}} [[Matteo Berrettini]] ''(first round)''
+|colwidth=30em}}
+
+== Draw ==
+{{draw key}}
+
+=== Finals ===
+{{8TeamBracket-Tennis5
+| RD1=Quarterfinals
+| RD2=Semifinals
+| RD3=Final
+| team-width=175
+
+| RD1-seed1=1
+| RD1-team1='''{{flagicon|ITA}} [[Jannik Sinner]]'''
+| RD1-score1-1='''77 '''
+| RD1-score1-2='''6'''
+| RD1-score1-3='''6'''
+| RD1-seed2=10
+| RD1-team2={{flagicon|USA}} [[Ben Shelton]]
+| RD1-score2-1=62
+| RD1-score2-2=4
+| RD1-score2-3=4
+
+| RD1-seed3=22
+| RD1-team3={{flagicon|ITA}} [[Flavio Cobolli]]
+| RD1-score3-1='''78 '''
+| RD1-score3-2=2
+| RD1-score3-3=5
+| RD1-score3-4=4
+| RD1-seed4=6
+| RD1-team4='''{{flagicon|SRB}} [[Novak Djokovic]]'''
+| RD1-score4-1=66
+| RD1-score4-2='''6'''
+| RD1-score4-3='''7'''
+| RD1-score4-4='''6'''
+
+| RD1-seed5=5
+| RD1-team5='''{{flagicon|USA}} [[Taylor Fritz]]'''
+| RD1-score5-1='''6'''
+| RD1-score5-2='''6'''
+| RD1-score5-3=1
+| RD1-score5-4='''77 '''
+| RD1-seed6=17
+| RD1-team6={{flagicon|}} [[Karen Khachanov]]
+| RD1-score6-1=3
+| RD1-score6-2=4
+| RD1-score6-3='''6'''
+| RD1-score6-4=64
+
+| RD1-seed7=
+| RD1-team7={{flagicon|GBR}} [[Cameron Norrie]]
+| RD1-score7-1=2
+| RD1-score7-2=3
+| RD1-score7-3=3
+| RD1-seed8=2
+| RD1-team8='''{{flagicon|ESP}} [[Carlos Alcaraz]]'''
+| RD1-score8-1='''6'''
+| RD1-score8-2='''6'''
+| RD1-score8-3='''6'''
+
+| RD2-seed1=1
+| RD2-team1='''{{flagicon|ITA}} [[Jannik Sinner]]'''
+| RD2-score1-1='''6'''
+| RD2-score1-2='''6'''
+| RD2-score1-3='''6'''
+| RD2-seed2=6
+| RD2-team2={{flagicon|SRB}} [[Novak Djokovic]]
+| RD2-score2-1=3
+| RD2-score2-2=3
+| RD2-score2-3=4
+
+| RD2-seed3=5
+| RD2-team3={{flagicon|USA}} [[Taylor Fritz]]
+| RD2-score3-1=4
+| RD2-score3-2='''7'''
+| RD2-score3-3=3
+| RD2-score3-4=66
+| RD2-seed4=2
+| RD2-team4='''{{flagicon|ESP}} [[Carlos Alcaraz]]'''
+| RD2-score4-1='''6'''
+| RD2-score4-2=5
+| RD2-score4-3='''6'''
+| RD2-score4-4='''78 '''
+
+| RD3-seed1=1
+| RD3-team1='''{{flagicon|ITA}} [[Jannik Sinner]]'''
+| RD3-score1-1=4
+| RD3-score1-2='''6'''
+| RD3-score1-3='''6'''
+| RD3-score1-4='''6'''
+| RD3-seed2=2
+| RD3-team2={{flagicon|ESP}} [[Carlos Alcaraz]]
+| RD3-score2-1='''6'''
+| RD3-score2-2=4
+| RD3-score2-3=4
+| RD3-score2-4=4
+}}
+
+=== Top half ===
+
+==== Section 1 ====
+{{16TeamBracket-Compact-Tennis5
+| RD1=First round
+| RD2=Second round
+| RD3=Third round
+| RD4=Fourth round
+|-
+| RD1-seed01=1
+| RD1-team01='''{{flagicon|ITA}} [[Jannik Sinner|J Sinner]]'''
+| RD1-score01-1='''6'''
+| RD1-score01-2='''6'''
+| RD1-score01-3='''6'''
+| RD1-seed02=
+| RD1-team02={{flagicon|ITA}} [[Luca Nardi|L Nardi]]
+| RD1-score02-1=4
+| RD1-score02-2=3
+| RD1-score02-3=0
+|-
+| RD1-seed03=
+| RD1-team03={{flagicon|TPE}} [[Tseng Chun-hsin|C-h Tseng]]
+| RD1-score03-1=3
+| RD1-score03-2=4
+| RD1-score03-3='''6'''
+| RD1-score03-4=65
+| RD1-seed04=
+| RD1-team04='''{{flagicon|AUS}} [[Aleksandar Vukic|A Vukic]]'''
+| RD1-score04-1='''6'''
+| RD1-score04-2='''6'''
+| RD1-score04-3=4
+| RD1-score04-4='''77 '''
+|-
+| RD1-seed05=
+| RD1-team05='''{{flagicon|ESP}} [[Pedro Martínez (tennis)|P Martínez]]'''
+| RD1-score05-1=2
+| RD1-score05-2='''6'''
+| RD1-score05-3='''6'''
+| RD1-score05-4='''6'''
+| RD1-seed06=WC
+| RD1-team06={{flagicon|GBR}} [[George Loffhagen|G Loffhagen]]
+| RD1-score06-1='''6'''
+| RD1-score06-2=2
+| RD1-score06-3=4
+| RD1-score06-4=2
+|-
+| RD1-seed07=
+| RD1-team07='''{{flagicon|ARG}} [[Mariano Navone|M Navone]]'''
+| RD1-score07-1=3
+| RD1-score07-2='''6'''
+| RD1-score07-3='''6'''
+| RD1-score07-4='''6'''
+| RD1-seed08=27
+| RD1-team08={{flagicon|CAN}} [[Denis Shapovalov|D Shapovalov]]
+| RD1-score08-1='''6'''
+| RD1-score08-2=4
+| RD1-score08-3=1
+| RD1-score08-4=4
+|-
+| RD1-seed09=19
+| RD1-team09='''{{flagicon|BUL}} [[Grigor Dimitrov|G Dimitrov]]'''
+| RD1-score09-1='''6'''
+| RD1-score09-2='''6'''
+| RD1-score09-3='''6'''
+| RD1-seed10=
+| RD1-team10={{flagicon|JPN}} [[Yoshihito Nishioka|Y Nishioka]]
+| RD1-score10-1=2
+| RD1-score10-2=3
+| RD1-score10-3=4
+|-
+| RD1-seed11=
+| RD1-team11={{flagicon|ARG}} [[Francisco Comesaña|F Comesaña]]
+| RD1-score11-1=4
+| RD1-score11-2=4
+| RD1-score11-3=2
+| RD1-seed12=
+| RD1-team12='''{{flagicon|FRA}} [[Corentin Moutet|C Moutet]]'''
+| RD1-score12-1='''6'''
+| RD1-score12-2='''6'''
+| RD1-score12-3='''6'''
+|-
+| RD1-seed13=PR
+| RD1-team13='''{{flagicon|AUT}} [[Sebastian Ofner|S Ofner]]'''
+| RD1-score13-1='''710 '''
+| RD1-score13-2=3
+| RD1-seed14=
+| RD1-team14={{flagicon|SRB}} [[Hamad Medjedovic|H Medjedovic]]
+| RD1-score14-1=68
+| RD1-score14-2=1r
+|-
+| RD1-seed15=WC
+| RD1-team15={{flagicon|GBR}} [[Johannus Monday|J Monday]]
+| RD1-score15-1=4
+| RD1-score15-2=4
+| RD1-score15-3=2
+| RD1-seed16=13
+| RD1-team16='''{{flagicon|USA}} [[Tommy Paul (tennis)|T Paul]]'''
+| RD1-score16-1='''6'''
+| RD1-score16-2='''6'''
+| RD1-score16-3='''6'''
+|-
+| RD2-seed01=1
+| RD2-team01='''{{flagicon|ITA}} [[Jannik Sinner|J Sinner]]'''
+| RD2-score01-1='''6'''
+| RD2-score01-2='''6'''
+| RD2-score01-3='''6'''
+| RD2-seed02=
+| RD2-team02={{flagicon|AUS}} [[Aleksandar Vukic|A Vukic]]
+| RD2-score02-1=1
+| RD2-score02-2=1
+| RD2-score02-3=3
+|-
+| RD2-seed03=
+| RD2-team03='''{{flagicon|ESP}} [[Pedro Martínez (tennis)|P Martínez]]'''
+| RD2-score03-1='''7'''
+| RD2-score03-2='''7'''
+| RD2-score03-3='''78 '''
+| RD2-seed04=
+| RD2-team04={{flagicon|ARG}} [[Mariano Navone|M Navone]]
+| RD2-score04-1=5
+| RD2-score04-2=5
+| RD2-score04-3=66
+|-
+| RD2-seed05=19
+| RD2-team05='''{{flagicon|BUL}} [[Grigor Dimitrov|G Dimitrov]]'''
+| RD2-score05-1='''7'''
+| RD2-score05-2=4
+| RD2-score05-3='''7'''
+| RD2-score05-4='''7'''
+| RD2-seed06=
+| RD2-team06={{flagicon|FRA}} [[Corentin Moutet|C Moutet]]
+| RD2-score06-1=5
+| RD2-score06-2='''6'''
+| RD2-score06-3=5
+| RD2-score06-4=5
+|-
+| RD2-seed07=PR
+| RD2-team07='''{{flagicon|AUT}} [[Sebastian Ofner|S Ofner]]'''
+| RD2-score07-1=1
+| RD2-score07-2='''7'''
+| RD2-score07-3='''6'''
+| RD2-score07-4='''7'''
+| RD2-seed08=13
+| RD2-team08={{flagicon|USA}} [[Tommy Paul (tennis)|T Paul]]
+| RD2-score08-1='''6'''
+| RD2-score08-2=5
+| RD2-score08-3=4
+| RD2-score08-4=5
+|-
+| RD3-seed01=1
+| RD3-team01='''{{flagicon|ITA}} [[Jannik Sinner|J Sinner]]'''
+| RD3-score01-1='''6'''
+| RD3-score01-2='''6'''
+| RD3-score01-3='''6'''
+| RD3-seed02=
+| RD3-team02={{flagicon|ESP}} [[Pedro Martínez (tennis)|P Martínez]]
+| RD3-score02-1=1
+| RD3-score02-2=3
+| RD3-score02-3=1
+|-
+| RD3-seed03=19
+| RD3-team03='''{{flagicon|BUL}} [[Grigor Dimitrov|G Dimitrov]]'''
+| RD3-score03-1='''6'''
+| RD3-score03-2='''6'''
+| RD3-score03-3='''77 '''
+| RD3-seed04=PR
+| RD3-team04={{flagicon|AUT}} [[Sebastian Ofner|S Ofner]]
+| RD3-score04-1=3
+| RD3-score04-2=4
+| RD3-score04-3=60
+|-
+| RD4-seed01=1
+| RD4-team01='''{{flagicon|ITA}} [[Jannik Sinner|J Sinner]]'''
+| RD4-score01-1=3
+| RD4-score01-2=5
+| RD4-score01-3=2
+| RD4-seed02=19
+| RD4-team02={{flagicon|BUL}} [[Grigor Dimitrov|G Dimitrov]]
+| RD4-score02-1='''6'''
+| RD4-score02-2='''7'''
+| RD4-score02-3=2r
+}}
+
+==== Section 2 ====
+{{16TeamBracket-Compact-Tennis5
+| RD1=First round
+| RD2=Second round
+| RD3=Third round
+| RD4=Fourth round
+|-
+| RD1-seed01=10
+| RD1-team01='''{{flagicon|USA}} [[Ben Shelton|B Shelton]]'''
+| RD1-score01-1='''6'''
+| RD1-score01-2='''77 '''
+| RD1-score01-3='''77 '''
+| RD1-seed02=Q
+| RD1-team02={{flagicon|AUS}} [[Alex Bolt|A Bolt]]
+| RD1-score02-1=4
+| RD1-score02-2=61
+| RD1-score02-3=64
+|-
+| RD1-seed03=
+| RD1-team03='''{{flagicon|AUS}} [[Rinky Hijikata|R Hijikata]]'''
+| RD1-score03-1='''6'''
+| RD1-score03-2='''6'''
+| RD1-score03-3='''6'''
+| RD1-seed04=
+| RD1-team04={{flagicon|BEL}} [[David Goffin|D Goffin]]
+| RD1-score04-1=3
+| RD1-score04-2=1
+| RD1-score04-3=1
+|-
+| RD1-seed05=
+| RD1-team05={{flagicon|USA}} [[Aleksandar Kovacevic (tennis)|A Kovacevic]]
+| RD1-score05-1=3
+| RD1-score05-2='''77 '''
+| RD1-score05-3=1
+| RD1-score05-4='''7'''
+| RD1-score05-5=4
+| RD1-seed06=LL
+| RD1-team06='''{{flagicon|HUN}} [[Márton Fucsovics|M Fucsovics]]'''
+| RD1-score06-1='''6'''
+| RD1-score06-2=65
+| RD1-score06-3='''6'''
+| RD1-score06-4=5
+| RD1-score06-5='''6'''
+|-
+| RD1-seed07=
+| RD1-team07='''{{flagicon|FRA}} [[Gaël Monfils|G Monfils]]'''
+| RD1-score07-1='''6'''
+| RD1-score07-2=3
+| RD1-score07-3=65
+| RD1-score07-4='''7'''
+| RD1-score07-5='''6'''
+| RD1-seed08=18
+| RD1-team08={{flagicon|FRA}} [[Ugo Humbert|U Humbert]]
+| RD1-score08-1=4
+| RD1-score08-2='''6'''
+| RD1-score08-3='''77 '''
+| RD1-score08-4=5
+| RD1-score08-5=2
+|-
+| RD1-seed09=29
+| RD1-team09='''{{flagicon|USA}} [[Brandon Nakashima|B Nakashima]]'''
+| RD1-score09-1='''6'''
+| RD1-score09-2=4
+| RD1-score09-3='''77 '''
+| RD1-score09-4='''6'''
+| RD1-seed10=
+| RD1-team10={{flagicon|CHN}} [[Bu Yunchaokete|Y Bu]]
+| RD1-score10-1=4
+| RD1-score10-2='''6'''
+| RD1-score10-3=61
+| RD1-score10-4=4
+|-
+| RD1-seed11=
+| RD1-team11={{flagicon|KAZ}} [[Alexander Shevchenko (tennis)|A Shevchenko]]
+| RD1-score11-1=3
+| RD1-score11-2=5
+| RD1-score11-3=64
+| RD1-seed12=
+| RD1-team12='''{{flagicon|USA}} [[Reilly Opelka|R Opelka]]'''
+| RD1-score12-1='''6'''
+| RD1-score12-2='''7'''
+| RD1-score12-3='''77 '''
+|-
+| RD1-seed13=Q
+| RD1-team13={{flagicon|POR}} [[Jaime Faria|J Faria]]
+| RD1-score13-1=3
+| RD1-score13-2=4
+| RD1-score13-3=2
+| RD1-seed14=
+| RD1-team14='''{{flagicon|ITA}} [[Lorenzo Sonego|L Sonego]]'''
+| RD1-score14-1='''6'''
+| RD1-score14-2='''6'''
+| RD1-score14-3='''6'''
+|-
+| RD1-seed15=Q
+| RD1-team15='''{{flagicon|GEO}} [[Nikoloz Basilashvili|N Basilashvili]]'''
+| RD1-score15-1='''6'''
+| RD1-score15-2=4
+| RD1-score15-3='''7'''
+| RD1-score15-4='''6'''
+| RD1-seed16=7
+| RD1-team16={{flagicon|ITA}} [[Lorenzo Musetti|L Musetti]]
+| RD1-score16-1=2
+| RD1-score16-2='''6'''
+| RD1-score16-3=5
+| RD1-score16-4=1
+|-
+| RD2-seed01=10
+| RD2-team01='''{{flagicon|USA}} [[Ben Shelton|B Shelton]]'''
+| RD2-score01-1='''6'''
+| RD2-score01-2='''7'''
+| RD2-score01-3='''6'''
+| RD2-seed02=
+| RD2-team02={{flagicon|AUS}} [[Rinky Hijikata|R Hijikata]]
+| RD2-score02-1=2
+| RD2-score02-2=5
+| RD2-score02-3=4
+|-
+| RD2-seed03=LL
+| RD2-team03='''{{flagicon|HUN}} [[Márton Fucsovics|M Fucsovics]]'''
+| RD2-score03-1='''6'''
+| RD2-score03-2=1
+| RD2-score03-3=4
+| RD2-score03-4='''77 '''
+| RD2-score03-5='''6'''
+| RD2-seed04=
+| RD2-team04={{flagicon|FRA}} [[Gaël Monfils|G Monfils]]
+| RD2-score04-1=4
+| RD2-score04-2='''6'''
+| RD2-score04-3='''6'''
+| RD2-score04-4=65
+| RD2-score04-5=4
+|-
+| RD2-seed05=29
+| RD2-team05='''{{flagicon|USA}} [[Brandon Nakashima|B Nakashima]]'''
+| RD2-score05-1='''7'''
+| RD2-score05-2='''6'''
+| RD2-score05-3=66
+| RD2-score05-4='''6'''
+| RD2-seed06=
+| RD2-team06={{flagicon|USA}} [[Reilly Opelka|R Opelka]]
+| RD2-score06-1=5
+| RD2-score06-2=2
+| RD2-score06-3='''78 '''
+| RD2-score06-4=3
+|-
+| RD2-seed07=
+| RD2-team07='''{{flagicon|ITA}} [[Lorenzo Sonego|L Sonego]]'''
+| RD2-score07-1='''6'''
+| RD2-score07-2='''6'''
+| RD2-score07-3=63
+| RD2-score07-4='''77 '''
+| RD2-seed08=Q
+| RD2-team08={{flagicon|GEO}} [[Nikoloz Basilashvili|N Basilashvili]]
+| RD2-score08-1=1
+| RD2-score08-2=3
+| RD2-score08-3='''77 '''
+| RD2-score08-4=64
+|-
+| RD3-seed01=10
+| RD3-team01='''{{flagicon|USA}} [[Ben Shelton|B Shelton]]'''
+| RD3-score01-1='''6'''
+| RD3-score01-2='''77 '''
+| RD3-score01-3='''6'''
+| RD3-seed02=LL
+| RD3-team02={{flagicon|HUN}} [[Márton Fucsovics|M Fucsovics]]
+| RD3-score02-1=3
+| RD3-score02-2=64
+| RD3-score02-3=2
+|-
+| RD3-seed03=29
+| RD3-team03={{flagicon|USA}} [[Brandon Nakashima|B Nakashima]]
+| RD3-score03-1='''77 '''
+| RD3-score03-2=68
+| RD3-score03-3=62
+| RD3-score03-4='''6'''
+| RD3-score03-5=63
+| RD3-seed04=
+| RD3-team04={{flagicon|ITA}} '''[[Lorenzo Sonego|L Sonego]]'''
+| RD3-score04-1=65
+| RD3-score04-2='''710 '''
+| RD3-score04-3='''77 '''
+| RD3-score04-4=3
+| RD3-score04-5='''710 '''
+|-
+| RD4-seed01=10
+| RD4-team01='''{{flagicon|USA}} [[Ben Shelton|B Shelton]]'''
+| RD4-score01-1=3
+| RD4-score01-2='''6'''
+| RD4-score01-3='''77 '''
+| RD4-score01-4='''7'''
+| RD4-seed02=
+| RD4-team02={{flagicon|ITA}} [[Lorenzo Sonego|L Sonego]]
+| RD4-score02-1='''6'''
+| RD4-score02-2=1
+| RD4-score02-3=61
+| RD4-score02-4=5
+}}
+
+==== Section 3 ====
+{{16TeamBracket-Compact-Tennis5
+| RD1=First round
+| RD2=Second round
+| RD3=Third round
+| RD4=Fourth round
+|-
+| RD1-seed01=4
+| RD1-team01='''{{flagicon|GBR}} [[Jack Draper|J Draper]]'''
+| RD1-score01-1='''6'''
+| RD1-score01-2='''6'''
+| RD1-score01-3=2
+| RD1-seed02=
+| RD1-team02={{flagicon|ARG}} [[Sebastián Báez|S Báez]]
+| RD1-score02-1=2
+| RD1-score02-2=2
+| RD1-score02-3=1r
+|-
+| RD1-seed03=
+| RD1-team03={{flagicon|BEL}} [[Raphaël Collignon|R Collignon]]
+| RD1-score03-1=3
+| RD1-score03-2=4
+| RD1-score03-3=3
+| RD1-seed04=
+| RD1-team04='''{{flagicon|CRO}} [[Marin Čilić|M Čilić]]'''
+| RD1-score04-1='''6'''
+| RD1-score04-2='''6'''
+| RD1-score04-3='''6'''
+|-
+| RD1-seed05=Q
+| RD1-team05={{flagicon|AUS}} [[James McCabe (tennis)|J McCabe]]
+| RD1-score05-1=1
+| RD1-score05-2=4
+| RD1-score05-3=3
+| RD1-seed06=
+| RD1-team06='''{{flagicon|HUN}} [[Fábián Marozsán|F Marozsán]]'''
+| RD1-score06-1='''6'''
+| RD1-score06-2='''6'''
+| RD1-score06-3='''6'''
+|-
+| RD1-seed07=
+| RD1-team07='''{{flagicon|ESP}} [[Jaume Munar|J Munar]]'''
+| RD1-score07-1='''6'''
+| RD1-score07-2=3
+| RD1-score07-3=4
+| RD1-score07-4='''77 '''
+| RD1-score07-5='''6'''
+| RD1-seed08=28
+| RD1-team08={{flagicon|KAZ}} [[Alexander Bublik|A Bublik]]
+| RD1-score08-1=4
+| RD1-score08-2='''6'''
+| RD1-score08-3='''6'''
+| RD1-score08-4=65
+| RD1-score08-5=2
+|-
+| RD1-seed09=22
+| RD1-team09='''{{flagicon|ITA}} [[Flavio Cobolli|F Cobolli]]'''
+| RD1-score09-1='''6'''
+| RD1-score09-2='''79 '''
+| RD1-score09-3='''6'''
+| RD1-seed10=Q
+| RD1-team10={{flagicon|KAZ}} [[Beibit Zhukayev|B Zhukayev]]
+| RD1-score10-1=3
+| RD1-score10-2=67
+| RD1-score10-3=1
+|-
+| RD1-seed11=
+| RD1-team11={{flagicon|ARG}} [[Tomás Martín Etcheverry|TM Etcheverry]]
+| RD1-score11-1=64
+| RD1-score11-2=3
+| RD1-score11-3=5
+| RD1-seed12=WC
+| RD1-team12='''{{flagicon|GBR}} [[Jack Pinnington Jones|J Pinnington Jones]]'''
+| RD1-score12-1='''77 '''
+| RD1-score12-2='''6'''
+| RD1-score12-3='''7'''
+|-
+| RD1-seed13=
+| RD1-team13='''{{flagicon|USA}} [[Marcos Giron|M Giron]]'''
+| RD1-score13-1='''78 '''
+| RD1-score13-2='''77 '''
+| RD1-score13-3='''6'''
+| RD1-seed14=
+| RD1-team14={{flagicon|ARG}} [[Camilo Ugo Carabelli|C Ugo Carabelli]]
+| RD1-score14-1=66
+| RD1-score14-2=64
+| RD1-score14-3=3
+|-
+| RD1-seed15=
+| RD1-team15={{flagicon|FRA}} [[Hugo Gaston|H Gaston]]
+| RD1-score15-1=1
+| RD1-score15-2='''6'''
+| RD1-score15-3=2
+| RD1-score15-4=2
+| RD1-seed16=15
+| RD1-team16='''{{flagicon|CZE}} [[Jakub Menšík|J Menšík]]'''
+| RD1-score16-1='''6'''
+| RD1-score16-2=4
+| RD1-score16-3='''6'''
+| RD1-score16-4='''6'''
+|-
+| RD2-seed01=4
+| RD2-team01={{flagicon|GBR}} [[Jack Draper|J Draper]]
+| RD2-score01-1=4
+| RD2-score01-2=3
+| RD2-score01-3='''6'''
+| RD2-score01-4=4
+| RD2-seed02=
+| RD2-team02='''{{flagicon|CRO}} [[Marin Čilić|M Čilić]]'''
+| RD2-score02-1='''6'''
+| RD2-score02-2='''6'''
+| RD2-score02-3=1
+| RD2-score02-4='''6'''
+|-
+| RD2-seed03=
+| RD2-team03={{flagicon|HUN}} [[Fábián Marozsán|F Marozsán]]
+| RD2-score03-1=2
+| RD2-score03-2=3
+| RD2-score03-3=69
+| RD2-seed04=
+| RD2-team04='''{{flagicon|ESP}} [[Jaume Munar|J Munar]]'''
+| RD2-score04-1='''6'''
+| RD2-score04-2='''6'''
+| RD2-score04-3='''711 '''
+|-
+| RD2-seed05=22
+| RD2-team05='''{{flagicon|ITA}} [[Flavio Cobolli|F Cobolli]]'''
+| RD2-score05-1='''6'''
+| RD2-score05-2='''78 '''
+| RD2-score05-3='''6'''
+| RD2-seed06=WC
+| RD2-team06={{flagicon|GBR}} [[Jack Pinnington Jones|J Pinnington Jones]]
+| RD2-score06-1=1
+| RD2-score06-2=66
+| RD2-score06-3=2
+|-
+| RD2-seed07=
+| RD2-team07={{flagicon|USA}} [[Marcos Giron|M Giron]]
+| RD2-score07-1=4
+| RD2-score07-2='''6'''
+| RD2-score07-3=4
+| RD2-score07-4=64
+| RD2-seed08=15
+| RD2-team08='''{{flagicon|CZE}} [[Jakub Menšík|J Menšík]]'''
+| RD2-score08-1='''6'''
+| RD2-score08-2=3
+| RD2-score08-3='''6'''
+| RD2-score08-4='''77 '''
+|-
+| RD3-seed01=
+| RD3-team01='''{{flagicon|CRO}} [[Marin Čilić|M Čilić]]'''
+| RD3-score01-1='''6'''
+| RD3-score01-2=3
+| RD3-score01-3='''6'''
+| RD3-score01-4='''6'''
+| RD3-seed02=
+| RD3-team02={{flagicon|ESP}} [[Jaume Munar|J Munar]]
+| RD3-score02-1=3
+| RD3-score02-2='''6'''
+| RD3-score02-3=2
+| RD3-score02-4=4
+|-
+| RD3-seed03=22
+| RD3-team03='''{{flagicon|ITA}} [[Flavio Cobolli|F Cobolli]]'''
+| RD3-score03-1='''6'''
+| RD3-score03-2='''6'''
+| RD3-score03-3='''6'''
+| RD3-seed04=15
+| RD3-team04={{flagicon|CZE}} [[Jakub Menšík|J Menšík]]
+| RD3-score04-1=2
+| RD3-score04-2=4
+| RD3-score04-3=2
+|-
+| RD4-seed01=
+| RD4-team01={{flagicon|CRO}} [[Marin Čilić|M Čilić]]
+| RD4-score01-1=4
+| RD4-score01-2=4
+| RD4-score01-3='''77 '''
+| RD4-score01-4=63
+| RD4-seed02=22
+| RD4-team02='''{{flagicon|ITA}} [[Flavio Cobolli|F Cobolli]]'''
+| RD4-score02-1='''6'''
+| RD4-score02-2='''6'''
+| RD4-score02-3=64
+| RD4-score02-4='''77 '''
+}}
+
+==== Section 4 ====
+{{16TeamBracket-Compact-Tennis5
+| RD1=First round
+| RD2=Second round
+| RD3=Third round
+| RD4=Fourth round
+|-
+| RD1-seed01=11
+| RD1-team01='''{{flagicon|AUS}} [[Alex de Minaur|A de Minaur]]'''
+| RD1-score01-1='''6'''
+| RD1-score01-2='''6'''
+| RD1-score01-3='''77 '''
+| RD1-seed02=
+| RD1-team02={{flagicon|ESP}} [[Roberto Carballés Baena|R Carballés Baena]]
+| RD1-score02-1=2
+| RD1-score02-2=2
+| RD1-score02-3=62
+|-
+| RD1-seed03=Q
+| RD1-team03='''{{flagicon|FRA}} [[Arthur Cazaux|A Cazaux]]'''
+| RD1-score03-1='''6'''
+| RD1-score03-2='''78 '''
+| RD1-score03-3=4
+| RD1-score03-4=65
+| RD1-score03-5='''6'''
+| RD1-seed04=
+| RD1-team04={{flagicon|AUS}} [[Adam Walton|A Walton]]
+| RD1-score04-1=3
+| RD1-score04-2=66
+| RD1-score04-3='''6'''
+| RD1-score04-4='''77 '''
+| RD1-score04-5=1
+|-
+| RD1-seed05=
+| RD1-team05={{flagicon|FRA}} [[Quentin Halys|Q Halys]]
+| RD1-score05-1=64
+| RD1-score05-2=3
+| RD1-score05-3=4
+| RD1-seed06=Q
+| RD1-team06='''{{flagicon|DEN}} [[August Holmgren (tennis)|A Holmgren]]'''
+| RD1-score06-1='''77 '''
+| RD1-score06-2='''6'''
+| RD1-score06-3='''6'''
+|-
+| RD1-seed07=
+| RD1-team07={{flagicon|BIH}} [[Damir Džumhur|D Džumhur]]
+| RD1-score07-1=3
+| RD1-score07-2=2
+| RD1-score07-3=4
+| RD1-seed08=21
+| RD1-team08='''{{flagicon|CZE}} [[Tomáš Macháč|T Macháč]]'''
+| RD1-score08-1='''6'''
+| RD1-score08-2='''6'''
+| RD1-score08-3='''6'''
+|-
+| RD1-seed09=30
+| RD1-team09={{flagicon|USA}} [[Alex Michelsen|A Michelsen]]
+| RD1-score09-1=2
+| RD1-score09-2='''6'''
+| RD1-score09-3=3
+| RD1-score09-4='''6'''
+| RD1-score09-5=66
+| RD1-seed10=
+| RD1-team10='''{{flagicon|SRB}} [[Miomir Kecmanović|M Kecmanović]]'''
+| RD1-score10-1='''6'''
+| RD1-score10-2=3
+| RD1-score10-3='''6'''
+| RD1-score10-4=3
+| RD1-score10-5='''710 '''
+|-
+| RD1-seed11=
+| RD1-team11='''{{flagicon|NED}} [[Jesper de Jong|J de Jong]]'''
+| RD1-score11-1='''6'''
+| RD1-score11-2=65
+| RD1-score11-3=67
+| RD1-score11-4='''6'''
+| RD1-score11-5='''710 '''
+| RD1-seed12=
+| RD1-team12={{flagicon|USA}} [[Christopher Eubanks|C Eubanks]]
+| RD1-score12-1=3
+| RD1-score12-2='''77 '''
+| RD1-score12-3='''79 '''
+| RD1-score12-4=3
+| RD1-score12-5=63
+|-
+| RD1-seed13=WC
+| RD1-team13='''{{flagicon|GBR}} [[Dan Evans (tennis)|D Evans]]'''
+| RD1-score13-1='''6'''
+| RD1-score13-2='''7'''
+| RD1-score13-3='''6'''
+| RD1-seed14=WC
+| RD1-team14={{flagicon|GBR}} [[Jay Clarke (tennis)|J Clarke]]
+| RD1-score14-1=1
+| RD1-score14-2=5
+| RD1-score14-3=2
+|-
+| RD1-seed15=
+| RD1-team15={{flagicon|FRA}} [[Alexandre Müller|A Müller]]
+| RD1-score15-1=1
+| RD1-score15-2='''79 '''
+| RD1-score15-3=2
+| RD1-score15-4=2
+| RD1-seed16=6
+| RD1-team16='''{{flagicon|SRB}} [[Novak Djokovic|N Djokovic]]'''
+| RD1-score16-1='''6'''
+| RD1-score16-2=67
+| RD1-score16-3='''6'''
+| RD1-score16-4='''6'''
+|-
+| RD2-seed01=11
+| RD2-team01='''{{flagicon|AUS}} [[Alex de Minaur|A de Minaur]]'''
+| RD2-score01-1=4
+| RD2-score01-2='''6'''
+| RD2-score01-3='''6'''
+| RD2-score01-4='''6'''
+| RD2-seed02=Q
+| RD2-team02={{flagicon|FRA}} [[Arthur Cazaux|A Cazaux]]
+| RD2-score02-1='''6'''
+| RD2-score02-2=2
+| RD2-score02-3=4
+| RD2-score02-4=0
+|-
+| RD2-seed03=Q
+| RD2-team03='''{{flagicon|DEN}} [[August Holmgren (tennis)|A Holmgren]]'''
+| RD2-score03-1='''77 '''
+| RD2-score03-2=68
+| RD2-score03-3=65
+| RD2-score03-4='''7'''
+| RD2-score03-5='''710 '''
+| RD2-seed04=21
+| RD2-team04={{flagicon|CZE}} [[Tomáš Macháč|T Macháč]]
+| RD2-score04-1=65
+| RD2-score04-2='''710 '''
+| RD2-score04-3='''77 '''
+| RD2-score04-4=5
+| RD2-score04-5=65
+|-
+| RD2-seed05=
+| RD2-team05='''{{flagicon|SRB}} [[Miomir Kecmanović|M Kecmanović]]'''
+| RD2-score05-1=1
+| RD2-score05-2='''6'''
+| RD2-score05-3='''6'''
+| RD2-score05-4='''6'''
+| RD2-seed06=
+| RD2-team06={{flagicon|NED}} [[Jesper de Jong|J de Jong]]
+| RD2-score06-1='''6'''
+| RD2-score06-2=3
+| RD2-score06-3=2
+| RD2-score06-4=4
+|-
+| RD2-seed07=WC
+| RD2-team07={{flagicon|GBR}} [[Dan Evans (tennis)|D Evans]]
+| RD2-score07-1=3
+| RD2-score07-2=2
+| RD2-score07-3=0
+| RD2-seed08=6
+| RD2-team08='''{{flagicon|SRB}} [[Novak Djokovic|N Djokovic]]'''
+| RD2-score08-1='''6'''
+| RD2-score08-2='''6'''
+| RD2-score08-3='''6'''
+|-
+| RD3-seed01=11
+| RD3-team01='''{{flagicon|AUS}} [[Alex de Minaur|A de Minaur]]'''
+| RD3-score01-1='''6'''
+| RD3-score01-2='''77 '''
+| RD3-score01-3='''6'''
+| RD3-seed02=Q
+| RD3-team02={{flagicon|DEN}} [[August Holmgren (tennis)|A Holmgren]]
+| RD3-score02-1=4
+| RD3-score02-2=65
+| RD3-score02-3=3
+|-
+| RD3-seed03=
+| RD3-team03={{flagicon|SRB}} [[Miomir Kecmanović|M Kecmanović]]
+| RD3-score03-1=3
+| RD3-score03-2=0
+| RD3-score03-3=4
+| RD3-seed04=6
+| RD3-team04={{flagicon|SRB}} '''[[Novak Djokovic|N Djokovic]]'''
+| RD3-score04-1='''6'''
+| RD3-score04-2='''6'''
+| RD3-score04-3='''6'''
+|-
+| RD4-seed01=11
+| RD4-team01={{flagicon|AUS}} [[Alex de Minaur|A de Minaur]]
+| RD4-score01-1='''6'''
+| RD4-score01-2=4
+| RD4-score01-3=4
+| RD4-score01-4=4
+| RD4-seed02=6
+| RD4-team02='''{{flagicon|SRB}} [[Novak Djokovic|N Djokovic]]'''
+| RD4-score02-1=1
+| RD4-score02-2='''6'''
+| RD4-score02-3='''6'''
+| RD4-score02-4='''6'''
+}}
+
+=== Bottom half ===
+
+==== Section 5 ====
+{{16TeamBracket-Compact-Tennis5
+| RD1=First round
+| RD2=Second round
+| RD3=Third round
+| RD4=Fourth round
+|-
+| RD1-seed01=5
+| RD1-team01='''{{flagicon|USA}} [[Taylor Fritz|T Fritz]]'''
+| RD1-score01-1=66
+| RD1-score01-2=68
+| RD1-score01-3='''6'''
+| RD1-score01-4='''78 '''
+| RD1-score01-5='''6'''
+| RD1-seed02=
+| RD1-team02={{flagicon|FRA}} [[Giovanni Mpetshi Perricard|G Mpetshi Perricard]]
+| RD1-score02-1='''78 '''
+| RD1-score02-2='''710 '''
+| RD1-score02-3=4
+| RD1-score02-4=66
+| RD1-score02-5=4
+|-
+| RD1-seed03=
+| RD1-team03='''{{flagicon|CAN}} [[Gabriel Diallo|G Diallo]]'''
+| RD1-score03-1='''6'''
+| RD1-score03-2='''6'''
+| RD1-score03-3='''6'''
+| RD1-seed04=
+| RD1-team04={{flagicon|GER}} [[Daniel Altmaier|D Altmaier]]
+| RD1-score04-1=1
+| RD1-score04-2=2
+| RD1-score04-3=4
+|-
+| RD1-seed05=
+| RD1-team05={{flagicon|ITA}} [[Matteo Arnaldi|M Arnaldi]]
+| RD1-score05-1=64
+| RD1-score05-2=65
+| RD1-score05-3=4
+| RD1-seed06=
+| RD1-team06='''{{flagicon|NED}} [[Botic van de Zandschulp|B van de Zandschulp]]'''
+| RD1-score06-1='''77 '''
+| RD1-score06-2='''77 '''
+| RD1-score06-3='''6'''
+|-
+| RD1-seed07=
+| RD1-team07={{flagicon|USA}} [[Brandon Holt|B Holt]]
+| RD1-score07-1=2
+| RD1-score07-2=4
+| RD1-score07-3=5
+| RD1-seed08=26
+| RD1-team08='''{{flagicon|ESP}} [[Alejandro Davidovich Fokina|A Davidovich Fokina]]'''
+| RD1-score08-1='''6'''
+| RD1-score08-2='''6'''
+| RD1-score08-3='''7'''
+|-
+| RD1-seed09=20
+| RD1-team09={{flagicon|AUS}} [[Alexei Popyrin|A Popyrin]]
+| RD1-score09-1=4
+| RD1-score09-2=1
+| RD1-score09-3='''6'''
+| RD1-score09-4=4
+| RD1-seed10=WC
+| RD1-team10='''{{flagicon|GBR}} [[Arthur Fery|A Fery]]'''
+| RD1-score10-1='''6'''
+| RD1-score10-2='''6'''
+| RD1-score10-3=4
+| RD1-score10-4='''6'''
+|-
+| RD1-seed11=
+| RD1-team11='''{{flagicon|ITA}} [[Luciano Darderi|L Darderi]]'''
+| RD1-score11-1='''77 '''
+| RD1-score11-2=1
+| RD1-score11-3=62
+| RD1-score11-4='''6'''
+| RD1-score11-5='''6'''
+| RD1-seed12=
+| RD1-team12={{flagicon|}} [[Roman Safiullin|R Safiullin]]
+| RD1-score12-1=63
+| RD1-score12-2='''6'''
+| RD1-score12-3='''77 '''
+| RD1-score12-4=3
+| RD1-score12-5=1
+|-
+| RD1-seed13=
+| RD1-team13={{flagicon|CZE}} [[Vít Kopřiva|V Kopřiva]]
+| RD1-score13-1='''6'''
+| RD1-score13-2='''6'''
+| RD1-score13-3=3
+| RD1-score13-4=61
+| RD1-score13-5=1
+| RD1-seed14=
+| RD1-team14='''{{flagicon|AUS}} [[Jordan Thompson (tennis)|J Thompson]]'''
+| RD1-score14-1=3
+| RD1-score14-2=4
+| RD1-score14-3='''6'''
+| RD1-score14-4='''77 '''
+| RD1-score14-5='''6'''
+|-
+| RD1-seed15=
+| RD1-team15='''{{flagicon|FRA}} [[Benjamin Bonzi|B Bonzi]]'''
+| RD1-score15-1='''77 '''
+| RD1-score15-2=3
+| RD1-score15-3='''77 '''
+| RD1-score15-4='''6'''
+| RD1-seed16=9
+| RD1-team16={{flagicon|}} [[Daniil Medvedev|D Medvedev]]
+| RD1-score16-1=62
+| RD1-score16-2='''6'''
+| RD1-score16-3=63
+| RD1-score16-4=2
+|-
+| RD2-seed01=5
+| RD2-team01='''{{flagicon|USA}} [[Taylor Fritz|T Fritz]]'''
+| RD2-score01-1=3
+| RD2-score01-2='''6'''
+| RD2-score01-3='''77 '''
+| RD2-score01-4=4
+| RD2-score01-5='''6'''
+| RD2-seed02=
+| RD2-team02={{flagicon|CAN}} [[Gabriel Diallo|G Diallo]]
+| RD2-score02-1='''6'''
+| RD2-score02-2=3
+| RD2-score02-3=60
+| RD2-score02-4='''6'''
+| RD2-score02-5=3
+|-
+| RD2-seed03=
+| RD2-team03={{flagicon|NED}} [[Botic van de Zandschulp|B van de Zandschulp]]
+| RD2-score03-1=1
+| RD2-score03-2='''6'''
+| RD2-score03-3=3
+| RD2-score03-4=65
+| RD2-seed04=26
+| RD2-team04='''{{flagicon|ESP}} [[Alejandro Davidovich Fokina|A Davidovich Fokina]]'''
+| RD2-score04-1='''6'''
+| RD2-score04-2=4
+| RD2-score04-3='''6'''
+| RD2-score04-4='''77 '''
+|-
+| RD2-seed05=WC
+| RD2-team05={{flagicon|GBR}} [[Arthur Fery|A Fery]]
+| RD2-score05-1=4
+| RD2-score05-2=3
+| RD2-score05-3=3
+| RD2-seed06=
+| RD2-team06='''{{flagicon|ITA}} [[Luciano Darderi|L Darderi]]'''
+| RD2-score06-1='''6'''
+| RD2-score06-2='''6'''
+| RD2-score06-3='''6'''
+|-
+| RD2-seed07=
+| RD2-team07='''{{flagicon|AUS}} [[Jordan Thompson (tennis)|J Thompson]]'''
+| RD2-score07-1='''7'''
+| RD2-score07-2=62
+| RD2-score07-3=4
+| RD2-score07-4='''6'''
+| RD2-score07-5='''6'''
+| RD2-seed08=
+| RD2-team08={{flagicon|FRA}} [[Benjamin Bonzi|B Bonzi]]
+| RD2-score08-1=5
+| RD2-score08-2='''77 '''
+| RD2-score08-3='''6'''
+| RD2-score08-4=2
+| RD2-score08-5=4
+|-
+| RD3-seed01=5
+| RD3-team01='''{{flagicon|USA}} [[Taylor Fritz|T Fritz]]'''
+| RD3-score01-1='''6'''
+| RD3-score01-2='''6'''
+| RD3-score01-3=65
+| RD3-score01-4='''6'''
+| RD3-seed02=26
+| RD3-team02={{flagicon|ESP}} [[Alejandro Davidovich Fokina|A Davidovich Fokina]]
+| RD3-score02-1=4
+| RD3-score02-2=3
+| RD3-score02-3='''77 '''
+| RD3-score02-4=1
+|-
+| RD3-seed03=
+| RD3-team03={{flagicon|ITA}} [[Luciano Darderi|L Darderi]]
+| RD3-score03-1=4
+| RD3-score03-2=4
+| RD3-score03-3='''6'''
+| RD3-score03-4=3
+| RD3-seed04=
+| RD3-team04='''{{flagicon|AUS}} [[Jordan Thompson (tennis)|J Thompson]]'''
+| RD3-score04-1='''6'''
+| RD3-score04-2='''6'''
+| RD3-score04-3=3
+| RD3-score04-4='''6'''
+|-
+| RD4-seed01=5
+| RD4-team01='''{{flagicon|USA}} [[Taylor Fritz|T Fritz]]'''
+| RD4-score01-1='''6'''
+| RD4-score01-2=3
+| RD4-seed02=
+| RD4-team02={{flagicon|AUS}} [[Jordan Thompson (tennis)|J Thompson]]
+| RD4-score02-1=1
+| RD4-score02-2=0r
+}}
+
+==== Section 6 ====
+{{16TeamBracket-Compact-Tennis5
+| RD1=First round
+| RD2=Second round
+| RD3=Third round
+| RD4=Fourth round
+|-
+| RD1-seed01=16
+| RD1-team01={{flagicon|ARG}} [[Francisco Cerúndolo|F Cerúndolo]]
+| RD1-score01-1='''6'''
+| RD1-score01-2=3
+| RD1-score01-3=65
+| RD1-score01-4=0
+| RD1-seed02=
+| RD1-team02='''{{flagicon|POR}} [[Nuno Borges (tennis)|N Borges]]'''
+| RD1-score02-1=4
+| RD1-score02-2='''6'''
+| RD1-score02-3='''77 '''
+| RD1-score02-4='''6'''
+|-
+| RD1-seed03=
+| RD1-team03='''{{flagicon|GBR}} [[Billy Harris (tennis)|B Harris]]'''
+| RD1-score03-1='''6'''
+| RD1-score03-2='''6'''
+| RD1-score03-3='''6'''
+| RD1-seed04=LL
+| RD1-team04={{flagicon|SRB}} [[Dušan Lajović|D Lajović]]
+| RD1-score04-1=3
+| RD1-score04-2=2
+| RD1-score04-3=4
+|-
+| RD1-seed05=Q
+| RD1-team05='''{{flagicon|JPN}} [[Shintaro Mochizuki|S Mochizuki]]'''
+| RD1-score05-1=2
+| RD1-score05-2=3
+| RD1-score05-3='''6'''
+| RD1-score05-4='''78 '''
+| RD1-score05-5='''7'''
+| RD1-seed06=Q
+| RD1-team06={{flagicon|ITA}} [[Giulio Zeppieri|G Zeppieri]]
+| RD1-score06-1='''6'''
+| RD1-score06-2='''6'''
+| RD1-score06-3=3
+| RD1-score06-4=66
+| RD1-score06-5=5
+|-
+| RD1-seed07=
+| RD1-team07={{flagicon|USA}} [[Mackenzie McDonald|M McDonald]]
+| RD1-score07-1=5
+| RD1-score07-2=4
+| RD1-score07-3=4
+| RD1-seed08=17
+| RD1-team08='''{{flagicon|}} [[Karen Khachanov|K Khachanov]]'''
+| RD1-score08-1='''7'''
+| RD1-score08-2='''6'''
+| RD1-score08-3='''6'''
+|-
+| RD1-seed09=32
+| RD1-team09={{flagicon|ITA}} [[Matteo Berrettini|M Berrettini]]
+| RD1-score09-1='''6'''
+| RD1-score09-2=2
+| RD1-score09-3=4
+| RD1-score09-4='''7'''
+| RD1-score09-5=3
+| RD1-seed10=
+| RD1-team10='''{{flagicon|POL}} [[Kamil Majchrzak|K Majchrzak]]'''
+| RD1-score10-1=4
+| RD1-score10-2='''6'''
+| RD1-score10-3='''6'''
+| RD1-score10-4=5
+| RD1-score10-5='''6'''
+|-
+| RD1-seed11=
+| RD1-team11='''{{flagicon|USA}} [[Ethan Quinn|E Quinn]]'''
+| RD1-score11-1=4
+| RD1-score11-2='''6'''
+| RD1-score11-3='''713 '''
+| RD1-score11-4='''6'''
+| RD1-seed12=WC
+| RD1-team12={{flagicon|GBR}} [[Henry Searle (tennis)|H Searle]]
+| RD1-score12-1='''6'''
+| RD1-score12-2=2
+| RD1-score12-3=611
+| RD1-score12-4=2
+|-
+| RD1-seed13=LL
+| RD1-team13='''{{flagicon|CHI}} [[Cristian Garín|C Garín]]'''
+| RD1-score13-1='''710 '''
+| RD1-score13-2='''6'''
+| RD1-score13-3='''6'''
+| RD1-seed14=Q
+| RD1-team14={{flagicon|LUX}} [[Chris Rodesch|C Rodesch]]
+| RD1-score14-1=68
+| RD1-score14-2=4
+| RD1-score14-3=4
+|-
+| RD1-seed15=
+| RD1-team15='''{{flagicon|FRA}} [[Arthur Rinderknech|A Rinderknech]]'''
+| RD1-score15-1='''77 '''
+| RD1-score15-2=68
+| RD1-score15-3='''6'''
+| RD1-score15-4=65
+| RD1-score15-5='''6'''
+| RD1-seed16=3
+| RD1-team16={{flagicon|GER}} [[Alexander Zverev|A Zverev]]
+| RD1-score16-1=63
+| RD1-score16-2='''710 '''
+| RD1-score16-3=3
+| RD1-score16-4='''77 '''
+| RD1-score16-5=4
+|-
+| RD2-seed01=
+| RD2-team01='''{{flagicon|POR}} [[Nuno Borges (tennis)|N Borges]]'''
+| RD2-score01-1='''6'''
+| RD2-score01-2='''6'''
+| RD2-score01-3='''79 '''
+| RD2-seed02=
+| RD2-team02={{flagicon|GBR}} [[Billy Harris (tennis)|B Harris]]
+| RD2-score02-1=3
+| RD2-score02-2=4
+| RD2-score02-3=67
+|-
+| RD2-seed03=Q
+| RD2-team03={{flagicon|JPN}} [[Shintaro Mochizuki|S Mochizuki]]
+| RD2-score03-1='''6'''
+| RD2-score03-2=67
+| RD2-score03-3='''6'''
+| RD2-score03-4=3
+| RD2-score03-5=4
+| RD2-seed04=17
+| RD2-team04='''{{flagicon|}} [[Karen Khachanov|K Khachanov]]'''
+| RD2-score04-1=1
+| RD2-score04-2='''79 '''
+| RD2-score04-3=4
+| RD2-score04-4='''6'''
+| RD2-score04-5='''6'''
+|-
+| RD2-seed05=
+| RD2-team05='''{{flagicon|POL}} [[Kamil Majchrzak|K Majchrzak]]'''
+| RD2-score05-1='''6'''
+| RD2-score05-2='''6'''
+| RD2-score05-3='''6'''
+| RD2-seed06=
+| RD2-team06={{flagicon|USA}} [[Ethan Quinn|E Quinn]]
+| RD2-score06-1=1
+| RD2-score06-2=4
+| RD2-score06-3=3
+|-
+| RD2-seed07=LL
+| RD2-team07={{flagicon|CHI}} [[Cristian Garín|C Garín]]
+| RD2-score07-1='''6'''
+| RD2-score07-2=3
+| RD2-score07-3=63
+| RD2-score07-4='''6'''
+| RD2-score07-5=3
+| RD2-seed08=
+| RD2-team08='''{{flagicon|FRA}} [[Arthur Rinderknech|A Rinderknech]]'''
+| RD2-score08-1=3
+| RD2-score08-2='''6'''
+| RD2-score08-3='''77 '''
+| RD2-score08-4=4
+| RD2-score08-5='''6'''
+|-
+| RD3-seed01=
+| RD3-team01={{flagicon|POR}} [[Nuno Borges (tennis)|N Borges]]
+| RD3-score01-1=66
+| RD3-score01-2='''6'''
+| RD3-score01-3='''6'''
+| RD3-score01-4=3
+| RD3-score01-5=68
+| RD3-seed02=17
+| RD3-team02='''{{flagicon|}} [[Karen Khachanov|K Khachanov]]'''
+| RD3-score02-1='''78 '''
+| RD3-score02-2=4
+| RD3-score02-3=4
+| RD3-score02-4='''6'''
+| RD3-score02-5='''710 '''
+|-
+| RD3-seed03=
+| RD3-team03='''{{flagicon|POL}} [[Kamil Majchrzak|K Majchrzak]]'''
+| RD3-score03-1='''6'''
+| RD3-score03-2='''77 '''
+| RD3-score03-3='''78 '''
+| RD3-seed04=
+| RD3-team04={{flagicon|FRA}} [[Arthur Rinderknech|A Rinderknech]]
+| RD3-score04-1=3
+| RD3-score04-2=64
+| RD3-score04-3=66
+|-
+| RD4-seed01=17
+| RD4-team01='''{{flagicon|}} [[Karen Khachanov|K Khachanov]]'''
+| RD4-score01-1='''6'''
+| RD4-score01-2='''6'''
+| RD4-score01-3='''6'''
+| RD4-seed02=
+| RD4-team02={{flagicon|POL}} [[Kamil Majchrzak|K Majchrzak]]
+| RD4-score02-1=4
+| RD4-score02-2=2
+| RD4-score02-3=3
+}}
+
+==== Section 7 ====
+{{16TeamBracket-Compact-Tennis5
+| RD1=First round
+| RD2=Second round
+| RD3=Third round
+| RD4=Fourth round
+|-
+| RD1-seed01=8
+| RD1-team01={{flagicon|DEN}} [[Holger Rune|H Rune]]
+| RD1-score01-1='''6'''
+| RD1-score01-2='''6'''
+| RD1-score01-3=5
+| RD1-score01-4=3
+| RD1-score01-5=4
+| RD1-seed02=Q
+| RD1-team02='''{{flagicon|CHI}} [[Nicolás Jarry|N Jarry]]'''
+| RD1-score02-1=4
+| RD1-score02-2=4
+| RD1-score02-3='''7'''
+| RD1-score02-4='''6'''
+| RD1-score02-5='''6'''
+|-
+| RD1-seed03=
+| RD1-team03='''{{flagicon|USA}} [[Learner Tien|L Tien]]'''
+| RD1-score03-1='''77 '''
+| RD1-score03-2='''6'''
+| RD1-score03-3='''6'''
+| RD1-seed04=
+| RD1-team04={{flagicon|USA}} [[Nishesh Basavareddy|N Basavareddy]]
+| RD1-score04-1=65
+| RD1-score04-2=3
+| RD1-score04-3=2
+|-
+| RD1-seed05=
+| RD1-team05={{flagicon|GBR}} [[Jacob Fearnley|J Fearnley]]
+| RD1-score05-1=4
+| RD1-score05-2=1
+| RD1-score05-3=65
+| RD1-seed06=
+| RD1-team06='''{{flagicon|BRA}} [[João Fonseca (tennis)|J Fonseca]]'''
+| RD1-score06-1='''6'''
+| RD1-score06-2='''6'''
+| RD1-score06-3='''77 '''
+|-
+| RD1-seed07=PR
+| RD1-team07='''{{flagicon|USA}} [[Jenson Brooksby|J Brooksby]]'''
+| RD1-score07-1='''6'''
+| RD1-score07-2='''7'''
+| RD1-score07-3='''6'''
+| RD1-seed08=31
+| RD1-team08={{flagicon|NED}} [[Tallon Griekspoor|T Griekspoor]]
+| RD1-score08-1=2
+| RD1-score08-2=5
+| RD1-score08-3=3
+|-
+| RD1-seed09=23
+| RD1-team09='''{{flagicon|CZE}} [[Jiří Lehečka|J Lehečka]]'''
+| RD1-score09-1=4
+| RD1-score09-2='''6'''
+| RD1-score09-3='''6'''
+| RD1-score09-4='''77 '''
+| RD1-seed10=
+| RD1-team10={{flagicon|BOL}} [[Hugo Dellien|H Dellien]]
+| RD1-score10-1='''6'''
+| RD1-score10-2=2
+| RD1-score10-3=2
+| RD1-score10-4=60
+|-
+| RD1-seed11=
+| RD1-team11='''{{flagicon|ITA}} [[Mattia Bellucci|M Bellucci]]'''
+| RD1-score11-1=62
+| RD1-score11-2='''6'''
+| RD1-score11-3='''6'''
+| RD1-score11-4='''6'''
+| RD1-seed12=WC
+| RD1-team12={{flagicon|GBR}} [[Oliver Crawford (tennis)|O Crawford]]
+| RD1-score12-1='''77 '''
+| RD1-score12-2=3
+| RD1-score12-3=4
+| RD1-score12-4=4
+|-
+| RD1-seed13=
+| RD1-team13='''{{flagicon|GBR}} [[Cameron Norrie|C Norrie]]'''
+| RD1-score13-1='''6'''
+| RD1-score13-2=3
+| RD1-score13-3='''6'''
+| RD1-score13-4='''77 '''
+| RD1-seed14=
+| RD1-team14={{flagicon|ESP}} [[Roberto Bautista Agut|R Bautista Agut]]
+| RD1-score14-1=3
+| RD1-score14-2='''6'''
+| RD1-score14-3=4
+| RD1-score14-4=63
+|-
+| RD1-seed15=
+| RD1-team15={{flagicon|DEN}} [[Elmer Møller|E Møller]]
+| RD1-score15-1=3
+| RD1-score15-2=4
+| RD1-score15-3=2
+| RD1-seed16=12
+| RD1-team16='''{{flagicon|USA}} [[Frances Tiafoe|F Tiafoe]]'''
+| RD1-score16-1='''6'''
+| RD1-score16-2='''6'''
+| RD1-score16-3='''6'''
+|-
+| RD2-seed01=Q
+| RD2-team01='''{{flagicon|CHI}} [[Nicolás Jarry|N Jarry]]'''
+| RD2-score01-1='''6'''
+| RD2-score01-2='''6'''
+| RD2-score01-3='''6'''
+| RD2-seed02=
+| RD2-team02={{flagicon|USA}} [[Learner Tien|L Tien]]
+| RD2-score02-1=2
+| RD2-score02-2=2
+| RD2-score02-3=3
+|-
+| RD2-seed03=
+| RD2-team03='''{{flagicon|BRA}} [[João Fonseca (tennis)|J Fonseca]]'''
+| RD2-score03-1='''6'''
+| RD2-score03-2=5
+| RD2-score03-3='''6'''
+| RD2-score03-4='''6'''
+| RD2-seed04=PR
+| RD2-team04={{flagicon|USA}} [[Jenson Brooksby|J Brooksby]]
+| RD2-score04-1=4
+| RD2-score04-2='''7'''
+| RD2-score04-3=2
+| RD2-score04-4=4
+|-
+| RD2-seed05=23
+| RD2-team05={{flagicon|CZE}} [[Jiří Lehečka|J Lehečka]]
+| RD2-score05-1=64
+| RD2-score05-2=1
+| RD2-score05-3=5
+| RD2-seed06=
+| RD2-team06='''{{flagicon|ITA}} [[Mattia Bellucci|M Bellucci]]'''
+| RD2-score06-1='''77 '''
+| RD2-score06-2='''6'''
+| RD2-score06-3='''7'''
+|-
+| RD2-seed07=
+| RD2-team07='''{{flagicon|GBR}} [[Cameron Norrie|C Norrie]]'''
+| RD2-score07-1=4
+| RD2-score07-2='''6'''
+| RD2-score07-3='''6'''
+| RD2-score07-4='''7'''
+| RD2-seed08=12
+| RD2-team08={{flagicon|USA}} [[Frances Tiafoe|F Tiafoe]]
+| RD2-score08-1='''6'''
+| RD2-score08-2=4
+| RD2-score08-3=3
+| RD2-score08-4=5
+|-
+| RD3-seed01=Q
+| RD3-team01='''{{flagicon|CHI}} [[Nicolás Jarry|N Jarry]]'''
+| RD3-score01-1='''6'''
+| RD3-score01-2='''6'''
+| RD3-score01-3=3
+| RD3-score01-4='''77 '''
+| RD3-seed02=
+| RD3-team02={{flagicon|BRA}} [[João Fonseca (tennis)|J Fonseca]]
+| RD3-score02-1=3
+| RD3-score02-2=4
+| RD3-score02-3='''6'''
+| RD3-score02-4=64
+|-
+| RD3-seed03=
+| RD3-team03={{flagicon|ITA}} [[Mattia Bellucci|M Bellucci]]
+| RD3-score03-1=65
+| RD3-score03-2=4
+| RD3-score03-3=3
+| RD3-seed04=
+| RD3-team04='''{{flagicon|GBR}} [[Cameron Norrie|C Norrie]]'''
+| RD3-score04-1='''77 '''
+| RD3-score04-2='''6'''
+| RD3-score04-3='''6'''
+|-
+| RD4-seed01=Q
+| RD4-team01={{flagicon|CHI}} [[Nicolás Jarry|N Jarry]]
+| RD4-score01-1=3
+| RD4-score01-2=64
+| RD4-score01-3='''79 '''
+| RD4-score01-4='''77 '''
+| RD4-score01-5=3
+| RD4-seed02=
+| RD4-team02='''{{flagicon|GBR}} [[Cameron Norrie|C Norrie]]'''
+| RD4-score02-1='''6'''
+| RD4-score02-2='''77 '''
+| RD4-score02-3=67
+| RD4-score02-4=65
+| RD4-score02-5='''6'''
+}}
+
+==== Section 8 ====
+{{16TeamBracket-Compact-Tennis5
+| RD1=First round
+| RD2=Second round
+| RD3=Third round
+| RD4=Fourth round
+|-
+| RD1-seed01=14
+| RD1-team01='''{{flagicon|}} [[Andrey Rublev|A Rublev]]'''
+| RD1-score01-1='''6'''
+| RD1-score01-2='''77 '''
+| RD1-score01-3=69
+| RD1-score01-4='''78 '''
+| RD1-seed02=
+| RD1-team02={{flagicon|SRB}} [[Laslo Djere|L Djere]]
+| RD1-score02-1=0
+| RD1-score02-2=65
+| RD1-score02-3='''711 '''
+| RD1-score02-4=66
+|-
+| RD1-seed03=
+| RD1-team03={{flagicon|BEL}} [[Zizou Bergs|Z Bergs]]
+| RD1-score03-1=67
+| RD1-score03-2=62
+| RD1-score03-3='''77 '''
+| RD1-score03-4=2
+| RD1-seed04=PR
+| RD1-team04='''{{flagicon|RSA}} [[Lloyd Harris (tennis)|L Harris]]'''
+| RD1-score04-1='''79 '''
+| RD1-score04-2='''77 '''
+| RD1-score04-3=65
+| RD1-score04-4='''6'''
+|-
+| RD1-seed05=Q
+| RD1-team05='''{{flagicon|FRA}} [[Adrian Mannarino|A Mannarino]]'''
+| RD1-score05-1='''6'''
+| RD1-score05-2='''6'''
+| RD1-score05-3='''6'''
+| RD1-seed06=
+| RD1-team06={{flagicon|AUS}} [[Christopher O'Connell|C O'Connell]]
+| RD1-score06-1=2
+| RD1-score06-2=4
+| RD1-score06-3=3
+|-
+| RD1-seed07=Q
+| RD1-team07='''{{flagicon|FRA}} [[Valentin Royer|V Royer]]'''
+| RD1-score07-1='''6'''
+| RD1-score07-2='''6'''
+| RD1-score07-3=0
+| RD1-seed08=24
+| RD1-team08={{flagicon|GRE}} [[Stefanos Tsitsipas|S Tsitsipas]]
+| RD1-score08-1=3
+| RD1-score08-2=2
+| RD1-score08-3=0r
+|-
+| RD1-seed09=25
+| RD1-team09='''{{flagicon|CAN}} [[Félix Auger-Aliassime|F Auger-Aliassime]]'''
+| RD1-score09-1='''6'''
+| RD1-score09-2=3
+| RD1-score09-3=62
+| RD1-score09-4='''6'''
+| RD1-score09-5='''6'''
+| RD1-seed10=
+| RD1-team10={{flagicon|AUS}} [[James Duckworth (tennis)|J Duckworth]]
+| RD1-score10-1=2
+| RD1-score10-2='''6'''
+| RD1-score10-3='''77 '''
+| RD1-score10-4=4
+| RD1-score10-5=4
+|-
+| RD1-seed11=
+| RD1-team11='''{{flagicon|GER}} [[Jan-Lennard Struff|J-L Struff]]'''
+| RD1-score11-1='''6'''
+| RD1-score11-2=5
+| RD1-score11-3='''6'''
+| RD1-score11-4='''6'''
+| RD1-seed12=Q
+| RD1-team12={{flagicon|AUT}} [[Filip Misolic|F Misolic]]
+| RD1-score12-1=2
+| RD1-score12-2='''7'''
+| RD1-score12-3=3
+| RD1-score12-4=3
+|-
+| RD1-seed13=Q
+| RD1-team13='''{{flagicon|GBR}} [[Oliver Tarvet|O Tarvet]]'''
+| RD1-score13-1='''6'''
+| RD1-score13-2='''6'''
+| RD1-score13-3='''6'''
+| RD1-seed14=Q
+| RD1-team14={{flagicon|SUI}} [[Leandro Riedi|L Riedi]]
+| RD1-score14-1=4
+| RD1-score14-2=4
+| RD1-score14-3=4
+|-
+| RD1-seed15=
+| RD1-team15={{flagicon|ITA}} [[Fabio Fognini|F Fognini]]
+| RD1-score15-1=5
+| RD1-score15-2='''77 '''
+| RD1-score15-3=5
+| RD1-score15-4='''6'''
+| RD1-score15-5=1
+| RD1-seed16=2
+| RD1-team16='''{{flagicon|ESP}} [[Carlos Alcaraz|C Alcaraz]]'''
+| RD1-score16-1='''7'''
+| RD1-score16-2=65
+| RD1-score16-3='''7'''
+| RD1-score16-4=2
+| RD1-score16-5='''6'''
+|-
+| RD2-seed01=14
+| RD2-team01='''{{flagicon|}} [[Andrey Rublev|A Rublev]]'''
+| RD2-score01-1=61
+| RD2-score01-2='''6'''
+| RD2-score01-3='''77 '''
+| RD2-score01-4='''6'''
+| RD2-seed02=PR
+| RD2-team02={{flagicon|RSA}} [[Lloyd Harris (tennis)|L Harris]]
+| RD2-score02-1='''77 '''
+| RD2-score02-2=4
+| RD2-score02-3=65
+| RD2-score02-4=3
+|-
+| RD2-seed03=Q
+| RD2-team03='''{{flagicon|FRA}} [[Adrian Mannarino|A Mannarino]]'''
+| RD2-score03-1='''6'''
+| RD2-score03-2='''6'''
+| RD2-score03-3=5
+| RD2-score03-4='''77 '''
+| RD2-seed04=Q
+| RD2-team04={{flagicon|FRA}} [[Valentin Royer|V Royer]]
+| RD2-score04-1=4
+| RD2-score04-2=4
+| RD2-score04-3='''7'''
+| RD2-score04-4=61
+|-
+| RD2-seed05=25
+| RD2-team05={{flagicon|CAN}} [[Félix Auger-Aliassime|F Auger-Aliassime]]
+| RD2-score05-1='''6'''
+| RD2-score05-2=69
+| RD2-score05-3=3
+| RD2-score05-4=4
+| RD2-seed06=
+| RD2-team06='''{{flagicon|GER}} [[Jan-Lennard Struff|J-L Struff]]'''
+| RD2-score06-1=3
+| RD2-score06-2='''711 '''
+| RD2-score06-3='''6'''
+| RD2-score06-4='''6'''
+|-
+| RD2-seed07=Q
+| RD2-team07={{flagicon|GBR}} [[Oliver Tarvet|O Tarvet]]
+| RD2-score07-1=1
+| RD2-score07-2=4
+| RD2-score07-3=4
+| RD2-seed08=2
+| RD2-team08='''{{flagicon|ESP}} [[Carlos Alcaraz|C Alcaraz]]'''
+| RD2-score08-1='''6'''
+| RD2-score08-2='''6'''
+| RD2-score08-3='''6'''
+|-
+| RD3-seed01=14
+| RD3-team01='''{{flagicon|}} [[Andrey Rublev|A Rublev]]'''
+| RD3-score01-1='''7'''
+| RD3-score01-2='''6'''
+| RD3-score01-3='''6'''
+| RD3-seed02=Q
+| RD3-team02={{flagicon|FRA}} [[Adrian Mannarino|A Mannarino]]
+| RD3-score02-1=5
+| RD3-score02-2=2
+| RD3-score02-3=3
+|-
+| RD3-seed03=
+| RD3-team03={{flagicon|GER}} [[Jan-Lennard Struff|J-L Struff]]
+| RD3-score03-1=1
+| RD3-score03-2='''6'''
+| RD3-score03-3=3
+| RD3-score03-4=4
+| RD3-seed04=2
+| RD3-team04='''{{flagicon|ESP}} [[Carlos Alcaraz|C Alcaraz]]'''
+| RD3-score04-1='''6'''
+| RD3-score04-2=3
+| RD3-score04-3='''6'''
+| RD3-score04-4='''6'''
+|-
+| RD4-seed01=14
+| RD4-team01={{flagicon|}} [[Andrey Rublev|A Rublev]]
+| RD4-score01-1='''77 '''
+| RD4-score01-2=3
+| RD4-score01-3=4
+| RD4-score01-4=4
+| RD4-seed02=2
+| RD4-team02='''{{flagicon|ESP}} [[Carlos Alcaraz|C Alcaraz]]'''
+| RD4-score02-1=65
+| RD4-score02-2='''6'''
+| RD4-score02-3='''6'''
+| RD4-score02-4='''6'''
+}}
+
+== Seeded players ==
+The following are the seeded players. Seedings are based on ATP rankings as of 23 June 2025. Rankings and points before are as of 30 June 2025.
+
+{|class="wikitable sortable"
+! style="width:30px;" |Seed
+! style="width:30px;" |Rank
+! style="width:185px;" |Player
+! style="width:75px;" |Points before
+! style="width:75px;" |[[2024 Wimbledon Championships – Men's singles|Points defending]]
+! style="width:75px;" |Points earned
+! style="width:75px;" |Points after
+! style="width:320px;" |Status
+|-style=background:#cfc
+| style="text-align:center;" | 1
+| style="text-align:center;" | 1
+| {{flagicon|ITA}} [[Jannik Sinner]]
+| style="text-align:center;" | 10,430
+| style="text-align:center;" | 400
+| style="text-align:center;" | 2,000
+| style="text-align:center;" | '''12,030'''
+| '''Champion''', defeated {{flagicon|ESP}} [[Carlos Alcaraz]] [2]
+|-style=background:#cfcfff
+| style="text-align:center;" | 2
+| style="text-align:center;" | 2
+| {{flagicon|ESP}} [[Carlos Alcaraz]]
+| style="text-align:center;" | 9,300
+| style="text-align:center;" | 2,000
+| style="text-align:center;" | 1,300
+| style="text-align:center;" | '''8,600'''
+| Runner-up, lost to {{flagicon|ITA}} [[Jannik Sinner]] [1]
+|-
+| style="text-align:center;" | 3
+| style="text-align:center;" | 3
+| {{flagicon|GER}} [[Alexander Zverev]]
+| style="text-align:center;" | 6,500
+| style="text-align:center;" | 200
+| style="text-align:center;" | 10
+| style="text-align:center;" | '''6,310'''
+| First round lost to {{flagicon|FRA}} [[Arthur Rinderknech]]
+|-
+| style="text-align:center;" | 4
+| style="text-align:center;" | 4
+| {{flagicon|GBR}} [[Jack Draper]]
+| style="text-align:center;" | 4,650
+| style="text-align:center;" | 50
+| style="text-align:center;" | 50
+| style="text-align:center;" | '''4,650'''
+| Second round lost to {{flagicon|CRO}} [[Marin Čilić]]
+|-
+| style="text-align:center;" | 5
+| style="text-align:center;" | 5
+| {{flagicon|USA}} [[Taylor Fritz]]
+| style="text-align:center;" | 4,635
+| style="text-align:center;" | 400
+| style="text-align:center;" | 800
+| style="text-align:center;" | '''5,035'''
+| Semifinals lost to {{flagicon|ESP}} [[Carlos Alcaraz]] [2]
+|-
+| style="text-align:center;" | 6
+| style="text-align:center;" | 6
+| {{flagicon|SRB}} [[Novak Djokovic]]
+| style="text-align:center;" | 4,630
+| style="text-align:center;" | 1,300
+| style="text-align:center;" | 800
+| style="text-align:center;" | '''4,130'''
+| Semifinals lost to {{flagicon|ITA}} [[Jannik Sinner]] [1]
+|-
+| style="text-align:center;" | 7
+| style="text-align:center;" | 7
+| {{flagicon|ITA}} [[Lorenzo Musetti]]
+| style="text-align:center;" | 4,140
+| style="text-align:center;" | 800
+| style="text-align:center;" | 10
+| style="text-align:center;" | '''3,350'''
+| {{nowrap|First round lost to {{flagicon|GEO}} [[Nikoloz Basilashvili]] [Q]}}
+|-
+| style="text-align:center;" | 8
+| style="text-align:center;" | 8
+| {{flagicon|DEN}} [[Holger Rune]]
+| style="text-align:center;" | 3,530
+| style="text-align:center;" | 200
+| style="text-align:center;" | 10
+| style="text-align:center;" | '''3,340'''
+| First round lost to {{flagicon|CHI}} [[Nicolás Jarry]] [Q]
+|-
+| style="text-align:center;" | 9
+| style="text-align:center;" | 9
+| {{flagicon|}} [[Daniil Medvedev]]
+| style="text-align:center;" | 3,420
+| style="text-align:center;" | 800
+| style="text-align:center;" | 10
+| style="text-align:center;" | '''2,630'''
+| First round lost to {{flagicon|FRA}} [[Benjamin Bonzi]]
+|-
+| style="text-align:center;" | 10
+| style="text-align:center;" | 10
+| {{flagicon|USA}} [[Ben Shelton]]
+| style="text-align:center;" | 3,130
+| style="text-align:center;" | 200
+| style="text-align:center;" | 400
+| style="text-align:center;" | '''3,330'''
+| Quarterfinals lost to {{flagicon|ITA}} [[Jannik Sinner]] [1]
+|-
+| style="text-align:center;" | 11
+| style="text-align:center;" | 11
+| {{flagicon|AUS}} [[Alex de Minaur]]
+| style="text-align:center;" | 3,085
+| style="text-align:center;" | 400
+| style="text-align:center;" | 200
+| style="text-align:center;" | '''2,885'''
+| Fourth round lost to {{flagicon|SRB}} [[Novak Djokovic]] [6]
+|-
+| style="text-align:center;" | 12
+| style="text-align:center;" | 12
+| {{flagicon|USA}} [[Frances Tiafoe]]
+| style="text-align:center;" | 2,990
+| style="text-align:center;" | 100
+| style="text-align:center;" | 50
+| style="text-align:center;" | '''2,940'''
+| Second round lost to {{flagicon|GBR}} [[Cameron Norrie]]
+|-
+| style="text-align:center;" | 13
+| style="text-align:center;" | 13
+| {{flagicon|USA}} [[Tommy Paul (tennis)|Tommy Paul]]
+| style="text-align:center;" | 2,970
+| style="text-align:center;" | 400
+| style="text-align:center;" | 50
+| style="text-align:center;" | '''2,620'''
+| Second round lost to {{flagicon|AUT}} [[Sebastian Ofner]] [PR]
+|-
+| style="text-align:center;" | 14
+| style="text-align:center;" | 14
+| {{flagicon|}} [[Andrey Rublev]]
+| style="text-align:center;" | 2,920
+| style="text-align:center;" | 10
+| style="text-align:center;" | 200
+| style="text-align:center;" | '''3,110'''
+| Fourth round lost to {{flagicon|ESP}} [[Carlos Alcaraz]] [2]
+|-
+| style="text-align:center;" | 15
+| style="text-align:center;" | 17
+| {{flagicon|CZE}} [[Jakub Menšík]]
+| style="text-align:center;" | 2,356
+| style="text-align:center;" | 10
+| style="text-align:center;" | 100
+| style="text-align:center;" | '''2,446'''
+| Third round lost to {{flagicon|ITA}} [[Flavio Cobolli]] [22]
+|-
+| style="text-align:center;" | 16
+| style="text-align:center;" | 19
+| {{flagicon|ARG}} [[Francisco Cerúndolo]]
+| style="text-align:center;" | 2,285
+| style="text-align:center;" | 10
+| style="text-align:center;" | 10
+| style="text-align:center;" | '''2,285'''
+| First round lost to {{flagicon|POR}} [[Nuno Borges (tennis)|Nuno Borges]]
+|-
+| style="text-align:center;" | 17
+| style="text-align:center;" | 20
+| {{flagicon|}} [[Karen Khachanov]]
+| style="text-align:center;" | 2,240
+| style="text-align:center;" | 50
+| style="text-align:center;" | 400
+| style="text-align:center;" | '''2,590'''
+| Quarterfinals lost to {{flagicon|USA}} [[Taylor Fritz]] [5]
+|-
+| style="text-align:center;" | 18
+| style="text-align:center;" | 18
+| {{flagicon|FRA}} [[Ugo Humbert]]
+| style="text-align:center;" | 2,285
+| style="text-align:center;" | 200
+| style="text-align:center;" | 10
+| style="text-align:center;" | '''2,095'''
+| First round lost to {{flagicon|FRA}} [[Gaël Monfils]]
+|-
+| style="text-align:center;" | 19
+| style="text-align:center;" | 21
+| {{flagicon|BUL}} [[Grigor Dimitrov]]
+| style="text-align:center;" | 2,155
+| style="text-align:center;" | 200
+| style="text-align:center;" | 200
+| style="text-align:center;" | '''2,155'''
+| {{nowrap|Fourth round retired against {{flagicon|ITA}} [[Jannik Sinner]] [1]}}
+|-
+| style="text-align:center;" | 20
+| style="text-align:center;" | 22
+| {{flagicon|AUS}} [[Alexei Popyrin]]
+| style="text-align:center;" | 2,140
+| style="text-align:center;" | 100
+| style="text-align:center;" | 10
+| style="text-align:center;" | '''2,050'''
+| First round lost to {{flagicon|GBR}} [[Arthur Fery]] [WC]
+|-
+| style="text-align:center;" | 21
+| style="text-align:center;" | 23
+| {{flagicon|CZE}} [[Tomáš Macháč]]
+| style="text-align:center;" | 2,110
+| style="text-align:center;" | 50
+| style="text-align:center;" | 50
+| style="text-align:center;" | '''2,110'''
+| Second round lost to {{flagicon|DEN}} [[August Holmgren (tennis)|August Holmgren]] [Q]
+|-
+| style="text-align:center;" | 22
+| style="text-align:center;" | 24
+| {{flagicon|ITA}} [[Flavio Cobolli]]
+| style="text-align:center;" | 2,035
+| style="text-align:center;" | 50
+| style="text-align:center;" | 400
+| style="text-align:center;" | '''2,385'''
+| Quarterfinals lost to {{flagicon|SRB}} [[Novak Djokovic]] [6]
+|-
+| style="text-align:center;" | 23
+| style="text-align:center;" | 25
+| {{flagicon|CZE}} [[Jiří Lehečka]]
+| style="text-align:center;" | 1,965
+| style="text-align:center;" | 0
+| style="text-align:center;" | 50
+| style="text-align:center;" | '''2,015'''
+| Second round lost to {{flagicon|ITA}} [[Mattia Bellucci]]
+|-
+| style="text-align:center;" | 24
+| style="text-align:center;" | 26
+| {{flagicon|GRE}} [[Stefanos Tsitsipas]]
+| style="text-align:center;" | 1,920
+| style="text-align:center;" | 50
+| style="text-align:center;" | 10
+| style="text-align:center;" | '''1,880'''
+| {{nowrap|First round retired against {{flagicon|FRA}} [[Valentin Royer]] [Q]}}
+|-
+| style="text-align:center;" | 25
+| style="text-align:center;" | 28
+| {{flagicon|CAN}} [[Félix Auger-Aliassime]]
+| style="text-align:center;" | 1,825
+| style="text-align:center;" | 10
+| style="text-align:center;" | 50
+| style="text-align:center;" | '''1,865'''
+| Second round lost to {{flagicon|GER}} [[Jan-Lennard Struff]]
+|-
+| style="text-align:center;" | 26
+| style="text-align:center;" | 27
+| {{nowrap|{{flagicon|ESP}} [[Alejandro Davidovich Fokina]]}}
+| style="text-align:center;" | 1,845
+| style="text-align:center;" | 0
+| style="text-align:center;" | 100
+| style="text-align:center;" | '''1,945'''
+| Third round lost to {{flagicon|USA}} [[Taylor Fritz]] [5]
+|-
+| style="text-align:center;" | 27
+| style="text-align:center;" | 30
+| {{flagicon|CAN}} [[Denis Shapovalov]]
+| style="text-align:center;" | 1,676
+| style="text-align:center;" | 100
+| style="text-align:center;" | 10
+| style="text-align:center;" | '''1,586'''
+| First round lost to {{flagicon|ARG}} [[Mariano Navone]]
+|-
+| style="text-align:center;" | 28
+| style="text-align:center;" | 31
+| {{flagicon|KAZ}} [[Alexander Bublik]]
+| style="text-align:center;" | 1,675
+| style="text-align:center;" | 100
+| style="text-align:center;" | 10
+| style="text-align:center;" | '''1,585'''
+| First round lost to {{flagicon|ESP}} [[Jaume Munar]]
+|-
+| style="text-align:center;" | 29
+| style="text-align:center;" | 34
+| {{flagicon|USA}} [[Brandon Nakashima]]
+| style="text-align:center;" | 1,650
+| style="text-align:center;" | 100
+| style="text-align:center;" | 100
+| style="text-align:center;" | '''1,650'''
+| Third round lost to {{flagicon|ITA}} [[Lorenzo Sonego]]
+|-
+| style="text-align:center;" | 30
+| style="text-align:center;" | 32
+| {{flagicon|USA}} [[Alex Michelsen]]
+| style="text-align:center;" | 1,670
+| style="text-align:center;" | 10
+| style="text-align:center;" | 10
+| style="text-align:center;" | '''1,670'''
+| First round lost to {{flagicon|SRB}} [[Miomir Kecmanović]]
+|-
+| style="text-align:center;" | 31
+| style="text-align:center;" | 29
+| {{flagicon|NED}} [[Tallon Griekspoor]]
+| style="text-align:center;" | 1,755
+| style="text-align:center;" | 50
+| style="text-align:center;" | 10
+| style="text-align:center;" | '''1,715'''
+| First round lost to {{flagicon|USA}} [[Jenson Brooksby]] [PR]
+|-
+| style="text-align:center;" | 32
+| style="text-align:center;" | 35
+| {{flagicon|ITA}} [[Matteo Berrettini]]
+| style="text-align:center;" | 1,515
+| style="text-align:center;" | 50
+| style="text-align:center;" | 10
+| style="text-align:center;" | '''1,475'''
+| First round lost to {{flagicon|POL}} [[Kamil Majchrzak]]
+|}
+
+=== Withdrawn seeded players ===
+The following players would have been seeded, but withdrew before the tournament began.
+{| class="wikitable sortable"
+! Rank
+! style="width:185px;" |Player
+! style="width:75px;" |Points before
+! style="width:75px;" |Points dropping
+! style="width:75px;" |Points after
+! Withdrawal reason
+|-
+| style="text-align:center;" | 15
+|{{flagicon|NOR}} [[Casper Ruud]]
+| style="text-align:center;" | 2,905
+| style="text-align:center;" | 50
+| style="text-align:center;" | '''2,855'''
+| Knee injury[ {{cite web|url=https://www.espn.com/tennis/story/_/id/45557704/lingering-knee-injury-forces-casper-ruud-pull-wimbledon|title= Lingering knee injury forces Casper Ruud to pull out of Wimbledon|date=21 June 2025|publisher=[[ESPN]]}} ]
+|-
+| style="text-align:center;" | 16
+|{{flagicon|FRA}} [[Arthur Fils]]
+| style="text-align:center;" | 2,830
+| style="text-align:center;" | 200
+| style="text-align:center;" | '''2,630'''
+| Back injury[{{cite web|url=https://www.tennis.com/news/articles/casper-ruud-arthur-fils-to-miss-wimbledon-with-ongoing-injuries|title=Casper Ruud, Arthur Fils to miss Wimbledon with ongoing injuries|publisher=tennis.com|accessdate=27 June 2025}}]
+|-
+| style="text-align:center;" | 33
+|{{flagicon|USA}} [[Sebastian Korda]]
+| style="text-align:center;" | 1,655
+| style="text-align:center;" | 10
+| style="text-align:center;" | '''1,645'''
+| Leg injury[{{cite web|url=https://www.thetennisgazette.com/news/sebastian-korda-confirms-the-reason-why-he-has-withdrawn-from-wimbledon/|title=Sebastian Korda confirms the reason why he has withdrawn from Wimbledon|date=18 June 2025 |publisher=The Tennis Gazette|accessdate=27 June 2025}}]
+|}
+
+==Other entry information==
+===Wildcards===
+{{columns-list|colwidth=30em|
+* {{flagicon|GBR}} [[Jay Clarke (tennis)|Jay Clarke]]
+* {{flagicon|GBR}} [[Oliver Crawford (tennis)|Oliver Crawford]]
+* {{flagicon|GBR}} [[Dan Evans (tennis)|Dan Evans]]
+* {{flagicon|GBR}} [[Arthur Fery]]
+* {{flagicon|GBR}} [[George Loffhagen]]
+* {{flagicon|GBR}} [[Johannus Monday]]
+* {{flagicon|GBR}} [[Jack Pinnington Jones]]
+* {{flagicon|GBR}} [[Henry Searle (tennis)|Henry Searle]]
+}}
+
+===Protected ranking===
+{{columns-list|colwidth=30em|
+* {{flagicon|USA}} [[Jenson Brooksby]] (52)
+* {{flagicon|AUT}} [[Sebastian Ofner]] (74)
+* {{flagicon|RSA}} [[Lloyd Harris (tennis)|Lloyd Harris]] (108)
+}}
+
+===Qualifiers===
+{{main|2025 Wimbledon Championships – Men's singles qualifying}}
+{{columns-list|colwidth=30em|
+* {{flagicon|GEO}} [[Nikoloz Basilashvili]]
+* {{flagicon|AUS}} [[Alex Bolt]]
+* {{flagicon|FRA}} [[Arthur Cazaux]]
+* {{flagicon|POR}} [[Jaime Faria]]
+* {{flagicon|DEN}} [[August Holmgren (tennis)|August Holmgren]]
+* {{flagicon|CHI}} [[Nicolás Jarry]]
+* {{flagicon|FRA}} [[Adrian Mannarino]]
+* {{flagicon|AUS}} [[James McCabe (tennis)|James McCabe]]
+* {{flagicon|AUT}} [[Filip Misolic]]
+* {{flagicon|JPN}} [[Shintaro Mochizuki]]
+* {{flagicon|SUI}} [[Leandro Riedi]]
+* {{flagicon|LUX}} [[Chris Rodesch]]
+* {{flagicon|FRA}} [[Valentin Royer]]
+* {{flagicon|GBR}} [[Oliver Tarvet]]
+* {{flagicon|ITA}} [[Giulio Zeppieri]]
+* {{flagicon|KAZ}} [[Beibit Zhukayev]]
+}}
+
+===Lucky losers===
+{{columns-list|colwidth=30em|
+* {{flagicon|HUN}} [[Márton Fucsovics]]
+* {{flagicon|CHI}} [[Cristian Garín]]
+* {{flagicon|SRB}} [[Dušan Lajović]]
+}}
+
+===Withdrawals===
+{{columns-list|colwidth=30em|
+* ‡ {{flagicon|CHN}} [[Shang Juncheng]] (71) → replaced by {{flagicon|USA}} [[Nishesh Basavareddy]] (101)
+* ‡ {{flagicon|FIN}} [[Emil Ruusuvuori]] (83 PR) → replaced by {{flagicon|USA}} [[Brandon Holt]] (102)[{{cite web|url=https://lastwordonsports.com/tennis/2025/06/25/2025-wimbledon-five-more-players-withdraw-from-event/|title=2025 Wimbledon: Five More Players Withdraw From Event|date=25 June 2025 |publisher=lastwordonsports.com|accessdate=27 June 2025}}]
+* ‡ {{flagicon|JPN}} [[Kei Nishikori]] (62) → replaced by {{flagicon|GBR}} [[Billy Harris (tennis)|Billy Harris]] (103)[{{cite web|url=https://lastwordonsports.com/tennis/2025/06/18/2025-wimbledon-four-players-withdraw-from-event/|title=2025 Wimbledon: Four Players Withdraw From Event|date=18 June 2025 |publisher=lastwordonsports.com|accessdate=27 June 2025}}]
+* ‡ {{flagicon|CHI}} [[Alejandro Tabilo]] (61) → replaced by {{flagicon|CRO}} [[Marin Čilić]] (104)[
+* ‡ {{flagicon|USA}} [[Sebastian Korda]] (23) → replaced by {{flagicon|USA}} [[Christopher Eubanks]] (105)][
+* ‡ {{flagicon|AUS}} [[Nick Kyrgios]] (21 PR) → replaced by {{flagicon|USA}} [[Ethan Quinn]] (106)][{{cite magazine|url=https://www.si.com/onsi/serve/news/nick-kyrgios-pulls-out-of-wimbledon-after-small-setback|title=Nick Kyrgios Pulls Out of Wimbledon After "Small Setback"|magazine=[[Sports Illustrated]]|last=Benson|first=Pat|date=3 June 2025|access-date=4 June 2025}}]
+* ‡ {{flagicon|FRA}} [[Arthur Fils]] (14) → replaced by {{flagicon|ITA}} [[Fabio Fognini]] (107)[
+* ‡ {{flagicon|NOR}} [[Casper Ruud]] (16) → replaced by {{flagicon|DEN}} [[Elmer Møller]] (108)][
+* ‡ {{flagicon|CHN}} [[Zhang Zhizhen]] (81) → replaced by {{flagicon|RSA}} [[Lloyd Harris (tennis)|Lloyd Harris]] (108 PR)][
+* @ {{flagicon|CRO}} [[Borna Ćorić]] (83) → replaced by {{flagicon|HUN}} [[Márton Fucsovics]] (LL)][{{cite web|url=https://www.puntodebreak.com/en/2025/06/26/this-is-the-important-last-minute-withdrawal-at-wimbledon-2025|title=This is the important last minute withdrawal at Wimbledon 2025|publisher=puntodebreak.com|accessdate=27 June 2025}}]
+* § {{flagicon|POL}} [[Hubert Hurkacz]] (39) → replaced by {{flagicon|SRB}} [[Dušan Lajović]] (LL)[{{cite web|url=https://www.reuters.com/sports/tennis/injured-hurkacz-withdraws-wimbledon-2025-06-27/|title=Injured Hurkacz withdraws from Wimbledon|work=Reuters |date=27 June 2025 |accessdate=27 June 2025}}]
+* § {{flagicon|ESP}} [[Pablo Carreño Busta]] (93) → replaced by {{flagicon|CHI}} [[Cristian Garín]] (LL)[{{cite web|url=https://www.thetennisgazette.com/news/former-top-10-atp-player-withdraws-from-wimbledon-at-the-last-minute-and-is-replaced-by-2022-quarter-finalist/|title=Former top 10 ATP player withdraws from Wimbledon at the last minute and is replaced by 2022 quarter-finalist|date=30 June 2025 |publisher=The Tennis Gazette|accessdate=5 July 2025}}]
+}}
+
+‡ – withdrew from entry list before qualifying began
+@ – withdrew from entry list after qualifying began
+§ – withdrew from main draw
+
+Source: [{{cite web|url=https://www.wimbledon.com/pdf/update/referees/2025/MS_Entries.pdf|title=Gentlemen's Singles Entry List List|website=wimbledon.com}}]
+
+==Finals statistics==
+
+{| class="wikitable"
+|-
+! Category !! Sinner !! Alcaraz
+|-
+| Aces || 8 || bgcolor=98fb98|15
+|-
+| Double faults || bgcolor=98fb98|2 || 7
+|-
+| 1st serve % in || bgcolor=98fb98|{{tennis record|won=72|lost=116}} = 62% || {{tennis record|won=64|lost=121}} = 53%
+|-
+| Winning % on 1st Serve || {{tennis record|won=53|lost=72}} = 74% || bgcolor=98fb98|{{tennis record|won=48|lost=64}} = 75%
+|-
+| Winning % on 2nd Serve || bgcolor=98fb98|{{tennis record|won=27|lost=44}} = 61% || {{tennis record|won=29|lost=57}} = 51%
+|-
+| Net points won || bgcolor=98fb98|{{tennis record|won=30|lost=40}} = 75% || {{tennis record|won=17|lost=23}} = 74%
+|-
+| Break points won || bgcolor=98fb98|{{tennis record|won=4|lost=9}} = 44% || {{tennis record|won=2|lost=6}} = 33%
+|-
+| Receiving points won || bgcolor=98fb98|{{tennis record|won=44|lost=121}} = 36% || {{tennis record|won=36|lost=116}} = 31%
+|-
+| Winners || bgcolor=98fb98|40 || 38
+|-
+| Unforced errors || 40 || bgcolor=98fb98|36
+|-
+| Winners-UFE || 0 || bgcolor=98fb98|2
+|-
+| Total points won || bgcolor=98fb98|124 || 113
+|-
+| Total games won || bgcolor=98fb98|22 || 18
+|}
+Source:[https://www.wimbledon.com/en_GB/scores/results/index.html][{{cite web |title=Sinner – Alcaraz Head to Head| url=https://www.atptour.com/en/players/atp-head-2-head/jannik-sinner-vs-carlos-alcaraz/s0ag/a0e2|website=ATP Tour |access-date=13 July 2025 |date=13 July 2025}}]
+
+
+==References==
+{{reflist}}
+
+==External links==
+* [http://www.wimbledon.com/pdf/update/referees/2025/MS_Entries.pdf Entry list]
+* [http://www.wimbledon.com/en_GB/scores/draws/2025_MS_draw.pdf Draw]
+
+{{s-start}}
+{{Succession box|before=[[2025 French Open – Men's singles]]|title=[[List of Grand Slam men's singles champions|Grand Slam men's singles]]|after=[[2025 US Open – Men's singles]]}}
+{{s-end}}
+
+{{Wimbledon men's singles drawsheets}}
+{{Wimbledon men's singles champions}}
+{{2025 ATP Tour}}
+
+{{DEFAULTSORT:2025 Wimbledon Championships - Men's Singles}}
+
+[[Category:2025 Wimbledon Championships|Men's Singles]]
+[[Category:2025 ATP Tour|Wimbledon Championships Singles]]
+[[Category:Wimbledon Championship by year – Men's singles|2025]]
\ No newline at end of file
diff --git a/app/services/match-sync/__tests__/tennis-draw-mapping.test.ts b/app/services/match-sync/__tests__/tennis-draw-mapping.test.ts
new file mode 100644
index 0000000..ca976fa
--- /dev/null
+++ b/app/services/match-sync/__tests__/tennis-draw-mapping.test.ts
@@ -0,0 +1,147 @@
+import { describe, it, expect } from "vitest";
+import {
+ playerKey,
+ canonicalPlayerName,
+ buildResolvedMatches,
+} from "../tennis-draw-mapping";
+import type { FetchedDraw, FetchedPlayer } from "../types";
+
+const sinner: FetchedPlayer = { name: "J Sinner", externalId: "Jannik Sinner", seed: 1 };
+const djokovic: FetchedPlayer = { name: "N Djokovic", externalId: "Novak Djokovic", seed: 6 };
+const noLink: FetchedPlayer = { name: "Some Qualifier", externalId: null, seed: null };
+
+describe("playerKey", () => {
+ it("prefers the external id", () => {
+ expect(playerKey(sinner)).toBe("id:Jannik Sinner");
+ });
+ it("falls back to a normalized name when there is no external id", () => {
+ expect(playerKey(noLink)).toBe("name:some qualifier");
+ });
+});
+
+describe("canonicalPlayerName", () => {
+ it("uses the full name from the external id, not the abbreviated display", () => {
+ expect(canonicalPlayerName(sinner)).toBe("Jannik Sinner");
+ });
+ it("uses the display name when there is no external id", () => {
+ expect(canonicalPlayerName(noLink)).toBe("Some Qualifier");
+ });
+
+ it("strips the trailing Wikipedia disambiguator", () => {
+ expect(
+ canonicalPlayerName({ name: "Arthur Fils", externalId: "Arthur Fils (tennis)", seed: null }),
+ ).toBe("Arthur Fils");
+ });
+});
+
+describe("buildResolvedMatches", () => {
+ const draw: FetchedDraw = {
+ templateId: "tennis_128",
+ rounds: [
+ {
+ roundName: "Round of 128",
+ matches: [
+ {
+ externalMatchId: "m-r128-1",
+ round: "Round of 128",
+ matchNumber: 1,
+ player1: sinner,
+ player2: noLink,
+ winner: 1,
+ },
+ ],
+ },
+ {
+ roundName: "Final",
+ matches: [
+ {
+ externalMatchId: "m-final",
+ round: "Final",
+ matchNumber: 1,
+ player1: djokovic,
+ player2: sinner,
+ winner: 2,
+ },
+ ],
+ },
+ ],
+ };
+
+ const idByKey = new Map([
+ [playerKey(sinner), "sp-sinner"],
+ [playerKey(djokovic), "sp-djokovic"],
+ [playerKey(noLink), "sp-qual"],
+ ]);
+ const roundIsScoring = new Map([
+ ["Round of 128", false],
+ ["Final", true],
+ ]);
+
+ const resolved = buildResolvedMatches(draw, idByKey, roundIsScoring);
+
+ it("maps winner slot 1 to winnerId/loserId", () => {
+ const r128 = resolved.find((m) => m.externalMatchId === "m-r128-1");
+ expect(r128?.winnerId).toBe("sp-sinner");
+ expect(r128?.loserId).toBe("sp-qual");
+ expect(r128?.isScoring).toBe(false);
+ });
+
+ it("maps winner slot 2 to the second player", () => {
+ const final = resolved.find((m) => m.externalMatchId === "m-final");
+ expect(final?.participant1Id).toBe("sp-djokovic");
+ expect(final?.participant2Id).toBe("sp-sinner");
+ expect(final?.winnerId).toBe("sp-sinner");
+ expect(final?.loserId).toBe("sp-djokovic");
+ expect(final?.isScoring).toBe(true);
+ });
+
+ it("leaves winner/loser null when the source reported no winner", () => {
+ const noWinnerDraw: FetchedDraw = {
+ templateId: "tennis_128",
+ rounds: [
+ {
+ roundName: "Round of 128",
+ matches: [
+ {
+ externalMatchId: "m-x",
+ round: "Round of 128",
+ matchNumber: 1,
+ player1: sinner,
+ player2: djokovic,
+ winner: null,
+ },
+ ],
+ },
+ ],
+ };
+ const [m] = buildResolvedMatches(noWinnerDraw, idByKey, roundIsScoring);
+ expect(m.winnerId).toBeNull();
+ expect(m.loserId).toBeNull();
+ });
+
+ it("defaults isScoring to false for an unknown round", () => {
+ const [m] = buildResolvedMatches(
+ {
+ templateId: "tennis_128",
+ rounds: [
+ {
+ roundName: "Mystery",
+ matches: [
+ {
+ externalMatchId: "m-y",
+ round: "Mystery",
+ matchNumber: 1,
+ player1: sinner,
+ player2: djokovic,
+ winner: 1,
+ },
+ ],
+ },
+ ],
+ },
+ idByKey,
+ new Map(),
+ );
+ expect(m.isScoring).toBe(false);
+ });
+});
diff --git a/app/services/match-sync/__tests__/wikipedia-tennis.test.ts b/app/services/match-sync/__tests__/wikipedia-tennis.test.ts
new file mode 100644
index 0000000..3823581
--- /dev/null
+++ b/app/services/match-sync/__tests__/wikipedia-tennis.test.ts
@@ -0,0 +1,176 @@
+import { describe, it, expect } from "vitest";
+import { readFileSync } from "node:fs";
+import { join } from "node:path";
+import {
+ parseTennisDrawWikitext,
+ parsePlayerCell,
+ parseTemplateParams,
+ extractTemplates,
+ slugifyArticle,
+ articleTitleFromInput,
+} from "../wikipedia-tennis";
+import type { FetchedRound } from "../types";
+
+const FIXTURE = readFileSync(
+ join(__dirname, "fixtures", "wimbledon-2025-mens-singles.wikitext"),
+ "utf-8",
+);
+const ARTICLE = "2025 Wimbledon Championships – Men's singles";
+
+function roundOrThrow(rounds: FetchedRound[], name: string): FetchedRound {
+ const round = rounds.find((r) => r.roundName === name);
+ if (!round) throw new Error(`round ${name} missing`);
+ return round;
+}
+
+describe("parsePlayerCell", () => {
+ it("extracts name + wikilink target and detects the bolded winner", () => {
+ const cell = parsePlayerCell("'''{{flagicon|ITA}} [[Jannik Sinner]]'''");
+ expect(cell).toEqual({
+ player: { name: "Jannik Sinner", externalId: "Jannik Sinner", seed: null },
+ isWinner: true,
+ });
+ });
+
+ it("treats a non-bolded slot as a loser", () => {
+ const cell = parsePlayerCell("{{flagicon|USA}} [[Ben Shelton]]");
+ expect(cell?.isWinner).toBe(false);
+ expect(cell?.player.name).toBe("Ben Shelton");
+ });
+
+ it("handles piped wikilinks (qualifier disambiguation)", () => {
+ const cell = parsePlayerCell("{{flagicon|FRA}} [[Arthur Fils (tennis)|Arthur Fils]]");
+ expect(cell?.player.name).toBe("Arthur Fils");
+ expect(cell?.player.externalId).toBe("Arthur Fils (tennis)");
+ });
+
+ it("parses a player whose cell is wrapped in {{nowrap}} (long names)", () => {
+ const loser = parsePlayerCell(
+ "{{nowrap|{{flagicon|ESP}} [[Alejandro Davidovich Fokina|A Davidovich Fokina]]}}",
+ );
+ expect(loser?.player.externalId).toBe("Alejandro Davidovich Fokina");
+ expect(loser?.player.name).toBe("A Davidovich Fokina");
+ expect(loser?.isWinner).toBe(false);
+
+ const winner = parsePlayerCell(
+ "'''{{nowrap|{{flagicon|ARG}} [[Juan Manuel Cerúndolo|JM Cerúndolo]]}}'''",
+ );
+ expect(winner?.player.externalId).toBe("Juan Manuel Cerúndolo");
+ expect(winner?.isWinner).toBe(true);
+ });
+
+ it("returns null for empty, Bye, and not-yet-determined placeholder slots", () => {
+ expect(parsePlayerCell("")).toBeNull();
+ expect(parsePlayerCell("Bye")).toBeNull();
+ expect(parsePlayerCell("Qualifier")).toBeNull();
+ expect(parsePlayerCell("{{flagicon|GBR}} Qualifier")).toBeNull();
+ expect(parsePlayerCell("TBD")).toBeNull();
+ });
+});
+
+describe("parseTemplateParams", () => {
+ it("splits on top-level pipes only, preserving piped links and templates", () => {
+ const params = parseTemplateParams(
+ "RD1-team1={{flagicon|ITA}} [[A B|A]]|RD1-seed1=1|RD1-score1-1=6",
+ );
+ expect(params.get("RD1-team1")).toBe("{{flagicon|ITA}} [[A B|A]]");
+ expect(params.get("RD1-seed1")).toBe("1");
+ expect(params.get("RD1-score1-1")).toBe("6");
+ });
+});
+
+describe("extractTemplates", () => {
+ it("finds the 8 section brackets and 1 finals bracket", () => {
+ expect(extractTemplates(FIXTURE, ["16TeamBracket"]).length).toBe(8);
+ expect(extractTemplates(FIXTURE, ["8TeamBracket"]).length).toBe(1);
+ });
+});
+
+describe("parseTennisDrawWikitext (2025 Wimbledon men's singles)", () => {
+ const draw = parseTennisDrawWikitext(FIXTURE, ARTICLE);
+
+ it("maps to the tennis_128 template", () => {
+ expect(draw.templateId).toBe("tennis_128");
+ });
+
+ it("produces the full 127-match draw with correct per-round counts", () => {
+ const byRound = Object.fromEntries(draw.rounds.map((r) => [r.roundName, r.matches.length]));
+ expect(byRound).toEqual({
+ "Round of 128": 64,
+ "Round of 64": 32,
+ "Round of 32": 16,
+ "Round of 16": 8,
+ Quarterfinals: 4,
+ Semifinals: 2,
+ Final: 1,
+ });
+ const total = draw.rounds.reduce((sum, r) => sum + r.matches.length, 0);
+ expect(total).toBe(127);
+ });
+
+ it("populates both players in section rounds (not just the finals bracket)", () => {
+ const r128 = roundOrThrow(draw.rounds, "Round of 128");
+ // A full 128 draw has no byes — every R128 slot is filled.
+ const populated = r128.matches.filter((m) => m.player1 && m.player2);
+ expect(populated.length).toBe(64);
+ // Section brackets abbreviate display names ("J Sinner") but the wikilink
+ // target stays the full name — so externalId is the reliable key.
+ const sinnerMatch = r128.matches.find(
+ (m) => m.player1?.externalId === "Jannik Sinner" || m.player2?.externalId === "Jannik Sinner",
+ );
+ expect(sinnerMatch).toBeDefined();
+ const sinner =
+ sinnerMatch?.player1?.externalId === "Jannik Sinner"
+ ? sinnerMatch?.player1
+ : sinnerMatch?.player2;
+ expect(sinner?.name).toBe("J Sinner");
+ expect(sinner?.seed).toBe(1);
+ });
+
+ it("identifies Sinner as the champion (won the Final)", () => {
+ const final = roundOrThrow(draw.rounds, "Final").matches[0];
+ const winner = final.winner === 1 ? final.player1 : final.player2;
+ expect(winner?.name).toBe("Jannik Sinner");
+ });
+
+ it("gives every match a unique externalMatchId", () => {
+ const ids = draw.rounds.flatMap((r) => r.matches.map((m) => m.externalMatchId));
+ expect(new Set(ids).size).toBe(ids.length);
+ });
+
+ it("records seeds for the 32 seeded players in round 1", () => {
+ const r128 = roundOrThrow(draw.rounds, "Round of 128");
+ const seeded = r128.matches
+ .flatMap((m) => [m.player1, m.player2])
+ .filter((p) => typeof p?.seed === "number");
+ expect(seeded.length).toBe(32);
+ });
+});
+
+describe("articleTitleFromInput", () => {
+ it("extracts the title from a pasted Wikipedia URL", () => {
+ expect(
+ articleTitleFromInput(
+ "https://en.wikipedia.org/wiki/2025_Wimbledon_Championships_%E2%80%93_Men%27s_singles",
+ ),
+ ).toBe("2025 Wimbledon Championships – Men's singles");
+ });
+
+ it("strips query strings and fragments", () => {
+ expect(articleTitleFromInput("https://en.wikipedia.org/wiki/French_Open?action=raw#Draw")).toBe(
+ "French Open",
+ );
+ });
+
+ it("passes a plain title through unchanged", () => {
+ expect(articleTitleFromInput(" 2025 US Open – Men's singles ")).toBe(
+ "2025 US Open – Men's singles",
+ );
+ });
+});
+
+describe("slugifyArticle", () => {
+ it("produces an id-safe slug and strips diacritics", () => {
+ expect(slugifyArticle(ARTICLE)).toBe("2025-wimbledon-championships-men-s-singles");
+ });
+});
diff --git a/app/services/match-sync/index.ts b/app/services/match-sync/index.ts
index 8435753..27c3e38 100644
--- a/app/services/match-sync/index.ts
+++ b/app/services/match-sync/index.ts
@@ -1,15 +1,46 @@
import { database } from "~/database/context";
import { eq } from "drizzle-orm";
import * as schema from "~/database/schema";
-import { findParticipantsBySportsSeasonId, updateParticipant } from "~/models/season-participant";
+import {
+ findParticipantsBySportsSeasonId,
+ updateParticipant,
+ createParticipant,
+ createManyParticipants,
+} from "~/models/season-participant";
import { upsertSeasonMatchBulk, upsertMatchSubGame } from "~/models/season-match";
-import { setMatchWinner, updatePlayoffMatch, advanceWinnerTemplate, findPlayoffMatchesByEventId, doesLoserAdvance } from "~/models/playoff-match";
-import { processMatchResult, autoCompleteRoundIfDone } from "~/models/scoring-calculator";
-import { findMatchingTeamName } from "~/lib/normalize-team-name";
+import {
+ setMatchWinner,
+ updatePlayoffMatch,
+ advanceWinnerTemplate,
+ findPlayoffMatchesByEventId,
+ doesLoserAdvance,
+ populateBracketFromDraw,
+} from "~/models/playoff-match";
+import {
+ processMatchResult,
+ autoCompleteRoundIfDone,
+ processQualifyingBracketEvent,
+ recalculateAffectedLeagues,
+} from "~/models/scoring-calculator";
+import { fanOutMajorIfPrimary } from "~/services/sync-tournament-results";
+import { recalculateParticipantQP } from "~/models/qualifying-points";
+import { getScoringEventById, updateScoringEvent, isReadOnlySibling } from "~/models/scoring-event";
+import { findMatchingTeamName, normalizeTeamName } from "~/lib/normalize-team-name";
+import { buildUnmatchedTeamResolutionView } from "~/lib/unmatched-team-reconciliation";
import { getBracketTemplate } from "~/lib/bracket-templates";
import { PandaScoreMatchSyncAdapter } from "./pandascore";
import { EspnScheduleAdapter } from "./espn-schedule";
-import type { MatchSyncAdapter, MatchSyncResult } from "./types";
+import { WikipediaTennisAdapter } from "./wikipedia-tennis";
+import { playerKey, canonicalPlayerName, buildResolvedMatches } from "./tennis-draw-mapping";
+import type {
+ MatchSyncAdapter,
+ MatchSyncResult,
+ DrawSyncAdapter,
+ DrawSyncResult,
+ DrawSyncPreview,
+ FetchedDraw,
+ FetchedPlayer,
+} from "./types";
import { logger } from "~/lib/logger";
function getMatchSyncAdapter(simulatorType: string): MatchSyncAdapter {
@@ -269,3 +300,336 @@ export async function syncMatches(sportsSeasonId: string): Promise {
+ const players = new Map();
+ for (const round of draw.rounds) {
+ for (const match of round.matches) {
+ for (const player of [match.player1, match.player2]) {
+ if (player) players.set(playerKey(player), player);
+ }
+ }
+ }
+ return players;
+}
+
+type SeasonParticipant = Awaited>[number];
+
+type PlayerLookups = {
+ byExternalId: Map;
+ byName: Map;
+ names: string[];
+ recon: { id: string; name: string; externalId: string | null }[];
+};
+
+function buildLookups(participants: SeasonParticipant[]): PlayerLookups {
+ return {
+ byExternalId: new Map(
+ participants.filter((p) => p.externalId).map((p) => [p.externalId as string, p]),
+ ),
+ byName: new Map(participants.map((p) => [p.name, p])),
+ names: participants.map((p) => p.name),
+ recon: participants.map((p) => ({ id: p.id, name: p.name, externalId: p.externalId })),
+ };
+}
+
+type Classification =
+ | { kind: "matched"; participant: SeasonParticipant; backfill: boolean }
+ | {
+ kind: "create";
+ name: string;
+ suggestion: string | null;
+ suggestionId: string | null;
+ };
+
+/** Read-only: decide how a drawn player resolves against existing participants. */
+function classifyPlayer(player: FetchedPlayer, lk: PlayerLookups): Classification {
+ const name = canonicalPlayerName(player);
+ const byId = player.externalId ? lk.byExternalId.get(player.externalId) : undefined;
+ if (byId) return { kind: "matched", participant: byId, backfill: false };
+
+ const matchedName = findMatchingTeamName(name, lk.names);
+ if (matchedName) {
+ const p = lk.byName.get(matchedName);
+ if (p) return { kind: "matched", participant: p, backfill: !p.externalId && !!player.externalId };
+ }
+
+ // No confident match. Surface the closest existing participant (if any) as a
+ // possible duplicate to review.
+ const view = buildUnmatchedTeamResolutionView(name, lk.recon);
+ const top = view.confidence !== "none" ? view.topCandidates[0] ?? null : null;
+ return {
+ kind: "create",
+ name,
+ suggestion: top?.participantName ?? null,
+ suggestionId: top?.participantId ?? null,
+ };
+}
+
+/**
+ * Dry run: report what a draw sync would do (matches, would-be participant
+ * creations, likely duplicates, unfilled first-round slots) without any writes.
+ */
+export async function previewTennisDraw(eventId: string): Promise {
+ const { event, sportsSeasonId, draw } = await loadTennisDraw(eventId);
+ const participants = await findParticipantsBySportsSeasonId(sportsSeasonId);
+ const lk = buildLookups(participants);
+ const uniquePlayers = collectUniquePlayers(draw);
+
+ let matched = 0;
+ const willCreate: DrawSyncPreview["willCreate"] = [];
+ const possibleDuplicates: DrawSyncPreview["possibleDuplicates"] = [];
+
+ for (const player of uniquePlayers.values()) {
+ const c = classifyPlayer(player, lk);
+ if (c.kind === "matched") {
+ matched++;
+ } else {
+ willCreate.push({ name: c.name, externalId: player.externalId });
+ if (c.suggestion && c.suggestionId) {
+ possibleDuplicates.push({
+ name: c.name,
+ externalId: player.externalId,
+ suggestion: c.suggestion,
+ suggestionId: c.suggestionId,
+ });
+ }
+ }
+ }
+
+ const firstRound = draw.rounds[0];
+ const tbdFirstRound = firstRound
+ ? firstRound.matches.reduce((n, m) => n + (m.player1 ? 0 : 1) + (m.player2 ? 0 : 1), 0)
+ : 0;
+ const allMatches = draw.rounds.flatMap((r) => r.matches);
+
+ return {
+ article: event.externalSourceKey ?? "",
+ totalPlayers: uniquePlayers.size,
+ matched,
+ willCreate,
+ possibleDuplicates,
+ tbdFirstRound,
+ totalMatches: allMatches.length,
+ completedMatches: allMatches.filter((m) => m.winner !== null).length,
+ };
+}
+
+/**
+ * Populate and auto-score a tennis major's bracket from its external draw
+ * (Wikipedia). Resolves every drawn player to a participant — matching by
+ * external id, then name, else auto-creating — propagates new participants to
+ * every sports season linked to the same tournament (so result fan-out lands),
+ * writes the full bracket, and runs the qualifying-points scorer.
+ *
+ * Runs only on a major's primary window; read-only siblings receive results via
+ * fan-out, not direct scoring.
+ */
+export async function syncTennisDraw(eventId: string): Promise {
+ const db = database();
+
+ const { event, sportsSeasonId, draw, template } = await loadTennisDraw(eventId);
+ const roundIsScoring = new Map(template.rounds.map((r) => [r.name, r.isScoring]));
+
+ // ---- Resolve players → participants (primary season) ----------------------
+ const participants = await findParticipantsBySportsSeasonId(sportsSeasonId);
+ const lk = buildLookups(participants);
+ const uniquePlayers = collectUniquePlayers(draw);
+
+ const keyToParticipantId = new Map();
+ const resolvedParticipants = new Map<
+ string,
+ { canonicalId: string | null; name: string; externalId: string | null }
+ >();
+ const unmatched: DrawSyncResult["unmatched"] = [];
+ let participantsCreated = 0;
+
+ for (const [key, player] of uniquePlayers) {
+ const c = classifyPlayer(player, lk);
+ let participant: SeasonParticipant;
+
+ if (c.kind === "matched") {
+ participant = c.participant;
+ if (c.backfill && player.externalId) {
+ await updateParticipant(participant.id, { externalId: player.externalId });
+ }
+ } else {
+ // No confident match → auto-create so the bracket is complete; flag a
+ // likely duplicate for admin review when it resembles an existing player.
+ if (c.suggestion) unmatched.push({ name: c.name, externalId: player.externalId });
+ participant = await createParticipant({
+ sportsSeasonId,
+ name: c.name,
+ externalId: player.externalId ?? null,
+ });
+ lk.names.push(participant.name);
+ lk.byName.set(participant.name, participant);
+ lk.recon.push({ id: participant.id, name: participant.name, externalId: participant.externalId });
+ if (participant.externalId) lk.byExternalId.set(participant.externalId, participant);
+ participantsCreated++;
+ }
+
+ keyToParticipantId.set(key, participant.id);
+ resolvedParticipants.set(key, {
+ canonicalId: participant.participantId ?? null,
+ name: participant.name,
+ externalId: participant.externalId ?? null,
+ });
+ }
+
+ // ---- Propagate participants to linked sibling seasons ---------------------
+ if (event.tournamentId) {
+ const linkedRows = await db
+ .select({ sportsSeasonId: schema.scoringEvents.sportsSeasonId })
+ .from(schema.scoringEvents)
+ .where(eq(schema.scoringEvents.tournamentId, event.tournamentId));
+ const linkedSeasonIds = [...new Set(linkedRows.map((r) => r.sportsSeasonId))].filter(
+ (id) => id !== sportsSeasonId,
+ );
+
+ // Dedupe by canonical participant: two drawn players can fuzzy-match the same
+ // existing participant, which would otherwise queue duplicate sibling inserts
+ // and trip the (sportsSeasonId, name) unique index.
+ const resolved = [
+ ...new Map(
+ [...resolvedParticipants.values()]
+ .filter((r) => r.canonicalId)
+ .map((r) => [r.canonicalId, r]),
+ ).values(),
+ ];
+
+ for (const seasonId of linkedSeasonIds) {
+ const sib = await findParticipantsBySportsSeasonId(seasonId);
+ const sibCanon = new Set(sib.map((s) => s.participantId).filter(Boolean));
+ const sibNames = new Set(sib.map((s) => normalizeTeamName(s.name)));
+
+ const toCreate = resolved
+ .filter(
+ (r) =>
+ r.canonicalId &&
+ !sibCanon.has(r.canonicalId) &&
+ !sibNames.has(normalizeTeamName(r.name)),
+ )
+ .map((r) => ({
+ sportsSeasonId: seasonId,
+ name: r.name,
+ externalId: r.externalId,
+ participantId: r.canonicalId,
+ }));
+
+ if (toCreate.length > 0) {
+ await createManyParticipants(toCreate);
+ participantsCreated += toCreate.length;
+ }
+ }
+ }
+
+ // ---- Write the bracket ----------------------------------------------------
+ const resolvedMatches = buildResolvedMatches(draw, keyToParticipantId, roundIsScoring);
+
+ // Ensure the event is configured for QP bracket scoring before populating.
+ if (
+ event.bracketTemplateId !== draw.templateId ||
+ event.scoringStartsAtRound !== template.scoringStartsAtRound
+ ) {
+ await updateScoringEvent(eventId, {
+ bracketTemplateId: draw.templateId,
+ scoringStartsAtRound: template.scoringStartsAtRound,
+ });
+ }
+
+ const { written, completed } = await populateBracketFromDraw(eventId, resolvedMatches);
+
+ // ---- Score (qualifying points) + fan out to siblings ----------------------
+ // The tennis bracket is the sole QP source for its event, so reconcile stale
+ // rows: snapshot existing result rows, clear them, re-derive from the bracket,
+ // then recalc QP totals for any participant who no longer earns points (e.g.
+ // early-round losers — only Round of 16+ losers score). writeEventResultsQP
+ // upserts but never deletes, so without this a previously mis-scored player
+ // would keep phantom QP across re-syncs.
+ //
+ // Wrapped in a transaction so a mid-rescore failure can't leave the event with
+ // its results deleted and not rebuilt. NOTE: this clears ALL event_results for
+ // the event, including any manually-entered rows (e.g. a notParticipating
+ // withdrawal) — acceptable because the synced draw is authoritative for tennis.
+ await db.transaction(async (tx) => {
+ const beforeRows = await tx
+ .select({ pid: schema.eventResults.seasonParticipantId })
+ .from(schema.eventResults)
+ .where(eq(schema.eventResults.scoringEventId, eventId));
+ await tx.delete(schema.eventResults).where(eq(schema.eventResults.scoringEventId, eventId));
+
+ await processQualifyingBracketEvent(eventId, tx);
+
+ const afterRows = await tx
+ .select({ pid: schema.eventResults.seasonParticipantId })
+ .from(schema.eventResults)
+ .where(eq(schema.eventResults.scoringEventId, eventId));
+ const afterIds = new Set(afterRows.map((r) => r.pid));
+ for (const { pid } of beforeRows) {
+ if (!afterIds.has(pid)) await recalculateParticipantQP(pid, sportsSeasonId, tx);
+ }
+ });
+
+ await recalculateAffectedLeagues(sportsSeasonId, db, {
+ eventId,
+ eventName: event.name ?? undefined,
+ });
+ await fanOutMajorIfPrimary(
+ { id: event.id, isPrimary: event.isPrimary, tournamentId: event.tournamentId },
+ { markComplete: false },
+ );
+
+ return { matchesWritten: written, completed, participantsCreated, unmatched };
+}
diff --git a/app/services/match-sync/tennis-draw-mapping.ts b/app/services/match-sync/tennis-draw-mapping.ts
new file mode 100644
index 0000000..100b09e
--- /dev/null
+++ b/app/services/match-sync/tennis-draw-mapping.ts
@@ -0,0 +1,63 @@
+/**
+ * Pure (DB-free) mapping helpers for tennis draw sync, separated so the
+ * scoring-critical logic — winner/loser assignment and which rounds score — can
+ * be unit-tested without a database.
+ */
+import { normalizeTeamName } from "~/lib/normalize-team-name";
+import type { ResolvedDrawMatch } from "~/models/playoff-match";
+import type { FetchedDraw, FetchedPlayer } from "./types";
+
+/** Stable identity key for a drawn player (prefer the source's external id). */
+export function playerKey(p: FetchedPlayer): string {
+ return p.externalId ? `id:${p.externalId}` : `name:${normalizeTeamName(p.name)}`;
+}
+
+/**
+ * Canonical participant name for a drawn player. Section brackets abbreviate the
+ * display name ("J Sinner"); the wikilink target (externalId) is the full name,
+ * so prefer it. Strips the trailing Wikipedia disambiguator (e.g. "Arthur Fils
+ * (tennis)" → "Arthur Fils") so the stored name and fuzzy matching use the plain
+ * name — the raw externalId (with the disambiguator) is kept as the stable key.
+ */
+export function canonicalPlayerName(p: FetchedPlayer): string {
+ return (p.externalId ?? p.name).replace(/\s*\([^)]*\)\s*$/, "").trim();
+}
+
+/**
+ * Turn a fetched draw into bracket rows with participants/winner resolved to ids.
+ * `winnerId`/`loserId` are set only when both players resolved and the source
+ * reported a winner. `isScoring` comes from the bracket template's round config.
+ */
+export function buildResolvedMatches(
+ draw: FetchedDraw,
+ keyToParticipantId: Map,
+ roundIsScoring: Map,
+): ResolvedDrawMatch[] {
+ const resolved: ResolvedDrawMatch[] = [];
+ for (const round of draw.rounds) {
+ for (const m of round.matches) {
+ const p1 = m.player1 ? keyToParticipantId.get(playerKey(m.player1)) ?? null : null;
+ const p2 = m.player2 ? keyToParticipantId.get(playerKey(m.player2)) ?? null : null;
+ let winnerId: string | null = null;
+ let loserId: string | null = null;
+ if (m.winner === 1) {
+ winnerId = p1;
+ loserId = p2;
+ } else if (m.winner === 2) {
+ winnerId = p2;
+ loserId = p1;
+ }
+ resolved.push({
+ externalMatchId: m.externalMatchId,
+ round: m.round,
+ matchNumber: m.matchNumber,
+ participant1Id: p1,
+ participant2Id: p2,
+ winnerId,
+ loserId,
+ isScoring: roundIsScoring.get(m.round) ?? false,
+ });
+ }
+ }
+ return resolved;
+}
diff --git a/app/services/match-sync/types.ts b/app/services/match-sync/types.ts
index a5f49c9..3375161 100644
--- a/app/services/match-sync/types.ts
+++ b/app/services/match-sync/types.ts
@@ -42,3 +42,103 @@ export interface MatchSyncResult {
unmatchedTeams: { externalId: string; name: string }[];
errors: { externalMatchId: string; error: string }[];
}
+
+// ---------------------------------------------------------------------------
+// Tennis Grand Slam draw sync
+//
+// Unlike the team-sport `MatchSyncAdapter` (result-only updates on a
+// pre-existing bracket), a tennis major must be populated from an empty draw:
+// 128 players, scaffolded across all 7 rounds, with winners auto-scored as the
+// source reports them. `DrawSyncAdapter` returns the full draw; `syncTennisDraw`
+// (index.ts) auto-creates/links participants, builds the bracket, propagates
+// participants to linked sibling seasons, and runs the qualifying-points scorer.
+// ---------------------------------------------------------------------------
+
+/** A competitor in a fetched draw match, as seen by the external source. */
+export interface FetchedPlayer {
+ /** Display name, e.g. "Jannik Sinner". */
+ name: string;
+ /**
+ * Stable source identifier, the primary key against
+ * `seasonParticipants.externalId`. For Wikipedia this is the article link
+ * target (stable across years). Null when the source gives only plain text
+ * (e.g. a qualifier with no article).
+ */
+ externalId: string | null;
+ /** Tournament seed, if seeded. */
+ seed: number | null;
+}
+
+/** A single fetched draw match. */
+export interface FetchedDrawMatch {
+ /** Stable, source-derived id, e.g. "wimbledon-2025-ms-Round-of-16-m03". */
+ externalMatchId: string;
+ /** Template round name, e.g. "Round of 128" … "Final". */
+ round: string;
+ /** 1-based position within the round (cosmetic ordering only). */
+ matchNumber: number;
+ player1: FetchedPlayer | null;
+ player2: FetchedPlayer | null;
+ /** Which slot won; null while undrawn or not yet completed. */
+ winner: 1 | 2 | null;
+}
+
+export interface FetchedRound {
+ /** Template round name. */
+ roundName: string;
+ matches: FetchedDrawMatch[];
+}
+
+/** A complete (or partially-completed) knockout draw. */
+export interface FetchedDraw {
+ /** Bracket template this draw maps onto, e.g. "tennis_128". */
+ templateId: string;
+ /** Ordered earliest → latest round (Round of 128 → Final). */
+ rounds: FetchedRound[];
+}
+
+/** Adapter that fetches a knockout draw from an external source. */
+export interface DrawSyncAdapter {
+ /** @param sourceKey provider locator (Wikipedia article title, etc.). */
+ fetchDraw(sourceKey: string): Promise;
+}
+
+/** A drawn player that couldn't be confidently auto-resolved to a participant. */
+export interface UnmatchedPlayer {
+ name: string;
+ externalId: string | null;
+}
+
+export interface DrawSyncResult {
+ /** Matches inserted or updated in the bracket. */
+ matchesWritten: number;
+ /** Of those, how many carried a completed result this sync. */
+ completed: number;
+ /** New participants auto-created (across all linked seasons). */
+ participantsCreated: number;
+ /** Ambiguous players queued for admin reconciliation rather than guessed. */
+ unmatched: UnmatchedPlayer[];
+}
+
+/** Read-only preview of what a draw sync would do, with no DB writes. */
+export interface DrawSyncPreview {
+ /** Resolved Wikipedia article title. */
+ article: string;
+ totalPlayers: number;
+ /** Players that resolve to an existing participant. */
+ matched: number;
+ /** Players that would be auto-created. */
+ willCreate: UnmatchedPlayer[];
+ /** Of those, ones that resemble an existing participant (likely duplicates). */
+ possibleDuplicates: {
+ name: string;
+ externalId: string | null;
+ /** Existing participant this resembles. */
+ suggestion: string;
+ suggestionId: string;
+ }[];
+ /** Unfilled slots in the first round (e.g. a qualifier not yet drawn). */
+ tbdFirstRound: number;
+ totalMatches: number;
+ completedMatches: number;
+}
diff --git a/app/services/match-sync/wikipedia-tennis.ts b/app/services/match-sync/wikipedia-tennis.ts
new file mode 100644
index 0000000..0c6edd6
--- /dev/null
+++ b/app/services/match-sync/wikipedia-tennis.ts
@@ -0,0 +1,307 @@
+/**
+ * Wikipedia tennis Grand Slam draw adapter.
+ *
+ * Source of truth: the singles-draw article (e.g.
+ * "2025 Wimbledon Championships – Men's singles"), whose full bracket is encoded
+ * as MediaWiki bracket templates:
+ * - eight `{{16TeamBracket-Compact-Tennis5}}` sections (Round of 128 → Round
+ * of 16), 16 players each = 128, and
+ * - one `{{8TeamBracket-Tennis5}}` finals bracket (Quarterfinals → Final).
+ * (`-Tennis3` best-of-3 variants render women's draws identically for our needs.)
+ *
+ * The winner of each match is the slot whose `team` value is bolded (`'''…'''`)
+ * — losers are never bolded — which lets us derive winner/loser without parsing
+ * set scores. Player names come from `[[wikilinks]]`, whose target is a stable
+ * cross-year id used to match `seasonParticipants.externalId`.
+ *
+ * Parsing is split out (pure functions) so it can be unit-tested against a saved
+ * wikitext fixture without network access.
+ */
+import type {
+ DrawSyncAdapter,
+ FetchedDraw,
+ FetchedDrawMatch,
+ FetchedPlayer,
+ FetchedRound,
+} from "./types";
+
+const WIKIPEDIA_API = "https://en.wikipedia.org/w/api.php";
+const CONTACT = process.env.WIKIPEDIA_CONTACT_EMAIL ?? "admin@brackt.com";
+const USER_AGENT = `Brackt.com tennis draw sync - ${CONTACT}`;
+
+/** Round names for a 16-player draw section, earliest → latest. */
+const SECTION_ROUNDS = ["Round of 128", "Round of 64", "Round of 32", "Round of 16"] as const;
+/** Round names for the 8-player finals bracket, earliest → latest. */
+const FINALS_ROUNDS = ["Quarterfinals", "Semifinals", "Final"] as const;
+
+/**
+ * Accept either a plain article title or a pasted Wikipedia URL and return the
+ * article title. URLs like
+ * `https://en.wikipedia.org/wiki/2025_Wimbledon_Championships_%E2%80%93_Men%27s_singles`
+ * become "2025 Wimbledon Championships – Men's singles".
+ */
+export function articleTitleFromInput(input: string): string {
+ const trimmed = input.trim();
+ const match = trimmed.match(/\/wiki\/([^?#]+)/);
+ if (!match) return trimmed;
+ let title = match[1];
+ try {
+ title = decodeURIComponent(title);
+ } catch {
+ // leave as-is if it isn't valid percent-encoding
+ }
+ return title.replace(/_/g, " ").trim();
+}
+
+/** Turn an article title into a stable, id-safe slug. */
+export function slugifyArticle(title: string): string {
+ return title
+ .normalize("NFKD")
+ .replace(/[̀-ͯ]/g, "") // strip combining diacritics
+ .toLowerCase()
+ .replace(/[^a-z0-9]+/g, "-")
+ .replace(/^-+|-+$/g, "");
+}
+
+/**
+ * Scan wikitext for `{{...}}` invocations, returning each body
+ * (text between the outer braces) with balanced-brace handling so nested
+ * templates like `{{flagicon|ITA}}` don't terminate the capture early.
+ */
+export function extractTemplates(wikitext: string, namePrefixes: string[]): { name: string; body: string }[] {
+ const results: { name: string; body: string }[] = [];
+ for (let i = 0; i < wikitext.length - 1; i++) {
+ if (wikitext[i] !== "{" || wikitext[i + 1] !== "{") continue;
+ const afterBraces = wikitext.slice(i + 2);
+ const match = afterBraces.match(/^\s*([A-Za-z0-9 _-]+)/);
+ const name = match?.[1]?.trim() ?? "";
+ if (!namePrefixes.some((p) => name.startsWith(p))) continue;
+
+ // Walk forward tracking {{ }} depth to find the matching close.
+ let depth = 0;
+ let j = i;
+ for (; j < wikitext.length - 1; j++) {
+ if (wikitext[j] === "{" && wikitext[j + 1] === "{") {
+ depth++;
+ j++;
+ } else if (wikitext[j] === "}" && wikitext[j + 1] === "}") {
+ depth--;
+ j++;
+ if (depth === 0) break;
+ }
+ }
+ const body = wikitext.slice(i + 2, j - 1);
+ results.push({ name, body });
+ i = j; // continue past this template
+ }
+ return results;
+}
+
+/**
+ * Split a template body into `key=value` params on top-level `|` only —
+ * pipes inside `{{…}}` or `[[…]]` are part of a value (flag templates, piped
+ * wikilinks) and must not split.
+ */
+export function parseTemplateParams(body: string): Map {
+ const params = new Map();
+ let braceDepth = 0;
+ let linkDepth = 0;
+ let current = "";
+ const parts: string[] = [];
+
+ for (let i = 0; i < body.length; i++) {
+ const two = body.slice(i, i + 2);
+ if (two === "{{") { braceDepth++; current += two; i++; continue; }
+ if (two === "}}") { braceDepth--; current += two; i++; continue; }
+ if (two === "[[") { linkDepth++; current += two; i++; continue; }
+ if (two === "]]") { linkDepth--; current += two; i++; continue; }
+ if (body[i] === "|" && braceDepth === 0 && linkDepth === 0) {
+ parts.push(current);
+ current = "";
+ continue;
+ }
+ current += body[i];
+ }
+ parts.push(current);
+
+ for (const part of parts) {
+ const eq = part.indexOf("=");
+ if (eq === -1) continue;
+ const key = part.slice(0, eq).trim();
+ const value = part.slice(eq + 1).trim();
+ if (key) params.set(key, value);
+ }
+ return params;
+}
+
+/**
+ * Parse a bracket `team` value into a player + whether it won.
+ * Returns null for an empty/Bye slot.
+ */
+export function parsePlayerCell(value: string): { player: FetchedPlayer; isWinner: boolean } | null {
+ if (!value) return null;
+ // Winner detection: only the winning slot's name is bolded.
+ const isWinner = value.includes("'''");
+ let text = value.replace(/'''/g, "");
+
+ // Extract the wikilink FIRST, before stripping templates. A team cell may wrap
+ // the flag + link in a display template, e.g.
+ // {{nowrap|{{flagicon|ESP}} [[Alejandro Davidovich Fokina|A Davidovich Fokina]]}}
+ // Blanket template-stripping would, after removing the inner {{flagicon}}, treat
+ // the outer {{nowrap| … [[link]] … }} as a single template and delete the link
+ // with it — yielding an empty cell (TBD). Pulling the link out first avoids that.
+ const linkMatch = text.match(/\[\[([^\]]+)\]\]/);
+ let name: string;
+ let externalId: string | null;
+ if (linkMatch) {
+ const [target, display] = linkMatch[1].split("|");
+ externalId = target.trim();
+ name = (display ?? target).trim();
+ } else {
+ // No link: unwrap display templates ({{nowrap|X}}) to their content, drop flag
+ // templates, and keep whatever plain text remains (e.g. an unlinked qualifier).
+ let prev: string;
+ do {
+ prev = text;
+ text = text.replace(/\{\{\s*(?:nowrap|nobr)\s*\|([^{}]*)\}\}/gi, "$1");
+ } while (text !== prev);
+ do {
+ prev = text;
+ text = text.replace(/\{\{[^{}]*\}\}/g, " ");
+ } while (text !== prev);
+ name = text.replace(/\[\[|\]\]/g, "").trim();
+ externalId = null;
+ }
+ name = name.replace(/\s+/g, " ").trim();
+ // Treat empty slots and not-yet-determined placeholders (an unfilled qualifier,
+ // a bye, a TBD feeder) as no player → the bracket shows TBD rather than
+ // inventing a shared "Qualifier" participant across many slots.
+ if (!name || /^(bye|tbd|qualifier|to be determined)$/i.test(name)) return null;
+ return { player: { name, externalId, seed: null }, isWinner };
+}
+
+/**
+ * Build matches for one round of a bracket. Within a round, consecutive slots
+ * pair up: (team1,team2)=match1, (team3,team4)=match2, …
+ */
+function parseRound(
+ params: Map,
+ rdIndex: number,
+ roundName: string,
+ idPrefix: string,
+ matchNumberOffset: number,
+): FetchedDrawMatch[] {
+ // Collect team slot tokens present for this round, preserving the raw token
+ // (slots are zero-padded in the compact section brackets, e.g. "team01", but
+ // unpadded in the finals bracket, e.g. "team1"). Sort by numeric value.
+ const slots: string[] = [];
+ for (const key of params.keys()) {
+ const m = key.match(new RegExp(`^RD${rdIndex}-team(\\d+)$`));
+ if (m) slots.push(m[1]);
+ }
+ slots.sort((a, b) => Number(a) - Number(b));
+
+ const matches: FetchedDrawMatch[] = [];
+ for (let p = 0; p < slots.length; p += 2) {
+ const slot1 = slots[p];
+ const slot2 = slots[p + 1];
+ const cell1 = parsePlayerCell(params.get(`RD${rdIndex}-team${slot1}`) ?? "");
+ const cell2 = slot2 !== undefined ? parsePlayerCell(params.get(`RD${rdIndex}-team${slot2}`) ?? "") : null;
+
+ const seed1 = params.get(`RD${rdIndex}-seed${slot1}`);
+ const seed2 = slot2 !== undefined ? params.get(`RD${rdIndex}-seed${slot2}`) : undefined;
+ const player1 = cell1 ? { ...cell1.player, seed: parseSeed(seed1) } : null;
+ const player2 = cell2 ? { ...cell2.player, seed: parseSeed(seed2) } : null;
+
+ let winner: 1 | 2 | null = null;
+ if (cell1?.isWinner) winner = 1;
+ else if (cell2?.isWinner) winner = 2;
+
+ const matchNumber = matchNumberOffset + matches.length + 1;
+ matches.push({
+ externalMatchId: `${idPrefix}:${roundName.replace(/\s+/g, "-")}:m${matchNumber}`,
+ round: roundName,
+ matchNumber,
+ player1,
+ player2,
+ winner,
+ });
+ }
+ return matches;
+}
+
+function parseSeed(raw: string | undefined): number | null {
+ if (!raw) return null;
+ const n = parseInt(raw.replace(/[^0-9]/g, ""), 10);
+ return Number.isFinite(n) ? n : null;
+}
+
+/**
+ * Parse a full Grand Slam singles draw article into a `FetchedDraw`.
+ * @param wikitext raw article wikitext
+ * @param articleTitle used to build stable match ids
+ */
+export function parseTennisDrawWikitext(wikitext: string, articleTitle: string): FetchedDraw {
+ const slug = slugifyArticle(articleTitle);
+ const sectionBrackets = extractTemplates(wikitext, ["16TeamBracket"]);
+ const finalsBrackets = extractTemplates(wikitext, ["8TeamBracket"]);
+
+ if (finalsBrackets.length !== 1 || sectionBrackets.length !== 8) {
+ throw new Error(
+ `Unexpected tennis draw layout in "${articleTitle}": found ${sectionBrackets.length} ` +
+ `16-team section bracket(s) and ${finalsBrackets.length} 8-team finals bracket(s); ` +
+ "expected 8 sections + 1 finals. Template variant may be unsupported.",
+ );
+ }
+
+ // Accumulate matches per round across all sections (section order is cosmetic).
+ const roundMatches = new Map();
+ const pushMatches = (round: string, ms: FetchedDrawMatch[]) => {
+ const existing = roundMatches.get(round) ?? [];
+ roundMatches.set(round, existing.concat(ms));
+ };
+
+ sectionBrackets.forEach((bracket, sectionIdx) => {
+ const params = parseTemplateParams(bracket.body);
+ SECTION_ROUNDS.forEach((roundName, rdIdx) => {
+ const offset = roundMatches.get(roundName)?.length ?? 0;
+ pushMatches(roundName, parseRound(params, rdIdx + 1, roundName, `${slug}:s${sectionIdx}`, offset));
+ });
+ });
+
+ const finalsParams = parseTemplateParams(finalsBrackets[0].body);
+ FINALS_ROUNDS.forEach((roundName, rdIdx) => {
+ pushMatches(roundName, parseRound(finalsParams, rdIdx + 1, roundName, `${slug}:f`, 0));
+ });
+
+ const orderedRoundNames = [...SECTION_ROUNDS, ...FINALS_ROUNDS];
+ const rounds: FetchedRound[] = orderedRoundNames.map((roundName) => ({
+ roundName,
+ matches: roundMatches.get(roundName) ?? [],
+ }));
+
+ return { templateId: "tennis_128", rounds };
+}
+
+export class WikipediaTennisAdapter implements DrawSyncAdapter {
+ async fetchDraw(sourceKey: string): Promise {
+ const title = articleTitleFromInput(sourceKey);
+ const url = `${WIKIPEDIA_API}?action=parse&prop=wikitext&format=json&formatversion=2&redirects=1&page=${encodeURIComponent(title)}`;
+ const response = await fetch(url, { headers: { "User-Agent": USER_AGENT } });
+ if (!response.ok) {
+ throw new Error(`Wikipedia API returned ${response.status}: ${response.statusText}`);
+ }
+ const json = (await response.json()) as {
+ error?: { info?: string };
+ parse?: { wikitext?: string };
+ };
+ if (json.error) {
+ throw new Error(`Wikipedia API error for "${title}": ${json.error.info ?? "unknown error"}`);
+ }
+ const wikitext = json.parse?.wikitext;
+ if (!wikitext) {
+ throw new Error(`No wikitext returned for "${title}"`);
+ }
+ return parseTennisDrawWikitext(wikitext, title);
+ }
+}
diff --git a/database/schema.ts b/database/schema.ts
index a96ef6b..acab470 100644
--- a/database/schema.ts
+++ b/database/schema.ts
@@ -641,6 +641,10 @@ export const scoringEvents = pgTable("scoring_events", {
scoringStartsAtRound: varchar("scoring_starts_at_round", { length: 50 }), // "Elite Eight", "Quarterfinals", etc.
// Per-event region config for NCAA-style brackets (overrides template defaults)
bracketRegionConfig: jsonb("bracket_region_config"), // BracketRegion[] | null
+ // External data-source identifier for auto-populating this event from a feed.
+ // For Wikipedia tennis draws this is the article title, e.g.
+ // "2025 Wimbledon Championships – Men's singles". Null = no external source.
+ externalSourceKey: varchar("external_source_key", { length: 255 }),
isComplete: boolean("is_complete").notNull().default(false),
completedAt: timestamp("completed_at"),
// For tournament-linked majors shared across windows: marks the single window
diff --git a/drizzle/0123_previous_expediter.sql b/drizzle/0123_previous_expediter.sql
new file mode 100644
index 0000000..0992433
--- /dev/null
+++ b/drizzle/0123_previous_expediter.sql
@@ -0,0 +1 @@
+ALTER TABLE "scoring_events" ADD COLUMN "external_source_key" varchar(255);
\ No newline at end of file
diff --git a/drizzle/meta/0123_snapshot.json b/drizzle/meta/0123_snapshot.json
new file mode 100644
index 0000000..d6833dd
--- /dev/null
+++ b/drizzle/meta/0123_snapshot.json
@@ -0,0 +1,6747 @@
+{
+ "id": "0e2a43af-72f3-4e32-bf42-f173b2ce1aa1",
+ "prevId": "67a28e61-c2e6-4fd8-a224-9ebe658a21ec",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.accounts": {
+ "name": "accounts",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "account_id": {
+ "name": "account_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_token_expires_at": {
+ "name": "access_token_expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token_expires_at": {
+ "name": "refresh_token_expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "accounts_user_id_users_id_fk": {
+ "name": "accounts_user_id_users_id_fk",
+ "tableFrom": "accounts",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.autodraft_settings": {
+ "name": "autodraft_settings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "season_id": {
+ "name": "season_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "team_id": {
+ "name": "team_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "is_enabled": {
+ "name": "is_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "mode": {
+ "name": "mode",
+ "type": "autodraft_mode",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'next_pick'"
+ },
+ "queue_only": {
+ "name": "queue_only",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "autodraft_settings_season_id_seasons_id_fk": {
+ "name": "autodraft_settings_season_id_seasons_id_fk",
+ "tableFrom": "autodraft_settings",
+ "tableTo": "seasons",
+ "columnsFrom": [
+ "season_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "autodraft_settings_team_id_teams_id_fk": {
+ "name": "autodraft_settings_team_id_teams_id_fk",
+ "tableFrom": "autodraft_settings",
+ "tableTo": "teams",
+ "columnsFrom": [
+ "team_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.commissioner_audit_log": {
+ "name": "commissioner_audit_log",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "season_id": {
+ "name": "season_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "league_id": {
+ "name": "league_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "actor_user_id": {
+ "name": "actor_user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "actor_display_name": {
+ "name": "actor_display_name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "action": {
+ "name": "action",
+ "type": "audit_action",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "affected_team_ids": {
+ "name": "affected_team_ids",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "details": {
+ "name": "details",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "audit_log_season_id_idx": {
+ "name": "audit_log_season_id_idx",
+ "columns": [
+ {
+ "expression": "season_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "commissioner_audit_log_season_id_seasons_id_fk": {
+ "name": "commissioner_audit_log_season_id_seasons_id_fk",
+ "tableFrom": "commissioner_audit_log",
+ "tableTo": "seasons",
+ "columnsFrom": [
+ "season_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "commissioner_audit_log_league_id_leagues_id_fk": {
+ "name": "commissioner_audit_log_league_id_leagues_id_fk",
+ "tableFrom": "commissioner_audit_log",
+ "tableTo": "leagues",
+ "columnsFrom": [
+ "league_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.commissioners": {
+ "name": "commissioners",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "league_id": {
+ "name": "league_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "commissioners_league_id_leagues_id_fk": {
+ "name": "commissioners_league_id_leagues_id_fk",
+ "tableFrom": "commissioners",
+ "tableTo": "leagues",
+ "columnsFrom": [
+ "league_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.cs2_major_stage_results": {
+ "name": "cs2_major_stage_results",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "scoring_event_id": {
+ "name": "scoring_event_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "participant_id": {
+ "name": "participant_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "stage_entry": {
+ "name": "stage_entry",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "stage_eliminated": {
+ "name": "stage_eliminated",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stage_eliminated_wins": {
+ "name": "stage_eliminated_wins",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "final_placement": {
+ "name": "final_placement",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "cs2_major_stage_results_unique": {
+ "name": "cs2_major_stage_results_unique",
+ "columns": [
+ {
+ "expression": "scoring_event_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "participant_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "cs2_major_stage_results_scoring_event_id_scoring_events_id_fk": {
+ "name": "cs2_major_stage_results_scoring_event_id_scoring_events_id_fk",
+ "tableFrom": "cs2_major_stage_results",
+ "tableTo": "scoring_events",
+ "columnsFrom": [
+ "scoring_event_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "cs2_major_stage_results_participant_id_season_participants_id_fk": {
+ "name": "cs2_major_stage_results_participant_id_season_participants_id_fk",
+ "tableFrom": "cs2_major_stage_results",
+ "tableTo": "season_participants",
+ "columnsFrom": [
+ "participant_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.draft_picks": {
+ "name": "draft_picks",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "season_id": {
+ "name": "season_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "team_id": {
+ "name": "team_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "participant_id": {
+ "name": "participant_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "pick_number": {
+ "name": "pick_number",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "round": {
+ "name": "round",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "pick_in_round": {
+ "name": "pick_in_round",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "picked_by_user_id": {
+ "name": "picked_by_user_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "picked_by_type": {
+ "name": "picked_by_type",
+ "type": "picked_by_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "time_used": {
+ "name": "time_used",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "draft_picks_season_pick_unique": {
+ "name": "draft_picks_season_pick_unique",
+ "columns": [
+ {
+ "expression": "season_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "pick_number",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "draft_picks_season_id_seasons_id_fk": {
+ "name": "draft_picks_season_id_seasons_id_fk",
+ "tableFrom": "draft_picks",
+ "tableTo": "seasons",
+ "columnsFrom": [
+ "season_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "draft_picks_team_id_teams_id_fk": {
+ "name": "draft_picks_team_id_teams_id_fk",
+ "tableFrom": "draft_picks",
+ "tableTo": "teams",
+ "columnsFrom": [
+ "team_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "draft_picks_participant_id_season_participants_id_fk": {
+ "name": "draft_picks_participant_id_season_participants_id_fk",
+ "tableFrom": "draft_picks",
+ "tableTo": "season_participants",
+ "columnsFrom": [
+ "participant_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.draft_queue": {
+ "name": "draft_queue",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "season_id": {
+ "name": "season_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "team_id": {
+ "name": "team_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "participant_id": {
+ "name": "participant_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "queue_position": {
+ "name": "queue_position",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "draft_queue_season_id_seasons_id_fk": {
+ "name": "draft_queue_season_id_seasons_id_fk",
+ "tableFrom": "draft_queue",
+ "tableTo": "seasons",
+ "columnsFrom": [
+ "season_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "draft_queue_team_id_teams_id_fk": {
+ "name": "draft_queue_team_id_teams_id_fk",
+ "tableFrom": "draft_queue",
+ "tableTo": "teams",
+ "columnsFrom": [
+ "team_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "draft_queue_participant_id_season_participants_id_fk": {
+ "name": "draft_queue_participant_id_season_participants_id_fk",
+ "tableFrom": "draft_queue",
+ "tableTo": "season_participants",
+ "columnsFrom": [
+ "participant_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.draft_slots": {
+ "name": "draft_slots",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "season_id": {
+ "name": "season_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "team_id": {
+ "name": "team_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "draft_order": {
+ "name": "draft_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "draft_slots_season_id_seasons_id_fk": {
+ "name": "draft_slots_season_id_seasons_id_fk",
+ "tableFrom": "draft_slots",
+ "tableTo": "seasons",
+ "columnsFrom": [
+ "season_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "draft_slots_team_id_teams_id_fk": {
+ "name": "draft_slots_team_id_teams_id_fk",
+ "tableFrom": "draft_slots",
+ "tableTo": "teams",
+ "columnsFrom": [
+ "team_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.draft_timers": {
+ "name": "draft_timers",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "season_id": {
+ "name": "season_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "team_id": {
+ "name": "team_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "time_remaining": {
+ "name": "time_remaining",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "picks_expires_at": {
+ "name": "picks_expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "picks_started_at": {
+ "name": "picks_started_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "draft_timers_season_id_seasons_id_fk": {
+ "name": "draft_timers_season_id_seasons_id_fk",
+ "tableFrom": "draft_timers",
+ "tableTo": "seasons",
+ "columnsFrom": [
+ "season_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "draft_timers_team_id_teams_id_fk": {
+ "name": "draft_timers_team_id_teams_id_fk",
+ "tableFrom": "draft_timers",
+ "tableTo": "teams",
+ "columnsFrom": [
+ "team_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.event_results": {
+ "name": "event_results",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "scoring_event_id": {
+ "name": "scoring_event_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "season_participant_id": {
+ "name": "season_participant_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "placement": {
+ "name": "placement",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "qualifying_points_awarded": {
+ "name": "qualifying_points_awarded",
+ "type": "numeric(10, 2)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "eliminated": {
+ "name": "eliminated",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "raw_score": {
+ "name": "raw_score",
+ "type": "numeric(10, 2)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "not_participating": {
+ "name": "not_participating",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "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": {
+ "event_results_event_participant_unique": {
+ "name": "event_results_event_participant_unique",
+ "columns": [
+ {
+ "expression": "scoring_event_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "season_participant_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "event_results_scoring_event_id_scoring_events_id_fk": {
+ "name": "event_results_scoring_event_id_scoring_events_id_fk",
+ "tableFrom": "event_results",
+ "tableTo": "scoring_events",
+ "columnsFrom": [
+ "scoring_event_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "event_results_season_participant_id_season_participants_id_fk": {
+ "name": "event_results_season_participant_id_season_participants_id_fk",
+ "tableFrom": "event_results",
+ "tableTo": "season_participants",
+ "columnsFrom": [
+ "season_participant_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.group_stage_matches": {
+ "name": "group_stage_matches",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "tournament_group_id": {
+ "name": "tournament_group_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "participant1_id": {
+ "name": "participant1_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "participant2_id": {
+ "name": "participant2_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "participant1_score": {
+ "name": "participant1_score",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "participant2_score": {
+ "name": "participant2_score",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_complete": {
+ "name": "is_complete",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "matchday": {
+ "name": "matchday",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scheduled_at": {
+ "name": "scheduled_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "group_stage_matches_group_pair_unique": {
+ "name": "group_stage_matches_group_pair_unique",
+ "columns": [
+ {
+ "expression": "tournament_group_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "participant1_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "participant2_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "group_stage_matches_tournament_group_id_tournament_groups_id_fk": {
+ "name": "group_stage_matches_tournament_group_id_tournament_groups_id_fk",
+ "tableFrom": "group_stage_matches",
+ "tableTo": "tournament_groups",
+ "columnsFrom": [
+ "tournament_group_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "group_stage_matches_participant1_id_season_participants_id_fk": {
+ "name": "group_stage_matches_participant1_id_season_participants_id_fk",
+ "tableFrom": "group_stage_matches",
+ "tableTo": "season_participants",
+ "columnsFrom": [
+ "participant1_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "group_stage_matches_participant2_id_season_participants_id_fk": {
+ "name": "group_stage_matches_participant2_id_season_participants_id_fk",
+ "tableFrom": "group_stage_matches",
+ "tableTo": "season_participants",
+ "columnsFrom": [
+ "participant2_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.leagues": {
+ "name": "leagues",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "current_season_id": {
+ "name": "current_season_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_public_draft_board": {
+ "name": "is_public_draft_board",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "discord_webhook_url": {
+ "name": "discord_webhook_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "discord_picks_announcement_enabled": {
+ "name": "discord_picks_announcement_enabled",
+ "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.match_sub_games": {
+ "name": "match_sub_games",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "season_match_id": {
+ "name": "season_match_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "game_number": {
+ "name": "game_number",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "game_label": {
+ "name": "game_label",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "participant1_score": {
+ "name": "participant1_score",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "participant2_score": {
+ "name": "participant2_score",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "winner_id": {
+ "name": "winner_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "match_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'scheduled'"
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "external_game_id": {
+ "name": "external_game_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": {
+ "match_sub_games_match_game_number_unique": {
+ "name": "match_sub_games_match_game_number_unique",
+ "columns": [
+ {
+ "expression": "season_match_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "game_number",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "match_sub_games_season_match_id_season_matches_id_fk": {
+ "name": "match_sub_games_season_match_id_season_matches_id_fk",
+ "tableFrom": "match_sub_games",
+ "tableTo": "season_matches",
+ "columnsFrom": [
+ "season_match_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "match_sub_games_winner_id_season_participants_id_fk": {
+ "name": "match_sub_games_winner_id_season_participants_id_fk",
+ "tableFrom": "match_sub_games",
+ "tableTo": "season_participants",
+ "columnsFrom": [
+ "winner_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.participant_ev_snapshots": {
+ "name": "participant_ev_snapshots",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "participant_id": {
+ "name": "participant_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "sports_season_id": {
+ "name": "sports_season_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "snapshot_date": {
+ "name": "snapshot_date",
+ "type": "date",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "prob_first": {
+ "name": "prob_first",
+ "type": "numeric(5, 2)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "prob_second": {
+ "name": "prob_second",
+ "type": "numeric(5, 2)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "prob_third": {
+ "name": "prob_third",
+ "type": "numeric(5, 2)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "prob_fourth": {
+ "name": "prob_fourth",
+ "type": "numeric(5, 2)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "prob_fifth": {
+ "name": "prob_fifth",
+ "type": "numeric(5, 2)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "prob_sixth": {
+ "name": "prob_sixth",
+ "type": "numeric(5, 2)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "prob_seventh": {
+ "name": "prob_seventh",
+ "type": "numeric(5, 2)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "prob_eighth": {
+ "name": "prob_eighth",
+ "type": "numeric(5, 2)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "calculated_ev": {
+ "name": "calculated_ev",
+ "type": "numeric(10, 4)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "source": {
+ "name": "source",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "participant_ev_snapshots_unique": {
+ "name": "participant_ev_snapshots_unique",
+ "columns": [
+ {
+ "expression": "participant_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sports_season_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "snapshot_date",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "participant_ev_snapshots_participant_id_season_participants_id_fk": {
+ "name": "participant_ev_snapshots_participant_id_season_participants_id_fk",
+ "tableFrom": "participant_ev_snapshots",
+ "tableTo": "season_participants",
+ "columnsFrom": [
+ "participant_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "participant_ev_snapshots_sports_season_id_sports_seasons_id_fk": {
+ "name": "participant_ev_snapshots_sports_season_id_sports_seasons_id_fk",
+ "tableFrom": "participant_ev_snapshots",
+ "tableTo": "sports_seasons",
+ "columnsFrom": [
+ "sports_season_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.participant_golf_skills": {
+ "name": "participant_golf_skills",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "participant_id": {
+ "name": "participant_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "sg_total": {
+ "name": "sg_total",
+ "type": "numeric(5, 2)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "datagolf_rank": {
+ "name": "datagolf_rank",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "masters_odds": {
+ "name": "masters_odds",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "us_open_odds": {
+ "name": "us_open_odds",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "open_championship_odds": {
+ "name": "open_championship_odds",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "pga_championship_odds": {
+ "name": "pga_championship_odds",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "participant_golf_skills_participant_unique": {
+ "name": "participant_golf_skills_participant_unique",
+ "columns": [
+ {
+ "expression": "participant_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "participant_golf_skills_participant_id_participants_id_fk": {
+ "name": "participant_golf_skills_participant_id_participants_id_fk",
+ "tableFrom": "participant_golf_skills",
+ "tableTo": "participants",
+ "columnsFrom": [
+ "participant_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.participant_season_results": {
+ "name": "participant_season_results",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "participant_id": {
+ "name": "participant_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "sports_season_id": {
+ "name": "sports_season_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "current_points": {
+ "name": "current_points",
+ "type": "numeric(10, 2)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "current_position": {
+ "name": "current_position",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "psr_participant_season_idx": {
+ "name": "psr_participant_season_idx",
+ "columns": [
+ {
+ "expression": "participant_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sports_season_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "participant_season_results_participant_id_season_participants_id_fk": {
+ "name": "participant_season_results_participant_id_season_participants_id_fk",
+ "tableFrom": "participant_season_results",
+ "tableTo": "season_participants",
+ "columnsFrom": [
+ "participant_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "participant_season_results_sports_season_id_sports_seasons_id_fk": {
+ "name": "participant_season_results_sports_season_id_sports_seasons_id_fk",
+ "tableFrom": "participant_season_results",
+ "tableTo": "sports_seasons",
+ "columnsFrom": [
+ "sports_season_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.participant_surface_elos": {
+ "name": "participant_surface_elos",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "participant_id": {
+ "name": "participant_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "world_ranking": {
+ "name": "world_ranking",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "elo_hard": {
+ "name": "elo_hard",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "elo_clay": {
+ "name": "elo_clay",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "elo_grass": {
+ "name": "elo_grass",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "participant_surface_elos_participant_unique": {
+ "name": "participant_surface_elos_participant_unique",
+ "columns": [
+ {
+ "expression": "participant_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "participant_surface_elos_participant_id_participants_id_fk": {
+ "name": "participant_surface_elos_participant_id_participants_id_fk",
+ "tableFrom": "participant_surface_elos",
+ "tableTo": "participants",
+ "columnsFrom": [
+ "participant_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.participants": {
+ "name": "participants",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "sport_id": {
+ "name": "sport_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "external_key": {
+ "name": "external_key",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "participants_sport_name_unique": {
+ "name": "participants_sport_name_unique",
+ "columns": [
+ {
+ "expression": "sport_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "participants_sport_id_sports_id_fk": {
+ "name": "participants_sport_id_sports_id_fk",
+ "tableFrom": "participants",
+ "tableTo": "sports",
+ "columnsFrom": [
+ "sport_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.pending_standings_mappings": {
+ "name": "pending_standings_mappings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "sports_season_id": {
+ "name": "sports_season_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "external_team_id": {
+ "name": "external_team_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "team_name": {
+ "name": "team_name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "standing_data": {
+ "name": "standing_data",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "psm_season_external_id_idx": {
+ "name": "psm_season_external_id_idx",
+ "columns": [
+ {
+ "expression": "sports_season_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "external_team_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "pending_standings_mappings_sports_season_id_sports_seasons_id_fk": {
+ "name": "pending_standings_mappings_sports_season_id_sports_seasons_id_fk",
+ "tableFrom": "pending_standings_mappings",
+ "tableTo": "sports_seasons",
+ "columnsFrom": [
+ "sports_season_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.playoff_match_games": {
+ "name": "playoff_match_games",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "playoff_match_id": {
+ "name": "playoff_match_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "game_number": {
+ "name": "game_number",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scheduled_at": {
+ "name": "scheduled_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "playoff_match_game_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'scheduled'"
+ },
+ "participant1_score": {
+ "name": "participant1_score",
+ "type": "numeric(10, 2)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "participant2_score": {
+ "name": "participant2_score",
+ "type": "numeric(10, 2)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "winner_id": {
+ "name": "winner_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "notes": {
+ "name": "notes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "playoff_match_games_playoff_match_id_playoff_matches_id_fk": {
+ "name": "playoff_match_games_playoff_match_id_playoff_matches_id_fk",
+ "tableFrom": "playoff_match_games",
+ "tableTo": "playoff_matches",
+ "columnsFrom": [
+ "playoff_match_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "playoff_match_games_winner_id_season_participants_id_fk": {
+ "name": "playoff_match_games_winner_id_season_participants_id_fk",
+ "tableFrom": "playoff_match_games",
+ "tableTo": "season_participants",
+ "columnsFrom": [
+ "winner_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.playoff_match_odds": {
+ "name": "playoff_match_odds",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "playoff_match_id": {
+ "name": "playoff_match_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "participant_id": {
+ "name": "participant_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "moneyline_odds": {
+ "name": "moneyline_odds",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "implied_probability": {
+ "name": "implied_probability",
+ "type": "numeric(6, 4)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "odds_source": {
+ "name": "odds_source",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "recorded_at": {
+ "name": "recorded_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "playoff_match_odds_playoff_match_id_playoff_matches_id_fk": {
+ "name": "playoff_match_odds_playoff_match_id_playoff_matches_id_fk",
+ "tableFrom": "playoff_match_odds",
+ "tableTo": "playoff_matches",
+ "columnsFrom": [
+ "playoff_match_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "playoff_match_odds_participant_id_season_participants_id_fk": {
+ "name": "playoff_match_odds_participant_id_season_participants_id_fk",
+ "tableFrom": "playoff_match_odds",
+ "tableTo": "season_participants",
+ "columnsFrom": [
+ "participant_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.playoff_matches": {
+ "name": "playoff_matches",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "scoring_event_id": {
+ "name": "scoring_event_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "round": {
+ "name": "round",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "match_number": {
+ "name": "match_number",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "participant1_id": {
+ "name": "participant1_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "participant2_id": {
+ "name": "participant2_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "winner_id": {
+ "name": "winner_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "loser_id": {
+ "name": "loser_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_complete": {
+ "name": "is_complete",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "participant1_score": {
+ "name": "participant1_score",
+ "type": "numeric(10, 2)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "participant2_score": {
+ "name": "participant2_score",
+ "type": "numeric(10, 2)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_scoring": {
+ "name": "is_scoring",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "template_round": {
+ "name": "template_round",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "seed_info": {
+ "name": "seed_info",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "external_match_id": {
+ "name": "external_match_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": {
+ "playoff_matches_scoring_event_id_scoring_events_id_fk": {
+ "name": "playoff_matches_scoring_event_id_scoring_events_id_fk",
+ "tableFrom": "playoff_matches",
+ "tableTo": "scoring_events",
+ "columnsFrom": [
+ "scoring_event_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "playoff_matches_participant1_id_season_participants_id_fk": {
+ "name": "playoff_matches_participant1_id_season_participants_id_fk",
+ "tableFrom": "playoff_matches",
+ "tableTo": "season_participants",
+ "columnsFrom": [
+ "participant1_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "playoff_matches_participant2_id_season_participants_id_fk": {
+ "name": "playoff_matches_participant2_id_season_participants_id_fk",
+ "tableFrom": "playoff_matches",
+ "tableTo": "season_participants",
+ "columnsFrom": [
+ "participant2_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "playoff_matches_winner_id_season_participants_id_fk": {
+ "name": "playoff_matches_winner_id_season_participants_id_fk",
+ "tableFrom": "playoff_matches",
+ "tableTo": "season_participants",
+ "columnsFrom": [
+ "winner_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "playoff_matches_loser_id_season_participants_id_fk": {
+ "name": "playoff_matches_loser_id_season_participants_id_fk",
+ "tableFrom": "playoff_matches",
+ "tableTo": "season_participants",
+ "columnsFrom": [
+ "loser_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.qualifying_point_config": {
+ "name": "qualifying_point_config",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "sports_season_id": {
+ "name": "sports_season_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "placement": {
+ "name": "placement",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "points": {
+ "name": "points",
+ "type": "numeric(10, 2)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "qualifying_point_config_sports_season_id_sports_seasons_id_fk": {
+ "name": "qualifying_point_config_sports_season_id_sports_seasons_id_fk",
+ "tableFrom": "qualifying_point_config",
+ "tableTo": "sports_seasons",
+ "columnsFrom": [
+ "sports_season_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.regular_season_standings": {
+ "name": "regular_season_standings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "participant_id": {
+ "name": "participant_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "sports_season_id": {
+ "name": "sports_season_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "wins": {
+ "name": "wins",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "losses": {
+ "name": "losses",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "ot_losses": {
+ "name": "ot_losses",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ties": {
+ "name": "ties",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "table_points": {
+ "name": "table_points",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "goals_for": {
+ "name": "goals_for",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "goals_against": {
+ "name": "goals_against",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "goal_difference": {
+ "name": "goal_difference",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "win_pct": {
+ "name": "win_pct",
+ "type": "numeric(5, 4)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "games_played": {
+ "name": "games_played",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "games_back": {
+ "name": "games_back",
+ "type": "numeric(5, 1)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "conference": {
+ "name": "conference",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "division": {
+ "name": "division",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "conference_rank": {
+ "name": "conference_rank",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "division_rank": {
+ "name": "division_rank",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "league_rank": {
+ "name": "league_rank",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "streak": {
+ "name": "streak",
+ "type": "varchar(20)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_ten": {
+ "name": "last_ten",
+ "type": "varchar(15)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "home_record": {
+ "name": "home_record",
+ "type": "varchar(15)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "away_record": {
+ "name": "away_record",
+ "type": "varchar(15)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "external_team_id": {
+ "name": "external_team_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "srs": {
+ "name": "srs",
+ "type": "numeric(6, 2)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "synced_at": {
+ "name": "synced_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "rss_participant_season_idx": {
+ "name": "rss_participant_season_idx",
+ "columns": [
+ {
+ "expression": "participant_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sports_season_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "regular_season_standings_participant_id_season_participants_id_fk": {
+ "name": "regular_season_standings_participant_id_season_participants_id_fk",
+ "tableFrom": "regular_season_standings",
+ "tableTo": "season_participants",
+ "columnsFrom": [
+ "participant_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "regular_season_standings_sports_season_id_sports_seasons_id_fk": {
+ "name": "regular_season_standings_sports_season_id_sports_seasons_id_fk",
+ "tableFrom": "regular_season_standings",
+ "tableTo": "sports_seasons",
+ "columnsFrom": [
+ "sports_season_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.scoring_events": {
+ "name": "scoring_events",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "sports_season_id": {
+ "name": "sports_season_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tournament_id": {
+ "name": "tournament_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "event_date": {
+ "name": "event_date",
+ "type": "date",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "event_starts_at": {
+ "name": "event_starts_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "event_type": {
+ "name": "event_type",
+ "type": "event_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "playoff_round": {
+ "name": "playoff_round",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_qualifying_event": {
+ "name": "is_qualifying_event",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "bracket_template_id": {
+ "name": "bracket_template_id",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scoring_starts_at_round": {
+ "name": "scoring_starts_at_round",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bracket_region_config": {
+ "name": "bracket_region_config",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "external_source_key": {
+ "name": "external_source_key",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_complete": {
+ "name": "is_complete",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_primary": {
+ "name": "is_primary",
+ "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": {
+ "scoring_events_sports_season_id_sports_seasons_id_fk": {
+ "name": "scoring_events_sports_season_id_sports_seasons_id_fk",
+ "tableFrom": "scoring_events",
+ "tableTo": "sports_seasons",
+ "columnsFrom": [
+ "sports_season_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "scoring_events_tournament_id_tournaments_id_fk": {
+ "name": "scoring_events_tournament_id_tournaments_id_fk",
+ "tableFrom": "scoring_events",
+ "tableTo": "tournaments",
+ "columnsFrom": [
+ "tournament_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "scoring_events_qualifying_require_tournament": {
+ "name": "scoring_events_qualifying_require_tournament",
+ "value": "NOT \"scoring_events\".\"is_qualifying_event\" OR \"scoring_events\".\"tournament_id\" IS NOT NULL"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.season_matches": {
+ "name": "season_matches",
+ "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
+ },
+ "scoring_event_id": {
+ "name": "scoring_event_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "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
+ },
+ "participant1_score": {
+ "name": "participant1_score",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "participant2_score": {
+ "name": "participant2_score",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "match_stage": {
+ "name": "match_stage",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "match_round": {
+ "name": "match_round",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "matchday": {
+ "name": "matchday",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_series": {
+ "name": "is_series",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "status": {
+ "name": "status",
+ "type": "match_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'scheduled'"
+ },
+ "scheduled_at": {
+ "name": "scheduled_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "external_match_id": {
+ "name": "external_match_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": {
+ "season_matches_external_id_unique": {
+ "name": "season_matches_external_id_unique",
+ "columns": [
+ {
+ "expression": "external_match_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"season_matches\".\"external_match_id\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "season_matches_sports_season_status_idx": {
+ "name": "season_matches_sports_season_status_idx",
+ "columns": [
+ {
+ "expression": "sports_season_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "season_matches_sports_season_stage_round_idx": {
+ "name": "season_matches_sports_season_stage_round_idx",
+ "columns": [
+ {
+ "expression": "sports_season_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "match_stage",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "match_round",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "season_matches_scoring_event_idx": {
+ "name": "season_matches_scoring_event_idx",
+ "columns": [
+ {
+ "expression": "scoring_event_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "season_matches_sports_season_id_sports_seasons_id_fk": {
+ "name": "season_matches_sports_season_id_sports_seasons_id_fk",
+ "tableFrom": "season_matches",
+ "tableTo": "sports_seasons",
+ "columnsFrom": [
+ "sports_season_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "season_matches_scoring_event_id_scoring_events_id_fk": {
+ "name": "season_matches_scoring_event_id_scoring_events_id_fk",
+ "tableFrom": "season_matches",
+ "tableTo": "scoring_events",
+ "columnsFrom": [
+ "scoring_event_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "season_matches_participant1_id_season_participants_id_fk": {
+ "name": "season_matches_participant1_id_season_participants_id_fk",
+ "tableFrom": "season_matches",
+ "tableTo": "season_participants",
+ "columnsFrom": [
+ "participant1_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "season_matches_participant2_id_season_participants_id_fk": {
+ "name": "season_matches_participant2_id_season_participants_id_fk",
+ "tableFrom": "season_matches",
+ "tableTo": "season_participants",
+ "columnsFrom": [
+ "participant2_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "season_matches_winner_id_season_participants_id_fk": {
+ "name": "season_matches_winner_id_season_participants_id_fk",
+ "tableFrom": "season_matches",
+ "tableTo": "season_participants",
+ "columnsFrom": [
+ "winner_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.season_participant_expected_values": {
+ "name": "season_participant_expected_values",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "participant_id": {
+ "name": "participant_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "sports_season_id": {
+ "name": "sports_season_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "prob_first": {
+ "name": "prob_first",
+ "type": "numeric(6, 4)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "prob_second": {
+ "name": "prob_second",
+ "type": "numeric(6, 4)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "prob_third": {
+ "name": "prob_third",
+ "type": "numeric(6, 4)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "prob_fourth": {
+ "name": "prob_fourth",
+ "type": "numeric(6, 4)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "prob_fifth": {
+ "name": "prob_fifth",
+ "type": "numeric(6, 4)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "prob_sixth": {
+ "name": "prob_sixth",
+ "type": "numeric(6, 4)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "prob_seventh": {
+ "name": "prob_seventh",
+ "type": "numeric(6, 4)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "prob_eighth": {
+ "name": "prob_eighth",
+ "type": "numeric(6, 4)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "expected_value": {
+ "name": "expected_value",
+ "type": "numeric(10, 4)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "source": {
+ "name": "source",
+ "type": "probability_source",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'manual'"
+ },
+ "source_odds": {
+ "name": "source_odds",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_elo": {
+ "name": "source_elo",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "world_ranking": {
+ "name": "world_ranking",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "calculated_at": {
+ "name": "calculated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "participant_ev_participant_season_unique": {
+ "name": "participant_ev_participant_season_unique",
+ "columns": [
+ {
+ "expression": "participant_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sports_season_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "season_participant_expected_values_participant_id_season_participants_id_fk": {
+ "name": "season_participant_expected_values_participant_id_season_participants_id_fk",
+ "tableFrom": "season_participant_expected_values",
+ "tableTo": "season_participants",
+ "columnsFrom": [
+ "participant_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "season_participant_expected_values_sports_season_id_sports_seasons_id_fk": {
+ "name": "season_participant_expected_values_sports_season_id_sports_seasons_id_fk",
+ "tableFrom": "season_participant_expected_values",
+ "tableTo": "sports_seasons",
+ "columnsFrom": [
+ "sports_season_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.season_participant_golf_skills": {
+ "name": "season_participant_golf_skills",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "participant_id": {
+ "name": "participant_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "sports_season_id": {
+ "name": "sports_season_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "sg_total": {
+ "name": "sg_total",
+ "type": "numeric(5, 2)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "datagolf_rank": {
+ "name": "datagolf_rank",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "masters_odds": {
+ "name": "masters_odds",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "us_open_odds": {
+ "name": "us_open_odds",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "open_championship_odds": {
+ "name": "open_championship_odds",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "pga_championship_odds": {
+ "name": "pga_championship_odds",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "season_participant_golf_skills_unique": {
+ "name": "season_participant_golf_skills_unique",
+ "columns": [
+ {
+ "expression": "participant_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sports_season_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "season_participant_golf_skills_participant_id_season_participants_id_fk": {
+ "name": "season_participant_golf_skills_participant_id_season_participants_id_fk",
+ "tableFrom": "season_participant_golf_skills",
+ "tableTo": "season_participants",
+ "columnsFrom": [
+ "participant_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "season_participant_golf_skills_sports_season_id_sports_seasons_id_fk": {
+ "name": "season_participant_golf_skills_sports_season_id_sports_seasons_id_fk",
+ "tableFrom": "season_participant_golf_skills",
+ "tableTo": "sports_seasons",
+ "columnsFrom": [
+ "sports_season_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.season_participant_qualifying_totals": {
+ "name": "season_participant_qualifying_totals",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "participant_id": {
+ "name": "participant_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "sports_season_id": {
+ "name": "sports_season_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "total_qualifying_points": {
+ "name": "total_qualifying_points",
+ "type": "numeric(10, 2)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "events_scored": {
+ "name": "events_scored",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "final_ranking": {
+ "name": "final_ranking",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "season_participant_qualifying_totals_participant_id_season_participants_id_fk": {
+ "name": "season_participant_qualifying_totals_participant_id_season_participants_id_fk",
+ "tableFrom": "season_participant_qualifying_totals",
+ "tableTo": "season_participants",
+ "columnsFrom": [
+ "participant_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "season_participant_qualifying_totals_sports_season_id_sports_seasons_id_fk": {
+ "name": "season_participant_qualifying_totals_sports_season_id_sports_seasons_id_fk",
+ "tableFrom": "season_participant_qualifying_totals",
+ "tableTo": "sports_seasons",
+ "columnsFrom": [
+ "sports_season_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.season_participant_results": {
+ "name": "season_participant_results",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "participant_id": {
+ "name": "participant_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "sports_season_id": {
+ "name": "sports_season_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "final_position": {
+ "name": "final_position",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_partial_score": {
+ "name": "is_partial_score",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "qualifying_points": {
+ "name": "qualifying_points",
+ "type": "numeric(10, 2)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "notes": {
+ "name": "notes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "season_participant_results_participant_id_season_participants_id_fk": {
+ "name": "season_participant_results_participant_id_season_participants_id_fk",
+ "tableFrom": "season_participant_results",
+ "tableTo": "season_participants",
+ "columnsFrom": [
+ "participant_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "season_participant_results_sports_season_id_sports_seasons_id_fk": {
+ "name": "season_participant_results_sports_season_id_sports_seasons_id_fk",
+ "tableFrom": "season_participant_results",
+ "tableTo": "sports_seasons",
+ "columnsFrom": [
+ "sports_season_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.season_participant_simulator_inputs": {
+ "name": "season_participant_simulator_inputs",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "participant_id": {
+ "name": "participant_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "sports_season_id": {
+ "name": "sports_season_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_odds": {
+ "name": "source_odds",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_elo": {
+ "name": "source_elo",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "world_ranking": {
+ "name": "world_ranking",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rating": {
+ "name": "rating",
+ "type": "numeric(10, 4)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "projected_wins": {
+ "name": "projected_wins",
+ "type": "numeric(6, 2)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "projected_table_points": {
+ "name": "projected_table_points",
+ "type": "numeric(6, 2)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "seed": {
+ "name": "seed",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "region": {
+ "name": "region",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "season_participant_simulator_inputs_unique": {
+ "name": "season_participant_simulator_inputs_unique",
+ "columns": [
+ {
+ "expression": "participant_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sports_season_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "season_participant_simulator_inputs_season_idx": {
+ "name": "season_participant_simulator_inputs_season_idx",
+ "columns": [
+ {
+ "expression": "sports_season_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "season_participant_simulator_inputs_participant_id_season_participants_id_fk": {
+ "name": "season_participant_simulator_inputs_participant_id_season_participants_id_fk",
+ "tableFrom": "season_participant_simulator_inputs",
+ "tableTo": "season_participants",
+ "columnsFrom": [
+ "participant_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "season_participant_simulator_inputs_sports_season_id_sports_seasons_id_fk": {
+ "name": "season_participant_simulator_inputs_sports_season_id_sports_seasons_id_fk",
+ "tableFrom": "season_participant_simulator_inputs",
+ "tableTo": "sports_seasons",
+ "columnsFrom": [
+ "sports_season_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.season_participants": {
+ "name": "season_participants",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "sports_season_id": {
+ "name": "sports_season_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "participant_id": {
+ "name": "participant_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "short_name": {
+ "name": "short_name",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expected_value": {
+ "name": "expected_value",
+ "type": "numeric(10, 4)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "vorp_value": {
+ "name": "vorp_value",
+ "type": "numeric(10, 4)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "participants_sports_season_name_unique": {
+ "name": "participants_sports_season_name_unique",
+ "columns": [
+ {
+ "expression": "sports_season_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "season_participants_sports_season_id_sports_seasons_id_fk": {
+ "name": "season_participants_sports_season_id_sports_seasons_id_fk",
+ "tableFrom": "season_participants",
+ "tableTo": "sports_seasons",
+ "columnsFrom": [
+ "sports_season_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "season_participants_participant_id_participants_id_fk": {
+ "name": "season_participants_participant_id_participants_id_fk",
+ "tableFrom": "season_participants",
+ "tableTo": "participants",
+ "columnsFrom": [
+ "participant_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.season_sports": {
+ "name": "season_sports",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "season_id": {
+ "name": "season_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "sports_season_id": {
+ "name": "sports_season_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "season_sports_season_id_seasons_id_fk": {
+ "name": "season_sports_season_id_seasons_id_fk",
+ "tableFrom": "season_sports",
+ "tableTo": "seasons",
+ "columnsFrom": [
+ "season_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "season_sports_sports_season_id_sports_seasons_id_fk": {
+ "name": "season_sports_sports_season_id_sports_seasons_id_fk",
+ "tableFrom": "season_sports",
+ "tableTo": "sports_seasons",
+ "columnsFrom": [
+ "sports_season_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.season_template_sports": {
+ "name": "season_template_sports",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "template_id": {
+ "name": "template_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "sport_id": {
+ "name": "sport_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "season_template_sports_template_sport_unique": {
+ "name": "season_template_sports_template_sport_unique",
+ "columns": [
+ {
+ "expression": "template_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sport_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "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_sport_id_sports_id_fk": {
+ "name": "season_template_sports_sport_id_sports_id_fk",
+ "tableFrom": "season_template_sports",
+ "tableTo": "sports",
+ "columnsFrom": [
+ "sport_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
+ },
+ "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": {
+ "season_templates_name_unique": {
+ "name": "season_templates_name_unique",
+ "columns": [
+ {
+ "expression": "name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "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
+ },
+ "auto_start_draft": {
+ "name": "auto_start_draft",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "draft_initial_time": {
+ "name": "draft_initial_time",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 120
+ },
+ "draft_increment_time": {
+ "name": "draft_increment_time",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 30
+ },
+ "draft_timer_mode": {
+ "name": "draft_timer_mode",
+ "type": "draft_timer_mode",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'chess_clock'"
+ },
+ "current_pick_number": {
+ "name": "current_pick_number",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 1
+ },
+ "draft_started_at": {
+ "name": "draft_started_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "draft_completed_at": {
+ "name": "draft_completed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "draft_paused": {
+ "name": "draft_paused",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "overnight_pause_mode": {
+ "name": "overnight_pause_mode",
+ "type": "overnight_pause_mode",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'none'"
+ },
+ "overnight_pause_start": {
+ "name": "overnight_pause_start",
+ "type": "varchar(5)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "overnight_pause_end": {
+ "name": "overnight_pause_end",
+ "type": "varchar(5)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "overnight_pause_timezone": {
+ "name": "overnight_pause_timezone",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "invite_code": {
+ "name": "invite_code",
+ "type": "varchar(20)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "points_for_1st": {
+ "name": "points_for_1st",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 100
+ },
+ "points_for_2nd": {
+ "name": "points_for_2nd",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 70
+ },
+ "points_for_3rd": {
+ "name": "points_for_3rd",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 50
+ },
+ "points_for_4th": {
+ "name": "points_for_4th",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 40
+ },
+ "points_for_5th": {
+ "name": "points_for_5th",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 25
+ },
+ "points_for_6th": {
+ "name": "points_for_6th",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 25
+ },
+ "points_for_7th": {
+ "name": "points_for_7th",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 15
+ },
+ "points_for_8th": {
+ "name": "points_for_8th",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 15
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "seasons_league_id_leagues_id_fk": {
+ "name": "seasons_league_id_leagues_id_fk",
+ "tableFrom": "seasons",
+ "tableTo": "leagues",
+ "columnsFrom": [
+ "league_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "seasons_template_id_season_templates_id_fk": {
+ "name": "seasons_template_id_season_templates_id_fk",
+ "tableFrom": "seasons",
+ "tableTo": "season_templates",
+ "columnsFrom": [
+ "template_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "seasons_invite_code_unique": {
+ "name": "seasons_invite_code_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "invite_code"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sessions": {
+ "name": "sessions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "sessions_user_id_users_id_fk": {
+ "name": "sessions_user_id_users_id_fk",
+ "tableFrom": "sessions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "sessions_token_unique": {
+ "name": "sessions_token_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "token"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.simulator_profiles": {
+ "name": "simulator_profiles",
+ "schema": "",
+ "columns": {
+ "simulator_type": {
+ "name": "simulator_type",
+ "type": "simulator_type",
+ "typeSchema": "public",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "display_name": {
+ "name": "display_name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "default_config": {
+ "name": "default_config",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "input_schema": {
+ "name": "input_schema",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sports": {
+ "name": "sports",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "sport_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "slug": {
+ "name": "slug",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "icon_url": {
+ "name": "icon_url",
+ "type": "varchar(512)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "simulator_type": {
+ "name": "simulator_type",
+ "type": "simulator_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "exclude_from_homepage": {
+ "name": "exclude_from_homepage",
+ "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": {
+ "sports_slug_unique": {
+ "name": "sports_slug_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "slug"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sports_season_simulator_configs": {
+ "name": "sports_season_simulator_configs",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "sports_season_id": {
+ "name": "sports_season_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "simulator_type": {
+ "name": "simulator_type",
+ "type": "simulator_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "config": {
+ "name": "config",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "sports_season_simulator_config_unique": {
+ "name": "sports_season_simulator_config_unique",
+ "columns": [
+ {
+ "expression": "sports_season_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "sports_season_simulator_config_type_idx": {
+ "name": "sports_season_simulator_config_type_idx",
+ "columns": [
+ {
+ "expression": "simulator_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "sports_season_simulator_configs_sports_season_id_sports_seasons_id_fk": {
+ "name": "sports_season_simulator_configs_sports_season_id_sports_seasons_id_fk",
+ "tableFrom": "sports_season_simulator_configs",
+ "tableTo": "sports_seasons",
+ "columnsFrom": [
+ "sports_season_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sports_seasons": {
+ "name": "sports_seasons",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "sport_id": {
+ "name": "sport_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "fantasy_season_id": {
+ "name": "fantasy_season_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "year": {
+ "name": "year",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "start_date": {
+ "name": "start_date",
+ "type": "date",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "end_date": {
+ "name": "end_date",
+ "type": "date",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "sports_season_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'upcoming'"
+ },
+ "external_season_id": {
+ "name": "external_season_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scoring_type": {
+ "name": "scoring_type",
+ "type": "scoring_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scoring_pattern": {
+ "name": "scoring_pattern",
+ "type": "scoring_pattern",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "total_majors": {
+ "name": "total_majors",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "majors_completed": {
+ "name": "majors_completed",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "qualifying_points_finalized": {
+ "name": "qualifying_points_finalized",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "elo_calibration_exponent": {
+ "name": "elo_calibration_exponent",
+ "type": "numeric(3, 2)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "elo_min_rating": {
+ "name": "elo_min_rating",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 1250
+ },
+ "elo_max_rating": {
+ "name": "elo_max_rating",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 1750
+ },
+ "simulation_status": {
+ "name": "simulation_status",
+ "type": "simulation_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'idle'"
+ },
+ "standings_last_changed_at": {
+ "name": "standings_last_changed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_simulated_at": {
+ "name": "last_simulated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "draft_on": {
+ "name": "draft_on",
+ "type": "date",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "draft_off": {
+ "name": "draft_off",
+ "type": "date",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "sports_seasons_sport_id_sports_id_fk": {
+ "name": "sports_seasons_sport_id_sports_id_fk",
+ "tableFrom": "sports_seasons",
+ "tableTo": "sports",
+ "columnsFrom": [
+ "sport_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "sports_seasons_fantasy_season_id_seasons_id_fk": {
+ "name": "sports_seasons_fantasy_season_id_seasons_id_fk",
+ "tableFrom": "sports_seasons",
+ "tableTo": "seasons",
+ "columnsFrom": [
+ "fantasy_season_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.team_ev_snapshots": {
+ "name": "team_ev_snapshots",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "team_id": {
+ "name": "team_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "season_id": {
+ "name": "season_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "snapshot_date": {
+ "name": "snapshot_date",
+ "type": "date",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "projected_points": {
+ "name": "projected_points",
+ "type": "numeric(10, 2)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "actual_points": {
+ "name": "actual_points",
+ "type": "numeric(10, 2)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "team_ev_snapshots_unique": {
+ "name": "team_ev_snapshots_unique",
+ "columns": [
+ {
+ "expression": "team_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "season_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "snapshot_date",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "team_ev_snapshots_team_id_teams_id_fk": {
+ "name": "team_ev_snapshots_team_id_teams_id_fk",
+ "tableFrom": "team_ev_snapshots",
+ "tableTo": "teams",
+ "columnsFrom": [
+ "team_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "team_ev_snapshots_season_id_seasons_id_fk": {
+ "name": "team_ev_snapshots_season_id_seasons_id_fk",
+ "tableFrom": "team_ev_snapshots",
+ "tableTo": "seasons",
+ "columnsFrom": [
+ "season_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.team_score_events": {
+ "name": "team_score_events",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "team_id": {
+ "name": "team_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "season_id": {
+ "name": "season_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scoring_event_id": {
+ "name": "scoring_event_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scoring_event_name": {
+ "name": "scoring_event_name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sport_name": {
+ "name": "sport_name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "match_id": {
+ "name": "match_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "participant_ids": {
+ "name": "participant_ids",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "points_delta": {
+ "name": "points_delta",
+ "type": "numeric(10, 2)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "occurred_at": {
+ "name": "occurred_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "team_score_events_match_unique": {
+ "name": "team_score_events_match_unique",
+ "columns": [
+ {
+ "expression": "team_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "season_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "match_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "match_id IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "team_score_events_event_unique": {
+ "name": "team_score_events_event_unique",
+ "columns": [
+ {
+ "expression": "team_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "season_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "scoring_event_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "match_id IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "team_score_events_team_id_teams_id_fk": {
+ "name": "team_score_events_team_id_teams_id_fk",
+ "tableFrom": "team_score_events",
+ "tableTo": "teams",
+ "columnsFrom": [
+ "team_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "team_score_events_season_id_seasons_id_fk": {
+ "name": "team_score_events_season_id_seasons_id_fk",
+ "tableFrom": "team_score_events",
+ "tableTo": "seasons",
+ "columnsFrom": [
+ "season_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "team_score_events_scoring_event_id_scoring_events_id_fk": {
+ "name": "team_score_events_scoring_event_id_scoring_events_id_fk",
+ "tableFrom": "team_score_events",
+ "tableTo": "scoring_events",
+ "columnsFrom": [
+ "scoring_event_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "team_score_events_match_id_playoff_matches_id_fk": {
+ "name": "team_score_events_match_id_playoff_matches_id_fk",
+ "tableFrom": "team_score_events",
+ "tableTo": "playoff_matches",
+ "columnsFrom": [
+ "match_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.team_sport_scores": {
+ "name": "team_sport_scores",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "team_id": {
+ "name": "team_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "sports_season_id": {
+ "name": "sports_season_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "total_points": {
+ "name": "total_points",
+ "type": "numeric(10, 2)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "participants_completed": {
+ "name": "participants_completed",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "participants_total": {
+ "name": "participants_total",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "calculated_at": {
+ "name": "calculated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "team_sport_scores_team_id_teams_id_fk": {
+ "name": "team_sport_scores_team_id_teams_id_fk",
+ "tableFrom": "team_sport_scores",
+ "tableTo": "teams",
+ "columnsFrom": [
+ "team_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "team_sport_scores_sports_season_id_sports_seasons_id_fk": {
+ "name": "team_sport_scores_sports_season_id_sports_seasons_id_fk",
+ "tableFrom": "team_sport_scores",
+ "tableTo": "sports_seasons",
+ "columnsFrom": [
+ "sports_season_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.team_standings": {
+ "name": "team_standings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "team_id": {
+ "name": "team_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "season_id": {
+ "name": "season_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "total_points": {
+ "name": "total_points",
+ "type": "numeric(10, 2)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "current_rank": {
+ "name": "current_rank",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "previous_rank": {
+ "name": "previous_rank",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "first_place_count": {
+ "name": "first_place_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "second_place_count": {
+ "name": "second_place_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "third_place_count": {
+ "name": "third_place_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "fourth_place_count": {
+ "name": "fourth_place_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "fifth_place_count": {
+ "name": "fifth_place_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "sixth_place_count": {
+ "name": "sixth_place_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "seventh_place_count": {
+ "name": "seventh_place_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "eighth_place_count": {
+ "name": "eighth_place_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "participants_remaining": {
+ "name": "participants_remaining",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "actual_points": {
+ "name": "actual_points",
+ "type": "numeric(10, 2)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "projected_points": {
+ "name": "projected_points",
+ "type": "numeric(10, 2)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "participants_finished": {
+ "name": "participants_finished",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "calculated_at": {
+ "name": "calculated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "team_standings_team_id_teams_id_fk": {
+ "name": "team_standings_team_id_teams_id_fk",
+ "tableFrom": "team_standings",
+ "tableTo": "teams",
+ "columnsFrom": [
+ "team_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "team_standings_season_id_seasons_id_fk": {
+ "name": "team_standings_season_id_seasons_id_fk",
+ "tableFrom": "team_standings",
+ "tableTo": "seasons",
+ "columnsFrom": [
+ "season_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.team_standings_snapshots": {
+ "name": "team_standings_snapshots",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "team_id": {
+ "name": "team_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "season_id": {
+ "name": "season_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "snapshot_date": {
+ "name": "snapshot_date",
+ "type": "date",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "total_points": {
+ "name": "total_points",
+ "type": "numeric(10, 2)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "rank": {
+ "name": "rank",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "first_place_count": {
+ "name": "first_place_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "second_place_count": {
+ "name": "second_place_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "third_place_count": {
+ "name": "third_place_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "fourth_place_count": {
+ "name": "fourth_place_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "fifth_place_count": {
+ "name": "fifth_place_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "sixth_place_count": {
+ "name": "sixth_place_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "seventh_place_count": {
+ "name": "seventh_place_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "eighth_place_count": {
+ "name": "eighth_place_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "participants_remaining": {
+ "name": "participants_remaining",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "actual_points": {
+ "name": "actual_points",
+ "type": "numeric(10, 2)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "projected_points": {
+ "name": "projected_points",
+ "type": "numeric(10, 2)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "participants_finished": {
+ "name": "participants_finished",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "team_standings_snapshots_unique": {
+ "name": "team_standings_snapshots_unique",
+ "columns": [
+ {
+ "expression": "team_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "season_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "snapshot_date",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "team_standings_snapshots_team_id_teams_id_fk": {
+ "name": "team_standings_snapshots_team_id_teams_id_fk",
+ "tableFrom": "team_standings_snapshots",
+ "tableTo": "teams",
+ "columnsFrom": [
+ "team_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "team_standings_snapshots_season_id_seasons_id_fk": {
+ "name": "team_standings_snapshots_season_id_seasons_id_fk",
+ "tableFrom": "team_standings_snapshots",
+ "tableTo": "seasons",
+ "columnsFrom": [
+ "season_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.teams": {
+ "name": "teams",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "season_id": {
+ "name": "season_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "logo_url": {
+ "name": "logo_url",
+ "type": "varchar(512)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "flag_config": {
+ "name": "flag_config",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "avatar_type": {
+ "name": "avatar_type",
+ "type": "varchar(20)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'owner'"
+ },
+ "owner_id": {
+ "name": "owner_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "teams_season_id_lower_name_unique": {
+ "name": "teams_season_id_lower_name_unique",
+ "columns": [
+ {
+ "expression": "season_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "lower(\"name\")",
+ "asc": true,
+ "isExpression": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "teams_season_id_seasons_id_fk": {
+ "name": "teams_season_id_seasons_id_fk",
+ "tableFrom": "teams",
+ "tableTo": "seasons",
+ "columnsFrom": [
+ "season_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tournament_group_members": {
+ "name": "tournament_group_members",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "tournament_group_id": {
+ "name": "tournament_group_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "participant_id": {
+ "name": "participant_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "eliminated": {
+ "name": "eliminated",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "tournament_group_members_tournament_group_id_tournament_groups_id_fk": {
+ "name": "tournament_group_members_tournament_group_id_tournament_groups_id_fk",
+ "tableFrom": "tournament_group_members",
+ "tableTo": "tournament_groups",
+ "columnsFrom": [
+ "tournament_group_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "tournament_group_members_participant_id_season_participants_id_fk": {
+ "name": "tournament_group_members_participant_id_season_participants_id_fk",
+ "tableFrom": "tournament_group_members",
+ "tableTo": "season_participants",
+ "columnsFrom": [
+ "participant_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tournament_groups": {
+ "name": "tournament_groups",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "scoring_event_id": {
+ "name": "scoring_event_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "group_name": {
+ "name": "group_name",
+ "type": "varchar(10)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "tournament_groups_scoring_event_id_idx": {
+ "name": "tournament_groups_scoring_event_id_idx",
+ "columns": [
+ {
+ "expression": "scoring_event_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "tournament_groups_scoring_event_id_scoring_events_id_fk": {
+ "name": "tournament_groups_scoring_event_id_scoring_events_id_fk",
+ "tableFrom": "tournament_groups",
+ "tableTo": "scoring_events",
+ "columnsFrom": [
+ "scoring_event_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tournament_results": {
+ "name": "tournament_results",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "tournament_id": {
+ "name": "tournament_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "participant_id": {
+ "name": "participant_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "placement": {
+ "name": "placement",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "raw_score": {
+ "name": "raw_score",
+ "type": "numeric(10, 2)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "tournament_results_tournament_participant_unique": {
+ "name": "tournament_results_tournament_participant_unique",
+ "columns": [
+ {
+ "expression": "tournament_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "participant_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "tournament_results_tournament_id_tournaments_id_fk": {
+ "name": "tournament_results_tournament_id_tournaments_id_fk",
+ "tableFrom": "tournament_results",
+ "tableTo": "tournaments",
+ "columnsFrom": [
+ "tournament_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "tournament_results_participant_id_participants_id_fk": {
+ "name": "tournament_results_participant_id_participants_id_fk",
+ "tableFrom": "tournament_results",
+ "tableTo": "participants",
+ "columnsFrom": [
+ "participant_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tournaments": {
+ "name": "tournaments",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "sport_id": {
+ "name": "sport_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "year": {
+ "name": "year",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "starts_at": {
+ "name": "starts_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ends_at": {
+ "name": "ends_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "surface": {
+ "name": "surface",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "location": {
+ "name": "location",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "tournament_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'scheduled'"
+ },
+ "external_key": {
+ "name": "external_key",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "tournaments_sport_name_year_unique": {
+ "name": "tournaments_sport_name_year_unique",
+ "columns": [
+ {
+ "expression": "sport_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "year",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "tournaments_sport_id_sports_id_fk": {
+ "name": "tournaments_sport_id_sports_id_fk",
+ "tableFrom": "tournaments",
+ "tableTo": "sports",
+ "columnsFrom": [
+ "sport_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.users": {
+ "name": "users",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "clerk_id": {
+ "name": "clerk_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "email": {
+ "name": "email",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email_verified": {
+ "name": "email_verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "username": {
+ "name": "username",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "display_name": {
+ "name": "display_name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image_url": {
+ "name": "image_url",
+ "type": "varchar(512)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "flag_config": {
+ "name": "flag_config",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "custom_avatar_url": {
+ "name": "custom_avatar_url",
+ "type": "varchar(512)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "avatar_type": {
+ "name": "avatar_type",
+ "type": "varchar(20)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'flag'"
+ },
+ "is_admin": {
+ "name": "is_admin",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "timezone": {
+ "name": "timezone",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "discord_ping_enabled": {
+ "name": "discord_ping_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "draft_email_notifications_enabled": {
+ "name": "draft_email_notifications_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "last_data_request_at": {
+ "name": "last_data_request_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "users_username_lower_unique": {
+ "name": "users_username_lower_unique",
+ "columns": [
+ {
+ "expression": "lower(\"username\")",
+ "asc": true,
+ "isExpression": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "users_clerk_id_unique": {
+ "name": "users_clerk_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "clerk_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.verifications": {
+ "name": "verifications",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.watchlist": {
+ "name": "watchlist",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "season_id": {
+ "name": "season_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "team_id": {
+ "name": "team_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "participant_id": {
+ "name": "participant_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "watchlist_season_team_participant_unique": {
+ "name": "watchlist_season_team_participant_unique",
+ "columns": [
+ {
+ "expression": "season_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "team_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "participant_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "watchlist_season_id_seasons_id_fk": {
+ "name": "watchlist_season_id_seasons_id_fk",
+ "tableFrom": "watchlist",
+ "tableTo": "seasons",
+ "columnsFrom": [
+ "season_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "watchlist_team_id_teams_id_fk": {
+ "name": "watchlist_team_id_teams_id_fk",
+ "tableFrom": "watchlist",
+ "tableTo": "teams",
+ "columnsFrom": [
+ "team_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "watchlist_participant_id_season_participants_id_fk": {
+ "name": "watchlist_participant_id_season_participants_id_fk",
+ "tableFrom": "watchlist",
+ "tableTo": "season_participants",
+ "columnsFrom": [
+ "participant_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {
+ "public.audit_action": {
+ "name": "audit_action",
+ "schema": "public",
+ "values": [
+ "league_settings_changed",
+ "draft_settings_changed",
+ "scoring_rules_changed",
+ "sports_changed",
+ "draft_order_set",
+ "draft_order_randomized",
+ "draft_started",
+ "draft_paused",
+ "draft_resumed",
+ "draft_reset",
+ "draft_rollback",
+ "force_autopick",
+ "force_manual_pick",
+ "draft_pick_changed",
+ "time_bank_edited",
+ "brackt_resolved"
+ ]
+ },
+ "public.autodraft_mode": {
+ "name": "autodraft_mode",
+ "schema": "public",
+ "values": [
+ "next_pick",
+ "while_on"
+ ]
+ },
+ "public.draft_timer_mode": {
+ "name": "draft_timer_mode",
+ "schema": "public",
+ "values": [
+ "chess_clock",
+ "standard"
+ ]
+ },
+ "public.event_type": {
+ "name": "event_type",
+ "schema": "public",
+ "values": [
+ "playoff_game",
+ "major_tournament",
+ "final_standings",
+ "schedule_event"
+ ]
+ },
+ "public.match_status": {
+ "name": "match_status",
+ "schema": "public",
+ "values": [
+ "scheduled",
+ "in_progress",
+ "complete",
+ "canceled",
+ "postponed"
+ ]
+ },
+ "public.overnight_pause_mode": {
+ "name": "overnight_pause_mode",
+ "schema": "public",
+ "values": [
+ "none",
+ "league",
+ "per_user"
+ ]
+ },
+ "public.picked_by_type": {
+ "name": "picked_by_type",
+ "schema": "public",
+ "values": [
+ "owner",
+ "commissioner",
+ "admin",
+ "auto"
+ ]
+ },
+ "public.playoff_match_game_status": {
+ "name": "playoff_match_game_status",
+ "schema": "public",
+ "values": [
+ "scheduled",
+ "complete",
+ "postponed"
+ ]
+ },
+ "public.probability_source": {
+ "name": "probability_source",
+ "schema": "public",
+ "values": [
+ "manual",
+ "futures_odds",
+ "elo_simulation",
+ "performance_model"
+ ]
+ },
+ "public.scoring_pattern": {
+ "name": "scoring_pattern",
+ "schema": "public",
+ "values": [
+ "playoff_bracket",
+ "season_standings",
+ "qualifying_points"
+ ]
+ },
+ "public.scoring_type": {
+ "name": "scoring_type",
+ "schema": "public",
+ "values": [
+ "playoffs",
+ "regular_season",
+ "majors"
+ ]
+ },
+ "public.season_status": {
+ "name": "season_status",
+ "schema": "public",
+ "values": [
+ "pre_draft",
+ "draft",
+ "active",
+ "completed"
+ ]
+ },
+ "public.simulation_status": {
+ "name": "simulation_status",
+ "schema": "public",
+ "values": [
+ "idle",
+ "running",
+ "failed"
+ ]
+ },
+ "public.simulator_type": {
+ "name": "simulator_type",
+ "schema": "public",
+ "values": [
+ "f1_standings",
+ "indycar_standings",
+ "golf_qualifying_points",
+ "playoff_bracket",
+ "ucl_bracket",
+ "ncaam_bracket",
+ "ncaaw_bracket",
+ "nba_bracket",
+ "nhl_bracket",
+ "nfl_bracket",
+ "afl_bracket",
+ "epl_standings",
+ "snooker_bracket",
+ "tennis_qualifying_points",
+ "mlb_bracket",
+ "wnba_bracket",
+ "world_cup",
+ "darts_bracket",
+ "cs2_major_qualifying_points",
+ "ncaa_football_bracket",
+ "llws_bracket",
+ "college_hockey_bracket",
+ "brackt",
+ "nll_bracket",
+ "mls_bracket"
+ ]
+ },
+ "public.sport_type": {
+ "name": "sport_type",
+ "schema": "public",
+ "values": [
+ "team",
+ "individual"
+ ]
+ },
+ "public.sports_season_status": {
+ "name": "sports_season_status",
+ "schema": "public",
+ "values": [
+ "upcoming",
+ "active",
+ "completed"
+ ]
+ },
+ "public.tournament_status": {
+ "name": "tournament_status",
+ "schema": "public",
+ "values": [
+ "scheduled",
+ "in_progress",
+ "completed"
+ ]
+ }
+ },
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json
index 8640b1f..62f229e 100644
--- a/drizzle/meta/_journal.json
+++ b/drizzle/meta/_journal.json
@@ -862,6 +862,13 @@
"when": 1782103950846,
"tag": "0122_soft_longshot",
"breakpoints": true
+ },
+ {
+ "idx": 123,
+ "version": "7",
+ "when": 1782250520349,
+ "tag": "0123_previous_expediter",
+ "breakpoints": true
}
]
}
\ No newline at end of file