Address code-review cleanup notes:
- Extract the base/odds blend ladder (duplicated between resolveSourceElos and
resolveRatings) into one generic `blendBaseAndOdds` helper.
- Move the futures blend weight to a single home: oddsWeight now lives only under
inputPolicy. Manifest per-profile defaults moved from top-level config into
inputPolicy.oddsWeight, and getSimulatorInputPolicy no longer reads a legacy
top-level config.oddsWeight. Drops the now-dead mlb rDiffWeight config knob.
- Clarify the college_hockey oddsWeight 0 rationale (it disables the central
blend so its internal odds blend isn't double-counted, while still letting odds
resolve an Elo when they're the only source — a separate centralBlend flag
would wrongly drop that sole-source path and break readiness).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
Code review caught a regression in the single-Elo blend: resolveSourceElos /
resolveRatings clamped every resolved value to [eloMin, eloMax] /
[ratingMin, ratingMax], including a directly entered base Elo. The original
resolver only clamped derived values (projections, odds, fallbacks) and passed
direct inputs through untouched. With the default 1900 Elo ceiling this
truncated manual Elos for sports that enter higher ratings (e.g. snooker ~2450),
collapsing their spread.
Clamp only odds-derived / blended / fallback values now; direct Elo and rating
inputs pass through as entered. Adds a regression test, and fixes two comments
that still referenced the removed `sourceEloPriority` field (now `oddsWeight`).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QhNeB7gN6VKene7sdbBVQT
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