Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3.1 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Commands
uv run uvicorn app:app --reload # start dev server at http://localhost:8000
uv run --env-file .env uvicorn app:app --reload # load env vars from .env file
uv sync # install/sync dependencies
uv add <package> # add a new dependency
Set LLAMACPP_BASE_URL=http://host:port/v1 to point at a local LLM (inline, exported, or via .env).
Architecture
Two-file app: a FastAPI backend (app.py) and a single-page frontend (static/index.html).
app.py parses all GPX files in garmin_gpx_exports/ at startup into an in-memory dict (_rides), keyed by filename stem. Stats (haversine distance, elevation gain/loss, HR zones) are computed once at load time. API routes:
GET /api/rides— summary list (no track points)GET /api/rides/{id}— full ride metadata + cached insightGET /api/rides/{id}/points— full lat/lon/ele/hr arrayPOST /api/rides/{id}/insights— streams AI coaching responsePOST /api/insights/generate-all— SSE stream, generates insights for all rides in chronological orderPOST /api/fetch-rides— SSE stream, fetches new rides from Garmin Connect (supports MFA)POST /api/mfa— submits MFA code for an in-progress Garmin session
AI integration uses the OpenAI Python SDK pointed at a local LLM endpoint (LLAMACPP_BASE_URL env var, defaults to http://localhost:8080/v1). The model name is auto-discovered from GET /v1/models at startup (falls back to "local-model"). If the endpoint is unreachable at startup a warning is logged and the app continues — only insight generation is affected. The chunk reader handles both content and reasoning_content delta fields for reasoning models.
Insights cache is persisted to insights.json (gitignored) and loaded at startup. The prompt includes the previous ride's cached insight so the coach can reference its own prior advice.
static/index.html is a self-contained SPA (no build step). Leaflet.js draws the route as per-segment polylines colored by HR intensity zone. Chart.js renders elevation and HR profiles against cumulative distance. AI insights stream via fetch + ReadableStream. Units (km/mi) toggle and resting HR are persisted in localStorage.
main.py is a standalone CLI script for bulk-downloading Garmin Connect GPX files without running the web server.
GPX data notes
- Garmin extension namespace for HR:
http://www.garmin.com/xmlschemas/TrackPointExtension/v1(ns3:hr) - Activities with an empty
<trkseg/>(e.g. indoor rides) gethas_gps: false— frontend shows no map or charts - HR zone boundaries (fixed, used for API summary): Z1 <120, Z2 120–140, Z3 140–160, Z4 160–180, Z5 >180 bpm
- Karvonen zones (used in the UI and AI prompt) are computed from resting HR input + lifetime max HR observed across all rides
Logging
httpx, httpcore, and openai loggers are set to WARNING to suppress connection-level debug noise. The bikeslop logger runs at DEBUG.