---
title: "Predicting F1 from a terminal"
slug: f1-predictor
date: 2026-06-11
tags: ["ml", "rust", "ai", "build"]
summary: "An F1 race predictor with Python training and Rust inference, held together by one SQL file and a hash check. Built in a night as a Fable 5 test."
canonical: https://abrahamgonzalez.dev/blog/f1-predictor
---

# Predicting F1 from a terminal

This project started as a test of Fable 5. The model had just dropped and I wanted to see what it could do with a real project, not a benchmark. So I handed it an idea I had been sitting on for months: a terminal app that predicts Formula 1 race results. It finished in one night. I went back later and reworked some of the architecture and the UX by hand, smaller stuff, but the core shipped while I slept. I am still not sure how I feel about that.

## What it is

A retro TUI built with Ratatui. Pick a race, past or upcoming, and it shows a predicted finishing order with a Spearman rank correlation next to it so you can see exactly how wrong it was. Press 3 and it simulates the rest of the season ten thousand times and prints championship odds. During an actual Grand Prix it tails the unofficial F1 live timing feed and blends the prediction toward real track position as the laps count down. Everything runs locally. No API keys, no accounts. Historical data comes from the Jolpica API, the free successor to Ergast.

## Two languages, one contract

Python trains the model. Rust runs it. They never import each other and they never talk. They agree on exactly three things: a SQLite schema, a file called features.sql, and the exported ONNX model.

That SQL file is the whole trick. Both sides execute the same query against the same database to build feature vectors, so train/serve skew is impossible by construction instead of prevented by discipline. Training records a SHA-256 hash of the file inside the model artifact, and the Rust app checks that hash at startup. Edit the SQL without retraining and the app refuses to launch. I have been burned by silent feature drift before. Not again.

The ONNX export has its own gate: if the ONNX output differs from XGBoost by more than 1e-4, or produces a different ordering for any race, the export fails. Paranoid? Sure. It also caught real bugs.

## The grid is humiliating

Here is the part that kept me honest. The dumbest possible baseline, predicting that everyone finishes where they qualified, calls the race winner 70% of the time. My model calls it 60%. The gradient boosted model with ten engineered features loses to "just copy the grid" on winners.

Where the model earns its keep is everywhere else. Counting finishers only, with DNFs set aside, its rank correlation is 0.77 against the grid's 0.75, and it is far better at midfield ordering and guessing who retires. The final prediction blends 75% model rank with 25% grid rank, a weight tuned on 2024 validation data. It was the only blend that improved winner calls and overall order at the same time.

I also tried feeding in FP2 long run pace through FastF1. It was weak on its own and it made the podium predictions worse, so it went in the bin. Constructor form and quali gap already carry that signal. More data is not automatically better, which I knew in theory and apparently needed to relearn in practice.

## Honest championship odds

The season simulator draws a per-driver bias once per simulated season, not fresh every race. That one detail matters more than anything else in the simulator. With independent noise the errors wash out over 24 races and the points leader cruises to a near certain title in every run. Correlated noise admits the model might be consistently wrong about a specific driver, and that admission keeps the odds humble.

## What I took from it

The interesting part of this project is not the model. It is the contract. Two languages that share nothing but files, with a hash check standing guard between them, turned out to be sturdier than most production ML setups I have seen. And the fact that an AI built most of it in one night is either exciting or unsettling, depending on the day you ask me.

The code is on GitHub at [theabecaster/f1-predictor](https://github.com/theabecaster/f1-predictor) if you want to dig in or run it yourself.
