Files
bikeslop/CLAUDE.md
T
2026-08-06 21:13:52 -04:00

47 lines
3.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Commands
```bash
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 insight
- `GET /api/rides/{id}/points` — full lat/lon/ele/hr array
- `POST /api/rides/{id}/insights` — streams AI coaching response
- `POST /api/insights/generate-all` — SSE stream, generates insights for all rides in chronological order
- `POST /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) get `has_gps: false` — frontend shows no map or charts
- HR zone boundaries (fixed, used for API summary): Z1 <120, Z2 120140, Z3 140160, Z4 160180, 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.