Scoring and salary calculation for GridRival fantasy F1
Project description
gr_analytics
Python package for scoring and salary calculation in GridRival fantasy F1.
Installation
pip install -e .
Quick Start
import pandas as pd
from gr_analytics import score_event, score_my_team, optimal_lineup
# Load your race scenario
scenario = pd.read_csv("my_race.csv")
# Score the event (defaults to the latest round in driver_data)
result = score_event(scenario)
# Or score a specific race: pass the round one less than the race
# (round=1 is the post-race-1 state, so this scores race 2). See "Driver Data".
result = score_event(scenario, round=1)
# Score your specific team selection
points, salary_change = score_my_team(
scenario,
drivers=["RUS", "ANT", "LEC", "BEA", "LIN"],
team="MER",
star_driver="BEA",
round=1,
)
# Find the optimal lineup (maximise points, £100M budget)
lineup = optimal_lineup(result)
# With locked-in drivers (already under contract, cost nothing)
# and a budget for the remaining open spots
lineup = optimal_lineup(
result,
locked_in=["HAM", "LEC"], # driver_abbr or team code
optimize_for="points", # or "salary_change"
budget=60.0, # £M available for non-locked picks
)
Scenario Format
The scenario DataFrame must have one row per driver with these columns:
| Column | Type | Description |
|---|---|---|
driver_abbr |
str | Driver abbreviation (e.g. "RUS", "VER") |
qualifying_position |
int | Official qualifying position (1–22) |
completed_qualifying |
int | 1 if driver completed qualifying, 0 if DNQ |
finishing_position |
int | Race finishing position (1–22) |
completed_pct |
float or "DNS" |
Fraction of race completed (0.0–1.0), or "DNS" if driver did not start |
DNQ drivers get 0 qualifying points but their qualifying_position is still used to calculate overtake points.
DNS drivers (completed_pct="DNS") get 0 for all race-related points (race, overtake, improvement, completion, teammate).
DNF drivers (e.g. completed_pct=0.3) receive partial completion bonus points and their finishing position is used normally.
Qualifying and race positions must each form a complete sequence 1..n with no duplicates or gaps.
Output
score_event returns a DataFrame with all drivers and constructors, with scoring columns appended:
Drivers:
pts_qualifying,pts_race,pts_overtake,pts_improvement,pts_completion,pts_teammatepoints_earned— total fantasy pointssalary_after_event,salary_change
Constructors:
pts_qualifying,pts_race(sum across both drivers, using constructor-specific tables)points_earned,salary_after_event,salary_change
Scoring Rules
All scoring follows GridRival's rules for Grand Prix events (no sprint races).
Drivers
| Bonus | Rule |
|---|---|
| Qualifying | P1=50, P2=48, … P22=8 (step −2) |
| Race finish | P1=100, P2=97, … P22=37 (step −3) |
| Overtake | (qualifying pos − finishing pos) × 3, min 0 |
| Improvement | Points for finishing ahead of 8-race average (2 pos=2 pts, 3=4, 4=6, 5=9, 6=12, 7=16, 8=20, 9=25, 10+=30) |
| Completion | 3 pts each at 25%, 50%, 75%, 90% of race distance (max 12) |
| Teammate | Points for beating teammate by margin: ≥1 pos=2 pts, ≥4=5, ≥8=8, ≥13=12 |
Constructors
Constructor qualifying and race points use separate tables (P1=30/60, step −1/−2 per driver) summed across both drivers. No overtake, improvement, completion, or teammate bonuses.
Salary Adjustment
After each race, salaries adjust based on the gap between a driver's actual starting salary and the default salary for their points-ranking position:
adjustment = truncate(variation / 4, to nearest £100K)
Capped at ±£2M for drivers, ±£3M for constructors.
Driver Data
Bundled driver data (gr_analytics/data/driver_data.csv) contains starting salaries and 8-race averages by round:
round=0— pre-season (before Australia 2026)round=1— post-Australia 2026
Each round=N row holds the state after race N: the post-race salaries,
the finishing position from race N, and the 8-race average including race N.
Round semantics for score_event: the round argument is that
post-race state, so score_event(scenario, round=N) scores the race that
follows it. Pass the round one less than the race you're scoring:
round=0 scores race 1 (off the pre-season seeds), round=1 scores race 2,
and so on. Improvement points therefore compare each finish to the 8-race
average going into that race (calculate_eight_race_averages(through_round=round)).
Eight-Race Average
GridRival's "8 race average" can be computed instead of entered by hand.
GridRival seeds the season with 8 slots holding a hard-coded initial
average (the round=0 values in driver_data); each race replaces one
slot with the driver's classified finishing position, and the displayed
value is the ceiling of the slot mean. Each driver's finishing
position per race is recorded in the finishing_position column of
driver_data.csv (the round=N row holds the finish from race N), so
the average is derived entirely from driver_data.
from gr_analytics import calculate_eight_race_averages, eight_race_average
# All drivers, after the latest round with recorded finishing positions
calculate_eight_race_averages()
# All drivers, after round 2
calculate_eight_race_averages(through_round=2)
# Single driver from scratch: seed 1, finished P6 then P16
eight_race_average(1, [6, 16])
This reproduces GridRival's displayed values exactly for all rounds so
far (verified in tests/test_eight_race_average.py).
score_event uses this computed average for improvement points (the value
going into the race, i.e. calculate_eight_race_averages(through_round=round))
rather than the stored eight_race_average column. That column is kept only as
GridRival's transcribed ground-truth/test oracle and may be blank for recent
rounds; scoring no longer depends on it.
Lineup Optimisation
optimal_lineup uses mixed-integer linear programming (via scipy.optimize.milp) to find the best 5-driver + 1-constructor lineup within a salary budget.
lineup = optimal_lineup(
scored, # DataFrame from score_event()
locked_in=None, # list of driver_abbr / team codes already on your team
optimize_for="points", # "points" or "salary_change"
budget=100.0, # £M for non-locked picks
)
locked_inpicks are included free (already under contract) and don't count against the budget.optimize_for="points"selects the optimal star driver (who earns double points) across all candidates.optimize_for="salary_change"maximises total salary gain for the next race's team valuation.
The returned DataFrame has a star column (1 = starred driver, points mode only).
Running Tests
python -m pytest tests/test_scoring.py -v
Tests include hand-calculated unit tests for all scoring components and a full integration test against real GridRival results from Australia 2026 (22 drivers + 3 constructors).
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file gr_analytics-0.4.0.tar.gz.
File metadata
- Download URL: gr_analytics-0.4.0.tar.gz
- Upload date:
- Size: 27.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a19817e070055895298b54ea253162836711cc06e6b7bda4ee5e6086c128fbe2
|
|
| MD5 |
0a10c327290f7222cb9eeefd467ef31d
|
|
| BLAKE2b-256 |
3c4f0fcffb0a0975539d3699c8fed2de82702d1c5a3a5f59530411f98e0d097c
|
File details
Details for the file gr_analytics-0.4.0-py3-none-any.whl.
File metadata
- Download URL: gr_analytics-0.4.0-py3-none-any.whl
- Upload date:
- Size: 17.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
040a1c7f19f02cac709f7e8465eedd6b7650bbd16b0717502785afd46a60892c
|
|
| MD5 |
c3d53d93fd6899fe64ce5ed59db08f78
|
|
| BLAKE2b-256 |
cf136cec507aebc1e5ca757de7fdc482ad7637552c30101f6e956208d41073f1
|