Skip to content

slice-8: season chaining and gp season - #21

Merged
Meerav29 merged 2 commits into
auto/queuefrom
auto/grid-prophet-slice-8-season-chaining
Oct 21, 2026
Merged

Meerav29 merged 2 commits into
auto/queuefrom
auto/grid-prophet-slice-8-season-chaining

Conversation

@Meerav29

Copy link
Copy Markdown
Owner

Tracking: #19

What this slice does

This is spec §3.4 season chaining plus the gp season command from §6. It covers the first half of Phase 3.

  • src/sim/season.py (new, pure numpy).
    • simulate_season runs rounds r+1..R in sequence inside each trial. Before each round, every team's car takes one random-walk step from its running value, so the wander compounds over the season.
    • Each round goes through simulate_weekend, so a sprint reuses the Grand Prix's pace draw and is scored with the sprint points table.
    • Team points are the per-trial sum of that team's drivers' points (driver_points @ membership).
    • remaining_calendar(season, after_round, loader=None) reads the remaining rounds from the FastF1 event schedule. loader is the seam tests use to stub it. If the schedule can't be read, or comes back empty, it raises ScheduleUnavailable.
    • drivers_championship and constructors_championship build the two §6 tables.
  • src/cli.py
    • forecast_season handles the posterior plumbing. The chain starts at the fit's own cutoff round, and points and wins scored so far are read from the R/S rows.
    • cmd_season writes drivers_championship.csv and constructors_championship.csv to --out-dir (default: the fit directory). If the schedule can't be read it exits 1 and prints the error.
  • src/sim/race.py: the points-per-trial calculation moved out of summarize_trials into points_for_trials so both simulators use it. summarize_trials behaves the same.

Review pack

About 20 minutes.

  1. Read src/sim/season.py::simulate_season (~45 lines). The carry-forward is the line car = car + rng.normal(...).
  2. Read cli.forecast_season, in particular where the season and cutoff round come from (max(data.round_to_idx, ...)) and how current points are computed (_season_so_far).
  3. Run the slice's tests and the full suite:
    PYTHONPATH=src pytest tests/test_season.py -q
    PYTHONPATH=src pytest tests -q
  4. Optional, on your machine with a real fit (needs network for the FastF1 schedule, ~1 min):
    PYTHONPATH=src python -m cli season --n-trials 10000
    Check that current_points matches the real 2026 standings through the fit's cutoff round, and that the title odds look sane.
  5. Read the two decision entries dated 2026-10-21 in docs/autopilot/decisions.md.

Acceptance criteria

Commands were run in this sandbox on Python 3.11.17, pymc 5.28.5, arviz 0.23.4. The full real backtest was not run.

  • The random walk carries forward, and a late-round car draw has a wider spread than an early-round one.
    • Test: TestRandomWalkCarriesForward::test_late_round_car_draw_is_wider_than_early. With an identical starting car in every trial and 12 rounds, the round-12 sd is more than 2.5× the round-1 sd, and it matches 0.08·√12 within 10%.
    • test_rounds_draw_pace_from_the_wandered_car checks that the resolver reads the running car.
    • Mutations:
      • car = inputs.car + rng.normal(...) (stepping from the fitted car each round): 1 failed, 11 passed.
      • car_d = inputs.car[:, team_col]: 1 failed, 11 passed.
  • Sprints are included with their own points table and share the weekend pace draw (via simulate_weekend).
    • test_sprint_rounds_score_their_own_table: with no DNFs, each trial hands out exactly 3×101 + 36 points.
    • test_sprint_goes_through_simulate_weekend: a spy records the has_sprint flag on each call as [False, False, True].
    • Mutation has_sprint=False: 2 failed.
  • Drivers' and constructors' totals come from the same trials.
    • test_constructor_points_are_the_sum_of_their_drivers_per_trial checks points and wins per trial, with DNFs on and a sprint round included.
    • test_forecast_season_tables checks that each team's constructor exp_final_points equals the sum of its drivers' values.
    • Mutation that leaves one driver per team out of the sum: 2 failed.
  • gp season writes both CSVs with the §6 columns, and current points come from rounds already run.
    • test_forecast_season_tables checks the column sets and current points (d1 = 50 and d2 = 36 from two completed rounds).
    • test_season_finished_means_standings_are_final: with no rounds left, final points equal current points.
    • TestCmdSeason::test_writes_both_championship_csvs.
    • Both tables also carry a current_points column, and the drivers table carries team. These are additions beyond the §6 columns.
  • The calendar comes from FastF1 through a stubbable seam, and an unreadable schedule fails with a clear message.
    • TestRemainingCalendar covers round filtering, the sprint flag, and a loader that raises.
    • TestCmdSeason::test_unreadable_schedule_exits_with_a_message: SystemExit, the error appears on stderr, and no CSV is written.
    • Mutation that swallows the loader exception: 2 failed.
    • No test touches the network. The real fastf1.get_event_schedule path has not been exercised here.
  • Size. The diff is 586 additions and 8 deletions, so 594 lines, under the 600-line cap. The CLI wiring is included, so there is no follow-up split.
  • Existing tests still pass. PYTHONPATH=src pytest tests -q gives 309 passed, 660 warnings in 34.69s: the 297 that passed before plus 12 new. test_season.py alone gives 12 passed in 0.78s.

A separate countback mutation (dropping wins from the tie key) gives 1 failed, from test_final_positions_break_points_ties_on_wins.

Decisions made without you

Both are recorded in docs/autopilot/decisions.md under 2026-10-21.

  1. What is held fixed through the season. Everything except car is drawn once per trial and kept for the rest of the season: driver skill, track interaction, quali offset, noise scales, and both DNF hazards. Only car steps, using the pace model's own SIGMA_DEV, doubled for the early rounds of a reset year, as in _step_sigma_scale. The incident-hazard grid slot is the season-start posterior-mean quali rank.
    • Rejected: redrawing these per round. That turns parameter uncertainty into round-to-round noise, which averages out and makes title odds overconfident.
  2. Who races and how ties break.
    • The last completed round's entrants race the remaining rounds.
    • Drivers who are no longer entered keep the points they scored.
    • Team current points come from recorded points, so they follow whoever actually drove the car.
    • A tie on points goes to whoever has more Grand Prix wins, then to column order.
    • Rejected: an explicit entry list, because nothing in the repo maintains one, and the full FIA countback, which adds complexity for ties that almost never happen.

Also worth knowing: the overtaking parameter for future rounds comes from a single build_overtaking_data call over every circuit with races before the cutoff, then a per-circuit lookup with the hand rating as fallback. That is one fit per gp season call, not one per round.

Deliberately not included

  • The season-level backtest. That is slice 9.
  • Retiring confidence.py or the pkl. Spec §8 makes that conditional on the Phase 3 backtest passing.
  • Pre-season forecasts. gp season chains from the fit's own cutoff season. Forecasting a season the fit has not reached yet (for example, fitting through 2025:24 and asking for 2026) is not supported.
  • Mid-season line-up changes in future rounds. There is no entry-list input.
  • Fastest-lap bonus points. These are not in points_tables.csv and don't apply in 2026.
  • Any claim about forecast quality. Only the backtest can show that.

How to revert

git revert <merge commit> on auto/queue. The changes are additive apart from the points_for_trials extraction in sim/race.py, and no data files or schemas change.


Generated by Claude Code

Chains rounds r+1..R within each trial, carrying the car random walk
forward; sprints via simulate_weekend; drivers' and constructors' tables
from the same trials. gp season writes drivers_championship.csv and
constructors_championship.csv; the remaining calendar comes from FastF1
through a stubbable loader and the command fails if it cannot be read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QRJHJJN7CD3ym1wMdmWnsM
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QRJHJJN7CD3ym1wMdmWnsM
@Meerav29
Meerav29 merged commit e69f8c3 into auto/queue Oct 21, 2026
1 check passed
Meerav29 pushed a commit that referenced this pull request Oct 21, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QuFzDH4gT5WLYUkweqAXpV
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants