claude/mlb-simulator-projected-wins-elo-siess8 #146
Merged
chrisp
merged 2 commits from 2026-08-29 07:53:09 +00:00
claude/mlb-simulator-projected-wins-elo-siess8 into main
2 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
101e102e23
|
Fix review findings on the MLB projected-wins change
The previous commit did not build. `simulatorInputLabel` was called from the rendered component for the Base Elo Source select, which defeated the treeshaking that keeps the simulator manifest — and through it the registry, every simulator, and ~/database/context — out of the browser. Vite fails the client build outright with "Server-only module referenced by client". The label is now resolved in the loader alongside inputColumns, which is the pattern the loader comment already documents. Typecheck, lint and the unit suite all passed on the broken commit; none of them run a production build. A stale projection now yields to Elo instead of clamping to an extreme. seedingWinRateFor clamped the rest-of-season target into [0.01, 0.99], so a 96-40 team projected for 95 was simulated to go 0-26 and a 40-70 team projected for 95 was simulated to win out. A target outside (0, 1) is proof the projection has gone stale, not a reason to bet everything on it, so it falls back to the Elo rate — the same escape hatch nll-simulator uses. The blend weight is also clamped inside the helper now, so a stray config value cannot turn it into an extrapolation. Seeding only applies a projection that actually produced the resolved Elo, gated on metadata.sourceEloMethod via projectionForSeeding. Previously the raw projection drove seeding regardless of the input policy: with the default Elo-first ordering a projection was ignored as the Elo source yet still dictated the standings, and with futures odds blended in at oddsWeight, seeding and playoff matchups ran on two different strength scales. The simulator page's preview resolved its Elo and rating maps only for required inputs, but renders those columns solely from the maps, so stored values displayed as "—" for profiles that treat the input as optional (playoff_bracket, ncaam_bracket, golf_qualifying_points). Both maps now key off the same required-plus-optional set the columns do. The metadata upsert's CASE branches were mutually exclusive, so supplying any metadata skipped the stale-flag clearing: a bulk row carrying both a direct rating and a projection kept a stale ratingMethod and hid the rating it had just set. Stripping now always runs, with the caller's metadata merged over the result. Not changed: projectedWinsWeight still defaults to 1 with no decay toward Elo. That is the final-win-total semantics chosen for this work; the stale-target fallback removes its pathological case. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CQSEmmojmqmGdJttgzqCWK |
||
|
|
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 |