A retro terminal app that predicts Formula 1 race finishing order and championship outcomes, with all ML running on your machine. Rust + ratatui TUI, XGBoost → ONNX models, optional local-LLM explanations via Ollama.
▐▛ F1 PREDICTOR ▜▌ RACES SEASON «2026»
╔ R09 BRITISH GRAND PRIX ── ORDER: RACE RESULT ── ρ 0.671 ═════════════╗
║ PRED NO DRIVER TEAM GRID SCORE ACT Δ ║
║ P1 NOR Lando Norris McLaren 1 3.12 P1 ● ║
║ P2 VER Max Verstappen Red Bull 3 4.81 P2 ● ║
...
The prediction table sorts by whatever is most informative for that race — finished: actual result (green) · quali done: grid order (cyan) · before quali: the model's predicted order (amber). The title says which.
Live races: during a Grand Prix / sprint the app tails the free F1 live timing feed (livetiming.formula1.com, unofficial) and the table becomes a live view: rows ordered by a continuously updated estimate of the FINAL finishing order (model prediction blended toward track position as laps run out), plus current position, tyre changes so far, and an estimated next tyre-change lap per driver. No setup needed; if the feed is unreachable the app just falls back to the normal prediction view.
git clone https://github.com/theabecaster/f1-predictor && cd f1-predictor
make setup # installs rust + uv + libomp if missing, syncs python deps
make data # downloads 2014-present history from the Jolpica API (~5 min)
make run # launches the TUI (trained models ship with the repo)Optional — natural-language explanations (press e on any driver):
# install Ollama from https://ollama.com/download, then:
make ollama # pulls a local model (qwen3.5:27b-mlx / gemma4 / llama3.2)No API keys, no accounts. Data comes from the free Jolpica F1 API (the Ergast successor); inference is local ONNX; the LLM is local Ollama and purely decorative — predictions never depend on it.
- Race predictions — full finishing-order forecast for any race, past or upcoming. Past races show predicted-vs-actual with per-race Spearman ρ.
- Two models, honestly separated:
- race model — used once Saturday qualifying exists (grid position + quali-pace gap are the strongest predictors in the literature)
- pre-quali model — used before qualifying; trained WITHOUT grid/quali features, so early-week forecasts are real form-based predictions, not a model fed placeholder grids
- Championship forecasts (
3key) — Monte Carlo simulation of the rest of the season (10,000 runs): WDC and WCC title probabilities, top-3 odds, and projected final points. The pre-quali model is re-run for every remaining round, so circuit history varies driver strength track by track. Noise is hierarchical, calibrated from held-out residuals: a per-driver rating error τ drawn once per simulation (correlated across the season — this is what keeps title odds honest instead of inflating leaders to ~100%), an independent per-race σ, and a per-round form random-walk term (mechanism in place; current calibration finds ≈0 residual drift because rolling-form features already absorb car development). σ and τ are calibrated on classified finishers only — DNF chaos enters the simulation exactly once, through explicit DNF injection: a 50/50 blend of the constructor's current-season retirement rate (reliability lives in the car; 2026 reg-change attrition is team-specific) and the driver's 3-season rate scaled by the field's current-vs-trailing attrition ratio. 2026 points rules (sprint weekends included, countback tiebreaks). - Driver detail — the exact feature vector behind any prediction.
- LLM analyst (
ekey) — a local model explains a prediction using only the feature values it was given; it is instructed not to invent facts.
Features are engineered in a single SQL file (shared/features.sql) executed by both the Python training pipeline and the Rust app against the same SQLite database — training/inference feature drift is structurally impossible. The app verifies a SHA-256 contract hash at startup and refuses to run with a stale model.
Feature set (research-backed; see sources in the file header): grid position, qualifying-lap % gap to session best, rolling driver form (last 3 / last 5), constructor rolling form (last 3 events), constructor season share, driver's 4-year history at the circuit, DNF rate (last 10), teammate qualifying delta, championship position.
Training: XGBoost regression on finishing position with rank-weighted samples
(podium 3×, points 1.5×) to counter regression-to-the-mean on winners, and
monotonic constraints on grid/quali-gap features. Time-based splits: train
2014–2023, validate 2024 (model selection), test 2025+ (reported metrics).
Exported to ONNX with a parity gate (ONNX vs XGBoost must agree to 1e-4 and
produce identical orderings); inference in Rust via ort.
At deployment the race model's order is a rank blend, 75% model / 25% grid: a 2026 r1–6 backtest showed the raw model tying the grid baseline on order while losing winner/podium hits, and w=0.25 was the only blend weight that helped on both the 2024 validation season and the 2025–26 test seasons. FP2 long-run pace (via FastF1, 2021–26) was evaluated and rejected the same way: weak alone (ρ≈0.33) and harmful to winner/top-3 hits once trained in — constructor season share and quali gap already carry that signal.
Current held-out test metrics live in model/metrics.json and on the TUI's model screen, always next to the honest baseline (predict-the-grid-order), which any model must beat to earn its keep. Metrics are reported twice: over the full classification and over classified finishers only — the second isolates pace-ranking skill from DNF/DSQ chaos (2026's reg-change attrition runs ~2× the 2024–25 rate; 2025's worst-predicted races were post-race disqualifications and mass-attrition outliers, which no grid/form feature can see coming).
shared/ schema.sql + features.sql — single source of truth for data + features
model/ *.onnx + *.features.json + metrics.json (committed; retrain optional)
data/ f1.db SQLite cache (gitignored; built by `make data`)
ml/ Python training pipeline (uv, Python 3.12)
app/ Rust TUI, crate `f1tui`
docs/ ARCHITECTURE.md — module map, ml↔app contract, where to add things
Contributing or pointing an AI agent at this repo? Start with docs/ARCHITECTURE.md — it explains the cross-language contract and the invariants the golden test enforces.
| command | what |
|---|---|
make run |
launch the TUI |
make data |
download / refresh historical data |
make train |
full retrain (features → train → evaluate → ONNX export) |
make test |
golden-race parity test (Rust must match Python exactly) |
cargo run --release -- predict 2025 10 |
one-off CLI prediction |
cargo run --release -- season |
championship forecast in the terminal |
↑↓/jk navigate · ENTER predict / driver detail · e LLM explanation ·
TAB races/season · i model info · r resync · ESC back · q quit
macOS or Linux, ~2 GB disk (Rust toolchain + deps), terminal with truecolor. Windows: untested, but nothing is platform-specific beyond the setup script.
MIT