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

3.1 KiB
Raw Blame History

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 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.