When an admin entered futures (preseason) odds for a season that already
had Elo ratings stored, the simulator kept using the old Elo and silently
ignored the new odds. This affected any Elo-based simulator (e.g. NHL).
Root cause: resolveSourceElos() ranks a direct sourceElo above the
sourceOdds -> convertFuturesToElo branch, but batchSaveFuturesOddsForSimulator()
only cleared the bracket-seeding `rating`/`ratingMethod` — never the stale
`sourceElo`/`sourceEloMethod`. A manually entered Elo (method "direct") is not
treated as generated, so it survived and short-circuited the resolver.
Fix:
- batchSaveFuturesOddsForSimulator now also nulls sourceElo and strips
sourceEloMethod (both the pre-update and upsert-conflict paths), so the
existing futures -> Elo conversion drives the run.
- resolveSourceElos' sourceOdds branch now guards for >= 2 participants
(mirroring resolveRatings), so a lone-odds season falls through to the
configured missing-Elo strategy instead of getting a flat ~1500.
- batchSaveSourceOdds clears the legacy EV sourceElo and marks source as
futures_odds so the elo-ratings page won't resurrect a stale rating.
Adds unit coverage for odds-derived Elo, the single-participant guard, the
post-clear regression, generated-vs-direct sourceElo suppression, and the
new clearing behavior in batchSaveFuturesOddsForSimulator.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YNfUEd9RzD3zm84oLHBHUH