Commit graph

5 commits

Author SHA1 Message Date
Claude
280a46eb5f
Make MLB projected wins actually drive the simulation
Entering projected wins for an in-progress MLB season did not behave as
expected: the entered numbers came back changed, and the simulation appeared
to ignore them in favour of whatever Elo was already stored. Four separate
defects were involved.

Projections are now stored and shown verbatim. The Elo Ratings page never
kept the number typed into it — the field was a display derived from Elo, so
a pasted 95 rendered as 95.1 the moment it was applied (wins to Elo rounds to
an integer Elo) and drifted again after each run, because a run re-resolves
that Elo through the input policy. The loader now reads back the stored
projection and the paste flow keeps the pasted value as-is; the derived
round-trip survives only as a prefill for seasons that have never had a
projection saved.

A stale Elo no longer silently outranks a projection. baseEloPriority takes
the first available base source, and the simulator page's bulk CSV wrote
projectedWins without stamping metadata.sourceEloMethod, so the
non-destructive upsert left the old Elo in place as a trusted direct value
and it won the race — the projection was stored and then ignored on every
run. The CSV path now stamps the flag like the Elo Ratings page does, the
metadata upsert merges rather than replaces so a flag-only write keeps
unrelated keys, and Base Elo Source is editable per season for the case where
a genuine hand-entered Elo should still lose to projections.

Projected wins now act as a projected final total. The value was baked into a
flat season-long rate (projectedWins / 162) applied to every remaining game,
so a team at 60-50 projected for 95 finished around 90.5 and the projection
was never reached mid-season. seedingWinRateFor spreads the difference over
the games still to play, which is a no-op pre-season where the two rates
coincide; projectedWinsWeight blends it back toward the Elo-implied rate.

Playoff-parity compression is restored for Elo-rated teams. eloToRDif scaled
by RDIF_DIVISOR, making it the exact algebraic inverse of winRateFromRDif, so
any team with an Elo skipped the compression every hardcoded-rdif team gets:
a 95-win projection became RDif +686 and played playoff games at .586 instead
of the documented ~.517. It now scales by SEEDING_RDIF_SCALE, landing at ~+140
alongside the Dodgers' hardcoded +137.

Also fixes the preview table's "missing a required input" marker, which
flagged every projection-configured participant because a generated Elo or
rating is deliberately hidden from getParticipantSimulatorInputs. It now
consults the resolved values, so it agrees with readiness.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CQSEmmojmqmGdJttgzqCWK
2026-08-29 05:31:15 +00:00
a528aecc86 Fix MLB simulator: projected wins drive seeding, perf fix, + surface simulate errors (#65)
All checks were successful
🚀 Deploy / 🧪 Test (push) Successful in 2m34s
🚀 Deploy / ʦ🔍 Typecheck & Lint (push) Successful in 1m19s
🚀 Deploy / 🐳 Build (push) Successful in 1m9s
🚀 Deploy / 🚀 Deploy (push) Successful in 13s
## Summary

- **MLB simulator seeding now responds to projected wins**: replaced hardcoded FanGraphs `p_div`/`p_wc` probability draws with Binomial regular-season simulation. Admin-entered projected wins (via Elo) now drive both playoff _qualification_ odds and in-bracket game win probability, not just the latter.
- **Reads current standings mid-season**: `getRegularSeasonStandings` is called so synced `wins`/`gamesPlayed` feed into each simulation iteration's projected final standings.
- **27× seeding performance improvement**: `sampleBinomial` now uses a Box-Muller normal approximation for `n ≥ 30`, dropping the seeding phase from ~243M to ~3M `Math.random()` calls per 50K-sim run (pre-season, 162 remaining games × 30 teams).
- **Missing-standings warning**: when standings exist for some teams but not all recognized ones (partial sync), a `logger.warn` now surfaces which teams are falling back to 0 wins / 162 remaining instead of silently distorting seeding.
- **Cleanup**: `eloToRDif` calls `rawWinRateFromElo` instead of inlining the same formula.
- **Simulate errors surface inline**: moved simulation action from dedicated route to parent page `intent="simulate"` so errors display inline rather than crashing to an error page.

## Test plan

- [ ] `npm run test:run -- mlb-simulator` — 49 tests pass
- [ ] `npm run typecheck` — clean
- [ ] Trigger a simulation from the admin panel for an MLB season with projected wins entered; confirm teams with higher projected wins show meaningfully higher EVs
- [ ] Confirm that running simulate with no projected wins entered (pre-season defaults) still produces a valid bracket
- [ ] Check logs after simulating a season where some teams lack standings rows — confirm warning appears

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Chris Parsons <chrisparsons1127@gmail.com>
Reviewed-on: #65
2026-06-01 20:50:03 +00:00
Chris Parsons
ef0ddeff39
Fix simulator brackets: NHL bracket-aware mode, MLB/NBA/WNBA sourceElo support (#358)
* Fix NHL bracket-aware simulation and MLB sourceElo support

NHL: NHLSimulator now checks for a populated playoff_game bracket before
falling back to season-projection mode. When a bracket exists (e.g. via
simple_16 template), it simulates the actual bracket structure, respecting
completed matches and using ELO+odds blending for the rest — matching the
pattern used by Snooker, NBA, and UCL simulators.

MLB: MLBSimulator now reads sourceElo from participantExpectedValues and
converts it to an effective RDif (via eloToRDif), overriding the hardcoded
TEAMS_DATA.rdif when present. This wires up the expected-wins admin form
(which saves sourceElo) to the simulation engine.

https://claude.ai/code/session_01139GJVg2gqgDjcUzqhnBNn

* Wire sourceElo from projected wins form into NBA, NHL, and WNBA simulators

All three simulators had entries in simulator-config.ts (enabling the projected
wins form in the admin UI) but silently ignored the sourceElo that was saved.

NBA: Both bracket-aware and season-projection modes now load sourceElo and
prefer it over hardcoded TEAMS_DATA elo. Added resolvedElo field to TeamEntry
so eloOfEntry() uses the correct value throughout.

NHL: Season-projection mode now fetches sourceElo + sourceOdds in a single
parallel query (removing the duplicate evRows query). Bracket-aware mode also
loads sourceElo alongside sourceOdds. Both modes use sourceElo > TEAMS_DATA > 1400.

WNBA: resolveElo() now accepts sourceElo as an optional highest-priority
argument. The evRows query fetches sourceElo alongside sourceOdds and passes
it through when building team entries.

https://claude.ai/code/session_01139GJVg2gqgDjcUzqhnBNn

* Add review fixes: NHL R1 validation, WNBA sourceElo tests, NHL bracket unit tests

- NHLSimulator.simulateBracket: remove unused nameById variable
- NHLSimulator.simulateBracket: validate all R1 participants before entering
  Monte Carlo loop; throws with a clear message if any slot is missing
- wnba-simulator.test.ts: add 5 tests covering the sourceElo override parameter
  on resolveElo() (overrides SRS, overrides futures, overrides fallback,
  null/undefined fall through to normal priority chain)
- nhl-simulator.test.ts: add full bracket-aware integration suite (10 tests)
  covering fallback to season-projection, structure validation errors, probability
  distributions, fully-decided champion, and source tag

https://claude.ai/code/session_01139GJVg2gqgDjcUzqhnBNn

* Fix TS error: add resolvedElo to makeEntry in nhl-simulator.test.ts

TeamEntry requires resolvedElo after the simulator refactor.

https://claude.ai/code/session_01139GJVg2gqgDjcUzqhnBNn

* Fix lint: replace non-null assertions with null-safe alternatives in NHL test

https://claude.ai/code/session_01139GJVg2gqgDjcUzqhnBNn

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-30 08:33:57 -07:00
Chris Parsons
2b48ef285d
Rework MLB simulator: RDif-based matchup probs, 2026 data, fixes #227 (#228)
Fixes #227

- Replace hardcoded Elo ratings with 2026 FanGraphs Depth Charts projected
  run differential (RDif) for all 30 teams
- Replace Elo formula with Bill James log5 using RDif → win rate conversion
- Restore p_div/p_wc from FanGraphs playoff odds (divTitle/wcTitle) for
  seeding draws — previously these were incorrectly derived from raw win
  rates, which gave the Dodgers only ~24% division win probability instead
  of the correct ~90%
- Add RDIF_DIVISOR constant to compress team strengths toward .500 for
  playoff parity; set to 8000 (Dodgers +137 → ~51.7% per-game win rate)
- Fix minimum team check: < 5 → < 6 (need 3 div winners + 3 wild cards)
- Fix Colorado Rockies p_div 0.000 → 0.001 for consistency
- Fix stale comments throughout; update tests

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 19:05:12 -07:00
Chris Parsons
2e2d8db777
Add MLB playoff simulator with AL/NL division standings, fixes #121 (#225)
* Add MLB playoff simulator with AL/NL division standings, fixes #121

- New `mlb-simulator.ts`: 50k Monte Carlo sim drawing division winners
  (weighted by p_div) and 3 WC teams per league, then simulating
  WC best-of-3 → DS best-of-5 → LCS best-of-7 → World Series.
  Elo parity factor 350; blends Vegas odds 30/70 when sourceOdds available.
- New `standings-sync/mlb.ts`: adapter for free statsapi.mlb.com API,
  mapping AL/NL conferences, division ranks, streaks, home/away/L10 splits.
- New `mlb-divisions` display mode in RegularSeasonStandings: shows 1 division
  winner per division section + Wild Card section with 3-spot playoff line,
  headings read "AL/NL League" instead of "Conference".
- Refactored buildNhlSections/buildMlbSections into shared buildDivisionSections
  (divisionSpots + wcSpots params); conferenceLabel now flows through group
  objects rather than being derived from displayMode in the render.
- DB migration 0062 adds `mlb_bracket` to simulator_type enum.
- 37 new tests across simulator, adapter, and component.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix flaky tennis simulator test: increase trials and lower threshold

500 trials with a ~3–5% expected win rate had enough variance to
occasionally land below the 0.03 threshold (~2% failure rate).
Bumping to 2000 trials reduces the std dev by 2x; lowering the
threshold to 0.02 keeps the assertion meaningful (still well above
random 0.78%) while eliminating the flakiness.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 08:47:02 -07:00