brackt/app/services/simulations/registry.ts
2026-05-12 22:20:36 -07:00

209 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Simulator Registry
*
* Maps a sport's simulatorType to the appropriate Simulator implementation.
* Set simulatorType on the sport record in the admin UI.
* Add new simulators here as new sports are supported.
*/
import type { Simulator } from "./types";
import { BracketSimulator } from "./bracket-simulator";
import { AutoRacingSimulator } from "./auto-racing-simulator";
import { GolfSimulator } from "./golf-simulator";
import { UCLSimulator } from "./ucl-simulator";
import { NCAAMSimulator } from "./ncaam-simulator";
import { NCAAWSimulator } from "./ncaaw-simulator";
import { NBASimulator } from "./nba-simulator";
import { NHLSimulator } from "./nhl-simulator";
import { AFLSimulator } from "./afl-simulator";
import { EPLSimulator } from "./epl-simulator";
import { SnookerSimulator } from "./snooker-simulator";
import { TennisSimulator } from "./tennis-simulator";
import { DartsSimulator } from "./darts-simulator";
import { CSMajorSimulator } from "./cs-major-simulator";
import { MLBSimulator } from "./mlb-simulator";
import { NFLSimulator } from "./nfl-simulator";
import { WNBASimulator } from "./wnba-simulator";
import { WorldCupSimulator } from "./world-cup-simulator";
import { NCAAFootballSimulator } from "./ncaa-football-simulator";
import { LLWSSimulator } from "./llws-simulator";
import { CollegeHockeySimulator } from "./college-hockey-simulator";
import { BracktSimulator } from "./brackt-simulator";
import { NLLSimulator } from "./nll-simulator";
export const SIMULATOR_TYPES = [
"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",
] as const;
export type SimulatorType = typeof SIMULATOR_TYPES[number];
// ─── Race points tables ───────────────────────────────────────────────────────
/** F1 points: positions 110. */
const F1_RACE_POINTS: Record<number, number> = {
1: 25, 2: 18, 3: 15, 4: 12, 5: 10, 6: 8, 7: 6, 8: 4, 9: 2, 10: 1,
};
/** IndyCar standard race points: positions 126. */
const INDYCAR_RACE_POINTS: Record<number, number> = {
1: 50, 2: 40, 3: 35, 4: 32, 5: 30, 6: 28, 7: 26, 8: 24, 9: 22, 10: 20,
11: 19, 12: 18, 13: 17, 14: 16, 15: 15, 16: 14, 17: 13, 18: 12, 19: 11, 20: 10,
21: 9, 22: 8, 23: 7, 24: 6, 25: 5, 26: 5,
};
export interface SimulatorInfo {
name: string;
description: string;
}
const REGISTRY: Record<SimulatorType, { info: SimulatorInfo; create: () => Simulator }> = {
f1_standings: {
info: { name: "F1 Standings Model", description: "Simulates remaining races using current F1 standings and futures odds" },
create: () => new AutoRacingSimulator(F1_RACE_POINTS, "f1_standings_model"),
},
indycar_standings: {
info: { name: "IndyCar Standings Model", description: "Simulates remaining races using current IndyCar standings and futures odds" },
create: () => new AutoRacingSimulator(INDYCAR_RACE_POINTS, "indycar_standings_model"),
},
golf_qualifying_points: {
info: { name: "Golf Qualifying Points Monte Carlo", description: "Simulates remaining majors using a Plackett-Luce model with SG: Total ratings. Awards QP by finishing position; ranks players by total QP across all 4 majors." },
create: () => new GolfSimulator(),
},
playoff_bracket: {
info: { name: "Bracket Monte Carlo", description: "Simulates playoff bracket outcomes using Elo ratings" },
create: () => new BracketSimulator(),
},
ucl_bracket: {
info: { name: "UCL Bracket Monte Carlo", description: "Simulates the UEFA Champions League 16-team knockout bracket using blended Elo + futures odds" },
create: () => new UCLSimulator(),
},
ncaam_bracket: {
info: { name: "NCAAM Bracket Monte Carlo", description: "Simulates preseason field selection or the NCAA Men's Basketball Tournament bracket using season-scoped KenPom-like ratings" },
create: () => new NCAAMSimulator(),
},
ncaaw_bracket: {
info: { name: "NCAAW Bracket Monte Carlo", description: "Simulates preseason field selection or the NCAA Women's Basketball Tournament bracket using season-scoped Barthag-like ratings" },
create: () => new NCAAWSimulator(),
},
nba_bracket: {
info: { name: "NBA Playoff Monte Carlo", description: "Simulates NBA playoff seedings (via seed probabilities) and full bracket (best-of-7 series) using Elo ratings" },
create: () => new NBASimulator(),
},
nhl_bracket: {
info: { name: "NHL Playoff Monte Carlo", description: "Simulates NHL playoff seedings (divisional format, via seed probabilities) and full bracket (best-of-7 series) using Elo ratings" },
create: () => new NHLSimulator(),
},
nfl_bracket: {
info: {
name: "NFL Season + Playoffs Monte Carlo",
description: "Projects NFL regular season standings (via nfelo Elo ratings) to determine the 14-team playoff field, then simulates the full bracket (Wild Card → Divisional → Conference Championship → Super Bowl). Elo ratings maintained via admin sourceElo field.",
},
create: () => new NFLSimulator(),
},
afl_bracket: {
info: { name: "AFL Season + Finals Monte Carlo", description: "Projects AFL regular season standings via Elo, then simulates the 10-team finals series (Wildcard → QF/EF → SF → PF → Grand Final). Reads current standings from DB; falls back to full-season projection pre-season." },
create: () => new AFLSimulator(),
},
epl_standings: {
info: { name: "EPL Season Standings Monte Carlo", description: "Projects the English Premier League table using current standings and Elo/projected-points inputs. Simulates draws correctly and returns top-eight placement probabilities." },
create: () => new EPLSimulator(),
},
snooker_bracket: {
info: { name: "Snooker World Championship Monte Carlo", description: "Simulates the 32-player World Championship bracket using frame-by-frame Bernoulli win probability and direct Elo ratings. Pre-bracket path simulates qualifying (ranks 17-48) and randomly draws qualifiers vs top 16 seeds." },
create: () => new SnookerSimulator(),
},
tennis_qualifying_points: {
info: { name: "Tennis Grand Slam Monte Carlo", description: "Simulates all 4 Grand Slam majors (128-player seeded bracket) using surface-specific Elo ratings. Accumulates qualifying points per round (tie-split applied) and ranks players by total QP across the season." },
create: () => new TennisSimulator(),
},
mlb_bracket: {
info: { name: "MLB Playoff Monte Carlo", description: "Simulates MLB division races + full playoff bracket (WC best-of-3, DS best-of-5, LCS/WS best-of-7) using Elo ratings calibrated from FanGraphs projected wins" },
create: () => new MLBSimulator(),
},
wnba_bracket: {
info: { name: "WNBA Playoff Monte Carlo", description: "Projects WNBA regular season seedings and simulates full playoff bracket (R1 best-of-3, Semis best-of-5, Finals best-of-7) using SRS-derived Elo ratings. SRS values sourced from basketball-reference.com." },
create: () => new WNBASimulator(),
},
world_cup: {
info: { name: "FIFA World Cup 2026 Monte Carlo", description: "Simulates the full 48-team World Cup: round-robin group stage (12 groups), best-8 third-place selection, R32→R16→QF→SF→3rd place game→Final. Uses blended Elo + futures odds." },
create: () => new WorldCupSimulator(),
},
darts_bracket: {
info: { name: "PDC World Darts Championship Monte Carlo", description: "Simulates the 128-player World Darts Championship bracket using set-level Bernoulli win probability and Elo ratings. Top 32 seeds placed in fixed positions; remaining 96 players randomly drawn per simulation." },
create: () => new DartsSimulator(),
},
cs2_major_qualifying_points: {
info: { name: "CS2 Major Qualifying Points Monte Carlo", description: "Simulates 2 CS2 Majors per season (3 Swiss stages: Opening Bo1, Elimination Bo1, Decider Bo3, then Champions Stage single-elimination). Awards QP by final placement; ranks teams by total QP across both majors. Stage 3 exits (placements 916) are sub-ranked by W-L record." },
create: () => new CSMajorSimulator(),
},
ncaa_football_bracket: {
info: { name: "NCAA Football CFP Monte Carlo", description: "Simulates the 12-team College Football Playoff bracket using Elo/FPI ratings entered via Admin → Elo Ratings. Optionally blends with championship futures odds (60% Elo / 40% odds). Seeds 14 receive first-round byes; First Round losers score 0 points." },
create: () => new NCAAFootballSimulator(),
},
llws_bracket: {
info: {
name: "LLWS Bracket Monte Carlo",
description: "Simulates the 20-team Little League World Series: pool play round-robin (5 teams/pool, top 2 advance) → 4-team double-elimination bracket per side (US & International) → consolation game (3rd/4th) → World Series. Uses championship futures odds for all win probabilities. Set externalId to 'US'/'Intl' (randomized pools) or 'US:A'/'US:B'/'Intl:A'/'Intl:B' (fixed pools).",
},
create: () => new LLWSSimulator(),
},
college_hockey_bracket: {
info: {
name: "Men's College Hockey Monte Carlo",
description: "Simulates the 16-team NCAA men's hockey tournament. Pre-bracket mode samples the field using admin-entered futures odds, NPI rank, and/or Elo; bracket mode honors the existing Frozen Four bracket and completed results.",
},
create: () => new CollegeHockeySimulator(),
},
brackt: {
info: {
name: "Brackt Harville Model",
description: "Projects league manager standings from drafted non-Brackt EVs and simulates final league placement probabilities.",
},
create: () => new BracktSimulator(),
},
nll_bracket: {
info: {
name: "NLL Season + Playoffs Monte Carlo",
description: "Projects NLL regular season standings via Elo (14 teams, 18 games) to determine the top-8 playoff field, then simulates the bracket (QF single-game; SF and Finals best-of-3). Reads current standings from DB; falls back to full-season projection pre-season.",
},
create: () => new NLLSimulator(),
},
};
export function getSimulator(simulatorType: SimulatorType): Simulator {
const entry = REGISTRY[simulatorType];
if (!entry) {
throw new Error(
`No simulator registered for type: "${simulatorType}". ` +
`Add a simulator to app/services/simulations/registry.ts.`
);
}
return entry.create();
}
export function getSimulatorInfo(simulatorType: SimulatorType): SimulatorInfo | null {
return REGISTRY[simulatorType]?.info ?? null;
}