Add tennis Grand Slam simulator with surface Elo ratings, fixes #116 (#216)
Implements a Monte Carlo simulator for men's/women's tennis seasons scored
on the qualifying_points pattern. Simulates all 4 Grand Slam majors
(Australian Open, French Open, Wimbledon, US Open) using surface-specific
Elo ratings and ATP/WTA world rankings for seeding.
New table: participant_surface_elos — one row per (participant, season)
storing worldRanking, eloHard, eloClay, eloGrass.
Key design decisions:
- Seeding uses ATP/WTA world ranking (not Elo), matching real draw procedure
- Top 32 seeded with standard slot placement (1→0, 2→64, 3-4→quarters, etc.)
- QP per round with tie-splitting pre-applied: W=20, F=14, SF=9, QF=4, R16=1.5
- Completed majors read actual qualifyingPointsAwarded from eventResults
- 10,000 Monte Carlo simulations; column sums naturally 1.0 (no normalization)
Admin UI at /admin/sports-seasons/:id/surface-elo:
- 5-column grid (Player | Rank | Hard | Clay | Grass)
- Bulk import: "Name, ranking, hardElo, clayElo, grassElo" one per line
- Fuzzy name matching (bigram Dice coefficient) with "Did you mean?" suggestions
- Inline participant creation for unmatched names via useFetcher
- Saves Elos and auto-runs simulation on submit
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 23:59:35 -07:00
|
|
|
/**
|
2026-05-01 23:01:59 +00:00
|
|
|
* Surface Elo model (canonical).
|
Add tennis Grand Slam simulator with surface Elo ratings, fixes #116 (#216)
Implements a Monte Carlo simulator for men's/women's tennis seasons scored
on the qualifying_points pattern. Simulates all 4 Grand Slam majors
(Australian Open, French Open, Wimbledon, US Open) using surface-specific
Elo ratings and ATP/WTA world rankings for seeding.
New table: participant_surface_elos — one row per (participant, season)
storing worldRanking, eloHard, eloClay, eloGrass.
Key design decisions:
- Seeding uses ATP/WTA world ranking (not Elo), matching real draw procedure
- Top 32 seeded with standard slot placement (1→0, 2→64, 3-4→quarters, etc.)
- QP per round with tie-splitting pre-applied: W=20, F=14, SF=9, QF=4, R16=1.5
- Completed majors read actual qualifyingPointsAwarded from eventResults
- 10,000 Monte Carlo simulations; column sums naturally 1.0 (no normalization)
Admin UI at /admin/sports-seasons/:id/surface-elo:
- 5-column grid (Player | Rank | Hard | Clay | Grass)
- Bulk import: "Name, ranking, hardElo, clayElo, grassElo" one per line
- Fuzzy name matching (bigram Dice coefficient) with "Did you mean?" suggestions
- Inline participant creation for unmatched names via useFetcher
- Saves Elos and auto-runs simulation on submit
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 23:59:35 -07:00
|
|
|
*
|
2026-05-01 23:01:59 +00:00
|
|
|
* Surface Elos are stored in the canonical `participant_surface_elos` table,
|
|
|
|
|
* keyed by canonical participant id. The admin UI still works in terms of
|
|
|
|
|
* season_participants — we join through season_participants → canonical
|
|
|
|
|
* participant → canonical surface Elo so the UI doesn't need to know about
|
|
|
|
|
* the canonical layer.
|
|
|
|
|
*
|
|
|
|
|
* See `docs/superpowers/specs/2026-05-01-canonical-tournament-layer-design.md`.
|
Add tennis Grand Slam simulator with surface Elo ratings, fixes #116 (#216)
Implements a Monte Carlo simulator for men's/women's tennis seasons scored
on the qualifying_points pattern. Simulates all 4 Grand Slam majors
(Australian Open, French Open, Wimbledon, US Open) using surface-specific
Elo ratings and ATP/WTA world rankings for seeding.
New table: participant_surface_elos — one row per (participant, season)
storing worldRanking, eloHard, eloClay, eloGrass.
Key design decisions:
- Seeding uses ATP/WTA world ranking (not Elo), matching real draw procedure
- Top 32 seeded with standard slot placement (1→0, 2→64, 3-4→quarters, etc.)
- QP per round with tie-splitting pre-applied: W=20, F=14, SF=9, QF=4, R16=1.5
- Completed majors read actual qualifyingPointsAwarded from eventResults
- 10,000 Monte Carlo simulations; column sums naturally 1.0 (no normalization)
Admin UI at /admin/sports-seasons/:id/surface-elo:
- 5-column grid (Player | Rank | Hard | Clay | Grass)
- Bulk import: "Name, ranking, hardElo, clayElo, grassElo" one per line
- Fuzzy name matching (bigram Dice coefficient) with "Did you mean?" suggestions
- Inline participant creation for unmatched names via useFetcher
- Saves Elos and auto-runs simulation on submit
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 23:59:35 -07:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { database } from "~/database/context";
|
2026-05-01 23:01:59 +00:00
|
|
|
import { participantSurfaceElos, seasonParticipants } from "~/database/schema";
|
2026-05-01 22:23:44 +00:00
|
|
|
import { eq, inArray, sql } from "drizzle-orm";
|
Add tennis Grand Slam simulator with surface Elo ratings, fixes #116 (#216)
Implements a Monte Carlo simulator for men's/women's tennis seasons scored
on the qualifying_points pattern. Simulates all 4 Grand Slam majors
(Australian Open, French Open, Wimbledon, US Open) using surface-specific
Elo ratings and ATP/WTA world rankings for seeding.
New table: participant_surface_elos — one row per (participant, season)
storing worldRanking, eloHard, eloClay, eloGrass.
Key design decisions:
- Seeding uses ATP/WTA world ranking (not Elo), matching real draw procedure
- Top 32 seeded with standard slot placement (1→0, 2→64, 3-4→quarters, etc.)
- QP per round with tie-splitting pre-applied: W=20, F=14, SF=9, QF=4, R16=1.5
- Completed majors read actual qualifyingPointsAwarded from eventResults
- 10,000 Monte Carlo simulations; column sums naturally 1.0 (no normalization)
Admin UI at /admin/sports-seasons/:id/surface-elo:
- 5-column grid (Player | Rank | Hard | Clay | Grass)
- Bulk import: "Name, ranking, hardElo, clayElo, grassElo" one per line
- Fuzzy name matching (bigram Dice coefficient) with "Did you mean?" suggestions
- Inline participant creation for unmatched names via useFetcher
- Saves Elos and auto-runs simulation on submit
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 23:59:35 -07:00
|
|
|
|
|
|
|
|
export type CourtSurface = "hard" | "clay" | "grass";
|
|
|
|
|
|
|
|
|
|
export interface SurfaceEloRecord {
|
|
|
|
|
id: string;
|
|
|
|
|
participantId: string;
|
|
|
|
|
sportsSeasonId: string;
|
|
|
|
|
worldRanking: number | null;
|
|
|
|
|
eloHard: number | null;
|
|
|
|
|
eloClay: number | null;
|
|
|
|
|
eloGrass: number | null;
|
|
|
|
|
updatedAt: Date;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface SurfaceEloWithName extends SurfaceEloRecord {
|
|
|
|
|
participantName: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface SurfaceEloInput {
|
|
|
|
|
participantId: string;
|
|
|
|
|
sportsSeasonId: string;
|
|
|
|
|
worldRanking?: number | null;
|
|
|
|
|
eloHard?: number | null;
|
|
|
|
|
eloClay?: number | null;
|
|
|
|
|
eloGrass?: number | null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-05-01 23:01:59 +00:00
|
|
|
* Load surface Elos for a sports season's roster, joined with the per-window
|
|
|
|
|
* participant's name. Internally joins season_participants → canonical
|
|
|
|
|
* participants → canonical participant_surface_elos.
|
|
|
|
|
*
|
|
|
|
|
* The returned `id` is the canonical `participant_surface_elos.id`;
|
|
|
|
|
* `participantId` is the season_participant id (admin UI keys rows by that).
|
Add tennis Grand Slam simulator with surface Elo ratings, fixes #116 (#216)
Implements a Monte Carlo simulator for men's/women's tennis seasons scored
on the qualifying_points pattern. Simulates all 4 Grand Slam majors
(Australian Open, French Open, Wimbledon, US Open) using surface-specific
Elo ratings and ATP/WTA world rankings for seeding.
New table: participant_surface_elos — one row per (participant, season)
storing worldRanking, eloHard, eloClay, eloGrass.
Key design decisions:
- Seeding uses ATP/WTA world ranking (not Elo), matching real draw procedure
- Top 32 seeded with standard slot placement (1→0, 2→64, 3-4→quarters, etc.)
- QP per round with tie-splitting pre-applied: W=20, F=14, SF=9, QF=4, R16=1.5
- Completed majors read actual qualifyingPointsAwarded from eventResults
- 10,000 Monte Carlo simulations; column sums naturally 1.0 (no normalization)
Admin UI at /admin/sports-seasons/:id/surface-elo:
- 5-column grid (Player | Rank | Hard | Clay | Grass)
- Bulk import: "Name, ranking, hardElo, clayElo, grassElo" one per line
- Fuzzy name matching (bigram Dice coefficient) with "Did you mean?" suggestions
- Inline participant creation for unmatched names via useFetcher
- Saves Elos and auto-runs simulation on submit
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 23:59:35 -07:00
|
|
|
*/
|
|
|
|
|
export async function getSurfaceElosForSeason(
|
2026-05-01 23:01:59 +00:00
|
|
|
sportsSeasonId: string,
|
Add tennis Grand Slam simulator with surface Elo ratings, fixes #116 (#216)
Implements a Monte Carlo simulator for men's/women's tennis seasons scored
on the qualifying_points pattern. Simulates all 4 Grand Slam majors
(Australian Open, French Open, Wimbledon, US Open) using surface-specific
Elo ratings and ATP/WTA world rankings for seeding.
New table: participant_surface_elos — one row per (participant, season)
storing worldRanking, eloHard, eloClay, eloGrass.
Key design decisions:
- Seeding uses ATP/WTA world ranking (not Elo), matching real draw procedure
- Top 32 seeded with standard slot placement (1→0, 2→64, 3-4→quarters, etc.)
- QP per round with tie-splitting pre-applied: W=20, F=14, SF=9, QF=4, R16=1.5
- Completed majors read actual qualifyingPointsAwarded from eventResults
- 10,000 Monte Carlo simulations; column sums naturally 1.0 (no normalization)
Admin UI at /admin/sports-seasons/:id/surface-elo:
- 5-column grid (Player | Rank | Hard | Clay | Grass)
- Bulk import: "Name, ranking, hardElo, clayElo, grassElo" one per line
- Fuzzy name matching (bigram Dice coefficient) with "Did you mean?" suggestions
- Inline participant creation for unmatched names via useFetcher
- Saves Elos and auto-runs simulation on submit
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 23:59:35 -07:00
|
|
|
): Promise<SurfaceEloWithName[]> {
|
|
|
|
|
const db = database();
|
2026-05-01 23:01:59 +00:00
|
|
|
return await db
|
Add tennis Grand Slam simulator with surface Elo ratings, fixes #116 (#216)
Implements a Monte Carlo simulator for men's/women's tennis seasons scored
on the qualifying_points pattern. Simulates all 4 Grand Slam majors
(Australian Open, French Open, Wimbledon, US Open) using surface-specific
Elo ratings and ATP/WTA world rankings for seeding.
New table: participant_surface_elos — one row per (participant, season)
storing worldRanking, eloHard, eloClay, eloGrass.
Key design decisions:
- Seeding uses ATP/WTA world ranking (not Elo), matching real draw procedure
- Top 32 seeded with standard slot placement (1→0, 2→64, 3-4→quarters, etc.)
- QP per round with tie-splitting pre-applied: W=20, F=14, SF=9, QF=4, R16=1.5
- Completed majors read actual qualifyingPointsAwarded from eventResults
- 10,000 Monte Carlo simulations; column sums naturally 1.0 (no normalization)
Admin UI at /admin/sports-seasons/:id/surface-elo:
- 5-column grid (Player | Rank | Hard | Clay | Grass)
- Bulk import: "Name, ranking, hardElo, clayElo, grassElo" one per line
- Fuzzy name matching (bigram Dice coefficient) with "Did you mean?" suggestions
- Inline participant creation for unmatched names via useFetcher
- Saves Elos and auto-runs simulation on submit
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 23:59:35 -07:00
|
|
|
.select({
|
2026-05-01 23:01:59 +00:00
|
|
|
id: participantSurfaceElos.id,
|
|
|
|
|
participantId: seasonParticipants.id,
|
|
|
|
|
sportsSeasonId: seasonParticipants.sportsSeasonId,
|
|
|
|
|
worldRanking: participantSurfaceElos.worldRanking,
|
|
|
|
|
eloHard: participantSurfaceElos.eloHard,
|
|
|
|
|
eloClay: participantSurfaceElos.eloClay,
|
|
|
|
|
eloGrass: participantSurfaceElos.eloGrass,
|
|
|
|
|
updatedAt: participantSurfaceElos.updatedAt,
|
2026-05-01 06:37:53 +00:00
|
|
|
participantName: seasonParticipants.name,
|
Add tennis Grand Slam simulator with surface Elo ratings, fixes #116 (#216)
Implements a Monte Carlo simulator for men's/women's tennis seasons scored
on the qualifying_points pattern. Simulates all 4 Grand Slam majors
(Australian Open, French Open, Wimbledon, US Open) using surface-specific
Elo ratings and ATP/WTA world rankings for seeding.
New table: participant_surface_elos — one row per (participant, season)
storing worldRanking, eloHard, eloClay, eloGrass.
Key design decisions:
- Seeding uses ATP/WTA world ranking (not Elo), matching real draw procedure
- Top 32 seeded with standard slot placement (1→0, 2→64, 3-4→quarters, etc.)
- QP per round with tie-splitting pre-applied: W=20, F=14, SF=9, QF=4, R16=1.5
- Completed majors read actual qualifyingPointsAwarded from eventResults
- 10,000 Monte Carlo simulations; column sums naturally 1.0 (no normalization)
Admin UI at /admin/sports-seasons/:id/surface-elo:
- 5-column grid (Player | Rank | Hard | Clay | Grass)
- Bulk import: "Name, ranking, hardElo, clayElo, grassElo" one per line
- Fuzzy name matching (bigram Dice coefficient) with "Did you mean?" suggestions
- Inline participant creation for unmatched names via useFetcher
- Saves Elos and auto-runs simulation on submit
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 23:59:35 -07:00
|
|
|
})
|
2026-05-01 23:01:59 +00:00
|
|
|
.from(seasonParticipants)
|
|
|
|
|
.innerJoin(
|
|
|
|
|
participantSurfaceElos,
|
|
|
|
|
eq(participantSurfaceElos.participantId, seasonParticipants.participantId),
|
|
|
|
|
)
|
|
|
|
|
.where(eq(seasonParticipants.sportsSeasonId, sportsSeasonId))
|
2026-05-01 06:37:53 +00:00
|
|
|
.orderBy(seasonParticipants.name);
|
Add tennis Grand Slam simulator with surface Elo ratings, fixes #116 (#216)
Implements a Monte Carlo simulator for men's/women's tennis seasons scored
on the qualifying_points pattern. Simulates all 4 Grand Slam majors
(Australian Open, French Open, Wimbledon, US Open) using surface-specific
Elo ratings and ATP/WTA world rankings for seeding.
New table: participant_surface_elos — one row per (participant, season)
storing worldRanking, eloHard, eloClay, eloGrass.
Key design decisions:
- Seeding uses ATP/WTA world ranking (not Elo), matching real draw procedure
- Top 32 seeded with standard slot placement (1→0, 2→64, 3-4→quarters, etc.)
- QP per round with tie-splitting pre-applied: W=20, F=14, SF=9, QF=4, R16=1.5
- Completed majors read actual qualifyingPointsAwarded from eventResults
- 10,000 Monte Carlo simulations; column sums naturally 1.0 (no normalization)
Admin UI at /admin/sports-seasons/:id/surface-elo:
- 5-column grid (Player | Rank | Hard | Clay | Grass)
- Bulk import: "Name, ranking, hardElo, clayElo, grassElo" one per line
- Fuzzy name matching (bigram Dice coefficient) with "Did you mean?" suggestions
- Inline participant creation for unmatched names via useFetcher
- Saves Elos and auto-runs simulation on submit
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 23:59:35 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-05-01 23:01:59 +00:00
|
|
|
* Upsert surface Elos for a batch of season_participants. Writes to the
|
|
|
|
|
* canonical `participant_surface_elos` table; callers pass season_participant
|
|
|
|
|
* ids and we resolve canonical ids internally.
|
|
|
|
|
*
|
|
|
|
|
* season_participants without a canonical link are silently skipped. In
|
|
|
|
|
* practice every qualifying-points roster entry is canonical-linked after
|
|
|
|
|
* Phase 2 + the Phase 3 auto-linking on createParticipant.
|
Add tennis Grand Slam simulator with surface Elo ratings, fixes #116 (#216)
Implements a Monte Carlo simulator for men's/women's tennis seasons scored
on the qualifying_points pattern. Simulates all 4 Grand Slam majors
(Australian Open, French Open, Wimbledon, US Open) using surface-specific
Elo ratings and ATP/WTA world rankings for seeding.
New table: participant_surface_elos — one row per (participant, season)
storing worldRanking, eloHard, eloClay, eloGrass.
Key design decisions:
- Seeding uses ATP/WTA world ranking (not Elo), matching real draw procedure
- Top 32 seeded with standard slot placement (1→0, 2→64, 3-4→quarters, etc.)
- QP per round with tie-splitting pre-applied: W=20, F=14, SF=9, QF=4, R16=1.5
- Completed majors read actual qualifyingPointsAwarded from eventResults
- 10,000 Monte Carlo simulations; column sums naturally 1.0 (no normalization)
Admin UI at /admin/sports-seasons/:id/surface-elo:
- 5-column grid (Player | Rank | Hard | Clay | Grass)
- Bulk import: "Name, ranking, hardElo, clayElo, grassElo" one per line
- Fuzzy name matching (bigram Dice coefficient) with "Did you mean?" suggestions
- Inline participant creation for unmatched names via useFetcher
- Saves Elos and auto-runs simulation on submit
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 23:59:35 -07:00
|
|
|
*/
|
|
|
|
|
export async function batchUpsertSurfaceElos(
|
2026-05-01 23:01:59 +00:00
|
|
|
inputs: SurfaceEloInput[],
|
Add tennis Grand Slam simulator with surface Elo ratings, fixes #116 (#216)
Implements a Monte Carlo simulator for men's/women's tennis seasons scored
on the qualifying_points pattern. Simulates all 4 Grand Slam majors
(Australian Open, French Open, Wimbledon, US Open) using surface-specific
Elo ratings and ATP/WTA world rankings for seeding.
New table: participant_surface_elos — one row per (participant, season)
storing worldRanking, eloHard, eloClay, eloGrass.
Key design decisions:
- Seeding uses ATP/WTA world ranking (not Elo), matching real draw procedure
- Top 32 seeded with standard slot placement (1→0, 2→64, 3-4→quarters, etc.)
- QP per round with tie-splitting pre-applied: W=20, F=14, SF=9, QF=4, R16=1.5
- Completed majors read actual qualifyingPointsAwarded from eventResults
- 10,000 Monte Carlo simulations; column sums naturally 1.0 (no normalization)
Admin UI at /admin/sports-seasons/:id/surface-elo:
- 5-column grid (Player | Rank | Hard | Clay | Grass)
- Bulk import: "Name, ranking, hardElo, clayElo, grassElo" one per line
- Fuzzy name matching (bigram Dice coefficient) with "Did you mean?" suggestions
- Inline participant creation for unmatched names via useFetcher
- Saves Elos and auto-runs simulation on submit
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 23:59:35 -07:00
|
|
|
): Promise<void> {
|
|
|
|
|
if (inputs.length === 0) return;
|
|
|
|
|
const db = database();
|
|
|
|
|
const now = new Date();
|
|
|
|
|
|
2026-05-01 23:01:59 +00:00
|
|
|
// Resolve canonical ids for every season_participant in the batch.
|
2026-05-01 22:23:44 +00:00
|
|
|
const sps = await db
|
|
|
|
|
.select({ id: seasonParticipants.id, canonicalId: seasonParticipants.participantId })
|
|
|
|
|
.from(seasonParticipants)
|
|
|
|
|
.where(inArray(seasonParticipants.id, inputs.map((i) => i.participantId)));
|
|
|
|
|
const canonicalByInputId = new Map(sps.map((sp) => [sp.id, sp.canonicalId]));
|
|
|
|
|
|
|
|
|
|
const canonicalRows = inputs
|
|
|
|
|
.map((i) => ({
|
|
|
|
|
canonicalId: canonicalByInputId.get(i.participantId),
|
|
|
|
|
worldRanking: i.worldRanking ?? null,
|
|
|
|
|
eloHard: i.eloHard ?? null,
|
|
|
|
|
eloClay: i.eloClay ?? null,
|
|
|
|
|
eloGrass: i.eloGrass ?? null,
|
|
|
|
|
}))
|
|
|
|
|
.filter((r): r is typeof r & { canonicalId: string } => typeof r.canonicalId === "string");
|
|
|
|
|
|
|
|
|
|
if (canonicalRows.length === 0) return;
|
|
|
|
|
|
|
|
|
|
await db
|
|
|
|
|
.insert(participantSurfaceElos)
|
|
|
|
|
.values(canonicalRows.map(({ canonicalId, ...rest }) => ({
|
|
|
|
|
participantId: canonicalId,
|
|
|
|
|
...rest,
|
|
|
|
|
updatedAt: now,
|
|
|
|
|
})))
|
|
|
|
|
.onConflictDoUpdate({
|
|
|
|
|
target: [participantSurfaceElos.participantId],
|
|
|
|
|
set: {
|
|
|
|
|
worldRanking: sql`excluded.world_ranking`,
|
|
|
|
|
eloHard: sql`excluded.elo_hard`,
|
|
|
|
|
eloClay: sql`excluded.elo_clay`,
|
|
|
|
|
eloGrass: sql`excluded.elo_grass`,
|
|
|
|
|
updatedAt: sql`excluded.updated_at`,
|
|
|
|
|
},
|
|
|
|
|
});
|
Add tennis Grand Slam simulator with surface Elo ratings, fixes #116 (#216)
Implements a Monte Carlo simulator for men's/women's tennis seasons scored
on the qualifying_points pattern. Simulates all 4 Grand Slam majors
(Australian Open, French Open, Wimbledon, US Open) using surface-specific
Elo ratings and ATP/WTA world rankings for seeding.
New table: participant_surface_elos — one row per (participant, season)
storing worldRanking, eloHard, eloClay, eloGrass.
Key design decisions:
- Seeding uses ATP/WTA world ranking (not Elo), matching real draw procedure
- Top 32 seeded with standard slot placement (1→0, 2→64, 3-4→quarters, etc.)
- QP per round with tie-splitting pre-applied: W=20, F=14, SF=9, QF=4, R16=1.5
- Completed majors read actual qualifyingPointsAwarded from eventResults
- 10,000 Monte Carlo simulations; column sums naturally 1.0 (no normalization)
Admin UI at /admin/sports-seasons/:id/surface-elo:
- 5-column grid (Player | Rank | Hard | Clay | Grass)
- Bulk import: "Name, ranking, hardElo, clayElo, grassElo" one per line
- Fuzzy name matching (bigram Dice coefficient) with "Did you mean?" suggestions
- Inline participant creation for unmatched names via useFetcher
- Saves Elos and auto-runs simulation on submit
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 23:59:35 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-05-01 23:01:59 +00:00
|
|
|
* Build a map keyed by season_participant.id → surface Elo values, sourced
|
|
|
|
|
* from the canonical `participant_surface_elos` table. Used by the tennis
|
|
|
|
|
* simulator.
|
2026-05-01 21:48:52 +00:00
|
|
|
*
|
|
|
|
|
* Season participants without a linked canonical participant, or with no
|
2026-05-01 23:01:59 +00:00
|
|
|
* canonical surface Elo row, are absent from the map — callers handle
|
|
|
|
|
* `undefined` by falling back to 1500 (the simulator's default).
|
2026-05-01 21:48:52 +00:00
|
|
|
*/
|
Add tennis Grand Slam simulator with surface Elo ratings, fixes #116 (#216)
Implements a Monte Carlo simulator for men's/women's tennis seasons scored
on the qualifying_points pattern. Simulates all 4 Grand Slam majors
(Australian Open, French Open, Wimbledon, US Open) using surface-specific
Elo ratings and ATP/WTA world rankings for seeding.
New table: participant_surface_elos — one row per (participant, season)
storing worldRanking, eloHard, eloClay, eloGrass.
Key design decisions:
- Seeding uses ATP/WTA world ranking (not Elo), matching real draw procedure
- Top 32 seeded with standard slot placement (1→0, 2→64, 3-4→quarters, etc.)
- QP per round with tie-splitting pre-applied: W=20, F=14, SF=9, QF=4, R16=1.5
- Completed majors read actual qualifyingPointsAwarded from eventResults
- 10,000 Monte Carlo simulations; column sums naturally 1.0 (no normalization)
Admin UI at /admin/sports-seasons/:id/surface-elo:
- 5-column grid (Player | Rank | Hard | Clay | Grass)
- Bulk import: "Name, ranking, hardElo, clayElo, grassElo" one per line
- Fuzzy name matching (bigram Dice coefficient) with "Did you mean?" suggestions
- Inline participant creation for unmatched names via useFetcher
- Saves Elos and auto-runs simulation on submit
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 23:59:35 -07:00
|
|
|
export async function getSurfaceEloMap(
|
2026-05-01 23:01:59 +00:00
|
|
|
sportsSeasonId: string,
|
Add tennis Grand Slam simulator with surface Elo ratings, fixes #116 (#216)
Implements a Monte Carlo simulator for men's/women's tennis seasons scored
on the qualifying_points pattern. Simulates all 4 Grand Slam majors
(Australian Open, French Open, Wimbledon, US Open) using surface-specific
Elo ratings and ATP/WTA world rankings for seeding.
New table: participant_surface_elos — one row per (participant, season)
storing worldRanking, eloHard, eloClay, eloGrass.
Key design decisions:
- Seeding uses ATP/WTA world ranking (not Elo), matching real draw procedure
- Top 32 seeded with standard slot placement (1→0, 2→64, 3-4→quarters, etc.)
- QP per round with tie-splitting pre-applied: W=20, F=14, SF=9, QF=4, R16=1.5
- Completed majors read actual qualifyingPointsAwarded from eventResults
- 10,000 Monte Carlo simulations; column sums naturally 1.0 (no normalization)
Admin UI at /admin/sports-seasons/:id/surface-elo:
- 5-column grid (Player | Rank | Hard | Clay | Grass)
- Bulk import: "Name, ranking, hardElo, clayElo, grassElo" one per line
- Fuzzy name matching (bigram Dice coefficient) with "Did you mean?" suggestions
- Inline participant creation for unmatched names via useFetcher
- Saves Elos and auto-runs simulation on submit
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 23:59:35 -07:00
|
|
|
): Promise<Map<string, { worldRanking: number | null; eloHard: number | null; eloClay: number | null; eloGrass: number | null }>> {
|
|
|
|
|
const db = database();
|
|
|
|
|
const rows = await db
|
|
|
|
|
.select({
|
2026-05-01 21:48:52 +00:00
|
|
|
seasonParticipantId: seasonParticipants.id,
|
|
|
|
|
worldRanking: participantSurfaceElos.worldRanking,
|
|
|
|
|
eloHard: participantSurfaceElos.eloHard,
|
|
|
|
|
eloClay: participantSurfaceElos.eloClay,
|
|
|
|
|
eloGrass: participantSurfaceElos.eloGrass,
|
Add tennis Grand Slam simulator with surface Elo ratings, fixes #116 (#216)
Implements a Monte Carlo simulator for men's/women's tennis seasons scored
on the qualifying_points pattern. Simulates all 4 Grand Slam majors
(Australian Open, French Open, Wimbledon, US Open) using surface-specific
Elo ratings and ATP/WTA world rankings for seeding.
New table: participant_surface_elos — one row per (participant, season)
storing worldRanking, eloHard, eloClay, eloGrass.
Key design decisions:
- Seeding uses ATP/WTA world ranking (not Elo), matching real draw procedure
- Top 32 seeded with standard slot placement (1→0, 2→64, 3-4→quarters, etc.)
- QP per round with tie-splitting pre-applied: W=20, F=14, SF=9, QF=4, R16=1.5
- Completed majors read actual qualifyingPointsAwarded from eventResults
- 10,000 Monte Carlo simulations; column sums naturally 1.0 (no normalization)
Admin UI at /admin/sports-seasons/:id/surface-elo:
- 5-column grid (Player | Rank | Hard | Clay | Grass)
- Bulk import: "Name, ranking, hardElo, clayElo, grassElo" one per line
- Fuzzy name matching (bigram Dice coefficient) with "Did you mean?" suggestions
- Inline participant creation for unmatched names via useFetcher
- Saves Elos and auto-runs simulation on submit
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 23:59:35 -07:00
|
|
|
})
|
2026-05-01 21:48:52 +00:00
|
|
|
.from(seasonParticipants)
|
|
|
|
|
.innerJoin(
|
|
|
|
|
participantSurfaceElos,
|
|
|
|
|
eq(participantSurfaceElos.participantId, seasonParticipants.participantId),
|
|
|
|
|
)
|
|
|
|
|
.where(eq(seasonParticipants.sportsSeasonId, sportsSeasonId));
|
Add tennis Grand Slam simulator with surface Elo ratings, fixes #116 (#216)
Implements a Monte Carlo simulator for men's/women's tennis seasons scored
on the qualifying_points pattern. Simulates all 4 Grand Slam majors
(Australian Open, French Open, Wimbledon, US Open) using surface-specific
Elo ratings and ATP/WTA world rankings for seeding.
New table: participant_surface_elos — one row per (participant, season)
storing worldRanking, eloHard, eloClay, eloGrass.
Key design decisions:
- Seeding uses ATP/WTA world ranking (not Elo), matching real draw procedure
- Top 32 seeded with standard slot placement (1→0, 2→64, 3-4→quarters, etc.)
- QP per round with tie-splitting pre-applied: W=20, F=14, SF=9, QF=4, R16=1.5
- Completed majors read actual qualifyingPointsAwarded from eventResults
- 10,000 Monte Carlo simulations; column sums naturally 1.0 (no normalization)
Admin UI at /admin/sports-seasons/:id/surface-elo:
- 5-column grid (Player | Rank | Hard | Clay | Grass)
- Bulk import: "Name, ranking, hardElo, clayElo, grassElo" one per line
- Fuzzy name matching (bigram Dice coefficient) with "Did you mean?" suggestions
- Inline participant creation for unmatched names via useFetcher
- Saves Elos and auto-runs simulation on submit
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 23:59:35 -07:00
|
|
|
|
2026-05-01 21:48:52 +00:00
|
|
|
return new Map(rows.map((r) => [r.seasonParticipantId, {
|
Add tennis Grand Slam simulator with surface Elo ratings, fixes #116 (#216)
Implements a Monte Carlo simulator for men's/women's tennis seasons scored
on the qualifying_points pattern. Simulates all 4 Grand Slam majors
(Australian Open, French Open, Wimbledon, US Open) using surface-specific
Elo ratings and ATP/WTA world rankings for seeding.
New table: participant_surface_elos — one row per (participant, season)
storing worldRanking, eloHard, eloClay, eloGrass.
Key design decisions:
- Seeding uses ATP/WTA world ranking (not Elo), matching real draw procedure
- Top 32 seeded with standard slot placement (1→0, 2→64, 3-4→quarters, etc.)
- QP per round with tie-splitting pre-applied: W=20, F=14, SF=9, QF=4, R16=1.5
- Completed majors read actual qualifyingPointsAwarded from eventResults
- 10,000 Monte Carlo simulations; column sums naturally 1.0 (no normalization)
Admin UI at /admin/sports-seasons/:id/surface-elo:
- 5-column grid (Player | Rank | Hard | Clay | Grass)
- Bulk import: "Name, ranking, hardElo, clayElo, grassElo" one per line
- Fuzzy name matching (bigram Dice coefficient) with "Did you mean?" suggestions
- Inline participant creation for unmatched names via useFetcher
- Saves Elos and auto-runs simulation on submit
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 23:59:35 -07:00
|
|
|
worldRanking: r.worldRanking,
|
|
|
|
|
eloHard: r.eloHard,
|
|
|
|
|
eloClay: r.eloClay,
|
|
|
|
|
eloGrass: r.eloGrass,
|
|
|
|
|
}]));
|
|
|
|
|
}
|