The IndyCar simulator gave a near-clinched championship leader ~60% to win
the title. It was reporting the futures odds and nothing else.
`event_type` has no race value, so a season_standings calendar (F1, IndyCar)
is stored as `schedule_event` rows — the admin default for that scoring
pattern. The simulator skipped exactly those rows when counting races, so it
saw zero remaining races, took the branch labelled "pre-season", and never
looked at `participant_season_results`. Since `sourceOdds` is an admin input
that is never auto-refreshed, the output was whatever the market said months
ago. With a realistic late-season field (leader on 601 pts vs 480, 2 races
left, stale futures at -300) the old path returns ~55%; counting the races
returns 100.0%.
- Add `countSeasonRaces` in a new leaf model. A race is every event except
`final_standings`, and completion is inferred from the event date, since
nobody marks rows labelled "Non-Scoring" complete. Its own module because
`scoring-event.ts` reaches back into `simulator.ts` through
`scoring-calculator`.
- Split "season over" from "pre-season". Both had `remainingRaces === 0`, so
a finished season reverted to an odds draw instead of reporting the final
standings.
- Warn when a season has championship points but no calendar, in the
simulator and as a non-blocking readiness warning on the setup page.
- Replace proportional vig removal with a power devig. Dividing every runner
by the same book sum guts the favourite in a 27-driver market: a 75%
implied favourite came out at 55%, a -20000 near-lock at 95%. Power devig
gives 69.5% and 99.3%. Unpriced drivers now floor at the bottom of the
market instead of being handed 1/N.
- Move the race points tables to their own module so tests can read them
without tripping the manifest/registry import cycle.
The existing tests missed all of this because their event fixtures used
`eventType: "race"`, which is not a value the enum has. Rebuilt on real enum
values, plus a regression test for the reported case.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TUcV7KenckF893zQ46EDXt