Developers
Install, license, integrations, embedding. The Python library + CLI side. Methodology lives in /methodology; usage docs in /help.
Is it on PyPI? How do I install the library?
Not yet. Pre-1.0 the API still moves enough that a PyPI wheel would mislead about stability. Install from source:
git clone https://github.com/sam-dumont/bike-power-model cd bike-power-model uv sync --extra api --extra analysis uv run bpm --help
uv is the supported path. Bare pip install -e . works but isn't covered by CI. PyPI publish lands with the 1.0 SemVer commit.
What's the license? Can I ship it in a paid product?
MIT. Fork it, modify it, ship it in a closed-source product, sublicense it. Attribution required (standard MIT notice in your dist), no copyleft, no royalty.
Per-sector surface Crr database, OTS-stamped predictions, validation cohort: same repo, same license. A heads-up at info@bikepowermodel.fit is appreciated if you ship commercially, but not required.
Does it integrate with TrainingPeaks / Wahoo / intervals.icu?
Partial.
- intervals.icu: wired. The 90-day power curve fit feeds the per-rider PDC used as effort ceiling. Auth lives in
~/.config/bike-power-model/.env; see the README integration section. - TrainingPeaks / Wahoo: not yet. Today's workflow is GPX in → Garmin Power Guide FIT out. The FIT you download from the planner sideloads onto an Edge via Garmin Express; nothing pushes to a coach platform.
- CSV export: the planner has a CSV download for the per-split plan if you want to pull it into your own pipeline.
What's the SemVer / breaking-change policy?
Pre-1.0, so technically anything-goes. In practice:
- The public surface (
plan_race,plan_ride,RiderProfile,Course, the 11for_*constructors) is treated as stable. Breaking changes here get a CHANGELOG entry quantifying the impact. - Internal helpers (modifiers, splitters, calibration tables) move freely without notice.
- Recalibrations can shift predicted times by 2-5 % even with identical inputs. That's a customer-visible drift with no input change. Treated as a minor bump, not a patch.
1.0 commits to strict SemVer with a deprecation cycle. Until then, embedders should pin to a specific commit, not a moving tag.
Can I run bulk predictions for a squad?
The web UI is one-rider, one-route at a time. The Python library handles bulk:
from bike_power_model import RiderProfile, plan_race
for athlete in roster:
rider = RiderProfile.for_race_day(
ftp=athlete.ftp,
rider_mass_kg=athlete.weight_kg,
bike_mass_kg=8,
cda=athlete.cda or 0.30,
height_m=athlete.height_m,
gender=athlete.gender,
)
rider.effort = 1.0
plan = plan_race(course_path, rider,
race_date="2026-08-15",
race_start_hour_local=9)
print(athlete.name, plan.result.total_time_s)A coach with Python familiarity can wire athlete CSVs to per-rider FIT files in an afternoon. CLI wrappers + a coach-surface UI are planned but not shipped.
What dependencies does the library pull in?
Lean. Core: fitparse, numpy, requests, pmtiles (OSM surface tiles), scipy (cKDTree for spatial sector lookup), click (CLI), and the Garmin fit-tool + garmin-fit-sdk for FIT writing.
No PyTorch, no scikit-learn at runtime (a Lasso baseline is a one-off benchmark, not a runtime dep). The API extras pull in fastapi, uvicorn, boto3, altcha, email-validator: only needed if you're running the SaaS, not the library.
Versions exact-pinned (supply-chain policy); full set in pyproject.toml + uv.lock.
What's on the CLI?
bpm plan generates a Power Guide FIT from a GPX + a rider profile. bpm calibrate back- solves CdA from a flat ride of yours. bpm power-guide reads the messages 352/353 out of an existing Garmin Power Guide FIT. bpm --help for the full list.
All commands take a --ftp, --weight, --cda, --effort, --intent flag set; the preset chips on the SPA are just shorthand for these.