Initial commit: GPX ride viewer with local-LLM coaching insights
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
# gpxport
|
||||
|
||||
A local web app for visualising Garmin cycling GPX files with AI coaching insights powered by a local LLM.
|
||||
|
||||

|
||||
|
||||
## Features
|
||||
|
||||
- **Route map** — Leaflet.js map with per-segment polylines coloured by heart-rate intensity zone
|
||||
- **Elevation & HR charts** — Chart.js profiles plotted against cumulative distance
|
||||
- **Stats sidebar** — distance, duration, speed, elevation gain, avg/max HR, HR zone breakdown (Karvonen)
|
||||
- **AI coaching insights** — streaming post-ride feedback from a local LLM (via OpenAI-compatible API)
|
||||
- **Garmin sync** — fetch new rides directly from Garmin Connect, including MFA support
|
||||
- **Imperial/metric toggle** — persisted in `localStorage`
|
||||
|
||||
## Requirements
|
||||
|
||||
- Python 3.14+
|
||||
- [uv](https://github.com/astral-sh/uv)
|
||||
- A local OpenAI-compatible LLM server (e.g. [llama.cpp](https://github.com/ggerganov/llama.cpp), [Ollama](https://ollama.com))
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
# 1. Clone and install dependencies
|
||||
git clone https://github.com/yourname/gpxport
|
||||
cd gpxport
|
||||
uv sync
|
||||
|
||||
# 2. Drop your GPX files into garmin_gpx_exports/
|
||||
# (or use the in-app "Fetch from Garmin" button)
|
||||
|
||||
# 3. Start the server
|
||||
LLAMACPP_BASE_URL=http://localhost:8080/v1 uv run uvicorn app:app --reload
|
||||
```
|
||||
|
||||
Open http://localhost:8000 in your browser.
|
||||
|
||||
## Configuration
|
||||
|
||||
| Env var | Default | Description |
|
||||
|---|---|---|
|
||||
| `LLAMACPP_BASE_URL` | `http://localhost:8080/v1` | Base URL of your OpenAI-compatible LLM endpoint |
|
||||
|
||||
The model name is auto-discovered from `GET /v1/models` at startup. If the endpoint is unreachable, insights generation is still available — only that feature degrades.
|
||||
|
||||
## GPX files
|
||||
|
||||
Place `.gpx` files exported from Garmin Connect (or any GPX-compliant device) in the `garmin_gpx_exports/` directory. The app parses all files at startup. Supported data:
|
||||
|
||||
- Track points with `lat`/`lon`/`ele`/`time`
|
||||
- Heart rate via the Garmin extension namespace (`ns3:hr`)
|
||||
|
||||
Activities with no track points (e.g. indoor rides) are listed with an "Indoor" badge and shown without a map or charts.
|
||||
|
||||
## Standalone GPX downloader
|
||||
|
||||
`main.py` is a standalone script for bulk-downloading your Garmin Connect cycling activities as GPX files without running the web server:
|
||||
|
||||
```bash
|
||||
uv run python main.py
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
| Method | Path | Description |
|
||||
|---|---|---|
|
||||
| `GET` | `/api/rides` | Summary list of all rides (no track points) |
|
||||
| `GET` | `/api/rides/{id}` | Full ride metadata + cached insight |
|
||||
| `GET` | `/api/rides/{id}/points` | Full lat/lon/ele/hr array |
|
||||
| `POST` | `/api/rides/{id}/insights?rhr=60` | Stream AI coaching insight |
|
||||
| `POST` | `/api/insights/generate-all?rhr=60` | SSE stream — generate insights for all rides |
|
||||
| `POST` | `/api/fetch-rides` | SSE stream — fetch new rides from Garmin Connect |
|
||||
| `POST` | `/api/mfa` | Submit MFA code for an in-progress Garmin session |
|
||||
|
||||
## HR zone boundaries (Karvonen)
|
||||
|
||||
Zones are calculated from your resting HR (configurable in the UI) and the lifetime max HR observed across all rides:
|
||||
|
||||
| Zone | % of HRR |
|
||||
|---|---|
|
||||
| Z1 | < 50% |
|
||||
| Z2 | 50–60% |
|
||||
| Z3 | 60–70% |
|
||||
| Z4 | 70–80% |
|
||||
| Z5 | > 80% |
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
Reference in New Issue
Block a user