brackt/app/services/simulations/ncaa-football-simulator.ts

278 lines
12 KiB
TypeScript
Raw Normal View History

Add NCAA Football CFP simulator (12-team bracket) (#278) Fixes #124 * Add NCAA Football CFP simulator (12-team bracket) Implements a Monte Carlo simulator for the College Football Playoff using the 2024-present 12-team format. Elo/FPI ratings are entered manually via the existing admin Elo Ratings page; championship futures odds can optionally be blended in (60% Elo / 40% odds). - Add CFP_12 bracket template (First Round not scoring, QFs onward score) - Add generateCFP12Bracket() with correct seeding: 5v12, 6v11, 7v10, 8v9 in First Round; seeds 1–4 receive QF byes - Add NCAAFootballSimulator: 50k Monte Carlo sims, seeds teams by blended Elo+odds strength, tracks champion/finalist/SF/QF placement tiers - Register ncaa_football_bracket simulator type in registry and schema enum - Add migration 0071: ALTER TYPE simulator_type ADD VALUE 'ncaa_football_bracket' - Add tests: 30 tests covering bracket template structure and simulator probability distributions, seeding, edge cases, futures blending Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix lint errors in NCAA Football CFP simulator Replace non-null assertions with optional chaining, change let to const, use toSorted() instead of sort(), and add a bump() helper to avoid repeated map lookups with non-null assertions in simulateBracket. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix TS2345 in NCAA football simulator test Add ?? 0 fallback so optional-chained probFirst is number, not number | undefined. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 09:48:32 -04:00
/**
* NCAA Football CFP Simulator
*
* Monte Carlo simulation of the College Football Playoff (12-team format, 2024present).
*
* Algorithm:
* 1. Load all participants for the sports season from DB
* 2. Load Elo/FPI ratings from participantExpectedValues.sourceElo
* (entered via Admin Elo Ratings page; use FPI, S&P+, or any Elo-scale rating)
* 3. If sourceOdds (American format) are also stored, build a normalized selection
* weight from implied championship probability (used for field selection in step 4)
* and blend into per-game win probability (ELO_WEIGHT=0.6 / ODDS_WEIGHT=0.4).
* 4. Per simulation, select 12 teams for the CFP field:
* - If the pool has exactly 12 teams: use all of them (post-bracket mode).
* - If the pool has >12 teams: weighted sample without replacement using each
* team's selection weight odds-derived if available, Elo-based otherwise.
* Teams with stronger championship odds are sampled more often, naturally
* encoding both selection probability and bracket strength into one signal.
* 5. Seed the 12 selected teams by Elo (best Elo = seed 1).
* 6. Simulate the CFP bracket:
* First Round (not scoring): 5v12, 6v11, 7v10, 8v9
* Quarterfinals (scoring): 1 vs 8/9w, 4 vs 5/12w, 3 vs 6/11w, 2 vs 7/10w
* Semifinals (scoring): QF1w vs QF2w, QF3w vs QF4w
* National Championship: SF1w vs SF2w
* 7. Track placement counts per scoring tier across all simulations.
* 8. Convert counts to probability distributions.
*
* Pre-bracket vs post-bracket mode:
* Pre-bracket (>12 participants): probabilities reflect both selection uncertainty
* and bracket performance. A bubble team might appear in only 40% of simulated
* fields, so its champion probability accounts for that.
* Post-bracket (exactly 12 participants): deterministic field, bracket-only sim.
*
* Win probability (per game): eloWinProbability() from probability-engine (400-divisor).
* Blended win probability: ELO_WEIGHT * eloProb + ODDS_WEIGHT * oddsProb (when odds present).
*
* Selection weight (pre-bracket mode):
* With sourceOdds: normalized implied championship probability (vig removed, sums to 1).
* Without sourceOdds: softmax on Elo with temperature SELECTION_TEMP (sharply favors
* higher-rated teams a 200-point Elo gap yields ~7× selection weight difference).
*
* Placement tiers SimulationProbabilities mapping:
* probFirst = National Champion (1 per sim)
* probSecond = Championship game loser (1 per sim)
* probThird / probFourth = Semifinal losers (2 per sim split evenly)
* probFifthprobEighth = Quarterfinal losers (4 per sim split evenly)
* First Round losers all 0 (score 0 fantasy points)
* Teams not selected all 0 (not in field for that sim)
*
* Admin setup:
* 1. Create a Sport with simulatorType = "ncaa_football_bracket"
* 2. Create a Sports Season and add all contender participants (12 or more)
* 3. Enter FPI ratings via Admin Elo Ratings (stored as sourceElo)
* 4. Optionally enter championship futures odds via Admin Futures Odds (sourceOdds)
* strongly recommended for pre-bracket mode; drives both selection and bracket strength
* 5. Run simulation via Admin Simulate
*/
import { database } from "~/database/context";
import { eq } from "drizzle-orm";
import * as schema from "~/database/schema";
Unify simulator strength on a single blended Elo Replace the two parallel odds/Elo mechanisms with one model: every strength source (raw Elo, projected wins, projected table points, futures odds) converts to an Elo, those blend by weight into a single Elo, and that one Elo feeds every simulator. This supersedes the earlier source-priority + per-match probability blend approach. Resolution (app/services/simulations/input-policy.ts): - SimulatorInputPolicy gains baseEloPriority (order among the substitutable base sources: raw Elo / projected wins / projected table points) and oddsWeight (0–1). resolveSourceElos/resolveRatings now compute baseElo, derive an odds Elo via convertFuturesToElo, and blend: 0 = base only, 1 = futures override, between = weighted blend (method "blend"). - Drops the odds-inclusive sourceEloPriority and the prefersFuturesOdds helper. Simulators consume the single resolved Elo: - UCL, World Cup, NCAA Football, MLB drop their separate normalized-odds signal, convertFuturesToElo calls, and per-match probability blend; they read the resolved sourceElo (preserving each sim's hardcoded fallback Elo table). The optional SimulationContext oddsWeight plumbing (types.ts/runner.ts) is removed. - UCL is routed through the central blend (requiredInputs sourceElo, derivable from sourceOdds) so any entered Elo and futures blend uniformly. - College hockey already blends odds into Elo internally (and uses NPI rank the central resolver can't), so its central oddsWeight is set to 0 to avoid double-counting; the simulator is unchanged. Manifest: per-profile oddsWeight defaults (World Cup/UCL/MLB 0.3, NCAA FB 0.4, college hockey 0; global default 0.3). UI: the Input Policy card exposes one "Futures vs. Elo — Odds Blend Weight" control; the /admin/simulators inventory badge shows the effective blend ("Elo only" / "NN% blend" / "overrides Elo") with the odds participant count. Tests: input-policy blend math (0/0.5/1) for Elo and ratings, baseEloPriority and oddsWeight parsing/clamping, manifest per-profile weights; obsolete source-priority and oddsWeight-context tests removed/replaced. Note: this intentionally shifts the calibrated EV outputs of the four sims that previously blended at the probability level (accepted in design discussion). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-26 01:39:12 +00:00
import { eloWinProbability } from "~/services/probability-engine";
import type { Simulator, SimulationResult } from "./types";
Add NCAA Football CFP simulator (12-team bracket) (#278) Fixes #124 * Add NCAA Football CFP simulator (12-team bracket) Implements a Monte Carlo simulator for the College Football Playoff using the 2024-present 12-team format. Elo/FPI ratings are entered manually via the existing admin Elo Ratings page; championship futures odds can optionally be blended in (60% Elo / 40% odds). - Add CFP_12 bracket template (First Round not scoring, QFs onward score) - Add generateCFP12Bracket() with correct seeding: 5v12, 6v11, 7v10, 8v9 in First Round; seeds 1–4 receive QF byes - Add NCAAFootballSimulator: 50k Monte Carlo sims, seeds teams by blended Elo+odds strength, tracks champion/finalist/SF/QF placement tiers - Register ncaa_football_bracket simulator type in registry and schema enum - Add migration 0071: ALTER TYPE simulator_type ADD VALUE 'ncaa_football_bracket' - Add tests: 30 tests covering bracket template structure and simulator probability distributions, seeding, edge cases, futures blending Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix lint errors in NCAA Football CFP simulator Replace non-null assertions with optional chaining, change let to const, use toSorted() instead of sort(), and add a bump() helper to avoid repeated map lookups with non-null assertions in simulateBracket. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix TS2345 in NCAA football simulator test Add ?? 0 fallback so optional-chained probFirst is number, not number | undefined. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 09:48:32 -04:00
// ─── Simulation parameters ────────────────────────────────────────────────────
const NUM_SIMULATIONS = 50_000;
const BRACKET_SIZE = 12;
/**
Unify simulator strength on a single blended Elo Replace the two parallel odds/Elo mechanisms with one model: every strength source (raw Elo, projected wins, projected table points, futures odds) converts to an Elo, those blend by weight into a single Elo, and that one Elo feeds every simulator. This supersedes the earlier source-priority + per-match probability blend approach. Resolution (app/services/simulations/input-policy.ts): - SimulatorInputPolicy gains baseEloPriority (order among the substitutable base sources: raw Elo / projected wins / projected table points) and oddsWeight (0–1). resolveSourceElos/resolveRatings now compute baseElo, derive an odds Elo via convertFuturesToElo, and blend: 0 = base only, 1 = futures override, between = weighted blend (method "blend"). - Drops the odds-inclusive sourceEloPriority and the prefersFuturesOdds helper. Simulators consume the single resolved Elo: - UCL, World Cup, NCAA Football, MLB drop their separate normalized-odds signal, convertFuturesToElo calls, and per-match probability blend; they read the resolved sourceElo (preserving each sim's hardcoded fallback Elo table). The optional SimulationContext oddsWeight plumbing (types.ts/runner.ts) is removed. - UCL is routed through the central blend (requiredInputs sourceElo, derivable from sourceOdds) so any entered Elo and futures blend uniformly. - College hockey already blends odds into Elo internally (and uses NPI rank the central resolver can't), so its central oddsWeight is set to 0 to avoid double-counting; the simulator is unchanged. Manifest: per-profile oddsWeight defaults (World Cup/UCL/MLB 0.3, NCAA FB 0.4, college hockey 0; global default 0.3). UI: the Input Policy card exposes one "Futures vs. Elo — Odds Blend Weight" control; the /admin/simulators inventory badge shows the effective blend ("Elo only" / "NN% blend" / "overrides Elo") with the odds participant count. Tests: input-policy blend math (0/0.5/1) for Elo and ratings, baseEloPriority and oddsWeight parsing/clamping, manifest per-profile weights; obsolete source-priority and oddsWeight-context tests removed/replaced. Note: this intentionally shifts the calibrated EV outputs of the four sims that previously blended at the probability level (accepted in design discussion). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-26 01:39:12 +00:00
* Softmax temperature for Elo-based selection weights (pre-bracket mode).
Add NCAA Football CFP simulator (12-team bracket) (#278) Fixes #124 * Add NCAA Football CFP simulator (12-team bracket) Implements a Monte Carlo simulator for the College Football Playoff using the 2024-present 12-team format. Elo/FPI ratings are entered manually via the existing admin Elo Ratings page; championship futures odds can optionally be blended in (60% Elo / 40% odds). - Add CFP_12 bracket template (First Round not scoring, QFs onward score) - Add generateCFP12Bracket() with correct seeding: 5v12, 6v11, 7v10, 8v9 in First Round; seeds 1–4 receive QF byes - Add NCAAFootballSimulator: 50k Monte Carlo sims, seeds teams by blended Elo+odds strength, tracks champion/finalist/SF/QF placement tiers - Register ncaa_football_bracket simulator type in registry and schema enum - Add migration 0071: ALTER TYPE simulator_type ADD VALUE 'ncaa_football_bracket' - Add tests: 30 tests covering bracket template structure and simulator probability distributions, seeding, edge cases, futures blending Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix lint errors in NCAA Football CFP simulator Replace non-null assertions with optional chaining, change let to const, use toSorted() instead of sort(), and add a bump() helper to avoid repeated map lookups with non-null assertions in simulateBracket. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix TS2345 in NCAA football simulator test Add ?? 0 fallback so optional-chained probFirst is number, not number | undefined. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 09:48:32 -04:00
* At T=100, a 200-point Elo gap produces ~7× weight difference enough to strongly
* favour the top teams while still giving bubble teams meaningful selection probability.
*/
const SELECTION_TEMP = 100;
// ─── Types ────────────────────────────────────────────────────────────────────
interface Team {
participantId: string;
Unify simulator strength on a single blended Elo Replace the two parallel odds/Elo mechanisms with one model: every strength source (raw Elo, projected wins, projected table points, futures odds) converts to an Elo, those blend by weight into a single Elo, and that one Elo feeds every simulator. This supersedes the earlier source-priority + per-match probability blend approach. Resolution (app/services/simulations/input-policy.ts): - SimulatorInputPolicy gains baseEloPriority (order among the substitutable base sources: raw Elo / projected wins / projected table points) and oddsWeight (0–1). resolveSourceElos/resolveRatings now compute baseElo, derive an odds Elo via convertFuturesToElo, and blend: 0 = base only, 1 = futures override, between = weighted blend (method "blend"). - Drops the odds-inclusive sourceEloPriority and the prefersFuturesOdds helper. Simulators consume the single resolved Elo: - UCL, World Cup, NCAA Football, MLB drop their separate normalized-odds signal, convertFuturesToElo calls, and per-match probability blend; they read the resolved sourceElo (preserving each sim's hardcoded fallback Elo table). The optional SimulationContext oddsWeight plumbing (types.ts/runner.ts) is removed. - UCL is routed through the central blend (requiredInputs sourceElo, derivable from sourceOdds) so any entered Elo and futures blend uniformly. - College hockey already blends odds into Elo internally (and uses NPI rank the central resolver can't), so its central oddsWeight is set to 0 to avoid double-counting; the simulator is unchanged. Manifest: per-profile oddsWeight defaults (World Cup/UCL/MLB 0.3, NCAA FB 0.4, college hockey 0; global default 0.3). UI: the Input Policy card exposes one "Futures vs. Elo — Odds Blend Weight" control; the /admin/simulators inventory badge shows the effective blend ("Elo only" / "NN% blend" / "overrides Elo") with the odds participant count. Tests: input-policy blend math (0/0.5/1) for Elo and ratings, baseEloPriority and oddsWeight parsing/clamping, manifest per-profile weights; obsolete source-priority and oddsWeight-context tests removed/replaced. Note: this intentionally shifts the calibrated EV outputs of the four sims that previously blended at the probability level (accepted in design discussion). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-26 01:39:12 +00:00
/** Single resolved Elo (already a blend of any raw Elo/FPI and futures odds). */
Add NCAA Football CFP simulator (12-team bracket) (#278) Fixes #124 * Add NCAA Football CFP simulator (12-team bracket) Implements a Monte Carlo simulator for the College Football Playoff using the 2024-present 12-team format. Elo/FPI ratings are entered manually via the existing admin Elo Ratings page; championship futures odds can optionally be blended in (60% Elo / 40% odds). - Add CFP_12 bracket template (First Round not scoring, QFs onward score) - Add generateCFP12Bracket() with correct seeding: 5v12, 6v11, 7v10, 8v9 in First Round; seeds 1–4 receive QF byes - Add NCAAFootballSimulator: 50k Monte Carlo sims, seeds teams by blended Elo+odds strength, tracks champion/finalist/SF/QF placement tiers - Register ncaa_football_bracket simulator type in registry and schema enum - Add migration 0071: ALTER TYPE simulator_type ADD VALUE 'ncaa_football_bracket' - Add tests: 30 tests covering bracket template structure and simulator probability distributions, seeding, edge cases, futures blending Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix lint errors in NCAA Football CFP simulator Replace non-null assertions with optional chaining, change let to const, use toSorted() instead of sort(), and add a bump() helper to avoid repeated map lookups with non-null assertions in simulateBracket. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix TS2345 in NCAA football simulator test Add ?? 0 fallback so optional-chained probFirst is number, not number | undefined. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 09:48:32 -04:00
elo: number;
Unify simulator strength on a single blended Elo Replace the two parallel odds/Elo mechanisms with one model: every strength source (raw Elo, projected wins, projected table points, futures odds) converts to an Elo, those blend by weight into a single Elo, and that one Elo feeds every simulator. This supersedes the earlier source-priority + per-match probability blend approach. Resolution (app/services/simulations/input-policy.ts): - SimulatorInputPolicy gains baseEloPriority (order among the substitutable base sources: raw Elo / projected wins / projected table points) and oddsWeight (0–1). resolveSourceElos/resolveRatings now compute baseElo, derive an odds Elo via convertFuturesToElo, and blend: 0 = base only, 1 = futures override, between = weighted blend (method "blend"). - Drops the odds-inclusive sourceEloPriority and the prefersFuturesOdds helper. Simulators consume the single resolved Elo: - UCL, World Cup, NCAA Football, MLB drop their separate normalized-odds signal, convertFuturesToElo calls, and per-match probability blend; they read the resolved sourceElo (preserving each sim's hardcoded fallback Elo table). The optional SimulationContext oddsWeight plumbing (types.ts/runner.ts) is removed. - UCL is routed through the central blend (requiredInputs sourceElo, derivable from sourceOdds) so any entered Elo and futures blend uniformly. - College hockey already blends odds into Elo internally (and uses NPI rank the central resolver can't), so its central oddsWeight is set to 0 to avoid double-counting; the simulator is unchanged. Manifest: per-profile oddsWeight defaults (World Cup/UCL/MLB 0.3, NCAA FB 0.4, college hockey 0; global default 0.3). UI: the Input Policy card exposes one "Futures vs. Elo — Odds Blend Weight" control; the /admin/simulators inventory badge shows the effective blend ("Elo only" / "NN% blend" / "overrides Elo") with the odds participant count. Tests: input-policy blend math (0/0.5/1) for Elo and ratings, baseEloPriority and oddsWeight parsing/clamping, manifest per-profile weights; obsolete source-priority and oddsWeight-context tests removed/replaced. Note: this intentionally shifts the calibrated EV outputs of the four sims that previously blended at the probability level (accepted in design discussion). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-26 01:39:12 +00:00
/** Weight used for probabilistic CFP field selection (pre-bracket mode only). */
Add NCAA Football CFP simulator (12-team bracket) (#278) Fixes #124 * Add NCAA Football CFP simulator (12-team bracket) Implements a Monte Carlo simulator for the College Football Playoff using the 2024-present 12-team format. Elo/FPI ratings are entered manually via the existing admin Elo Ratings page; championship futures odds can optionally be blended in (60% Elo / 40% odds). - Add CFP_12 bracket template (First Round not scoring, QFs onward score) - Add generateCFP12Bracket() with correct seeding: 5v12, 6v11, 7v10, 8v9 in First Round; seeds 1–4 receive QF byes - Add NCAAFootballSimulator: 50k Monte Carlo sims, seeds teams by blended Elo+odds strength, tracks champion/finalist/SF/QF placement tiers - Register ncaa_football_bracket simulator type in registry and schema enum - Add migration 0071: ALTER TYPE simulator_type ADD VALUE 'ncaa_football_bracket' - Add tests: 30 tests covering bracket template structure and simulator probability distributions, seeding, edge cases, futures blending Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix lint errors in NCAA Football CFP simulator Replace non-null assertions with optional chaining, change let to const, use toSorted() instead of sort(), and add a bump() helper to avoid repeated map lookups with non-null assertions in simulateBracket. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix TS2345 in NCAA football simulator test Add ?? 0 fallback so optional-chained probFirst is number, not number | undefined. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 09:48:32 -04:00
selectionWeight: number;
}
// ─── Helpers ─────────────────────────────────────────────────────────────────
Unify simulator strength on a single blended Elo Replace the two parallel odds/Elo mechanisms with one model: every strength source (raw Elo, projected wins, projected table points, futures odds) converts to an Elo, those blend by weight into a single Elo, and that one Elo feeds every simulator. This supersedes the earlier source-priority + per-match probability blend approach. Resolution (app/services/simulations/input-policy.ts): - SimulatorInputPolicy gains baseEloPriority (order among the substitutable base sources: raw Elo / projected wins / projected table points) and oddsWeight (0–1). resolveSourceElos/resolveRatings now compute baseElo, derive an odds Elo via convertFuturesToElo, and blend: 0 = base only, 1 = futures override, between = weighted blend (method "blend"). - Drops the odds-inclusive sourceEloPriority and the prefersFuturesOdds helper. Simulators consume the single resolved Elo: - UCL, World Cup, NCAA Football, MLB drop their separate normalized-odds signal, convertFuturesToElo calls, and per-match probability blend; they read the resolved sourceElo (preserving each sim's hardcoded fallback Elo table). The optional SimulationContext oddsWeight plumbing (types.ts/runner.ts) is removed. - UCL is routed through the central blend (requiredInputs sourceElo, derivable from sourceOdds) so any entered Elo and futures blend uniformly. - College hockey already blends odds into Elo internally (and uses NPI rank the central resolver can't), so its central oddsWeight is set to 0 to avoid double-counting; the simulator is unchanged. Manifest: per-profile oddsWeight defaults (World Cup/UCL/MLB 0.3, NCAA FB 0.4, college hockey 0; global default 0.3). UI: the Input Policy card exposes one "Futures vs. Elo — Odds Blend Weight" control; the /admin/simulators inventory badge shows the effective blend ("Elo only" / "NN% blend" / "overrides Elo") with the odds participant count. Tests: input-policy blend math (0/0.5/1) for Elo and ratings, baseEloPriority and oddsWeight parsing/clamping, manifest per-profile weights; obsolete source-priority and oddsWeight-context tests removed/replaced. Note: this intentionally shifts the calibrated EV outputs of the four sims that previously blended at the probability level (accepted in design discussion). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-26 01:39:12 +00:00
/** Win probability for team1 vs team2 from the single resolved Elo. */
function gameWinProb(team1: Team, team2: Team): number {
return eloWinProbability(team1.elo, team2.elo);
Add NCAA Football CFP simulator (12-team bracket) (#278) Fixes #124 * Add NCAA Football CFP simulator (12-team bracket) Implements a Monte Carlo simulator for the College Football Playoff using the 2024-present 12-team format. Elo/FPI ratings are entered manually via the existing admin Elo Ratings page; championship futures odds can optionally be blended in (60% Elo / 40% odds). - Add CFP_12 bracket template (First Round not scoring, QFs onward score) - Add generateCFP12Bracket() with correct seeding: 5v12, 6v11, 7v10, 8v9 in First Round; seeds 1–4 receive QF byes - Add NCAAFootballSimulator: 50k Monte Carlo sims, seeds teams by blended Elo+odds strength, tracks champion/finalist/SF/QF placement tiers - Register ncaa_football_bracket simulator type in registry and schema enum - Add migration 0071: ALTER TYPE simulator_type ADD VALUE 'ncaa_football_bracket' - Add tests: 30 tests covering bracket template structure and simulator probability distributions, seeding, edge cases, futures blending Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix lint errors in NCAA Football CFP simulator Replace non-null assertions with optional chaining, change let to const, use toSorted() instead of sort(), and add a bump() helper to avoid repeated map lookups with non-null assertions in simulateBracket. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix TS2345 in NCAA football simulator test Add ?? 0 fallback so optional-chained probFirst is number, not number | undefined. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 09:48:32 -04:00
}
Unify simulator strength on a single blended Elo Replace the two parallel odds/Elo mechanisms with one model: every strength source (raw Elo, projected wins, projected table points, futures odds) converts to an Elo, those blend by weight into a single Elo, and that one Elo feeds every simulator. This supersedes the earlier source-priority + per-match probability blend approach. Resolution (app/services/simulations/input-policy.ts): - SimulatorInputPolicy gains baseEloPriority (order among the substitutable base sources: raw Elo / projected wins / projected table points) and oddsWeight (0–1). resolveSourceElos/resolveRatings now compute baseElo, derive an odds Elo via convertFuturesToElo, and blend: 0 = base only, 1 = futures override, between = weighted blend (method "blend"). - Drops the odds-inclusive sourceEloPriority and the prefersFuturesOdds helper. Simulators consume the single resolved Elo: - UCL, World Cup, NCAA Football, MLB drop their separate normalized-odds signal, convertFuturesToElo calls, and per-match probability blend; they read the resolved sourceElo (preserving each sim's hardcoded fallback Elo table). The optional SimulationContext oddsWeight plumbing (types.ts/runner.ts) is removed. - UCL is routed through the central blend (requiredInputs sourceElo, derivable from sourceOdds) so any entered Elo and futures blend uniformly. - College hockey already blends odds into Elo internally (and uses NPI rank the central resolver can't), so its central oddsWeight is set to 0 to avoid double-counting; the simulator is unchanged. Manifest: per-profile oddsWeight defaults (World Cup/UCL/MLB 0.3, NCAA FB 0.4, college hockey 0; global default 0.3). UI: the Input Policy card exposes one "Futures vs. Elo — Odds Blend Weight" control; the /admin/simulators inventory badge shows the effective blend ("Elo only" / "NN% blend" / "overrides Elo") with the odds participant count. Tests: input-policy blend math (0/0.5/1) for Elo and ratings, baseEloPriority and oddsWeight parsing/clamping, manifest per-profile weights; obsolete source-priority and oddsWeight-context tests removed/replaced. Note: this intentionally shifts the calibrated EV outputs of the four sims that previously blended at the probability level (accepted in design discussion). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-26 01:39:12 +00:00
function simGame(team1: Team, team2: Team): { winner: Team; loser: Team } {
const p1Wins = Math.random() < gameWinProb(team1, team2);
Add NCAA Football CFP simulator (12-team bracket) (#278) Fixes #124 * Add NCAA Football CFP simulator (12-team bracket) Implements a Monte Carlo simulator for the College Football Playoff using the 2024-present 12-team format. Elo/FPI ratings are entered manually via the existing admin Elo Ratings page; championship futures odds can optionally be blended in (60% Elo / 40% odds). - Add CFP_12 bracket template (First Round not scoring, QFs onward score) - Add generateCFP12Bracket() with correct seeding: 5v12, 6v11, 7v10, 8v9 in First Round; seeds 1–4 receive QF byes - Add NCAAFootballSimulator: 50k Monte Carlo sims, seeds teams by blended Elo+odds strength, tracks champion/finalist/SF/QF placement tiers - Register ncaa_football_bracket simulator type in registry and schema enum - Add migration 0071: ALTER TYPE simulator_type ADD VALUE 'ncaa_football_bracket' - Add tests: 30 tests covering bracket template structure and simulator probability distributions, seeding, edge cases, futures blending Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix lint errors in NCAA Football CFP simulator Replace non-null assertions with optional chaining, change let to const, use toSorted() instead of sort(), and add a bump() helper to avoid repeated map lookups with non-null assertions in simulateBracket. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix TS2345 in NCAA football simulator test Add ?? 0 fallback so optional-chained probFirst is number, not number | undefined. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 09:48:32 -04:00
return p1Wins
? { winner: team1, loser: team2 }
: { winner: team2, loser: team1 };
}
/**
* Weighted sample without replacement selects `n` teams from `pool` where each
* team's probability of being drawn is proportional to its selectionWeight.
* Returns the selected teams sorted by Elo descending (seed 1 = best Elo).
*/
function sampleBracketField(pool: Team[], n: number): Team[] {
const remaining = [...pool];
const selected: Team[] = [];
for (let i = 0; i < n; i++) {
const totalWeight = remaining.reduce((sum, t) => sum + t.selectionWeight, 0);
let r = Math.random() * totalWeight;
let j = 0;
for (; j < remaining.length - 1; j++) {
r -= remaining[j].selectionWeight;
if (r <= 0) break;
}
selected.push(remaining[j]);
remaining.splice(j, 1);
}
// Seed by Elo so that the best team in the sampled field is always seed 1.
return selected.toSorted((a, b) => b.elo - a.elo);
}
// ─── Bracket simulation ───────────────────────────────────────────────────────
interface PlacementCounts {
champion: number;
finalist: number;
sfLoser: number;
qfLoser: number;
}
/**
* Simulate one full 12-team CFP bracket.
*
* Seeding (teams sorted bestworst Elo, index 0 = seed 1):
* First Round: [4]v[11], [5]v[10], [6]v[9], [7]v[8]
* Quarterfinals: [0] vs fr4w, [3] vs fr1w, [2] vs fr2w, [1] vs fr3w
* Semifinals: qf1w vs qf2w, qf3w vs qf4w
* Championship: sf1w vs sf2w
*/
Unify simulator strength on a single blended Elo Replace the two parallel odds/Elo mechanisms with one model: every strength source (raw Elo, projected wins, projected table points, futures odds) converts to an Elo, those blend by weight into a single Elo, and that one Elo feeds every simulator. This supersedes the earlier source-priority + per-match probability blend approach. Resolution (app/services/simulations/input-policy.ts): - SimulatorInputPolicy gains baseEloPriority (order among the substitutable base sources: raw Elo / projected wins / projected table points) and oddsWeight (0–1). resolveSourceElos/resolveRatings now compute baseElo, derive an odds Elo via convertFuturesToElo, and blend: 0 = base only, 1 = futures override, between = weighted blend (method "blend"). - Drops the odds-inclusive sourceEloPriority and the prefersFuturesOdds helper. Simulators consume the single resolved Elo: - UCL, World Cup, NCAA Football, MLB drop their separate normalized-odds signal, convertFuturesToElo calls, and per-match probability blend; they read the resolved sourceElo (preserving each sim's hardcoded fallback Elo table). The optional SimulationContext oddsWeight plumbing (types.ts/runner.ts) is removed. - UCL is routed through the central blend (requiredInputs sourceElo, derivable from sourceOdds) so any entered Elo and futures blend uniformly. - College hockey already blends odds into Elo internally (and uses NPI rank the central resolver can't), so its central oddsWeight is set to 0 to avoid double-counting; the simulator is unchanged. Manifest: per-profile oddsWeight defaults (World Cup/UCL/MLB 0.3, NCAA FB 0.4, college hockey 0; global default 0.3). UI: the Input Policy card exposes one "Futures vs. Elo — Odds Blend Weight" control; the /admin/simulators inventory badge shows the effective blend ("Elo only" / "NN% blend" / "overrides Elo") with the odds participant count. Tests: input-policy blend math (0/0.5/1) for Elo and ratings, baseEloPriority and oddsWeight parsing/clamping, manifest per-profile weights; obsolete source-priority and oddsWeight-context tests removed/replaced. Note: this intentionally shifts the calibrated EV outputs of the four sims that previously blended at the probability level (accepted in design discussion). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-26 01:39:12 +00:00
function simulateBracket(teams: Team[], counts: Map<string, PlacementCounts>): void {
Add NCAA Football CFP simulator (12-team bracket) (#278) Fixes #124 * Add NCAA Football CFP simulator (12-team bracket) Implements a Monte Carlo simulator for the College Football Playoff using the 2024-present 12-team format. Elo/FPI ratings are entered manually via the existing admin Elo Ratings page; championship futures odds can optionally be blended in (60% Elo / 40% odds). - Add CFP_12 bracket template (First Round not scoring, QFs onward score) - Add generateCFP12Bracket() with correct seeding: 5v12, 6v11, 7v10, 8v9 in First Round; seeds 1–4 receive QF byes - Add NCAAFootballSimulator: 50k Monte Carlo sims, seeds teams by blended Elo+odds strength, tracks champion/finalist/SF/QF placement tiers - Register ncaa_football_bracket simulator type in registry and schema enum - Add migration 0071: ALTER TYPE simulator_type ADD VALUE 'ncaa_football_bracket' - Add tests: 30 tests covering bracket template structure and simulator probability distributions, seeding, edge cases, futures blending Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix lint errors in NCAA Football CFP simulator Replace non-null assertions with optional chaining, change let to const, use toSorted() instead of sort(), and add a bump() helper to avoid repeated map lookups with non-null assertions in simulateBracket. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix TS2345 in NCAA football simulator test Add ?? 0 fallback so optional-chained probFirst is number, not number | undefined. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 09:48:32 -04:00
// ── First Round (seeds 512) ───────────────────────────────────────────────
Unify simulator strength on a single blended Elo Replace the two parallel odds/Elo mechanisms with one model: every strength source (raw Elo, projected wins, projected table points, futures odds) converts to an Elo, those blend by weight into a single Elo, and that one Elo feeds every simulator. This supersedes the earlier source-priority + per-match probability blend approach. Resolution (app/services/simulations/input-policy.ts): - SimulatorInputPolicy gains baseEloPriority (order among the substitutable base sources: raw Elo / projected wins / projected table points) and oddsWeight (0–1). resolveSourceElos/resolveRatings now compute baseElo, derive an odds Elo via convertFuturesToElo, and blend: 0 = base only, 1 = futures override, between = weighted blend (method "blend"). - Drops the odds-inclusive sourceEloPriority and the prefersFuturesOdds helper. Simulators consume the single resolved Elo: - UCL, World Cup, NCAA Football, MLB drop their separate normalized-odds signal, convertFuturesToElo calls, and per-match probability blend; they read the resolved sourceElo (preserving each sim's hardcoded fallback Elo table). The optional SimulationContext oddsWeight plumbing (types.ts/runner.ts) is removed. - UCL is routed through the central blend (requiredInputs sourceElo, derivable from sourceOdds) so any entered Elo and futures blend uniformly. - College hockey already blends odds into Elo internally (and uses NPI rank the central resolver can't), so its central oddsWeight is set to 0 to avoid double-counting; the simulator is unchanged. Manifest: per-profile oddsWeight defaults (World Cup/UCL/MLB 0.3, NCAA FB 0.4, college hockey 0; global default 0.3). UI: the Input Policy card exposes one "Futures vs. Elo — Odds Blend Weight" control; the /admin/simulators inventory badge shows the effective blend ("Elo only" / "NN% blend" / "overrides Elo") with the odds participant count. Tests: input-policy blend math (0/0.5/1) for Elo and ratings, baseEloPriority and oddsWeight parsing/clamping, manifest per-profile weights; obsolete source-priority and oddsWeight-context tests removed/replaced. Note: this intentionally shifts the calibrated EV outputs of the four sims that previously blended at the probability level (accepted in design discussion). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-26 01:39:12 +00:00
const fr1 = simGame(teams[4], teams[11]); // 5 vs 12
const fr2 = simGame(teams[5], teams[10]); // 6 vs 11
const fr3 = simGame(teams[6], teams[9]); // 7 vs 10
const fr4 = simGame(teams[7], teams[8]); // 8 vs 9
Add NCAA Football CFP simulator (12-team bracket) (#278) Fixes #124 * Add NCAA Football CFP simulator (12-team bracket) Implements a Monte Carlo simulator for the College Football Playoff using the 2024-present 12-team format. Elo/FPI ratings are entered manually via the existing admin Elo Ratings page; championship futures odds can optionally be blended in (60% Elo / 40% odds). - Add CFP_12 bracket template (First Round not scoring, QFs onward score) - Add generateCFP12Bracket() with correct seeding: 5v12, 6v11, 7v10, 8v9 in First Round; seeds 1–4 receive QF byes - Add NCAAFootballSimulator: 50k Monte Carlo sims, seeds teams by blended Elo+odds strength, tracks champion/finalist/SF/QF placement tiers - Register ncaa_football_bracket simulator type in registry and schema enum - Add migration 0071: ALTER TYPE simulator_type ADD VALUE 'ncaa_football_bracket' - Add tests: 30 tests covering bracket template structure and simulator probability distributions, seeding, edge cases, futures blending Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix lint errors in NCAA Football CFP simulator Replace non-null assertions with optional chaining, change let to const, use toSorted() instead of sort(), and add a bump() helper to avoid repeated map lookups with non-null assertions in simulateBracket. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix TS2345 in NCAA football simulator test Add ?? 0 fallback so optional-chained probFirst is number, not number | undefined. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 09:48:32 -04:00
// ── Quarterfinals (seeds 14 get byes) ────────────────────────────────────
Unify simulator strength on a single blended Elo Replace the two parallel odds/Elo mechanisms with one model: every strength source (raw Elo, projected wins, projected table points, futures odds) converts to an Elo, those blend by weight into a single Elo, and that one Elo feeds every simulator. This supersedes the earlier source-priority + per-match probability blend approach. Resolution (app/services/simulations/input-policy.ts): - SimulatorInputPolicy gains baseEloPriority (order among the substitutable base sources: raw Elo / projected wins / projected table points) and oddsWeight (0–1). resolveSourceElos/resolveRatings now compute baseElo, derive an odds Elo via convertFuturesToElo, and blend: 0 = base only, 1 = futures override, between = weighted blend (method "blend"). - Drops the odds-inclusive sourceEloPriority and the prefersFuturesOdds helper. Simulators consume the single resolved Elo: - UCL, World Cup, NCAA Football, MLB drop their separate normalized-odds signal, convertFuturesToElo calls, and per-match probability blend; they read the resolved sourceElo (preserving each sim's hardcoded fallback Elo table). The optional SimulationContext oddsWeight plumbing (types.ts/runner.ts) is removed. - UCL is routed through the central blend (requiredInputs sourceElo, derivable from sourceOdds) so any entered Elo and futures blend uniformly. - College hockey already blends odds into Elo internally (and uses NPI rank the central resolver can't), so its central oddsWeight is set to 0 to avoid double-counting; the simulator is unchanged. Manifest: per-profile oddsWeight defaults (World Cup/UCL/MLB 0.3, NCAA FB 0.4, college hockey 0; global default 0.3). UI: the Input Policy card exposes one "Futures vs. Elo — Odds Blend Weight" control; the /admin/simulators inventory badge shows the effective blend ("Elo only" / "NN% blend" / "overrides Elo") with the odds participant count. Tests: input-policy blend math (0/0.5/1) for Elo and ratings, baseEloPriority and oddsWeight parsing/clamping, manifest per-profile weights; obsolete source-priority and oddsWeight-context tests removed/replaced. Note: this intentionally shifts the calibrated EV outputs of the four sims that previously blended at the probability level (accepted in design discussion). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-26 01:39:12 +00:00
const qf1 = simGame(teams[0], fr4.winner); // 1 vs 8/9 winner
const qf2 = simGame(teams[3], fr1.winner); // 4 vs 5/12 winner
const qf3 = simGame(teams[2], fr2.winner); // 3 vs 6/11 winner
const qf4 = simGame(teams[1], fr3.winner); // 2 vs 7/10 winner
Add NCAA Football CFP simulator (12-team bracket) (#278) Fixes #124 * Add NCAA Football CFP simulator (12-team bracket) Implements a Monte Carlo simulator for the College Football Playoff using the 2024-present 12-team format. Elo/FPI ratings are entered manually via the existing admin Elo Ratings page; championship futures odds can optionally be blended in (60% Elo / 40% odds). - Add CFP_12 bracket template (First Round not scoring, QFs onward score) - Add generateCFP12Bracket() with correct seeding: 5v12, 6v11, 7v10, 8v9 in First Round; seeds 1–4 receive QF byes - Add NCAAFootballSimulator: 50k Monte Carlo sims, seeds teams by blended Elo+odds strength, tracks champion/finalist/SF/QF placement tiers - Register ncaa_football_bracket simulator type in registry and schema enum - Add migration 0071: ALTER TYPE simulator_type ADD VALUE 'ncaa_football_bracket' - Add tests: 30 tests covering bracket template structure and simulator probability distributions, seeding, edge cases, futures blending Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix lint errors in NCAA Football CFP simulator Replace non-null assertions with optional chaining, change let to const, use toSorted() instead of sort(), and add a bump() helper to avoid repeated map lookups with non-null assertions in simulateBracket. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix TS2345 in NCAA football simulator test Add ?? 0 fallback so optional-chained probFirst is number, not number | undefined. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 09:48:32 -04:00
const bump = (id: string, key: keyof PlacementCounts) => {
const entry = counts.get(id);
if (entry) entry[key]++;
};
bump(qf1.loser.participantId, "qfLoser");
bump(qf2.loser.participantId, "qfLoser");
bump(qf3.loser.participantId, "qfLoser");
bump(qf4.loser.participantId, "qfLoser");
// ── Semifinals ────────────────────────────────────────────────────────────
Unify simulator strength on a single blended Elo Replace the two parallel odds/Elo mechanisms with one model: every strength source (raw Elo, projected wins, projected table points, futures odds) converts to an Elo, those blend by weight into a single Elo, and that one Elo feeds every simulator. This supersedes the earlier source-priority + per-match probability blend approach. Resolution (app/services/simulations/input-policy.ts): - SimulatorInputPolicy gains baseEloPriority (order among the substitutable base sources: raw Elo / projected wins / projected table points) and oddsWeight (0–1). resolveSourceElos/resolveRatings now compute baseElo, derive an odds Elo via convertFuturesToElo, and blend: 0 = base only, 1 = futures override, between = weighted blend (method "blend"). - Drops the odds-inclusive sourceEloPriority and the prefersFuturesOdds helper. Simulators consume the single resolved Elo: - UCL, World Cup, NCAA Football, MLB drop their separate normalized-odds signal, convertFuturesToElo calls, and per-match probability blend; they read the resolved sourceElo (preserving each sim's hardcoded fallback Elo table). The optional SimulationContext oddsWeight plumbing (types.ts/runner.ts) is removed. - UCL is routed through the central blend (requiredInputs sourceElo, derivable from sourceOdds) so any entered Elo and futures blend uniformly. - College hockey already blends odds into Elo internally (and uses NPI rank the central resolver can't), so its central oddsWeight is set to 0 to avoid double-counting; the simulator is unchanged. Manifest: per-profile oddsWeight defaults (World Cup/UCL/MLB 0.3, NCAA FB 0.4, college hockey 0; global default 0.3). UI: the Input Policy card exposes one "Futures vs. Elo — Odds Blend Weight" control; the /admin/simulators inventory badge shows the effective blend ("Elo only" / "NN% blend" / "overrides Elo") with the odds participant count. Tests: input-policy blend math (0/0.5/1) for Elo and ratings, baseEloPriority and oddsWeight parsing/clamping, manifest per-profile weights; obsolete source-priority and oddsWeight-context tests removed/replaced. Note: this intentionally shifts the calibrated EV outputs of the four sims that previously blended at the probability level (accepted in design discussion). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-26 01:39:12 +00:00
const sf1 = simGame(qf1.winner, qf2.winner);
const sf2 = simGame(qf3.winner, qf4.winner);
Add NCAA Football CFP simulator (12-team bracket) (#278) Fixes #124 * Add NCAA Football CFP simulator (12-team bracket) Implements a Monte Carlo simulator for the College Football Playoff using the 2024-present 12-team format. Elo/FPI ratings are entered manually via the existing admin Elo Ratings page; championship futures odds can optionally be blended in (60% Elo / 40% odds). - Add CFP_12 bracket template (First Round not scoring, QFs onward score) - Add generateCFP12Bracket() with correct seeding: 5v12, 6v11, 7v10, 8v9 in First Round; seeds 1–4 receive QF byes - Add NCAAFootballSimulator: 50k Monte Carlo sims, seeds teams by blended Elo+odds strength, tracks champion/finalist/SF/QF placement tiers - Register ncaa_football_bracket simulator type in registry and schema enum - Add migration 0071: ALTER TYPE simulator_type ADD VALUE 'ncaa_football_bracket' - Add tests: 30 tests covering bracket template structure and simulator probability distributions, seeding, edge cases, futures blending Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix lint errors in NCAA Football CFP simulator Replace non-null assertions with optional chaining, change let to const, use toSorted() instead of sort(), and add a bump() helper to avoid repeated map lookups with non-null assertions in simulateBracket. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix TS2345 in NCAA football simulator test Add ?? 0 fallback so optional-chained probFirst is number, not number | undefined. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 09:48:32 -04:00
bump(sf1.loser.participantId, "sfLoser");
bump(sf2.loser.participantId, "sfLoser");
// ── National Championship ─────────────────────────────────────────────────
Unify simulator strength on a single blended Elo Replace the two parallel odds/Elo mechanisms with one model: every strength source (raw Elo, projected wins, projected table points, futures odds) converts to an Elo, those blend by weight into a single Elo, and that one Elo feeds every simulator. This supersedes the earlier source-priority + per-match probability blend approach. Resolution (app/services/simulations/input-policy.ts): - SimulatorInputPolicy gains baseEloPriority (order among the substitutable base sources: raw Elo / projected wins / projected table points) and oddsWeight (0–1). resolveSourceElos/resolveRatings now compute baseElo, derive an odds Elo via convertFuturesToElo, and blend: 0 = base only, 1 = futures override, between = weighted blend (method "blend"). - Drops the odds-inclusive sourceEloPriority and the prefersFuturesOdds helper. Simulators consume the single resolved Elo: - UCL, World Cup, NCAA Football, MLB drop their separate normalized-odds signal, convertFuturesToElo calls, and per-match probability blend; they read the resolved sourceElo (preserving each sim's hardcoded fallback Elo table). The optional SimulationContext oddsWeight plumbing (types.ts/runner.ts) is removed. - UCL is routed through the central blend (requiredInputs sourceElo, derivable from sourceOdds) so any entered Elo and futures blend uniformly. - College hockey already blends odds into Elo internally (and uses NPI rank the central resolver can't), so its central oddsWeight is set to 0 to avoid double-counting; the simulator is unchanged. Manifest: per-profile oddsWeight defaults (World Cup/UCL/MLB 0.3, NCAA FB 0.4, college hockey 0; global default 0.3). UI: the Input Policy card exposes one "Futures vs. Elo — Odds Blend Weight" control; the /admin/simulators inventory badge shows the effective blend ("Elo only" / "NN% blend" / "overrides Elo") with the odds participant count. Tests: input-policy blend math (0/0.5/1) for Elo and ratings, baseEloPriority and oddsWeight parsing/clamping, manifest per-profile weights; obsolete source-priority and oddsWeight-context tests removed/replaced. Note: this intentionally shifts the calibrated EV outputs of the four sims that previously blended at the probability level (accepted in design discussion). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-26 01:39:12 +00:00
const final = simGame(sf1.winner, sf2.winner);
Add NCAA Football CFP simulator (12-team bracket) (#278) Fixes #124 * Add NCAA Football CFP simulator (12-team bracket) Implements a Monte Carlo simulator for the College Football Playoff using the 2024-present 12-team format. Elo/FPI ratings are entered manually via the existing admin Elo Ratings page; championship futures odds can optionally be blended in (60% Elo / 40% odds). - Add CFP_12 bracket template (First Round not scoring, QFs onward score) - Add generateCFP12Bracket() with correct seeding: 5v12, 6v11, 7v10, 8v9 in First Round; seeds 1–4 receive QF byes - Add NCAAFootballSimulator: 50k Monte Carlo sims, seeds teams by blended Elo+odds strength, tracks champion/finalist/SF/QF placement tiers - Register ncaa_football_bracket simulator type in registry and schema enum - Add migration 0071: ALTER TYPE simulator_type ADD VALUE 'ncaa_football_bracket' - Add tests: 30 tests covering bracket template structure and simulator probability distributions, seeding, edge cases, futures blending Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix lint errors in NCAA Football CFP simulator Replace non-null assertions with optional chaining, change let to const, use toSorted() instead of sort(), and add a bump() helper to avoid repeated map lookups with non-null assertions in simulateBracket. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix TS2345 in NCAA football simulator test Add ?? 0 fallback so optional-chained probFirst is number, not number | undefined. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 09:48:32 -04:00
bump(final.winner.participantId, "champion");
bump(final.loser.participantId, "finalist");
}
// ─── Simulator ────────────────────────────────────────────────────────────────
export class NCAAFootballSimulator implements Simulator {
Unify simulator strength on a single blended Elo Replace the two parallel odds/Elo mechanisms with one model: every strength source (raw Elo, projected wins, projected table points, futures odds) converts to an Elo, those blend by weight into a single Elo, and that one Elo feeds every simulator. This supersedes the earlier source-priority + per-match probability blend approach. Resolution (app/services/simulations/input-policy.ts): - SimulatorInputPolicy gains baseEloPriority (order among the substitutable base sources: raw Elo / projected wins / projected table points) and oddsWeight (0–1). resolveSourceElos/resolveRatings now compute baseElo, derive an odds Elo via convertFuturesToElo, and blend: 0 = base only, 1 = futures override, between = weighted blend (method "blend"). - Drops the odds-inclusive sourceEloPriority and the prefersFuturesOdds helper. Simulators consume the single resolved Elo: - UCL, World Cup, NCAA Football, MLB drop their separate normalized-odds signal, convertFuturesToElo calls, and per-match probability blend; they read the resolved sourceElo (preserving each sim's hardcoded fallback Elo table). The optional SimulationContext oddsWeight plumbing (types.ts/runner.ts) is removed. - UCL is routed through the central blend (requiredInputs sourceElo, derivable from sourceOdds) so any entered Elo and futures blend uniformly. - College hockey already blends odds into Elo internally (and uses NPI rank the central resolver can't), so its central oddsWeight is set to 0 to avoid double-counting; the simulator is unchanged. Manifest: per-profile oddsWeight defaults (World Cup/UCL/MLB 0.3, NCAA FB 0.4, college hockey 0; global default 0.3). UI: the Input Policy card exposes one "Futures vs. Elo — Odds Blend Weight" control; the /admin/simulators inventory badge shows the effective blend ("Elo only" / "NN% blend" / "overrides Elo") with the odds participant count. Tests: input-policy blend math (0/0.5/1) for Elo and ratings, baseEloPriority and oddsWeight parsing/clamping, manifest per-profile weights; obsolete source-priority and oddsWeight-context tests removed/replaced. Note: this intentionally shifts the calibrated EV outputs of the four sims that previously blended at the probability level (accepted in design discussion). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-26 01:39:12 +00:00
async simulate(sportsSeasonId: string): Promise<SimulationResult[]> {
Add NCAA Football CFP simulator (12-team bracket) (#278) Fixes #124 * Add NCAA Football CFP simulator (12-team bracket) Implements a Monte Carlo simulator for the College Football Playoff using the 2024-present 12-team format. Elo/FPI ratings are entered manually via the existing admin Elo Ratings page; championship futures odds can optionally be blended in (60% Elo / 40% odds). - Add CFP_12 bracket template (First Round not scoring, QFs onward score) - Add generateCFP12Bracket() with correct seeding: 5v12, 6v11, 7v10, 8v9 in First Round; seeds 1–4 receive QF byes - Add NCAAFootballSimulator: 50k Monte Carlo sims, seeds teams by blended Elo+odds strength, tracks champion/finalist/SF/QF placement tiers - Register ncaa_football_bracket simulator type in registry and schema enum - Add migration 0071: ALTER TYPE simulator_type ADD VALUE 'ncaa_football_bracket' - Add tests: 30 tests covering bracket template structure and simulator probability distributions, seeding, edge cases, futures blending Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix lint errors in NCAA Football CFP simulator Replace non-null assertions with optional chaining, change let to const, use toSorted() instead of sort(), and add a bump() helper to avoid repeated map lookups with non-null assertions in simulateBracket. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix TS2345 in NCAA football simulator test Add ?? 0 fallback so optional-chained probFirst is number, not number | undefined. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 09:48:32 -04:00
const db = database();
// 1. Load all participants for this sports season.
const participants = await db
Canonical tournament layer: schema + backfill (1/2) (#365) * refactor(schema): rename per-window tables to season_* prefix Renames participants, participant_expected_values, participant_qualifying_totals, participant_results, participant_surface_elos to season_* prefixed names. Renames event_results.participant_id to season_participant_id. Phase 1a of canonical tournament layer migration. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor: rename participant.ts model file to season-participant.ts Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(models): update model layer to use renamed schema exports Updated all model files to use the renamed schema exports from Task 1: - participants → seasonParticipants - participantExpectedValues → seasonParticipantExpectedValues - participantQualifyingTotals → seasonParticipantQualifyingTotals - participantResults → seasonParticipantResults - participantSurfaceElos → seasonParticipantSurfaceElos - eventResults.participantId → eventResults.seasonParticipantId - db.query relation accessors updated - Relation field .participant → .seasonParticipant where applicable - Import paths updated: ./participant → ./season-participant Files updated (14 model files + 3 test files): - draft-pick.ts - draft-utils.ts - event-result.ts - group-stage-match.ts - participant-result.ts - qualifying-points.ts - scoring-calculator.ts - scoring-event.ts - sports-season.ts - surface-elo.ts - team-score-events.ts - cs2-major-stage.ts - golf-skills.ts - participant-expected-value.ts - __tests__/sports-season.clone.test.ts - __tests__/auto-pick.test.ts - __tests__/executeAutoPick.timer.test.ts Typecheck errors decreased: 779 → 499 (280 fewer) All model file errors related to renamed schemas resolved. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(routes): update route layer to use renamed schema exports - Update model import from ~/models/participant to ~/models/season-participant - Rename schema.participants to schema.seasonParticipants - Rename schema.participantResults to schema.seasonParticipantResults - Rename db.query.participants to db.query.seasonParticipants - Update 9 route files and 1 test file Affected files: - admin.sports-seasons.$id.events.$eventId.bracket.server.ts - admin.sports-seasons.$id.participants.tsx - api/draft.force-manual-pick.ts - api/draft.make-pick.ts - api/draft.replace-pick.ts - api/seasons.$seasonId.draft.ts - leagues/$leagueId.draft-board.$seasonId.tsx - leagues/$leagueId.sports-seasons.$sportsSeasonId.server.ts - admin/__tests__/sports-seasons-participants.test.ts Error count reduced from 499 to 453 (46 errors fixed). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(routes): update route files for schema rename Update route imports from ~/models/participant to ~/models/season-participant and fix references to .participant/.participantId on event results to use .seasonParticipant/.seasonParticipantId after schema rename. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(services): update simulators and services for renamed schema Update all simulators, services, and server files to use renamed schema tables: - participants → seasonParticipants - participantExpectedValues → seasonParticipantExpectedValues - participantResults → seasonParticipantResults - eventResults.participantId → eventResults.seasonParticipantId Files updated: - 20 sport simulators (NBA, NHL, NFL, MLB, etc.) - probability-updater.ts - standings-sync/index.ts - sports-data-sync.server.ts - server/socket.ts Typecheck errors reduced from 365 to 0. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * migration: rename per-window tables to season_* prefix * fix(tests): update mock query keys after participants table rename Change mock db.query.participants to db.query.seasonParticipants in test files to match the schema rename from commit 66145a9. This fixes "Cannot read properties of undefined (reading 'findFirst'/'findMany')" errors that occurred when production code queries db.query.seasonParticipants but test mocks only defined the old participants key. Files updated: - app/services/simulations/__tests__/world-cup-simulator.test.ts - app/routes/api/__tests__/draft.force-manual-pick.test.ts - app/routes/api/__tests__/draft.force-manual-pick.timer-mode.test.ts - app/routes/api/__tests__/draft.make-pick.timer-mode.test.ts - server/__tests__/timer-autodraft.test.ts - app/models/__tests__/team-score-events.test.ts Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(tests): update remaining mock paths and keys after schema rename * fix(tests): final two mock stragglers after schema rename - draft-pick.test.ts: assertion on db.query.participantQualifyingTotals - process-match-result.test.ts: mock key participants → seasonParticipants Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * chore: add post-phase1a baseline capture (temp, for diff verification) * chore: capture pre-migration baselines * chore: remove post-phase1a capture helper after verification * schema: add canonical tournament & participant tables Adds tournaments, participants (canonical), tournament_results, and participant_surface_elos (canonical). Adds nullable tournament_id to scoring_events and nullable participant_id to season_participants. Phase 1b of canonical tournament layer migration. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(models): add canonical tournament, participant, result, surface-elo models Adds CRUD modules for the canonical tables created in commit 775b905. Each module mirrors existing app/models conventions (database() from ~/database/context, schema from ~/database/schema, mock-based tests). Key implementation notes: - participant.ts exports use "Canonical" prefix (CanonicalParticipant, createCanonicalParticipant, etc.) to avoid collision with existing season-participant.ts exports - All four models include comprehensive unit tests following the audit-log.test.ts pattern - Tests use mocked db responses (no real database access) - Upsert functions use onConflictDoUpdate for appropriate unique constraints Part of Phase 1b of canonical tournament layer migration. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * migration: create canonical tables, add nullable FKs * scripts: add extractTournamentIdentity helper for backfill Pure function that derives canonical (name, year) identity from a scoring_events row, stripping trailing 4-digit years from the name or falling back to eventDate. Used by the Phase 2 backfill to group per-window events into canonical tournaments. * scripts: add backfill orchestrator for canonical layer Populates canonical tournaments, participants, tournament_results, and participant_surface_elos from per-window data for qualifying-points sports. Skips already-linked rows, is idempotent, and supports dry-run mode. Critical invariants enforced by the implementation: - qualifying_points_awarded is never copied to tournament_results - season_participant_qualifying_totals is never touched - conflicting surface-Elo values between windows raise a loud error (recorded in report.errors) rather than overwriting * scripts: add backfill CLI with dry-run default Wires backfill-canonical-layer.ts to a CLI entry point exposed as `npm run backfill:canonical`. Defaults to --dry-run; requires --apply to actually write. Supports --sport=<uuid> to limit to a single sport. Exits 2 if the backfill reports errors (e.g., surface-Elo conflicts). * fix(backfill-cli): wrap runBackfill in DatabaseContext.run The orchestrator uses database() from ~/database/context, which requires AsyncLocalStorage to be populated. Wrap the CLI invocation with DatabaseContext.run(db, ...) using server/db's cached connection pool. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(backfill-cli): exit 0 on success so pg pool doesn't block The cached postgres connection pool keeps the Node event loop open after main() returns. Explicit process.exit(0) on success mirrors the pattern in scripts/capture-baseline.ts. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Chris Parsons <chrisp@extrahop.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-01 20:13:18 -07:00
.select({ id: schema.seasonParticipants.id })
.from(schema.seasonParticipants)
.where(eq(schema.seasonParticipants.sportsSeasonId, sportsSeasonId));
Add NCAA Football CFP simulator (12-team bracket) (#278) Fixes #124 * Add NCAA Football CFP simulator (12-team bracket) Implements a Monte Carlo simulator for the College Football Playoff using the 2024-present 12-team format. Elo/FPI ratings are entered manually via the existing admin Elo Ratings page; championship futures odds can optionally be blended in (60% Elo / 40% odds). - Add CFP_12 bracket template (First Round not scoring, QFs onward score) - Add generateCFP12Bracket() with correct seeding: 5v12, 6v11, 7v10, 8v9 in First Round; seeds 1–4 receive QF byes - Add NCAAFootballSimulator: 50k Monte Carlo sims, seeds teams by blended Elo+odds strength, tracks champion/finalist/SF/QF placement tiers - Register ncaa_football_bracket simulator type in registry and schema enum - Add migration 0071: ALTER TYPE simulator_type ADD VALUE 'ncaa_football_bracket' - Add tests: 30 tests covering bracket template structure and simulator probability distributions, seeding, edge cases, futures blending Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix lint errors in NCAA Football CFP simulator Replace non-null assertions with optional chaining, change let to const, use toSorted() instead of sort(), and add a bump() helper to avoid repeated map lookups with non-null assertions in simulateBracket. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix TS2345 in NCAA football simulator test Add ?? 0 fallback so optional-chained probFirst is number, not number | undefined. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 09:48:32 -04:00
if (participants.length < BRACKET_SIZE) {
throw new Error(
`CFP simulator requires at least ${BRACKET_SIZE} participants, ` +
`found ${participants.length}. Add all contender teams to the sports season.`
);
}
Unify simulator strength on a single blended Elo Replace the two parallel odds/Elo mechanisms with one model: every strength source (raw Elo, projected wins, projected table points, futures odds) converts to an Elo, those blend by weight into a single Elo, and that one Elo feeds every simulator. This supersedes the earlier source-priority + per-match probability blend approach. Resolution (app/services/simulations/input-policy.ts): - SimulatorInputPolicy gains baseEloPriority (order among the substitutable base sources: raw Elo / projected wins / projected table points) and oddsWeight (0–1). resolveSourceElos/resolveRatings now compute baseElo, derive an odds Elo via convertFuturesToElo, and blend: 0 = base only, 1 = futures override, between = weighted blend (method "blend"). - Drops the odds-inclusive sourceEloPriority and the prefersFuturesOdds helper. Simulators consume the single resolved Elo: - UCL, World Cup, NCAA Football, MLB drop their separate normalized-odds signal, convertFuturesToElo calls, and per-match probability blend; they read the resolved sourceElo (preserving each sim's hardcoded fallback Elo table). The optional SimulationContext oddsWeight plumbing (types.ts/runner.ts) is removed. - UCL is routed through the central blend (requiredInputs sourceElo, derivable from sourceOdds) so any entered Elo and futures blend uniformly. - College hockey already blends odds into Elo internally (and uses NPI rank the central resolver can't), so its central oddsWeight is set to 0 to avoid double-counting; the simulator is unchanged. Manifest: per-profile oddsWeight defaults (World Cup/UCL/MLB 0.3, NCAA FB 0.4, college hockey 0; global default 0.3). UI: the Input Policy card exposes one "Futures vs. Elo — Odds Blend Weight" control; the /admin/simulators inventory badge shows the effective blend ("Elo only" / "NN% blend" / "overrides Elo") with the odds participant count. Tests: input-policy blend math (0/0.5/1) for Elo and ratings, baseEloPriority and oddsWeight parsing/clamping, manifest per-profile weights; obsolete source-priority and oddsWeight-context tests removed/replaced. Note: this intentionally shifts the calibrated EV outputs of the four sims that previously blended at the probability level (accepted in design discussion). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-26 01:39:12 +00:00
// 2. Load the resolved single Elo (the input policy already blended any
// raw Elo/FPI and futures odds into sourceElo before the run).
Add NCAA Football CFP simulator (12-team bracket) (#278) Fixes #124 * Add NCAA Football CFP simulator (12-team bracket) Implements a Monte Carlo simulator for the College Football Playoff using the 2024-present 12-team format. Elo/FPI ratings are entered manually via the existing admin Elo Ratings page; championship futures odds can optionally be blended in (60% Elo / 40% odds). - Add CFP_12 bracket template (First Round not scoring, QFs onward score) - Add generateCFP12Bracket() with correct seeding: 5v12, 6v11, 7v10, 8v9 in First Round; seeds 1–4 receive QF byes - Add NCAAFootballSimulator: 50k Monte Carlo sims, seeds teams by blended Elo+odds strength, tracks champion/finalist/SF/QF placement tiers - Register ncaa_football_bracket simulator type in registry and schema enum - Add migration 0071: ALTER TYPE simulator_type ADD VALUE 'ncaa_football_bracket' - Add tests: 30 tests covering bracket template structure and simulator probability distributions, seeding, edge cases, futures blending Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix lint errors in NCAA Football CFP simulator Replace non-null assertions with optional chaining, change let to const, use toSorted() instead of sort(), and add a bump() helper to avoid repeated map lookups with non-null assertions in simulateBracket. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix TS2345 in NCAA football simulator test Add ?? 0 fallback so optional-chained probFirst is number, not number | undefined. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 09:48:32 -04:00
const evRows = await db
.select({
Canonical tournament layer: schema + backfill (1/2) (#365) * refactor(schema): rename per-window tables to season_* prefix Renames participants, participant_expected_values, participant_qualifying_totals, participant_results, participant_surface_elos to season_* prefixed names. Renames event_results.participant_id to season_participant_id. Phase 1a of canonical tournament layer migration. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor: rename participant.ts model file to season-participant.ts Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(models): update model layer to use renamed schema exports Updated all model files to use the renamed schema exports from Task 1: - participants → seasonParticipants - participantExpectedValues → seasonParticipantExpectedValues - participantQualifyingTotals → seasonParticipantQualifyingTotals - participantResults → seasonParticipantResults - participantSurfaceElos → seasonParticipantSurfaceElos - eventResults.participantId → eventResults.seasonParticipantId - db.query relation accessors updated - Relation field .participant → .seasonParticipant where applicable - Import paths updated: ./participant → ./season-participant Files updated (14 model files + 3 test files): - draft-pick.ts - draft-utils.ts - event-result.ts - group-stage-match.ts - participant-result.ts - qualifying-points.ts - scoring-calculator.ts - scoring-event.ts - sports-season.ts - surface-elo.ts - team-score-events.ts - cs2-major-stage.ts - golf-skills.ts - participant-expected-value.ts - __tests__/sports-season.clone.test.ts - __tests__/auto-pick.test.ts - __tests__/executeAutoPick.timer.test.ts Typecheck errors decreased: 779 → 499 (280 fewer) All model file errors related to renamed schemas resolved. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(routes): update route layer to use renamed schema exports - Update model import from ~/models/participant to ~/models/season-participant - Rename schema.participants to schema.seasonParticipants - Rename schema.participantResults to schema.seasonParticipantResults - Rename db.query.participants to db.query.seasonParticipants - Update 9 route files and 1 test file Affected files: - admin.sports-seasons.$id.events.$eventId.bracket.server.ts - admin.sports-seasons.$id.participants.tsx - api/draft.force-manual-pick.ts - api/draft.make-pick.ts - api/draft.replace-pick.ts - api/seasons.$seasonId.draft.ts - leagues/$leagueId.draft-board.$seasonId.tsx - leagues/$leagueId.sports-seasons.$sportsSeasonId.server.ts - admin/__tests__/sports-seasons-participants.test.ts Error count reduced from 499 to 453 (46 errors fixed). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(routes): update route files for schema rename Update route imports from ~/models/participant to ~/models/season-participant and fix references to .participant/.participantId on event results to use .seasonParticipant/.seasonParticipantId after schema rename. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(services): update simulators and services for renamed schema Update all simulators, services, and server files to use renamed schema tables: - participants → seasonParticipants - participantExpectedValues → seasonParticipantExpectedValues - participantResults → seasonParticipantResults - eventResults.participantId → eventResults.seasonParticipantId Files updated: - 20 sport simulators (NBA, NHL, NFL, MLB, etc.) - probability-updater.ts - standings-sync/index.ts - sports-data-sync.server.ts - server/socket.ts Typecheck errors reduced from 365 to 0. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * migration: rename per-window tables to season_* prefix * fix(tests): update mock query keys after participants table rename Change mock db.query.participants to db.query.seasonParticipants in test files to match the schema rename from commit 66145a9. This fixes "Cannot read properties of undefined (reading 'findFirst'/'findMany')" errors that occurred when production code queries db.query.seasonParticipants but test mocks only defined the old participants key. Files updated: - app/services/simulations/__tests__/world-cup-simulator.test.ts - app/routes/api/__tests__/draft.force-manual-pick.test.ts - app/routes/api/__tests__/draft.force-manual-pick.timer-mode.test.ts - app/routes/api/__tests__/draft.make-pick.timer-mode.test.ts - server/__tests__/timer-autodraft.test.ts - app/models/__tests__/team-score-events.test.ts Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(tests): update remaining mock paths and keys after schema rename * fix(tests): final two mock stragglers after schema rename - draft-pick.test.ts: assertion on db.query.participantQualifyingTotals - process-match-result.test.ts: mock key participants → seasonParticipants Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * chore: add post-phase1a baseline capture (temp, for diff verification) * chore: capture pre-migration baselines * chore: remove post-phase1a capture helper after verification * schema: add canonical tournament & participant tables Adds tournaments, participants (canonical), tournament_results, and participant_surface_elos (canonical). Adds nullable tournament_id to scoring_events and nullable participant_id to season_participants. Phase 1b of canonical tournament layer migration. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(models): add canonical tournament, participant, result, surface-elo models Adds CRUD modules for the canonical tables created in commit 775b905. Each module mirrors existing app/models conventions (database() from ~/database/context, schema from ~/database/schema, mock-based tests). Key implementation notes: - participant.ts exports use "Canonical" prefix (CanonicalParticipant, createCanonicalParticipant, etc.) to avoid collision with existing season-participant.ts exports - All four models include comprehensive unit tests following the audit-log.test.ts pattern - Tests use mocked db responses (no real database access) - Upsert functions use onConflictDoUpdate for appropriate unique constraints Part of Phase 1b of canonical tournament layer migration. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * migration: create canonical tables, add nullable FKs * scripts: add extractTournamentIdentity helper for backfill Pure function that derives canonical (name, year) identity from a scoring_events row, stripping trailing 4-digit years from the name or falling back to eventDate. Used by the Phase 2 backfill to group per-window events into canonical tournaments. * scripts: add backfill orchestrator for canonical layer Populates canonical tournaments, participants, tournament_results, and participant_surface_elos from per-window data for qualifying-points sports. Skips already-linked rows, is idempotent, and supports dry-run mode. Critical invariants enforced by the implementation: - qualifying_points_awarded is never copied to tournament_results - season_participant_qualifying_totals is never touched - conflicting surface-Elo values between windows raise a loud error (recorded in report.errors) rather than overwriting * scripts: add backfill CLI with dry-run default Wires backfill-canonical-layer.ts to a CLI entry point exposed as `npm run backfill:canonical`. Defaults to --dry-run; requires --apply to actually write. Supports --sport=<uuid> to limit to a single sport. Exits 2 if the backfill reports errors (e.g., surface-Elo conflicts). * fix(backfill-cli): wrap runBackfill in DatabaseContext.run The orchestrator uses database() from ~/database/context, which requires AsyncLocalStorage to be populated. Wrap the CLI invocation with DatabaseContext.run(db, ...) using server/db's cached connection pool. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(backfill-cli): exit 0 on success so pg pool doesn't block The cached postgres connection pool keeps the Node event loop open after main() returns. Explicit process.exit(0) on success mirrors the pattern in scripts/capture-baseline.ts. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Chris Parsons <chrisp@extrahop.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-01 20:13:18 -07:00
participantId: schema.seasonParticipantExpectedValues.participantId,
sourceElo: schema.seasonParticipantExpectedValues.sourceElo,
Add NCAA Football CFP simulator (12-team bracket) (#278) Fixes #124 * Add NCAA Football CFP simulator (12-team bracket) Implements a Monte Carlo simulator for the College Football Playoff using the 2024-present 12-team format. Elo/FPI ratings are entered manually via the existing admin Elo Ratings page; championship futures odds can optionally be blended in (60% Elo / 40% odds). - Add CFP_12 bracket template (First Round not scoring, QFs onward score) - Add generateCFP12Bracket() with correct seeding: 5v12, 6v11, 7v10, 8v9 in First Round; seeds 1–4 receive QF byes - Add NCAAFootballSimulator: 50k Monte Carlo sims, seeds teams by blended Elo+odds strength, tracks champion/finalist/SF/QF placement tiers - Register ncaa_football_bracket simulator type in registry and schema enum - Add migration 0071: ALTER TYPE simulator_type ADD VALUE 'ncaa_football_bracket' - Add tests: 30 tests covering bracket template structure and simulator probability distributions, seeding, edge cases, futures blending Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix lint errors in NCAA Football CFP simulator Replace non-null assertions with optional chaining, change let to const, use toSorted() instead of sort(), and add a bump() helper to avoid repeated map lookups with non-null assertions in simulateBracket. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix TS2345 in NCAA football simulator test Add ?? 0 fallback so optional-chained probFirst is number, not number | undefined. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 09:48:32 -04:00
})
Canonical tournament layer: schema + backfill (1/2) (#365) * refactor(schema): rename per-window tables to season_* prefix Renames participants, participant_expected_values, participant_qualifying_totals, participant_results, participant_surface_elos to season_* prefixed names. Renames event_results.participant_id to season_participant_id. Phase 1a of canonical tournament layer migration. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor: rename participant.ts model file to season-participant.ts Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(models): update model layer to use renamed schema exports Updated all model files to use the renamed schema exports from Task 1: - participants → seasonParticipants - participantExpectedValues → seasonParticipantExpectedValues - participantQualifyingTotals → seasonParticipantQualifyingTotals - participantResults → seasonParticipantResults - participantSurfaceElos → seasonParticipantSurfaceElos - eventResults.participantId → eventResults.seasonParticipantId - db.query relation accessors updated - Relation field .participant → .seasonParticipant where applicable - Import paths updated: ./participant → ./season-participant Files updated (14 model files + 3 test files): - draft-pick.ts - draft-utils.ts - event-result.ts - group-stage-match.ts - participant-result.ts - qualifying-points.ts - scoring-calculator.ts - scoring-event.ts - sports-season.ts - surface-elo.ts - team-score-events.ts - cs2-major-stage.ts - golf-skills.ts - participant-expected-value.ts - __tests__/sports-season.clone.test.ts - __tests__/auto-pick.test.ts - __tests__/executeAutoPick.timer.test.ts Typecheck errors decreased: 779 → 499 (280 fewer) All model file errors related to renamed schemas resolved. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(routes): update route layer to use renamed schema exports - Update model import from ~/models/participant to ~/models/season-participant - Rename schema.participants to schema.seasonParticipants - Rename schema.participantResults to schema.seasonParticipantResults - Rename db.query.participants to db.query.seasonParticipants - Update 9 route files and 1 test file Affected files: - admin.sports-seasons.$id.events.$eventId.bracket.server.ts - admin.sports-seasons.$id.participants.tsx - api/draft.force-manual-pick.ts - api/draft.make-pick.ts - api/draft.replace-pick.ts - api/seasons.$seasonId.draft.ts - leagues/$leagueId.draft-board.$seasonId.tsx - leagues/$leagueId.sports-seasons.$sportsSeasonId.server.ts - admin/__tests__/sports-seasons-participants.test.ts Error count reduced from 499 to 453 (46 errors fixed). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(routes): update route files for schema rename Update route imports from ~/models/participant to ~/models/season-participant and fix references to .participant/.participantId on event results to use .seasonParticipant/.seasonParticipantId after schema rename. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(services): update simulators and services for renamed schema Update all simulators, services, and server files to use renamed schema tables: - participants → seasonParticipants - participantExpectedValues → seasonParticipantExpectedValues - participantResults → seasonParticipantResults - eventResults.participantId → eventResults.seasonParticipantId Files updated: - 20 sport simulators (NBA, NHL, NFL, MLB, etc.) - probability-updater.ts - standings-sync/index.ts - sports-data-sync.server.ts - server/socket.ts Typecheck errors reduced from 365 to 0. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * migration: rename per-window tables to season_* prefix * fix(tests): update mock query keys after participants table rename Change mock db.query.participants to db.query.seasonParticipants in test files to match the schema rename from commit 66145a9. This fixes "Cannot read properties of undefined (reading 'findFirst'/'findMany')" errors that occurred when production code queries db.query.seasonParticipants but test mocks only defined the old participants key. Files updated: - app/services/simulations/__tests__/world-cup-simulator.test.ts - app/routes/api/__tests__/draft.force-manual-pick.test.ts - app/routes/api/__tests__/draft.force-manual-pick.timer-mode.test.ts - app/routes/api/__tests__/draft.make-pick.timer-mode.test.ts - server/__tests__/timer-autodraft.test.ts - app/models/__tests__/team-score-events.test.ts Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(tests): update remaining mock paths and keys after schema rename * fix(tests): final two mock stragglers after schema rename - draft-pick.test.ts: assertion on db.query.participantQualifyingTotals - process-match-result.test.ts: mock key participants → seasonParticipants Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * chore: add post-phase1a baseline capture (temp, for diff verification) * chore: capture pre-migration baselines * chore: remove post-phase1a capture helper after verification * schema: add canonical tournament & participant tables Adds tournaments, participants (canonical), tournament_results, and participant_surface_elos (canonical). Adds nullable tournament_id to scoring_events and nullable participant_id to season_participants. Phase 1b of canonical tournament layer migration. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(models): add canonical tournament, participant, result, surface-elo models Adds CRUD modules for the canonical tables created in commit 775b905. Each module mirrors existing app/models conventions (database() from ~/database/context, schema from ~/database/schema, mock-based tests). Key implementation notes: - participant.ts exports use "Canonical" prefix (CanonicalParticipant, createCanonicalParticipant, etc.) to avoid collision with existing season-participant.ts exports - All four models include comprehensive unit tests following the audit-log.test.ts pattern - Tests use mocked db responses (no real database access) - Upsert functions use onConflictDoUpdate for appropriate unique constraints Part of Phase 1b of canonical tournament layer migration. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * migration: create canonical tables, add nullable FKs * scripts: add extractTournamentIdentity helper for backfill Pure function that derives canonical (name, year) identity from a scoring_events row, stripping trailing 4-digit years from the name or falling back to eventDate. Used by the Phase 2 backfill to group per-window events into canonical tournaments. * scripts: add backfill orchestrator for canonical layer Populates canonical tournaments, participants, tournament_results, and participant_surface_elos from per-window data for qualifying-points sports. Skips already-linked rows, is idempotent, and supports dry-run mode. Critical invariants enforced by the implementation: - qualifying_points_awarded is never copied to tournament_results - season_participant_qualifying_totals is never touched - conflicting surface-Elo values between windows raise a loud error (recorded in report.errors) rather than overwriting * scripts: add backfill CLI with dry-run default Wires backfill-canonical-layer.ts to a CLI entry point exposed as `npm run backfill:canonical`. Defaults to --dry-run; requires --apply to actually write. Supports --sport=<uuid> to limit to a single sport. Exits 2 if the backfill reports errors (e.g., surface-Elo conflicts). * fix(backfill-cli): wrap runBackfill in DatabaseContext.run The orchestrator uses database() from ~/database/context, which requires AsyncLocalStorage to be populated. Wrap the CLI invocation with DatabaseContext.run(db, ...) using server/db's cached connection pool. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(backfill-cli): exit 0 on success so pg pool doesn't block The cached postgres connection pool keeps the Node event loop open after main() returns. Explicit process.exit(0) on success mirrors the pattern in scripts/capture-baseline.ts. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Chris Parsons <chrisp@extrahop.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-01 20:13:18 -07:00
.from(schema.seasonParticipantExpectedValues)
.where(eq(schema.seasonParticipantExpectedValues.sportsSeasonId, sportsSeasonId));
Add NCAA Football CFP simulator (12-team bracket) (#278) Fixes #124 * Add NCAA Football CFP simulator (12-team bracket) Implements a Monte Carlo simulator for the College Football Playoff using the 2024-present 12-team format. Elo/FPI ratings are entered manually via the existing admin Elo Ratings page; championship futures odds can optionally be blended in (60% Elo / 40% odds). - Add CFP_12 bracket template (First Round not scoring, QFs onward score) - Add generateCFP12Bracket() with correct seeding: 5v12, 6v11, 7v10, 8v9 in First Round; seeds 1–4 receive QF byes - Add NCAAFootballSimulator: 50k Monte Carlo sims, seeds teams by blended Elo+odds strength, tracks champion/finalist/SF/QF placement tiers - Register ncaa_football_bracket simulator type in registry and schema enum - Add migration 0071: ALTER TYPE simulator_type ADD VALUE 'ncaa_football_bracket' - Add tests: 30 tests covering bracket template structure and simulator probability distributions, seeding, edge cases, futures blending Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix lint errors in NCAA Football CFP simulator Replace non-null assertions with optional chaining, change let to const, use toSorted() instead of sort(), and add a bump() helper to avoid repeated map lookups with non-null assertions in simulateBracket. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix TS2345 in NCAA football simulator test Add ?? 0 fallback so optional-chained probFirst is number, not number | undefined. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 09:48:32 -04:00
const eloFromDb = new Map<string, number>();
for (const row of evRows) {
if (row.sourceElo !== null) {
eloFromDb.set(row.participantId, row.sourceElo);
}
}
Unify simulator strength on a single blended Elo Replace the two parallel odds/Elo mechanisms with one model: every strength source (raw Elo, projected wins, projected table points, futures odds) converts to an Elo, those blend by weight into a single Elo, and that one Elo feeds every simulator. This supersedes the earlier source-priority + per-match probability blend approach. Resolution (app/services/simulations/input-policy.ts): - SimulatorInputPolicy gains baseEloPriority (order among the substitutable base sources: raw Elo / projected wins / projected table points) and oddsWeight (0–1). resolveSourceElos/resolveRatings now compute baseElo, derive an odds Elo via convertFuturesToElo, and blend: 0 = base only, 1 = futures override, between = weighted blend (method "blend"). - Drops the odds-inclusive sourceEloPriority and the prefersFuturesOdds helper. Simulators consume the single resolved Elo: - UCL, World Cup, NCAA Football, MLB drop their separate normalized-odds signal, convertFuturesToElo calls, and per-match probability blend; they read the resolved sourceElo (preserving each sim's hardcoded fallback Elo table). The optional SimulationContext oddsWeight plumbing (types.ts/runner.ts) is removed. - UCL is routed through the central blend (requiredInputs sourceElo, derivable from sourceOdds) so any entered Elo and futures blend uniformly. - College hockey already blends odds into Elo internally (and uses NPI rank the central resolver can't), so its central oddsWeight is set to 0 to avoid double-counting; the simulator is unchanged. Manifest: per-profile oddsWeight defaults (World Cup/UCL/MLB 0.3, NCAA FB 0.4, college hockey 0; global default 0.3). UI: the Input Policy card exposes one "Futures vs. Elo — Odds Blend Weight" control; the /admin/simulators inventory badge shows the effective blend ("Elo only" / "NN% blend" / "overrides Elo") with the odds participant count. Tests: input-policy blend math (0/0.5/1) for Elo and ratings, baseEloPriority and oddsWeight parsing/clamping, manifest per-profile weights; obsolete source-priority and oddsWeight-context tests removed/replaced. Note: this intentionally shifts the calibrated EV outputs of the four sims that previously blended at the probability level (accepted in design discussion). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-26 01:39:12 +00:00
// 3. Build team list; field-selection weight is a softmax on the resolved Elo.
Add NCAA Football CFP simulator (12-team bracket) (#278) Fixes #124 * Add NCAA Football CFP simulator (12-team bracket) Implements a Monte Carlo simulator for the College Football Playoff using the 2024-present 12-team format. Elo/FPI ratings are entered manually via the existing admin Elo Ratings page; championship futures odds can optionally be blended in (60% Elo / 40% odds). - Add CFP_12 bracket template (First Round not scoring, QFs onward score) - Add generateCFP12Bracket() with correct seeding: 5v12, 6v11, 7v10, 8v9 in First Round; seeds 1–4 receive QF byes - Add NCAAFootballSimulator: 50k Monte Carlo sims, seeds teams by blended Elo+odds strength, tracks champion/finalist/SF/QF placement tiers - Register ncaa_football_bracket simulator type in registry and schema enum - Add migration 0071: ALTER TYPE simulator_type ADD VALUE 'ncaa_football_bracket' - Add tests: 30 tests covering bracket template structure and simulator probability distributions, seeding, edge cases, futures blending Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix lint errors in NCAA Football CFP simulator Replace non-null assertions with optional chaining, change let to const, use toSorted() instead of sort(), and add a bump() helper to avoid repeated map lookups with non-null assertions in simulateBracket. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix TS2345 in NCAA football simulator test Add ?? 0 fallback so optional-chained probFirst is number, not number | undefined. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 09:48:32 -04:00
const allTeams: Team[] = participants.map((p) => ({
participantId: p.id,
elo: eloFromDb.get(p.id) ?? 1500,
selectionWeight: 0, // computed below
}));
Unify simulator strength on a single blended Elo Replace the two parallel odds/Elo mechanisms with one model: every strength source (raw Elo, projected wins, projected table points, futures odds) converts to an Elo, those blend by weight into a single Elo, and that one Elo feeds every simulator. This supersedes the earlier source-priority + per-match probability blend approach. Resolution (app/services/simulations/input-policy.ts): - SimulatorInputPolicy gains baseEloPriority (order among the substitutable base sources: raw Elo / projected wins / projected table points) and oddsWeight (0–1). resolveSourceElos/resolveRatings now compute baseElo, derive an odds Elo via convertFuturesToElo, and blend: 0 = base only, 1 = futures override, between = weighted blend (method "blend"). - Drops the odds-inclusive sourceEloPriority and the prefersFuturesOdds helper. Simulators consume the single resolved Elo: - UCL, World Cup, NCAA Football, MLB drop their separate normalized-odds signal, convertFuturesToElo calls, and per-match probability blend; they read the resolved sourceElo (preserving each sim's hardcoded fallback Elo table). The optional SimulationContext oddsWeight plumbing (types.ts/runner.ts) is removed. - UCL is routed through the central blend (requiredInputs sourceElo, derivable from sourceOdds) so any entered Elo and futures blend uniformly. - College hockey already blends odds into Elo internally (and uses NPI rank the central resolver can't), so its central oddsWeight is set to 0 to avoid double-counting; the simulator is unchanged. Manifest: per-profile oddsWeight defaults (World Cup/UCL/MLB 0.3, NCAA FB 0.4, college hockey 0; global default 0.3). UI: the Input Policy card exposes one "Futures vs. Elo — Odds Blend Weight" control; the /admin/simulators inventory badge shows the effective blend ("Elo only" / "NN% blend" / "overrides Elo") with the odds participant count. Tests: input-policy blend math (0/0.5/1) for Elo and ratings, baseEloPriority and oddsWeight parsing/clamping, manifest per-profile weights; obsolete source-priority and oddsWeight-context tests removed/replaced. Note: this intentionally shifts the calibrated EV outputs of the four sims that previously blended at the probability level (accepted in design discussion). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-26 01:39:12 +00:00
const maxElo = Math.max(...allTeams.map((t) => t.elo));
const expWeights = allTeams.map((t) => Math.exp((t.elo - maxElo) / SELECTION_TEMP));
const expSum = expWeights.reduce((a, b) => a + b, 0);
for (let i = 0; i < allTeams.length; i++) {
allTeams[i].selectionWeight = expSum > 0 ? expWeights[i] / expSum : 1 / allTeams.length;
Add NCAA Football CFP simulator (12-team bracket) (#278) Fixes #124 * Add NCAA Football CFP simulator (12-team bracket) Implements a Monte Carlo simulator for the College Football Playoff using the 2024-present 12-team format. Elo/FPI ratings are entered manually via the existing admin Elo Ratings page; championship futures odds can optionally be blended in (60% Elo / 40% odds). - Add CFP_12 bracket template (First Round not scoring, QFs onward score) - Add generateCFP12Bracket() with correct seeding: 5v12, 6v11, 7v10, 8v9 in First Round; seeds 1–4 receive QF byes - Add NCAAFootballSimulator: 50k Monte Carlo sims, seeds teams by blended Elo+odds strength, tracks champion/finalist/SF/QF placement tiers - Register ncaa_football_bracket simulator type in registry and schema enum - Add migration 0071: ALTER TYPE simulator_type ADD VALUE 'ncaa_football_bracket' - Add tests: 30 tests covering bracket template structure and simulator probability distributions, seeding, edge cases, futures blending Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix lint errors in NCAA Football CFP simulator Replace non-null assertions with optional chaining, change let to const, use toSorted() instead of sort(), and add a bump() helper to avoid repeated map lookups with non-null assertions in simulateBracket. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix TS2345 in NCAA football simulator test Add ?? 0 fallback so optional-chained probFirst is number, not number | undefined. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 09:48:32 -04:00
}
const preBracketMode = participants.length > BRACKET_SIZE;
// In post-bracket mode (exactly 12), sort once and reuse the same field every sim.
const deterministicField = preBracketMode
? null
: [...allTeams].toSorted((a, b) => b.elo - a.elo);
// 5. Initialise placement count accumulators for all participants.
const allParticipantIds = participants.map((p) => p.id);
const counts = new Map<string, PlacementCounts>(
allParticipantIds.map((id) => [id, { champion: 0, finalist: 0, sfLoser: 0, qfLoser: 0 }])
);
// 6. Run Monte Carlo simulations.
for (let s = 0; s < NUM_SIMULATIONS; s++) {
const field = preBracketMode
? sampleBracketField(allTeams, BRACKET_SIZE)
: (deterministicField ?? []);
Unify simulator strength on a single blended Elo Replace the two parallel odds/Elo mechanisms with one model: every strength source (raw Elo, projected wins, projected table points, futures odds) converts to an Elo, those blend by weight into a single Elo, and that one Elo feeds every simulator. This supersedes the earlier source-priority + per-match probability blend approach. Resolution (app/services/simulations/input-policy.ts): - SimulatorInputPolicy gains baseEloPriority (order among the substitutable base sources: raw Elo / projected wins / projected table points) and oddsWeight (0–1). resolveSourceElos/resolveRatings now compute baseElo, derive an odds Elo via convertFuturesToElo, and blend: 0 = base only, 1 = futures override, between = weighted blend (method "blend"). - Drops the odds-inclusive sourceEloPriority and the prefersFuturesOdds helper. Simulators consume the single resolved Elo: - UCL, World Cup, NCAA Football, MLB drop their separate normalized-odds signal, convertFuturesToElo calls, and per-match probability blend; they read the resolved sourceElo (preserving each sim's hardcoded fallback Elo table). The optional SimulationContext oddsWeight plumbing (types.ts/runner.ts) is removed. - UCL is routed through the central blend (requiredInputs sourceElo, derivable from sourceOdds) so any entered Elo and futures blend uniformly. - College hockey already blends odds into Elo internally (and uses NPI rank the central resolver can't), so its central oddsWeight is set to 0 to avoid double-counting; the simulator is unchanged. Manifest: per-profile oddsWeight defaults (World Cup/UCL/MLB 0.3, NCAA FB 0.4, college hockey 0; global default 0.3). UI: the Input Policy card exposes one "Futures vs. Elo — Odds Blend Weight" control; the /admin/simulators inventory badge shows the effective blend ("Elo only" / "NN% blend" / "overrides Elo") with the odds participant count. Tests: input-policy blend math (0/0.5/1) for Elo and ratings, baseEloPriority and oddsWeight parsing/clamping, manifest per-profile weights; obsolete source-priority and oddsWeight-context tests removed/replaced. Note: this intentionally shifts the calibrated EV outputs of the four sims that previously blended at the probability level (accepted in design discussion). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
2026-06-26 01:39:12 +00:00
simulateBracket(field, counts);
Add NCAA Football CFP simulator (12-team bracket) (#278) Fixes #124 * Add NCAA Football CFP simulator (12-team bracket) Implements a Monte Carlo simulator for the College Football Playoff using the 2024-present 12-team format. Elo/FPI ratings are entered manually via the existing admin Elo Ratings page; championship futures odds can optionally be blended in (60% Elo / 40% odds). - Add CFP_12 bracket template (First Round not scoring, QFs onward score) - Add generateCFP12Bracket() with correct seeding: 5v12, 6v11, 7v10, 8v9 in First Round; seeds 1–4 receive QF byes - Add NCAAFootballSimulator: 50k Monte Carlo sims, seeds teams by blended Elo+odds strength, tracks champion/finalist/SF/QF placement tiers - Register ncaa_football_bracket simulator type in registry and schema enum - Add migration 0071: ALTER TYPE simulator_type ADD VALUE 'ncaa_football_bracket' - Add tests: 30 tests covering bracket template structure and simulator probability distributions, seeding, edge cases, futures blending Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix lint errors in NCAA Football CFP simulator Replace non-null assertions with optional chaining, change let to const, use toSorted() instead of sort(), and add a bump() helper to avoid repeated map lookups with non-null assertions in simulateBracket. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix TS2345 in NCAA football simulator test Add ?? 0 fallback so optional-chained probFirst is number, not number | undefined. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 09:48:32 -04:00
}
// 7. Convert counts to probability distributions.
// SF losers: 2 per sim → each team's share = sfLoser / (2 * N).
// QF losers: 4 per sim → each team's share = qfLoser / (4 * N).
const sfDivisor = 2 * NUM_SIMULATIONS;
const qfDivisor = 4 * NUM_SIMULATIONS;
return allParticipantIds.map((id) => {
const c = counts.get(id) ?? { champion: 0, finalist: 0, sfLoser: 0, qfLoser: 0 };
const sfProb = c.sfLoser / sfDivisor;
const qfProb = c.qfLoser / qfDivisor;
return {
participantId: id,
probabilities: {
probFirst: c.champion / NUM_SIMULATIONS,
probSecond: c.finalist / NUM_SIMULATIONS,
probThird: sfProb,
probFourth: sfProb,
probFifth: qfProb,
probSixth: qfProb,
probSeventh: qfProb,
probEighth: qfProb,
},
source: "cfp_monte_carlo",
};
});
}
}