brackt/app/services/simulations/registry.ts
Chris Parsons 70c22d03fc
feat: add UCL bracket Monte Carlo simulator (#131)
Implements a 16-team UEFA Champions League knockout bracket simulator
using blended Elo + futures odds probabilities (70/30 split). Tracks
integer placement counts per tier to avoid fractional accumulation
drift, ensuring total EV sums to exactly 340 by construction.

- New UCLSimulator class reading live playoffMatches from DB
- Respects completed match results (eliminated teams get exact EV)
- New ucl_bracket simulator_type enum value + migration
- Registered in simulator registry; added to admin sport selector
- 18 unit tests covering math, bracket path, and integration scenarios

Fixes #113

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 23:04:51 -07:00

64 lines
2.2 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";
export type SimulatorType =
| "f1_standings"
| "indycar_standings"
| "golf_qualifying_points"
| "playoff_bracket"
| "ucl_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(),
},
};
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;
}