- NCAAM: KenPom AEM logistic formula (1/(1+exp(-diff/7.5))), data through 2025-26 March 15 - NCAAW: Barttorvik Barthag Log5 formula (A*(1-B)/(A*(1-B)+B*(1-A))), same bracket structure - Both simulators: 50,000-iteration Monte Carlo, First Four simulation, honors completed matches - Track E8+ placements only: champion, finalist, FF losers (3rd/4th), E8 losers (5th–8th) - Add bracket configuration validation: null R64 slots must exactly match First Four mapping - Fix DEFAULT_SCORING_RULES to 100/70/45/45/20/20/20/20 (3rd/4th=45, 5th–8th=20) - Align scoring constants across simulate route, expected-values display, and server action - Zero out EVs for non-bracket participants on every simulation run (prevents EV inflation) - Add EV total invariant warning (expected ~340) on expected-values admin page - 98 unit tests across NCAAM, NCAAW, and UCL simulators — all passing Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
76 lines
2.8 KiB
TypeScript
76 lines
2.8 KiB
TypeScript
/**
|
|
* 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 { F1Simulator } from "./f1-simulator";
|
|
import { GolfSimulator } from "./golf-simulator";
|
|
import { UCLSimulator } from "./ucl-simulator";
|
|
import { NCAAMSimulator } from "./ncaam-simulator";
|
|
import { NCAAWSimulator } from "./ncaaw-simulator";
|
|
|
|
export type SimulatorType =
|
|
| "f1_standings"
|
|
| "indycar_standings"
|
|
| "golf_qualifying_points"
|
|
| "playoff_bracket"
|
|
| "ucl_bracket"
|
|
| "ncaam_bracket"
|
|
| "ncaaw_bracket";
|
|
|
|
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 F1Simulator(),
|
|
},
|
|
indycar_standings: {
|
|
info: { name: "IndyCar Standings Model", description: "Simulates remaining races using current IndyCar standings and futures odds" },
|
|
create: () => new F1Simulator(),
|
|
},
|
|
golf_qualifying_points: {
|
|
info: { name: "Qualifying Points Model", description: "Simulates remaining majors using current QP standings and futures odds" },
|
|
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 the NCAA Men's Basketball Tournament 64-team bracket using KenPom net ratings" },
|
|
create: () => new NCAAMSimulator(),
|
|
},
|
|
ncaaw_bracket: {
|
|
info: { name: "NCAAW Bracket Monte Carlo", description: "Simulates the NCAA Women's Basketball Tournament 64-team bracket using Barttorvik Barthag ratings" },
|
|
create: () => new NCAAWSimulator(),
|
|
},
|
|
};
|
|
|
|
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;
|
|
}
|
|
|