F1-Zoom is a full-stack Formula 1 app built by the Monash DeepNeuron team. It pulls race weekend data from FastF1, stores it in Supabase, trains a ranking model to predict the next Grand Prix, and serves everything through a React frontend with 3D circuit visuals and a live timing view.
The goal was to go beyond a static stats page. We wanted something that feels like race weekend: a circuit you can explore, predictions that update as new sessions land, and a live page that can follow a session in real time.
A React + TypeScript app with four main routes:
- Home (
/): A Three.js circuit hero that loads the track for the upcoming race. Below that you get predicted top-10 results, driver and constructor standings, next race info, and a season calendar map. On first visit, a short cinematic intro zooms into the circuit and drops you into a playable pixel mini game on the same track layout. - Predictions (
/predictions): Full predicted classification for all 20 drivers, with model scores, grid comparison, and a written insight generated from the weekend data. - Live (
/live): Live timing board and track map fed by Server-Sent Events. Works against a mock replay by default, or the official F1 SignalR feed when configured. - About (
/about): A breakdown of the prediction pipeline, model settings, and the feature columns the ranker uses.
Track geometry lives in frontend/public/circuit_3d/. We have centreline coordinates for 23 of the 24 circuits on the 2026 calendar (Madrid is the one still missing). CSVs are converted to JS modules for the renderer, with corner markers derived from curvature in the source data.
A Spring Boot API on port 8080. It proxies the Ergast F1 API for championship standings, race schedule, and next/last race info. Circuit metadata, session times, past winners, stored predictions, and AI insights come from Supabase.
Base path: /api/v1
Python scripts that run after each session finishes. The orchestrator looks for sessions that ended more than two hours ago but have not been ingested yet, fetches results and lap telemetry from FastF1, and writes structured rows to Supabase.
From that raw data we compute:
- Practice, qualifying, and race session results per driver
- Lap-level pace, clean-air pace, speed ranks, and tyre metrics
- Rolling 3-, 5-, and 7-race form with a one-race lag so nothing leaks into training
- Track-level stats (overtakes, safety car rates, red flag frequency)
After predictions are written, ai_insights.py calls Gemini to produce a short summary, key reasons, contenders, and caveats for the Predictions page.
A GitHub Actions workflow (.github/workflows/f1-data-pipeline.yml) runs the orchestrator and retrains the model every two hours, or on manual trigger.
We use a LightGBM LambdaRank model rather than predicting a raw finishing position. Each race is one group, and the model learns to rank drivers within that group, optimised for NDCG. That matches how F1 results actually work: you care about relative order, not an absolute number.
Inputs include grid position, practice and qualifying times, pace deltas, speed metrics, track characteristics, and rolling history. After each race weekend the model is retrained from Supabase (or from the local CSV fallback) and predictions for the next Grand Prix are stored back in Supabase.
prediction_service.py exposes a FastAPI API on port 8000. The Spring Boot backend reads predictions from Supabase directly, so the frontend does not need the Python service running for normal use. The FastAPI layer is still useful for local testing and direct model access.
A FastAPI service that maintains in-memory session state and broadcasts incremental updates over SSE. In mock mode it replays timing against a local track file. Set F1_MODE=live to connect to the official feed via f1_client.py.
The frontend proxies /api/realtime and /api/state to this service through Vite.
FastF1 --> data_pipeline orchestrator --> Supabase
|
v
LightGBM ranker (retrain + predict)
|
v
Spring Boot API <-- React frontend
^
Live F1 feed / mock replay --> live_service (SSE) --+
- Sessions finish. The pipeline ingests FP, qualifying, sprint, and race data.
- Features are computed and stored alongside per-driver race entries.
- The ranker retrains and writes predicted positions to Supabase.
- Gemini generates a human-readable insight for the top of the grid.
- The frontend reads everything through the Spring Boot API. The Live page connects separately to the timing service.
F1-Zoom/
├── backend/ Spring Boot API (Java 21)
├── frontend/ React + Vite + Three.js
│ └── public/circuit_3d/ Track CSV/JS assets and generators
├── Data/Simulation/ LightGBM training and prediction API
├── live_service/ FastAPI SSE live/mock timing
├── data_pipeline/ FastF1 ETL, feature engineering, AI insights
├── .github/workflows/ Scheduled pipeline + model retrain
├── .env.example Environment variable template
└── README.md
- Java 21+
- Node.js 18+ and npm
- Python 3.11+ (Conda works well)
- Maven is optional if you use
./mvnw
Copy the template and fill in your values:
cp .env.example .envRequired for Supabase-backed features (pipeline, circuits, predictions):
SUPABASE_URLSUPABASE_SERVICE_ROLE_KEY
Optional:
GEMINI_API_KEYfor AI-written prediction insights
cd backend
./mvnw clean install
./mvnw spring-boot:runRuns at http://localhost:8080. Try GET /api/v1/test to confirm it is up.
cd frontend
npm install
npm run devRuns at http://localhost:5173.
Vite proxies:
/api/realtimeand/api/state->http://localhost:8000(live timing)/api->http://localhost:8080(Spring Boot)
conda create -n f1-project python=3.11 -y
conda activate f1-projectcd data_pipeline
pip install -r requirements.txtRun from the project root:
python -m data_pipeline.orchestratorcd Data/Simulation
pip install -r requirements.txt
python lightgbm_model.py --from-supabase --auto
python prediction_service.py # optional, for direct API accesscd live_service
pip install -r requirements.txt
uvicorn main:app --reload --host 0.0.0.0 --port 8000Mock mode is the default. For the live feed:
F1_MODE=live uvicorn main:app --reload --host 0.0.0.0 --port 8000Useful env vars: F1_MODE=mock|live, TRACK_NAME=Melbourne, TRACK_DATA_DIR=/path/to/TrackCoordinateJS
Port note: prediction_service.py and live_service both default to port 8000. Run one at a time on that port, or move one service and update the Vite proxy in frontend/vite.config.ts.
# Terminal 1
cd backend && ./mvnw spring-boot:run
# Terminal 2
cd frontend && npm run devPredictions and circuit data come from Supabase, so the pipeline and model need to have run at least once (locally or via GitHub Actions).
# Terminal 1
cd backend && ./mvnw spring-boot:run
# Terminal 2
cd live_service && uvicorn main:app --reload --host 0.0.0.0 --port 8000
# Terminal 3
cd frontend && npm run devThen open /live.
Convert CSV tracks to JS assets for the renderer:
cd frontend/public/circuit_3d
python csv_reading.pyGenerate missing track CSVs from FastF1:
cd frontend/public/circuit_3d
python generate_missing_track_csvs_fastf1.pyCompare a generated Melbourne variant against the current baseline:
cd frontend/public/circuit_3d
python compare_melbourne_fastf1.pySee frontend/public/circuit_3d/TrackCoordinateCSVs/F1_Track_Coordinate_Coverage.md for which circuits are covered.
Backend:
cd backend
./mvnw test
./mvnw clean packageFrontend:
cd frontend
npm run lint
npm run build
npm run preview- Supabase credentials are required for the data pipeline and for circuit/prediction endpoints on the backend.
- The About page (
/about) documents every feature column the ranker consumes. That list is kept in sync withData/Simulation/lightgbm_model.py. - If predictions show as unavailable, check that the GitHub Actions workflow has run successfully or execute the orchestrator and model scripts locally.
- The 3D track renderer and mini game share the same coordinate files under
frontend/public/circuit_3d/TrackCoordinateJS/. Some street circuits still have rough geometry; corner detection and direction are active areas of work.