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
Make the futures-vs-Elo relationship an explicit, configurable rule and fix
futures odds failing to override stored Elo.
Root causes addressed:
- resolveSourceElos/resolveRatings used a hardcoded precedence (direct Elo ->
projectedWins -> projectedTablePoints -> odds). The prior fix nulled sourceElo
on futures entry but not projections, which still outranked odds.
- The override relied on a destructive null-on-save hack that also wiped
manually entered Elo.
- Blended simulators buried their Elo/odds weight in module constants.
- Futures odds were not surfaced on the /admin/simulators inventory.
Changes:
- Add a configurable source policy: sourceEloPriority (ordered) and oddsWeight,
parsed/clamped in getSimulatorInputPolicy. resolveSourceElos/resolveRatings now
resolve each participant by the configured priority instead of a fixed order.
Futures-centric simulators (ncaam, ncaaw, world_cup, ncaa_football,
college_hockey) default to a futures-override priority.
- Drop the destructive nulling in batchSaveFuturesOddsForSimulator and
batchSaveSourceOdds; override is now governed by policy, preserving stored Elo.
- Thread an optional SimulationContext (oddsWeight) through the Simulator
interface so blended sims (UCL, World Cup, NCAA FB, MLB) read the blend weight
from the season policy; defaults preserve prior calibration when no context is
passed.
- Add a "Futures vs. Elo" strategy control and Odds Blend Weight input to the
Simulator Setup input-policy card, persisted via save-input-policy.
- Surface futures on /admin/simulators: a source badge and a Futures Odds quick
link; extend listSportsSeasonSimulatorSummaries with odds source info.
- Tests: configurable priority override (Elo/projections/rating), oddsWeight
parsing/clamping, prefersFuturesOdds, manifest defaults, an NCAA Football
context-blend behavioral test, and an updated non-destructive save test.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
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>