From 81d063faa1529fb7d05c76c131b4bb9c27a4342b Mon Sep 17 00:00:00 2001 From: Chris Parsons Date: Fri, 26 Jun 2026 00:32:35 -0700 Subject: [PATCH] Auto-populate & auto-score tennis Grand Slam brackets from Wikipedia Add a per-event "Sync Draw" that pulls a tennis major's full 128-player draw from its Wikipedia article, auto-creates/links participants (and propagates them to every linked sibling season), builds the bracket, and runs the qualifying-points scorer. Re-running advances the bracket and scoring as matches complete. Core - match-sync: WikipediaTennisAdapter + wikitext bracket parser, DrawSync DTOs, syncTennisDraw orchestrator, pure draw->rows mapping - playoff-match: populateBracketFromDraw (idempotent upsert on externalMatchId) - scoring_events.externalSourceKey column (Wikipedia article; migration 0123) - admin bracket "Sync Draw" card (accepts URL or title) + cron pass Scoring fix - deriveBracketQualifyingStates only floors players who have reached the scoring stage; for deep brackets (tennis_128) early-round losers earn 0 QP instead of a phantom 9th-place floor. CS2/simple_8 behavior preserved (gated on rounds[0].isScoring). Re-sync reconciles stale QP rows in a transaction. Matching & parsing - accent-folding in normalizeTeamName; strip Wikipedia "(tennis)" disambiguators; treat Bye/TBD/Qualifier as TBD; extract wikilink before template-stripping so {{nowrap}}-wrapped players parse Admin UX - dry-run preview (matched / will-create / possible duplicates / unfilled slots) with inline "rename existing" / "create as new" resolution via fetcher (no full reload) Tests: parser vs real 2025 Wimbledon fixture, draw mapping, tennis_128 scoring, accent/disambiguator/nowrap parsing, URL parsing. Co-Authored-By: Claude Opus 4.8 --- app/lib/__tests__/normalize-team-name.test.ts | 11 + app/lib/normalize-team-name.ts | 12 +- .../qualifying-bracket-scoring.test.ts | 51 + app/models/playoff-match.ts | 90 + app/models/scoring-calculator.ts | 29 +- app/models/scoring-event.ts | 3 + ...sons.$id.events.$eventId.bracket.server.ts | 74 +- ...ts-seasons.$id.events.$eventId.bracket.tsx | 216 +- app/routes/admin/jobs.sync-matches.ts | 38 +- .../wimbledon-2025-mens-singles.wikitext | 2181 ++++++ .../__tests__/tennis-draw-mapping.test.ts | 147 + .../__tests__/wikipedia-tennis.test.ts | 176 + app/services/match-sync/index.ts | 374 +- .../match-sync/tennis-draw-mapping.ts | 63 + app/services/match-sync/types.ts | 100 + app/services/match-sync/wikipedia-tennis.ts | 307 + database/schema.ts | 4 + drizzle/0123_previous_expediter.sql | 1 + drizzle/meta/0123_snapshot.json | 6747 +++++++++++++++++ drizzle/meta/_journal.json | 7 + 20 files changed, 10616 insertions(+), 15 deletions(-) create mode 100644 app/services/match-sync/__tests__/fixtures/wimbledon-2025-mens-singles.wikitext create mode 100644 app/services/match-sync/__tests__/tennis-draw-mapping.test.ts create mode 100644 app/services/match-sync/__tests__/wikipedia-tennis.test.ts create mode 100644 app/services/match-sync/tennis-draw-mapping.ts create mode 100644 app/services/match-sync/wikipedia-tennis.ts create mode 100644 drizzle/0123_previous_expediter.sql create mode 100644 drizzle/meta/0123_snapshot.json 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} + ) : ( + + + + + + + + )} + {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. + + + +
    +
    + + +

    + Paste the article URL or type the title — both work. Use{" "} + Preview first to see matches and any + new/duplicate players before writing anything. +

    +
    +
    + + +
    +
    +
    +
    + )} + {/* 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