Local-first personal fitness dashboard with an LLM coach.
Eine Frage im Zentrum: „Wo werde ich besser, wo schlechter?"
Milon zieht deine über viele Apps verstreuten Fitnessdaten an einem Ort zusammen — Körperdaten, Schritte, Laufen & Radfahren (Health Connect), Krafttraining (Hevy) und Ernährung (FDDB) — und beantwortet die eine Frage, die zählt: werde ich besser oder schlechter? Vier Bereiche (Körper · Laufen · Kraft + ein LLM-Coach) zeigen Trends, Prognosen und ehrliche Auswertungen.
Alles läuft lokal auf dem eigenen Rechner — die Gesundheitsdaten verlassen die Maschine nie. UI deutsch, Code englisch. Bewusst schlank.

Kraft-Analyse — drift-freier Gesamtstärke-Index, Stärke ↔ Energiebilanz, Tonnage & RPE.

Trainings-Konsistenz (Übersicht) — GitHub-Style-Heatmap über 365 Tage + aktueller Streak.
| Bereich | Highlights |
|---|---|
| Übersicht | Wochenvergleich (rollierende 7 Tage), Konsistenz-Heatmap + Streak, PR-Trophäen, letzte Aktivitäten |
| Körper | Gewichts-/KFA-Trends (roh · 7-Tage · EWMA), adaptives TDEE, Magermasse/Recomp, Komposition-Prognose |
| Ernährung | kcal + Makros (Protein/KH/Fett), Protein Ø/Tag vs. Ziel, Defizit vs. TDEE |
| Gesundheit | Schritte (Galaxy-Watch-genau), Radfahren |
| Laufen | Wochenvolumen, Pace-Trend, VO₂max |
| Kraft | alle Übungen nach Muskelgruppe, e1RM/Tonnage/RPE, Übungs-Detailseiten, drift-freier Gesamtstärke-Index, Stärke ↔ Energiebilanz |
| Fortschritt | Foto-Timeline mit Browser-Crop (3:4) + Silhouetten-/Pose-Schablonen |
| Coach | LLM-Coach mit Tool-Calling (ruft die echten Kennzahlen selbst ab) + Kosten/Token-Statistik |
| Einstellungen | Keys maskiert, Modellwahl, Scheduler-Toggle |
Zwei analytische Schmuckstücke:
- Gesamtstärke-Index — ein Wert für „werde ich insgesamt stärker?". Wöchentlich aufgelöst und drift-frei: ein monatlicher, muskel-balancierter, verketteter e1RM-Index als Rückgrat plus eine monatsverankerte Wochenspur (Methodik per Design-Panel validiert & adversarial reviewed).
- Stärke ↔ Energiebilanz — korreliert den Index mit TDEE/Defizit und sagt ehrlich, was belastbar ist (Woche-zu-Woche ≈ 0) und was nur Schein-Trend (Niveau-Korrelation), plus ein Phasen-Read (Cut/Recomp).
„Eine Abfrageschicht, drei Gesichter": die metrics/-Funktionen sind die einzige Wahrheit und
speisen REST, den Coach und den MCP-Server — alle lesen dieselbe lokale SQLite-DB.
flowchart LR
HC["Health Connect"] --> ING
HV["Hevy API"] --> ING
FD["FDDB"] --> ING
ING["ingest/ (inkrementell)"] --> DB[("SQLite · data/tracker.db")]
DB --> M["metrics/ Schicht"]
M --> REST["REST /metrics/*"]
M --> CO["LLM-Coach (Tool-Calling)"]
M --> MCP["MCP-Server (20 Tools)"]
REST --> UI["Next.js Dashboard"]
- Backend
server/— FastAPI · SQLModel/SQLite · pandas/numpy · APScheduler · FastMCP · OpenAI-SDK (OpenRouter) - Frontend
client/— Next.js 16 (App Router, TS) · Tailwind v4 · Inline-SVG-Charts (keine Chart-Lib) - Coach — OpenRouter (OpenAI-kompatibel), Context-Injection und Tool-Calling
- Design
design/— Studie „Klar & Klinisch" + gpt-image-2-Asset-Tooling
| Quelle | Was | Wie / benötigte API |
|---|---|---|
| Health Connect (Android) | Gewicht, KFA, Schritte, Laufen, Radfahren, VO₂max | automatischer Drive-Pull der täglichen Export-Zip (öffentlicher Datei-Link → HC_DRIVE_FILE_ID, keylos via gdown) oder Zip manuell nach data/incoming/ legen |
| Hevy | Krafttraining (Sätze, Gewicht, Reps, RPE) | offizielle Hevy-API (HEVY_API_KEY), echter Inkrement-Sync via /v1/workouts/events |
| FDDB | Ernährung (kcal + Makros) | Login-Cookie fddb (oder Auto-Login mit FDDB_USER/FDDB_PW), CSV-Export |
| OpenRouter | LLM-Coach | OpenAI-kompatibler Endpunkt (OPENROUTER_API_KEY, Modell frei wählbar) |
Alle Quellen sind optional — Milon läuft auch nur mit einer davon. Importe sind idempotent
(?full=true erzwingt eine Voll-Reconciliation).
Vorlagen kopieren und echte Werte eintragen — die echten .env werden nie committet (gitignored):
cp .env.example .env # OPENAI_API_KEY (nur Design-Asset-Generierung)
cp server/.env.example server/.env # App-Secrets (siehe Tabelle)| Datei | Variable | Zweck |
|---|---|---|
server/.env |
OPENROUTER_API_KEY |
LLM-Coach (OpenRouter) |
OPENROUTER_MODEL |
Modell-ID, z. B. deepseek/deepseek-v4-flash |
|
HEVY_API_KEY |
Hevy-Krafttraining-Sync | |
FDDB_USER / FDDB_PW |
FDDB-Auto-Login (Ernährung) | |
FDDB_COOKIE |
alternativ: fddb-Cookie (userid,token) |
|
HC_DRIVE_FILE_ID |
Health-Connect-Auto-Pull: Datei-ID aus dem Drive-Freigabelink der Export-Zip | |
DATABASE_URL |
optional, Default sqlite:///./data/tracker.db |
|
.env (Root) |
OPENAI_API_KEY |
nur für gpt-image-2-Design-Assets |
client/.env |
NEXT_PUBLIC_API_URL |
optional (Default: /api-Proxy) |
cd server
python -m venv .venv && .venv/Scripts/pip install -e . # Linux/Mac: .venv/bin/pip
.venv/Scripts/python -m uvicorn app.main:app --host 0.0.0.0 --port 8000 # Docs: /docscd client
npm install && npm run dev # http://localhost:3000In VS Code gibt es fertige Tasks (Start: Server + Client, Design: HTML-Server). Das Handy
erreicht das Dashboard im Heim-WLAN über http://<PC-IP>:3000 — das Frontend proxyt /api/*
serverseitig ans Backend (kein CORS, keine Firewall-Freigabe für :8000 nötig).
curl -X POST http://localhost:8000/ingest/hevy # Hevy
curl -X POST http://localhost:8000/ingest/fddb # FDDB
# Health Connect aus Google Drive ziehen (HC_DRIVE_FILE_ID gesetzt):
curl -X POST http://localhost:8000/ingest/health-connect-pull
# ...oder die Export-Zip manuell nach data/incoming/ legen und:
curl -X POST http://localhost:8000/ingest/health-connect
curl -X POST "http://localhost:8000/ingest/refresh" # alle Quellen + StatusAb Phase 2 erledigt das ein Scheduler automatisch (Hevy alle 6 h, FDDB täglich, HC-Drive-Pull
täglich 05:00 + Ordner-Scan). Für den Drive-Pull: die Export-Zip in Drive auf „Jeder mit dem
Link" freigeben, den Datei-Link kopieren und die ID daraus als HC_DRIVE_FILE_ID in server/.env.
Statt Backend und Frontend einzeln zu starten, gibt es ein schlankes Zwei-Container-Setup für
Docker Desktop. Voraussetzung: server/.env existiert (siehe Schritt 1).
docker compose up -d --build # baut & startet server + client
docker compose logs -f # Logs verfolgen
docker compose down # stoppenAufruf danach: http://localhost (Port 80). Im Heim-WLAN erreicht das Handy das Dashboard über
http://<PC-IP> — z. B. http://192.168.0.26.
- Nur das Frontend ist von außen erreichbar. Der FastAPI-Server hat keinen Host-Port; er lebt
nur im internen Compose-Netz (Servicename
server) und wird vom Frontend serverseitig unter/api/*angesprochen. Die Host-Ports 3000 und 8000 bleiben frei für andere Projekte. - Frontend-Port ist per
WEB_PORTüberschreibbar (z. B.WEB_PORT=8080in einer.envim Repo-Root, falls Port 80 belegt ist). - Persistenz:
data/(SQLite-DB,incoming/, Fortschritts-Fotos) undserver/.envsind als Volumes gemountet — Daten und Secrets überleben Rebuilds; die Settings-Seite schreibt nachserver/.envzurück. - RAM: ~150 MB im Leerlauf (Server ~110 MB, Client ~35 MB), kurze Spitzen beim großen Health-Connect-Import. Das Grundrauschen von Docker Desktop selbst (WSL2-VM, ~1–2 GB) kommt hinzu.
- Der MCP-Server läuft nicht im Container — er liest
data/tracker.dbdirekt und wird über.mcp.jsongestartet (siehe Beispiel-Workflows).
Der Next-Rewrite-Proxy backt sein Backend-Ziel zur Build-Zeit ein; das Compose reicht es daher als Build-Arg
API_PROXY_TARGET=http://server:8000durch. Wer den Server unter anderem Namen/Port fährt, baut den Client neu (docker compose up -d --build client).
- Frag deine Daten: Reiter Coach → „Wie ist mein Kraft-Trend diese Woche?" — der Coach ruft per Tool-Calling die echten Kennzahlen ab und antwortet ehrlich-motivierend (Markdown).
- Täglicher/Wöchentlicher Report: ein Klick erzeugt einen kompakten Lagebericht; Kosten/Token werden je Report mitgeschrieben.
- Stärke über Zeit: Reiter Kraft → Gesamtstärke-Index mit Umschalter 1M/3M/6M/12M, dazu die Treiber-/Bremse-Übungen und die Stärke-↔-Energiebilanz-Karte.
- Aus Claude/Cursor heraus (MCP): der MCP-Server (
python -m app.mcp.server, registriert über.mcp.json) exponiert 20 Tools über dieselbe Datenschicht — die Fitnessdaten lassen sich so direkt im Editor/Chat befragen.
server/ FastAPI: ingest/ · metrics/ · coach/ · mcp/ · sync/ · api/
client/ Next.js: app/ (9 Seiten) · components/ · lib/
design/ „Klar & Klinisch"-Studie + Foto-Schablonen + Asset-Tooling
data/ tracker.db + incoming/ (lokal, gitignored)
docs/ README-Bilder
docker-compose.yml · server/Dockerfile · client/Dockerfile schlankes Container-Setup
- Secrets liegen ausschließlich in
.env(jede Ebene gitignored); committet werden nur die*.env.example-Vorlagen mit leeren Platzhaltern. - Gesundheitsdaten (
data/,*.db, Fortschritts-Fotos) sind gitignored und werden nie committet. - Local-first: keine Cloud, keine Telemetrie. Der einzige ausgehende Aufruf ist der LLM-Coach (OpenRouter) — und nur, wenn du ihn nutzt.
Veröffentlicht unter der CC0 1.0 Universal Public-Domain-Dedication — gemeinfrei, ohne Gewährleistung. Du darfst alles damit machen: nutzen, ändern, weitergeben, auch kommerziell, ohne Namensnennung. (Marken-/Patentrechte sind davon nicht berührt.)
Phasen 1–3 umgesetzt: Ingest (inkl. Health-Connect-Auto-Pull aus Google Drive) + Metriken +
Dashboards · Auto-Syncs (Scheduler) · MCP-Server. Phase 4 (Hosting) angefangen: schlankes
docker-compose-Setup für Docker Desktop (nur das Frontend nach außen, Server intern).
Source of Truth für Architektur & Plan: ARCHITECTURE.md. Projekt-Memory: CLAUDE.md.

