Skip to content

About

Local-first running intelligence: import Strava, Garmin and OpenTracks, get injury-risk, 80/20 and VO2max analytics, and an AI coach that plans your next race.

Topics

Resources

Stars

3 stars

Watchers

0 watching

Forks

Latest commit

Β 

History

48 Commits

Folders and files

Repository files navigation

Coach Running πŸƒ

CI License: AGPL-3.0

Local running-tracking dashboard, inspired by Strava. It reads your activity files in KMZ (OpenTracks), GPX or TCX format (e.g. Strava exports), computes your statistics, and displays the route on a map. Drop files into data/running/ (see Data folders & external connectors below).

Features

Tracking & map

  • πŸ“‹ List of all your runs (sorted by date, most recent first)
  • πŸ—ΊοΈ Interactive map of the route (OpenStreetMap), colored by speed, with a speed legend
  • πŸ“Š Statistics: distance, moving time, average pace, elevation, max speed, heart rate (when present in the file)
  • πŸ“ Personal notes per run (persisted in coach.db)
  • ⬆️ Drag and drop import: drop KMZ/GPX/TCX activities, nutrition exports or workout files into the Import view. Each zone only accepts the formats its importer understands, and a file is parsed before it replaces anything.
  • 🧩 Exercise-level sessions: record what you actually did (leg press, hip flexor stretch…). Muscle groups are inferred from the name, and a hard session in the 48 h before a pain report is surfaced as a possible link.
  • πŸ‘Ÿ Shoe automation: pick a default pair and have new runs assigned to it automatically.
  • 🩹 Injury watch: episodes derived from the pain already reported in each run's check-in β€” nothing to record twice β€” with the base rate, so a link is shown against chance rather than in isolation.
  • 🧭 Load change: last 7 days against the 28 before them, described as ramping down / steady / ramping up / sharp increase. Not a risk score, and withheld after a break.
  • 🌑️ Conditions and RPE divergence: record heat and humidity, and see the runs that felt much harder than the pace implies.
  • 🧾 Report export: a one-page Markdown or print/PDF summary for a coach or physiotherapist, generated locally.
  • πŸ€– A coach that can look things up: eleven read-only tools (runs, sessions, load series, weights, nutrition, shoes, plans, injuries, period comparison) so follow-up questions get answers from your records, not from a summary. Bounded and aggregate-only: no coordinates, no full notes.
  • 🏁 Race-day pacing plan: import the course GPX and get per-kilometre splits at constant effort β€” climbs slower, descents faster β€” solved so the total lands on your target time.
  • πŸ“ˆ Critical speed and fade: a speed-duration fit from your own efforts (no reference table, no heart rate) and the second-half slowdown of your long runs, grade-adjusted.
  • ⛰️ Grade handling: GAP uses the Minetti (2002) energy-cost polynomial with the gradient measured over at least 50 m, so GPS altitude noise cannot invent a climb.
  • πŸŒ™ Dark theme: follows the operating system, one click to override; the choice is remembered.
  • πŸ”„ Automatic re-read of the data/ folder (↻ button or reload)

Advanced analysis (per run)

  • πŸ”’ Splits: pace per kilometer
  • πŸ” Laps: analysis of the manual segments (your interval workouts)
  • 🎨 Pace zones: time/distance spent in 6 zones (recovery β†’ sprint)
  • ⛰️ GAP: grade-adjusted pace (flat-ground equivalent)

Global views

  • πŸ”₯ Personal heatmap: all your routes overlaid
  • πŸ† Records: best efforts (400 m, 1 km, 1 mile β†’ marathon) + repeated routes (matched runs) to track your progression
  • πŸ“ˆ Monthly statistics: distance/time/elevation per month + comparison
  • 🎯 Goals: weekly / monthly / yearly, in distance, time or elevation, with a progress bar

Coaching (pace-based, no heart rate needed)

  • πŸ“Š Training load: daily pace-based load (zone-weighted minutes), or session-RPE Γ— duration when you log an effort (Foster scale)
  • πŸ‹οΈ Fitness / fatigue / form (CTL / ATL / TSB): Coggan Performance Management Chart with "fresh / neutral / fatigued" bands
  • ⚠️ Injury risk (ACWR): Acute:Chronic Workload Ratio (Gabbett 2016) with alert bands and trend
  • 🎚️ Intensity distribution (80/10/10): time in easy / moderate / hard vs the polarised ideal (Seiler 2010): 80% easy, 10% moderate, 10% hard
  • 🫁 VO2max & race predictor: effective VO2max (Daniels VDOT) + race-time predictions (Riegel), with predicted-vs-actual comparison
  • πŸ—“οΈ Training plans: periodized plan generator (race or weekly-volume goal), 80/20 distribution, +10% weekly progression, deload every 4th week, plus scheduled strength/mobility/rest support sessions
  • 🩹 Post-run check-in: RPE, session intent, surface, completion, readiness, sleep, stress, legs, a structured pain log (location/side/severity/timing/ trend) and a race flag β€” the injury-prevention loop
  • πŸ“… Training planner: a weekly view merging runs from your files with strength, mobility, cross-training and rest sessions (planned/completed/ skipped); support load is tracked separately from running load
  • β›½ Fuel & recovery (optional): a daily check-in for hydration, energy, appetite, steps, fueling and supplements (adherence only), linked to your weight and training load. Coach insights flag rapid weight loss, low energy and sedentary days β€” never medical or dietary prescriptions.
  • πŸ€– Assistant coach (optional): rule-based insight cards that always work offline, plus a conversational coach powered by a local Ollama model or any OpenAI-compatible endpoint. The LLM only receives compact derived metrics β€” never your GPS coordinates.

Home & accessibility

  • 🏠 Showcase home: all KPIs and the AI coach side by side, then schedule, race history and stats β€” the most important features first.
  • β™Ώ Accessible: WCAG 2.1/2.2 AA-oriented β€” skip link, landmarks, keyboard navigation, ARIA states, visible focus, reduced-motion support and contrast-safe colors (automated axe-core audit clean on every view).

Installation

python3 -m venv .venv
.venv/bin/pip install -r requirements.txt

Run

.venv/bin/python run.py

Then open http://127.0.0.1:8000 in your browser.

AI coach (optional)

The app is fully functional offline. The conversational coach is opt-in. Model metadata lives in models.json (base URL, model name, roles); keys live in .env (gitignored) or your OS environment. The coach panel has a model selector, so you can switch between your providers.

  1. Edit models.json with your OpenAI-compatible endpoints (apiBase, model, apiKeyEnv).
  2. Put the keys in .env:
    ALBERT_API_KEY=...
    GEMMA_API_KEY=...
  3. Restart and open Coaching β†’ Coach (or Home).

A local Ollama entry is included (ollama-local, no key needed) as an offline fallback.

Only derived metrics are sent (load, CTL/ATL/TSB, ACWR, intensity split, VO2max, recent summaries) β€” never raw GPS trackpoints or coordinates. Keys are resolved server-side and never sent to the browser.

See docs/AI_MODELS.md for fields, chatPath, roles and troubleshooting. Which model actually gives the better coaching answers is a separate question: docs/AI_BENCHMARK.md describes the local benchmark that compares them.

Thinking models (e.g. qwen3.5) spend tokens on hidden reasoning before answering. Raise maxTokens for that model or use a non-thinking model.

Project structure

open-running-coach/
β”œβ”€β”€ data/                 # Your activity files (KMZ / GPX / TCX)
β”œβ”€β”€ coach.db              # SQLite database (notes, goals) β€” created automatically
β”œβ”€β”€ .env.example          # Optional LLM coach configuration
β”œβ”€β”€ run.py                # Server launcher
β”œβ”€β”€ requirements.txt
└── app/
    β”œβ”€β”€ main.py           # FastAPI app + API routes
    β”œβ”€β”€ parsers/          # Format parsers (kmz.py, gpx.py, tcx.py, model.py)
    β”œβ”€β”€ connectors/       # External imports (OpenNutriTracker, generic CSV/JSON)
    β”œβ”€β”€ activity.py       # Basic statistics (distance, pace, elevation)
    β”œβ”€β”€ analysis.py       # Advanced analyses (splits, laps, zones, GAP, records…)
    β”œβ”€β”€ coaching.py       # Coaching analytics (load, fitness, ACWR, 80/20, VO2max, plans)
    β”œβ”€β”€ coach_llm.py      # Optional LLM coach (prompt + provider call)
    β”œβ”€β”€ store.py          # SQLite persistence (notes, goals, profile, RPE, feedback, sessions, plans)
    β”œβ”€β”€ config.py         # Configuration (pace zones, GAP, coaching models…)
    └── static/           # Frontend (HTML/CSS/JS + vendored Leaflet)

API

Method Route Description
GET /api/summary Global totals (runs, distance, time, elevation)
GET /api/activities List of runs (summary)
GET /api/activities/{id} Full detail (trackpoints + markers + analyses)
PUT /api/activities/{id}/note Save a run's note
GET /api/heatmap Points of all routes (heatmap)
GET /api/best-efforts All-time best efforts over reference distances
GET /api/matched-runs Groups of identical routes
GET /api/monthly Monthly statistics + comparison
GET/POST /api/goals List / create a goal
PUT/DELETE /api/goals/{id} Update / delete a goal
PUT /api/activities/{id}/rpe Set/clear a run's RPE (1–10)
GET /api/coaching/load Daily training-load series
GET /api/coaching/fitness CTL / ATL / Form (TSB) series
GET /api/coaching/acwr Injury risk (ACWR) series
GET /api/coaching/intensity Intensity distribution (80/10/10 target)
GET /api/coaching/vo2max Effective VO2max + race predictions
GET/POST /api/coaching/plans List / generate a training plan
GET/DELETE /api/coaching/plans/{id} Get / delete a training plan
GET/PUT /api/activities/{id}/feedback Get / save post-run feedback (+ RPE)
GET/POST /api/sessions List / create support sessions
PUT/DELETE /api/sessions/{id} Update / delete a support session
GET /api/nutrition List fuel & recovery check-ins
PUT /api/nutrition/{date} Upsert a daily check-in
DELETE /api/nutrition/{id} Delete a check-in
GET/POST /api/supplements List / add supplements
PUT/DELETE /api/supplements/{id} Update / delete a supplement
GET /api/coaching/insights Rule-based insight cards (offline)
GET /api/coaching/chat/status Whether the LLM coach is configured
POST /api/coaching/chat Ask the coach (session history in the body)
POST /api/coaching/review Generate a structured weekly review
POST /api/reload Re-read the data/ folder

Data format

The app reads three formats and normalises them all to the same internal model:

  • KMZ (OpenTracks) β€” a ZIP containing doc.kml; trackpoints, speed, heart rate, cadence, point type, segments and markers.
  • GPX β€” universal export (Strava "Download my data" gives one .gpx per activity). Trackpoints, elevation, time, heart rate/cadence from extensions; <trkseg> maps to segments. Strava's numeric <type> codes are mapped (9 β†’ running, 12 β†’ trail running).
  • TCX (Garmin) β€” explicit <Lap> elements map directly to segments; <Sport> maps to the activity type; heart rate, cadence and speed.

When the same run is present in several files (e.g. OpenTracks KMZ and a Strava GPX), a fingerprint (first/last GPS timestamps + distance) deduplicates them, keeping the richest copy. Formats without a speed array (GPX) fall back to positional speed for moving-time detection.

Heart rate and cadence are often absent; the app only displays them when present. FIT is not yet supported (planned for later).

Data folders & external connectors

Data is split by domain:

data/
β”œβ”€β”€ running/     # KMZ / GPX / TCX (OpenTracks, Strava, Garmin)
β”œβ”€β”€ workout/     # strength / mobility / cross-training exports
└── nutrition/   # nutrition exports (daily macros + weight)

Drop files in the matching folder and reload. Imports are idempotent: each file is tracked, so re-importing an unchanged file does nothing.

Recommended open-source apps (F-Droid first)

Domain F-Droid option Notes
Nutrition Waistline On F-Droid (GPL-3). OpenNutriTracker has the cleaner, documented JSON export but is not on F-Droid yet β€” install it from GitHub Releases (e.g. via Obtainium, which is on F-Droid) or Google Play.
Workout wger On F-Droid, self-hostable, with a REST API.

The OpenNutriTracker connector below works with the app regardless of how you install it (GitHub APK, Obtainium or Play Store). Waistline's diary CSV uses ; separators with localised headers, so it needs a dedicated adapter.

Nutrition β€” OpenNutriTracker

Export from Settings β†’ Export / Import App Data β†’ Export (JSON or CSV bundle) and put the .zip in data/nutrition/. The connector reads user_tracked_day (daily kcal / protein / carbs / fat) and weight_log (weight history). It only writes the macros and weights, so manual check-in fields are preserved.

A generic CSV is also accepted (, or ;, English or French headers, ISO or DD/MM/YYYY dates, optional unit suffixes):

Date;Calories (kcal);ProtΓ©ines (g);Glucides (g);Lipides (g);Poids (kg)
20/10/2026;2100;120;260;70;72,4

That is the Waistline diary-export style (semicolon + French headers + decimal comma), so Waistline works without a per-app adapter.

Workout β€” generic CSV/JSON

Export or map any workout app to this CSV (or a JSON list with the same fields) and put it in data/workout/:

date,kind,title,duration_min,rpe,focus,notes,status
2026-10-21,strength,Upper body,45,6,shoulders|triceps,Imported,completed
2026-10-23,mobility,Hips and calves,20,2,hip|calves,,completed
  • kind: strength Β· mobility Β· cross Β· rest Β· physio Β· other
  • focus: pipe-separated body areas
  • status: planned Β· completed Β· skipped

A wger-style JSON is also accepted:

{ "workouts": [ { "name": "Upper body", "sessions": [
  { "date": "2026-10-20", "duration_min": 45, "rpe": 6,
    "focus": ["shoulders", "triceps"],
    "exercises": [ { "name": "Bench press", "sets": 3, "reps": 8, "weight_kg": 60 } ] }
] } ] }

exercises are summarized into the session notes (e.g. "Bench press 3x8 @ 60 kg").

wger over its REST API (optional)

Instead of exporting wger by hand, the app can pull an account: Import β†’ wger over its REST API β†’ Connect to wger. It reads weight, nutrition and strength sessions once β€” GET only, nothing written back β€” and stores them as ordinary exports in data/, so a second pull replaces the first result.

Mint a key under User settings β†’ API key on your wger instance β€” either the permanent token or the 120-day JWT refresh token β€” and put it in .env:

WGER_TOKEN=your-personal-api-key
# Self-hosted instance only:
# WGER_BASE_URL=https://wger.example.org

The key stays on the server (never logged, never in the database, never sent to the browser). From a terminal the same pull is .venv/bin/python -m app.connectors.wger_api. wger has no GPS tracks, so running files still come from OpenTracks, Strava or Garmin. Details, skipped items and error messages: docs/WGER.md.

Sample files are included so you can see the result immediately: examples/nutrition/waistline-sample.csv and examples/workout/wger-sample.json β€” copy them into the matching data/ folder to try them. Sessions appear in the Planner. Re-importing a changed file replaces the sessions it previously created.

Development

.venv/bin/pip install -r requirements-dev.txt   # pytest + ruff
.venv/bin/python -m pytest                      # test suite
.venv/bin/ruff check .                          # lint
.venv/bin/ruff format --check .                 # formatting

The suite covers the coaching models (load, CTL/ATL/TSB, ACWR, intensity, VDOT, Riegel, plans), the analysis layer (zones, GAP, splits, GPS robustness, matched runs), every parser and every connector.

No real activity data is used. All fixtures are generated from tests/fixtures/:

Fixture Reproduces
make_gpx.py Strava export: numeric <type>, no speed sensor, nested HR extension
make_tcx.py Garmin export: one <Lap> per lap, HeartRateBpm, TPX/Speed
make_kmz.py OpenTracks export: <MultiTrack>, parallel arrays, IDLE samples, no HR
make_nutrition.py Waistline CSV (French headers, decimal comma) and JSON: meals, steps, weight
make_workout.py wger JSON and generic CSV: sessions with exercise summaries
track.py One canonical run rendered into all three activity formats

test_formats.py parses the same run from GPX, TCX and KMZ and asserts that distance, moving time, pace zones and GAP agree, so a parser cannot drift away from the shared Activity model.

To try the app on synthetic data in a fresh clone:

.venv/bin/python examples/make_demo_data.py

It writes 5 activities across the three formats plus nutrition, weight, steps and workouts into data/. Delete those files when you switch to real data.

CI (.github/workflows/ci.yml) runs three jobs: lint and format, the suite on Python 3.11–3.14, and a smoke job that generates the demo dataset, boots uvicorn and probes every API endpoint.

Documentation

  • docs/GETTING_STARTED.md β€” quick start for beta testers
  • docs/USER_MANUAL.md β€” how to use the app (for beta testers)
  • docs/AI_MODELS.md β€” model registry (models.json) and key handling
  • docs/AI_BENCHMARK.md β€” comparing models against five prompts and three athlete situations
  • docs/DEPLOYMENT.md β€” packaging as a standalone local app
  • docs/SALES_PROPOSAL_B2C.md / docs/SALES_PROPOSAL_B2B.md β€” product proposals
  • docs/research/ β€” cited notes behind every formula (01–09) and the competitive landscape (10)

Notes & limitations

  • Distance: the computation rejects aberrant GPS jumps (implied speed

    10 m/s) and falls back to the distance integrated from the recorded speed, which is more reliable. Some activities contain positional noise that can still slightly inflate the distance.

  • Elevation & GAP: GPS altitude is noisy; it is smoothed before computing. The GAP relies on a simplified model (linear correction per % of grade) and remains an estimate.
  • Pace zones: "standard" bounds (adjustable in app/config.py, PACE_ZONES) as long as heart-rate data is unavailable.
  • Matched runs: heuristic detection (grid similarity + distance); it may group two close-but-not-identical routes.

License

Released under the GNU Affero General Public License v3.0 β€” see LICENSE for the full text.

In short: you may use, study, modify and redistribute this software, including commercially, provided that derivative works stay under the same license and that anyone interacting with a modified version over a network is offered its source code.

Third-party components bundled in app/static/vendor/ keep their own licenses (Leaflet: BSD-2-Clause, Leaflet.heat/simpleheat: MIT) β€” see THIRD_PARTY_NOTICES.md.

Copyright (c) 2026 Mathis Certenais.

About

Local-first running intelligence: import Strava, Garmin and OpenTracks, get injury-risk, 80/20 and VO2max analytics, and an AI coach that plans your next race.

Topics

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages