Predict your race time.
Open-source race-time prediction with per-split surface Crr. Pick a pro race or upload a course, set your FTP, weight, CdA, effort. Get a finish-time prediction plus a head-unit FIT (Garmin Power Guide overlay for Edge 540+, Fenix 7+, Forerunner 955+; FIT Workout for Wahoo, Hammerhead, Bryton, and older Garmin).
Built for races and high-effort days. Honest numbers when you're at your ceiling; loosens up when watts are held back. Validated on pro rides because that's where public power data lives.
Remco's public FTP, total weight (rider + TT bike), height, and aero. Archived rainy race-day weather (Paris hour-16 hit 2.9 mm/h).
Tadej's public FTP, total weight, height, aero. Effort 1.0 (GC podium duel). Vingegaard at his own preset lands +0.68% on the same stage. Wet flag picked up automatically from archived weather.
MvdP's race-day FTP, system mass 83 kg (75 kg rider + classics bike), aero-hoods CdA. Effort 1.0 (solo from 60 km out). The server detects a 13% pavé fraction in the GPX and auto-promotes intent=race → for_cobbled (paceline draft, cobbled effort defaults). Without that auto-promote the model would over-predict by 30+ minutes.
PFP's race-day FTP per chronoswatts (5.15 ᵉW/kg × 1h04 on the full Madeleine), rider mass 53 kg + 8 kg WT bike, classic-aero CdA. The 297 W étalon climb-average backs the model's effort=1.0 sustained-CP read; small bias toward fast suggests her Madeleine pace was held back of full ceiling for tactical reasons.
The model also tracks the pacing curve across the course, not just the finish. Intermediate checkpoints at Château de Vincennes land within tens of seconds of Remco's real splits. Details on Methodology.
The model targets what a rider can do at their public-data ceiling, not what they did. On the Pogi tile above the +1.49% bias is the slow side: he rode the GC podium-duel ~3 minutes harder than the steady-state model predicts. Effort 1.0 means "you're at your ceiling", not "you'll attack mid-race".
How accurate is this?
Without measured inputs (FTP + weight + intent only) those land at 6.41% pro and 10.85% amateur: each override the user supplies (real CdA, current FTP, route intent) tightens the prediction. Full breakdown + held-out evidence + OTS-stamped pre-race forecasts on the Methodology page.
Custom-course uploads are end-to-end encrypted. The AES key lives in the URL fragment (the part after #), which browsers don't send to the server. Only someone with the full link can decrypt the route. The server only ever sees ciphertext. Privacy →
Pre-race forecasts for the 2026 spring classics + Tour de Romandie are git-committed and OpenTimestamps-stamped to the Bitcoin blockchain before the gun. The proof says: this prediction existed before the race, no hindsight tuning. Per-race results sit on the Methodology page.
Run it locally
Open source, MIT. Repo flips public with the v1.0 SemVer commit (PyPI publish lands the same day). Until then the link 404s; the install path will work the moment the repo flips.
git clone https://github.com/sam-dumont/bike-power-model cd bike-power-model uv sync --extra api --extra analysis uv run bpm --help
Full install + license + integration notes on Developers.
Why this exists
One thing led to another. The original goal was a single Paris-Roubaix Challenge: prep a plan, write it onto the Edge as a Garmin Power Guide, ride. Turned out the two FIT messages behind Power Guide (352 and 353) weren't documented publicly, so they had to be reverse-engineered from binary dumps before anything else.
Once the format was writable, what to write was the next problem. Garmin's native plan only sees gradient: not Arenberg cobble, not a 33 km/h Zeeland headwind, not the mud-slick descent out of La Redoute. So the next step was a Martin-1998 power balance to drive better targets. Then per-surface Crr because cobble isn't tarmac, asymmetric wind because real weather isn't uniform, tier-aware draft schedules because pros, cat-2s, and sportive groups don't shelter the same. Heat / altitude / fatigue / sector-aware rain decay piled on after. A long thread of "OK the prediction is wrong on this stage, why?" → fix → next stage. Methodology →