Skip to content

analyticalmonk/strava-reporting

Repository files navigation

Strava training infographic

A small local pipeline that pulls a year of your Strava activities (and their photos), then renders two static outputs:

  • report.html - detailed tables and charts of your training volume.
  • infographic.html - an editorial "How I trained" poster (1080px wide), which you can screenshot to a PNG.

Everything runs locally against your own Strava account. The only file you edit to make it yours is config.py.

What you'll need

  • Python 3 and pip.
  • A Strava account (and a free Strava API application - set up below).
  • Optional: a coding agent (Claude Code, Codex, etc.) - only if you want the poster to break down your strength work by reading workout-tracker screenshots. See Optional: workout-photo vision.
  • Optional: Google Chrome + Pillow (pip install pillow) - only to render the poster to a PNG. The HTML renders fine in any browser without them.

Setup

1. Create a Strava API application

  1. Go to https://www.strava.com/settings/api.
  2. Create an application. For Authorization Callback Domain enter localhost (this matches the local OAuth flow in auth_setup.py).
  3. Copy your Client ID and Client Secret - you'll paste them in the next step.

2. Install dependencies

pip install -r requirements.txt

3. Authorize (one time)

python auth_setup.py

This prompts for your Client ID and Secret, opens a browser to authorize the activity:read_all scope, and writes a .env file with your credentials and a refresh token. .env is gitignored - never commit it.

4. Make it yours

Edit config.py:

  • START_DATE - the start of your training window (change the year).
  • HEADLINE, SUBTITLE, TAGLINE, PAGE_TITLE - the poster's editorial copy.
  • RACES - your race milestones (Strava's activity feed has no description field, so named races are listed by hand). Set this to [] if you have none.
  • EXCLUDED_TITLE_KEYWORDS - workout titles to leave out of strength/mobility counts.

Run

python fetch_strava.py        # activities + photos -> cache/ (idempotent)
# (optional) vision step - see below
python build_report.py        # -> report.html
python build_infographic.py   # -> infographic.html

fetch_strava.py writes everything into cache/ and is resumable - re-running it skips already-downloaded data.

Optional: workout-photo vision

If your Strava activities include workout-tracker screenshots, you can have an agent read them so the poster's "What Strength Covered" section is populated. After fetch_strava.py runs, it prints any photos lacking a sidecar. For each photo in cache/photos/, your agent reads the image and writes a JSON sidecar to cache/vision/<activity_id>_<index>.json with this shape:

{
  "type": "workout",
  "exercises": [
    {"name": "Goblet squat", "sets": 4, "reps": "8-10", "weight": "24kg",
     "muscle_groups": ["legs"]}
  ],
  "notes": "optional"
}
  • Use "type": "other" with an empty exercises list for non-workout photos (e.g. scenery). Always write a sidecar so the photo isn't re-listed.
  • muscle_groups drive strength-vs-mobility classification: use push / pull / legs for strength logs and mobility for mobility/stability lists.

This step is entirely optional. With no sidecars, the infographic still renders - the "What Strength Covered" section is just empty, and strength sessions tagged in Strava as WeightTraining are still counted.

Optional: render the poster to a PNG

infographic.html is self-contained (fonts are embedded), so you can open it in a browser and screenshot it. To do it from the command line with headless Chrome (and pip install pillow to crop the trailing margin):

google-chrome --headless --disable-gpu --no-sandbox --hide-scrollbars \
  --force-device-scale-factor=2 --screenshot=_raw.png --window-size=1080,3200 \
  "file://$PWD/infographic.html"
# then crop _raw.png down to the poster (background #cfc9ba) with Pillow

Notes and limitations

  • The poster surfaces these activity types: running (Run/TrailRun), strength, mobility, swimming, yoga, and walks/hikes. Other sports (e.g. Pickleball) are counted in report.html but don't get their own row on the poster.
  • Strength-vs-mobility classification is title-first and keys off the muscle_groups vocabulary above - keep it consistent when writing sidecars.
  • This assumes you have at least some activities in the configured window.
  • There are no tests or build tooling; the scripts read/write cache/ and are idempotent. CLAUDE.md has additional notes aimed at coding agents.

About

Local pipeline that turns Strava activities into a shareable training report and infographic poster.

Topics

Resources

Stars

Watchers

Forks

Contributors

Languages