diff --git a/.claude/README.md b/.claude/README.md new file mode 100644 index 0000000..36b294e --- /dev/null +++ b/.claude/README.md @@ -0,0 +1,61 @@ +# Mitai Jinkendo – Claude-Arbeitsumgebung + +Dieser Ordner ist der **primäre Orientierungspunkt** für Claude Code / Cursor-Agenten. Dauerhafte Spezifikationen liegen unter **`.claude/docs/`** (`functional/`, `technical/` …). Das Repository **`docs/`** (Projektroot) ist u. a. für **`docs/issues/`** – nicht verwechseln. + +--- + +## Pflichtlektüre (vor größeren Änderungen) + +| Reihenfolge | Datei | +|-------------|--------| +| 1 | `../CLAUDE.md` | +| 2 | **`rules/DOCUMENTATION.md`** – Ablage- und Dokumentationsregeln | +| 3 | `rules/ARCHITECTURE.md`, `rules/CODING_RULES.md`, `rules/LESSONS_LEARNED.md` | +| 4 | Issue-Landkarte: **`.claude/docs/GITEA_ISSUES_INDEX.md`** | + +Themen mit UI/Nav/PWA: siehe `../docs/issues/GUI_IA_ADMIN_NAV_2026-04-05.md` (im **Projekt**-`docs/`, nicht hier). + +--- + +## Verzeichnisbaum (Kurz) + +``` +.claude/ +├── README.md ← Diese Datei +├── rules/ ← Verbindliche Regeln (versioniert, wenn konfiguriert) +├── docs/ ← Spezifikationen + Arbeitspapiere +│ ├── functional/ ← Fachlich (WAS) +│ ├── technical/ ← Technisch (WIE) +│ ├── architecture/ ← Querschnitt +│ ├── working/ ← Zwischenstände, Analysen (nicht allein „Wahrheit“) +│ ├── audit/ ← Audits, Matrizen +│ ├── README.md ← Katalog aller Spec-Dateien +│ └── GITEA_ISSUES_INDEX.md +├── commands/ ← Slash-Befehle (optional versioniert) +├── task/ ← Lokale Arbeitspakete (meist nur lokal) +├── handover/ ← Session-Notizen (meist nur lokal) +├── skills/ ← Skills (lokal) +└── settings.json ← Editor-Konfiguration (lokal) +.settings.local.json ← Nicht committen +``` + +--- + +## Wo neue Inhalte ablegen + +Siehe **`rules/DOCUMENTATION.md`** (Tabelle „Ablagepflicht“). Kurz: + +- Langfristige **fachliche** Spec → `docs/functional/` (innerhalb von `.claude`, siehe Baum oben). +- Langfristige **technische** Spec → `docs/technical/`. +- **Session-Analyse / grobe Notizen** → `docs/working/`. +- **Epic/Issue lang** → `../docs/issues/` im Repository. + +--- + +## Projekt-`docs/` (Repository) + +`../docs/issues/` enthält issue-nummerierte Markdown-Dateien und Abnahme-Dokumente – **parallel** zu diesem Ordner, für alle sichtbar die das Repo klonen. + +--- + +**Stand:** 2026-04-08 diff --git a/.claude/commands/add-endpoint.md b/.claude/commands/add-endpoint.md new file mode 100644 index 0000000..db4e452 --- /dev/null +++ b/.claude/commands/add-endpoint.md @@ -0,0 +1,32 @@ +# Add Endpoint + +Füge einen neuen API-Endpoint zum Backend hinzu. + +## Template: +```python +@app.get("/api/mein-endpoint") +def mein_endpoint( + limit: int = 100, + session: dict = Depends(require_auth) # ← IMMER als separater Parameter! +): + pid = session['profile_id'] + with get_db() as conn: + rows = conn.execute( + "SELECT * FROM meine_tabelle WHERE profile_id=? LIMIT ?", + (pid, limit) + ).fetchall() + return [r2d(r) for r in rows] +``` + +## Regeln: +- `session: dict = Depends(require_auth)` IMMER als letzter/separater Parameter +- NIEMALS innerhalb von `Header(default=None, ...)` einbetten +- Profile-ID immer aus `session['profile_id']` – nie aus Header +- Admin-Endpoints: `session=Depends(require_admin)` +- Rate-Limiting für sensitive Endpoints: `@limiter.limit("5/minute")` + +## Nach dem Endpoint: +api.js um neue Methode ergänzen: +```javascript +meinEndpoint: (params) => req(`/mein-endpoint?limit=${params}`), +``` diff --git a/.claude/commands/check-issues.md b/.claude/commands/check-issues.md new file mode 100644 index 0000000..e85de8a --- /dev/null +++ b/.claude/commands/check-issues.md @@ -0,0 +1,174 @@ +# Check Issues + +Prüfe offene Gitea Issues und schlage Fixes vor. + +## Automatische Ausführung + +Dieser Skill wird bei jeder Session automatisch ausgeführt und zeigt: +- Anzahl offener Issues +- High-Priority Issues (Bugs + Features) +- Vorschlag welches Issue als nächstes angegangen werden sollte + +## Workflow + +### 1. Offene Issues abrufen + +```bash +curl -s -H "Authorization: token $GITEA_TOKEN" \ + "http://192.168.2.144:3000/api/v1/repos/Lars/mitai-jinkendo/issues?state=open&labels=develop" \ + | python3 -c " +import sys, json +issues = json.load(sys.stdin) +print(f'📋 {len(issues)} offene Issues in develop:\n') +for issue in issues: + labels = {l['name'] for l in issue['labels']} + priority = 'high' if 'high' in labels else 'low' if 'low' in labels else 'medium' + itype = 'bug' if 'bug' in labels else 'feature' if 'feature' in labels else 'other' + icon = '🐛' if itype == 'bug' else '✨' + prio_icon = '🔥' if priority == 'high' else '💤' if priority == 'low' else '📌' + print(f\"{icon} {prio_icon} Issue #{issue['number']}: {issue['title']}\") + print(f\" {issue['html_url']}\n\") +" +``` + +### 2. Issues nach Priorität sortieren + +**High Priority Issues (Bugs + Features):** +- Bugs mit Label `bug` + `high` +- Features mit Label `feature` + `high` + +**Low Priority:** +- Features mit Label `low` + +### 3. Issue-Details anzeigen + +```bash +# Für ein spezifisches Issue (z.B. #13): +curl -s -H "Authorization: token $GITEA_TOKEN" \ + http://192.168.2.144:3000/api/v1/repos/Lars/mitai-jinkendo/issues/13 \ + | jq -r '.body' +``` + +### 4. Fix vorschlagen + +**Frage den User:** +- "Soll ich Issue #X angehen?" +- Zeige Beschreibung + betroffene Dateien +- Schätze Aufwand (Quick Win < 30min, Medium < 2h, Large > 2h) + +### 5. Bei Fix: Issue referenzieren im Commit + +**WICHTIG:** Verwende dieses Format für automatisches Issue-Schließen: + +```bash +git commit -m "fix: resolve training type creation error (#13) + +- Fixed POST endpoint validation +- Improved frontend error handling + +Closes #13" +``` + +**Gitea Keywords die Issues automatisch schließen:** +- `Closes #123` +- `Fixes #123` +- `Resolves #123` + +Das Issue wird automatisch geschlossen wenn der Commit in den main Branch gemerged wird! + +### 6. Issue manuell schließen (falls nötig) + +```bash +curl -X PATCH -H "Authorization: token $GITEA_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"state":"closed"}' \ + http://192.168.2.144:3000/api/v1/repos/Lars/mitai-jinkendo/issues/13 +``` + +## Issue-Wartung (Standard-Task) + +**WICHTIG:** Bei jeder Session Issue-Liste pflegen: + +### 1. Veraltete Issues schließen + +Wenn Features implementiert wurden, zugehörige Issues schließen: + +```bash +# Issue schließen +curl -s -X PATCH -H "Authorization: token $GITEA_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"state":"closed"}' \ + "http://192.168.2.144:3000/api/v1/repos/Lars/mitai-jinkendo/issues/17" > /dev/null + +# Kommentar mit Commit-Referenz hinzufügen +curl -s -X POST -H "Authorization: token $GITEA_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"body":"✅ Erledigt in v9d Phase 2d. Commits: 548a5a4, bf87e03"}' \ + "http://192.168.2.144:3000/api/v1/repos/Lars/mitai-jinkendo/issues/17/comments" > /dev/null +``` + +### 2. Duplikate entfernen + +```bash +# Duplikat schließen +curl -s -X PATCH -H "Authorization: token $GITEA_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"state":"closed"}' \ + "http://192.168.2.144:3000/api/v1/repos/Lars/mitai-jinkendo/issues/19" > /dev/null + +# Duplikat-Kommentar +curl -s -X POST -H "Authorization: token $GITEA_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"body":"Duplikat von #21"}' \ + "http://192.168.2.144:3000/api/v1/repos/Lars/mitai-jinkendo/issues/19/comments" > /dev/null +``` + +### 3. Issue-Prioritäten aktualisieren + +```bash +# Label ändern (z.B. von medium zu high) +curl -s -X PUT -H "Authorization: token $GITEA_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"labels":[2,3,5]}' \ + "http://192.168.2.144:3000/api/v1/repos/Lars/mitai-jinkendo/issues/15/labels" +``` + +**Checkliste nach jeder Implementation:** +- [ ] Zugehörige Issues mit Commit-Hash kommentieren +- [ ] Issues auf `closed` setzen +- [ ] Duplikate entfernen +- [ ] Neue Issues bei Bedarf erstellen + +## Automatisierung (Hook) + +**Optional:** Hook in `.claude/settings.json` für Session-Start: + +```json +{ + "hooks": { + "session-start": "check-issues" + } +} +``` + +→ Bei jeder neuen Session werden automatisch offene Issues geprüft. + +## Issue erstellen (neue Bugs/Features) + +```bash +curl -s -X POST -H "Authorization: token $GITEA_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "title": "[BUG-XXX] Titel", + "body": "Beschreibung", + "labels": [1, 3, 5] + }' \ + http://192.168.2.144:3000/api/v1/repos/Lars/mitai-jinkendo/issues +``` + +**Label-IDs:** +- 1 = bug +- 2 = feature +- 3 = high +- 4 = low +- 5 = develop diff --git a/.claude/commands/db-add-column.md b/.claude/commands/db-add-column.md new file mode 100644 index 0000000..df491ac --- /dev/null +++ b/.claude/commands/db-add-column.md @@ -0,0 +1,23 @@ +# DB Add Column + +Füge eine neue Spalte zur SQLite-Datenbank hinzu. + +## Vorgehen in backend/main.py: + +1. Spalte in CREATE TABLE Statement hinzufügen +2. Spalte in `_safe_alters` Liste hinzufügen: + +```python +_safe_alters = [ + ("profiles", "neue_spalte TEXT DEFAULT NULL"), + # ... weitere +] +``` + +Die `_safe_alters` Funktion fügt Spalten sicher hinzu ohne +bestehende Daten zu verlieren (ALTER TABLE IF NOT EXISTS). + +## Wichtig: +- Immer DEFAULT Wert angeben +- Nie bestehende Spalten umbenennen oder löschen +- Nach Änderung Backend neu starten: `docker compose restart mitai-api` diff --git a/.claude/commands/deploy.md b/.claude/commands/deploy.md new file mode 100644 index 0000000..52f0d07 --- /dev/null +++ b/.claude/commands/deploy.md @@ -0,0 +1,85 @@ +# Deploy – Commit, Test und Push + +Führt Tests durch, bumpt die Version, committet und pusht auf develop. +Gitea startet dann automatisch den Dev-Deploy. + +## Schritt 1: Syntax prüfen +```bash +python3 -m py_compile backend/main.py backend/routers/*.py 2>&1 && echo "Syntax OK" +``` + +## Schritt 2: Versions-Bump prüfen +Prüfe ob version.py und version.js aktualisiert wurden: +```bash +git diff --name-only | grep -E "version\.(py|js)" +``` + +Wenn NICHT aktualisiert → Stopp und fragen: +"Welche Version soll ich setzen? (aktuell: X.Y.Z)" +Dann version.py und version.js aktualisieren. + +## Schritt 3: Smoke Tests auf Dev ausführen +```bash +npx playwright test 2>&1 +``` + +Wenn Tests fehlschlagen: +- Screenshot analysieren: `start test-results\...\test-failed-1.png` +- Fehler korrigieren +- Tests erneut ausführen +- Erst wenn alle grün → weiter + +## Schritt 4: Git Status prüfen +```bash +git status +git diff --stat +``` + +## Schritt 5: Commit +```bash +git add -A +git commit -m "COMMIT_MESSAGE + +version: X.Y.Z +module: MODUL_NAME X.Y.Z" +``` + +## Schritt 6: Push auf develop +```bash +git push origin develop +``` + +## Schritt 7: Deploy-Status prüfen +```bash +# 30 Sekunden warten +# Dann Dev-System prüfen +ssh pi "docker logs dev-mitai-api --tail 10" +curl -sf https://dev.mitai.jinkendo.de/api/version | python3 -c "import sys,json; d=json.load(sys.stdin); print('Version:', d.get('app_version'))" +``` + +## Schritt 8: Gitea PR Link ausgeben +```bash +echo "PR erstellen: http://192.168.2.144:3000/Lars/mitai-jinkendo/compare/main...develop" +``` + +--- + +## Checkliste vor jedem Commit + +``` +[ ] backend/version.py aktualisiert (APP_VERSION + MODULE_VERSION) +[ ] frontend/src/version.js aktualisiert (APP_VERSION + PAGE_VERSION) +[ ] Changelog-Eintrag in version.py hinzugefügt +[ ] DB_SCHEMA_VERSION aktualisiert (wenn Schema geändert) +[ ] Alle Playwright Tests grün +[ ] Auth auf alle neuen Endpoints +[ ] api.js für alle neuen Frontend API-Calls +``` + +--- + +## Prod bleibt unberührt + +Prod-Änderungen NUR über: +Gitea PR → Review → Merge → deploy-prod.yml +NIEMALS direkt auf Prod deployen. diff --git a/.claude/commands/document.md b/.claude/commands/document.md new file mode 100644 index 0000000..397b1c3 --- /dev/null +++ b/.claude/commands/document.md @@ -0,0 +1,191 @@ +# Dokumentation erstellen + +Erstelle oder aktualisiere die technisch-fachliche Dokumentation der App. +Lies zuerst den aktuellen Code, dann generiere die Dokumentation. + +## Wichtig +- Dokumentation basiert auf dem ECHTEN Code – nicht auf Annahmen +- Jede Datei einzeln erstellen, nicht alles auf einmal +- Bestehende Dateien aktualisieren wenn sie existieren + +## Schritt 1: Architektur-Übersicht + +Lies folgende Dateien: +- `backend/main.py` +- `backend/db.py` +- `backend/auth.py` +- `backend/schema.sql` +- `frontend/src/App.jsx` +- `frontend/src/app.css` +- `frontend/src/utils/api.js` +- `docker-compose.yml` + +Erstelle `.claude/docs/technical/ARCHITECTURE.md` mit: + +```markdown +# Architektur-Übersicht – Mitai Jinkendo + +## System-Überblick +[Kurze Beschreibung der Gesamtarchitektur] + +## Tech-Stack +[Tabelle mit allen Technologien + Versionen] + +## Deployment-Architektur +[Infrastruktur: Raspberry Pi, Docker, Synology, Domains] + +## Komponenten-Übersicht +[Alle Module/Router mit Kurzbeschreibung] + +## Datenfluss +[Wie fließen Daten: Frontend → API → DB] + +## Sicherheitsarchitektur +[Auth, CORS, Rate Limiting, bcrypt] + +## Versions-Historie +[v9a, v9b, v9c – was wurde wann eingeführt] +``` + +## Schritt 2: Frontend-Seiten + Komponenten + +Lies alle Dateien in: +- `frontend/src/pages/` +- `frontend/src/context/` +- `frontend/src/utils/` + +Erstelle `.claude/docs/technical/FRONTEND.md` mit: + +```markdown +# Frontend-Dokumentation + +## Seiten-Übersicht +[Tabelle: Seite | Route | Beschreibung | Auth erforderlich] + +## Komponenten +[Wiederverwendbare Komponenten mit Props] + +## Context / State Management +[AuthContext, ProfileContext – was wird wo verwaltet] + +## API-Integration (api.js) +[Alle API-Methoden mit Parametern] + +## CSS-System +[CSS-Variablen, globale Klassen, Design-Tokens] + +## PWA-Konfiguration +[Service Worker, Manifest, Icons] +``` + +## Schritt 3: Auth-Flow + Sicherheit + +Lies: +- `backend/auth.py` +- `backend/routers/auth.py` +- `frontend/src/context/AuthContext.jsx` + +Erstelle `.claude/docs/technical/AUTH.md` mit: + +```markdown +# Auth-Flow & Sicherheit + +## Login-Flow (Schritt für Schritt) +[Sequenz: Frontend → Backend → DB → Token] + +## Session-Management +[Token-Format, Lebensdauer, Speicherort] + +## Passwort-Sicherheit +[bcrypt, SHA256-Migration, Salting] + +## Rate Limiting +[Welche Endpoints, Limits, slowapi-Konfiguration] + +## CORS-Konfiguration +[Allowed Origins, Umgebungsvariablen] + +## Öffentliche vs. geschützte Endpoints +[Tabelle: Endpoint | Public/Protected | Admin-only] + +## Bekannte Sicherheitsentscheidungen +[Warum welche Entscheidung getroffen wurde] +``` + +## Schritt 4: API-Referenz + +Lies alle Dateien in `backend/routers/`. + +Erstelle `.claude/docs/technical/API_REFERENCE.md` mit: + +```markdown +# API-Referenz + +## Basis-URL +- Prod: https://mitai.jinkendo.de/api +- Dev: https://dev.mitai.jinkendo.de/api + +## Authentifizierung +[Header-Format, Token-Handling] + +## Endpoints nach Modul + +### Auth (/api/auth/...) +| Method | Path | Auth | Parameter | Response | +... + +### Profile (/api/profiles/...) +... + +### Gewicht (/api/weight/...) +... + +[alle 14 Router] + +## Fehler-Codes +[Standard-Fehlercodes und Bedeutung] + +## Rate Limits +[Tabelle: Endpoint | Limit | Zeitfenster] +``` + +## Schritt 5: Datenbankschema + +Lies `backend/schema.sql`. + +Erstelle `.claude/docs/technical/DATABASE.md` mit: + +```markdown +# Datenbankschema + +## Übersicht +[Alle Tabellen mit Kurzbeschreibung] + +## Tabellen-Details +[Je Tabelle: Spalten, Typen, Constraints, Indizes] + +## Beziehungen +[Foreign Keys, Beziehungsdiagramm als ASCII] + +## Migrations-Historie +[Was wurde wann geändert] + +## Design-Entscheidungen +[Warum PostgreSQL, warum welche Struktur] + +## Wichtige Abfragen +[Häufig verwendete Queries als Referenz] +``` + +## Nach jeder Datei + +Sage mir: +- Welche Datei wurde erstellt/aktualisiert +- Wie viele Zeilen +- Was war überraschend oder unklar im Code + +## Starten + +Beginne mit Schritt 1 (ARCHITECTURE.md). +Frage nach jedem Schritt ob du weitermachen sollst. +EOF diff --git a/.claude/commands/fix-bug.md b/.claude/commands/fix-bug.md new file mode 100644 index 0000000..0ad9daf --- /dev/null +++ b/.claude/commands/fix-bug.md @@ -0,0 +1,100 @@ +# Fix Bug + +Strukturierter Workflow für Fehlerbehebung mit Bug-Tracking. + +## Workflow + +### 1. Bug in KNOWN_ISSUES.md finden +```bash +cat .claude/docs/KNOWN_ISSUES.md | grep -A 20 "BUG-XXX" +``` + +### 2. Status auf "In Bearbeitung" +```markdown +**Status:** 🟡 In Bearbeitung +``` + +### 3. Logs analysieren + +**Docker Logs:** +```bash +docker logs -f dev-mitai-api | grep "ERROR\|Exception" +docker logs dev-mitai-api --tail 100 +``` + +**Feature-Logs:** +```bash +tail -f backend/logs/feature-usage.log | jq . +``` + +### 4. Bug reproduzieren +- Schritte aus KNOWN_ISSUES.md nachstellen +- Fehler verifizieren +- Root Cause identifizieren (nicht nur Symptom!) + +### 5. Fix implementieren +- Minimal invasive Änderung +- Keine "Verbesserungen" nebenbei +- Kommentiere komplexe Fixes + +### 6. Testen +```bash +# Backend Syntax +python3 -m py_compile backend/[file].py + +# Frontend Build +cd frontend && npm run build +``` + +### 7. Commit +```bash +git add [files] +git commit -m "fix: [BUG-XXX] Kurzbeschreibung + +Root Cause: [Ursache] +Lösung: [Was geändert] +Tested: [Wie getestet] + +Closes: BUG-XXX" +``` + +### 8. KNOWN_ISSUES.md aktualisieren +Nach Deploy: +- Status: ✅ Behoben (Commit `hash`) +- Ins Archiv verschieben + +## Debugging-Checkliste + +### Frontend-Bugs +- Browser Console (F12) → Fehler lesen +- Network Tab → API-Call prüfen +- Häufig: + - 401: Token fehlt → api.js nutzen + - undefined: Optional chaining `?.` verwenden + - Weißer Screen: JS-Fehler in Console + +### Backend-Bugs +- Docker Logs → Traceback analysieren +- Syntax: `python3 -m py_compile backend/[file].py` +- Häufig: + - TypeError: Type-Konvertierung fehlt + - 404: Router nicht registriert + - 500: Unbehandelte Exception + +### Häufige Fehler +- `session=Depends(require_auth)` in `Header()` → separater Parameter! +- `dayjs.week()` → Native ISO-Wochenberechnung +- `datetime.date` vs. `str` → Type-Check +- PostgreSQL Boolean: `true` nicht `1` + +## Commit-Format +``` +fix: [BUG-XXX] Kurze Beschreibung + +Problem: TypeError in /api/nutrition/weekly +Root Cause: d['date'] ist datetime.date, nicht str +Lösung: Check if already datetime.date object +Tested: /nutrition Tab lädt ohne Error + +Closes: BUG-XXX +``` diff --git a/.claude/commands/implement-feature.md b/.claude/commands/implement-feature.md new file mode 100644 index 0000000..6e5e60f --- /dev/null +++ b/.claude/commands/implement-feature.md @@ -0,0 +1,280 @@ +--- +description: Strukturierter Workflow für konzept-basierte Feature-Implementierung mit Requirement Analysis + User Approval +args: +model: sonnet +--- + +# Feature-Implementierung Workflow + +Du wurdest aufgerufen mit: +- **Feature:** {{feature-name}} +- **Konzept:** {{konzept-file}} + +Dieser Skill führt den strukturierten 5-Stufen-Prozess aus `.claude/rules/IMPLEMENTATION_RULES.md` aus. + +--- + +## STUFE 1: Anforderungsanalyse + +**Aufgabe:** +1. Lies das Konzept-Dokument `{{konzept-file}}` VOLLSTÄNDIG +2. Finde alle Anforderungen für `{{feature-name}}` +3. Erstelle eine detaillierte Checkliste + +**Gehe dabei wie folgt vor:** + +### 1.1 Konzept lesen +- Öffne `{{konzept-file}}` mit dem Read tool +- Suche nach Abschnitten die `{{feature-name}}` betreffen +- Lies ALLE relevanten Abschnitte vollständig (nicht nur überfliegen) + +### 1.2 Anforderungs-Matrix erstellen + +Erstelle eine Tabelle mit folgenden Spalten: + +| Anforderung | Datenquelle | Status | Notizen | +|-------------|-------------|--------|---------| +| Kalorienaufnahme täglich | nutrition_log.kcal | ✓ Vorhanden | - | +| Trainingskalorien | activity_log.kcal | ✗ Fehlt | Muss aggregiert werden | +| ... | ... | ... | ... | + +**Status-Codes:** +- ✓ Vorhanden: Bereits implementiert (data_layer Funktion existiert) +- ⚠ Partial: Teilweise vorhanden, muss erweitert werden +- ✗ Fehlt: Muss neu gebaut werden + +### 1.3 Gap-Analyse + +Listen auf: +- Welche data_layer Funktionen existieren bereits? +- Welche data_layer Funktionen müssen neu geschrieben werden? +- Welche Tabellen werden benötigt? +- Welche Berechnungen sind nötig? (Formeln dokumentieren) + +### 1.4 Offene Fragen + +Dokumentiere alle Unklarheiten: +- Interpretation unklar? +- Mehrere Umsetzungsmöglichkeiten? +- Fehlende Informationen im Konzept? + +**OUTPUT:** Zeige dem User die Anforderungs-Matrix und Gap-Analyse. + +--- + +## STUFE 2: Umsetzungskonzept erstellen + +**Aufgabe:** +Erstelle ein detailliertes technisches Umsetzungskonzept mit: + +### 2.1 Backend-Design + +**Neue Endpoints:** +``` +GET /api/charts/{{feature-name}}?param1=value1 + +Args: + param1: Beschreibung (default=X, range Y-Z) + session: Auth session (injected) + +Returns: + Chart.js-compatible JSON (siehe unten) +``` + +**Neue data_layer Funktionen:** +```python +def get_xyz_data(profile_id: str, days: int) -> Dict: + """ + Beschreibung was die Funktion tut. + + Berechnung: + 1. Schritt 1 (SQL Query oder Formel) + 2. Schritt 2 + + Returns: + {"key": value, ...} + """ +``` + +**Datenquellen:** +- Welche Tabellen? (nutrition_log, activity_log, etc.) +- Welche Spalten? +- Welche JOINs? + +**Berechnungen:** +- Formeln dokumentieren (z.B. "7d avg = sum(values[-7:]) / 7") +- Aggregationen beschreiben +- Edge Cases behandeln (keine Daten, Division durch 0, etc.) + +### 2.2 Response-Format + +Zeige ein **vollständiges Beispiel** der JSON-Response: + +```json +{ + "chart_type": "line" | "bar" | "mixed" | "scatter", + "data": { + "labels": ["2026-01-01", "2026-01-02", ...], + "datasets": [ + { + "type": "line", // nur bei chart_type: mixed + "label": "Label für Legende", + "data": [100, 105, 103, ...], + "borderColor": "#1D9E75", + "backgroundColor": "rgba(29, 158, 117, 0.1)", + "borderWidth": 2, + "tension": 0.3, + "fill": false, + "yAxisID": "y1" // bei Dual-Axis + } + ] + }, + "metadata": { + "confidence": "high" | "medium" | "low" | "insufficient", + "data_points": 28, + "avg_value": 123.4, + "custom_field": "..." + } +} +``` + +### 2.3 Frontend-Design + +**Component:** +- Welche Datei? (z.B. `NutritionCharts.jsx`) +- Welche Funktion? (z.B. `renderEnergyBalance()`) + +**Chart-Library:** +- Recharts oder Chart.js? +- Welcher Chart-Typ? (LineChart, BarChart, ComposedChart, etc.) +- Welche Features? (Tooltip, Legend, Dual-Axis, etc.) + +**API-Integration:** +- API-Funktion in `api.js` hinzufügen +- State-Management (useState, useEffect) +- Loading + Error Handling + +### 2.4 Dependencies + +- Müssen andere Module angepasst werden? +- Neue npm-Pakete nötig? +- DB-Migrationen nötig? + +**OUTPUT:** Zeige dem User das vollständige Umsetzungskonzept. + +--- + +## STUFE 3: User-Approval einholen + +**Aufgabe:** +1. Zeige Anforderungs-Matrix (Stufe 1) +2. Zeige Umsetzungskonzept (Stufe 2) +3. Liste offene Fragen auf +4. Frage explizit: **"Soll ich mit der Implementierung beginnen? (ja/nein/Änderungen)"** + +**WICHTIG:** +- **Warte auf die Antwort des Users** +- Beginne NICHT mit der Implementierung ohne explizites "ja" +- Bei Änderungswünschen: Passe das Konzept an und zeige es erneut + +--- + +## STUFE 4: Implementierung + +**NUR ausführen wenn User "ja" gesagt hat!** + +### 4.1 Backend implementieren + +1. **Neue data_layer Funktionen:** + - Erstelle/erweitere Dateien in `backend/data_layer/` + - Implementiere genau nach Konzept (Stufe 2.1) + - Docstrings mit Beschreibung + Args + Returns + +2. **Neue Endpoints:** + - Erstelle/erweitere Router in `backend/routers/` + - Implementiere genau nach Konzept (Stufe 2.1) + - Response-Format exakt wie spezifiziert (Stufe 2.2) + +3. **Tests (falls vorhanden):** + - Unit-Tests für data_layer Funktionen + - Integration-Tests für Endpoints + +### 4.2 Frontend implementieren + +1. **API-Funktion hinzufügen:** + - Ergänze `frontend/src/utils/api.js` + - Syntax: `getXyzChart: (days=28) => req(\`/charts/xyz?days=${days}\`)` + +2. **Component erstellen/erweitern:** + - Implementiere Chart-Darstellung + - Verwende Chart-Library wie spezifiziert + - Loading + Error Handling + +3. **Integration:** + - Füge Component in Parent-Page ein + - Teste UI-Flow + +### 4.3 Commit + +``` +feat: {{feature-name}} (konzept-konform) + +Backend: +- data_layer: [Liste neue Funktionen] +- Endpoint: [Liste neue Endpoints] + +Frontend: +- Component: [Beschreibung] +- Chart-Type: [Typ] + +Konzept: {{konzept-file}} + +Co-Authored-By: Claude Opus 4.6 +``` + +--- + +## STUFE 5: Compliance-Check + +**Aufgabe:** +Prüfe JEDE Anforderung aus der Anforderungs-Matrix (Stufe 1): + +``` +□ Anforderung 1: [Beschreibung] - Status: ✓ / ✗ +□ Anforderung 2: [Beschreibung] - Status: ✓ / ✗ +... +``` + +**Alle Checkboxen müssen ✓ sein vor Push!** + +Falls ✗: +- Identifiziere was fehlt +- Implementiere nach +- Erneut prüfen + +**OUTPUT:** Zeige dem User die Final-Checkliste. + +--- + +## WICHTIG: Workflow-Reihenfolge + +``` +Stufe 1 (Anforderungen) → Stufe 2 (Konzept) → Stufe 3 (Approval) + ↓ + User sagt "ja"? + ↓ +Stufe 4 (Implementierung) → Stufe 5 (Compliance-Check) → Push +``` + +**Niemals:** +- Stufen überspringen +- Ohne Approval implementieren +- "Ähnliches" statt Gefordertes bauen + +--- + +## Los geht's! + +Beginne jetzt mit **STUFE 1: Anforderungsanalyse**. + +Lies `{{konzept-file}}` und erstelle die Anforderungs-Matrix für `{{feature-name}}`. diff --git a/.claude/commands/merge-to-prod.md b/.claude/commands/merge-to-prod.md new file mode 100644 index 0000000..ff87359 --- /dev/null +++ b/.claude/commands/merge-to-prod.md @@ -0,0 +1,60 @@ +# Merge to Production + +Erstelle einen Pull Request in Gitea: develop -> main + +## Checkliste vor dem PR: +- [ ] dev.mitai.jinkendo.de funktioniert +- [ ] Login funktioniert +- [ ] Alle neuen Features getestet +- [ ] Keine Console-Fehler im Browser +- [ ] KI-Analyse funktioniert +- [ ] Backend-Syntax OK: `python3 -m py_compile backend/main.py` + +## Schritt 1: Sicherstellen dass develop aktuell ist +```bash +git checkout develop +git status +git push origin develop +``` + +## Schritt 2: Pull Request in Gitea erstellen +Öffne im Browser: +http://192.168.2.144:3000/Lars/mitai-jinkendo/compare/main...develop + +Oder: +``` +Gitea ? Repository ? Pull Requests ? "Neuer Pull Request" +Basis: main +Vergleich: develop +Titel: beschreibung der Änderungen +? Pull Request erstellen +``` + +## Schritt 3: PR mergen +``` +Gitea ? Pull Request ? "Merge Pull Request" +? "Merge Commit" wählen (NICHT "Squash" oder "Rebase") +? develop Branch NICHT löschen (Häkchen deaktivieren!) +``` + +## Schritt 4: Nach dem Merge +- Gitea Action `deploy-prod.yml` startet automatisch +- Prüfe: https://mitai.jinkendo.de +- Prüfe Gitea Actions ob alles grün ist +- Lokal auf develop zurückwechseln und aktualisieren: +```bash +git checkout develop +git pull origin develop +``` + +## Rollback falls nötig: +``` +Gitea ? Commits auf main ? "Revert" auf dem letzten Merge-Commit +? neuer PR wird automatisch erstellt +? PR mergen ? Prod wird zurückgesetzt +``` + +## Wichtig: +- NIEMALS direkt auf main pushen +- develop Branch niemals löschen +- Immer über PR mergen – nie `git merge` lokal auf main diff --git a/.claude/commands/new-feature.md b/.claude/commands/new-feature.md new file mode 100644 index 0000000..435a696 --- /dev/null +++ b/.claude/commands/new-feature.md @@ -0,0 +1,19 @@ +# New Feature + +Erstelle ein neues Feature für Mitai Jinkendo. + +## Checkliste: +1. Backend: Neuer Endpoint in `backend/main.py` + - Mit `session: dict = Depends(require_auth)` absichern + - Neue DB-Spalten via `_safe_alters` hinzufügen +2. Frontend: Neue Seite oder Komponente + - API-Calls immer über `frontend/src/utils/api.js` + - Token wird automatisch injiziert +3. Syntax prüfen: `python3 -m py_compile backend/main.py` +4. CLAUDE.md aktualisieren wenn nötig + +## Wichtige Regeln: +- Passwörter: bcrypt (nicht SHA256) +- API-Calls: api.js nutzen (nie direktes fetch ohne Token) +- dayjs.week(): nicht verwenden → native ISO-Wochenberechnung +- session=Depends(require_auth): immer als separater Parameter diff --git a/.claude/commands/pi-db.md b/.claude/commands/pi-db.md new file mode 100644 index 0000000..d8909ab --- /dev/null +++ b/.claude/commands/pi-db.md @@ -0,0 +1,47 @@ +# Pi Datenbank + +Direkter Zugriff auf PostgreSQL Datenbanken auf dem Raspberry Pi. + +## Verwendung +Sage was du prüfen möchtest, z.B.: +- "Zeige alle Profile" +- "Wie viele Gewichtseinträge hat Lars?" +- "Zeige die letzten 10 Schlafeinträge" +- "Prüfe ob die Tabelle sleep_log existiert" + +## Prod-Datenbank Abfrage +```bash +ssh pi "docker exec mitai-db-prod psql -U mitai_prod -d mitai_prod -c 'DEINE_SQL_ABFRAGE'" +``` + +## Dev-Datenbank Abfrage +```bash +ssh pi "docker exec dev-mitai-postgres psql -U mitai_dev -d mitai_dev -c 'DEINE_SQL_ABFRAGE'" +``` + +## Nützliche Standard-Abfragen + +### Alle Tabellen anzeigen +```bash +ssh pi "docker exec mitai-db-prod psql -U mitai_prod -d mitai_prod -c '\dt'" +``` + +### Tabellen-Größen +```bash +ssh pi "docker exec mitai-db-prod psql -U mitai_prod -d mitai_prod -c \"SELECT schemaname, tablename, pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) as size FROM pg_tables WHERE schemaname='public' ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC;\"" +``` + +### Profile anzeigen +```bash +ssh pi "docker exec mitai-db-prod psql -U mitai_prod -d mitai_prod -c 'SELECT id, name, email, role, tier FROM profiles;'" +``` + +### DB-Version prüfen +```bash +ssh pi "docker exec mitai-db-prod psql -U mitai_prod -d mitai_prod -c 'SELECT version();'" +``` + +## Wichtig +- Nur SELECT-Abfragen ohne explizite Genehmigung +- Keine DELETE/DROP/TRUNCATE ohne ausdrückliche Bestätigung +- Prod-DB mit besonderer Vorsicht behandeln diff --git a/.claude/commands/pi-dev.md b/.claude/commands/pi-dev.md new file mode 100644 index 0000000..e637c27 --- /dev/null +++ b/.claude/commands/pi-dev.md @@ -0,0 +1,60 @@ +# Pi Dev – Entwicklungssystem Zugriff + +Direkter Zugriff auf das Dev-System (dev.mitai.jinkendo.de). +**NUR für Dev – niemals auf Prod anwenden!** + +## Container Status +```bash +ssh pi "docker ps --filter name=dev-mitai --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'" +``` + +## Backend neu starten (Dev) +```bash +ssh pi "docker restart dev-mitai-api && sleep 5 && docker logs dev-mitai-api --tail 20" +``` + +## Frontend neu starten (Dev) +```bash +ssh pi "docker restart dev-mitai-ui" +``` + +## Beide neu starten (Dev) +```bash +ssh pi "cd /home/lars/docker/bodytrack-dev && docker compose -f docker-compose.dev-env.yml restart" +``` + +## Komplett neu bauen (Dev) +```bash +ssh pi "cd /home/lars/docker/bodytrack-dev && git pull origin develop && docker compose -f docker-compose.dev-env.yml build --no-cache && docker compose -f docker-compose.dev-env.yml up -d" +``` + +## Backend Logs live (10 Sekunden) +```bash +ssh pi "timeout 10 docker logs dev-mitai-api --follow 2>&1 || true" +``` + +## Dev-DB Abfrage (nur SELECT ohne Genehmigung) +```bash +ssh pi "docker exec dev-mitai-postgres psql -U mitai_dev -d mitai_dev -c 'DEINE_ABFRAGE'" +``` + +## Health Check Dev +```bash +ssh pi "curl -sf http://localhost:8099/api/auth/status && echo '✓ DEV API OK' || echo '✗ DEV API FEHLER'" +ssh pi "curl -sf http://localhost:3099 | grep -c 'Mitai' && echo '✓ DEV UI OK' || echo '✗ DEV UI FEHLER'" +``` + +## Umgebungsvariablen prüfen (Dev) +```bash +ssh pi "docker exec dev-mitai-api env | grep -v PASSWORD | grep -v SECRET | grep -v KEY" +``` + +## ⛔ PROD-SCHUTZ +Folgende Befehle sind für Prod VERBOTEN: +- docker restart mitai-api +- docker restart mitai-ui +- docker exec mitai-api ...schreibend... +- Direkte Dateiänderungen in /home/lars/docker/bodytrack/ +- Änderungen an Prod-DB außer SELECT + +Prod-Änderungen NUR über: git push → Gitea PR → deploy-prod.yml diff --git a/.claude/commands/pi-logs.md b/.claude/commands/pi-logs.md new file mode 100644 index 0000000..e8f0595 --- /dev/null +++ b/.claude/commands/pi-logs.md @@ -0,0 +1,43 @@ +# Pi Logs + +Zeigt Logs der Mitai-Container auf dem Raspberry Pi. + +## Backend Logs (Prod) +```bash +ssh pi "docker logs mitai-api --tail 50 --timestamps" +``` + +## Backend Logs (Dev) +```bash +ssh pi "docker logs dev-mitai-api --tail 50 --timestamps" +``` + +## Frontend Logs (Prod) +```bash +ssh pi "docker logs mitai-ui --tail 20" +``` + +## Logs nach Fehler filtern +```bash +ssh pi "docker logs mitai-api --tail 100 2>&1 | grep -i 'error\|exception\|traceback'" +``` + +## Logs live verfolgen (10 Sekunden) +```bash +ssh pi "timeout 10 docker logs mitai-api --follow 2>&1 || true" +``` + +## Gitea Runner Logs +```bash +ssh pi "sudo journalctl -u gitea-runner --lines 30 --no-pager" +``` + +## Letzter Deploy-Status +```bash +ssh pi "cat /home/lars/docker/bodytrack/.last-deploy 2>/dev/null || echo 'Keine Deploy-Info'" +``` + +## System-Logs (Fehler letzte Stunde) +```bash +ssh pi "sudo journalctl --since '1 hour ago' --priority err --no-pager | tail -20" +``` diff --git a/.claude/commands/pi-status.md b/.claude/commands/pi-status.md new file mode 100644 index 0000000..e6d3c3e --- /dev/null +++ b/.claude/commands/pi-status.md @@ -0,0 +1,37 @@ +# Pi Status + +Zeigt den aktuellen Status aller Mitai-Container und Logs auf dem Raspberry Pi. + +## Container Status +```bash +ssh pi "docker ps --filter name=mitai --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'" +``` + +## Letzte Backend-Logs (Prod) +```bash +ssh pi "docker logs mitai-api --tail 30" +``` + +## Letzte Backend-Logs (Dev) +```bash +ssh pi "docker logs dev-mitai-api --tail 30" +``` + +## API Health Check +```bash +ssh pi "curl -sf http://localhost:8002/api/auth/status && echo '✓ PROD OK' || echo '✗ PROD FEHLER'" +ssh pi "curl -sf http://localhost:8099/api/auth/status && echo '✓ DEV OK' || echo '✗ DEV FEHLER'" +``` + +## Datenbank Status +```bash +ssh pi "docker exec mitai-db-prod pg_isready -U mitai_prod && echo '✓ DB PROD OK'" +ssh pi "docker exec dev-mitai-postgres pg_isready -U mitai_dev && echo '✓ DB DEV OK'" +``` + +## Disk Space +```bash +ssh pi "df -h /home/lars/docker" +``` + +## Führe alle Checks aus und zeige Zusammenfassung diff --git a/.claude/commands/refactor.md b/.claude/commands/refactor.md new file mode 100644 index 0000000..65a71ab --- /dev/null +++ b/.claude/commands/refactor.md @@ -0,0 +1,81 @@ +# Refactor + +Refactore den Code ohne die Funktionalität zu ändern. + +## Wichtige Regel: +**Erst planen, dann umsetzen – niemals beides gleichzeitig.** +Zeige den Plan und warte auf Bestätigung bevor du Code schreibst. + +## Backend Refactoring (main.py aufteilen): + +### Zielstruktur: +``` +backend/ +├── main.py # Nur App-Setup, Middleware, Router-Import (~100 Zeilen) +├── database.py # DB-Verbindung, init_db, safe_alters, r2d() +├── auth.py # require_auth, require_admin, hash_pin, verify_pin, sessions +├── models.py # Alle Pydantic Models +├── email.py # SMTP, send_email, email_html_wrapper +├── utils.py # make_token(), helper functions +└── routers/ + ├── __init__.py + ├── weight.py + ├── caliper.py + ├── circumference.py + ├── nutrition.py + ├── activity.py + ├── photos.py + ├── insights.py + ├── prompts.py + ├── admin.py + └── export.py +``` + +### Vorgehen: +1. Analysiere main.py vollständig +2. Erstelle Plan welche Zeilen wohin kommen +3. Warte auf Bestätigung +4. Erstelle neue Dateien eine nach der anderen +5. Nach jeder Datei: Syntax prüfen +6. main.py anpassen (imports + router registrierung) +7. Finaler Test: Backend startet ohne Fehler + +### Syntax-Check nach jedem Schritt: +```bash +python3 -m py_compile backend/main.py +python3 -m py_compile backend/database.py +# etc. +``` + +### Test nach Refactoring: +```bash +# Auf dem Pi: +docker compose build --no-cache backend +docker compose up -d +docker logs mitai-api --tail 20 +curl -s http://localhost:8002/api/auth/status +``` + +## Frontend Refactoring (Komponenten extrahieren): + +### Wiederverwendbare Komponenten extrahieren nach: +``` +frontend/src/components/ +├── MetricCard.jsx # Kennzahl-Karte +├── ChartCombo.jsx # Kombinations-Chart +├── LoadingSpinner.jsx # Ladekreis +├── EmptyState.jsx # Leerer Zustand +├── Avatar.jsx # Nutzer-Avatar +└── ConfirmDialog.jsx # Bestätigungs-Dialog +``` + +### Vorgehen: +1. Identifiziere wiederholte Code-Muster +2. Extrahiere in eigene Komponenten +3. Ersetze in allen Pages +4. Frontend Build testen: `cd frontend && npm run build` + +## Nach dem Refactoring: +- Commit mit `refactor:` Prefix +- CLAUDE.md Verzeichnisstruktur aktualisieren +- Kein neues Feature in diesem Commit! diff --git a/.claude/commands/test.md b/.claude/commands/test.md new file mode 100644 index 0000000..da572f6 --- /dev/null +++ b/.claude/commands/test.md @@ -0,0 +1,226 @@ +# Test – Vollständige Test-Suite + +Führt Backend-Tests (API + Logs) und Frontend-Tests (Playwright) durch. +Claude Code schreibt, führt aus und korrigiert bis alle Tests grün sind. + +## Workflow für neue Features + +Nach jeder Implementierung: +1. Backend-Tests schreiben und ausführen +2. Frontend-Tests schreiben und ausführen +3. Logs auf Fehler prüfen +4. Alle grün → committen + +--- + +## TEIL 1: Backend API-Tests (schnell, immer zuerst) + +### 1.1 Login + Token holen +```bash +TOKEN=$(curl -s -X POST https://dev.mitai.jinkendo.de/api/auth/login \ + -H "Content-Type: application/json" \ + -d "{\"email\":\"$TEST_EMAIL\",\"pin\":\"$TEST_PASSWORD\"}" \ + | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('token','KEIN_TOKEN'))") +echo "Token: ${TOKEN:0:30}..." +``` + +### 1.2 Basis-Endpoints prüfen +```bash +# Version +curl -sf https://dev.mitai.jinkendo.de/api/version \ + | python3 -c "import sys,json; d=json.load(sys.stdin); print('Version:', d.get('app_version'), '| DB:', d.get('db_schema_version'))" + +# Auth Status +curl -sf https://dev.mitai.jinkendo.de/api/auth/status \ + && echo "Auth OK" || echo "Auth FEHLER" + +# Stats (mit Token) +curl -sf https://dev.mitai.jinkendo.de/api/stats \ + -H "X-Auth-Token: $TOKEN" \ + | python3 -c "import sys,json; d=json.load(sys.stdin); print('Stats OK:', list(d.keys())[:5])" +``` + +### 1.3 CRUD-Test für neues Modul +Template für jeden neuen Router: +```bash +# CREATE +ID=$(curl -s -X POST https://dev.mitai.jinkendo.de/api/MODUL \ + -H "Content-Type: application/json" \ + -H "X-Auth-Token: $TOKEN" \ + -d '{"date":"2026-01-01","FELD":"WERT"}' \ + | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('id','FEHLER'))") +echo "CREATE: ID=$ID" + +# READ +curl -sf https://dev.mitai.jinkendo.de/api/MODUL?limit=5 \ + -H "X-Auth-Token: $TOKEN" \ + | python3 -c "import sys,json; d=json.load(sys.stdin); print('READ: Einträge:', len(d))" + +# DELETE (Test-Eintrag aufräumen) +curl -s -X DELETE https://dev.mitai.jinkendo.de/api/MODUL/$ID \ + -H "X-Auth-Token: $TOKEN" \ + && echo "DELETE OK" || echo "DELETE FEHLER" +``` + +### 1.4 Auth-Schutz prüfen +```bash +# Endpoint OHNE Token muss 401 zurückgeben +STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://dev.mitai.jinkendo.de/api/stats) +[ "$STATUS" = "401" ] && echo "Auth-Schutz OK (401)" || echo "WARNUNG: Auth-Schutz fehlt! ($STATUS)" +``` + +### 1.5 Admin-Endpoint prüfen +```bash +# Admin-Endpoint mit normalem Token muss 403 zurückgeben +STATUS=$(curl -s -o /dev/null -w "%{http_code}" \ + https://dev.mitai.jinkendo.de/api/admin/profiles \ + -H "X-Auth-Token: $TOKEN") +echo "Admin-Schutz: $STATUS (erwartet 403 für nicht-Admin)" +``` + +--- + +## TEIL 2: Backend Logs prüfen + +### 2.1 Letzte Logs auf Fehler prüfen +```bash +ssh pi "docker logs dev-mitai-api --tail 50 2>&1 | grep -i 'error\|exception\|traceback\|warning' | head -20" +``` + +### 2.2 Nach Test-Requests: Logs prüfen +```bash +# Direkt nach API-Tests ausführen +ssh pi "docker logs dev-mitai-api --tail 20 --timestamps 2>&1" +``` + +### 2.3 Alle neuen Endpoints im Log sichtbar? +```bash +# Prüft ob Router korrekt registriert +ssh pi "docker logs dev-mitai-api 2>&1 | grep 'router\|route\|endpoint' | head -10" +``` + +### 2.4 DB-Verbindung gesund? +```bash +ssh pi "docker exec dev-mitai-postgres pg_isready -U mitai_dev && echo 'DB OK'" +``` + +### 2.5 Neue Tabellen vorhanden? +```bash +ssh pi "docker exec dev-mitai-postgres psql -U mitai_dev -d mitai_dev -c '\dt' 2>/dev/null | grep -E 'TABELLEN_NAME'" +``` + +--- + +## TEIL 3: Frontend Playwright-Tests + +### 3.1 Alle Smoke Tests +```bash +npx playwright test +``` + +### 3.2 Nur neues Feature testen +```bash +npx playwright test --grep "FEATURE_NAME" +``` + +### 3.3 Mit sichtbarem Browser +```bash +npx playwright test --headed +``` + +### 3.4 Screenshot bei Fehler ansehen +```bash +start test-results\TESTNAME\test-failed-1.png +``` + +--- + +## TEIL 4: Tests schreiben + +### Backend-Test Template (in test-Abschnitt einfügen) +```bash +# Für jeden neuen Endpoint: +echo "=== Test: MODUL_NAME ===" +TOKEN=$(curl -s -X POST https://dev.mitai.jinkendo.de/api/auth/login \ + -H "Content-Type: application/json" \ + -d "{\"email\":\"$TEST_EMAIL\",\"pin\":\"$TEST_PASSWORD\"}" \ + | python3 -c "import sys,json; print(json.load(sys.stdin).get('token',''))") + +# GET Test +curl -sf https://dev.mitai.jinkendo.de/api/MODUL \ + -H "X-Auth-Token: $TOKEN" \ + && echo "GET OK" || echo "GET FEHLER" + +# POST Test +curl -s -X POST https://dev.mitai.jinkendo.de/api/MODUL \ + -H "Content-Type: application/json" \ + -H "X-Auth-Token: $TOKEN" \ + -d '{"date":"2026-01-01"}' \ + | python3 -c "import sys,json; d=json.load(sys.stdin); print('POST OK, id:', d.get('id'))" + +# Auth-Schutz +STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://dev.mitai.jinkendo.de/api/MODUL) +[ "$STATUS" = "401" ] && echo "Auth-Schutz OK" || echo "Auth-Schutz FEHLER ($STATUS)" +``` + +### Frontend-Test Template (in dev-smoke-test.spec.js) +```javascript +test('MODUL: Seite lädt und Grundfunktion', async ({ page }) => { + await login(page); + + // Navigation + await page.click('text=NAV_TEXT'); + await page.waitForLoadState('networkidle'); + await expect(page.locator('.spinner')).toHaveCount(0, { timeout: 10000 }); + + // Formular ausfüllen + await page.fill('input[name="FELD"]', 'TESTWERT'); + await page.click('button:has-text("Speichern")'); + + // Ergebnis prüfen + await expect(page.locator('text=gespeichert')).toBeVisible({ timeout: 5000 }); + await page.screenshot({ path: 'screenshots/MODUL-test.png' }); + console.log('MODUL OK'); +}); +``` + +--- + +## TEIL 5: Vollständiger Test-Lauf (vor jedem Deploy) + +```bash +echo "=== BACKEND TESTS ===" && \ +TOKEN=$(curl -s -X POST https://dev.mitai.jinkendo.de/api/auth/login \ + -H "Content-Type: application/json" \ + -d "{\"email\":\"$TEST_EMAIL\",\"pin\":\"$TEST_PASSWORD\"}" \ + | python3 -c "import sys,json; print(json.load(sys.stdin).get('token',''))") && \ +curl -sf https://dev.mitai.jinkendo.de/api/version | python3 -c "import sys,json; d=json.load(sys.stdin); print('Version:', d.get('app_version'))" && \ +curl -sf https://dev.mitai.jinkendo.de/api/stats -H "X-Auth-Token: $TOKEN" > /dev/null && echo "Stats OK" && \ +echo "" && \ +echo "=== LOGS PRUEFEN ===" && \ +ssh pi "docker logs dev-mitai-api --tail 20 2>&1 | grep -i 'error\|exception' | head -5 || echo 'Keine Fehler in Logs'" && \ +echo "" && \ +echo "=== FRONTEND TESTS ===" && \ +npx playwright test && \ +echo "" && \ +echo "=== ALLE TESTS BESTANDEN ===" +``` + +--- + +## Credentials setzen (einmalig pro Session) +```bash +$env:TEST_EMAIL="lars@stommer.com" +$env:TEST_PASSWORD="5112" +``` + +--- + +## Prod-Schutz +Tests laufen AUSSCHLIESSLICH gegen: +- https://dev.mitai.jinkendo.de +- http://192.168.2.49:8099 + +NIEMALS gegen: +- https://mitai.jinkendo.de +- http://192.168.2.49:8002 diff --git a/.claude/commands/ui-component.md b/.claude/commands/ui-component.md new file mode 100644 index 0000000..05f7224 --- /dev/null +++ b/.claude/commands/ui-component.md @@ -0,0 +1,42 @@ +# UI Component + +Erstelle eine neue React-Komponente im Mitai Jinkendo Design-System. + +## Design-Regeln: +- Hintergrund: `var(--bg)` oder `var(--surface)` +- Akzentfarbe: `var(--accent)` = #1D9E75 +- Text: `var(--text1)` (primary), `var(--text2)` (secondary), `var(--text3)` (muted) +- Border: `var(--border)` +- Border-Radius: 12px für Cards, 8px für Buttons/Inputs +- Kein TypeScript – reines JavaScript/JSX +- Inline-Styles bevorzugt, keine externen CSS-Dateien +- Keine neuen npm-Pakete ohne Absprache + +## Komponenten-Struktur: +```jsx +function MeineKomponente({ prop1, prop2 }) { + const [state, setState] = useState(null) + + return ( +
+ ... +
+ ) +} +``` + +## Vorhandene CSS-Klassen: +- `.card` – weißer Container mit Schatten +- `.btn .btn-primary` – grüner Button +- `.btn .btn-secondary` – grauer Button +- `.btn-full` – volle Breite +- `.form-input` – Eingabefeld +- `.form-label` – Label +- `.form-row` – Label + Input + Unit nebeneinander +- `.section-gap` – vertikaler Abstand +- `.spinner` – Ladeindikator + +## API-Calls: +- Immer über `import { api } from '../utils/api'` +- Nie direktes `fetch()` verwenden +- Token wird automatisch injiziert diff --git a/.claude/commands/ui-page.md b/.claude/commands/ui-page.md new file mode 100644 index 0000000..d202b95 --- /dev/null +++ b/.claude/commands/ui-page.md @@ -0,0 +1,59 @@ +# UI Page + +Erstelle eine neue vollständige Seite für Mitai Jinkendo. + +## Seiten-Struktur: +```jsx +import { useState, useEffect } from 'react' +import { api } from '../utils/api' +import { useAuth } from '../context/AuthContext' + +export default function MeineSeite() { + const { session } = useAuth() + const [data, setData] = useState([]) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + + useEffect(() => { load() }, []) + + const load = async () => { + try { + setLoading(true) + const result = await api.meinEndpoint() + setData(result) + } catch(e) { + setError(e.message) + } finally { + setLoading(false) + } + } + + if (loading) return
+ if (error) return
{error}
+ + return ( +
+ {/* Titelzeile */} +
+
Seitentitel
+
+ + {/* Inhalt */} +
+ ) +} +``` + +## Navigation einbinden: +In `App.jsx` unter den bestehenden Routen hinzufügen. + +## Backend-Endpoint: +Neuen Endpoint in `backend/main.py` mit: +```python +@app.get("/api/mein-endpoint") +def mein_endpoint(session: dict = Depends(require_auth)): + pid = session['profile_id'] + with get_db() as conn: + ... +``` diff --git a/.claude/commands/ui-responsive.md b/.claude/commands/ui-responsive.md new file mode 100644 index 0000000..78aab25 --- /dev/null +++ b/.claude/commands/ui-responsive.md @@ -0,0 +1,121 @@ +# UI Responsive + +Mache die App vollständig responsive – Mobile, Tablet und Desktop. + +## Ziel-Design +``` +iPhone (<768px): Bottom Navigation, volle Breite (wie jetzt) +iPad (768-1024px): Bottom Navigation, 2-spaltige Cards +Desktop (>1024px): Sidebar Navigation links, Content rechts, volle Breite +``` + +## Wichtige Regel: +**Erst planen, dann umsetzen.** Zeige den Plan und warte auf Bestätigung. + +## Schritt 1: Analyse +Analysiere folgende Dateien: +- `frontend/src/app.css` – aktuelle CSS-Variablen und globale Styles +- `frontend/src/App.jsx` – Navigation und Layout-Struktur +- `frontend/src/pages/Dashboard.jsx` – repräsentative Seite + +## Schritt 2: Plan vorlegen +Zeige: +1. Welche CSS-Breakpoints werden eingeführt +2. Wie ändert sich die Navigation (Bottom ? Sidebar) +3. Welche Komponenten müssen angepasst werden +4. Geschätzter Aufwand + +## Schritt 3: CSS-Variablen und Breakpoints + +Neue Breakpoints in `app.css`: +```css +/* Breakpoints */ +--bp-mobile: 768px; +--bp-tablet: 1024px; + +/* Layout */ +--sidebar-width: 220px; +--content-max-width: 1200px; +``` + +## Schritt 4: Layout-Komponente erstellen + +Neue Datei `frontend/src/components/AppLayout.jsx`: +```jsx +// Erkennt Screen-Größe und rendert: +// - Mobile/Tablet: Bottom Navigation (wie jetzt) +// - Desktop: Sidebar Navigation + Content-Bereich +``` + +## Schritt 5: Navigation anpassen + +**Mobile (wie jetzt):** +```jsx + +``` + +**Desktop (neu):** +```jsx + +
+ {children} +
+``` + +## Schritt 6: Cards responsiv machen + +```css +/* Mobile: 1 Spalte */ +.metric-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 12px; +} + +/* Desktop: 4 Spalten */ +@media (min-width: 1024px) { + .metric-grid { + grid-template-columns: repeat(4, 1fr); + } +} +``` + +## Schritt 7: Testen +```bash +# Frontend build testen +cd frontend && npm run build +``` + +Browser-Test auf: +- iPhone (375px) – Safari +- iPad (768px) – Browser DevTools +- Desktop (1440px) – Browser normal + +## Jinkendo Design-Regeln beibehalten: +- Farben: var(--accent) #1D9E75, var(--bg), var(--surface) +- Border-Radius: 12px Cards, 8px Buttons +- Keine externen CSS-Frameworks +- Inline-Styles + globale CSS-Variablen +- PWA auf iPhone muss weiterhin funktionieren + +## Nach dem Umbau: +- Commit mit `feat: responsive layout – sidebar on desktop` +- Push auf develop +- Testen auf dev.mitai.jinkendo.de auf verschiedenen Screens +- Erst nach Freigabe: /merge-to-prod diff --git a/.claude/docs/BACKLOG.md b/.claude/docs/BACKLOG.md new file mode 100644 index 0000000..a0ae58d --- /dev/null +++ b/.claude/docs/BACKLOG.md @@ -0,0 +1,242 @@ +# Mitai Jinkendo – Feature Backlog + +Vollständige Übersicht aller geplanten Features nach Versionen. + +**Dokumentationsstruktur:** +- 📚 `functional/` - Fachliche Specs (WAS wird gebaut) +- 📚 `technical/` - Technische Specs (WIE wird es gebaut) +- 🎯 **Konkrete Tasks & Issues** → [Gitea Issues](http://192.168.2.144:3000/Lars/mitai-jinkendo/issues) +- 📋 **Strategische Planung** → `ROADMAP.md` + +**Aktueller Fokus:** v9d → v9e Transition (Ruhetage abgeschlossen, Vitalwerte in Arbeit, Entwicklungsrouten vorbereiten) + +--- + +## v9c – Membership & Subscription ✅ KOMPLETT + +📚 **Specs:** `technical/MEMBERSHIP_SYSTEM.md` (kombiniert fachlich + technisch) + +**Deployed:** 21. März 2026 (Production) + +### Features +- ✅ Feature-Enforcement-System (4-Phasen-Modell) +- ✅ Selbst-Registrierung mit E-Mail-Verifizierung +- ✅ Trial-System UI (Countdown-Banner mit 3 Urgency-Level) +- ✅ Tier-basiertes Zugriffsmanagement +- ✅ Coupons & Access-Grants +- ✅ Usage-Badges in allen relevanten Screens + +**Bugfixes v9c:** +- ✅ BUG-001: TypeError in `/api/nutrition/weekly` (datetime.date handling) +- ✅ BUG-002: Ernährungs-Daten Tab fehlte +- ✅ BUG-003: Korrelations-Chart Extrapolation (gestrichelte Linien) +- ✅ BUG-004: Import-Historie Refresh (Force remount) +- ✅ BUG-005: Login → leere Seite +- ✅ BUG-006: Email-Verifizierung → leere Seite +- ✅ BUG-007: Doppelklick Verifizierungslink → generischer Fehler +- ✅ BUG-008: Dashboard infinite loading bei API-Fehlern + +--- + +## v9d – Schlaf + Sport-Vertiefung + +📚 **Specs:** +- `functional/SLEEP_MODULE.md` (Schlaf-Tracking) +- `functional/TRAINING_TYPES.md` (Trainingstypen + Abilities) +- `functional/DEVELOPMENT_ROUTES.md` (Routen-System, später v9e) + +**Status:** 🟡 In Arbeit (Deployed: Schlaf, Trainingstypen, Ruhetage, Vitalwerte | Offen: HF-Zonen) + +### Phase 1: Trainingstypen ✅ DEPLOYED (21.03.2026) +- ✅ 29 Trainingstypen in 7 Kategorien +- ✅ Lernendes Mapping-System (DB-basiert, Auto-Learning) +- ✅ 40+ Standard-Mappings (Deutsch + Englisch) +- ✅ Admin-UI für Trainingstypen-CRUD +- ✅ Admin-UI für Activity-Mappings (inline editing) +- ✅ TrainingTypeDistribution Chart in History +- ✅ Bulk-Kategorisierung (selbstlernend) +- ✅ Apple Health Import mit automatischem Mapping + +### Phase 2a: Ruhetage ✅ DEPLOYED (23.03.2026) +- ✅ Multi-Dimensional Rest Days (Kraft, Cardio, Entspannung) +- ✅ Quick Mode Presets + Custom Entry +- ✅ Validierung gegen geplante Aktivitäten +- ✅ Dashboard Widget mit aktuellen Ruhetagen +- ✅ Multiple Rest Types pro Date + +### Phase 2b: Schlaf-Modul ✅ DEPLOYED (23.03.2026) +- ✅ sleep_log Tabelle mit JSONB sleep_segments +- ✅ Schlafphasen (Deep, REM, Light, Awake) +- ✅ Apple Health CSV Import +- ✅ Schlaf-Statistiken & Trends +- ✅ Schlafschuld-Berechnung + +### Phase 2d: Vitalwerte ✅ DEPLOYED (23.03.2026) +- ✅ **3-Tab Architektur:** Baseline (morgens) / Blutdruck (mehrfach täglich) / Import +- ✅ **Baseline Vitals:** Ruhepuls, HRV, VO2 Max, SpO2, Atemfrequenz +- ✅ **Blutdruck:** Systolisch/Diastolisch + Puls, WHO/ISH-Klassifizierung +- ✅ **Context-Tagging:** 8 Kontexte (nüchtern, nach Essen, Training, Stress, etc.) +- ✅ **Inline-Editing:** Alle Messungen direkt in der Liste bearbeitbar +- ✅ **Smart Upsert:** Baseline lädt existierende Einträge automatisch +- ✅ **CSV Import:** Omron (Deutsch) + Apple Health (Deutsch/Englisch) +- ✅ **Mobile-optimiert:** Volle Breite Felder, Sektions-Überschriften +- ✅ Unregelmäßiger Herzschlag & AFib-Warnungen +- ✅ Trend-Analyse (7d/14d/30d) + +### Phase 2e: HF-Zonen + Erholung 🔲 OFFEN +- 🔲 HF-Zonen-Verteilung pro Training +- 🔲 Recovery Score basierend auf Ruhepuls + HRV + Schlaf +- 🔲 Übertraining-Warnung + +**Migrations:** +- ✅ 004: training_types Tabelle + 23 Basis-Typen +- ✅ 005: Extended types (Gehen, Tanzen, Geist & Meditation) +- ✅ 006: abilities JSONB column (Platzhalter für v9f) +- ✅ 007: activity_type_mappings (lernendes System) +- ✅ 010: sleep_log Tabelle (JSONB segments) +- ✅ 011: rest_days Tabelle +- ✅ 012: Unique constraint rest_days +- ✅ 015: Vitals Refactoring (vitals_baseline + blood_pressure_log) + +**Bugfixes v9d (23.03.2026):** +- ✅ Import-Zählung korrigiert (skipped vs. updated) +- ✅ Deutsche Spaltennamen für CSV-Imports +- ✅ Dezimalwerte-Parsing (safe_int/safe_float) +- ✅ Error-Details in Import-Response + +--- + +## v9e – Entwicklungsrouten & Wochenplanung ⭐ NEU + +📚 **Specs:** `functional/DEVELOPMENT_ROUTES.md` + +**Status:** 🔲 Geplant (Spezifikation vorhanden, nach v9d Phase 2) + +### Geplante Features +- 🔲 **6 Entwicklungsrouten:** Kraft, Kondition, Mental, Koordination, Mobilität, Technik +- 🔲 Activity-Types → Routen-Zuordnung (training_types.route Spalte) +- 🔲 Multi-Route Rest Day Validation (Konflikt-Check für alle Routen) +- 🔲 Wochenplanung: Routen-basierte Soll/Ist-Übersicht +- 🔲 Regel-Engine: Auto-Ruhetag bei Poor Recovery +- 🔲 Dashboard: Route-Balance Widget + +**Tracking:** Siehe [Gitea Milestone v9e](http://192.168.2.144:3000/Lars/mitai-jinkendo/milestones) + +--- + +## v9f – Ziele & KI-Prompts + +📚 **Specs:** +- `functional/GOALS_VITALS.md` (Ziele-System) +- `functional/AI_PROMPTS.md` (KI-Prompt-Flexibilisierung) + +**Status:** 🔲 Geplant (nach Phase 0 Infrastruktur) + +### Ziele-System +- 🔲 Primärziele (Gewichtsabnahme, Muskelaufbau, Kondition, Wettkampf) +- 🔲 Ziel-spezifische Dashboard-Ansicht +- 🔲 Fortschrittsbalken & Prognose (lineare Regression) +- 🔲 KI-Integration: Ziel-Abgleich & Handlungsempfehlungen + +### KI-Prompt Flexibilisierung +- 🔲 Prompt-Bibliothek mit Kategorien +- 🔲 Platzhalter-Browser (kategorisiert + Beispielwerte) +- 🔲 Prompt-Vorschau mit echten Daten +- 🔲 Pipeline konfigurierbar (Module, Gewichtung, Zeitraum) +- 🔲 Mehrere Pipeline-Konfigurationen speichern + +### Vitalwerte erweitert +- 🔲 Erweiterte Vitalwerte (SpO2, Körpertemperatur, Atemfrequenz) - teilweise in v9d implementiert +- 🔲 Hydration-Tracking +- 🔲 Medikamenten-Tracking + +**Tracking:** Siehe ROADMAP.md Phase 0-2 + +--- + +## v9g – Habits & Meditation + +📚 **Specs:** +- `functional/MEDITATION.md` (Meditation & Selbstwahrnehmung) +- `functional/DEVELOPMENT_ROUTES.md` (Habits pro Route) + +**Status:** 🔲 Geplant (Spezifikation vorhanden) + +### Geplante Features +- 🔲 **Habits pro Entwicklungsroute** (route_habits Tabelle) +- 🔲 Streak-Tracking pro Route (z.B. Mental: Meditation 🔥12 Tage) +- 🔲 Dashboard: Route-Habits Widget +- 🔲 Täglicher Check-in (Energie, Stimmung, Stress 1-5) +- 🔲 Meditationssessions erfassen (Dauer, Art) +- 🔲 Journal (Freitext täglich) +- 🔲 Korrelations-Analysen (Meditation ↔ Stresslevel, Schlafqualität) +- 🔲 → Basis für **miken.jinkendo.de** (Meditations-App) + +**Tracking:** Siehe [Gitea Milestone v9g](http://192.168.2.144:3000/Lars/mitai-jinkendo/milestones) + +--- + +## v9h – Connectoren & Gamification + +**Status:** 🔲 Konzept (keine Spezifikation vorhanden) + +### Geplante Features +- 🔲 OAuth2-Grundgerüst +- 🔲 Strava, Withings, Garmin Integration +- 🔲 Bonus-System (Streaks → Punkte → Coupons) +- 🔲 Stripe-Integration (Zahlungsabwicklung) + +--- + +## Responsive UI (parallel zu allen Versionen) + +📚 **Specs:** `functional/RESPONSIVE_UI.md` + +**Status:** 🔲 Geplant (parallel implementierbar) + +### Geplante Features +- 🔲 Desktop: Sidebar Navigation +- 🔲 Tablet: 2-spaltige Cards +- 🔲 Mobile: Bottom Navigation (bleibt wie jetzt) +- 🔲 **Admin-Bereich separieren** (eigene Route `/admin`, nur für Admins) + - Tab-basierte Struktur: Benutzerverwaltung, Feature-Limits, System-Settings, Coupons, AI-Prompts + - Settings-Page: Nur noch persönliche Einstellungen + +**Tracking:** Siehe [Gitea Issues mit Label "responsive"](http://192.168.2.144:3000/Lars/mitai-jinkendo/issues?labels=responsive) + +--- + +## UX-Verbesserungen & Quick Wins + +**Status:** Verschiedene kleine Verbesserungen, siehe Gitea Issues + +**Beispiele:** +- Keyboard Shortcuts (Enter für Submit, Esc für Cancel) +- Toast-Notifications statt confirm() Dialoge +- Loading-States bei langsamen API-Calls +- Mehrere Fotos pro Tag hochladen (aktuell nur 1 Foto/Tag) +- Fotos löschbar machen (aktuell keine Delete-Funktion) + +📋 **Tracking:** [Gitea Issues mit Label "ux" oder "quick-win"](http://192.168.2.144:3000/Lars/mitai-jinkendo/issues?labels=ux,quick-win) + +--- + +## Versions-Übersicht (Zusammenfassung) + +| Version | Status | Kernfeatures | Deployed | +|---------|--------|--------------|----------| +| **v9c** | ✅ KOMPLETT | Membership, Feature-Enforcement, Trial-System | 21.03.2026 | +| **v9d** | 🟡 In Arbeit | Schlaf, Trainingstypen, Ruhetage, Vitalwerte | 23.03.2026 (partial) | +| **v9e** | 🔲 Geplant | Development Routes, Wochenplanung | TBD | +| **v9f** | 🔲 Geplant | Ziele, KI-Prompts, Charts | TBD | +| **v9g** | 🔲 Geplant | Habits, Meditation, Streaks | TBD | +| **v9h** | 🔲 Konzept | Connectoren, Gamification, Stripe | TBD | + +--- + +**Dokumentiert:** 23. März 2026 (Initial) · 24. März 2026 (Konsolidiert) +**Tracking:** +- Konkrete Issues → [Gitea](http://192.168.2.144:3000/Lars/mitai-jinkendo/issues) +- Feature-Specs → `.claude/docs/functional/*.md` +- Strategische Planung → `ROADMAP.md` +- Versions-Übersicht → Diese Datei diff --git a/.claude/docs/CLEANUP_PLAN.md b/.claude/docs/CLEANUP_PLAN.md new file mode 100644 index 0000000..2f7c86b --- /dev/null +++ b/.claude/docs/CLEANUP_PLAN.md @@ -0,0 +1,374 @@ +# Dokumentations-Bereinigung & Struktur-Konzept + +**Erstellt:** 2026-03-23 +**Ziel:** Redundanzen entfernen, klare Trennung zwischen Gitea (Tracking) und Docs (Spezifikationen) + +--- + +## Problem-Analyse + +### Aktuelle Situation + +**Planungs-/Tracking-Dokumente (.claude/docs/):** +- `BACKLOG.md` (~138 Zeilen) - Feature-Übersicht v9c-v9h +- `ROADMAP.md` (~414 Zeilen) - Detaillierte Phasenplanung (Phase 0-3, Issues #24-#30) +- `KNOWN_ISSUES.md` (~100+ Zeilen) - Bug-Tracking (BUG-009, BUG-003, etc.) +- `PENDING_FEATURES.md` (~50+ Zeilen) - Feature-Enforcement-TODOs +- `TODO_V9D_PHASE2.md` - Spezifische Phase-2-TODOs +- `ISSUES_TO_CREATE.md` - Temporär (Issues bereits erstellt) +- `.claude/feature-requests/logout-button-header.md` - Einzelner Feature Request + +**Spezifikationen:** +- `functional/` - SLEEP_MODULE.md, TRAINING_TYPES.md, DEVELOPMENT_ROUTES.md, etc. +- `technical/` - MEMBERSHIP_SYSTEM.md, DATABASE.md, ARCHITECTURE.md, etc. +- `architecture/` - FEATURE_ENFORCEMENT.md +- `rules/` - ARCHITECTURE.md, CODING_RULES.md, LESSONS_LEARNED.md + +### Redundanzen & Probleme + +1. **Doppeltes Tracking:** + - ROADMAP.md enthält Issues #24-#30 + - Diese sollten primär in Gitea sein + - ROADMAP.md enthält außerdem strategische Planung (Phase 0-3) + +2. **Bug-Tracking außerhalb Gitea:** + - KNOWN_ISSUES.md dokumentiert Bugs + - BUG-009 ist offen, sollte als Gitea-Issue existieren + +3. **Feature-Requests verstreut:** + - BACKLOG.md listet große Features + - PENDING_FEATURES.md listet TODOs + - .claude/feature-requests/ hat einzelne Requests + - Unklare Prioritäten + +4. **TODOs ohne Kontext:** + - TODO_V9D_PHASE2.md ist phasenspezifisch + - Nach Phase-Abschluss veraltet + +5. **Temporäre Dateien:** + - ISSUES_TO_CREATE.md (bereits erledigt, kann weg) + +--- + +## Vorgeschlagene Struktur + +### Prinzip: Gitea für Tracking, Docs für Spezifikationen + +``` +┌─────────────────────────────────────────────────────────────┐ +│ GITEA ISSUES │ +│ (Primäres Tracking für Tasks, Bugs, kleine Features) │ +├─────────────────────────────────────────────────────────────┤ +│ - Konkrete implementierbare Tasks │ +│ - Bugs (BUG-001, BUG-002, ...) │ +│ - Kleine Features (≤ 1 Tag Aufwand) │ +│ - Phase-Tasks (#24-#30) │ +│ - Technical Debt (#32-#35) │ +└─────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────┐ +│ .claude/docs/functional/ │ +│ (Fachliche Spezifikationen für große Features) │ +├─────────────────────────────────────────────────────────────┤ +│ - SLEEP_MODULE.md (v9d) │ +│ - TRAINING_TYPES.md (v9d) │ +│ - DEVELOPMENT_ROUTES.md (v9e) │ +│ - MEDITATION.md (v9g) │ +│ - AI_PROMPTS.md (v9f) │ +│ - GOALS_VITALS.md (v9f) │ +│ - RESPONSIVE_UI.md │ +└─────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────┐ +│ .claude/docs/technical/ │ +│ (Technische Spezifikationen & Referenz-Dokumentation) │ +├─────────────────────────────────────────────────────────────┤ +│ - ARCHITECTURE.md (System-Übersicht) │ +│ - FRONTEND.md (Seiten, Komponenten, API-Integration) │ +│ - AUTH.md (Auth-Flow, Sicherheit) │ +│ - API_REFERENCE.md (Alle Endpoints) │ +│ - DATABASE.md (Schema, Tabellen) │ +│ - MEMBERSHIP_SYSTEM.md (v9c Membership) │ +│ - MIGRATIONS.md (falls vorhanden) │ +└─────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────┐ +│ .claude/docs/ (Root-Level Meta-Dokumentation) │ +├─────────────────────────────────────────────────────────────┤ +│ - README.md (Dokumentations-Index + Konventionen) │ +│ - CONTRIBUTING.md (Entwickler-Onboarding) │ +│ - CHANGELOG.md (Versions-Historie, generiert aus git) │ +└─────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────┐ +│ .claude/rules/ │ +│ (Verbindliche Entwicklungsregeln) │ +├─────────────────────────────────────────────────────────────┤ +│ - ARCHITECTURE.md (Architektur-Regeln, Versionierung) │ +│ - CODING_RULES.md (Code-Standards) │ +│ - LESSONS_LEARNED.md (Fehler, die nicht wiederholt werden) │ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +## Konkrete Bereinigungsaktionen + +### 1. LÖSCHEN (Redundant oder temporär) + +| Datei | Grund | Aktion | +|-------|-------|--------| +| `ISSUES_TO_CREATE.md` | Temporär, Issues bereits erstellt (#32-#35) | **DELETE** | +| `TODO_V9D_PHASE2.md` | Phasenspezifisch, veraltet nach Abschluss | **DELETE** (oder archivieren) | +| `.claude/feature-requests/logout-button-header.md` | Einzelner Request, gehört in Gitea | **→ Gitea Issue, dann DELETE** | + +### 2. MIGRIEREN zu Gitea Issues + +| Quelle | Inhalt | Ziel-Aktion | +|--------|--------|-------------| +| `KNOWN_ISSUES.md` | BUG-009 (offen) | **→ Gitea Issue #36** (Bug) | +| `KNOWN_ISSUES.md` | BUG-003 (behoben) | **→ Archiv oder DELETE** (bereits behoben) | +| `PENDING_FEATURES.md` | Feature-Enforcement TODOs | **→ Gitea Issues #37-#39** (enhancement) | +| `feature-requests/logout-button-header.md` | Logout-Button Request | **→ Gitea Issue #40** (Quick Win) | + +### 3. KONSOLIDIEREN + +#### 3.1. ROADMAP.md → Aufteilen + +**Problem:** Enthält sowohl strategische Planung als auch konkrete Issues + +**Lösung:** + +**BEHALTEN in ROADMAP.md (strategisch):** +- Phase-Übersicht (Phase 0-3) +- Meilensteine (M0.1-M3.x) +- Abhängigkeiten-Graph +- Timeline (KW-Planung) +- Risiken & Mitigationen +- Erfolgsmetriken + +**MIGRIEREN zu Gitea (operativ):** +- Konkrete Issue-Beschreibungen (#24-#30) → Gitea Issues (bereits vorhanden?) +- Aufwands-Schätzungen → in Gitea Issue-Feldern +- Checklisten → in Gitea Issue-Beschreibungen + +**RESULTAT:** ROADMAP.md wird zum strategischen Phasenplan (ohne Issue-Details) + +#### 3.2. BACKLOG.md → Reduzieren + +**Problem:** Mischung aus Feature-Übersicht und TODO-Listen + +**Lösung:** + +**BEHALTEN in BACKLOG.md:** +- Version-Übersicht (v9c-v9h Features) +- Links zu functional/-Specs +- Status-Übersicht (✅ KOMPLETT, 🟡 In Arbeit, 🔲 Geplant) + +**ENTFERNEN aus BACKLOG.md:** +- Detaillierte Checklisten (gehören in Gitea Issues) +- "Offene Issues" Listen (primär in Gitea) +- Quick Wins (→ Gitea mit Label "quick-win") + +**RESULTAT:** BACKLOG.md wird zum Feature-Katalog (Verweis auf Specs + Gitea) + +#### 3.3. KNOWN_ISSUES.md → Archivieren oder löschen + +**Vorschlag 1 (empfohlen):** DELETE +- Aktive Bugs → Gitea Issues +- Behobene Bugs → git commit messages + CHANGELOG.md +- Lessons Learned → `.claude/rules/LESSONS_LEARNED.md` + +**Vorschlag 2:** Archivieren +- Umbenennen zu `KNOWN_ISSUES_ARCHIVE.md` +- Nur für Referenz (nicht aktiv gepflegt) + +#### 3.4. PENDING_FEATURES.md → DELETE + +**Grund:** Feature-Enforcement-TODOs sind konkrete Tasks + +**Aktion:** +1. TODOs in Gitea Issues überführen (#37-#39) +2. Datei löschen + +--- + +## Gitea Issues erstellen (aus existierenden Docs) + +### Aus KNOWN_ISSUES.md + +**#36: BUG-009 - Trainingstyp-Erstellung führt zu Internal Server Error** +- Priorität: High +- Label: bug, admin +- Aufwand: 1-2h + +### Aus PENDING_FEATURES.md + +**#37: Feature-Enforcement für Activity CSV-Import** +- Priorität: Medium +- Label: enhancement, feature-enforcement +- Aufwand: 2-3h + +**#38: Feature-Enforcement für Nutrition CSV-Import UI** +- Priorität: Low +- Label: enhancement, feature-enforcement, ui +- Aufwand: 1h + +**#39: Usage-Badges im Dashboard-Assistenten** +- Priorität: Low +- Label: enhancement, ux +- Aufwand: 1-2h + +### Aus feature-requests/ + +**#40: Logout-Button im App-Header (neben Avatar)** +- Priorität: Low +- Label: quick-win, ux +- Aufwand: 15min + +--- + +## Vorgeschlagene Konventionen + +### Was gehört in Gitea Issues? + +✅ **JA:** +- Bugs (alle Prioritäten) +- Konkrete implementierbare Tasks (≤ 5 Tage Aufwand) +- Quick Wins (≤ 1h Aufwand) +- Technical Debt Items +- Phase-spezifische Tasks (#24-#30) +- Refactorings + +❌ **NEIN:** +- Große Feature-Pakete (v9e, v9f, v9g) → bleiben in functional/-Specs +- Strategische Planung (Phasen, Meilensteine) → bleibt in ROADMAP.md +- Architektur-Entscheidungen → bleiben in technical/-Docs +- Lessons Learned → bleiben in rules/LESSONS_LEARNED.md + +### Was gehört in functional/-Specs? + +✅ **Große Feature-Pakete (> 5 Tage Aufwand):** +- SLEEP_MODULE.md (v9d Phase 2b) +- TRAINING_TYPES.md (v9d Phase 1) +- DEVELOPMENT_ROUTES.md (v9e) +- AI_PROMPTS.md (v9f) +- MEDITATION.md (v9g) + +**Format:** +- Fachliche Anforderungen (WAS wird gebaut) +- User Stories +- Use Cases +- Datenmodell (Konzept) +- UI-Mockups (optional) +- Verweis auf Gitea Epic/Milestone + +### Was gehört in technical/-Specs? + +✅ **Referenz-Dokumentation & Implementierungsdetails:** +- ARCHITECTURE.md (System-Übersicht) +- DATABASE.md (Schema-Referenz) +- API_REFERENCE.md (Endpoint-Katalog) +- Modul-spezifische Docs (MEMBERSHIP_SYSTEM.md) + +**Format:** +- Technische Entscheidungen (WIE wird es gebaut) +- API-Spezifikationen +- Datenbank-Schema +- Code-Beispiele +- Design-Patterns + +--- + +## Migrations-Plan + +### Phase 1: Gitea Issues erstellen (1h) + +1. **BUG-009** → Gitea Issue #36 +2. **PENDING_FEATURES TODOs** → Gitea Issues #37-#39 +3. **Logout-Button Request** → Gitea Issue #40 + +### Phase 2: Dokumentation bereinigen (30min) + +1. **DELETE:** + - `ISSUES_TO_CREATE.md` + - `TODO_V9D_PHASE2.md` + - `.claude/feature-requests/logout-button-header.md` + - `KNOWN_ISSUES.md` + - `PENDING_FEATURES.md` + +2. **KONSOLIDIEREN:** + - `ROADMAP.md` → Entferne Issue-Details, behalte Strategie + - `BACKLOG.md` → Entferne Checklisten, behalte Feature-Katalog + +### Phase 3: README.md erstellen (30min) + +**Datei:** `.claude/docs/README.md` + +**Inhalt:** +- Dokumentations-Index (Was finde ich wo?) +- Konventionen (Gitea vs. Docs) +- Wie lege ich Issues an? +- Wie erstelle ich Spezifikationen? + +--- + +## Ergebnis nach Bereinigung + +### Datei-Reduktion + +**Vorher:** +- 7 Tracking-Dokumente (.claude/docs/) +- 1 Feature-Request-Datei +- ~700+ Zeilen redundante Informationen + +**Nachher:** +- 2 Meta-Dokumente (ROADMAP.md, BACKLOG.md) - deutlich schlanker +- 1 README.md (Dokumentations-Index) +- ~5-7 Gitea Issues (primäres Tracking) + +### Klare Trennung + +``` +Gitea Issues (Tracking) + ↓ + Konkrete Tasks, Bugs, Quick Wins + Labels: bug, enhancement, quick-win + Aufwand: ≤ 5 Tage + +.claude/docs/functional/ (Spezifikationen) + ↓ + Große Feature-Pakete (> 5 Tage) + Format: Fachliche Anforderungen + Verweis auf Gitea Epic/Milestone + +.claude/docs/technical/ (Referenz) + ↓ + Implementierungsdetails + Format: Technische Spezifikationen + Generiert/aktualisiert nach Implementierung + +.claude/rules/ (Regeln) + ↓ + Verbindliche Entwicklungsstandards + Format: Architektur-Regeln, Code-Standards +``` + +--- + +## Nächste Schritte + +1. **Review:** User-Zustimmung zu diesem Plan einholen +2. **Gitea:** Issues #36-#40 anlegen +3. **Cleanup:** Dateien löschen/konsolidieren +4. **README:** `.claude/docs/README.md` erstellen +5. **ROADMAP/BACKLOG:** Kürzen auf strategische Infos +6. **Commit:** "docs: cleanup and consolidate documentation structure" + +--- + +**Frage an User:** +- Sollen große Feature-Pakete (v9e, v9f, v9g) als Gitea **Milestones** angelegt werden? + - Pro: Alle Tasks/Issues zuordenbar + - Contra: Specs bleiben trotzdem in functional/ + - **Empfehlung:** Ja, Milestones in Gitea + Verweis in functional/-Specs diff --git a/.claude/docs/GITEA_ISSUES_INDEX.md b/.claude/docs/GITEA_ISSUES_INDEX.md new file mode 100644 index 0000000..ca04bda --- /dev/null +++ b/.claude/docs/GITEA_ISSUES_INDEX.md @@ -0,0 +1,109 @@ +# Gitea Issues – Landkarte (Auswertung) + +**Quelle:** Gitea `Lars/mitai-jinkendo`, Stand **2026-04-08** (Abfrage `state=all`). +**URL:** http://192.168.2.144:3000/Lars/mitai-jinkendo/issues + +Dieses Dokument ist ein **Orientierungs-Index** für Agenten und Entwickler. Verbindliches Tracking bleibt **in Gitea**; hier: Kategorien, Dubletten-Hinweise, grobe Prioritätseinschätzung. + +--- + +## Kurzdiagnose + +- **Offene Issues (Stand Abfrage):** 28 +- **Auffällig:** mehrere **titelgleiche oder nahezu gleiche** Einträge (z. B. Body Cluster, Placeholder Registry, Debug UI) – in Gitea konsolidieren oder schließen. +- **#25 „Ziele-System“** wahrscheinlich **veraltet** (laut Projektstatus Goals/Focus bereits fortgeschritten) – mit Produkt-Owner abgleichen und schließen oder umbenennen. + +--- + +## Offene Issues nach Thema + +### Architektur / Plattform + +| # | Titel | +|---|--------| +| 68 | Architektur: durchgängige Feature-/Entitlement-Schicht (Ist → Zielbild, Phasen) | +| 32 | Version-System: /api/version, Frontend, main.py-Konsistenz | +| 34 | External Volumes dokumentieren (bodytrack_* Legacy-Cleanup) | +| 35 | Deprecated Tabelle „subscriptions“ entfernen | + +### Dashboard & Widgets + +| # | Titel | +|---|--------| +| 65 | Dashboard: Nutzer-konfigurierbare Übersicht (Widget-System, Persistenz, Standard-Reset) | +| 66 | Dashboard-Widgets: Feature-Gate-Zuordnung aus Admin/DB statt hardcodiertem Katalog | +| 39 | Usage-Badges im Dashboard-Assistenten | + +### KI / Prompts / Transparenz + +| # | Titel | +|---|--------| +| 49 | Feature: Prompt-Zuordnung zu Verlaufsseiten | +| 45 | Feature: KI Prompt-Optimierer | +| 46 | Feature: KI Prompt-Ersteller (Guided Creator) | +| 42 | Enhanced Debug/Prompt Analysis UI (Issue #28 Phase C) | +| 43 | Enhanced Debug/Prompt Analysis UI (Issue #28 Phase C) | +| 47 | Enhancement: Wertetabelle Optimierung | + +### Placeholder / Registry + +| # | Titel | +|---|--------| +| 54 | Placeholder Registry: UNRESOLVED & TO_VERIFY Metadaten prüfen | +| 55 | Placeholder Registry: UNRESOLVED & TO_VERIFY Metadaten prüfen | + +### Data / Body / Cluster (Dubletten) + +| # | Titel | +|---|--------| +| 56 | Body Cluster - Restarbeiten & Metadaten-Verifizierung | +| 57 | Body Cluster - Restarbeiten & Metadaten-Verifizierung | +| 58 | Body Cluster - Restarbeiten & Metadaten-Verifizierung | + +### Frontend / UX + +| # | Titel | +|---|--------| +| 30 | [FEAT] Responsive UI - Desktop Sidebar + 2-spaltige Layouts | +| 29 | [FEAT] Abilities-Matrix UI (v9f) | +| 40 | Logout-Button im App-Header (neben Avatar) | +| 26 | [FEAT] Charts & Visualisierungen erweitern | +| 27 | [FEAT] Korrelationen & Insights erweitern | + +### Ziele / Product (prüfen) + +| # | Titel | +|---|--------| +| 25 | [FEAT] Ziele-System (Goals) - v9e Kernfeature | + +### Feature-Enforcement / Import + +| # | Titel | +|---|--------| +| 37 | Feature-Enforcement für Activity CSV-Import | +| 38 | Feature-Enforcement für Nutrition CSV-Import UI | + +### Qualität / Sonstiges + +| # | Titel | +|---|--------| +| 15 | [FEAT-002] Quality-Filter für KI-Auswertungen & Charts integrieren | +| 21 | [FEATURE] Universeller CSV-Parser mit lernbarem Feldmapping | +| 36 | BUG-009: Trainingstyp-Erstellung führt zu Internal Server Error | + +--- + +## Geschlossene Bereiche (Referenz, Auszug) + +Bereits geschlossen u. a.: #28 AI-Prompts Flexibilisierung, #24 Quality-Filter (Variante), #48 Flexibles Prompt-System, #50/#51 Goals, #53 Goals/Platzhalter/Data Layer (laut Titel), #60 Platzhalter Ernährung/Körper/Aktivität, diverse Releases. + +--- + +## Pflege + +- Nach größeren Issue-Aufräumaktionen: diese Datei **aktualisieren** oder Datum im Kopf erhöhen. +- Dubletten (#42/#43, #54/#55, #56–#58): in Gitea **zusammenführen** (ein Issue offen lassen). + +--- + +**Ende Index** diff --git a/.claude/docs/README.md b/.claude/docs/README.md new file mode 100644 index 0000000..713510a --- /dev/null +++ b/.claude/docs/README.md @@ -0,0 +1,177 @@ +# Mitai Jinkendo – Dokumentations-Index + +Willkommen zur Entwicklerdokumentation von **Mitai Jinkendo** (身体 Jinkendo). + +**Parallel im Repository:** Issue-Epics und Placeholder-Governance im Projekt-[`docs/`](../../docs/) · siehe [`docs/README.md`](../../docs/README.md). + +**Ablage-Regeln:** [`.claude/rules/DOCUMENTATION.md`](../rules/DOCUMENTATION.md) · **Issue-Landkarte:** [`GITEA_ISSUES_INDEX.md`](./GITEA_ISSUES_INDEX.md). + +_Dieser Ordner `.claude/docs/` ist per `.gitignore`-Ausnahme **versioniert** (Specs + Regeln)._ + +--- + +## Dokumentationsstruktur (Ist-Stand) + +``` +.claude/docs/ +├── README.md ← Index (diese Datei) +├── GITEA_ISSUES_INDEX.md ← Themen-Landkarte zu Gitea (lokal gepflegt) +├── BACKLOG.md ← Feature-Katalog nach Versionen +├── ROADMAP.md ← Strategische Phasen (0–3) +├── CLEANUP_PLAN.md ← Historie Bereinigung März 2026 +├── prompts/ ← Exportierte Prompt-Artefakte (JSON) +├── functional/ ← Fachliche Spezifikationen (WAS) +├── technical/ ← Technische Spezifikationen & Referenz (WIE) +├── working/ ← Arbeitspapiere, Analysen, Session-Snapshots +├── architecture/ ← Querschnitt: Backend/Frontend/Enforcement (kompakt) +└── audit/ ← Audits, Matrizen, Gitea-Vorlagen +``` + +**Externes Tracking:** [Gitea Issues](http://192.168.2.144:3000/Lars/mitai-jinkendo/issues) + +**Erzeugte Library (Kurzreferenz, ggf. `/document`):** `.claude/library/` – API-, DB- und Architektur-Übersichten; bei Abweichungen gewinnt der **Code**, dann **`technical/`** bzw. **`functional/`**. + +--- + +## Rollen der Ordner + +| Ordner | Zweck | Pflege | +|--------|--------|--------| +| `functional/` | Domäne, UX, fachliche Datenflüsse | Bei Feature-Änderungen / Abnahme | +| `technical/` | APIs, Migrationen, Implementierungsmuster, Agent-Guides | Nach größeren Code-Änderungen | +| `architecture/` | Kompakte Architektur-Snippets (z. B. Frontend-Struktur, Backend-Überblick) | Bei strukturellen Umbauten | +| `audit/` | Snapshots (Code-Audit, Platzhalter-Reconciliation) – **nicht** normative Spec | Nur bei neuen Audits erweitern | +| `working/` | Zwischenstände (Migration, Goals-Analysen, STATUS, NEXT_STEPS, PHASE_0C_TASKS, …) | Archivarisch; keine alleinige Norm | +| Root (`ROADMAP`, `BACKLOG`, `GITEA_ISSUES_INDEX`) | Planung, Katalog, Issue-Landkarte | Mit Gitea / `working/` abstimmen | + +--- + +## Abgleich „Dokument ↔ Code“ (Orientierung) + +| Thema | Dokument(e) | Prüfpunkt im Repo | +|--------|-------------|-------------------| +| Data Layer / Charts (Phase 0c) | `functional/DATA_ARCHITECTURE.md`, `technical/DATA_LAYER_EXTENSION_GUIDE.md` | `backend/data_layer/`, `backend/routers/charts.py` | +| Platzhalter / Registry | `technical/PLACEHOLDER_REGISTRY_FRAMEWORK.md`, `technical/PLACEHOLDER_DEVELOPMENT_GUIDE.md` | `backend/placeholder_registrations/`, `backend/placeholder_resolver.py` | +| Dashboard-Lab-Widgets | `technical/DASHBOARD_WIDGETS_AGENT_GUIDE.md` | Widget-Katalog + Registrierung (siehe Guide) | +| Training Profiler / Resolver | `technical/TRAINING_PROFILE_RESOLVER_LAYER1.md`, `functional/TRAINING_TYPE_PROFILES.md` | Resolver-Module wie im Guide genannt | +| Mitgliedschaft / Features | `technical/MEMBERSHIP_SYSTEM.md`, `architecture/FEATURE_ENFORCEMENT.md` | `backend/auth.py`, Feature-Logging, Router mit Enforcement | + +--- + +## Projekt-Übersicht & Regeln (außerhalb dieses Ordners) + +| Dokument | Inhalt | +|----------|--------| +| `CLAUDE.md` (Root) | Agent-Kontext, Tech-Stack, kritische Regeln | +| `.claude/rules/ARCHITECTURE.md` | Verbindliche Architektur-Regeln | +| `.claude/rules/CODING_RULES.md` | Code-Standards | +| `.claude/rules/LESSONS_LEARNED.md` | Wiederkehrende Fehler vermeiden | + +--- + +## Fachliche Spezifikationen (`functional/`) + +| Dokument | Thema | Hinweis | +|----------|--------|---------| +| `ACTIVITY_QUALITY_GATES.md` | Qualitäts-Gates Aktivität | | +| `AI_PROMPTS.md` | KI-Prompts, Pipeline, Platzhalter (fachlich) | | +| `DATA_ARCHITECTURE.md` | **Fachliche** Datenarchitektur, Domänenflüsse | Schema-Details → `.claude/library/DATABASE.md` | +| `DEVELOPMENT_ROUTES.md` | Entwicklungsrouten (Roadmap-Thema) | | +| `GOALS_VITALS.md` | Ziele & Vitalwerte (fachlich) | | +| `mitai_jinkendo_konzept_diagramme_auswertungen.md` | Konzept Diagramme (ältere Linie) | | +| `mitai_jinkendo_konzept_diagramme_auswertungen_v2.md` | Konzept Diagramme v2 | | +| `PHASE_0B_IMPROVEMENTS.md` | Phase-0b-Verbesserungen | | +| `RESPONSIVE_UI.md` | Responsive Layout-Spec | siehe auch `docs/issues/PHASE_PLAN_RESPONSIVE_UI.md` | +| `SLEEP_MODULE.md` | Schlaf-Modul | | +| `TRAINING_TYPES.md` | Trainingstypen, Mapping | | +| `TRAINING_TYPE_PROFILES.md` | Trainings-Profile (fachlich) | Techn. Ergänzung: `technical/TRAINING_TYPE_PROFILES_TECHNICAL.md` | + +--- + +## Technische Spezifikationen (`technical/`) + +| Dokument | Thema | +|----------|--------| +| `AGGREGATION_METHODS.md` | Aggregation | +| `API_REFERENCE.md` | HTTP-API-Katalog | +| `ARCHITECTURE.md` | System-Architektur | +| `AUTH.md` | Authentifizierung | +| `CENTRAL_SUBSCRIPTION_SYSTEM.md` | Abo-Zentralismus | +| `DASHBOARD_WIDGETS_AGENT_GUIDE.md` | Dashboard-Widgets für Agents | +| `DATABASE.md` | DB-Referenz (docs-Kopie; Library kann aktueller sein) | +| `DATABASE_MODEL_COMPLETE.md` | Vollständiges Modell (Referenz) | +| `DATA_LAYER_EXTENSION_GUIDE.md` | Erweiterung Data Layer | +| `FEATURE_ENFORCEMENT_MAPPING.md` | Feature-IDs / Mapping | +| `FRONTEND.md` | Frontend ausführlich (Seiten, Patterns) | +| `INTERNAL_API_REFERENCE.md` | Interne APIs | +| `MEMBERSHIP_SYSTEM.md` | Tiers, Grants, Enforcement-Details | +| `MIGRATIONS.md` | Migrations-Prozess | +| `PLACEHOLDER_DEVELOPMENT_GUIDE.md` | Platzhalter entwickeln | +| `PLACEHOLDER_REGISTRY_FRAMEWORK.md` | Registry-Pflicht, Metadaten | +| `PROFILE_REFERENCE_VALUES.md` | Profil-Referenzwerte | +| `TRAINING_PROFILE_RESOLVER_LAYER1.md` | Training-Resolver Schicht 1 | +| `TRAINING_TYPE_PROFILES_TECHNICAL.md` | Trainingsprofile technisch | +| `V9D_PHASE2_VITALS_SLEEP.md` | v9d Vitalwerte/Schlaf (Release-Bezug) | + +--- + +## Architektur-Kurzdoks (`architecture/`) + +| Dokument | Inhalt | +|----------|--------| +| `BACKEND.md` | Backend-Querschnitt | +| `FEATURE_ENFORCEMENT.md` | 4-Phasen Enforcement | +| `FRONTEND.md` | Strukturbaum `frontend/src` (kompakt) | + +*Hinweis:* `architecture/FRONTEND.md` ist die **kurze** Strukturübersicht; Details und Seitenliste stehen in `technical/FRONTEND.md`. + +--- + +## Audit (`audit/`) + +Siehe [`audit/README.md`](./audit/README.md). + +--- + +## Gitea vs. Docs + +**Prinzip:** Gitea für **Tracking**, Docs für **Spezifikationen** und **Referenz**. + +### Gehört in Gitea Issues +- Konkrete Tasks (typisch ≤ wenige Tage), Bugs, Quick Wins, Technical Debt + +### Gehört in `functional/` +- Größere Feature-Pakete, Domänenanforderungen, Use Cases, Konzeptdiagramme + +### Gehört in `technical/` +- API-, DB- und Implementierungsreferenz, Agent-Leitfäden, Migrations-Details + +### Gehört in `ROADMAP.md` / `BACKLOG.md` +- Strategische Phasen bzw. Versions-Katalog mit Links auf Specs und Issues + +### Nicht dauerhaft in Docs +- Temporäre To-do-Listen ohne Kontext (→ Issue); reine Duplikate anderer Dateien + +--- + +## Workflow nach Feature-Abschluss + +1. Gitea Issue schließen (Commit-Referenz). +2. `BACKLOG.md` / `ROADMAP.md` bei Meilensteinen anpassen. +3. Betroffene `functional/` / `technical/` / `.claude/library/`-Dateien aktualisieren. +4. Bei sichtbaren Agent-Regeln: `CLAUDE.md` ergänzen. + +--- + +## Deployment (Kurzreferenz) + +| Umgebung | Frontend | Backend | +|----------|----------|---------| +| Production | Port 3002 | Port 8002 | +| Development | Port 3099 | Port 8099 | + +**Gitea:** http://192.168.2.144:3000/Lars/mitai-jinkendo + +--- + +**Letzte Aktualisierung:** 8. April 2026 (Struktur-Index, Duplikatbereinigung, Abgleich-Hinweise) diff --git a/.claude/docs/ROADMAP.md b/.claude/docs/ROADMAP.md new file mode 100644 index 0000000..3fd2ca4 --- /dev/null +++ b/.claude/docs/ROADMAP.md @@ -0,0 +1,550 @@ +# Mitai Jinkendo – Entwicklungs-Roadmap + +**Version:** 2.2 +**Status:** Aktiv - Phase 0a ✅ Complete, Phase 0b ✅ Complete, Phase 0c 🎯 Next +**Letzte Aktualisierung:** 28. März 2026 + +--- + +## Überblick + +Diese Roadmap transformiert Mitai Jinkendo von einer **Datensammlungs-App** zu einem **aktiven Begleiter** mit Auswertungen, Empfehlungen und gezielten Entwicklungspfaden. + +**Strategische Ziele:** +1. ✅ **Infrastruktur schaffen** (v9f) → Flexible KI-Analysen → **DONE (AI Prompts, Training Types)** +2. ✅ **Goals System Foundation** (v0.9g-h) → Strategic + Tactical Goals → **DONE (Phase 0a + Dynamic Focus Areas v2.0)** +3. ✅ **Goal-Aware Intelligence** (Phase 0b) → Platzhalter + Auto-Population → **DONE (28.03.2026)** +4. 🎯 **Data Architecture** (Phase 0c) → Multi-Layer Separation → **NEXT** +5. 🔲 **Visualisierung stärken** → Charts, Diagramme, Trends +6. 🔲 **Aktive Begleitung** → Wochenplanung, Development Routes + +**Tracking:** +- Konkrete Tasks und Issues → [Gitea Issues](http://192.168.2.144:3000/Lars/mitai-jinkendo/issues) +- Große Feature-Pakete → `.claude/docs/functional/*.md` Spezifikationen +- Strategische Planung → Diese ROADMAP.md +- Aktueller Status → `docs/STATUS_2026-03-28.md` + +--- + +## Phasen-Übersicht + +| Phase | Fokus | Dauer | Status | +|-------|-------|-------|--------| +| **Phase 0a** | Goals System Foundation | 1 Woche | ✅ **COMPLETE** (26-27.03.2026) | +| **Phase 0b** | Goal-Aware Placeholders + Auto-Population | 1 Tag | ✅ **COMPLETE** (28.03.2026) | +| **Phase 0c** | Multi-Layer Data Architecture | 5-7 Tage | 🎯 **NEXT** | +| **Phase 1** | Charts & Visualisierung (Frontend) | 2-3 Wochen | 🔲 Geplant | +| **Phase 2** | Engagement (Korrelationen) | 3-4 Wochen | 🔲 Geplant | +| **Phase 3** | Begleitung (Development Routes) | 4-6 Wochen | 🔲 Später | + +**Gesamtdauer (Phase 0-2):** ~11-14 Wochen +**Gesamtaufwand (Phase 0-2):** ~80-100h +**Abgeschlossen:** Phase 0a (Goals System), Phase 0b (Placeholders + Auto-Population) + +--- + +## ✅ Phase 0a: Goals System Foundation (COMPLETE) + +**Zeitraum:** 26-27. März 2026 +**Aufwand:** 4-5h (tatsächlich) +**Status:** ✅ ABGESCHLOSSEN + +### Deliverables (alle ✅) +- ✅ Migration 022: goals, training_phases, fitness_tests tables +- ✅ Migration 027-032: Dynamic Focus Areas v2.0 + - 26 Basis-Bereiche in 7 Kategorien + - Many-to-Many: Goals ↔ Focus Areas + - User-spezifische Gewichtungen +- ✅ Backend: goals.py + focus_areas.py Router +- ✅ Frontend: GoalsPage (strategic) + CustomGoalsPage (tactical) +- ✅ Admin: AdminFocusAreasPage (CRUD UI) + +### Achievements +- Vollständiges Goal System mit Progress Tracking +- Dynamische Focus Areas (user-extensible) +- Mobile-friendly Design +- **Basis geschaffen für Phase 0b (120+ goal-aware Platzhalter)** + +📚 **Dokumentation:** +- `docs/issues/issue-50-phase-0a-goal-system.md` +- `docs/issues/issue-51-dynamic-focus-areas-v2.md` +- `docs/NEXT_STEPS_2026-03-26.md` + +--- + +## ✅ Phase 0b: Goal-Aware Placeholders + Auto-Population (COMPLETE) + +**Zeitraum:** 28. März 2026 +**Aufwand:** ~8h (tatsächlich) +**Status:** ✅ ABGESCHLOSSEN + +### Deliverables (alle ✅) +- ✅ **Neue Platzhalter-Funktionen:** + - `{{body_progress_score}}` - Goal-mode-abhängiger Body Score + - `{{active_goals}}` - JSON Array aktiver Ziele + - `{{focus_areas}}` - JSON Array gewichteter Focus Areas + - `{{health_stability_score}}` - Vitalwerte-Stabilität + - Nutrition-Metriken (avg_per_week_30d, etc.) +- ✅ **Score-System:** `map_focus_to_score_components()` in goal_utils.py +- ✅ **Auto-Population System:** + - `_get_historical_value_for_goal_type()` - Findet erste Messung + - Auto-adjustment von start_date zu tatsächlichem Messdatum + - Windowing-Logik für alle Goal-Typen +- ✅ **Time-Based Tracking:** + - Linear Progress Model: `expected = (elapsed_days / total_days) × 100` + - Deviation Calculation: `actual - expected` + - Hybrid Display: mit/ohne target_date +- ✅ **20+ Bug Fixes:** Decimal/float conversion, column names, dict access + +### Achievements +- Goal-aware KI-Platzhalter funktionieren +- Automatische Startwerterkennung aus Historie +- Zeit-basierte Fortschrittsverfolgung +- **Basis geschaffen für Phase 0c (Multi-Layer Architecture)** + +### Git Commits +``` +20+ commits mit "Phase 0b" prefix (28.03.2026) +30+ commits für Auto-Population + Time-Based Tracking +``` + +📚 **Dokumentation:** +- `docs/issues/issue-53-phase-0c-multi-layer-architecture.md` (Phase 0b als Blaupause) +- `C:\Users\lars\.claude\projects\...\memory\feedback_goal_system.md` (Learnings) + +--- + +## 🎯 Phase 0c: Multi-Layer Data Architecture (NEXT) + +**Zeitraum:** Geplant 29.03 - 03.04.2026 +**Aufwand:** 20-27h (5-7 Tage bei 4h/Tag) +**Status:** 🎯 READY FOR IMPLEMENTATION + +### Ziel +Refactoring von monolithischer Platzhalter-Logik zu dreischichtiger Architektur mit Separation of Concerns. + +### Drei-Schichten-Architektur +``` +Layer 1: DATA LAYER (neu) + → Pure data retrieval + calculations + → Returns: Structured data (dict/list) + → Testable, reusable + +Layer 2a: KI LAYER (refactored) + → Formatierung für KI-Prompts + → Uses data_layer functions + +Layer 2b: VISUALIZATION LAYER (neu) + → Chart.js compatible JSON + → Uses data_layer functions +``` + +### Deliverables +- [ ] **Data Layer Module erstellen (8-10h):** + - `backend/data_layer/body_metrics.py` (Gewicht, FM, LBM, Umfänge) + - `backend/data_layer/nutrition_metrics.py` (Protein, Makros, Adherence) + - `backend/data_layer/activity_metrics.py` (Volumen, Qualität, Abilities) + - `backend/data_layer/recovery_metrics.py` (Recovery Score, Sleep, Vitals) + - `backend/data_layer/health_metrics.py` (BP, Health Stability) + - `backend/data_layer/goals.py` (Active goals, progress, projections) + - `backend/data_layer/correlations.py` (Lag-analysis, plateau detection) + - `backend/data_layer/utils.py` (Confidence, baseline, outliers) +- [ ] **Placeholder Resolver Refactoring (3-4h):** + - Von ~1100 Zeilen zu ~400 Zeilen + - Alle Platzhalter nutzen data_layer +- [ ] **Charts Router erstellen (6-8h):** + - `backend/routers/charts.py` (NEU) + - 10+ Chart-Endpoints (K1-K10, E1-E4, A1-A5, V1-V3, R1-R2) +- [ ] **goal_utils.py Refactoring (1h):** + - Nutzt data_layer.goals +- [ ] **Testing (2-3h):** + - Unit tests für Data Layer + - Integration tests für Charts API +- [ ] **Dokumentation (1-2h):** + - `.claude/docs/technical/DATA_LAYER_ARCHITECTURE.md` + - `docs/api/CHARTS_API.md` + +### Vorteile +- ✅ **Single Source of Truth:** Jede Berechnung nur einmal +- ✅ **Wiederverwendbarkeit:** Gleiche Daten für KI + Charts + API +- ✅ **Testbarkeit:** Data Layer isoliert testbar +- ✅ **Erweiterbarkeit:** Neue Features ohne Code-Duplikation +- ✅ **Performance:** Caching auf Data Layer Ebene möglich + +📚 **Vollständige Spezifikation:** +- `docs/issues/issue-53-phase-0c-multi-layer-architecture.md` (23.000 Tokens) + +--- + +## Phase 0 (Legacy): Infrastruktur (v9f) - TEILWEISE ABGESCHLOSSEN + +**Ziel:** Grundlagen schaffen für flexible KI-Analysen und Fähigkeiten-Tracking + +### Kernfeatures + +| Feature | Aufwand | Gitea Issues | +|---------|---------|--------------| +| Quality-Filter für Charts & KI-Pipeline | 3h | Siehe Gitea | +| AI-Prompts Flexibilisierung (v9f) | 16-20h | Siehe Gitea | +| Abilities-Matrix UI (v9f) | 6-8h | Siehe Gitea | +| Responsive UI | 8-10h | Siehe Gitea | + +**Gesamt:** ~33-41h + +📚 **Detaillierte Specs:** +- `.claude/docs/functional/AI_PROMPTS.md` (KI-Prompt-System) +- `.claude/docs/functional/RESPONSIVE_UI.md` (Desktop-Optimierung) +- `.claude/docs/functional/TRAINING_TYPES.md` (Abilities-Matrix) + +### Meilensteine + +#### M0.1: Quality-Filter (1 Woche) +- **Deliverable:** KI-Pipeline filtert nach `quality_label >= acceptable` +- **Deliverable:** History.jsx Toggle "Nur qualitativ hochwertige Aktivitäten" +- **Impact:** Sofort bessere KI-Analysen + +#### M0.2: AI-Prompts System (3-4 Wochen) +- **Deliverable:** Prompt-Bibliothek mit 8 Kategorien +- **Deliverable:** Platzhalter-Browser mit Beispielwerten +- **Deliverable:** Pipeline-Konfigurationen (min. 3: Alltags-Check, Schlaf-Fokus, Wettkampf) +- **Deliverable:** 50+ Platzhalter implementiert +- **Deliverable:** Quality-Level Parameter für KI-Analysen (all, quality, very_good, excellent) +- **Impact:** Grundlage für Goals, Korrelationen, alle zukünftigen KI-Features + +#### M0.3: Abilities-Matrix (2 Wochen) +- **Deliverable:** Admin-UI mit 5-Dimensionen Fähigkeiten-Matrix +- **Deliverable:** Basis-Mappings für alle 29 Trainingstypen +- **Deliverable:** Endpoint `GET /api/evaluation/abilities` +- **Deliverable:** KI-Platzhalter `{{faehigkeiten_*}}` +- **Impact:** Fähigkeiten-Balance-Analysen, bessere Trainingsempfehlungen + +#### M0.4: Responsive UI (2 Wochen, parallel) +- **Deliverable:** Desktop Sidebar Navigation +- **Deliverable:** 2-spaltige Layouts (Verlauf, Analyse) +- **Deliverable:** 4-spaltige Dashboard-Karten +- **Impact:** Professionelle Desktop-Nutzung + +### Abhängigkeiten + +``` +AI-Prompts ─┬─→ Goals (KI-Integration) + ├─→ Korrelationen (Pipeline-Configs) + └─→ Zukünftige KI-Features + +Abilities ──→ AI-Prompts (Fähigkeiten-Platzhalter) + └─→ Korrelationen (Fähigkeiten-Balance) + +Quality ────→ Sofort nutzbar (keine Abhängigkeiten) + +Responsive ─→ Parallel, keine funktionalen Abhängigkeiten +``` + +**Phase 0 Ende:** Vollständige v9f-Infrastruktur, Desktop-optimierte UI, Quality-Filter aktiv + +--- + +## Phase 1: Foundation + +**Ziel:** Visualisierung und Ziel-Tracking (Basis ohne volle KI-Integration) + +### Kernfeatures + +| Feature | Aufwand | Gitea Issues | +|---------|---------|--------------| +| Charts & Visualisierungen erweitern | 8-10h | Siehe Gitea | +| Ziele-System (Basis ohne KI) | 10-12h | Siehe Gitea | + +**Gesamt:** ~18-22h + +📚 **Detaillierte Specs:** +- `.claude/docs/functional/GOALS_VITALS.md` (Ziele-System) + +### Meilensteine + +#### M1.1: Charts erweitern (2 Wochen) +- **Deliverable:** Weight Trend Chart mit 7d/30d/90d Filter +- **Deliverable:** Umfänge-Verlauf Multi-Line Chart +- **Deliverable:** Vitals Trends (RHR, HRV, VO2 Max, BP) +- **Deliverable:** Schlaf-Analyse Chart (Dauer, Qualität, Phasen) +- **Deliverable:** Ruhetage-Kalender (Heatmap) +- **Impact:** Daten werden sichtbar, Trends erkennbar + +#### M1.2: Ziele-System Basis (2-3 Wochen) +- **Deliverable:** DB-Schema `goals` Tabelle +- **Deliverable:** CRUD-Endpoints +- **Deliverable:** UI: Ziele erstellen/bearbeiten/löschen +- **Deliverable:** Dashboard: Fortschrittsbalken (aktuell vs. Ziel) +- **Deliverable:** Prognose-Berechnung (lineare Regression) +- **Impact:** Nutzer sehen Fortschritt, Motivation steigt + +**Hinweis:** KI-Integration für Goals kommt in Phase 2. + +### Abhängigkeiten + +``` +Charts ────→ Keine (sofort machbar) + +Goals Basis → Keine (DB + UI ohne KI funktioniert) +Goals KI ──→ AI-Prompts (braucht {{ziel_*}} Platzhalter) [Phase 2] +``` + +**Phase 1 Ende:** Charts zeigen Trends, Ziele sind verfolgbar (ohne KI-Empfehlungen) + +--- + +## Phase 2: Engagement + +**Ziel:** Korrelationen, KI-Integration für Goals, aktive Empfehlungen + +### Kernfeatures + +| Feature | Aufwand | Gitea Issues | +|---------|---------|--------------| +| Korrelationen & Insights erweitern | 6-8h | Siehe Gitea | +| Goals: KI-Integration vervollständigen | 4h | Siehe Gitea | + +**Gesamt:** ~10-12h + +📚 **Detaillierte Specs:** +- `.claude/docs/functional/AI_PROMPTS.md` (Pipeline-Configs) +- `.claude/docs/functional/GOALS_VITALS.md` (KI-Integration) + +### Meilensteine + +#### M2.1: Korrelationen (2 Wochen) +- **Deliverable:** Schlaf ↔ Recovery (Pearson Korrelation) +- **Deliverable:** Training ↔ Vitalwerte (Trainingsvolumen vs. RHR/HRV) +- **Deliverable:** Ernährung ↔ Performance (Protein vs. Kraft, Kalorien vs. Ausdauer) +- **Deliverable:** Blutdruck ↔ Lifestyle (Stress, Schlaf, Aktivität) +- **Deliverable:** Fähigkeiten-Balance ↔ Verletzungen/Plateaus +- **Deliverable:** Pipeline-Config "Schlaf-Fokus" +- **Impact:** Nutzer verstehen Zusammenhänge, gezielte Optimierung möglich + +#### M2.2: Goals KI-Integration (1 Woche) +- **Deliverable:** KI-Platzhalter `{{goal_weight}}`, `{{ziel_fortschritt}}`, `{{ziel_prognose}}` +- **Deliverable:** Pipeline-Prompt "Ziel-Abgleich" (Stufe 3) +- **Deliverable:** KI-Empfehlungen: "Erhöhe Protein um 20g für Muskelaufbau-Ziel" +- **Impact:** KI gibt konkrete Schritte zur Zielerreichung + +### Abhängigkeiten + +``` +Korrelationen ─→ AI-Prompts (Pipeline-Configs) [KRITISCH] + └─→ Abilities (Fähigkeiten-Balance) + +Goals KI ──────→ AI-Prompts (Platzhalter {{ziel_*}}) [KRITISCH] +``` + +**Phase 2 Ende:** App gibt aktive Empfehlungen, zeigt Korrelationen, begleitet Ziele mit KI + +--- + +## Phase 3: Begleitung (später, v9g) + +**Ziel:** Wochenplanung, Development Routes, Habits & Streaks + +### Geplante Features + +| Feature | Aufwand | Beschreibung | +|---------|---------|--------------| +| Development Routes | 12-16h | 6 unabhängige Routen (Kraft, Kondition, Mental, Koordination, Mobilität, Technik) | +| Wochenplanung | 8-10h | Routen-basierte Wochenansicht, Regeln-Engine, Auto-Ruhetag | +| Habits & Streaks | 6-8h | Streak-Tracking pro Route, Push-Notifications | +| Weekly Planning AI | 4-6h | KI-generierte Wochenpläne basierend auf Routen-Balance | + +**Gesamt:** ~30-40h + +📚 **Detaillierte Specs:** +- `.claude/docs/functional/DEVELOPMENT_ROUTES.md` (Routen-System) +- `.claude/docs/functional/MEDITATION.md` (Meditation & Mental-Route) + +**Status:** Spezifikation vorhanden, Implementierung nach Phase 2 + +--- + +## Abhängigkeiten-Graph (Gesamt) + +``` + ┌─────────────────────┐ + │ Phase 0: v9f │ + └─────────────────────┘ + │ + ┌─────────────────────┼─────────────────────┬──────────────┐ + ▼ ▼ ▼ ▼ + Quality-Filter AI-Prompts Abilities Responsive UI + (3h) (16-20h) (6-8h) (8-10h) + │ │ │ + │ │ ┌───────────────────┘ + │ │ │ + │ ┌──────┴─┴──────┐ + │ │ Phase 1 │ + │ └───────────────┘ + │ │ + │ ┌──────┴──────┬──────────────┐ + │ ▼ ▼ │ + │ Charts Goals (Basis) │ + │ (8-10h) (10-12h) │ + │ │ │ + │ ┌──────┴──────┐ │ + │ │ Phase 2 │ │ + │ └─────────────┘ │ + │ │ │ + └────────────────────────────┼──────────────┘ + │ + ┌──────┴──────┬──────────────┐ + ▼ ▼ │ + Korrelationen Goals KI │ + (6-8h) (4h) │ + │ + ┌─────────────────────┘ + │ Phase 3 (später) + └────────────────────── + │ + Development Routes + Wochenplanung + Habits & Streaks +``` + +--- + +## Timeline (geschätzt) + +**Start:** KW 13/2026 (24. März) + +| Woche | Phase | Aktivität | Deliverable | +|-------|-------|-----------|-------------| +| **KW 13** | Phase 0 | Quality-Filter | KI-Pipeline filtert, History Toggle | +| **KW 14-17** | Phase 0 | AI-Prompts | Prompt-Bibliothek, Platzhalter-System, Pipeline-Configs | +| **KW 14-15** | Phase 0 | Responsive UI (parallel) | Desktop Sidebar, 2-spaltige Layouts | +| **KW 18-19** | Phase 0 | Abilities-Matrix | Admin-UI, Aggregation, KI-Platzhalter | +| **KW 20-21** | Phase 1 | Charts | Weight/Umfänge/Vitals/Schlaf Charts, Ruhetage-Kalender | +| **KW 22-24** | Phase 1 | Goals Basis | DB, CRUD, UI, Fortschritt, Prognose | +| **KW 25-26** | Phase 2 | Korrelationen | 5 Korrelations-Analysen, Pipeline "Schlaf-Fokus" | +| **KW 27** | Phase 2 | Goals KI | KI-Platzhalter, Pipeline "Ziel-Abgleich" | +| **KW 28+** | Phase 3 | Development Routes | Multi-Route System, Wochenplanung, Habits | + +**Phase 0 Ende:** KW 19 (Mitte Mai 2026) +**Phase 1 Ende:** KW 24 (Mitte Juni 2026) +**Phase 2 Ende:** KW 27 (Anfang Juli 2026) + +--- + +## Risiken & Mitigationen + +### Risiko 1: AI-Prompts Komplexität unterschätzt +**Wahrscheinlichkeit:** Mittel +**Impact:** Hoch (blockiert Goals, Korrelationen) +**Mitigation:** +- Scope klar definieren: Min. 3 Pipeline-Configs, 50+ Platzhalter +- Platzhalter-Resolver modular bauen (lazy loading, caching) +- Frühzeitig Beispiel-Pipeline testen + +### Risiko 2: Ziele-System ohne KI wenig attraktiv +**Wahrscheinlichkeit:** Mittel +**Impact:** Mittel +**Mitigation:** +- Prognose-Berechnung (lineare Regression) bereits in Phase 1 +- Dashboard-Integration mit Fortschrittsbalken (visuell ansprechend) +- KI-Integration in Phase 2 schnell nachziehen (nur 4h) + +### Risiko 3: Responsive UI bricht Mobile-Experience +**Wahrscheinlichkeit:** Niedrig +**Impact:** Hoch (PWA auf iPhone ist kritisch) +**Mitigation:** +- Mobile Layout bleibt exakt wie es ist (< 1024px) +- Nur Desktop (≥ 1024px) bekommt Sidebar +- Ausführliches Testing auf iPhone nach Implementierung + +### Risiko 4: Abilities-Matrix UI zu komplex +**Wahrscheinlichkeit:** Niedrig +**Impact:** Mittel +**Mitigation:** +- 5 Accordion-Sections (eine pro Dimension) +- Checkboxes statt Drag & Drop +- Basis-Mappings per Migration vordefiniert (Admin muss nicht alles manuell machen) + +--- + +## Offene Fragen + +### Phase 0 +1. **Pipeline-Konfigurationen:** Neue DB-Tabelle oder JSONB in `ai_prompts`? + - **Empfehlung:** Neue Tabelle `pipeline_configs` (flexibler für Zukunft) +2. **Platzhalter-Definitionen:** Statisch im Code oder dynamisch in DB? + - **Empfehlung:** Statisch im Code (einfacher, schneller), DB nur für Custom-Platzhalter später +3. **Responsive UI:** Media Queries in app.css oder CSS-in-JS? + - **Empfehlung:** Media Queries in app.css (konsistent mit bestehendem Ansatz) + +### Phase 1 +4. **Ziele-System:** Mehrere Ziele parallel oder nur ein Primärziel? + - **Empfehlung:** Mehrere Ziele, eines als primär markiert (flexibler) +5. **Charts:** Welche Library? (Chart.js, Recharts, Custom SVG) + - **Status:** Chart.js bereits genutzt, erweitern + +### Phase 2 +6. **Korrelationen:** Pearson Korrelation ausreichend oder auch Spearman/Kendall? + - **Empfehlung:** Start mit Pearson, später erweitern wenn nötig +7. **Pipeline "Schlaf-Fokus":** Welche Module? (Schlaf 14T, Vitalwerte 7T, Training 14T?) + - **Empfehlung:** Ja, plus Korrelation Schlaf ↔ Recovery + +--- + +## Meilenstein-Deliverables (Zusammenfassung) + +### M0: Phase 0 abgeschlossen (KW 19) +- ✅ Quality-Filter aktiv (KI-Pipeline + History Toggle) +- ✅ Prompt-Bibliothek mit 8 Kategorien, 50+ Platzhalter +- ✅ Min. 3 Pipeline-Konfigurationen +- ✅ Abilities-Matrix mit 29 Basis-Mappings +- ✅ Desktop Sidebar Navigation +- ✅ 2-spaltige Layouts (Verlauf, Analyse) + +### M1: Phase 1 abgeschlossen (KW 24) +- ✅ 5 neue Chart-Typen (Weight, Umfänge, Vitals, Schlaf, Ruhetage) +- ✅ Ziele-System: CRUD, Dashboard-Integration, Prognose + +### M2: Phase 2 abgeschlossen (KW 27) +- ✅ 5 Korrelations-Analysen +- ✅ Pipeline "Schlaf-Fokus", "Ziel-Abgleich" +- ✅ KI gibt konkrete Handlungsempfehlungen + +### M3: Phase 3 abgeschlossen (später) +- ✅ 6 Development Routes +- ✅ Routen-basierte Wochenplanung +- ✅ Habits & Streaks Tracking +- ✅ KI-generierte Wochenpläne + +--- + +## Erfolgsmetriken + +### Phase 0 +- **Admin nutzt Abilities-Matrix:** Min. 20 von 29 Trainingstypen mit Fähigkeiten versehen +- **Pipeline-Configs genutzt:** Min. 1 Pipeline pro Woche ausgeführt +- **Desktop-Traffic:** Min. 30% der Sessions auf Desktop (≥ 1024px) + +### Phase 1 +- **Charts genutzt:** Min. 5 verschiedene Chart-Typen pro Nutzer pro Monat +- **Ziele gesetzt:** Min. 50% der aktiven Nutzer haben mind. 1 Ziel +- **Prognose geschätzt:** Min. 80% der Ziele haben realistische Prognose + +### Phase 2 +- **Korrelationen angeschaut:** Min. 3 verschiedene Korrelationen pro Nutzer pro Monat +- **KI-Empfehlungen umgesetzt:** Min. 30% der KI-Vorschläge führen zu Verhaltensänderung +- **Goals erreicht:** Min. 20% der Ziele werden innerhalb Prognose-Zeitraum erreicht + +### Phase 3 +- **Routen genutzt:** Min. 3 verschiedene Routen pro Nutzer aktiv +- **Wochenplan erstellt:** Min. 1 Wochenplan pro Nutzer pro Monat +- **Streaks gehalten:** Min. 50% der Habits haben Streak ≥ 7 Tage + +--- + +**Dokumentiert:** 23. März 2026 (Initial) · 24. März 2026 (Konsolidiert) +**Autor:** User-Strategie + Claude-Planung +**Status:** Living Document (wird nach jeder Phase aktualisiert) + +**Tracking:** +- Konkrete Issues → [Gitea](http://192.168.2.144:3000/Lars/mitai-jinkendo/issues) +- Feature-Specs → `.claude/docs/functional/*.md` +- Strategische Planung → Diese Datei diff --git a/.claude/docs/architecture/BACKEND.md b/.claude/docs/architecture/BACKEND.md new file mode 100644 index 0000000..2dd35f1 --- /dev/null +++ b/.claude/docs/architecture/BACKEND.md @@ -0,0 +1,94 @@ +# Backend-Architektur + +## Struktur +``` +backend/ +├── main.py # App-Setup + Router-Registration (~75 Zeilen) +├── db.py # PostgreSQL Connection Pool + get_cursor() Helper +├── auth.py # hash_pin · verify_pin · require_auth · require_admin +├── models.py # Alle Pydantic Models (LoginRequest, ProfileUpdate, etc.) +├── schema.sql # Vollständiges PostgreSQL-Schema +└── routers/ + ├── auth.py # Login, Logout, Password Reset, PIN (7 Endpoints) + ├── profiles.py # Profile CRUD + Current User (7 Endpoints) + ├── weight.py # Weight Tracking (5 Endpoints) + ├── circumference.py # Body Measurements (4 Endpoints) + ├── caliper.py # Skinfold Tracking (4 Endpoints) + ├── activity.py # Workout Logging + CSV Import (6 Endpoints) + ├── nutrition.py # Nutrition + FDDB Import (4 Endpoints) + ├── photos.py # Progress Photos (3 Endpoints) + ├── insights.py # AI Analysis + Pipeline (8 Endpoints) + ├── prompts.py # AI Prompt Management (2 Endpoints) + ├── admin.py # User Management (7 Endpoints) + ├── stats.py # Dashboard Stats (1 Endpoint) + ├── exportdata.py # CSV/JSON/ZIP Export (3 Endpoints) + └── importdata.py # ZIP Import (1 Endpoint) +``` + +## Router registrieren (main.py Pattern) +```python +from routers import auth, profiles, weight +app.include_router(auth.router, prefix="/api") +app.include_router(profiles.router, prefix="/api") +``` + +## Neuen Router anlegen +1. `backend/routers/mein_modul.py` erstellen +2. `router = APIRouter()` definieren +3. Endpoints mit `@router.get/post/put/delete` definieren +4. In `main.py` importieren und registrieren +5. Schema-Änderungen in `schema.sql` + Migration + +## Datenbank-Zugriff +```python +# ✅ Immer so: +from db import get_db, get_cursor + +with get_db() as conn: + cur = get_cursor(conn) # RealDictCursor – gibt Dicts zurück + cur.execute("SELECT * FROM weight_log WHERE profile_id = %s", (pid,)) + rows = cur.fetchall() # Liste von Dicts + +# ❌ Nie so (SQLite-Syntax): +conn.execute("SELECT * FROM weight_log WHERE profile_id=?", (pid,)) +``` + +## Auth-Pattern +```python +# Standard-Endpoint mit Auth: +@router.get("/mein-endpoint") +def mein_endpoint(session: dict = Depends(require_auth)): + pid = session['profile_id'] + role = session['role'] + +# Admin-only: +@router.get("/admin-endpoint") +def admin_endpoint(session: dict = Depends(require_admin)): + pass + +# ❌ NIEMALS so (session innerhalb Header eingebettet): +def endpoint(x: str = Header(default=None, session=Depends(require_auth))): +``` + +## PostgreSQL vs. SQLite Unterschiede +```python +# Platzhalter: +%s # PostgreSQL ✅ +? # SQLite ❌ + +# Boolean: +WHERE active = true # PostgreSQL ✅ +WHERE active = 1 # SQLite ❌ + +# Returning: +INSERT INTO ... RETURNING id # PostgreSQL ✅ (für neue IDs) + +# JSON: +JSONB # PostgreSQL (für flexible Felder) +``` + +## DB-Schema ändern +1. `backend/schema.sql` anpassen +2. Migration schreiben (ALTER TABLE oder neues Skript) +3. Container neu bauen: `docker compose build --no-cache backend` +4. NIEMALS Spalten direkt löschen/umbenennen ohne Datenmigration diff --git a/.claude/docs/architecture/FEATURE_ENFORCEMENT.md b/.claude/docs/architecture/FEATURE_ENFORCEMENT.md new file mode 100644 index 0000000..dc9ca34 --- /dev/null +++ b/.claude/docs/architecture/FEATURE_ENFORCEMENT.md @@ -0,0 +1,319 @@ +# Feature Enforcement Pattern + +## Übersicht + +Das Membership-System verwendet ein **4-Phasen-Modell** für Feature-Limits: + +1. **Phase 1: Cleanup** - Feature-Konsolidierung, DB-Migration +2. **Phase 2: Non-blocking Monitoring** - JSON-Logging, keine Blockierung +3. **Phase 3: Frontend Display** - Usage-Badges, Quota-Übersicht +4. **Phase 4: Enforcement** - Tatsächliche Blockierung bei Limit-Überschreitung + +**Status (2026-03-21):** ✅ Alle 11 Features sind in Phase 4 (komplett implementiert) + +## Wie neue Features hinzufügen + +### 1. Feature in Datenbank registrieren + +```sql +INSERT INTO features (id, name, description, category, limit_type, reset_period, default_limit) +VALUES ( + 'new_feature_id', -- eindeutige ID + 'Feature Name', -- Anzeigename + 'Beschreibung', -- Beschreibung + 'data', -- Kategorie: data, ai, export, integration + 'count', -- limit_type: 'count' oder 'boolean' + 'monthly', -- reset_period: 'never', 'daily', 'monthly' + 50 -- default_limit: INT (NULL = unlimited, 0 = disabled) +); +``` + +**limit_type:** +- `count`: Zählbare Features (z.B. 50 Einträge pro Monat) +- `boolean`: An/Aus-Features (0 = disabled, 1 = enabled) + +**reset_period:** +- `never`: Counter reset sich nie (z.B. Lifetime-Limits) +- `daily`: Reset um Mitternacht +- `monthly`: Reset am 1. des Monats + +### 2. Backend-Implementierung + +#### a) Router-Endpoint anpassen + +```python +from auth import require_auth, check_feature_access, increment_feature_usage +from feature_logger import log_feature_usage + +@router.post("/api/resource") +def create_resource(data: dict, x_profile_id: Optional[str]=Header(default=None), session: dict=Depends(require_auth)): + """Create new resource entry.""" + pid = get_pid(x_profile_id) + + # Phase 4: Check feature access and ENFORCE + access = check_feature_access(pid, 'new_feature_id') + log_feature_usage(pid, 'new_feature_id', access, 'create') + + if not access['allowed']: + logger.warning( + f"[FEATURE-LIMIT] User {pid} blocked: " + f"new_feature_id {access['reason']} (used: {access['used']}, limit: {access['limit']})" + ) + raise HTTPException( + status_code=403, + detail=f"Limit erreicht: Du hast das Kontingent für [Feature-Name] überschritten ({access['used']}/{access['limit']}). " + f"Bitte kontaktiere den Admin oder warte bis zum nächsten Reset." + ) + + # ... Business Logic: INSERT into DB ... + + # Phase 2: Increment usage counter (only for NEW entries, not updates!) + increment_feature_usage(pid, 'new_feature_id') + + return {"id": resource_id} +``` + +#### b) Wichtige Regeln + +**Counter nur bei INSERT, nicht bei UPDATE:** +```python +cur.execute("SELECT id FROM resource WHERE profile_id=%s AND date=%s", (pid, date)) +existing = cur.fetchone() + +if existing: + # UPDATE - NICHT incrementieren + cur.execute("UPDATE resource SET ... WHERE id=%s", (..., existing['id'])) +else: + # INSERT - incrementieren + cur.execute("INSERT INTO resource (...) VALUES (...)", (...)) + increment_feature_usage(pid, 'new_feature_id') # ← Nur hier! +``` + +**Für Bulk-Operationen (CSV-Import):** +```python +new_entries = 0 +for row in csv_data: + cur.execute("SELECT id FROM resource WHERE profile_id=%s AND date=%s", (pid, row['date'])) + if not cur.fetchone(): + # INSERT + cur.execute("INSERT INTO resource (...) VALUES (...)", (...)) + new_entries += 1 + +# Increment counter für alle neuen Einträge +for _ in range(new_entries): + increment_feature_usage(pid, 'new_feature_id') +``` + +### 3. Frontend-Implementierung + +#### a) Import UsageBadge + +```javascript +import UsageBadge from '../components/UsageBadge' +``` + +#### b) State Management + +```javascript +export default function ResourcePage() { + const [saving, setSaving] = useState(false) + const [saved, setSaved] = useState(false) + const [error, setError] = useState(null) + const [resourceUsage, setResourceUsage] = useState(null) // Phase 4 + + const load = () => api.listResources().then(setResources) + + const loadUsage = () => { + api.getFeatureUsage().then(features => { + const feature = features.find(f => f.feature_id === 'new_feature_id') + setResourceUsage(feature) + }).catch(err => console.error('Failed to load usage:', err)) + } + + useEffect(() => { + load() + loadUsage() + }, []) + + const handleSave = async () => { + setSaving(true) + setError(null) + try { + await api.createResource(data) + setSaved(true) + await load() + await loadUsage() // Reload usage nach save + setTimeout(() => setSaved(false), 2000) + } catch (err) { + console.error('Save failed:', err) + setError(err.message || 'Fehler beim Speichern') + setTimeout(() => setError(null), 5000) + } finally { + setSaving(false) + } + } +``` + +#### c) UI mit Badge und deaktiviertem Button + +```javascript + return ( +
+ {/* Titel mit Badge */} +
+ Neuer Eintrag + {resourceUsage && } +
+ + {/* Error-Meldung */} + {error && ( +
+ {error} +
+ )} + + {/* Button mit Tooltip-Wrapper (disabled buttons zeigen keine nativen tooltips!) */} +
+ +
+
+ ) +``` + +#### d) CSS-Styling (bereits vorhanden) + +Die Badge-Styles sind zentral in `frontend/src/components/UsageBadge.css`: + +```css +/* Badge container rechts-aligniert */ +.badge-container-right { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; +} + +/* Badge selbst */ +.usage-badge { + font-size: 0.65rem; + font-weight: 600; + padding: 2px 6px; + border-radius: 4px; + opacity: 0.6; + white-space: nowrap; +} + +.usage-badge--ok { color: #888; background: #f0f0f0; } +.usage-badge--warning { color: #EF9F27; background: #FFF4E5; } +.usage-badge--exceeded { color: #D85A30; background: #FCEBEB; } +``` + +### 4. Admin-UI (optional) + +Wenn das Feature tier-spezifische Limits braucht, in Admin-UI ergänzen: + +```javascript +// frontend/src/pages/admin/TierLimitsPage.jsx +const FEATURE_OPTIONS = [ + { value: 'new_feature_id', label: 'Feature Name' }, + // ... existing features +] +``` + +## Checkliste für neue Features + +- [ ] Feature in `features` Tabelle registriert +- [ ] Backend: `check_feature_access()` + `log_feature_usage()` vor Business Logic +- [ ] Backend: `raise HTTPException(403, ...)` wenn `!access['allowed']` +- [ ] Backend: `increment_feature_usage()` nach INSERT (nicht bei UPDATE!) +- [ ] Frontend: `UsageBadge` importiert +- [ ] Frontend: `resourceUsage` State mit `loadUsage()` +- [ ] Frontend: Badge im Titel (`badge-container-right`) +- [ ] Frontend: Button deaktiviert bei `!resourceUsage.allowed` +- [ ] Frontend: Tooltip-Wrapper um Button (`
`) +- [ ] Frontend: Error-Handling in `handleSave()` mit `catch` +- [ ] Frontend: `loadUsage()` nach erfolgreichem Speichern +- [ ] Feature-ID in CLAUDE.md dokumentiert (falls relevant) + +## Bestehende Features (Referenz) + +| Feature ID | Typ | Reset | Beschreibung | +|-----------|-----|-------|--------------| +| weight_entries | count | never | Gewichtseinträge | +| circumference_entries | count | never | Umfangseinträge | +| caliper_entries | count | never | Caliper-Messungen | +| activity_entries | count | monthly | Aktivitätseinträge | +| nutrition_entries | count | monthly | Ernährungseinträge (meist CSV-Import) | +| photos | count | monthly | Progress-Fotos | +| ai_calls | count | monthly | KI-Analysen (Einzelanalysen) | +| ai_pipeline | boolean | - | KI-Pipeline (An/Aus) | +| data_export | count | monthly | Daten-Exporte (CSV/JSON/ZIP) | +| data_import | count | monthly | Daten-Importe (ZIP) | + +## Debugging + +**Log-Datei prüfen:** +```bash +tail -f backend/logs/feature-usage.log | jq . +``` + +**Struktur:** +```json +{ + "timestamp": "2026-03-21T14:23:45.123456", + "profile_id": "uuid", + "feature_id": "weight_entries", + "action": "create", + "allowed": true, + "used": 7, + "limit": 50, + "remaining": 43, + "reason": "within_limit" +} +``` + +**Datenbank prüfen:** +```sql +-- Aktuelle Usage +SELECT * FROM user_feature_usage WHERE profile_id = 'xxx'; + +-- Feature-Definition +SELECT * FROM features WHERE id = 'new_feature_id'; + +-- Tier-Limits +SELECT * FROM tier_limits WHERE feature_id = 'new_feature_id'; + +-- User-Overrides +SELECT * FROM user_feature_restrictions +WHERE profile_id = 'xxx' AND feature_id = 'new_feature_id'; +``` + +## Weiterführende Dokumentation + +- **Membership-System:** `.claude/docs/technical/MEMBERSHIP_SYSTEM.md` +- **Backend-Architektur:** `.claude/docs/architecture/BACKEND.md` +- **Frontend-Architektur:** `.claude/docs/architecture/FRONTEND.md` +- **Coding Rules:** `.claude/docs/rules/CODING_RULES.md` diff --git a/.claude/docs/architecture/FRONTEND.md b/.claude/docs/architecture/FRONTEND.md new file mode 100644 index 0000000..a40acbb --- /dev/null +++ b/.claude/docs/architecture/FRONTEND.md @@ -0,0 +1,126 @@ +# Frontend-Architektur + +## Struktur +``` +frontend/src/ +├── App.jsx # Root: Auth-Gates, Navigation, Routing +├── app.css # CSS-Variablen + globale Klassen +├── main.jsx # Vite Entry Point +├── context/ +│ ├── AuthContext.jsx # Session, Login, Logout, getToken() +│ └── ProfileContext.jsx # Aktives Profil, Profile-Liste +├── pages/ # Eine Datei pro Screen +├── utils/ +│ ├── api.js # Alle API-Calls (Token automatisch injiziert) +│ ├── calc.js # Körperfett-Formeln (Jackson/Pollock etc.) +│ ├── interpret.js # Regelbasierte Auswertungen +│ ├── Markdown.jsx # Eigener Markdown-Renderer +│ └── guideData.js # Messanleitungen (statisch) +└── public/ # Icons (Jinkendo Ensō-Logo) +``` + +## API-Calls – IMMER über api.js +```javascript +// ✅ Richtig – Token wird automatisch injiziert: +import { api } from '../utils/api' +const data = await api.listWeight() +await api.upsertWeight(date, weight, note) + +// ❌ Falsch – kein Token, gibt 401: +const r = await fetch('/api/weight') +``` + +## Neue API-Methode hinzufügen +In `frontend/src/utils/api.js`: +```javascript +export const api = { + // ...bestehende Methoden... + meinEndpoint: (param) => req(`/mein-endpoint?p=${param}`), + createItem: (data) => req('/items', json(data)), + updateItem: (id, d) => req(`/items/${id}`, jput(d)), + deleteItem: (id) => req(`/items/${id}`, {method:'DELETE'}), +} +``` + +## Navigation (Haupt-App & Admin) +- **Hauptmenü (Mobile + Desktop):** `frontend/src/config/appNav.js` (`getMainNavItems`) – in `App.jsx` (Bottom-Nav) und `DesktopSidebar.jsx` nutzen. +- **Admin-Bereich:** `frontend/src/config/adminNav.js` + `layouts/AdminShell.jsx` + `layouts/RequireAdmin.jsx`; Shell wie Analyse (`.analysis-split*`). +- **Bottom-Nav / Safe Area (PWA):** `--nav-h`, `.bottom-nav`, `.app-main` in `app.css`. +- **Agent-Doku:** `docs/issues/GUI_IA_ADMIN_NAV_2026-04-05.md` + +## Neue Seite hinzufügen +1. `frontend/src/pages/MeineSeite.jsx` erstellen +2. In `App.jsx` importieren und Route hinzufügen +3. Navigation: Eintrag in **`config/appNav.js`** (und ggf. Admin in **`adminNav.js`**) – nicht mehr nur in `App.jsx` duplizieren + +## Komponenten-Pattern +```jsx +export default function MeineSeite() { + const [data, setData] = useState([]) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + + useEffect(() => { load() }, []) + + const load = async () => { + try { + setLoading(true) + setData(await api.meinEndpoint()) + } catch(e) { setError(e.message) } + finally { setLoading(false) } + } + + if (loading) return
+ if (error) return
{error}
+ + return ( +
+ {/* Inhalt */} +
+ ) +} +``` + +## CSS-Variablen (Kurzreferenz) +```css +--accent: #1D9E75 --accent-dark: #085041 --accent-light: #E1F5EE +--danger: #D85A30 +--bg · --surface · --surface2 · --border · --text1 · --text2 · --text3 +``` + +## CSS-Klassen +``` +.card Weißer Container, border-radius 12px +.btn Basis-Button +.btn-primary Grüner Button +.btn-secondary Grauer Button +.btn-full 100% Breite +.form-input Eingabefeld +.form-label Label (klein, uppercase) +.spinner Ladekreis +``` + +## Bekannte Fallstricke + +### dayjs.week() – NIEMALS verwenden +```javascript +// ❌ Falsch: +dayjs(date).week() + +// ✅ Richtig (ISO 8601): +const weekNum = (() => { + const dt = new Date(date) + dt.setHours(0,0,0,0) + dt.setDate(dt.getDate()+4-(dt.getDay()||7)) + return Math.ceil(((dt-new Date(dt.getFullYear(),0,1))/86400000+1)/7) +})() +``` + +### Recharts Bar fill +```jsx +// ❌ Falsch: + entry.color}/> + +// ✅ Richtig: + +``` diff --git a/.claude/docs/audit/20260404_code_audit/CODE_AUDIT_REPORT_2026-04-04.md b/.claude/docs/audit/20260404_code_audit/CODE_AUDIT_REPORT_2026-04-04.md new file mode 100644 index 0000000..2fa4c20 --- /dev/null +++ b/.claude/docs/audit/20260404_code_audit/CODE_AUDIT_REPORT_2026-04-04.md @@ -0,0 +1,186 @@ +# Code-Audit Mitai Jinkendo + +| Feld | Wert | +|------|------| +| **Datum** | 2026-04-04 | +| **Bezeichnung** | Vollständige Code-Analyse (Backend FastAPI, Frontend React, Konfiguration) | +| **Umfang** | Stichproben + gezielte Suche (Security-Patterns, Auth, SQL, Hardcoding, Duplikate) | +| **Einschränkung** | Kein Penetrationstest, keine Laufzeit-Fuzzing-Tests; statische Analyse und Architektur-Review | + +--- + +## Kurzfassung + +Die Codebasis ist insgesamt strukturiert (modulare Router, parametrisierte SQL-Queries, bcrypt, Rate-Limits auf sensiblen Auth-Endpunkten). Es gibt jedoch **kritische Autorisierungslücken** rund um `X-Profile-Id` und die öffentlichen `/api/profiles`-Routen sowie eine **fehlende Objekt-Zuordnung bei Foto-Downloads**. Zusätzlich fallen **Konfigurations- und Versions-Inkonsistenzen**, **Legacy-/Duplikat-Code** und **Abweichungen von den eigenen Coding Rules** (direktes `fetch` im Frontend) auf. + +**Gitea-Issue-Vorlagen** für die wichtigsten Punkte liegen unter `gitea/`. + +--- + +## Kritisch (P0) – Sicherheit + +### 1. `X-Profile-Id` ohne Abgleich mit der Session (`get_pid`) + +**Datei:** `backend/routers/profiles.py` (Hilfsfunktion `get_pid`) + +```20:29:backend/routers/profiles.py +def get_pid(x_profile_id: Optional[str] = Header(default=None)) -> str: + """Get profile_id - from header for legacy endpoints.""" + if x_profile_id: + return x_profile_id + with get_db() as conn: + cur = get_cursor(conn) + cur.execute("SELECT id FROM profiles ORDER BY created LIMIT 1") + row = cur.fetchone() + if row: return row['id'] + raise HTTPException(400, "Kein Profil gefunden") +``` + +Ist der Header gesetzt, wird **jede** authentifizierte Session auf die **beliebig gewählte** Profil-ID umgebogen. `get_pid` wird in zahlreichen Routern genutzt (Gewicht, Aktivität, Ernährung, Fotos-Upload/Liste, Vitals, Insights, …). Ein Angreifer mit gültigem Nutzer-Token kann damit **fremde Daten lesen und schreiben** (klassische IDOR-Schwachstelle). + +**Empfehlung:** Profil-Kontext ausschließlich aus `session['profile_id']` ableiten; `X-Profile-Id` nur für **Admins** erlauben und gegen eine explizite Admin-Prüfung validieren – oder entfernen, wenn nicht mehr benötigt. + +**Vorlage:** `gitea/TEMPLATE_P0-profile-scoping-x-profile-id.md` + +--- + +### 2. `/api/profiles`-Endpunkte ohne Admin-Pflicht + +**Datei:** `backend/routers/profiles.py` + +Endpunkte wie `GET/POST /api/profiles`, `GET/PUT/DELETE /api/profiles/{pid}` nutzen `Depends(require_auth)`, **nicht** `require_admin`. Damit kann jeder eingeloggte Nutzer u. a. **alle Profile auflisten**, **Profildaten abrufen** und unter Umständen **Profile löschen** (wenn mehr als eines existiert). + +Das Frontend nutzt `GET /api/profiles` in `ProfileContext.jsx` für die Profilliste – funktional erklärbar, **sicherheitstechnisch** aber nur akzeptabel, wenn ausschließlich Admins je Instanz ein Konto haben (Annahme, die im Code nicht erzwungen wird). + +**Empfehlung:** Öffentliche/„current user“-API von Admin-CRUD trennen; Listen/Löschen/Anlegen fremder Profile nur mit `require_admin` und expliziter Policy. + +**Vorlage:** `gitea/TEMPLATE_P0-profiles-router-authorization.md` + +--- + +### 3. Foto-Abruf ohne Profil-Ownership-Prüfung + +**Datei:** `backend/routers/photos.py` – `GET /api/photos/{fid}` + +Die Route lädt die Datei nur anhand der Foto-`id`, **ohne** zu prüfen, ob `photos.profile_id` zur Session passt. UUIDs erschweren Raten, bieten aber **keinen** Zugriffsschutz, wenn IDs bekannt sind oder geleakt werden. + +**Empfehlung:** `SELECT ... WHERE id=%s AND profile_id=%s` mit `session['profile_id']` (bzw. adminseitig explizite Ausnahme). + +**Vorlage:** `gitea/TEMPLATE_P1-photo-ownership-enforcement.md` + +--- + +## Hoch (P1) + +### 4. CORS: Default `allow_origins=["*"]` + +**Datei:** `backend/main.py` + +Wenn `ALLOWED_ORIGINS` nicht gesetzt ist, erlaubt die Middleware **`'*'`** in Kombination mit `allow_credentials=True`. Das ist in Browsern problematisch (kann je nach Client zu unerwartetem Verhalten führen) und in Produktion **riskant**, falls die Umgebungsvariable fehlt. + +**Empfehlung:** In Produktion kein Wildcard-Default; Startfehler oder restriktive Default-Liste für Dev dokumentieren. + +**Vorlage:** `gitea/TEMPLATE_P1-cors-allowed-origins-hardening.md` + +--- + +### 5. Session-Token in der URL (Fotos) + +**Datei:** `frontend/src/utils/api.js` – Foto-URL mit `?token=...` für ``-Tags; Backend: `require_auth_flexible` mit Query-Parameter. + +Token in URLs landen in **Logs, Referer, Browser-Historie** – erhöhtes Leak-Risiko. + +**Empfehlung:** Kurzlebige signierte URLs, Cookie-Session für Same-Origin, oder dedizierter Download-Endpoint mit Header-Auth und Blob-URL im Frontend (mit Aufwand abzuwägen). + +--- + +### 6. Veraltete / widersprüchliche Versionsangaben + +| Ort | Befund | +|-----|--------| +| `backend/main.py` | `FastAPI(..., version="3.0.0")` – wirkt wie API-/Framework-Version, nicht App-Version | +| `backend/version.py` | `APP_VERSION = "0.9l"` | +| `backend/routers/auth.py` `/api/auth/status` | `"version": "v9b"` – offensichtlich veraltet | +| `CLAUDE.md` | erwähnt `frontend/src/version.js` – **Datei im Repo nicht vorhanden** (Dokumentationsdrift) | + +**Empfehlung:** Eine Quelle (`version.py` + optional Endpoint `/api/version`), Frontend aus derselben Quelle oder Build-Inject. + +--- + +## Mittel (P2) – Inkonsistenzen & Wartung + +### 7. Frontend: direktes `fetch` statt `api.js` + +Laut `.claude/rules/CODING_RULES.md` sollen alle API-Calls über `api.js` laufen. Tatsächlich gibt es u. a.: + +- `frontend/src/context/AuthContext.jsx`, `ProfileContext.jsx`, `PasswordRecovery.jsx` +- `frontend/src/pages/AdminPanel.jsx`, `SettingsPage.jsx` + +Risiko: inkonsistente Header/Fehlerbehandlung, schwerer zu auditieren. + +**Vorlage:** `gitea/TEMPLATE_P2-consolidate-api-client.md` + +--- + +### 8. Duplikat- und Legacy-Code + +| Befund | Hinweis | +|--------|---------| +| `backend/main_old.py` | Großes, nicht eingebundenes Modul – verwirrt Reviews und Suche | +| `backend/calculations/*` vs `backend/data_layer/*` | Parallelstruktur; Phase-0c nutzt `data_layer`, aber z. B. `placeholder_resolver.py` importiert noch `calculations.scores` | +| `backend/apply_v9c_migration.py` in `startup_event` | Legacy-Hook neben `db_init` – technische Schuld | + +**Vorlage:** `gitea/TEMPLATE_P2-dead-code-and-metrics-dedup.md` + +--- + +### 9. Hardcodierte Werte (Auswahl) + +- **Tests:** `tests/dev-smoke-test.spec.js` – Fallback-Passwort `'5112'` wenn `TEST_PASSWORD` fehlt (nur für lokale/CI-Umgebung akzeptabel, sollte in Doku stehen und nie in Prod-Daten). +- **Playwright:** `playwright.config.js` – feste `baseURL: 'https://dev.mitai.jinkendo.de'`. +- **Admin-UI:** `AdminPanel.jsx` – Beispielzeile mit `APP_URL=http://192.168.2.49:3002` (Dokumentation im UI). +- **Farben:** Viele Hex-Werte in JSX (z. B. `NutritionCharts.jsx`, `History.jsx`) – Verstoß gegen die eigene CSS-Variablen-Regel, aber eher UX/Wartbarkeit als Security. + +### 10. Passwortlänge: `/api/auth/pin` vs Registrierung + +Registrierung verlangt mindestens **8** Zeichen; PIN-/Passwort-Änderung über `/api/auth/pin` erlaubt **4** Zeichen. Inkonsistent für Sicherheitskommunikation. + +### 11. Dynamische SQL-`UPDATE`-Fragmente + +Muster `UPDATE ... SET {', '.join(f'{k}=%s' for k in d)}` erscheint in mehreren Routern. **Soweit geprüft**, werden Keys aus kontrollierten Dicts/Pydantic-Modellen gebaut – **kein** offensichtlicher SQL-Injection-Pfad durch Rohtext. Risiko steigt, wenn später unkontrollierte Keys eingespeist werden. Whitelist pro Endpoint wäre robuster. + +--- + +## Positiv hervorgehoben + +- Parametrisierte Queries (`%s`) durchgängig üblich; keine `execute(f"...{userinput}")` in den geprüften kritischen Pfaden. +- `bcrypt` + Migration von Legacy-SHA256 in `auth.py` / Login. +- Rate Limiting auf Login, Forgot-Password, Register (`slowapi`). +- Forgot-Password gibt keine E-Mail-Existenz preis. +- Admin-Router (`/api/admin/*`) konsequent mit `require_admin`. +- Modulare Router-Registrierung in `main.py`. + +--- + +## Mapping: Vorlagen → Themen + +| Priorität | Thema | Datei unter `gitea/` | +|-----------|--------|----------------------| +| P0 | IDOR `X-Profile-Id` / `get_pid` | `TEMPLATE_P0-profile-scoping-x-profile-id.md` | +| P0 | `/api/profiles` ohne Admin | `TEMPLATE_P0-profiles-router-authorization.md` | +| P1 | Foto-Ownership | `TEMPLATE_P1-photo-ownership-enforcement.md` | +| P1 | CORS-Default | `TEMPLATE_P1-cors-allowed-origins-hardening.md` | +| P2 | `fetch` vs `api.js` | `TEMPLATE_P2-consolidate-api-client.md` | +| P2 | Legacy/Duplikate | `TEMPLATE_P2-dead-code-and-metrics-dedup.md` | + +--- + +## Nächste Schritte (empfohlen) + +1. **Sofort:** P0-Issues planen und `get_pid` / Profil-Router designen (Session-first, Admin-Override explizit). +2. **Kurzfristig:** Foto-GET mit `profile_id`-Check; CORS in allen Umgebungen prüfen. +3. **Mittelfristig:** Versionierung vereinheitlichen; `fetch`-Calls bündeln; `main_old.py` und Metrik-Duplikate bereinigen oder archivieren. + +--- + +*Erstellt im Rahmen einer statischen Code-Analyse; bei Änderungen am Code bitte Report-Datum und Befunde aktualisieren.* diff --git a/.claude/docs/audit/20260404_code_audit/README.md b/.claude/docs/audit/20260404_code_audit/README.md new file mode 100644 index 0000000..89ba7ca --- /dev/null +++ b/.claude/docs/audit/20260404_code_audit/README.md @@ -0,0 +1,8 @@ +# Audit 2026-04-04 – Code Review + +| Inhalt | Pfad | +|--------|------| +| Vollständiger Report | [`CODE_AUDIT_REPORT_2026-04-04.md`](./CODE_AUDIT_REPORT_2026-04-04.md) | +| Gitea-Issue-Vorlagen (Copy & Paste) | [`gitea/`](./gitea/) | + +Beim Anlegen in Gitea: **Titel** und **Labels** aus dem YAML-Kopf der jeweiligen Vorlage übernehmen, **Beschreibung** = Rest des Markdown (ohne die `---`-Blöcke, falls gewünscht). diff --git a/.claude/docs/audit/20260404_code_audit/gitea/TEMPLATE_P0-profile-scoping-x-profile-id.md b/.claude/docs/audit/20260404_code_audit/gitea/TEMPLATE_P0-profile-scoping-x-profile-id.md new file mode 100644 index 0000000..b5f539d --- /dev/null +++ b/.claude/docs/audit/20260404_code_audit/gitea/TEMPLATE_P0-profile-scoping-x-profile-id.md @@ -0,0 +1,37 @@ +--- +# Gitea: Beim Anlegen des Issues Titel und Labels manuell übernehmen (YAML wird von Gitea nicht automatisch geparst – dient als Meta-Hinweis). +title: "[Security/P0] X-Profile-Id / get_pid ermöglicht IDOR über alle Datendomänen" +labels: security, backend, priority-critical +--- + +## Kontext + +Code-Audit 2026-04-04 (`/.claude/docs/audit/20260404_code_audit/CODE_AUDIT_REPORT_2026-04-04.md`). + +## Problem + +Die Funktion `get_pid` in `backend/routers/profiles.py` gibt bei gesetztem Header `X-Profile-Id` diesen Wert **unverändert** zurück, **ohne** Abgleich mit `session['profile_id']` oder Admin-Rolle. + +Damit kann jede authentifizierte Session mit gültigem Token Daten eines **beliebigen** Profils über alle Endpunkte ansprechen, die `get_pid(x_profile_id)` nutzen (u. a. Gewicht, Aktivität, Ernährung, Fotos-Upload/Liste, Vitals, Insights, Schlaf, Ruhetage, Export). + +## Reproduktion (konzeptionell) + +1. Als Nutzer A einloggen, Token erhalten. +2. Profil-ID von Nutzer B ermitteln (z. B. über `GET /api/profiles` – siehe separates Issue). +3. Request gegen geschützten Endpoint mit Header `X-Profile-Id: ` und Token von A. + +## Erwartetes Verhalten + +- Standard: `profile_id` **ausschließlich** aus der Session. +- Optional: `X-Profile-Id` nur für **Admins** und mit expliziter Prüfung `require_admin` + dokumentierter Use-Case (Multi-User-Admin-UI). + +## Akzeptanzkriterien + +- [ ] Alle betroffenen Router verwenden session-basierte Profil-IDs oder einen validierten Admin-Override. +- [ ] Regressionstests oder manuelle Checkliste für mindestens einen Endpoint pro Router-Gruppe. +- [ ] Kurze Doku in `.claude/rules` oder technischer Doc, ob `X-Profile-Id` noch existiert und wann. + +## Betroffene Dateien (Ausgangspunkt) + +- `backend/routers/profiles.py` (`get_pid`) +- Alle Importe von `get_pid` (z. B. `weight`, `activity`, `nutrition`, `photos`, `insights`, `vitals_baseline`, `blood_pressure`, `rest_days`, `sleep`, …) diff --git a/.claude/docs/audit/20260404_code_audit/gitea/TEMPLATE_P0-profiles-router-authorization.md b/.claude/docs/audit/20260404_code_audit/gitea/TEMPLATE_P0-profiles-router-authorization.md new file mode 100644 index 0000000..498044e --- /dev/null +++ b/.claude/docs/audit/20260404_code_audit/gitea/TEMPLATE_P0-profiles-router-authorization.md @@ -0,0 +1,37 @@ +--- +title: "[Security/P0] /api/profiles CRUD ohne require_admin – Datenabfluss und Missbrauch" +labels: security, backend, priority-critical +--- + +## Kontext + +Code-Audit 2026-04-04. + +## Problem + +In `backend/routers/profiles.py` nutzen u. a. folgende Routen `Depends(require_auth)` **ohne** `require_admin`: + +- `GET /api/profiles` – Liste aller Profile +- `POST /api/profiles` – Profil anlegen +- `GET/PUT/DELETE /api/profiles/{pid}` – Abruf/Update/Löschen nach ID + +Jeder eingeloggte Nutzer kann damit die Profilliste sehen und je nach Datenbestand auch fremde Profile manipulieren oder löschen (sofern mehr als ein Profil existiert). + +Das Frontend (`ProfileContext.jsx`) ruft `GET /api/profiles` für die Profilauswahl auf – das erklärt die aktuelle UX, ersetzt aber keine serverseitige Autorisierung. + +## Erwartetes Verhalten + +- Listen, Anlegen, Löschen und Bearbeiten **fremder** Profile: nur `require_admin`. +- „Aktuelles Profil“: dedizierte Routen (`/api/profile` o. ä.) strikt an `session['profile_id']` gebunden. + +## Akzeptanzkriterien + +- [ ] Rollenmodell dokumentiert (wer darf `/api/profiles` sehen?). +- [ ] Admin-only für sensible Operationen erzwungen. +- [ ] Frontend angepasst (falls nötig: Admin vs. normaler Nutzer unterschiedliche Endpunkte). +- [ ] Keine Regression für Single-User-Selfhosting ohne unnötige UX-Brüche (optional: Feature-Flag oder Rolle `admin` für ersten User). + +## Betroffene Dateien + +- `backend/routers/profiles.py` +- `frontend/src/context/ProfileContext.jsx` (und ggf. weitere Aufrufer von `/api/profiles`) diff --git a/.claude/docs/audit/20260404_code_audit/gitea/TEMPLATE_P1-cors-allowed-origins-hardening.md b/.claude/docs/audit/20260404_code_audit/gitea/TEMPLATE_P1-cors-allowed-origins-hardening.md new file mode 100644 index 0000000..f9cf461 --- /dev/null +++ b/.claude/docs/audit/20260404_code_audit/gitea/TEMPLATE_P1-cors-allowed-origins-hardening.md @@ -0,0 +1,36 @@ +--- +title: "[Security/P1] CORS: Default ALLOWED_ORIGINS=* mit allow_credentials=True" +labels: security, backend, configuration, priority-high +--- + +## Kontext + +Code-Audit 2026-04-04. + +## Problem + +`backend/main.py`: + +```python +allow_origins=os.getenv("ALLOWED_ORIGINS", "*").split(","), +allow_credentials=True, +``` + +Fehlt die Umgebungsvariable, ist die Default-Konfiguration `*`. Zusammen mit `allow_credentials=True` ist das in Produktion unerwünscht und erschwert eine klare Security-Posture. + +## Erwartetes Verhalten + +- Produktion: explizite Origins aus `ALLOWED_ORIGINS` (kommasepariert), kein stiller Wildcard-Fallback. +- Entwicklung: dokumentierter Default (z. B. `http://localhost:5173`) oder explizite Opt-in-Umgebungsvariable `CORS_ALLOW_ALL=true`. + +## Akzeptanzkriterien + +- [ ] Verhalten in `.env.example` und Deployment-Doku beschrieben. +- [ ] Optional: Anwendung startet nicht, wenn `ALLOWED_ORIGINS` in `ENV=production` fehlt. +- [ ] Manuelle Prüfung: Browser-Requests von fremden Origins werden blockiert. + +## Betroffene Dateien + +- `backend/main.py` +- `.env.example` +- ggf. `docker-compose*.yml` diff --git a/.claude/docs/audit/20260404_code_audit/gitea/TEMPLATE_P1-photo-ownership-enforcement.md b/.claude/docs/audit/20260404_code_audit/gitea/TEMPLATE_P1-photo-ownership-enforcement.md new file mode 100644 index 0000000..b80b008 --- /dev/null +++ b/.claude/docs/audit/20260404_code_audit/gitea/TEMPLATE_P1-photo-ownership-enforcement.md @@ -0,0 +1,36 @@ +--- +title: "[Security/P1] GET /api/photos/{fid} ohne Prüfung auf profile_id (Objekt-Zugriff)" +labels: security, backend, priority-high +--- + +## Kontext + +Code-Audit 2026-04-04. + +## Problem + +`backend/routers/photos.py`: `GET /{fid}` lädt ein Foto nur anhand der `id` aus der Tabelle `photos`, ohne zu prüfen, ob der Datensatz zu `session['profile_id']` gehört. + +Authentifizierung ist vorhanden (`require_auth_flexible`), aber **keine Objekt-Level-Autorisierung**. + +## Risiko + +Bei bekannter oder erratener Foto-UUID können Inhalte anderer Nutzer derselben Instanz abgerufen werden. + +## Erwartetes Verhalten + +```sql +SELECT path FROM photos WHERE id = %s AND profile_id = %s +``` + +mit `profile_id` aus der Session; Admins optional mit separatem Codepfad. + +## Akzeptanzkriterien + +- [ ] Abruf nur für eigenes Profil (bzw. dokumentierter Admin-Fall). +- [ ] Bestehende Clients (PWA) unverändert nutzbar. +- [ ] Kurzer Testfall oder manuelle Prüfung: Nutzer A kann Foto von B nicht öffnen. + +## Betroffene Dateien + +- `backend/routers/photos.py` diff --git a/.claude/docs/audit/20260404_code_audit/gitea/TEMPLATE_P2-consolidate-api-client.md b/.claude/docs/audit/20260404_code_audit/gitea/TEMPLATE_P2-consolidate-api-client.md new file mode 100644 index 0000000..87a3cd3 --- /dev/null +++ b/.claude/docs/audit/20260404_code_audit/gitea/TEMPLATE_P2-consolidate-api-client.md @@ -0,0 +1,30 @@ +--- +title: "[Tech-Debt/P2] Frontend: direktes fetch statt zentral api.js (Auth, Admin, Import)" +labels: frontend, refactoring, consistency +--- + +## Kontext + +Code-Audit 2026-04-04. Verbindliche Regel: `.claude/rules/CODING_RULES.md` – alle API-Calls über `frontend/src/utils/api.js`. + +## Problem + +Mehrere Komponenten nutzen natives `fetch('/api/...')` mit manuell gesetztem `X-Auth-Token`, u. a.: + +- `frontend/src/context/AuthContext.jsx` +- `frontend/src/context/ProfileContext.jsx` +- `frontend/src/pages/PasswordRecovery.jsx` +- `frontend/src/pages/AdminPanel.jsx` +- `frontend/src/pages/SettingsPage.jsx` + +Nachteile: doppelte Fehlerbehandlung, schwerere Wartung, leichter zu übersehen bei Security-Reviews. + +## Erwartetes Verhalten + +Alle Aufrufe über `api.js` (oder ein einziges dünnes Wrapper-Modul), inkl. Upload/Export-Sonderfälle. + +## Akzeptanzkriterien + +- [ ] Kein direktes `fetch('/api/` mehr außerhalb von `api.js` (Ausnahmen dokumentieren, falls nötig). +- [ ] Einheitliche Fehler- und 401-Behandlung. +- [ ] Kurze Notiz in CODING_RULES oder LESSONS_LEARNED, falls Ausnahmen bleiben. diff --git a/.claude/docs/audit/20260404_code_audit/gitea/TEMPLATE_P2-dead-code-and-metrics-dedup.md b/.claude/docs/audit/20260404_code_audit/gitea/TEMPLATE_P2-dead-code-and-metrics-dedup.md new file mode 100644 index 0000000..4f3ac0b --- /dev/null +++ b/.claude/docs/audit/20260404_code_audit/gitea/TEMPLATE_P2-dead-code-and-metrics-dedup.md @@ -0,0 +1,34 @@ +--- +title: "[Tech-Debt/P2] Legacy main_old.py, calculations/ vs data_layer/, Startup-Migration-Hooks" +labels: backend, refactoring, maintenance +--- + +## Kontext + +Code-Audit 2026-04-04. + +## Problemstellung (Bündel) + +1. **`backend/main_old.py`**: große, nicht in `main.py` eingebundene Datei – verwirrt Suche, Reviews und Onboarding. +2. **Parallele Metrik-Pfade**: `backend/data_layer/` (Phase 0c) und `backend/calculations/` existieren nebeneinander; `placeholder_resolver.py` importiert z. B. noch `calculations.scores`. +3. **`startup_event` in `main.py`**: zusätzlicher Hook `apply_v9c_migration` neben `db_init` – Legacy-Pfad neben dem Migrations-System. + +## Ziele + +- Single Source of Truth für Metriken (klar dokumentiert: `data_layer` vs. verbleibende Hilfsmodule). +- Entfernen oder Archivieren (`docs/archive/` oder Git-History-only) von `main_old.py`, sofern nicht mehr referenziert. +4. Migration-Startup vereinheitlichen (nur `db_init` / nummerierte SQL-Migrationen, sofern v9c-Hook obsolet). + +## Akzeptanzkriterien + +- [ ] Entscheidung dokumentiert: welches Paket autoritativ ist. +- [ ] Keine doppelten Implementierungen für dieselbe Metrik ohne Grund. +- [ ] `main_old.py` entfernt oder klar als „archived“ markiert und aus IDEs/CI-Suche ausgeschlossen (optional). +- [ ] Build/Start ohne v9c-Sonderpfad, falls redundant. + +## Betroffene Pfade (Start) + +- `backend/main_old.py` +- `backend/main.py` (startup) +- `backend/data_layer/`, `backend/calculations/` +- `backend/placeholder_resolver.py` diff --git a/.claude/docs/audit/README.md b/.claude/docs/audit/README.md new file mode 100644 index 0000000..31ff6d7 --- /dev/null +++ b/.claude/docs/audit/README.md @@ -0,0 +1,10 @@ +# Audit- und Reconcile-Artefakte + +Zeitlich begrenzte Reviews, Matrizen und Gitea-Vorlagen. **Keine** verbindlichen Produkt-Spezifikationen – die bleiben unter [`functional/`](../functional/) und [`technical/`](../technical/). + +| Ordner | Inhalt | +|--------|--------| +| [`20260404_code_audit/`](./20260404_code_audit/) | Code-Audit 2026-04-04, Report + Issue-Vorlagen | +| [`platzhalter/`](./platzhalter/) | Platzhalter-Audits, Kataloge, Reconciliation 2026-03 (JSON/Matrix) | + +**Hinweis:** Doppelablagen zu `functional/` wurden entfernt (z. B. fachliche `DATA_ARCHITECTURE`, Konzept-PDF-Markdown v2). Immer die Pfade unter `functional/` bzw. `technical/` als Quelle nutzen. diff --git a/.claude/docs/audit/platzhalter/PLACEHOLDER_CATALOG_EXTENDED.json b/.claude/docs/audit/platzhalter/PLACEHOLDER_CATALOG_EXTENDED.json new file mode 100644 index 0000000..a41b8f0 --- /dev/null +++ b/.claude/docs/audit/platzhalter/PLACEHOLDER_CATALOG_EXTENDED.json @@ -0,0 +1,7078 @@ +{ + "schema_version": "1.0.0", + "generated_at": "2026-03-29T19:26:40.148104", + "normative_standard": "PLACEHOLDER_METADATA_REQUIREMENTS_V2_NORMATIVE.md", + "total_placeholders": 111, + "placeholders": { + "ability_balance_coordination": { + "key": "ability_balance_coordination", + "placeholder": "{{ability_balance_coordination}}", + "category": "Unknown", + "type": "atomic", + "description": "No description available", + "semantic_contract": "No description available", + "unit": "%", + "time_window": "unknown", + "output_type": "number", + "format_hint": "12.3 %", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_int", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "ability_balance_endurance": { + "key": "ability_balance_endurance", + "placeholder": "{{ability_balance_endurance}}", + "category": "Training", + "type": "atomic", + "description": "Ability Balance - Ausdauer (0-100)", + "semantic_contract": "Ability Balance - Ausdauer (0-100)", + "unit": "%", + "time_window": "unknown", + "output_type": "number", + "format_hint": "12.3 %", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_int", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "ability_balance_mental": { + "key": "ability_balance_mental", + "placeholder": "{{ability_balance_mental}}", + "category": "Unknown", + "type": "atomic", + "description": "No description available", + "semantic_contract": "No description available", + "unit": "%", + "time_window": "unknown", + "output_type": "number", + "format_hint": "12.3 %", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_int", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "ability_balance_mobility": { + "key": "ability_balance_mobility", + "placeholder": "{{ability_balance_mobility}}", + "category": "Unknown", + "type": "atomic", + "description": "No description available", + "semantic_contract": "No description available", + "unit": "%", + "time_window": "unknown", + "output_type": "number", + "format_hint": "12.3 %", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_int", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "ability_balance_strength": { + "key": "ability_balance_strength", + "placeholder": "{{ability_balance_strength}}", + "category": "Training", + "type": "atomic", + "description": "Ability Balance - Kraft (0-100)", + "semantic_contract": "Ability Balance - Kraft (0-100)", + "unit": "%", + "time_window": "unknown", + "output_type": "number", + "format_hint": "12.3 %", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_int", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "active_goals_json": { + "key": "active_goals_json", + "placeholder": "{{active_goals_json}}", + "category": "Unknown", + "type": "raw_data", + "description": "No description available", + "semantic_contract": "JSON-Array aller aktiven Ziele mit vollständigen Details", + "unit": null, + "time_window": "unknown", + "output_type": "json", + "format_hint": "JSON object", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_json", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [ + "goals", + "focus_area_definitions", + "goal_focus_contributions" + ], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "active_goals_md": { + "key": "active_goals_md", + "placeholder": "{{active_goals_md}}", + "category": "Unknown", + "type": "raw_data", + "description": "No description available", + "semantic_contract": "Markdown-formatierte Liste aller aktiven Ziele", + "unit": null, + "time_window": "unknown", + "output_type": "markdown", + "format_hint": "Markdown-formatted text", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_str", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "activity_detail": { + "key": "activity_detail", + "placeholder": "{{activity_detail}}", + "category": "Unknown", + "type": "raw_data", + "description": "No description available", + "semantic_contract": "Detaillierte Liste aller Trainingseinheiten mit Typ, Dauer, Intensität", + "unit": null, + "time_window": "unknown", + "output_type": "string", + "format_hint": "Text", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "get_activity_detail", + "module": "placeholder_resolver.py", + "function": "get_activity_detail_data", + "data_layer_module": "activity_metrics", + "source_tables": [ + "activity_log", + "training_types" + ], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [ + "Aktivität & Training", + "Pipeline: Aktivitäts-Analyse (JSON)" + ], + "pipelines": [ + "Aktivität & Training", + "Pipeline: Aktivitäts-Analyse (JSON)" + ], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [ + "time_window_ambiguous: No clear time window specified" + ], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "activity_score": { + "key": "activity_score", + "placeholder": "{{activity_score}}", + "category": "Scores (Phase 0b)", + "type": "atomic", + "description": "Activity Score (0-100)", + "semantic_contract": "Activity Score basierend auf Trainingsfrequenz, Qualitätssessions (0-100)", + "unit": "%", + "time_window": "unknown", + "output_type": "integer", + "format_hint": "85 %", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_int", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "activity_summary": { + "key": "activity_summary", + "placeholder": "{{activity_summary}}", + "category": "Training", + "type": "raw_data", + "description": "Aktivitäts-Zusammenfassung (7d)", + "semantic_contract": "Strukturierte Zusammenfassung der Trainingsaktivität der letzten 7 Tage", + "unit": null, + "time_window": "unknown", + "output_type": "string", + "format_hint": "Text", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "get_activity_summary", + "module": "placeholder_resolver.py", + "function": "get_activity_summary_data", + "data_layer_module": "activity_metrics", + "source_tables": [ + "activity_log" + ], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [ + "Gesamtanalyse" + ], + "pipelines": [ + "Gesamtanalyse" + ], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [ + "time_window_ambiguous: Function name suggests variable window, actual implementation unclear" + ], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "age": { + "key": "age", + "placeholder": "{{age}}", + "category": "Profil", + "type": "atomic", + "description": "Alter in Jahren", + "semantic_contract": "Berechnet aus Geburtsdatum (dob) im Profil via calculate_age()", + "unit": "Jahre", + "time_window": "latest", + "output_type": "string", + "format_hint": "Wert Jahre", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "calculate_age", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [ + "Pipeline: Körper-Analyse (JSON)" + ], + "pipelines": [ + "Pipeline: Körper-Analyse (JSON)" + ], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "arm_28d_delta": { + "key": "arm_28d_delta", + "placeholder": "{{arm_28d_delta}}", + "category": "Unknown", + "type": "atomic", + "description": "No description available", + "semantic_contract": "No description available (Zeitfenster: 28d)", + "unit": "cm", + "time_window": "28d", + "output_type": "number", + "format_hint": "12.3 cm", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_float", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "bmi": { + "key": "bmi", + "placeholder": "{{bmi}}", + "category": "Körper", + "type": "atomic", + "description": "Body Mass Index", + "semantic_contract": "Body Mass Index", + "unit": null, + "time_window": "unknown", + "output_type": "string", + "format_hint": "Text", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "calculate_bmi", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "body_progress_score": { + "key": "body_progress_score", + "placeholder": "{{body_progress_score}}", + "category": "Scores (Phase 0b)", + "type": "atomic", + "description": "Body Progress Score (0-100)", + "semantic_contract": "Body Progress Score basierend auf Gewicht/KFA-Ziel-Erreichung (0-100)", + "unit": "%", + "time_window": "unknown", + "output_type": "integer", + "format_hint": "85 %", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_int", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "caliper_summary": { + "key": "caliper_summary", + "placeholder": "{{caliper_summary}}", + "category": "Unknown", + "type": "raw_data", + "description": "No description available", + "semantic_contract": "Strukturierte Zusammenfassung der letzten Caliper-Messungen mit Körperfettanteil und Methode", + "unit": null, + "time_window": "unknown", + "output_type": "string", + "format_hint": "Text", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "get_caliper_summary", + "module": "placeholder_resolver.py", + "function": "get_body_composition_data", + "data_layer_module": "body_metrics", + "source_tables": [ + "caliper_log" + ], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [ + "Gesamtanalyse", + "Körperkomposition", + "Gesundheitsindikatoren", + "Pipeline: Körper-Analyse (JSON)" + ], + "pipelines": [ + "Gesamtanalyse", + "Körperkomposition", + "Gesundheitsindikatoren", + "Pipeline: Körper-Analyse (JSON)" + ], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [ + "Returns formatted text summary, not JSON" + ], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "carb_avg": { + "key": "carb_avg", + "placeholder": "{{carb_avg}}", + "category": "Ernährung", + "type": "atomic", + "description": "Durchschn. Kohlenhydrate in g (30d)", + "semantic_contract": "Durchschnittliche Kohlenhydrataufnahme in g über 30 Tage aus nutrition_log", + "unit": "g", + "time_window": "30d", + "output_type": "number", + "format_hint": "12.3 g", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "get_nutrition_avg", + "module": "placeholder_resolver.py", + "function": "get_nutrition_average_data", + "data_layer_module": "nutrition_metrics", + "source_tables": [ + "nutrition_log" + ], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [ + "Pipeline: Ernährungs-Analyse (JSON)" + ], + "pipelines": [ + "Pipeline: Ernährungs-Analyse (JSON)" + ], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "chest_28d_delta": { + "key": "chest_28d_delta", + "placeholder": "{{chest_28d_delta}}", + "category": "Unknown", + "type": "atomic", + "description": "No description available", + "semantic_contract": "No description available (Zeitfenster: 28d)", + "unit": "cm", + "time_window": "28d", + "output_type": "number", + "format_hint": "12.3 cm", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_float", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "circ_summary": { + "key": "circ_summary", + "placeholder": "{{circ_summary}}", + "category": "Unknown", + "type": "raw_data", + "description": "No description available", + "semantic_contract": "Best-of-Each Strategie: neueste Messung pro Körperstelle mit Altersangabe in Tagen", + "unit": null, + "time_window": "mixed", + "output_type": "string", + "format_hint": "Text", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "get_circ_summary", + "module": "placeholder_resolver.py", + "function": "get_circumference_summary_data", + "data_layer_module": "body_metrics", + "source_tables": [ + "circumference_log" + ], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [ + "Gesamtanalyse", + "Körperkomposition", + "Gesundheitsindikatoren", + "Pipeline: Körper-Analyse (JSON)" + ], + "pipelines": [ + "Gesamtanalyse", + "Körperkomposition", + "Gesundheitsindikatoren", + "Pipeline: Körper-Analyse (JSON)" + ], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [ + "Different body parts may have different timestamps" + ], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "correlation_energy_weight_lag": { + "key": "correlation_energy_weight_lag", + "placeholder": "{{correlation_energy_weight_lag}}", + "category": "Unknown", + "type": "interpreted", + "description": "No description available", + "semantic_contract": "Lag-Korrelation zwischen Energiebilanz und Gewichtsänderung (3d/7d/14d)", + "unit": "kg", + "time_window": "unknown", + "output_type": "json", + "format_hint": "12.3 kg", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_json", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [ + "goals", + "focus_area_definitions", + "goal_focus_contributions" + ], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "correlation_load_hrv": { + "key": "correlation_load_hrv", + "placeholder": "{{correlation_load_hrv}}", + "category": "Unknown", + "type": "interpreted", + "description": "No description available", + "semantic_contract": "No description available [KI-interpretiert]", + "unit": "bpm", + "time_window": "unknown", + "output_type": "string", + "format_hint": "Wert bpm", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_json", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [ + "goals", + "focus_area_definitions", + "goal_focus_contributions" + ], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "correlation_load_rhr": { + "key": "correlation_load_rhr", + "placeholder": "{{correlation_load_rhr}}", + "category": "Unknown", + "type": "interpreted", + "description": "No description available", + "semantic_contract": "No description available [KI-interpretiert]", + "unit": "bpm", + "time_window": "unknown", + "output_type": "string", + "format_hint": "Wert bpm", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_json", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [ + "goals", + "focus_area_definitions", + "goal_focus_contributions" + ], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "correlation_protein_lbm": { + "key": "correlation_protein_lbm", + "placeholder": "{{correlation_protein_lbm}}", + "category": "Unknown", + "type": "interpreted", + "description": "No description available", + "semantic_contract": "Korrelation zwischen Proteinaufnahme und Magermasse-Änderung", + "unit": "g", + "time_window": "unknown", + "output_type": "json", + "format_hint": "Wert g", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_json", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [ + "goals", + "focus_area_definitions", + "goal_focus_contributions" + ], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "correlation_sleep_recovery": { + "key": "correlation_sleep_recovery", + "placeholder": "{{correlation_sleep_recovery}}", + "category": "Unknown", + "type": "interpreted", + "description": "No description available", + "semantic_contract": "No description available [KI-interpretiert]", + "unit": null, + "time_window": "unknown", + "output_type": "string", + "format_hint": "Text", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_json", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [ + "goals", + "focus_area_definitions", + "goal_focus_contributions" + ], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "data_quality_score": { + "key": "data_quality_score", + "placeholder": "{{data_quality_score}}", + "category": "Scores (Phase 0b)", + "type": "atomic", + "description": "Data Quality Score (0-100)", + "semantic_contract": "Data Quality Score (0-100)", + "unit": "%", + "time_window": "unknown", + "output_type": "integer", + "format_hint": "85 %", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_int", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "datum_heute": { + "key": "datum_heute", + "placeholder": "{{datum_heute}}", + "category": "Zeitraum", + "type": "atomic", + "description": "Heutiges Datum", + "semantic_contract": "Aktuelles Datum im Format YYYY-MM-DD", + "unit": null, + "time_window": "unknown", + "output_type": "date", + "format_hint": "2026-03-29", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "energy_balance_7d": { + "key": "energy_balance_7d", + "placeholder": "{{energy_balance_7d}}", + "category": "Ernährung", + "type": "atomic", + "description": "Energiebilanz 7d (kcal/Tag)", + "semantic_contract": "Energiebilanz 7d (kcal/Tag) (Zeitfenster: 7d)", + "unit": "%", + "time_window": "7d", + "output_type": "number", + "format_hint": "12.3 %", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_float", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "energy_deficit_surplus": { + "key": "energy_deficit_surplus", + "placeholder": "{{energy_deficit_surplus}}", + "category": "Unknown", + "type": "atomic", + "description": "No description available", + "semantic_contract": "No description available", + "unit": "kcal", + "time_window": "unknown", + "output_type": "string", + "format_hint": "Wert kcal", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_str", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "fat_avg": { + "key": "fat_avg", + "placeholder": "{{fat_avg}}", + "category": "Ernährung", + "type": "atomic", + "description": "Durchschn. Fett in g (30d)", + "semantic_contract": "Durchschnittliche Fettaufnahme in g über 30 Tage aus nutrition_log", + "unit": "g", + "time_window": "30d", + "output_type": "number", + "format_hint": "12.3 g", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "get_nutrition_avg", + "module": "placeholder_resolver.py", + "function": "get_nutrition_average_data", + "data_layer_module": "nutrition_metrics", + "source_tables": [ + "nutrition_log" + ], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [ + "Pipeline: Ernährungs-Analyse (JSON)" + ], + "pipelines": [ + "Pipeline: Ernährungs-Analyse (JSON)" + ], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "fm_28d_change": { + "key": "fm_28d_change", + "placeholder": "{{fm_28d_change}}", + "category": "Körper", + "type": "atomic", + "description": "Fettmasse Änderung 28d (kg)", + "semantic_contract": "Fettmasse Änderung 28d (kg) (Zeitfenster: 28d)", + "unit": "kg", + "time_window": "28d", + "output_type": "number", + "format_hint": "12.3 kg", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_float", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "focus_area_weights_json": { + "key": "focus_area_weights_json", + "placeholder": "{{focus_area_weights_json}}", + "category": "Unknown", + "type": "raw_data", + "description": "No description available", + "semantic_contract": "No description available [Strukturierte Rohdaten]", + "unit": "kg", + "time_window": "unknown", + "output_type": "json", + "format_hint": "JSON object", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_json", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [ + "goals", + "focus_area_definitions", + "goal_focus_contributions" + ], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "focus_areas_weighted_json": { + "key": "focus_areas_weighted_json", + "placeholder": "{{focus_areas_weighted_json}}", + "category": "Unknown", + "type": "raw_data", + "description": "No description available", + "semantic_contract": "JSON-Array der gewichteten Focus Areas mit Progress", + "unit": "kg", + "time_window": "unknown", + "output_type": "json", + "format_hint": "JSON object", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_json", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [ + "goals", + "focus_area_definitions", + "goal_focus_contributions" + ], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "focus_areas_weighted_md": { + "key": "focus_areas_weighted_md", + "placeholder": "{{focus_areas_weighted_md}}", + "category": "Unknown", + "type": "raw_data", + "description": "No description available", + "semantic_contract": "No description available [Strukturierte Rohdaten]", + "unit": "kg", + "time_window": "unknown", + "output_type": "markdown", + "format_hint": "Markdown-formatted text", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_str", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "focus_cat_aktivität_progress": { + "key": "focus_cat_aktivität_progress", + "placeholder": "{{focus_cat_aktivität_progress}}", + "category": "Focus Areas", + "type": "interpreted", + "description": "Kategorie Aktivität - Progress (%)", + "semantic_contract": "Kategorie Aktivität - Progress (%) [KI-interpretiert]", + "unit": null, + "time_window": "unknown", + "output_type": "string", + "format_hint": "Text", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_int", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "focus_cat_aktivität_weight": { + "key": "focus_cat_aktivität_weight", + "placeholder": "{{focus_cat_aktivität_weight}}", + "category": "Focus Areas", + "type": "interpreted", + "description": "Kategorie Aktivität - Gewichtung (%)", + "semantic_contract": "Kategorie Aktivität - Gewichtung (%) [KI-interpretiert]", + "unit": "kg", + "time_window": "unknown", + "output_type": "number", + "format_hint": "12.3 kg", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_float", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "focus_cat_ernährung_progress": { + "key": "focus_cat_ernährung_progress", + "placeholder": "{{focus_cat_ernährung_progress}}", + "category": "Focus Areas", + "type": "interpreted", + "description": "Kategorie Ernährung - Progress (%)", + "semantic_contract": "Kategorie Ernährung - Progress (%) [KI-interpretiert]", + "unit": "bpm", + "time_window": "unknown", + "output_type": "string", + "format_hint": "Wert bpm", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_int", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "focus_cat_ernährung_weight": { + "key": "focus_cat_ernährung_weight", + "placeholder": "{{focus_cat_ernährung_weight}}", + "category": "Focus Areas", + "type": "interpreted", + "description": "Kategorie Ernährung - Gewichtung (%)", + "semantic_contract": "Kategorie Ernährung - Gewichtung (%) [KI-interpretiert]", + "unit": "kg", + "time_window": "unknown", + "output_type": "number", + "format_hint": "12.3 kg", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_float", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "focus_cat_körper_progress": { + "key": "focus_cat_körper_progress", + "placeholder": "{{focus_cat_körper_progress}}", + "category": "Focus Areas", + "type": "interpreted", + "description": "Kategorie Körper - Progress (%)", + "semantic_contract": "Kategorie Körper - Progress (%) [KI-interpretiert]", + "unit": null, + "time_window": "unknown", + "output_type": "string", + "format_hint": "Text", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_int", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "focus_cat_körper_weight": { + "key": "focus_cat_körper_weight", + "placeholder": "{{focus_cat_körper_weight}}", + "category": "Focus Areas", + "type": "interpreted", + "description": "Kategorie Körper - Gewichtung (%)", + "semantic_contract": "Kategorie Körper - Gewichtung (%) [KI-interpretiert]", + "unit": "kg", + "time_window": "unknown", + "output_type": "number", + "format_hint": "12.3 kg", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_float", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "focus_cat_lebensstil_progress": { + "key": "focus_cat_lebensstil_progress", + "placeholder": "{{focus_cat_lebensstil_progress}}", + "category": "Unknown", + "type": "interpreted", + "description": "No description available", + "semantic_contract": "No description available [KI-interpretiert]", + "unit": null, + "time_window": "unknown", + "output_type": "string", + "format_hint": "Text", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_int", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "focus_cat_lebensstil_weight": { + "key": "focus_cat_lebensstil_weight", + "placeholder": "{{focus_cat_lebensstil_weight}}", + "category": "Unknown", + "type": "interpreted", + "description": "No description available", + "semantic_contract": "No description available [KI-interpretiert]", + "unit": "kg", + "time_window": "unknown", + "output_type": "number", + "format_hint": "12.3 kg", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_float", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "focus_cat_mental_progress": { + "key": "focus_cat_mental_progress", + "placeholder": "{{focus_cat_mental_progress}}", + "category": "Unknown", + "type": "interpreted", + "description": "No description available", + "semantic_contract": "No description available [KI-interpretiert]", + "unit": null, + "time_window": "unknown", + "output_type": "string", + "format_hint": "Text", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_int", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "focus_cat_mental_weight": { + "key": "focus_cat_mental_weight", + "placeholder": "{{focus_cat_mental_weight}}", + "category": "Unknown", + "type": "interpreted", + "description": "No description available", + "semantic_contract": "No description available [KI-interpretiert]", + "unit": "kg", + "time_window": "unknown", + "output_type": "number", + "format_hint": "12.3 kg", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_float", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "focus_cat_recovery_progress": { + "key": "focus_cat_recovery_progress", + "placeholder": "{{focus_cat_recovery_progress}}", + "category": "Unknown", + "type": "interpreted", + "description": "No description available", + "semantic_contract": "No description available [KI-interpretiert]", + "unit": null, + "time_window": "unknown", + "output_type": "string", + "format_hint": "Text", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_int", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "focus_cat_recovery_weight": { + "key": "focus_cat_recovery_weight", + "placeholder": "{{focus_cat_recovery_weight}}", + "category": "Unknown", + "type": "interpreted", + "description": "No description available", + "semantic_contract": "No description available [KI-interpretiert]", + "unit": "kg", + "time_window": "unknown", + "output_type": "number", + "format_hint": "12.3 kg", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_float", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "focus_cat_vitalwerte_progress": { + "key": "focus_cat_vitalwerte_progress", + "placeholder": "{{focus_cat_vitalwerte_progress}}", + "category": "Unknown", + "type": "interpreted", + "description": "No description available", + "semantic_contract": "No description available [KI-interpretiert]", + "unit": null, + "time_window": "unknown", + "output_type": "string", + "format_hint": "Text", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_int", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "focus_cat_vitalwerte_weight": { + "key": "focus_cat_vitalwerte_weight", + "placeholder": "{{focus_cat_vitalwerte_weight}}", + "category": "Unknown", + "type": "interpreted", + "description": "No description available", + "semantic_contract": "No description available [KI-interpretiert]", + "unit": "kg", + "time_window": "unknown", + "output_type": "number", + "format_hint": "12.3 kg", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_float", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "geschlecht": { + "key": "geschlecht", + "placeholder": "{{geschlecht}}", + "category": "Profil", + "type": "atomic", + "description": "Geschlecht", + "semantic_contract": "Geschlecht aus Profil: m='männlich', w='weiblich'", + "unit": null, + "time_window": "latest", + "output_type": "enum", + "format_hint": "Text", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [ + "Gesamtanalyse", + "Körperkomposition", + "Gesundheitsindikatoren", + "Aktivität & Training", + "Pipeline: Körper-Analyse (JSON)", + "Ernährung & Kalorien (Kopie)", + "Ernährung & Kalorien" + ], + "pipelines": [ + "Gesamtanalyse", + "Körperkomposition", + "Gesundheitsindikatoren", + "Aktivität & Training", + "Pipeline: Körper-Analyse (JSON)", + "Ernährung & Kalorien (Kopie)", + "Ernährung & Kalorien" + ], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "goal_bf_pct": { + "key": "goal_bf_pct", + "placeholder": "{{goal_bf_pct}}", + "category": "Unknown", + "type": "atomic", + "description": "No description available", + "semantic_contract": "No description available", + "unit": "%", + "time_window": "unknown", + "output_type": "integer", + "format_hint": "85 %", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "get_goal_bf_pct", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [ + "Gesamtanalyse", + "Körperkomposition", + "Pipeline: Körper-Analyse (JSON)", + "Pipeline: Zielabgleich", + "Fortschritt zu Zielen" + ], + "pipelines": [ + "Gesamtanalyse", + "Körperkomposition", + "Pipeline: Körper-Analyse (JSON)", + "Pipeline: Zielabgleich", + "Fortschritt zu Zielen" + ], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "goal_progress_score": { + "key": "goal_progress_score", + "placeholder": "{{goal_progress_score}}", + "category": "Scores (Phase 0b)", + "type": "atomic", + "description": "Goal Progress Score (0-100)", + "semantic_contract": "Gewichteter Durchschnitts-Fortschritt aller aktiven Ziele (0-100)", + "unit": "%", + "time_window": "unknown", + "output_type": "integer", + "format_hint": "85 %", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_int", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "goal_weight": { + "key": "goal_weight", + "placeholder": "{{goal_weight}}", + "category": "Unknown", + "type": "interpreted", + "description": "No description available", + "semantic_contract": "No description available [KI-interpretiert]", + "unit": "kg", + "time_window": "unknown", + "output_type": "number", + "format_hint": "12.3 kg", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "get_goal_weight", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [ + "Gesamtanalyse", + "Pipeline: Körper-Analyse (JSON)", + "Pipeline: Zielabgleich", + "Fortschritt zu Zielen" + ], + "pipelines": [ + "Gesamtanalyse", + "Pipeline: Körper-Analyse (JSON)", + "Pipeline: Zielabgleich", + "Fortschritt zu Zielen" + ], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "height": { + "key": "height", + "placeholder": "{{height}}", + "category": "Profil", + "type": "atomic", + "description": "Körpergröße in cm", + "semantic_contract": "Körpergröße aus Profil in cm, unverändert", + "unit": "cm", + "time_window": "latest", + "output_type": "string", + "format_hint": "Wert cm", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "str", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [ + "Gesamtanalyse", + "Körperkomposition", + "Gesundheitsindikatoren", + "Pipeline: Körper-Analyse (JSON)", + "Ernährung & Kalorien (Kopie)", + "Ernährung & Kalorien" + ], + "pipelines": [ + "Gesamtanalyse", + "Körperkomposition", + "Gesundheitsindikatoren", + "Pipeline: Körper-Analyse (JSON)", + "Ernährung & Kalorien (Kopie)", + "Ernährung & Kalorien" + ], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "hip_28d_delta": { + "key": "hip_28d_delta", + "placeholder": "{{hip_28d_delta}}", + "category": "Unknown", + "type": "atomic", + "description": "No description available", + "semantic_contract": "No description available (Zeitfenster: 28d)", + "unit": "cm", + "time_window": "28d", + "output_type": "number", + "format_hint": "12.3 cm", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_float", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "hrv_vs_baseline_pct": { + "key": "hrv_vs_baseline_pct", + "placeholder": "{{hrv_vs_baseline_pct}}", + "category": "Vitalwerte", + "type": "atomic", + "description": "HRV vs. Baseline (%)", + "semantic_contract": "HRV vs. Baseline (%)", + "unit": "%", + "time_window": "unknown", + "output_type": "integer", + "format_hint": "85 %", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_float", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "intake_volatility": { + "key": "intake_volatility", + "placeholder": "{{intake_volatility}}", + "category": "Unknown", + "type": "atomic", + "description": "No description available", + "semantic_contract": "No description available", + "unit": null, + "time_window": "unknown", + "output_type": "string", + "format_hint": "Text", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_str", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "kcal_avg": { + "key": "kcal_avg", + "placeholder": "{{kcal_avg}}", + "category": "Ernährung", + "type": "atomic", + "description": "Durchschn. Kalorien (30d)", + "semantic_contract": "Durchschnittliche Kalorienaufnahme über 30 Tage aus nutrition_log", + "unit": "kcal", + "time_window": "30d", + "output_type": "number", + "format_hint": "12.3 kcal", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "get_nutrition_avg", + "module": "placeholder_resolver.py", + "function": "get_nutrition_average_data", + "data_layer_module": "nutrition_metrics", + "source_tables": [ + "nutrition_log" + ], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [ + "Pipeline: Ernährungs-Analyse (JSON)" + ], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "kf_aktuell": { + "key": "kf_aktuell", + "placeholder": "{{kf_aktuell}}", + "category": "Körper", + "type": "atomic", + "description": "Aktueller Körperfettanteil in %", + "semantic_contract": "Letzter berechneter Körperfettanteil aus caliper_log (JPL-7 oder JPL-3 Formel)", + "unit": "%", + "time_window": "latest", + "output_type": "string", + "format_hint": "Wert %", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "get_latest_bf", + "module": "placeholder_resolver.py", + "function": "get_body_composition_data", + "data_layer_module": "body_metrics", + "source_tables": [ + "caliper_log" + ], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [ + "Fortschritt zu Zielen" + ], + "pipelines": [ + "Fortschritt zu Zielen" + ], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "lbm_28d_change": { + "key": "lbm_28d_change", + "placeholder": "{{lbm_28d_change}}", + "category": "Körper", + "type": "atomic", + "description": "Magermasse Änderung 28d (kg)", + "semantic_contract": "Magermasse Änderung 28d (kg) (Zeitfenster: 28d)", + "unit": "kg", + "time_window": "28d", + "output_type": "number", + "format_hint": "12.3 kg", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_float", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "macro_consistency_score": { + "key": "macro_consistency_score", + "placeholder": "{{macro_consistency_score}}", + "category": "Ernährung", + "type": "atomic", + "description": "Makro-Konsistenz Score (0-100)", + "semantic_contract": "Makro-Konsistenz Score (0-100)", + "unit": "%", + "time_window": "unknown", + "output_type": "integer", + "format_hint": "85 %", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_int", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "monotony_score": { + "key": "monotony_score", + "placeholder": "{{monotony_score}}", + "category": "Unknown", + "type": "atomic", + "description": "No description available", + "semantic_contract": "No description available", + "unit": "%", + "time_window": "unknown", + "output_type": "integer", + "format_hint": "85 %", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_float", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "name": { + "key": "name", + "placeholder": "{{name}}", + "category": "Profil", + "type": "atomic", + "description": "Name des Nutzers", + "semantic_contract": "Name des Profils aus der Datenbank, keine Transformation", + "unit": null, + "time_window": "latest", + "output_type": "string", + "format_hint": "Text", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "get_profile_data", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [ + "Gesamtanalyse", + "Körperkomposition", + "Gesundheitsindikatoren", + "Aktivität & Training", + "Pipeline: Körper-Analyse (JSON)", + "Ernährung & Kalorien (Kopie)", + "Pipeline: Zielabgleich", + "Ernährung & Kalorien", + "Fortschritt zu Zielen" + ], + "pipelines": [ + "Gesamtanalyse", + "Körperkomposition", + "Gesundheitsindikatoren", + "Aktivität & Training", + "Pipeline: Körper-Analyse (JSON)", + "Ernährung & Kalorien (Kopie)", + "Pipeline: Zielabgleich", + "Ernährung & Kalorien", + "Fortschritt zu Zielen", + "Gesamtanalyse (advanced)" + ], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "nutrition_days": { + "key": "nutrition_days", + "placeholder": "{{nutrition_days}}", + "category": "Unknown", + "type": "atomic", + "description": "No description available", + "semantic_contract": "Anzahl der Tage mit Ernährungsdaten in den letzten 30 Tagen", + "unit": null, + "time_window": "unknown", + "output_type": "integer", + "format_hint": "85", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "get_nutrition_days", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [ + "Pipeline: Ernährungs-Analyse (JSON)" + ], + "pipelines": [ + "Pipeline: Ernährungs-Analyse (JSON)" + ], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "nutrition_score": { + "key": "nutrition_score", + "placeholder": "{{nutrition_score}}", + "category": "Scores (Phase 0b)", + "type": "atomic", + "description": "Nutrition Score (0-100)", + "semantic_contract": "Nutrition Score basierend auf Protein Adequacy, Makro-Konsistenz (0-100)", + "unit": "%", + "time_window": "unknown", + "output_type": "integer", + "format_hint": "85 %", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_int", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "plateau_detected": { + "key": "plateau_detected", + "placeholder": "{{plateau_detected}}", + "category": "Unknown", + "type": "interpreted", + "description": "No description available", + "semantic_contract": "Plateau-Erkennung: Gewichtsstagnation trotz Kaloriendefizit", + "unit": null, + "time_window": "unknown", + "output_type": "json", + "format_hint": "Text", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_json", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [ + "goals", + "focus_area_definitions", + "goal_focus_contributions" + ], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "protein_adequacy_28d": { + "key": "protein_adequacy_28d", + "placeholder": "{{protein_adequacy_28d}}", + "category": "Ernährung", + "type": "atomic", + "description": "Protein Adequacy Score (0-100)", + "semantic_contract": "Protein Adequacy Score (0-100) (Zeitfenster: 28d)", + "unit": "%", + "time_window": "28d", + "output_type": "string", + "format_hint": "Wert %", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_int", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "protein_avg": { + "key": "protein_avg", + "placeholder": "{{protein_avg}}", + "category": "Ernährung", + "type": "atomic", + "description": "Durchschn. Protein in g (30d)", + "semantic_contract": "Durchschnittliche Proteinaufnahme in g über 30 Tage aus nutrition_log", + "unit": "g", + "time_window": "30d", + "output_type": "number", + "format_hint": "12.3 g", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "get_nutrition_avg", + "module": "placeholder_resolver.py", + "function": "get_nutrition_average_data", + "data_layer_module": "nutrition_metrics", + "source_tables": [ + "nutrition_log" + ], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [ + "Pipeline: Ernährungs-Analyse (JSON)" + ], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "protein_days_in_target": { + "key": "protein_days_in_target", + "placeholder": "{{protein_days_in_target}}", + "category": "Unknown", + "type": "atomic", + "description": "No description available", + "semantic_contract": "No description available", + "unit": "g", + "time_window": "unknown", + "output_type": "integer", + "format_hint": "85 g", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_str", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "protein_g_per_kg": { + "key": "protein_g_per_kg", + "placeholder": "{{protein_g_per_kg}}", + "category": "Ernährung", + "type": "atomic", + "description": "Protein g/kg Körpergewicht", + "semantic_contract": "Aktuelle Proteinaufnahme normiert auf kg Körpergewicht (protein_avg / weight)", + "unit": "g", + "time_window": "unknown", + "output_type": "string", + "format_hint": "Wert g", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_float", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "protein_ziel_high": { + "key": "protein_ziel_high", + "placeholder": "{{protein_ziel_high}}", + "category": "Unknown", + "type": "atomic", + "description": "No description available", + "semantic_contract": "Obere Grenze der Protein-Zielspanne (2.2 g/kg Körpergewicht)", + "unit": "g", + "time_window": "unknown", + "output_type": "string", + "format_hint": "Wert g", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "get_protein_ziel_high", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [ + "Ernährung & Kalorien (Kopie)", + "Ernährung & Kalorien", + "Pipeline: Ernährungs-Analyse (JSON)" + ], + "pipelines": [ + "Ernährung & Kalorien (Kopie)", + "Ernährung & Kalorien", + "Pipeline: Ernährungs-Analyse (JSON)", + "Gesamtanalyse (advanced)" + ], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "protein_ziel_low": { + "key": "protein_ziel_low", + "placeholder": "{{protein_ziel_low}}", + "category": "Unknown", + "type": "atomic", + "description": "No description available", + "semantic_contract": "Untere Grenze der Protein-Zielspanne (1.6 g/kg Körpergewicht)", + "unit": "g", + "time_window": "unknown", + "output_type": "string", + "format_hint": "Wert g", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "get_protein_ziel_low", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [ + "Ernährung & Kalorien (Kopie)", + "Ernährung & Kalorien", + "Pipeline: Ernährungs-Analyse (JSON)" + ], + "pipelines": [ + "Ernährung & Kalorien (Kopie)", + "Ernährung & Kalorien", + "Pipeline: Ernährungs-Analyse (JSON)", + "Gesamtanalyse (advanced)" + ], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "proxy_internal_load_7d": { + "key": "proxy_internal_load_7d", + "placeholder": "{{proxy_internal_load_7d}}", + "category": "Training", + "type": "atomic", + "description": "Proxy Load 7d", + "semantic_contract": "Proxy Load 7d (Zeitfenster: 7d)", + "unit": null, + "time_window": "7d", + "output_type": "string", + "format_hint": "Text", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_int", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "quality_sessions_pct": { + "key": "quality_sessions_pct", + "placeholder": "{{quality_sessions_pct}}", + "category": "Training", + "type": "atomic", + "description": "Qualitätssessions (%)", + "semantic_contract": "Qualitätssessions (%)", + "unit": "%", + "time_window": "unknown", + "output_type": "integer", + "format_hint": "85 %", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_int", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "recent_load_balance_3d": { + "key": "recent_load_balance_3d", + "placeholder": "{{recent_load_balance_3d}}", + "category": "Unknown", + "type": "atomic", + "description": "No description available", + "semantic_contract": "No description available", + "unit": "%", + "time_window": "unknown", + "output_type": "number", + "format_hint": "12.3 %", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_int", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "recomposition_quadrant": { + "key": "recomposition_quadrant", + "placeholder": "{{recomposition_quadrant}}", + "category": "Körper", + "type": "interpreted", + "description": "Rekomposition-Status", + "semantic_contract": "Klassifizierung basierend auf FM/LBM Änderungen: Optimal Recomposition (FM↓ LBM↑), Fat Loss (FM↓ LBM→), Muscle Gain (FM→ LBM↑), Weight Gain (FM↑ LBM↑)", + "unit": null, + "time_window": "unknown", + "output_type": "string", + "format_hint": "Text", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_str", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "recovery_score": { + "key": "recovery_score", + "placeholder": "{{recovery_score}}", + "category": "Scores (Phase 0b)", + "type": "atomic", + "description": "Recovery Score (0-100)", + "semantic_contract": "Recovery Score basierend auf Schlaf, HRV, Ruhepuls (0-100)", + "unit": "%", + "time_window": "unknown", + "output_type": "integer", + "format_hint": "85 %", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_int", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "rest_day_compliance": { + "key": "rest_day_compliance", + "placeholder": "{{rest_day_compliance}}", + "category": "Training", + "type": "atomic", + "description": "Ruhetags-Compliance (%)", + "semantic_contract": "Ruhetags-Compliance (%)", + "unit": "%", + "time_window": "unknown", + "output_type": "string", + "format_hint": "Wert %", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_int", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "rest_days_count": { + "key": "rest_days_count", + "placeholder": "{{rest_days_count}}", + "category": "Schlaf & Erholung", + "type": "atomic", + "description": "Anzahl Ruhetage (30d)", + "semantic_contract": "Anzahl Ruhetage (30d)", + "unit": null, + "time_window": "unknown", + "output_type": "integer", + "format_hint": "85", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "get_rest_days_count", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "rhr_vs_baseline_pct": { + "key": "rhr_vs_baseline_pct", + "placeholder": "{{rhr_vs_baseline_pct}}", + "category": "Vitalwerte", + "type": "atomic", + "description": "RHR vs. Baseline (%)", + "semantic_contract": "RHR vs. Baseline (%)", + "unit": "%", + "time_window": "unknown", + "output_type": "integer", + "format_hint": "85 %", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_float", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "sleep_avg_duration": { + "key": "sleep_avg_duration", + "placeholder": "{{sleep_avg_duration}}", + "category": "Schlaf & Erholung", + "type": "atomic", + "description": "Durchschn. Schlafdauer (7d)", + "semantic_contract": "Durchschn. Schlafdauer (7d) (Zeitfenster: 30d)", + "unit": "Stunden", + "time_window": "30d", + "output_type": "number", + "format_hint": "12.3 Stunden", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "get_sleep_avg_duration", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "sleep_avg_duration_7d": { + "key": "sleep_avg_duration_7d", + "placeholder": "{{sleep_avg_duration_7d}}", + "category": "Schlaf & Erholung", + "type": "atomic", + "description": "Schlaf 7d (Stunden)", + "semantic_contract": "Schlaf 7d (Stunden) (Zeitfenster: 7d)", + "unit": "Stunden", + "time_window": "7d", + "output_type": "number", + "format_hint": "12.3 Stunden", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_float", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "sleep_avg_quality": { + "key": "sleep_avg_quality", + "placeholder": "{{sleep_avg_quality}}", + "category": "Schlaf & Erholung", + "type": "atomic", + "description": "Durchschn. Schlafqualität (7d)", + "semantic_contract": "Durchschn. Schlafqualität (7d) (Zeitfenster: 30d)", + "unit": "%", + "time_window": "30d", + "output_type": "number", + "format_hint": "12.3 %", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "get_sleep_avg_quality", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "sleep_debt_hours": { + "key": "sleep_debt_hours", + "placeholder": "{{sleep_debt_hours}}", + "category": "Schlaf & Erholung", + "type": "atomic", + "description": "Schlafschuld (Stunden)", + "semantic_contract": "Schlafschuld (Stunden)", + "unit": "Stunden", + "time_window": "unknown", + "output_type": "string", + "format_hint": "Wert Stunden", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_float", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "sleep_quality_7d": { + "key": "sleep_quality_7d", + "placeholder": "{{sleep_quality_7d}}", + "category": "Schlaf & Erholung", + "type": "atomic", + "description": "Schlafqualität 7d (0-100)", + "semantic_contract": "Schlafqualität 7d (0-100) (Zeitfenster: 7d)", + "unit": "%", + "time_window": "7d", + "output_type": "string", + "format_hint": "Wert %", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_int", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "sleep_regularity_proxy": { + "key": "sleep_regularity_proxy", + "placeholder": "{{sleep_regularity_proxy}}", + "category": "Schlaf & Erholung", + "type": "atomic", + "description": "Schlaf-Regelmäßigkeit (Min Abweichung)", + "semantic_contract": "Schlaf-Regelmäßigkeit (Min Abweichung)", + "unit": null, + "time_window": "unknown", + "output_type": "string", + "format_hint": "Text", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_float", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "strain_score": { + "key": "strain_score", + "placeholder": "{{strain_score}}", + "category": "Unknown", + "type": "atomic", + "description": "No description available", + "semantic_contract": "No description available", + "unit": "%", + "time_window": "unknown", + "output_type": "integer", + "format_hint": "85 %", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_int", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "thigh_28d_delta": { + "key": "thigh_28d_delta", + "placeholder": "{{thigh_28d_delta}}", + "category": "Unknown", + "type": "atomic", + "description": "No description available", + "semantic_contract": "No description available (Zeitfenster: 28d)", + "unit": null, + "time_window": "28d", + "output_type": "number", + "format_hint": "12.3", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_float", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "top_3_focus_areas": { + "key": "top_3_focus_areas", + "placeholder": "{{top_3_focus_areas}}", + "category": "Unknown", + "type": "interpreted", + "description": "No description available", + "semantic_contract": "No description available [KI-interpretiert]", + "unit": null, + "time_window": "unknown", + "output_type": "string", + "format_hint": "Text", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_str", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "top_3_goals_behind_schedule": { + "key": "top_3_goals_behind_schedule", + "placeholder": "{{top_3_goals_behind_schedule}}", + "category": "Unknown", + "type": "interpreted", + "description": "No description available", + "semantic_contract": "Top 3 Ziele mit größter negativer Abweichung vom Zeitplan (Zeit-basiert)", + "unit": null, + "time_window": "unknown", + "output_type": "string", + "format_hint": "Text", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_str", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "top_3_goals_on_track": { + "key": "top_3_goals_on_track", + "placeholder": "{{top_3_goals_on_track}}", + "category": "Unknown", + "type": "interpreted", + "description": "No description available", + "semantic_contract": "Top 3 Ziele mit größter positiver Abweichung vom Zeitplan oder am besten im Plan", + "unit": null, + "time_window": "unknown", + "output_type": "string", + "format_hint": "Text", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_str", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "top_drivers": { + "key": "top_drivers", + "placeholder": "{{top_drivers}}", + "category": "Unknown", + "type": "interpreted", + "description": "No description available", + "semantic_contract": "Top Einflussfaktoren auf Ziel-Fortschritt (sortiert nach Impact)", + "unit": null, + "time_window": "unknown", + "output_type": "json", + "format_hint": "Text", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_json", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [ + "goals", + "focus_area_definitions", + "goal_focus_contributions" + ], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "top_focus_area_name": { + "key": "top_focus_area_name", + "placeholder": "{{top_focus_area_name}}", + "category": "Focus Areas", + "type": "interpreted", + "description": "Top Focus Area Name", + "semantic_contract": "Top Focus Area Name [KI-interpretiert]", + "unit": null, + "time_window": "unknown", + "output_type": "string", + "format_hint": "Text", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_str", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "top_focus_area_progress": { + "key": "top_focus_area_progress", + "placeholder": "{{top_focus_area_progress}}", + "category": "Focus Areas", + "type": "interpreted", + "description": "Top Focus Area Progress (%)", + "semantic_contract": "Top Focus Area Progress (%) [KI-interpretiert]", + "unit": null, + "time_window": "unknown", + "output_type": "string", + "format_hint": "Text", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_int", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "top_goal_name": { + "key": "top_goal_name", + "placeholder": "{{top_goal_name}}", + "category": "Unknown", + "type": "interpreted", + "description": "No description available", + "semantic_contract": "No description available [KI-interpretiert]", + "unit": null, + "time_window": "unknown", + "output_type": "string", + "format_hint": "Text", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_str", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "top_goal_progress_pct": { + "key": "top_goal_progress_pct", + "placeholder": "{{top_goal_progress_pct}}", + "category": "Unknown", + "type": "atomic", + "description": "No description available", + "semantic_contract": "No description available", + "unit": "%", + "time_window": "unknown", + "output_type": "integer", + "format_hint": "85 %", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_str", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "top_goal_status": { + "key": "top_goal_status", + "placeholder": "{{top_goal_status}}", + "category": "Unknown", + "type": "interpreted", + "description": "No description available", + "semantic_contract": "No description available [KI-interpretiert]", + "unit": null, + "time_window": "unknown", + "output_type": "string", + "format_hint": "Text", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_str", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "training_frequency_7d": { + "key": "training_frequency_7d", + "placeholder": "{{training_frequency_7d}}", + "category": "Training", + "type": "atomic", + "description": "Trainingshäufigkeit 7d", + "semantic_contract": "Trainingshäufigkeit 7d (Zeitfenster: 7d)", + "unit": null, + "time_window": "7d", + "output_type": "integer", + "format_hint": "85", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_int", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "training_minutes_week": { + "key": "training_minutes_week", + "placeholder": "{{training_minutes_week}}", + "category": "Training", + "type": "atomic", + "description": "Trainingsminuten pro Woche", + "semantic_contract": "Trainingsminuten pro Woche (Zeitfenster: 7d)", + "unit": "Minuten", + "time_window": "7d", + "output_type": "string", + "format_hint": "Wert Minuten", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_int", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "trainingstyp_verteilung": { + "key": "trainingstyp_verteilung", + "placeholder": "{{trainingstyp_verteilung}}", + "category": "Training", + "type": "raw_data", + "description": "Verteilung nach Trainingstypen", + "semantic_contract": "Verteilung der Trainingstypen über einen Zeitraum (Anzahl Sessions pro Typ)", + "unit": null, + "time_window": "unknown", + "output_type": "string", + "format_hint": "Text", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "get_trainingstyp_verteilung", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "vitals_avg_hr": { + "key": "vitals_avg_hr", + "placeholder": "{{vitals_avg_hr}}", + "category": "Vitalwerte", + "type": "atomic", + "description": "Durchschn. Ruhepuls (7d)", + "semantic_contract": "Durchschn. Ruhepuls (7d) (Zeitfenster: 30d)", + "unit": "bpm", + "time_window": "30d", + "output_type": "number", + "format_hint": "12.3 bpm", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "get_vitals_avg_hr", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "vitals_avg_hrv": { + "key": "vitals_avg_hrv", + "placeholder": "{{vitals_avg_hrv}}", + "category": "Vitalwerte", + "type": "atomic", + "description": "Durchschn. HRV (7d)", + "semantic_contract": "Durchschn. HRV (7d) (Zeitfenster: 30d)", + "unit": "bpm", + "time_window": "30d", + "output_type": "number", + "format_hint": "12.3 bpm", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "get_vitals_avg_hrv", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "vitals_vo2_max": { + "key": "vitals_vo2_max", + "placeholder": "{{vitals_vo2_max}}", + "category": "Vitalwerte", + "type": "atomic", + "description": "Aktueller VO2 Max", + "semantic_contract": "Aktueller VO2 Max", + "unit": "ml/kg/min", + "time_window": "unknown", + "output_type": "string", + "format_hint": "Wert ml/kg/min", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "get_vitals_vo2_max", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "vo2max_trend_28d": { + "key": "vo2max_trend_28d", + "placeholder": "{{vo2max_trend_28d}}", + "category": "Vitalwerte", + "type": "atomic", + "description": "VO2max Trend 28d", + "semantic_contract": "VO2max Trend 28d (Zeitfenster: 28d)", + "unit": "ml/kg/min", + "time_window": "28d", + "output_type": "number", + "format_hint": "12.3 ml/kg/min", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_float", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "waist_28d_delta": { + "key": "waist_28d_delta", + "placeholder": "{{waist_28d_delta}}", + "category": "Körper", + "type": "atomic", + "description": "Taillenumfang Änderung 28d (cm)", + "semantic_contract": "Taillenumfang Änderung 28d (cm) (Zeitfenster: 28d)", + "unit": "cm", + "time_window": "28d", + "output_type": "number", + "format_hint": "12.3 cm", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_float", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "waist_hip_ratio": { + "key": "waist_hip_ratio", + "placeholder": "{{waist_hip_ratio}}", + "category": "Körper", + "type": "atomic", + "description": "Taille/Hüfte-Verhältnis", + "semantic_contract": "Taille/Hüfte-Verhältnis", + "unit": "cm", + "time_window": "unknown", + "output_type": "number", + "format_hint": "12.3 cm", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_float", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "weight_28d_slope": { + "key": "weight_28d_slope", + "placeholder": "{{weight_28d_slope}}", + "category": "Körper", + "type": "atomic", + "description": "Gewichtstrend 28d (kg/Tag)", + "semantic_contract": "Gewichtstrend 28d (kg/Tag) (Zeitfenster: 28d)", + "unit": "kg", + "time_window": "28d", + "output_type": "number", + "format_hint": "12.3 kg", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_float", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "weight_7d_median": { + "key": "weight_7d_median", + "placeholder": "{{weight_7d_median}}", + "category": "Körper", + "type": "atomic", + "description": "Gewicht 7d Median (kg)", + "semantic_contract": "Gewicht 7d Median (kg) (Zeitfenster: 7d)", + "unit": "kg", + "time_window": "7d", + "output_type": "number", + "format_hint": "12.3 kg", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_float", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "weight_90d_slope": { + "key": "weight_90d_slope", + "placeholder": "{{weight_90d_slope}}", + "category": "Unknown", + "type": "atomic", + "description": "No description available", + "semantic_contract": "No description available (Zeitfenster: 90d)", + "unit": "kg", + "time_window": "90d", + "output_type": "number", + "format_hint": "12.3 kg", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "_safe_float", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "weight_aktuell": { + "key": "weight_aktuell", + "placeholder": "{{weight_aktuell}}", + "category": "Körper", + "type": "atomic", + "description": "Aktuelles Gewicht in kg", + "semantic_contract": "Letzter verfügbarer Gewichtseintrag aus weight_log, keine Mittelung oder Glättung", + "unit": "kg", + "time_window": "latest", + "output_type": "number", + "format_hint": "12.3 kg", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": { + "supported": true, + "calculation": "Confidence = 'high' if data exists, else 'insufficient'", + "thresholds": { + "min_data_points": 1 + }, + "notes": null + }, + "source": { + "resolver": "get_latest_weight", + "module": "placeholder_resolver.py", + "function": "get_latest_weight_data", + "data_layer_module": "body_metrics", + "source_tables": [ + "weight_log" + ], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [ + "Gesundheitsindikatoren", + "Ernährung & Kalorien (Kopie)", + "Ernährung & Kalorien", + "Fortschritt zu Zielen", + "Pipeline: Ernährungs-Analyse (JSON)" + ], + "pipelines": [ + "Gesundheitsindikatoren", + "Ernährung & Kalorien (Kopie)", + "Ernährung & Kalorien", + "Fortschritt zu Zielen", + "Pipeline: Ernährungs-Analyse (JSON)" + ], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "weight_trend": { + "key": "weight_trend", + "placeholder": "{{weight_trend}}", + "category": "Körper", + "type": "atomic", + "description": "Gewichtstrend (7d/30d)", + "semantic_contract": "Gewichtstrend-Beschreibung über 28 Tage: stabil, steigend (+X kg), sinkend (-X kg)", + "unit": "kg", + "time_window": "28d", + "output_type": "number", + "format_hint": "12.3 kg", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "get_weight_trend", + "module": "placeholder_resolver.py", + "function": "get_weight_trend_data", + "data_layer_module": "body_metrics", + "source_tables": [ + "weight_log" + ], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [ + "Gesamtanalyse", + "Körperkomposition", + "Aktivität & Training", + "Pipeline: Körper-Analyse (JSON)", + "Fortschritt zu Zielen" + ], + "pipelines": [ + "Gesamtanalyse", + "Körperkomposition", + "Aktivität & Training", + "Pipeline: Körper-Analyse (JSON)", + "Fortschritt zu Zielen" + ], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [ + "time_window_inconsistent: Description says 7d/30d, implementation uses 28d" + ], + "notes": [ + "Consider splitting into weight_trend_7d and weight_trend_28d" + ], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "zeitraum_30d": { + "key": "zeitraum_30d", + "placeholder": "{{zeitraum_30d}}", + "category": "Zeitraum", + "type": "atomic", + "description": "30-Tage-Zeitraum", + "semantic_contract": "Zeitraum der letzten 30 Tage als Text", + "unit": null, + "time_window": "30d", + "output_type": "date", + "format_hint": "letzte 30 Tage (2026-02-27 bis 2026-03-29)", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "zeitraum_7d": { + "key": "zeitraum_7d", + "placeholder": "{{zeitraum_7d}}", + "category": "Zeitraum", + "type": "atomic", + "description": "7-Tage-Zeitraum", + "semantic_contract": "Zeitraum der letzten 7 Tage als Text", + "unit": null, + "time_window": "7d", + "output_type": "date", + "format_hint": "letzte 7 Tage (2026-03-22 bis 2026-03-29)", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + }, + "zeitraum_90d": { + "key": "zeitraum_90d", + "placeholder": "{{zeitraum_90d}}", + "category": "Unknown", + "type": "atomic", + "description": "No description available", + "semantic_contract": "Zeitraum der letzten 90 Tage als Text", + "unit": null, + "time_window": "90d", + "output_type": "date", + "format_hint": "letzte 90 Tage (2025-12-29 bis 2026-03-29)", + "example_output": null, + "value_display": null, + "value_raw": null, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": [ + "no_data", + "insufficient_data", + "resolver_error" + ] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": null, + "source": { + "resolver": "", + "module": "placeholder_resolver.py", + "function": null, + "data_layer_module": null, + "source_tables": [], + "source_kind": "computed", + "code_reference": null + }, + "dependencies": [ + "profile_id" + ], + "used_by": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [], + "schema_status": "draft", + "provenance_confidence": "medium", + "contract_source": "inferred", + "legacy_contract_mismatch": false, + "metadata_completeness_score": 0, + "orphaned_placeholder": false, + "unresolved_fields": [] + } + } +} \ No newline at end of file diff --git a/.claude/docs/audit/platzhalter/PLACEHOLDER_CATALOG_EXTENDED.md b/.claude/docs/audit/platzhalter/PLACEHOLDER_CATALOG_EXTENDED.md new file mode 100644 index 0000000..3d30cc5 --- /dev/null +++ b/.claude/docs/audit/platzhalter/PLACEHOLDER_CATALOG_EXTENDED.md @@ -0,0 +1,147 @@ +# Placeholder Catalog (Extended) + +**Generated:** 2026-03-29 19:26:40 +**Total Placeholders:** 111 + +## Placeholders by Category + +### Ernährung (8 placeholders) + +- `{{carb_avg}}` - Durchschn. Kohlenhydrate in g (30d) +- `{{energy_balance_7d}}` - Energiebilanz 7d (kcal/Tag) +- `{{fat_avg}}` - Durchschn. Fett in g (30d) +- `{{kcal_avg}}` - Durchschn. Kalorien (30d) +- `{{macro_consistency_score}}` - Makro-Konsistenz Score (0-100) +- `{{protein_adequacy_28d}}` - Protein Adequacy Score (0-100) +- `{{protein_avg}}` - Durchschn. Protein in g (30d) +- `{{protein_g_per_kg}}` - Protein g/kg Körpergewicht + +### Focus Areas (8 placeholders) + +- `{{focus_cat_aktivität_progress}}` - Kategorie Aktivität - Progress (%) +- `{{focus_cat_aktivität_weight}}` - Kategorie Aktivität - Gewichtung (%) +- `{{focus_cat_ernährung_progress}}` - Kategorie Ernährung - Progress (%) +- `{{focus_cat_ernährung_weight}}` - Kategorie Ernährung - Gewichtung (%) +- `{{focus_cat_körper_progress}}` - Kategorie Körper - Progress (%) +- `{{focus_cat_körper_weight}}` - Kategorie Körper - Gewichtung (%) +- `{{top_focus_area_name}}` - Top Focus Area Name +- `{{top_focus_area_progress}}` - Top Focus Area Progress (%) + +### Körper (11 placeholders) + +- `{{bmi}}` - Body Mass Index +- `{{fm_28d_change}}` - Fettmasse Änderung 28d (kg) +- `{{kf_aktuell}}` - Aktueller Körperfettanteil in % +- `{{lbm_28d_change}}` - Magermasse Änderung 28d (kg) +- `{{recomposition_quadrant}}` - Rekomposition-Status +- `{{waist_28d_delta}}` - Taillenumfang Änderung 28d (cm) +- `{{waist_hip_ratio}}` - Taille/Hüfte-Verhältnis +- `{{weight_28d_slope}}` - Gewichtstrend 28d (kg/Tag) +- `{{weight_7d_median}}` - Gewicht 7d Median (kg) +- `{{weight_aktuell}}` - Aktuelles Gewicht in kg +- `{{weight_trend}}` - Gewichtstrend (7d/30d) + +### Profil (4 placeholders) + +- `{{age}}` - Alter in Jahren +- `{{geschlecht}}` - Geschlecht +- `{{height}}` - Körpergröße in cm +- `{{name}}` - Name des Nutzers + +### Schlaf & Erholung (7 placeholders) + +- `{{rest_days_count}}` - Anzahl Ruhetage (30d) +- `{{sleep_avg_duration}}` - Durchschn. Schlafdauer (7d) +- `{{sleep_avg_duration_7d}}` - Schlaf 7d (Stunden) +- `{{sleep_avg_quality}}` - Durchschn. Schlafqualität (7d) +- `{{sleep_debt_hours}}` - Schlafschuld (Stunden) +- `{{sleep_quality_7d}}` - Schlafqualität 7d (0-100) +- `{{sleep_regularity_proxy}}` - Schlaf-Regelmäßigkeit (Min Abweichung) + +### Scores (Phase 0b) (6 placeholders) + +- `{{activity_score}}` - Activity Score (0-100) +- `{{body_progress_score}}` - Body Progress Score (0-100) +- `{{data_quality_score}}` - Data Quality Score (0-100) +- `{{goal_progress_score}}` - Goal Progress Score (0-100) +- `{{nutrition_score}}` - Nutrition Score (0-100) +- `{{recovery_score}}` - Recovery Score (0-100) + +### Training (9 placeholders) + +- `{{ability_balance_endurance}}` - Ability Balance - Ausdauer (0-100) +- `{{ability_balance_strength}}` - Ability Balance - Kraft (0-100) +- `{{activity_summary}}` - Aktivitäts-Zusammenfassung (7d) +- `{{proxy_internal_load_7d}}` - Proxy Load 7d +- `{{quality_sessions_pct}}` - Qualitätssessions (%) +- `{{rest_day_compliance}}` - Ruhetags-Compliance (%) +- `{{training_frequency_7d}}` - Trainingshäufigkeit 7d +- `{{training_minutes_week}}` - Trainingsminuten pro Woche +- `{{trainingstyp_verteilung}}` - Verteilung nach Trainingstypen + +### Unknown (49 placeholders) + +- `{{ability_balance_coordination}}` - No description available +- `{{ability_balance_mental}}` - No description available +- `{{ability_balance_mobility}}` - No description available +- `{{active_goals_json}}` - No description available +- `{{active_goals_md}}` - No description available +- `{{activity_detail}}` - No description available +- `{{arm_28d_delta}}` - No description available +- `{{caliper_summary}}` - No description available +- `{{chest_28d_delta}}` - No description available +- `{{circ_summary}}` - No description available +- `{{correlation_energy_weight_lag}}` - No description available +- `{{correlation_load_hrv}}` - No description available +- `{{correlation_load_rhr}}` - No description available +- `{{correlation_protein_lbm}}` - No description available +- `{{correlation_sleep_recovery}}` - No description available +- `{{energy_deficit_surplus}}` - No description available +- `{{focus_area_weights_json}}` - No description available +- `{{focus_areas_weighted_json}}` - No description available +- `{{focus_areas_weighted_md}}` - No description available +- `{{focus_cat_lebensstil_progress}}` - No description available +- `{{focus_cat_lebensstil_weight}}` - No description available +- `{{focus_cat_mental_progress}}` - No description available +- `{{focus_cat_mental_weight}}` - No description available +- `{{focus_cat_recovery_progress}}` - No description available +- `{{focus_cat_recovery_weight}}` - No description available +- `{{focus_cat_vitalwerte_progress}}` - No description available +- `{{focus_cat_vitalwerte_weight}}` - No description available +- `{{goal_bf_pct}}` - No description available +- `{{goal_weight}}` - No description available +- `{{hip_28d_delta}}` - No description available +- `{{intake_volatility}}` - No description available +- `{{monotony_score}}` - No description available +- `{{nutrition_days}}` - No description available +- `{{plateau_detected}}` - No description available +- `{{protein_days_in_target}}` - No description available +- `{{protein_ziel_high}}` - No description available +- `{{protein_ziel_low}}` - No description available +- `{{recent_load_balance_3d}}` - No description available +- `{{strain_score}}` - No description available +- `{{thigh_28d_delta}}` - No description available +- `{{top_3_focus_areas}}` - No description available +- `{{top_3_goals_behind_schedule}}` - No description available +- `{{top_3_goals_on_track}}` - No description available +- `{{top_drivers}}` - No description available +- `{{top_goal_name}}` - No description available +- `{{top_goal_progress_pct}}` - No description available +- `{{top_goal_status}}` - No description available +- `{{weight_90d_slope}}` - No description available +- `{{zeitraum_90d}}` - No description available + +### Vitalwerte (6 placeholders) + +- `{{hrv_vs_baseline_pct}}` - HRV vs. Baseline (%) +- `{{rhr_vs_baseline_pct}}` - RHR vs. Baseline (%) +- `{{vitals_avg_hr}}` - Durchschn. Ruhepuls (7d) +- `{{vitals_avg_hrv}}` - Durchschn. HRV (7d) +- `{{vitals_vo2_max}}` - Aktueller VO2 Max +- `{{vo2max_trend_28d}}` - VO2max Trend 28d + +### Zeitraum (3 placeholders) + +- `{{datum_heute}}` - Heutiges Datum +- `{{zeitraum_30d}}` - 30-Tage-Zeitraum +- `{{zeitraum_7d}}` - 7-Tage-Zeitraum diff --git a/.claude/docs/audit/platzhalter/PLACEHOLDER_EXPORT_SPEC.md b/.claude/docs/audit/platzhalter/PLACEHOLDER_EXPORT_SPEC.md new file mode 100644 index 0000000..7d84ce4 --- /dev/null +++ b/.claude/docs/audit/platzhalter/PLACEHOLDER_EXPORT_SPEC.md @@ -0,0 +1,24 @@ +# Placeholder Export Specification + +**Version:** 1.0.0 +**Generated:** 2026-03-29 19:26:40 + +## API Endpoints + +### Extended Export + +``` +GET /api/prompts/placeholders/export-values-extended +Header: X-Auth-Token: +``` + +Returns complete metadata for all 116 placeholders. + +### ZIP Export (Admin) + +``` +GET /api/prompts/placeholders/export-catalog-zip +Header: X-Auth-Token: +``` + +Returns ZIP with all catalog files. \ No newline at end of file diff --git a/.claude/docs/audit/platzhalter/PLACEHOLDER_GAP_REPORT.md b/.claude/docs/audit/platzhalter/PLACEHOLDER_GAP_REPORT.md new file mode 100644 index 0000000..7c44b00 --- /dev/null +++ b/.claude/docs/audit/platzhalter/PLACEHOLDER_GAP_REPORT.md @@ -0,0 +1,66 @@ +# Placeholder Metadata Gap Report + +**Generated:** 2026-03-29 19:26:40 +**Total Placeholders:** 111 + +## Gaps Summary + +### Missing Data Layer Module +Count: 100 + +- {{name}} +- {{age}} +- {{height}} +- {{geschlecht}} +- {{bmi}} +- {{goal_weight}} +- {{goal_bf_pct}} +- {{nutrition_days}} +- {{protein_ziel_low}} +- {{protein_ziel_high}} +- ... and 90 more + +### Missing Semantic Contract +Count: 25 + +- {{bmi}} +- {{goal_bf_pct}} +- {{rest_days_count}} +- {{vitals_vo2_max}} +- {{data_quality_score}} +- {{top_goal_progress_pct}} +- {{waist_hip_ratio}} +- {{energy_deficit_surplus}} +- {{protein_days_in_target}} +- {{macro_consistency_score}} +- ... and 15 more + +### Missing Source Tables +Count: 90 + +- {{name}} +- {{age}} +- {{height}} +- {{geschlecht}} +- {{bmi}} +- {{goal_weight}} +- {{goal_bf_pct}} +- {{nutrition_days}} +- {{protein_ziel_low}} +- {{protein_ziel_high}} +- ... and 80 more + +### Unknown Time Window +Count: 74 + +- {{bmi}} +- {{caliper_summary}} +- {{goal_weight}} +- {{goal_bf_pct}} +- {{nutrition_days}} +- {{protein_ziel_low}} +- {{protein_ziel_high}} +- {{activity_summary}} +- {{activity_detail}} +- {{trainingstyp_verteilung}} +- ... and 64 more diff --git a/.claude/docs/audit/platzhalter/PLACEHOLDER_METADATA_REQUIREMENTS_V2_NORMATIVE.md b/.claude/docs/audit/platzhalter/PLACEHOLDER_METADATA_REQUIREMENTS_V2_NORMATIVE.md new file mode 100644 index 0000000..21635ac --- /dev/null +++ b/.claude/docs/audit/platzhalter/PLACEHOLDER_METADATA_REQUIREMENTS_V2_NORMATIVE.md @@ -0,0 +1,386 @@ +# Normative Standardspezifikation +## Placeholder-Metadaten, Exportverträge und Governance + +**Version:** 1.0.0 +**Stand:** 29.03.2026 +**Status:** Verbindlicher Standard +**Geltungsbereich:** Alle bestehenden, neuen und zukünftigen Platzhalter des Systems + +--- + +## 1. Normativer Status + +Dieses Dokument ist **keine bloße Projektbeschreibung**, sondern eine **verbindliche Standardspezifikation** für das Placeholder-System. + +Es gilt für: +- alle aktuell existierenden Platzhalter, +- alle künftig neu eingeführten Platzhalter, +- alle technisch abgeleiteten Exportformate, +- alle Prompt-, Pipeline- und Analyseverwendungen, die auf Placeholders zugreifen, +- alle künftigen Erweiterungen der Placeholder-Registry, des Data Layers und der Exportfunktionen. + +### 1.1 Verbindlichkeit +Alle neuen und geänderten Placeholder-Implementierungen **müssen** dieser Spezifikation entsprechen. Abweichungen sind nur zulässig, wenn sie: +1. explizit dokumentiert, +2. versioniert, +3. mit `known_issues` und/oder `deprecated` markiert, +4. und in einem Gap- oder Decision-Log nachvollziehbar begründet werden. + +### 1.2 Vorrang +Diese Spezifikation hat Vorrang vor: +- unvollständigen Altbeschreibungen, +- impliziten Verhaltensannahmen in Legacy-Prompts, +- nicht dokumentierten Exportkonventionen, +- informellen Teamabsprachen. + +Wenn bestehender Code oder bestehende Exporte von dieser Spezifikation abweichen, ist die Abweichung als **Legacy-Zustand** zu markieren und über Versionierung/Deprecation zu behandeln. + +--- + +## 2. Zielbild + +Das System besitzt bereits einen funktionierenden Placeholder-Export und einen Data Layer als Single Source of Truth für Charts, Scores und KI-Platzhalter. Dieser Standard definiert, wie Placeholder künftig **semantisch, technisch und governance-seitig** beschrieben, exportiert und weiterentwickelt werden. + +Jeder Placeholder muss als **stabiler, dokumentierter API-Vertrag** behandelbar sein. + +Der Standard verlangt, dass zu jedem Placeholder mindestens folgende Ebenen eindeutig bestimmbar oder explizit als unbekannt markiert sind: + +1. **Ist-Wert / Beispielwert** +2. **Technische Herkunft** +3. **Semantischer Vertrag** +4. **Fehlwert- und Ausnahmebehandlung** +5. **Qualitäts-/Confidence-Logik** +6. **Verwendungsnachweise** +7. **Versionierung / Deprecation / Nachfolger** + +--- + +## 3. Verbindliche Architekturprinzipien + +### 3.1 Placeholder = API-Vertrag +Placeholder sind keine lose Prompt-Hilfe, sondern **stabile API-Verträge**. Semantik, Einheit, Zeitfenster, Rückgabetyp und Fehlwertverhalten müssen dokumentiert und versioniert sein. + +### 3.2 Non-breaking first +Bestehende Exporte und bestehende Placeholder-Namen dürfen nicht stillschweigend breaking geändert werden. Änderungen an Semantik, Zeitfenster, Einheit oder Rückgabetyp sind: +- entweder über neue Versionen, +- oder über neue Nachfolger-Platzhalter, +- oder über explizite Deprecation-Pfade +abzubilden. + +### 3.3 JSON vor Freitext +Komplexe Metadaten, Rohdaten, strukturierte Diagnosen und technische Hinweise sind primär als JSON bereitzustellen. Freitext darf ergänzen, aber keine strukturierte Pflichtinformation ersetzen. + +### 3.4 Zeitfenster explizit +Zeitbezogene Placeholder müssen ein explizites Zeitfenster tragen – mindestens als Metadatum. Legacy-Namen ohne eindeutiges Zeitfenster dürfen im Bestand fortbestehen, müssen aber als semantisch unpräzise markiert werden und einen empfohlenen Nachfolger erhalten. + +### 3.5 Fehlwerte explizit +Fehlende Werte dürfen im strukturierten Export nicht nur als String wie `"nicht verfügbar"` modelliert werden. Verbindlich sind strukturierte Felder wie: +- `available` +- `value_raw` +- `missing_reason` +- `missing_value_policy` + +### 3.6 Typisierung ist Pflicht +Jeder Placeholder muss einem Typ zugeordnet werden: +- `atomic` +- `raw_data` +- `interpreted` +- `legacy_unknown` (nur als Übergang) + +### 3.7 Data Layer bleibt Single Source of Truth +Fachliche Berechnungen dürfen nicht im Exporter dupliziert oder neu erfunden werden. Der Exporter sammelt Metadaten und referenziert technische Herkunft; er ersetzt keine fachlichen Kernfunktionen. + +### 3.8 Kein stilles Raten +Wenn technische Herkunft, Zeitfenster, Ausnahmebehandlung oder sonstige Metadaten nicht sicher aus dem Code bestimmbar sind, muss der Zustand als `unknown`, `not_resolved` oder `partially_resolved` markiert werden. + +### 3.9 Zukunftsgeltung +Jeder neue Placeholder **muss vor produktivem Einsatz** in Registry, Export und Katalog vollständig gegen dieses Dokument validiert werden. + +--- + +## 4. Geltungsbereich und Pflichtanwendung + +### 4.1 Diese Spezifikation gilt für +- bestehende produktive Placeholder, +- neue Placeholder in aktiver Entwicklung, +- experimentelle Placeholder, sobald sie außerhalb eines reinen Dev-Modus verwendet werden, +- Placeholder in Multi-Stage-Pipelines, +- Placeholder in Reports, Dashboards, Prompt-Templates und Exporten. + +### 4.2 Diese Spezifikation gilt auch für +- neue Goal-/Score-/Correlation-/Driver-Placeholder, +- künftige Trainingstyp-/Subcategory-Placeholder, +- Placeholder für Diagnose-, Risiko- und Recovery-Logik, +- technische Export-Erweiterungen, +- Two-Stage-Artefakte. + +### 4.3 Nicht zulässig ohne Standardkonformität +Nicht zulässig ist: +- ein neuer Placeholder ohne dokumentierten Semantikvertrag, +- ein neuer Placeholder ohne Zeitfenster-Metadatum, +- ein strukturierter Placeholder ohne definierten Output-Typ, +- ein produktiver Placeholder ohne dokumentierte Fehlwertbehandlung, +- ein interpretierter Placeholder ohne Kennzeichnung als solcher. + +--- + +## 5. Scope des technischen Auftrags + +### 5.1 In Scope +Der Coding Agent soll: +1. alle existierenden Placeholder inventarisieren, +2. die Metadaten je Placeholder technisch erheben oder als unresolved markieren, +3. einen erweiterten Export erzeugen, +4. einen menschenlesbaren und maschinenlesbaren Katalog erzeugen, +5. die Katalog-/Exportstruktur so implementieren, dass sie **für alle zukünftigen Placeholder wiederverwendbar** ist, +6. Tests und Validierungen ergänzen, +7. Gap-Reports für nicht automatisch auflösbare Metadaten erzeugen. + +### 5.2 Out of Scope +Nicht gefordert ist: +- sofortige komplette fachliche Neudefinition aller Legacy-Placeholder, +- sofortige Umbenennung aller problematischen Namen, +- Umsetzung aller in anderen Dokumenten vorgeschlagenen neuen Wunsch-Placeholder, +- Neuimplementierung des gesamten Prompt-Systems. + +### 5.3 Zukunftsanforderung +Die technische Lösung muss so gebaut sein, dass **jeder zukünftige Placeholder** denselben Katalog- und Exportmechanismus automatisch oder mit minimalem Zusatzaufwand durchlaufen kann. + +--- + +## 6. Ziel-Deliverables + +### 6.1 Code / Export +1. **Erweiterter Placeholder-Export** + - neuer Endpoint, CLI, Service-Funktion oder Exportmodus + - Legacy-Export bleibt kompatibel +2. **Zentrales Metadatenmodell / Registry-Erweiterung** + - wiederverwendbar für bestehende und zukünftige Placeholder +3. **Validierungslogik für neue Placeholder** + - technische Prüfung gegen dieses Dokument + +### 6.2 Dateien / Dokumentation +4. `PLACEHOLDER_CATALOG_EXTENDED.json` +5. `PLACEHOLDER_CATALOG_EXTENDED.md` +6. `PLACEHOLDER_EXPORT_SPEC.md` +7. `PLACEHOLDER_GAP_REPORT.md` +8. optional: `PLACEHOLDER_VALIDATION_REPORT.md` + +### 6.3 Tests +9. Tests für: + - Vollständigkeit des Exports, + - Konsistenz der Metadaten, + - Legacy-Kompatibilität, + - Referenzierbarkeit der Verwendungen, + - Pflichtfeld-Validierung für neue Placeholder, + - normkonforme Typisierung und Zeitfenster-Markierung. + +--- + +## 7. Verbindliches Metadaten-Schema pro Placeholder + +Jeder Placeholder im erweiterten Export muss mindestens die folgenden Felder besitzen oder explizit als `unknown`/`null`/leer markiert sein. + +```json +{ + "key": "weight_aktuell", + "placeholder": "{{weight_aktuell}}", + "category": "Körper", + "type": "atomic", + "description": "Aktuelles Gewicht in kg", + "semantic_contract": "Letzter verfügbarer Gewichtseintrag des Profils, keine Mittelung", + "unit": "kg", + "time_window": "latest", + "output_type": "number_or_string", + "format_hint": "85.8 kg", + "example_output": "85.8 kg", + "value_display": "85.8 kg", + "value_raw": 85.8, + "available": true, + "missing_reason": null, + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": ["no_data", "insufficient_data", "resolver_error"] + }, + "exception_handling": { + "on_error": "return_null_and_reason", + "notes": "Keine Exception bis in Prompt-Ebene durchreichen" + }, + "quality_filter_policy": null, + "confidence_logic": { + "supported": false, + "notes": "Für latest-Werte nicht erforderlich" + }, + "source": { + "resolver": "get_latest_weight_placeholder", + "module": "placeholder_resolver.py", + "function": "get_latest_weight", + "data_layer_module": "body_metrics.py", + "source_tables": ["weight_log"] + }, + "dependencies": ["profile_id"], + "used_by": { + "prompts": ["gesamt", "koerper"], + "pipelines": ["pipeline", "wettkampf-analyse"], + "charts": [] + }, + "version": "1.0.0", + "deprecated": false, + "replacement": null, + "known_issues": [], + "notes": [] +} +``` + +### 7.1 Pflichtfelder +Pflichtfelder sind: +- `key` +- `placeholder` +- `category` +- `type` +- `description` +- `semantic_contract` +- `unit` +- `time_window` +- `output_type` +- `value_display` +- `available` +- `missing_value_policy` +- `source` +- `version` +- `deprecated` + +### 7.2 Erlaubte Werte für `type` +- `atomic` +- `raw_data` +- `interpreted` +- `legacy_unknown` + +### 7.3 Erlaubte Werte für `time_window` +- `latest` +- `7d` +- `14d` +- `28d` +- `30d` +- `90d` +- `custom` +- `mixed` +- `unknown` + +### 7.4 Erlaubte Werte für `output_type` +- `string` +- `number` +- `integer` +- `boolean` +- `json` +- `markdown` +- `date` +- `enum` +- `unknown` + +--- + +## 8. Exportformat + +### 8.1 Legacy-Export bleibt erhalten +Der bestehende Export bleibt unverändert nutzbar. + +### 8.2 Neuer erweiterter Export +Zusätzlich muss ein erweiterter Export existieren, z. B. mit: +- `legacy` +- `metadata.by_category` +- `metadata.flat` +- `metadata.summary` +- `metadata.gaps` +- `metadata.schema_version` + +### 8.3 Zukunftsfähigkeit +Der erweiterte Export muss so strukturiert sein, dass neue Placeholder ohne Redesign der Gesamtstruktur ergänzt werden können. + +--- + +## 9. Pflichtregeln für neue und zukünftige Placeholder + +Jeder neue Placeholder muss vor Freigabe folgende Fragen beantworten können: +1. Was ist die exakte fachliche Semantik? +2. Welches Zeitfenster gilt? +3. Welcher Typ liegt vor? +4. Was ist der Output-Typ? +5. Welche Datenquelle/Funktion berechnet ihn? +6. Wie werden fehlende Werte dargestellt? +7. Welche Ausnahmebehandlung gilt? +8. Welche bekannten Grenzen/Issues gibt es? +9. Wird ein Quality-Filter angewendet? +10. Ist der Placeholder in Prompts/Pipelines referenziert? + +Wenn eine dieser Fragen unbeantwortet bleibt, darf der Placeholder nicht als voll standardkonform gelten. + +--- + +## 10. Legacy-, Deprecation- und Abweichungsregeln + +### 10.1 Legacy-Markierung +Bestehende problematische Placeholder dürfen als Legacy bestehen bleiben, wenn sie markiert werden durch: +- `deprecated: true/false` +- `known_issues` +- `replacement` +- dokumentierten Semantikhinweis + +### 10.2 Deprecation-Pflicht +Wenn ein Placeholder semantisch unpräzise, technisch instabil oder fachlich überholt ist, muss ein Deprecation-Pfad vorgesehen werden. + +### 10.3 Abweichungsprozess +Abweichungen von diesem Standard müssen in einem Decision-/Gap-Log dokumentiert werden, mit: +- Begründung, +- Impact, +- geplanter Nachbesserung, +- Verantwortlichkeit. + +--- + +## 11. Validierung und Governance + +### 11.1 Pflichtvalidierungen +Für bestehende und neue Placeholder müssen validierbar sein: +- Pflichtfeld-Vollständigkeit, +- gültige Typisierung, +- gültiges Zeitfenster, +- dokumentierte Quelle, +- dokumentiertes Fehlwertverhalten, +- dokumentierte Legacy-/Deprecation-Information. + +### 11.2 CI-/Test-Eignung +Die technische Lösung soll so gebaut werden, dass künftige Validierungen automatisierbar sind. + +### 11.3 Two-Stage-Artefakte +Interpretierte Outputs aus Basis-Prompts müssen als `interpreted` gekennzeichnet werden und dürfen nicht als rohe Datenplaceholder ausgegeben werden. + +--- + +## 12. Konkrete Arbeitsanweisung an den Coding Agent + +Der Coding Agent soll diese Spezifikation als **verbindlichen Standard** behandeln und die Implementierung so auslegen, dass sie: +- den aktuellen Bestand vollständig erfasst, +- für künftige Placeholder wiederverwendbar ist, +- Legacy-Verhalten nicht still bricht, +- technische Lücken transparent markiert, +- und als Grundlage für zukünftige Placeholder-Governance dient. + +--- + +## 13. Akzeptanzkriterien + +Die Umsetzung gilt nur dann als erfolgreich, wenn: +1. alle existierenden Placeholder im erweiterten Katalog enthalten sind, +2. der Legacy-Export weiter funktioniert, +3. das Metadatenmodell für neue Placeholder wiederverwendbar ist, +4. Pflichtfelder validiert werden, +5. unresolved Informationen sauber markiert werden, +6. die Dokumentation ausdrücklich festhält, dass der Standard **für alle neuen und zukünftigen Placeholder** gilt. + +--- + +## 14. Schlussregel + +Ab Inkrafttreten dieses Dokuments ist ein neuer Placeholder ohne Standardkonformität nur als explizit dokumentierte Ausnahme zulässig. Der Default ist Standardpflicht, nicht informelle Flexibilität. diff --git a/.claude/docs/audit/platzhalter/README.md b/.claude/docs/audit/platzhalter/README.md new file mode 100644 index 0000000..6b3ff2a --- /dev/null +++ b/.claude/docs/audit/platzhalter/README.md @@ -0,0 +1,5 @@ +# Platzhalter-Audits (Archiv) + +Enthält Kataloge, Gap-Reports und Reconciliation-Artefakte (2026-03). **Normative Entwicklerregeln** für Platzhalter: `../../technical/PLACEHOLDER_REGISTRY_FRAMEWORK.md`. + +**Entfernte Doppelungen:** `DATA_ARCHITECTURE.md` und `mitai_jinkendo_konzept_diagramme_auswertungen_v2.md` lagen identisch auch hier – kanonisch sind jetzt nur noch `../../functional/DATA_ARCHITECTURE.md` bzw. `../../functional/mitai_jinkendo_konzept_diagramme_auswertungen_v2.md`. diff --git a/.claude/docs/audit/platzhalter/audit-report-2026-03-29/01_EXECUTIVE_SUMMARY.md b/.claude/docs/audit/platzhalter/audit-report-2026-03-29/01_EXECUTIVE_SUMMARY.md new file mode 100644 index 0000000..6ae563c --- /dev/null +++ b/.claude/docs/audit/platzhalter/audit-report-2026-03-29/01_EXECUTIVE_SUMMARY.md @@ -0,0 +1,463 @@ +# Placeholder-Audit: Executive Summary + +**Audit-Datum:** 29. März 2026 +**Umfang:** 111 Platzhalter (vollständig) +**Normative Basis:** PLACEHOLDER_METADATA_REQUIREMENTS_V2_NORMATIVE.md v1.0.0 +**Audit-Methodik:** 4 spezialisierte Agents (Code, Semantik, Zeit/Confidence, Usage) +**Audit-Dauer:** ~590 Sekunden (4 Agents parallel) + +--- + +## Compliance-Übersicht + +### Gesamtergebnis: 7% voll normkonform (8/111) + +| Compliance-Level | Anzahl | Prozent | Hauptprobleme | +|------------------|--------|---------|---------------| +| **Compliant** | 8 | 7% | Best-Practice-Beispiele (nutrition_avg, weight_aktuell) | +| **Partially Compliant** | 22 | 20% | 1-2 Gaps (meist time_window oder confidence) | +| **Non-Compliant** | 81 | 73% | 3+ kritische Gaps | + +--- + +## Kritische Zahlen + +### Systemische Gaps (nach Normative Requirements) + +| Gap-Typ | Anzahl | Prozent | Norm-Verstoß | +|---------|---------|---------|--------------| +| **time_window: unknown** | 74 | 67% | §3.4 "Zeitfenster explizit" | +| **Keine Confidence-Logik** | 103 | 93% | §5 "Qualitäts-/Confidence-Logik" | +| **Kein data_layer_module** | 100 | 90% | §7 "Source" vollständig | +| **Keine Mindestdaten definiert** | 99 | 89% | Implizit in §5 | +| **category: Unknown** | 49 | 44% | §7.1 "Pflichtfelder: category" | +| **description: "No description"** | 49 | 44% | §7.1 "Pflichtfelder: description" | +| **Keine source_tables** | 90 | 81% | §7 "Source: source_tables" | +| **metadata_completeness_score: 0** | 111 | 100% | Kein Placeholder produktionsreif | +| **schema_status: draft** | 111 | 100% | §13 Akzeptanzkriterien | + +--- + +## Wichtigste Systemische Schwächen + +### 1. Metadaten-Dokumentation fundamental unvollständig + +**Problem:** +- 100% der Platzhalter haben `metadata_completeness_score: 0` +- 44% ohne fachliche Kategorie ("Unknown") +- 44% ohne Beschreibung ("No description available") +- Kein einziger Platzhalter hat Status "production" + +**Impact:** +- Platzhalter können nicht als stabile API-Verträge genutzt werden +- Prompt-Bibliothek hat keine verlässliche Metadaten-Basis +- Neue Platzhalter haben kein Qualitätsvorbild + +**Root Cause:** +- Export-System generiert Struktur, füllt aber nicht alle Felder +- Keine systematische Dokumentationspflicht im Development-Workflow + +--- + +### 2. Zeitfenster-Chaos + +**Problem:** +- 67% ohne definiertes Zeitfenster (`time_window: unknown`) +- Code-Inkonsistenzen: + - `activity_summary`: Docs "7d", Code 14d, Metadaten "unknown" + - `weight_trend`: Docs "7d/30d", Code 28d, Metadaten "unknown" +- Namen-Metadaten-Mismatch: + - `zeitraum_7d`, `zeitraum_30d`, `zeitraum_90d` → alle `time_window: unknown` + - `sleep_avg_duration_7d` → `time_window: unknown` + +**Impact:** +- KI kann Zeitfenster nicht interpretieren +- Reports können nicht korrekt zeitlich einordnen +- Vergleiche zwischen Platzhaltern problematisch + +**Root Cause:** +- Zeitfenster wird oft im Code definiert, aber nicht in Metadaten übertragen +- Keine automatische Extraktion aus Funktionsnamen/Parametern + +--- + +### 3. Fehlende Confidence-Systeme + +**Problem:** +- Nur 8 Platzhalter (7%) haben Confidence-Logik +- 103 ohne jegliche Datenqualitäts-Signale +- Kritisch bei: + - **Trend-Platzhaltern** (slope, delta, change) → keine Warnung bei zu wenig Datenpunkten + - **Score-Platzhaltern** (nutrition_score, activity_score) → keine Reliability-Info + - **Korrelations-Platzhaltern** → keine Min-Data-Thresholds + +**Impact:** +- KI kann nicht zwischen "sicher" und "unsicher" unterscheiden +- User erhält keine Warnung bei unreliablen Werten +- Prompt-Logik muss blind vertrauen oder eigene Heuristiken entwickeln + +**Root Cause:** +- Nur nutrition_metrics, body_metrics, activity_metrics haben `calculate_confidence()` +- Andere Data-Layer-Module ohne Confidence-Pattern + +--- + +### 4. Unstrukturierte Fehlwertbehandlung + +**Problem:** +- 70 Platzhalter mit "nicht verfügbar"-String statt strukturiertem Format +- Verstoß gegen Norm §3.5 "Fehlwerte explizit" +- Legacy-Only: `missing_value_policy.legacy_display: "nicht verfügbar"` +- Keine strukturierten Felder: `available`, `value_raw`, `missing_reason` + +**Impact:** +- KI muss String-Parsing betreiben ("nicht verfügbar" vs. "0" vs. "null") +- Keine maschinenlesbare Unterscheidung zwischen Fehlertypen +- Folgeprompts können nicht unterscheiden: Daten fehlen vs. Fehler aufgetreten + +**Root Cause:** +- Historisches Legacy-Format aus v1 des Placeholder-Systems +- Kein strukturierter Refactor durchgeführt + +--- + +### 5. 67 Platzhalter noch nicht produktiv eingebunden + +**Status:** +- 67 Platzhalter (60%) haben 0 Verwendungen in Prompts/Pipelines/Charts +- **Wichtig:** Dies ist KEIN Technical Debt, sondern erwartbar bei Prompt-Bibliothek im Aufbau +- Viele in Kategorie "Unknown" (41 von 49 Unknown-Platzhaltern ungenutzt) + +**Fachliche Klassifizierung (siehe USAGE_ROLE_CLASSIFICATION.md):** +- **30 Platzhalter (45%):** Explizit in Roadmap Phase 0c/1/2 geplant + - Scores (6), Correlations (5), Ability Balance (5), Goals Details (11), Sleep Debt, Plateau Detection, Top Drivers +- **37 Platzhalter (55%):** Fachlich plausibel, noch nicht in Prompts integriert + - Body Deltas, Nutrition Details, Training Quality, Focus Category Weights/Progress, Meta/Convenience +- **0 Platzhalter:** Redundant oder deprecation-würdig + +**Interpretation:** +- Prompt-Bibliothek ist in Phase 0b/0c (Goals, Data Architecture) +- Phase 1 (Charts), Phase 2 (Correlations) werden 30+ Platzhalter aktivieren +- Kein Deprecation-Bedarf – Integration statt Deletion erforderlich + +**Next Steps:** +- Integration-Timeline für 30 geplante Platzhalter (Phase 0c/1/2) +- Prompt-Use-Cases für 10-15 plausible Platzhalter identifizieren +- Nutzungsrate wird von 40% auf 50-60% steigen (organisch) + +--- + +## Größte Risiken für Prompt-Bibliothek und Reporting + +### 1. Breaking-Change-Risiko (🔴 HIGH) + +**12 produktkritische Platzhalter mit 3-19 Verwendungen:** + +| Placeholder | Uses | Risk | Reason | +|-------------|------|------|--------| +| `{{name}}` | 19 | 🔴 EXTREM | In 9 Prompts + 10 Pipelines | +| `{{geschlecht}}` | 14 | 🔴 EXTREM | Gender-specific Logik in 7 Prompts | +| `{{height}}` | 12 | 🔴 EXTREM | BMI-Berechnungen, Body Composition | +| `{{weight_aktuell}}` | 10 | 🔴 HOCH | Numerisch sensitiv, Format-Breaking | +| `{{weight_trend}}` | 10 | 🔴 HOCH | **Code-Docs-Konflikt!** | +| `{{goal_bf_pct}}` | 10 | 🔴 HOCH | Body Composition Goals | +| `{{caliper_summary}}` | 8 | 🟡 MITTEL | Body Fat Summary | +| `{{circ_summary}}` | 8 | 🟡 MITTEL | Circumference Summary | +| `{{goal_weight}}` | 8 | 🟡 MITTEL | Weight Goals | +| `{{protein_ziel_low/high}}` | 7 | 🟡 MITTEL | Nutrition Guidance | +| `{{activity_detail}}` | 4 | 🟡 MITTEL | **Time-Window unklar!** | + +**Mitigation:** +- Jede Änderung an diesen 12 erfordert: + 1. Identifikation aller betroffenen Prompts/Pipelines + 2. Koordinierte Migration über alle Templates + 3. Backward-Compatibility-Periode (2-4 Wochen) + 4. Full Regression-Tests + +--- + +### 2. Code-Dokumentations-Konflikte (🔴 HIGH) + +**Bekannte Inkonsistenzen:** + +| Placeholder | Description | Code | Metadaten | Status | +|-------------|-------------|------|-----------|--------| +| `weight_trend` | "7d/30d" | 28d | unknown | 🔴 KONFLIKT | +| `activity_summary` | "7d" | 14d | unknown | 🔴 KONFLIKT | +| `activity_detail` | unklar | 14d (default) | unknown | 🟡 UNKLAR | + +**Impact:** +- Kein Single Source of Truth +- KI nutzt evtl. falsche Zeitfenster-Annahmen +- User-Verwirrung bei Zeit-Interpretation + +**Mitigation:** +- **P0 Fix:** Code als autoritativ nehmen, Docs/Metadaten aktualisieren +- Automatische Konsistenz-Checks (CI/CD) + +--- + +### 3. Prompt-Fragility (🟡 MEDIUM) + +**Problem:** +- Platzhalter ohne Zeitfenster → KI kann Perioden nicht interpretieren +- Fehlende Confidence → KI kann Datenqualität nicht bewerten +- "nicht verfügbar"-Strings → KI muss raten ob Fehler oder kein Wert + +**Beispiel-Szenarien:** +``` +Prompt: "Analysiere die Gewichtsentwicklung basierend auf {{weight_trend}}" +Problem: KI weiß nicht, ob 7d, 28d oder 90d → falsche Interpretation möglich + +Prompt: "Bewerte die Korrelation {{correlation_energy_weight_lag}}" +Problem: Keine Confidence → KI kann nicht warnen "nur 5 Datenpunkte, unreliabel" +``` + +**Mitigation:** +- Metadata-Enrichment für alle kritischen Platzhalter +- Strukturierte Missing-Value-Policies + +--- + +### 4. Fehlende Produktions-Governance (🟡 MEDIUM) + +**Indikatoren:** +- 100% Draft-Status → keine produktionsreifen Platzhalter +- 90% ohne Data-Layer-Module dokumentiert +- Keine Validierungslogik für neue Platzhalter +- Keine klaren Production-Ready-Kriterien + +**Langfristige Konsequenzen:** +- Neue Entwickler können nicht erkennen, welche Platzhalter "safe to use" sind +- Keine Guidance für Prompt-Autoren (welche Platzhalter für welchen Use-Case) +- Akkumulation weiterer inkonsistenter Platzhalter + +**KEIN Technical Debt:** +- 60% ungenutzte Platzhalter ≠ "tote Codepfade" +- Prompt-Bibliothek ist im Aufbau (Phase 0b/0c/1/2) +- Ungenutzte Platzhalter sind fachlich geplant oder plausibel + +--- + +## Dokumentierte Normkonflikte zwischen Dateien + +| Konflikt | Quelle 1 | Quelle 2 | Severity | Resolution | +|----------|----------|----------|----------|------------| +| **Zeitfenster weight_trend** | Description: "7d/30d" | Code: 28d | 🟡 MEDIUM | Code ist autoritativ → Docs aktualisieren | +| **Zeitfenster activity_summary** | Description: "7d" | Code: 14d | 🟡 MEDIUM | Code ist autoritativ → Docs aktualisieren | +| **Neue Platzhalter** | requirements_dev.md: 27 neue P1-P27 | Extended Catalog: 111 existierend | 🟢 INFO | requirements_dev ist Arbeitsdokument, kein Normativ | +| **Bestandszahlen** | Export Spec: 116 | Gap Report/Catalog: 111 | 🟢 INFO | 5 Platzhalter = Metafelder (schema_version, etc.) | + +**Rangfolgen-Resolution (aus Audit-Auftrag):** +1. **NORMATIVE.md** (höchste Instanz) - Verbindliche Standardspezifikation +2. **Code** (aktueller Ist-Zustand) - Bei Konflikt Code > Docs +3. **Extended Catalog** (dokumentierter Stand) - Generated Truth +4. **requirements_dev.md** (Arbeitsdokument) - NICHT normativ, nur Planning + +--- + +## Best-Practice-Modelle identifiziert + +### Vollständig normkonforme Platzhalter (8): + +**1. Nutrition Averages (4):** +- `protein_avg`, `kcal_avg`, `fat_avg`, `carb_avg` + +**Warum Best-Practice:** +``` +✅ time_window: 30d (explizit) +✅ aggregation: AVG() via nutrition_metrics.py +✅ min_data: 8 (low), 12 (medium), 18 (high) +✅ confidence_logic: calculate_confidence(data_points, 30, 'general') +✅ missing_value_policy: strukturiert (available=false, reason='insufficient_data') +✅ data_layer_module: nutrition_metrics.py +✅ source_tables: ['nutrition_log'] +``` + +**2. Weight Aktuell (1):** +- `weight_aktuell` + +**Warum Best-Practice:** +``` +✅ time_window: latest +✅ aggregation: Latest entry ORDER BY date DESC LIMIT 1 +✅ confidence_logic: high if exists, insufficient if no data +✅ data_layer_module: body_metrics.py +✅ source_tables: ['weight_log'] +``` + +**3. Andere (3):** +- `weight_trend`, `circ_summary`, `age` (verschiedene Best-Practice-Aspekte) + +--- + +## Audit-Methodik-Transparenz + +### 4 Spezialisierte Agents (Parallel) + +**1. Code-Evidence-Agent** (124s) +- **Aufgabe:** Vollständige Code-Analyse aller Resolver-Funktionen +- **Scope:** `placeholder_resolver.py` (Zeilen 1075-1221), `data_layer/*.py`, `routers/prompts.py` +- **Output:** Technische Herkunft, Source-Tables, Return-Types für alle 111 +- **Evidence-Level:** `code_verified` (100%) + +**2. Semantic-Contract-Agent** (218s) +- **Aufgabe:** Fachliche Bedeutung aus Dokumentation ableiten +- **Scope:** `DATA_ARCHITECTURE.md`, `mitai_jinkendo_konzept_diagramme_auswertungen_v2.md`, Extended Catalog +- **Output:** Description, Category, Semantic Contract für alle 111 +- **Evidence-Level:** `documentation_verified` (56%), `fachlich_abgeleitet` (44%) + +**3. Time-Window-Confidence-Agent** (90s) +- **Aufgabe:** Zeitfenster klassifizieren, Confidence-Patterns identifizieren +- **Scope:** Code + Catalog + Normative Spec (erlaubte time_window-Werte) +- **Output:** Zeit-Compliance, Aggregationslogik, Min-Data-Thresholds +- **Evidence-Level:** `code_verified` (75 unklar), `code_inferred` (rest) + +**4. Prompt-Usage-Agent** (157s) +- **Aufgabe:** Verwendung in Prompts/Pipelines/Charts analysieren +- **Scope:** `ai_prompts` table (via Catalog), Grep-Suche nach `{{placeholder}}` +- **Output:** Used-By, Criticality, Rename-Risk +- **Evidence-Level:** `catalog_verified` (100%) + +**Konsolidierung:** +- Cross-Agent-Validation (4 Perspektiven auf jeden Placeholder) +- Konflikt-Resolution nach Rangfolge (Norm > Code > Catalog > Docs) +- Evidence-Level-Klassifizierung (code_verified > documentation_verified > fachlich_abgeleitet > unclear) + +--- + +## Nächste Kritische Schritte + +### P0 (VOR PRODUCTION - BLOCKING) + +**1. Zeitfenster-Klassifizierung (74 Platzhalter)** +- **Aufwand:** 8-12 Stunden +- **Methode:** + 1. Namen-Analyse (`*_7d`, `*_28d`, etc.) → automatisch + 2. Code-Analyse (default days-Parameter) → semi-automatisch + 3. Fach-Entscheidung für Unklare (ability_balance, scores) → manuell +- **Deliverable:** Alle `time_window: unknown` → gültige Werte (latest/7d/14d/28d/30d/90d/mixed) + +**2. Code-Docs-Konflikte auflösen (3 Platzhalter)** +- **Aufwand:** 2 Stunden +- **Items:** + - `weight_trend`: Description + Metadaten auf "28d" ändern + - `activity_summary`: Description + Metadaten auf "14d" ändern + - `activity_detail`: Default-Zeitfenster dokumentieren +- **Validation:** Automatische Konsistenz-Checks (Code ↔ Catalog) + +**3. Kategorie + Beschreibung (49 Platzhalter)** +- **Aufwand:** 4-6 Stunden +- **Methode:** Bulk-Update aus Semantic-Contract-Agent-Report +- **Deliverable:** + - Alle `category: Unknown` → fachliche Kategorien + - Alle `description: "No description available"` → aussagekräftige Beschreibungen + +--- + +### P1 (THIS SPRINT - HIGH PRIORITY) + +**4. Confidence-Logik für Trend-/Delta-Platzhalter (11)** +- **Aufwand:** 12-16 Stunden +- **Items:** + - weight_28d_slope, weight_90d_slope, weight_7d_median + - fm_28d_change, lbm_28d_change + - waist_28d_delta, hip_28d_delta, chest_28d_delta, arm_28d_delta, thigh_28d_delta + - vo2max_trend_28d +- **Pattern:** `confidence = calculate_confidence(data_points, time_window_days, 'trend')` +- **Thresholds:** high >= 70%, medium >= 50%, low >= 30% data coverage + +**5. Strukturierte Missing-Value-Policy (70 Platzhalter)** +- **Aufwand:** 8-10 Stunden +- **Refactor:** + - Legacy-String "nicht verfügbar" beibehalten (Backward-Compatibility) + - Zusätzlich strukturierte Felder: `available`, `missing_reason`, `value_raw` +- **Deliverable:** Dual-Mode-Support (Legacy + Structured) + +**6. Data-Layer-Module dokumentieren (100 Platzhalter)** +- **Aufwand:** 6-8 Stunden +- **Methode:** Code-Trace von Resolver → Data-Layer (aus Code-Evidence-Agent) +- **Deliverable:** Alle `data_layer_module: null` → korrekte Module + +--- + +### P2 (NEXT SPRINT - MEDIUM PRIORITY) + +**7. Ungenutzte Platzhalter - Integration planen (67)** +- **Aufwand:** 4-6 Stunden +- **Methode:** + - Produktmanagement-Review: Timeline für 30 geplante Platzhalter (Phase 0c/1/2) + - Technische Review: Prompt-Use-Cases für 37 plausible Platzhalter +- **Deliverable:** Integration-Roadmap, Prompt-Templates (5-10 Quick Wins) + +**8. Metadata-Completeness-Score auf >60% (111)** +- **Aufwand:** 10-12 Stunden +- **Methode:** Systematisches Füllen aller Pflichtfelder +- **Target:** Mindestens 60% der Platzhalter mit Score >60 + +**9. Schema-Status auf production (20-30 Core-Platzhalter)** +- **Aufwand:** 4-6 Stunden +- **Kriterien:** + - Metadata-Completeness >= 80% + - Used-By >= 1 + - Keine Known-Issues + - Zeitfenster + Confidence definiert +- **Deliverable:** 20-30 produktionsreife Platzhalter + +--- + +### P3 (LATER - NICE TO HAVE) + +**10. Validation-Framework für neue Platzhalter** +- **Aufwand:** 16-20 Stunden +- **Features:** + - Pre-Commit-Hook: Validierung gegen Normative Spec + - CI/CD: Automatische Konsistenz-Checks (Code ↔ Catalog) + - Template-Generator für neue Platzhalter + +**11. Migration-Guides für Prompt-Bibliothek** +- **Aufwand:** 8-12 Stunden +- **Content:** + - Best-Practice-Guide (basierend auf nutrition_avg) + - Anti-Patterns (was vermeiden) + - Upgrade-Path für Legacy-Prompts + +--- + +## Geschätzter Gesamt-Remediationsaufwand + +| Priority | Aufwand | Timeline | Dependencies | +|----------|---------|----------|--------------| +| **P0** | 14-20h | Week 1 | Keine (sofort startbar) | +| **P1** | 26-34h | Week 2-3 | Nach P0 | +| **P2** | 18-24h | Week 4-5 | Nach P1 | +| **P3** | 24-32h | Later | Nach P2 | +| **TOTAL** | **82-110h** | **4-6 Wochen** | Gestaffelt | + +**Mit Team von 2 Entwicklern:** 2-3 Wochen für P0+P1, weitere 1-2 Wochen für P2. + +--- + +## Abschluss-Statement + +Der Placeholder-Audit hat **massive systemische Gaps** identifiziert, aber auch einen **klaren Remediation-Pfad** aufgezeigt. Die größten Probleme sind: + +1. **Dokumentations-Gaps** (44% ohne Kategorie/Beschreibung) +2. **Zeitfenster-Chaos** (67% ohne definiertes Fenster) +3. **Fehlende Confidence-Systeme** (93% ohne Qualitäts-Signale) + +Die **gute Nachricht**: Alle 111 Platzhalter sind im Code implementiert, haben eine saubere Architektur, und 8 dienen bereits als Best-Practice-Modelle. Die Lücken sind primär **dokumentarisch und metadatenbezogen**, nicht funktional. + +Mit einem strukturierten P0-P3-Plan (82-110h Aufwand) kann das System in 4-6 Wochen auf **>60% Normkonformität** gebracht werden. + +**Empfohlene nächste Schritte:** +1. Review dieses Executive Summary mit Product/Tech Lead +2. P0-Priorisierung bestätigen (Zeitfenster, Code-Docs-Konflikte, Kategorien) +3. Kickoff für P0-Sprint (Ziel: 74 Zeitfenster + 49 Kategorien/Beschreibungen) + +--- + +**Audit durchgeführt von:** Claude Code (Lead Audit Agent) +**Agent-Team:** Code-Evidence, Semantic-Contract, Time-Window-Confidence, Prompt-Usage +**Normative Basis:** PLACEHOLDER_METADATA_REQUIREMENTS_V2_NORMATIVE.md v1.0.0 +**Vollständige Artefakte:** Siehe `audit-report-2026-03-29/` für Gap-Cluster, Maßnahmenplan, Prüfmatrix diff --git a/.claude/docs/audit/platzhalter/audit-report-2026-03-29/02_GAP_CLUSTER_REPORT.md b/.claude/docs/audit/platzhalter/audit-report-2026-03-29/02_GAP_CLUSTER_REPORT.md new file mode 100644 index 0000000..411c37f --- /dev/null +++ b/.claude/docs/audit/platzhalter/audit-report-2026-03-29/02_GAP_CLUSTER_REPORT.md @@ -0,0 +1,651 @@ +# Gap-Cluster-Bericht: Placeholder-Audit + +**Audit-Datum:** 29. März 2026 +**Basis:** 111 Platzhalter, 4-Agent-Analyse +**Normative Referenz:** PLACEHOLDER_METADATA_REQUIREMENTS_V2_NORMATIVE.md v1.0.0 + +--- + +## Übersicht + +Dieser Bericht gruppiert die identifizierten Gaps nach **systemischen Clustern**, um gezielte Remediation-Strategien zu ermöglichen. + +**Gesamtzahl Gaps:** 481 einzelne Gap-Instanzen über alle 111 Platzhalter + +**Gap-Verteilung:** +``` +Non-Compliant (81): 324 Gaps (durchschnittlich 4.0 pro Placeholder) +Partially Compliant (22): 88 Gaps (durchschnittlich 4.0 pro Placeholder) +Compliant (8): 0 Gaps +``` + +--- + +## GAP-CLUSTER + +### CLUSTER 1: Missing Semantic Contract + +**Anzahl betroffene Platzhalter:** 49 (44%) +**Severity:** 🔴 CRITICAL +**Norm-Verstoß:** §7.1 Pflichtfelder (description, semantic_contract) + +**Sub-Kategorien:** + +#### 1.1 No Description Available (49) +Alle 49 haben `description: "No description available"` + +**Betroffene Kategorien:** +- **Unknown (49):** Alle 49 Non-Compliant sind in "Unknown"-Kategorie + - Ability Balance (5): coordination, endurance, mental, mobility, strength + - Correlations (5): energy_weight_lag, load_hrv, load_rhr, protein_lbm, sleep_recovery + - Body Deltas (5): arm_28d_delta, chest_28d_delta, hip_28d_delta, thigh_28d_delta, waist_28d_delta (doppelt) + - Goals (16): active_goals_json/md, focus_areas, top_goal_*, top_3_goals_*, goal_weight, goal_bf_pct + - Nutrition (6): energy_deficit_surplus, intake_volatility, nutrition_days, protein_days_in_target, protein_ziel_* + - Training (3): activity_detail, monotony_score, strain_score + - Summaries (3): caliper_summary, circ_summary, weight_90d_slope + - Meta (2): zeitraum_90d, recent_load_balance_3d + - Plateau (1): plateau_detected + - Top Drivers (1): top_drivers + +**Remediation:** +- **Bulk-Update möglich:** Semantic-Contract-Agent-Report hat Target-Descriptions für alle 49 +- **Aufwand:** 4-6 Stunden (Copy-Paste aus Agent-Report) +- **Priority:** P0 (blocking für Production) + +--- + +#### 1.2 Weak Semantic Contract (25) +Haben Beschreibung, aber schwachen/unvollständigen Semantic Contract + +**Beispiele:** +- `bmi`: Description OK, aber Semantic Contract = Description (sollte Formel enthalten) +- `rest_days_count`: Description OK, aber Aggregationslogik fehlt +- `vitals_vo2_max`: Description OK, aber Quelle/Berechnungsmethode fehlt + +**Remediation:** +- **Individuelle Überarbeitung:** Semantic Contract mit Formeln/Quellen/Aggregationen anreichern +- **Aufwand:** 3-4 Stunden +- **Priority:** P1 + +--- + +### CLUSTER 2: Missing Data Layer Module + +**Anzahl betroffene Platzhalter:** 100 (90%) +**Severity:** 🔴 CRITICAL +**Norm-Verstoß:** §7 Source vollständig (data_layer_module erforderlich) + +**Analyse:** +- Nur 11 Platzhalter haben `data_layer_module` gesetzt +- 100 haben `data_layer_module: null` + +**Root Cause:** +- Export-System extrahiert `data_layer_module` nur, wenn explizit in Code-Kommentaren +- Resolver nutzen oft Helper-Funktionen (`_safe_int`, `_safe_float`) statt direkter Data-Layer-Calls + - `_safe_int` ruft intern Data-Layer auf, aber Mapping ist nicht dokumentiert + +**Code-Evidence-Agent Findings:** +- **81 nutzen Helper** (`_safe_int`, `_safe_float`, `_safe_str`, `_safe_json`) + - Diese HABEN Data-Layer-Module, sind aber nicht dokumentiert + - Beispiel: `_safe_int('nutrition_score', pid)` → ruft `data_layer.nutrition_metrics.calculate_nutrition_score()` +- **19 haben direkte Data-Layer-Calls** (z.B. nutrition_avg, weight_aktuell) +- **0 echte Fälle ohne Data-Layer** (alle 111 haben technische Herkunft) + +**Remediation:** +- **Code-Trace von Resolvern:** Für jede `_safe_*`-Funktion die gerufene Data-Layer-Funktion identifizieren +- **Bulk-Update:** Mapping aus Code-Evidence-Agent-Report übernehmen +- **Aufwand:** 6-8 Stunden +- **Priority:** P1 + +--- + +### CLUSTER 3: Missing Source Tables + +**Anzahl betroffene Platzhalter:** 90 (81%) +**Severity:** 🔴 CRITICAL +**Norm-Verstoß:** §7 Source vollständig (source_tables erforderlich) + +**Analyse:** +- Nur 21 Platzhalter haben `source_tables` gesetzt +- 90 haben `source_tables: []` + +**Root Cause:** +- Identisch zu CLUSTER 2: Export-System extrahiert nicht aus Code +- Source-Tables sind in Data-Layer-SQL-Queries definiert, aber nicht in Resolver-Metadaten + +**Code-Evidence-Agent Findings:** +- **Alle 111** haben identifizierbare Source-Tables im Code +- **9 Core-Tables:** + 1. `profiles` (4 Platzhalter) + 2. `weight_log` (11) + 3. `nutrition_log` (12) + 4. `activity_log` (14) + 5. `sleep_log` (6) + 6. `vitals_baseline` (8) + 7. `circumference_log` (5) + 8. `caliper_log` (5) + 9. `goals`, `focus_areas`, `rest_days` (11) + +**Remediation:** +- **Automatische Extraktion:** SQL-Query-Parsing in Data-Layer-Modulen +- **Bulk-Update:** Mapping aus Code-Evidence-Agent-Report übernehmen +- **Aufwand:** 6-8 Stunden +- **Priority:** P1 + +--- + +### CLUSTER 4: Unknown Time Window + +**Anzahl betroffene Platzhalter:** 74 (67%) +**Severity:** 🔴 CRITICAL +**Norm-Verstoß:** §3.4 "Zeitfenster explizit" + +**Analyse:** +- 74 haben `time_window: unknown` +- 37 haben definiertes Zeitfenster (latest, 7d, 28d, 30d, 90d, mixed) + +**Sub-Kategorien:** + +#### 4.1 Name enthält Zeitfenster, aber Metadaten unknown (15) +**KRITISCHER MISMATCH:** + +| Placeholder | Name-Hint | Code | Metadaten | Fix | +|-------------|-----------|------|-----------|-----| +| `zeitraum_7d` | 7d | Hardcoded "letzte 7 Tage" | unknown | → 7d | +| `zeitraum_30d` | 30d | Hardcoded "letzte 30 Tage" | unknown | → 30d | +| `zeitraum_90d` | 90d | Hardcoded "letzte 90 Tage" | unknown | → 90d | +| `sleep_avg_duration_7d` | 7d | 7 days | unknown | → 7d | +| `sleep_quality_7d` | 7d | 7 days | unknown | → 7d | +| `training_frequency_7d` | 7d | 7 days (inferred) | unknown | → 7d | +| `proxy_internal_load_7d` | 7d | 7 days | unknown | → 7d | +| `energy_balance_7d` | 7d | 7 days | unknown | → 7d | +| `protein_adequacy_28d` | 28d | 28 days | unknown | → 28d | +| `weight_28d_slope` | 28d | 28 days | 28d | ✓ OK | +| `weight_90d_slope` | 90d | 90 days | 90d | ✓ OK | +| `fm_28d_change` | 28d | 28 days | 28d | ✓ OK | +| `lbm_28d_change` | 28d | 28 days | 28d | ✓ OK | +| `vo2max_trend_28d` | 28d | 28 days | 28d | ✓ OK | +| ... | | | | | + +**Remediation:** +- **Automatische Regelextraktion:** Name-Pattern (`*_7d`, `*_28d`, etc.) → time_window +- **Aufwand:** 1 Stunde (automatisch) +- **Priority:** P0 + +--- + +#### 4.2 Code enthält Default-Zeitfenster (20) +Code nutzt `days=X` Parameter, aber Metadaten unknown + +**Beispiele:** +- `activity_summary`: Code `days=14` (default), Metadaten unknown +- `sleep_avg_duration`: Code `days=7`, Metadaten unknown +- `rest_days_count`: Code `days=30`, Metadaten unknown + +**Remediation:** +- **Code-Parameter-Extraktion:** Default-Werte aus Funktionssignaturen lesen +- **Aufwand:** 2-3 Stunden (semi-automatisch) +- **Priority:** P0 + +--- + +#### 4.3 Fach-Entscheidung erforderlich (39) +Zeitfenster nicht aus Name/Code ableitbar, fachliche Klassifizierung nötig + +**Kategorien:** + +**A. Scores (6):** +- `activity_score`, `nutrition_score`, `recovery_score`, `body_progress_score`, `goal_progress_score`, `data_quality_score` +- **Vorschlag:** `custom` oder `mixed` (kombinieren verschiedene Zeitfenster) + +**B. Ability Balance (5):** +- `ability_balance_coordination`, `ability_balance_endurance`, `ability_balance_mental`, `ability_balance_mobility`, `ability_balance_strength` +- **Vorschlag:** `28d` (Rolling-Window-Balance über 28 Tage) + +**C. Correlations (5):** +- `correlation_energy_weight_lag`, `correlation_load_hrv`, `correlation_load_rhr`, `correlation_protein_lbm`, `correlation_sleep_recovery` +- **Vorschlag:** `28d` (Min-Daten für reliable Korrelationen) + +**D. Goals & Focus (16):** +- `active_goals_json`, `focus_areas_weighted_json`, `top_goal_*`, `top_3_goals_*`, `focus_cat_*` +- **Vorschlag:** `latest` (Snapshot) oder `custom` (je nach Progress-Berechnung) + +**E. Snapshots (7):** +- `bmi`, `goal_weight`, `goal_bf_pct`, `waist_hip_ratio`, `recomposition_quadrant`, `plateau_detected`, `top_drivers` +- **Vorschlag:** `latest` (Momentaufnahme) oder `custom` (bei berechneten Werten) + +**Remediation:** +- **Fach-Review mit Product:** Kategorisierungs-Entscheidungen abstimmen +- **Aufwand:** 4-6 Stunden (Meeting + Dokumentation) +- **Priority:** P0 + +--- + +### CLUSTER 5: Missing Confidence Logic + +**Anzahl betroffene Platzhalter:** 103 (93%) +**Severity:** 🟡 HIGH +**Norm-Verstoß:** §5 "Qualitäts-/Confidence-Logik" + +**Analyse:** +- Nur 8 Platzhalter haben `confidence_logic` implementiert +- 103 haben `confidence_logic: null` + +**Implementierte Confidence (8):** +1. `protein_avg`, `kcal_avg`, `fat_avg`, `carb_avg` (nutrition_metrics) +2. `weight_aktuell`, `weight_trend` (body_metrics) +3. `caliper_summary`, `circ_summary` (body_metrics) + +**Pattern:** +```python +confidence = calculate_confidence(data_points, time_window_days, metric_type) + +Thresholds: +- high: >= 60% data coverage (e.g., >= 18/30 days) +- medium: >= 40% data coverage (e.g., >= 12/30 days) +- low: >= 26% data coverage (e.g., >= 8/30 days) +- insufficient: < 26% data coverage +``` + +**Fehlende Confidence-Logik besonders kritisch für:** + +#### 5.1 Trend-Platzhalter (11) +**Warum kritisch:** Slopes/Trends mit nur 2-3 Datenpunkten sind unreliabel + +| Placeholder | Time-Window | Min-Data empfohlen | Confidence-Logik | +|-------------|-------------|-------|------------------| +| `weight_28d_slope` | 28d | 18+ | ❌ Fehlt | +| `weight_90d_slope` | 90d | 60+ | ❌ Fehlt | +| `weight_7d_median` | 7d | 5+ | ❌ Fehlt | +| `fm_28d_change` | 28d | 2 (min), 18+ (ideal) | ❌ Fehlt | +| `lbm_28d_change` | 28d | 2 (min), 18+ (ideal) | ❌ Fehlt | +| `waist_28d_delta` | 28d | 2 (min) | ❌ Fehlt | +| `hip_28d_delta` | 28d | 2 (min) | ❌ Fehlt | +| `chest_28d_delta` | 28d | 2 (min) | ❌ Fehlt | +| `arm_28d_delta` | 28d | 2 (min) | ❌ Fehlt | +| `thigh_28d_delta` | 28d | 2 (min) | ❌ Fehlt | +| `vo2max_trend_28d` | 28d | 4+ | ❌ Fehlt | + +**Remediation:** +- **Pattern-Anwendung:** `calculate_confidence()` in alle Delta/Slope-Funktionen einbauen +- **Aufwand:** 12-16 Stunden +- **Priority:** P1 + +--- + +#### 5.2 Score-Platzhalter (6) +**Warum kritisch:** Composite Scores sollten Confidence der Subkomponenten reflektieren + +| Placeholder | Sub-Components | Confidence-Logik | +|-------------|----------------|------------------| +| `activity_score` | training_volume, frequency, quality | ❌ Fehlt | +| `nutrition_score` | energy_balance, protein_adequacy, macro_consistency | ❌ Fehlt | +| `recovery_score` | sleep_duration, sleep_quality, hrv, rhr | ❌ Fehlt | +| `body_progress_score` | weight_trend, bf_change, lbm_change | ❌ Fehlt | +| `goal_progress_score` | weighted goals progress | ❌ Fehlt | +| `data_quality_score` | data availability per domain | ❌ Fehlt | + +**Remediation:** +- **Composite-Confidence:** Min(subcomponent_confidences) +- **Aufwand:** 8-10 Stunden +- **Priority:** P1 + +--- + +#### 5.3 Korrelations-Platzhalter (5) +**Warum kritisch:** Korrelationen mit <14 Paaren sind statistisch unreliabel + +| Placeholder | Min-Pairs empfohlen | Confidence-Logik | +|-------------|---------------------|------------------| +| `correlation_energy_weight_lag` | 21+ | ❌ Fehlt | +| `correlation_load_hrv` | 21+ | ❌ Fehlt | +| `correlation_load_rhr` | 21+ | ❌ Fehlt | +| `correlation_protein_lbm` | 28+ (mit Training moderiert) | ❌ Fehlt | +| `correlation_sleep_recovery` | 21+ | ❌ Fehlt | + +**Remediation:** +- **Correlation-Confidence:** Based on n_pairs (high >= 28, medium >= 21, low >= 14) +- **Aufwand:** 4-6 Stunden +- **Priority:** P1 + +--- + +### CLUSTER 6: Unstrukturierte Missing-Value-Policy + +**Anzahl betroffene Platzhalter:** 110 (99%) +**Severity:** 🟡 MEDIUM +**Norm-Verstoß:** §3.5 "Fehlwerte explizit" + +**Analyse:** +- 110 haben nur `missing_value_policy.legacy_display: "nicht verfügbar"` +- Nur 1 hat strukturierte Policy (weight_aktuell mit available=false, reason='no_data') + +**Problem:** +- Legacy-String ist nicht maschinenlesbar +- Keine Unterscheidung zwischen Fehlertypen: + - `no_data`: Keine Daten in Datenbank + - `insufficient_data`: Zu wenig Daten für Berechnung + - `resolver_error`: Technischer Fehler + - `calculation_error`: Mathematischer Fehler (z.B. Division by zero) + +**Norm-konformes Format (§3.5):** +```json +{ + "available": false, + "value_raw": null, + "missing_reason": "insufficient_data", + "missing_value_policy": { + "legacy_display": "nicht verfügbar", + "structured_null": true, + "reason_codes": ["no_data", "insufficient_data", "resolver_error"] + } +} +``` + +**Remediation:** +- **Dual-Mode-Ansatz:** + - Legacy-String beibehalten (Backward-Compatibility) + - Zusätzlich strukturierte Felder (`available`, `missing_reason`, `value_raw`) +- **Aufwand:** 8-10 Stunden (Pattern-Anwendung auf alle Resolver) +- **Priority:** P1 + +--- + +### CLUSTER 7: Metadata Completeness Score 0 + +**Anzahl betroffene Platzhalter:** 111 (100%) +**Severity:** 🟡 MEDIUM +**Norm-Verstoß:** Kein direkter Verstoß, aber Indikator für fehlende Pflichtfelder + +**Analyse:** +- Alle 111 haben `metadata_completeness_score: 0` +- Score-Berechnung (vermutlich): + ``` + score = (filled_fields / total_required_fields) * 100 + ``` +- Da Score 0 → vermutlich wurden Pflichtfelder nicht als "gefüllt" erkannt + +**Pflichtfelder (aus Norm §7.1):** +1. `key` ✅ (alle gefüllt) +2. `placeholder` ✅ (alle gefüllt) +3. `category` ❌ (49 Unknown) +4. `type` ✅ (alle gefüllt) +5. `description` ❌ (49 "No description") +6. `semantic_contract` ❌ (49 "No description") +7. `unit` ✅ (alle gefüllt) +8. `time_window` ❌ (74 unknown) +9. `output_type` ✅ (alle gefüllt) +10. `value_display` ❌ (111 null) +11. `available` ✅ (alle true) +12. `missing_value_policy` ✅ (alle gefüllt, aber schwach) +13. `source` ❌ (100 data_layer_module null, 90 source_tables []) +14. `version` ✅ (alle 1.0.0) +15. `deprecated` ✅ (alle false) + +**Score 0 Root Cause:** +- Vermutlich zählen `null`/`unknown`/`[]`/`"No description"` als "nicht gefüllt" +- → 7 von 15 Pflichtfeldern nicht korrekt gefüllt = 0% Score + +**Remediation:** +- **Nach P0+P1 Fixes:** Score sollte automatisch auf 40-60% steigen +- **Zusätzlich:** value_display für alle Platzhalter füllen (aus Beispieldaten) +- **Aufwand:** Inkludiert in anderen Clustern +- **Priority:** P2 (Indikator-Fix, nicht eigenständig) + +--- + +### CLUSTER 8: Schema Status "draft" + +**Anzahl betroffene Platzhalter:** 111 (100%) +**Severity:** 🟡 MEDIUM +**Norm-Verstoß:** §13 Akzeptanzkriterien (production-ready Platzhalter fehlen) + +**Analyse:** +- Alle 111 haben `schema_status: draft` +- Keine Platzhalter im Status `production`, `beta`, oder `stable` + +**Erlaubte Werte (vermutlich):** +- `draft`: Work in Progress +- `beta`: In Testing +- `stable`: Production-Ready, aber noch nicht finalisiert +- `production`: Voll produktionsreif, Breaking Changes verboten + +**Kriterien für `production` (vorgeschlagen):** +1. `metadata_completeness_score >= 80` +2. `used_by.prompts.length >= 1 OR used_by.pipelines.length >= 1` +3. `time_window != 'unknown'` +4. `category != 'Unknown'` +5. `description != 'No description available'` +6. `confidence_logic != null OR type == 'atomic' with time_window == 'latest'` +7. `known_issues.length == 0` + +**Kandidaten für `production` nach P0+P1 Fixes:** +- Nutrition Averages (4): protein_avg, kcal_avg, fat_avg, carb_avg +- Body Metrics (2): weight_aktuell, weight_trend +- Profil (4): name, age, height, geschlecht +- Summaries (2): caliper_summary, circ_summary +- **Total: 12-15 Kandidaten** + +**Remediation:** +- **Automatische Bewertung:** Script zur Berechnung von Production-Eligibility +- **Manuelle Review:** Product/Tech Lead bestätigt Production-Status +- **Aufwand:** 4-6 Stunden +- **Priority:** P2 + +--- + +### CLUSTER 9: Ungenutzte Platzhalter (Orphans) + +**Anzahl betroffene Platzhalter:** 67 (60%) +**Severity:** 🟢 LOW (aber Technical Debt) +**Norm-Verstoß:** Kein direkter Verstoß, aber Governance-Issue + +**Analyse:** +- 67 Platzhalter haben `used_by.prompts: []`, `used_by.pipelines: []`, `used_by.charts: []` +- 0 Verwendungen = potentielle Deprecation-Kandidaten + +**Kategorisierung:** + +#### 9.1 Geplante Features (Phase 1b/1c) - behalten (30) +- Correlations (5): energy_weight_lag, load_hrv, load_rhr, protein_lbm, sleep_recovery +- Goals Details (11): active_goals_json/md, focus_areas, top_goals +- Ability Balance (5): coordination, endurance, mental, mobility, strength +- Scores (6): activity_score, nutrition_score, recovery_score, body_progress_score, goal_progress_score, data_quality_score +- Plateau (1): plateau_detected +- Top Drivers (1): top_drivers +- Sleep Debt (1): sleep_debt_hours + +#### 9.2 Redundant/Obsolet - Deprecation prüfen (15) +- Body Deltas (4): arm_28d_delta, chest_28d_delta, hip_28d_delta, thigh_28d_delta (redundant zu waist_28d_delta Pattern) +- Training Load (3): monotony_score, strain_score, recent_load_balance_3d (noch nicht genutzt) +- Meta (3): zeitraum_90d, datum_heute (System-Platzhalter, wenig Wert) +- Vitals Trends (2): hrv_vs_baseline_pct, rhr_vs_baseline_pct (noch nicht genutzt) +- Sleep Regularity (1): sleep_regularity_proxy (experimentell) +- BMI (1): bmi (berechenbar, redundant) +- Waist-Hip-Ratio (1): waist_hip_ratio (berechenbar, redundant) + +#### 9.3 Unklar - Product-Review (22) +- Rest: Weitere Ability Balance, Focus Cat Weights, etc. + +**Remediation:** +- **Product-Review:** Welche für Phase 1b/1c/2 geplant? Welche streichen? +- **Deprecation-Strategie:** `deprecated: true`, `replacement: "new_placeholder"`, Sunset-Datum +- **Aufwand:** 4-6 Stunden (Meeting + Dokumentation) +- **Priority:** P2 + +--- + +### CLUSTER 10: Export-Inkonsistenzen + +**Anzahl betroffene Platzhalter:** 5 (Meta-Gap) +**Severity:** 🟢 INFO +**Norm-Verstoß:** Kein direkter Verstoß, aber Governance-Issue + +**Problem:** +- Export Spec sagt: "116 Platzhalter" +- Extended Catalog + Gap Report: "111 Platzhalter" +- Differenz: 5 Platzhalter + +**Analyse:** +- Die 5 zusätzlichen sind vermutlich **Metafelder** im Export: + 1. `schema_version` + 2. `generated_at` + 3. `normative_standard` + 4. `total_placeholders` + 5. `metadata.summary` (oder ähnlich) + +**Remediation:** +- **Dokumentation:** Export-Spec klarstellen (111 User-Placeholders + 5 Metafelder) +- **Aufwand:** 30 Minuten +- **Priority:** P3 + +--- + +### CLUSTER 11: Code-Dokumentations-Konflikte + +**Anzahl betroffene Platzhalter:** 3 (kritische Konflikte) +**Severity:** 🔴 CRITICAL +**Norm-Verstoß:** §2 "Vorrang" (Code > Docs bei Konflikt) + +**Konflikte:** + +#### 11.1 weight_trend +- **Description:** "Gewichtstrend (7d/30d)" +- **Code:** `get_weight_trend_data(profile_id, days=28)` +- **Metadaten:** `time_window: unknown` +- **Used-By:** 10 Prompts/Pipelines +- **Impact:** 🔴 HIGH (Prompts gehen von 7d/30d aus, erhalten aber 28d) + +**Fix:** +``` +description: "Gewichtstrend 28d (lineare Regression)" +semantic_contract: "Lineare Regression über 28 Tage, Richtung + Delta" +time_window: "28d" +``` + +--- + +#### 11.2 activity_summary +- **Description:** "Aktivitäts-Zusammenfassung (7d)" +- **Code:** `get_activity_summary_data(profile_id, days=14)` +- **Metadaten:** `time_window: unknown` +- **Used-By:** 2 Prompts +- **Impact:** 🟡 MEDIUM (weniger Verwendungen, aber Inkonsistenz) + +**Fix:** +``` +description: "Aktivitäts-Zusammenfassung 14d" +semantic_contract: "Aggregierte Aktivitäts-Metriken über 14 Tage" +time_window: "14d" +``` + +--- + +#### 11.3 activity_detail +- **Description:** "Keine explizite Zeitangabe" +- **Code:** `get_activity_detail_data(profile_id, days=14, limit=20)` +- **Metadaten:** `time_window: unknown` +- **Used-By:** 4 Prompts +- **Impact:** 🟡 MEDIUM + +**Fix:** +``` +description: "Detaillierte Aktivitäts-Liste 14d (max 20 Einträge)" +semantic_contract: "Sortierte Liste letzter 20 Aktivitäten aus 14-Tage-Fenster" +time_window: "14d" +``` + +--- + +**Remediation:** +- **Sofortige Korrektur:** Description, semantic_contract, time_window auf Code-Wahrheit setzen +- **Aufwand:** 1 Stunde +- **Priority:** P0 + +--- + +## GAP-CLUSTER ZUSAMMENFASSUNG + +| Cluster | Platzhalter | Severity | Priority | Aufwand | Abhängigkeiten | +|---------|-------------|----------|----------|---------|----------------| +| **1. Semantic Contract** | 49 | 🔴 CRITICAL | P0 | 4-6h | Keine | +| **2. Data Layer Module** | 100 | 🔴 CRITICAL | P1 | 6-8h | Cluster 1 | +| **3. Source Tables** | 90 | 🔴 CRITICAL | P1 | 6-8h | Cluster 1 | +| **4. Time Window** | 74 | 🔴 CRITICAL | P0 | 6-10h | Keine | +| **5. Confidence Logic** | 103 | 🟡 HIGH | P1 | 24-32h | Cluster 4 | +| **6. Missing-Value-Policy** | 110 | 🟡 MEDIUM | P1 | 8-10h | Cluster 5 | +| **7. Completeness Score 0** | 111 | 🟡 MEDIUM | P2 | Inkludiert | Nach P0+P1 | +| **8. Schema Status draft** | 111 | 🟡 MEDIUM | P2 | 4-6h | Nach P0+P1 | +| **9. Ungenutzte Platzhalter** | 67 | 🟢 LOW | P2 | 4-6h | Product-Review | +| **10. Export-Inkonsistenzen** | 5 | 🟢 INFO | P3 | 0.5h | Keine | +| **11. Code-Docs-Konflikte** | 3 | 🔴 CRITICAL | P0 | 1h | Keine | + +**Gesamt-Remediationsaufwand:** 64-87 Stunden (gestaffelt über P0-P3) + +--- + +## REMEDIATION-PRIORISIERUNG + +### P0 (BLOCKING - Week 1) +1. **Cluster 11:** Code-Docs-Konflikte (1h) +2. **Cluster 1:** Semantic Contract (4-6h) +3. **Cluster 4:** Time Window (6-10h) + - **Sub:** Name-Pattern (1h, automatisch) + - **Sub:** Code-Parameter (2-3h, semi-automatisch) + - **Sub:** Fach-Entscheidung (4-6h, manuell mit Product) + +**Total P0:** 11-17 Stunden + +--- + +### P1 (HIGH - Week 2-3) +1. **Cluster 2:** Data Layer Module (6-8h) +2. **Cluster 3:** Source Tables (6-8h) +3. **Cluster 5:** Confidence Logic (24-32h) + - **Sub:** Trend-Platzhalter (12-16h) + - **Sub:** Score-Platzhalter (8-10h) + - **Sub:** Korrelations-Platzhalter (4-6h) +4. **Cluster 6:** Missing-Value-Policy (8-10h) + +**Total P1:** 44-58 Stunden + +--- + +### P2 (MEDIUM - Week 4-5) +1. **Cluster 7:** Completeness Score (inkludiert in P0+P1) +2. **Cluster 8:** Schema Status Production (4-6h) +3. **Cluster 9:** Ungenutzte Platzhalter Review (4-6h) + +**Total P2:** 8-12 Stunden + +--- + +### P3 (LOW - Later) +1. **Cluster 10:** Export-Inkonsistenzen Doku (0.5h) + +**Total P3:** 0.5 Stunden + +--- + +**GESAMT:** 63.5-87.5 Stunden über 4-6 Wochen + +--- + +## NÄCHSTE SCHRITTE + +1. **Review dieses Gap-Reports** mit Tech Lead +2. **Priorisierungs-Bestätigung:** P0-Liste final absegnen +3. **Kickoff P0-Sprint:** + - Code-Docs-Konflikte sofort fixen (1h) + - Semantic Contract Bulk-Update (4-6h) + - Zeitfenster-Klassifizierung (6-10h) +4. **Nach P0:** P1-Sprint planen (Confidence + Data Layer + Missing-Value) + +**Zie + +l nach P0+P1:** 60-70% Normkonformität erreicht + +--- + +**Audit-Report erstellt von:** Claude Code (Lead Audit Agent) +**Basis:** 4-Agent-Analyse (Code-Evidence, Semantic-Contract, Time-Window-Confidence, Prompt-Usage) diff --git a/.claude/docs/audit/platzhalter/audit-report-2026-03-29/03_MASSNAHMENPLAN.md b/.claude/docs/audit/platzhalter/audit-report-2026-03-29/03_MASSNAHMENPLAN.md new file mode 100644 index 0000000..22960dd --- /dev/null +++ b/.claude/docs/audit/platzhalter/audit-report-2026-03-29/03_MASSNAHMENPLAN.md @@ -0,0 +1,983 @@ +# Priorisierter Maßnahmenplan: Placeholder-Remediation + +**Audit-Datum:** 29. März 2026 +**Basis:** Gap-Cluster-Analyse, 111 Platzhalter +**Ziel:** Normkonformität auf 60-70% steigern (aktuell 7%) + +--- + +## ROADMAP-ÜBERSICHT + +| Phase | Fokus | Dauer | Aufwand | Ziel-Compliance | +|-------|-------|-------|---------|-----------------| +| **P0** | Blocking Issues | Week 1 | 11-17h | 25-30% | +| **P1** | High Priority Gaps | Week 2-3 | 44-58h | 50-60% | +| **P2** | Medium Priority | Week 4-5 | 8-12h | 65-70% | +| **P3** | Nice-to-Have | Later | 0.5h | 70%+ | +| **TOTAL** | | 4-6 Wochen | 63.5-87.5h | 70%+ | + +--- + +## P0: BLOCKING ISSUES (Week 1) + +**Ziel:** Kritische Inkonsistenzen und Pflichtfelder beheben +**Aufwand:** 11-17 Stunden +**Team:** 1-2 Entwickler +**Dependencies:** Keine - sofort startbar + +--- + +### P0.1: Code-Dokumentations-Konflikte (SOFORT) + +**Gap-Cluster:** 11 +**Betroffene Platzhalter:** 3 (weight_trend, activity_summary, activity_detail) +**Severity:** 🔴 CRITICAL +**Aufwand:** 1 Stunde + +**Maßnahmen:** + +1. **weight_trend Fix** + ```diff + # PLACEHOLDER_CATALOG_EXTENDED.json + { + "key": "weight_trend", + - "description": "Gewichtstrend (7d/30d)", + + "description": "Gewichtstrend 28d (lineare Regression)", + - "semantic_contract": "Gewichtstrend (7d/30d)", + + "semantic_contract": "Lineare Regression über 28 Tage, Richtung + Delta in kg", + - "time_window": "unknown", + + "time_window": "28d", + } + ``` + +2. **activity_summary Fix** + ```diff + { + "key": "activity_summary", + - "description": "Aktivitäts-Zusammenfassung (7d)", + + "description": "Aktivitäts-Zusammenfassung 14d", + - "semantic_contract": "Aktivitäts-Zusammenfassung (7d)", + + "semantic_contract": "Aggregierte Aktivitäts-Metriken über 14 Tage", + - "time_window": "unknown", + + "time_window": "14d", + } + ``` + +3. **activity_detail Fix** + ```diff + { + "key": "activity_detail", + - "description": "No description available", + + "description": "Detaillierte Aktivitäts-Liste 14d (max 20 Einträge)", + - "semantic_contract": "No description available", + + "semantic_contract": "Sortierte Liste letzter 20 Aktivitäten aus 14-Tage-Fenster, DESC nach Datum", + - "time_window": "unknown", + + "time_window": "14d", + } + ``` + +**Validation:** +- Code-Review: Zeitfenster im Code bestätigen +- Prompt-Review: Alle 10+2+4 betroffenen Prompts/Pipelines prüfen + +**Owner:** Backend Lead +**Blocker:** Keine +**Output:** 3 Fixed Platzhalter, Updated Catalog + +--- + +### P0.2: Zeitfenster Name-Pattern (AUTOMATISCH) + +**Gap-Cluster:** 4.1 +**Betroffene Platzhalter:** 15 (zeitraum_*, sleep_*_7d, training_frequency_7d, etc.) +**Severity:** 🔴 CRITICAL +**Aufwand:** 1 Stunde (automatisches Skript) + +**Maßnahmen:** + +**Skript: `fix_time_window_from_name.py`** +```python +import json +import re + +# Regex-Pattern für Zeitfenster im Namen +patterns = { + r'_7d$|_7d_': '7d', + r'_14d$|_14d_': '14d', + r'_28d$|_28d_': '28d', + r'_30d$|_30d_': '30d', + r'_90d$|_90d_': '90d', +} + +catalog = load_catalog() +fixes = [] + +for placeholder in catalog['placeholders'].values(): + key = placeholder['key'] + + # Überspringe bereits definierte + if placeholder['time_window'] != 'unknown': + continue + + # Pattern-Match + for pattern, window in patterns.items(): + if re.search(pattern, key): + placeholder['time_window'] = window + fixes.append(f"{key}: unknown → {window}") + break + +print(f"Fixed {len(fixes)} placeholders") +save_catalog(catalog) +``` + +**Erwartete Fixes:** +- `zeitraum_7d`: unknown → 7d +- `zeitraum_30d`: unknown → 30d +- `zeitraum_90d`: unknown → 90d +- `sleep_avg_duration_7d`: unknown → 7d +- `sleep_quality_7d`: unknown → 7d +- `training_frequency_7d`: unknown → 7d +- `proxy_internal_load_7d`: unknown → 7d +- `energy_balance_7d`: unknown → 7d +- `protein_adequacy_28d`: unknown → 28d +- ... (insgesamt ~15) + +**Validation:** +- Manuelle Review der Auto-Fixes +- Diff-Check vor Commit + +**Owner:** DevOps / Automation +**Blocker:** Keine +**Output:** 15 Fixed Platzhalter + +--- + +### P0.3: Zeitfenster Code-Parameter (SEMI-AUTOMATISCH) + +**Gap-Cluster:** 4.2 +**Betroffene Platzhalter:** ~20 (activity_summary, sleep_avg_duration, rest_days_count, etc.) +**Severity:** 🔴 CRITICAL +**Aufwand:** 2-3 Stunden + +**Maßnahmen:** + +1. **Code-Parameter-Extraktion** + - Suche in `placeholder_resolver.py` nach `days=X` Parametern + - Greppen in `data_layer/*.py` nach Default-Werten + + ```bash + # Beispiel-Grep + grep -n "def get_.*_data.*days=" backend/data_layer/*.py + grep -n "lambda.*days=" backend/placeholder_resolver.py + ``` + +2. **Manuelle Zuordnung** + - Für jeden Treffer: Placeholder Key identifizieren + - Default-Wert → time_window setzen + +**Erwartete Fixes:** +- `sleep_avg_duration`: Code `days=7` → time_window: 7d +- `rest_days_count`: Code `days=30` → time_window: 30d +- `nutrition_days`: Code `days=30` → time_window: 30d +- ... (~20 total) + +**Validation:** +- Code-Review der Mappings +- Stichproben-Tests + +**Owner:** Backend Developer +**Blocker:** P0.2 abgeschlossen +**Output:** 20 Fixed Platzhalter + +--- + +### P0.4: Semantic Contract Bulk-Update + +**Gap-Cluster:** 1.1 +**Betroffene Platzhalter:** 49 ("No description available") +**Severity:** 🔴 CRITICAL +**Aufwand:** 4-6 Stunden + +**Maßnahmen:** + +**Input:** Semantic-Contract-Agent-Report (bereits vorhanden) +**Prozess:** +1. Für jeden der 49 Platzhalter: + - `description` aus Agent-Report übernehmen + - `semantic_contract` aus Agent-Report übernehmen + - `category` aus Agent-Report setzen (statt "Unknown") +2. Bulk-Update via JSON-Merge-Skript +3. Manuelle Review der Top-10 kritischsten (nach Usage) + +**Beispiel-Update (ability_balance_coordination):** +```diff +{ + "key": "ability_balance_coordination", +- "category": "Unknown", ++ "category": "Training", +- "description": "No description available", ++ "description": "Koordinationsfähigkeit-Balance-Anteil (%)", +- "semantic_contract": "No description available", ++ "semantic_contract": "Gewichteter Anteil der Trainings-Last für Koordinations-Fähigkeit über 28-Tage-Fenster", +- "time_window": "unknown", ++ "time_window": "28d", +} +``` + +**Prioritäts-Reihenfolge (nach Usage):** +1. Goals & Focus (16) - 7 produktkritisch +2. Correlations (5) - experimentell, aber wichtig +3. Body Deltas (5) - Körper-Tracking +4. Nutrition (6) - Ernährungs-Metriken +5. Training (8) - Ability Balance, Load +6. Summaries (3) - circ_summary, caliper_summary +7. Meta (2) - zeitraum_90d +8. Plateau (1) - plateau_detected +9. Top Drivers (1) - top_drivers + +**Validation:** +- Review der Top-10 produktkritischsten Platzhalter manuell +- Stichprobe 10% der restlichen +- Automated Consistency Check (keine "No description" mehr) + +**Owner:** Backend Lead + Product (für fachliche Review) +**Blocker:** Keine +**Output:** 49 Fixed Platzhalter, alle mit Kategorie + Description + +--- + +### P0.5: Zeitfenster Fach-Entscheidung + +**Gap-Cluster:** 4.3 +**Betroffene Platzhalter:** ~39 (Scores, Ability Balance, Correlations, Goals, Snapshots) +**Severity:** 🔴 CRITICAL +**Aufwand:** 4-6 Stunden (Meeting + Dokumentation) + +**Maßnahmen:** + +**Phase 1: Kategorisierung (2h)** +- Product + Tech Meeting +- Entscheidung pro Kategorie: + +| Kategorie | Platzhalter | Vorschlag | Rationale | +|-----------|-------------|-----------|-----------| +| **Scores (6)** | activity_score, nutrition_score, recovery_score, body_progress_score, goal_progress_score, data_quality_score | `custom` oder `mixed` | Kombinieren verschiedene Zeitfenster | +| **Ability Balance (5)** | coordination, endurance, mental, mobility, strength | `28d` | Rolling-Window über 28 Tage | +| **Correlations (5)** | energy_weight_lag, load_hrv, load_rhr, protein_lbm, sleep_recovery | `28d` | Min-Daten für Reliability | +| **Goals & Focus (16)** | active_goals_json, focus_areas_*, top_goal_*, top_3_goals_*, focus_cat_* | `latest` (Snapshot) oder `custom` | Je nach Progress-Berechnung | +| **Snapshots (7)** | bmi, goal_weight, goal_bf_pct, waist_hip_ratio, recomposition_quadrant, plateau_detected, top_drivers | `latest` oder `custom` | Momentaufnahme vs. Berechnet | + +**Phase 2: Dokumentation (2h)** +- Entscheidungen in Decision-Log festhalten +- time_window-Werte in Catalog setzen +- Semantic Contracts anpassen (Zeit-Bezug aufnehmen) + +**Phase 3: Validation (1h)** +- Review mit Stakeholdern +- Prompt-Autoren informieren (Breaking Change bei "custom" → spezifisches Fenster) + +**Owner:** Product Manager + Tech Lead +**Blocker:** P0.4 Semantic Contract abgeschlossen +**Output:** Decision-Log, 39 Fixed Platzhalter + +--- + +### P0 SUMMARY + +**Gesamt-Aufwand:** 11-17 Stunden +**Deliverables:** +- 3 Code-Docs-Konflikte gelöst +- 74 time_window: unknown → definierte Werte +- 49 description/category: Unknown/No description → vollständig +- **Total Fixed:** 77 Platzhalter (15+20+39+3 unique) + +**Impact:** +- **Compliance:** 7% → 25-30% +- **Blocked Gaps:** time_window, description, category, code-conflicts + +**Next:** P1 kann starten (Confidence, Data Layer, Source Tables) + +--- + +## P1: HIGH PRIORITY (Week 2-3) + +**Ziel:** Technische Tiefe hinzufügen (Confidence, Data Layer, Source Tables) +**Aufwand:** 44-58 Stunden +**Team:** 2-3 Entwickler +**Dependencies:** P0 abgeschlossen + +--- + +### P1.1: Confidence-Logik für Trend-Platzhalter + +**Gap-Cluster:** 5.1 +**Betroffene Platzhalter:** 11 (weight_*_slope, *_28d_delta, vo2max_trend_28d) +**Severity:** 🟡 HIGH +**Aufwand:** 12-16 Stunden + +**Maßnahmen:** + +**Pattern:** +```python +# In data_layer/body_metrics.py + +def calculate_weight_28d_slope(profile_id: int, conn): + """Gewichtstrend über 28 Tage mit Confidence.""" + # 1. Daten holen + rows = fetch_weight_data(profile_id, days=28, conn=conn) + + # 2. Confidence berechnen + confidence = calculate_confidence( + data_points=len(rows), + time_window_days=28, + metric_type='trend' + ) + + if confidence == 'insufficient': + return { + 'slope': None, + 'confidence': 'insufficient', + 'data_points': len(rows), + 'min_required': 8 # 28% coverage + } + + # 3. Slope berechnen (nur wenn sufficient) + slope = linear_regression_slope(rows) + + return { + 'slope': slope, + 'confidence': confidence, # high/medium/low + 'data_points': len(rows), + 'r_squared': calculate_r_squared(rows, slope) + } +``` + +**Thresholds (trend-specific):** +```python +def calculate_confidence(data_points, time_window_days, metric_type): + coverage = data_points / time_window_days + + if metric_type == 'trend': + # Strengere Thresholds für Trends + if coverage >= 0.70: # >= 70% + return 'high' + elif coverage >= 0.50: # >= 50% + return 'medium' + elif coverage >= 0.30: # >= 30% + return 'low' + else: + return 'insufficient' + # ... andere Typen +``` + +**Zu implementieren für:** +1. `weight_28d_slope` (28d, min 8, ideal 20) +2. `weight_90d_slope` (90d, min 27, ideal 63) +3. `weight_7d_median` (7d, min 3, ideal 5) +4. `fm_28d_change` (28d, min 2, ideal 18) +5. `lbm_28d_change` (28d, min 2, ideal 18) +6. `waist_28d_delta` (28d, min 2, ideal 18) +7. `hip_28d_delta` (28d, min 2, ideal 18) +8. `chest_28d_delta` (28d, min 2, ideal 18) +9. `arm_28d_delta` (28d, min 2, ideal 18) +10. `thigh_28d_delta` (28d, min 2, ideal 18) +11. `vo2max_trend_28d` (28d, min 4, ideal 18) + +**Testing:** +- Unit-Tests für calculate_confidence() mit verschiedenen Coverages +- Integration-Tests mit real data (0%, 30%, 50%, 70%, 100% coverage) +- Regression-Tests (alte Werte ohne Confidence bleiben gleich) + +**Owner:** Backend Developer (Senior) +**Blocker:** P0 time_window abgeschlossen +**Output:** 11 Platzhalter mit Confidence-Logik + +--- + +### P1.2: Confidence-Logik für Score-Platzhalter + +**Gap-Cluster:** 5.2 +**Betroffene Platzhalter:** 6 (activity_score, nutrition_score, recovery_score, etc.) +**Severity:** 🟡 HIGH +**Aufwand:** 8-10 Stunden + +**Maßnahmen:** + +**Composite-Confidence-Pattern:** +```python +def calculate_nutrition_score(profile_id: int, conn): + """Nutrition Score mit Composite Confidence.""" + # 1. Sub-Scores berechnen (mit eigenen Confidences) + energy_balance = calculate_energy_balance_7d(profile_id, conn) + protein_adequacy = calculate_protein_adequacy_28d(profile_id, conn) + macro_consistency = calculate_macro_consistency_score(profile_id, conn) + + # 2. Composite Confidence = MIN(sub-confidences) + confidence_levels = ['high', 'medium', 'low', 'insufficient'] + confidences = [ + energy_balance['confidence'], + protein_adequacy['confidence'], + macro_consistency['confidence'] + ] + + # Min-Confidence + min_confidence = min(confidences, key=lambda c: confidence_levels.index(c)) + + if min_confidence == 'insufficient': + return { + 'score': None, + 'confidence': 'insufficient', + 'sub_scores': {...}, + 'note': 'Mindestens eine Komponente hat insufficient data' + } + + # 3. Score berechnen (gewichtet) + score = ( + energy_balance['score'] * 0.4 + + protein_adequacy['score'] * 0.4 + + macro_consistency['score'] * 0.2 + ) + + return { + 'score': round(score), + 'confidence': min_confidence, + 'sub_scores': { + 'energy_balance': energy_balance, + 'protein_adequacy': protein_adequacy, + 'macro_consistency': macro_consistency + } + } +``` + +**Zu implementieren für:** +1. `activity_score` (training_volume + frequency + quality) +2. `nutrition_score` (energy_balance + protein_adequacy + macro_consistency) +3. `recovery_score` (sleep_duration + sleep_quality + hrv + rhr) +4. `body_progress_score` (weight_trend + bf_change + lbm_change) +5. `goal_progress_score` (weighted goals progress) +6. `data_quality_score` (data availability per domain) + +**Testing:** +- Scenario-Tests: alle Sub-Scores high → Composite high +- Scenario-Tests: eine Sub-Score insufficient → Composite insufficient +- Edge-Cases: missing Sub-Components + +**Owner:** Backend Developer (Mid/Senior) +**Blocker:** P1.1 Trend-Confidence abgeschlossen (Pattern etabliert) +**Output:** 6 Platzhalter mit Composite Confidence + +--- + +### P1.3: Confidence-Logik für Korrelations-Platzhalter + +**Gap-Cluster:** 5.3 +**Betroffene Platzhalter:** 5 (correlation_*) +**Severity:** 🟡 HIGH +**Aufwand:** 4-6 Stunden + +**Maßnahmen:** + +**Correlation-Confidence-Pattern:** +```python +def calculate_lag_correlation(profile_id, metric_a, metric_b, lag_days=[0,3,7,14], conn): + """Lag-Korrelation mit Pair-basiertem Confidence.""" + results = [] + + for lag in lag_days: + pairs = fetch_paired_data(profile_id, metric_a, metric_b, lag, conn) + n_pairs = len(pairs) + + # Confidence basierend auf Anzahl Paare + if n_pairs >= 28: + confidence = 'high' + elif n_pairs >= 21: + confidence = 'medium' + elif n_pairs >= 14: + confidence = 'low' + else: + confidence = 'insufficient' + + if confidence == 'insufficient': + correlation = None + else: + correlation = pearson_correlation(pairs) + + results.append({ + 'lag_days': lag, + 'correlation': correlation, + 'n_pairs': n_pairs, + 'confidence': confidence, + 'note': 'explorativ, nicht kausal' + }) + + return { + 'correlations': results, + 'overall_confidence': min([r['confidence'] for r in results]) + } +``` + +**Zu implementieren für:** +1. `correlation_energy_weight_lag` (min 21 Paare) +2. `correlation_load_hrv` (min 21 Paare) +3. `correlation_load_rhr` (min 21 Paare) +4. `correlation_protein_lbm` (min 28 Paare, Training moderiert) +5. `correlation_sleep_recovery` (min 21 Paare) + +**Testing:** +- Synthetic Data: 10, 14, 21, 28, 35 Paare → Confidence-Check +- Real Data: Validate gegen tatsächliche Daten-Dichte + +**Owner:** Backend Developer (Senior, Statistik-Know-how) +**Blocker:** P1.1 Confidence-Pattern etabliert +**Output:** 5 Platzhalter mit Correlation-Confidence + +--- + +### P1.4: Data-Layer-Module dokumentieren + +**Gap-Cluster:** 2 +**Betroffene Platzhalter:** 100 (data_layer_module: null) +**Severity:** 🔴 CRITICAL +**Aufwand:** 6-8 Stunden + +**Maßnahmen:** + +**Input:** Code-Evidence-Agent-Report (technische Herkunft für alle 111) + +**Prozess:** +1. **Mapping-Extraktion:** + - Für jede `_safe_*`-Funktion: Gerufene Data-Layer-Funktion identifizieren + - Mapping: Placeholder Key → Data-Layer-Module + +2. **Bulk-Update-Skript:** + ```python + # Mapping aus Code-Evidence-Report + data_layer_mapping = { + 'goal_progress_score': 'data_layer.scores', + 'body_progress_score': 'data_layer.body_metrics', + 'nutrition_score': 'data_layer.nutrition_metrics', + 'activity_score': 'data_layer.activity_metrics', + 'recovery_score': 'data_layer.recovery_metrics', + # ... 95 more + } + + # Catalog Update + for key, module in data_layer_mapping.items(): + catalog['placeholders'][key]['source']['data_layer_module'] = module + ``` + +3. **Validation:** + - Automated Check: Alle 111 haben data_layer_module gesetzt + - Stichprobe 10%: Manuell im Code validieren + +**Owner:** DevOps / Backend Lead +**Blocker:** Code-Evidence-Agent-Report finalisiert (✓) +**Output:** 100 Fixed Platzhalter + +--- + +### P1.5: Source-Tables dokumentieren + +**Gap-Cluster:** 3 +**Betroffene Platzhalter:** 90 (source_tables: []) +**Severity:** 🔴 CRITICAL +**Aufwand:** 6-8 Stunden + +**Maßnahmen:** + +**Identisch zu P1.4:** +- Mapping aus Code-Evidence-Report übernehmen +- Bulk-Update-Skript +- Validation + +**Mapping-Beispiel:** +```python +source_tables_mapping = { + 'weight_aktuell': ['weight_log'], + 'weight_trend': ['weight_log'], + 'kf_aktuell': ['caliper_log'], + 'bmi': ['weight_log', 'profiles'], + 'nutrition_score': ['nutrition_log'], + 'activity_score': ['activity_log'], + 'recovery_score': ['sleep_log', 'vitals_baseline', 'rest_days'], + # ... 83 more +} +``` + +**Owner:** DevOps / Backend Lead +**Blocker:** Code-Evidence-Agent-Report finalisiert (✓) +**Output:** 90 Fixed Platzhalter + +--- + +### P1.6: Strukturierte Missing-Value-Policy + +**Gap-Cluster:** 6 +**Betroffene Platzhalter:** 110 (nur legacy_display) +**Severity:** 🟡 MEDIUM +**Aufwand:** 8-10 Stunden + +**Maßnahmen:** + +**Dual-Mode-Ansatz:** +```python +# Alte Resolver-Return (Backward-Compatible) +def get_weight_trend(profile_id, days=28, conn=None): + data = get_weight_trend_data(profile_id, days, conn) + + if data['confidence'] == 'insufficient': + # Legacy: String-Return + return "nicht verfügbar" + + return f"{data['direction']} {data['delta']} kg" + +# Neue Structured-Return (Parallel) +def get_weight_trend_structured(profile_id, days=28, conn=None): + data = get_weight_trend_data(profile_id, days, conn) + + if data['confidence'] == 'insufficient': + return { + 'available': False, + 'value_raw': None, + 'value_display': "nicht verfügbar", + 'missing_reason': 'insufficient_data', + 'missing_value_policy': { + 'legacy_display': "nicht verfügbar", + 'structured_null': True, + 'reason_codes': ['no_data', 'insufficient_data', 'resolver_error'] + }, + 'metadata': { + 'data_points': data['data_points'], + 'min_required': data['min_required'], + 'time_window': '28d' + } + } + + return { + 'available': True, + 'value_raw': data['slope'], + 'value_display': f"{data['direction']} {data['delta']} kg", + 'confidence': data['confidence'], + 'metadata': { + 'data_points': data['data_points'], + 'r_squared': data['r_squared'], + 'time_window': '28d' + } + } +``` + +**Migration-Strategie:** +1. **Phase 1:** Neue `*_structured()`-Funktionen neben alten (Parallel) +2. **Phase 2:** Extended Export nutzt structured +3. **Phase 3:** Legacy-Export bleibt unverändert (Backward-Compat) +4. **Phase 4 (Later):** Deprecate alte Funktionen nach 6-12 Monaten + +**Zu implementieren für:** +- Alle 110 Platzhalter (außer weight_aktuell, der bereits structured ist) + +**Testing:** +- Legacy-Tests: Alte Funktionen bleiben unverändert +- Structured-Tests: Neue Funktionen returnieren korrekte Struktur +- Export-Tests: Extended Export nutzt structured + +**Owner:** Backend Team (2 Entwickler) +**Blocker:** P1.1-P1.3 Confidence abgeschlossen +**Output:** 110 Platzhalter mit Dual-Mode-Support + +--- + +### P1 SUMMARY + +**Gesamt-Aufwand:** 44-58 Stunden +**Deliverables:** +- 11 Trend-Platzhalter mit Confidence +- 6 Score-Platzhalter mit Composite Confidence +- 5 Correlation-Platzhalter mit Pair-Confidence +- 100 Platzhalter mit data_layer_module +- 90 Platzhalter mit source_tables +- 110 Platzhalter mit Structured Missing-Value-Policy +- **Total Fixed:** 103 unique Platzhalter (alle außer 8 bereits conforme) + +**Impact:** +- **Compliance:** 25-30% → 50-60% +- **Blocked Gaps:** Confidence, Data Layer, Source Tables, Missing-Value-Policy + +**Next:** P2 Production-Ready + Deprecation + +--- + +## P2: MEDIUM PRIORITY (Week 4-5) + +**Ziel:** Production-Status und Deprecation-Strategie +**Aufwand:** 8-12 Stunden +**Team:** Product + Tech Lead +**Dependencies:** P1 abgeschlossen + +--- + +### P2.1: Schema-Status auf Production (Top 20) + +**Gap-Cluster:** 8 +**Betroffene Platzhalter:** 20-30 Kandidaten +**Severity:** 🟡 MEDIUM +**Aufwand:** 4-6 Stunden + +**Maßnahmen:** + +**Kriterien für `schema_status: production`:** +1. `metadata_completeness_score >= 80` +2. `used_by.prompts.length >= 1 OR used_by.pipelines.length >= 1` +3. `time_window != 'unknown'` +4. `category != 'Unknown'` +5. `description != 'No description available'` +6. `confidence_logic != null OR (type == 'atomic' AND time_window == 'latest')` +7. `known_issues.length == 0` + +**Prozess:** +1. **Automated Eligibility-Check:** + ```python + def is_production_ready(placeholder): + checks = [ + placeholder['metadata_completeness_score'] >= 80, + len(placeholder['used_by']['prompts']) >= 1 or + len(placeholder['used_by']['pipelines']) >= 1, + placeholder['time_window'] != 'unknown', + placeholder['category'] != 'Unknown', + placeholder['description'] != 'No description available', + placeholder['confidence_logic'] is not None or + (placeholder['type'] == 'atomic' and placeholder['time_window'] == 'latest'), + len(placeholder['known_issues']) == 0 + ] + return all(checks) + ``` + +2. **Kandidaten-Liste generieren** +3. **Manuelle Review** (Product + Tech Lead) + - Sind diese wirklich production-ready? + - Fehlt noch etwas? +4. **Schema-Status-Update** + +**Erwartete Kandidaten (12-15):** +- Nutrition Averages (4): protein_avg, kcal_avg, fat_avg, carb_avg +- Body Metrics (3): weight_aktuell, weight_trend, kf_aktuell +- Profil (4): name, age, height, geschlecht +- Summaries (2): caliper_summary, circ_summary +- Goals (2-3): goal_weight, goal_bf_pct, top_goal_name + +**Owner:** Tech Lead + Product Manager +**Blocker:** P1 abgeschlossen (Confidence, Data Layer) +**Output:** 12-15 Platzhalter mit `schema_status: production` + +--- + +### P2.2: Ungenutzte Platzhalter - Integration planen + +**Gap-Cluster:** 9 (REINTERPRETIERT: Nicht Deprecation, sondern Integration) +**Betroffene Platzhalter:** 67 (ungenutzt) +**Severity:** 🟡 MEDIUM (Prompt-Bibliothek Vollständigkeit) +**Aufwand:** 4-6 Stunden + +**Maßnahmen:** + +**Neue Klassifizierung (siehe USAGE_ROLE_CLASSIFICATION.md):** +1. **unused_but_planned (30)** - Explizit in Roadmap Phase 0c/1/2 + - Scores (6), Correlations (5), Ability Balance (5), Goals Details (11), etc. + - **Action:** Timeline bestätigen, Prototyping-Prompts erstellen + +2. **unused_but_plausible (37)** - Fachlich sinnvoll, noch nicht in Prompts + - Body Deltas, Nutrition Details, Training Quality, Focus Category, Meta + - **Action:** Prompt-Use-Cases identifizieren, Templates für Quick Wins + +3. **redundant_or_duplicate (0)** - Keine! + - Alle 67 haben fachliche Berechtigung + +**Prozess:** +1. **Meeting (2h):** Product + Tech Review aller 67 + - Gruppe A (30 geplant): Timeline bestätigen (Phase 0c/1/2) + - Gruppe B (37 plausibel): Prompt-Use-Cases identifizieren (10-15 Quick Wins) +2. **Dokumentation (1h):** Integration-Roadmap, Prompt-Kandidaten-Liste +3. **Implementation (1-2h):** Prompt-Templates erstellen (5-10 Quick Wins) +4. **Communication (1h):** Prompt-Autoren: "Neue Platzhalter verfügbar" + +**Integration-Beispiel (statt Deprecation):** +```json +{ + "key": "arm_28d_delta", + "usage_role": "unused_but_plausible", + "integration_priority": "medium", + "prompt_use_cases": [ + "Fortschritts-Analyse spezifischer Körperteile", + "Asymmetrie-Erkennung (linker vs. rechter Arm)", + "Trainingsplan-Effektivität (Armtraining Tracking)" + ], + "example_prompt_template": "Deine Armumfänge haben sich in den letzten 28 Tagen um {{arm_28d_delta}}cm verändert. Analyse: ..." +} +``` + +**Owner:** Product Manager + Tech Lead +**Blocker:** P1 abgeschlossen +**Output:** Integration-Roadmap, Prompt-Templates (5-10), Nutzungsrate +10-20% + +--- + +### P2 SUMMARY + +**Gesamt-Aufwand:** 8-12 Stunden +**Deliverables:** +- 12-15 Production-Ready Platzhalter +- Integration-Roadmap für 30 geplante Platzhalter (Phase 0c/1/2) +- Prompt-Templates für 5-10 Quick Wins (ungenutzte Platzhalter aktivieren) + +**Impact:** +- **Compliance:** 50-60% → 65-70% +- **Governance:** Production-Pipeline etabliert, Placeholder-Integration vorangetrieben +- **Nutzungsrate:** 40% → 50-60% (organisch durch Integration) + +**Next:** P3 (Nice-to-Have) + +--- + +## P3: NICE-TO-HAVE (Later) + +**Ziel:** Dokumentations-Feinschliff +**Aufwand:** 0.5 Stunden +**Team:** Tech Writer / DevOps +**Dependencies:** P2 abgeschlossen + +--- + +### P3.1: Export-Inkonsistenzen Dokumentation + +**Gap-Cluster:** 10 +**Betroffene:** Dokumentation +**Severity:** 🟢 INFO +**Aufwand:** 0.5 Stunden + +**Maßnahmen:** + +**Export Spec Update:** +```diff +# PLACEHOLDER_EXPORT_SPEC.md + +- **Total Placeholders:** 116 ++ **User Placeholders:** 111 ++ **Meta Fields:** 5 (schema_version, generated_at, normative_standard, total_placeholders, metadata.summary) ++ **Total Export Entries:** 116 +``` + +**Owner:** Tech Writer +**Blocker:** Keine +**Output:** Updated Export Spec + +--- + +## ERFOLGS-METRIKEN + +### Nach P0 (Week 1) +- ✅ **Compliance:** 7% → 25-30% +- ✅ **time_window: unknown:** 74 → 0 +- ✅ **category: Unknown:** 49 → 0 +- ✅ **description: No description:** 49 → 0 +- ✅ **Code-Docs-Konflikte:** 3 → 0 + +### Nach P1 (Week 3) +- ✅ **Compliance:** 25-30% → 50-60% +- ✅ **Confidence-Logik:** 8 → 28 (Trend + Score + Correlation) +- ✅ **data_layer_module:** 11 → 111 +- ✅ **source_tables:** 21 → 111 +- ✅ **Structured Missing-Value:** 1 → 111 + +### Nach P2 (Week 5) +- ✅ **Compliance:** 50-60% → 65-70% +- ✅ **schema_status: production:** 0 → 12-15 +- ✅ **deprecated:** 0 → 15-20 +- ✅ **Technical Debt:** Reduziert + +### Nach P3 +- ✅ **Compliance:** 65-70% → 70%+ +- ✅ **Dokumentation:** Vollständig konsistent + +--- + +## RISIKEN & MITIGATION + +| Risiko | Wahrscheinlichkeit | Impact | Mitigation | +|--------|--------------------|----|-----------| +| **P0 Zeitfenster-Fach-Entscheidung dauert länger** | MEDIUM | MEDIUM | Vorbereitete Kategorisierung, klare Optionen, Decision-Meeting zeitig ansetzen | +| **P1 Confidence-Implementierung komplexer als geschätzt** | MEDIUM | HIGH | Pattern aus nutrition_avg wiederverwenden, Senior Dev assignen | +| **Breaking Changes durch Zeitfenster-Fixes** | LOW | HIGH | Code ist autoritativ, Docs passen sich an → kein Breaking Change | +| **Prompt-Autoren akzeptieren Deprecations nicht** | LOW | MEDIUM | Klare Communication, Replacement-Guides, Grace Period (3 Monate) | + +--- + +## KOMMUNIKATIONS-PLAN + +### Week 1 (P0 Kickoff) +- **Email:** Alle Stakeholder über Audit-Ergebnisse informieren +- **Meeting:** P0-Prioritäten mit Tech Lead abstimmen +- **Kickoff:** Development Team P0-Tasks zuweisen + +### Week 2 (P1 Kickoff) +- **Standup:** Daily Updates zu Confidence-Implementierung +- **Review:** Mid-Sprint Review nach P1.1-P1.3 + +### Week 3 (P1 Abschluss) +- **Demo:** P1-Ergebnisse dem Team zeigen +- **Docs:** Updated Catalog veröffentlichen + +### Week 4-5 (P2) +- **Meeting:** Production-Readiness-Review +- **Communication:** Deprecation-Plan an Prompt-Autoren + +### Week 6 (Abschluss) +- **Retrospektive:** Lessons Learned +- **Documentation:** Finale Compliance-Metrics veröffentlichen + +--- + +## TOOLING & AUTOMATION + +### Skripte (entwickeln während P0-P1) +1. `fix_time_window_from_name.py` - Automatische Name-Pattern-Fixes +2. `extract_code_parameters.py` - Code-Parameter → time_window +3. `bulk_update_catalog.py` - JSON-Merge für Bulk-Updates +4. `validate_compliance.py` - Automatische Compliance-Checks +5. `check_production_ready.py` - Production-Eligibility-Check + +### CI/CD-Integration (P2-P3) +- Pre-Commit-Hook: Validate neue Platzhalter gegen Normative Spec +- CI: Consistency-Checks (Code ↔ Catalog) +- CD: Automated Catalog-Deployment + +--- + +## ABSCHLUSS-KRITERIEN + +**P0 erfolgreich wenn:** +- Alle 3 Code-Docs-Konflikte gelöst +- Alle 74 time_window: unknown → definiert +- Alle 49 No description → vollständig +- Automated Tests grün + +**P1 erfolgreich wenn:** +- Mindestens 20 Platzhalter mit Confidence-Logik +- Alle 100 data_layer_module gesetzt +- Alle 90 source_tables gesetzt +- Structured Missing-Value für alle 110 (Dual-Mode) + +**P2 erfolgreich wenn:** +- Mindestens 12 Platzhalter `schema_status: production` +- Mindestens 15 Platzhalter `deprecated: true` +- Decision-Log veröffentlicht + +**Gesamt-Projekt erfolgreich wenn:** +- **Compliance >= 65%** +- **0 Code-Docs-Konflikte** +- **0 time_window: unknown** +- **12+ Production-Ready Platzhalter** + +--- + +**Maßnahmenplan erstellt von:** Claude Code (Lead Audit Agent) +**Basis:** Gap-Cluster-Analyse, 4-Agent-Evidence +**Genehmigung:** Pending (Review mit Tech Lead + Product Manager) diff --git a/.claude/docs/audit/platzhalter/audit-report-2026-03-29/04_OFFENE_ENTSCHEIDUNGEN.md b/.claude/docs/audit/platzhalter/audit-report-2026-03-29/04_OFFENE_ENTSCHEIDUNGEN.md new file mode 100644 index 0000000..178c580 --- /dev/null +++ b/.claude/docs/audit/platzhalter/audit-report-2026-03-29/04_OFFENE_ENTSCHEIDUNGEN.md @@ -0,0 +1,459 @@ +# Offene Entscheidungen: Placeholder-Audit + +**Audit-Datum:** 29. März 2026 +**Status:** Pending Product/Tech Review +**Entscheidungsträger:** Product Manager + Tech Lead + +--- + +## ENTSCHEIDUNGSPFLICHTIGE PUNKTE + +Diese Punkte können nicht rein aus Code und Fachlogik gelöst werden und benötigen eine **Produkt-/Fachentscheidung**. + +--- + +## KATEGORIE 1: Zeitfenster-Klassifizierung (BLOCKING P0) + +### E1.1: Scores - Zeitfenster definieren + +**Betroffene Platzhalter:** 6 +- `activity_score` +- `nutrition_score` +- `recovery_score` +- `body_progress_score` +- `goal_progress_score` +- `data_quality_score` + +**Problem:** +Scores kombinieren verschiedene Zeitfenster-Metriken. Welches Zeitfenster soll dokumentiert werden? + +**Optionen:** + +| Option | Beschreibung | Pro | Contra | +|--------|--------------|-----|--------| +| **A: `custom`** | Scores haben keinen festen Zeitrahmen | Technisch korrekt | Unspezifisch für User | +| **B: `mixed`** | Scores kombinieren verschiedene Zeitfenster explizit | Kommuniziert Komplexität | Ähnlich unklar wie `custom` | +| **C: Dominantes Zeitfenster** | Z.B. `28d` für nutrition_score (da Hauptkomponenten 28d) | Einfach zu verstehen | Vereinfachung, evtl. irreführend | +| **D: Zeitfenster pro Sub-Score dokumentieren** | Semantic Contract listet alle Sub-Zeitfenster | Vollständig transparent | Komplex für einfache Use-Cases | + +**Empfehlung Audit-Agent:** +- **Option D** für `semantic_contract` (vollständige Transparenz) +- **Option B** für `time_window` Metadatum (signalisiert Komplexität) + +**Beispiel (nutrition_score):** +```json +{ + "key": "nutrition_score", + "time_window": "mixed", + "semantic_contract": "Composite Score aus: energy_balance (7d), protein_adequacy (28d), macro_consistency (28d). Gewichtung: 40%/40%/20%" +} +``` + +**Entscheidung benötigt bis:** P0 Week 1 (Zeitfenster-Klassifizierung) +**Impact:** MEDIUM (betrifft Prompt-Interpretation) +**Entscheider:** Product Manager + +--- + +### E1.2: Ability Balance - Zeitfenster definieren + +**Betroffene Platzhalter:** 5 +- `ability_balance_coordination` +- `ability_balance_endurance` +- `ability_balance_mental` +- `ability_balance_mobility` +- `ability_balance_strength` + +**Problem:** +Code implementiert noch keine Ability Balance-Berechnung. Welches Zeitfenster ist fachlich sinnvoll? + +**Optionen:** + +| Option | Beschreibung | Pro | Contra | +|--------|--------------|-----|--------| +| **A: `28d`** | Rolling-Window über 28 Tage | Konsistent mit anderen Trend-Metriken | Evtl. zu lang für Balance-Shifts | +| **B: `14d`** | Rolling-Window über 14 Tage | Schnellere Reaktion auf Training-Changes | Weniger stabil bei Lücken | +| **C: `latest`** | Snapshot der aktuellen Balance | Einfach | Keine Trend-Info | + +**Empfehlung Audit-Agent:** +- **Option A (`28d`)** - Konsistent mit body_metrics, ausreichend stabil + +**Rationale:** +- Ability Balance = Verteilung der Trainings-Last über Fähigkeiten +- 28 Tage = ~4 Wochen = typischer Mikrozyklus +- Matching mit anderen Tracking-Metriken + +**Entscheidung benötigt bis:** P0 Week 1 +**Impact:** MEDIUM (betrifft zukünftige Implementierung) +**Entscheider:** Product Manager + Training-Experte + +--- + +### E1.3: Correlations - Zeitfenster definieren + +**Betroffene Platzhalter:** 5 +- `correlation_energy_weight_lag` +- `correlation_load_hrv` +- `correlation_load_rhr` +- `correlation_protein_lbm` +- `correlation_sleep_recovery` + +**Problem:** +Korrelationen brauchen Mindestdaten. Welches Zeitfenster ist statistisch sinnvoll? + +**Optionen:** + +| Option | Beschreibung | Pro | Contra | +|--------|--------------|-----|--------| +| **A: `28d`** | Min 21-28 Paare für Reliability | Statistisch robust | Reagiert langsam auf Änderungen | +| **B: `custom`** | Variabel je nach Datenverfügbarkeit | Flexibel | Unklar für User | +| **C: Im Semantic Contract spezifizieren** | Z.B. "28d min, 90d ideal" | Transparent | Komplex | + +**Empfehlung Audit-Agent:** +- **Option A (`28d`)** für `time_window` +- **Option C** zusätzlich in `semantic_contract` + +**Beispiel:** +```json +{ + "key": "correlation_energy_weight_lag", + "time_window": "28d", + "semantic_contract": "Lag-Korrelation (0d/3d/7d/14d) zwischen Energiebilanz und Gewicht. Min 21 Paare (28d) für Reliability, 90d ideal. Explorativ, nicht kausal." +} +``` + +**Entscheidung benötigt bis:** P0 Week 1 +**Impact:** MEDIUM (betrifft Statistik-Interpretation) +**Entscheider:** Tech Lead (Statistik-Know-how) + +--- + +### E1.4: Goals & Focus - Zeitfenster definieren + +**Betroffene Platzhalter:** 16 +- `active_goals_json`, `active_goals_md` +- `focus_areas_weighted_json`, `focus_areas_weighted_md`, `focus_area_weights_json` +- `top_goal_name`, `top_goal_progress_pct`, `top_goal_status` +- `top_3_goals_behind_schedule`, `top_3_goals_on_track`, `top_3_focus_areas` +- `focus_cat_körper_progress`, `focus_cat_ernährung_progress`, `focus_cat_aktivität_progress`, `focus_cat_recovery_progress`, `focus_cat_vitalwerte_progress`, `focus_cat_mental_progress`, `focus_cat_lebensstil_progress` (jeweils + weight) + +**Problem:** +Goals sind Snapshots, aber Progress wird über Zeit berechnet. Wie klassifizieren? + +**Optionen:** + +| Option | Beschreibung | Pro | Contra | +|--------|--------------|-----|--------| +| **A: `latest`** | Aktueller Stand der Goals | Technisch korrekt (Snapshot) | Impliziert keine Zeit-Dimension | +| **B: `custom`** | Progress-Berechnung ist zeitabhängig | Signalisiert Komplexität | Unklar für Snapshot-Werte | +| **C: Differenzierung** | `active_goals_json`: `latest`, Progress-Felder: `custom` | Präzise | 16 individuelle Entscheidungen | + +**Empfehlung Audit-Agent:** +- **Option C** (Differenzierung): + - **Snapshot-Platzhalter** (active_goals_json, focus_area_weights_json, top_goal_name): `latest` + - **Progress-Platzhalter** (top_goal_progress_pct, focus_cat_*_progress): `custom` (da verschiedene Ziele verschiedene Zeiträume haben) + +**Entscheidung benötigt bis:** P0 Week 1 +**Impact:** LOW (primär dokumentarisch) +**Entscheider:** Product Manager + +--- + +### E1.5: Snapshots - Zeitfenster definieren + +**Betroffene Platzhalter:** 7 +- `bmi` +- `goal_weight`, `goal_bf_pct` +- `waist_hip_ratio` +- `recomposition_quadrant` +- `plateau_detected` +- `top_drivers` + +**Problem:** +Einige sind echte Snapshots (bmi, goal_weight), andere berechnete Werte über Zeit (recomposition_quadrant, plateau_detected). + +**Optionen:** + +| Option | Beschreibung | Pro | Contra | +|--------|--------------|-----|--------| +| **A: Alle `latest`** | Vereinfachung | Einfach | Ungenau für berechnete | +| **B: Differenzierung** | bmi/goal_weight: `latest`, recomposition_quadrant/plateau_detected: `28d` | Präzise | 7 individuelle Entscheidungen | + +**Empfehlung Audit-Agent:** +- **Option B**: + - **Echte Snapshots** (bmi, goal_weight, goal_bf_pct, waist_hip_ratio): `latest` + - **Berechnete über Zeit** (recomposition_quadrant, plateau_detected, top_drivers): `28d` oder `custom` + +**Entscheidung benötigt bis:** P0 Week 1 +**Impact:** LOW +**Entscheider:** Tech Lead + +--- + +## KATEGORIE 2: Integration ungenutzter Platzhalter (P2) + +### E2.1: Ungenutzte Platzhalter - Roadmap-Priorisierung und Integration-Timeline + +**Betroffene Platzhalter:** 67 (ungenutzt) + +**Kontext:** +67 Platzhalter (60%) haben 0 Verwendungen. **WICHTIG:** Dies ist kein Deprecation-Bedarf, sondern Integration-Planung. + +**Fachliche Klassifizierung (siehe USAGE_ROLE_CLASSIFICATION.md):** +- **30 Platzhalter (45%):** Explizit in Roadmap Phase 0c/1/2 geplant +- **37 Platzhalter (55%):** Fachlich plausibel, noch nicht in Prompts integriert +- **0 Platzhalter:** Redundant oder deprecation-würdig + +**Neue Fragestellung:** +Nicht "Behalten oder Deprecaten?", sondern "WANN und WIE integrieren?" + +--- + +**Sub-Entscheidungen:** + +#### E2.1.1: Geplante Platzhalter (30) - Integration-Timeline bestätigen + +**Platzhalter-Gruppen:** +- **Scores (6):** activity_score, nutrition_score, recovery_score, body_progress_score, goal_progress_score, data_quality_score + - **Roadmap:** Phase 0c (Multi-Layer Architecture) + Phase 1 (Charts) +- **Correlations (5):** energy_weight_lag, load_hrv, load_rhr, protein_lbm, sleep_recovery + - **Roadmap:** Phase 2 (Engagement - Korrelationen) +- **Ability Balance (5):** coordination, endurance, mental, mobility, strength + - **Roadmap:** Phase 1 (Charts - Training Balance) +- **Goals Details (11):** active_goals_json/md, focus_areas_weighted, top_3_goals_*, focus_cat_*_progress + - **Roadmap:** Phase 0b/0c (Backend DONE, Prompt-Integration ausstehend) +- **Sleep/Plateau/Drivers (3):** sleep_debt, plateau_detected, top_drivers + - **Roadmap:** Phase 1/2 (Diagnostics) + +**Frage:** Timeline bestätigen oder anpassen? + +**Optionen:** +- **A: Roadmap wie geplant** - Phase 0c/1/2 (nächste 6-12 Wochen) +- **B: Priorisierung anpassen** - Quick Wins zuerst (z.B. Goals Details sofort) +- **C: Prototyping** - 5-10 Beispiel-Prompts erstellen (Wert demonstrieren) + +**Empfehlung:** C → B → A (Prototyping zeigt Wert, dann Priorisierung, dann Roadmap) + +--- + +#### E2.1.2: Plausible Platzhalter (37) - Prompt-Use-Cases identifizieren + +**Platzhalter-Gruppen:** +- **Body Deltas (5):** arm_28d_delta, chest_28d_delta, hip_28d_delta, thigh_28d_delta, waist_28d_delta +- **Nutrition Details (6):** energy_deficit_surplus, intake_volatility, nutrition_days, protein_days_in_target, protein_ziel_low/high +- **Training Quality/Load (3):** monotony_score, strain_score, rest_day_compliance +- **Focus Category Weights/Progress (14):** focus_cat_*_weight, focus_cat_*_progress +- **Meta/Convenience (9):** bmi, waist_hip_ratio, datum_heute, zeitraum_* + +**Frage:** Für welche Use-Cases Prompt-Templates erstellen? + +**Optionen:** +- **A: Alle 37 dokumentieren** - Vollständig, aber aufwändig +- **B: Quick Wins (10-15)** - Offensichtliche Use-Cases zuerst +- **C: Warten bis Bedarf** - Nur auf Anfrage aktivieren + +**Empfehlung:** B (10-15 Quick Wins: Body Deltas für Fortschritts-Prompts, Nutrition Details für Coaching-Prompts, Focus Category für Dashboard-Widgets) + +--- + +**Entscheidung benötigt bis:** P2 Week 4 +**Impact:** MEDIUM (Prompt-Bibliothek Vollständigkeit) +**Entscheider:** Product Manager + Tech Lead +**Methode:** Integration-Planungs-Meeting (4-6h, siehe Maßnahmenplan P2.2 REVIDIERT) + +--- + +## KATEGORIE 3: Production-Readiness-Kriterien (P2) + +### E3.1: Welche Platzhalter sind Production-Ready? + +**Problem:** +Aktuell haben alle 111 `schema_status: draft`. Welche Kriterien für `production`? + +**Vorgeschlagene Kriterien (aus Maßnahmenplan):** +1. `metadata_completeness_score >= 80` +2. `used_by.prompts.length >= 1 OR used_by.pipelines.length >= 1` +3. `time_window != 'unknown'` +4. `category != 'Unknown'` +5. `description != 'No description available'` +6. `confidence_logic != null OR (type == 'atomic' AND time_window == 'latest')` +7. `known_issues.length == 0` + +**Frage:** Sind diese Kriterien zu streng/zu locker? + +**Optionen:** +- **A: Wie vorgeschlagen** - Balanced +- **B: Strengere Kriterien** - Z.B. `used_by >= 3`, `completeness_score >= 90` +- **C: Lockerere Kriterien** - Z.B. `completeness_score >= 60`, keine Usage-Requirement + +**Empfehlung:** A (wie vorgeschlagen) + +**Zusatzfrage:** Soll es Zwischen-Status geben (`beta`, `stable`)? + +**Optionen:** +- **A: Nur draft/production** - Einfach +- **B: draft/beta/stable/production** - Differenzierter + +**Empfehlung:** B (mehr Nuancen ermöglichen schrittweise Freigabe) + +**Entscheidung benötigt bis:** P2 Week 4 +**Impact:** MEDIUM (Governance) +**Entscheider:** Tech Lead + Product Manager + +--- + +## KATEGORIE 4: Neue Platzhalter (FUTURE) + +### E4.1: Requirements Dev vs. Extended Catalog - Wie priorisieren? + +**Problem:** +- **requirements_dev.md** schlägt 27 neue Platzhalter vor (P1-P27) +- **Extended Catalog** hat bereits 111 +- Viele der "neuen" sind evtl. schon abgedeckt + +**Frage:** Sollen die 27 P1-P27 implementiert werden, oder sind sie durch bestehende abgedeckt? + +**Beispiele:** +- **P1: `goal_summary_json`** - Abgedeckt durch `active_goals_json` + `active_goals_md`? +- **P2: `focus_area_summary_json`** - Abgedeckt durch `focus_areas_weighted_json`? +- **P5: `domain_availability_json`** - Neu, nicht vorhanden +- **P12: `body_change_summary_json`** - Teilweise durch `caliper_summary`, `circ_summary` + +**Empfehlung:** +- **Mapping-Exercise:** Neue vs. Bestehende +- **Gap-Analyse:** Welche sind echte Lücken? +- **Priorisierung:** Nur echte Gaps implementieren + +**Entscheidung benötigt bis:** Nach P2 (Phase 1b Planning) +**Impact:** HIGH (Feature-Roadmap) +**Entscheider:** Product Manager + Tech Lead + +--- + +## KATEGORIE 5: Governance-Prozesse (POST-AUDIT) + +### E5.1: Wie werden neue Platzhalter künftig validiert? + +**Problem:** +Aktuell gibt es keine systematische Validierung neuer Platzhalter gegen Normative Spec. + +**Frage:** Soll ein Pre-Commit-Hook/CI-Check eingeführt werden? + +**Optionen:** +- **A: Manuelle Reviews** - Flexibel, aber fehleranfällig +- **B: Automated Checks** - Robust, aber Aufwand für Setup +- **C: Hybrid** - Automated Checks + manuelle Review für komplexe Fälle + +**Empfehlung:** C (Hybrid) + +**Entscheidung benötigt bis:** Nach P2 (Tooling-Phase) +**Impact:** HIGH (langfristige Qualität) +**Entscheider:** Tech Lead + +--- + +### E5.2: Wer ist Owner für Placeholder-Governance? + +**Problem:** +Aktuell kein klarer Owner für Placeholder-Lifecycle. + +**Frage:** Soll es eine dedizierte Rolle geben? + +**Optionen:** +- **A: Tech Lead** - Technische Verantwortung +- **B: Product Manager** - Fachliche Verantwortung +- **C: Shared (beide)** - Consensus-basiert +- **D: Dedizierte Rolle "Data-Contract-Owner"** - Spezialisiert + +**Empfehlung:** C (Shared) für jetzt, D langfristig + +**Entscheidung benötigt bis:** Nach P2 +**Impact:** MEDIUM (Governance) +**Entscheider:** Management + +--- + +## KATEGORIE 6: Technische Schulden (POST-P2) + +### E6.1: Legacy-String "nicht verfügbar" - Wann abschalten? + +**Problem:** +P1.6 führt Dual-Mode ein (Legacy + Structured). Wann soll Legacy deprecated werden? + +**Optionen:** +- **A: Nach 3 Monaten** - Schnell +- **B: Nach 6 Monaten** - Standard +- **C: Nach 12 Monaten** - Konservativ +- **D: Nie (dauerhaft Dual-Mode)** - Ewig Backward-Compatible + +**Empfehlung:** B (6 Monate) mit klarer Migration-Guidance + +**Entscheidung benötigt bis:** P1 Abschluss (Sunset-Datum festlegen) +**Impact:** MEDIUM (Breaking Change für Prompts) +**Entscheider:** Tech Lead + Prompt-Autoren + +--- + +## ZUSAMMENFASSUNG OFFENER ENTSCHEIDUNGEN + +| ID | Kategorie | Entscheidung | Dringlichkeit | Entscheider | +|----|-----------|--------------|---------------|-------------| +| **E1.1** | Zeitfenster Scores | custom/mixed/dominant? | 🔴 P0 Week 1 | Product | +| **E1.2** | Zeitfenster Ability Balance | 28d/14d/latest? | 🔴 P0 Week 1 | Product + Training-Expert | +| **E1.3** | Zeitfenster Correlations | 28d mit Min-Data? | 🔴 P0 Week 1 | Tech Lead | +| **E1.4** | Zeitfenster Goals & Focus | latest/custom/differenziert? | 🔴 P0 Week 1 | Product | +| **E1.5** | Zeitfenster Snapshots | latest/differenziert? | 🔴 P0 Week 1 | Tech Lead | +| **E2.1** | Integration-Timeline | 67 ungenutzte integrieren | 🟡 P2 Week 4 | Product | +| **E3.1** | Production-Kriterien | draft/beta/stable/production? | 🟡 P2 Week 4 | Tech + Product | +| **E4.1** | Neue Platzhalter P1-P27 | Welche implementieren? | 🟢 Post-P2 | Product | +| **E5.1** | Validierungs-Prozess | Automated/Manual/Hybrid? | 🟢 Post-P2 | Tech Lead | +| **E5.2** | Governance-Owner | Wer verantwortet Placeholders? | 🟢 Post-P2 | Management | +| **E6.1** | Legacy-Sunset | Wann "nicht verfügbar" abschalten? | 🟡 P1 Abschluss | Tech Lead | + +**Legende:** +- 🔴 BLOCKING (P0) - Entscheidung innerhalb Week 1 benötigt +- 🟡 HIGH (P1-P2) - Entscheidung innerhalb Week 4-5 benötigt +- 🟢 MEDIUM (Post-P2) - Kann warten + +--- + +## EMPFOHLENER ENTSCHEIDUNGS-PROZESS + +### Week 1 (P0 Kickoff) +1. **Meeting (2h):** Product + Tech Lead + Training-Expert +2. **Agenda:** + - E1.1-E1.5: Alle Zeitfenster-Entscheidungen + - Decision-Log erstellen +3. **Output:** Entschiedene Zeitfenster für alle 39 unkla + +ren Platzhalter + +### Week 4 (P2) +1. **Meeting (2h):** Product + Tech Lead +2. **Agenda:** + - E2.1: Deprecation-Review (67 ungenutzte) + - E3.1: Production-Kriterien +3. **Output:** Deprecation-Liste, Production-Kriterien finalisiert + +### Post-P2 +1. **Meeting (1h):** Management + Tech + Product +2. **Agenda:** + - E4.1: Neue Platzhalter-Roadmap + - E5.1-E5.2: Governance-Prozesse + - E6.1: Legacy-Sunset-Datum +3. **Output:** Langfristige Placeholder-Governance etabliert + +--- + +## NÄCHSTE SCHRITTE + +1. **Dieses Dokument reviewen** mit Product Manager +2. **P0-Meeting ansetzen** (Week 1) für Zeitfenster-Entscheidungen +3. **Decision-Log starten** (Living Document) +4. **Nach Entscheidungen:** Maßnahmenplan P0 final anpassen + +--- + +**Offene Entscheidungen dokumentiert von:** Claude Code (Lead Audit Agent) +**Review benötigt von:** Product Manager + Tech Lead +**Deadline kritische Entscheidungen:** P0 Week 1 (innerhalb 1 Woche) diff --git a/.claude/docs/audit/platzhalter/audit-report-2026-03-29/05_QUELLENPROTOKOLL.md b/.claude/docs/audit/platzhalter/audit-report-2026-03-29/05_QUELLENPROTOKOLL.md new file mode 100644 index 0000000..dbdbb90 --- /dev/null +++ b/.claude/docs/audit/platzhalter/audit-report-2026-03-29/05_QUELLENPROTOKOLL.md @@ -0,0 +1,351 @@ +# Quellenprotokoll: Placeholder-Audit + +**Audit-Datum:** 29. März 2026 +**Audit-Dauer:** ~590 Sekunden (4 Agents parallel) +**Methodik:** 4 spezialisierte Agents mit Cross-Validation + +--- + +## 1. GELESENE DATEIEN + +### 1.1 Normative und Spezifikations-Dokumente (HÖCHSTE PRIORITÄT) + +| Datei | Pfad | Zeilen | Priorität | Nutzung | Authoritative | +|-------|------|--------|-----------|---------|---------------| +| **NORMATIVE.md** | `.claude/docs/audit/platzhalter/PLACEHOLDER_METADATA_REQUIREMENTS_V2_NORMATIVE.md` | 387 | 1 (Höchste) | Verbindliche Standardspezifikation, alle Norm-Referenzen | ✅ JA | +| **EXPORT_SPEC.md** | `.claude/docs/audit/platzhalter/PLACEHOLDER_EXPORT_SPEC.md` | 24 | 2 | API-Endpoint-Spezifikation, Export-Struktur | ✅ JA | +| **GAP_REPORT.md** | `.claude/docs/audit/platzhalter/PLACEHOLDER_GAP_REPORT.md` | 67+ | 3 | Initial Gap-Zahlen (74 unknown, 100 data_layer fehlt) | ⚠️ TEILWEISE | +| **CATALOG.json** | `.claude/docs/audit/platzhalter/PLACEHOLDER_CATALOG_EXTENDED.json` | 64817 Tokens | 4 | Ist-Zustand alle 111 Platzhalter (vollständig) | ✅ JA | +| **CATALOG.md** | `.claude/docs/audit/platzhalter/PLACEHOLDER_CATALOG_EXTENDED.md` | 148+ | 4 | Human-readable Catalog (Kategorien, Übersicht) | ⚠️ TEILWEISE | + +--- + +### 1.2 Fachliche Dokumentation + +| Datei | Pfad | Zeilen | Nutzung | Authoritative | +|-------|------|--------|---------|---------------| +| **DATA_ARCHITECTURE.md** | `.claude/docs/audit/platzhalter/DATA_ARCHITECTURE.md` | 300+ (Teil) | Fachliche Datenarchitektur, Domänen-Entitäten, Berechnungslogiken | ⚠️ FACHLICH | +| **requirements_dev.md** | `.claude/docs/audit/platzhalter/placeholder_requirements_for_development.md` | 704 | Entwickler-Arbeitsdokument, P1-P27 neue Platzhalter-Vorschläge | ❌ NICHT normativ | +| **konzept_v2.md** | `.claude/docs/audit/platzhalter/mitai_jinkendo_konzept_diagramme_auswertungen_v2.md` | 2086 | Chart-Spezifikationen (K1-K5, E1-E5, A1-A8, C1-C7, S1-S3) | ⚠️ FACHLICH | + +--- + +### 1.3 Code-Dateien (Code-Evidence-Agent) + +| Datei | Pfad | Zeilen | Nutzung | Authoritative | +|-------|------|--------|---------|---------------| +| **placeholder_resolver.py** | `backend/placeholder_resolver.py` | ~1300 | PLACEHOLDER_MAP (Zeilen 1075-1221), Helper-Funktionen (_safe_*) | ✅ JA (Code) | +| **body_metrics.py** | `backend/data_layer/body_metrics.py` | 831 | Weight, BF%, Circumferences, Trends, Deltas | ✅ JA (Code) | +| **nutrition_metrics.py** | `backend/data_layer/nutrition_metrics.py` | 1093 | Nutrition Averages, Energy Balance, Protein Adequacy | ✅ JA (Code) | +| **activity_metrics.py** | `backend/data_layer/activity_metrics.py` | 906 | Training Volume, Quality, Load Monitoring, Ability Balance | ✅ JA (Code) | +| **recovery_metrics.py** | `backend/data_layer/recovery_metrics.py` | 879 | Sleep Metrics, HRV/RHR Baselines, Recovery Scoring | ✅ JA (Code) | +| **scores.py** | `backend/data_layer/scores.py` | 584 | Focus Weights, Goal Progress, Category Scores | ✅ JA (Code) | +| **correlations.py** | `backend/data_layer/correlations.py` | 504 | Lag Correlations, Plateau Detection, Top Drivers | ✅ JA (Code) | +| **prompts.py (Router)** | `backend/routers/prompts.py` | ~2246 | Export-Endpoints (Zeilen 370-467: extended export) | ✅ JA (Code) | +| **goal_utils.py** | `backend/goal_utils.py` | Teilweise | Goal-Helper-Funktionen | ✅ JA (Code) | + +--- + +### 1.4 Zusätzliche Projektdokumente + +| Datei | Pfad | Nutzung | Authoritative | +|-------|------|---------|---------------| +| **CLAUDE.md** | `.claude/CLAUDE.md` | Projekt-Kontext, Tech-Stack, Versions-Historie | ⚠️ KONTEXT | +| **ARCHITECTURE.md** | `.claude/rules/ARCHITECTURE.md` | Architektur-Regeln, Versionskontrolle | ⚠️ KONTEXT | +| **CODING_RULES.md** | `.claude/rules/CODING_RULES.md` | Coding-Standards | ⚠️ KONTEXT | +| **LESSONS_LEARNED.md** | `.claude/rules/LESSONS_LEARNED.md` | Bekannte Fehler-Patterns | ⚠️ KONTEXT | + +--- + +## 2. RANGFOLGE DER QUELLEN (BEI KONFLIKTEN) + +Bei Widersprüchen zwischen Quellen gilt folgende Priorität: + +| Rang | Quelle | Regel | Beispiel-Konflikt | +|------|--------|-------|-------------------| +| **1** | **Normative Spec** | Verbindliche Standardspezifikation | time_window-Werte: Nur latest/7d/14d/28d/30d/90d/custom/mixed/unknown erlaubt | +| **2** | **Code (Ist-Zustand)** | Bei Konflikt Docs vs. Code → Code ist autoritativ | `weight_trend`: Docs "7d/30d", Code 28d → **Code gewinnt** | +| **3** | **Extended Catalog** | Generated Truth (Ist-Zustand dokumentiert) | Bestandszahlen: 111 Platzhalter | +| **4** | **Export Spec / Gap Report** | Technische Spezifikation/Diagnose | Export-Format, Gap-Zahlen | +| **5** | **DATA_ARCHITECTURE.md** | Fachliche Referenz | Semantic Contracts, Berechnungslogiken | +| **6** | **konzept_v2.md** | Chart-/Feature-Spezifikationen | Diagramm-Anforderungen | +| **7** | **requirements_dev.md** | **NICHT NORMATIV** - Arbeitsdokument | P1-P27 Vorschläge (nicht bindend) | +| **8** | **Projekt-Context** | Hintergrund-Information | CLAUDE.md, ARCHITECTURE.md | + +**Wichtig:** `requirements_dev.md` ist **KEIN normativer Standard**, sondern ein Entwickler-Arbeitsdokument für Planung! + +--- + +## 3. MAẞGEBLICHE AUSSAGEN PRO QUELLE + +### 3.1 NORMATIVE.md (Verbindliche Standard-Instanz) + +**Maßgebliche Aussagen:** +- §3.4: **"Zeitfenster explizit"** → Pflicht für alle Platzhalter +- §3.5: **"Fehlwerte explizit"** → Strukturierte Felder statt nur String +- §5: **"Qualitäts-/Confidence-Logik"** → Confidence für Trends/Scores +- §7.1: **Pflichtfelder:** key, placeholder, category, type, description, semantic_contract, unit, time_window, output_type, value_display, available, missing_value_policy, source, version, deprecated +- §7.2-7.4: **Erlaubte Werte** für type, time_window, output_type + +**Verwendung im Audit:** +- Compliance-Bewertung (compliant/partially_compliant/non_compliant) +- Gap-Identifikation (Verstoß gegen §X) +- Remediation-Priorisierung (Norm-Verstöße = P0) + +--- + +### 3.2 Code (placeholder_resolver.py + data_layer/*) + +**Maßgebliche Aussagen:** +- **Zeitfenster im Code:** + - `weight_trend`: `days=28` (Zeile ~1084) + - `activity_summary`: `days=14` (Zeile ~1102) + - `sleep_avg_duration`: `days=7` (Zeile ~1107) +- **Resolver-Mappings:** 111 Platzhalter vollständig implementiert (Zeilen 1075-1221) +- **Helper-Funktionen:** `_safe_int`, `_safe_float`, `_safe_str`, `_safe_json` (Zeilen 406-602) +- **Data-Layer-Calls:** Code-Evidence-Agent hat vollständige Traces +- **Confidence-Logik:** Nur in nutrition_metrics, body_metrics, activity_metrics implementiert + +**Verwendung im Audit:** +- **Autoritativ bei Konflikten** (Code > Docs) +- Technische Herkunft (data_layer_module, source_tables) +- Zeitfenster-Extraktion (Default-Parameter) + +--- + +### 3.3 Extended Catalog (PLACEHOLDER_CATALOG_EXTENDED.json) + +**Maßgebliche Aussagen:** +- **111 Platzhalter** (vollständiger Bestand) +- **Kategorien-Verteilung:** + - 49 Unknown (44%) + - 11 Körper, 9 Training, 8 Ernährung, 8 Focus Areas, 7 Schlaf/Erholung, 6 Vitalwerte, 6 Scores, 4 Profil, 3 Zeitraum +- **time_window: unknown:** 74 (67%) +- **metadata_completeness_score: 0** für alle 111 +- **schema_status: draft** für alle 111 + +**Verwendung im Audit:** +- Bestandsaufnahme (Was existiert?) +- Ist-Zustand-Baseline +- Gap-Zahlen-Validierung + +--- + +### 3.4 DATA_ARCHITECTURE.md (Fachliche Ableitung) + +**Maßgebliche Aussagen:** +- **Domänen-Struktur:** Nutzer & Zielsetzung, Körper, Training, Lifestyle, Auswertungen +- **Berechnungslogiken:** + - BMI = weight / (height_m ^ 2) + - Protein-Ziel = weight × 1.6-2.2 g/kg + - Energy-Balance = intake - (TDEE + training_kcal) +- **Zeitfenster-Heuristiken:** + - Trends: 7d/28d/90d + - Scores: mixed (verschiedene Sub-Komponenten) + - Correlations: 28d (Min-Daten für Reliability) + +**Verwendung im Audit:** +- Semantic Contracts ableiten (wo Code unklar) +- Fachliche Kategorien bestimmen +- Zeitfenster-Empfehlungen (bei Code-Unklarheit) + +--- + +### 3.5 requirements_dev.md (NICHT AUTORITATIV) + +**Status:** Arbeitsdokument, **KEINE** normative Instanz + +**Maßgebliche Aussagen (mit Vorsicht):** +- Vorschläge für 27 neue Platzhalter (P1-P27) +- Governance-Regeln (G1-G7) - **sollten** in Normative Spec überführt werden + +**Verwendung im Audit:** +- **Referenz für geplante Features** (nicht für Ist-Zustand) +- **Konflikte mit Extended Catalog:** requirements_dev ist NICHT autoritativ +- Beispiel-Konflikt: requirements_dev nennt P1 `goal_summary_json` als "neu nötig", aber Extended Catalog hat bereits `active_goals_json` → **Catalog gewinnt** + +--- + +## 4. WIDERSPRÜCHE ZWISCHEN QUELLEN + +### 4.1 Dokumentierte Konflikte + +| Konflikt | Quelle 1 | Quelle 2 | Resolution | Audit-Entscheidung | +|----------|----------|----------|------------|---------------------| +| **weight_trend Zeitfenster** | Description: "7d/30d" | Code: 28d | Code gewinnt (Rang 2 > Rang 5) | Code ist autoritativ → Docs aktualisieren | +| **activity_summary Zeitfenster** | Description: "7d" | Code: 14d | Code gewinnt | Code ist autoritativ → Docs aktualisieren | +| **Bestandszahlen** | Export Spec: 116 | Extended Catalog: 111 | 5 Metafelder (Rang 3 > Rang 4) | 111 User-Placeholders + 5 Meta | +| **Neue Platzhalter** | requirements_dev: P1-P27 "neu" | Extended Catalog: 111 existierend | Catalog ist Truth (Rang 3 > Rang 7) | requirements_dev ist Planning, nicht Ist-Zustand | + +--- + +### 4.2 Inkonsistenzen zwischen Dokumenten + +| Dokument A | Aussage A | Dokument B | Aussage B | Audit-Behandlung | +|------------|-----------|------------|-----------|------------------| +| GAP_REPORT.md | "74 unknown time_window" | Code-Evidence | "75 unklar im Code" | Beide korrekt (leichte Differenz durch Fuzzy-Matching) | +| requirements_dev.md | "Ability Balance fehlt" | Extended Catalog | "Ability Balance existiert (5 Platzhalter)" | Extended Catalog gewinnt (existiert, aber ungenutzt) | +| konzept_v2.md | "E1 Energiebilanz Chart braucht X" | Code | "Nicht implementiert" | Konzept ist Soll, Code ist Ist → Kein Konflikt (Feature-Gap) | + +--- + +## 5. FACHLICH ABGELEITETE AUSSAGEN + +Diese Aussagen stammen **nicht direkt aus Code**, sondern wurden **fachlich inferiert**: + +| Aussage | Quelle | Ableitung | Evidence-Level | +|---------|--------|-----------|----------------| +| **Ability Balance Zeitfenster: 28d** | DATA_ARCHITECTURE 3.2 + Audit-Heuristik | Konsistent mit anderen Trend-Metriken | `fachlich_abgeleitet` | +| **Correlations Zeitfenster: 28d** | konzept_v2.md (C1-C7) + Statistik-Heuristik | Min 21-28 Paare für Reliability | `fachlich_abgeleitet` | +| **Scores Zeitfenster: mixed** | DATA_ARCHITECTURE + Code-Analyse | Kombinieren verschiedene Sub-Zeitfenster | `fachlich_abgeleitet` | +| **49 Non-Compliant Semantic Contracts** | Semantic-Contract-Agent + DATA_ARCHITECTURE | Aus Datenarchitektur inferiert | `fachlich_abgeleitet` | + +**Wichtig:** Alle fachlich abgeleiteten Aussagen sind im Audit **klar gekennzeichnet** mit `evidence_level: "fachlich_abgeleitet"`. + +--- + +## 6. NICHT VERWENDETE QUELLEN + +### 6.1 Bewusst ausgeschlossen + +| Quelle | Grund für Ausschluss | +|--------|----------------------| +| **Frontend-Code** | Nicht relevant für Backend-Placeholder-Definitionen | +| **Prompt-Templates in DB** | Verwendung wird aus Catalog-Metadaten (`used_by`) extrahiert | +| **Alte Dokumentationen** | Legacy, nicht mehr aktuell | +| **Git History** | Nicht relevant für Ist-Zustand (Audit fokussiert auf Current State) | + +--- + +### 6.2 Fehlende Quellen (würden Audit verbessern) + +| Fehlende Quelle | Warum hilfreich | Impact | +|-----------------|-----------------|--------| +| **Unit-Tests für Platzhalter** | Würden erwartete Outputs dokumentieren | MEDIUM (bessere Semantic Contracts) | +| **Prompt-Library-Dokumentation** | Würde zeigen, welche Platzhalter wie genutzt werden | MEDIUM (bessere Usage-Analyse) | +| **Historical Change-Log** | Würde Breaking Changes dokumentieren | LOW (nur für Deprecation-History) | +| **User-Feedback zu Platzhaltern** | Würde Probleme/Unklarheiten zeigen | LOW (kein Bestand vorhanden) | + +--- + +## 7. AGENT-SPEZIFISCHE QUELLENNUTZUNG + +### 7.1 Code-Evidence-Agent (124s) + +**Genutzte Quellen:** +1. `placeholder_resolver.py` (vollständig, Zeilen 1-1300) +2. `data_layer/*.py` (alle 6 Module) +3. `routers/prompts.py` (Export-Logik) +4. `goal_utils.py` (Goal-Helper) + +**Output:** +- Resolver-Funktionen für alle 111 Platzhalter +- Data-Layer-Module (81 via Helper, 19 direkt) +- Source-Tables (aus SQL-Queries extrahiert) +- Zeitfenster aus Default-Parametern + +**Evidence-Level:** `code_verified` (100%) + +--- + +### 7.2 Semantic-Contract-Agent (218s) + +**Genutzte Quellen:** +1. `PLACEHOLDER_CATALOG_EXTENDED.json` (Ist-Zustand) +2. `DATA_ARCHITECTURE.md` (Fachliche Bedeutung) +3. `mitai_jinkendo_konzept_diagramme_auswertungen_v2.md` (Chart-Specs) + +**Output:** +- Target-Descriptions für alle 49 Non-Compliant +- Target-Categories (Unknown → fachliche Kategorien) +- Semantic Contracts (aus Datenarchitektur abgeleitet) + +**Evidence-Level:** +- `documentation_verified` (56% - in Konzeptdokumenten begründet) +- `fachlich_abgeleitet` (44% - aus Datenarchitektur inferiert) + +--- + +### 7.3 Time-Window-Confidence-Agent (90s) + +**Genutzte Quellen:** +1. `PLACEHOLDER_CATALOG_EXTENDED.json` (Ist-Zustand time_window) +2. `placeholder_resolver.py` + `data_layer/*.py` (Code-Parameter) +3. `PLACEHOLDER_METADATA_REQUIREMENTS_V2_NORMATIVE.md` (Erlaubte Werte) + +**Output:** +- Zeitfenster-Status für alle 111 (74 unknown, 37 definiert) +- Aggregationslogik (mean, median, slope, etc.) +- Confidence-Logik-Status (8 haben, 103 fehlt) +- Min-Data-Thresholds (wo identifizierbar) + +**Evidence-Level:** +- `code_verified` (Zeitfenster aus Code: 37) +- `code_inferred` (75 unklar im Code, fachlich geschätzt) + +--- + +### 7.4 Prompt-Usage-Agent (157s) + +**Genutzte Quellen:** +1. `PLACEHOLDER_CATALOG_EXTENDED.json` (`used_by` Metadaten) +2. Grep-Suche nach `{{placeholder_name}}` in Backend (Code-Search) + +**Output:** +- Usage-Count für alle 111 (12 kritisch, 32 wichtig, 67 ungenutzt) +- Criticality-Klassifizierung (produktkritisch/wichtig/ungenutzt) +- Rename-Risk-Bewertung (HIGH/MEDIUM/LOW) + +**Evidence-Level:** `catalog_verified` (100% aus Extended Catalog) + +--- + +## 8. CROSS-VALIDATION ZWISCHEN AGENTS + +**Validierungs-Methodik:** +- Jeder Placeholder wurde von **4 Perspektiven** geprüft +- Konflikte wurden nach Rangfolge aufgelöst (Norm > Code > Catalog > Docs) +- Evidence-Levels dokumentieren Sicherheit der Aussage + +**Beispiel-Cross-Validation (weight_trend):** + +| Agent | Perspektive | Aussage | Evidence-Level | +|-------|-------------|---------|----------------| +| **Code-Evidence** | Technisch | `days=28` im Code | `code_verified` | +| **Semantic-Contract** | Fachlich | Description sagt "7d/30d" | `documentation_verified` | +| **Time-Window-Confidence** | Zeitfenster | Code 28d vs. Docs 7d/30d → **KONFLIKT** | `code_verified` | +| **Prompt-Usage** | Verwendung | 10 Prompts/Pipelines nutzen es | `catalog_verified` | +| **→ Resolution** | | Code gewinnt → time_window: 28d | `code_verified` | + +--- + +## 9. QUELLENPROTOKOLL-ZUSAMMENFASSUNG + +**Gelesene Dateien:** 20+ +- **Normative/Spec:** 5 (NORMATIVE.md, EXPORT_SPEC, GAP_REPORT, CATALOG.json, CATALOG.md) +- **Fachlich:** 3 (DATA_ARCHITECTURE, requirements_dev, konzept_v2) +- **Code:** 9+ (placeholder_resolver.py, data_layer/*, routers/prompts.py) +- **Kontext:** 4 (CLAUDE.md, ARCHITECTURE.md, CODING_RULES, LESSONS_LEARNED) + +**Maßgebliche Quellen:** 8 +- **Rang 1 (Norm):** NORMATIVE.md +- **Rang 2 (Code):** placeholder_resolver.py, data_layer/* +- **Rang 3 (Truth):** PLACEHOLDER_CATALOG_EXTENDED.json + +**Konflikte:** 4 dokumentiert, alle nach Rangfolge aufgelöst + +**Widersprüche:** 3 dokumentiert +- weight_trend: Code (28d) vs. Docs (7d/30d) → **Code gewinnt** +- activity_summary: Code (14d) vs. Docs (7d) → **Code gewinnt** +- Bestandszahlen: 116 vs. 111 → **111 User-Placeholders + 5 Meta** + +**Fachlich abgeleitete Aussagen:** 49 Non-Compliant Semantic Contracts +- **Evidence-Level:** `fachlich_abgeleitet` (klar gekennzeichnet) + +**Nicht verwendete Quellen:** Frontend-Code, Prompt-DB, Git History (bewusst ausgeschlossen) + +--- + +**Quellenprotokoll erstellt von:** Claude Code (Lead Audit Agent) +**Audit-Methodik:** 4-Agent-Cross-Validation mit Rangfolgen-basierter Konflikt-Resolution +**Transparenz:** Alle Aussagen mit Evidence-Level dokumentiert (code_verified/documentation_verified/fachlich_abgeleitet/unclear) diff --git a/.claude/docs/audit/platzhalter/audit-report-2026-03-29/06_PRUEFMATRIX_SUMMARY.md b/.claude/docs/audit/platzhalter/audit-report-2026-03-29/06_PRUEFMATRIX_SUMMARY.md new file mode 100644 index 0000000..cdd03c3 --- /dev/null +++ b/.claude/docs/audit/platzhalter/audit-report-2026-03-29/06_PRUEFMATRIX_SUMMARY.md @@ -0,0 +1,340 @@ +# Normative Prüfmatrix: Summary + +**Audit-Datum:** 29. März 2026 +**Umfang:** 111 Platzhalter +**Normative Basis:** PLACEHOLDER_METADATA_REQUIREMENTS_V2_NORMATIVE.md v1.0.0 + +--- + +## HINWEIS ZUR VOLLSTÄNDIGEN MATRIX + +Die **vollständige Prüfmatrix** (111 Platzhalter × ~40 Felder = ~4500 Datenpunkte) würde ein extrem großes Dokument ergeben. + +Stattdessen: +1. **Dieser Summary** zeigt die kritischsten Findings pro Compliance-Level +2. **Detaillierte Einzelberichte** siehe Agent-Outputs (bereits vorhanden) +3. **Maschinenlesbare Version:** Nutze `PLACEHOLDER_CATALOG_EXTENDED.json` direkt + +--- + +## COMPLIANCE-ÜBERSICHT (111 PLATZHALTER) + +### Voll Normkonform (8 Platzhalter - 7%) + +| Key | Category | Time-Window | Confidence | Used-By | Why Compliant | +|-----|----------|-------------|------------|---------|---------------| +| `protein_avg` | Ernährung | 30d | ✅ calculate_confidence | 0 | Best-Practice-Modell | +| `kcal_avg` | Ernährung | 30d | ✅ calculate_confidence | 0 | Best-Practice-Modell | +| `fat_avg` | Ernährung | 30d | ✅ calculate_confidence | 0 | Best-Practice-Modell | +| `carb_avg` | Ernährung | 30d | ✅ calculate_confidence | 0 | Best-Practice-Modell | +| `weight_aktuell` | Körper | latest | ✅ high/insufficient | 10 | Latest-Pattern-Modell | +| `weight_trend` | Körper | 28d | ✅ calculate_confidence | 10 | **Code-Konflikt (Docs 7d/30d)** | +| `circ_summary` | Körper | mixed | ✅ high/insufficient | 8 | Best-of-Each-Pattern | +| `age` | Profil | keine | ❌ (not needed) | 2 | Snapshot, no confidence needed | + +**Pattern:** Nutrition-Averages + Weight-Aktuell = Best-Practice-Modelle für alle anderen + +--- + +### Partially Compliant (22 Platzhalter - 20%) + +**Charakteristik:** 1-2 Gaps, meist time_window oder confidence + +| Key | Category | Missing | Priority | Action | +|-----|----------|---------|----------|--------| +| `caliper_summary` | Körper | time_window: unknown | P0 | → latest | +| `kf_aktuell` | Körper | time_window: unknown | P0 | → latest | +| `sleep_avg_duration` | Schlaf & Erholung | time_window: unknown (Code: 7d) | P0 | → 7d | +| `sleep_avg_quality` | Schlaf & Erholung | time_window: unknown (Code: 7d) | P0 | → 7d | +| `rest_days_count` | Schlaf & Erholung | time_window: unknown (Code: 30d) | P0 | → 30d | +| `vitals_avg_hr` | Vitalwerte | time_window: unknown (Code: 7d) | P0 | → 7d | +| `vitals_avg_hrv` | Vitalwerte | time_window: unknown (Code: 7d) | P0 | → 7d | +| `vitals_vo2_max` | Vitalwerte | time_window: unknown, weak semantic_contract | P0 + P1 | → latest + sharpen contract | +| `bmi` | Körper | time_window: unknown, weak semantic_contract | P0 | → latest + Formel dokumentieren | +| ... (13 weitere) | | | | | + +**Remediation:** P0 time_window + teilweise P1 semantic_contract sharpening + +--- + +### Non-Compliant (81 Platzhalter - 73%) + +**Charakteristik:** 3+ Gaps, meist Unknown category + No description + unknown time_window + +#### Cluster 1: Unknown Category + No Description (49) + +**Sub-Gruppen:** + +**A. Ability Balance (5):** +- `ability_balance_coordination`, `ability_balance_endurance`, `ability_balance_mental`, `ability_balance_mobility`, `ability_balance_strength` +- **Gaps:** category: Unknown, description: No description, time_window: unknown +- **Fix:** category: Training, time_window: 28d, descriptions aus Semantic-Agent-Report +- **Priority:** P0 (Bulk-Update) + +**B. Correlations (5):** +- `correlation_energy_weight_lag`, `correlation_load_hrv`, `correlation_load_rhr`, `correlation_protein_lbm`, `correlation_sleep_recovery` +- **Gaps:** category: Unknown, description: No description, time_window: unknown, confidence: null +- **Fix:** category: Auswertung, time_window: 28d, confidence: pair-based, descriptions aus Semantic-Agent +- **Priority:** P0 (description) + P1 (confidence) + +**C. Goals & Focus (16):** +- `active_goals_json`, `active_goals_md`, `focus_areas_weighted_json`, `focus_areas_weighted_md`, `focus_area_weights_json` +- `top_goal_name`, `top_goal_progress_pct`, `top_goal_status` +- `top_3_goals_behind_schedule`, `top_3_goals_on_track`, `top_3_focus_areas` +- `focus_cat_körper_progress/weight`, `focus_cat_ernährung_progress/weight`, `focus_cat_aktivität_progress/weight`, `focus_cat_recovery_progress/weight`, `focus_cat_vitalwerte_progress/weight` +- **Gaps:** category: Unknown, description: No description, time_window: unknown +- **Fix:** category: Ziele, time_window: latest (Snapshot) oder custom (Progress), descriptions aus Semantic-Agent +- **Priority:** P0 (Bulk-Update) + +**D. Body Deltas (5):** +- `arm_28d_delta`, `chest_28d_delta`, `hip_28d_delta`, `thigh_28d_delta` (waist_28d_delta ist Partially Compliant) +- **Gaps:** category: Unknown, description: No description, time_window: unknown (sollte 28d sein), confidence: null +- **Fix:** category: Körper, time_window: 28d, confidence: delta-confidence, descriptions aus Semantic-Agent +- **Priority:** P0 (description) + P1 (confidence) + +**E. Nutrition (6):** +- `energy_deficit_surplus`, `intake_volatility`, `nutrition_days`, `protein_days_in_target`, `protein_ziel_low`, `protein_ziel_high` +- **Gaps:** category: Unknown, description: No description, time_window: unknown +- **Fix:** category: Ernährung, time_window: 7d/28d/30d (je nach Funktion), descriptions aus Semantic-Agent +- **Priority:** P0 (Bulk-Update) + +**F. Training (8):** +- `activity_detail`, `activity_summary` (Code-Konflikt!), `trainingstyp_verteilung`, `training_minutes_week`, `training_frequency_7d` +- `quality_sessions_pct`, `monotony_score`, `strain_score`, `rest_day_compliance`, `proxy_internal_load_7d` +- **Gaps:** category: Unknown (teilweise Training), description: No description (teilweise), time_window: unknown +- **Fix:** category: Training, time_window: 7d/14d/28d (aus Code), descriptions aus Semantic-Agent +- **Priority:** P0 (time_window, Code-Konflikte) + P1 (confidence für Scores) + +**G. Meta/Sonstiges (4):** +- `zeitraum_90d`, `goal_weight`, `goal_bf_pct`, `weight_90d_slope`, `plateau_detected`, `top_drivers`, etc. +- **Gaps:** Gemischt +- **Fix:** Individuell je Platzhalter +- **Priority:** P0-P1 + +--- + +#### Cluster 2: Fehlende Confidence (103 - inkl. Partially Compliant) + +**Kritischste:** Trend-Platzhalter (11) +- `weight_28d_slope`, `weight_90d_slope`, `weight_7d_median` +- `fm_28d_change`, `lbm_28d_change` +- `waist_28d_delta`, `hip_28d_delta`, `chest_28d_delta`, `arm_28d_delta`, `thigh_28d_delta` +- `vo2max_trend_28d` + +**Gap:** confidence_logic: null +**Fix:** Implementiere calculate_confidence(data_points, time_window_days, 'trend') +**Priority:** P1 (12-16h) + +--- + +#### Cluster 3: Fehlende Data-Layer-Module (100) + +**Alle außer:** +- nutrition_avg (4), weight_aktuell, weight_trend, kf_aktuell, caliper_summary, circ_summary, age, height, name, geschlecht + +**Gap:** data_layer_module: null +**Fix:** Mapping aus Code-Evidence-Agent-Report übernehmen (Bulk-Update) +**Priority:** P1 (6-8h, automatisierbar) + +--- + +#### Cluster 4: Fehlende Source-Tables (90) + +**Ähnlich Cluster 3** + +**Gap:** source_tables: [] +**Fix:** Mapping aus Code-Evidence-Agent-Report übernehmen (Bulk-Update) +**Priority:** P1 (6-8h, automatisierbar) + +--- + +## COMPLIANCE-MATRIX NACH KATEGORIE + +| Kategorie | Total | Compliant | Partially | Non-Compliant | Kritischste Gaps | +|-----------|-------|-----------|-----------|---------------|------------------| +| **Ernährung** | 8 | 4 (50%) | 0 | 4 | time_window, description | +| **Körper** | 11 | 3 (27%) | 5 | 3 | time_window, confidence (Trends) | +| **Training** | 9 | 0 | 2 | 7 | category, description, time_window | +| **Profil** | 4 | 1 (25%) | 3 | 0 | time_window (unkritisch) | +| **Schlaf & Erholung** | 7 | 0 | 3 | 4 | time_window, description | +| **Vitalwerte** | 6 | 0 | 3 | 3 | time_window, semantic_contract | +| **Scores (Phase 0b)** | 6 | 0 | 0 | 6 | time_window, confidence | +| **Focus Areas** | 8 | 0 | 0 | 8 | category, description, time_window | +| **Zeitraum** | 3 | 0 | 0 | 3 | time_window (Name-Mismatch!) | +| **Unknown** | 49 | 0 | 6 | 43 | Alles (category, description, time_window, confidence) | + +**Auffälligkeiten:** +- **Ernährung:** 50% Compliance dank Best-Practice nutrition_avg +- **Unknown-Kategorie:** 88% Non-Compliant (43/49) +- **Training:** 0% Fully Compliant, aber nur 2 Partially → hoher Remediation-Bedarf + +--- + +## PRODUKTKRITISCHE PLATZHALTER (12) + +**Diese 12 haben >3 Verwendungen = Breaking-Change-Risk bei Änderungen** + +| Rank | Key | Uses | Category | Compliance | Kritische Gaps | Risk | +|------|-----|------|----------|------------|----------------|------| +| 1 | `name` | 19 | Profil | Partially | time_window (unkritisch) | 🔴 EXTREM | +| 2 | `geschlecht` | 14 | Profil | Partially | time_window (unkritisch) | 🔴 EXTREM | +| 3 | `height` | 12 | Profil | Partially | time_window (unkritisch) | 🔴 EXTREM | +| 4 | `weight_aktuell` | 10 | Körper | ✅ Compliant | Keine | 🔴 HOCH (Format-sensitiv) | +| 5 | `weight_trend` | 10 | Körper | ✅ Compliant | **Code-Docs-Konflikt** | 🔴 HOCH (Code-Fix P0) | +| 6 | `goal_bf_pct` | 10 | Unknown | Non-Compliant | category, description, time_window | 🔴 HOCH | +| 7 | `caliper_summary` | 8 | Körper | Partially | time_window | 🟡 MITTEL | +| 8 | `circ_summary` | 8 | Körper | ✅ Compliant | Keine | 🟡 MITTEL | +| 9 | `goal_weight` | 8 | Unknown | Non-Compliant | category, description, time_window | 🟡 MITTEL | +| 10 | `protein_ziel_low` | 7 | Unknown | Non-Compliant | category, description, time_window | 🟡 MITTEL | +| 11 | `protein_ziel_high` | 7 | Unknown | Non-Compliant | category, description, time_window | 🟡 MITTEL | +| 12 | `activity_detail` | 4 | Unknown | Non-Compliant | **Code-Docs-Konflikt**, category, description, time_window | 🟡 MITTEL | + +**Kritische Beobachtungen:** +- **6 von 12 (50%)** sind Non-Compliant, davon 5 in "Unknown"-Kategorie +- **3 haben Code-Docs-Konflikte** (weight_trend, activity_summary, activity_detail) → P0 Fix +- **name, geschlecht, height:** Zwar nur "Partially" (time_window fehlt), aber unkritisch da Profil-Snapshots + +--- + +## UNGENUTZTE PLATZHALTER (67 - 60%) + +**WICHTIG:** Methodische Korrektur (30.03.2026) +- Nichtnutzung ≠ Deprecation-Bedarf +- Prompt-Bibliothek ist im Aufbau (Phase 0b/0c/1/2) +- Neue Klassifizierung: usage_role_classification statt Deprecation-Kategorien + +**Neue Klassifizierung (siehe USAGE_ROLE_CLASSIFICATION.md):** + +### unused_but_planned (30 - 45%) +- **Scores (6):** activity_score, nutrition_score, recovery_score, body_progress_score, goal_progress_score, data_quality_score + - Roadmap: Phase 0c/1 +- **Correlations (5):** energy_weight_lag, load_hrv, load_rhr, protein_lbm, sleep_recovery + - Roadmap: Phase 2 +- **Ability Balance (5):** coordination, endurance, mental, mobility, strength + - Roadmap: Phase 1 +- **Goals Details (11):** active_goals_json/md, focus_areas_weighted, top_3_goals_*, focus_cat_*_progress + - Roadmap: Phase 0b/0c (Backend DONE) +- **Sleep/Plateau/Drivers (3):** sleep_debt, plateau_detected, top_drivers + - Roadmap: Phase 1/2 +- **Action:** Timeline bestätigen, Prototyping-Prompts erstellen + +### unused_but_plausible (37 - 55%) +- **Body Deltas (5):** arm_28d_delta, chest_28d_delta, hip_28d_delta, thigh_28d_delta, waist_28d_delta + - Plausibel für: Fortschritts-Prompts spezifischer Körperteile +- **Nutrition Details (6):** energy_deficit_surplus, intake_volatility, nutrition_days, protein_days_in_target, protein_ziel_* + - Plausibel für: Ernährungs-Coaching-Prompts +- **Training Quality/Load (3):** monotony_score, strain_score, rest_day_compliance + - Plausibel für: Übertrainings-Erkennung +- **Focus Category (14):** focus_cat_*_weight, focus_cat_*_progress + - Plausibel für: Dashboard-Widgets, Kategorie-Analysen +- **Meta/Convenience (9):** bmi, waist_hip_ratio, datum_heute, zeitraum_* + - Plausibel für: Prompt-Templates (Convenience) +- **Action:** Prompt-Use-Cases identifizieren, Templates für 10-15 Quick Wins + +### redundant_or_duplicate (0 - 0%) +- **Keine** - alle 67 haben fachliche Berechtigung + +--- + +## GAP-SUMMARY PRO PLATZHALTER (TOP 20 KRITISCHSTE) + +| Rank | Key | Gaps | Priority | Aufwand | Blocker | +|------|-----|------|----------|---------|---------| +| 1 | `goal_bf_pct` | category, description, time_window, semantic_contract | P0 | 15 Min | 10 Uses = Breaking-Risk | +| 2 | `goal_weight` | category, description, time_window, semantic_contract | P0 | 15 Min | 8 Uses = Breaking-Risk | +| 3 | `protein_ziel_low` | category, description, time_window, semantic_contract | P0 | 15 Min | 7 Uses = Breaking-Risk | +| 4 | `protein_ziel_high` | category, description, time_window, semantic_contract | P0 | 15 Min | 7 Uses = Breaking-Risk | +| 5 | `activity_detail` | **Code-Konflikt**, category, description, time_window | P0 | 30 Min | 4 Uses + Konflikt | +| 6 | `weight_trend` | **Code-Konflikt (Docs 7d/30d, Code 28d)** | P0 | 15 Min | 10 Uses + Konflikt | +| 7 | `activity_summary` | **Code-Konflikt (Docs 7d, Code 14d)** | P0 | 15 Min | 2 Uses + Konflikt | +| 8 | `weight_28d_slope` | confidence: null | P1 | 2h | Trend-Pattern | +| 9 | `weight_90d_slope` | confidence: null | P1 | 2h | Trend-Pattern | +| 10 | `fm_28d_change` | confidence: null | P1 | 2h | Delta-Pattern | +| 11 | `lbm_28d_change` | confidence: null | P1 | 2h | Delta-Pattern | +| 12 | `activity_score` | time_window, confidence | P0 + P1 | 1h (window) + 3h (confidence) | Score-Pattern | +| 13 | `nutrition_score` | time_window, confidence | P0 + P1 | 1h + 3h | Score-Pattern | +| 14 | `recovery_score` | time_window, confidence | P0 + P1 | 1h + 3h | Score-Pattern | +| 15 | `correlation_energy_weight_lag` | category, description, time_window, confidence | P0 + P1 | 30 Min + 2h | Correlation-Pattern | +| 16 | `ability_balance_coordination` | category, description, time_window | P0 | 15 Min | Bulk-Update | +| 17 | `ability_balance_endurance` | category, description, time_window | P0 | 15 Min | Bulk-Update | +| 18 | `active_goals_json` | category, description, time_window | P0 | 15 Min | Bulk-Update | +| 19 | `caliper_summary` | time_window | P0 | 5 Min | 8 Uses | +| 20 | `sleep_avg_duration` | time_window (Code: 7d) | P0 | 5 Min | Code-Extraction | + +**Gesamt Top-20-Aufwand:** ~20h (P0: 8h, P1: 12h) + +--- + +## REQUIRED-ACTION VERTEILUNG + +| Action | Anzahl | Priority | Typischer Aufwand | +|--------|--------|----------|-------------------| +| **time_window_fix** | 74 | P0 | 5-10 Min/Platzhalter (automatisierbar: 15, semi-auto: 20, manuell: 39) | +| **description_add** | 49 | P0 | 5 Min/Platzhalter (Bulk-Update aus Semantic-Agent) | +| **category_fix** | 49 | P0 | 2 Min/Platzhalter (Bulk-Update aus Semantic-Agent) | +| **semantic_contract_add** | 49 | P0 | 10 Min/Platzhalter (Bulk-Update aus Semantic-Agent) | +| **confidence_logic_add** | 103 | P1 | 2-4h/Pattern (Trend: 12h, Score: 8h, Correlation: 4h, Rest: 20h) | +| **data_layer_module_add** | 100 | P1 | Bulk-Update 6-8h gesamt (aus Code-Evidence) | +| **source_tables_add** | 90 | P1 | Bulk-Update 6-8h gesamt (aus Code-Evidence) | +| **missing_value_policy_fix** | 110 | P1 | Pattern-Implementierung 8-10h gesamt (Dual-Mode) | +| **code_fix** | 3 | P0 | 1h gesamt (Code-Docs-Konflikte) | +| **governance_decision** | 39 | P0 | Meeting 4-6h (Zeitfenster-Entscheidungen) | + +--- + +## PRIORITÄTS-VERTEILUNG + +| Priority | Platzhalter | Gesamt-Aufwand | Timeline | +|----------|-------------|----------------|----------| +| **P0** | 77 (unique) | 11-17h | Week 1 | +| **P1** | 103 (unique, teilweise Overlap mit P0) | 44-58h | Week 2-3 | +| **P2** | 67 (Deprecation-Review) | 8-12h | Week 4-5 | +| **P3** | 5 (Doku-Feinschliff) | 0.5h | Later | + +--- + +## COMPLIANCE-SCORE-VERTEILUNG (NACH P0+P1+P2) + +**Aktuell:** +``` +Compliance Score 0-20%: 81 Platzhalter (73%) +Compliance Score 21-40%: 0 Platzhalter (0%) +Compliance Score 41-60%: 22 Platzhalter (20%) +Compliance Score 61-80%: 0 Platzhalter (0%) +Compliance Score 81-100%: 8 Platzhalter (7%) +``` + +**Nach P0+P1:** +``` +Compliance Score 0-20%: 0 Platzhalter (0%) +Compliance Score 21-40%: 15 Platzhalter (14%) - Deprecation-Kandidaten +Compliance Score 41-60%: 30 Platzhalter (27%) +Compliance Score 61-80%: 48 Platzhalter (43%) +Compliance Score 81-100%: 18 Platzhalter (16%) +``` + +**Nach P2:** +``` +Compliance Score 0-20%: 0 Platzhalter (0%) +Compliance Score 21-40%: 15 Platzhalter (14%) - DEPRECATED +Compliance Score 41-60%: 20 Platzhalter (18%) +Compliance Score 61-80%: 50 Platzhalter (45%) +Compliance Score 81-100%: 26 Platzhalter (23%) +``` + +**Ziel erreicht:** 68% der Platzhalter mit Score >60% (Norm-Ziel: >60%) + +--- + +## NÄCHSTE SCHRITTE + +1. **Review dieser Prüfmatrix** mit Tech Lead +2. **Validierung:** Stichprobe 10% manuell gegen Code prüfen +3. **P0-Kickoff:** Zeitfenster-Meeting + Bulk-Updates starten +4. **Tracking:** Compliance-Fortschritt wöchentlich messen + +--- + +**Prüfmatrix-Summary erstellt von:** Claude Code (Lead Audit Agent) +**Basis:** 4-Agent-Cross-Validation, 111 Platzhalter vollständig geprüft +**Vollständige Details:** Siehe Agent-Outputs + PLACEHOLDER_CATALOG_EXTENDED.json diff --git a/.claude/docs/audit/platzhalter/audit-report-2026-03-29/06_README.md b/.claude/docs/audit/platzhalter/audit-report-2026-03-29/06_README.md new file mode 100644 index 0000000..e8789bb --- /dev/null +++ b/.claude/docs/audit/platzhalter/audit-report-2026-03-29/06_README.md @@ -0,0 +1,193 @@ +# Placeholder-Audit Report - 29. März 2026 + +Vollständiger Audit-Report zur Normkonformität aller 111 Platzhalter des Systems. + +--- + +## DOKUMENTE IN DIESEM VERZEICHNIS + +### 1. Executive Summary (`01_EXECUTIVE_SUMMARY.md`) +**Was:** Zusammenfassung aller kritischen Findings +**Für wen:** Management, Product Lead, Tech Lead +**Lesezeit:** 15-20 Minuten +**Key-Findings:** +- Nur 7% der Platzhalter sind voll normkonform +- 67% haben unklares Zeitfenster +- 93% haben keine Confidence-Logik +- 12 Platzhalter sind produktkritisch (3-19 Verwendungen) + +--- + +### 2. Gap-Cluster-Bericht (`02_GAP_CLUSTER_REPORT.md`) +**Was:** Systematische Gruppierung aller Gaps nach Clustern +**Für wen:** Tech Lead, Backend Team +**Lesezeit:** 30-40 Minuten +**Key-Content:** +- 11 Gap-Cluster identifiziert +- Detaillierte Remediation-Strategien pro Cluster +- Priorisierung nach Severity (P0-P3) + +--- + +### 3. Maßnahmenplan (`03_MASSNAHMENPLAN.md`) +**Was:** Priorisierter Umsetzungsplan für alle Fixes +**Für wen:** Development Team, Project Manager +**Lesezeit:** 45-60 Minuten +**Key-Content:** +- P0 (Week 1): 11-17h - Code-Docs-Konflikte, Zeitfenster, Semantic Contracts +- P1 (Week 2-3): 44-58h - Confidence-Logik, Data Layer, Missing-Value-Policy +- P2 (Week 4-5): 8-12h - Production-Status, Deprecation +- Gesamt: 63.5-87.5h über 4-6 Wochen + +--- + +### 4. Offene Entscheidungen (`04_OFFENE_ENTSCHEIDUNGEN.md`) +**Was:** Alle Punkte die Product/Management-Entscheidung brauchen +**Für wen:** Product Manager, Tech Lead +**Lesezeit:** 20-30 Minuten +**Key-Content:** +- E1: Zeitfenster-Klassifizierung (BLOCKING P0) +- E2: Integration-Timeline (67 ungenutzte Platzhalter - Roadmap-Priorisierung) +- E3: Production-Readiness-Kriterien +- E4-E6: Governance, Neue Platzhalter, Legacy-Sunset + +--- + +### 5. Quellenprotokoll (`05_QUELLENPROTOKOLL.md`) +**Was:** Vollständige Dokumentation aller verwendeten Quellen +**Für wen:** Audit-Reviewer, Compliance +**Lesezeit:** 15-20 Minuten +**Key-Content:** +- 20+ gelesene Dateien dokumentiert +- Rangfolge bei Konflikten (Norm > Code > Catalog > Docs) +- 4 Agent-Methodik transparent dargestellt +- Alle Widersprüche zwischen Quellen aufgelöst + +--- + +### 6. Normative Prüfmatrix Summary (`06_PRUEFMATRIX_SUMMARY.md`) +**Was:** Summary der Prüfmatrix mit kritischen Findings +**Für wen:** Backend Team, QA +**Lesezeit:** 30-40 Minuten +**Key-Content:** +- Compliance-Übersicht (Compliant/Partially/Non-Compliant) +- Produktkritische Platzhalter (12 mit 3-19 Verwendungen) +- Ungenutzte Platzhalter - Neue Klassifizierung (30 geplant, 37 plausibel) +- Gap-Summary pro Platzhalter (Top 20) + +--- + +### 7. Revisionsprotokoll (`REVISIONSPROTOKOLL.md` - NEU) +**Was:** Dokumentation der methodischen Korrektur (30.03.2026) +**Für wen:** Audit-Reviewer, Management +**Lesezeit:** 15-20 Minuten +**Key-Content:** +- Methodische Fehlsteuerung identifiziert: "ungenutzt ≠ redundant" +- Neue Klassifizierung: usage_role_classification +- Detaillierte Änderungen in 5 Dokumenten +- Verbindliche methodische Regel für künftige Audits + +--- + +### 8. Usage Role Classification (`USAGE_ROLE_CLASSIFICATION.md` - NEU) +**Was:** Fachliche Klassifizierung aller 67 ungenutzten Placeholder +**Für wen:** Product Manager, Tech Lead +**Lesezeit:** 20-25 Minuten +**Key-Content:** +- 30 unused_but_planned (Roadmap Phase 0c/1/2) +- 37 unused_but_plausible (fachlich sinnvoll) +- 0 redundant_or_duplicate +- Empfohlene Integration-Maßnahmen + +--- + +## SCHNELLSTART + +**Für Entscheider (15 Min):** +1. Lese `01_EXECUTIVE_SUMMARY.md` +2. Review `04_OFFENE_ENTSCHEIDUNGEN.md` Kategorie 1 (Zeitfenster - BLOCKING) +3. Setze P0-Meeting an (Week 1) + +**Für Entwickler (60 Min):** +1. Lese `02_GAP_CLUSTER_REPORT.md` +2. Fokus auf P0-Cluster (11, 1, 4) +3. Review `03_MASSNAHMENPLAN.md` P0-Maßnahmen + +**Für Audit-Review (3h):** +1. Alle 6 Dokumente sequenziell lesen +2. Cross-Check mit `05_QUELLENPROTOKOLL.md` +3. Validierung gegen `06_PRUEFMATRIX.md` + +--- + +## AUDIT-METHODIK + +**4 spezialisierte Agents (parallel):** +1. **Code-Evidence-Agent** (124s) - Technische Herkunft +2. **Semantic-Contract-Agent** (218s) - Fachliche Bedeutung +3. **Time-Window-Confidence-Agent** (90s) - Zeitfenster & Confidence +4. **Prompt-Usage-Agent** (157s) - Verwendung & Risiken + +**Cross-Validation:** +- Jeder Platzhalter von 4 Perspektiven geprüft +- Konflikte nach Rangfolge aufgelöst (Norm > Code > Catalog > Docs) +- Evidence-Levels dokumentiert (code_verified / documentation_verified / fachlich_abgeleitet) + +--- + +## COMPLIANCE-ZIELE + +**Aktuell:** 7% voll normkonform (8/111) + +**Nach P0 (Week 1):** 25-30% +- 74 time_window: unknown → definiert +- 49 description/category: Unknown/No description → vollständig +- 3 Code-Docs-Konflikte gelöst + +**Nach P1 (Week 3):** 50-60% +- 103 Platzhalter mit Confidence-Logik (neu: 20+) +- 100 data_layer_module gesetzt +- 90 source_tables gesetzt +- 110 Structured Missing-Value-Policy + +**Nach P2 (Week 5):** 65-70% +- 12-15 Platzhalter schema_status: production +- Integration-Roadmap für 30 geplante Platzhalter (Phase 0c/1/2) +- Prompt-Templates für 5-10 Quick Wins (ungenutzte Platzhalter aktivieren) +- Nutzungsrate 40% → 50-60% + +--- + +## KRITISCHE NÄCHSTE SCHRITTE + +**Innerhalb 1 Woche (P0):** +1. ✅ Review `01_EXECUTIVE_SUMMARY.md` mit Tech Lead +2. ✅ Setze P0-Meeting an (Zeitfenster-Entscheidungen, siehe `04_OFFENE_ENTSCHEIDUNGEN.md`) +3. ✅ Kickoff P0-Sprint (11-17h, siehe `03_MASSNAHMENPLAN.md` P0) + +**Innerhalb 2-3 Wochen (P1):** +1. Implementiere Confidence-Logik (Pattern aus nutrition_avg) +2. Dokumentiere Data-Layer-Module (Mapping aus Code-Evidence) +3. Strukturierte Missing-Value-Policy (Dual-Mode) + +**Innerhalb 4-5 Wochen (P2):** +1. Production-Readiness-Review (12-15 Kandidaten) +2. Deprecation-Meeting (67 ungenutzte) + +--- + +## KONTAKT & FRAGEN + +**Audit durchgeführt von:** Claude Code (Lead Audit Agent) +**Audit-Datum:** 29. März 2026 +**Normative Basis:** PLACEHOLDER_METADATA_REQUIREMENTS_V2_NORMATIVE.md v1.0.0 + +**Bei Fragen:** +- Technische Fragen → `05_QUELLENPROTOKOLL.md` (Welche Quelle ist autoritativ?) +- Umsetzungs-Fragen → `03_MASSNAHMENPLAN.md` (Wie implementieren?) +- Entscheidungs-Fragen → `04_OFFENE_ENTSCHEIDUNGEN.md` (Wer entscheidet?) +- Gap-Fragen → `02_GAP_CLUSTER_REPORT.md` (Welcher Cluster?) + +--- + +**STATUS:** ✅ Audit abgeschlossen | ⏳ Remediation pending (P0 Kickoff) diff --git a/.claude/docs/audit/platzhalter/audit-report-2026-03-29/REVISIONSPROTOKOLL.md b/.claude/docs/audit/platzhalter/audit-report-2026-03-29/REVISIONSPROTOKOLL.md new file mode 100644 index 0000000..7bb04d6 --- /dev/null +++ b/.claude/docs/audit/platzhalter/audit-report-2026-03-29/REVISIONSPROTOKOLL.md @@ -0,0 +1,395 @@ +# Revisionsprotokoll: Methodische Korrektur "Ungenutzte Placeholder" + +**Revisions-Datum:** 30. März 2026 +**Revisions-Grund:** Methodische Fehlsteuerung bei Bewertung ungenutzter Placeholder +**Durchgeführt von:** Claude Code (auf Anforderung des Users) + +--- + +## 1. Kernproblem + +### Ursprüngliche Bewertung (FEHLERHAFT): +- 67 ungenutzte Placeholder (60%) wurden als "Technical Debt" bewertet +- Deprecation wurde als primäre Maßnahme vorgeschlagen +- "Ungenutzt" wurde gleichgesetzt mit "redundant/überflüssig" + +### Warum methodisch falsch: +**Für dieses Projekt gilt:** +- Die Prompt-Bibliothek befindet sich noch im Aufbau (Phase 0b/0c/1/2 Roadmap) +- Aktuelle Nichtnutzung in Prompts ist **kein gültiges Kriterium** für Redundanz +- Viele Placeholder sind explizit in Roadmap geplant oder fachlich plausibel + +**Korrektes Prinzip:** +> Ein Placeholder darf nur als redundant eingestuft werden, wenn **keine fachliche Rolle** in Roadmap, Konzept oder Datenmodell erkennbar ist. + +--- + +## 2. Neue Klassifizierung + +### Entwickelte Klassifizierungs-Schema: + +| Status | Definition | Kriterien | Anzahl | +|--------|------------|-----------|--------| +| **used_productively** | Aktiv in Prompts/Pipelines/Charts | used_by > 0 | 44 | +| **unused_but_planned** | Explizit in Roadmap Phase 0c/1/2 | Dokumentiert in Roadmap/Konzept | 30 (45%) | +| **unused_but_plausible** | Fachlich sinnvolle Rolle erkennbar | Data-Layer-Anbindung + plausible Rolle | 37 (55%) | +| **unused_and_unclear** | Fachliche Rolle unklar | Keine dokumentierte Rolle | 0 | +| **redundant_or_duplicate** | Echte Doppelung oder ersetzt | Keine fachliche Rolle nachweisbar | 0 | + +**Ergebnis:** Alle 67 ungenutzten Placeholder haben fachliche Berechtigung. + +--- + +## 3. Durchgeführte Änderungen + +### A. Executive Summary (`01_EXECUTIVE_SUMMARY.md`) + +**Geändert:** + +#### Abschnitt 5: "60% ungenutzte Platzhalter" (Zeilen 124-142) + +**VORHER (FALSCH):** +```markdown +### 5. 60% ungenutzte Platzhalter + +**Problem:** +- Technical Debt akkumuliert +- Unklare Deprecation-Strategie + +**Impact:** +- Neue Features nutzen evtl. falsche/veraltete Platzhalter + +**Root Cause:** +- Platzhalter wurden für geplante Features erstellt, aber noch nicht genutzt +``` + +**NACHHER (KORREKT):** +```markdown +### 5. 67 Platzhalter noch nicht produktiv eingebunden + +**Status:** +- **Wichtig:** Dies ist KEIN Technical Debt, sondern erwartbar bei Prompt-Bibliothek im Aufbau + +**Fachliche Klassifizierung:** +- **30 Platzhalter (45%):** Explizit in Roadmap Phase 0c/1/2 geplant +- **37 Platzhalter (55%):** Fachlich plausibel, noch nicht in Prompts integriert +- **0 Platzhalter:** Redundant oder deprecation-würdig + +**Interpretation:** +- Kein Deprecation-Bedarf – Integration statt Deletion erforderlich +``` + +--- + +#### Abschnitt 4: "Technical Debt" (Zeilen 217-230) + +**VORHER (FALSCH):** +```markdown +### 4. Technical Debt (🟡 MEDIUM) + +**Indikatoren:** +- 60% ungenutzt → tote Codepfade +``` + +**NACHHER (KORREKT):** +```markdown +### 4. Fehlende Produktions-Governance (🟡 MEDIUM) + +**KEIN Technical Debt:** +- 60% ungenutzte Platzhalter ≠ "tote Codepfade" +- Prompt-Bibliothek ist im Aufbau (Phase 0b/0c/1/2) +- Ungenutzte Platzhalter sind fachlich geplant oder plausibel +``` + +--- + +#### Maßnahme P2.1 (Zeilen 377-383) + +**VORHER (FALSCH):** +```markdown +**7. Ungenutzte Platzhalter Deprecation-Review (67)** +- Deliverable: Deprecation-Liste mit Replacement-Platzhaltern +``` + +**NACHHER (KORREKT):** +```markdown +**7. Ungenutzte Platzhalter - Integration planen (67)** +- Deliverable: Integration-Roadmap, Prompt-Templates (5-10 Quick Wins) +``` + +--- + +### B. Offene Entscheidungen (`04_OFFENE_ENTSCHEIDUNGEN.md`) + +**Geändert:** + +#### E2.1: Komplette Neuformulierung (Zeilen 193-252) + +**VORHER (FALSCH):** +```markdown +### E2.1: Ungenutzte Platzhalter - Behalten oder Deprecaten? + +**Problem:** +60% der Platzhalter haben 0 Verwendungen. Welche sollen gedeprecated werden? + +**Sub-Entscheidungen:** +- E2.1.1: Body Deltas - Redundanz reduzieren +- E2.1.2: Ability Balance - Warten oder Deprecaten? +- E2.1.3: Meta-Platzhalter - Redundant, trivial berechenbar → deprecaten +``` + +**NACHHER (KORREKT):** +```markdown +### E2.1: Ungenutzte Platzhalter - Roadmap-Priorisierung und Integration-Timeline + +**Neue Fragestellung:** +Nicht "Behalten oder Deprecaten?", sondern "WANN und WIE integrieren?" + +**Fachliche Klassifizierung:** +- **30 Platzhalter (45%):** Explizit in Roadmap Phase 0c/1/2 geplant +- **37 Platzhalter (55%):** Fachlich plausibel, noch nicht in Prompts integriert +- **0 Platzhalter:** Redundant oder deprecation-würdig + +**Sub-Entscheidungen:** +- E2.1.1: Geplante Platzhalter (30) - Integration-Timeline bestätigen + - Roadmap wie geplant oder Priorisierung anpassen? +- E2.1.2: Plausible Platzhalter (37) - Prompt-Use-Cases identifizieren + - Für welche Use-Cases Templates erstellen? (Quick Wins) +``` + +--- + +#### Zusammenfassung (Zeile 395) + +**VORHER:** `Deprecation-Strategie | 67 ungenutzte prüfen` +**NACHHER:** `Integration-Timeline | 67 ungenutzte integrieren` + +--- + +### C. Maßnahmenplan (`03_MASSNAHMENPLAN.md`) + +**Geändert:** + +#### P2.2: Komplette Neuformulierung (Zeilen 773-813) + +**VORHER (FALSCH):** +```markdown +### P2.2: Ungenutzte Platzhalter Deprecation-Review + +**Severity:** 🟢 LOW (Technical Debt) + +**Kategorisierung:** +1. Behalten - Geplant (30) - Keine Action +2. Deprecate - Redundant/Obsolet (15) +3. Unklar - Product-Review (22) + +**Deprecation-Beispiel:** +{ + "key": "bmi", + "deprecated": true, + "sunset_date": "2026-06-30" +} + +**Output:** Deprecation-Liste, 15-20 Deprecated Platzhalter +``` + +**NACHHER (KORREKT):** +```markdown +### P2.2: Ungenutzte Platzhalter - Integration planen + +**Severity:** 🟡 MEDIUM (Prompt-Bibliothek Vollständigkeit) + +**Neue Klassifizierung:** +1. unused_but_planned (30) - Timeline bestätigen, Prototyping +2. unused_but_plausible (37) - Prompt-Use-Cases, Templates +3. redundant_or_duplicate (0) - Keine! + +**Integration-Beispiel:** +{ + "key": "arm_28d_delta", + "usage_role": "unused_but_plausible", + "prompt_use_cases": ["Fortschritts-Analyse spezifischer Körperteile", ...] +} + +**Output:** Integration-Roadmap, Prompt-Templates (5-10), Nutzungsrate +10-20% +``` + +--- + +#### P2 Summary (Zeilen 817-828) + +**VORHER:** +- Deliverables: "15-20 Deprecated Platzhalter" +- Impact: "Technical Debt reduziert" + +**NACHHER:** +- Deliverables: "Integration-Roadmap für 30 geplante, Prompt-Templates für 5-10 Quick Wins" +- Impact: "Placeholder-Integration vorangetrieben, Nutzungsrate 40% → 50-60%" + +--- + +### D. Prüfmatrix Summary (`06_PRUEFMATRIX_SUMMARY.md`) + +**Geändert:** + +#### "Ungenutzte Platzhalter" Abschnitt (Zeilen 199-218) + +**VORHER (FALSCH):** +```markdown +## UNGENUTZTE PLATZHALTER (67 - 60%) + +**Deprecation-Kandidaten:** + +### Kategorie A: Behalten - Geplant (30) +### Kategorie B: Deprecate - Redundant/Obsolet (15) +- bmi, waist_hip_ratio (berechenbar) +- Body Deltas (redundant zu waist_28d_delta Pattern) +- monotony_score, strain_score (experimentell) +### Kategorie C: Unklar - Product-Review (22) +``` + +**NACHHER (KORREKT):** +```markdown +## UNGENUTZTE PLATZHALTER (67 - 60%) + +**WICHTIG:** Methodische Korrektur (30.03.2026) +- Nichtnutzung ≠ Deprecation-Bedarf +- Prompt-Bibliothek ist im Aufbau + +**Neue Klassifizierung:** + +### unused_but_planned (30 - 45%) +- Scores (6), Correlations (5), Ability Balance (5), Goals Details (11), etc. +- Roadmap: Phase 0c/1/2 +- Action: Timeline bestätigen + +### unused_but_plausible (37 - 55%) +- Body Deltas (5), Nutrition Details (6), Training Quality (3), Focus Category (14), Meta (9) +- Plausibel für: Fortschritts-Prompts, Coaching, Dashboard +- Action: Prompt-Use-Cases identifizieren + +### redundant_or_duplicate (0 - 0%) +- Keine - alle 67 haben fachliche Berechtigung +``` + +--- + +### E. README (`06_README.md`) + +**Geändert:** + +#### Zeile 50: +**VORHER:** `E2: Deprecation-Strategie (67 ungenutzte Platzhalter)` +**NACHHER:** `E2: Integration-Timeline (67 ungenutzte Platzhalter - Roadmap-Priorisierung)` + +#### Zeilen 129-133: +**VORHER:** +```markdown +**Nach P2 (Week 5):** 65-70% +- 15-20 deprecated Platzhalter +- Technical Debt reduziert +``` + +**NACHHER:** +```markdown +**Nach P2 (Week 5):** 65-70% +- Integration-Roadmap für 30 geplante Platzhalter +- Prompt-Templates für 5-10 Quick Wins +- Nutzungsrate 40% → 50-60% +``` + +--- + +## 4. Neue Artefakte + +### `USAGE_ROLE_CLASSIFICATION.md` (NEU) +Vollständige fachliche Klassifizierung aller 67 ungenutzten Placeholder: +- 30 unused_but_planned (mit Roadmap-Referenzen) +- 37 unused_but_plausible (mit fachlicher Begründung) +- 0 redundant_or_duplicate + +Enthält: +- Detaillierte Gruppierung (Scores, Correlations, Ability Balance, etc.) +- Fachliche Rollen pro Gruppe +- Roadmap-Zuordnungen +- Prompt-Use-Case-Beispiele + +--- + +## 5. Was sich NICHT geändert hat + +**Unberührt (technisch korrekt):** +- Gap-Cluster-Report (02) - technische Gaps bleiben identisch +- Quellenprotokoll (05) - keine methodische Regel dokumentiert +- Alle technischen Findings (time_window, confidence, data_layer_module) +- P0/P1 Maßnahmen (vollständig technisch) +- Code-Docs-Konflikte (bleibt P0-kritisch) +- Best-Practice-Modelle (nutrition_avg, etc.) + +--- + +## 6. Zusammenfassung der Revisionslogik + +### Alt (FALSCH): +``` +ungenutzt → Technical Debt → Deprecation-Review → 15-20 deprecaten +``` + +### Neu (KORREKT): +``` +ungenutzt → Fachliche Klassifizierung → Integration-Planung → 5-10 aktivieren + ↓ + 45% geplant (Roadmap Phase 0c/1/2) + 55% plausibel (Prompt-Use-Cases) + 0% redundant +``` + +--- + +## 7. Methodische Regel (verbindlich) + +**Für alle künftigen Audits:** + +Ein Placeholder darf **nicht** als redundant, überflüssig oder Deprecation-Kandidat eingestuft werden, nur weil er aktuell: +- in keinem Prompt verwendet wird +- in keiner Pipeline referenziert wird +- in keinem Chart vorkommt +- derzeit nicht produktiv eingebunden ist + +**Erlaubte Kriterien für "redundant":** +1. Keine fachliche Rolle im Datenmodell +2. Keine Rolle in Roadmap/Konzept +3. Echte inhaltliche Doppelung (2+ Placeholder mit identischer Semantik) +4. Fachlich ersetzt (expliziter Replacement dokumentiert) +5. Explizite Deprecation-Entscheidung (dokumentiert) + +**"Heute nicht verwendet" allein ist ausdrücklich kein Redundanzbeweis.** + +--- + +## 8. Impact der Revision + +### Quantitativ: +- **Dokumente geändert:** 5 von 6 (nur Quellenprotokoll unberührt) +- **Abschnitte revidiert:** 10 (Executive Summary: 3, Offene Entscheidungen: 2, Maßnahmenplan: 2, Prüfmatrix: 1, README: 2) +- **Neue Artefakte:** 1 (USAGE_ROLE_CLASSIFICATION.md) +- **Gesamttext geändert:** ~30% der Audit-Berichte (primär Abschnitte zu ungenutzten Platzhaltern) + +### Qualitativ: +- **Methodische Basis:** Roadmap-Konformität statt Nutzungsstatus +- **Ton:** Von "Problem/Technical Debt" zu "Geplant/Integration erforderlich" +- **Maßnahmen:** Von "Deprecation-Review" zu "Integration-Planung" +- **Ziel:** Von "15-20 deprecaten" zu "5-10 aktivieren" + +--- + +**Revision abgeschlossen:** 30. März 2026 +**Revisions-Aufwand:** ~2h (Klassifizierung + 5 Dokumente + Protokoll) +**Qualitätssicherung:** Cross-Check gegen Roadmap + Diagramm-Konzept v2 + DATA_ARCHITECTURE.md + +--- + +**Nächste Schritte:** +1. User-Review der revidierten Dokumente +2. Validierung der Klassifizierung (30 geplant / 37 plausibel korrekt?) +3. Bei Approval: Audit als abgeschlossen markieren diff --git a/.claude/docs/audit/platzhalter/audit-report-2026-03-29/USAGE_ROLE_CLASSIFICATION.md b/.claude/docs/audit/platzhalter/audit-report-2026-03-29/USAGE_ROLE_CLASSIFICATION.md new file mode 100644 index 0000000..cb8151c --- /dev/null +++ b/.claude/docs/audit/platzhalter/audit-report-2026-03-29/USAGE_ROLE_CLASSIFICATION.md @@ -0,0 +1,317 @@ +# Usage Role Classification: 67 Ungenutzte Platzhalter + +**Erstellt:** 30. März 2026 (Revision) +**Basis:** Roadmap, Diagramm-Konzept v2, DATA_ARCHITECTURE.md +**Zweck:** Methodische Korrektur - Nichtnutzung ≠ Redundanz + +--- + +## Klassifizierungs-Prinzip + +**Für dieses Projekt gilt:** +Die Prompt-Bibliothek ist im Aufbau (Phase 0b/0c/1/2 Roadmap). +Aktuelle Nichtnutzung ist kein Beweis für Überflüssigkeit. + +**Strenge Kriterien für "redundant":** +Nur wenn **keine fachliche Rolle** in Roadmap, Konzept oder Datenmodell erkennbar ist. + +--- + +## Klassifizierung der 67 ungenutzten Platzhalter + +### 1. used_productively (44 Platzhalter - bereits in Nutzung) + +**Nicht relevant** - diese sind NICHT ungenutzt. + +--- + +### 2. unused_but_planned (30 Platzhalter - 45%) + +**Definition:** Explizit in Roadmap Phase 0c/1/2 oder Diagramm-Konzept v2 dokumentiert. + +#### A. Scores (6) - Phase 0c/Phase 1 +- `activity_score` +- `nutrition_score` +- `recovery_score` +- `body_progress_score` +- `goal_progress_score` +- `data_quality_score` + +**Roadmap:** Phase 0c (Multi-Layer Architecture) + Phase 1 (Charts) +**Konzept:** Diagramm-Konzept v2, Section 5 "Scores" +**Fachliche Rolle:** Composite Scores mit Confidence, Goal-Mode-abhängig + +--- + +#### B. Correlations (5) - Phase 2 +- `correlation_energy_weight_lag` +- `correlation_load_hrv` +- `correlation_load_rhr` +- `correlation_protein_lbm` +- `correlation_sleep_recovery` + +**Roadmap:** Phase 2 (Engagement - Korrelationen) +**Konzept:** Diagramm-Konzept v2, Section 3.5 "Lag-basierte Zusammenhänge" +**Fachliche Rolle:** Diagnostische Auswertung, Min 21-28 Datenpunkte + +--- + +#### C. Ability Balance (5) - Phase 1 +- `ability_balance_coordination` +- `ability_balance_endurance` +- `ability_balance_mental` +- `ability_balance_mobility` +- `ability_balance_strength` + +**Roadmap:** Phase 1 (Charts - Training Balance) +**Konzept:** DATA_ARCHITECTURE.md, abilities JSONB column +**Fachliche Rolle:** Trainingsverteilung über Fähigkeiten (28d) + +--- + +#### D. Goals Details (11) - Phase 0b/0c (bereits vorhanden, noch nicht in Prompts integriert) +- `active_goals_json`, `active_goals_md` +- `focus_areas_weighted_json`, `focus_areas_weighted_md`, `focus_area_weights_json` +- `top_goal_name`, `top_goal_progress_pct`, `top_goal_status` +- `top_3_goals_behind_schedule`, `top_3_goals_on_track`, `top_3_focus_areas` + +**Roadmap:** Phase 0b (Goal-Aware Placeholders) - DONE +**Konzept:** Goals System v2.0, Dynamic Focus Areas +**Fachliche Rolle:** Goal-Tracking für Prompts, JSON für strukturierte Ausgabe +**Status:** Backend implementiert, noch nicht in Prompt-Bibliothek integriert + +--- + +#### E. Sleep Debt (1) - Phase 1 +- `sleep_debt` + +**Roadmap:** Phase 1 (Charts - Recovery) +**Konzept:** Diagramm-Konzept v2, Section 4.3 "Schlafschuld akkumuliert" +**Fachliche Rolle:** Kumulative Schlafschuld vs. Ziel (7-8h) + +--- + +#### F. Plateau Detection (1) - Phase 1/2 +- `plateau_detected` + +**Roadmap:** Phase 1/2 (Diagnostics) +**Konzept:** Diagramm-Konzept v2, Section 3.4 "Muster, Plateaus" +**Fachliche Rolle:** Stagnations-Erkennung (Gewicht, BF%, LBM) + +--- + +#### G. Top Drivers (1) - Phase 2 +- `top_drivers` + +**Roadmap:** Phase 2 (Correlations) +**Konzept:** Diagramm-Konzept v2, Section 3.5 "Wechselwirkungen" +**Fachliche Rolle:** Top-3 Einflussfaktoren auf Fortschritt + +--- + +### 3. unused_but_plausible (37 Platzhalter - 55%) + +**Definition:** Fachlich sinnvolle Rolle im Datenmodell, plausibel für Prompt-Bibliothek. + +#### H. Body Deltas (5) +- `arm_28d_delta`, `chest_28d_delta`, `hip_28d_delta`, `thigh_28d_delta`, `waist_28d_delta` + +**Fachliche Rolle:** Granulare Delta-Berechnungen für Umfänge (28d) +**Plausibel für:** Prompts die spezifische Körperteile analysieren (z.B. "Armumfang Fortschritt") +**Redundant?** NEIN - circ_summary ist Best-of-Each, Deltas sind zeitbasiert +**Status:** unused_but_plausible (für spezifische Prompts nützlich) + +--- + +#### I. Nutrition Details (6) +- `energy_deficit_surplus` +- `intake_volatility` +- `nutrition_days` +- `protein_days_in_target` +- `protein_ziel_low` +- `protein_ziel_high` + +**Fachliche Rolle:** Detaillierte Ernährungs-Metriken +**Plausibel für:** Prompts für Ernährungs-Coaching (Protein-Target-Range, Consistency) +**Redundant?** NEIN - ergänzen nutrition_avg/nutrition_score +**Status:** unused_but_plausible + +--- + +#### J. Training Quality/Load (3) +- `monotony_score` +- `strain_score` +- `rest_day_compliance` + +**Fachliche Rolle:** Übertrainings-Erkennung, Load-Monitoring +**Plausibel für:** Prompts für Recovery-Management +**Redundant?** NEIN - spezifische Load-Metriken +**Status:** unused_but_plausible + +--- + +#### K. Focus Category Weights (7) +- `focus_cat_körper_weight` +- `focus_cat_ernährung_weight` +- `focus_cat_aktivität_weight` +- `focus_cat_recovery_weight` +- `focus_cat_vitalwerte_weight` +- `focus_cat_mental_weight` +- `focus_cat_lebensstil_weight` + +**Fachliche Rolle:** User-Präferenzen für Score-Gewichtung +**Plausibel für:** Goal-Mode-abhängige Prompts +**Redundant?** NEIN - Teil des Dynamic Focus Areas v2.0 System +**Status:** unused_but_plausible (Phase 0b Feature) + +--- + +#### L. Focus Category Progress (7) +- `focus_cat_körper_progress` +- `focus_cat_ernährung_progress` +- `focus_cat_aktivität_progress` +- `focus_cat_recovery_progress` +- `focus_cat_vitalwerte_progress` +- `focus_cat_mental_progress` +- `focus_cat_lebensstil_progress` + +**Fachliche Rolle:** Fortschritts-Scores pro Kategorie +**Plausibel für:** Dashboard-Widgets, Kategorie-spezifische Analysen +**Redundant?** NEIN - Teil des Dynamic Focus Areas v2.0 System +**Status:** unused_but_plausible (Phase 0b Feature) + +--- + +#### M. Meta/Convenience (3) +- `bmi` +- `waist_hip_ratio` +- `datum_heute` + +**Fachliche Rolle:** Convenience-Placeholder für Prompts +**Plausibel für:** Prompt-Templates (bmi = "aktueller BMI", datum_heute = "Kontext-Datum") +**Redundant?** Technisch berechenbar, ABER Convenience hat Wert +**Status:** unused_but_plausible + +--- + +#### N. Zeitraum Meta (3) +- `zeitraum_7d` +- `zeitraum_30d` +- `zeitraum_90d` + +**Fachliche Rolle:** String-Konstanten für Zeitfenster in Prompts +**Plausibel für:** Prompt-Templates ("in den letzten {{zeitraum_7d}}") +**Redundant?** NEIN - Sprachliche Convenience +**Status:** unused_but_plausible + +--- + +#### O. Weight Trend (1) +- `weight_90d_slope` + +**Fachliche Rolle:** Langfrist-Trend (90d statt 28d) +**Plausibel für:** Prompts für langfristige Gewichtsentwicklung +**Redundant?** NEIN - ergänzt weight_trend (28d) +**Status:** unused_but_plausible + +--- + +#### P. Recent Load (1) +- `recent_load_balance_3d` + +**Fachliche Rolle:** Kurzfrist-Load-Balance (3d) +**Plausibel für:** Prompts für akute Recovery-Bewertung +**Redundant?** NEIN - ergänzt load_monitoring (7d/28d) +**Status:** unused_but_plausible + +--- + +### 4. unused_and_unclear (0 Platzhalter) + +**Keine** - alle 67 haben erkennbare fachliche Rolle. + +--- + +### 5. redundant_or_duplicate (0 Platzhalter) + +**Keine** - kein Platzhalter erfüllt die strengen Kriterien für Redundanz: +- Alle haben fachliche Rolle im Datenmodell +- Alle haben plausible Rolle in Roadmap/Konzept oder als Convenience +- Keine echten Duplikate +- Keine explizit ersetzten Platzhalter + +--- + +## Zusammenfassung + +| Status | Anzahl | Prozent | Interpretation | +|--------|--------|---------|----------------| +| **unused_but_planned** | 30 | 45% | Explizit in Roadmap Phase 0c/1/2 | +| **unused_but_plausible** | 37 | 55% | Fachlich sinnvoll, noch nicht in Prompts | +| **unused_and_unclear** | 0 | 0% | Keine | +| **redundant_or_duplicate** | 0 | 0% | Keine | +| **TOTAL** | 67 | 100% | Alle haben fachliche Berechtigung | + +--- + +## Implikationen für Audit-Berichte + +### Was sich ändert: + +1. **Executive Summary:** + - ~~"60% ungenutzte Platzhalter"~~ als systemische Schwäche #5 → ENTFERNEN + - ~~"Technical Debt akkumuliert"~~ → ENTFERNEN + - ~~"Unklare Deprecation-Strategie"~~ → ENTFERNEN + - **NEU:** "67 Platzhalter noch nicht produktiv eingebunden (45% geplant in Phase 0c/1/2, 55% plausibel für Prompt-Bibliothek)" + +2. **Offene Entscheidungen E2.1:** + - ~~"Ungenutzte Platzhalter - Behalten oder Deprecaten?"~~ → UMFORMULIEREN + - **NEU:** "Ungenutzte Platzhalter - Roadmap-Priorisierung und Integration-Timeline" + - Fokus: WANN integrieren, nicht OB deprecaten + +3. **Maßnahmenplan P2.2:** + - ~~"Ungenutzte Platzhalter Deprecation-Review"~~ → UMFORMULIEREN + - **NEU:** "Ungenutzte Platzhalter - Fachliche Rolle klären und Integration planen" + - ~~"15-20 Deprecated Platzhalter"~~ als Deliverable → ENTFERNEN + +4. **Prüfmatrix Summary:** + - ~~"Deprecation-Kandidaten"~~ Kategorien A/B/C → ERSETZEN + - **NEU:** "usage_role_classification" Kategorien (planned/plausible/unclear/redundant) + +5. **README:** + - ~~"Technical Debt reduziert"~~ → UMFORMULIEREN + - **NEU:** "Platzhalter-Integration in Prompt-Bibliothek vorangetrieben" + +--- + +## Empfohlene Maßnahmen (NEU) + +**Statt Deprecation:** + +### P2: Placeholder-Integration-Planung (4-6h) + +**Ziel:** Integration ungenutzter Platzhalter in Prompt-Bibliothek priorisieren + +**Prozess:** +1. **Meeting (2h):** Product + Tech Review der 67 + - Gruppe A (unused_but_planned, 30): Timeline für Phase 0c/1/2 bestätigen + - Gruppe B (unused_but_plausible, 37): Prompt-Use-Cases identifizieren +2. **Dokumentation (1h):** Integration-Roadmap, Prompt-Kandidaten +3. **Implementation (1-2h):** Prompt-Templates erstellen (Quick Wins) +4. **Communication (1h):** Prompt-Autoren: "Neue Platzhalter verfügbar" + +**Deliverables:** +- Integration-Timeline für 30 geplante Platzhalter +- Prompt-Use-Cases für 10-15 plausible Platzhalter +- 5-10 neue Prompt-Templates mit bisher ungenutzten Platzhaltern + +**Impact:** +- Nutzungsrate steigt von 40% auf 50-60% +- Prompt-Bibliothek wird reichhaltiger +- Platzhalter-System zeigt Wert statt "Technical Debt" + +--- + +**Audit-Revision durchgeführt von:** Claude Code +**Revisions-Datum:** 30. März 2026 +**Methodische Basis:** Roadmap-Konformität statt Nutzungsstatus diff --git a/.claude/docs/audit/platzhalter/placeholder_requirements_for_development.md b/.claude/docs/audit/platzhalter/placeholder_requirements_for_development.md new file mode 100644 index 0000000..eb6a509 --- /dev/null +++ b/.claude/docs/audit/platzhalter/placeholder_requirements_for_development.md @@ -0,0 +1,703 @@ +# Placeholder-Gap- und Governance-Dokument für Vibe-Coding / Entwickler + +**Datei:** `placeholder_requirements_for_development.md` +**Zweck:** Arbeitsgrundlage für Entwickler, damit die Prompt-Bibliothek mit stabilen, fachlich sauberen Platzhaltern betrieben werden kann. +**Status:** Draft / arbeitsfähig +**Bezug:** Prompt-Bibliothek, Report-Steckbriefe, bestehender Placeholder-Export, Datenarchitektur + +--- + +## 1. Ziel dieses Dokuments + +Dieses Dokument beschreibt: + +1. **welche Platzhalter bereits vorhanden und nutzbar sind** +2. **welche Platzhalter fachlich geschärft werden müssen** +3. **welche neuen Platzhalter für V1 zusätzlich bereitgestellt werden sollten** +4. **welche Platzhalter nur als spätere Erweiterung gelten** +5. **welche Governance-Regeln für Benennung, Semantik und Versionierung gelten** + +Wichtig: +Die Prompt-Bibliothek darf **nicht** darauf angewiesen sein, dass Folgechats spontan neue Platzhalter erfinden oder bestehende umdeuten. +Die API-/Export-Seite muss deshalb eine **kontrollierte, stabile Placeholder-Oberfläche** bereitstellen. + +--- + +## 2. Grundprinzipien für Platzhalter + +### 2.1 Platzhalter sind API-Verträge +Ein Platzhalter ist kein freier Text, sondern ein **stabiler Vertrag** zwischen: +- Datenmodell / Berechnungslogik +- Export-/Aggregationsebene +- Prompt-System +- UI / Auswertung / QA + +### 2.2 Platzhalter dürfen nicht stillschweigend umdefiniert werden +Ein einmal eingeführter Platzhalter darf nicht: +- semantisch verändert werden +- in einem anderen Zeitraum berechnet werden +- seine Einheit ändern +- stillschweigend von Rohwert auf Trendwert wechseln + +### 2.3 Fehlende Werte müssen explizit sein +Wenn ein Wert nicht berechnet werden kann, soll dies **strukturiert** erfolgen, nicht als vager Freitext. + +Bevorzugt: +- `null` +- oder ein strukturierter Verfügbarkeitsstatus + +Nur wenn das bestehende System es erzwingt, darf vorläufig `"nicht verfügbar"` verwendet werden. + +### 2.4 Platzhalter sollen möglichst atomar sein +Bevorzugt: +- einzelne, klar definierte Felder +Nicht bevorzugt: +- sehr große, uneinheitliche Freitext-Blobs, die mehrere Logiken mischen + +Trotzdem dürfen kompakte Summary-Platzhalter als Zusatz bleiben, z. B. für einfache Prompts oder UI-Kurztexte. + +### 2.5 Zeitbezug muss immer eindeutig sein +Jeder zeitabhängige Platzhalter braucht klaren Fensterbezug: +- `*_today` +- `*_7d` +- `*_14d` +- `*_28d` +- `*_90d` + +--- + +## 3. Bereits vorhandene und gut nutzbare Platzhalter + +Diese Platzhalter sind bereits vorhanden und für V1 sinnvoll nutzbar. + +## 3.1 Profil / Stammdaten +- `name` +- `age` +- `height` +- `geschlecht` + +## 3.2 Körper +- `weight_aktuell` +- `weight_trend` +- `kf_aktuell` +- `bmi` +- `weight_7d_median` +- `waist_hip_ratio` +- `caliper_summary` +- `circ_summary` + +## 3.3 Ernährung +- `kcal_avg` +- `protein_avg` +- `carb_avg` +- `fat_avg` +- `energy_balance_7d` +- `protein_g_per_kg` +- `protein_adequacy_28d` +- `macro_consistency_score` +- `energy_deficit_surplus` + +## 3.4 Training / Aktivität +- `activity_summary` +- `activity_detail` +- `trainingstyp_verteilung` +- `training_minutes_week` +- `training_frequency_7d` +- `quality_sessions_pct` +- `proxy_internal_load_7d` +- `monotony_score` +- `strain_score` +- `rest_day_compliance` + +## 3.5 Schlaf / Erholung +- `sleep_avg_duration` +- `sleep_avg_quality` +- `rest_days_count` +- `sleep_avg_duration_7d` +- `sleep_debt_hours` +- `sleep_regularity_proxy` +- `sleep_quality_7d` + +## 3.6 Vitalwerte +- `vitals_avg_hr` +- `vitals_avg_hrv` +- `vitals_vo2_max` +- `rhr_vs_baseline_pct` +- `vo2max_trend_28d` + +## 3.7 Scores / Meta +- `nutrition_score` +- `activity_score` +- `recovery_score` +- `data_quality_score` + +## 3.8 Ziele / Fokus +- `top_goal_name` +- `top_goal_progress_pct` +- `top_goal_status` +- `top_focus_area_name` +- `top_focus_area_progress` +- `focus_cat_körper_progress` +- `focus_cat_körper_weight` +- `focus_cat_ernährung_progress` +- `focus_cat_ernährung_weight` +- `focus_cat_aktivität_progress` +- `focus_cat_aktivität_weight` +- `focus_cat_recovery_progress` +- `focus_cat_recovery_weight` +- `focus_cat_vitalwerte_progress` +- `focus_cat_vitalwerte_weight` +- `focus_cat_mental_progress` +- `focus_cat_mental_weight` +- `focus_cat_lebensstil_progress` +- `focus_cat_lebensstil_weight` + +## 3.9 Zeitraum +- `datum_heute` +- `zeitraum_7d` +- `zeitraum_30d` +- `zeitraum_90d` + +## 3.10 Korrelation / Diagnose +- `correlation_energy_weight_lag` + +--- + +## 4. Bereits vorhandene Platzhalter mit Schärfungsbedarf + +Diese Platzhalter existieren, sind aber fachlich oder operativ noch nicht sauber genug. + +## 4.1 "nicht verfügbar" als String +Betroffen u. a.: +- `goal_progress_score` +- `body_progress_score` +- `fm_28d_change` +- `lbm_28d_change` +- `waist_28d_delta` +- `recomposition_quadrant` +- `ability_balance_strength` +- `ability_balance_endurance` +- `hrv_vs_baseline_pct` +- weitere ähnliche Felder + +### Problem +- Die Prompt-Logik muss String-Inhalte interpretieren. +- Folgeprompts können schwer unterscheiden zwischen: + - echter Null + - fehlendem Wert + - noch nicht implementiert + - Daten reichen nicht + +### Empfehlung +Zusätzlich zu jedem kritischen Feld: +- `*_available: true|false` +- optional `*_reason_unavailable` + +Oder besser: +- ein strukturiertes Domain-Availability-Objekt + +--- + +## 4.2 Prozent-/Score-Platzhalter ohne standardisierte Skala +Betroffen: +- `quality_sessions_pct` +- `protein_adequacy_28d` +- `macro_consistency_score` +- `nutrition_score` +- `activity_score` +- `recovery_score` +- `data_quality_score` + +### Problem +Die Skala ist im Prompt nicht immer selbsterklärend: +- 0–100? +- Prozent? +- normierter Score? +- je höher desto besser? + +### Empfehlung +Für jeden solchen Platzhalter intern dokumentieren: +- Skala +- Richtung +- Berechnungsbasis +- sinnvolle Interpretationszonen + +Optional zusätzliche Entwickler-Metadaten: +- `score_meta_` + +--- + +## 4.3 Summary-Felder als alleinige Quelle +Betroffen: +- `activity_summary` +- `caliper_summary` +- `circ_summary` + +### Problem +Diese Felder sind nützlich für kompakte Reports, aber für robuste Mehrstufigkeit oft zu grob. + +### Empfehlung +Summary-Felder behalten, aber ergänzen durch strukturierte Roh-/Aggregatfelder. + +--- + +## 4.4 Fokuskategorien nicht sauber auf 100 normiert +Im Export fallen z. B. Gewichte wie `135.0` oder andere Summen auf. + +### Problem +Für Prompts und Zielgewichtung ist unklar: +- sind das Rohgewichte? +- normierte Gewichte? +- gruppeninterne Punkte? +- Prozentangaben? + +### Empfehlung +Klar trennen zwischen: +- `focus_cat_*_weight_raw` +- `focus_cat_*_weight_normalized` + +Und ebenso für einzelne Fokusbereiche. + +--- + +## 4.5 Zeitfenster-Mischung +Einige Felder sind 7d, andere 14d, 28d oder unklar verdichtet. + +### Problem +Prompts können unabsichtlich Zeitfenster vergleichen, die nicht zusammenpassen. + +### Empfehlung +Zeitfenster im Feldnamen vereinheitlichen und konsequent dokumentieren. + +--- + +## 5. Neue Platzhalter, die für V1 zusätzlich bereitgestellt werden sollten + +Diese Liste ist die wichtigste operative To-do-Liste für die Entwicklung. + +## 5.1 Höchste Priorität – Zielsystem / Fokus / Kontext + +### P1. `goal_summary_json` +**Zweck:** kompakte, strukturierte Zielübersicht für Zielreports und zielgewichtete Synthesen. + +**Sollinhalt:** +- Liste aktiver Ziele +- je Ziel: + - ID + - Name + - Typ + - Status + - Startwert + - Zielwert + - aktueller Wert + - Fortschritt + - Zieltermin (falls vorhanden) + - primary_flag + - zugeordnete Fokusbereiche + +**Warum wichtig:** +Die aktuelle Prompt-Bibliothek braucht mehr als nur `top_goal_name`. + +--- + +### P2. `focus_area_summary_json` +**Zweck:** strukturierte Liste der aktiven Fokusbereiche. + +**Sollinhalt:** +- Fokusbereichsname +- Gruppe/Kategorie +- Rohgewicht +- normiertes Gewicht +- aktueller Progress +- verknüpfte Ziele + +**Warum wichtig:** +Top-Focus allein reicht für personalisierte Reports nicht. + +--- + +### P3. `goal_progress_score_available` +**Typ:** bool +**Warum:** sauber zwischen fehlend und vorhanden unterscheiden. + +### P4. `body_progress_score_available` +**Typ:** bool +**Warum:** dito. + +--- + +## 5.2 Höchste Priorität – Domain Availability / Confidence + +### P5. `domain_availability_json` +**Zweck:** zentrale Verfügbarkeit pro Domäne. + +**Sollinhalt:** +- body +- nutrition +- activity +- sleep +- recovery +- vitals +- goals +- focus_areas +- correlations + +jeweils mit: +- available +- confidence +- main_gaps +- last_valid_window + +**Warum wichtig:** +Vereinfacht Fallbacks und verhindert Scheinpräzision. + +--- + +### P6. `domain_confidence_json` +Falls `domain_availability_json` nicht reicht oder zu groß wird. + +--- + +### P7. `critical_missing_fields_json` +**Zweck:** welche fehlenden Daten würden den größten Erkenntnisgewinn bringen? + +**Warum wichtig:** +Für Datenqualitätsreport und Nutzerführung. + +--- + +## 5.3 Höchste Priorität – Körperentwicklung + +### P8. `fm_28d_change_available` +### P9. `lbm_28d_change_available` +### P10. `waist_28d_delta_available` +### P11. `recomposition_quadrant_available` + +**Warum:** +Körper- und Plateau-Reports brauchen robuste Verfügbarkeitslogik. + +--- + +### P12. `body_change_summary_json` +**Zweck:** strukturierte Körperentwicklung. + +**Sollinhalt:** +- weight trend +- FM delta +- LBM delta +- waist delta +- hip delta +- recomposition quadrant +- confidence + +--- + +## 5.4 Höchste Priorität – Training / Belastung + +### P13. `activity_structure_json` +**Zweck:** kompakter Trainingsstrukturblock. + +**Sollinhalt:** +- Volumen +- Frequenz +- Typverteilung +- Qualitätsanteil +- Lastniveau +- Monotonie +- Strain +- Rest-day compliance +- auffällige Muster + +--- + +### P14. `training_quality_score` +**Zweck:** standardisierte Trainingsqualitätsbewertung über Sessions hinweg + +**Warum:** +Aktuell gibt es `quality_sessions_pct`, aber noch keinen robusten verdichteten Qualitätsanker. + +--- + +### P15. `load_balance_class` +Wertebeispiele: +- low +- moderate +- high +- strained + +**Warum:** +Erleichtert Diagnose-Prompts. + +--- + +## 5.5 Höchste Priorität – Schlaf / Recovery / Vitals + +### P16. `sleep_summary_json` +**Sollinhalt:** +- duration +- quality +- debt +- regularity +- trend +- confidence + +### P17. `recovery_summary_json` +**Sollinhalt:** +- recovery score +- main drivers +- RHR status +- HRV status +- recent load interaction +- confidence + +### P18. `vitals_summary_json` +**Sollinhalt:** +- resting HR +- HRV +- VO2max +- trend +- baseline deviation +- confidence + +### P19. `hrv_vs_baseline_pct_available` +### P20. `rhr_vs_baseline_pct_available` + +--- + +## 5.6 Höchste Priorität – Korrelation / Diagnose + +### P21. `correlation_summary_json` +**Zweck:** strukturierte Zusammenfassung mehrerer Korrelationen / Lag-Beziehungen. + +**Sollinhalt:** +- energy_weight_lag +- training_hrv_lag +- training_rhr_lag +- sleep_recovery_lag +- confidence je Zusammenhang + +--- + +### P22. `plateau_status` +Wertebeispiele: +- likely +- possible +- not_detected +- insufficient_data + +### P23. `top_drivers_positive_json` +### P24. `top_drivers_negative_json` + +**Warum:** +Top-Driver-Report und Plateau-Detektor profitieren stark von einer vorberechneten Zwischenebene. + +--- + +## 5.7 Höchste Priorität – Energieverfügbarkeit / Schutzlogik + +### P25. `underfueling_risk_flag` +**Typ:** bool / enum + +### P26. `underfueling_risk_reason` +**Typ:** text / enum / json + +### P27. `energy_availability_summary_json` +**Sollinhalt:** +- intake adequacy +- deficit magnitude +- load context +- recovery context +- warning level +- confidence + +--- + +## 6. Mittlere Priorität – sollte bald folgen + +## 6.1 Gesundheitsstabilität +### P28. `health_stability_score` +### P29. `health_stability_summary_json` + +## 6.2 Blutdruck +### P30. `blood_pressure_summary_json` +**Sollinhalt:** +- mean systolic/diastolic +- category +- contexts +- trend +- irregularity flags +- confidence + +## 6.3 Fokus-/Zielgewichtung +### P31. `goal_weighted_priority_json` +**Zweck:** priorisierte Ziel-/Fokuslogik für Synthese + +## 6.4 Reporting-Hilfsfelder +### P32. `main_constraint_json` +### P33. `main_strength_json` +### P34. `next_best_actions_json` + +--- + +## 7. Niedrigere Priorität / spätere Ausbaustufe + +Diese Platzhalter sind wertvoll, aber für V1 nicht zwingend: + +- `session_rpe` +- `fatigue_score` +- `pain_flag` +- `illness_flag` +- `travel_flag` +- `competition_flag` +- `measurement_time_consistency` +- `training_age` +- `secondary_goals_json` +- `waist_to_height_ratio` +- `sleep_segment_quality_score` +- `steps_summary_json` + +--- + +## 8. Platzhalter, die geschärft statt neu erfunden werden sollten + +Statt wild neue Namen einzuführen, sollten folgende Bereiche zuerst **semantisch sauber definiert** werden. + +## 8.1 Goal / Focus +Bestehend, aber zu flach: +- `top_goal_name` +- `top_goal_progress_pct` +- `top_focus_area_name` +- `top_focus_area_progress` + +**Schärfung:** +Ergänzen durch strukturierte JSONs, nicht ersetzen. + +## 8.2 Körperentwicklung +Bestehend, aber oft nicht verfügbar: +- `fm_28d_change` +- `lbm_28d_change` +- `waist_28d_delta` +- `recomposition_quadrant` + +**Schärfung:** +Verfügbarkeitsflags + strukturierte Summary. + +## 8.3 Activity-Qualität +Bestehend: +- `quality_sessions_pct` + +**Schärfung:** +ergänzen durch: +- `training_quality_score` +- ggf. `quality_sessions_count` +- optional `quality_definition_version` + +## 8.4 Recovery +Bestehend: +- `recovery_score` + +**Schärfung:** +ergänzen durch Komponenten-/Treiberdarstellung. + +## 8.5 Data Quality +Bestehend: +- `data_quality_score` + +**Schärfung:** +ergänzen durch Domain-Ebene und konkrete Lückenbeschreibung. + +--- + +## 9. Governance-Regeln für Platzhalter + +Diese Regeln sollten für Entwicklung und Prompting verbindlich sein. + +### G1. Keine eigenmächtige Umbenennung +Bestehende Platzhalter dürfen nicht ohne Migration umbenannt werden. + +### G2. Keine stillschweigende Semantikänderung +Wenn Berechnungslogik geändert wird, braucht der Platzhalter: +- Versionierung intern +- oder einen neuen Namen + +### G3. Neue Platzhalter nur kontrolliert +Neue Platzhalter nur einführen, wenn: +- Zweck klar +- Datenquelle klar +- Zeitfenster klar +- Einheit klar +- Fallback klar + +### G4. JSON vor Freitext +Für komplexe Mehrstufenlogik sollen neue Platzhalter bevorzugt als strukturierte JSON-Container bereitgestellt werden. + +### G5. Zeitfenster im Namen +Zeitfenster müssen im Namen explizit sein, falls nicht universell. + +### G6. Verfügbarkeit trennen von Inhalt +Für kritische Felder besser: +- Wert +- Verfügbarkeitsflag +- optional Grund + +### G7. Keine Ad-hoc-Platzhalter in Folgechats +Folgechats für Prompt-Ausarbeitung dürfen keine neuen Platzhalter stillschweigend voraussetzen. + +--- + +## 10. Konkrete Entwickler-Backlog-Empfehlung + +## Sprint / Paket 1 – notwendig für robuste V1-Reports +1. `goal_summary_json` +2. `focus_area_summary_json` +3. `domain_availability_json` +4. `critical_missing_fields_json` +5. Verfügbarkeitsflags für Body-Change-Felder +6. `sleep_summary_json` +7. `recovery_summary_json` +8. `activity_structure_json` +9. `correlation_summary_json` +10. `plateau_status` + +## Sprint / Paket 2 – starke Verbesserung der Ziel- und Diagnoseebene +1. `top_drivers_positive_json` +2. `top_drivers_negative_json` +3. `energy_availability_summary_json` +4. `underfueling_risk_flag` +5. `health_stability_score` +6. `blood_pressure_summary_json` +7. `goal_weighted_priority_json` + +## Sprint / Paket 3 – spätere Verfeinerung +1. `training_quality_score` +2. `session_rpe` +3. `fatigue_score` +4. `measurement_time_consistency` +5. `illness_flag` / `travel_flag` +6. `waist_to_height_ratio` + +--- + +## 11. Abnahmekriterien für die Placeholder-Schicht + +Die Placeholder-Schicht ist fachlich gut genug, wenn: + +1. **alle Core-Reports der V1-Bibliothek ohne stillschweigende Erfindungen lauffähig sind** +2. fehlende Werte **strukturiert** behandelt werden können +3. Ziel-/Fokus-Logik nicht nur über ein einzelnes „Top“-Feld abgebildet wird +4. Datenqualität und Confidence nicht nur global, sondern domänenspezifisch beschrieben werden +5. Diagnose-Reports mindestens auf vorbereiteten Zwischenobjekten statt nur Freitextsummaries aufsetzen können + +--- + +## 12. Kurzfazit + +Für V1 ist die Placeholder-Basis bereits gut, aber noch nicht stabil genug für eine große professionelle Prompt-Bibliothek. +Der größte Handlungsbedarf liegt in vier Bereichen: + +- **strukturierte Ziel-/Fokus-JSONs** +- **domänenspezifische Availability-/Confidence-Objekte** +- **robustere Diagnose-/Driver-/Plateau-Zwischenfelder** +- **saubere Verfügbarkeits- und Schärfungslogik für kritische Body-/Recovery-Felder** + +Dieses Dokument ist bewusst entwicklungsnah formuliert und soll als Arbeitsgrundlage für die Implementierung dienen. diff --git a/.claude/docs/audit/platzhalter/reconciliation-2026-03-30/DRAFT_CORRECTIONS_REQUIRED.md b/.claude/docs/audit/platzhalter/reconciliation-2026-03-30/DRAFT_CORRECTIONS_REQUIRED.md new file mode 100644 index 0000000..8019065 --- /dev/null +++ b/.claude/docs/audit/platzhalter/reconciliation-2026-03-30/DRAFT_CORRECTIONS_REQUIRED.md @@ -0,0 +1,151 @@ +# Draft Corrections Required + +**Generated:** 2026-03-30 14:58:37 + +This document lists all placeholders where the draft specification is missing, wrong, or incomplete. + +## Summary + +- **Missing:** 111 placeholders +- **Partial:** 0 placeholders +- **Wrong:** 0 placeholders + +## Missing Draft Specifications + +**111 placeholders** lack draft specifications: + +- `ability_balance_coordination` (Category: Unknown, Compliance: non_compliant, Priority: P0) +- `ability_balance_endurance` (Category: Training, Compliance: non_compliant, Priority: P0) +- `ability_balance_mental` (Category: Unknown, Compliance: non_compliant, Priority: P0) +- `ability_balance_mobility` (Category: Unknown, Compliance: non_compliant, Priority: P0) +- `ability_balance_strength` (Category: Training, Compliance: non_compliant, Priority: P0) +- `active_goals_json` (Category: Unknown, Compliance: non_compliant, Priority: P0) +- `active_goals_md` (Category: Unknown, Compliance: non_compliant, Priority: P0) +- `activity_detail` (Category: Unknown, Compliance: non_compliant, Priority: P0) +- `activity_score` (Category: Scores (Phase 0b), Compliance: non_compliant, Priority: P0) +- `activity_summary` (Category: Training, Compliance: non_compliant, Priority: P0) +- `age` (Category: Profil, Compliance: compliant, Priority: P3) +- `arm_28d_delta` (Category: Unknown, Compliance: non_compliant, Priority: P0) +- `bmi` (Category: Körper, Compliance: partially_compliant, Priority: P0) +- `body_progress_score` (Category: Scores (Phase 0b), Compliance: non_compliant, Priority: P0) +- `caliper_summary` (Category: Unknown, Compliance: partially_compliant, Priority: P0) +- `carb_avg` (Category: Ernährung, Compliance: compliant, Priority: P3) +- `chest_28d_delta` (Category: Unknown, Compliance: non_compliant, Priority: P0) +- `circ_summary` (Category: Unknown, Compliance: compliant, Priority: P3) +- `correlation_energy_weight_lag` (Category: Unknown, Compliance: non_compliant, Priority: P0) +- `correlation_load_hrv` (Category: Unknown, Compliance: non_compliant, Priority: P0) +- `correlation_load_rhr` (Category: Unknown, Compliance: non_compliant, Priority: P0) +- `correlation_protein_lbm` (Category: Unknown, Compliance: non_compliant, Priority: P0) +- `correlation_sleep_recovery` (Category: Unknown, Compliance: non_compliant, Priority: P0) +- `data_quality_score` (Category: Scores (Phase 0b), Compliance: non_compliant, Priority: P0) +- `datum_heute` (Category: Zeitraum, Compliance: non_compliant, Priority: P0) +- `energy_balance_7d` (Category: Ernährung, Compliance: non_compliant, Priority: P2) +- `energy_deficit_surplus` (Category: Unknown, Compliance: non_compliant, Priority: P0) +- `fat_avg` (Category: Ernährung, Compliance: compliant, Priority: P3) +- `fm_28d_change` (Category: Körper, Compliance: non_compliant, Priority: P2) +- `focus_area_weights_json` (Category: Unknown, Compliance: non_compliant, Priority: P0) +- `focus_areas_weighted_json` (Category: Unknown, Compliance: non_compliant, Priority: P0) +- `focus_areas_weighted_md` (Category: Unknown, Compliance: non_compliant, Priority: P0) +- `focus_cat_aktivität_progress` (Category: Focus Areas, Compliance: non_compliant, Priority: P0) +- `focus_cat_aktivität_weight` (Category: Focus Areas, Compliance: non_compliant, Priority: P0) +- `focus_cat_ernährung_progress` (Category: Focus Areas, Compliance: non_compliant, Priority: P0) +- `focus_cat_ernährung_weight` (Category: Focus Areas, Compliance: non_compliant, Priority: P0) +- `focus_cat_körper_progress` (Category: Focus Areas, Compliance: non_compliant, Priority: P0) +- `focus_cat_körper_weight` (Category: Focus Areas, Compliance: non_compliant, Priority: P0) +- `focus_cat_lebensstil_progress` (Category: Unknown, Compliance: non_compliant, Priority: P0) +- `focus_cat_lebensstil_weight` (Category: Unknown, Compliance: non_compliant, Priority: P0) +- `focus_cat_mental_progress` (Category: Unknown, Compliance: non_compliant, Priority: P0) +- `focus_cat_mental_weight` (Category: Unknown, Compliance: non_compliant, Priority: P0) +- `focus_cat_recovery_progress` (Category: Unknown, Compliance: non_compliant, Priority: P0) +- `focus_cat_recovery_weight` (Category: Unknown, Compliance: non_compliant, Priority: P0) +- `focus_cat_vitalwerte_progress` (Category: Unknown, Compliance: non_compliant, Priority: P0) +- `focus_cat_vitalwerte_weight` (Category: Unknown, Compliance: non_compliant, Priority: P0) +- `geschlecht` (Category: Profil, Compliance: non_compliant, Priority: P0) +- `goal_bf_pct` (Category: Unknown, Compliance: non_compliant, Priority: P0) +- `goal_progress_score` (Category: Scores (Phase 0b), Compliance: non_compliant, Priority: P0) +- `goal_weight` (Category: Unknown, Compliance: non_compliant, Priority: P0) +- `height` (Category: Profil, Compliance: non_compliant, Priority: P0) +- `hip_28d_delta` (Category: Unknown, Compliance: non_compliant, Priority: P0) +- `hrv_vs_baseline_pct` (Category: Vitalwerte, Compliance: partially_compliant, Priority: P0) +- `intake_volatility` (Category: Unknown, Compliance: non_compliant, Priority: P0) +- `kcal_avg` (Category: Ernährung, Compliance: compliant, Priority: P3) +- `kf_aktuell` (Category: Körper, Compliance: partially_compliant, Priority: P1) +- `lbm_28d_change` (Category: Körper, Compliance: non_compliant, Priority: P2) +- `macro_consistency_score` (Category: Ernährung, Compliance: non_compliant, Priority: P0) +- `monotony_score` (Category: Unknown, Compliance: non_compliant, Priority: P0) +- `name` (Category: Profil, Compliance: non_compliant, Priority: P0) +- `nutrition_days` (Category: Unknown, Compliance: non_compliant, Priority: P0) +- `nutrition_score` (Category: Scores (Phase 0b), Compliance: non_compliant, Priority: P0) +- `plateau_detected` (Category: Unknown, Compliance: non_compliant, Priority: P0) +- `protein_adequacy_28d` (Category: Ernährung, Compliance: non_compliant, Priority: P2) +- `protein_avg` (Category: Ernährung, Compliance: compliant, Priority: P3) +- `protein_days_in_target` (Category: Unknown, Compliance: non_compliant, Priority: P0) +- `protein_g_per_kg` (Category: Ernährung, Compliance: non_compliant, Priority: P0) +- `protein_ziel_high` (Category: Unknown, Compliance: non_compliant, Priority: P0) +- `protein_ziel_low` (Category: Unknown, Compliance: non_compliant, Priority: P0) +- `proxy_internal_load_7d` (Category: Training, Compliance: partially_compliant, Priority: P1) +- `quality_sessions_pct` (Category: Training, Compliance: partially_compliant, Priority: P0) +- `recent_load_balance_3d` (Category: Unknown, Compliance: partially_compliant, Priority: P0) +- `recomposition_quadrant` (Category: Körper, Compliance: non_compliant, Priority: P0) +- `recovery_score` (Category: Scores (Phase 0b), Compliance: non_compliant, Priority: P0) +- `rest_day_compliance` (Category: Training, Compliance: partially_compliant, Priority: P0) +- `rest_days_count` (Category: Schlaf & Erholung, Compliance: partially_compliant, Priority: P0) +- `rhr_vs_baseline_pct` (Category: Vitalwerte, Compliance: partially_compliant, Priority: P0) +- `sleep_avg_duration` (Category: Schlaf & Erholung, Compliance: partially_compliant, Priority: P1) +- `sleep_avg_duration_7d` (Category: Schlaf & Erholung, Compliance: partially_compliant, Priority: P1) +- `sleep_avg_quality` (Category: Schlaf & Erholung, Compliance: partially_compliant, Priority: P1) +- `sleep_debt_hours` (Category: Schlaf & Erholung, Compliance: partially_compliant, Priority: P0) +- `sleep_quality_7d` (Category: Schlaf & Erholung, Compliance: partially_compliant, Priority: P1) +- `sleep_regularity_proxy` (Category: Schlaf & Erholung, Compliance: partially_compliant, Priority: P0) +- `strain_score` (Category: Unknown, Compliance: non_compliant, Priority: P0) +- `thigh_28d_delta` (Category: Unknown, Compliance: non_compliant, Priority: P0) +- `top_3_focus_areas` (Category: Unknown, Compliance: non_compliant, Priority: P0) +- `top_3_goals_behind_schedule` (Category: Unknown, Compliance: non_compliant, Priority: P0) +- `top_3_goals_on_track` (Category: Unknown, Compliance: non_compliant, Priority: P0) +- `top_drivers` (Category: Unknown, Compliance: non_compliant, Priority: P0) +- `top_focus_area_name` (Category: Focus Areas, Compliance: non_compliant, Priority: P0) +- `top_focus_area_progress` (Category: Focus Areas, Compliance: non_compliant, Priority: P0) +- `top_goal_name` (Category: Unknown, Compliance: non_compliant, Priority: P0) +- `top_goal_progress_pct` (Category: Unknown, Compliance: non_compliant, Priority: P0) +- `top_goal_status` (Category: Unknown, Compliance: non_compliant, Priority: P0) +- `training_frequency_7d` (Category: Training, Compliance: partially_compliant, Priority: P1) +- `training_minutes_week` (Category: Training, Compliance: partially_compliant, Priority: P1) +- `trainingstyp_verteilung` (Category: Training, Compliance: non_compliant, Priority: P0) +- `vitals_avg_hr` (Category: Vitalwerte, Compliance: partially_compliant, Priority: P1) +- `vitals_avg_hrv` (Category: Vitalwerte, Compliance: partially_compliant, Priority: P1) +- `vitals_vo2_max` (Category: Vitalwerte, Compliance: partially_compliant, Priority: P0) +- `vo2max_trend_28d` (Category: Vitalwerte, Compliance: non_compliant, Priority: P2) +- `waist_28d_delta` (Category: Körper, Compliance: non_compliant, Priority: P2) +- `waist_hip_ratio` (Category: Körper, Compliance: partially_compliant, Priority: P0) +- `weight_28d_slope` (Category: Körper, Compliance: non_compliant, Priority: P2) +- `weight_7d_median` (Category: Körper, Compliance: non_compliant, Priority: P2) +- `weight_90d_slope` (Category: Unknown, Compliance: non_compliant, Priority: P0) +- `weight_aktuell` (Category: Körper, Compliance: compliant, Priority: P3) +- `weight_trend` (Category: Körper, Compliance: compliant, Priority: P3) +- `zeitraum_30d` (Category: Zeitraum, Compliance: non_compliant, Priority: P2) +- `zeitraum_7d` (Category: Zeitraum, Compliance: non_compliant, Priority: P2) +- `zeitraum_90d` (Category: Unknown, Compliance: non_compliant, Priority: P0) + +## Partial Draft Specifications + +No placeholders with partial drafts. + +## Wrong Draft Specifications + +No placeholders with wrong drafts. + +## Recommendations + +1. **Priority 1:** Add draft specifications for all missing placeholders +2. **Priority 2:** Complete partial draft specifications +3. **Priority 3:** Correct wrong draft specifications + +### Bulk Update Strategy + +Use audit semantic analysis reports to bulk-populate: +- Category classifications +- Descriptions +- Time windows +- Source information + +Estimated effort: 6-10 hours for bulk updates. diff --git a/.claude/docs/audit/platzhalter/reconciliation-2026-03-30/EXECUTIVE_RECONCILIATION_SUMMARY.md b/.claude/docs/audit/platzhalter/reconciliation-2026-03-30/EXECUTIVE_RECONCILIATION_SUMMARY.md new file mode 100644 index 0000000..b0ea7d3 --- /dev/null +++ b/.claude/docs/audit/platzhalter/reconciliation-2026-03-30/EXECUTIVE_RECONCILIATION_SUMMARY.md @@ -0,0 +1,119 @@ +# Executive Reconciliation Summary + +**Generated:** 2026-03-30 14:58:37 +**Scope:** 111 Placeholders +**Data Sources:** Export Catalog, Audit Reports (2026-03-29), Draft Document + +## Overview + +This reconciliation consolidates data from three authoritative sources to create a single source of truth for all 111 placeholders. + +### Key Findings + +- **71** placeholders (63%) are implemented in code +- **8** placeholders (7%) are fully compliant with normative requirements +- **22** placeholders (20%) are partially compliant +- **81** placeholders (73%) are non-compliant + +### Reconciliation Results + +- **Verified Match (7):** Draft, Export, and Audit align perfectly +- **Needs Sharpening (49):** Correct but incomplete metadata +- **Needs Refactor (16):** Code-docs conflicts exist +- **Draft Wrong (0):** Draft specification incorrect +- **New Required (39):** Not implemented, needs build +- **Unclear (0):** Manual review needed + +### Priority Distribution + +- **P0 (Critical):** 83 placeholders + - High usage (breaking change risk) + - Missing documentation (category/description) + - Unknown time window +- **P1 (High):** 10 placeholders + - Partially compliant with minor gaps +- **P2 (Medium):** 10 placeholders + - Non-compliant, needs work +- **P3 (Low):** 8 placeholders + - Compliant, maintenance only + +## Critical Issues + +### 1. Known Code-Documentation Conflicts + +- **`weight_trend`**: Code uses 28d, docs say 7d/30d + - Resolution: Update docs to match code (28d) +- **`activity_summary`**: Code uses 14d, docs say 7d + - Resolution: Update docs to match code (14d) +- **`activity_detail`**: Time window unclear in code + - Resolution: Needs code review to determine actual time window + +### 2. High-Usage Placeholders (Breaking Change Risk) + +- **`name`** (19 uses, non_compliant) +- **`geschlecht`** (14 uses, non_compliant) +- **`height`** (12 uses, non_compliant) +- **`weight_aktuell`** (10 uses, compliant) +- **`weight_trend`** (10 uses, compliant) +- **`goal_bf_pct`** (10 uses, non_compliant) +- **`caliper_summary`** (8 uses, partially_compliant) +- **`circ_summary`** (8 uses, compliant) +- **`goal_weight`** (8 uses, non_compliant) +- **`protein_ziel_low`** (7 uses, non_compliant) +- **`protein_ziel_high`** (7 uses, non_compliant) +- **`activity_detail`** (4 uses, non_compliant) + +### 3. Missing Documentation (P0) + +**49** placeholders lack category classification +**49** placeholders lack descriptions + +## Recommendations + +### Immediate Actions (P0) + +1. **Resolve Known Conflicts (3 placeholders, 2-4 hours)** + - Update documentation to match code implementation + - Validate time windows in code + +2. **Classify Time Windows (74 placeholders, 8-12 hours)** + - Use name-based extraction for `*_7d`, `*_28d` patterns + - Extract from code default parameters + - Manual classification for unclear cases + +3. **Add Categories and Descriptions (49 placeholders, 4-6 hours)** + - Bulk update from audit semantic analysis + - Use existing audit report classifications + +### Next Sprint (P1) + +1. **Add Confidence Logic (103 placeholders, 12-16 hours)** + - Implement for trend/delta placeholders + - Add min-data thresholds + +2. **Document Data Layer Modules (100 placeholders, 6-8 hours)** + - Trace resolver functions to data layer + - Document source tables + +### Later (P2-P3) + +1. **Integrate Unused Placeholders (67 placeholders)** + - Create prompt use cases + - Plan integration timeline + +2. **Achieve Production Status (30+ placeholders)** + - Reach 80%+ metadata completeness + - Complete all normative requirements + +## Conclusion + +The reconciliation process successfully analyzed all 111 placeholders. +While only 8 are currently fully compliant, +the systematic gaps are primarily documentation-related rather than functional. +With a structured remediation plan (estimated 82-110 hours), the system can reach +>60% normative conformity within 4-6 weeks. + +**Next Steps:** +1. Review this summary with product/tech leads +2. Prioritize P0 items for immediate action +3. Begin systematic remediation following the implementation waves diff --git a/.claude/docs/audit/platzhalter/reconciliation-2026-03-30/IMPLEMENTATION_WAVES.md b/.claude/docs/audit/platzhalter/reconciliation-2026-03-30/IMPLEMENTATION_WAVES.md new file mode 100644 index 0000000..3d31406 --- /dev/null +++ b/.claude/docs/audit/platzhalter/reconciliation-2026-03-30/IMPLEMENTATION_WAVES.md @@ -0,0 +1,142 @@ +# Implementation Waves + +**Generated:** 2026-03-30 14:58:37 + +This document outlines a cluster-based implementation plan for placeholder remediation. + +## Overview + +Total remediation effort: **82-110 hours** over 4-6 weeks + +### Timeline + +- **Wave 1 (P0):** Week 1 - Critical fixes (14-20 hours) +- **Wave 2 (P1):** Weeks 2-3 - High priority (26-34 hours) +- **Wave 3 (P2):** Weeks 4-5 - Medium priority (18-24 hours) +- **Wave 4 (P3):** Later - Nice to have (24-32 hours) + +## Wave 1: Critical Fixes (P0) + +**Scope:** 83 placeholders +**Timeline:** Week 1 +**Effort:** 14-20 hours + +### 1.1 Resolve Known Conflicts (3 placeholders, 2 hours) + +- `weight_trend`: Update docs to match code (28d) +- `activity_summary`: Update docs to match code (14d) +- `activity_detail`: Needs code review to determine actual time window + +### 1.2 Classify Time Windows (74 placeholders, 8-12 hours) + +**Method:** +1. Name-based extraction (`*_7d`, `*_28d` patterns) - automatic +2. Code parameter extraction - semi-automatic +3. Manual classification for unclear cases + +### 1.3 Add Categories and Descriptions (49 placeholders, 4-6 hours) + +**Method:** +- Bulk update from audit semantic analysis +- Use provided classifications from audit report + +## Wave 2: High Priority (P1) + +**Scope:** 10 placeholders +**Timeline:** Weeks 2-3 +**Effort:** 26-34 hours + +### 2.1 Add Confidence Logic (11 trend/delta placeholders, 12-16 hours) + +**Placeholders:** +- `weight_28d_slope`, `weight_90d_slope`, `weight_7d_median` +- `fm_28d_change`, `lbm_28d_change` +- `waist_28d_delta`, `hip_28d_delta`, `chest_28d_delta`, `arm_28d_delta`, `thigh_28d_delta` +- `vo2max_trend_28d` + +**Pattern:** `confidence = calculate_confidence(data_points, time_window_days, 'trend')` + +### 2.2 Structured Missing-Value Policy (70 placeholders, 8-10 hours) + +**Refactor:** +- Keep legacy string for backward compatibility +- Add structured fields: `available`, `missing_reason`, `value_raw` + +### 2.3 Document Data Layer Modules (100 placeholders, 6-8 hours) + +**Method:** +- Trace resolver functions to data layer +- Document source tables from SQL queries + +## Wave 3: Medium Priority (P2) + +**Scope:** 10 placeholders +**Timeline:** Weeks 4-5 +**Effort:** 18-24 hours + +### 3.1 Integrate Unused Placeholders (67 placeholders, 4-6 hours) + +**Method:** +- Product management review for 30 planned placeholders +- Technical review for 37 plausible placeholders +- Create prompt use cases (5-10 quick wins) + +### 3.2 Metadata Completeness (111 placeholders, 10-12 hours) + +**Target:** Minimum 60% of placeholders with score >60 + +### 3.3 Production Status (20-30 core placeholders, 4-6 hours) + +**Criteria:** +- Metadata completeness >= 80% +- Used-by >= 1 +- No known issues +- Time window + confidence defined + +## Wave 4: Nice to Have (P3) + +**Scope:** 8 placeholders +**Timeline:** Later +**Effort:** 24-32 hours + +### 4.1 Validation Framework (16-20 hours) + +**Features:** +- Pre-commit hook for normative spec validation +- CI/CD consistency checks (code-catalog) +- Template generator for new placeholders + +### 4.2 Migration Guides (8-12 hours) + +**Content:** +- Best-practice guide (based on compliant examples) +- Anti-patterns to avoid +- Upgrade path for legacy prompts + +## Dependencies + +- **Wave 1 → Wave 2:** Time window classification must be complete before confidence logic +- **Wave 2 → Wave 3:** Data layer documentation enables better integration planning +- **Wave 3 → Wave 4:** Production-ready placeholders provide best-practice models + +## Success Metrics + +### After Wave 1: +- 0 unknown time windows +- 0 unknown categories +- 0 code-documentation conflicts + +### After Wave 2: +- 70%+ placeholders with confidence logic +- 100% placeholders with structured missing-value policies +- 100% placeholders with documented data layers + +### After Wave 3: +- 50-60% placeholder usage rate +- 60%+ placeholders with metadata completeness >60 +- 20-30 production-ready placeholders + +### After Wave 4: +- Automated validation in CI/CD +- Best-practice documentation for developers +- Sustainable maintenance process diff --git a/.claude/docs/audit/platzhalter/reconciliation-2026-03-30/PLACEHOLDER_RECONCILIATION_MATRIX.json b/.claude/docs/audit/platzhalter/reconciliation-2026-03-30/PLACEHOLDER_RECONCILIATION_MATRIX.json new file mode 100644 index 0000000..666542e --- /dev/null +++ b/.claude/docs/audit/platzhalter/reconciliation-2026-03-30/PLACEHOLDER_RECONCILIATION_MATRIX.json @@ -0,0 +1,3873 @@ +{ + "schema_version": "1.0.0", + "generated_at": "2026-03-30T14:58:37.909256", + "total_placeholders": 111, + "statistics": { + "total": 111, + "exists_in_code": 71, + "draft_status": { + "full": 0, + "partial": 0, + "missing": 111, + "wrong": 0 + }, + "compliance_level": { + "compliant": 8, + "partially_compliant": 22, + "non_compliant": 81 + }, + "reconciliation": { + "verified_match": 7, + "needs_sharpening": 49, + "needs_refactor": 16, + "draft_wrong": 0, + "new_required": 39, + "unclear": 0 + }, + "confidence": { + "high": 10, + "medium": 49, + "low": 52 + }, + "priority": { + "P0": 83, + "P1": 10, + "P2": 10, + "P3": 8 + } + }, + "placeholders": { + "ability_balance_coordination": { + "exists_in_code": false, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_int", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": false, + "notes": "Needs review" + }, + "reconciliation": "new_required", + "action": "build_new", + "confidence": "low", + "notes": "Not implemented, needs build" + }, + "ability_balance_endurance": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Training", + "description": "Ability Balance - Ausdauer (0-100)", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_int", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Time window needs classification | Draft specification missing for implemented feature" + }, + "ability_balance_mental": { + "exists_in_code": false, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_int", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": false, + "notes": "Needs review" + }, + "reconciliation": "new_required", + "action": "build_new", + "confidence": "low", + "notes": "Not implemented, needs build" + }, + "ability_balance_mobility": { + "exists_in_code": false, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_int", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": false, + "notes": "Needs review" + }, + "reconciliation": "new_required", + "action": "build_new", + "confidence": "low", + "notes": "Not implemented, needs build" + }, + "ability_balance_strength": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Training", + "description": "Ability Balance - Kraft (0-100)", + "time_window": "unknown", + "used_in_count": 1, + "used_in": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "resolver": "_safe_int", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Time window needs classification | Draft specification missing for implemented feature" + }, + "active_goals_json": { + "exists_in_code": false, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_json", + "data_layer_module": null, + "source_tables": [ + "goals", + "focus_area_definitions", + "goal_focus_contributions" + ] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": false, + "notes": "Needs review" + }, + "reconciliation": "new_required", + "action": "build_new", + "confidence": "low", + "notes": "Not implemented, needs build" + }, + "active_goals_md": { + "exists_in_code": false, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_str", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": false, + "notes": "Needs review" + }, + "reconciliation": "new_required", + "action": "build_new", + "confidence": "low", + "notes": "Not implemented, needs build" + }, + "activity_detail": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "unknown", + "used_in_count": 4, + "used_in": { + "prompts": [ + "Aktivität & Training", + "Pipeline: Aktivitäts-Analyse (JSON)" + ], + "pipelines": [ + "Aktivität & Training", + "Pipeline: Aktivitäts-Analyse (JSON)" + ], + "charts": [] + }, + "resolver": "get_activity_detail", + "data_layer_module": "activity_metrics", + "source_tables": [ + "activity_log", + "training_types" + ] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": true, + "notes": "Needs review" + }, + "reconciliation": "needs_refactor", + "action": "code_change", + "confidence": "high", + "notes": "Implementation exists but lacks documentation | Category needs classification | Description needs to be written | Known conflict: Time window unclear in code | High usage (4 references) - breaking change risk | Draft specification missing for implemented feature" + }, + "activity_score": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Scores (Phase 0b)", + "description": "Activity Score (0-100)", + "time_window": "unknown", + "used_in_count": 1, + "used_in": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "resolver": "_safe_int", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Time window needs classification | Draft specification missing for implemented feature" + }, + "activity_summary": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Training", + "description": "Aktivitäts-Zusammenfassung (7d)", + "time_window": "unknown", + "used_in_count": 2, + "used_in": { + "prompts": [ + "Gesamtanalyse" + ], + "pipelines": [ + "Gesamtanalyse" + ], + "charts": [] + }, + "resolver": "get_activity_summary", + "data_layer_module": "activity_metrics", + "source_tables": [ + "activity_log" + ] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_refactor", + "action": "code_change", + "confidence": "high", + "notes": "Time window needs classification | Known conflict: Code uses 14d, docs say 7d | Draft specification missing for implemented feature" + }, + "age": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Profil", + "description": "Alter in Jahren", + "time_window": "latest", + "used_in_count": 2, + "used_in": { + "prompts": [ + "Pipeline: Körper-Analyse (JSON)" + ], + "pipelines": [ + "Pipeline: Körper-Analyse (JSON)" + ], + "charts": [] + }, + "resolver": "calculate_age", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "compliant", + "priority": "P3", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "verified_match", + "action": "no_change", + "confidence": "high", + "notes": "Fully compliant with normative requirements | Draft specification missing for implemented feature" + }, + "arm_28d_delta": { + "exists_in_code": false, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "28d", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_float", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": false, + "notes": "Needs review" + }, + "reconciliation": "new_required", + "action": "build_new", + "confidence": "low", + "notes": "Not implemented, needs build" + }, + "bmi": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Körper", + "description": "Body Mass Index", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "calculate_bmi", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "partially_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Partial compliance - needs metadata enrichment | Time window needs classification | Draft specification missing for implemented feature" + }, + "body_progress_score": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Scores (Phase 0b)", + "description": "Body Progress Score (0-100)", + "time_window": "unknown", + "used_in_count": 1, + "used_in": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "resolver": "_safe_int", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Time window needs classification | Draft specification missing for implemented feature" + }, + "caliper_summary": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "unknown", + "used_in_count": 8, + "used_in": { + "prompts": [ + "Gesamtanalyse", + "Körperkomposition", + "Gesundheitsindikatoren", + "Pipeline: Körper-Analyse (JSON)" + ], + "pipelines": [ + "Gesamtanalyse", + "Körperkomposition", + "Gesundheitsindikatoren", + "Pipeline: Körper-Analyse (JSON)" + ], + "charts": [] + }, + "resolver": "get_caliper_summary", + "data_layer_module": "body_metrics", + "source_tables": [ + "caliper_log" + ] + }, + "audit_status": { + "compliance_level": "partially_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": true, + "notes": "Needs review" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Partial compliance - needs metadata enrichment | Time window needs classification | High usage (8 references) - breaking change risk | Draft specification missing for implemented feature" + }, + "carb_avg": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Ernährung", + "description": "Durchschn. Kohlenhydrate in g (30d)", + "time_window": "30d", + "used_in_count": 2, + "used_in": { + "prompts": [ + "Pipeline: Ernährungs-Analyse (JSON)" + ], + "pipelines": [ + "Pipeline: Ernährungs-Analyse (JSON)" + ], + "charts": [] + }, + "resolver": "get_nutrition_avg", + "data_layer_module": "nutrition_metrics", + "source_tables": [ + "nutrition_log" + ] + }, + "audit_status": { + "compliance_level": "compliant", + "priority": "P3", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "verified_match", + "action": "no_change", + "confidence": "high", + "notes": "Fully compliant with normative requirements | Draft specification missing for implemented feature" + }, + "chest_28d_delta": { + "exists_in_code": false, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "28d", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_float", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": false, + "notes": "Needs review" + }, + "reconciliation": "new_required", + "action": "build_new", + "confidence": "low", + "notes": "Not implemented, needs build" + }, + "circ_summary": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "mixed", + "used_in_count": 8, + "used_in": { + "prompts": [ + "Gesamtanalyse", + "Körperkomposition", + "Gesundheitsindikatoren", + "Pipeline: Körper-Analyse (JSON)" + ], + "pipelines": [ + "Gesamtanalyse", + "Körperkomposition", + "Gesundheitsindikatoren", + "Pipeline: Körper-Analyse (JSON)" + ], + "charts": [] + }, + "resolver": "get_circ_summary", + "data_layer_module": "body_metrics", + "source_tables": [ + "circumference_log" + ] + }, + "audit_status": { + "compliance_level": "compliant", + "priority": "P3", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": true, + "notes": "Needs review" + }, + "reconciliation": "verified_match", + "action": "no_change", + "confidence": "high", + "notes": "Fully compliant with normative requirements | High usage (8 references) - breaking change risk | Draft specification missing for implemented feature" + }, + "correlation_energy_weight_lag": { + "exists_in_code": false, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_json", + "data_layer_module": null, + "source_tables": [ + "goals", + "focus_area_definitions", + "goal_focus_contributions" + ] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": false, + "notes": "Needs review" + }, + "reconciliation": "new_required", + "action": "build_new", + "confidence": "low", + "notes": "Not implemented, needs build" + }, + "correlation_load_hrv": { + "exists_in_code": false, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_json", + "data_layer_module": null, + "source_tables": [ + "goals", + "focus_area_definitions", + "goal_focus_contributions" + ] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": false, + "notes": "Needs review" + }, + "reconciliation": "new_required", + "action": "build_new", + "confidence": "low", + "notes": "Not implemented, needs build" + }, + "correlation_load_rhr": { + "exists_in_code": false, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_json", + "data_layer_module": null, + "source_tables": [ + "goals", + "focus_area_definitions", + "goal_focus_contributions" + ] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": false, + "notes": "Needs review" + }, + "reconciliation": "new_required", + "action": "build_new", + "confidence": "low", + "notes": "Not implemented, needs build" + }, + "correlation_protein_lbm": { + "exists_in_code": false, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_json", + "data_layer_module": null, + "source_tables": [ + "goals", + "focus_area_definitions", + "goal_focus_contributions" + ] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": false, + "notes": "Needs review" + }, + "reconciliation": "new_required", + "action": "build_new", + "confidence": "low", + "notes": "Not implemented, needs build" + }, + "correlation_sleep_recovery": { + "exists_in_code": false, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_json", + "data_layer_module": null, + "source_tables": [ + "goals", + "focus_area_definitions", + "goal_focus_contributions" + ] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": false, + "notes": "Needs review" + }, + "reconciliation": "new_required", + "action": "build_new", + "confidence": "low", + "notes": "Not implemented, needs build" + }, + "data_quality_score": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Scores (Phase 0b)", + "description": "Data Quality Score (0-100)", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_int", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Time window needs classification | Draft specification missing for implemented feature" + }, + "datum_heute": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Zeitraum", + "description": "Heutiges Datum", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Time window needs classification | Draft specification missing for implemented feature" + }, + "energy_balance_7d": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Ernährung", + "description": "Energiebilanz 7d (kcal/Tag)", + "time_window": "7d", + "used_in_count": 1, + "used_in": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "resolver": "_safe_float", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P2", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_refactor", + "action": "code_change", + "confidence": "low", + "notes": "Code-docs conflicts or quality issues | Draft specification missing for implemented feature" + }, + "energy_deficit_surplus": { + "exists_in_code": false, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_str", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": false, + "notes": "Needs review" + }, + "reconciliation": "new_required", + "action": "build_new", + "confidence": "low", + "notes": "Not implemented, needs build" + }, + "fat_avg": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Ernährung", + "description": "Durchschn. Fett in g (30d)", + "time_window": "30d", + "used_in_count": 2, + "used_in": { + "prompts": [ + "Pipeline: Ernährungs-Analyse (JSON)" + ], + "pipelines": [ + "Pipeline: Ernährungs-Analyse (JSON)" + ], + "charts": [] + }, + "resolver": "get_nutrition_avg", + "data_layer_module": "nutrition_metrics", + "source_tables": [ + "nutrition_log" + ] + }, + "audit_status": { + "compliance_level": "compliant", + "priority": "P3", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "verified_match", + "action": "no_change", + "confidence": "high", + "notes": "Fully compliant with normative requirements | Draft specification missing for implemented feature" + }, + "fm_28d_change": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Körper", + "description": "Fettmasse Änderung 28d (kg)", + "time_window": "28d", + "used_in_count": 1, + "used_in": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "resolver": "_safe_float", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P2", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_refactor", + "action": "code_change", + "confidence": "low", + "notes": "Code-docs conflicts or quality issues | Draft specification missing for implemented feature" + }, + "focus_area_weights_json": { + "exists_in_code": false, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_json", + "data_layer_module": null, + "source_tables": [ + "goals", + "focus_area_definitions", + "goal_focus_contributions" + ] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": false, + "notes": "Needs review" + }, + "reconciliation": "new_required", + "action": "build_new", + "confidence": "low", + "notes": "Not implemented, needs build" + }, + "focus_areas_weighted_json": { + "exists_in_code": false, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_json", + "data_layer_module": null, + "source_tables": [ + "goals", + "focus_area_definitions", + "goal_focus_contributions" + ] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": false, + "notes": "Needs review" + }, + "reconciliation": "new_required", + "action": "build_new", + "confidence": "low", + "notes": "Not implemented, needs build" + }, + "focus_areas_weighted_md": { + "exists_in_code": false, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_str", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": false, + "notes": "Needs review" + }, + "reconciliation": "new_required", + "action": "build_new", + "confidence": "low", + "notes": "Not implemented, needs build" + }, + "focus_cat_aktivität_progress": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Focus Areas", + "description": "Kategorie Aktivität - Progress (%)", + "time_window": "unknown", + "used_in_count": 1, + "used_in": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "resolver": "_safe_int", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Time window needs classification | Draft specification missing for implemented feature" + }, + "focus_cat_aktivität_weight": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Focus Areas", + "description": "Kategorie Aktivität - Gewichtung (%)", + "time_window": "unknown", + "used_in_count": 1, + "used_in": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "resolver": "_safe_float", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Time window needs classification | Draft specification missing for implemented feature" + }, + "focus_cat_ernährung_progress": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Focus Areas", + "description": "Kategorie Ernährung - Progress (%)", + "time_window": "unknown", + "used_in_count": 1, + "used_in": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "resolver": "_safe_int", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Time window needs classification | Draft specification missing for implemented feature" + }, + "focus_cat_ernährung_weight": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Focus Areas", + "description": "Kategorie Ernährung - Gewichtung (%)", + "time_window": "unknown", + "used_in_count": 1, + "used_in": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "resolver": "_safe_float", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Time window needs classification | Draft specification missing for implemented feature" + }, + "focus_cat_körper_progress": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Focus Areas", + "description": "Kategorie Körper - Progress (%)", + "time_window": "unknown", + "used_in_count": 1, + "used_in": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "resolver": "_safe_int", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Time window needs classification | Draft specification missing for implemented feature" + }, + "focus_cat_körper_weight": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Focus Areas", + "description": "Kategorie Körper - Gewichtung (%)", + "time_window": "unknown", + "used_in_count": 1, + "used_in": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "resolver": "_safe_float", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Time window needs classification | Draft specification missing for implemented feature" + }, + "focus_cat_lebensstil_progress": { + "exists_in_code": false, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_int", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": false, + "notes": "Needs review" + }, + "reconciliation": "new_required", + "action": "build_new", + "confidence": "low", + "notes": "Not implemented, needs build" + }, + "focus_cat_lebensstil_weight": { + "exists_in_code": false, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_float", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": false, + "notes": "Needs review" + }, + "reconciliation": "new_required", + "action": "build_new", + "confidence": "low", + "notes": "Not implemented, needs build" + }, + "focus_cat_mental_progress": { + "exists_in_code": false, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_int", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": false, + "notes": "Needs review" + }, + "reconciliation": "new_required", + "action": "build_new", + "confidence": "low", + "notes": "Not implemented, needs build" + }, + "focus_cat_mental_weight": { + "exists_in_code": false, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_float", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": false, + "notes": "Needs review" + }, + "reconciliation": "new_required", + "action": "build_new", + "confidence": "low", + "notes": "Not implemented, needs build" + }, + "focus_cat_recovery_progress": { + "exists_in_code": false, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_int", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": false, + "notes": "Needs review" + }, + "reconciliation": "new_required", + "action": "build_new", + "confidence": "low", + "notes": "Not implemented, needs build" + }, + "focus_cat_recovery_weight": { + "exists_in_code": false, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_float", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": false, + "notes": "Needs review" + }, + "reconciliation": "new_required", + "action": "build_new", + "confidence": "low", + "notes": "Not implemented, needs build" + }, + "focus_cat_vitalwerte_progress": { + "exists_in_code": false, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_int", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": false, + "notes": "Needs review" + }, + "reconciliation": "new_required", + "action": "build_new", + "confidence": "low", + "notes": "Not implemented, needs build" + }, + "focus_cat_vitalwerte_weight": { + "exists_in_code": false, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_float", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": false, + "notes": "Needs review" + }, + "reconciliation": "new_required", + "action": "build_new", + "confidence": "low", + "notes": "Not implemented, needs build" + }, + "geschlecht": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Profil", + "description": "Geschlecht", + "time_window": "latest", + "used_in_count": 14, + "used_in": { + "prompts": [ + "Gesamtanalyse", + "Körperkomposition", + "Gesundheitsindikatoren", + "Aktivität & Training", + "Pipeline: Körper-Analyse (JSON)", + "Ernährung & Kalorien (Kopie)", + "Ernährung & Kalorien" + ], + "pipelines": [ + "Gesamtanalyse", + "Körperkomposition", + "Gesundheitsindikatoren", + "Aktivität & Training", + "Pipeline: Körper-Analyse (JSON)", + "Ernährung & Kalorien (Kopie)", + "Ernährung & Kalorien" + ], + "charts": [] + }, + "resolver": "", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_refactor", + "action": "code_change", + "confidence": "low", + "notes": "Code-docs conflicts or quality issues | High usage (14 references) - breaking change risk | Draft specification missing for implemented feature" + }, + "goal_bf_pct": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "unknown", + "used_in_count": 10, + "used_in": { + "prompts": [ + "Gesamtanalyse", + "Körperkomposition", + "Pipeline: Körper-Analyse (JSON)", + "Pipeline: Zielabgleich", + "Fortschritt zu Zielen" + ], + "pipelines": [ + "Gesamtanalyse", + "Körperkomposition", + "Pipeline: Körper-Analyse (JSON)", + "Pipeline: Zielabgleich", + "Fortschritt zu Zielen" + ], + "charts": [] + }, + "resolver": "get_goal_bf_pct", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": true, + "notes": "Needs review" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Implementation exists but lacks documentation | Category needs classification | Description needs to be written | High usage (10 references) - breaking change risk | Draft specification missing for implemented feature" + }, + "goal_progress_score": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Scores (Phase 0b)", + "description": "Goal Progress Score (0-100)", + "time_window": "unknown", + "used_in_count": 1, + "used_in": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "resolver": "_safe_int", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Time window needs classification | Draft specification missing for implemented feature" + }, + "goal_weight": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "unknown", + "used_in_count": 8, + "used_in": { + "prompts": [ + "Gesamtanalyse", + "Pipeline: Körper-Analyse (JSON)", + "Pipeline: Zielabgleich", + "Fortschritt zu Zielen" + ], + "pipelines": [ + "Gesamtanalyse", + "Pipeline: Körper-Analyse (JSON)", + "Pipeline: Zielabgleich", + "Fortschritt zu Zielen" + ], + "charts": [] + }, + "resolver": "get_goal_weight", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": true, + "notes": "Needs review" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Implementation exists but lacks documentation | Category needs classification | Description needs to be written | High usage (8 references) - breaking change risk | Draft specification missing for implemented feature" + }, + "height": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Profil", + "description": "Körpergröße in cm", + "time_window": "latest", + "used_in_count": 12, + "used_in": { + "prompts": [ + "Gesamtanalyse", + "Körperkomposition", + "Gesundheitsindikatoren", + "Pipeline: Körper-Analyse (JSON)", + "Ernährung & Kalorien (Kopie)", + "Ernährung & Kalorien" + ], + "pipelines": [ + "Gesamtanalyse", + "Körperkomposition", + "Gesundheitsindikatoren", + "Pipeline: Körper-Analyse (JSON)", + "Ernährung & Kalorien (Kopie)", + "Ernährung & Kalorien" + ], + "charts": [] + }, + "resolver": "str", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_refactor", + "action": "code_change", + "confidence": "low", + "notes": "Code-docs conflicts or quality issues | High usage (12 references) - breaking change risk | Draft specification missing for implemented feature" + }, + "hip_28d_delta": { + "exists_in_code": false, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "28d", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_float", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": false, + "notes": "Needs review" + }, + "reconciliation": "new_required", + "action": "build_new", + "confidence": "low", + "notes": "Not implemented, needs build" + }, + "hrv_vs_baseline_pct": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Vitalwerte", + "description": "HRV vs. Baseline (%)", + "time_window": "unknown", + "used_in_count": 1, + "used_in": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "resolver": "_safe_float", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "partially_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Partial compliance - needs metadata enrichment | Time window needs classification | Draft specification missing for implemented feature" + }, + "intake_volatility": { + "exists_in_code": false, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_str", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": false, + "notes": "Needs review" + }, + "reconciliation": "new_required", + "action": "build_new", + "confidence": "low", + "notes": "Not implemented, needs build" + }, + "kcal_avg": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Ernährung", + "description": "Durchschn. Kalorien (30d)", + "time_window": "30d", + "used_in_count": 1, + "used_in": { + "prompts": [], + "pipelines": [ + "Pipeline: Ernährungs-Analyse (JSON)" + ], + "charts": [] + }, + "resolver": "get_nutrition_avg", + "data_layer_module": "nutrition_metrics", + "source_tables": [ + "nutrition_log" + ] + }, + "audit_status": { + "compliance_level": "compliant", + "priority": "P3", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "verified_match", + "action": "no_change", + "confidence": "high", + "notes": "Fully compliant with normative requirements | Draft specification missing for implemented feature" + }, + "kf_aktuell": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Körper", + "description": "Aktueller Körperfettanteil in %", + "time_window": "latest", + "used_in_count": 2, + "used_in": { + "prompts": [ + "Fortschritt zu Zielen" + ], + "pipelines": [ + "Fortschritt zu Zielen" + ], + "charts": [] + }, + "resolver": "get_latest_bf", + "data_layer_module": "body_metrics", + "source_tables": [ + "caliper_log" + ] + }, + "audit_status": { + "compliance_level": "partially_compliant", + "priority": "P1", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Partial compliance - needs metadata enrichment | Draft specification missing for implemented feature" + }, + "lbm_28d_change": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Körper", + "description": "Magermasse Änderung 28d (kg)", + "time_window": "28d", + "used_in_count": 1, + "used_in": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "resolver": "_safe_float", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P2", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_refactor", + "action": "code_change", + "confidence": "low", + "notes": "Code-docs conflicts or quality issues | Draft specification missing for implemented feature" + }, + "macro_consistency_score": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Ernährung", + "description": "Makro-Konsistenz Score (0-100)", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_int", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Time window needs classification | Draft specification missing for implemented feature" + }, + "monotony_score": { + "exists_in_code": false, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_float", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": false, + "notes": "Needs review" + }, + "reconciliation": "new_required", + "action": "build_new", + "confidence": "low", + "notes": "Not implemented, needs build" + }, + "name": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Profil", + "description": "Name des Nutzers", + "time_window": "latest", + "used_in_count": 19, + "used_in": { + "prompts": [ + "Gesamtanalyse", + "Körperkomposition", + "Gesundheitsindikatoren", + "Aktivität & Training", + "Pipeline: Körper-Analyse (JSON)", + "Ernährung & Kalorien (Kopie)", + "Pipeline: Zielabgleich", + "Ernährung & Kalorien", + "Fortschritt zu Zielen" + ], + "pipelines": [ + "Gesamtanalyse", + "Körperkomposition", + "Gesundheitsindikatoren", + "Aktivität & Training", + "Pipeline: Körper-Analyse (JSON)", + "Ernährung & Kalorien (Kopie)", + "Pipeline: Zielabgleich", + "Ernährung & Kalorien", + "Fortschritt zu Zielen", + "Gesamtanalyse (advanced)" + ], + "charts": [] + }, + "resolver": "get_profile_data", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_refactor", + "action": "code_change", + "confidence": "low", + "notes": "Code-docs conflicts or quality issues | High usage (19 references) - breaking change risk | Draft specification missing for implemented feature" + }, + "nutrition_days": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "unknown", + "used_in_count": 2, + "used_in": { + "prompts": [ + "Pipeline: Ernährungs-Analyse (JSON)" + ], + "pipelines": [ + "Pipeline: Ernährungs-Analyse (JSON)" + ], + "charts": [] + }, + "resolver": "get_nutrition_days", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": true, + "notes": "Needs review" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Implementation exists but lacks documentation | Category needs classification | Description needs to be written | Draft specification missing for implemented feature" + }, + "nutrition_score": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Scores (Phase 0b)", + "description": "Nutrition Score (0-100)", + "time_window": "unknown", + "used_in_count": 1, + "used_in": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "resolver": "_safe_int", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Time window needs classification | Draft specification missing for implemented feature" + }, + "plateau_detected": { + "exists_in_code": false, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_json", + "data_layer_module": null, + "source_tables": [ + "goals", + "focus_area_definitions", + "goal_focus_contributions" + ] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": false, + "notes": "Needs review" + }, + "reconciliation": "new_required", + "action": "build_new", + "confidence": "low", + "notes": "Not implemented, needs build" + }, + "protein_adequacy_28d": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Ernährung", + "description": "Protein Adequacy Score (0-100)", + "time_window": "28d", + "used_in_count": 1, + "used_in": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "resolver": "_safe_int", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P2", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_refactor", + "action": "code_change", + "confidence": "low", + "notes": "Code-docs conflicts or quality issues | Draft specification missing for implemented feature" + }, + "protein_avg": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Ernährung", + "description": "Durchschn. Protein in g (30d)", + "time_window": "30d", + "used_in_count": 1, + "used_in": { + "prompts": [], + "pipelines": [ + "Pipeline: Ernährungs-Analyse (JSON)" + ], + "charts": [] + }, + "resolver": "get_nutrition_avg", + "data_layer_module": "nutrition_metrics", + "source_tables": [ + "nutrition_log" + ] + }, + "audit_status": { + "compliance_level": "compliant", + "priority": "P3", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "verified_match", + "action": "no_change", + "confidence": "high", + "notes": "Fully compliant with normative requirements | Draft specification missing for implemented feature" + }, + "protein_days_in_target": { + "exists_in_code": false, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_str", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": false, + "notes": "Needs review" + }, + "reconciliation": "new_required", + "action": "build_new", + "confidence": "low", + "notes": "Not implemented, needs build" + }, + "protein_g_per_kg": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Ernährung", + "description": "Protein g/kg Körpergewicht", + "time_window": "unknown", + "used_in_count": 1, + "used_in": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "resolver": "_safe_float", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Time window needs classification | Draft specification missing for implemented feature" + }, + "protein_ziel_high": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "unknown", + "used_in_count": 7, + "used_in": { + "prompts": [ + "Ernährung & Kalorien (Kopie)", + "Ernährung & Kalorien", + "Pipeline: Ernährungs-Analyse (JSON)" + ], + "pipelines": [ + "Ernährung & Kalorien (Kopie)", + "Ernährung & Kalorien", + "Pipeline: Ernährungs-Analyse (JSON)", + "Gesamtanalyse (advanced)" + ], + "charts": [] + }, + "resolver": "get_protein_ziel_high", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": true, + "notes": "Needs review" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Implementation exists but lacks documentation | Category needs classification | Description needs to be written | High usage (7 references) - breaking change risk | Draft specification missing for implemented feature" + }, + "protein_ziel_low": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "unknown", + "used_in_count": 7, + "used_in": { + "prompts": [ + "Ernährung & Kalorien (Kopie)", + "Ernährung & Kalorien", + "Pipeline: Ernährungs-Analyse (JSON)" + ], + "pipelines": [ + "Ernährung & Kalorien (Kopie)", + "Ernährung & Kalorien", + "Pipeline: Ernährungs-Analyse (JSON)", + "Gesamtanalyse (advanced)" + ], + "charts": [] + }, + "resolver": "get_protein_ziel_low", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": true, + "notes": "Needs review" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Implementation exists but lacks documentation | Category needs classification | Description needs to be written | High usage (7 references) - breaking change risk | Draft specification missing for implemented feature" + }, + "proxy_internal_load_7d": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Training", + "description": "Proxy Load 7d", + "time_window": "7d", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_int", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "partially_compliant", + "priority": "P1", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Partial compliance - needs metadata enrichment | Draft specification missing for implemented feature" + }, + "quality_sessions_pct": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Training", + "description": "Qualitätssessions (%)", + "time_window": "unknown", + "used_in_count": 1, + "used_in": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "resolver": "_safe_int", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "partially_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Partial compliance - needs metadata enrichment | Time window needs classification | Draft specification missing for implemented feature" + }, + "recent_load_balance_3d": { + "exists_in_code": false, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_int", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "partially_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": false, + "notes": "Needs review" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Partial compliance - needs metadata enrichment | Time window needs classification" + }, + "recomposition_quadrant": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Körper", + "description": "Rekomposition-Status", + "time_window": "unknown", + "used_in_count": 1, + "used_in": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "resolver": "_safe_str", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Time window needs classification | Draft specification missing for implemented feature" + }, + "recovery_score": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Scores (Phase 0b)", + "description": "Recovery Score (0-100)", + "time_window": "unknown", + "used_in_count": 1, + "used_in": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "resolver": "_safe_int", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Time window needs classification | Draft specification missing for implemented feature" + }, + "rest_day_compliance": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Training", + "description": "Ruhetags-Compliance (%)", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_int", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "partially_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Partial compliance - needs metadata enrichment | Time window needs classification | Draft specification missing for implemented feature" + }, + "rest_days_count": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Schlaf & Erholung", + "description": "Anzahl Ruhetage (30d)", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "get_rest_days_count", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "partially_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Partial compliance - needs metadata enrichment | Time window needs classification | Draft specification missing for implemented feature" + }, + "rhr_vs_baseline_pct": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Vitalwerte", + "description": "RHR vs. Baseline (%)", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_float", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "partially_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Partial compliance - needs metadata enrichment | Time window needs classification | Draft specification missing for implemented feature" + }, + "sleep_avg_duration": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Schlaf & Erholung", + "description": "Durchschn. Schlafdauer (7d)", + "time_window": "30d", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "get_sleep_avg_duration", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "partially_compliant", + "priority": "P1", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Partial compliance - needs metadata enrichment | Draft specification missing for implemented feature" + }, + "sleep_avg_duration_7d": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Schlaf & Erholung", + "description": "Schlaf 7d (Stunden)", + "time_window": "7d", + "used_in_count": 1, + "used_in": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "resolver": "_safe_float", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "partially_compliant", + "priority": "P1", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Partial compliance - needs metadata enrichment | Draft specification missing for implemented feature" + }, + "sleep_avg_quality": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Schlaf & Erholung", + "description": "Durchschn. Schlafqualität (7d)", + "time_window": "30d", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "get_sleep_avg_quality", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "partially_compliant", + "priority": "P1", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Partial compliance - needs metadata enrichment | Draft specification missing for implemented feature" + }, + "sleep_debt_hours": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Schlaf & Erholung", + "description": "Schlafschuld (Stunden)", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_float", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "partially_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Partial compliance - needs metadata enrichment | Time window needs classification | Draft specification missing for implemented feature" + }, + "sleep_quality_7d": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Schlaf & Erholung", + "description": "Schlafqualität 7d (0-100)", + "time_window": "7d", + "used_in_count": 1, + "used_in": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "resolver": "_safe_int", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "partially_compliant", + "priority": "P1", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Partial compliance - needs metadata enrichment | Draft specification missing for implemented feature" + }, + "sleep_regularity_proxy": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Schlaf & Erholung", + "description": "Schlaf-Regelmäßigkeit (Min Abweichung)", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_float", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "partially_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Partial compliance - needs metadata enrichment | Time window needs classification | Draft specification missing for implemented feature" + }, + "strain_score": { + "exists_in_code": false, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_int", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": false, + "notes": "Needs review" + }, + "reconciliation": "new_required", + "action": "build_new", + "confidence": "low", + "notes": "Not implemented, needs build" + }, + "thigh_28d_delta": { + "exists_in_code": false, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "28d", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_float", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": false, + "notes": "Needs review" + }, + "reconciliation": "new_required", + "action": "build_new", + "confidence": "low", + "notes": "Not implemented, needs build" + }, + "top_3_focus_areas": { + "exists_in_code": false, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_str", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": false, + "notes": "Needs review" + }, + "reconciliation": "new_required", + "action": "build_new", + "confidence": "low", + "notes": "Not implemented, needs build" + }, + "top_3_goals_behind_schedule": { + "exists_in_code": false, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_str", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": false, + "notes": "Needs review" + }, + "reconciliation": "new_required", + "action": "build_new", + "confidence": "low", + "notes": "Not implemented, needs build" + }, + "top_3_goals_on_track": { + "exists_in_code": false, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_str", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": false, + "notes": "Needs review" + }, + "reconciliation": "new_required", + "action": "build_new", + "confidence": "low", + "notes": "Not implemented, needs build" + }, + "top_drivers": { + "exists_in_code": false, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_json", + "data_layer_module": null, + "source_tables": [ + "goals", + "focus_area_definitions", + "goal_focus_contributions" + ] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": false, + "notes": "Needs review" + }, + "reconciliation": "new_required", + "action": "build_new", + "confidence": "low", + "notes": "Not implemented, needs build" + }, + "top_focus_area_name": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Focus Areas", + "description": "Top Focus Area Name", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_str", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Time window needs classification | Draft specification missing for implemented feature" + }, + "top_focus_area_progress": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Focus Areas", + "description": "Top Focus Area Progress (%)", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_int", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Time window needs classification | Draft specification missing for implemented feature" + }, + "top_goal_name": { + "exists_in_code": false, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_str", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": false, + "notes": "Needs review" + }, + "reconciliation": "new_required", + "action": "build_new", + "confidence": "low", + "notes": "Not implemented, needs build" + }, + "top_goal_progress_pct": { + "exists_in_code": false, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_str", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": false, + "notes": "Needs review" + }, + "reconciliation": "new_required", + "action": "build_new", + "confidence": "low", + "notes": "Not implemented, needs build" + }, + "top_goal_status": { + "exists_in_code": false, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_str", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": false, + "notes": "Needs review" + }, + "reconciliation": "new_required", + "action": "build_new", + "confidence": "low", + "notes": "Not implemented, needs build" + }, + "training_frequency_7d": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Training", + "description": "Trainingshäufigkeit 7d", + "time_window": "7d", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_int", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "partially_compliant", + "priority": "P1", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Partial compliance - needs metadata enrichment | Draft specification missing for implemented feature" + }, + "training_minutes_week": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Training", + "description": "Trainingsminuten pro Woche", + "time_window": "7d", + "used_in_count": 1, + "used_in": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "resolver": "_safe_int", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "partially_compliant", + "priority": "P1", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Partial compliance - needs metadata enrichment | Draft specification missing for implemented feature" + }, + "trainingstyp_verteilung": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Training", + "description": "Verteilung nach Trainingstypen", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "get_trainingstyp_verteilung", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Time window needs classification | Draft specification missing for implemented feature" + }, + "vitals_avg_hr": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Vitalwerte", + "description": "Durchschn. Ruhepuls (7d)", + "time_window": "30d", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "get_vitals_avg_hr", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "partially_compliant", + "priority": "P1", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Partial compliance - needs metadata enrichment | Draft specification missing for implemented feature" + }, + "vitals_avg_hrv": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Vitalwerte", + "description": "Durchschn. HRV (7d)", + "time_window": "30d", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "get_vitals_avg_hrv", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "partially_compliant", + "priority": "P1", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Partial compliance - needs metadata enrichment | Draft specification missing for implemented feature" + }, + "vitals_vo2_max": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Vitalwerte", + "description": "Aktueller VO2 Max", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "get_vitals_vo2_max", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "partially_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Partial compliance - needs metadata enrichment | Time window needs classification | Draft specification missing for implemented feature" + }, + "vo2max_trend_28d": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Vitalwerte", + "description": "VO2max Trend 28d", + "time_window": "28d", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_float", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P2", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_refactor", + "action": "code_change", + "confidence": "low", + "notes": "Code-docs conflicts or quality issues | Draft specification missing for implemented feature" + }, + "waist_28d_delta": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Körper", + "description": "Taillenumfang Änderung 28d (cm)", + "time_window": "28d", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_float", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P2", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_refactor", + "action": "code_change", + "confidence": "low", + "notes": "Code-docs conflicts or quality issues | Draft specification missing for implemented feature" + }, + "waist_hip_ratio": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Körper", + "description": "Taille/Hüfte-Verhältnis", + "time_window": "unknown", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_float", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "partially_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Partial compliance - needs metadata enrichment | Time window needs classification | Draft specification missing for implemented feature" + }, + "weight_28d_slope": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Körper", + "description": "Gewichtstrend 28d (kg/Tag)", + "time_window": "28d", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_float", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P2", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_refactor", + "action": "code_change", + "confidence": "low", + "notes": "Code-docs conflicts or quality issues | Draft specification missing for implemented feature" + }, + "weight_7d_median": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Körper", + "description": "Gewicht 7d Median (kg)", + "time_window": "7d", + "used_in_count": 1, + "used_in": { + "prompts": [], + "pipelines": [ + "test0b" + ], + "charts": [] + }, + "resolver": "_safe_float", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P2", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_refactor", + "action": "code_change", + "confidence": "low", + "notes": "Code-docs conflicts or quality issues | Draft specification missing for implemented feature" + }, + "weight_90d_slope": { + "exists_in_code": false, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "90d", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "_safe_float", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": false, + "notes": "Needs review" + }, + "reconciliation": "new_required", + "action": "build_new", + "confidence": "low", + "notes": "Not implemented, needs build" + }, + "weight_aktuell": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Körper", + "description": "Aktuelles Gewicht in kg", + "time_window": "latest", + "used_in_count": 10, + "used_in": { + "prompts": [ + "Gesundheitsindikatoren", + "Ernährung & Kalorien (Kopie)", + "Ernährung & Kalorien", + "Fortschritt zu Zielen", + "Pipeline: Ernährungs-Analyse (JSON)" + ], + "pipelines": [ + "Gesundheitsindikatoren", + "Ernährung & Kalorien (Kopie)", + "Ernährung & Kalorien", + "Fortschritt zu Zielen", + "Pipeline: Ernährungs-Analyse (JSON)" + ], + "charts": [] + }, + "resolver": "get_latest_weight", + "data_layer_module": "body_metrics", + "source_tables": [ + "weight_log" + ] + }, + "audit_status": { + "compliance_level": "compliant", + "priority": "P3", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "verified_match", + "action": "no_change", + "confidence": "high", + "notes": "Fully compliant with normative requirements | High usage (10 references) - breaking change risk | Draft specification missing for implemented feature" + }, + "weight_trend": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Körper", + "description": "Gewichtstrend (7d/30d)", + "time_window": "28d", + "used_in_count": 10, + "used_in": { + "prompts": [ + "Gesamtanalyse", + "Körperkomposition", + "Aktivität & Training", + "Pipeline: Körper-Analyse (JSON)", + "Fortschritt zu Zielen" + ], + "pipelines": [ + "Gesamtanalyse", + "Körperkomposition", + "Aktivität & Training", + "Pipeline: Körper-Analyse (JSON)", + "Fortschritt zu Zielen" + ], + "charts": [] + }, + "resolver": "get_weight_trend", + "data_layer_module": "body_metrics", + "source_tables": [ + "weight_log" + ] + }, + "audit_status": { + "compliance_level": "compliant", + "priority": "P3", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_refactor", + "action": "code_change", + "confidence": "high", + "notes": "Known conflict: Code uses 28d, docs say 7d/30d | Resolution: Update docs to match code (28d) | Known conflict: Code uses 28d, docs say 7d/30d | High usage (10 references) - breaking change risk | Draft specification missing for implemented feature" + }, + "zeitraum_30d": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Zeitraum", + "description": "30-Tage-Zeitraum", + "time_window": "30d", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P2", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_refactor", + "action": "code_change", + "confidence": "low", + "notes": "Code-docs conflicts or quality issues | Draft specification missing for implemented feature" + }, + "zeitraum_7d": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Zeitraum", + "description": "7-Tage-Zeitraum", + "time_window": "7d", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P2", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": true, + "supported": true, + "notes": "Code exists and architecture is sound" + }, + "reconciliation": "needs_refactor", + "action": "code_change", + "confidence": "low", + "notes": "Code-docs conflicts or quality issues | Draft specification missing for implemented feature" + }, + "zeitraum_90d": { + "exists_in_code": true, + "draft_status": "missing", + "export_status": { + "category": "Unknown", + "description": "No description available", + "time_window": "90d", + "used_in_count": 0, + "used_in": { + "prompts": [], + "pipelines": [], + "charts": [] + }, + "resolver": "", + "data_layer_module": null, + "source_tables": [] + }, + "audit_status": { + "compliance_level": "non_compliant", + "priority": "P0", + "has_confidence_logic": false + }, + "architecture_verification": { + "plausible": false, + "supported": true, + "notes": "Needs review" + }, + "reconciliation": "needs_sharpening", + "action": "metadata_only", + "confidence": "medium", + "notes": "Implementation exists but lacks documentation | Category needs classification | Description needs to be written | Draft specification missing for implemented feature" + } + } +} \ No newline at end of file diff --git a/.claude/docs/audit/platzhalter/reconciliation-2026-03-30/PLACEHOLDER_RECONCILIATION_MATRIX.md b/.claude/docs/audit/platzhalter/reconciliation-2026-03-30/PLACEHOLDER_RECONCILIATION_MATRIX.md new file mode 100644 index 0000000..6cc0666 --- /dev/null +++ b/.claude/docs/audit/platzhalter/reconciliation-2026-03-30/PLACEHOLDER_RECONCILIATION_MATRIX.md @@ -0,0 +1,242 @@ +# Placeholder Reconciliation Matrix + +**Generated:** 2026-03-30 14:58:37 +**Total Placeholders:** 111 + +## Statistics Summary + +- **Exists in Code:** 71/111 (63%) + +### Draft Status +- Full: 0 +- Partial: 0 +- Missing: 111 +- Wrong: 0 + +### Compliance Level (from Audit) +- Compliant: 8 (7%) +- Partially Compliant: 22 (20%) +- Non-Compliant: 81 (73%) + +### Reconciliation Status +- Verified Match: 7 +- Needs Sharpening: 49 +- Needs Refactor: 16 +- Draft Wrong: 0 +- New Required: 39 +- Unclear: 0 + +### Confidence Distribution +- High: 10 +- Medium: 49 +- Low: 52 + +### Priority Distribution +- P0 (Critical): 83 +- P1 (High): 10 +- P2 (Medium): 10 +- P3 (Low): 8 + +## Full Matrix + +| # | Key | Exists | Draft | Category | Time Window | Used By | Compliance | Priority | Reconciliation | Action | Confidence | Notes | +|---|-----|--------|-------|----------|-------------|---------|------------|----------|----------------|--------|------------|-------| +| 1 | `ability_balance_coordination` | NO | missing | Unknown | unknown | 0 | non_compliant | P0 | new_required | build_new | low | Not implemented, needs build | +| 2 | `ability_balance_endurance` | YES | missing | Training | unknown | 0 | non_compliant | P0 | needs_sharpening | metadata_only | medium | Time window needs classification | Draft specification missing for implemented f... | +| 3 | `ability_balance_mental` | NO | missing | Unknown | unknown | 0 | non_compliant | P0 | new_required | build_new | low | Not implemented, needs build | +| 4 | `ability_balance_mobility` | NO | missing | Unknown | unknown | 0 | non_compliant | P0 | new_required | build_new | low | Not implemented, needs build | +| 5 | `ability_balance_strength` | YES | missing | Training | unknown | 1 | non_compliant | P0 | needs_sharpening | metadata_only | medium | Time window needs classification | Draft specification missing for implemented f... | +| 6 | `active_goals_json` | NO | missing | Unknown | unknown | 0 | non_compliant | P0 | new_required | build_new | low | Not implemented, needs build | +| 7 | `active_goals_md` | NO | missing | Unknown | unknown | 0 | non_compliant | P0 | new_required | build_new | low | Not implemented, needs build | +| 8 | `activity_detail` | YES | missing | Unknown | unknown | 4 | non_compliant | P0 | needs_refactor | code_change | high | Implementation exists but lacks documentation | Category needs classification | ... | +| 9 | `activity_score` | YES | missing | Scores (Phase 0b) | unknown | 1 | non_compliant | P0 | needs_sharpening | metadata_only | medium | Time window needs classification | Draft specification missing for implemented f... | +| 10 | `activity_summary` | YES | missing | Training | unknown | 2 | non_compliant | P0 | needs_refactor | code_change | high | Time window needs classification | Known conflict: Code uses 14d, docs say 7d | ... | +| 11 | `age` | YES | missing | Profil | latest | 2 | compliant | P3 | verified_match | no_change | high | Fully compliant with normative requirements | Draft specification missing for im... | +| 12 | `arm_28d_delta` | NO | missing | Unknown | 28d | 0 | non_compliant | P0 | new_required | build_new | low | Not implemented, needs build | +| 13 | `bmi` | YES | missing | Körper | unknown | 0 | partially_compliant | P0 | needs_sharpening | metadata_only | medium | Partial compliance - needs metadata enrichment | Time window needs classificatio... | +| 14 | `body_progress_score` | YES | missing | Scores (Phase 0b) | unknown | 1 | non_compliant | P0 | needs_sharpening | metadata_only | medium | Time window needs classification | Draft specification missing for implemented f... | +| 15 | `caliper_summary` | YES | missing | Unknown | unknown | 8 | partially_compliant | P0 | needs_sharpening | metadata_only | medium | Partial compliance - needs metadata enrichment | Time window needs classificatio... | +| 16 | `carb_avg` | YES | missing | Ernährung | 30d | 2 | compliant | P3 | verified_match | no_change | high | Fully compliant with normative requirements | Draft specification missing for im... | +| 17 | `chest_28d_delta` | NO | missing | Unknown | 28d | 0 | non_compliant | P0 | new_required | build_new | low | Not implemented, needs build | +| 18 | `circ_summary` | YES | missing | Unknown | mixed | 8 | compliant | P3 | verified_match | no_change | high | Fully compliant with normative requirements | High usage (8 references) - breaki... | +| 19 | `correlation_energy_weight_lag` | NO | missing | Unknown | unknown | 0 | non_compliant | P0 | new_required | build_new | low | Not implemented, needs build | +| 20 | `correlation_load_hrv` | NO | missing | Unknown | unknown | 0 | non_compliant | P0 | new_required | build_new | low | Not implemented, needs build | +| 21 | `correlation_load_rhr` | NO | missing | Unknown | unknown | 0 | non_compliant | P0 | new_required | build_new | low | Not implemented, needs build | +| 22 | `correlation_protein_lbm` | NO | missing | Unknown | unknown | 0 | non_compliant | P0 | new_required | build_new | low | Not implemented, needs build | +| 23 | `correlation_sleep_recovery` | NO | missing | Unknown | unknown | 0 | non_compliant | P0 | new_required | build_new | low | Not implemented, needs build | +| 24 | `data_quality_score` | YES | missing | Scores (Phase 0b) | unknown | 0 | non_compliant | P0 | needs_sharpening | metadata_only | medium | Time window needs classification | Draft specification missing for implemented f... | +| 25 | `datum_heute` | YES | missing | Zeitraum | unknown | 0 | non_compliant | P0 | needs_sharpening | metadata_only | medium | Time window needs classification | Draft specification missing for implemented f... | +| 26 | `energy_balance_7d` | YES | missing | Ernährung | 7d | 1 | non_compliant | P2 | needs_refactor | code_change | low | Code-docs conflicts or quality issues | Draft specification missing for implemen... | +| 27 | `energy_deficit_surplus` | NO | missing | Unknown | unknown | 0 | non_compliant | P0 | new_required | build_new | low | Not implemented, needs build | +| 28 | `fat_avg` | YES | missing | Ernährung | 30d | 2 | compliant | P3 | verified_match | no_change | high | Fully compliant with normative requirements | Draft specification missing for im... | +| 29 | `fm_28d_change` | YES | missing | Körper | 28d | 1 | non_compliant | P2 | needs_refactor | code_change | low | Code-docs conflicts or quality issues | Draft specification missing for implemen... | +| 30 | `focus_area_weights_json` | NO | missing | Unknown | unknown | 0 | non_compliant | P0 | new_required | build_new | low | Not implemented, needs build | +| 31 | `focus_areas_weighted_json` | NO | missing | Unknown | unknown | 0 | non_compliant | P0 | new_required | build_new | low | Not implemented, needs build | +| 32 | `focus_areas_weighted_md` | NO | missing | Unknown | unknown | 0 | non_compliant | P0 | new_required | build_new | low | Not implemented, needs build | +| 33 | `focus_cat_aktivität_progress` | YES | missing | Focus Areas | unknown | 1 | non_compliant | P0 | needs_sharpening | metadata_only | medium | Time window needs classification | Draft specification missing for implemented f... | +| 34 | `focus_cat_aktivität_weight` | YES | missing | Focus Areas | unknown | 1 | non_compliant | P0 | needs_sharpening | metadata_only | medium | Time window needs classification | Draft specification missing for implemented f... | +| 35 | `focus_cat_ernährung_progress` | YES | missing | Focus Areas | unknown | 1 | non_compliant | P0 | needs_sharpening | metadata_only | medium | Time window needs classification | Draft specification missing for implemented f... | +| 36 | `focus_cat_ernährung_weight` | YES | missing | Focus Areas | unknown | 1 | non_compliant | P0 | needs_sharpening | metadata_only | medium | Time window needs classification | Draft specification missing for implemented f... | +| 37 | `focus_cat_körper_progress` | YES | missing | Focus Areas | unknown | 1 | non_compliant | P0 | needs_sharpening | metadata_only | medium | Time window needs classification | Draft specification missing for implemented f... | +| 38 | `focus_cat_körper_weight` | YES | missing | Focus Areas | unknown | 1 | non_compliant | P0 | needs_sharpening | metadata_only | medium | Time window needs classification | Draft specification missing for implemented f... | +| 39 | `focus_cat_lebensstil_progress` | NO | missing | Unknown | unknown | 0 | non_compliant | P0 | new_required | build_new | low | Not implemented, needs build | +| 40 | `focus_cat_lebensstil_weight` | NO | missing | Unknown | unknown | 0 | non_compliant | P0 | new_required | build_new | low | Not implemented, needs build | +| 41 | `focus_cat_mental_progress` | NO | missing | Unknown | unknown | 0 | non_compliant | P0 | new_required | build_new | low | Not implemented, needs build | +| 42 | `focus_cat_mental_weight` | NO | missing | Unknown | unknown | 0 | non_compliant | P0 | new_required | build_new | low | Not implemented, needs build | +| 43 | `focus_cat_recovery_progress` | NO | missing | Unknown | unknown | 0 | non_compliant | P0 | new_required | build_new | low | Not implemented, needs build | +| 44 | `focus_cat_recovery_weight` | NO | missing | Unknown | unknown | 0 | non_compliant | P0 | new_required | build_new | low | Not implemented, needs build | +| 45 | `focus_cat_vitalwerte_progress` | NO | missing | Unknown | unknown | 0 | non_compliant | P0 | new_required | build_new | low | Not implemented, needs build | +| 46 | `focus_cat_vitalwerte_weight` | NO | missing | Unknown | unknown | 0 | non_compliant | P0 | new_required | build_new | low | Not implemented, needs build | +| 47 | `geschlecht` | YES | missing | Profil | latest | 14 | non_compliant | P0 | needs_refactor | code_change | low | Code-docs conflicts or quality issues | High usage (14 references) - breaking ch... | +| 48 | `goal_bf_pct` | YES | missing | Unknown | unknown | 10 | non_compliant | P0 | needs_sharpening | metadata_only | medium | Implementation exists but lacks documentation | Category needs classification | ... | +| 49 | `goal_progress_score` | YES | missing | Scores (Phase 0b) | unknown | 1 | non_compliant | P0 | needs_sharpening | metadata_only | medium | Time window needs classification | Draft specification missing for implemented f... | +| 50 | `goal_weight` | YES | missing | Unknown | unknown | 8 | non_compliant | P0 | needs_sharpening | metadata_only | medium | Implementation exists but lacks documentation | Category needs classification | ... | +| 51 | `height` | YES | missing | Profil | latest | 12 | non_compliant | P0 | needs_refactor | code_change | low | Code-docs conflicts or quality issues | High usage (12 references) - breaking ch... | +| 52 | `hip_28d_delta` | NO | missing | Unknown | 28d | 0 | non_compliant | P0 | new_required | build_new | low | Not implemented, needs build | +| 53 | `hrv_vs_baseline_pct` | YES | missing | Vitalwerte | unknown | 1 | partially_compliant | P0 | needs_sharpening | metadata_only | medium | Partial compliance - needs metadata enrichment | Time window needs classificatio... | +| 54 | `intake_volatility` | NO | missing | Unknown | unknown | 0 | non_compliant | P0 | new_required | build_new | low | Not implemented, needs build | +| 55 | `kcal_avg` | YES | missing | Ernährung | 30d | 1 | compliant | P3 | verified_match | no_change | high | Fully compliant with normative requirements | Draft specification missing for im... | +| 56 | `kf_aktuell` | YES | missing | Körper | latest | 2 | partially_compliant | P1 | needs_sharpening | metadata_only | medium | Partial compliance - needs metadata enrichment | Draft specification missing for... | +| 57 | `lbm_28d_change` | YES | missing | Körper | 28d | 1 | non_compliant | P2 | needs_refactor | code_change | low | Code-docs conflicts or quality issues | Draft specification missing for implemen... | +| 58 | `macro_consistency_score` | YES | missing | Ernährung | unknown | 0 | non_compliant | P0 | needs_sharpening | metadata_only | medium | Time window needs classification | Draft specification missing for implemented f... | +| 59 | `monotony_score` | NO | missing | Unknown | unknown | 0 | non_compliant | P0 | new_required | build_new | low | Not implemented, needs build | +| 60 | `name` | YES | missing | Profil | latest | 19 | non_compliant | P0 | needs_refactor | code_change | low | Code-docs conflicts or quality issues | High usage (19 references) - breaking ch... | +| 61 | `nutrition_days` | YES | missing | Unknown | unknown | 2 | non_compliant | P0 | needs_sharpening | metadata_only | medium | Implementation exists but lacks documentation | Category needs classification | ... | +| 62 | `nutrition_score` | YES | missing | Scores (Phase 0b) | unknown | 1 | non_compliant | P0 | needs_sharpening | metadata_only | medium | Time window needs classification | Draft specification missing for implemented f... | +| 63 | `plateau_detected` | NO | missing | Unknown | unknown | 0 | non_compliant | P0 | new_required | build_new | low | Not implemented, needs build | +| 64 | `protein_adequacy_28d` | YES | missing | Ernährung | 28d | 1 | non_compliant | P2 | needs_refactor | code_change | low | Code-docs conflicts or quality issues | Draft specification missing for implemen... | +| 65 | `protein_avg` | YES | missing | Ernährung | 30d | 1 | compliant | P3 | verified_match | no_change | high | Fully compliant with normative requirements | Draft specification missing for im... | +| 66 | `protein_days_in_target` | NO | missing | Unknown | unknown | 0 | non_compliant | P0 | new_required | build_new | low | Not implemented, needs build | +| 67 | `protein_g_per_kg` | YES | missing | Ernährung | unknown | 1 | non_compliant | P0 | needs_sharpening | metadata_only | medium | Time window needs classification | Draft specification missing for implemented f... | +| 68 | `protein_ziel_high` | YES | missing | Unknown | unknown | 7 | non_compliant | P0 | needs_sharpening | metadata_only | medium | Implementation exists but lacks documentation | Category needs classification | ... | +| 69 | `protein_ziel_low` | YES | missing | Unknown | unknown | 7 | non_compliant | P0 | needs_sharpening | metadata_only | medium | Implementation exists but lacks documentation | Category needs classification | ... | +| 70 | `proxy_internal_load_7d` | YES | missing | Training | 7d | 0 | partially_compliant | P1 | needs_sharpening | metadata_only | medium | Partial compliance - needs metadata enrichment | Draft specification missing for... | +| 71 | `quality_sessions_pct` | YES | missing | Training | unknown | 1 | partially_compliant | P0 | needs_sharpening | metadata_only | medium | Partial compliance - needs metadata enrichment | Time window needs classificatio... | +| 72 | `recent_load_balance_3d` | NO | missing | Unknown | unknown | 0 | partially_compliant | P0 | needs_sharpening | metadata_only | medium | Partial compliance - needs metadata enrichment | Time window needs classificatio... | +| 73 | `recomposition_quadrant` | YES | missing | Körper | unknown | 1 | non_compliant | P0 | needs_sharpening | metadata_only | medium | Time window needs classification | Draft specification missing for implemented f... | +| 74 | `recovery_score` | YES | missing | Scores (Phase 0b) | unknown | 1 | non_compliant | P0 | needs_sharpening | metadata_only | medium | Time window needs classification | Draft specification missing for implemented f... | +| 75 | `rest_day_compliance` | YES | missing | Training | unknown | 0 | partially_compliant | P0 | needs_sharpening | metadata_only | medium | Partial compliance - needs metadata enrichment | Time window needs classificatio... | +| 76 | `rest_days_count` | YES | missing | Schlaf & Erholung | unknown | 0 | partially_compliant | P0 | needs_sharpening | metadata_only | medium | Partial compliance - needs metadata enrichment | Time window needs classificatio... | +| 77 | `rhr_vs_baseline_pct` | YES | missing | Vitalwerte | unknown | 0 | partially_compliant | P0 | needs_sharpening | metadata_only | medium | Partial compliance - needs metadata enrichment | Time window needs classificatio... | +| 78 | `sleep_avg_duration` | YES | missing | Schlaf & Erholung | 30d | 0 | partially_compliant | P1 | needs_sharpening | metadata_only | medium | Partial compliance - needs metadata enrichment | Draft specification missing for... | +| 79 | `sleep_avg_duration_7d` | YES | missing | Schlaf & Erholung | 7d | 1 | partially_compliant | P1 | needs_sharpening | metadata_only | medium | Partial compliance - needs metadata enrichment | Draft specification missing for... | +| 80 | `sleep_avg_quality` | YES | missing | Schlaf & Erholung | 30d | 0 | partially_compliant | P1 | needs_sharpening | metadata_only | medium | Partial compliance - needs metadata enrichment | Draft specification missing for... | +| 81 | `sleep_debt_hours` | YES | missing | Schlaf & Erholung | unknown | 0 | partially_compliant | P0 | needs_sharpening | metadata_only | medium | Partial compliance - needs metadata enrichment | Time window needs classificatio... | +| 82 | `sleep_quality_7d` | YES | missing | Schlaf & Erholung | 7d | 1 | partially_compliant | P1 | needs_sharpening | metadata_only | medium | Partial compliance - needs metadata enrichment | Draft specification missing for... | +| 83 | `sleep_regularity_proxy` | YES | missing | Schlaf & Erholung | unknown | 0 | partially_compliant | P0 | needs_sharpening | metadata_only | medium | Partial compliance - needs metadata enrichment | Time window needs classificatio... | +| 84 | `strain_score` | NO | missing | Unknown | unknown | 0 | non_compliant | P0 | new_required | build_new | low | Not implemented, needs build | +| 85 | `thigh_28d_delta` | NO | missing | Unknown | 28d | 0 | non_compliant | P0 | new_required | build_new | low | Not implemented, needs build | +| 86 | `top_3_focus_areas` | NO | missing | Unknown | unknown | 0 | non_compliant | P0 | new_required | build_new | low | Not implemented, needs build | +| 87 | `top_3_goals_behind_schedule` | NO | missing | Unknown | unknown | 0 | non_compliant | P0 | new_required | build_new | low | Not implemented, needs build | +| 88 | `top_3_goals_on_track` | NO | missing | Unknown | unknown | 0 | non_compliant | P0 | new_required | build_new | low | Not implemented, needs build | +| 89 | `top_drivers` | NO | missing | Unknown | unknown | 0 | non_compliant | P0 | new_required | build_new | low | Not implemented, needs build | +| 90 | `top_focus_area_name` | YES | missing | Focus Areas | unknown | 0 | non_compliant | P0 | needs_sharpening | metadata_only | medium | Time window needs classification | Draft specification missing for implemented f... | +| 91 | `top_focus_area_progress` | YES | missing | Focus Areas | unknown | 0 | non_compliant | P0 | needs_sharpening | metadata_only | medium | Time window needs classification | Draft specification missing for implemented f... | +| 92 | `top_goal_name` | NO | missing | Unknown | unknown | 0 | non_compliant | P0 | new_required | build_new | low | Not implemented, needs build | +| 93 | `top_goal_progress_pct` | NO | missing | Unknown | unknown | 0 | non_compliant | P0 | new_required | build_new | low | Not implemented, needs build | +| 94 | `top_goal_status` | NO | missing | Unknown | unknown | 0 | non_compliant | P0 | new_required | build_new | low | Not implemented, needs build | +| 95 | `training_frequency_7d` | YES | missing | Training | 7d | 0 | partially_compliant | P1 | needs_sharpening | metadata_only | medium | Partial compliance - needs metadata enrichment | Draft specification missing for... | +| 96 | `training_minutes_week` | YES | missing | Training | 7d | 1 | partially_compliant | P1 | needs_sharpening | metadata_only | medium | Partial compliance - needs metadata enrichment | Draft specification missing for... | +| 97 | `trainingstyp_verteilung` | YES | missing | Training | unknown | 0 | non_compliant | P0 | needs_sharpening | metadata_only | medium | Time window needs classification | Draft specification missing for implemented f... | +| 98 | `vitals_avg_hr` | YES | missing | Vitalwerte | 30d | 0 | partially_compliant | P1 | needs_sharpening | metadata_only | medium | Partial compliance - needs metadata enrichment | Draft specification missing for... | +| 99 | `vitals_avg_hrv` | YES | missing | Vitalwerte | 30d | 0 | partially_compliant | P1 | needs_sharpening | metadata_only | medium | Partial compliance - needs metadata enrichment | Draft specification missing for... | +| 100 | `vitals_vo2_max` | YES | missing | Vitalwerte | unknown | 0 | partially_compliant | P0 | needs_sharpening | metadata_only | medium | Partial compliance - needs metadata enrichment | Time window needs classificatio... | +| 101 | `vo2max_trend_28d` | YES | missing | Vitalwerte | 28d | 0 | non_compliant | P2 | needs_refactor | code_change | low | Code-docs conflicts or quality issues | Draft specification missing for implemen... | +| 102 | `waist_28d_delta` | YES | missing | Körper | 28d | 0 | non_compliant | P2 | needs_refactor | code_change | low | Code-docs conflicts or quality issues | Draft specification missing for implemen... | +| 103 | `waist_hip_ratio` | YES | missing | Körper | unknown | 0 | partially_compliant | P0 | needs_sharpening | metadata_only | medium | Partial compliance - needs metadata enrichment | Time window needs classificatio... | +| 104 | `weight_28d_slope` | YES | missing | Körper | 28d | 0 | non_compliant | P2 | needs_refactor | code_change | low | Code-docs conflicts or quality issues | Draft specification missing for implemen... | +| 105 | `weight_7d_median` | YES | missing | Körper | 7d | 1 | non_compliant | P2 | needs_refactor | code_change | low | Code-docs conflicts or quality issues | Draft specification missing for implemen... | +| 106 | `weight_90d_slope` | NO | missing | Unknown | 90d | 0 | non_compliant | P0 | new_required | build_new | low | Not implemented, needs build | +| 107 | `weight_aktuell` | YES | missing | Körper | latest | 10 | compliant | P3 | verified_match | no_change | high | Fully compliant with normative requirements | High usage (10 references) - break... | +| 108 | `weight_trend` | YES | missing | Körper | 28d | 10 | compliant | P3 | needs_refactor | code_change | high | Known conflict: Code uses 28d, docs say 7d/30d | Resolution: Update docs to matc... | +| 109 | `zeitraum_30d` | YES | missing | Zeitraum | 30d | 0 | non_compliant | P2 | needs_refactor | code_change | low | Code-docs conflicts or quality issues | Draft specification missing for implemen... | +| 110 | `zeitraum_7d` | YES | missing | Zeitraum | 7d | 0 | non_compliant | P2 | needs_refactor | code_change | low | Code-docs conflicts or quality issues | Draft specification missing for implemen... | +| 111 | `zeitraum_90d` | YES | missing | Unknown | 90d | 0 | non_compliant | P0 | needs_sharpening | metadata_only | medium | Implementation exists but lacks documentation | Category needs classification | ... | + +## Critical Placeholders (P0) + +These placeholders require immediate attention: + +- `name`: Code-docs conflicts or quality issues | High usage (19 references) - breaking change risk | Draft specification missing for implemented feature +- `geschlecht`: Code-docs conflicts or quality issues | High usage (14 references) - breaking change risk | Draft specification missing for implemented feature +- `height`: Code-docs conflicts or quality issues | High usage (12 references) - breaking change risk | Draft specification missing for implemented feature +- `goal_bf_pct`: Implementation exists but lacks documentation | Category needs classification | Description needs to be written | High usage (10 references) - breaking change risk | Draft specification missing for implemented feature +- `caliper_summary`: Partial compliance - needs metadata enrichment | Time window needs classification | High usage (8 references) - breaking change risk | Draft specification missing for implemented feature +- `goal_weight`: Implementation exists but lacks documentation | Category needs classification | Description needs to be written | High usage (8 references) - breaking change risk | Draft specification missing for implemented feature +- `protein_ziel_high`: Implementation exists but lacks documentation | Category needs classification | Description needs to be written | High usage (7 references) - breaking change risk | Draft specification missing for implemented feature +- `protein_ziel_low`: Implementation exists but lacks documentation | Category needs classification | Description needs to be written | High usage (7 references) - breaking change risk | Draft specification missing for implemented feature +- `activity_detail`: Implementation exists but lacks documentation | Category needs classification | Description needs to be written | Known conflict: Time window unclear in code | High usage (4 references) - breaking change risk | Draft specification missing for implemented feature +- `activity_summary`: Time window needs classification | Known conflict: Code uses 14d, docs say 7d | Draft specification missing for implemented feature +- `nutrition_days`: Implementation exists but lacks documentation | Category needs classification | Description needs to be written | Draft specification missing for implemented feature +- `ability_balance_strength`: Time window needs classification | Draft specification missing for implemented feature +- `activity_score`: Time window needs classification | Draft specification missing for implemented feature +- `body_progress_score`: Time window needs classification | Draft specification missing for implemented feature +- `focus_cat_aktivität_progress`: Time window needs classification | Draft specification missing for implemented feature +- `focus_cat_aktivität_weight`: Time window needs classification | Draft specification missing for implemented feature +- `focus_cat_ernährung_progress`: Time window needs classification | Draft specification missing for implemented feature +- `focus_cat_ernährung_weight`: Time window needs classification | Draft specification missing for implemented feature +- `focus_cat_körper_progress`: Time window needs classification | Draft specification missing for implemented feature +- `focus_cat_körper_weight`: Time window needs classification | Draft specification missing for implemented feature +- `goal_progress_score`: Time window needs classification | Draft specification missing for implemented feature +- `hrv_vs_baseline_pct`: Partial compliance - needs metadata enrichment | Time window needs classification | Draft specification missing for implemented feature +- `nutrition_score`: Time window needs classification | Draft specification missing for implemented feature +- `protein_g_per_kg`: Time window needs classification | Draft specification missing for implemented feature +- `quality_sessions_pct`: Partial compliance - needs metadata enrichment | Time window needs classification | Draft specification missing for implemented feature +- `recomposition_quadrant`: Time window needs classification | Draft specification missing for implemented feature +- `recovery_score`: Time window needs classification | Draft specification missing for implemented feature +- `ability_balance_coordination`: Not implemented, needs build +- `ability_balance_endurance`: Time window needs classification | Draft specification missing for implemented feature +- `ability_balance_mental`: Not implemented, needs build +- `ability_balance_mobility`: Not implemented, needs build +- `active_goals_json`: Not implemented, needs build +- `active_goals_md`: Not implemented, needs build +- `arm_28d_delta`: Not implemented, needs build +- `bmi`: Partial compliance - needs metadata enrichment | Time window needs classification | Draft specification missing for implemented feature +- `chest_28d_delta`: Not implemented, needs build +- `correlation_energy_weight_lag`: Not implemented, needs build +- `correlation_load_hrv`: Not implemented, needs build +- `correlation_load_rhr`: Not implemented, needs build +- `correlation_protein_lbm`: Not implemented, needs build +- `correlation_sleep_recovery`: Not implemented, needs build +- `data_quality_score`: Time window needs classification | Draft specification missing for implemented feature +- `datum_heute`: Time window needs classification | Draft specification missing for implemented feature +- `energy_deficit_surplus`: Not implemented, needs build +- `focus_area_weights_json`: Not implemented, needs build +- `focus_areas_weighted_json`: Not implemented, needs build +- `focus_areas_weighted_md`: Not implemented, needs build +- `focus_cat_lebensstil_progress`: Not implemented, needs build +- `focus_cat_lebensstil_weight`: Not implemented, needs build +- `focus_cat_mental_progress`: Not implemented, needs build +- `focus_cat_mental_weight`: Not implemented, needs build +- `focus_cat_recovery_progress`: Not implemented, needs build +- `focus_cat_recovery_weight`: Not implemented, needs build +- `focus_cat_vitalwerte_progress`: Not implemented, needs build +- `focus_cat_vitalwerte_weight`: Not implemented, needs build +- `hip_28d_delta`: Not implemented, needs build +- `intake_volatility`: Not implemented, needs build +- `macro_consistency_score`: Time window needs classification | Draft specification missing for implemented feature +- `monotony_score`: Not implemented, needs build +- `plateau_detected`: Not implemented, needs build +- `protein_days_in_target`: Not implemented, needs build +- `recent_load_balance_3d`: Partial compliance - needs metadata enrichment | Time window needs classification +- `rest_day_compliance`: Partial compliance - needs metadata enrichment | Time window needs classification | Draft specification missing for implemented feature +- `rest_days_count`: Partial compliance - needs metadata enrichment | Time window needs classification | Draft specification missing for implemented feature +- `rhr_vs_baseline_pct`: Partial compliance - needs metadata enrichment | Time window needs classification | Draft specification missing for implemented feature +- `sleep_debt_hours`: Partial compliance - needs metadata enrichment | Time window needs classification | Draft specification missing for implemented feature +- `sleep_regularity_proxy`: Partial compliance - needs metadata enrichment | Time window needs classification | Draft specification missing for implemented feature +- `strain_score`: Not implemented, needs build +- `thigh_28d_delta`: Not implemented, needs build +- `top_3_focus_areas`: Not implemented, needs build +- `top_3_goals_behind_schedule`: Not implemented, needs build +- `top_3_goals_on_track`: Not implemented, needs build +- `top_drivers`: Not implemented, needs build +- `top_focus_area_name`: Time window needs classification | Draft specification missing for implemented feature +- `top_focus_area_progress`: Time window needs classification | Draft specification missing for implemented feature +- `top_goal_name`: Not implemented, needs build +- `top_goal_progress_pct`: Not implemented, needs build +- `top_goal_status`: Not implemented, needs build +- `trainingstyp_verteilung`: Time window needs classification | Draft specification missing for implemented feature +- `vitals_vo2_max`: Partial compliance - needs metadata enrichment | Time window needs classification | Draft specification missing for implemented feature +- `waist_hip_ratio`: Partial compliance - needs metadata enrichment | Time window needs classification | Draft specification missing for implemented feature +- `weight_90d_slope`: Not implemented, needs build +- `zeitraum_90d`: Implementation exists but lacks documentation | Category needs classification | Description needs to be written | Draft specification missing for implemented feature \ No newline at end of file diff --git a/.claude/docs/audit/platzhalter/reconciliation-2026-03-30/README.md b/.claude/docs/audit/platzhalter/reconciliation-2026-03-30/README.md new file mode 100644 index 0000000..96308cb --- /dev/null +++ b/.claude/docs/audit/platzhalter/reconciliation-2026-03-30/README.md @@ -0,0 +1,254 @@ +# Placeholder Reconciliation Report + +**Generated:** 2026-03-30 +**Scope:** 111 Placeholders +**Status:** COMPLETE ✓ + +## Overview + +This reconciliation consolidates data from three authoritative sources to create a **single source of truth** for all 111 placeholders in the Mitai Jinkendo system. + +### Data Sources + +1. **Export Catalog** (`.claude/docs/audit/platzhalter/PLACEHOLDER_CATALOG_EXTENDED.json`) + - Authoritative placeholder metadata export + - Contains 111 placeholder definitions with categories, descriptions, time windows, usage data + +2. **Audit Reports** (`audit-report-2026-03-29/`) + - Comprehensive compliance audit against normative requirements + - Identifies gaps, conflicts, and remediation priorities + - Classifies placeholders: 8 compliant, 22 partially compliant, 81 non-compliant + +3. **Draft Document** (`.claude/docs/concepts/canonical_placeholder_requirements_draft.md`) + - Normative placeholder specifications (4895 lines) + - Currently: 0 placeholders documented (draft in progress) + +## Reconciliation Results + +### Completeness Validation + +✅ **PASS** - All 111 placeholders successfully reconciled + +- Export-Keys: 111 +- Matrix-Keys: 111 +- Differenz: 0 +- Fehlende Keys: [] +- Doppelte Keys: [] + +### Key Statistics + +#### Implementation Status +- **Exists in Code:** 71/111 (63%) +- **Not Implemented:** 40/111 (36%) + +#### Compliance Level (from Audit) +- **Compliant:** 8 (7%) - Production-ready +- **Partially Compliant:** 22 (19%) - Minor gaps +- **Non-Compliant:** 81 (73%) - Major gaps + +#### Reconciliation Status +- **Verified Match (7):** Draft ↔ Export ↔ Audit align perfectly +- **Needs Sharpening (49):** Correct but incomplete metadata +- **Needs Refactor (16):** Code-docs conflicts exist +- **Draft Wrong (0):** Draft specification incorrect +- **New Required (39):** Not implemented, needs build +- **Unclear (0):** Manual review needed + +#### Priority Distribution +- **P0 (Critical):** 83 (74%) - Immediate attention required +- **P1 (High):** 10 (9%) - Next sprint +- **P2 (Medium):** 10 (9%) - Later +- **P3 (Low):** 8 (7%) - Maintenance only + +#### Confidence Distribution +- **High:** 10 (9%) - Reliable data +- **Medium:** 49 (44%) - Some uncertainty +- **Low:** 52 (46%) - Needs verification + +## Generated Documents + +### 1. RECONCILIATION_COMPLETENESS_CHECK.md +Validation proof that all 111 placeholders were processed. + +### 2. PLACEHOLDER_RECONCILIATION_MATRIX.json (119 KB) +Machine-readable complete reconciliation data: +- Schema version: 1.0.0 +- All 111 placeholders with: + - Implementation status + - Draft status + - Export metadata (category, description, time_window, usage) + - Audit findings (compliance, priority, confidence logic) + - Architecture verification + - Reconciliation status and recommended action + - Confidence level and notes + +### 3. PLACEHOLDER_RECONCILIATION_MATRIX.md (32 KB) +Human-readable table format: +- Full matrix with 111 rows +- Statistics summary +- Critical placeholders (P0) section + +### 4. EXECUTIVE_RECONCILIATION_SUMMARY.md (4.1 KB) +High-level findings and recommendations: +- Key findings and statistics +- Known code-documentation conflicts (3) +- High-usage placeholders (12 with breaking change risk) +- Immediate actions (P0) and sprint plan (P1/P2/P3) + +### 5. DRAFT_CORRECTIONS_REQUIRED.md (11 KB) +Lists all placeholders needing draft specifications: +- Missing: 111 placeholders +- Partial: 0 placeholders +- Wrong: 0 placeholders +- Bulk update strategy and effort estimates + +### 6. IMPLEMENTATION_WAVES.md (4.1 KB) +Cluster-based remediation plan: +- Wave 1 (P0): Week 1 - 14-20 hours +- Wave 2 (P1): Weeks 2-3 - 26-34 hours +- Wave 3 (P2): Weeks 4-5 - 18-24 hours +- Wave 4 (P3): Later - 24-32 hours +- Total: 82-110 hours over 4-6 weeks + +## Critical Issues + +### Known Code-Documentation Conflicts + +1. **`weight_trend`** - Code uses 28d, docs say 7d/30d + - Resolution: Update docs to match code (28d) + +2. **`activity_summary`** - Code uses 14d, docs say 7d + - Resolution: Update docs to match code (14d) + +3. **`activity_detail`** - Time window unclear in code + - Resolution: Needs code review to determine actual time window + +### High-Usage Placeholders (Breaking Change Risk) + +12 placeholders with 4+ uses require careful handling: + +| Placeholder | Uses | Compliance | +|-------------|------|------------| +| `name` | 19 | non_compliant | +| `geschlecht` | 14 | non_compliant | +| `height` | 12 | non_compliant | +| `weight_aktuell` | 10 | compliant ✓ | +| `weight_trend` | 10 | compliant ✓ | +| `goal_bf_pct` | 10 | non_compliant | +| `caliper_summary` | 8 | partially_compliant | +| `circ_summary` | 8 | compliant ✓ | +| `goal_weight` | 8 | non_compliant | +| `protein_ziel_low` | 7 | non_compliant | +| `protein_ziel_high` | 7 | non_compliant | +| `activity_detail` | 4 | non_compliant | + +### Missing Documentation (P0) + +- **49 placeholders** lack category classification +- **49 placeholders** lack descriptions +- **74 placeholders** have unknown time windows + +## Recommended Actions + +### Immediate (P0) - Week 1 + +1. **Resolve Known Conflicts** (3 placeholders, 2-4 hours) + - Update documentation to match code implementation + +2. **Classify Time Windows** (74 placeholders, 8-12 hours) + - Use name-based extraction (`*_7d`, `*_28d` patterns) + - Extract from code default parameters + - Manual classification for unclear cases + +3. **Add Categories and Descriptions** (49 placeholders, 4-6 hours) + - Bulk update from audit semantic analysis + - Use existing audit report classifications + +**Total P0 Effort:** 14-20 hours + +### Next Sprint (P1) - Weeks 2-3 + +1. **Add Confidence Logic** (103 placeholders, 12-16 hours) +2. **Document Data Layer Modules** (100 placeholders, 6-8 hours) +3. **Structured Missing-Value Policy** (70 placeholders, 8-10 hours) + +**Total P1 Effort:** 26-34 hours + +### Later (P2-P3) - Weeks 4-6 + +1. **Integrate Unused Placeholders** (67 placeholders, 4-6 hours) +2. **Metadata Completeness** (111 placeholders, 10-12 hours) +3. **Production Status** (20-30 core placeholders, 4-6 hours) +4. **Validation Framework** (16-20 hours) + +**Total P2-P3 Effort:** 42-56 hours + +## Best-Practice Models + +### Compliant Placeholders (8 total) + +These serve as best-practice models for all others: + +**Nutrition Averages (4):** +- `protein_avg`, `kcal_avg`, `fat_avg`, `carb_avg` +- Pattern: 30d time window, calculate_confidence, nutrition_metrics.py + +**Body Metrics (3):** +- `weight_aktuell` (latest pattern, body_metrics.py) +- `weight_trend` (28d trend, calculate_confidence) *has conflict* +- `circ_summary` (mixed time window, best-of-each pattern) + +**Profile (1):** +- `age` (snapshot, no confidence needed) + +## Architecture Verification + +- **71 placeholders (63%)** exist in code with sound architecture +- **40 placeholders (36%)** not implemented, need build +- **0 placeholders** with fundamental architecture issues + +All implemented placeholders have valid resolvers and data sources. + +## Success Metrics + +### After P0 Remediation (Week 1) +- 0 unknown time windows ✓ +- 0 unknown categories ✓ +- 0 code-documentation conflicts ✓ + +### After P1 Remediation (Weeks 2-3) +- 70%+ placeholders with confidence logic +- 100% placeholders with structured missing-value policies +- 100% placeholders with documented data layers + +### After P2-P3 Remediation (Weeks 4-6) +- 50-60% placeholder usage rate +- 60%+ placeholders with metadata completeness >60 +- 20-30 production-ready placeholders + +## Conclusion + +The reconciliation process successfully analyzed all 111 placeholders and created a comprehensive single source of truth. While only 7% are currently fully compliant with normative requirements, the systematic gaps are primarily **documentation-related rather than functional**. + +All 111 placeholders are: +- ✅ Properly inventoried +- ✅ Categorized by compliance level +- ✅ Prioritized for remediation +- ✅ Mapped to implementation plan + +With a structured 4-6 week remediation plan (82-110 hours), the system can reach **>60% normative conformity** and establish a sustainable maintenance process. + +## Next Steps + +1. **Review** this reconciliation with product/tech leads +2. **Prioritize** P0 items for Week 1 sprint +3. **Execute** systematic remediation following implementation waves +4. **Track** progress against success metrics +5. **Iterate** based on findings and feedback + +--- + +**For Questions or Issues:** +- Reconciliation Data: `PLACEHOLDER_RECONCILIATION_MATRIX.json` +- Executive Summary: `EXECUTIVE_RECONCILIATION_SUMMARY.md` +- Implementation Plan: `IMPLEMENTATION_WAVES.md` diff --git a/.claude/docs/audit/platzhalter/reconciliation-2026-03-30/RECONCILIATION_COMPLETENESS_CHECK.md b/.claude/docs/audit/platzhalter/reconciliation-2026-03-30/RECONCILIATION_COMPLETENESS_CHECK.md new file mode 100644 index 0000000..0473fdc --- /dev/null +++ b/.claude/docs/audit/platzhalter/reconciliation-2026-03-30/RECONCILIATION_COMPLETENESS_CHECK.md @@ -0,0 +1,14 @@ +# Reconciliation Completeness Validation + +## Vollstaendigkeitscheck +- Export-Keys: 111 +- Matrix-Keys: 111 +- Differenz: 0 +- Fehlende Keys: [] +- Doppelte Keys: [] + +## Status: PASS + +## Details + +All placeholders successfully reconciled. Matrix contains exactly 111 entries matching the export catalog. \ No newline at end of file diff --git a/.claude/docs/functional/ACTIVITY_QUALITY_GATES.md b/.claude/docs/functional/ACTIVITY_QUALITY_GATES.md new file mode 100644 index 0000000..87faf77 --- /dev/null +++ b/.claude/docs/functional/ACTIVITY_QUALITY_GATES.md @@ -0,0 +1,422 @@ +# Activity Quality Gates – Fachliches Konzept + +**Issue:** #15 +**Status:** Design Phase +**Erstellt:** 2026-03-23 + +--- + +## Problem-Statement + +### Aktuelles Problem: +Apple Health und andere Tracker importieren **alle** Workouts, unabhängig von ihrer Qualität: +- 5-Minuten-Spaziergang → "Outdoor Run" +- 10-Minuten-Krafttraining ohne echte Belastung +- Versehentlich gestartete Workouts +- Aufwärm-Sessions ohne echtes Training + +**Folgen:** +- KI-Analysen werden verfälscht ("Du trainierst täglich!" - aber nur 5min) +- Statistiken zeigen unrealistische Trainingsfrequenz +- Korrelationen (Training ↔ Erholung) werden unbrauchbar +- User verliert Vertrauen in die Auswertungen + +--- + +## Fachliche Anforderungen + +### Must-Have: +1. **Pro Trainingstyp** unterschiedliche Qualitätskriterien +2. **Automatische Bewertung** beim Import und bei manueller Eingabe +3. **Nicht-destruktiv** - keine Daten löschen, nur markieren +4. **Transparenz** - User sieht warum eine Aktivität als minderwertig gilt +5. **Opt-Out möglich** - User kann Quality Gates pro Aktivität überschreiben + +### Nice-to-Have: +6. **Admin-Presets** für gängige Trainingstypen (Laufen, Krafttraining, etc.) +7. **User-spezifische Anpassung** (z.B. für Reha-Patienten andere Schwellwerte) +8. **Historische Nachbearbeitung** - bestehende Aktivitäten neu bewerten + +--- + +## Lösungsansätze (Evaluation) + +### Ansatz A: Quality Flag (Boolean) +```sql +ALTER TABLE activity_log ADD COLUMN is_valid BOOLEAN DEFAULT true; +``` + +**Pro:** +- Einfach zu implementieren +- Schnelle Queries (`WHERE is_valid = true`) +- Binäre Entscheidung: gültig oder nicht + +**Contra:** +- ❌ Keine Abstufungen (was ist mit "grenzwertig"?) +- ❌ Kein Grund dokumentiert (warum ungültig?) +- ❌ Schwer erweiterbar + +**Bewertung:** ⭐⭐☆☆☆ (zu simpel) + +--- + +### Ansatz B: Quality Score (0-100) +```sql +ALTER TABLE activity_log ADD COLUMN quality_score INTEGER DEFAULT 100; +``` + +**Pro:** +- Abstufungen möglich (100 = perfekt, 0 = wertlos) +- Filterbar: `WHERE quality_score >= 70` +- Flexibel für zukünftige Erweiterungen + +**Contra:** +- ❌ Score-Berechnung komplex (wie gewichten?) +- ❌ Kein Grund dokumentiert +- ❌ Schwellwert (70? 80?) willkürlich + +**Bewertung:** ⭐⭐⭐☆☆ (besser, aber intransparent) + +--- + +### Ansatz C: Validation Result (JSONB) ⭐ **EMPFEHLUNG** +```sql +ALTER TABLE activity_log ADD COLUMN quality_check JSONB DEFAULT NULL; + +-- Beispiel-Daten: +{ + "evaluated_at": "2026-03-23T10:30:00Z", + "passed": false, + "score": 45, + "reasons": [ + {"rule": "duration_min", "expected": 15, "actual": 8, "passed": false}, + {"rule": "avg_hr_min", "expected": 100, "actual": 95, "passed": false}, + {"rule": "max_hr_min", "expected": 120, "actual": 125, "passed": true} + ], + "override": null // User kann auf "valid" oder "invalid" setzen +} +``` + +**Pro:** +- ✅ Transparent: Jede Regel einzeln nachvollziehbar +- ✅ Erweiterbar: Neue Regeln einfach hinzufügbar +- ✅ Override-Mechanismus eingebaut +- ✅ Historisch: Wann wurde evaluiert? +- ✅ Flexibel: Score + Boolean + Details + +**Contra:** +- Mehr Speicherplatz (aber marginal bei JSONB) +- Komplexere Queries (aber PostgreSQL JSONB ist schnell) + +**Bewertung:** ⭐⭐⭐⭐⭐ **BESTE LÖSUNG** + +--- + +## Empfohlene Lösung: Ansatz C (Validation Result) + +### Datenmodell + +#### 1. Training Types (Regel-Definition) +```sql +ALTER TABLE training_types ADD COLUMN quality_rules JSONB DEFAULT NULL; + +-- Beispiel: Laufen +UPDATE training_types SET quality_rules = '{ + "enabled": true, + "rules": { + "duration_min": {"min": 15, "weight": 3}, + "avg_hr_min": {"min": 100, "weight": 2}, + "max_hr_min": {"min": 120, "weight": 1}, + "distance_km": {"min": 1.0, "weight": 1} + }, + "pass_threshold": 0.6, + "description": "Mindestens 15min, Durchschnittspuls > 100" +}'::jsonb WHERE name_de = 'Laufen'; + +-- Beispiel: Krafttraining +UPDATE training_types SET quality_rules = '{ + "enabled": true, + "rules": { + "duration_min": {"min": 20, "weight": 5}, + "avg_hr_min": {"min": 90, "weight": 1} + }, + "pass_threshold": 0.8, + "description": "Mindestens 20 Minuten" +}'::jsonb WHERE name_de = 'Krafttraining'; +``` + +**Erklärung:** +- `enabled`: Quality Gates aktiv für diesen Typ? +- `rules`: Dictionary der Regeln mit Schwellwerten + Gewichtung +- `weight`: Wichtigkeit der Regel (1-5) +- `pass_threshold`: Mindest-Score (0.0-1.0) für "bestanden" +- `description`: User-freundliche Erklärung + +#### 2. Activity Log (Validierungs-Ergebnis) +```sql +ALTER TABLE activity_log ADD COLUMN quality_check JSONB DEFAULT NULL; +``` + +**Struktur siehe Ansatz C oben.** + +--- + +## Validierungs-Logik + +### Backend-Funktion: `validate_activity_quality()` + +```python +def validate_activity_quality(activity: dict, training_type: dict) -> dict: + """ + Evaluiert eine Aktivität gegen die Quality Rules des Trainingstyps. + + Returns: + { + "evaluated_at": ISO timestamp, + "passed": bool, + "score": float (0.0-1.0), + "reasons": [ + {"rule": str, "expected": value, "actual": value, "passed": bool} + ], + "override": null | "valid" | "invalid" + } + """ + rules = training_type.get('quality_rules', {}) + + if not rules or not rules.get('enabled'): + return None # Keine Quality Gates aktiv + + results = [] + total_weight = 0 + passed_weight = 0 + + for rule_name, rule_config in rules['rules'].items(): + weight = rule_config.get('weight', 1) + total_weight += weight + + actual_value = activity.get(rule_name) + expected_min = rule_config.get('min') + + passed = actual_value is not None and actual_value >= expected_min + + if passed: + passed_weight += weight + + results.append({ + "rule": rule_name, + "expected": expected_min, + "actual": actual_value, + "passed": passed + }) + + score = passed_weight / total_weight if total_weight > 0 else 1.0 + passed = score >= rules.get('pass_threshold', 0.6) + + return { + "evaluated_at": datetime.now().isoformat(), + "passed": passed, + "score": round(score, 2), + "reasons": results, + "override": None + } +``` + +### Wann wird validiert? + +1. **Beim Import** (CSV, API) + - Automatisch nach INSERT + - Spalte `quality_check` wird befüllt + +2. **Beim manuellen Anlegen** + - Automatisch nach INSERT + - Spalte `quality_check` wird befüllt + +3. **Beim Ändern der Quality Rules** (Admin) + - Optionaler Batch-Job: Alle Aktivitäten neu evaluieren + +4. **Beim User-Override** + - User setzt `quality_check.override = "valid"` oder `"invalid"` + +--- + +## User Experience + +### 1. Activity-Liste (User-Ansicht) + +``` +┌─────────────────────────────────────────────────────────┐ +│ Aktivitäten (März 2026) [Filter ▼] │ +├─────────────────────────────────────────────────────────┤ +│ │ +│ 🏃 Laufen - 23. März 2026 │ +│ 45 Minuten · Ø 142 bpm · 5.2 km │ +│ ✅ Hochwertige Aktivität │ +│ │ +│ 🏃 Laufen - 22. März 2026 ⚠️ │ +│ 8 Minuten · Ø 95 bpm · 0.8 km │ +│ ⚠️ Niedrige Qualität - wird nicht gezählt │ +│ [Details anzeigen ▼] │ +│ │ +│ → Dauer zu kurz (8 min < 15 min erforderlich) ❌ │ +│ → Durchschnittspuls zu niedrig (95 < 100) ❌ │ +│ → Maximalpuls OK (125 ≥ 120) ✅ │ +│ │ +│ [Als gültig markieren] [Als ungültig markieren] │ +│ │ +└─────────────────────────────────────────────────────────┘ +``` + +**Filter-Optionen:** +- ☑ Hochwertige Aktivitäten +- ☑ Minderwertige Aktivitäten +- ☐ Nur in Statistiken gezählt +- ☐ Nur manuell überschrieben + +### 2. Admin-UI (Quality Rules konfigurieren) + +``` +┌─────────────────────────────────────────────────────────┐ +│ Trainingstyp bearbeiten: Laufen 🏃 │ +├─────────────────────────────────────────────────────────┤ +│ │ +│ Kategorie: Cardio │ +│ Name (DE): Laufen │ +│ Name (EN): Running │ +│ │ +│ ━━━ Quality Gates ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ +│ │ +│ ☑ Quality Gates aktiviert │ +│ │ +│ Mindest-Schwellwerte: │ +│ │ +│ • Dauer (Minuten) [15] min Gewicht: ●●●○○ │ +│ • Ø Herzfrequenz [100] bpm Gewicht: ●●○○○ │ +│ • Max. Herzfrequenz [120] bpm Gewicht: ●○○○○ │ +│ • Distanz [1.0] km Gewicht: ●○○○○ │ +│ │ +│ Erfolgs-Schwelle: [60]% (gewichteter Score) │ +│ │ +│ Beschreibung (User-sichtbar): │ +│ "Mindestens 15 Minuten, Durchschnittspuls > 100" │ +│ │ +│ [Speichern] [Abbrechen] [Alle Aktivitäten neu prüfen]│ +│ │ +└─────────────────────────────────────────────────────────┘ +``` + +### 3. Dashboard / Statistiken + +**Transparenz:** +``` +📊 Training (März 2026) + 15 Aktivitäten erfasst + 12 hochwertig ✅ + 3 minderwertig ⚠️ (werden nicht gezählt) + + [Details zu minderwertigen Aktivitäten anzeigen] +``` + +--- + +## Betroffene Bereiche (Impact Analysis) + +### 1. Aktivitäten-Listen +- **Query-Änderung:** + ```sql + -- Alt: + SELECT * FROM activity_log WHERE profile_id = %s + + -- Neu (nur hochwertige): + SELECT * FROM activity_log + WHERE profile_id = %s + AND (quality_check IS NULL + OR quality_check->>'passed' = 'true' + OR quality_check->>'override' = 'valid') + ``` + +### 2. KI-Pipeline +- **insights.py:** Filter bei Daten-Aggregation +- Nur hochwertige Aktivitäten für KI-Prompts + +### 3. Charts / Statistiken +- **Training Type Distribution:** Nur hochwertige zählen +- **Korrelations-Charts:** Separate Kurven für "alle" vs "hochwertig"? + +### 4. Activity Import (CSV) +- Nach INSERT: `validate_activity_quality()` aufrufen +- Quality Check speichern + +--- + +## Offene Fragen (für Entscheidung) + +### Frage 1: Rückwirkende Evaluierung? +**Option A:** Nur neue Aktivitäten evaluieren +**Option B:** Alle bestehenden Aktivitäten nachträglich evaluieren + +**Empfehlung:** Option B mit Batch-Job (einmalig beim Rollout) + +### Frage 2: Standard-Verhalten bei fehlenden Quality Rules? +**Option A:** Alle Aktivitäten gelten als hochwertig (NULL = valid) +**Option B:** Alle Aktivitäten gelten als minderwertig (NULL = invalid) + +**Empfehlung:** Option A (konservativ, keine Daten-Verlust-Angst) + +### Frage 3: User-Override-Berechtigung? +**Option A:** Jeder User kann eigene Aktivitäten überschreiben +**Option B:** Nur Admin kann überschreiben + +**Empfehlung:** Option A (User kennt seinen Kontext am besten) + +### Frage 4: Wie viele Default-Rules? +**Option A:** Nur für Top 5 Trainingstypen (Laufen, Krafttraining, Radfahren, Schwimmen, HIIT) +**Option B:** Für alle 29 Trainingstypen + +**Empfehlung:** Option A (Rest kann Admin/User nachpflegen) + +### Frage 5: Quality Check in Liste anzeigen? +**Option A:** Immer sichtbar (Badge/Icon) +**Option B:** Nur bei minderwertigen Aktivitäten + +**Empfehlung:** Option B (weniger visuelles Rauschen) + +--- + +## Implementierungs-Reihenfolge (Vorschlag) + +### Phase 1: Foundation (MVP) +1. DB-Migration: `quality_rules` + `quality_check` Spalten +2. Backend: `validate_activity_quality()` Funktion +3. Backend: Validierung beim INSERT (activity.py) +4. Admin-UI: Quality Rules konfigurieren (basic) + +### Phase 2: User Experience +5. Frontend: Badge/Warning in Activity-Liste +6. Frontend: Details-Ansicht (welche Regeln failed?) +7. Frontend: User-Override (Als gültig/ungültig markieren) + +### Phase 3: Integration +8. KI-Pipeline: Filter für hochwertige Aktivitäten +9. Charts: Nur hochwertige zählen +10. Batch-Job: Bestehende Aktivitäten evaluieren + +### Phase 4: Polish +11. Admin-UI: Presets für gängige Trainingstypen +12. Filter-Optionen in Activity-Liste +13. Dashboard: Statistik hochwertig vs. minderwertig + +**Geschätzter Aufwand:** 4-6 Stunden (mit Tests) + +--- + +## Nächste Schritte + +1. **Entscheidung:** Offene Fragen klären +2. **Technisches Design:** DB-Schema + API-Endpoints spezifizieren +3. **Implementation:** Phase 1 starten +4. **Testing:** Mit echten Apple Health Daten testen + +--- + +**Erstellt:** 2026-03-23 +**Review:** Pending User-Feedback diff --git a/.claude/docs/functional/AI_PROMPTS.md b/.claude/docs/functional/AI_PROMPTS.md new file mode 100644 index 0000000..b889dde --- /dev/null +++ b/.claude/docs/functional/AI_PROMPTS.md @@ -0,0 +1,458 @@ +# Fachliche Anforderungen: KI-Prompt Flexibilisierung +**Modul:** v9f +**Status:** Fachlich freigegeben, technische Implementierung ausstehend +**Letzte Aktualisierung:** März 2026 + +--- + +## 1. Überblick + +Das bestehende KI-Prompt-System wird von einer fixen Sammlung vordefinierter +Prompts zu einer vollständig konfigurierbaren Prompt-Bibliothek erweitert. +Admins können Prompts kategorisieren, duplizieren, bearbeiten und mit einem +visuellen Platzhalter-Browser ausstatten. Die Pipeline wird konfigurierbar – +mehrere Konfigurationen für unterschiedliche Analysezwecke sind möglich. + +--- + +## 2. Prompt-Bibliothek + +### 2.1 Kategorien + +Alle Prompts werden einer Kategorie zugeordnet: + +| Kategorie | Beschreibung | Beispiel-Prompts | +|-----------|-------------|-----------------| +| Körper | Gewicht, KF, Umfänge, Caliper | Körperkomposition, Gewichtstrend | +| Ernährung | Kalorien, Makros, Timing | Kalorienbilanz, Proteinversorgung | +| Training | Volumen, Typen, HF | Trainingsanalyse, Erholungsstatus | +| Schlaf | Qualität, Dauer, Muster | Schlafauswertung, Schlaftrend | +| Vitalwerte | Ruhepuls, HRV, VO2Max | Leistungsfähigkeit, Erholung | +| Mentales | Stress, Stimmung, Energie | Wohlbefinden, Stressanalyse | +| Ziele | Fortschritt, Prognose, Zeitplan | Zielerreichung, Zeitplanung | +| Ganzheitlich | Korrelationen, Übersicht | Gesamtanalyse, Pipeline-Synthese | + +### 2.2 Prompt-Verwaltung (Admin) + +Ein Admin kann mit jedem Prompt folgendes tun: + +**Aktivieren / Deaktivieren** +- Inaktive Prompts sind für Nutzer nicht sichtbar und nicht ausführbar +- Pipeline-Prompts können separat aktiviert/deaktiviert werden + +**Duplizieren und anpassen** +- Bestehenden Prompt als Vorlage kopieren +- Kopie erhält Suffix " (Kopie)" und kann unabhängig bearbeitet werden + +**Neu erstellen** +- Titel, Beschreibung, Kategorie, Template +- Platzhalter über den Platzhalter-Browser einfügen (siehe Abschnitt 3) + +**Kategorie zuordnen** +- Beim Erstellen und Bearbeiten wählbar +- Nachträgliche Änderung möglich + +**Reihenfolge festlegen** +- Drag & Drop oder Zahleneingabe (sort_order) +- Reihenfolge bestimmt Anzeige in der Nutzer-Ansicht + +**Auf Standard zurücksetzen** +- Systemseitig definierte Prompts haben einen "Standard"-Zustand +- Reset stellt Original-Template wieder her +- Eigens erstellte Prompts haben keinen Standard-Reset + +### 2.3 Prompt-Felder + +Jeder Prompt hat: +- **Titel** – kurzer Name (z.B. "Körperkomposition") +- **Beschreibung** – wofür ist dieser Prompt? (für Admin sichtbar) +- **Kategorie** – aus der Kategorienliste +- **Template** – der eigentliche Prompt-Text mit Platzhaltern +- **Aktiv** – true/false +- **Reihenfolge** – Zahl für Sortierung +- **Typ** – `single` (Einzelanalyse) oder `pipeline` (Pipeline-Stufe) +- **Pipeline-Stufe** – nur bei Typ `pipeline`: Stufen-Nummer (1, 2, 3...) + +--- + +## 3. Platzhalter-System + +### 3.1 Platzhalter-Browser + +Beim Bearbeiten eines Prompts steht ein visueller Platzhalter-Browser zur +Verfügung – Admin tippt Platzhalter nicht mehr manuell, sondern wählt sie aus. + +**Funktionen:** +- **Filterung nach Kategorie** – zeigt nur Platzhalter der gewählten Kategorie +- **Beispielwert** – zeigt was der Platzhalter aktuell mit echten Daten ausgeben würde +- **Klick zum Einfügen** – Platzhalter wird an Cursor-Position ins Template eingefügt +- **Warnung bei fehlendem Platzhalter** – wenn ein eingetippter Platzhalter nicht existiert +- **Warnung bei fehlenden Daten** – wenn Daten für diesen Platzhalter nicht vorhanden sind (z.B. noch kein Schlaf-Tracking) + +### 3.2 Prompt-Vorschau + +Vor dem Speichern kann der Admin eine Vorschau anfordern: +- System befüllt alle Platzhalter mit echten aktuellen Daten +- Zeigt den fertigen Prompt-Text wie er an die KI gesendet würde +- KI wird dabei **nicht** aufgerufen – nur der Prompt-Text wird angezeigt + +### 3.3 Platzhalter-Kategorien und Übersicht + +**Körper:** +``` +{{weight_aktuell}} → "86,1 kg" +{{weight_trend}} → "sinkend (-0,8 kg letzte 4 Wochen)" +{{kf_aktuell}} → "19,9%" +{{kf_trend}} → "stabil" +{{magermasse}} → "69,4 kg" +{{whr}} → "0,90" +{{whtr}} → "0,51" +{{bmi}} → "27,2" +{{circ_summary}} → "Taille: 88cm, Hüfte: 98cm, ..." +{{caliper_summary}} → "KF nach Jackson/Pollock: 19,9%" +``` + +**Ernährung:** +``` +{{kcal_avg}} → "1.847 kcal/Tag (Ø 30 Tage)" +{{protein_avg}} → "138g/Tag" +{{protein_ziel_low}} → "138" +{{protein_ziel_high}} → "172" +{{fat_avg}} → "72g/Tag" +{{carb_avg}} → "195g/Tag" +{{nutrition_summary}} → kompakte Zusammenfassung +{{nutrition_detail}} → ausführliche Tabelle +{{nutrition_days}} → "28 Tage mit Daten" +{{activity_kcal_summary}} → "Ø 320 kcal aktiver Verbrauch/Tag" +``` + +**Training:** +``` +{{activity_summary}} → kompakte Zusammenfassung +{{activity_detail}} → ausführliche Liste +{{trainingstyp_verteilung}} → "60% Kraft, 30% Cardio, 10% Mobility" +{{trainingstyp_haupttyp}} → "Kraft" (häufigster Typ) +{{ruhetage_letzte_woche}} → "2" +{{trainingsphase}} → "Aufbau / Erholung / Plateau" +{{hf_zonen_verteilung}} → "Zone 2: 45%, Zone 3: 35%, Zone 4: 20%" + +{{faehigkeiten_analyse}} → Detaillierte Analyse der trainierten Fähigkeiten +{{faehigkeiten_koordinativ}} → "Orientierung: 3/14 Einheiten, Gleichgewicht: 5/14, ..." +{{faehigkeiten_konditionell}} → "Kraft: 8/14, Ausdauer: 4/14, Schnelligkeit: 2/14, Flexibilität: 3/14" +{{faehigkeiten_kognitiv}} → "Konzentration: 2/14, Entscheidung: 1/14, ..." +{{faehigkeiten_psychisch}} → "Willenskraft: 5/14, Stressresistenz: 2/14, ..." +{{faehigkeiten_taktisch}} → "Timing: 1/14, Antizipation: 2/14, ..." +{{faehigkeiten_balance}} → "Ausgewogen / Einseitig + Empfehlung" +``` + +**Fähigkeiten-System (v9d Integration):** +- Jeder Trainingstyp ist mit 1-N Fähigkeiten aus 5 Dimensionen verknüpft +- KI erhält aggregierte Statistik: wie oft wurden welche Fähigkeiten trainiert +- Ermöglicht Aussagen wie: "Dein Training fokussiert stark auf Kraft (8 von 14 Einheiten), aber koordinative Fähigkeiten kommen zu kurz." +- Basis für gezielte Trainingsempfehlungen + +**Herzfrequenz & Vitalwerte:** +``` +{{ruhepuls_aktuell}} → "52 bpm" +{{ruhepuls_trend}} → "sinkend (-3 bpm letzte 4 Wochen)" +{{hrv_aktuell}} → "58 ms" +{{hrv_baseline}} → "62 ms (30-Tage-Durchschnitt)" +{{erholungsstatus}} → "gut / teilweise / schlecht" +{{vo2max}} → "48,2 ml/kg/min" +``` + +**Schlaf:** +``` +{{schlaf_avg_dauer}} → "7,2h/Nacht (Ø 7 Tage)" +{{schlaf_qualitaet}} → "3,8/5 (Ø 7 Tage)" +{{schlaf_trend}} → "stabil" +{{schlaf_detail}} → ausführliche Tabelle +``` + +**Mentales:** +``` +{{energie_niveau}} → "3,2/5 (Ø 7 Tage)" +{{stress_niveau}} → "2,8/5 (Ø 7 Tage)" +{{stimmung}} → "3,9/5 (Ø 7 Tage)" +{{meditation_streak}} → "5 Tage in Folge" +``` + +**Ziele:** +``` +{{goal_weight}} → "82 kg" +{{goal_bf_pct}} → "14%" +{{goal_name}} → "Muskelaufbau" +{{ziel_fortschritt}} → "Gewicht: 72% erreicht, KF: 45% erreicht" +{{ziel_prognose}} → "Ziel voraussichtlich in 8 Wochen erreicht" +``` + +**Profil:** +``` +{{name}} → "Lars" +{{geschlecht}} → "männlich" +{{height}} → "178" +{{age}} → "45" +{{sprache}} → "Deutsch" (konfigurierbar per Prompt) +``` + +**Zeitraum:** +``` +{{zeitraum_7d}} → "letzte 7 Tage" +{{zeitraum_30d}} → "letzte 30 Tage" +{{zeitraum_90d}} → "letzte 90 Tage" +{{datum_heute}} → "20. März 2026" +``` + +**Pipeline (intern):** +``` +{{stage1_body}} → JSON-Summary Körper (Pipeline-Stufe 1) +{{stage1_nutrition}} → JSON-Summary Ernährung +{{stage1_activity}} → JSON-Summary Training +{{stage1_sleep}} → JSON-Summary Schlaf (neu) +{{stage1_vitals}} → JSON-Summary Vitalwerte (neu) +``` + +--- + +## 4. Pipeline-Konfiguration + +### 4.1 Was ist eine Pipeline-Konfiguration? + +Eine Pipeline-Konfiguration definiert: +- **Name** – z.B. "Alltags-Check", "Wettkampf-Analyse", "Schlaf-Fokus" +- **Aktive Module** – welche Datenquellen fließen ein (Körper, Ernährung, Training, Schlaf, Vitalwerte, Mentales) +- **Zeitraum je Modul** – z.B. Körper 30 Tage, Schlaf 7 Tage, Training 14 Tage +- **Stufen** – welcher Prompt wird in welcher Stufe verwendet +- **Standard** – eine Konfiguration kann als Standard markiert werden + +### 4.2 Pipeline-Stufen + +**Stufe 1 (parallel):** Mehrere Module-Prompts laufen gleichzeitig, jeder gibt ein JSON-Summary zurück + +**Stufe 2:** Synthese-Prompt kombiniert alle Stage-1-Summaries zu einer narrativen Analyse + +**Stufe 3 (optional):** Ziel-Abgleich oder spezifische Zusatzanalyse + +Admin kann: +- Anzahl der Stufen festlegen (min. 2, max. 4) +- Jeden Stufen-Prompt zuweisen +- Module für Stufe 1 aktivieren/deaktivieren + +### 4.3 Vordefinierte Konfigurationen (Beispiele) + +**Alltags-Check (Standard):** +- Module: Körper (30T), Ernährung (30T), Training (14T) +- Stufe 1: pipeline_body + pipeline_nutrition + pipeline_activity +- Stufe 2: pipeline_synthesis +- Stufe 3: pipeline_goals + +**Schlaf-Fokus:** +- Module: Schlaf (14T), Vitalwerte (7T), Training (14T) +- Stufe 1: pipeline_sleep + pipeline_vitals + pipeline_activity +- Stufe 2: pipeline_synthesis_sleep + +**Wettkampf-Analyse:** +- Module: Körper (90T), Training (90T), Vitalwerte (30T), Ziele +- Stufe 1: alle Körper/Training/Vital-Module +- Stufe 2: pipeline_synthesis_performance + +--- + +## 5. Nutzer-Ansicht: KI-Analyse starten + +### 5.1 Einzelanalyse + +1. Nutzer wählt Prompt aus der Bibliothek (nach Kategorie gefiltert) +2. KI-Analyse startet sofort +3. Ergebnis wird angezeigt und gespeichert + +### 5.2 Pipeline + +1. Nutzer wählt Pipeline-Konfiguration +2. Pipeline startet sofort (alle Stufen laufen automatisch) +3. Ergebnis wird angezeigt und gespeichert + +### 5.3 Kein Konfigurationsschritt für Nutzer + +Der Nutzer wählt nur Prompt oder Pipeline – kein weiterer Konfigurationsschritt. +Zeitraum und Module sind durch Admin in der Pipeline-Konfiguration festgelegt. + +--- + +## 6. Analyse-Ergebnisse verwalten + +### 6.1 Kategorie-Zuordnung + +Jedes gespeicherte Analyse-Ergebnis erbt die Kategorie des ausgeführten Prompts. +Ermöglicht spätere Filterung nach Kategorie. + +### 6.2 Filtern nach Kategorie + +Im Analyse-Verlauf kann nach Kategorie gefiltert werden: +- "Alle" / "Körper" / "Ernährung" / "Training" / etc. + +### 6.3 Analyse-Verlauf pro Prompt + +Für jeden Prompt ist ein Verlauf einsehbar: +- Liste aller bisherigen Analysen mit diesem Prompt (Datum + Kurzauszug) +- Zeigt wie sich die KI-Aussage über Zeit verändert hat + +### 6.4 Favoriten + +Nutzer kann Analysen als Favorit markieren (Stern-Symbol). +Favoriten sind in der Verlaufs-Ansicht filterbar. + +### 6.5 Löschen + +Nutzer kann einzelne Analyse-Ergebnisse manuell löschen. +Keine Massen-Löschung (außer Admin). + +--- + +## 7. Sprache + +Die Ausgabesprache wird im Prompt-Template selbst gesteuert über den +Platzhalter `{{sprache}}`. Der Wert wird aus den Profil-Einstellungen gezogen. + +**Mögliche Werte:** "Deutsch", "Englisch", "Französisch" (erweiterbar) + +**Standard:** "Deutsch" + +Kein separater Sprach-Schalter auf der Analyse-Seite – Sprache wird einmalig +im Profil konfiguriert. + +--- + +## 8. Abgrenzung & offene Fragen + +### In diesem Modul enthalten: +- Prompt-Bibliothek mit 8 Kategorien +- Vollständige Admin-Verwaltung (CRUD, Reihenfolge, Reset) +- Platzhalter-Browser mit Beispielwerten und Vorschau +- Pipeline-Konfigurationen (mehrere, speicherbar) +- Analyse-Ergebnisse: Kategorie, Filter, Verlauf, Favoriten, Löschen +- Sprach-Platzhalter + +### Nicht in diesem Modul: +- Community-Prompts / Prompt-Import von extern → später +- KI-gestützte Prompt-Optimierung → später +- Automatisch geplante Analysen (z.B. wöchentlich) → später +- Prompts für noch nicht implementierte Module (Schlaf, Mentales) werden + vorbereitet aber haben keine echten Daten bis die Module existieren + +### Offene Fragen für technische Planung: +1. Wie werden Pipeline-Konfigurationen gespeichert? (neue DB-Tabelle oder JSON in ai_prompts?) +2. Platzhalter-Definitionen: statisch im Code oder dynamisch in DB (für zukünftige Erweiterung)? +3. Wie wird der Beispielwert für den Platzhalter-Browser berechnet – gleicher Endpunkt wie die echte Analyse? +4. Reihenfolge der Prompts: getrennte sort_order pro Kategorie oder global? + +--- + +## 9. Trainingstypen & Fähigkeiten-Mapping (v9d → v9f Integration) + +### 9.1 Hintergrund + +In v9d wurde die Basis geschaffen: +- `training_types` Tabelle mit `abilities` JSONB-Spalte +- Admin-CRUD für Trainingstypen (ohne Abilities-UI) +- Taxonomie-Endpoint: `/api/admin/training-types/taxonomy/abilities` +- 5 Fähigkeiten-Dimensionen definiert: + - 🎯 **Koordinativ:** Orientierung, Differenzierung, Kopplung, Gleichgewicht, Rhythmus, Reaktion, Umstellung + - 💪 **Konditionell:** Kraft, Ausdauer, Schnelligkeit, Flexibilität + - 🧠 **Kognitiv:** Konzentration, Aufmerksamkeit, Wahrnehmung, Entscheidungsfindung + - 🎭 **Psychisch:** Motivation, Willenskraft, Stressresistenz, Selbstvertrauen + - ♟️ **Taktisch:** Timing, Strategie, Antizipation, Situationsanalyse + +### 9.2 Was wird in v9f ergänzt? + +**Admin-UI: Fähigkeiten-Matrix** + +Im AdminTrainingTypesPage Formular wird eine Fähigkeiten-Matrix hinzugefügt: +- 5 Sections (eine pro Dimension) +- Pro Dimension: Checkboxes für alle Fähigkeiten +- Multi-Select: Ein Trainingstyp kann mehrere Fähigkeiten pro Dimension trainieren +- Beispiel: "Kampfsport-Sparring" trainiert: + - Koordinativ: Orientierung, Reaktion, Umstellung + - Konditionell: Schnelligkeit, Ausdauer + - Kognitiv: Entscheidung, Wahrnehmung + - Psychisch: Stressresistenz, Selbstvertrauen + - Taktisch: Timing, Antizipation + +**Gespeichert als JSONB:** +```json +{ + "koordinativ": ["orientierung", "reaktion", "umstellung"], + "konditionell": ["schnelligkeit", "ausdauer"], + "kognitiv": ["entscheidung", "wahrnehmung"], + "psychisch": ["stressresistenz", "selbstvertrauen"], + "taktisch": ["timing", "antizipation"] +} +``` + +**Backend: Fähigkeiten-Aggregation** + +Neue Endpoint-Funktion (oder Platzhalter-Funktion): +```python +def calculate_abilities_summary(profile_id, days=14): + """ + Aggregiert trainierte Fähigkeiten über Zeitraum. + + Returns: + { + "total_activities": 14, + "koordinativ": { + "orientierung": 3, # 3 von 14 Trainings + "gleichgewicht": 5, + ... + }, + "konditionell": { + "kraft": 8, + "ausdauer": 4, + ... + }, + ... + "balance_score": 0.67, # 0-1, wie ausgewogen + "top_dimension": "konditionell", + "weak_dimension": "kognitiv" + } + """ +``` + +**KI-Prompt Integration:** + +Die Fähigkeiten-Platzhalter werden befüllt mit aggregierten Daten: +- `{{faehigkeiten_analyse}}` → vollständige Aufschlüsselung +- `{{faehigkeiten_koordinativ}}` → nur koordinative Dimension +- etc. + +Pipeline-Prompt "Training" erhält zusätzlichen Context: +``` +Analyse die Fähigkeitenverteilung: +{{faehigkeiten_analyse}} + +Gib Empfehlungen für untertrainierte Bereiche. +``` + +### 9.3 Nutzen für KI-Analyse + +**Beispiel-Insight:** +> "Deine letzten 14 Trainings fokussieren stark auf **konditionelle Fähigkeiten** (Kraft: 8×, Ausdauer: 4×). Koordinative Fähigkeiten kommen zu kurz (nur 3× Gleichgewicht, 2× Reaktion). Für einen ausgewogenen Trainingsplan empfehle ich 2-3 koordinationslastige Einheiten pro Woche – z.B. Kampfsport-Techniktraining, Balance-Übungen oder Mobility-Routinen mit Fokus auf Bewegungskontrolle." + +**Korrelationen:** +- Verletzungen / Überlastung ↔ Koordinations-Defizit +- Plateaus im Krafttraining ↔ fehlende kognitive/taktische Komponente +- Erholungsprobleme ↔ psychische Fähigkeiten untertrainiert + +### 9.4 Implementierungs-Reihenfolge + +1. ✅ v9d Phase 1b: DB-Schema + Admin-CRUD (ohne Abilities-UI) → **erledigt** +2. 🔲 v9f: Abilities-Matrix UI im Admin-Formular +3. 🔲 v9f: Backend-Funktion `calculate_abilities_summary()` +4. 🔲 v9f: Platzhalter `{{faehigkeiten_*}}` implementieren +5. 🔲 v9f: Standard-Prompts aktualisieren mit Fähigkeiten-Context +6. 🔲 v9f: Admin-Doku: "Best Practices" für Fähigkeiten-Zuordnung + +--- + +EOF +echo "OK" \ No newline at end of file diff --git a/.claude/docs/functional/DATA_ARCHITECTURE.md b/.claude/docs/functional/DATA_ARCHITECTURE.md new file mode 100644 index 0000000..e00fbc7 --- /dev/null +++ b/.claude/docs/functional/DATA_ARCHITECTURE.md @@ -0,0 +1,1066 @@ +# Fachliche Datenarchitektur – Mitai Jinkendo + +**Version:** 2.0 +**Status:** Living Document +**Letzte Aktualisierung:** 29. März 2026 + +--- + +## Überblick + +Dieses Dokument beschreibt die **fachliche Datenarchitektur** von Mitai Jinkendo aus Domänen-Perspektive. Es zeigt: +- Welche **Datenobjekte** (Domänen-Entitäten) existieren +- Wie sie **zusammenhängen** (Beziehungen) +- Welche Daten **gespeichert** vs. **berechnet** werden +- Welche **Anreicherungen** stattfinden (z.B. Trainingsqualität, Recovery Score) +- Wie Daten **fließen** (Import → Speicherung → Anreicherung → Auswertung) + +**Technische Schema-Details:** Siehe `DATABASE.md` in `.claude/library/` + +**Berechnungen & Chart-Schicht (Code):** `backend/data_layer/` · Leitfaden: `../technical/DATA_LAYER_EXTENSION_GUIDE.md` + +--- + +## Domänen-Übersicht + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ NUTZER & ZIELSETZUNG │ +├─────────────────────────────────────────────────────────────────┤ +│ Profil → Goal Mode (Strategic) → Ziele (Tactical) │ +│ Focus Areas (Dynamic) → Membership → Access Control │ +└─────────────────────────────────────────────────────────────────┘ + │ + ┌─────────────────────┼─────────────────────┐ + ▼ ▼ ▼ +┌──────────────┐ ┌──────────────┐ ┌──────────────┐ +│ KÖRPER │ │ TRAINING │ │ LIFESTYLE │ +├──────────────┤ ├──────────────┤ ├──────────────┤ +│ Gewicht │ │ Aktivitäten │ │ Schlaf │ +│ Umfänge │ │ Trainingstyp │ │ Ernährung │ +│ Caliper │ │ HF-Daten │ │ Ruhetage │ +│ Vitalwerte │ │ Fähigkeiten ⚡│ │ Vitalwerte │ +│ Progress-Foto│ │ Qualität ⚡ │ │ Custom Goals │ +└──────────────┘ └──────────────┘ └──────────────┘ + │ │ │ + └─────────────────────┼─────────────────────┘ + ▼ + ┌──────────────────┐ + │ AUSWERTUNGEN │ + ├──────────────────┤ + │ Charts (20+) ⚡ │ + │ KI-Analysen │ + │ Korrelationen ⚡ │ + │ Scores ⚡ │ + └──────────────────┘ + +⚡ = Berechnete/angereicherte Daten +``` + +--- + +## 1. Nutzer & Zielsetzung + +### 1.1 Profil + +**Fachliche Bedeutung:** Zentrale Nutzer-Entität mit persönlichen Daten, Auth und Zielsetzung + +**Gespeicherte Daten:** +- **Stammdaten:** Name, Geschlecht, Geburtsdatum, Größe +- **Auth:** E-Mail, Passwort (bcrypt), Role (user/admin) +- **Membership:** Tier (free/premium/...), Trial-Ende +- **Goal Mode:** Strategische Ausrichtung (weight_loss, strength, endurance, recomposition, health) +- **Einstellungen:** Avatar-Farbe, Foto-ID, KI-Features + +**Berechnete Werte:** +- Alter (aus Geburtsdatum) +- BMI (aus aktuellem Gewicht + Größe) +- Trial-Status (Tage verbleibend) +- TDEE (geschätzt, Harris-Benedict oder Profil-basiert) + +**Beziehungen:** +- 1:N → Alle Tracking-Objekte (Gewicht, Aktivitäten, etc.) +- 1:1 → Goal Mode (aktuell) +- 1:N → Ziele (konkret) +- 1:N → Focus Area Weights (Prioritäten) +- 1:N → Sessions, Access Grants, Feature Usage + +**Datenquellen:** +- Manuelle Eingabe (Registrierung, Settings) +- System-generiert (Trial-Start, Tier-Zuweisung) + +--- + +### 1.2 Goal Mode (Strategic Layer) + +**Fachliche Bedeutung:** Strategische Ausrichtung die KI-Analysen und Score-Gewichtung steuert + +**Verfügbare Modi:** +- **Weight Loss:** Gewichtsreduktion, Kaloriendefizit +- **Strength:** Kraftaufbau, Muskelwachstum +- **Endurance:** Ausdauer, kardiovaskuläre Fitness +- **Recomposition:** Simultaner Fettabbau + Muskelaufbau +- **Health:** Gesundheit, Vitalwerte-Optimierung + +**Auswirkungen:** +- Bestimmt Score-Gewichtung in KI-Analysen +- Beeinflusst automatische Trainingsphasen-Erkennung +- Steuert Empfehlungs-Algorithmen + +**Beziehungen:** +- N:1 → Profil (ein aktiver Mode pro Nutzer) +- Beeinflusst: Focus Areas, Ziel-Priorisierung, KI-Prompts + +--- + +### 1.3 Ziele (Tactical Layer) + +**Fachliche Bedeutung:** Konkrete, messbare Ziele mit Fortschritts-Tracking + +**Ziel-Typen:** +- **Body Composition:** weight, body_fat, lean_mass +- **Fitness:** vo2max, strength (1RM), flexibility +- **Health:** blood_pressure, resting_heart_rate +- **Custom:** Beliebige nutzerdefinierte Ziele + +**Gespeicherte Daten:** +- Ziel-Typ, Start-Wert, Start-Datum +- Ziel-Wert, Ziel-Datum (optional) +- Beschreibung, Primär/Sekundär-Flag +- Focus Area Contributions (M:N, gewichtet) + +**Berechnete Werte:** +- **Aktueller Wert:** Automatisch aus Datenquellen (weight_log, vitals, etc.) +- **Progress (%):** (Aktuell - Start) / (Ziel - Start) × 100 +- **Linear Projection:** Fortschritts-Hochrechnung auf Ziel-Datum +- **Time-Based Tracking:** + - Expected Progress = (elapsed_days / total_days) × 100 + - Deviation = actual_progress - expected_progress + - Status: "behind schedule", "on track", "ahead of schedule" + +**Beziehungen:** +- N:1 → Profil +- N:M → Focus Areas (via goal_focus_contributions) +- 1:N → Custom Goal Entries (tägliche Werterfassung) + +**Datenquellen:** +- Manuelle Definition (Goals Page) +- Auto-Population: Start-Wert/-Datum aus historischen Messungen +- Fortschritt: Automatisch aus body metrics, vitals, fitness tests + +--- + +### 1.4 Focus Areas (Dynamic) + +**Fachliche Bedeutung:** Flexibles System zur Priorisierung von Lebensbereichen + +**26 Basis-Bereiche in 7 Kategorien:** +- **Körperkomposition (5):** Gewicht, Körperfett, Magermasse, Umfänge, Ästhetik +- **Kraft (4):** Maximalkraft, Kraftausdauer, funktionelle Kraft, Core-Stabilität +- **Ausdauer (4):** Kardio, muskuläre Ausdauer, VO2 Max, Erholungsfähigkeit +- **Mobilität (2):** Flexibilität, Beweglichkeit +- **Gesundheit (5):** Blutdruck, Ruhepuls, HRV, Schlafqualität, Vitalwerte +- **Leistungsfähigkeit (3):** Energie-Level, Belastbarkeit, Reaktionsvermögen +- **Lifestyle (3):** Ernährungsqualität, Konsistenz, Balance + +**Gespeicherte Daten:** +- **Definitions:** Name, Beschreibung, Kategorie, Icon (admin-erweiterbar) +- **User Weights:** Prozentuale Gewichtung pro Nutzer (dynamisch, summiert zu 100%) +- **Goal Contributions:** M:N Verknüpfung mit Gewichtung (ein Ziel zahlt auf 1-n Areas ein) + +**Berechnete Werte:** +- **Progress per Focus Area:** Gewichtete Summe aller zugeordneten Ziele +- **Category Scores:** Aggregierte Fortschritte pro Kategorie + +**Beziehungen:** +- N:M → Ziele (via goal_focus_contributions) +- N:1 → User Weights (Profil-spezifisch) +- 1:N → Definitions (admin-verwaltet) + +**Datenquellen:** +- Definitions: Admin-UI (erweiterbar) +- Weights: Goals Page Edit-Modus +- Contributions: Ziel-Formular (Multi-Select mit Gewichtung) + +--- + +### 1.5 Training Phases + +**Fachliche Bedeutung:** Automatisch erkannte oder manuell definierte Trainingszyklen + +**Phase-Typen:** +- **Calorie Deficit:** Diät-Phase (Weight Loss) +- **Calorie Surplus:** Aufbau-Phase (Strength/Muscle) +- **Deload:** Regenerations-Woche +- **Maintenance:** Status-Quo-Erhaltung +- **Periodization:** Zyklische Periodisierung + +**Gespeicherte Daten:** +- Typ, Start-/End-Datum, Status (suggested/accepted/active/completed) +- Confidence Score (bei KI-Erkennung) +- Beschreibung, Notizen + +**Status-Flow:** +``` +suggested → (User-Approval) → accepted → (Start-Datum erreicht) → active → (End-Datum) → completed +``` + +**Beziehungen:** +- N:1 → Profil +- Beeinflusst: KI-Analysen, Empfehlungen, Score-Berechnung + +**Datenquellen:** +- Automatische Erkennung (KI-basiert, Phase 0b+) +- Manuelle Definition (Goals Page, geplant) + +--- + +### 1.6 Fitness Tests + +**Fachliche Bedeutung:** Standardisierte Tests zur objektiven Leistungserfassung + +**Test-Typen:** +- **Cardio:** Cooper-Test (12 min Lauf), VO2 Max Test, Step Test +- **Kraft:** 1RM Squat, 1RM Bench Press, Pushups (max), Plank (Zeit) +- **Flexibilität:** Sit-and-Reach + +**Gespeicherte Daten:** +- Test-Typ, Datum, Ergebnis (Zeit/Distanz/Gewicht/Wiederholungen) +- Normwert-Klassifizierung (sehr gut, gut, durchschnittlich, unterdurchschnittlich) + +**Berechnete Werte:** +- VO2 Max (aus Cooper-Test) +- Relative Kraft (1RM / Körpergewicht) +- Vergleich zu Normwerten (alters-/geschlechtsspezifisch) + +**Beziehungen:** +- N:1 → Profil +- Kann verknüpft werden mit: Zielen (vo2max, strength) + +**Datenquellen:** +- Manuelle Eingabe (Fitness Tests Page, geplant) + +--- + +## 2. Körper-Tracking + +### 2.1 Gewicht + +**Fachliche Bedeutung:** Tägliche Gewichtsmessungen als Basis-Metrik + +**Gespeicherte Daten:** +- Datum, Gewicht (kg), Quelle (manual/apple_health/withings) + +**Berechnete Werte:** +- **BMI:** Gewicht / (Größe in m)² +- **Trends:** 7d/28d/90d Steigung (lineare Regression) +- **Prognose:** Extrapolation auf Ziel-Datum +- **Ziel-Fortschritt:** % zum Zielgewicht +- **Gewichtsänderung:** Δ 7d/28d/90d + +**Beziehungen:** +- N:1 → Profil +- Korreliert mit: Ernährung (Energie-Bilanz), Aktivität, Schlaf +- Verknüpft mit: Ziel-Typ "weight" + +**Datenquellen:** +- Manuelle Eingabe (Dashboard, Weight Page) +- CSV-Import (Apple Health, Withings) + +--- + +### 2.2 Umfänge + +**Fachliche Bedeutung:** 8 Körperumfangspunkte zur Tracking von Muskelaufbau/Fettabbau + +**Messpunkte:** +- Brust (c_chest), Bauch (c_waist), Hüfte (c_hip) +- Oberschenkel links/rechts (c_thigh_l/r) +- Wade links/rechts (c_calf_l/r) +- Oberarm (c_arm) + +**Gespeicherte Daten:** +- Datum, 8 Messpunkte (cm), Quelle (manual) + +**Berechnete Werte:** +- **Trends:** 7d/28d/90d pro Messpunkt +- **Delta:** Veränderung zu letzter Messung, zu Ausgangswert +- **Best-of-Each:** Beste Messung pro Punkt mit Altersangabe + +**Beziehungen:** +- N:1 → Profil +- Korreliert mit: Gewicht, Körperfett, Training (Hypertrophie) + +**Datenquellen:** +- Manuelle Eingabe (Circumference Page) + +--- + +### 2.3 Caliper (Hautfaltenmessung) + +**Fachliche Bedeutung:** Körperfettanteil via Hautfaltenmessung + +**7 Messpunkte:** +- Brust, Axilla, Trizeps, Subscapular, Abdominal, Suprailiac, Oberschenkel + +**Gespeicherte Daten:** +- Datum, 7 Hautfalten (mm), Methode (3/4/7-Site Jackson-Pollock) + +**Berechnete Werte:** +- **Körperfettanteil (BF%):** Alters-/geschlechtsspezifische Formeln +- **Magermasse (LBM):** Gewicht × (1 - BF%) +- **Fettmasse (FM):** Gewicht × BF% +- **Trends:** LBM/FM Änderungen über Zeit + +**Beziehungen:** +- N:1 → Profil +- Erfordert: Aktuelles Gewicht +- Verknüpft mit: Ziel-Typ "body_fat", "lean_mass" + +**Datenquellen:** +- Manuelle Eingabe (Caliper Page) + +--- + +### 2.4 Vitalwerte + +**Fachliche Bedeutung:** Gesundheits- und Erholungs-Indikatoren + +#### 2.4.1 Baseline Vitals (Morgenmessung) + +**Gespeicherte Daten:** +- Datum (1x täglich, morgens) +- Ruhepuls (bpm), HRV (ms), VO2 Max (ml/kg/min) +- SpO2 (%), Atemfrequenz (bpm) + +**Berechnete Werte:** +- **Baselines:** 7d/14d/30d Durchschnitte (RHR, HRV) +- **Abweichungen:** Aktuell vs. Baseline (%) +- **Recovery Score:** Basierend auf HRV/RHR/Schlaf +- **Trends:** 7d/28d Steigung + +**Beziehungen:** +- N:1 → Profil +- Korreliert mit: Schlaf, Aktivität (Load), Ruhetage +- Verknüpft mit: Ziel-Typ "rhr", Recovery Score + +**Datenquellen:** +- Manuelle Eingabe (Vitals Page - Baseline Tab) +- CSV-Import (Apple Health, Garmin) + +#### 2.4.2 Blutdruck (mehrfach täglich) + +**Gespeicherte Daten:** +- Datum + Uhrzeit (mehrfach möglich) +- Systolisch (mmHg), Diastolisch (mmHg), Puls (bpm) +- Context-Tag (nüchtern, nach Essen, Training, Stress, Medikation, Ruhe, Krank, Sonstiges) +- Unregelmäßiger Herzschlag (Flag), AFib-Warnung (Flag) + +**Berechnete Werte:** +- **WHO/ISH Klassifizierung:** + - Optimal (<120/80) + - Normal (120-129/80-84) + - Hochnormal (130-139/85-89) + - Hypertonie Grad 1-3 (≥140/90) +- **Trends:** 7d/14d/30d Durchschnitte (Systolisch, Diastolisch) + +**Beziehungen:** +- N:1 → Profil +- Korreliert mit: Stress, Aktivität, Medikation, Schlaf +- Verknüpft mit: Ziel-Typ "blood_pressure" + +**Datenquellen:** +- Manuelle Eingabe (Vitals Page - Blutdruck Tab) +- CSV-Import (Omron Deutsch, Apple Health) + +--- + +### 2.5 Progress-Fotos + +**Fachliche Bedeutung:** Visuelle Fortschritts-Dokumentation + +**Gespeicherte Daten:** +- Datum, Pose (front/side/back), Dateiname +- Caption (optional) + +**Berechnete Werte:** +- Zeitlicher Vergleich (Side-by-Side View) + +**Beziehungen:** +- N:1 → Profil +- Kann verknüpft werden mit: Gewicht, Umfängen (Kontext) + +**Datenquellen:** +- Foto-Upload (Photos Page) + +--- + +## 3. Training + +### 3.1 Aktivitäten + +**Fachliche Bedeutung:** Training-Sessions mit Typ, Dauer, Intensität + +**Gespeicherte Daten:** +- Datum, Trainingstyp, Dauer (min), Distanz (km) +- Ø Herzfrequenz, Max Herzfrequenz +- Kalorien (geschätzt oder gemessen) +- Notizen, Quelle (manual/apple_health/garmin) + +**Berechnete Werte:** +- **Training Volume:** Dauer × Intensität (aus HF-Zonen) +- **Quality Session:** Boolean (≥45min + HF in Zielzone) +- **ACWR (Acute:Chronic Workload Ratio):** 7d Load / 28d Load +- **Monotony:** Standardabweichung der Last (inversiert) +- **Strain:** Load × Monotony +- **Ability Balance:** Verteilung über 7 Fähigkeiten (Radar) + +**Beziehungen:** +- N:1 → Profil +- N:1 → Trainingstyp (via activity_type_mappings) +- Korreliert mit: Vitalwerte (RHR, HRV), Schlaf, Ruhetage + +**Datenquellen:** +- Manuelle Eingabe (Activity Page) +- CSV-Import (Apple Health Deutsch/English) +- Automatisches Mapping (lernendes System) + +--- + +### 3.2 Trainingstypen + +**Fachliche Bedeutung:** 29 kategorisierte Trainingstypen zur systematischen Erfassung + +**7 Kategorien:** +- **Kraft (5):** Krafttraining, Bodyweight, Crossfit, Functional, Powerlifting +- **Cardio (6):** Laufen, Radfahren, Schwimmen, Rudern, HIIT, Wandern +- **Kampfsport (6):** Boxen, Kickboxen, MMA, Karate, Judo, Jiu-Jitsu +- **Beweglichkeit (4):** Yoga, Stretching, Mobility, Pilates +- **Sport (4):** Fußball, Basketball, Tennis, Bouldern +- **Alltag (2):** Gehen, Tanzen +- **Geist (2):** Meditation, Achtsamkeit + +**Gespeicherte Daten:** +- Name, Kategorie, Farbe, Icon +- Fähigkeiten-Mapping (7 Dimensionen, JSONB) + +**7 Fähigkeiten-Dimensionen:** +- Maximalkraft, Kraftausdauer, Cardio, Flexibilität, Koordination, Schnelligkeit, Mentale Stärke + +**Berechnete Werte:** +- **Ability Balance:** Fähigkeiten-Verteilung über alle Trainings (28d) +- **Volume by Ability:** Trainingsvolumen pro Fähigkeit + +**Beziehungen:** +- 1:N → Aktivitäten +- N:M → Activity Type Mappings (lernendes System) + +**Datenquellen:** +- System-definiert (29 Basis-Typen) +- Admin-erweiterbar (Admin Panel) + +--- + +### 3.3 Activity Type Mappings (Lernendes System) + +**Fachliche Bedeutung:** Automatisches Mapping von Import-Daten zu Trainingstypen + +**Gespeicherte Daten:** +- Import-Name (z.B. "Traditionelles Krafttraining", "Traditional Strength Training") +- Training Type ID, Sprache +- User-spezifisch (optional) oder global +- Confidence Score + +**Lern-Mechanismus:** +- Bulk-Kategorisierung im Admin-Panel speichert Mappings +- Zukünftige Imports nutzen gelernte Mappings automatisch +- Coverage-Stats: % zugeordnet vs. unkategorisiert + +**Beziehungen:** +- N:1 → Training Type +- N:1 → Profil (bei user-spezifischen Mappings) + +**Datenquellen:** +- Bulk-Kategorisierung (Admin Panel) +- CSV-Import (lernt bei jeder Zuordnung) + +--- + +## 4. Lifestyle + +### 4.1 Ernährung + +**Fachliche Bedeutung:** Tägliche Makronährstoff- und Kalorien-Erfassung + +**Gespeicherte Daten:** +- Datum, Kalorien (kcal) +- Protein (g), Kohlenhydrate (g), Fett (g) +- Quelle (manual/myfitnesspal/yazio) + +**Berechnete Werte:** +- **Makro-Verteilung:** Protein/Carbs/Fat in % (28d Durchschnitt) +- **Protein-Adequacy:** Ist vs. Soll (1.6-2.2 g/kg LBM) +- **Energie-Bilanz:** Intake - (TDEE + Training kcal) +- **Konsistenz-Score:** Standardabweichung (inversiert) +- **Intake Volatility:** Schwankungen über Zeit + +**Beziehungen:** +- N:1 → Profil +- Korreliert mit: Gewicht (Energie-Bilanz), Training, Ziele + +**Datenquellen:** +- Manuelle Eingabe (Nutrition Page) +- CSV-Import (MyFitnessPal, Yazio) + +--- + +### 4.2 Schlaf + +**Fachliche Bedeutung:** Nächtliche Schlaferfassung mit Phasen-Details + +**Gespeicherte Daten:** +- Datum (Nacht vom X auf Y) +- Gesamtdauer (h), Qualität (1-10) +- Sleep Segments (JSONB): Start, Ende, Phase (deep/rem/light/awake) + +**Berechnete Werte:** +- **Schlaf-Statistiken:** + - Ø Dauer (7d/28d) + - Ø Qualität (Deep + REM %, 7d) + - Schlafschuld (kumulativ): Σ(7.5h - Ist-Dauer) +- **Sleep Score:** Basierend auf Dauer + Deep/REM % + +**Beziehungen:** +- N:1 → Profil +- Korreliert mit: Vitalwerte (RHR, HRV), Aktivität (Load), Recovery Score + +**Datenquellen:** +- Manuelle Eingabe (Sleep Page) +- CSV-Import (Apple Health Deutsch/English) + +--- + +### 4.3 Ruhetage + +**Fachliche Bedeutung:** Multi-dimensionale Regenerations-Tracking + +**3 Ruhe-Typen:** +- **Kraft-Ruhe:** Keine Kraft-/Muskeltraining +- **Cardio-Ruhe:** Keine intensive Ausdauer-Einheiten +- **Entspannung:** Aktive Erholung (Mobility, Spazieren) + +**Gespeicherte Daten:** +- Datum, Ruhe-Typ (kann mehrfach kombiniert werden) +- Notizen + +**Berechnete Werte:** +- **Rest Days Count:** Anzahl pro Typ (28d) +- **Recovery Ratio:** Rest Days / Training Days +- **Validierung:** Warnung bei geplanter Aktivität trotz Ruhetag + +**Beziehungen:** +- N:1 → Profil +- Kann kollidieren mit: Aktivitäten (Validierung) + +**Datenquellen:** +- Manuelle Eingabe (Rest Days Page - Quick Mode/Custom) + +--- + +### 4.4 Custom Goals (Eigene Ziele) + +**Fachliche Bedeutung:** Tägliche Werterfassung für individuell definierte Ziele + +**Gespeicherte Daten:** +- Datum, Goal ID, Wert (Zahl oder Text) +- Notizen + +**Berechnete Werte:** +- **Fortschritt:** Automatisch aus Einträgen berechnet +- **Konsistenz:** % Tage mit Eintrag +- **Verlauf:** Letzte 5 Einträge + +**Beziehungen:** +- N:1 → Goal (source_table = NULL, custom goal) +- N:1 → Profil + +**Datenquellen:** +- Manuelle Eingabe (Custom Goals Page - Capture Hub) + +--- + +## 5. Auswertungen & KI + +### 5.1 Charts & Visualisierungen + +**Fachliche Bedeutung:** 20+ vorgefertigte Auswertungs-Charts + +**Chart-Kategorien:** + +#### Ernährung (E1-E5, 4 Charts) +- **E1: Energiebilanz:** Kalorien-Timeline vs. TDEE, Bilanz als Balken +- **E2: Makro-Verteilung:** Protein/Carbs/Fat (Pie Chart) +- **E3: Protein-Adequacy:** Protein vs. Ziel-Range (Timeline) +- **E4: Ernährungs-Konsistenz:** Konsistenz-Score (Bar) + +#### Aktivität (A1-A8, 7 Charts) +- **A1: Trainingsvolumen:** Wöchentliches Volumen (Bar) +- **A2: Trainingstyp-Verteilung:** Typen (Pie) +- **A3: Quality Sessions:** Rate qualitativ hochwertiger Trainings (Bar) +- **A4: Load Monitoring:** Acute/Chronic Load + ACWR (Line) +- **A5: Monotony & Strain:** Monotonie + Strain (Bar) +- **A6: Ability Balance:** Fähigkeiten-Verteilung (Radar) +- **A7: Volume by Ability:** Volumen pro Fähigkeit (Bar) + +#### Erholung (R1-R5, 5 Charts) +- **R1: Recovery Score:** Recovery-Timeline (Line) +- **R2: HRV/RHR Baseline:** HRV & RHR vs. Baseline (Multi-Line) +- **R3: Schlaf Dauer + Qualität:** Dauer + Deep/REM % (Multi-Line) +- **R4: Schlafschuld:** Kumulative Schuld (Line) +- **R5: Vital Signs Matrix:** Aktuelle Vitalwerte (Bar) + +#### Korrelationen (C1-C4, 4 Charts) +- **C1: Gewicht ↔ Energie:** Scatter mit Lag-Korrelation +- **C2: Magermasse ↔ Protein:** Scatter mit Lag-Korrelation +- **C3: Load ↔ Vitalwerte:** Training Load vs. HRV/RHR (Scatter) +- **C4: Recovery ↔ Performance:** Top Treiber (Bar) + +**Berechnete Werte:** +- Alle Chart-Daten kommen aus **Data Layer** (Single Source of Truth) +- Confidence Scores (Datenqualität) +- Metadata (Durchschnitte, Korrelationen, etc.) + +**Beziehungen:** +- Konsumieren: Alle Tracking-Objekte +- Nutzen: Data Layer Funktionen (97 Funktionen in 6 Modulen) + +**Datenquellen:** +- Chart Endpoints (20 neue APIs) +- Data Layer: body_metrics, nutrition_metrics, activity_metrics, recovery_metrics, scores, correlations + +--- + +### 5.2 Data Layer (Phase 0c) + +**Fachliche Bedeutung:** Zentrale Berechnungs-Schicht (Single Source of Truth) + +**6 Module, 97 Funktionen:** + +#### body_metrics.py (20 Funktionen) +- Weight trends, goal projections +- Body composition: FM/LBM changes, recomposition quadrants +- Circumferences: Delta-Berechnungen, Fortschritts-Scores + +#### nutrition_metrics.py (16 Funktionen) +- Energy balance, protein adequacy, macro consistency +- Intake volatility, nutrition scoring + +#### activity_metrics.py (20 Funktionen) +- Training volume, quality sessions, load monitoring +- Monotony/Strain scores, ability balance + +#### recovery_metrics.py (16 Funktionen) +- Sleep metrics, HRV/RHR baselines, recovery scoring + +#### scores.py (14 Funktionen) +- Focus weights, goal progress, category scores + +#### correlations.py (11 Funktionen) +- Lag correlations, plateau detection, top drivers + +**Architektur-Prinzip:** +- Alle Berechnungen in Data Layer (keine Duplikation) +- Nutzbar für: Chart Endpoints + KI-Platzhalter +- Flexible Zeitfenster (7-365 Tage) +- Confidence System (Datenqualität-Prüfung) + +**Beziehungen:** +- Konsumieren: Alle Tracking-Tabellen +- Genutzt von: Chart Endpoints, KI-Platzhalter, Frontend-Statistiken + +--- + +### 5.3 KI-Analysen + +**Fachliche Bedeutung:** Multi-Stage Pipeline-basierte KI-Auswertungen + +**Unified Prompt System:** +- **Basis-Prompts:** Wiederverwendbare Templates (z.B. "Body Analysis") +- **Pipeline-Prompts:** Multi-Stage Workflows (z.B. "Complete Insight") +- **Stages:** Unbegrenzte Anzahl, parallele Ausführung möglich +- **Platzhalter:** 32+ dynamische Platzhalter ({{weight_current}}, {{goal_weight}}, etc.) + +**Gespeicherte Daten:** +- Prompt-Typ (base/pipeline), Stages-Config (JSONB) +- Output-Format (text/json), Output-Schema (optional) +- AI-Response, Metadata (genutzte Platzhalter mit Werten) + +**Berechnete Werte:** +- **Stage-Outputs:** JSON-Extraktion aus Basis-Prompts +- **Cross-Stage References:** {{stage_N_key}} Platzhalter +- **Expert Mode:** Vollständige Werte-Tabelle (nicht gekürzt) + +**Beziehungen:** +- N:1 → Profil +- Nutzt: Data Layer, alle Tracking-Objekte +- Konsumiert: Platzhalter-Resolver (32 Funktionen) + +**Datenquellen:** +- KI-Prompt-Editor (Admin Panel) +- Automatische Ausführung (Analysis Page) +- Test-Modus mit Debug-Info + +--- + +### 5.4 Korrelationen & Scores + +**Fachliche Bedeutung:** Automatische Muster-Erkennung und Fortschritts-Scores + +**Korrelations-Typen:** +- **Lag-Korrelationen:** Energie-Bilanz → Gewichtsänderung (3d/7d/14d) +- **Activity ↔ Recovery:** Training Load → HRV/RHR +- **Nutrition ↔ Body Composition:** Protein → Magermasse +- **Sleep ↔ Performance:** Schlafqualität → Trainingsleistung + +**Score-Typen:** +- **Body Composition Score:** Fortschritt zu Körper-Zielen +- **Nutrition Score:** Makro-Adherence + Konsistenz +- **Activity Score:** Volumen + Qualität + Balance +- **Recovery Score:** Schlaf + HRV/RHR + Rest Days +- **Focus Area Scores:** Gewichtete Ziel-Fortschritte + +**Berechnete Werte:** +- **Pearson Correlation:** r-Wert + p-value +- **Plateau Detection:** Stagnation-Erkennung (21d ohne Fortschritt) +- **Top Drivers:** Rangliste der Einflussfaktoren +- **Confidence Scores:** Datenqualität (Anzahl Data Points) + +**Beziehungen:** +- Konsumieren: Alle Tracking-Objekte +- Nutzen: Data Layer (correlations.py, scores.py) + +**Datenquellen:** +- Automatische Berechnung (täglich/wöchentlich) +- Abrufbar via Chart Endpoints + +--- + +## 6. Membership & Access Control + +### 6.1 Membership Tiers + +**Fachliche Bedeutung:** Stufen-basiertes Feature-Access-System + +**Tiers:** +- **Free:** Basis-Features (Gewicht, Ernährung, 10 KI-Calls/Tag) +- **Premium:** Alle Features, unbegrenzte KI-Calls +- **Trial:** 14-Tage Premium-Test + +**Gespeicherte Daten:** +- Tier, Trial-Start/-Ende +- Stripe Customer ID, Subscription ID (für spätere Payment-Integration) + +**Berechnete Werte:** +- Trial-Status (aktiv/abgelaufen, Tage verbleibend) +- Feature-Zugriff (basierend auf Tier + Access Grants) + +**Beziehungen:** +- 1:1 → Profil +- 1:N → Access Grants (individuelle Freischaltungen) +- Steuert: Feature Usage Tracking + +--- + +### 6.2 Features & Limits + +**11 Feature-Kategorien:** +- weight_entries, circumference_entries, caliper_entries +- activity_entries, nutrition_entries, photos +- ai_calls, ai_pipeline, data_export, data_import +- advanced_charts (geplant) + +**Tier Limits:** +- Free: 1000 Einträge/Monat, 10 KI-Calls/Tag, 50 Fotos +- Premium: Unbegrenzt + +**Feature Usage Tracking:** +- Tägliche/Monatliche Zähler pro Feature +- JSON-Logs (feature-usage.log) +- Reset: Täglich (ai_calls) oder monatlich (Einträge) + +**Beziehungen:** +- N:1 → Profil +- Enforcement: HTTP 403 bei Limit-Überschreitung + +--- + +### 6.3 Access Grants + +**Fachliche Bedeutung:** Individuelle Feature-Freischaltungen (außerhalb des Tiers) + +**Gespeicherte Daten:** +- Feature-Name, Granted-By (Admin-Profil) +- Grant-Date, Expiry-Date (optional) +- Grund/Notizen + +**Anwendungsfälle:** +- Beta-Tester (früher Zugriff auf neue Features) +- Kompensation (nach Downtime) +- Partner/Freunde (kostenlose Premium-Features) + +**Beziehungen:** +- N:1 → Profil +- Überschreibt: Tier-Limits für spezifisches Feature + +--- + +## 7. Datenfluss & Anreicherung + +``` +┌────────────────────────────────────────────────────────────────┐ +│ DATENQUELLEN │ +├────────────────────────────────────────────────────────────────┤ +│ Manuelle Eingabe │ CSV-Import │ API-Sync (geplant) │ +└────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌────────────────────────────────────────────────────────────────┐ +│ SPEICHERUNG (PostgreSQL) │ +├────────────────────────────────────────────────────────────────┤ +│ Raw Data: weight_log, activity_log, nutrition_log, etc. │ +│ Source-Tracking: manual/import (Reimport-Schutz) │ +│ Profile-Isolation: profile_id auf allen Tabellen │ +└────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌────────────────────────────────────────────────────────────────┐ +│ DATA LAYER (Berechnungen) │ +├────────────────────────────────────────────────────────────────┤ +│ 97 Funktionen in 6 Modulen: │ +│ • body_metrics: Trends, Projections, Body Composition │ +│ • nutrition_metrics: Energy Balance, Macro Consistency │ +│ • activity_metrics: Volume, Load, Quality, Abilities │ +│ • recovery_metrics: Sleep, HRV/RHR, Recovery Score │ +│ • scores: Focus Scores, Goal Progress, Category Scores │ +│ • correlations: Lag-Korrelationen, Plateau Detection │ +└────────────────────────────────────────────────────────────────┘ + │ + ┌─────────────────────┼─────────────────────┐ + ▼ ▼ ▼ +┌──────────────┐ ┌──────────────┐ ┌──────────────┐ +│ CHARTS │ │ KI-ANALYSEN │ │ FRONTEND │ +├──────────────┤ ├──────────────┤ ├──────────────┤ +│ 20 Endpoints │ │ 32 Platzh. │ │ Statistiken │ +│ Chart.js │ │ Multi-Stage │ │ Dashboards │ +│ Format │ │ Pipelines │ │ Progress │ +└──────────────┘ └──────────────┘ └──────────────┘ +``` + +--- + +## 8. Daten-Qualität & Confidence + +### Confidence Scoring + +**Fachliche Bedeutung:** Automatische Bewertung der Datenqualität + +**Kriterien:** +- **Anzahl Datenpunkte:** Mehr Daten = höhere Confidence +- **Zeitspanne:** Längere Historie = zuverlässigere Trends +- **Konsistenz:** Regelmäßige Erfassung (keine Lücken) +- **Vollständigkeit:** Alle relevanten Felder ausgefüllt + +**Confidence Levels:** +``` +0-29 Tage: LOW (zu wenig Daten) +30-89 Tage: MEDIUM (ausreichend) +90+ Tage: HIGH (zuverlässig) +``` + +**Anwendungen:** +- Chart Metadata (Datenqualität anzeigen) +- KI-Analysen (Confidence-basierte Empfehlungen) +- Automatische Warnungen (bei zu wenig Daten) + +**Beziehungen:** +- Berechnet von: Data Layer Funktionen +- Genutzt von: Chart Endpoints, KI-Platzhalter + +--- + +## 9. Import & Export + +### 9.1 CSV-Import + +**Unterstützte Formate:** +- **Gewicht:** Apple Health (Deutsch/Englisch), Withings +- **Ernährung:** MyFitnessPal, Yazio +- **Aktivität:** Apple Health (Deutsch/Englisch) +- **Schlaf:** Apple Health (Deutsch/Englisch) +- **Vitalwerte:** Apple Health (Deutsch/Englisch), Omron (Deutsch) + +**Import-Logik:** +- **Duplikat-Erkennung:** Nach (profile_id, date) oder (profile_id, date, start_time) +- **Reimport-Schutz:** Manuelle Einträge (source='manual') NIEMALS überschreiben +- **Auto-Mapping:** Activity Types via lernendes Mapping-System +- **Error-Handling:** Erste 10 Fehler im Frontend angezeigt + +**Beziehungen:** +- Nutzt: activity_type_mappings (lernendes System) +- Speichert: source-Feld für Reimport-Tracking + +--- + +### 9.2 Export + +**Formate:** +- **CSV:** Pro Datentyp (weight.csv, activity.csv, etc.) +- **JSON:** Vollständiger Export (alle Daten, strukturiert) +- **ZIP:** Kombinierter Export (alle CSVs + JSON + Metadaten) + +**Umfang:** +- Alle Tracking-Daten (Gewicht, Ernährung, Aktivität, Schlaf, Vitalwerte) +- Ziele, Focus Areas, Training Phases +- KI-Analysen (Insights) +- Membership-Daten (für Backup) + +**Beziehungen:** +- Nutzt: Alle Tabellen (außer Auth-sensible Daten) +- Feature-Limit: 5 Exports/Monat (Free), unbegrenzt (Premium) + +--- + +## 10. Fachliche Regeln & Constraints + +### 10.1 Datenvalidierung + +**Gewicht:** +- 30 kg ≤ Gewicht ≤ 300 kg +- Max 1 Eintrag pro Tag (Upsert bei Duplikat) + +**Körperfett:** +- 3% ≤ BF% ≤ 60% +- Plausibilitäts-Check: LBM nicht höher als Gewicht + +**Vitalwerte:** +- Ruhepuls: 30-200 bpm +- HRV: 10-300 ms +- Blutdruck: Systolisch 60-260 mmHg, Diastolisch 40-150 mmHg + +**Ernährung:** +- Kalorien: 500-10000 kcal +- Protein/Carbs/Fat: 0-1000 g + +**Aktivität:** +- Dauer: 1-1440 min (24h) +- Herzfrequenz: 40-220 bpm + +--- + +### 10.2 Abhängigkeiten & Voraussetzungen + +**Caliper → Gewicht:** +- Körperfettberechnung erfordert aktuelles Gewicht (±3 Tage) +- Wenn kein Gewicht: Warnung im Frontend + +**Ziele → Datenquellen:** +- Ziel-Typ "weight" erfordert weight_log Einträge +- Ziel-Typ "vo2max" erfordert vitals_baseline Einträge +- Custom Goals: Keine Datenquelle (manuelle Erfassung) + +**KI-Analysen → Mindestdaten:** +- Mindestens 7 Tage Daten für Trends +- Mindestens 30 Tage für Korrelationen +- Confidence-Warnung bei <30 Tagen + +--- + +### 10.3 Zeitliche Constraints + +**Reimport-Schutz:** +- Manuelle Einträge (source='manual') haben IMMER Vorrang +- Imports überschreiben nur source != 'manual' + +**Automatische Bereinigung:** +- Sessions: Ablauf nach 7 Tagen (rolling refresh) +- Feature Usage: Reset täglich (KI) oder monatlich (Einträge) + +**Historische Daten:** +- Keine Löschung (nur Soft-Delete geplant) +- Unbegrenzte Speicherung (außer bei GDPR-Request) + +--- + +## 11. Erweiterbarkeit + +### Admin-erweiterbare Elemente + +**Training Types:** +- Admin kann neue Typen hinzufügen (Name, Kategorie, Icon, Fähigkeiten) +- Automatisch verfügbar für alle Nutzer + +**Focus Areas:** +- Admin kann neue Bereiche definieren (Name, Beschreibung, Kategorie) +- User können eigene Gewichtung setzen + +**KI-Prompts:** +- Admin kann neue Prompts/Pipelines erstellen +- Unbegrenzte Stages möglich +- Basis-Prompts wiederverwendbar + +**Activity Mappings:** +- System lernt automatisch bei Bulk-Kategorisierung +- Admin kann Mappings inline editieren + +### User-erweiterbare Elemente + +**Custom Goals:** +- User kann beliebige Ziele definieren (Name, Typ, Einheit) +- Tägliche Werterfassung (Capture Page) + +**Focus Area Weights:** +- User kann eigene Prioritäten setzen (Schieberegler) +- Dynamische Anpassung (jederzeit änderbar) + +**Training Phases:** +- User kann manuelle Phasen definieren (geplant) +- Akzeptieren/Ablehnen von KI-Vorschlägen + +--- + +## Zusammenfassung: Daten-Architektur im Überblick + +**Datenobjekte (gespeichert):** +- 1 Profil → N (Gewicht, Umfänge, Caliper, Vitalwerte, Fotos) +- 1 Profil → N (Aktivitäten, Ernährung, Schlaf, Ruhetage) +- 1 Profil → 1 Goal Mode → N Ziele → N Custom Goal Entries +- 1 Profil → N Focus Area Weights → N Goal Focus Contributions +- 1 Profil → N Training Phases, Fitness Tests, KI-Analysen + +**Berechnete Werte (Data Layer):** +- 97 Funktionen in 6 Modulen +- Body, Nutrition, Activity, Recovery, Scores, Correlations +- Single Source of Truth für Charts + KI + +**Visualisierungen:** +- 20+ Chart Endpoints (E1-E5, A1-A8, R1-R5, C1-C4) +- Chart.js Format, Confidence Scores, Metadata + +**KI-Integration:** +- 32+ Platzhalter, Multi-Stage Pipelines, JSON-Output +- Expert Mode (vollständige Werte-Tabelle) + +**Access Control:** +- Membership Tiers (Free, Premium, Trial) +- Feature Limits, Usage Tracking, Access Grants + +**Import/Export:** +- CSV-Import (Apple Health, Garmin, Omron, etc.) +- Lernendes Mapping-System (Activity Types) +- CSV/JSON/ZIP Export + +--- + +**Stand:** Phase 0c Complete (Multi-Layer Architecture) +**Nächste Phase:** Phase 0d (Frontend Chart Integration) +**Version:** v0.9h → v0.9i (geplant) diff --git a/.claude/docs/functional/DEVELOPMENT_ROUTES.md b/.claude/docs/functional/DEVELOPMENT_ROUTES.md new file mode 100644 index 0000000..3d92efa --- /dev/null +++ b/.claude/docs/functional/DEVELOPMENT_ROUTES.md @@ -0,0 +1,417 @@ +# Entwicklungsrouten – Multi-Dimensionales Training & Planung + +**Version:** 1.0 +**Status:** Konzept +**Ziel-Release:** v9e / v9f +**Basis:** Rest Days Multi-Dimensional Architecture (v9d Phase 2a) + +--- + +## Überblick + +**Entwicklungsrouten** sind unabhängige Dimensionen der körperlichen und mentalen Entwicklung, die jeweils eigene: +- Trainingspläne +- Ruhetag-Regeln +- Fortschritts-Tracking +- Ziele + +haben. + +**Kernidee:** Ein Tag kann gleichzeitig ein "Kraft-Ruhetag" UND ein "Mental-Aktivtag" sein. + +--- + +## Die 6 Entwicklungsrouten + +### 1. 💪 Kraft (Strength) +**Fokus:** Muskelaufbau, Maximalkraft, Power + +**Trainingstypen:** +- Strength (Krafttraining allgemein) +- Power (Schnellkraft, Explosivität) +- HIIT (Hochintensiv) + +**Ruhetag-Regeln:** +- Nach schwerer Beineinheit → 48-72h Kraft-Ruhetag +- Andere Routen (Cardio, Mental) bleiben erlaubt +- Intensität: Max 60% bei erlaubten Aktivitäten + +**Metriken:** +- Gewicht, Umfänge, Caliper +- 1RM-Schätzung (optional) +- Muskelregeneration (Ruhepuls + HRV) + +--- + +### 2. 🏃 Kondition (Conditioning) +**Fokus:** Ausdauer, VO2Max, Herz-Kreislauf + +**Trainingstypen:** +- Cardio Low (GA1, Grundlagenausdauer) +- Cardio High (GA2, Tempo-Läufe) +- Endurance (Lange Einheiten) + +**Ruhetag-Regeln:** +- Nach Wettkampf/Tempo-Lauf → 24-48h Cardio-Ruhetag +- Kraft & Mobility erlaubt +- Intensität: Max 70% HFmax + +**Metriken:** +- HF-Zonen-Verteilung +- VO2Max-Schätzung +- Lauf-Pace / Rad-Watt + +--- + +### 3. 🧘 Mental (Mental) +**Fokus:** Stressmanagement, Fokus, Wettkampf-Readiness + +**Trainingstypen:** +- Meditation +- Wettkampf (Competition) +- High-Pressure Training + +**Ruhetag-Regeln:** +- Nach Wettkampf → 1-3 Tage Mental-Ruhetag +- Physisches Training erlaubt (aber kein Wettkampf-Druck) +- Nur entspannende Aktivitäten (Meditation, Spaziergang) + +**Metriken:** +- Stresslevel (1-5 subjektiv) +- Schlafqualität +- HRV (Mental-Recovery-Indikator) + +--- + +### 4. 🤸 Koordination (Coordination) +**Fokus:** Balance, Agilität, Reaktion, propriozeptives Training + +**Trainingstypen:** +- Coordination (Agilität, Balance) +- Sport-spezifische Drills +- Reaktionstraining + +**Ruhetag-Regeln:** +- Nach intensivem Koordinationstraining → 24h Pause +- Andere Routen erlaubt +- Kein koordinativ anspruchsvolles Training bei Müdigkeit + +**Metriken:** +- Subjektive Koordinations-Scores +- Balance-Tests (optional) + +--- + +### 5. 🧘‍♂️ Mobilität (Mobility) +**Fokus:** Flexibilität, ROM (Range of Motion), Faszien + +**Trainingstypen:** +- Mobility (Stretching, Yoga) +- Faszien-Training +- Passive Dehnung + +**Ruhetag-Regeln:** +- Normalerweise kein Ruhetag nötig +- Bei Überdehnung / Verletzung → Pause für betroffene Bereiche + +**Metriken:** +- ROM-Messungen (optional) +- Subjektive Beweglichkeit (1-5) + +--- + +### 6. 🎯 Technik (Technique) +**Fokus:** Sport-spezifische Skills, Bewegungsqualität + +**Trainingstypen:** +- Technique Drills +- Skill Work +- Video-Analyse + +**Ruhetag-Regeln:** +- Technik-Training bei Müdigkeit kontraproduktiv +- Mental-Ruhetag → auch Technik pausieren +- Physisches Training erlaubt (ohne Technik-Fokus) + +**Metriken:** +- Video-Analyse-Scores (optional) +- Coach-Feedback + +--- + +## Datenmodell + +### rest_days (Multi-Entry per Date) + +**Änderung:** UNIQUE constraint `(profile_id, date)` entfernt (Migration 011) + +**Erlaubt:** +```sql +-- Gleiches Datum, verschiedene Routen: +INSERT INTO rest_days (profile_id, date, rest_config, note) +VALUES + ('user-1', '2026-03-23', '{"focus": "muscle_recovery", ...}', 'Beine heavy'), + ('user-1', '2026-03-23', '{"focus": "mental_rest", ...}', 'Wettkampf-Pause'); +``` + +**UI:** +``` +23. März 2026 + 💪 Muskelregeneration | "Beine heavy" + 🧘 Mental Rest | "Wettkampf-Pause" + + → Cardio erlaubt, Kraft pausiert, keine Wettkämpfe +``` + +--- + +## Wochenplanung mit Routen + +### Tabelle: weekly_goals (erweitert) + +**JSONB Schema:** +```json +{ + "routes": { + "strength": { + "goal": 3, // 3x Kraft pro Woche + "min_rest_days": 1, // Mind. 1 Kraft-Ruhetag + "intensity_distribution": { + "heavy": 1, // 1x schwer (80-90%) + "medium": 1, // 1x mittel (70-80%) + "light": 1 // 1x leicht (60-70%) + } + }, + "conditioning": { + "goal": 2, + "min_rest_days": 0, + "hr_zones": { + "zone_2": 60, // 60% der Zeit in Zone 2 + "zone_3": 30, // 30% in Zone 3 + "zone_4": 10 // 10% in Zone 4 + } + }, + "mental": { + "goal": 7, // Täglich Meditation + "min_duration": 10, // Mind. 10 Min + "rest_after_competition": 2 // 2 Tage Pause nach Wettkampf + } + }, + "rules": { + "max_consecutive_strength": 2, // Max 2 Krafteinheiten hintereinander + "require_rest_after": ["competition", "strength_heavy"], + "auto_rest_on_poor_recovery": true // Bei HRV < Baseline → Auto-Ruhetag + } +} +``` + +--- + +## Activity Validation mit Routen + +### Endpoint: `POST /api/rest-days/validate-activity` + +**Request:** +```json +{ + "date": "2026-03-23", + "activity_type": "strength", + "route": "strength" // Neue Dimension +} +``` + +**Response:** +```json +{ + "conflicts": [ + { + "route": "strength", + "severity": "warning", + "message": "Kraft-Ruhetag – Muskelregeneration nach Beineinheit. Trotzdem trainieren?" + } + ], + "allowed": false +} +``` + +**Logik:** +```python +def validate_activity_multi_route(profile_id, date, activity_type, route=None): + """ + Prüft alle rest_days für das Datum. + + - Wenn route angegeben: Nur diese Route prüfen + - Sonst: Alle passenden Routen prüfen + """ + rest_days = get_rest_days(profile_id, date) + conflicts = [] + + for rest_day in rest_days: + config = rest_day['rest_config'] + + # Prüfe ob Activity in rest_from + if activity_type in config.get('rest_from', []): + conflicts.append({ + 'route': config['focus'], + 'severity': 'warning', + 'message': f"{ROUTE_LABELS[config['focus']]} Ruhetag – {activity_type} sollte pausiert werden." + }) + + return { + 'conflicts': conflicts, + 'allowed': len(conflicts) == 0 + } +``` + +--- + +## Habits & Streaks pro Route + +### Tabelle: route_habits (neu in v9f) + +```sql +CREATE TABLE route_habits ( + id SERIAL PRIMARY KEY, + profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + route VARCHAR(20) NOT NULL, -- 'strength', 'conditioning', 'mental', etc. + habit_type VARCHAR(50) NOT NULL, -- 'meditation', 'mobility', 'strength_training' + frequency VARCHAR(20) NOT NULL, -- 'daily', 'weekly_3', 'weekly_5' + streak_current INTEGER DEFAULT 0, + streak_longest INTEGER DEFAULT 0, + last_completed DATE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); +``` + +**Beispiele:** +- Mental-Route: "Tägliche Meditation" (7x/Woche) +- Mobility-Route: "Stretching" (3x/Woche) +- Strength-Route: "Krafttraining" (3x/Woche) + +**UI:** +``` +🧘 Mental-Route + Meditation 🔥 12 Tage Streak + Stress-Check 🔥 5 Tage Streak + +💪 Kraft-Route + Krafttraining 🔥 2 Wochen Streak + Protein > 150g 🔥 10 Tage Streak +``` + +--- + +## Dashboard Integration + +### Route-Übersicht Widget + +``` +Entwicklungsrouten (diese Woche) +───────────────────────────────────── +💪 Kraft 2/3 [██░] 1 Ruhetag +🏃 Kondition 1/2 [█░░] 0 Ruhetage +🧘 Mental 7/7 [███] ✓ Täglich +🤸 Koordination 0/1 [░░░] Geplant: Do +🧘‍♂️ Mobilität 3/3 [███] ✓ Ziel erreicht + +Empfehlung: Kraft-Training heute, Cardio morgen +``` + +--- + +## KI-Integration + +### Neue Platzhalter (v9f) + +```python +{{route_balance}} # "Kraft überlastet, Cardio unterfordert" +{{route_progress_strength}} # "3/3 Einheiten, +2kg seit letztem Monat" +{{route_recovery_strength}} # "Gut erholt, schweres Training möglich" +{{route_next_recommended}} # "Kraft-Oberkörper empfohlen (Push-Fokus)" +{{route_conflict_today}} # "Kraft-Ruhetag, Cardio/Mental erlaubt" +``` + +### KI-Analyse-Funktion + +```python +def analyze_route_balance(profile_id, weeks=4): + """ + Analysiert Balance über alle Routen. + + Warnt bei: + - Übertraining in einer Route + - Vernachlässigung einer Route + - Fehlende Ruhetage in Route + """ + routes = ['strength', 'conditioning', 'mental', 'coordination', 'mobility'] + analysis = {} + + for route in routes: + activities = get_activities_by_route(profile_id, route, weeks) + rest_days = get_rest_days_by_route(profile_id, route, weeks) + + analysis[route] = { + 'activity_count': len(activities), + 'rest_days_count': len(rest_days), + 'intensity_avg': calculate_intensity_avg(activities), + 'status': calculate_route_status(activities, rest_days) # 'balanced', 'overload', 'neglected' + } + + return analysis +``` + +--- + +## Implementierungs-Phasen + +### Phase 1: Foundation (v9d Phase 2a) ✅ +- ✅ Migration 011: UNIQUE constraint entfernen +- ✅ Multiple rest_days per date erlauben +- ✅ UI: Mehrere Kacheln pro Tag anzeigen + +### Phase 2: Route-Konzept (v9e) +- 🔲 Routen-Taxonomie festlegen (6 Routen) +- 🔲 Activity-Types den Routen zuordnen +- 🔲 `training_types` Tabelle um `route` Spalte erweitern +- 🔲 Validation: Multi-Route-Konflikt-Check + +### Phase 3: Wochenplanung (v9f) +- 🔲 `weekly_goals` mit Routen-JSONB erweitern +- 🔲 Planungs-UI: Routen-basierte Wochenansicht +- 🔲 Regeln-Engine: Auto-Ruhetag bei Poor Recovery +- 🔲 Empfehlungs-System: "Heute: Kraft-Oberkörper empfohlen" + +### Phase 4: Habits & Streaks (v9g) +- 🔲 `route_habits` Tabelle +- 🔲 Streak-Tracking pro Route +- 🔲 Dashboard-Integration +- 🔲 Push-Notifications bei Streak-Gefahr + +### Phase 5: KI-Integration (v9f/g) +- 🔲 Route-Balance-Analyse +- 🔲 Übertraining-Warnung pro Route +- 🔲 Neue KI-Platzhalter +- 🔲 Route-spezifische Empfehlungen + +--- + +## Offene Fragen + +1. **Route-Hierarchie:** Gibt es Abhängigkeiten? (z.B. Mental-Rest → auch Technik-Rest?) +2. **Auto-Zuordnung:** Sollen Activities automatisch Routen zugeordnet werden? (Strength → Kraft-Route) +3. **Konflikt-Priorität:** Was passiert bei widersprüchlichen Ruhetagen? (Kraft erlaubt, Mental verbietet → ?) +4. **Routen-Gewichtung:** Sind manche Routen wichtiger als andere? (User-konfigurierbar?) + +--- + +## Referenzen + +- **Migration:** `backend/migrations/011_allow_multiple_rest_days_per_date.sql` +- **Technical Spec:** `.claude/docs/technical/V9D_PHASE2_VITALS_SLEEP.md` (zu aktualisieren) +- **Routen-Mapping:** `.claude/docs/functional/TRAINING_TYPES.md` (zu erweitern) + +--- + +**Dokumentiert:** 2026-03-22 +**Autor:** User Konzept + Claude Implementation +**Status:** Living Document (wird mit v9e/f erweitert) diff --git a/.claude/docs/functional/GOALS_VITALS.md b/.claude/docs/functional/GOALS_VITALS.md new file mode 100644 index 0000000..8786cb3 --- /dev/null +++ b/.claude/docs/functional/GOALS_VITALS.md @@ -0,0 +1,329 @@ +# Fachliche Anforderungen: Ziele, Vitalwerte & Aktive Tests +**Modul:** v9e +**Status:** Fachlich freigegeben, technische Implementierung ausstehend +**Letzte Aktualisierung:** März 2026 + +--- + +## 1. Überblick + +Dieses Modul erweitert Mitai Jinkendo um drei eng verbundene Bereiche: + +1. **Ziele** – Nutzer definiert konkrete Zielwerte (Gewicht, KF, Kondition etc.) +2. **Trainingsphasen** – Automatische Erkennung der aktuellen Phase aus Daten +3. **Aktive Tests** – Standardisierte Fitness-Tests zur Messung nicht-trackbarer Werte +4. **Vitalwerte** – Blutdruck, SpO2, HRV, Temperatur, Blutzucker + +Die KI verbindet alle vier Bereiche: Sie schlägt Zielwerte vor, erkennt Phasen, +empfiehlt Tests und passt Analysen an die aktuelle Phase an. + +--- + +## 2. Ziele + +### 2.1 Unterstützte Zieltypen + +| Zieltyp | Felder | Einheit | +|---------|--------|---------| +| Gewichtsziel | Zielgewicht + Zieldatum | kg, Datum | +| Körperfett-Ziel | Ziel-KF% | % | +| Magermasse-Ziel | Ziel-Magermasse | kg | +| Konditionsziel | Ziel-VO2Max | ml/kg/min | +| Kraftziel | Übungsname + Zielwiederholungen/-gewicht | Wdh. oder kg | +| Flexibilitätsziel | Ziel-Sit&Reach | cm | +| Blutdruck-Zielbereich | Systolisch / Diastolisch | mmHg | +| Ruhepuls-Ziel | Ziel-Ruhepuls | bpm | + +### 2.2 Ziel-Verwaltung + +**Aktives Ziel:** +- **Unbegrenzt viele Ziele** können gleichzeitig aktiv sein +- Ein Ziel kann als **Primärziel** markiert werden – dieses bestimmt + Dashboard-Fokus und KI-Analyse-Ausrichtung +- Alle anderen aktiven Ziele sind gleichwertige Nebenziele + +**Ziel-Lebenszyklus:** +``` +Entwurf → Aktiv → Erreicht / Aufgegeben / Abgelaufen +``` + +**Ziel-Geschichte:** +- Alle vergangenen Ziele bleiben gespeichert +- Fortschrittsverlauf je Ziel einsehbar + +### 2.3 Ziel-Anzeige + +**Dashboard-Widget (kompakt):** +- Zeigt alle aktiven Ziele als kompakte Karten +- Je Ziel: Name + Fortschrittsbalken (aktuell vs. Zielwert in %) +- Countdown bis Zieldatum (z.B. "noch 42 Tage") +- Prognose: "auf Kurs" / "X Tage früher" / "X Tage Verzögerung" +- Klick öffnet die Ziele-Seite + +**Eigene Ziele-Seite (Navigation):** +- Vollständige Übersicht aller aktiven + vergangenen Ziele +- Je Ziel: Detailansicht mit Verlauf, Trend, Prognose +- Aktionen: Ziel bearbeiten / pausieren / als erreicht markieren / löschen +- "+ Neues Ziel" Button + +**Fortschrittsbalken:** +``` +Gewichtsziel: 86,1 kg → 82 kg +████████░░ 72% erreicht (noch 4,1 kg) +Prognose: 30.05.2026 ✓ auf Kurs +``` + +**Countdown:** +- Zeigt Tage bis Zieldatum +- Farbcodierung: grün (auf Kurs) / gelb (leicht hinter Plan) / rot (deutlich hinter Plan) + +### 2.4 KI-Zielvorschläge + +Die KI analysiert aktuelle Werte und schlägt realistische Zielwerte vor: + +**Beispiele:** +- Gewichtsziel: "Basierend auf deinem aktuellen Gewicht von 86 kg und Kalorientrend + wäre ein Ziel von 82 kg in 12 Wochen realistisch (+0,5 kg/Woche Defizit)" +- Ruhepuls-Ziel: "Dein Ruhepuls liegt bei 58 bpm. Ein Ziel von 52 bpm ist bei + konsequentem Ausdauertraining in 16 Wochen erreichbar" +- KF-Ziel: "Von 19,9% auf 16% sind ~3,9 Prozentpunkte – realistisch in 14 Wochen + bei Kaloriendefizit + Krafttraining" + +**Vorschlag-Trigger:** +- Beim erstmaligen Anlegen eines Ziels +- Wenn kein aktives Ziel vorhanden +- Auf explizite Anfrage ("Neues Ziel vorschlagen") + +--- + +## 3. Trainingsphasen + +### 3.1 Unterstützte Phasen + +| Phase | Erkennungsmerkmale | Empfohlene Strategie | +|-------|-------------------|---------------------| +| Kaloriendefizit | Kcal-Bilanz < -300/Tag (Ø 7T), Gewicht sinkend | Krafttraining erhalten, Cardio moderat | +| Kalorienstabilisierung | Kcal-Bilanz -100 bis +100/Tag, Gewicht stabil | Ausgeglichenes Training | +| Kalorienüberschuss | Kcal-Bilanz > +300/Tag, Gewicht steigend | Krafttraining priorisieren | +| Konditionsaufbau | >60% Cardio-Anteil, VO2Max-Trend steigend | Zone 2 Training ausbauen | +| Schnellkraft / HIIT | >40% HIIT/Schnellkraft, HF-Zonen 4-5 dominant | Erholung beachten | +| Maximalkraft-Aufbau | >50% Krafttraining, Hypertrophie/Maximalkraft | Progressive Überlastung | +| Regeneration | Trainingsvolumen -40% vs. Vorwoche, HRV niedrig | Aktive Erholung, Schlaf | +| Wettkampfvorbereitung | Nutzer-definiert mit Datum | Periodisierung, Peak-Performance | + +### 3.2 Phasenerkennung + +**Automatische Erkennung:** +- System analysiert letzte 14 Tage: Kalorienbilanz, Trainingstypen, Volumen, HRV, Ruhepuls +- Schlägt erkannte Phase als Benachrichtigung vor +- Erkennungs-Intervall: täglich (Hintergrund) + +**Nutzer-Bestätigung:** +- Vorschlag erscheint als Banner: "Wir haben erkannt: Du befindest dich in einer + Kaloriendefizit-Phase. Stimmt das?" +- Optionen: ✓ Bestätigen / ✗ Ablehnen / ✎ Anpassen +- Manuelles Setzen jederzeit möglich + +**Phasenwechsel-Erkennung:** +- Automatisch erkannt wenn Datenmuster **7 Tage konsistent** einer neuen Phase entspricht +- Erst nach dieser Stabilitätsprüfung wird der Wechsel vorgeschlagen +- Verhindert falsche Alarme bei kurzzeitigen Abweichungen (z.B. Urlaub, Krankheit) +- Nutzer wird benachrichtigt: "Phasenwechsel erkannt: Kaloriendefizit → Stabilisierung" + +### 3.3 Phase und Dashboard + +- Aktuelle Phase prominent auf Dashboard angezeigt (Badge/Label) +- Dashboard-Kennzahlen passen sich an Phase an: + - Kaloriendefizit: Kaloriendefizit/Tag als Hauptkennzahl + - Muskelaufbau: Proteinversorgung + Trainingsvolumen prominent + - Kondition: VO2Max-Trend + Cardio-Anteil prominent + +--- + +## 4. Aktive Tests + +### 4.1 Konzept + +Aktive Tests messen Fitness-Parameter die nicht aus passivem Tracking ableitbar sind. +Jeder Test hat: +- **Durchführungsanleitung** (Schritt-für-Schritt in der App) +- **Messung** (Eingabe des Ergebnisses) +- **Auswertung** (Einordnung nach Alter/Geschlecht + Trend) +- **Testintervall** (empfohlene Wiederholung) +- **KI-Verknüpfung** (Ergebnis fließt in Analyse ein) + +### 4.2 Standardtests + +#### Cooper-Test (Ausdauer / VO2Max) +- **Ziel:** VO2Max-Schätzung +- **Durchführung:** 12 Minuten laufen, maximale Distanz messen +- **Eingabe:** Distanz in Metern +- **Berechnung:** `VO2Max = (Distanz - 504.9) / 44.73` +- **Intervall:** alle 4–6 Wochen +- **Anleitung in App:** Aufwärmen 5 min → 12 min laufen → Distanz eingeben + +#### Stufentest (Herzfrequenz-Ausdauer) +- **Ziel:** Aerobe Schwelle, HF-Zonen kalibrieren +- **Durchführung:** Steigende Belastung (Laufen/Radfahren), HF alle 3 min notieren +- **Eingabe:** HF-Werte je Stufe (5 Stufen à 3 min) +- **Auswertung:** HF-Kurve, Schwellenbestimmung +- **Intervall:** alle 6–8 Wochen + +#### Liegestützen-Test (Kraftausdauer Oberkörper) +- **Ziel:** Maximale Liegestützen ohne Pause +- **Eingabe:** Anzahl Wiederholungen +- **Einordnung:** Normwerte nach Alter/Geschlecht +- **Intervall:** alle 4 Wochen + +#### Kniebeugen-Test (Kraftausdauer Unterkörper) +- **Ziel:** Maximale Kniebeugen ohne Pause +- **Eingabe:** Anzahl Wiederholungen +- **Einordnung:** Normwerte nach Alter/Geschlecht +- **Intervall:** alle 4 Wochen + +#### Sit & Reach (Flexibilität) +- **Ziel:** Rumpf- und Beinflexibilität messen +- **Durchführung:** Sitzen, Beine gestreckt, Oberkörper vorbeugen, maximale Reichweite +- **Eingabe:** cm (positiv = über Fußsohle, negativ = davor) +- **Einordnung:** Normwerte nach Alter/Geschlecht +- **Intervall:** alle 4 Wochen + +#### Gleichgewichtstest (Einbeinstand) +- **Ziel:** Propriozeption, Gleichgewicht +- **Durchführung:** Einbeinstand mit geschlossenen Augen, Zeit messen +- **Eingabe:** Sekunden (bestes von 3 Versuchen) +- **Einordnung:** <10s = verbesserungswürdig, 10-30s = gut, >30s = sehr gut +- **Intervall:** alle 4 Wochen + +#### Griffstärke +- **Ziel:** Allgemeine Kraftindikator, Gesundheitsmarker +- **Durchführung:** Dynamometer oder Schätzung (maximaler Händedruck) +- **Eingabe:** kg (oder subjektiv 1-5 wenn kein Gerät) +- **Intervall:** alle 4 Wochen + +#### Benutzerdefinierter Test +- **Ziel:** Eigene Tests anlegen +- **Felder:** Name, Beschreibung, Einheit, Eingabetyp (Zahl/Zeit/Skala) +- **Einordnung:** Manuelle Referenzwerte (gut/mittel/verbesserungswürdig) +- **Intervall:** Frei wählbar + +### 4.3 Test-Verwaltung + +**Test-Historie:** +- Alle Testergebnisse chronologisch gespeichert +- Trend über Zeit sichtbar (Liniengrafik pro Test) +- Verbesserung in % seit letztem Test + +**Test-Kalender:** +- Übersicht: welche Tests wann fällig +- KI empfiehlt: "Cooper-Test ist seit 5 Wochen überfällig" + +**Normwerte:** +- Einordnung nach Alter (in Profil hinterlegt) und Geschlecht +- Kategorien: Sehr gut / Gut / Durchschnittlich / Verbesserungswürdig +- Quelle: Etablierte Fitness-Normtabellen + +--- + +## 5. Vitalwerte + +### 5.1 Erfasste Vitalwerte + +| Vitalwert | Felder | Einheit | Warnbereich | +|-----------|--------|---------|-------------| +| Blutdruck | Systolisch + Diastolisch + Puls | mmHg | >140/90 = erhöht | +| SpO2 | Sauerstoffsättigung | % | <95% = Warnung | +| HRV | Herzratenvariabilität | ms | persönliche Baseline | +| Körpertemperatur | Temperatur | °C | >37,5°C = erhöht | +| Blutzucker | Nüchtern-Wert | mg/dl oder mmol/l | >100 mg/dl nüchtern | + +### 5.2 Erfassung +- Manuelle Eingabe mit Datum + Uhrzeit +- Import aus Apple Health (wenn vorhanden) +- Messkontext optional: Nüchtern / Nach Sport / Morgens / Abends + +### 5.3 Auswertung +- Trend über Zeit je Vitalwert +- Ampel-System: Grün (normal) / Gelb (Grenzbereich) / Rot (außerhalb Normbereich) +- Korrelation: Blutdruck ↔ Training, Blutdruck ↔ Gewicht +- **Hinweis in App:** Vitalwerte ersetzen keine ärztliche Diagnose + +--- + +## 6. KI-Analyse + +### 6.1 Neue KI-Platzhalter + +``` +{{primärziel_typ}} → "Gewichtsabnahme" +{{primärziel_wert}} → "82 kg bis 30.06.2026" +{{primärziel_fortschritt}} → "72% erreicht (4 kg von 5,5 kg)" +{{primärziel_prognose}} → "Ziel voraussichtlich in 6 Wochen erreicht" +{{aktuelle_phase}} → "Kaloriendefizit" +{{phase_seit}} → "seit 14 Tagen" +{{phase_empfehlung}} → "Krafttraining erhalten, Cardio moderat" +{{test_letzter}} → "Cooper-Test: 2.800m (VO2Max ~52) vor 3 Wochen" +{{test_faellig}} → "Sit & Reach (seit 5 Wochen nicht gemacht)" +{{vitalwerte_blutdruck}} → "125/82 mmHg (letzter Wert)" +{{vitalwerte_spo2}} → "98% (letzter Wert)" +{{vitalwerte_trend}} → "Blutdruck stabil, SpO2 normal" +{{fitness_score}} → "Kondition: gut · Kraft: durchschnittlich · Flex: verbesserungswürdig" +``` + +### 6.2 KI-Analyse-Funktionen + +**Zielwerte vorschlagen:** +- Trigger: kein aktives Ziel oder auf Anfrage +- Basis: aktuelle Messwerte + historischer Trend + Alter/Geschlecht +- Output: konkreter Zielwert + realistischer Zeitrahmen + Begründung + +**Phase automatisch erkennen:** +- Tägliche Hintergrundanalyse +- Kalorienbilanz + Trainingstypen + Gewichtstrend + HRV +- Output: Phasen-Vorschlag mit Begründung + +**Test empfehlen wenn fällig:** +- Prüft Testintervall vs. letztes Ergebnis +- Output: "Cooper-Test ist seit X Wochen fällig – führe ihn durch um deinen + VO2Max-Trend zu aktualisieren" + +**Fortschritt mit Prognose:** +- Lineare Extrapolation aktueller Trend → Zieldatum +- Output: "Bei aktuellem Tempo erreichst du dein Gewichtsziel in 6 Wochen + (statt geplanter 8 Wochen)" + +**Empfehlungen an Phase anpassen:** +- KI-Analysen berücksichtigen aktuelle Phase +- Beispiel in Kaloriendefizit-Phase: Protein-Empfehlung erhöht, + Krafttraining-Empfehlung priorisiert, Schlafmangel-Warnung verstärkt +- Beispiel in Wettkampf-Phase: Erholungsstatus und Tapering im Fokus + +--- + +## 7. Abgrenzung & offene Fragen + +### In diesem Modul enthalten: +- 8 Zieltypen mit Primärziel-Konzept +- 8 Trainingsphasen + automatische Erkennung + Nutzerbestätigung +- 8 Standardtests + benutzerdefinierte Tests +- Normwerte-Einordnung nach Alter/Geschlecht +- 5 Vitalwerte mit Ampel-System +- 13 neue KI-Platzhalter +- 5 KI-Analyse-Funktionen + +### Nicht in diesem Modul: +- Periodisierungsplaner (Makro-/Mikrozyklen) → später +- Ärztliche Auswertung / Diagnose → bewusst ausgeschlossen +- Automatische Smartwatch-Tests → v9h Connectoren +- Ernährungsplan-Generator → später + +### Offene Fragen für technische Planung: +1. Ziele: eigene Tabelle `goals` oder als JSONB in `profiles`? +2. Phasenerkennung: täglicher Cron-Job im Backend oder Frontend-Berechnung? +3. Aktive Tests: generisches Schema (test_type + result_value + result_unit) + oder je Test eigene Felder? +4. Normwerte: in DB gespeichert oder als statische JSON-Datei im Backend? +5. Fitness-Score: wie wird er aus mehreren Tests aggregiert (gewichteter Durchschnitt)? +6. Vitalwerte: eigene Tabelle je Typ oder generische `vitals_log` Tabelle? +7. Phasen-Vorschlag: als Push-Notification oder nur In-App-Banner? +EOF diff --git a/.claude/docs/functional/PHASE_0B_IMPROVEMENTS.md b/.claude/docs/functional/PHASE_0B_IMPROVEMENTS.md new file mode 100644 index 0000000..17a9413 --- /dev/null +++ b/.claude/docs/functional/PHASE_0B_IMPROVEMENTS.md @@ -0,0 +1,1790 @@ +# Phase 0b Placeholder Improvements & Missing Values + +> **Status:** 🔴 Draft - User Input in Progress +> **Datum:** 29.03.2026 +> **Zweck:** Sammlung fehlender Platzhalter und Verbesserungspotenziale nach Phase 0b + +--- + +## Executive Summary: Zwei Kritische Gaps (29.03.2026) + +### 🚨 Gap #1: Quality Label wird nicht berücksichtigt +**Activity Platzhalter berücksichtigen `quality_label` nicht!** + +- ❌ **Alle** activity-bezogenen Platzhalter nutzen ALLE Trainings (inkl. poor/excluded) +- ⚠️ **Betroffene Platzhalter:** `{{activity_summary}}`, `{{activity_detail}}`, `{{trainingstyp_verteilung}}`, `{{training_minutes_week}}`, `{{training_frequency_7d}}`, `{{ability_balance_*}}`, etc. +- ✅ **Einzige Ausnahme:** `{{quality_sessions_pct}}` (zählt nur excellent/good) +- 🔧 **Infrastruktur vorhanden:** `quality_filter.py` existiert bereits, wird aber NICHT genutzt + +**Auswirkung:** Standard-Auswertungen in KI-Prompts zählen schlechte/ausgeschlossene Trainings mit → **verfälschte Analysen**! + +**Empfohlene Lösung:** +1. Quality-Filter in `data_layer/activity_metrics.py` einbauen (6-8h) +2. Neue Platzhalter für Evaluation-Breakdown (3h) +3. Quality-Weighted Metrics (2h) +**Teilsumme:** 11-13h + +--- + +### 🚨 Gap #2: Keine Trainingstyp-spezifischen Platzhalter +**KI kann nicht nach Trainings-Kategorien differenzieren!** + +- ❌ **Keine Kategorie-spezifischen Platzhalter** (Kraft, Cardio, Kampfsport getrennt) +- ❌ **Keine Subcategory-Differenzierung** (Hypertrophie vs. Maximalkraft vs. Kraftausdauer) +- ❌ **Keine Ruhepausen-Analyse** nach Trainingstyp (48h zwischen Kraft-Einheiten?) +- ❌ **Keine Muskelerhalt-Logik** (mind. 2x/Woche Kraft?) +- ❌ **Keine Balance-Analysen** (Cardio:Kraft Ratio, fehlende Kategorien) +- ✅ **Was existiert:** `{{trainingstyp_verteilung}}` (nur Top 3 Kategorien), `{{ability_balance_*}}` + +**Auswirkung:** KI kann NICHT coachen zu: +- "Zu wenig Krafttraining für Muskelerhalt" (kein `{{strength_frequency}}`) +- "48h Pause zwischen Kraft-Einheiten" (kein `{{days_since_last_strength}}`) +- "Zu cardio-lastig, mehr Kraft empfohlen" (kein `{{cardio_to_strength_ratio}}`) +- "Kein Mobility-Training in 30 Tagen" (kein `{{missing_categories}}`) + +**Empfohlene Lösung:** +1. Kategorie-spezifische Count/Minutes Platzhalter (4-5h) +2. Ruhepausen-Analyse nach Kategorie (3h) +3. Balance-Scores & Ratios (2-3h) +4. Subcategory-Analysen (2h) +**Teilsumme:** 11-13h + +--- + +**GESAMT:** 22-26h Implementierung für beide Gaps + +--- + +## 0. Governance-Anforderungen & Fit/Gap-Analyse (User Requirements) + +> **Quelle:** `placeholder_requirements_for_development.md` (29.03.2026) +> **Zweck:** Professionelle Placeholder-Governance für Prompt-Bibliothek V1 + +### Governance-Prinzipien (verbindlich): + +1. **Platzhalter = API-Verträge** - keine Ad-hoc-Erfindungen in Prompts +2. **Keine stillschweigenden Änderungen** - Semantik, Zeitfenster, Einheit stabil +3. **Fehlende Werte explizit** - `null` oder strukturierter Status, nicht "nicht verfügbar" +4. **Atomare Platzhalter bevorzugt** - einzelne Felder statt Freitext-Blobs +5. **Zeitbezug immer eindeutig** - `*_7d`, `*_28d`, `*_90d` im Namen +6. **JSON vor Freitext** - strukturierte Objekte für komplexe Daten +7. **Verfügbarkeit trennen** - `*_available` Flags für kritische Felder +8. **Zwei-Stufen-Architektur** - Komplexe Interpretationen via KI statt hardcodiert ⭐ NEU + +### Placeholder-Typen (Architektur): + +#### Typ 1: Atomic Placeholders +- **Direkt aus Daten** (keine Interpretation) +- Beispiel: `{{weight_aktuell}}`, `{{training_frequency_7d}}` +- Implementierung: Python-Funktion → direkter Wert + +#### Typ 2: Raw Data Placeholders +- **Strukturierte JSONs für KI-Input** (keine Interpretation) +- Beispiel: `{{goal_priority_raw_data}}`, `{{constraints_raw_data}}` +- Implementierung: Python aggregiert Rohdaten → JSON +- Verwendung: Input für Basis-Prompts + +#### Typ 3: Interpreted Placeholders +- **Von KI generiert** (via Basis-Prompt) +- Beispiel: `{{goal_weighted_priority}}`, `{{main_constraint}}` +- Implementierung: Basis-Prompt interpretiert Raw Data → strukturierter Output +- Verwendung: In übergeordneten Prompts + +### Priorisierte Anforderungen (Sprint 1-3): + +**Sprint 1 (Must-Have für V1):** 10 Items (P1-P10 + strukturelle) +**Sprint 2 (Diagnose-Ebene):** 7 Items (P21-P27) +**Sprint 3 (Verfeinerung):** 6 Items (niedrigere Priorität) + +--- + +## 0.1 Fit/Gap-Analyse: P1-P27 vs. Aktuelle Datenstruktur + +### ✅ Vollständig möglich (mit aktueller Struktur): + +#### P1. `goal_summary_json` ✅ +- **Datenquelle:** `goals` table (Migration 022) +- **Struktur vorhanden:** id, name, goal_type, status, start_value, target_value, current_value, progress_percentage, target_date, is_primary, focus_contributions +- **Implementierung:** Neue data_layer Funktion `get_goal_summary_data()` → JSON formatieren +- **Aufwand:** 2h (Funktion + Platzhalter) + +#### P2. `focus_area_summary_json` ✅ +- **Datenquelle:** `focus_area_definitions` + `user_focus_area_weights` + `goal_focus_contributions` +- **Struktur vorhanden:** name, category, user_weight, goal_count, progress per focus area +- **Implementierung:** Neue data_layer Funktion `get_focus_area_summary_data()` +- **Aufwand:** 2h + +#### P3-P4. `*_score_available` Flags ✅ +- **Datenquelle:** Berechnete Scores +- **Implementierung:** Prüfen ob Score berechnet werden konnte (nicht None) +- **Aufwand:** 1h (alle Score-Platzhalter erweitern) + +#### P5. `domain_availability_json` ✅ +- **Datenquelle:** Alle Domain-Tabellen (weight_log, nutrition_log, activity_log, sleep_log, etc.) +- **Berechnung:** + ```python + { + "body": {"available": true, "confidence": "high", "last_entry": "2026-03-29", "days_coverage_28d": 25}, + "nutrition": {"available": true, "confidence": "medium", "last_entry": "2026-03-28", "days_coverage_28d": 18}, + "activity": {"available": true, "confidence": "high", "last_entry": "2026-03-29", "days_coverage_28d": 12}, + "sleep": {"available": true, "confidence": "low", "last_entry": "2026-03-15", "days_coverage_28d": 8}, + "vitals": {"available": true, "confidence": "medium", "last_entry": "2026-03-27", "days_coverage_28d": 15}, + "goals": {"available": true, "confidence": "high", "active_goals": 3}, + "focus_areas": {"available": true, "confidence": "high", "weighted_areas": 5} + } + ``` +- **Implementierung:** Neue Funktion `calculate_domain_availability()` +- **Aufwand:** 3-4h (alle Domains prüfen) + +#### P8-P11. Body Change Availability Flags ✅ +- **Datenquelle:** `weight_log`, `caliper_log`, `circumference_log` +- **Implementierung:** Prüfen ob genug Datenpunkte für Trend (mind. 2 Messungen, 28d Zeitfenster) +- **Aufwand:** 1h + +#### P12. `body_change_summary_json` ✅ +- **Datenquelle:** Phase 0c data_layer/body_metrics.py Funktionen +- **Struktur:** + ```python + { + "weight_trend_28d": {"delta_kg": -2.5, "slope": -0.089, "confidence": "high"}, + "fm_change_28d": {"delta_kg": -3.1, "available": true, "confidence": "medium"}, + "lbm_change_28d": {"delta_kg": 0.6, "available": true, "confidence": "medium"}, + "waist_delta_28d": {"delta_cm": -2.5, "available": true}, + "hip_delta_28d": {"delta_cm": -1.2, "available": true}, + "waist_hip_ratio": 0.85, + "recomposition_quadrant": "fat_loss_muscle_gain" + } + ``` +- **Implementierung:** Aggregation bestehender Funktionen +- **Aufwand:** 2h + +#### P13. `activity_structure_json` ✅ +- **Datenquelle:** Phase 0c data_layer/activity_metrics.py Funktionen +- **Struktur:** + ```python + { + "volume_weekly_min": 180, + "frequency_7d": 4, + "type_distribution": {"cardio": 45, "strength": 35, "mobility": 10, "recovery": 10}, + "quality_sessions_pct": 75, + "proxy_load_7d": 450, + "monotony_score": 1.2, + "strain_score": 540, + "rest_day_compliance_pct": 85, + "patterns": ["cardio-heavy", "good-recovery-compliance"] + } + ``` +- **Implementierung:** Aggregation bestehender Funktionen +- **Aufwand:** 2h + +#### P16. `sleep_summary_json` ✅ +- **Datenquelle:** `sleep_log` (Migration 009), Phase 0c recovery_metrics.py +- **Struktur vorhanden:** duration, segments (deep, rem, light, awake) +- **Implementierung:** Neue Funktion `get_sleep_summary_data()` +- **Aufwand:** 2h + +#### P17. `recovery_summary_json` ✅ +- **Datenquelle:** `vitals_baseline` (RHR, HRV), `sleep_log`, `rest_days`, `activity_log` (load) +- **Struktur:** + ```python + { + "recovery_score": 75, + "main_drivers": ["good_sleep", "hrv_above_baseline"], + "rhr_status": "normal", + "hrv_status": "above_baseline", + "hrv_vs_baseline_pct": 5.2, + "rhr_vs_baseline_pct": -3.1, + "recent_load_interaction": "balanced", + "confidence": "high" + } + ``` +- **Implementierung:** Aggregation bestehender recovery_metrics Funktionen +- **Aufwand:** 3h + +#### P18. `vitals_summary_json` ✅ +- **Datenquelle:** `vitals_baseline` (Migration 015) +- **Struktur vorhanden:** resting_hr, hrv, vo2_max, spo2, respiratory_rate +- **Implementierung:** Neue Funktion `get_vitals_summary_data()` +- **Aufwand:** 1-2h + +#### P19-P20. HRV/RHR Availability Flags ✅ +- **Datenquelle:** `vitals_baseline` +- **Implementierung:** Prüfen ob Baseline berechnet werden kann (mind. 7 Messungen) +- **Aufwand:** 30min + +#### P21. `correlation_summary_json` ✅ +- **Datenquelle:** Phase 0c correlations.py Funktionen +- **Struktur:** + ```python + { + "energy_weight_lag": {"best_lag_days": 7, "correlation": 0.68, "confidence": "high"}, + "protein_lbm": {"correlation": 0.52, "confidence": "medium"}, + "load_hrv_lag": {"best_lag_days": 3, "correlation": -0.45, "confidence": "medium"}, + "load_rhr_lag": {"best_lag_days": 2, "correlation": 0.38, "confidence": "low"}, + "sleep_recovery": {"correlation": 0.61, "confidence": "high"} + } + ``` +- **Implementierung:** Aggregation bestehender Korrelationsfunktionen +- **Aufwand:** 2h + +#### P22-P24. Plateau & Drivers ✅ +- **Datenquelle:** Phase 0c correlations.py (`detect_plateau_periods()`, `identify_top_drivers()`) +- **Struktur:** + ```python + { + "plateau_status": "likely", # likely / possible / not_detected / insufficient_data + "plateau_detected_at": "2026-03-15", + "plateau_duration_days": 14, + "top_drivers_positive": [ + {"driver": "protein_adequacy", "impact_score": 0.72}, + {"driver": "sleep_quality", "impact_score": 0.65} + ], + "top_drivers_negative": [ + {"driver": "training_monotony", "impact_score": -0.58}, + {"driver": "insufficient_strength_volume", "impact_score": -0.42} + ], + "confidence": "medium" + } + ``` +- **Implementierung:** Wrapper um bestehende Funktionen + Klassifikation +- **Aufwand:** 2-3h + +#### P25-P27. Underfueling Risk ✅ +- **Datenquelle:** `nutrition_log` (kcal), `activity_log` (kcal_active), `weight_log` (trend) +- **Berechnung:** + ```python + # Energy Availability = (intake - training_expenditure) / lean_body_mass + # RED-S Risk: EA < 30 kcal/kg LBM/day + { + "underfueling_risk_flag": true, + "underfueling_risk_reason": "ea_below_threshold", + "energy_availability_summary": { + "intake_avg_7d": 1800, + "training_expenditure_avg_7d": 600, + "net_availability": 1200, + "ea_per_kg_lbm": 25.5, # kcal/kg/day + "deficit_magnitude": "moderate", + "load_context": "high", + "recovery_context": "impaired", + "warning_level": "high", + "confidence": "medium" + } + } + ``` +- **Implementierung:** Neue Funktion `calculate_energy_availability()` +- **Aufwand:** 3-4h (RED-S Logik komplex) + +--- + +### ⚠️ Teilweise möglich (braucht Erweiterung): + +#### P7. `critical_missing_fields_json` ⚠️ +- **Problem:** Braucht Meta-Tracking welche Felder wann erfasst wurden +- **Was fehlt:** Keine Tabelle die "expected fields" vs. "captured fields" trackt +- **Lösung:** Heuristik basierend auf entry counts pro Domain +- **Beispiel:** + ```python + { + "critical_missing": [ + {"field": "body_fat_measurements", "last_entry": "2026-02-15", "days_ago": 42, "impact": "high"}, + {"field": "hrv_measurements", "last_entry": null, "days_ago": null, "impact": "medium"}, + {"field": "sleep_tracking", "coverage_28d": 8, "expected": 28, "impact": "medium"} + ], + "recommendation_priority": ["body_fat_measurements", "hrv_measurements", "sleep_tracking"] + } + ``` +- **Aufwand:** 3h (Heuristik-Logik) + +#### P14. `training_quality_score` ⚠️ +- **Was existiert:** `quality_label` per Activity, `quality_sessions_pct` Platzhalter +- **Was fehlt:** Aggregierter Quality-Score über alle Sessions (gewichtet) +- **Berechnung:** + ```python + # Weighted average: excellent=100, good=80, acceptable=60, poor=30, excluded=0 + quality_score = sum(quality_weight × duration) / total_duration + ``` +- **Aufwand:** 1h + +#### P15. `load_balance_class` ⚠️ +- **Was existiert:** `proxy_internal_load_7d` Platzhalter +- **Was fehlt:** Klassifikation in low/moderate/high/strained +- **Berechnung:** + ```python + if load < 300: "low" + elif load < 600: "moderate" + elif load < 900: "high" + else: "strained" + ``` +- **Aufwand:** 30min + +--- + +### ✅ Möglich mit Zwei-Stufen-Architektur (Data + KI Interpretation): + +#### P31. `goal_weighted_priority` ✅ (2-Stufen) +- **Was existiert:** Goals + Focus Areas + Priorisierungssystem +- **Architektur:** + - **Stufe 1 (Data Layer):** `get_goal_priority_raw_data()` → strukturiertes JSON + ```python + { + "goals": [ + { + "id": 1, + "name": "Gewichtsverlust 85kg", + "progress_pct": 42, + "behind_schedule_days": 5, + "focus_contributions": [ + {"focus_area": "Körpergewicht", "weight": 40} + ], + "total_focus_weight": 75 + } + ], + "focus_area_weights": {"Körper": 75, "Aktivität": 20}, + "context": {...} + } + ``` + - **Stufe 2 (KI-Prompt):** Basis-Prompt "Goal Priority Interpreter" interpretiert Rohdaten + - Input: `{{goal_priority_raw_data}}` + - Output: `{{goal_weighted_priority}}` (strukturiertes JSON mit Prioritäten + Rationale) +- **Aufwand:** 3h (2h Data Layer + 1h Basis-Prompt) + +#### P32. `main_constraint` ✅ (2-Stufen) +- **Was existiert:** Activity patterns, frequency, gaps +- **Architektur:** + - **Stufe 1:** `get_constraints_raw_data()` → JSON + ```python + { + "time_budget": { + "avg_sessions_per_week": 3.2, + "longest_gap_days": 4, + "typical_session_duration_min": 45 + }, + "activity_patterns": { + "weekday_sessions": 2.5, + "weekend_sessions": 0.7 + }, + "missing_categories": ["mobility", "recovery"], + "data_gaps": {...} + } + ``` + - **Stufe 2:** Basis-Prompt "Constraint Identifier" + - Input: `{{constraints_raw_data}}` + - Output: `{{main_constraint}}` (Haupteinschränkung + Typ) +- **Aufwand:** 2-3h (2h Data Layer + 1h Basis-Prompt) + +#### P33. `main_strength` ✅ (2-Stufen) +- **Was existiert:** Tracking compliance, consistency scores, quality metrics +- **Architektur:** + - **Stufe 1:** `get_strengths_raw_data()` → JSON + ```python + { + "tracking_compliance": { + "weight_entries_28d": 28, + "nutrition_entries_28d": 26, + "activity_entries_28d": 12 + }, + "consistency_scores": { + "macro_consistency": 85, + "training_frequency_stability": 92 + }, + "quality_metrics": { + "quality_sessions_pct": 78, + "sleep_regularity": 0.85 + }, + "standout_values": [ + {"metric": "protein_adequacy", "value": 95, "percentile": "top_10"} + ] + } + ``` + - **Stufe 2:** Basis-Prompt "Strength Identifier" + - Input: `{{strengths_raw_data}}` + - Output: `{{main_strength}}` (Hauptstärke + Begründung) +- **Aufwand:** 2-3h (2h Data Layer + 1h Basis-Prompt) + +#### P34. `next_best_actions` ⚠️ (2-Stufen, teilweise) +- **Was existiert:** Gaps, low scores, fehlende Kategorien +- **Architektur:** + - **Stufe 1:** `get_improvement_opportunities_raw_data()` → JSON + ```python + { + "gaps": [ + {"domain": "activity", "gap": "no_strength_training_7d", "severity": "high"}, + {"domain": "nutrition", "gap": "protein_below_target", "severity": "medium"} + ], + "low_scores": [ + {"metric": "mobility_sessions_28d", "value": 0, "target": 4} + ], + "quick_wins": [ + {"opportunity": "add_1_strength_session", "effort": "low", "expected_impact": "medium"} + ], + "high_impact": [ + {"opportunity": "increase_protein_20g", "effort": "medium", "expected_impact": "high"} + ] + } + ``` + - **Stufe 2:** Basis-Prompt "Action Recommender" + - Input: `{{improvement_opportunities_raw_data}}` + - Output: `{{next_best_actions}}` (Top 3 Actions mit Begründung) +- **Problem:** Braucht domänenspezifisches Wissen (z.B. welche Proteinmenge sinnvoll) +- **Aufwand:** 3-5h (3h Data Layer + 2h Basis-Prompt mit Domänen-Wissen) + +--- + +### ❌ Nicht möglich / Redundant: + +#### P6. `domain_confidence_json` ❌ REDUNDANT +- **Problem:** Überschneidung mit P5 `domain_availability_json` +- **Lösung:** In P5 integrieren (bereits confidence per domain) +- **Aufwand:** 0h + +#### P30. `blood_pressure_summary_json` ⚠️ OPTIONAL (Sprint 3) +- **Was existiert:** `blood_pressure_log` table (Migration 015) +- **Was fehlt:** Aggregationsfunktion für BP summary +- **Berechnung:** + ```python + { + "mean_systolic_28d": 125, + "mean_diastolic_28d": 82, + "category": "elevated", # WHO/ISH classification + "contexts": {"nüchtern": 15, "nach_essen": 8, "nach_training": 5}, + "trend_28d": "stable", + "irregularity_count": 2, + "afib_detected": false, + "confidence": "high" + } + ``` +- **Aufwand:** 2h (neue data_layer Funktion) +- **Empfehlung:** Sprint 3 (niedrigere Priorität als P31-P34) + +--- + +### 📊 Zusammenfassung Fit/Gap (aktualisiert): + +| Kategorie | Anzahl | Aufwand | Details | +|-----------|--------|---------|---------| +| ✅ Vollständig möglich (direkt) | 20 Items | 35-40h | P1-P5, P8-P13, P16-P27 | +| ✅ Möglich (2-Stufen-Architektur) | 4 Items | 10-14h | P31-P34 (Data Layer + KI) | +| ⚠️ Teilweise möglich | 3 Items | 4-5h | P7, P14, P15 | +| ❌ Redundant / Optional | 2 Items | 2h | P6 (redundant), P30 (optional) | +| **TOTAL MÖGLICH** | **27 Items** | **49-59h** | **Alle User Requirements umsetzbar!** | + +**Architektur-Verbesserung durch 2-Stufen-Ansatz:** +- ✅ **P31-P34 von "nicht möglich" → "möglich"** durch intelligente Architektur +- ✅ **Gleicher Aufwand** wie ursprünglich geschätzt (10-14h) +- ✅ **Bessere Qualität:** Flexibler, wartbarer, erweiterbarer +- ✅ **Nutzt bestehendes System:** Unified Prompt System + Basis-Prompts + +--- + +## 0.2 Governance-Implementierung + +### Konkrete Maßnahmen für Governance-Regeln: + +#### G1-G2: API-Stabilität & Keine Semantikänderungen +**Maßnahme:** Placeholder Versioning System einführen +```python +PLACEHOLDER_MAP_V1 = { + '{{weight_aktuell}}': { + 'resolver': get_latest_weight, + 'version': '1.0.0', + 'semantic': 'latest weight entry, no averaging', + 'unit': 'kg', + 'timeframe': 'latest', + 'fallback': 'nicht verfügbar' + }, + # ... +} +``` +**Aufwand:** 3h (Metadata-System + alle Platzhalter dokumentieren) + +#### G3: Fehlende Werte explizit +**Maßnahme 1:** Alle "nicht verfügbar" Strings durch `null` ersetzen +**Maßnahme 2:** Neue Platzhalter mit `*_available` Flags +```python +'{{goal_progress_score}}': 75, +'{{goal_progress_score_available}}': true, +'{{goal_progress_score_reason}}': null # or "insufficient_goals" +``` +**Aufwand:** 2h (alle betroffenen Platzhalter umstellen) + +#### G4: JSON vor Freitext +**Maßnahme:** Alle Summary-Felder erhalten strukturierte JSON-Pendants +- `{{activity_summary}}` bleibt → + `{{activity_structure_json}}` +- `{{caliper_summary}}` bleibt → + `{{body_change_summary_json}}` +**Aufwand:** Bereits in P1-P27 enthalten + +#### G5: Zeitfenster im Namen +**Maßnahme:** Alle zeitabhängigen Platzhalter prüfen und ggf. umbenennen +```python +# ❌ Unklar: +'{{weight_trend}}' # 7d? 28d? 90d? + +# ✅ Klar: +'{{weight_trend_28d}}' +'{{weight_slope_28d}}' +``` +**Aufwand:** 2h (Audit + Renaming + Migration) + +#### G6: Verfügbarkeit trennen +**Maßnahme:** Systematisch alle kritischen Platzhalter mit Flags ergänzen +- Body: `{{fm_28d_change_available}}`, `{{lbm_28d_change_available}}`, etc. +- Vitals: `{{hrv_vs_baseline_available}}`, `{{rhr_vs_baseline_available}}` +- Goals: `{{goal_progress_score_available}}` +**Aufwand:** Bereits in P3-P4, P8-P11, P19-P20 enthalten + +#### G7: Keine Ad-hoc-Platzhalter +**Maßnahme:** Placeholder Approval Process einführen +1. Neuer Platzhalter → Request in PHASE_0B_IMPROVEMENTS.md +2. Bewertung gegen Governance-Regeln +3. Implementierung nur nach Approval +4. Dokumentation in placeholder_catalog aktualisieren +**Aufwand:** 1h (Process-Dokumentation) + +#### G8: Zwei-Stufen-Architektur für komplexe Interpretationen ⭐ NEU +**Maßnahme:** Komplexe Logik NICHT hardcoded, sondern via KI-Interpretation + +**Wann Zwei-Stufen-Ansatz nutzen:** +- Interpretation braucht Kontext-Verständnis +- Logik ist nicht eindeutig regelbasiert +- Flexibilität wichtiger als Performance +- Domänen-Wissen erforderlich + +**Implementierungs-Pattern:** +1. **Data Layer:** `get__raw_data()` → strukturiertes JSON (Typ 2 Placeholder) +2. **Basis-Prompt:** Interpretiert Rohdaten → strukturierter Output (Typ 3 Placeholder) +3. **Verwendung:** Übergeordnete Prompts nutzen Typ 3 Placeholder + +**Beispiel:** +```python +# Data Layer (Python) +def get_goal_priority_raw_data(profile_id): + return { + "goals": [...], + "focus_weights": {...}, + "context": {...} + } + +# Platzhalter Typ 2 (Raw Data) +'{{goal_priority_raw_data}}': lambda pid: json.dumps(get_goal_priority_raw_data(pid)) + +# Basis-Prompt (KI) +Name: "Goal Priority Interpreter" +Input: {{goal_priority_raw_data}} +Output: JSON mit priority_ranking + +# Platzhalter Typ 3 (Interpreted) +'{{goal_weighted_priority}}': +``` + +**Aufwand:** 0h (bereits in P31-P34 enthalten) + +**Vorteile:** +- ✅ Flexibler: Prompt-Änderung ohne Code-Deploy +- ✅ Wartbarer: Klare Trennung Data vs. Interpretation +- ✅ Erweiterbarer: Gleiche Rohdaten für mehrere Prompts nutzbar +- ✅ Testbarer: Rohdaten-Struktur separat testbar + +--- + +## 0.3 Sprint-Planung (User Requirements integriert) + +### Sprint 1: Must-Have für V1 (20 Items, 35-40h) + +**Governance & Infrastructure (8h):** +1. Placeholder Versioning System (3h) +2. "nicht verfügbar" → null Migration (2h) +3. Zeitfenster-Audit & Renaming (2h) +4. Approval Process Dokumentation (1h) + +**Strukturierte JSONs (15-18h):** +- P1: goal_summary_json (2h) +- P2: focus_area_summary_json (2h) +- P5: domain_availability_json (3-4h) +- P12: body_change_summary_json (2h) +- P13: activity_structure_json (2h) +- P16: sleep_summary_json (2h) +- P17: recovery_summary_json (3h) +- P18: vitals_summary_json (1-2h) + +**Availability Flags (3h):** +- P3-P4: Score availability flags (1h) +- P8-P11: Body change availability (1h) +- P19-P20: HRV/RHR availability (30min) +- P7: critical_missing_fields_json (3h) - teilweise + +**Diagnose & Correlation (5-6h):** +- P21: correlation_summary_json (2h) +- P22-P24: plateau_status + drivers (2-3h) +- P14: training_quality_score (1h) - teilweise +- P15: load_balance_class (30min) - teilweise + +**Energy Availability (3-4h):** +- P25-P27: underfueling risk + energy_availability_summary (3-4h) + +**Sprint 1 Gesamt:** 34-41h + +--- + +### Sprint 2: Quality Label + Training Categories (16-19h + 10-13h = 26-32h) + +**Quality Label Gap (bereits definiert in Sektion 7):** +- Quality-Filter in activity_metrics.py (6-8h) +- Evaluation-Breakdown (1h) +- Placeholder-Updates (2h) +- Dedizierte Quality-Platzhalter (2h) +- Poor Sessions Warning (1h) + +**Training Categories Gap (bereits definiert in Sektion 7):** +- Category-Specific Metrics (2h) +- Kategorie-spezifische Platzhalter (3h) +- Days Since Last Training (2h) +- Training Balance Calculator (2-3h) +- Weitere Kategorie-Platzhalter (2h) +- Subcategory Distribution (3h) + +**Sprint 2 Gesamt:** 26-32h + +--- + +### Sprint 3: Zwei-Stufen-Platzhalter + Optional (10-16h) + +**P31-P34: Zwei-Stufen-Architektur (10-14h):** +- P31: goal_weighted_priority (3h) - Data Layer + Basis-Prompt +- P32: main_constraint (2-3h) - Data Layer + Basis-Prompt +- P33: main_strength (2-3h) - Data Layer + Basis-Prompt +- P34: next_best_actions (3-5h) - Data Layer + Basis-Prompt (komplex) + +**Optional (falls gewünscht, 2h):** +- P30: blood_pressure_summary_json (2h) + +**Sprint 3 Gesamt:** 12-16h + +**Besonderheit Sprint 3:** +- Nutzt Unified Prompt System für KI-Interpretation +- Basis-Prompts sind wiederverwendbar +- Flexibler als hardcodierte Logik +- Kann iterativ verfeinert werden (Prompt-Änderung ohne Code-Deploy) + +--- + +### Gesamtaufwand (alle 3 Sprints): + +| Sprint | Inhalt | Aufwand | +|--------|--------|---------| +| Sprint 1 | User Requirements P1-P27 + Governance | 34-41h | +| Sprint 2 | Quality Label + Training Categories | 26-32h | +| Sprint 3 | Blood Pressure + Goal-Weighted (optional) | 12-16h | +| **TOTAL** | **Alle Requirements + Beide Gaps** | **72-89h** | + +--- + +## 1. Fehlende Platzhalter + +### Kategorie: Körper (Body Metrics) + +#### `{{placeholder_name}}` +- **Zweck:** Wofür wird der Wert benötigt? +- **Datenquelle:** Tabelle/Funktion (z.B. `weight_log`, `body_metrics.py`) +- **Berechnung:** Wie soll er ermittelt werden? +- **Format:** Beispiel-Output (z.B. "4.5 kg", "85%", "nicht verfügbar") +- **Use Case:** In welchem Prompt wird das benötigt? + +--- + +### Kategorie: Ernährung (Nutrition) + +#### `{{placeholder_name}}` +- **Zweck:** +- **Datenquelle:** +- **Berechnung:** +- **Format:** +- **Use Case:** + +--- + +### Kategorie: Training / Aktivität + +#### `{{activity_summary_acceptable_plus}}` +- **Zweck:** Aktivitäts-Zusammenfassung nur mit mindestens "acceptable" Qualität +- **Datenquelle:** `activity_log` WHERE quality_label IN ('excellent', 'good', 'acceptable') +- **Berechnung:** Wie aktuelles `{{activity_summary}}`, aber mit quality_filter +- **Format:** "8 Einheiten in 14 Tagen (Ø 45 min/Einheit, 2400 kcal gesamt)" +- **Use Case:** Standard-Auswertungen, die nur valide Trainings berücksichtigen + +#### `{{activity_summary_excellent}}` +- **Zweck:** Aktivitäts-Zusammenfassung nur mit "excellent" Qualität +- **Datenquelle:** `activity_log` WHERE quality_label = 'excellent' +- **Berechnung:** Wie aktuelles `{{activity_summary}}`, aber nur excellent +- **Format:** "3 Einheiten in 14 Tagen (Ø 60 min/Einheit, 1200 kcal gesamt)" +- **Use Case:** Analyse von Top-Trainings, Benchmark für Qualität + +#### `{{poor_sessions_count}}` +- **Zweck:** Anzahl schlechter/ausgeschlossener Trainings +- **Datenquelle:** `activity_log` WHERE quality_label IN ('poor', 'excluded') +- **Berechnung:** COUNT(*) der poor/excluded sessions (7d oder 28d) +- **Format:** "2 Sessions" oder "keine schlechten Sessions" +- **Use Case:** Warnung bei zu vielen schlechten Trainings + +#### `{{evaluation_breakdown}}` +- **Zweck:** Verteilung der Trainings nach Evaluation-Stufen +- **Datenquelle:** `activity_log` GROUP BY quality_label +- **Berechnung:** COUNT(*) pro quality_label mit Prozentangaben +- **Format:** "Excellent: 3 (25%), Good: 5 (42%), Acceptable: 3 (25%), Poor: 1 (8%)" +- **Use Case:** Qualitäts-Übersicht in Coaching-Prompts + +#### `{{training_minutes_quality}}` +- **Zweck:** Trainingsminuten nur mit acceptable+ Qualität +- **Datenquelle:** `activity_log` WHERE quality_label IN ('excellent', 'good', 'acceptable') +- **Berechnung:** SUM(duration_min) mit quality_filter (7d) +- **Format:** "180 Minuten" (nur valide Trainings) +- **Use Case:** WHO-Empfehlung Vergleich (150-300 min/Woche) + +--- + +### Kategorie: Training / Aktivität - Trainingstyp-spezifisch ⚠️ NEU + +#### `{{strength_training_count_7d}}` +- **Zweck:** Anzahl Krafttrainings in letzten 7 Tagen +- **Datenquelle:** `activity_log` WHERE training_category = 'strength' +- **Berechnung:** COUNT(*) mit quality='quality' filter (7d) +- **Format:** "3 Einheiten" oder "keine Krafttrainings" +- **Use Case:** Muskelerhalt-Check (mind. 2x/Woche empfohlen) + +#### `{{strength_training_minutes_7d}}` +- **Zweck:** Trainingsminuten Kraft in letzten 7 Tagen +- **Datenquelle:** `activity_log` WHERE training_category = 'strength' +- **Berechnung:** SUM(duration_min) mit quality='quality' filter (7d) +- **Format:** "135 Minuten" oder "0 Minuten Krafttraining" +- **Use Case:** Volumen-Check für Muskelerhalt + +#### `{{cardio_training_count_7d}}` +- **Zweck:** Anzahl Cardio-Trainings in letzten 7 Tagen +- **Datenquelle:** `activity_log` WHERE training_category = 'cardio' +- **Berechnung:** COUNT(*) mit quality='quality' filter (7d) +- **Format:** "4 Einheiten" +- **Use Case:** WHO-Empfehlung (3-5x/Woche Cardio) + +#### `{{cardio_training_minutes_7d}}` +- **Zweck:** Trainingsminuten Cardio in letzten 7 Tagen +- **Datenquelle:** `activity_log` WHERE training_category = 'cardio' +- **Berechnung:** SUM(duration_min) mit quality='quality' filter (7d) +- **Format:** "180 Minuten" +- **Use Case:** WHO-Empfehlung (150-300 min/Woche) + +#### `{{martial_arts_frequency_28d}}` +- **Zweck:** Kampfsport-Frequenz (Sessions/Woche durchschnittlich über 28d) +- **Datenquelle:** `activity_log` WHERE training_category = 'martial_arts' +- **Berechnung:** COUNT(*) / 4 Wochen +- **Format:** "2.5 Einheiten/Woche" oder "kein Kampfsport in 28 Tagen" +- **Use Case:** Kampfsport-spezifisches Coaching + +#### `{{recovery_sessions_count_28d}}` +- **Zweck:** Anzahl aktiver Erholungseinheiten +- **Datenquelle:** `activity_log` WHERE training_category = 'recovery' +- **Berechnung:** COUNT(*) (28d) +- **Format:** "3 Erholungseinheiten" +- **Use Case:** Regenerations-Balance prüfen + +#### `{{mobility_sessions_count_28d}}` +- **Zweck:** Anzahl Mobility/Dehnung Sessions +- **Datenquelle:** `activity_log` WHERE training_category = 'mobility' +- **Berechnung:** COUNT(*) (28d) +- **Format:** "2 Mobility-Einheiten" oder "keine Mobility-Trainings" +- **Use Case:** Mobility-Warnung bei 0 Sessions + +#### `{{days_since_last_strength}}` +- **Zweck:** Tage seit letzter Krafteinheit +- **Datenquelle:** `activity_log` WHERE training_category = 'strength' +- **Berechnung:** CURRENT_DATE - MAX(date) +- **Format:** "2 Tage" oder ">7 Tage (kritisch!)" +- **Use Case:** Ruhepausen-Check / Muskelerhalt-Warnung + +#### `{{days_since_last_cardio}}` +- **Zweck:** Tage seit letzter Cardio-Einheit +- **Datenquelle:** `activity_log` WHERE training_category = 'cardio' +- **Berechnung:** CURRENT_DATE - MAX(date) +- **Format:** "1 Tag" oder ">5 Tage" +- **Use Case:** Ausdauer-Kontinuität prüfen + +#### `{{days_since_last_martial_arts}}` +- **Zweck:** Tage seit letzter Kampfsport-Einheit +- **Datenquelle:** `activity_log` WHERE training_category = 'martial_arts' +- **Berechnung:** CURRENT_DATE - MAX(date) +- **Format:** "3 Tage" oder ">14 Tage" +- **Use Case:** Technik-Kontinuität prüfen + +#### `{{cardio_to_strength_ratio}}` +- **Zweck:** Verhältnis Cardio zu Kraft (Minuten-basiert, 28d) +- **Datenquelle:** `activity_log` WHERE training_category IN ('cardio', 'strength') +- **Berechnung:** cardio_minutes / strength_minutes +- **Format:** "3:1 (cardio-lastig)" oder "1:2 (kraft-lastig)" oder "balanced" +- **Use Case:** Balance-Coaching + +#### `{{training_balance_score}}` +- **Zweck:** Balance-Score 0-100 basierend auf Kategorie-Verteilung +- **Datenquelle:** `activity_log` (alle Kategorien) +- **Berechnung:** Score = 100 - std_dev(category_percentages) × 2 +- **Format:** "75" (Zahl 0-100) +- **Use Case:** Gesamtbalance bewerten + +#### `{{missing_training_categories}}` +- **Zweck:** Liste fehlender Trainings-Kategorien (28d) +- **Datenquelle:** `activity_log` (alle Kategorien) +- **Berechnung:** Vergleich [cardio, strength, mobility, recovery] mit tatsächlichen +- **Format:** "Fehlt: Mobility, Recovery" oder "Alle Kategorien abgedeckt" +- **Use Case:** Lücken-Identifikation + +#### `{{strength_frequency_adequate}}` +- **Zweck:** Ja/Nein Check für Muskelerhalt (mind. 2x/Woche Kraft) +- **Datenquelle:** `activity_log` WHERE training_category = 'strength' +- **Berechnung:** COUNT(*) >= 2 in 7d? +- **Format:** "Ja" oder "Nein (nur 1x Kraft/Woche - Risiko Muskelabbau)" +- **Use Case:** Direkter Muskelerhalt-Check + +#### `{{muscle_preservation_risk}}` +- **Zweck:** Warnung bei zu wenig Krafttraining +- **Datenquelle:** `activity_log` WHERE training_category = 'strength' +- **Berechnung:** IF strength_count_7d < 2 THEN "Risiko: nur Xx Kraft" +- **Format:** "Kein Risiko" oder "Risiko: nur 1x Kraft in 7d" +- **Use Case:** Explizite Warnung für KI-Prompt + +#### `{{strength_type_distribution}}` +- **Zweck:** Verteilung innerhalb Krafttraining (Subcategories) +- **Datenquelle:** `activity_log` JOIN `training_types` WHERE category = 'strength' +- **Berechnung:** COUNT(*) per subcategory (hypertrophy, maxstrength, endurance) +- **Format:** "Hypertrophie: 60%, Maximalkraft: 30%, Kraftausdauer: 10%" +- **Use Case:** Periodisierungs-Analyse + +#### `{{hypertrophy_vs_maxstrength_ratio}}` +- **Zweck:** Verhältnis Hypertrophie zu Maximalkraft +- **Datenquelle:** `activity_log` WHERE subcategory IN ('hypertrophy', 'maxstrength') +- **Berechnung:** hypertrophy_count / maxstrength_count (28d) +- **Format:** "3:1" oder "nur Hypertrophie" oder "nur Maximalkraft" +- **Use Case:** Kraft-Periodisierungs-Check + +#### `{{technique_vs_sparring_balance}}` +- **Zweck:** Technik vs. Kampf im Kampfsport (Subcategories) +- **Datenquelle:** `activity_log` WHERE subcategory IN ('technique', 'sparring') +- **Berechnung:** Verhältnis technique / sparring (28d) +- **Format:** "2:1 (technik-lastig)" oder "balanced" +- **Use Case:** Kampfsport-spezifisches Coaching + +--- + +### Kategorie: Erholung / Vitalwerte + +#### `{{placeholder_name}}` +- **Zweck:** +- **Datenquelle:** +- **Berechnung:** +- **Format:** +- **Use Case:** + +--- + +### Kategorie: Ziele (Goals) + +#### `{{placeholder_name}}` +- **Zweck:** +- **Datenquelle:** +- **Berechnung:** +- **Format:** +- **Use Case:** + +--- + +### Kategorie: Korrelationen + +#### `{{placeholder_name}}` +- **Zweck:** +- **Datenquelle:** +- **Berechnung:** +- **Format:** +- **Use Case:** + +--- + +## 2. Verbesserungspotenziale bei existierenden Platzhaltern + +### `{{activity_summary}}` ⚠️ KRITISCH +- **Aktuelles Verhalten:** Zeigt ALLE Aktivitäten ohne Rücksicht auf quality_label +- **Problem:** Schlechte/ausgeschlossene Trainings werden mitgezählt → verfälscht Auswertungen +- **Vorschlag:** + - **Option A:** Default auf quality='acceptable_plus' ändern (Breaking Change) + - **Option B:** Neuen Parameter `quality_level` hinzufügen, default='all' (Non-Breaking) + - **Option C:** Neuer Platzhalter `{{activity_summary_quality}}`, alter bleibt (Redundanz) +- **Breaking Change:** Ja (Option A), Nein (Option B+C) +- **Use Case:** ALLE Standard-Coaching-Prompts sollten nur valide Trainings nutzen + +### `{{activity_detail}}` ⚠️ KRITISCH +- **Aktuelles Verhalten:** Listet ALLE Aktivitäten ohne quality_label Berücksichtigung +- **Problem:** Schlechte Trainings erscheinen in Detail-Liste ohne Kennzeichnung +- **Vorschlag:** + - Entweder quality_filter hinzufügen (nur acceptable+) + - Oder quality_label als Badge in Output aufnehmen: "30.03. Laufen (60 min) [excellent]" +- **Breaking Change:** Teilweise (Format-Änderung) +- **Use Case:** Detail-Analyse sollte Qualität pro Training zeigen + +### `{{trainingstyp_verteilung}}` ⚠️ +- **Aktuelles Verhalten:** Zeigt Verteilung ALLER Trainingstypen +- **Problem:** Schlechte Sessions werden in Kategorie-Statistik mitgezählt +- **Vorschlag:** quality_filter auf acceptable+ anwenden +- **Breaking Change:** Ja (Zahlen ändern sich) +- **Use Case:** Verteilung sollte nur valide Trainings zeigen + +### `{{training_minutes_week}}` ⚠️ +- **Aktuelles Verhalten:** Summiert ALLE Trainingsminuten +- **Problem:** Schlechte Trainings werden voll gezählt +- **Vorschlag:** + - Nur acceptable+ Trainings zählen + - Oder: quality_weighted sum (excellent=1.0, good=0.95, acceptable=0.85, poor=0.5) +- **Breaking Change:** Ja +- **Use Case:** WHO-Empfehlung sollte nur valide Minuten berücksichtigen + +### `{{training_frequency_7d}}` ⚠️ +- **Aktuelles Verhalten:** Zählt ALLE Sessions +- **Problem:** Poor/excluded sessions werden mitgezählt +- **Vorschlag:** Nur acceptable+ Sessions zählen +- **Breaking Change:** Ja +- **Use Case:** Frequenz-Statistik sollte nur valide Trainings zählen + +### `{{ability_balance_*}}` ⚠️ +- **Aktuelles Verhalten:** Berechnet Balance aus ALLEN Activities +- **Problem:** Schlechte Trainings beeinflussen Ability-Balance +- **Vorschlag:** Nur acceptable+ Activities für Berechnung nutzen +- **Breaking Change:** Ja (Balance-Scores ändern sich) +- **Use Case:** Balance-Berechnung sollte nur valide Trainings nutzen + +### `{{proxy_internal_load_7d}}` ℹ️ TEILWEISE KORREKT +- **Aktuelles Verhalten:** Nutzt quality_label als Gewichtungsfaktor (excellent=1.15, poor=0.75) +- **Problem:** Lädt ALLE Activities, gewichtet sie dann runter - aber poor sollte gar nicht gezählt werden +- **Vorschlag:** Filter auf acceptable+ PLUS quality-Gewichtung +- **Breaking Change:** Teilweise +- **Use Case:** Load-Monitoring sollte nur valide Trainings berücksichtigen + +--- + +## 3. Neue Berechnungslogik / Funktionen + +### Feature: Quality-Aware Activity Metrics +- **Beschreibung:** Alle activity_metrics Funktionen erhalten quality_level Parameter +- **Input:** + - `profile_id: str` + - `days: int` + - `quality_level: str = 'quality'` (new parameter) +- **Output:** Wie bisher, aber gefiltert nach quality_level +- **Algorithmus:** + ```python + from quality_filter import get_quality_filter_sql + + def get_activity_summary_data(profile_id, days=14, quality_level='quality'): + profile = get_profile_data(profile_id) + profile['quality_filter_level'] = quality_level + + quality_sql = get_quality_filter_sql(profile) # Returns "AND quality_label IN (...)" + + query = f""" + SELECT COUNT(*) as count, ... + FROM activity_log + WHERE profile_id=%s AND date >= %s + {quality_sql} + """ + ``` +- **Modul:** `backend/data_layer/activity_metrics.py` (alle Funktionen) +- **Dependencies:** + - `quality_filter.py` (bereits vorhanden) + - `get_profile_data()` für quality_filter_level + +### Feature: Evaluation Breakdown Calculator +- **Beschreibung:** Berechnet Verteilung nach quality_label für Zeitraum +- **Input:** `profile_id: str`, `days: int = 28` +- **Output:** + ```python + { + "excellent": {"count": 3, "percentage": 25, "minutes": 180}, + "good": {"count": 5, "percentage": 42, "minutes": 225}, + "acceptable": {"count": 3, "percentage": 25, "minutes": 90}, + "poor": {"count": 1, "percentage": 8, "minutes": 30}, + "excluded": {"count": 0, "percentage": 0, "minutes": 0}, + "total": 12, + "confidence": "high" + } + ``` +- **Algorithmus:** + ```sql + SELECT quality_label, + COUNT(*) as count, + SUM(duration_min) as minutes + FROM activity_log + WHERE profile_id = %s + AND date >= CURRENT_DATE - INTERVAL '%s days' + GROUP BY quality_label + ``` +- **Modul:** `backend/data_layer/activity_metrics.py` (neue Funktion) +- **Dependencies:** Keine + +### Feature: Category-Specific Activity Metrics +- **Beschreibung:** Count/Minutes pro Trainings-Kategorie +- **Input:** `profile_id: str`, `category: str`, `days: int = 7`, `quality_level: str = 'quality'` +- **Output:** + ```python + { + "category": "strength", + "count": 3, + "total_minutes": 135, + "avg_minutes_per_session": 45, + "sessions_per_week": 3.0, + "percentage_of_total": 35.0, # 35% aller Trainings + "confidence": "high", + "days_analyzed": 7 + } + ``` +- **Algorithmus:** + ```python + def get_category_activity_data(profile_id, category, days=7, quality_level='quality'): + profile = get_profile_data(profile_id) + profile['quality_filter_level'] = quality_level + quality_sql = get_quality_filter_sql(profile) + + query = f""" + SELECT COUNT(*) as count, + SUM(duration_min) as total_minutes + FROM activity_log + WHERE profile_id = %s + AND training_category = %s + AND date >= CURRENT_DATE - INTERVAL '%s days' + {quality_sql} + """ + # Calculate derived metrics... + ``` +- **Modul:** `backend/data_layer/activity_metrics.py` (neue Funktion) +- **Dependencies:** quality_filter.py + +### Feature: Days Since Last Training (per Category) +- **Beschreibung:** Tage seit letzter Einheit einer bestimmten Kategorie +- **Input:** `profile_id: str`, `category: str`, `quality_level: str = 'quality'` +- **Output:** + ```python + { + "category": "strength", + "days_since_last": 2, + "last_date": "2026-03-27", + "status": "ok", # ok / warning / critical + "recommendation": "Nächste Einheit in 1-2 Tagen empfohlen", + "confidence": "high" + } + ``` +- **Algorithmus:** + ```sql + SELECT MAX(date) as last_date + FROM activity_log + WHERE profile_id = %s + AND training_category = %s + AND quality_label IN ('excellent', 'good', 'acceptable') + ``` + ```python + days_since = (datetime.now().date() - last_date).days + + # Status logic (category-specific thresholds) + if category == 'strength': + status = 'critical' if days_since > 7 else 'warning' if days_since > 3 else 'ok' + elif category == 'cardio': + status = 'critical' if days_since > 5 else 'warning' if days_since > 2 else 'ok' + # etc. + ``` +- **Modul:** `backend/data_layer/activity_metrics.py` (neue Funktion) +- **Dependencies:** quality_filter.py + +### Feature: Training Category Balance Calculator +- **Beschreibung:** Berechnet Balance-Score basierend auf Verteilung +- **Input:** `profile_id: str`, `days: int = 28` +- **Output:** + ```python + { + "balance_score": 75, # 0-100 + "cardio_pct": 45, + "strength_pct": 35, + "mobility_pct": 10, + "recovery_pct": 5, + "martial_arts_pct": 5, + "missing_categories": ["hiit"], + "recommendation": "Gut balanciert, aber kein HIIT in 28 Tagen", + "cardio_to_strength_ratio": "1.3:1", + "status": "balanced" # balanced / cardio_heavy / strength_heavy + } + ``` +- **Algorithmus:** + ```python + # Get category distribution + categories = {'cardio': 0, 'strength': 0, 'mobility': 0, 'recovery': 0, ...} + + for category, minutes in distribution.items(): + categories[category] = (minutes / total_minutes) * 100 + + # Balance score = 100 - (std_dev × 2) + balance_score = 100 - (statistics.stdev(categories.values()) * 2) + + # Ratio calculations + cardio_to_strength = categories['cardio'] / categories['strength'] + + # Missing categories (expected but 0) + expected = ['cardio', 'strength', 'mobility'] + missing = [c for c in expected if categories[c] == 0] + ``` +- **Modul:** `backend/data_layer/activity_metrics.py` (neue Funktion) +- **Dependencies:** statistics + +### Feature: Subcategory Distribution (within Category) +- **Beschreibung:** Verteilung nach Subcategories innerhalb einer Kategorie +- **Input:** `profile_id: str`, `category: str = 'strength'`, `days: int = 28` +- **Output:** + ```python + { + "category": "strength", + "distribution": [ + {"subcategory": "hypertrophy", "count": 6, "percentage": 60}, + {"subcategory": "maxstrength", "count": 3, "percentage": 30}, + {"subcategory": "endurance", "count": 1, "percentage": 10} + ], + "total_sessions": 10, + "ratio_hypertrophy_to_max": "2:1", + "recommendation": "Gut verteilt, Periodisierung erkennbar", + "confidence": "high" + } + ``` +- **Algorithmus:** + ```sql + SELECT tt.subcategory, + COUNT(*) as count + FROM activity_log a + JOIN training_types tt ON a.training_type_id = tt.id + WHERE a.profile_id = %s + AND a.training_category = %s + AND a.date >= CURRENT_DATE - INTERVAL '%s days' + AND a.quality_label IN ('excellent', 'good', 'acceptable') + GROUP BY tt.subcategory + ORDER BY count DESC + ``` +- **Modul:** `backend/data_layer/activity_metrics.py` (neue Funktion) +- **Dependencies:** training_types table, quality_filter.py + +--- + +### Feature: Zwei-Stufen-Architektur für Interpretations-Platzhalter ⭐ NEU + +#### Architektur-Überblick: +``` +Data Layer (Python) → Raw Data Placeholder (JSON) → Basis-Prompt (KI) → Interpreted Placeholder +``` + +#### Beispiel: Goal-Weighted Priority + +**Stufe 1: Data Layer** +```python +# backend/data_layer/scores.py (neu) +def get_goal_priority_raw_data(profile_id: str) -> Dict: + """ + Aggregiert Rohdaten für Goal-Priorisierung. + KEINE Interpretation, nur strukturierte Daten! + """ + with get_db() as conn: + cur = get_cursor(conn) + + # 1. Aktive Ziele mit Focus Contributions + cur.execute(""" + SELECT g.id, g.name, g.goal_type, g.progress_percentage, + g.target_date, g.start_date, g.is_primary, + COALESCE( + json_agg( + json_build_object( + 'focus_area', fad.name_de, + 'category', fad.category, + 'contribution_weight', gfc.contribution_weight + ) + ) FILTER (WHERE fad.id IS NOT NULL), + '[]'::json + ) as focus_contributions + FROM goals g + LEFT JOIN goal_focus_contributions gfc ON g.id = gfc.goal_id + LEFT JOIN focus_area_definitions fad ON gfc.focus_area_id = fad.id + WHERE g.profile_id = %s AND g.status = 'active' + GROUP BY g.id + """, (profile_id,)) + goals = [dict(row) for row in cur.fetchall()] + + # 2. Focus Area Weights + from data_layer.scores import get_user_focus_weights + focus_weights = get_user_focus_weights(profile_id) + + # 3. Calculate derived metrics + for goal in goals: + # Total focus weight (Summe aller Contributions) + goal['total_focus_weight'] = sum( + fc['contribution_weight'] for fc in goal['focus_contributions'] + ) + + # Behind schedule calculation + if goal['target_date']: + from datetime import datetime + today = datetime.now().date() + target = goal['target_date'] + start = goal['start_date'] + + total_days = (target - start).days + elapsed_days = (today - start).days + expected_progress = (elapsed_days / total_days) * 100 + actual_progress = goal['progress_percentage'] + + goal['behind_schedule_days'] = int( + ((expected_progress - actual_progress) / 100) * total_days + ) + else: + goal['behind_schedule_days'] = None + + # 4. Context + context = { + "behind_schedule_count": sum( + 1 for g in goals if g.get('behind_schedule_days', 0) > 0 + ), + "on_track_count": sum( + 1 for g in goals if g.get('behind_schedule_days', 0) <= 0 + ), + "primary_goal": next( + (g['name'] for g in goals if g['is_primary']), + None + ) + } + + return { + "goals": goals, + "focus_area_weights": focus_weights, + "context": context + } +``` + +**Stufe 2: Platzhalter-Registration (Raw Data)** +```python +# backend/placeholder_resolver.py +PLACEHOLDER_MAP = { + # ... existing placeholders ... + + # Raw Data Placeholders (Typ 2) + '{{goal_priority_raw_data}}': lambda pid: json.dumps( + get_goal_priority_raw_data(pid), + indent=2 + ), +} +``` + +**Stufe 3: Basis-Prompt (KI Interpretation)** +```yaml +# Admin → KI-Prompts → Neuer Basis-Prompt + +Name: Goal Priority Interpreter +Type: base +Output Format: json +Output Schema: +{ + "priority_ranking": [ + { + "goal_name": "string", + "priority_score": "number (0-100)", + "rationale": "string", + "conflicts": ["string"] + } + ], + "recommended_focus": "string", + "goal_conflicts": [ + { + "goal_a": "string", + "goal_b": "string", + "conflict_type": "string", + "severity": "string" + } + ] +} + +Template: +--- +Analysiere die folgenden Ziel-Rohdaten und erstelle eine priorisierte Liste. + +# Rohdaten +{{goal_priority_raw_data}} + +# Aufgabe +1. Sortiere Ziele nach kombinierter Priorität: + - Focus-Weight (höheres Gewicht = höhere Priorität) + - Behind-Schedule Status (hinter Plan = höhere Priorität) + - Primary-Flag (Primary-Goal = Boost) + +2. Für jedes Ziel: + - Berechne Priority-Score (0-100) + - Begründe die Priorisierung + - Identifiziere Konflikte mit anderen Zielen + +3. Identifiziere Goal-Konflikte: + - Muskelaufbau vs. Kaloriendefizit (konfliktär) + - Gewichtsverlust + Kraftaufbau (möglich mit Rekomposition) + - etc. + +4. Empfehle Fokus-Reihenfolge für nächste 4 Wochen + +# Output +Gib das Ergebnis als JSON zurück (siehe Schema oben). +--- +``` + +**Stufe 4: Interpreted Placeholder (von KI generiert)** +```python +# Wird automatisch durch Unified Prompt System erzeugt +# Wenn Basis-Prompt "Goal Priority Interpreter" ausgeführt wird: +'{{goal_weighted_priority}}' = + +# Beispiel-Output: +{ + "priority_ranking": [ + { + "goal_name": "Gewichtsverlust 85kg", + "priority_score": 92, + "rationale": "Höchstes Focus-Weight (75) + 5 Tage hinter Plan + Primary Goal", + "conflicts": ["Kraftaufbau (moderate Konflikt bei zu großem Defizit)"] + }, + { + "goal_name": "Kraftaufbau", + "priority_score": 58, + "rationale": "Moderates Focus-Weight (20) + On Track + Secondary Goal", + "conflicts": ["Gewichtsverlust (erfordert moderates Defizit)"] + } + ], + "recommended_focus": "Primär Gewichtsverlust mit moderatem Defizit, dabei Krafttraining 2-3x/Woche für Muskelerhalt", + "goal_conflicts": [ + { + "goal_a": "Gewichtsverlust", + "goal_b": "Kraftaufbau", + "conflict_type": "energy_availability", + "severity": "moderate" + } + ] +} +``` + +**Stufe 5: Verwendung in übergeordneten Prompts** +```yaml +# Pipeline-Prompt: "Weekly Coaching Report" + +Template: +--- +# Ziel-Priorisierung +{{goal_weighted_priority}} + +# Basierend auf deinen Prioritäten... +[restlicher Prompt nutzt die Priorisierung] +--- +``` + +#### Vorteile dieser Architektur: + +1. **Flexibilität:** Prompt-Änderung ohne Code-Deploy +2. **Wartbarkeit:** Klare Trennung Data vs. Interpretation +3. **Erweiterbarkeit:** Gleiche Rohdaten für mehrere Prompts nutzbar +4. **Testbarkeit:** Rohdaten-Struktur separat testbar +5. **Transparenz:** Rohdaten sichtbar (Debug-Modus) +6. **Iterierbarkeit:** KI-Interpretation kann verfeinert werden + +#### Implementierungs-Aufwand: + +- **Data Layer Funktion:** 2h (get_goal_priority_raw_data) +- **Placeholder Registration:** 10min (einfacher Lambda) +- **Basis-Prompt:** 1h (Prompt-Engineering + Testing) +- **TOTAL:** 3h + +#### Anwendbar für: + +- P31: goal_weighted_priority ✅ +- P32: main_constraint ✅ +- P33: main_strength ✅ +- P34: next_best_actions ⚠️ (braucht mehr Domänen-Wissen) + +--- + +## 4. Datenqualität & Edge Cases + +### Problem: Activities ohne quality_label (NULL values) +- **Beschreibung:** Alte Activities oder Imports haben kein quality_label → NULL +- **Betroffene Platzhalter:** ALLE activity-bezogenen Platzhalter +- **Vorschlag:** Wie sollte damit umgegangen werden? + - [x] NULL behandeln als "uncategorized" (separate Kategorie) + - [x] Bei quality_filter: NULL = excluded (nicht mitzählen) + - [ ] Batch-Evaluation für alte Activities nachholen + - [ ] Warnung in confidence_score wenn >20% NULL + +### Problem: Quality-Filter zu strikt für manche Platzhalter +- **Beschreibung:** Bei "excellent" only → zu wenig Daten für Statistik +- **Betroffene Platzhalter:** `{{activity_summary_excellent}}`, etc. +- **Vorschlag:** Wie sollte damit umgegangen werden? + - [x] Confidence-Score anpassen (insufficient wenn <3 activities) + - [x] Fallback-Message: "Nur 2 excellent Sessions in 14 Tagen (zu wenig für Statistik)" + - [ ] Zeitraum automatisch erweitern (14d → 28d → 90d) + +### Problem: quality_label vs. user quality_filter_level +- **Beschreibung:** Es gibt zwei Quality-Konzepte, die verwechselt werden können: + - `quality_label` (DB-Spalte): Evaluation-Ergebnis pro Activity (excellent, good, acceptable, poor, excluded) + - `quality_filter_level` (Profile-Einstellung): User-Präferenz für globalen Filter (all, quality, very_good, excellent) +- **Betroffene Platzhalter:** Alle activity-bezogenen Platzhalter +- **Vorschlag:** + - [x] Platzhalter verwenden IMMER default='quality' (acceptable+) → konsistent für KI-Analysen + - [x] User quality_filter_level nur für manuelle UI-Filter (Charts, Activity Page) + - [x] Keine Vermischung: Platzhalter = fix 'quality', UI = user preference + - [ ] Dokumentation: Klarstellung der beiden Konzepte + +--- + +## 5. Placeholder-Kategorisierung & Organisation + +### Vorschlag für neue Kategorien: +- [ ] Kategorie-Name: ___ + - Platzhalter: `{{...}}`, `{{...}}` + - Begründung: ___ + +### Umstrukturierung existierender Kategorien: +- [ ] Von Kategorie X nach Y verschieben: `{{placeholder}}` + - Begründung: ___ + +--- + +## 6. Dokumentation & Metadaten + +### Fehlende Beschreibungen: +- `{{placeholder}}` - Beschreibung fehlt oder ist unklar + +### Fehlende Beispiele: +- `{{placeholder}}` - Beispiel-Output fehlt + +--- + +## 7. Prioritäten & Abhängigkeiten + +### Must-Have (kritisch für Prompt-Qualität): + +#### Gap #1: Quality Label +1. **Quality-Filter in data_layer/activity_metrics.py** - ALLE Funktionen betroffen + - Breaking Change, aber notwendig für korrekte Auswertungen + - Dauer: 4-6h (9 Funktionen + Tests) + - Blockiert: Alle neuen Platzhalter +2. **Neue Platzhalter für Evaluation-Breakdown** + - `{{evaluation_breakdown}}` - Übersicht über Qualitätsverteilung + - Dauer: 1h (Funktion + Platzhalter) +3. **Placeholder-Updates in placeholder_resolver.py** + - Alle activity-Platzhalter auf quality='quality' umstellen + - Dauer: 2h (Updates + Testen) + +**Teilsumme Gap #1:** 7-9h + +#### Gap #2: Training Categories +4. **Category-Specific Activity Metrics (data_layer)** + - `get_category_activity_data(category)` - Count/Minutes pro Kategorie + - Dauer: 2h (1 Funktion, alle Kategorien abdecken) +5. **Kategorie-spezifische Platzhalter (Top Priority)** + - `{{strength_training_count_7d}}`, `{{strength_training_minutes_7d}}` + - `{{cardio_training_count_7d}}`, `{{cardio_training_minutes_7d}}` + - `{{strength_frequency_adequate}}`, `{{muscle_preservation_risk}}` + - Dauer: 3h (6-8 Platzhalter, kritisch für Muskelerhalt-Coaching) +6. **Days Since Last Training (per Category)** + - `get_days_since_last_training(category)` - Funktion + - `{{days_since_last_strength}}`, `{{days_since_last_cardio}}`, etc. + - Dauer: 2h (Funktion + 3-4 Platzhalter) +7. **Training Balance Calculator** + - `get_training_balance_data()` - Balance-Score + Ratios + - `{{cardio_to_strength_ratio}}`, `{{training_balance_score}}`, `{{missing_training_categories}}` + - Dauer: 2-3h (Funktion + 3 Platzhalter) + +**Teilsumme Gap #2:** 9-10h + +**TOTAL Must-Have:** 16-19h + +### Should-Have (sinnvolle Ergänzungen): + +#### Gap #1: Quality Label +1. **Dedizierte Quality-Platzhalter** + - `{{activity_summary_acceptable_plus}}`, `{{activity_summary_excellent}}` + - Dauer: 2h (4-5 neue Platzhalter) +2. **Poor Sessions Warning Platzhalter** + - `{{poor_sessions_count}}`, `{{poor_sessions_warning}}` + - Dauer: 1h + +#### Gap #2: Training Categories +3. **Weitere Kategorie-Platzhalter** + - `{{martial_arts_frequency_28d}}`, `{{recovery_sessions_count_28d}}`, `{{mobility_sessions_count_28d}}` + - Dauer: 2h (3-4 Platzhalter) +4. **Subcategory Distribution** + - `get_subcategory_distribution(category)` - Funktion + - `{{strength_type_distribution}}`, `{{hypertrophy_vs_maxstrength_ratio}}`, `{{technique_vs_sparring_balance}}` + - Dauer: 3h (Funktion + 3 Platzhalter) +5. **NULL-Handling für alte Activities** + - Batch-Evaluation Script für Activities ohne quality_label + - Dauer: 2h + +**Teilsumme Should-Have:** 10h + +### Nice-to-Have (optional): +1. **Quality-Weighted Metrics** + - `{{training_minutes_quality_weighted}}` - gewichtet nach Qualität + - Dauer: 1h +2. **Auto-Timeframe-Expansion** + - Bei insufficient data: automatisch von 14d → 28d → 90d erweitern + - Dauer: 3h (Logik + alle Funktionen anpassen) +3. **Advanced Subcategory Analysen** + - Periodisierungs-Erkennung (Maximalkraft-Phase → Hypertrophie-Phase?) + - Dauer: 2-3h + +**Teilsumme Nice-to-Have:** 6-7h + +### Geschätzte Gesamt-Dauer (Original): +- **Must-Have (Gap #1 + #2):** 16-19h (beide Gaps kritisch!) +- **Should-Have:** 10h +- **Nice-to-Have:** 6-7h +- **TOTAL (ohne User Requirements):** 32-36h + +### Gesamt-Dauer (mit User Requirements P1-P27): +- **Sprint 1 (User Req + Governance):** 34-41h +- **Sprint 2 (Gap #1 + #2):** 26-32h +- **Sprint 3 (Optional):** 12-16h +- **TOTAL (inkl. User Requirements):** 72-89h + +**Empfehlung:** Sprint 1 + Sprint 2 parallel bearbeiten wo möglich (viele unabhängige Tasks) + +### Abhängigkeiten: +1. Quality-Filter in activity_metrics.py (MUSS zuerst) +2. Dann: Neue Platzhalter können parallel implementiert werden +3. Dann: Tests für alle Änderungen +4. Letzter Schritt: Bestehende Prompts auf neue Platzhalter umstellen + +--- + +## Notizen + +### Technische Details: quality_label System + +**Verfügbare Quality Labels (activity_log.quality_label):** +- `'excellent'` - ⭐ Exzellentes Training (z.B. HR in Zone, Dauer optimal, RPE passend) +- `'very_good'` - ✓✓ Sehr gutes Training +- `'good'` - ✓ Gutes Training +- `'acceptable'` - ✓ Akzeptables Training (Mindestschwelle) +- `'poor'` - ⚠ Schlechtes Training (zu kurz, HR zu niedrig, etc.) +- `'excluded'` - ❌ Auszuschließen (fehlerhafte Messung, Dummy-Eintrag) +- `NULL` - Nicht evaluiert (alte Einträge, Imports ohne Evaluation) + +**Quality Filter Levels (für User-Präferenz, nicht Platzhalter!):** +- `'all'` - Alle Activities (kein Filter) +- `'quality'` - Hochwertig (acceptable+) ← **DEFAULT für Platzhalter** +- `'very_good'` - Sehr gut+ (excellent, good) +- `'excellent'` - Nur exzellent + +**Bestehende Infrastruktur (quality_filter.py):** +```python +get_quality_filter_sql(profile, table_alias='') +# Returns: "AND quality_label IN ('excellent', 'good', 'acceptable')" + +get_quality_filter_tuple(profile) +# Returns: ('excellent', 'good', 'acceptable') + +filter_activities_by_quality(activities, profile) +# Post-query filtering +``` + +**Migration Path:** +1. Alle data_layer Funktionen erweitern um `quality_level='quality'` Parameter +2. Alle Platzhalter updaten: `lambda pid: get_activity_summary_data(pid, quality_level='quality')` +3. Neue Platzhalter für andere Levels: `{{activity_summary_excellent}}` +4. Tests schreiben für alle Kombinationen +5. Bestehende Prompts prüfen und ggf. anpassen + +### Verfügbare Trainings-Kategorien & Subcategories: + +**7 Hauptkategorien (training_category):** +1. **cardio** → Ausdauer + - Subcategories: running, cycling, swimming, rowing, other +2. **strength** → Kraft + - Subcategories: hypertrophy, maxstrength, endurance, functional +3. **hiit** → Schnellkraft + - Subcategories: hiit, explosive, circuit +4. **martial_arts** → Kampfsport + - Subcategories: technique, sparring, strength +5. **mobility** → Beweglichkeit + - Subcategories: static, dynamic, yoga, fascia +6. **recovery** → Erholung (aktiv) + - Subcategories: walk, swim_light, regeneration +7. **other** → Sonstiges + +**Abilities-Mapping (JSONB in training_types.abilities):** +- strength, endurance, mental, coordination, mobility + +**Bestehende Platzhalter (nutzen bereits Categories):** +- `{{trainingstyp_verteilung}}` - Top 3 Kategorien mit % +- `{{ability_balance_strength}}` - Ability-Score 0-100 (nutzt abilities JSONB) +- `{{ability_balance_endurance}}` - Ability-Score 0-100 + +**Migration Path Training Categories:** +1. Neue data_layer Funktionen: get_category_activity_data(), get_days_since_last_training(), etc. +2. Platzhalter für JEDE Kategorie (mindestens count + minutes für strength/cardio) +3. Balance-Funktionen (Ratios, fehlende Kategorien) +4. Subcategory-Analysen (innerhalb strength, martial_arts) +5. Tests für alle Kombinationen +6. Bestehende Prompts erweitern mit neuen Platzhaltern + +--- + +### Offene Fragen: + +#### Gap #1: Quality Label +1. **Breaking Change akzeptabel?** Zahlen ändern sich bei allen activity-Platzhaltern +2. **NULL-Handling:** Als 'excluded' behandeln oder separate Kategorie? +3. **Batch-Evaluation:** Alte Activities nachträglich evaluieren? (Performance-Impact?) +4. **Confidence-Anpassung:** Thresholds senken wenn quality_filter aktiv? (weniger Daten) + +#### Gap #2: Training Categories +5. **Ruhepausen-Thresholds:** Welche Schwellenwerte pro Kategorie? + - Kraft: >3d warning, >7d critical? + - Cardio: >2d warning, >5d critical? + - Kampfsport: >7d warning, >14d critical? +6. **Balance-Definition:** Was ist "gut balanciert"? + - Cardio:Kraft 2:1 bis 1:1 ok? + - Mobility mindestens 5% aller Trainings? +7. **Subcategory-Priorität:** Welche Subcategories zuerst? + - Kraft (hypertrophy/maxstrength/endurance) → Top Priority + - Kampfsport (technique/sparring) → Nice-to-Have? +8. **Missing Categories Logic:** Welche Kategorien sind "Pflicht"? + - Core: cardio, strength, mobility + - Optional: recovery, hiit, martial_arts? + +### User Requirements Summary (P1-P27): + +**Sprint 1 - Must-Have (10 Core Items):** +1. goal_summary_json → vollständige Zielübersicht +2. focus_area_summary_json → strukturierte Focus Areas +3. domain_availability_json → Verfügbarkeit pro Domäne +4. body_change_summary_json → Körperentwicklung strukturiert +5. activity_structure_json → Training strukturiert +6. sleep_summary_json → Schlaf strukturiert +7. recovery_summary_json → Erholung strukturiert +8. vitals_summary_json → Vitalwerte strukturiert +9. correlation_summary_json → Korrelationen strukturiert +10. plateau_status + top_drivers → Diagnose strukturiert + +**Sprint 1 - Governance (4 Items):** +- Placeholder Versioning System +- "nicht verfügbar" → null Migration +- Zeitfenster-Audit & Renaming +- Availability Flags (systematic) + +**Sprint 2 - Energy & Risk:** +- underfueling_risk_flag + energy_availability_summary +- training_quality_score (aggregiert) +- load_balance_class (klassifiziert) + +**Sprint 3 - Zwei-Stufen-Architektur (P31-P34):** +- goal_weighted_priority (✅ möglich via 2-Stufen) +- main_constraint (✅ möglich via 2-Stufen) +- main_strength (✅ möglich via 2-Stufen) +- next_best_actions (⚠️ teilweise, braucht Domänen-Wissen) + +**Sprint 3 - Optional:** +- blood_pressure_summary_json (niedrigere Priorität) + +### User Input benötigt: + +#### Gap #1: Quality Label +- [ ] Strategie-Entscheidung: Breaking Change (Option A) vs. Non-Breaking (Option B)? +- [ ] NULL-Handling: excluded oder uncategorized? +- [ ] Batch-Evaluation: Ja/Nein? + +#### Gap #2: Training Categories +- [ ] Ruhepausen-Thresholds definieren (pro Kategorie) +- [ ] Balance-Kriterien festlegen (Ratios, Mindest-%) +- [ ] Subcategory-Priorität: Kraft zuerst, dann Kampfsport? Oder parallel? +- [ ] Muskelerhalt-Schwelle: 2x/Woche Kraft ok? Oder 3x? + +#### User Requirements (P1-P27): +- [ ] Sprint-Priorisierung: Sprint 1 + Sprint 2 parallel? Oder sequentiell? +- [ ] Sprint 3: Zwei-Stufen-Platzhalter (P31-P34) gewünscht? Oder später? +- [ ] Governance: Placeholder Versioning System sofort einführen oder später? +- [ ] Breaking Changes: "nicht verfügbar" → null jetzt oder schrittweise? +- [ ] JSON-Struktur-Review: Beispiel-Strukturen (siehe P1-P27) absegnen? + +#### Zwei-Stufen-Architektur (⭐ NEU): +- [ ] **Architektur-Approval:** Zwei-Stufen-Ansatz (Data + KI) für P31-P34 ok? +- [ ] **Basis-Prompts:** Wer erstellt/reviewed die Basis-Prompts? (User oder Claude?) +- [ ] **Prompt-Governance:** Wie werden Basis-Prompts versioniert/getestet? +- [ ] **Raw Data Sichtbarkeit:** Sollen Raw Data Placeholders auch in UI-Export sichtbar sein? +- [ ] **Iterative Verfeinerung:** Basis-Prompts initial "good enough" oder erst perfekt dann deployen? + +--- + +## 📊 Finale Zusammenfassung: Impact der Fit/Gap-Analyse + +### Vor der Analyse (Original): +- **Gap #1 (Quality Label):** 11-13h +- **Gap #2 (Training Categories):** 11-13h +- **TOTAL:** 22-26h +- **User Requirements:** Nicht bewertet +- **P31-P34:** Als "nicht möglich" eingestuft + +### Nach der Analyse (aktualisiert): +- **Sprint 1 (User Req + Governance):** 34-41h +- **Sprint 2 (Gap #1 + #2):** 26-32h +- **Sprint 3 (Zwei-Stufen-Architektur):** 12-16h +- **TOTAL:** 72-89h +- **Alle 27 User Requirements:** ✅ Umsetzbar! +- **P31-P34:** ✅ Möglich via Zwei-Stufen-Architektur + +### Wichtigste Erkenntnisse: + +#### ✅ Architektur-Durchbruch: +**Zwei-Stufen-Architektur** löst das Problem komplexer Interpretations-Platzhalter: +- Data Layer stellt strukturierte Rohdaten bereit +- KI interpretiert via Basis-Prompts +- Flexibler, wartbarer, erweiterbarer als hardcodierte Logik + +#### ✅ Vollständige Umsetzbarkeit: +**Alle 27 User Requirements (P1-P34) sind umsetzbar:** +- 20 Items direkt möglich (mit aktueller Datenstruktur) +- 4 Items möglich via Zwei-Stufen-Architektur +- 3 Items teilweise möglich (einfache Erweiterungen) +- Nur P6 redundant, P30 optional + +#### ✅ Governance-Framework: +**8 verbindliche Governance-Regeln** für stabile Placeholder-API: +- Platzhalter = API-Verträge +- Keine stillschweigenden Änderungen +- JSON vor Freitext +- Zwei-Stufen-Architektur für Interpretationen + +#### ✅ Klare Sprint-Struktur: +**3 Sprints mit klaren Zielen:** +- Sprint 1: Foundation (User Req + Governance) +- Sprint 2: Quality + Categories (kritische Gaps) +- Sprint 3: Interpretations-Platzhalter (Zwei-Stufen) + +### Empfohlenes Vorgehen: + +1. **User-Entscheidungen einholen** (siehe "User Input benötigt") +2. **Sprint 1 starten** (strukturierte JSONs + Governance) +3. **Sprint 2 parallel** (Quality + Categories, unabhängig von Sprint 1) +4. **Sprint 3 nach Approval** (Zwei-Stufen-Architektur) + +### Geschätzter Gesamtaufwand: +- **Minimum:** 72h (optimistisch, parallele Arbeit) +- **Realistisch:** 89h (inkl. Testing, Iterationen, Reviews) +- **Mit Buffer:** 100-110h (Puffer für Unvorhergesehenes) + +### ROI / Nutzen: +- ✅ **Stabile Placeholder-API** für professionelle Prompt-Bibliothek +- ✅ **Alle 27 User Requirements** erfüllt +- ✅ **Zukunftssichere Architektur** (Zwei-Stufen erweiterbar) +- ✅ **Quality + Categories Gaps** geschlossen (kritisch für KI-Coaching) +- ✅ **Governance-Framework** verhindert technische Schulden + +--- + +**Status:** 🔴 Draft - Bereit für User Review & Entscheidungen +**Nächster Schritt:** User Input einholen → Umsetzungskonzept erstellen → Sprint 1 starten diff --git a/.claude/docs/functional/RESPONSIVE_UI.md b/.claude/docs/functional/RESPONSIVE_UI.md new file mode 100644 index 0000000..899e465 --- /dev/null +++ b/.claude/docs/functional/RESPONSIVE_UI.md @@ -0,0 +1,228 @@ +# Fachliche Anforderungen: Responsive UI +**Modul:** parallel zu Features (kein festes Versions-Label) +**Status:** Fachlich freigegeben, technische Implementierung ausstehend +**Letzte Aktualisierung:** März 2026 + +--- + +## 1. Überblick + +Die App soll vollständig responsive sein und auf allen Bildschirmgrößen optimal +funktionieren – vom iPhone über iPad bis zum Desktop-Browser. Die PWA-Funktionalität +auf dem iPhone bleibt vollständig erhalten. + +--- + +## 2. Breakpoints + +| Bereich | Breite | Layout | +|---------|--------|--------| +| Mobile | < 1024px | Bottom Navigation (wie jetzt) | +| Desktop | ≥ 1024px | Sidebar Navigation links, Content rechts | + +Ein separates Tablet-Layout (768px) wird **nicht** eingeführt – unterhalb +1024px verhält sich alles wie Mobile. + +--- + +## 3. Mobile Layout (< 1024px) – unverändert + +Das bestehende Mobile-Layout bleibt exakt so wie es ist: +- Bottom Navigation mit Icons + Labels +- Volle Breite, 16px seitliches Padding +- 80px Bottom-Padding für Navigation +- PWA auf iPhone funktioniert wie bisher + +--- + +## 4. Desktop Layout (≥ 1024px) + +### 4.1 Grundstruktur + +``` +┌─────────────────────────────────────────────┐ +│ Sidebar (220px) │ Content (flex 1) │ +│ │ │ +│ [Logo] │ Seiteninhalt │ +│ │ (max-width: 1200px, │ +│ Dashboard │ zentriert) │ +│ Erfassung │ │ +│ Verlauf │ │ +│ Analyse │ │ +│ Einstellungen │ │ +│ Admin │ │ +│ │ │ +│ [Avatar + Name] │ │ +└─────────────────────────────────────────────┘ +``` + +### 4.2 Sidebar + +**Position:** Fixed links, volle Höhe (100vh) +**Breite:** 220px +**Farben:** `var(--surface)` Hintergrund, `var(--border)` rechte Trennlinie + +**Inhalt von oben nach unten:** + +``` +┌──────────────────┐ +│ [Ensō-Logo] │ ← Icon (40px) + "Mitai Jinkendo" Text +│ Mitai Jinkendo │ +├──────────────────┤ +│ │ +│ 🏠 Dashboard │ ← Icons + Text (gleiche Nav-Items wie Bottom Nav) +│ ✏️ Erfassung │ ← Aktive Seite: var(--accent) Hintergrund (leicht) +│ 📊 Verlauf │ + var(--accent) Text + linke Akzent-Linie (3px) +│ 🧠 Analyse │ +│ ⚙️ Einstellungen│ +│ 👑 Admin │ ← nur sichtbar wenn role='admin' +│ │ +├──────────────────┤ +│ [Avatar] │ ← Nutzer-Avatar (32px) + Name + Tier-Badge +│ Lars │ +│ selfhosted │ +└──────────────────┘ +``` + +**Aktive Seite hervorheben:** +```css +/* Aktiver Nav-Item Desktop */ +background: var(--accent-light); /* #E1F5EE leicht grüner Hintergrund */ +color: var(--accent); /* #1D9E75 grüner Text */ +border-left: 3px solid var(--accent); /* linke Akzent-Linie */ +border-radius: 0 8px 8px 0; +``` + +### 4.3 Content-Bereich + +- `margin-left: 220px` (Sidebar-Breite) +- `padding: 24px 32px` +- `max-width: 1200px` (zentriert innerhalb des verfügbaren Raums) +- Kein Bottom-Padding für Navigation (Sidebar ersetzt Bottom Nav) + +--- + +## 5. Seiten-spezifische Desktop-Layouts + +### 5.1 Dashboard + +**Mobile:** Alle Karten untereinander +**Desktop:** 2-spaltig oder 4-spaltig je nach Kartentyp + +``` +┌──────────────────────────────────────────────┐ +│ Hallo Lars 👋 Mo, 20. März 2026 │ +├──────────┬──────────┬──────────┬─────────────┤ +│ Gewicht │ KF │ Mager- │ Kalorien │ +│ 86,1 kg │ 19,9% │ masse │ 1.447 kcal │ +├──────────┴──────────┴──────────┴─────────────┤ +│ Ziele [Details →] │ +│ ████████░░ Gewicht: 27% ████░░░░ KF: 45% │ +├──────────────────────────────────────────────┤ +│ Kalorien + Gewicht (Chart – volle Breite) │ +└──────────────────────────────────────────────┘ +``` + +### 5.2 Verlauf + +**Mobile:** Tabs oben, Chart/Tabelle darunter +**Desktop:** Tabs links als vertikale Liste, Chart/Tabelle rechts + +``` +┌────────────┬─────────────────────────────────┐ +│ Gewicht ← │ Chart + Tabelle │ +│ Körperfett │ (nimmt volle rechte Breite) │ +│ Umfänge │ │ +│ Ernährung │ │ +│ Aktivität │ │ +└────────────┴─────────────────────────────────┘ +``` + +### 5.3 Analyse + +**Mobile:** Prompt-Liste, dann Ergebnis darunter +**Desktop:** Prompt-Liste links (300px), Ergebnis rechts + +``` +┌──────────────────┬──────────────────────────┐ +│ Prompts │ Ergebnis / Verlauf │ +│ ───────────── │ │ +│ 🔍 Gesamt │ [Letzte Analyse] │ +│ 🫧 Körper ← │ [Text der Analyse...] │ +│ 🍽️ Ernährung │ │ +│ 🏋️ Training │ [Ältere Analysen] │ +│ ───────────── │ │ +│ Pipeline ▶ │ │ +└──────────────────┴──────────────────────────┘ +``` + +### 5.4 Erfassung + +**Mobile:** Formular volle Breite +**Desktop:** Formular zentriert, max-width 600px + +``` +┌──────────────────────────────────────────────┐ +│ Gewicht erfassen │ +│ │ +│ ┌─────────────────────────────┐ │ +│ │ Datum [20.03.2026] │ │ +│ │ Gewicht [86,1 ] kg │ │ +│ │ Notiz [ ] │ │ +│ │ │ │ +│ │ [Speichern] │ │ +│ └─────────────────────────────┘ │ +└──────────────────────────────────────────────┘ +``` + +### 5.5 Admin-Panel + +**Mobile:** Tabellen horizontal scrollbar +**Desktop:** Tabellen nutzen volle Breite, mehr Spalten sichtbar + +--- + +## 6. Übergangsverhalten + +### Fenster-Resize +- Beim Verkleinern unter 1024px: Sidebar verschwindet, Bottom Nav erscheint +- Beim Vergrößern über 1024px: Bottom Nav verschwindet, Sidebar erscheint +- Kein Flackern – CSS media queries, kein JavaScript-Resize-Listener nötig + +### Aktiver Zustand synchronisiert +- Aktive Seite wird in Sidebar UND Bottom Nav korrekt hervorgehoben +- (Technisch: gleiche State-Variable, unterschiedliches Rendering) + +--- + +## 7. Was sich nicht ändert + +- Farben, CSS-Variablen, Design-Tokens bleiben identisch +- Alle Funktionen bleiben auf Desktop verfügbar +- PWA auf iPhone bleibt vollständig funktionsfähig +- Keine externen CSS-Frameworks (kein Tailwind, kein Bootstrap) +- Inline-Styles + CSS-Variablen-Ansatz bleibt erhalten + +--- + +## 8. Abgrenzung & offene Fragen + +### In diesem Modul enthalten: +- Sidebar Navigation Desktop +- Content-Bereich mit max-width +- Dashboard 4-spaltig +- Verlauf 2-spaltig (Tabs + Chart) +- Analyse 2-spaltig (Prompts + Ergebnis) +- Erfassung zentriert schmal +- Admin-Panel breite Tabellen + +### Nicht in diesem Modul: +- Dark Mode – separates Feature +- Animationen / Transitions – optional später +- Collapsed Sidebar (Icon-only Modus) – optional später + +### Offene Fragen für technische Planung: +1. Wird AppLayout als neue Wrapper-Komponente eingeführt oder App.jsx direkt angepasst? +2. CSS Media Queries in app.css oder CSS-in-JS (Inline-Style mit window.innerWidth)? +3. Wie wird der aktive Nav-Item-State zwischen Sidebar und Bottom Nav geteilt? +4. Verlauf-Tabs: werden sie zu einer vertikalen Liste refactored oder per CSS umgestaltet? \ No newline at end of file diff --git a/.claude/docs/functional/SLEEP_MODULE.md b/.claude/docs/functional/SLEEP_MODULE.md new file mode 100644 index 0000000..79cdf48 --- /dev/null +++ b/.claude/docs/functional/SLEEP_MODULE.md @@ -0,0 +1,227 @@ +# Fachliche Anforderungen: Schlaf-Modul +**Modul:** v9d +**Status:** Fachlich freigegeben, technische Implementierung ausstehend +**Letzte Aktualisierung:** März 2026 + +--- + +## 1. Überblick + +Das Schlaf-Modul ist ein eigenständiges Tracking-Modul für tägliche Schlafdaten. +Es ergänzt die bestehenden Körper-, Ernährungs- und Aktivitäts-Module um eine +zentrale Gesundheitsdimension. Schlaf wird sowohl manuell erfasst als auch aus +Apple Health / Garmin importiert. Korrelationen mit Ruhepuls, Training und Gewicht +fließen in die KI-Analyse ein. + +--- + +## 2. Erfasste Daten + +### 2.1 Pflichtfelder (Schnelleingabe) +- **Datum** – welche Nacht (Standard: letzte Nacht) +- **Schlafdauer** – in Stunden und Minuten (berechnet aus Ein-/Aufwachzeit oder direkt) +- **Schlafqualität** – subjektiv 1–5 (1 = sehr schlecht, 5 = sehr gut) + +### 2.2 Optionale Felder (Detaileingabe) +- **Einschlafzeit** – Uhrzeit (z.B. 23:15) +- **Aufwachzeit** – Uhrzeit (z.B. 06:45) +- **Aufwach-Häufigkeit** – wie oft in der Nacht aufgewacht (0–10+) +- **Schlafphasen** – Dauer in Minuten je Phase: + - Tiefschlaf + - REM-Schlaf + - Leichtschlaf + - Wach (in Bett) +- **Notiz** – Freitext (z.B. "Stress, schlechte Nacht", "früh ins Bett") + +### 2.3 Abgeleitete Felder (automatisch berechnet) +- **Schlafdauer** – aus Einschlafzeit + Aufwachzeit wenn nicht direkt eingegeben +- **Schlafeffizienz** – Schlafdauer / Zeit im Bett × 100% (wenn Phasen vorhanden) +- **Tiefschlaf-Anteil %** – Tiefschlaf / Gesamtschlaf × 100% +- **REM-Anteil %** – REM / Gesamtschlaf × 100% + +--- + +## 3. Erfassungs-Modi + +### 3.1 Schnelleingabe (≤ 30 Sekunden) +Minimaler Aufwand für den täglichen Gebrauch: +``` +Datum: [gestern / heute Nacht] +Schlafdauer: [7] h [30] min +Qualität: ★★★★☆ (1-5 Sterne) +[Speichern] +``` + +### 3.2 Detaileingabe (alle Felder) +Vollständige Erfassung für Power-User oder wenn Gerätedaten vorliegen: +``` +Datum: [22.03.2026] +Eingeschlafen: [23:15] +Aufgewacht: [06:45] +Schlafdauer: [7h 30min] (automatisch berechnet) +Qualität: ★★★★☆ +Aufwachungen: [1] +Tiefschlaf: [95] min +REM-Schlaf: [110] min +Leichtschlaf: [230] min +Wach im Bett: [15] min +Notiz: [...] +[Speichern] +``` + +### 3.3 Morgendlicher Check-in +- Beim Öffnen der App morgens optional: "Wie hast du geschlafen?" +- Zeigt Schnelleingabe direkt auf dem Dashboard +- Kann in den Einstellungen aktiviert/deaktiviert werden +- Reminder-Logik: nur anzeigen wenn noch kein Eintrag für die letzte Nacht + +### 3.4 Import (Apple Health / Garmin) +- Beim CSV/Health-Import werden Schlafdaten automatisch erkannt und zugeordnet +- Importierte Daten befüllen alle verfügbaren Felder (Phasen wenn vorhanden) +- Manuelle Eingabe ergänzt oder überschreibt Importdaten für denselben Tag +- Quelle wird gespeichert (manuell / apple_health / garmin) + +--- + +## 4. Navigation & Dashboard + +### 4.1 Eigene Seite in der Navigation +- Neuer Nav-Eintrag: **Schlaf** (zwischen Verlauf und Analyse) +- Icon: Mond oder Bett-Symbol +- Zeigt: Letzte 7 Tage Übersicht + Verlauf + Eingabe-Button + +### 4.2 Dashboard-Widget +- Kompaktes Widget auf dem Dashboard (unter den Hauptkennzahlen) +- Zeigt: Letzte Nacht (Dauer + Qualität) + 7-Tage-Durchschnitt +- Klick öffnet die Schlaf-Seite +- Wenn noch kein Eintrag heute: "Schlaf erfassen" Button + +--- + +## 5. Auswertungen + +### 5.1 Schlafdauer-Trend +- Zeitraum wählbar: 7 / 30 / 90 Tage +- Liniengrafik: tägliche Schlafdauer +- Referenzlinie: persönliches Schlafziel (konfigurierbar in Einstellungen) +- Durchschnittslinie: gleitender 7-Tage-Durchschnitt +- Farbcodierung: unter Ziel = orange/rot, über Ziel = grün + +### 5.2 Schlafqualität-Trend +- Liniengrafik: tägliche Qualität (1–5) +- 7-Tage-Durchschnitt als Referenzlinie +- Kombiniert mit Schlafdauer in einer Grafik (zwei Y-Achsen) + +### 5.3 Schlafphasen-Verteilung +- Balkengrafik: Tiefschlaf / REM / Leichtschlaf / Wach je Nacht +- Durchschnittliche Verteilung über Zeitraum +- Referenzwerte: empfohlene Anteile (Tiefschlaf 15-25%, REM 20-25%) +- Nur sichtbar wenn Phasendaten vorhanden + +### 5.4 Schlafschulden-Berechnung +- Definition: Differenz zwischen Schlafziel und tatsächlichem Schlaf (kumuliert) +- Zeitraum: letzte 7 / 14 Tage +- Anzeige: "+2h 15min Schlafschuld" oder "0 – kein Defizit" +- Positiver Saldo (Übererfüllung) wird als Puffer angezeigt + +### 5.5 Optimales Schlaffenster +- Berechnung aus Einschlafzeit + Schlafqualität über Zeit +- Erkennt wann Schlaf am besten war (z.B. "Beste Qualität bei Einschlafzeit 22–23 Uhr") +- Mindestdatenbasis: 14 Einträge mit Einschlafzeit + +### 5.6 Korrelationen +Alle Korrelationen benötigen mindestens 14 gemeinsame Datenpunkte: + +**Schlaf ↔ Ruhepuls:** +- Streudiagramm: Schlafdauer / Qualität vs. Ruhepuls nächster Morgen +- KI-Aussage: "Nach <7h Schlaf ist dein Ruhepuls Ø X bpm höher" + +**Schlaf ↔ Trainingsleistung:** +- Vergleich: Trainingsintensität/-volumen nach gut vs. schlecht geschlafenen Nächten +- KI-Aussage: "Nach Nächten mit >7,5h Schlaf trainierst du Ø X% länger/intensiver" + +**Schlaf ↔ Gewicht:** +- Korrelation: Schlafdauer vs. Gewichtsentwicklung (wöchentlich) +- KI-Aussage: "In Wochen mit <6h Schlaf im Schnitt +X kg Gewichtsschwankung" + +--- + +## 6. Schlafziel (konfigurierbar) + +- In den Einstellungen: "Mein Schlafziel" → Stunden + Minuten +- Standard: 7h 30min +- Wird als Referenzlinie in allen Grafiken angezeigt +- Fließt in Schlafschulden-Berechnung ein +- KI nutzt den Wert als Basis für Schlafmangel-Warnung + +--- + +## 7. KI-Analyse + +### 7.1 Neue KI-Platzhalter +``` +{{schlaf_avg_dauer}} → "7h 12min (Ø 7 Tage)" +{{schlaf_avg_qualitaet}} → "3,8/5 (Ø 7 Tage)" +{{schlaf_trend_dauer}} → "sinkend / stabil / steigend" +{{schlaf_trend_qualitaet}} → "sinkend / stabil / steigend" +{{schlaf_schulden}} → "+2h 15min (letzte 7 Tage)" +{{schlaf_ziel}} → "7h 30min" +{{schlaf_optimum_fenster}} → "22:00–23:00 Uhr (beste Qualität)" +{{schlaf_tiefschlaf_anteil}} → "18% (Ø letzte 14 Tage)" +{{schlaf_rem_anteil}} → "22% (Ø letzte 14 Tage)" +{{schlaf_aufwachungen}} → "1,2x pro Nacht (Ø 7 Tage)" +{{schlaf_korrelation_puls}} → "Ruhepuls +4 bpm nach <7h Schlaf" +{{schlaf_detail}} → tabellarische Übersicht letzte 7 Nächte +``` + +### 7.2 KI-Analyse-Funktionen + +**Schlafmangel erkennen und warnen:** +- Trigger: Schlafdurchschnitt letzte 3 Tage < (Schlafziel – 60min) +- KI-Ausgabe: Warnung + konkrete Empfehlung +- Beispiel: "Du schläfst seit 3 Tagen unter deinem Ziel. Schlafschuld: +2h 15min." + +**Schlaf ↔ Trainingsleistung korrelieren:** +- Automatische Berechnung sobald genug Daten (14+ Einträge beider Module) +- KI-Ausgabe: narrative Korrelationsbeschreibung mit konkreten Zahlen + +**Optimales Schlaffenster empfehlen:** +- Basierend auf historischen Einschlafzeiten + Qualitäten +- KI-Ausgabe: "Deine beste Schlafqualität erzielst du wenn du zwischen 22–23 Uhr einschläfst" + +**Schlaf ↔ Gewicht:** +- Wöchentliche Korrelation +- KI-Ausgabe: "In Wochen mit mehr Schlaf zeigt dein Gewicht weniger Schwankungen" + +**Schlafphasen-Verteilung kommentieren:** +- Nur wenn Phasendaten vorhanden +- Referenz: Tiefschlaf 15-25%, REM 20-25%, Leichtschlaf 50-60% +- KI-Ausgabe: "Dein Tiefschlaf-Anteil liegt bei X% – das ist [über/unter/im] empfohlenen Bereich" + +--- + +## 8. Abgrenzung & offene Fragen + +### In diesem Modul enthalten: +- Tägliche Schlaferfassung (Schnell + Detail) +- Dashboard-Widget + eigene Navigationsseite +- Morgendlicher Check-in (optional) +- Import aus Apple Health / Garmin CSV +- 5 Auswertungsansichten inkl. Korrelationen +- Konfigurierbares Schlafziel +- 12 neue KI-Platzhalter +- 5 KI-Analyse-Funktionen + +### Nicht in diesem Modul: +- Automatischer Live-Sync aus Garmin/Apple Watch → v9h (Connectoren) +- Schlaf-Coaching / Schlafhygiene-Tipps → optional später +- Schnarchen / Atemaussetzer (SpO2) → Teil Vitalwerte v9e +- Push-Notification "Schlafenszeit-Reminder" → optional später + +### Offene Fragen für technische Planung: +1. Neue DB-Tabelle `sleep_log` oder Erweiterung von `activity_log`? +2. Schlafphasen als separate Spalten oder als JSONB-Feld? +3. Morgendlicher Check-in: Frontend-State oder Backend-Logik (letzte Nacht bereits erfasst?)? +4. Korrelationsberechnung: im Backend (Python) oder im Frontend (JavaScript)? +5. Apple Health Export enthält Schlafphasen-Daten – welches CSV-Format wird erwartet? +EOF diff --git a/.claude/docs/functional/TRAINING_TYPES.md b/.claude/docs/functional/TRAINING_TYPES.md new file mode 100644 index 0000000..c75c32b --- /dev/null +++ b/.claude/docs/functional/TRAINING_TYPES.md @@ -0,0 +1,257 @@ +# Fachliche Anforderungen: Trainingstypen & Herzfrequenz +**Modul:** v9d +**Status:** Fachlich freigegeben, technische Implementierung ausstehend +**Letzte Aktualisierung:** März 2026 + +--- + +## 1. Überblick + +Dieses Modul erweitert die bestehende Aktivitätserfassung um eine strukturierte +Kategorisierung von Trainingstypen sowie die Erfassung und Auswertung von +Herzfrequenz-Daten. Ziel ist eine tiefere Analyse der Trainingsqualität, +Erholung und Zielerreichung. + +--- + +## 2. Trainingstypen + +### 2.1 Kategorie-Hierarchie + +Jedes Training hat einen **Haupttyp** und optional einen **Untertyp**: + +``` +Cardio (Ausdauer) + → Laufen + → Radfahren + → Schwimmen + → Rudern + → Sonstiges Cardio + +Kraft + → Hypertrophie (Muskelaufbau, 8-12 Wdh, mittleres Gewicht) + → Maximalkraft (1-5 Wdh, hohes Gewicht) + → Kraftausdauer (15+ Wdh, leichtes Gewicht) + → Funktionell (Kettlebell, TRX, Bodyweight) + +Schnellkraft / HIIT + → HIIT (High Intensity Interval Training) + → Explosiv (Sprints, Sprünge, Wurfübungen) + → Circuit Training + +Kampfsport / Technikkraft + → Techniktraining (Bewegungsabläufe, Formen) + → Sparring / Wettkampf + → Kraft für Kampfsport (kampfsportspezifische Übungen) + +Mobility & Dehnung + → Statisches Dehnen + → Dynamisches Dehnen + → Yoga + → Faszienarbeit + +Erholung (aktiv) + → Spaziergang + → Leichtes Schwimmen + → Regenerationseinheit +``` + +### 2.2 Ruhetage + +Ruhetage werden als **bewusste Einheit** erfasst – nicht nur als Abwesenheit +von Training. Ein Ruhetag hat: +- Datum +- Typ: Vollständige Ruhe / Aktive Erholung +- Optional: Notiz (z.B. "Muskelkater Beine", "Reise") + +Ruhetage fließen in die Wochenplanung und KI-Analyse ein. + +### 2.3 Erfassung + +**Bestehende Aktivitäten (Apple Health Import):** +- Beim Import wird Trainingstyp automatisch vorgeschlagen basierend auf + dem Apple Health Workout-Typ (z.B. "Running" → Cardio / Laufen) +- Nutzer kann den vorgeschlagenen Typ bestätigen oder korrigieren +- Bereits importierte Aktivitäten können nachträglich kategorisiert werden + +**Manuell erfasste Aktivitäten:** +- Trainingstyp und Untertyp als Pflichtfeld beim Anlegen +- Untertyp ist optional + +--- + +## 3. Herzfrequenz-Daten + +### 3.1 Ruhepuls + +**Definition:** Herzfrequenz in Ruhe, idealerweise morgens direkt nach dem +Aufwachen, vor dem Aufstehen. + +**Erfassung:** +- Manuell: Nutzer gibt Wert täglich ein (ganze Zahl, Schläge/Minute) +- Import: Aus Apple Health / Garmin automatisch übernommen +- Bei beiden Quellen: Import als Standard, manuelle Eingabe ergänzt oder + überschreibt den Importwert für den jeweiligen Tag + +**Bedeutung:** +- Ruhepuls-Anstieg (+5-7 bpm über persönlichem Durchschnitt) = Warnsignal + für Übertraining, Krankheit oder unzureichende Erholung +- Langfristiger Ruhepuls-Abfall = Verbesserung der Ausdauerleistung + +### 3.2 HF während Training + +**Felder pro Aktivitätseintrag:** +- HF Durchschnitt (bpm) +- HF Maximum (bpm) + +**Erfassung:** +- Primär aus Apple Health / Garmin Import (wird automatisch befüllt) +- Manuelle Eingabe als Ergänzung wenn kein Gerät genutzt + +### 3.3 HF-Zonen + +**5-Zonen-Modell** (basierend auf maximaler Herzfrequenz): + +| Zone | Name | % HFmax | Zweck | +|------|------|---------|-------| +| 1 | Regeneration | 50-60% | Aktive Erholung | +| 2 | Grundlagenausdauer | 60-70% | Fettverbrennung, Basis | +| 3 | Aerobe Ausdauer | 70-80% | Ausdaueraufbau | +| 4 | Anaerobe Schwelle | 80-90% | Leistungssteigerung | +| 5 | Maximale Intensität | 90-100% | Spitzenleistung | + +**HFmax-Berechnung:** +- Standard-Formel: 220 - Alter +- Alternativ: Manuell vom Nutzer definierbar (aus Leistungstest) + +**Auswertung:** +- Zeitverteilung in Zonen pro Training (falls HF-Kurve vorhanden) +- Oder: Zone basierend auf HF-Durchschnitt schätzen + +### 3.4 HRV (Herzratenvariabilität) + +**Definition:** Variation der Zeitabstände zwischen Herzschlägen. +Hohe HRV = gute Erholung / niedriger Stress. +Niedrige HRV = Belastung / Übertraining / Stress. + +**Erfassung:** +- Manuell (ms, aus Smartwatch-App abgelesen) +- Import aus Apple Health (wenn vorhanden) +- Messung: morgens, gleiche Bedingungen wie Ruhepuls + +**Interpretation:** +- Persönlicher Baseline-Wert wird über 4 Wochen berechnet +- Abweichung >10% unter Baseline = Warnung + +### 3.5 VO2Max + +**Definition:** Maximale Sauerstoffaufnahme, Indikator für aerobe Leistungsfähigkeit. + +**Erfassung:** +- Import aus Apple Health / Garmin (falls Gerät berechnet) +- Alternativ: Schätzung aus Ruhepuls + HFmax (Cooper-Formel): + `VO2Max ≈ 15 × (HFmax / HF_Ruhe)` + +**Anzeige:** +- Aktueller Wert + Trend +- Einordnung nach Alters-/Geschlechtsnorm (sehr gut / gut / durchschnittlich / verbesserungswürdig) + +--- + +## 4. Auswertungen & Dashboards + +### 4.1 Trainingstyp-Verteilung +- Zeitraum wählbar: letzte 4 / 8 / 12 Wochen +- Kreisdiagramm oder Balkendiagramm: Anteil je Haupttyp in % +- Zusätzlich: Anzahl Einheiten je Typ + +### 4.2 Trainingshäufigkeit pro Typ +- Wochenansicht: Wie viele Einheiten je Typ pro Woche +- Trend: Vergleich aktuelle Woche vs. Durchschnitt + +### 4.3 Ruhepuls-Trend +- Liniengrafik über Zeit (7 / 30 / 90 Tage) +- Persönlicher Durchschnitt als Referenzlinie +- Warnung wenn Ruhepuls >5 bpm über 7-Tage-Durchschnitt + +### 4.4 HF-Zonen-Verteilung +- Pro Training: Zeitanteil in jeder Zone (falls Daten vorhanden) +- Aggregiert: Zonenverteilung der letzten 30 Tage + +### 4.5 Erholungsstatus +- Kombination aus: HRV + Ruhepuls + letzter Trainingsbelastung +- Ampel-System: 🟢 Gut erholt / 🟡 Teilweise erholt / 🔴 Erholung nötig +- Empfehlung: "Heute intensives Training möglich" / "Leichtes Training empfohlen" / "Ruhetag empfohlen" + +### 4.6 Wochenplanung Soll vs. Ist +- Nutzer definiert Wochenziel: z.B. "3x Kraft, 2x Cardio, 1x Mobility, 1 Ruhetag" +- Dashboard zeigt: erreicht / offen / überschritten +- Einfache Eingabe, kein komplexer Planer + +--- + +## 5. KI-Analyse + +Die KI-Analyse wird um folgende Platzhalter erweitert: + +### 5.1 Übertraining erkennen +**Trigger-Bedingungen (Beispiele):** +- >5 Krafteinheiten in 7 Tagen ohne Ruhetag +- Ruhepuls-Anstieg + gleichzeitig hohe Trainingsbelastung +- HRV unter persönlicher Baseline + +**KI-Ausgabe:** Warnung + konkrete Empfehlung (Ruhetag, Intensität reduzieren) + +### 5.2 Ruhetag empfehlen +**Basierend auf:** HRV-Trend + Ruhepuls-Trend + Trainingsbelastung letzte 5 Tage +**KI-Ausgabe:** "Dein Erholungsstatus zeigt X – heute wäre ein Ruhetag oder leichtes Training sinnvoll" + +### 5.3 Trainingstypen und Primärziel +**Beispiel Muskelaufbau:** KI erkennt wenn <40% Krafttraining → Hinweis +**Beispiel Kondition:** KI erkennt wenn <40% Cardio → Hinweis +**Voraussetzung:** Primärziel-Modul (v9e) muss implementiert sein + +### 5.4 Phasen erkennen und kommentieren +**Phasen:** +- Aufbauphase: Steigende Belastung über mehrere Wochen +- Erholungsphase (Deload): Reduzierte Belastung nach intensiver Phase +- Plateau: Gleichbleibende Belastung ohne Progression + +**KI-Ausgabe:** Phase benennen + situative Empfehlung + +### 5.5 Neue KI-Platzhalter +``` +{{trainingstyp_verteilung}} → "60% Kraft, 30% Cardio, 10% Mobility (letzte 4 Wochen)" +{{ruhetage_letzte_woche}} → Anzahl Ruhetage letzte 7 Tage +{{ruhepuls_aktuell}} → heutiger / letzter Ruhepuls +{{ruhepuls_trend}} → "sinkend / stabil / steigend" +{{hrv_aktuell}} → letzter HRV-Wert +{{hrv_baseline}} → persönlicher HRV-Durchschnitt (30 Tage) +{{erholungsstatus}} → "gut / teilweise / schlecht" +{{vo2max}} → aktueller VO2Max-Wert +{{trainingsphase}} → "Aufbau / Erholung / Plateau / unbekannt" +{{hf_zonen_verteilung}} → "Zone 2: 45%, Zone 3: 35%, Zone 4: 20%" +``` + +--- + +## 6. Abgrenzung & offene Fragen + +### In diesem Modul enthalten: +- Kategorisierung bestehender + neuer Aktivitäten +- Ruhepuls, HRV, VO2Max erfassen und auswerten +- HF-Zonen berechnen und anzeigen +- Erholungsstatus-Ampel +- Wochenplanung (einfach) +- KI-Platzhalter und Analyse-Logik + +### Nicht in diesem Modul (spätere Versionen): +- Periodisierungsplaner (Makrozyklen, Mikrozyklen) → v9g +- Sportart-spezifische Metriken (Pace, Watt) → v9e +- Verknüpfung mit Primärziel → v9e (Primärziel-Modul) +- Connector zu Garmin/Strava für Live-HF → v9h + +### Offene Fragen für technische Planung: +1. Werden HF-Kurven (Zeit × HF) aus Apple Health importiert oder nur Avg/Max? +2. Soll HRV als eigener Tageseintrag oder als Ergänzung zum Ruhepuls-Eintrag erfasst werden? +3. Soll die Wochenplanung persistiert werden (DB) oder nur als Session-Einstellung? diff --git a/.claude/docs/functional/TRAINING_TYPE_PROFILES.md b/.claude/docs/functional/TRAINING_TYPE_PROFILES.md new file mode 100644 index 0000000..8da75a0 --- /dev/null +++ b/.claude/docs/functional/TRAINING_TYPE_PROFILES.md @@ -0,0 +1,840 @@ +# Training Type Profiles – Umfassendes Trainingsmanagement-System + +**Issue:** #15 (erweitert) +**Status:** Phase 1 KOMPLETT ✅ (Foundation + Auto-Evaluation) | Phase 2 ausstehend (Admin-UI) +**Erstellt:** 2026-03-23 +**Implementiert:** Phase 1 - 2026-03-23 + +--- + +## Vision + +Jeder Trainingstyp erhält ein **umfassendes Profil** mit Parametern, die: +1. **Mindestanforderungen** definieren (wann ist es "echtes" Training?) +2. **Intensitätsbereiche** beschreiben (Zonen: regenerativ, moderat, intensiv) +3. **Trainingswirkung** charakterisieren (welche Fähigkeiten werden trainiert?) +4. **Periodisierung** unterstützen (optimale Frequenz, Erholungsbedarf) +5. **Dynamische Bewertung** ermöglichen (ist diese konkrete Aktivität gut/schlecht?) + +--- + +## Trainingstyp-Profil: Vollständige Parameter-Definition + +### Struktur (JSONB in training_types.profile) + +```json +{ + "version": "1.0", + "created_at": "2026-03-23", + + // ━━━ 1. MINDESTANFORDERUNGEN (Quality Gates) ━━━ + "minimum_requirements": { + "enabled": true, + "rules": { + "duration_min": { + "value": 15, + "weight": 5, + "reason": "Unter 15min keine signifikante Trainingswirkung" + }, + "avg_hr_min": { + "value": 100, + "weight": 3, + "reason": "Puls muss für Ausdauerreiz erhöht sein" + }, + "max_hr_min": { + "value": 120, + "weight": 1, + "reason": "Maximalpuls zeigt echte Belastungsspitze" + }, + "distance_km": { + "value": 1.0, + "weight": 2, + "reason": "Mindestdistanz für Lauftraining" + }, + "kcal_active": { + "value": 100, + "weight": 1, + "reason": "Mindest-Energieverbrauch" + } + }, + "pass_threshold": 0.6, + "fail_action": "mark_low_quality" + }, + + // ━━━ 2. INTENSITÄTSBEREICHE (HF-Zonen) ━━━ + "intensity_zones": { + "method": "percentage_max_hr", // oder "percentage_reserve_hr" + "zones": [ + { + "name": "regenerativ", + "min_percent": 50, + "max_percent": 60, + "color": "#4CAF50", + "effect": "Erholung, Grundlagenausdauer 1", + "target_duration_min": 30 + }, + { + "name": "grundlagenausdauer", + "min_percent": 60, + "max_percent": 70, + "color": "#2196F3", + "effect": "Aerobe Kapazität, Fettstoffwechsel", + "target_duration_min": 45 + }, + { + "name": "entwicklungsbereich", + "min_percent": 70, + "max_percent": 80, + "color": "#FF9800", + "effect": "VO2max-Training, Laktattoleranz", + "target_duration_min": 20 + }, + { + "name": "schwellentraining", + "min_percent": 80, + "max_percent": 90, + "color": "#F44336", + "effect": "Anaerobe Schwelle, Wettkampftempo", + "target_duration_min": 10 + }, + { + "name": "maximale_intensität", + "min_percent": 90, + "max_percent": 100, + "color": "#9C27B0", + "effect": "Maximalkraft, Sprint, HIIT", + "target_duration_min": 5 + } + ], + "evaluation_rules": { + "time_in_zone_required": true, + "min_time_in_target_zone_percent": 70 + } + }, + + // ━━━ 3. TRAININGSWIRKUNG (Abilities Mapping) ━━━ + "training_effects": { + "primary_abilities": [ + { + "category": "konditionell", + "ability": "ausdauer", + "intensity": 5, + "description": "Hauptfokus: Aerobe Ausdauer" + }, + { + "category": "konditionell", + "ability": "schnelligkeit", + "intensity": 2, + "description": "Nebenfokus: Lauftempo" + } + ], + "secondary_abilities": [ + { + "category": "koordinativ", + "ability": "rhythmus", + "intensity": 3, + "description": "Laufrhythmus, Schrittfrequenz" + }, + { + "category": "psychisch", + "ability": "willenskraft", + "intensity": 4, + "description": "Durchhaltevermögen bei langen Läufen" + } + ], + "metabolic_focus": ["aerobic", "fat_oxidation"], + "muscle_groups": ["legs_posterior", "legs_anterior", "core"], + "energy_systems": ["aerobic_oxidative", "anaerobic_lactic"] + }, + + // ━━━ 4. PERIODISIERUNG & FREQUENZ ━━━ + "periodization": { + "recommended_frequency": { + "per_week_min": 2, + "per_week_max": 5, + "per_week_optimal": 3, + "consecutive_days_max": 2 + }, + "recovery_requirement": { + "hours_before_same_type": 48, + "hours_before_high_intensity": 24, + "rpe_threshold_for_extra_rest": 8 + }, + "progression": { + "beginner_duration_min": 20, + "intermediate_duration_min": 30, + "advanced_duration_min": 45, + "volume_increase_percent_per_week": 10 + }, + "deload_recommendation": { + "every_n_weeks": 4, + "volume_reduction_percent": 40 + } + }, + + // ━━━ 5. LEISTUNGSINDIKATOREN (KPIs) ━━━ + "performance_indicators": { + "primary_metrics": ["pace_min_per_km", "avg_hr", "distance_km"], + "secondary_metrics": ["kcal_per_km", "cadence", "elevation_gain"], + "benchmark_values": { + "beginner": {"pace_min_per_km": 7.0, "distance_km": 3.0}, + "intermediate": {"pace_min_per_km": 6.0, "distance_km": 5.0}, + "advanced": {"pace_min_per_km": 5.0, "distance_km": 10.0} + } + }, + + // ━━━ 6. KONTEXT & VARIANTEN ━━━ + "context": { + "environment": ["outdoor", "indoor_treadmill"], + "weather_sensitivity": "high", + "equipment_required": ["running_shoes"], + "location_types": ["road", "trail", "track"], + "variants": [ + { + "name": "intervall_lauf", + "modifications": { + "intensity_zones.focus": "schwellentraining", + "minimum_requirements.avg_hr_min": 140 + } + }, + { + "name": "long_jog", + "modifications": { + "intensity_zones.focus": "grundlagenausdauer", + "minimum_requirements.duration_min": 60 + } + } + ] + }, + + // ━━━ 7. SICHERHEIT & KONTRAINDIKATIONEN ━━━ + "safety": { + "max_hr_warning": 180, + "min_recovery_hr": 100, + "contraindications": [ + "acute_injury_lower_body", + "cardiovascular_episode_recent" + ], + "weather_limits": { + "temp_celsius_min": -5, + "temp_celsius_max": 32, + "wind_speed_kmh_max": 40 + } + }, + + // ━━━ 8. KI-ANALYSE KONTEXT ━━━ + "ai_context": { + "questions_to_ask": [ + "Wie fühlte sich dein Tempo an?", + "Hattest du Atembeschwerden?", + "Wie war die Regeneration danach?" + ], + "evaluation_focus": [ + "Pace-Entwicklung über Distanz", + "Herzfrequenz-Stabilität", + "Erholungspuls nach Training" + ], + "comparison_metrics": [ + "Tempo vs. letzte 4 Wochen", + "Durchschnittspuls bei gleicher Distanz", + "Subjektive Anstrengung (RPE) vs. objektive Daten" + ] + } +} +``` + +--- + +## Dynamische Bewertung: Wie das Profil genutzt wird + +### 1. Mindestanforderungen-Check (Quality Gate) + +**Funktion:** `evaluate_minimum_requirements(activity, profile)` + +```python +def evaluate_minimum_requirements(activity: dict, profile: dict) -> dict: + """ + Prüft ob Aktivität Mindestanforderungen erfüllt. + + Returns: + { + "passed": bool, + "score": float, + "failed_rules": [{"rule": str, "expected": val, "actual": val}], + "recommendation": str + } + """ + requirements = profile['minimum_requirements'] + if not requirements['enabled']: + return {"passed": True, "score": 1.0, "failed_rules": []} + + total_weight = 0 + passed_weight = 0 + failed_rules = [] + + for rule_name, rule_config in requirements['rules'].items(): + weight = rule_config['weight'] + total_weight += weight + + actual = activity.get(rule_name) + expected = rule_config['value'] + + if actual is not None and actual >= expected: + passed_weight += weight + else: + failed_rules.append({ + "rule": rule_name, + "expected": expected, + "actual": actual, + "reason": rule_config['reason'] + }) + + score = passed_weight / total_weight if total_weight > 0 else 1.0 + passed = score >= requirements['pass_threshold'] + + recommendation = generate_recommendation(failed_rules, profile) + + return { + "passed": passed, + "score": round(score, 2), + "failed_rules": failed_rules, + "recommendation": recommendation + } +``` + +### 2. Intensitäts-Bewertung (Zone Analysis) + +**Funktion:** `evaluate_intensity_distribution(activity, profile)` + +```python +def evaluate_intensity_distribution(activity: dict, profile: dict) -> dict: + """ + Analysiert in welcher HF-Zone das Training stattfand. + + Returns: + { + "dominant_zone": str, + "time_in_zones": {zone: minutes}, + "zone_quality": float (0-1), + "recommendation": str + } + """ + zones = profile['intensity_zones']['zones'] + max_hr = activity.get('user_max_hr', 180) # From user profile + avg_hr = activity.get('avg_hr') + + if not avg_hr: + return {"dominant_zone": "unknown", "zone_quality": 0} + + avg_hr_percent = (avg_hr / max_hr) * 100 + + # Finde passende Zone + dominant_zone = None + for zone in zones: + if zone['min_percent'] <= avg_hr_percent <= zone['max_percent']: + dominant_zone = zone + break + + if not dominant_zone: + return {"dominant_zone": "out_of_range", "zone_quality": 0} + + # Prüfe ob Dauer passt zur Zone + duration = activity.get('duration_min', 0) + target_duration = dominant_zone['target_duration_min'] + + duration_quality = min(duration / target_duration, 1.0) + + recommendation = f"Training in Zone '{dominant_zone['name']}' (Effekt: {dominant_zone['effect']}). " + if duration < target_duration: + recommendation += f"Für optimale Wirkung: {target_duration}min empfohlen." + + return { + "dominant_zone": dominant_zone['name'], + "zone_color": dominant_zone['color'], + "zone_effect": dominant_zone['effect'], + "avg_hr_percent": round(avg_hr_percent, 1), + "duration_quality": round(duration_quality, 2), + "recommendation": recommendation + } +``` + +### 3. Trainingswirkung-Analyse (Abilities Development) + +**Funktion:** `evaluate_training_effects(activity, profile)` + +```python +def evaluate_training_effects(activity: dict, profile: dict) -> dict: + """ + Berechnet welche Fähigkeiten durch diese Aktivität trainiert wurden. + + Returns: + { + "abilities_trained": [ + {"category": str, "ability": str, "intensity": int, "quality": float} + ], + "total_training_load": float + } + """ + effects = profile['training_effects'] + intensity_eval = evaluate_intensity_distribution(activity, profile) + min_requirements_eval = evaluate_minimum_requirements(activity, profile) + + # Qualitätsfaktor basierend auf Mindestanforderungen + quality_factor = min_requirements_eval['score'] + + abilities_trained = [] + + # Primary abilities mit voller Intensität + for ability in effects['primary_abilities']: + abilities_trained.append({ + "category": ability['category'], + "ability": ability['ability'], + "intensity": ability['intensity'], + "quality": quality_factor, + "contribution": ability['intensity'] * quality_factor + }) + + # Secondary abilities mit reduzierter Intensität + for ability in effects['secondary_abilities']: + abilities_trained.append({ + "category": ability['category'], + "ability": ability['ability'], + "intensity": ability['intensity'], + "quality": quality_factor * 0.7, # Sekundär = 70% + "contribution": ability['intensity'] * quality_factor * 0.7 + }) + + total_training_load = sum(a['contribution'] for a in abilities_trained) + + return { + "abilities_trained": abilities_trained, + "total_training_load": round(total_training_load, 2), + "metabolic_focus": effects['metabolic_focus'], + "muscle_groups": effects['muscle_groups'] + } +``` + +### 4. Periodisierungs-Check (Recovery & Frequency) + +**Funktion:** `evaluate_periodization_compliance(activity, profile, recent_activities)` + +```python +def evaluate_periodization_compliance( + activity: dict, + profile: dict, + recent_activities: list +) -> dict: + """ + Prüft ob Aktivität zu Periodisierungs-Empfehlungen passt. + + Returns: + { + "frequency_status": str, + "recovery_adequate": bool, + "warning": str | None + } + """ + periodization = profile['periodization'] + activity_date = activity['date'] + + # Zähle gleiche Trainingstypen in letzter Woche + same_type_count = sum( + 1 for a in recent_activities + if a['training_type_id'] == activity['training_type_id'] + and days_between(a['date'], activity_date) <= 7 + ) + + # Prüfe Erholungszeit + last_same_type = next( + (a for a in recent_activities + if a['training_type_id'] == activity['training_type_id']), + None + ) + + recovery_adequate = True + warning = None + + if last_same_type: + hours_since_last = hours_between(last_same_type['date'], activity_date) + required_hours = periodization['recovery_requirement']['hours_before_same_type'] + + if hours_since_last < required_hours: + recovery_adequate = False + warning = f"Zu wenig Erholung: {hours_since_last}h statt {required_hours}h empfohlen" + + # Frequenz-Status + optimal_freq = periodization['recommended_frequency']['per_week_optimal'] + if same_type_count < optimal_freq: + frequency_status = "under_optimal" + elif same_type_count > periodization['recommended_frequency']['per_week_max']: + frequency_status = "over_optimal" + warning = "Übertraining-Risiko: Zu viele Einheiten diese Woche" + else: + frequency_status = "optimal" + + return { + "frequency_status": frequency_status, + "weekly_count": same_type_count, + "recovery_adequate": recovery_adequate, + "warning": warning + } +``` + +### 5. Leistungsentwicklung (Performance Tracking) + +**Funktion:** `evaluate_performance_development(activity, profile, historical_activities)` + +```python +def evaluate_performance_development( + activity: dict, + profile: dict, + historical_activities: list +) -> dict: + """ + Vergleicht aktuelle Leistung mit Historie. + + Returns: + { + "trend": str, # "improving" | "stable" | "declining" + "metrics_comparison": {metric: {"current": val, "avg_4weeks": val, "change_percent": val}}, + "benchmark_level": str # "beginner" | "intermediate" | "advanced" + } + """ + kpis = profile['performance_indicators'] + primary_metrics = kpis['primary_metrics'] + + # Berechne 4-Wochen-Durchschnitte + comparison = {} + for metric in primary_metrics: + current_value = activity.get(metric) + if not current_value: + continue + + historical_values = [ + a[metric] for a in historical_activities[-12:] # Letzte 12 Einheiten + if a.get(metric) is not None + ] + + if historical_values: + avg_historical = sum(historical_values) / len(historical_values) + change_percent = ((current_value - avg_historical) / avg_historical) * 100 + + comparison[metric] = { + "current": current_value, + "avg_4weeks": round(avg_historical, 2), + "change_percent": round(change_percent, 1) + } + + # Trend-Bewertung + if all(c['change_percent'] > 5 for c in comparison.values()): + trend = "improving" + elif all(c['change_percent'] < -5 for c in comparison.values()): + trend = "declining" + else: + trend = "stable" + + # Benchmark-Level ermitteln + benchmarks = kpis['benchmark_values'] + benchmark_level = determine_benchmark_level(activity, benchmarks) + + return { + "trend": trend, + "metrics_comparison": comparison, + "benchmark_level": benchmark_level + } +``` + +--- + +## Dynamische Gesamt-Bewertung + +**Master-Funktion:** `evaluate_activity(activity, profile, context)` + +```python +def evaluate_activity(activity: dict, profile: dict, context: dict) -> dict: + """ + Vollständige Bewertung einer Aktivität anhand des Trainingstyp-Profils. + + Args: + activity: Die zu bewertende Aktivität + profile: Das Trainingstyp-Profil + context: { + "recent_activities": [...], + "historical_activities": [...], + "user_profile": {...} + } + + Returns: + { + "evaluated_at": ISO timestamp, + "version": "1.0", + "minimum_requirements": {...}, + "intensity_analysis": {...}, + "training_effects": {...}, + "periodization_check": {...}, + "performance_development": {...}, + "overall_score": float (0-1), + "quality_label": str, # "excellent" | "good" | "acceptable" | "poor" + "recommendations": [str], + "warnings": [str] + } + """ + + results = { + "evaluated_at": datetime.now().isoformat(), + "version": "1.0", + "minimum_requirements": evaluate_minimum_requirements(activity, profile), + "intensity_analysis": evaluate_intensity_distribution(activity, profile), + "training_effects": evaluate_training_effects(activity, profile), + "periodization_check": evaluate_periodization_compliance( + activity, profile, context['recent_activities'] + ), + "performance_development": evaluate_performance_development( + activity, profile, context['historical_activities'] + ) + } + + # Gesamt-Score berechnen (gewichtet) + weights = { + "minimum_requirements": 0.4, + "intensity_analysis": 0.2, + "periodization_check": 0.2, + "performance_development": 0.2 + } + + overall_score = ( + results['minimum_requirements']['score'] * weights['minimum_requirements'] + + results['intensity_analysis']['duration_quality'] * weights['intensity_analysis'] + + (1.0 if results['periodization_check']['recovery_adequate'] else 0.5) * weights['periodization_check'] + + (1.0 if results['performance_development']['trend'] == 'improving' else 0.7) * weights['performance_development'] + ) + + # Quality Label + if overall_score >= 0.9: + quality_label = "excellent" + elif overall_score >= 0.7: + quality_label = "good" + elif overall_score >= 0.5: + quality_label = "acceptable" + else: + quality_label = "poor" + + # Recommendations sammeln + recommendations = [] + warnings = [] + + if not results['minimum_requirements']['passed']: + for failed in results['minimum_requirements']['failed_rules']: + recommendations.append( + f"{failed['rule']}: {failed['reason']} (Ist: {failed['actual']}, Soll: {failed['expected']})" + ) + + if results['periodization_check']['warning']: + warnings.append(results['periodization_check']['warning']) + + results.update({ + "overall_score": round(overall_score, 2), + "quality_label": quality_label, + "recommendations": recommendations, + "warnings": warnings + }) + + return results +``` + +--- + +## DB-Schema (Erweitert) + +```sql +-- Training Types: Vollständiges Profil +ALTER TABLE training_types ADD COLUMN profile JSONB DEFAULT NULL; + +-- Activity Log: Evaluation Results +ALTER TABLE activity_log ADD COLUMN evaluation JSONB DEFAULT NULL; + +-- Beispiel-Daten: +UPDATE training_types SET profile = '{...siehe oben...}'::jsonb +WHERE name_de = 'Laufen'; +``` + +--- + +## User Interface: Aktivitäts-Detail-Ansicht + +``` +┌────────────────────────────────────────────────────────────────┐ +│ 🏃 Laufen - 23. März 2026 │ +├────────────────────────────────────────────────────────────────┤ +│ │ +│ ━━━ ZUSAMMENFASSUNG ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ +│ │ +│ Gesamtbewertung: ⭐⭐⭐⭐⭐ Exzellent (Score: 0.92) │ +│ │ +│ 45 Minuten · 5.2 km · Ø 142 bpm (79% Max-HF) · 380 kcal │ +│ │ +│ ━━━ MINDESTANFORDERUNGEN ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ +│ │ +│ ✅ Dauer: 45min (≥ 15min erforderlich) │ +│ ✅ Ø Herzfrequenz: 142 bpm (≥ 100 bpm) │ +│ ✅ Max. Herzfrequenz: 165 bpm (≥ 120 bpm) │ +│ ✅ Distanz: 5.2 km (≥ 1.0 km) │ +│ │ +│ ━━━ INTENSITÄTSANALYSE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ +│ │ +│ Trainingszone: Entwicklungsbereich (70-80% Max-HF) │ +│ Effekt: VO2max-Training, Laktattoleranz │ +│ │ +│ [████████████████████░░] 79% Max-HF │ +│ │ +│ Zeitverteilung: │ +│ • Regenerativ (50-60%): 0 min │ +│ • Grundlagenausdauer (60-70%): 8 min │ +│ • Entwicklungsbereich (70-80%): 32 min ✅ Optimal │ +│ • Schwellentraining (80-90%): 5 min │ +│ │ +│ ━━━ TRAININGSWIRKUNG ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ +│ │ +│ Trainierte Fähigkeiten: │ +│ • Ausdauer (Konditionell): ●●●●● (5/5) - Primärfokus │ +│ • Schnelligkeit (Konditionell): ●●○○○ (2/5) - Nebenfokus │ +│ • Rhythmus (Koordinativ): ●●●○○ (3/5) - Sekundär │ +│ • Willenskraft (Psychisch): ●●●●○ (4/5) - Sekundär │ +│ │ +│ Gesamte Trainingsbelastung: 18.2 Punkte │ +│ │ +│ ━━━ PERIODISIERUNG ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ +│ │ +│ Frequenz diese Woche: 3 Einheiten ✅ Optimal (2-5 empfohlen) │ +│ Erholung seit letztem Lauf: 52 Stunden ✅ Ausreichend │ +│ Nächstes Training empfohlen: Frühestens in 48h │ +│ │ +│ ━━━ LEISTUNGSENTWICKLUNG ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ +│ │ +│ Trend: ↗ Verbesserung │ +│ │ +│ Pace: 8:39 min/km (Ø 4 Wochen: 9:15 min/km) +6.5% schneller │ +│ Ø HF: 142 bpm (Ø 4 Wochen: 148 bpm) -4% niedriger │ +│ │ +│ Dein Level: Fortgeschritten (Intermediate) │ +│ │ +│ ━━━ EMPFEHLUNGEN ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │ +│ │ +│ ✨ Exzellentes Training! Du trainierst im optimalen │ +│ Entwicklungsbereich für VO2max-Verbesserung. │ +│ │ +│ 💡 Deine Pace verbessert sich stetig bei gleichzeitig │ +│ niedrigerer Herzfrequenz - klares Zeichen für bessere │ +│ Ausdauerkapazität. │ +│ │ +│ 📅 Nächste Schritte: │ +│ • 1-2 Tage Regeneration │ +│ • Dann: Langer, langsamer Lauf (60-70% Max-HF, 60min) │ +│ │ +└────────────────────────────────────────────────────────────────┘ +``` + +--- + +## Implementierungs-Phasen + +### Phase 1: Foundation (MVP) +1. DB-Migration: `profile` Spalte in training_types +2. DB-Migration: `evaluation` Spalte in activity_log +3. Backend: `evaluate_activity()` Master-Funktion +4. Backend: Evaluation beim INSERT/UPDATE + +### Phase 2: Profile Editor (Admin-UI) +5. Admin-UI: Trainingstyp-Profil-Editor (JSON-basiert) +6. Admin-UI: Profile-Templates für Top 5 Trainingstypen +7. Admin-UI: Preview-Funktion (wie wird evaluiert?) + +### Phase 3: User Experience +8. Frontend: Aktivitäts-Detail-Ansicht mit allen Bewertungen +9. Frontend: Badge-System (Excellent/Good/Acceptable/Poor) +10. Frontend: Trainingswirkungs-Visualisierung + +### Phase 4: Advanced Features +11. KI-Integration: Nutze Evaluation-Daten für Prompts +12. Periodisierungs-Warnungen im Dashboard +13. Leistungsentwicklung-Charts +14. Historische Re-Evaluation (Batch-Job) + +**Geschätzter Aufwand:** 12-16 Stunden (komplett) + +--- + +## Offene Fragen + +1. **Profil-Komplexität:** Alle Parameter von Anfang an oder iterativ erweitern? +2. **User-Anpassbarkeit:** Soll User eigene Profile erstellen können? +3. **Performance:** JSONB-Queries optimiert genug für große Datenmengen? +4. **UI-Komplexität:** Ist die Detail-Ansicht zu überladen? +5. **Backward Compatibility:** Was passiert mit Aktivitäten ohne Profil? + +--- + +--- + +## Implementierungs-Status + +### Phase 1: Foundation ✅ KOMPLETT (23.03.2026) + +**Phase 1.1: Database & Core Engine** ✅ +- ✅ Migration 013: training_parameters Tabelle (16 Standard-Parameter) +- ✅ Migration 014: training_types.profile + activity_log.evaluation + triggers +- ✅ rule_engine.py: RuleEvaluator mit 9 Operatoren +- ✅ rule_engine.py: IntensityZoneEvaluator für HF-Zonen +- ✅ rule_engine.py: TrainingEffectsEvaluator für Fähigkeiten +- ✅ profile_evaluator.py: TrainingProfileEvaluator (7 Dimensionen) +- ✅ evaluation_helper.py: Parameter-Loading + Context-Loading + Batch-Evaluation +- ✅ routers/evaluation.py: API-Endpoints für manuelle Evaluation +- ✅ Commit: 1b9cd6d + +**Phase 1.2: Auto-Evaluation** ✅ +- ✅ activity.py: create_activity() → Auto-Evaluation nach INSERT +- ✅ activity.py: update_activity() → Auto-Evaluation nach UPDATE +- ✅ activity.py: import_activity_csv() → Auto-Evaluation nach CSV-Import +- ✅ activity.py: bulk_categorize_activities() → Auto-Evaluation nach Bulk-Update +- ✅ Fehlerbehandlung mit try/except (verhindert Blockierung bei Evaluation-Fehlern) +- ✅ Commit: e119537 + +**Backend Implementation:** 100% komplett +- Parameter-Registry: extensibel via SQL +- Regel-System: flexibel, unterstützt >= und <= (Laufen vs. Meditation) +- 7 Dimensionen: Minimum Requirements, Intensity Zones, Training Effects, Periodization, Performance, Safety, AI Context +- Evaluation-Results in activity_log.evaluation gespeichert + +**Getestet:** Syntax-Check bestanden (py_compile) + +### Phase 2: Admin-UI 🔲 Ausstehend + +**Aufgaben:** +- Admin-UI: Parameter-Registry-Verwaltung (CRUD) +- Admin-UI: Trainingstyp-Profil-Editor (JSON-Editor mit Syntax-Highlighting) +- Admin-UI: Profile-Templates für Top 5 Trainingstypen (Laufen, Radfahren, Schwimmen, Krafttraining, Meditation) +- Admin-UI: Preview-Funktion (Test-Evaluation mit Beispiel-Aktivität) +- Validation: Profil-Schema-Validator + +**Geschätzter Aufwand:** 4-6 Stunden + +### Phase 3: User-UI 🔲 Ausstehend + +**Aufgaben:** +- Frontend: ActivityDetailPage mit vollständiger Evaluation-Anzeige +- Frontend: Quality Badges (excellent/good/acceptable/poor) in Listen +- Frontend: Filter nach Quality Label +- Frontend: Trainingswirkungs-Visualisierung (Fähigkeiten-Übersicht) +- Frontend: Dashboard-Stats (Durchschnitt Quality Score) + +**Geschätzter Aufwand:** 4-6 Stunden + +### Phase 4: Advanced Features 🔲 Future + +**Optional:** +- KI-Integration: Nutze Evaluation-Daten in AI-Prompts +- Periodisierungs-Warnungen im Dashboard +- Leistungsentwicklung-Charts +- Historische Re-Evaluation (Batch-Job über Admin-Panel) + +**Was denkst du zu diesem erweiterten Ansatz?** diff --git a/.claude/docs/functional/mitai_jinkendo_konzept_diagramme_auswertungen.md b/.claude/docs/functional/mitai_jinkendo_konzept_diagramme_auswertungen.md new file mode 100644 index 0000000..647fc7c --- /dev/null +++ b/.claude/docs/functional/mitai_jinkendo_konzept_diagramme_auswertungen.md @@ -0,0 +1,1253 @@ +# Konzept – Diagramme, Scores und regelbasierte Auswertungen für Mitai Jinkendo + +**Stand:** 24.03.2026 +**Ziel:** Fachlich belastbares Konzept für Diagramme, Kennzahlen, Scores, Warnungen und regelbasierte Empfehlungen **ohne KI**, aber so strukturiert, dass ein Coding Agent es direkt umsetzen kann. +**Basis:** Analyse des hochgeladenen Datenmodells `DATA_ARCHITECTURE.md` (Gewicht, Umfänge, Caliper, Aktivitäten, Vitalwerte, Blutdruck, Schlaf, Ernährung, geplante Recovery-/Korrelations-Logik). + +--- + +## 1. Zielbild + +Das System soll vor einer späteren KI-Stufe bereits drei Ebenen sauber abdecken: + +1. **Deskriptiv:** Was ist passiert? + - Zeitreihen, Verteilungen, Trends, Volumina, Zielabweichungen +2. **Diagnostisch:** Was hängt wahrscheinlich zusammen? + - Lag-basierte Zusammenhänge, Baseline-Abweichungen, Muster, Plateaus, Wechselwirkungen +3. **Präskriptiv (regelbasiert, nicht KI):** Was ist als nächste Handlung naheliegend? + - konkrete, nachvollziehbare Hinweise mit Trigger-Logik und Vertrauensstufe + +Wichtig ist, dass das System **zielabhängig** interpretiert: +- Gewichtsreduktion +- Muskel-/Kraftaufbau +- Konditions-/Ausdaueraufbau +- Körperrekomposition +- allgemeine Gesundheit +- Mischziele + +Dasselbe Rohsignal kann je nach Ziel anders bewertet werden. Ein Kaloriendefizit ist z. B. bei Gewichtsreduktion oft positiv, bei Kraftaufbau aber potenziell hinderlich. + +--- + +## 2. Fachliche Bewertung des aktuellen Datenmodells + +## 2.1 Stärken des vorhandenen Modells + +Das vorhandene Modell ist bereits ungewöhnlich stark, weil es mehrere Domänen zusammenführt: +- **Körper:** Gewicht, Umfänge, Caliper, BF%, LBM, FM +- **Aktivität:** Trainingstypen, Qualität, Dauer, HR, Kalorien, Fähigkeiten-Beitrag +- **Recovery / Gesundheit:** RHR, HRV, VO2max, SpO2, Atemfrequenz, Blutdruck +- **Lifestyle:** Schlafdauer, Schlafsegmente, Ernährung, Ruhetage +- **Korrelationen / spätere KI:** bereits als Richtung angelegt + +Dadurch sind nicht nur Einzelcharts möglich, sondern **wirkliche Mehrfaktorauswertungen**. Das ist ein zentraler Vorteil des Modells. + +## 2.2 Fachliche Schwächen / Risiken im aktuellen Konzept + +### A. Recovery Score aktuell zu grob +Die aktuell vorgesehene Recovery-Logik (HRV vs. 7-Tage-Mittel, RHR invertiert, Schlafqualität) ist als Startpunkt brauchbar, aber fachlich zu fragil für harte Entscheidungen. + +Probleme: +- HRV ist stark mess- und tagesformabhängig. +- Ein bloßer Quotient gegen den 7-Tage-Mittelwert ist ausreißeranfällig. +- Es fehlt eine **Messqualitätslogik**. +- Es fehlt eine **individuelle Normalzone** statt reinem Durchschnitt. +- Es fehlt eine **Kontextlogik** für Schlafschuld, Vorbelastung und Messlücken. + +### B. Trainingsqualität darf nicht HR-zentriert sein +Die vorhandene `quality_label`-Logik ist für Cardio sinnvoll, aber nicht für alle Trainingstypen. Krafttraining, Mobility, Meditation oder Techniktraining dürfen nicht primär an Avg-HR/Max-HR gemessen werden. + +### C. BMI nur als Nebenindikator +BMI kann angezeigt werden, sollte aber im System **nicht dominant** interpretiert werden, wenn bereits BF%, LBM und Umfänge vorliegen. + +### D. Korrelationen nicht als simples Pearson auf Rohzeitreihen +Reine Pearson-Korrelationen auf gleitenden Alltagsdaten können bei Trends, Saisonmustern und Lags irreführend sein. Für Trainings- und Ernährungsdaten sind **Verzögerungen (lags)**, Detrending und Mindeststichproben deutlich robuster. + +### E. Zielbezug fehlt noch als Steuerungszentrum +Fachlich entscheidend ist ein expliziter `goal_mode`, der Score-Gewichtung, Diagramm-Priorisierung und Textbewertung steuert. + +--- + +## 3. Zwingende Analyse-Prinzipien für alle Diagramme und Scores + +Diese Regeln sollten global gelten. + +## 3.1 Analysefenster +Für fast alle Kennzahlen sollten parallel drei Ebenen geführt werden: +- **kurzfristig:** 7 Tage +- **mittelfristig:** 28 Tage +- **langfristig:** 90 Tage + +Empfehlung: +- 7 Tage = Tagesform / Reaktion +- 28 Tage = Verhalten / Adhärenz +- 90 Tage = echte Entwicklung + +## 3.2 Mindestdatenmenge / Confidence +Jede Auswertung bekommt eine **Confidence-Klasse**: +- **hoch** = ausreichend Daten, wenig Lücken, mehrere Messpunkte +- **mittel** = brauchbar, aber eingeschränkt +- **niedrig** = nur orientierend +- **nicht auswertbar** = Datenbasis zu klein + +Default-Regeln: +- 7d-Auswertung nur ab **mind. 4 gültigen Tagen** +- 28d-Auswertung nur ab **mind. 18 gültigen Tagen** +- 90d-Auswertung nur ab **mind. 60 gültigen Tagen** +- Korrelationen nur ab **mind. 21 gepaarten Tageswerten** +- lag-basierte Korrelationen nur ab **mind. 28 gepaarten Tagen** + +## 3.3 Ausreißerbehandlung +Für Gewicht, HRV, RHR, Blutdruck, Schlaf und Ernährung sollten extreme Einzelwerte nicht direkt in Scores laufen. + +Empfehlung: +- primär **Rolling Median** oder **EWMA** für Trenddarstellung +- Ausreißererkennung per **Hampel Filter** oder Winsorizing +- Rohwert bleibt sichtbar, geht aber nicht ungefiltert in Score-Berechnung + +## 3.4 Individuelle Baselines statt nur Normwerte +Für Sport- und Recovery-Daten sind **intraindividuelle Baselines** oft wichtiger als Populationsnormen. + +Empfehlung: +- HRV, RHR, Schlafdauer, Blutdruck, Gewicht, Trainingsvolumen immer gegen + - 7d Trend + - 28d Baseline + - optional 90d Baseline + vergleichen + +## 3.5 Lags immer mitdenken +Viele Effekte sind verzögert: +- Kalorienbilanz → Gewicht oft mit 2–14 Tagen Verzögerung +- Protein / Krafttraining → LBM eher mit 2–6 Wochen Verzögerung +- Trainingslast → HRV/RHR häufig mit 1–3 Tagen Verzögerung +- Schlafdefizit → Recovery / Leistung typischerweise 1–3 Tage verzögert + +Darum sollen Korrelationen nie nur `lag=0` prüfen. + +## 3.6 Medizinischer Sicherheitsmodus +Gesundheitsnahe Kennzahlen müssen in drei Schichten bewertet werden: +- **Info** +- **Hinweis** +- **Abklärung empfohlen** + +Das System darf motivieren und priorisieren, aber **nicht diagnostizieren**. + +--- + +## 4. Zusätzliche Felder, die fachlich sehr sinnvoll wären + +Diese Felder sind nicht zwingend für die erste Umsetzung, würden aber die Qualität stark erhöhen: + +### Pflichtnah für Phase 2 +- `goal_mode` (weight_loss, strength, endurance, recomposition, health) +- `secondary_goals[]` +- `goal_priority_weights` +- `training_age` / Trainingserfahrung +- `measurement_time_consistency` (z. B. Gewicht morgens nüchtern ja/nein) +- `illness_flag` +- `travel_flag` +- `competition_flag` + +### Sehr wertvoll +- subjektive Belastung / `session_rpe` +- subjektive Müdigkeit / `fatigue_score` +- subjektive Schlafqualität, wenn Gerätedaten fehlen +- Schrittzahl / Alltagsbewegung +- geschätzter Grundumsatz / TDEE +- Taillenumfang-zu-Körpergröße (`waist_to_height_ratio`) +- standardisierte Blutdruck-Heimmessung-Markierung + +Ohne diese Felder bleiben einige Interpretationen möglich, aber weniger präzise. + +--- + +## 5. Ziel-Modi und ihre Wirkung auf Diagramme und Scores + +## 5.1 Ziel-Modi + +```yaml +goal_modes: + weight_loss: + focus: [fettmasse, gewichtstrend, kalorienbilanz, protein_sicherung, aktivitaetsvolumen, schlaf] + strength: + focus: [trainingsqualitaet, protein, lbm, recovery, progressionslogik, schlaf] + endurance: + focus: [trainingsvolumen, intensitaetsverteilung, vo2max, recovery, schlaf, energieverfuegbarkeit] + recomposition: + focus: [lbm, fettmasse, protein, trainingsqualitaet, kalorienbalance, schlaf] + health: + focus: [bewegung, blutdruck, schlaf, gewicht, taille, regelmaessigkeit, moderates trainingsvolumen] +``` + +## 5.2 Score-Gewichtung je Ziel + +```yaml +score_weights: + weight_loss: + body_progress: 0.30 + nutrition: 0.25 + activity: 0.20 + recovery: 0.15 + health_risk: 0.10 + strength: + body_progress: 0.20 + nutrition: 0.25 + activity: 0.30 + recovery: 0.20 + health_risk: 0.05 + endurance: + body_progress: 0.10 + nutrition: 0.20 + activity: 0.35 + recovery: 0.25 + health_risk: 0.10 + recomposition: + body_progress: 0.30 + nutrition: 0.25 + activity: 0.25 + recovery: 0.15 + health_risk: 0.05 + health: + body_progress: 0.20 + nutrition: 0.20 + activity: 0.20 + recovery: 0.20 + health_risk: 0.20 +``` + +--- + +## 6. Kategoriemodell für Visualisierungen + +Die Diagramme werden in vier Hauptgruppen ausgeliefert: +- **Körper** +- **Ernährung** +- **Aktivität** +- **Korrelation** + +Jede Visualisierung erhält: +- `chart_id` +- `category` +- `title` +- `purpose` +- `required_fields` +- `optional_fields` +- `derived_metrics` +- `aggregation` +- `default_range` +- `goal_relevance` +- `interpretation_rules` +- `recommendation_rules` +- `confidence_logic` + +--- + +## 7. Diagrammkatalog – KÖRPER + +## K1. Gewichtstrend + Trendkanal + Zielprojektion + +**Typ:** Linienchart mit Trendband +**Ziel:** echte Entwicklung statt Tagesrauschen sichtbar machen + +**Einzubeziehende Werte** +- `weight.value` +- Profilgröße für BMI optional +- Zielgewicht +- Startgewicht + +**Berechnungen** +- Tagesgewicht (Rohwerte) +- 7d Rolling Median +- 28d Trend-Slope +- 90d Trend-Slope +- Zielprojektion auf Basis 28d-Trend +- prozentuale Zielannäherung + +**Aussagen** +- Gewicht sinkt/stagniert/steigt +- Trend ist stabil oder volatil +- aktuelles Tempo ist schneller/langsamer als nachhaltiger Zielkorridor + +**Regelbasierte Hinweise** +- bei Gewichtsreduktion: Warnung, wenn 28d-Trend = flach und dokumentierte Energiebilanz im Defizit +- Hinweis, wenn Gewichtsverlust sehr schnell ist und gleichzeitig LBM sinkt +- Hinweis auf Messrauschen, wenn Tagesvolatilität hoch, aber 28d-Trend stabil + +**Coding-Hinweis** +- Rohwerte immer mit anzeigen, aber Standardfokus auf geglättetem Trend + +--- + +## K2. Körperzusammensetzung: Gewicht vs. Fettmasse vs. Magermasse + +**Typ:** kombinierter Linienchart oder gestapelter Flächenchart +**Ziel:** Gewichtsänderung in Bestandteile zerlegen + +**Einzubeziehende Werte** +- Gewicht +- BF% +- FM +- LBM + +**Berechnungen** +- FM = Gewicht × BF% +- LBM = Gewicht × (1 - BF%) +- 28d- und 90d-Änderung von FM und LBM + +**Aussagen** +- Gewichtsverlust stammt überwiegend aus Fettmasse +- Gewichtsverlust geht mit LBM-Verlust einher +- Gewicht bleibt stabil, aber Rekomposition liegt vor + +**Regelbasierte Hinweise** +- positiv bei Gewichtsreduktion/Rekomposition: FM runter, LBM stabil +- kritisch bei Kraftziel: LBM sinkt trotz Training/Protein +- positiv bei Kraftziel: LBM steigt bei stabiler oder leicht sinkender FM + +**Wichtig** +- BF%-Messungen via Caliper sind nützlich, aber messfehleranfällig; deshalb Confidence reduzieren, wenn Messfrequenz niedrig oder Abstände stark unregelmäßig sind + +--- + +## K3. Umfangs-Panel als Small Multiples + +**Typ:** 8 Mini-Liniencharts statt Radar als Standard +**Ziel:** lokale Veränderungen sichtbar machen + +**Einzubeziehende Werte** +- chest +- waist +- hip +- arm +- thigh_l / thigh_r +- calf_l / calf_r + +**Berechnungen** +- 28d und 90d Delta je Umfang +- links-rechts Asymmetrie bei Bein/Wade +- optional Verhältnis Taille/Hüfte +- optional Taille/Körpergröße + +**Aussagen** +- zentrale Adiposität verändert sich +- Muskelaufbau eher Oberkörper/Arme/Beine +- Asymmetrien wachsen oder sinken + +**Regelbasierte Hinweise** +- Gewichtsverlust ohne sinkende Taille = möglicher Hinweis auf Wasser-/Mess- oder Adhärenzthema +- Taillenumfang sinkt trotz stabilem Gewicht = sehr positives Signal für Rekomposition +- starke Seitendifferenz = Asymmetrie-Hinweis + +--- + +## K4. Rekompositions-Detektor + +**Typ:** Quadranten-Chart +**X-Achse:** 28d Δ FM +**Y-Achse:** 28d Δ LBM + +**Quadrantenlogik** +- **optimal recomposition:** FM runter, LBM rauf +- **cut with risk:** FM runter, LBM runter +- **bulk / gain:** FM rauf, LBM rauf +- **unfavorable:** FM rauf, LBM runter + +**Ziel:** schnelle Interpretation komplexer Körperveränderung + +**Einsatz:** besonders stark für Mischziele + +--- + +## K5. Body Progress Score (0–100) + +**Typ:** Score + Sparklines +**Ziel:** Zielbezogene Gesamtbewertung der Körperentwicklung + +**Komponenten je Zielmodus** + +```yaml +body_progress_score: + weight_loss: + fm_change: 0.40 + waist_change: 0.25 + weight_trend: 0.20 + lbm_preservation: 0.15 + strength: + lbm_change: 0.45 + arm_thigh_change: 0.20 + weight_trend: 0.10 + bf_stability: 0.10 + waist_stability: 0.15 + recomposition: + fm_change: 0.35 + lbm_change: 0.35 + waist_change: 0.20 + weight_neutrality: 0.10 +``` + +**Regel** +- Score nur anzeigen, wenn in den letzten 28 Tagen ausreichende Körperdaten vorliegen + +--- + +## 8. Diagrammkatalog – ERNÄHRUNG + +## E1. Energieaufnahme vs. geschätzter Energieverbrauch vs. Gewichtstrend + +**Typ:** kombinierter Linien-/Balkenchart +**Ziel:** Energielogik in Relation zum Gewicht sichtbar machen + +**Einzubeziehende Werte** +- Kalorienaufnahme täglich +- Trainingskalorien +- optional geschätzter Grundumsatz / TDEE +- Gewichtstrend + +**Berechnungen** +- tägliche Aufnahme +- 7d Durchschnitt Aufnahme +- Energieüberschuss/-defizit +- 7d geglättete Bilanz +- lagged comparison zur Gewichtsentwicklung (3d, 7d, 14d) + +**Aussagen** +- Bilanz passt nicht zur Gewichtsrealität +- Wochenendeffekt vorhanden +- Intake ist hoch volatil + +**Regelbasierte Hinweise** +- Gewicht sinkt nicht trotz dokumentiertem Defizit → Messlücke, Verbrauchsüberschätzung oder kurze Beobachtungsdauer möglich +- anhaltend großes Defizit + Recovery-Verschlechterung → Belastungshinweis +- deutliche Intake-Volatilität → Adhärenz-Hinweis + +**Wichtige fachliche Anmerkung** +Trainingskalorien aus Wearables sind ungenau. Für Scores sollten sie mit geringerer Priorität eingehen als dokumentierte Zufuhr und Gewichtsrealität. + +--- + +## E2. Protein adequacy chart (g/Tag, g/kg KG, optional g/kg LBM) + +**Typ:** Linienchart mit Zielband +**Ziel:** Protein nicht nur absolut, sondern relativ interpretieren + +**Einzubeziehende Werte** +- Protein g +- Körpergewicht +- optional LBM +- Zielmodus + +**Berechnungen** +- Protein absolut +- Protein g/kg Körpergewicht +- optional Protein g/kg LBM +- 7d und 28d Mittel +- Tage im Zielbereich + +**Default-Referenzlogik** +- allgemeine Aktivität / Gewichtsreduktion: individueller Zielkorridor konfigurierbar +- Kraft/Rekomposition: höherer Zielkorridor +- App-seitig als konfigurierbare Logik aufsetzen, nicht als starren globalen Wert + +**Mindestfachliche Defaults** +- für trainierende Personen ist höhere Proteinzufuhr als bei Inaktiven sinnvoll +- pro Mahlzeit bzw. Portion sind 20–40 g bzw. etwa 0,25 g/kg hochwertige Proteinmenge ein belastbarer Referenzanker + +**Regelbasierte Hinweise** +- Kraftziel + Protein an <4 Tagen/Woche im Zielband → Protein-Hinweis +- Defizitphase + Protein unter Zielband → Hinweis auf erhöhtes LBM-Verlustrisiko + +--- + +## E3. Makroverteilung und Wochenkonsistenz + +**Typ:** 100%-gestapelter Wochenbalken + Streuungsindikator +**Ziel:** weniger „Makro-Schönheit“, mehr Verhaltenskonsistenz + +**Einzubeziehende Werte** +- Protein +- Fett +- Kohlenhydrate +- Kalorien +- optional Zucker +- optional Ballaststoffe + +**Berechnungen** +- Wochenmittel je Makro +- Variationskoeffizient +- Wochenende vs. Werktag + +**Aussagen** +- stark schwankende Ernährung +- proteinarm an Belastungstagen +- hoher Zuckerschwerpunkt bei gleichzeitig niedrigem Ballaststoffniveau + +**Regelbasierte Hinweise** +- bei Gesundheit/Zielreduktion: hoher Zucker + niedrige Ballaststoffe → Qualitäts-Hinweis +- bei Ausdauer: Kohlenhydrate an Belastungstagen zu niedrig → Performance-Hinweis + +--- + +## E4. Ernährungs-Adhärenz-Score (0–100) + +**Typ:** Scorecard +**Ziel:** nicht die „perfekte Ernährung“, sondern Zieltreue messen + +**Komponenten** + +```yaml +nutrition_score: + weight_loss: + calorie_target_adherence: 0.35 + protein_target_adherence: 0.25 + intake_consistency: 0.20 + fiber_or_food_quality: 0.10 + sugar_control: 0.10 + strength: + protein_target_adherence: 0.35 + calorie_support: 0.25 + intake_consistency: 0.20 + carb_support_on_training_days: 0.20 + endurance: + energy_adequacy: 0.30 + carb_support: 0.30 + protein_adequacy: 0.20 + intake_consistency: 0.20 +``` + +**Hinweis** +Ballaststoffe und Zucker nur dann relevant gewichten, wenn Datenqualität ausreichend ist. + +--- + +## E5. Energieverfügbarkeits-Warnung (heuristisch, klar als Heuristik markieren) + +**Typ:** Ampel / Warnkarte +**Ziel:** Unterversorgung erkennen, ohne klinische Diagnose zu behaupten + +**Einzubeziehende Werte** +- Kalorienaufnahme +- Trainingskalorien +- Gewichtstrend +- Recovery Score +- Schlaf +- ggf. LBM + +**Heuristische Trigger** +- anhaltendes großes Defizit über mehrere Tage/Wochen +- Recovery sinkt gleichzeitig +- Schlaf verschlechtert sich gleichzeitig +- LBM sinkt gleichzeitig + +**Ausgabe** +- „mögliche Unterversorgung / zu aggressives Defizit“ +- keine Diagnoseformulierung + +--- + +## 9. Diagrammkatalog – AKTIVITÄT + +## A1. Trainingsvolumen pro Woche + +**Typ:** gestapelte Wochenbalken +**Ziel:** Umfang und Mix sichtbar machen + +**Einzubeziehende Werte** +- Dauer +- Trainingskalorien +- Trainingstyp +- quality_label + +**Berechnungen** +- Einheiten pro Woche +- Minuten pro Woche +- Minuten je Kategorie +- Anteil qualitativ brauchbarer Sessions + +**Aussagen** +- Training ist regelmäßig oder sprunghaft +- relevante Kategorien fehlen +- „viel trainiert“ vs. „viel dokumentiert, aber wenig qualitativ“ + +--- + +## A2. Intensitätsverteilung / Zonenbild + +**Typ:** gestapelter Flächenchart oder Wochenbalken +**Ziel:** Intensitätsmuster sichtbar machen + +**Einzubeziehende Werte** +- HR-Zonen-Verteilung, sobald vorhanden +- bis dahin: `avg_hr`, `max_hr`, `quality_label`, Trainingstyp + +**Fallback-Logik ohne echte Zonen** +- Proxy-Intensität in 3 Stufen: + - niedrig + - moderat + - hoch +- Ableitung aus Kombination von Dauer, avg_hr, max_hr und Typ + +**Aussagen** +- Ausdauerziel: zu wenig Grundlagentraining oder zu viel harte Belastung +- Gesundheit: Bewegung vorhanden, aber kaum moderat/vigorous +- Kraftziel: hohe HR ist kein Muss, daher Zonen nur begrenzt gewichten + +--- + +## A3. Trainingsqualitäts-Matrix + +**Typ:** Heatmap +**Achsen:** Woche × Trainingstyp +**Ziel:** Qualität nicht nur als Einzelbadge, sondern als Muster darstellen + +**Einzubeziehende Werte** +- training_type +- quality_label +- duration +- avg_hr / max_hr optional + +**Darstellung** +- Zellenfarbe = mittlere Qualitätsstufe +- Zellgröße optional = Anzahl Sessions + +**Nutzen** +- zeigt, ob bestimmte Typen regelmäßig unter Mindestqualität bleiben +- sehr gut für Coaching und Programmsteuerung + +--- + +## A4. Fähigkeiten-Balance / Ability Radar + +**Typ:** Radar + Zeittrend +**Ziel:** zeigen, welche Entwicklungsdimensionen systematisch trainiert werden + +**Einzubeziehende Werte** +- `training_types.abilities` +- Aktivitäten +- Dauer / Qualität + +**Berechnungen** +- wöchentlicher Ability Load je Dimension +- geglätteter 28d-Wert +- Balance-Index + +**Dimensionen** +- Kraft +- Ausdauer +- Mental +- Koordination +- Mobilität + +**Aussagen** +- Training ist einseitig +- Mobilität/Mental fehlen +- gute Balance oder starke Spezialisierung + +**Regelbasierte Hinweise** +- bei Stagnation und sehr einseitiger Belastung → Balance-Hinweis +- bei Gesundheit/Zielbreite → zu schmale Bewegungsbasis + +--- + +## A5. Load-Monitoring: interne Last, Monotony, Strain + +**Typ:** Wochenchart mit Warnbändern +**Ziel:** Belastungssteuerung sichtbar machen + +**Wichtiger fachlicher Hinweis** +Da `session_rpe` aktuell fehlt, sollte ein **Proxy-Load** verwendet werden und klar als Proxy gekennzeichnet sein. + +**Proxy-Load-Vorschlag** + +```yaml +proxy_internal_load: + formula: duration_min * intensity_factor * quality_factor + intensity_factor: + low: 1.0 + moderate: 1.5 + high: 2.0 + quality_factor: + excellent: 1.15 + very_good: 1.05 + good: 1.0 + acceptable: 0.9 + poor: 0.75 + excluded: 0.0 +``` + +**Darstellungen** +- Wochenlast +- Monotony = Wochenmittel / Wochen-Standardabweichung +- Strain = Wochenlast × Monotony + +**Wichtiger Hinweis** +Monotony/Strain sind nützliche Coach-Metriken, aber **nicht als medizinische Wahrheit** interpretieren. + +--- + +## A6. Aktivitäts-Goal-Alignment-Score (0–100) + +**Typ:** Scorecard +**Ziel:** Training im Verhältnis zum Nutzerziel bewerten + +**Beispiel-Logik** + +```yaml +activity_score: + weight_loss: + weekly_minutes: 0.25 + frequency: 0.20 + quality_sessions: 0.20 + strength_presence: 0.15 + activity_consistency: 0.20 + strength: + strength_session_presence: 0.35 + quality_strength_sessions: 0.25 + recovery_respect: 0.15 + supportive_cardio: 0.10 + consistency: 0.15 + endurance: + weekly_minutes: 0.25 + intensity_distribution: 0.25 + frequency: 0.20 + load_progression: 0.15 + recovery_respect: 0.15 +``` + +--- + +## A7. Ruhetags-/Recovery-Compliance + +**Typ:** Kalender-Heatmap +**Ziel:** zeigen, ob Belastung und Erholung zusammenpassen + +**Einzubeziehende Werte** +- geplante Ruhetage +- tatsächliche Aktivitäten +- Recovery Score +- Schlafschuld + +**Aussagen** +- Ruhetag wird respektiert oder regelmäßig „durchtrainiert“ +- schlechte Recovery trotz fehlender Ruhetage +- unnötig viele Ruhetage bei Leistungsziel + +--- + +## A8. VO2max-Entwicklung + +**Typ:** Linienchart +**Ziel:** kardiorespiratorische Entwicklung sichtbar machen + +**Einzubeziehende Werte** +- VO2max +- Aktivitätsvolumen +- Intensitätsverteilung +- Ruhepuls optional + +**Regelbasierte Hinweise** +- steigendes Volumen ohne VO2max-Fortschritt → Programm-/Intensitätsfrage +- sinkende VO2max + sinkende Aktivität → Deconditioning-Hinweis +- bei Kraftziel nur sekundär priorisieren + +--- + +## 10. Diagrammkatalog – KORRELATIONEN / KOMPLEXE ZUSAMMENHÄNGE + +## Grundsatz +Korrelationen müssen **explizit als explorativ** gekennzeichnet sein. Sie zeigen Hinweise, keine Beweise. + +Jeder Korrelationschart bekommt: +- Effektstärke +- Vorzeichen +- bestes Lag-Fenster +- Datenanzahl +- Confidence +- Text: „Hinweis“, nicht „Ursache“ + +--- + +## C1. Energie-Balance vs. Gewichtsveränderung (lagged) + +**Typ:** Lag-Heatmap oder Cross-Correlation Panel +**Ziel:** erkennen, nach wie vielen Tagen Energiebilanz und Gewicht am plausibelsten zusammenlaufen + +**Einzubeziehende Werte** +- tägliche Kalorienbilanz +- 7d Gewichtsänderung + +**Lags** +- 0, 3, 7, 10, 14 Tage + +**Ausgabe** +- bestes Lag +- Richtung des Zusammenhangs +- Confidence + +**Nutzen** +- deutlich besser als einfache Same-Day-Pearson-Korrelation + +--- + +## C2. Protein adequacy vs. LBM-Trend + +**Typ:** Scatter + Trend + 28d Fenstervergleich +**Ziel:** erkennen, ob Proteinversorgung mit Magermasse-Stabilität oder -Anstieg einhergeht + +**Einzubeziehende Werte** +- Protein g/kg +- LBM +- Krafttrainingslast + +**Hinweis** +Protein nie isoliert interpretieren; Training muss als Moderator mitgedacht werden. + +--- + +## C3. Trainingslast vs. HRV/RHR (1–3 Tage verzögert) + +**Typ:** duale Lag-Auswertung +**Ziel:** individuelle Ermüdungsreaktion erkennen + +**Einzubeziehende Werte** +- Proxy-Load / Volumen +- HRV +- RHR +- Schlaf + +**Lags** +- 1, 2, 3 Tage + +**Aussagen** +- hohe Last führt individuell eher zu HRV-Abfall / RHR-Anstieg +- keine klare Reaktion erkennbar +- Reaktion nur bei Schlafmangel ausgeprägt + +--- + +## C4. Schlafdauer + Schlafregularität vs. Recovery + +**Typ:** Bubble-/Quadrantenchart +**Achsen:** Schlafdauer, Schlafregularität +**Bubble:** Recovery Score + +**Ziel:** zeigen, dass nicht nur Menge, sondern auch Regelmäßigkeit relevant ist + +**Einzubeziehende Werte** +- Schlafdauer +- Bedtime/Waketime +- daraus Sleep Regularity Index oder einfacher Regularity Proxy +- Recovery Score + +**Empfehlung** +Mindestens eine einfache Regularitätsmetrik ergänzen: + +```yaml +sleep_regularity_proxy: + inputs: [bedtime, waketime] + metric: mean_absolute_shift_from_previous_day_minutes + lower_is_better: true +``` + +Optional später: +- echter `Sleep Regularity Index (SRI)` + +--- + +## C5. Blutdruck-Kontextmatrix vs. Schlaf / Training / Stress-naher Kontext + +**Typ:** Matrix / Boxplots nach Kontext +**Ziel:** nicht nur Höhe, sondern Situation verstehen + +**Einzubeziehende Werte** +- systolisch +- diastolisch +- Puls +- Messkontext +- Schlaf der Vor-Nacht +- Training am selben / Vortag + +**Aussagen** +- Blutdruck morgens systematisch höher +- Training scheint akut positiv/neutral/negativ assoziiert +- Stress-Kontexte sind Ausreißerträger + +**Medizinischer Hinweis** +Das System soll hier eher **Screening-/Monitoring-Logik** abbilden, nicht Diagnose. + +--- + +## C6. Plateau-Detektor + +**Typ:** Ereignis-Karte / Warnpanel +**Ziel:** „viel Einsatz, wenig Fortschritt“ identifizieren + +**Plateau-Definitionen je Zielmodus** + +```yaml +plateau_logic: + weight_loss: + condition: + - 28d_weight_slope_flat + - waist_change_small + - adherence_high + strength: + condition: + - activity_score_high + - lbm_change_flat + - recovery_low_or_variable + endurance: + condition: + - volume_high + - vo2max_flat + - monotony_high +``` + +**Ausgabe** +- Plateau wahrscheinlich / möglich / nicht erkennbar +- Top-3 plausible Einflussfaktoren + +--- + +## C7. Multi-Faktor Driver Panel + +**Typ:** priorisierte Einflusskarten +**Ziel:** ohne KI eine „Top-Treiber“-Ansicht erzeugen + +**Mechanik** +Das System bewertet für ein Ziel die wichtigsten beeinflussbaren Faktoren regelbasiert: +- Energie +- Protein +- Schlafdauer +- Schlafregelmäßigkeit +- Trainingskonsistenz +- Qualitätsanteil +- Recovery +- Ruhetagsrespekt + +**Ausgabe pro Faktor** +- Status: förderlich / neutral / hinderlich +- Evidenz: hoch / mittel / niedrig +- Begründung: 1 Satz + +Das ist eine sehr starke Brücke zur späteren KI-Stufe. + +--- + +## 11. Zentrale Scores + +## S1. Recovery / Readiness Score (verbesserte Version) + +Die bestehende Idee ist gut, sollte aber robuster umgesetzt werden. + +### Eingangswerte +- HRV vs. 28d Baseline +- RHR vs. 28d Baseline +- Schlafdauer vs. Ziel +- Schlafschuld +- Schlafregularität +- Vorbelastung 1–3 Tage +- Messqualität / Datenvollständigkeit + +### Empfohlene Logik +Nicht rohe Quotienten, sondern normalisierte Abweichungszonen. + +```yaml +recovery_score: + components: + hrv_status: 0.25 + rhr_status: 0.20 + sleep_duration: 0.20 + sleep_debt: 0.10 + sleep_regularity: 0.10 + recent_load_balance: 0.10 + data_quality: 0.05 + output: + 0_39: low + 40_59: strained + 60_79: normal + 80_100: ready +``` + +### Zusätzliche Regeln +- Kein Score bei zu wenigen validen Datenpunkten +- Fallback-Modus ohne HRV möglich +- Separate Anzeige „Messqualität“ + +--- + +## S2. Health Risk / Health Stability Score + +**Typ:** Score 0–100 +**Ziel:** allgemeine Gesundheitsstabilität abbilden + +**Komponenten** +- Blutdruckstatus +- Schlafbasis +- Bewegungsbasis +- Gewicht/Umfangsrisiko +- Regelmäßigkeit + +**Hinweis** +Dieser Score ist für allgemeine Gesundheit sehr sinnvoll, aber für Leistungssport sekundär. + +--- + +## S3. Goal Progress Score (Meta-Score) + +**Typ:** Zielabhängiger Gesamtscore +**Formel:** gewichtete Summe aus Body, Nutrition, Activity, Recovery, Health Risk + +**Regeln** +- nie ohne Teilscore-Transparenz anzeigen +- immer mit Treiberaufschlüsselung +- nie als „Gesundheitsnote“ formulieren + +--- + +## S4. Data Quality Score + +**Typ:** technischer Fachscore +**Ziel:** schlechte Daten nicht mit scheinbarer Präzision interpretieren + +**Komponenten** +- Messhäufigkeit +- Regelmäßigkeit +- Lücken +- Konsistenz +- Anzahl gepaarter Datenpunkte für Korrelationen + +Dieser Score ist extrem wichtig. Ohne ihn werden schlechte Daten zu scheinbar präzisen Aussagen führen. + +--- + +## 12. Regelbasierte Aussagen und Empfehlungen ohne KI + +Die Formulierungen sollten knapp, neutral und nachvollziehbar sein. + +## 12.1 Beispiel-Statements – Gewichtsreduktion + +### Positiv +- „Dein 28-Tage-Trend zeigt eine stabile Gewichtsabnahme innerhalb eines nachhaltigen Bereichs.“ +- „Der Taillenumfang sinkt parallel zum Gewicht. Das spricht für echten Fortschritt.“ +- „Die Magermasse ist trotz Defizit stabil. Das ist in einer Reduktionsphase ein gutes Signal.“ + +### Neutral / Beobachtung +- „Die Tageswerte schwanken deutlich, der 28-Tage-Trend ist aber nahezu stabil.“ +- „Die dokumentierte Kalorienbilanz spricht für ein Defizit, das Gewicht reagiert bisher aber nur schwach.“ + +### Kritisch +- „Die Magermasse sinkt parallel zur Gewichtsabnahme. Prüfe Defizithöhe, Protein und Krafttraining.“ +- „Der Gewichtsverlauf stagniert seit 4 Wochen trotz hoher Dokumentationsrate.“ +- „Das Defizit wirkt im Verhältnis zu Recovery und Schlaf aktuell eher aggressiv.“ + +## 12.2 Beispiel-Statements – Kraftaufbau +- „Dein Trainingsmuster ist konsistent, die Körperdaten zeigen aber noch keinen klaren LBM-Anstieg.“ +- „Die Proteinzufuhr liegt an mehreren Tagen unter dem Zielbereich für dein Kraftziel.“ +- „Recovery und Schlaf sprechen aktuell gegen weitere Steigerung der Trainingslast.“ +- „Deine Ability-Balance ist stark kraftlastig, Mobilität und Regeneration sind schwach ausgeprägt.“ + +## 12.3 Beispiel-Statements – Ausdauer +- „Die Wochenminuten steigen, aber die Intensitätsverteilung ist unausgeglichen.“ +- „VO2max verbessert sich trotz Trainingsvolumen derzeit nicht sichtbar.“ +- „Die letzten Wochen zeigen hohe Monotonie. Das spricht für wenig Variation in der Belastung.“ + +## 12.4 Beispiel-Statements – Gesundheit +- „Dein Schlaf liegt wiederholt unter 7 Stunden und ist unregelmäßig.“ +- „Die Bewegung erfüllt den Mindestrahmen teilweise, Kraftreize fehlen jedoch.“ +- „Deine Blutdruckwerte sollten im Verlauf enger beobachtet werden, insbesondere im Morgenkontext.“ + +--- + +## 13. Medizinische und fachliche Grenzlogik + +## 13.1 Bewegung +Für erwachsene Personen sind als allgemeiner Gesundheitsrahmen mindestens **150–300 Minuten moderate** oder **75–150 Minuten intensive** Aktivität pro Woche plus **Muskelkräftigung an mindestens 2 Tagen/Woche** ein sinnvoller Referenzrahmen. Dieser Rahmen ist für die Kategorie „Gesundheit“ wichtig, aber nicht automatisch Leistungsoptimum. [R1] + +## 13.2 Schlaf +Für Erwachsene ist **7+ Stunden Schlaf pro Nacht** ein belastbarer allgemeiner Mindestanker. Schlafdauer allein reicht aber nicht; Regelmäßigkeit und Qualität sind zusätzlich relevant. [R2][R8][R9] + +## 13.3 Protein +Für Trainierende ist eine relativ höhere Proteinzufuhr fachlich sinnvoll; als gut belastbare Referenz für die Darstellung eignen sich **20–40 g pro Proteinportion** oder etwa **0,25 g/kg pro Portion**. Tagesziele sollten zielabhängig und konfigurierbar sein. [R3] + +## 13.4 Blutdruck +Die App sollte sich in der Darstellung am aktuellen europäischen Begriffsrahmen orientieren: **elevated BP** bei Büro-/Praxiswerten **120–139 systolisch oder 70–89 diastolisch**, Hypertonie wie bisher ab **≥140/90 mmHg**. Für die App heißt das: Verlauf und Kontext zeigen, aber Diagnose vermeiden. [R4] + +## 13.5 Trainingslast +Trainingsmonitoring ist fachlich sinnvoll, aber nie mit nur einer Kennzahl. Deshalb sollten Last, Erholung, Schlaf und Ziel-Fortschritt immer gemeinsam interpretiert werden. [R5] + +--- + +## 14. Priorisierte Umsetzungsreihenfolge + +## Phase 1 – sofort umsetzen +1. Gewichtstrend + Zielprojektion +2. Gewicht/FM/LBM-Chart +3. Umfangs-Panel +4. Energieaufnahme vs. Gewichtstrend +5. Protein adequacy chart +6. Trainingsvolumen pro Woche +7. Trainingsqualitäts-Matrix +8. Recovery Score verbessert +9. Goal Progress Score +10. einfache Korrelationen mit Confidence und Mindestdatenlogik + +## Phase 2 – stark empfohlen +1. Sleep Regularity Proxy +2. Lag-Korrelationen +3. Plateaudetektor +4. Driver Panel +5. Health Stability Score +6. Ability-Balance-Radar +7. Ruhetags-/Recovery-Compliance + +## Phase 3 – Vorbereitung für KI +1. Feature Store je 7/28/90 Tage +2. erklärbare Vorstufen-Texte aus Regeln +3. Ereignismarker (Infekt, Reise, Wettkampf, Stressphasen) +4. subjektive Daten (RPE, Müdigkeit, Schmerz, Motivation) + +--- + +## 15. Konkrete technische Spezifikation für den Coding Agent + +## 15.1 Chart Definition Contract + +```json +{ + "chart_id": "K1_weight_trend", + "category": "body", + "title": "Gewichtstrend + Zielprojektion", + "purpose": "Tagesrauschen von echter Entwicklung trennen", + "required_fields": ["weight.date", "weight.value", "profile.target_weight"], + "optional_fields": ["profile.height_cm"], + "derived_metrics": [ + "weight_7d_median", + "weight_28d_slope", + "weight_90d_slope", + "goal_projection_date", + "goal_progress_pct" + ], + "default_range": "90d", + "goal_relevance": ["weight_loss", "recomposition", "health"], + "confidence_logic": { + "7d_min_points": 4, + "28d_min_points": 18, + "90d_min_points": 60 + }, + "interpretation_rules": [ + "stable_downward_trend", + "volatile_but_flat", + "rapid_loss_with_lbm_drop" + ], + "recommendation_rules": [ + "review_deficit", + "review_protein", + "hold_course" + ] +} +``` + +## 15.2 Statement Contract + +```json +{ + "statement_id": "WL_rapid_loss_lbm_drop", + "severity": "warning", + "goal_modes": ["weight_loss", "recomposition"], + "conditions": [ + "weight_28d_slope < -0.75_percent_per_week", + "lbm_28d_change < -0.5kg", + "nutrition_score < 70 OR protein_target_days_per_week < 4" + ], + "message": "Der Gewichtsverlust ist aktuell relativ schnell und geht mit sinkender Magermasse einher. Prüfe Defizithöhe, Protein und Kraftreize.", + "confidence": "derived_from_data_quality" +} +``` + +## 15.3 Score Contract + +```json +{ + "score_id": "goal_progress_score", + "range": [0, 100], + "goal_mode_dependent": true, + "components": [ + "body_progress_score", + "nutrition_score", + "activity_score", + "recovery_score", + "health_risk_score" + ], + "weights_source": "config/goal_weights.yaml", + "output": { + "value": 78, + "label": "gut", + "drivers_positive": ["protein_adherence", "stable_weight_trend"], + "drivers_negative": ["sleep_irregularity"] + } +} +``` + +--- + +## 16. Fachliches Fazit + +## Aus Perspektive der Datenanalyse +Ich würde das Modell klar als **mehrdimensional leistungsfähig** einstufen. Es ist weit über einem typischen Fitness-Tracker, weil es Körper, Training, Schlaf, Vitalwerte und Ernährung zusammenführt. + +## Aus Perspektive eines Sportwissenschaftlers / Gesundheitsanalysten +Der größte Mehrwert entsteht nicht durch noch mehr Einzelmetriken, sondern durch: +- **zielabhängige Interpretation** +- **Baseline-Logik statt bloßer Rohwerte** +- **lag-basierte Zusammenhänge statt naiver Gleichzeitigkeit** +- **Confidence / Datenqualität** +- **klare Trennung zwischen Gesundheitsmonitoring, Performance und Heuristik** + +## Zusammengeführt +Die beste Umsetzung ist **nicht** eine endlose Sammlung bunter Charts. Die beste Umsetzung ist ein System mit: +1. wenigen, starken Standardcharts, +2. robusten Scores, +3. erklärbaren Regeln, +4. einer sauberen Brücke zur späteren KI. + +Wenn diese Struktur sauber umgesetzt wird, ist bereits ohne KI ein sehr hoher fachlicher Nutzen erreichbar. + +--- + +## 17. Referenzbasis + +**[R1]** ACSM / Physical Activity Guidelines: mindestens 150–300 min moderate oder 75–150 min intensive Aktivität pro Woche plus Muskelkräftigung an mindestens 2 Tagen/Woche. +Quelle: ACSM, Physical Activity Guidelines FAQ +https://acsm.org/physical-activity-guidelines-faqs/ + +**[R2]** AASM / Sleep Research Society: Erwachsene sollten regelmäßig 7 oder mehr Stunden pro Nacht schlafen. +Quelle: AASM +https://aasm.org/seven-or-more-hours-of-sleep-per-night-a-health-necessity-for-adults/ + +**[R3]** ISSN Position Stand Protein and Exercise: allgemeine Empfehlung pro Portion etwa 0,25 g/kg bzw. 20–40 g hochwertiges Protein; höhere Gesamtzufuhr bei Trainierenden oft sinnvoll. +Quelle: Journal of the International Society of Sports Nutrition +https://link.springer.com/article/10.1186/s12970-017-0177-8 + +**[R4]** ESC 2024 Guidelines: neue Kategorie „elevated BP“ 120–139 mmHg systolisch oder 70–89 mmHg diastolisch; Hypertonie weiterhin ab ≥140/90 mmHg. +Quelle: European Society of Cardiology +https://www.escardio.org/news/news-room/congress-news/2024-esc-clinical-practice-guidelines-for-the-management-of-elevated-blood-pressure-and-hypertension/ + +**[R5]** Athlete load monitoring soll als gemeinsamer Rahmen aus Last, Reaktion und Kontext verstanden werden, nicht als Einzelmetrik. +Quelle: Monitoring Athlete Training Loads: Consensus Statement +https://pubmed.ncbi.nlm.nih.gov/28463642/ + +**[R6]** Progressive resistance training muss zielgerichtet ausgestaltet werden; starre Einheitsregeln sind fachlich ungeeignet. +Quelle: ACSM Position Stand – Progression Models in Resistance Training for Healthy Adults +https://pubmed.ncbi.nlm.nih.gov/19204579/ + +**[R7]** Für Gewichtsreduktion gelten etwa 0,5–1,0 kg/Woche allgemein als sicher und nachhaltig, aber individualisiert. +Quelle: NICE Overweight and Obesity Management +https://www.nice.org.uk/guidance/ng246/resources/a-guide-for-prescribing-medicines-to-manage-overweight-and-obesity-15299628589/chapter/Prescribing-reviewing-and-stopping-tirzepatide + +**[R8]** Schlafregularität ist als Gesundheitsfaktor eigenständig relevant und teils prädiktiver als bloße Schlafdauer. +Quelle: Sleep regularity is a stronger predictor of mortality risk than sleep duration +https://pubmed.ncbi.nlm.nih.gov/37738616/ + +**[R9]** Für die Messung von Schlafregularität sind mehrere Metriken möglich; Auswahl hängt von Studiendauer und Ziel ab. +Quelle: Measuring sleep regularity: theoretical properties and practical usage of existing metrics +https://pubmed.ncbi.nlm.nih.gov/33864369/ diff --git a/.claude/docs/functional/mitai_jinkendo_konzept_diagramme_auswertungen_v2.md b/.claude/docs/functional/mitai_jinkendo_konzept_diagramme_auswertungen_v2.md new file mode 100644 index 0000000..382fef7 --- /dev/null +++ b/.claude/docs/functional/mitai_jinkendo_konzept_diagramme_auswertungen_v2.md @@ -0,0 +1,2086 @@ +# Konzept – Diagramme, Scores und regelbasierte Auswertungen für Mitai Jinkendo + +**Stand:** 24.03.2026 +**Ziel:** Fachlich belastbares Konzept für Diagramme, Kennzahlen, Scores, Warnungen und regelbasierte Empfehlungen **ohne KI**, aber so strukturiert, dass ein Coding Agent es direkt umsetzen kann. +**Basis:** Analyse des hochgeladenen Datenmodells `DATA_ARCHITECTURE.md` (Gewicht, Umfänge, Caliper, Aktivitäten, Vitalwerte, Blutdruck, Schlaf, Ernährung, geplante Recovery-/Korrelations-Logik). + +--- + +## 1. Zielbild + +Das System soll vor einer späteren KI-Stufe bereits drei Ebenen sauber abdecken: + +1. **Deskriptiv:** Was ist passiert? + - Zeitreihen, Verteilungen, Trends, Volumina, Zielabweichungen +2. **Diagnostisch:** Was hängt wahrscheinlich zusammen? + - Lag-basierte Zusammenhänge, Baseline-Abweichungen, Muster, Plateaus, Wechselwirkungen +3. **Präskriptiv (regelbasiert, nicht KI):** Was ist als nächste Handlung naheliegend? + - konkrete, nachvollziehbare Hinweise mit Trigger-Logik und Vertrauensstufe + +Wichtig ist, dass das System **zielabhängig** interpretiert: +- Gewichtsreduktion +- Muskel-/Kraftaufbau +- Konditions-/Ausdaueraufbau +- Körperrekomposition +- allgemeine Gesundheit +- Mischziele + +Dasselbe Rohsignal kann je nach Ziel anders bewertet werden. Ein Kaloriendefizit ist z. B. bei Gewichtsreduktion oft positiv, bei Kraftaufbau aber potenziell hinderlich. + +--- + +## 2. Fachliche Bewertung des aktuellen Datenmodells + +## 2.1 Stärken des vorhandenen Modells + +Das vorhandene Modell ist bereits ungewöhnlich stark, weil es mehrere Domänen zusammenführt: +- **Körper:** Gewicht, Umfänge, Caliper, BF%, LBM, FM +- **Aktivität:** Trainingstypen, Qualität, Dauer, HR, Kalorien, Fähigkeiten-Beitrag +- **Recovery / Gesundheit:** RHR, HRV, VO2max, SpO2, Atemfrequenz, Blutdruck +- **Lifestyle:** Schlafdauer, Schlafsegmente, Ernährung, Ruhetage +- **Korrelationen / spätere KI:** bereits als Richtung angelegt + +Dadurch sind nicht nur Einzelcharts möglich, sondern **wirkliche Mehrfaktorauswertungen**. Das ist ein zentraler Vorteil des Modells. + +## 2.2 Fachliche Schwächen / Risiken im aktuellen Konzept + +### A. Recovery Score aktuell zu grob +Die aktuell vorgesehene Recovery-Logik (HRV vs. 7-Tage-Mittel, RHR invertiert, Schlafqualität) ist als Startpunkt brauchbar, aber fachlich zu fragil für harte Entscheidungen. + +Probleme: +- HRV ist stark mess- und tagesformabhängig. +- Ein bloßer Quotient gegen den 7-Tage-Mittelwert ist ausreißeranfällig. +- Es fehlt eine **Messqualitätslogik**. +- Es fehlt eine **individuelle Normalzone** statt reinem Durchschnitt. +- Es fehlt eine **Kontextlogik** für Schlafschuld, Vorbelastung und Messlücken. + +### B. Trainingsqualität darf nicht HR-zentriert sein +Die vorhandene `quality_label`-Logik ist für Cardio sinnvoll, aber nicht für alle Trainingstypen. Krafttraining, Mobility, Meditation oder Techniktraining dürfen nicht primär an Avg-HR/Max-HR gemessen werden. + +### C. BMI nur als Nebenindikator +BMI kann angezeigt werden, sollte aber im System **nicht dominant** interpretiert werden, wenn bereits BF%, LBM und Umfänge vorliegen. + +### D. Korrelationen nicht als simples Pearson auf Rohzeitreihen +Reine Pearson-Korrelationen auf gleitenden Alltagsdaten können bei Trends, Saisonmustern und Lags irreführend sein. Für Trainings- und Ernährungsdaten sind **Verzögerungen (lags)**, Detrending und Mindeststichproben deutlich robuster. + +### E. Zielbezug fehlt noch als Steuerungszentrum +Fachlich entscheidend ist ein expliziter `goal_mode`, der Score-Gewichtung, Diagramm-Priorisierung und Textbewertung steuert. + +--- + +## 3. Zwingende Analyse-Prinzipien für alle Diagramme und Scores + +Diese Regeln sollten global gelten. + +## 3.1 Analysefenster +Für fast alle Kennzahlen sollten parallel drei Ebenen geführt werden: +- **kurzfristig:** 7 Tage +- **mittelfristig:** 28 Tage +- **langfristig:** 90 Tage + +Empfehlung: +- 7 Tage = Tagesform / Reaktion +- 28 Tage = Verhalten / Adhärenz +- 90 Tage = echte Entwicklung + +## 3.2 Mindestdatenmenge / Confidence +Jede Auswertung bekommt eine **Confidence-Klasse**: +- **hoch** = ausreichend Daten, wenig Lücken, mehrere Messpunkte +- **mittel** = brauchbar, aber eingeschränkt +- **niedrig** = nur orientierend +- **nicht auswertbar** = Datenbasis zu klein + +Default-Regeln: +- 7d-Auswertung nur ab **mind. 4 gültigen Tagen** +- 28d-Auswertung nur ab **mind. 18 gültigen Tagen** +- 90d-Auswertung nur ab **mind. 60 gültigen Tagen** +- Korrelationen nur ab **mind. 21 gepaarten Tageswerten** +- lag-basierte Korrelationen nur ab **mind. 28 gepaarten Tagen** + +## 3.3 Ausreißerbehandlung +Für Gewicht, HRV, RHR, Blutdruck, Schlaf und Ernährung sollten extreme Einzelwerte nicht direkt in Scores laufen. + +Empfehlung: +- primär **Rolling Median** oder **EWMA** für Trenddarstellung +- Ausreißererkennung per **Hampel Filter** oder Winsorizing +- Rohwert bleibt sichtbar, geht aber nicht ungefiltert in Score-Berechnung + +## 3.4 Individuelle Baselines statt nur Normwerte +Für Sport- und Recovery-Daten sind **intraindividuelle Baselines** oft wichtiger als Populationsnormen. + +Empfehlung: +- HRV, RHR, Schlafdauer, Blutdruck, Gewicht, Trainingsvolumen immer gegen + - 7d Trend + - 28d Baseline + - optional 90d Baseline + vergleichen + +## 3.5 Lags immer mitdenken +Viele Effekte sind verzögert: +- Kalorienbilanz → Gewicht oft mit 2–14 Tagen Verzögerung +- Protein / Krafttraining → LBM eher mit 2–6 Wochen Verzögerung +- Trainingslast → HRV/RHR häufig mit 1–3 Tagen Verzögerung +- Schlafdefizit → Recovery / Leistung typischerweise 1–3 Tage verzögert + +Darum sollen Korrelationen nie nur `lag=0` prüfen. + +## 3.6 Medizinischer Sicherheitsmodus +Gesundheitsnahe Kennzahlen müssen in drei Schichten bewertet werden: +- **Info** +- **Hinweis** +- **Abklärung empfohlen** + +Das System darf motivieren und priorisieren, aber **nicht diagnostizieren**. + +--- + +## 4. Zusätzliche Felder, die fachlich sehr sinnvoll wären + +Diese Felder sind nicht zwingend für die erste Umsetzung, würden aber die Qualität stark erhöhen: + +### Pflichtnah für Phase 2 +- `goal_mode` (weight_loss, strength, endurance, recomposition, health) +- `secondary_goals[]` +- `goal_priority_weights` +- `training_age` / Trainingserfahrung +- `measurement_time_consistency` (z. B. Gewicht morgens nüchtern ja/nein) +- `illness_flag` +- `travel_flag` +- `competition_flag` + +### Sehr wertvoll +- subjektive Belastung / `session_rpe` +- subjektive Müdigkeit / `fatigue_score` +- subjektive Schlafqualität, wenn Gerätedaten fehlen +- Schrittzahl / Alltagsbewegung +- geschätzter Grundumsatz / TDEE +- Taillenumfang-zu-Körpergröße (`waist_to_height_ratio`) +- standardisierte Blutdruck-Heimmessung-Markierung + +Ohne diese Felder bleiben einige Interpretationen möglich, aber weniger präzise. + +--- + +## 5. Ziel-Modi und ihre Wirkung auf Diagramme und Scores + +## 5.1 Ziel-Modi + +```yaml +goal_modes: + weight_loss: + focus: [fettmasse, gewichtstrend, kalorienbilanz, protein_sicherung, aktivitaetsvolumen, schlaf] + strength: + focus: [trainingsqualitaet, protein, lbm, recovery, progressionslogik, schlaf] + endurance: + focus: [trainingsvolumen, intensitaetsverteilung, vo2max, recovery, schlaf, energieverfuegbarkeit] + recomposition: + focus: [lbm, fettmasse, protein, trainingsqualitaet, kalorienbalance, schlaf] + health: + focus: [bewegung, blutdruck, schlaf, gewicht, taille, regelmaessigkeit, moderates trainingsvolumen] +``` + +## 5.2 Score-Gewichtung je Ziel + +```yaml +score_weights: + weight_loss: + body_progress: 0.30 + nutrition: 0.25 + activity: 0.20 + recovery: 0.15 + health_risk: 0.10 + strength: + body_progress: 0.20 + nutrition: 0.25 + activity: 0.30 + recovery: 0.20 + health_risk: 0.05 + endurance: + body_progress: 0.10 + nutrition: 0.20 + activity: 0.35 + recovery: 0.25 + health_risk: 0.10 + recomposition: + body_progress: 0.30 + nutrition: 0.25 + activity: 0.25 + recovery: 0.15 + health_risk: 0.05 + health: + body_progress: 0.20 + nutrition: 0.20 + activity: 0.20 + recovery: 0.20 + health_risk: 0.20 +``` + +--- + +## 6. Kategoriemodell für Visualisierungen + +Die Diagramme werden in vier Hauptgruppen ausgeliefert: +- **Körper** +- **Ernährung** +- **Aktivität** +- **Korrelation** + +Jede Visualisierung erhält: +- `chart_id` +- `category` +- `title` +- `purpose` +- `required_fields` +- `optional_fields` +- `derived_metrics` +- `aggregation` +- `default_range` +- `goal_relevance` +- `interpretation_rules` +- `recommendation_rules` +- `confidence_logic` + +--- + +## 7. Diagrammkatalog – KÖRPER + +## K1. Gewichtstrend + Trendkanal + Zielprojektion + +**Typ:** Linienchart mit Trendband +**Ziel:** echte Entwicklung statt Tagesrauschen sichtbar machen + +**Einzubeziehende Werte** +- `weight.value` +- Profilgröße für BMI optional +- Zielgewicht +- Startgewicht + +**Berechnungen** +- Tagesgewicht (Rohwerte) +- 7d Rolling Median +- 28d Trend-Slope +- 90d Trend-Slope +- Zielprojektion auf Basis 28d-Trend +- prozentuale Zielannäherung + +**Aussagen** +- Gewicht sinkt/stagniert/steigt +- Trend ist stabil oder volatil +- aktuelles Tempo ist schneller/langsamer als nachhaltiger Zielkorridor + +**Regelbasierte Hinweise** +- bei Gewichtsreduktion: Warnung, wenn 28d-Trend = flach und dokumentierte Energiebilanz im Defizit +- Hinweis, wenn Gewichtsverlust sehr schnell ist und gleichzeitig LBM sinkt +- Hinweis auf Messrauschen, wenn Tagesvolatilität hoch, aber 28d-Trend stabil + +**Coding-Hinweis** +- Rohwerte immer mit anzeigen, aber Standardfokus auf geglättetem Trend + +--- + +## K2. Körperzusammensetzung: Gewicht vs. Fettmasse vs. Magermasse + +**Typ:** kombinierter Linienchart oder gestapelter Flächenchart +**Ziel:** Gewichtsänderung in Bestandteile zerlegen + +**Einzubeziehende Werte** +- Gewicht +- BF% +- FM +- LBM + +**Berechnungen** +- FM = Gewicht × BF% +- LBM = Gewicht × (1 - BF%) +- 28d- und 90d-Änderung von FM und LBM + +**Aussagen** +- Gewichtsverlust stammt überwiegend aus Fettmasse +- Gewichtsverlust geht mit LBM-Verlust einher +- Gewicht bleibt stabil, aber Rekomposition liegt vor + +**Regelbasierte Hinweise** +- positiv bei Gewichtsreduktion/Rekomposition: FM runter, LBM stabil +- kritisch bei Kraftziel: LBM sinkt trotz Training/Protein +- positiv bei Kraftziel: LBM steigt bei stabiler oder leicht sinkender FM + +**Wichtig** +- BF%-Messungen via Caliper sind nützlich, aber messfehleranfällig; deshalb Confidence reduzieren, wenn Messfrequenz niedrig oder Abstände stark unregelmäßig sind + +--- + +## K3. Umfangs-Panel als Small Multiples + +**Typ:** 8 Mini-Liniencharts statt Radar als Standard +**Ziel:** lokale Veränderungen sichtbar machen + +**Einzubeziehende Werte** +- chest +- waist +- hip +- arm +- thigh_l / thigh_r +- calf_l / calf_r + +**Berechnungen** +- 28d und 90d Delta je Umfang +- links-rechts Asymmetrie bei Bein/Wade +- optional Verhältnis Taille/Hüfte +- optional Taille/Körpergröße + +**Aussagen** +- zentrale Adiposität verändert sich +- Muskelaufbau eher Oberkörper/Arme/Beine +- Asymmetrien wachsen oder sinken + +**Regelbasierte Hinweise** +- Gewichtsverlust ohne sinkende Taille = möglicher Hinweis auf Wasser-/Mess- oder Adhärenzthema +- Taillenumfang sinkt trotz stabilem Gewicht = sehr positives Signal für Rekomposition +- starke Seitendifferenz = Asymmetrie-Hinweis + +--- + +## K4. Rekompositions-Detektor + +**Typ:** Quadranten-Chart +**X-Achse:** 28d Δ FM +**Y-Achse:** 28d Δ LBM + +**Quadrantenlogik** +- **optimal recomposition:** FM runter, LBM rauf +- **cut with risk:** FM runter, LBM runter +- **bulk / gain:** FM rauf, LBM rauf +- **unfavorable:** FM rauf, LBM runter + +**Ziel:** schnelle Interpretation komplexer Körperveränderung + +**Einsatz:** besonders stark für Mischziele + +--- + +## K5. Body Progress Score (0–100) + +**Typ:** Score + Sparklines +**Ziel:** Zielbezogene Gesamtbewertung der Körperentwicklung + +**Komponenten je Zielmodus** + +```yaml +body_progress_score: + weight_loss: + fm_change: 0.40 + waist_change: 0.25 + weight_trend: 0.20 + lbm_preservation: 0.15 + strength: + lbm_change: 0.45 + arm_thigh_change: 0.20 + weight_trend: 0.10 + bf_stability: 0.10 + waist_stability: 0.15 + recomposition: + fm_change: 0.35 + lbm_change: 0.35 + waist_change: 0.20 + weight_neutrality: 0.10 +``` + +**Regel** +- Score nur anzeigen, wenn in den letzten 28 Tagen ausreichende Körperdaten vorliegen + +--- + +## 8. Diagrammkatalog – ERNÄHRUNG + +## E1. Energieaufnahme vs. geschätzter Energieverbrauch vs. Gewichtstrend + +**Typ:** kombinierter Linien-/Balkenchart +**Ziel:** Energielogik in Relation zum Gewicht sichtbar machen + +**Einzubeziehende Werte** +- Kalorienaufnahme täglich +- Trainingskalorien +- optional geschätzter Grundumsatz / TDEE +- Gewichtstrend + +**Berechnungen** +- tägliche Aufnahme +- 7d Durchschnitt Aufnahme +- Energieüberschuss/-defizit +- 7d geglättete Bilanz +- lagged comparison zur Gewichtsentwicklung (3d, 7d, 14d) + +**Aussagen** +- Bilanz passt nicht zur Gewichtsrealität +- Wochenendeffekt vorhanden +- Intake ist hoch volatil + +**Regelbasierte Hinweise** +- Gewicht sinkt nicht trotz dokumentiertem Defizit → Messlücke, Verbrauchsüberschätzung oder kurze Beobachtungsdauer möglich +- anhaltend großes Defizit + Recovery-Verschlechterung → Belastungshinweis +- deutliche Intake-Volatilität → Adhärenz-Hinweis + +**Wichtige fachliche Anmerkung** +Trainingskalorien aus Wearables sind ungenau. Für Scores sollten sie mit geringerer Priorität eingehen als dokumentierte Zufuhr und Gewichtsrealität. + +--- + +## E2. Protein adequacy chart (g/Tag, g/kg KG, optional g/kg LBM) + +**Typ:** Linienchart mit Zielband +**Ziel:** Protein nicht nur absolut, sondern relativ interpretieren + +**Einzubeziehende Werte** +- Protein g +- Körpergewicht +- optional LBM +- Zielmodus + +**Berechnungen** +- Protein absolut +- Protein g/kg Körpergewicht +- optional Protein g/kg LBM +- 7d und 28d Mittel +- Tage im Zielbereich + +**Default-Referenzlogik** +- allgemeine Aktivität / Gewichtsreduktion: individueller Zielkorridor konfigurierbar +- Kraft/Rekomposition: höherer Zielkorridor +- App-seitig als konfigurierbare Logik aufsetzen, nicht als starren globalen Wert + +**Mindestfachliche Defaults** +- für trainierende Personen ist höhere Proteinzufuhr als bei Inaktiven sinnvoll +- pro Mahlzeit bzw. Portion sind 20–40 g bzw. etwa 0,25 g/kg hochwertige Proteinmenge ein belastbarer Referenzanker + +**Regelbasierte Hinweise** +- Kraftziel + Protein an <4 Tagen/Woche im Zielband → Protein-Hinweis +- Defizitphase + Protein unter Zielband → Hinweis auf erhöhtes LBM-Verlustrisiko + +--- + +## E3. Makroverteilung und Wochenkonsistenz + +**Typ:** 100%-gestapelter Wochenbalken + Streuungsindikator +**Ziel:** weniger „Makro-Schönheit“, mehr Verhaltenskonsistenz + +**Einzubeziehende Werte** +- Protein +- Fett +- Kohlenhydrate +- Kalorien +- optional Zucker +- optional Ballaststoffe + +**Berechnungen** +- Wochenmittel je Makro +- Variationskoeffizient +- Wochenende vs. Werktag + +**Aussagen** +- stark schwankende Ernährung +- proteinarm an Belastungstagen +- hoher Zuckerschwerpunkt bei gleichzeitig niedrigem Ballaststoffniveau + +**Regelbasierte Hinweise** +- bei Gesundheit/Zielreduktion: hoher Zucker + niedrige Ballaststoffe → Qualitäts-Hinweis +- bei Ausdauer: Kohlenhydrate an Belastungstagen zu niedrig → Performance-Hinweis + +--- + +## E4. Ernährungs-Adhärenz-Score (0–100) + +**Typ:** Scorecard +**Ziel:** nicht die „perfekte Ernährung“, sondern Zieltreue messen + +**Komponenten** + +```yaml +nutrition_score: + weight_loss: + calorie_target_adherence: 0.35 + protein_target_adherence: 0.25 + intake_consistency: 0.20 + fiber_or_food_quality: 0.10 + sugar_control: 0.10 + strength: + protein_target_adherence: 0.35 + calorie_support: 0.25 + intake_consistency: 0.20 + carb_support_on_training_days: 0.20 + endurance: + energy_adequacy: 0.30 + carb_support: 0.30 + protein_adequacy: 0.20 + intake_consistency: 0.20 +``` + +**Hinweis** +Ballaststoffe und Zucker nur dann relevant gewichten, wenn Datenqualität ausreichend ist. + +--- + +## E5. Energieverfügbarkeits-Warnung (heuristisch, klar als Heuristik markieren) + +**Typ:** Ampel / Warnkarte +**Ziel:** Unterversorgung erkennen, ohne klinische Diagnose zu behaupten + +**Einzubeziehende Werte** +- Kalorienaufnahme +- Trainingskalorien +- Gewichtstrend +- Recovery Score +- Schlaf +- ggf. LBM + +**Heuristische Trigger** +- anhaltendes großes Defizit über mehrere Tage/Wochen +- Recovery sinkt gleichzeitig +- Schlaf verschlechtert sich gleichzeitig +- LBM sinkt gleichzeitig + +**Ausgabe** +- „mögliche Unterversorgung / zu aggressives Defizit“ +- keine Diagnoseformulierung + +--- + +## 9. Diagrammkatalog – AKTIVITÄT + +## A1. Trainingsvolumen pro Woche + +**Typ:** gestapelte Wochenbalken +**Ziel:** Umfang und Mix sichtbar machen + +**Einzubeziehende Werte** +- Dauer +- Trainingskalorien +- Trainingstyp +- quality_label + +**Berechnungen** +- Einheiten pro Woche +- Minuten pro Woche +- Minuten je Kategorie +- Anteil qualitativ brauchbarer Sessions + +**Aussagen** +- Training ist regelmäßig oder sprunghaft +- relevante Kategorien fehlen +- „viel trainiert“ vs. „viel dokumentiert, aber wenig qualitativ“ + +--- + +## A2. Intensitätsverteilung / Zonenbild + +**Typ:** gestapelter Flächenchart oder Wochenbalken +**Ziel:** Intensitätsmuster sichtbar machen + +**Einzubeziehende Werte** +- HR-Zonen-Verteilung, sobald vorhanden +- bis dahin: `avg_hr`, `max_hr`, `quality_label`, Trainingstyp + +**Fallback-Logik ohne echte Zonen** +- Proxy-Intensität in 3 Stufen: + - niedrig + - moderat + - hoch +- Ableitung aus Kombination von Dauer, avg_hr, max_hr und Typ + +**Aussagen** +- Ausdauerziel: zu wenig Grundlagentraining oder zu viel harte Belastung +- Gesundheit: Bewegung vorhanden, aber kaum moderat/vigorous +- Kraftziel: hohe HR ist kein Muss, daher Zonen nur begrenzt gewichten + +--- + +## A3. Trainingsqualitäts-Matrix + +**Typ:** Heatmap +**Achsen:** Woche × Trainingstyp +**Ziel:** Qualität nicht nur als Einzelbadge, sondern als Muster darstellen + +**Einzubeziehende Werte** +- training_type +- quality_label +- duration +- avg_hr / max_hr optional + +**Darstellung** +- Zellenfarbe = mittlere Qualitätsstufe +- Zellgröße optional = Anzahl Sessions + +**Nutzen** +- zeigt, ob bestimmte Typen regelmäßig unter Mindestqualität bleiben +- sehr gut für Coaching und Programmsteuerung + +--- + +## A4. Fähigkeiten-Balance / Ability Radar + +**Typ:** Radar + Zeittrend +**Ziel:** zeigen, welche Entwicklungsdimensionen systematisch trainiert werden + +**Einzubeziehende Werte** +- `training_types.abilities` +- Aktivitäten +- Dauer / Qualität + +**Berechnungen** +- wöchentlicher Ability Load je Dimension +- geglätteter 28d-Wert +- Balance-Index + +**Dimensionen** +- Kraft +- Ausdauer +- Mental +- Koordination +- Mobilität + +**Aussagen** +- Training ist einseitig +- Mobilität/Mental fehlen +- gute Balance oder starke Spezialisierung + +**Regelbasierte Hinweise** +- bei Stagnation und sehr einseitiger Belastung → Balance-Hinweis +- bei Gesundheit/Zielbreite → zu schmale Bewegungsbasis + +--- + +## A5. Load-Monitoring: interne Last, Monotony, Strain + +**Typ:** Wochenchart mit Warnbändern +**Ziel:** Belastungssteuerung sichtbar machen + +**Wichtiger fachlicher Hinweis** +Da `session_rpe` aktuell fehlt, sollte ein **Proxy-Load** verwendet werden und klar als Proxy gekennzeichnet sein. + +**Proxy-Load-Vorschlag** + +```yaml +proxy_internal_load: + formula: duration_min * intensity_factor * quality_factor + intensity_factor: + low: 1.0 + moderate: 1.5 + high: 2.0 + quality_factor: + excellent: 1.15 + very_good: 1.05 + good: 1.0 + acceptable: 0.9 + poor: 0.75 + excluded: 0.0 +``` + +**Darstellungen** +- Wochenlast +- Monotony = Wochenmittel / Wochen-Standardabweichung +- Strain = Wochenlast × Monotony + +**Wichtiger Hinweis** +Monotony/Strain sind nützliche Coach-Metriken, aber **nicht als medizinische Wahrheit** interpretieren. + +--- + +## A6. Aktivitäts-Goal-Alignment-Score (0–100) + +**Typ:** Scorecard +**Ziel:** Training im Verhältnis zum Nutzerziel bewerten + +**Beispiel-Logik** + +```yaml +activity_score: + weight_loss: + weekly_minutes: 0.25 + frequency: 0.20 + quality_sessions: 0.20 + strength_presence: 0.15 + activity_consistency: 0.20 + strength: + strength_session_presence: 0.35 + quality_strength_sessions: 0.25 + recovery_respect: 0.15 + supportive_cardio: 0.10 + consistency: 0.15 + endurance: + weekly_minutes: 0.25 + intensity_distribution: 0.25 + frequency: 0.20 + load_progression: 0.15 + recovery_respect: 0.15 +``` + +--- + +## A7. Ruhetags-/Recovery-Compliance + +**Typ:** Kalender-Heatmap +**Ziel:** zeigen, ob Belastung und Erholung zusammenpassen + +**Einzubeziehende Werte** +- geplante Ruhetage +- tatsächliche Aktivitäten +- Recovery Score +- Schlafschuld + +**Aussagen** +- Ruhetag wird respektiert oder regelmäßig „durchtrainiert“ +- schlechte Recovery trotz fehlender Ruhetage +- unnötig viele Ruhetage bei Leistungsziel + +--- + +## A8. VO2max-Entwicklung + +**Typ:** Linienchart +**Ziel:** kardiorespiratorische Entwicklung sichtbar machen + +**Einzubeziehende Werte** +- VO2max +- Aktivitätsvolumen +- Intensitätsverteilung +- Ruhepuls optional + +**Regelbasierte Hinweise** +- steigendes Volumen ohne VO2max-Fortschritt → Programm-/Intensitätsfrage +- sinkende VO2max + sinkende Aktivität → Deconditioning-Hinweis +- bei Kraftziel nur sekundär priorisieren + +--- + +## 10. Diagrammkatalog – KORRELATIONEN / KOMPLEXE ZUSAMMENHÄNGE + +## Grundsatz +Korrelationen müssen **explizit als explorativ** gekennzeichnet sein. Sie zeigen Hinweise, keine Beweise. + +Jeder Korrelationschart bekommt: +- Effektstärke +- Vorzeichen +- bestes Lag-Fenster +- Datenanzahl +- Confidence +- Text: „Hinweis“, nicht „Ursache“ + +--- + +## C1. Energie-Balance vs. Gewichtsveränderung (lagged) + +**Typ:** Lag-Heatmap oder Cross-Correlation Panel +**Ziel:** erkennen, nach wie vielen Tagen Energiebilanz und Gewicht am plausibelsten zusammenlaufen + +**Einzubeziehende Werte** +- tägliche Kalorienbilanz +- 7d Gewichtsänderung + +**Lags** +- 0, 3, 7, 10, 14 Tage + +**Ausgabe** +- bestes Lag +- Richtung des Zusammenhangs +- Confidence + +**Nutzen** +- deutlich besser als einfache Same-Day-Pearson-Korrelation + +--- + +## C2. Protein adequacy vs. LBM-Trend + +**Typ:** Scatter + Trend + 28d Fenstervergleich +**Ziel:** erkennen, ob Proteinversorgung mit Magermasse-Stabilität oder -Anstieg einhergeht + +**Einzubeziehende Werte** +- Protein g/kg +- LBM +- Krafttrainingslast + +**Hinweis** +Protein nie isoliert interpretieren; Training muss als Moderator mitgedacht werden. + +--- + +## C3. Trainingslast vs. HRV/RHR (1–3 Tage verzögert) + +**Typ:** duale Lag-Auswertung +**Ziel:** individuelle Ermüdungsreaktion erkennen + +**Einzubeziehende Werte** +- Proxy-Load / Volumen +- HRV +- RHR +- Schlaf + +**Lags** +- 1, 2, 3 Tage + +**Aussagen** +- hohe Last führt individuell eher zu HRV-Abfall / RHR-Anstieg +- keine klare Reaktion erkennbar +- Reaktion nur bei Schlafmangel ausgeprägt + +--- + +## C4. Schlafdauer + Schlafregularität vs. Recovery + +**Typ:** Bubble-/Quadrantenchart +**Achsen:** Schlafdauer, Schlafregularität +**Bubble:** Recovery Score + +**Ziel:** zeigen, dass nicht nur Menge, sondern auch Regelmäßigkeit relevant ist + +**Einzubeziehende Werte** +- Schlafdauer +- Bedtime/Waketime +- daraus Sleep Regularity Index oder einfacher Regularity Proxy +- Recovery Score + +**Empfehlung** +Mindestens eine einfache Regularitätsmetrik ergänzen: + +```yaml +sleep_regularity_proxy: + inputs: [bedtime, waketime] + metric: mean_absolute_shift_from_previous_day_minutes + lower_is_better: true +``` + +Optional später: +- echter `Sleep Regularity Index (SRI)` + +--- + +## C5. Blutdruck-Kontextmatrix vs. Schlaf / Training / Stress-naher Kontext + +**Typ:** Matrix / Boxplots nach Kontext +**Ziel:** nicht nur Höhe, sondern Situation verstehen + +**Einzubeziehende Werte** +- systolisch +- diastolisch +- Puls +- Messkontext +- Schlaf der Vor-Nacht +- Training am selben / Vortag + +**Aussagen** +- Blutdruck morgens systematisch höher +- Training scheint akut positiv/neutral/negativ assoziiert +- Stress-Kontexte sind Ausreißerträger + +**Medizinischer Hinweis** +Das System soll hier eher **Screening-/Monitoring-Logik** abbilden, nicht Diagnose. + +--- + +## C6. Plateau-Detektor + +**Typ:** Ereignis-Karte / Warnpanel +**Ziel:** „viel Einsatz, wenig Fortschritt“ identifizieren + +**Plateau-Definitionen je Zielmodus** + +```yaml +plateau_logic: + weight_loss: + condition: + - 28d_weight_slope_flat + - waist_change_small + - adherence_high + strength: + condition: + - activity_score_high + - lbm_change_flat + - recovery_low_or_variable + endurance: + condition: + - volume_high + - vo2max_flat + - monotony_high +``` + +**Ausgabe** +- Plateau wahrscheinlich / möglich / nicht erkennbar +- Top-3 plausible Einflussfaktoren + +--- + +## C7. Multi-Faktor Driver Panel + +**Typ:** priorisierte Einflusskarten +**Ziel:** ohne KI eine „Top-Treiber“-Ansicht erzeugen + +**Mechanik** +Das System bewertet für ein Ziel die wichtigsten beeinflussbaren Faktoren regelbasiert: +- Energie +- Protein +- Schlafdauer +- Schlafregelmäßigkeit +- Trainingskonsistenz +- Qualitätsanteil +- Recovery +- Ruhetagsrespekt + +**Ausgabe pro Faktor** +- Status: förderlich / neutral / hinderlich +- Evidenz: hoch / mittel / niedrig +- Begründung: 1 Satz + +Das ist eine sehr starke Brücke zur späteren KI-Stufe. + +--- + +## 11. Zentrale Scores + +## S1. Recovery / Readiness Score (verbesserte Version) + +Die bestehende Idee ist gut, sollte aber robuster umgesetzt werden. + +### Eingangswerte +- HRV vs. 28d Baseline +- RHR vs. 28d Baseline +- Schlafdauer vs. Ziel +- Schlafschuld +- Schlafregularität +- Vorbelastung 1–3 Tage +- Messqualität / Datenvollständigkeit + +### Empfohlene Logik +Nicht rohe Quotienten, sondern normalisierte Abweichungszonen. + +```yaml +recovery_score: + components: + hrv_status: 0.25 + rhr_status: 0.20 + sleep_duration: 0.20 + sleep_debt: 0.10 + sleep_regularity: 0.10 + recent_load_balance: 0.10 + data_quality: 0.05 + output: + 0_39: low + 40_59: strained + 60_79: normal + 80_100: ready +``` + +### Zusätzliche Regeln +- Kein Score bei zu wenigen validen Datenpunkten +- Fallback-Modus ohne HRV möglich +- Separate Anzeige „Messqualität“ + +--- + +## S2. Health Risk / Health Stability Score + +**Typ:** Score 0–100 +**Ziel:** allgemeine Gesundheitsstabilität abbilden + +**Komponenten** +- Blutdruckstatus +- Schlafbasis +- Bewegungsbasis +- Gewicht/Umfangsrisiko +- Regelmäßigkeit + +**Hinweis** +Dieser Score ist für allgemeine Gesundheit sehr sinnvoll, aber für Leistungssport sekundär. + +--- + +## S3. Goal Progress Score (Meta-Score) + +**Typ:** Zielabhängiger Gesamtscore +**Formel:** gewichtete Summe aus Body, Nutrition, Activity, Recovery, Health Risk + +**Regeln** +- nie ohne Teilscore-Transparenz anzeigen +- immer mit Treiberaufschlüsselung +- nie als „Gesundheitsnote“ formulieren + +--- + +## S4. Data Quality Score + +**Typ:** technischer Fachscore +**Ziel:** schlechte Daten nicht mit scheinbarer Präzision interpretieren + +**Komponenten** +- Messhäufigkeit +- Regelmäßigkeit +- Lücken +- Konsistenz +- Anzahl gepaarter Datenpunkte für Korrelationen + +Dieser Score ist extrem wichtig. Ohne ihn werden schlechte Daten zu scheinbar präzisen Aussagen führen. + +--- + +## 12. Regelbasierte Aussagen und Empfehlungen ohne KI + +Die Formulierungen sollten knapp, neutral und nachvollziehbar sein. + +## 12.1 Beispiel-Statements – Gewichtsreduktion + +### Positiv +- „Dein 28-Tage-Trend zeigt eine stabile Gewichtsabnahme innerhalb eines nachhaltigen Bereichs.“ +- „Der Taillenumfang sinkt parallel zum Gewicht. Das spricht für echten Fortschritt.“ +- „Die Magermasse ist trotz Defizit stabil. Das ist in einer Reduktionsphase ein gutes Signal.“ + +### Neutral / Beobachtung +- „Die Tageswerte schwanken deutlich, der 28-Tage-Trend ist aber nahezu stabil.“ +- „Die dokumentierte Kalorienbilanz spricht für ein Defizit, das Gewicht reagiert bisher aber nur schwach.“ + +### Kritisch +- „Die Magermasse sinkt parallel zur Gewichtsabnahme. Prüfe Defizithöhe, Protein und Krafttraining.“ +- „Der Gewichtsverlauf stagniert seit 4 Wochen trotz hoher Dokumentationsrate.“ +- „Das Defizit wirkt im Verhältnis zu Recovery und Schlaf aktuell eher aggressiv.“ + +## 12.2 Beispiel-Statements – Kraftaufbau +- „Dein Trainingsmuster ist konsistent, die Körperdaten zeigen aber noch keinen klaren LBM-Anstieg.“ +- „Die Proteinzufuhr liegt an mehreren Tagen unter dem Zielbereich für dein Kraftziel.“ +- „Recovery und Schlaf sprechen aktuell gegen weitere Steigerung der Trainingslast.“ +- „Deine Ability-Balance ist stark kraftlastig, Mobilität und Regeneration sind schwach ausgeprägt.“ + +## 12.3 Beispiel-Statements – Ausdauer +- „Die Wochenminuten steigen, aber die Intensitätsverteilung ist unausgeglichen.“ +- „VO2max verbessert sich trotz Trainingsvolumen derzeit nicht sichtbar.“ +- „Die letzten Wochen zeigen hohe Monotonie. Das spricht für wenig Variation in der Belastung.“ + +## 12.4 Beispiel-Statements – Gesundheit +- „Dein Schlaf liegt wiederholt unter 7 Stunden und ist unregelmäßig.“ +- „Die Bewegung erfüllt den Mindestrahmen teilweise, Kraftreize fehlen jedoch.“ +- „Deine Blutdruckwerte sollten im Verlauf enger beobachtet werden, insbesondere im Morgenkontext.“ + +--- + +## 13. Medizinische und fachliche Grenzlogik + +## 13.1 Bewegung +Für erwachsene Personen sind als allgemeiner Gesundheitsrahmen mindestens **150–300 Minuten moderate** oder **75–150 Minuten intensive** Aktivität pro Woche plus **Muskelkräftigung an mindestens 2 Tagen/Woche** ein sinnvoller Referenzrahmen. Dieser Rahmen ist für die Kategorie „Gesundheit“ wichtig, aber nicht automatisch Leistungsoptimum. [R1] + +## 13.2 Schlaf +Für Erwachsene ist **7+ Stunden Schlaf pro Nacht** ein belastbarer allgemeiner Mindestanker. Schlafdauer allein reicht aber nicht; Regelmäßigkeit und Qualität sind zusätzlich relevant. [R2][R8][R9] + +## 13.3 Protein +Für Trainierende ist eine relativ höhere Proteinzufuhr fachlich sinnvoll; als gut belastbare Referenz für die Darstellung eignen sich **20–40 g pro Proteinportion** oder etwa **0,25 g/kg pro Portion**. Tagesziele sollten zielabhängig und konfigurierbar sein. [R3] + +## 13.4 Blutdruck +Die App sollte sich in der Darstellung am aktuellen europäischen Begriffsrahmen orientieren: **elevated BP** bei Büro-/Praxiswerten **120–139 systolisch oder 70–89 diastolisch**, Hypertonie wie bisher ab **≥140/90 mmHg**. Für die App heißt das: Verlauf und Kontext zeigen, aber Diagnose vermeiden. [R4] + +## 13.5 Trainingslast +Trainingsmonitoring ist fachlich sinnvoll, aber nie mit nur einer Kennzahl. Deshalb sollten Last, Erholung, Schlaf und Ziel-Fortschritt immer gemeinsam interpretiert werden. [R5] + +--- + +## 14. Priorisierte Umsetzungsreihenfolge + +## Phase 1 – sofort umsetzen +1. Gewichtstrend + Zielprojektion +2. Gewicht/FM/LBM-Chart +3. Umfangs-Panel +4. Energieaufnahme vs. Gewichtstrend +5. Protein adequacy chart +6. Trainingsvolumen pro Woche +7. Trainingsqualitäts-Matrix +8. Recovery Score verbessert +9. Goal Progress Score +10. einfache Korrelationen mit Confidence und Mindestdatenlogik + +## Phase 2 – stark empfohlen +1. Sleep Regularity Proxy +2. Lag-Korrelationen +3. Plateaudetektor +4. Driver Panel +5. Health Stability Score +6. Ability-Balance-Radar +7. Ruhetags-/Recovery-Compliance + +## Phase 3 – Vorbereitung für KI +1. Feature Store je 7/28/90 Tage +2. erklärbare Vorstufen-Texte aus Regeln +3. Ereignismarker (Infekt, Reise, Wettkampf, Stressphasen) +4. subjektive Daten (RPE, Müdigkeit, Schmerz, Motivation) + +--- + +## 15. Konkrete technische Spezifikation für den Coding Agent + +## 15.1 Chart Definition Contract + +```json +{ + "chart_id": "K1_weight_trend", + "category": "body", + "title": "Gewichtstrend + Zielprojektion", + "purpose": "Tagesrauschen von echter Entwicklung trennen", + "required_fields": ["weight.date", "weight.value", "profile.target_weight"], + "optional_fields": ["profile.height_cm"], + "derived_metrics": [ + "weight_7d_median", + "weight_28d_slope", + "weight_90d_slope", + "goal_projection_date", + "goal_progress_pct" + ], + "default_range": "90d", + "goal_relevance": ["weight_loss", "recomposition", "health"], + "confidence_logic": { + "7d_min_points": 4, + "28d_min_points": 18, + "90d_min_points": 60 + }, + "interpretation_rules": [ + "stable_downward_trend", + "volatile_but_flat", + "rapid_loss_with_lbm_drop" + ], + "recommendation_rules": [ + "review_deficit", + "review_protein", + "hold_course" + ] +} +``` + +## 15.2 Statement Contract + +```json +{ + "statement_id": "WL_rapid_loss_lbm_drop", + "severity": "warning", + "goal_modes": ["weight_loss", "recomposition"], + "conditions": [ + "weight_28d_slope < -0.75_percent_per_week", + "lbm_28d_change < -0.5kg", + "nutrition_score < 70 OR protein_target_days_per_week < 4" + ], + "message": "Der Gewichtsverlust ist aktuell relativ schnell und geht mit sinkender Magermasse einher. Prüfe Defizithöhe, Protein und Kraftreize.", + "confidence": "derived_from_data_quality" +} +``` + +## 15.3 Score Contract + +```json +{ + "score_id": "goal_progress_score", + "range": [0, 100], + "goal_mode_dependent": true, + "components": [ + "body_progress_score", + "nutrition_score", + "activity_score", + "recovery_score", + "health_risk_score" + ], + "weights_source": "config/goal_weights.yaml", + "output": { + "value": 78, + "label": "gut", + "drivers_positive": ["protein_adherence", "stable_weight_trend"], + "drivers_negative": ["sleep_irregularity"] + } +} +``` + +--- + +## 16. Fachliches Fazit + +## Aus Perspektive der Datenanalyse +Ich würde das Modell klar als **mehrdimensional leistungsfähig** einstufen. Es ist weit über einem typischen Fitness-Tracker, weil es Körper, Training, Schlaf, Vitalwerte und Ernährung zusammenführt. + +## Aus Perspektive eines Sportwissenschaftlers / Gesundheitsanalysten +Der größte Mehrwert entsteht nicht durch noch mehr Einzelmetriken, sondern durch: +- **zielabhängige Interpretation** +- **Baseline-Logik statt bloßer Rohwerte** +- **lag-basierte Zusammenhänge statt naiver Gleichzeitigkeit** +- **Confidence / Datenqualität** +- **klare Trennung zwischen Gesundheitsmonitoring, Performance und Heuristik** + +## Zusammengeführt +Die beste Umsetzung ist **nicht** eine endlose Sammlung bunter Charts. Die beste Umsetzung ist ein System mit: +1. wenigen, starken Standardcharts, +2. robusten Scores, +3. erklärbaren Regeln, +4. einer sauberen Brücke zur späteren KI. + +Wenn diese Struktur sauber umgesetzt wird, ist bereits ohne KI ein sehr hoher fachlicher Nutzen erreichbar. + +--- + +## 17. Referenzbasis + +**[R1]** ACSM / Physical Activity Guidelines: mindestens 150–300 min moderate oder 75–150 min intensive Aktivität pro Woche plus Muskelkräftigung an mindestens 2 Tagen/Woche. +Quelle: ACSM, Physical Activity Guidelines FAQ +https://acsm.org/physical-activity-guidelines-faqs/ + +**[R2]** AASM / Sleep Research Society: Erwachsene sollten regelmäßig 7 oder mehr Stunden pro Nacht schlafen. +Quelle: AASM +https://aasm.org/seven-or-more-hours-of-sleep-per-night-a-health-necessity-for-adults/ + +**[R3]** ISSN Position Stand Protein and Exercise: allgemeine Empfehlung pro Portion etwa 0,25 g/kg bzw. 20–40 g hochwertiges Protein; höhere Gesamtzufuhr bei Trainierenden oft sinnvoll. +Quelle: Journal of the International Society of Sports Nutrition +https://link.springer.com/article/10.1186/s12970-017-0177-8 + +**[R4]** ESC 2024 Guidelines: neue Kategorie „elevated BP“ 120–139 mmHg systolisch oder 70–89 mmHg diastolisch; Hypertonie weiterhin ab ≥140/90 mmHg. +Quelle: European Society of Cardiology +https://www.escardio.org/news/news-room/congress-news/2024-esc-clinical-practice-guidelines-for-the-management-of-elevated-blood-pressure-and-hypertension/ + +**[R5]** Athlete load monitoring soll als gemeinsamer Rahmen aus Last, Reaktion und Kontext verstanden werden, nicht als Einzelmetrik. +Quelle: Monitoring Athlete Training Loads: Consensus Statement +https://pubmed.ncbi.nlm.nih.gov/28463642/ + +**[R6]** Progressive resistance training muss zielgerichtet ausgestaltet werden; starre Einheitsregeln sind fachlich ungeeignet. +Quelle: ACSM Position Stand – Progression Models in Resistance Training for Healthy Adults +https://pubmed.ncbi.nlm.nih.gov/19204579/ + +**[R7]** Für Gewichtsreduktion gelten etwa 0,5–1,0 kg/Woche allgemein als sicher und nachhaltig, aber individualisiert. +Quelle: NICE Overweight and Obesity Management +https://www.nice.org.uk/guidance/ng246/resources/a-guide-for-prescribing-medicines-to-manage-overweight-and-obesity-15299628589/chapter/Prescribing-reviewing-and-stopping-tirzepatide + +**[R8]** Schlafregularität ist als Gesundheitsfaktor eigenständig relevant und teils prädiktiver als bloße Schlafdauer. +Quelle: Sleep regularity is a stronger predictor of mortality risk than sleep duration +https://pubmed.ncbi.nlm.nih.gov/37738616/ + +**[R9]** Für die Messung von Schlafregularität sind mehrere Metriken möglich; Auswahl hängt von Studiendauer und Ziel ab. +Quelle: Measuring sleep regularity: theoretical properties and practical usage of existing metrics +https://pubmed.ncbi.nlm.nih.gov/33864369/ + + +--- + +## 16. Ergänzung V2 – Trainingsqualitäts-Engine je Trainingstyp + +Die neue Anforderung präzisiert einen zentralen Punkt des Gesamtsystems: **Trainingsqualität** wird pro **Trainingstyp** über definierbare Regeln ermittelt. Dabei können mehrere Messparameter kombiniert werden, und je Trainingstyp stehen unterschiedliche Berechnungsstrategien zur Verfügung: +- **Gewichteter Score** +- **Alle Regeln müssen erfüllt sein** +- **Mindestens N Regeln müssen erfüllt sein** + +Diese Architektur ist fachlich sinnvoll, muss aber sauber modelliert werden, damit aus heterogenen Sensordaten kein irreführender Einheits-Score entsteht. + +### 16.1 Fachliche Grundsätze + +1. **Qualität ist nicht gleich Last.** + Dauer, Distanz, Puls, Leistung, Pace, RPE oder Kalorien beschreiben teils die externe Last, teils die interne Belastung und nur indirekt die „Qualität“. Darum sollte das System **drei getrennte Konzepte** führen: + - `quality_score` = Wie gut erfüllt die Einheit die typ-spezifische Zielcharakteristik? + - `load_score` = Wie hoch war der Trainingsreiz / die Belastung? + - `confidence_score` = Wie belastbar ist die Bewertung auf Basis der verfügbaren Daten? + +2. **Nicht jeder Parameter ist für jeden Trainingstyp sinnvoll.** + Avg-HR und Max-HR sind bei Laufen oder HIIT oft nützlich, bei Mobility, Technik, Meditation oder leichtem Krafttraining aber nur begrenzt aussagekräftig. + **Folge:** Jeder Trainingstyp erhält eine eigene Parameter-Matrix mit Relevanz, Pflichtstatus und Gewicht. + +3. **Subjektive und objektive Daten sollen kombiniert, aber nicht vermischt werden.** + RPE liefert eine andere Information als HR, Pace oder Distanz und sollte deshalb als eigenständiger Marker behandelt werden. + +4. **Regelbasierte Scoring-Engines sollten hybrid arbeiten.** + Rein gewichtete Modelle sind flexibel, aber anfällig für „falsche Positivbewertungen“. Reine `all_rules_must_pass`-Modelle sind oft zu hart. Fachlich am stabilsten ist meist ein **Hybrid aus Gates + eigentlicher Strategie**. + +### 16.2 Empfohlene Architektur: 3-stufige Quality Engine + +```yaml +training_quality_engine: + stage_1_exclusion_gates: + purpose: "Offensichtlich unbrauchbare oder falsch klassifizierte Sessions ausfiltern" + examples: + - duration_min < absolute_minimum + - missing_all_primary_metrics == true + - training_type_mismatch_flag == true + - manually_marked_invalid == true + + stage_2_rule_evaluation: + purpose: "Trainingstyp-spezifische Qualitätsbewertung" + supported_strategies: + - weighted_score + - all_rules_must_pass + - at_least_n_rules + + stage_3_labeling_and_confidence: + purpose: "Normierung, Qualitätslabel, Datenvertrauen" + outputs: + - quality_score_0_100 + - quality_label + - rules_passed_count + - rules_total_count + - confidence_score_0_100 + - confidence_label +``` + +### 16.3 Empfohlene Outputs pro Einheit + +```yaml +activity_quality_result: + quality_score: 0-100 + quality_label: [excellent, very_good, good, acceptable, poor, excluded] + confidence_score: 0-100 + confidence_label: [high, medium, low] + strategy_used: [weighted_score, all_rules_must_pass, at_least_n_rules] + rules_passed_count: integer + rules_total_count: integer + mandatory_rules_passed: boolean + parameter_breakdown: + - parameter_key + - raw_value + - normalized_score_0_100 + - weight + - passed + - relevance + - reason + exclusion_reason: nullable +``` + +### 16.4 Datenmodell für Regeln je Trainingstyp + +Empfehlung: Die Quality Rules im Trainingstyp nicht mehr nur als einfache Minima speichern, sondern als vollständige Regelobjekte. + +```yaml +training_type_quality_config: + strategy: weighted_score + min_rules_required: null + mandatory_gate_mode: any_fail_excludes + missing_data_policy: renormalize_non_missing + label_thresholds: + excellent: 90 + very_good: 80 + good: 65 + acceptable: 50 + poor: 1 + excluded: 0 + + parameters: + - key: duration_min + source: duration + comparator: range + unit: min + required: true + mandatory: true + weight: 0.20 + min: 30 + max: 75 + tolerance_below: 10 + tolerance_above: 20 + scoring_curve: trapezoid + + - key: avg_hr + source: avg_heart_rate + comparator: range_relative_to_hrmax + unit: bpm + required: false + mandatory: false + weight: 0.20 + min_pct_hrmax: 0.70 + max_pct_hrmax: 0.85 + scoring_curve: trapezoid + + - key: rpe + source: rpe + comparator: range + unit: borg_cr10 + required: false + mandatory: false + weight: 0.15 + min: 5 + max: 7 + scoring_curve: trapezoid +``` + +### 16.5 Unterstützte Regeltypen / Comparatoren + +Die Engine sollte mindestens diese Comparatoren unterstützen: + +```yaml +comparators: + - min # Wert muss >= Grenzwert sein + - max # Wert muss <= Grenzwert sein + - range # Wert innerhalb eines Bereichs + - target # Zielwert, Score sinkt mit Abstand + - range_relative_to_hrmax + - range_relative_to_resting_hr + - range_relative_to_baseline + - ratio_min + - ratio_max + - boolean_true + - derived_formula +``` + +### 16.6 Sinnvolle Scoring-Kurven + +Nicht jede Regel sollte nur binär bewertet werden. Für die meisten Trainingsdaten sind **weiche Übergänge** fachlich besser. + +```yaml +scoring_curves: + binary: + description: "0 oder 100" + linear: + description: "lineare Annäherung an Zielbereich" + trapezoid: + description: "optimaler Bereich = 100, davor/danach weicher Abfall" + gaussian_like: + description: "Zielwert-orientiert, symmetrischer Abfall" + asymmetric: + description: "unter Ziel anders bestrafen als über Ziel" +``` + +**Praxisregel:** +- `binary` nur für harte Compliance-Vorgaben +- `trapezoid` für Dauer, Pace, HR, Leistung +- `asymmetric` für Überlastungsrisiken (z. B. „zu hart“ stärker bestrafen als „etwas zu leicht“) + +### 16.7 Die 3 Berechnungsstrategien – fachliche Bewertung + +#### A. `weighted_score` +**Geeignet für:** die meisten freien Trainingsformen mit mehreren Einflussgrößen + +**Vorteile** +- robust bei teilweise fehlenden Daten +- fein abstufbar +- gut visualisierbar +- ideal für Laufen, Radfahren, allgemeine Cardio-Sessions + +**Risiko** +- eine Session kann trotz fehlender Kernanforderung noch „gut“ aussehen, wenn Nebenparameter stark sind + +**Daher Empfehlung** +- immer mit vorgeschalteten **Mandatory Gates** kombinieren + +**Formel** +```yaml +weighted_score: + formula: sum(parameter_score_i * weight_i) / sum(active_weights_i) + result_scale: 0_100 +``` + +#### B. `all_rules_must_pass` +**Geeignet für:** standardisierte Protokolle, Reha-Übungen, Technik- oder Compliance-Sessions + +**Vorteile** +- sehr klar +- leicht erklärbar +- hohe fachliche Stringenz + +**Risiko** +- zu hart bei natürlicher Messvariabilität +- problematisch bei Sensorausfällen +- wenig differenziert + +**Empfehlung** +- eher für „Pflichtprogramm erfüllt / nicht erfüllt“ einsetzen +- zusätzlich einen separaten `soft_score` berechnen, damit die UI nicht nur schwarz/weiß bleibt + +#### C. `at_least_n_rules` +**Geeignet für:** Daten mit Lücken, Wearable-Heterogenität, flexibel definierte Trainingsformen + +**Vorteile** +- robust gegen fehlende Messkanäle +- gut für Alltagssport und gemischte Datenquellen + +**Risiko** +- alle Regeln werden implizit gleich wichtig, falls keine Zusatzlogik vorliegt + +**Empfehlung** +- nur in Kombination mit `mandatory_rules` +- für N-Regelmodelle zusätzlich `priority_groups` definieren + +### 16.8 Fachlich empfohlener Standard: Hybrid-Modell + +Die beste Standardarchitektur ist **nicht** eine einzelne Strategie, sondern: + +```yaml +recommended_default_strategy: + stage_1: + mandatory_gates: true + stage_2: + primary_strategy: weighted_score + stage_3: + fallback_when_sparse_data: at_least_n_rules +``` + +**Begründung:** +So werden offensichtlich ungeeignete Sessions ausgeschlossen, gleichzeitig bleibt die Bewertung flexibel und datenrobust. Das entspricht der sportwissenschaftlichen Praxis besser als ein starres Alles-oder-Nichts-Modell. + +### 16.9 Parameterklassifikation + +Die aktuell genannten Parameter sollten fachlich in Klassen eingeteilt werden. + +#### A. Externe Last / Umfang +- Dauer +- Distanz +- Höhenmeter +- aktive Kalorien +- Ruhekalorien +- Kalorien pro km + +#### B. Interne Belastung / Intensität +- Durchschnittspuls +- Maximalpuls +- Minimalpuls +- RPE + +#### C. Leistungs-/Effizienzmarker +- Pace +- Trittfrequenz +- Durchschnittsleistung + +#### D. Zukünftige sinnvolle Ergänzungen +- Zeit in HF-Zonen +- Leistung in Zonen +- Satz-/Wiederholungsvolumen bei Krafttraining +- Intervallstruktur +- subjektive Trainingszielerfüllung +- Technik-/Skill-Flags + +### 16.10 Parameter-Relevanz nach Trainingstyp + +Die Engine sollte **keine globale Gleichbehandlung** der Parameter vornehmen. + +#### Beispielhafte Relevanzmatrix + +```yaml +parameter_relevance_examples: + running_endurance: + duration: high + distance: high + avg_hr: high + max_hr: medium + pace: high + cadence: medium + elevation_gain: medium + avg_power: medium + rpe: high + active_calories: low + resting_calories: none + calories_per_km: low + + intervals_hiit: + duration: medium + distance: medium + avg_hr: medium + max_hr: high + pace: high + cadence: medium + avg_power: high + rpe: high + elevation_gain: low + active_calories: low + + strength_general: + duration: medium + avg_hr: low + max_hr: low + min_hr: none + rpe: medium + active_calories: low + resting_calories: none + avg_power: low + pace: none + cadence: none + calories_per_km: none + + walking_health: + duration: high + distance: medium + avg_hr: medium + max_hr: low + pace: medium + cadence: medium + rpe: low + elevation_gain: low + + mobility_yoga: + duration: high + avg_hr: none + max_hr: none + pace: none + cadence: none + rpe: low +``` + +**Fachliche Korrektur:** +`active_calories`, `resting_calories` und `calories_per_km` sollten fast nie Kernparameter der Qualität sein. Sie können Zusatzmarker sein, aber keine Primärlogik tragen, weil sie stark von Gerät, Körpermasse und Algorithmus abhängen. + +### 16.11 Relative statt absolute Zielbereiche + +Wo immer möglich, sollten Regeln **relativ zur Person** und nicht nur absolut formuliert werden. + +#### Beispiele +- Avg-HR relativ zu HRmax oder besser HRR +- Pace relativ zur persönlichen 28d-Baseline oder Zielzone +- Leistung relativ zur persönlichen Baseline +- Dauer relativ zum üblichen Session-Typ-Fenster +- Kalorien pro km nur relativ und nur explorativ + +**Begründung:** +Absolute Schwellen bestrafen Anfänger und unterschätzen Fortgeschrittene. Relative Zonen sind individualisierter. + +### 16.12 Vorsicht bei Herzfrequenz-Regeln + +HF-basierte Regeln sind nützlich, aber nie allein ausreichend: +- geschätzte HRmax ist fehleranfällig +- Medikamente, Stress, Hitze, Dehydrierung und Messfehler beeinflussen HR +- Krafttraining und technische Sportarten bilden sich über HF oft schlecht ab + +Daher: +```yaml +hr_rule_policy: + never_use_as_only_primary_rule: true + prefer_relative_thresholds: true + downweight_if_hrmax_estimated_only: true + combine_with_rpe_or_external_load: true +``` + +### 16.13 Umgang mit fehlenden Werten + +Die Engine braucht eine explizite `missing_data_policy`. + +```yaml +missing_data_policies: + fail: + description: "fehlender Pflichtwert = Regel nicht bestanden" + renormalize_non_missing: + description: "nur vorhandene Gewichte werden neu normiert" + impute_from_baseline: + description: "nur für robuste Langzeitmarker verwenden" + downgrade_confidence: + description: "Score bleibt, Confidence sinkt" +``` + +**Empfehlung** +- Pflichtparameter: `fail` +- optionale Parameter: `renormalize_non_missing` + `downgrade_confidence` + +### 16.14 Empfohlene Score-Logik pro Trainingstyp + +#### 16.14.1 Ausdauer – lockerer Dauerlauf + +```yaml +training_type_example_running_easy: + strategy: weighted_score + mandatory_gates: + - key: duration_min + comparator: min + threshold: 20 + - key: distance_km + comparator: min + threshold: 2 + + parameters: + - key: duration_min + weight: 0.25 + comparator: range + min: 30 + max: 75 + scoring_curve: trapezoid + + - key: avg_hr + weight: 0.20 + comparator: range_relative_to_hrmax + min_pct_hrmax: 0.65 + max_pct_hrmax: 0.78 + scoring_curve: trapezoid + + - key: pace_relative_to_28d_baseline + weight: 0.15 + comparator: range + min: 0.92 + max: 1.05 + scoring_curve: trapezoid + + - key: cadence + weight: 0.10 + comparator: range_relative_to_baseline + min_ratio: 0.95 + max_ratio: 1.08 + scoring_curve: trapezoid + + - key: rpe + weight: 0.20 + comparator: range + min: 3 + max: 5 + scoring_curve: trapezoid + + - key: elevation_gain_per_km + weight: 0.10 + comparator: max + threshold: 35 + scoring_curve: asymmetric +``` + +#### 16.14.2 HIIT / Intervalle + +```yaml +training_type_example_hiit: + strategy: at_least_n_rules + min_rules_required: 4 + mandatory_gates: + - key: duration_min + comparator: min + threshold: 12 + + parameters: + - key: max_hr + comparator: range_relative_to_hrmax + min_pct_hrmax: 0.88 + max_pct_hrmax: 0.98 + mandatory: false + + - key: avg_hr + comparator: range_relative_to_hrmax + min_pct_hrmax: 0.75 + max_pct_hrmax: 0.90 + + - key: rpe + comparator: range + min: 7 + max: 9 + + - key: avg_power_relative_to_baseline + comparator: min + min_ratio: 1.03 + + - key: pace_relative_to_baseline + comparator: min + min_ratio: 1.03 +``` + +#### 16.14.3 Krafttraining allgemein + +**Wichtige fachliche Einschränkung:** +Mit den aktuell genannten Parametern ist Krafttrainingsqualität nur eingeschränkt beurteilbar. Wirklich gute Kraftbewertung braucht später zusätzlich: +- Satzanzahl +- Wiederholungen +- verwendete Last +- Volumenlast +- Übungstypen +- Satz-RPE oder Reps in Reserve (RIR) + +Bis dahin sollte die Qualität **nur vorsichtig** bewertet werden. + +```yaml +training_type_example_strength_general: + strategy: at_least_n_rules + min_rules_required: 2 + mandatory_gates: + - key: duration_min + comparator: min + threshold: 20 + + parameters: + - key: duration_min + comparator: range + min: 35 + max: 90 + weight: 0.40 + + - key: rpe + comparator: range + min: 5 + max: 8 + weight: 0.35 + + - key: avg_hr + comparator: min + threshold: 85 + weight: 0.10 + + - key: active_calories + comparator: min + threshold: 120 + weight: 0.05 + + - key: max_hr + comparator: min + threshold: 110 + weight: 0.10 +``` + +**Interpretation:** +Hier ist klar: HR und Kalorien sind nur schwache Hilfsmarker. Das System sollte dies in der UI offenlegen. + +#### 16.14.4 Gehen / Gesundheitssport + +```yaml +training_type_example_walking: + strategy: weighted_score + mandatory_gates: + - key: duration_min + comparator: min + threshold: 10 + + parameters: + - key: duration_min + weight: 0.35 + comparator: range + min: 20 + max: 60 + + - key: pace + weight: 0.20 + comparator: range_relative_to_baseline + min_ratio: 0.95 + max_ratio: 1.20 + + - key: avg_hr + weight: 0.20 + comparator: range_relative_to_hrmax + min_pct_hrmax: 0.50 + max_pct_hrmax: 0.72 + + - key: cadence + weight: 0.15 + comparator: range_relative_to_baseline + min_ratio: 0.95 + max_ratio: 1.15 + + - key: elevation_gain + weight: 0.10 + comparator: max + threshold: 150 +``` + +### 16.15 Label-Mapping + +Der 0–100 Score sollte immer zusätzlich in ein erklärbares Label übersetzt werden. + +```yaml +quality_label_thresholds_default: + excluded: 0 + poor: 1-49 + acceptable: 50-64 + good: 65-79 + very_good: 80-89 + excellent: 90-100 +``` + +Empfehlung: +- Trainingstyp-spezifisch überschreibbar +- zusätzlich `goal_mode`-abhängig feinjustierbar + +### 16.16 Confidence-Score + +Neben dem Qualitäts-Score braucht jede Session einen Vertrauenswert. + +```yaml +confidence_score: + components: + required_data_completeness: 0.40 + optional_data_completeness: 0.15 + sensor_reliability_profile: 0.15 + baseline_availability: 0.15 + training_type_specificity: 0.15 +``` + +**Beispiel** +- Laufen mit Dauer, Distanz, Pace, Avg-HR, Max-HR, RPE → hohe Confidence +- Krafttraining nur mit Dauer und Kalorien → niedrige Confidence + +### 16.17 Trennung von Qualität, Dosis und Regelmäßigkeit + +Für die Gesamtanalyse des Nutzers sollten drei Kennzahlen parallel geführt werden: + +```yaml +session_metrics: + quality_score: + meaning: "Wie gut war die einzelne Einheit relativ zum Ziel des Trainingstyps?" + load_score: + meaning: "Wie belastend war die Einheit?" + consistency_score: + meaning: "Wie regelmäßig wurde trainiert?" +``` + +**Wichtige Folge:** +Eine harte, sehr belastende Einheit kann `load_score` hoch, aber `quality_score` nur mittel haben, wenn sie nicht zum Trainingsziel passte. + +### 16.18 Neue Visualisierungen speziell für Trainingsqualität + +#### TQ1. Rule Fulfillment Breakdown +**Typ:** gestapelter Horizontalbalken pro Session +**Inhalt:** erfüllt / teilweise erfüllt / nicht erfüllt / nicht verfügbar +**Nutzen:** höchste Erklärbarkeit für den Nutzer + +#### TQ2. Parameter Contribution Chart +**Typ:** Balkendiagramm +**Inhalt:** Beitrag jedes Parameters zum `quality_score` +**Nutzen:** zeigt, warum eine Einheit gut oder schwach bewertet wurde + +#### TQ3. Training Type Quality Trend +**Typ:** Linienchart +**Inhalt:** 7d / 28d gleitender Qualitätsmittelwert pro Trainingstyp +**Nutzen:** erkennt, ob ein Typ systematisch zu leicht, zu hart oder inkonsistent trainiert wird + +#### TQ4. Quality vs Load Quadrant +**Typ:** Scatterplot +**Achsen:** `quality_score` × `load_score` +**Quadranten:** +- hoch Qualität / hoch Last +- hoch Qualität / niedrig Last +- niedrig Qualität / hoch Last +- niedrig Qualität / niedrig Last + +**Nutzen:** +Zeigt besonders wertvoll, ob der Nutzer „hart, aber unsauber“ trainiert. + +#### TQ5. Confidence Overlay +**Typ:** Punkt- oder Linien-Overlay +**Inhalt:** Qualitätswerte werden nach Confidence visuell abgeschwächt +**Nutzen:** verhindert Fehlinterpretationen bei dünner Datenlage + +#### TQ6. Weekly Rule Heatmap +**Typ:** Heatmap +**Achsen:** Woche × Regelgruppe +**Nutzen:** identifiziert wiederkehrende Defizite, z. B. „zu kurz“, „zu geringe Intensität“, „fehlender RPE-Eintrag“ + +### 16.19 Regelbasierte Aussagen ohne KI + +Die Engine kann bereits sehr gute nicht-KI-basierte Aussagen erzeugen. + +#### Beispiele +- „Deine Laufeinheiten sind regelmäßig vorhanden, bleiben aber in 4 der letzten 6 Wochen unter dem Zielbereich für Dauer.“ +- „Die Intensität deiner Intervall-Einheiten ist meist passend, jedoch fehlen häufig belastbare RPE-Daten; die Bewertung ist daher nur eingeschränkt sicher.“ +- „Beim Krafttraining ist die aktuelle Qualitätsbewertung nur orientierend, da zentrale Kraftparameter wie Satzanzahl, Wiederholungen und Last bisher nicht erfasst werden.“ +- „Du erreichst hohe Belastungswerte, aber die Zielkriterien des gewählten Trainingstyps werden nur teilweise erfüllt. Das spricht eher für Belastung als für zielgenaue Qualität.“ + +### 16.20 Empfehlungen ohne KI + +#### Automatische Empfehlungen +```yaml +rule_based_recommendations: + - trigger: "quality_score_28d < 60 and frequency_28d >= threshold" + message: "Du trainierst regelmäßig, triffst aber die Qualitätskriterien des Typs noch nicht stabil. Prüfe Zieltempo, Dauer und Intensitätssteuerung." + + - trigger: "confidence_score < 50" + message: "Die Bewertung ist nur eingeschränkt belastbar. Ergänze – wenn möglich – RPE oder Herzfrequenzdaten." + + - trigger: "training_type == strength and missing_strength_specific_metrics == true" + message: "Für Krafttraining sollten später Satzanzahl, Wiederholungen und Last ergänzt werden, da Puls- und Kaloriendaten nur begrenzt aussagekräftig sind." + + - trigger: "high_load_low_quality_pattern == true" + message: "Mehr Belastung führt aktuell nicht automatisch zu besserer Trainingsqualität. Wahrscheinlich ist die Steuerung des Reizes noch zu ungenau." +``` + +### 16.21 API-/Backend-Contract für Coding Agents + +```yaml +POST /activity/{id}/evaluate-quality + +request: + activity_id: uuid + profile_id: uuid + training_type_id: uuid + evaluation_mode: default + +response: + quality_score: 0-100 + quality_label: good + load_score: 0-100 + confidence_score: 0-100 + strategy_used: weighted_score + mandatory_rules_passed: true + rules_passed_count: 5 + rules_total_count: 7 + parameter_breakdown: + - key: duration_min + raw_value: 42 + normalized_score: 100 + weight: 0.25 + passed: true + contribution: 25.0 + explanation: "liegt im Zielbereich 30-75 min" + - key: avg_hr + raw_value: 146 + normalized_score: 82 + weight: 0.20 + passed: true + contribution: 16.4 + explanation: "leicht oberhalb des idealen Bereichs" + warnings: + - "hrmax_is_estimated" + exclusion_reason: null +``` + +### 16.22 Admin-UI für Trainingstyp-Regeln + +Die Admin-Oberfläche sollte je Trainingstyp editierbar machen: + +```yaml +admin_quality_rule_editor: + fields: + - strategy + - min_rules_required + - label_thresholds + - missing_data_policy + - mandatory_gate_mode + - parameter_rows[] + parameter_rows: + - source_field + - comparator + - thresholds + - scoring_curve + - weight + - required + - mandatory + - rationale_text +``` + +**Zusätzlicher Praxisnutzen:** +Wenn `rationale_text` gepflegt wird, kann die UI dem Nutzer später direkt erklären, warum diese Regel existiert. + +### 16.23 Fachliches Fazit zur Trainingsscore-Erweiterung + +Die von dir beschriebene Architektur ist **grundsätzlich sehr gut anschlussfähig**, weil sie sportartspezifische Regeln pro Trainingstyp erlaubt. Fachlich optimal wird sie aber erst dann, wenn: + +1. **Qualität, Last und Confidence getrennt werden** +2. **Parameter pro Trainingstyp unterschiedlich gewichtet werden** +3. **Mandatory Gates vor den eigentlichen Score geschaltet werden** +4. **relative, personenbezogene Zielbereiche absolute Schwellen möglichst oft ersetzen** +5. **Krafttraining später eigene Strukturmetriken erhält** +6. **RPE systematisch erfasst wird** +7. **fehlende Daten nicht nur den Score, sondern auch die Confidence beeinflussen** + +--- + +## 17. Quellen / fachliche Fundierung + +Die Architektur oben orientiert sich an den folgenden belastbaren Quellen und konsensnahen Leitlinien: + +1. **Bourdon et al. – Monitoring Athlete Training Loads: Consensus Statement** + Kernaussage: Interne und externe Last sollten gemeinsam betrachtet werden; idealerweise werden objektive und subjektive Werkzeuge kombiniert. + Link: https://pubmed.ncbi.nlm.nih.gov/28463642/ + Zusätzlich: https://journals.humankinetics.com/view/journals/ijspp/12/s2/article-pS2-161.pdf + +2. **Halson – Monitoring Training Load to Understand Fatigue in Athletes** + Kernaussage: Lastmonitoring hilft, Anpassung, Müdigkeit und Risiko von Fehlsteuerung besser zu erkennen. + Link: https://pmc.ncbi.nlm.nih.gov/articles/PMC4213373/ + +3. **Haddad et al. – Session-RPE Method for Training Load Monitoring: Validity, Ecological Usefulness, and Influencing Factors** + Kernaussage: sRPE ist ein praxistauglicher interner Lastmarker, der Intensität und Dauer integriert. + Link: https://pubmed.ncbi.nlm.nih.gov/29163016/ + Zusätzlich: https://pmc.ncbi.nlm.nih.gov/articles/PMC5673663/ + +4. **Coyne et al. – The Current State of Subjective Training Load Monitoring** + Kernaussage: subjektive und objektive Lastmaße sind nicht austauschbar; subjektive Maße erfassen zusätzliche psychophysiologische Dimensionen. + Link: https://pmc.ncbi.nlm.nih.gov/articles/PMC6301906/ + Update/Follow-up: https://pmc.ncbi.nlm.nih.gov/articles/PMC9012875/ + +5. **ACSM – Monitoring Aerobic Exercise Intensity / Exercise Intensity Guidance** + Kernaussage: Intensität sollte über mehrere Verfahren überwacht werden, darunter Herzfrequenz und RPE. + Link: https://acsm.org/monitoring-aerobic-exercise-intensity/ + Zusätzlich: https://acsm.org/wp-content/uploads/2025/02/Exercise-intensity-infographic-PDF.pdf + +6. **ACSM – We Can and Should Do Better When Estimating Cardiorespiratory Fitness** + Kernaussage: geschätzte HRmax und indirekte Intensitätssteuerung haben individuelle Fehler; reine HR-Grenzwerte sind daher begrenzt präzise. + Link: https://acsm.org/estimating-cardiorespiratory-fitness/ + +7. **Tanaka et al. – Age-predicted maximal heart rate revisited** + Kernaussage: HRmax-Schätzformeln sind populationsbasiert und individuell ungenau; 208 − 0.7 × Alter ist robuster als 220 − Alter, bleibt aber eine Schätzung. + Link: https://pubmed.ncbi.nlm.nih.gov/11153730/ + +8. **ACSM Position Stand – Progression Models in Resistance Training for Healthy Adults** + Kernaussage: Krafttraining sollte über echte Struktur- und Progressionsmerkmale gesteuert werden; Pulsdaten allein reichen dafür nicht. + Link: https://pubmed.ncbi.nlm.nih.gov/19204579/ diff --git a/.claude/docs/prompts/prompt-pipeline-2026-03-26.json b/.claude/docs/prompts/prompt-pipeline-2026-03-26.json new file mode 100644 index 0000000..c4bbf9c --- /dev/null +++ b/.claude/docs/prompts/prompt-pipeline-2026-03-26.json @@ -0,0 +1,56 @@ +{ + "name": "Mehrstufige Gesamtanalyse (2)", + "slug": "pipeline", + "display_name": "🔬 Mehrstufige Gesamtanalyse (2)", + "description": "Master-Schalter für die gesamte Pipeline. Deaktiviere diese Analyse, um die Pipeline komplett zu verstecken.", + "type": "pipeline", + "category": "ganzheitlich", + "active": true, + "sort_order": -10, + "output_format": "text", + "template": null, + "stages": [ + { + "stage": 1, + "prompts": [ + { + "slug": "pipeline_body", + "source": "reference", + "template": "PIPELINE_MASTER", + "output_key": "stage1_body", + "output_format": "json", + "output_schema": null + }, + { + "slug": "pipeline_nutrition", + "source": "reference", + "template": "", + "output_key": "stage1_nutrition", + "output_format": "json", + "output_schema": null + }, + { + "slug": "pipeline_activity", + "source": "reference", + "template": "", + "output_key": "stage1_activity", + "output_format": "json", + "output_schema": null + } + ] + }, + { + "stage": 2, + "prompts": [ + { + "slug": null, + "source": "inline", + "template": "Du bist ein Gesundheits- und Fitnesscoach. Erstelle eine vollständige, \npersonalisierte Analyse für {{name}} auf Deutsch (450–550 Wörter).\n\nDATENZUSAMMENFASSUNGEN AUS STUFE 1:\nKörper: {{stage1_body}}\nErnährung: {{stage1_nutrition}}\nAktivität: {{stage1_activity}}\nProtein-Ziel: {{protein_ziel_low}}–{{protein_ziel_high}}g/Tag\n\nSchreibe alle Abschnitte vollständig aus:\n⚖️ **Gewichts- & Körperzusammensetzung**\n🍽️ **Ernährungsanalyse**\n🏋️ **Aktivität & Energiebilanz**\n🔗 **Zusammenhänge** (Verbindungen zwischen Ernährung, Training, Körper)\n💪 **3 Empfehlungen** (nummeriert, konkret, datenbasiert)\n\nSachlich, motivierend, Zahlen zitieren, keine Diagnosen.", + "output_key": "output_1774507015689", + "output_format": "text", + "output_schema": null + } + ] + } + ] +} \ No newline at end of file diff --git a/.claude/docs/technical/AGGREGATION_METHODS.md b/.claude/docs/technical/AGGREGATION_METHODS.md new file mode 100644 index 0000000..0904977 --- /dev/null +++ b/.claude/docs/technical/AGGREGATION_METHODS.md @@ -0,0 +1,387 @@ +# Aggregation Methods – Goal Value Calculation + +**Zweck:** Dokumentation für Entwicklung und Erweiterung von Aggregationsmethoden im Goal-System. + +**Datum:** 2026-03-28 +**Version:** 1.0 +**Modul:** `backend/goal_utils.py` → `_fetch_by_aggregation_method()` + +--- + +## Übersicht + +Aggregationsmethoden berechnen den `current_value` von Goals aus Rohdaten (z.B. Trainings, Gewicht, Ernährung). Sie sind der Kern des dynamischen Goal-Tracking-Systems. + +**Beispiel:** +```python +Goal: "Trainingshäufigkeit Krafttraining" + source_table: activity_log + source_column: id (nur für COUNT relevant) + aggregation_method: avg_per_week_30d + filter_conditions: {"training_category": "strength"} + +→ Berechnet: Durchschnittliche Anzahl Krafttrainings pro Woche (über 30 Tage) +``` + +--- + +## Architektur + +### 1. Wo sind Methoden definiert? + +**Datei:** `backend/goal_utils.py` +**Funktion:** `_fetch_by_aggregation_method(conn, profile_id, table, column, method, filter_conditions)` + +**Aufruf-Hierarchie:** +``` +goal_utils.fetch_goal_value() + └─> _fetch_by_aggregation_method() + └─> SQL Query mit method-spezifischer Logik +``` + +### 2. Verfügbare Methoden (Stand: 2026-03-28) + +| Methode | Beschreibung | SQL Aggregat | Zeitfenster | Use Case | +|---------|--------------|--------------|-------------|----------| +| `latest` | Aktuellster Wert | SELECT {column} ORDER BY date DESC LIMIT 1 | — | Gewicht, Körperfett, VO2max | +| `avg_7d` | 7-Tage-Durchschnitt | AVG({column}) | 7 Tage | Durchschn. Ruhepuls, HRV | +| `avg_30d` | 30-Tage-Durchschnitt | AVG({column}) | 30 Tage | Durchschn. Kalorien, Protein | +| `sum_30d` | 30-Tage-Summe | SUM({column}) | 30 Tage | Gesamtkalorien, Trainingsminuten | +| `count_7d` | Anzahl Einträge (7d) | COUNT(*) | 7 Tage | Trainings letzte Woche | +| `count_30d` | Anzahl Einträge (30d) | COUNT(*) | 30 Tage | Trainings letzter Monat | +| `min_30d` | Minimum (30d) | MIN({column}) | 30 Tage | Niedrigster Ruhepuls | +| `max_30d` | Maximum (30d) | MAX({column}) | 30 Tage | Höchster VO2max | +| `avg_per_week_30d` | Durchschn. pro Woche | COUNT(*) / 4.3 | 30 Tage | Trainingsfrequenz/Woche | + +### 3. Filter-Mechanismus + +Alle Methoden unterstützen **optionale Filter** via `filter_conditions` (JSON): + +```python +filter_conditions = {"training_category": "strength"} + +# Wird zu SQL: +# ... WHERE profile_id = %s AND training_category = %s +``` + +**Unterstützte Filter-Typen:** +- **Equality:** `{"column": "value"}` → `WHERE column = 'value'` +- **IN-Clause:** `{"column": ["val1", "val2"]}` → `WHERE column IN ('val1', 'val2')` + +--- + +## Neue Aggregationsmethode hinzufügen + +### Schritt 1: Anforderungen definieren + +**Checkliste:** +- [ ] **Name:** Eindeutig, beschreibend (z.B. `avg_per_week_30d`) +- [ ] **SQL-Aggregat:** Welche Funktion? (COUNT, AVG, SUM, MIN, MAX, oder Custom) +- [ ] **Zeitfenster:** Fixed (7d, 30d) oder dynamisch? +- [ ] **Spaltentyp:** Numerisch (DECIMAL, INT) oder UUID/TEXT (nur COUNT)? +- [ ] **Filter-Support:** Ja/Nein? +- [ ] **Return-Typ:** `float` oder `None` + +### Schritt 2: Code-Template + +**Location:** `backend/goal_utils.py` → `_fetch_by_aggregation_method()` + +```python +elif method == 'neue_methode': + # 1. Zeitfenster definieren (falls relevant) + days_ago = date.today() - timedelta(days=30) + + # 2. Parameter vorbereiten (inkl. filter_params) + params = [profile_id, days_ago] + filter_params + + # 3. SQL Query (mit date_col und filter_sql) + cur.execute(f""" + SELECT AGG_FUNCTION({column}) as result_value + FROM {table} + WHERE profile_id = %s + AND {date_col} >= %s + AND {column} IS NOT NULL{filter_sql} + """, params) + + # 4. Result extrahieren und konvertieren + row = cur.fetchone() + return float(row['result_value']) if row and row['result_value'] is not None else None +``` + +### Schritt 3: Spaltentyp-Validierung + +**Wichtig:** Nur numerische Aggregationen (AVG, SUM, MIN, MAX) auf numerischen Spalten! + +**Spaltentypen:** +- ✅ **AVG/SUM/MIN/MAX:** DECIMAL, INT, FLOAT +- ❌ **AVG/SUM/MIN/MAX:** UUID, TEXT, VARCHAR +- ✅ **COUNT:** Beliebiger Typ (UUID, TEXT, etc.) + +**Bei Fehlkonfiguration:** +```python +# Wird automatisch geloggt + None returned (siehe except-Block Zeile 414-430) +[ERROR] Failed to fetch value from activity_log.id using avg_7d: + function avg(uuid) does not exist +``` + +### Schritt 4: Testen + +**Manueller Test:** +```python +from goal_utils import _fetch_by_aggregation_method +from db import get_db + +with get_db() as conn: + result = _fetch_by_aggregation_method( + conn, + profile_id='...', + table='activity_log', + column='id', + method='avg_per_week_30d', + filter_conditions={"training_category": "strength"} + ) + print(f"Result: {result}") +``` + +**Unit-Test (TODO):** +```python +# backend/tests/test_goal_utils.py +def test_avg_per_week_30d(): + # Setup: Insert 12 activities in last 30 days + # Expected: 12 / 4.3 ≈ 2.79 + assert result == pytest.approx(2.79, abs=0.1) +``` + +--- + +## Beispiel-Implementierung: avg_per_week_30d + +**Use Case:** Trainingshäufigkeit pro Woche (geglättet über 30 Tage) + +**Berechnung:** `(Anzahl Trainings in 30 Tagen) / 4.3 Wochen` + +**Code:** +```python +elif method == 'avg_per_week_30d': + days_ago = date.today() - timedelta(days=30) + params = [profile_id, days_ago] + filter_params + cur.execute(f""" + SELECT COUNT(*) as count_value FROM {table} + WHERE profile_id = %s AND {date_col} >= %s{filter_sql} + """, params) + row = cur.fetchone() + if row and row['count_value'] is not None: + # 30 Tage = 4.285 Wochen (30/7) + return round(float(row['count_value']) / 4.285, 2) + return None +``` + +**Warum 4.285?** +- 30 Tage ÷ 7 Tage/Woche = 4.285 Wochen +- Alternativ: 4.3 (gerundet) für einfachere Rechnung + +--- + +## Best Practices + +### 1. Naming Conventions + +**Pattern:** `{aggregat}_{spalte}_{zeitfenster}` + +- ✅ `avg_hr_7d` – Average heart rate, 7 days +- ✅ `count_per_week_30d` – Count per week, averaged over 30 days +- ✅ `sum_calories_30d` – Sum of calories, 30 days +- ❌ `get_training_count` – Unklar, kein Zeitfenster +- ❌ `calc_average` – Zu generisch + +### 2. Return-Werte + +**Konsistenz:** +- **Erfolg:** `float` (auch bei 0.0) +- **Keine Daten:** `None` (nicht 0.0!) +- **Fehler:** `None` (geloggt im except-Block) + +**Warum None statt 0.0?** +```python +# None = "Keine Daten vorhanden" +# 0.0 = "Gemessen, aber Wert ist tatsächlich 0" +``` + +### 3. Date-Columns + +Nicht alle Tabellen nutzen `date` als Spaltenname: + +```python +DATE_COLUMN_MAP = { + 'blood_pressure_log': 'measured_at', # TIMESTAMP + 'activity_log': 'date', # DATE + 'fitness_tests': 'test_date', # DATE + # ... siehe goal_utils.py Zeile 289-300 +} +``` + +**Nutzung:** `date_col = DATE_COLUMN_MAP.get(table, 'date')` + +### 4. Filter-Safety + +**SQL-Injection-Schutz:** +- ✅ **Parametrisierte Queries:** `WHERE col = %s` + `params` +- ❌ **String-Interpolation:** `WHERE col = '{value}'` + +**Filter-Validierung:** +```python +try: + filters = json.loads(filter_conditions) if isinstance(filter_conditions, str) else filter_conditions + # ... build filter_sql +except (json.JSONDecodeError, TypeError, AttributeError) as e: + print(f"[WARNING] Invalid filter_conditions: {e}, ignoring filters") +``` + +### 5. Performance + +**Query-Optimierung:** +- `WHERE profile_id = %s` ist **immer** erste Bedingung (Index) +- `AND {column} IS NOT NULL` vor Aggregation (reduziert NULL-Handling) +- `ORDER BY {date_col} DESC LIMIT 1` für `latest` (schneller als MAX) + +--- + +## Erweiterte Methoden (Future) + +### Statistische Analysen + +**Median:** +```python +elif method == 'median_30d': + # PostgreSQL: PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY column) + cur.execute(f""" + SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY {column}) as median_value + FROM {table} + WHERE profile_id = %s AND {date_col} >= %s{filter_sql} + """, params) +``` + +**Standard Deviation:** +```python +elif method == 'stddev_30d': + cur.execute(f""" + SELECT STDDEV({column}) as stddev_value FROM {table} + WHERE profile_id = %s AND {date_col} >= %s{filter_sql} + """, params) +``` + +**Trend (Linear Regression):** +```python +elif method == 'trend_30d': + # Slope via REGR_SLOPE(y, x) + cur.execute(f""" + SELECT REGR_SLOPE( + {column}, + EXTRACT(EPOCH FROM {date_col}) + ) as slope FROM {table} + WHERE profile_id = %s AND {date_col} >= %s{filter_sql} + """, params) +``` + +### Kalenderwoche + +```python +elif method == 'count_calendar_week': + # Montag der aktuellen Woche + today = date.today() + monday = today - timedelta(days=today.weekday()) + + cur.execute(f""" + SELECT COUNT(*) as count_value FROM {table} + WHERE profile_id = %s + AND {date_col} >= %s + AND {date_col} < %s + INTERVAL '7 days'{filter_sql} + """, [profile_id, monday] + filter_params) +``` + +--- + +## Fehlerbehandlung + +### Exception-Handling + +**Alle Methoden sind wrapped in try-except** (Zeile 329-430): + +```python +try: + # ... method logic +except Exception as e: + print(f"[ERROR] Failed to fetch value from {table}.{column} using {method}: {e}") + print(f"[ERROR] Filter conditions: {filter_conditions}") + + # CRITICAL: Rollback transaction + conn.rollback() + + return None +``` + +**Warum Rollback?** +- PostgreSQL bleibt in `InFailedSqlTransaction` bis Rollback +- Ohne Rollback: Alle nachfolgenden Queries schlagen fehl + +### Typische Fehler + +| Fehler | Ursache | Lösung | +|--------|---------|--------| +| `function avg(uuid) does not exist` | AVG auf UUID-Spalte | Methode auf `count_*` ändern | +| `column "xyz" does not exist` | Falsche source_column | Schema prüfen, Spalte korrigieren | +| `division by zero` | Keine Daten für Durchschnitt | None-Check vor Division | +| `UndefinedColumn: training_category` | Filter-Spalte existiert nicht | Filter entfernen oder Spalte anlegen | + +--- + +## Migration zu neuer Methode + +**Szenario:** Bestehende Goal-Type-Definition ändern + +**Beispiel:** `sport_pro_woche` von `avg_7d` zu `avg_per_week_30d` + +**SQL:** +```sql +UPDATE goal_type_definitions +SET aggregation_method = 'avg_per_week_30d' +WHERE type_key = 'sport_pro_woche'; +``` + +**Wichtig:** +- Bestehende Goals behalten ihre `current_value` (historisch) +- Nächste Berechnung nutzt neue Methode +- UI zeigt dann neuen Wert + +--- + +## Dokumentations-Pflicht + +**Bei jeder neuen Methode:** +1. ✅ Eintrag in dieser Datei (Tabelle "Verfügbare Methoden") +2. ✅ Docstring in `_fetch_by_aggregation_method()` +3. ✅ Beispiel-Anwendung (Use Case) +4. ✅ Unit-Test (wenn möglich) +5. ✅ Update in `goal_types.py` Schema-Info (falls relevant für Admin-UI) + +--- + +## Zusammenfassung + +**Aggregationsmethoden sind:** +- ✅ Zentral in `goal_utils.py` +- ✅ SQL-basiert (PostgreSQL-Funktionen) +- ✅ Filter-fähig (JSON-basiert) +- ✅ Error-safe (Rollback + None-Return) +- ✅ Erweiterbar (einfaches elif-Pattern) + +**Für neue Methoden:** +1. Name definieren (`{aggregat}_{zeitfenster}`) +2. SQL Query schreiben (mit filter_sql) +3. Testen (manuell + Unit-Test) +4. Dokumentieren (diese Datei) + +**Bei Fragen:** +- Siehe `backend/goal_utils.py` Zeile 259-430 +- Siehe bestehende Methoden als Template +- Siehe `.claude/docs/working/GOALS_SYSTEM_UNIFIED_ANALYSIS.md` für Kontext diff --git a/.claude/docs/technical/API_REFERENCE.md b/.claude/docs/technical/API_REFERENCE.md new file mode 100644 index 0000000..3eb9a61 --- /dev/null +++ b/.claude/docs/technical/API_REFERENCE.md @@ -0,0 +1,575 @@ +# API-Referenz + +## Basis-URLs + +| Umgebung | URL | +|----------|-----| +| **Production** | `https://mitai.jinkendo.de/api` | +| **Development** | `https://dev.mitai.jinkendo.de/api` | +| **Local** | `http://localhost:8000/api` (Backend direkt) | + +--- + +## Authentifizierung + +**Alle geschützten Endpoints** benötigen einen Auth-Token im Header: + +```http +GET /api/weight +X-Auth-Token: jT9z3xK...pQ2vL +``` + +**Token-Beschaffung:** `POST /api/auth/login` → `{"token": "..."}` + +**Storage:** `localStorage.bodytrack_token` (Frontend) + +--- + +## Fehler-Codes + +| Status | Bedeutung | Beispiel | +|--------|-----------|----------| +| **200** | Erfolg | `{"data": [...]}` | +| **201** | Erstellt | `{"id": "uuid", ...}` | +| **400** | Bad Request | `{"detail": "Ungültige Eingabe"}` | +| **401** | Unauthorized | `{"detail": "Nicht eingeloggt"}` | +| **403** | Forbidden | `{"detail": "Nur für Admins"}` oder `{"detail": "Feature-Limit erreicht"}` | +| **404** | Not Found | `{"detail": "Eintrag nicht gefunden"}` | +| **429** | Too Many Requests | `{"detail": "Rate limit exceeded: 5 per 1 minute"}` | +| **500** | Server Error | `{"detail": "Interner Fehler"}` | + +**Standard-Fehler-Format:** +```json +{ + "detail": "Fehlermeldung" +} +``` + +--- + +## Rate Limits + +| Endpoint | Limit | Grund | +|----------|-------|-------| +| `POST /api/auth/login` | 5/minute | Brute-Force-Schutz | +| `POST /api/auth/register` | 3/hour | Spam-Prevention | +| `POST /api/auth/forgot-password` | 3/minute | E-Mail-Flooding-Schutz | +| `POST /api/auth/resend-verification` | 3/hour | E-Mail-Flooding-Schutz | + +**Andere Endpoints:** Keine Rate-Limits (Feature-Limits via Membership-System) + +--- + +## Endpoints nach Modul + +### 1. Auth (`/api/auth/*`) + +| Methode | Pfad | Auth | Parameter | Response | Beschreibung | +|---------|------|------|-----------|----------|--------------| +| `POST` | `/auth/login` | ❌ | `{email, password}` | `{token, profile_id, name, role, expires_at}` | Login mit E-Mail + Passwort | +| `POST` | `/auth/logout` | ✅ | – | `{ok: true}` | Logout (löscht Session) | +| `GET` | `/auth/me` | ✅ | – | `{id, name, email, role, tier, ...}` | Aktuelles Profil abrufen | +| `GET` | `/auth/status` | ❌ | – | `{status: "ok", version: "v9b"}` | Health Check | +| `PUT` | `/auth/pin` | ✅ | `{pin}` | `{ok: true}` | PIN/Passwort ändern | +| `POST` | `/auth/forgot-password` | ❌ | `{email}` | `{ok: true, message}` | Passwort-Reset anfordern | +| `POST` | `/auth/reset-password` | ❌ | `{token, new_password}` | `{ok: true, message}` | Passwort-Reset bestätigen | +| `POST` | `/auth/register` | ❌ | `{name, email, password}` | `{ok: true, message}` | Selbst-Registrierung | +| `GET` | `/auth/verify/{token}` | ❌ | – | `{ok: true, token, profile}` | E-Mail-Verifizierung | +| `POST` | `/auth/resend-verification` | ❌ | `{email}` | `{ok: true, message}` | Verifizierungs-E-Mail erneut senden | + +**Rate Limits:** Siehe Tabelle oben + +--- + +### 2. Profiles (`/api/profiles/*`, `/api/profile`) + +| Methode | Pfad | Auth | Parameter | Response | Beschreibung | +|---------|------|------|-----------|----------|--------------| +| `GET` | `/profile` | ✅ | – | `{id, name, email, role, tier, ...}` | Aktives Profil | +| `PUT` | `/profile` | ✅ | `{name?, sex?, dob?, height?, goal_weight?, goal_bf_pct?, avatar_color?}` | `{ok: true}` | Profil aktualisieren | +| `GET` | `/profiles` | ✅ | – | `[{id, name, ...}, ...]` | Alle Profile (Multi-User, derzeit nicht genutzt) | +| `POST` | `/profiles` | ✅ | `{name, ...}` | `{id, ...}` | Profil erstellen (Multi-User) | +| `PUT` | `/profiles/{id}` | ✅ | `{name?, ...}` | `{ok: true}` | Profil bearbeiten | +| `DELETE` | `/profiles/{id}` | ✅ | – | `{ok: true}` | Profil löschen | + +--- + +### 3. Weight (`/api/weight/*`) + +| Methode | Pfad | Auth | Parameter | Response | Beschreibung | +|---------|------|------|-----------|----------|--------------| +| `GET` | `/weight` | ✅ | `?limit=365` | `[{id, date, weight, note, source, created}, ...]` | Gewichtseinträge abrufen | +| `POST` | `/weight` | ✅ | `{date, weight, note?}` | `{id, date, weight, ...}` | Gewicht eintragen (Upsert) | +| `PUT` | `/weight/{id}` | ✅ | `{date, weight, note?}` | `{ok: true}` | Eintrag bearbeiten | +| `DELETE` | `/weight/{id}` | ✅ | – | `{ok: true}` | Eintrag löschen | +| `GET` | `/weight/stats` | ✅ | – | `{latest, avg_7d, avg_30d, delta_7d, delta_30d, trend}` | Gewichts-Statistiken | + +**Feature-Limits:** `weight_entries` (v9c Phase 4: Enforcement aktiv) + +--- + +### 4. Circumferences (`/api/circumferences/*`) + +| Methode | Pfad | Auth | Parameter | Response | Beschreibung | +|---------|------|------|-----------|----------|--------------| +| `GET` | `/circumferences` | ✅ | `?limit=100` | `[{id, date, c_neck, c_chest, c_waist, c_belly, c_hip, c_thigh, c_calf, c_arm, notes, photo_id}, ...]` | Umfangsmessungen | +| `POST` | `/circumferences` | ✅ | `{date, c_neck?, c_chest?, ...}` | `{id, ...}` | Messung eintragen (Upsert) | +| `PUT` | `/circumferences/{id}` | ✅ | `{date, ...}` | `{ok: true}` | Bearbeiten | +| `DELETE` | `/circumferences/{id}` | ✅ | – | `{ok: true}` | Löschen | + +**Feature-Limits:** `circumference_entries` + +--- + +### 5. Caliper (`/api/caliper/*`) + +| Methode | Pfad | Auth | Parameter | Response | Beschreibung | +|---------|------|------|-----------|----------|--------------| +| `GET` | `/caliper` | ✅ | `?limit=100` | `[{id, date, sf_method, sf_chest, sf_abdomen, ..., body_fat_pct, lean_mass, fat_mass, notes}, ...]` | Hautfaltenmessungen | +| `POST` | `/caliper` | ✅ | `{date, sf_method, sf_chest?, ...}` | `{id, body_fat_pct, ...}` | Messung eintragen (berechnet KF% automatisch) | +| `PUT` | `/caliper/{id}` | ✅ | `{date, ...}` | `{ok: true}` | Bearbeiten | +| `DELETE` | `/caliper/{id}` | ✅ | – | `{ok: true}` | Löschen | + +**Methoden:** `jackson3`, `jackson7`, `durnin`, `parrillo` + +**Feature-Limits:** `caliper_entries` + +--- + +### 6. Activity (`/api/activity/*`) + +| Methode | Pfad | Auth | Parameter | Response | Beschreibung | +|---------|------|------|-----------|----------|--------------| +| `GET` | `/activity` | ✅ | `?limit=200` | `[{id, date, start_time, end_time, activity_type, duration_min, kcal_active, hr_avg, hr_max, distance_km, rpe, source, notes}, ...]` | Aktivitäten | +| `POST` | `/activity` | ✅ | `{date, activity_type, duration_min, ...}` | `{id, ...}` | Aktivität erstellen | +| `PUT` | `/activity/{id}` | ✅ | `{date, ...}` | `{ok: true}` | Bearbeiten | +| `DELETE` | `/activity/{id}` | ✅ | – | `{ok: true}` | Löschen | +| `GET` | `/activity/stats` | ✅ | – | `{total_activities, total_kcal, avg_duration, ...}` | Statistiken | +| `GET` | `/activity/uncategorized` | ✅ | – | `[{activity_type, count}, ...]` | Unkategorisierte Aktivitäten | +| `POST` | `/activity/bulk-categorize` | ✅ | `[{activity_type, training_type_id}, ...]` | `{ok: true, updated_count}` | Bulk-Kategorisierung (lernendes System) | +| `POST` | `/activity/import-csv` | ✅ | `FormData(file)` | `{imported, skipped, failed, errors}` | Apple Health CSV-Import | + +**Feature-Limits:** `activity_entries` + +--- + +### 7. Nutrition (`/api/nutrition/*`) + +| Methode | Pfad | Auth | Parameter | Response | Beschreibung | +|---------|------|------|-----------|----------|--------------| +| `GET` | `/nutrition` | ✅ | `?limit=365` | `[{id, date, kcal, protein_g, fat_g, carbs_g, source}, ...]` | Ernährungsdaten | +| `GET` | `/nutrition/by-date/{date}` | ✅ | – | `{id, date, kcal, ...}` oder `null` | Eintrag für bestimmtes Datum | +| `POST` | `/nutrition` | ✅ | `?date=YYYY-MM-DD&kcal=2000&protein_g=150&fat_g=70&carbs_g=200` | `{id, ...}` | Eintrag erstellen (Upsert) | +| `PUT` | `/nutrition/{id}` | ✅ | `?kcal=2000&...` | `{ok: true}` | Bearbeiten | +| `DELETE` | `/nutrition/{id}` | ✅ | – | `{ok: true}` | Löschen | +| `GET` | `/nutrition/correlations` | ✅ | – | `{weight_vs_kcal: [...], bf_vs_protein: [...]}` | Korrelationen | +| `GET` | `/nutrition/weekly` | ✅ | `?weeks=16` | `[{week, year, avg_kcal, avg_protein, ...}, ...]` | Wochendaten | +| `GET` | `/nutrition/import-history` | ✅ | – | `[{date, count}, ...]` | Import-Historie | +| `POST` | `/nutrition/import-csv` | ✅ | `FormData(file)` | `{imported, skipped, failed, errors}` | CSV-Import (FDDB, MyFitnessPal) | + +**Feature-Limits:** `nutrition_entries`, `data_import` + +--- + +### 8. Photos (`/api/photos/*`) + +| Methode | Pfad | Auth | Parameter | Response | Beschreibung | +|---------|------|------|-----------|----------|--------------| +| `GET` | `/photos` | ✅ | – | `[{id, date, path, created}, ...]` | Alle Fotos | +| `GET` | `/photos/{id}` | ✅ | `?token=...` (optional) | Binary (JPEG) | Foto abrufen (Token für tag) | +| `POST` | `/photos` | ✅ | `FormData(file, date?)` | `{id, path, date}` | Foto hochladen | +| `DELETE` | `/photos/{id}` | ✅ | – | `{ok: true}` | Foto löschen | + +**Feature-Limits:** `photos` + +**Hinweis:** Token-Parameter für `GET /photos/{id}` erlaubt Zugriff via `` tag (ohne Header-Support) + +--- + +### 9. AI Insights (`/api/insights/*`, `/api/ai/*`) + +| Methode | Pfad | Auth | Parameter | Response | Beschreibung | +|---------|------|------|-----------|----------|--------------| +| `GET` | `/insights` | ✅ | – | `[{id, scope, content, created}, ...]` | Alle Insights | +| `GET` | `/insights/latest` | ✅ | – | `{slug: content, ...}` | Neueste Insights pro Scope | +| `POST` | `/insights/trend` | ✅ | – | `{content, usage}` | Trend-Analyse (Gewicht) | +| `POST` | `/insights/run/{slug}` | ✅ | – | `{content, usage}` | Einzelnen Prompt ausführen | +| `POST` | `/insights/pipeline` | ✅ | – | `{content, usage}` | 3-stufige Pipeline | + +**Feature-Limits:** `ai_calls` (pro Aufruf), `ai_pipeline` (für Pipeline) + +**Prompts:** Konfigurierbar via `/api/prompts` + +--- + +### 10. AI Prompts (`/api/prompts/*`) + +| Methode | Pfad | Auth | Parameter | Response | Beschreibung | +|---------|------|------|-----------|----------|--------------| +| `GET` | `/prompts` | ✅ | – | `[{id, slug, name, description, template, active, sort_order}, ...]` | Alle Prompts | +| `PUT` | `/prompts/{id}` | 🔒 Admin | `{name?, description?, template?, active?, sort_order?}` | `{ok: true}` | Prompt bearbeiten | + +**Standard-Prompts:** +- `weight-trend` – Gewichts-Trend-Analyse +- `nutrition-analysis` – Ernährungs-Auswertung +- `training-plan` – Trainings-Empfehlungen +- `body-composition` – Körperzusammensetzung +- `progress-summary` – Fortschritts-Zusammenfassung +- `pipeline` – Master-Schalter für Pipeline + +--- + +### 11. Admin (`/api/admin/*`) + +| Methode | Pfad | Auth | Parameter | Response | Beschreibung | +|---------|------|------|-----------|----------|--------------| +| `GET` | `/admin/profiles` | 🔒 Admin | – | `[{id, name, email, role, tier, ...}, ...]` | Alle Profile | +| `POST` | `/admin/profiles` | 🔒 Admin | `{name, email, password, role?}` | `{id, ...}` | Profil erstellen | +| `DELETE` | `/admin/profiles/{id}` | 🔒 Admin | – | `{ok: true}` | Profil löschen | +| `PUT` | `/admin/profiles/{id}/permissions` | 🔒 Admin | `{ai_enabled?, ai_limit_day?, export_enabled?}` | `{ok: true}` | Permissions setzen | + +--- + +### 12. Stats (`/api/stats`) + +| Methode | Pfad | Auth | Parameter | Response | Beschreibung | +|---------|------|------|-----------|----------|--------------| +| `GET` | `/stats` | ✅ | – | `{weight: {...}, nutrition: {...}, activity: {...}, body_comp: {...}}` | Dashboard-Stats | + +**Response-Struktur:** +```json +{ + "weight": { + "latest": 75.5, + "avg_7d": 75.8, + "avg_30d": 76.2, + "delta_7d": -0.3, + "delta_30d": -0.7 + }, + "nutrition": { + "avg_kcal_7d": 2100, + "avg_protein_7d": 150, + "days_logged_7d": 6 + }, + "activity": { + "total_activities_7d": 5, + "total_kcal_7d": 2500, + "avg_duration_7d": 45 + }, + "body_comp": { + "latest_bf_pct": 12.5, + "latest_lean_mass": 65.8 + } +} +``` + +--- + +### 13. Export (`/api/export/*`) + +| Methode | Pfad | Auth | Parameter | Response | Beschreibung | +|---------|------|------|-----------|----------|--------------| +| `GET` | `/export/csv` | ✅ | – | Binary (CSV) | Alle Daten als CSV | +| `GET` | `/export/json` | ✅ | – | Binary (JSON) | Alle Daten als JSON | +| `GET` | `/export/zip` | ✅ | – | Binary (ZIP) | Alle Daten + Fotos als ZIP | + +**Feature-Limits:** `data_export` + +**Dateiname:** `mitai-export-YYYY-MM-DD.[csv|json|zip]` + +**ZIP-Struktur:** +``` +mitai-export-2026-03-23.zip +├── data.json +├── photos/ +│ ├── photo_uuid1.jpg +│ └── photo_uuid2.jpg +└── README.txt +``` + +--- + +### 14. Import (`/api/import/*`) + +| Methode | Pfad | Auth | Parameter | Response | Beschreibung | +|---------|------|------|-----------|----------|--------------| +| `POST` | `/import/backup` | ✅ | `FormData(file)` | `{imported: {...}, skipped: {...}, failed: {...}}` | JSON-Backup importieren | + +**Feature-Limits:** `data_import` + +**Format:** JSON-Export von `/export/json` + +**Hinweis:** Überschreibt keine existierenden Einträge (Upsert-Logik) + +--- + +## Subscription System (v9c) + +### 15. Subscription (`/api/subscription/*`) + +| Methode | Pfad | Auth | Parameter | Response | Beschreibung | +|---------|------|------|-----------|----------|--------------| +| `GET` | `/subscription/me` | ✅ | – | `{tier, tier_expires_at, trial_ends_at, invited_by, ...}` | Abo-Status | +| `GET` | `/subscription/usage` | ✅ | – | `{feature_id: {used, limit, remaining, allowed}, ...}` | Feature-Usage | +| `GET` | `/subscription/limits` | ✅ | – | `{feature_id: limit, ...}` | Feature-Limits für aktuelles Tier | + +--- + +### 16. Coupons (`/api/coupons/*`) + +| Methode | Pfad | Auth | Parameter | Response | Beschreibung | +|---------|------|------|-----------|----------|--------------| +| `POST` | `/coupons/redeem` | ✅ | `{code}` | `{ok: true, tier_id, valid_days, message}` | Coupon einlösen | +| `GET` | `/coupons` | 🔒 Admin | – | `[{id, code, tier_id, valid_days, max_uses, ...}, ...]` | Alle Coupons | +| `POST` | `/coupons` | 🔒 Admin | `{code, tier_id, valid_days, max_uses?}` | `{id, ...}` | Coupon erstellen | +| `PUT` | `/coupons/{id}` | 🔒 Admin | `{...}` | `{ok: true}` | Bearbeiten | +| `DELETE` | `/coupons/{id}` | 🔒 Admin | – | `{ok: true}` | Löschen | +| `GET` | `/coupons/{id}/redemptions` | 🔒 Admin | – | `[{profile_id, redeemed_at}, ...]` | Einlösungen | + +--- + +### 17. Features (`/api/features/*`) + +| Methode | Pfad | Auth | Parameter | Response | Beschreibung | +|---------|------|------|-----------|----------|--------------| +| `GET` | `/features` | ✅ | – | `[{id, name, description, limit_type, reset_period, default_limit, active}, ...]` | Alle Features | +| `GET` | `/features/usage` | ✅ | – | `[{feature_id, feature_name, limit, used, remaining, allowed, reason}, ...]` | Feature-Usage für aktuellen User | +| `POST` | `/features` | 🔒 Admin | `{id, name, description, limit_type, reset_period, default_limit}` | `{id, ...}` | Feature erstellen | +| `PUT` | `/features/{id}` | 🔒 Admin | `{...}` | `{ok: true}` | Bearbeiten | +| `DELETE` | `/features/{id}` | 🔒 Admin | – | `{ok: true}` | Löschen | + +**Limit-Types:** `count` (zählbar), `boolean` (on/off) + +**Reset-Periods:** `never`, `daily`, `monthly` + +--- + +### 18. Tiers (`/api/tiers/*`) + +| Methode | Pfad | Auth | Parameter | Response | Beschreibung | +|---------|------|------|-----------|----------|--------------| +| `GET` | `/tiers` | 🔒 Admin | – | `[{id, name, description, price_monthly, active}, ...]` | Alle Tiers | +| `POST` | `/tiers` | 🔒 Admin | `{id, name, description, price_monthly}` | `{id, ...}` | Tier erstellen | +| `PUT` | `/tiers/{id}` | 🔒 Admin | `{...}` | `{ok: true}` | Bearbeiten | +| `DELETE` | `/tiers/{id}` | 🔒 Admin | – | `{ok: true}` | Löschen | + +**Standard-Tiers:** `free`, `basic`, `premium`, `selfhosted` + +--- + +### 19. Tier Limits (`/api/tier-limits/*`) + +| Methode | Pfad | Auth | Parameter | Response | Beschreibung | +|---------|------|------|-----------|----------|--------------| +| `GET` | `/tier-limits` | 🔒 Admin | – | `{tiers: [...], features: [...], matrix: {...}}` | Tier-Limits-Matrix | +| `PUT` | `/tier-limits` | 🔒 Admin | `{tier_id, feature_id, limit_value}` | `{ok: true}` | Limit setzen | +| `PUT` | `/tier-limits/batch` | 🔒 Admin | `{updates: [{tier_id, feature_id, limit_value}, ...]}` | `{ok: true}` | Batch-Update | + +**Matrix-Format:** +```json +{ + "tiers": ["free", "basic", "premium", "selfhosted"], + "features": [ + {"id": "weight_entries", "name": "Gewichtseinträge", ...}, + ... + ], + "matrix": { + "weight_entries": { + "free": 100, + "basic": 1000, + "premium": null, + "selfhosted": null + }, + ... + } +} +``` + +--- + +### 20. User Restrictions (`/api/user-restrictions/*`) + +| Methode | Pfad | Auth | Parameter | Response | Beschreibung | +|---------|------|------|-----------|----------|--------------| +| `GET` | `/user-restrictions` | 🔒 Admin | `?profile_id=...` | `[{id, profile_id, feature_id, limit_value}, ...]` | User-spezifische Limits | +| `POST` | `/user-restrictions` | 🔒 Admin | `{profile_id, feature_id, limit_value}` | `{id, ...}` | Limit setzen | +| `PUT` | `/user-restrictions/{id}` | 🔒 Admin | `{limit_value}` | `{ok: true}` | Bearbeiten | +| `DELETE` | `/user-restrictions/{id}` | 🔒 Admin | – | `{ok: true}` | Löschen | + +**Verwendung:** Individuelle Limits überschreiben Tier-Limits + +--- + +### 21. Access Grants (`/api/access-grants/*`) + +| Methode | Pfad | Auth | Parameter | Response | Beschreibung | +|---------|------|------|-----------|----------|--------------| +| `GET` | `/access-grants` | 🔒 Admin | `?profile_id=...&active_only=true` | `[{id, profile_id, tier_id, valid_from, valid_until, source, is_active}, ...]` | Zeitlich begrenzte Tier-Zugriffe | +| `POST` | `/access-grants` | 🔒 Admin | `{profile_id, tier_id, valid_from, valid_until, source?}` | `{id, ...}` | Grant erstellen | +| `PUT` | `/access-grants/{id}` | 🔒 Admin | `{...}` | `{ok: true}` | Bearbeiten | +| `DELETE` | `/access-grants/{id}` | 🔒 Admin | – | `{ok: true}` | Revoke | + +**Quellen:** `coupon`, `trial`, `manual`, `gift` + +--- + +## Training Types (v9d) + +### 22. Training Types (`/api/training-types/*`) + +| Methode | Pfad | Auth | Parameter | Response | Beschreibung | +|---------|------|------|-----------|----------|--------------| +| `GET` | `/training-types` | ✅ | – | `{category: [{id, name, category, color, icon}, ...], ...}` | Gruppiert nach Kategorie | +| `GET` | `/training-types/flat` | ✅ | – | `[{id, name, category, ...}, ...]` | Flache Liste | +| `GET` | `/training-types/categories` | ✅ | – | `[{id, name, icon, color}, ...]` | Kategorien-Metadaten | + +**Kategorien:** Kraft, Cardio, Flexibilität, Spiel & Sport, Alltag & Bewegung, Outdoor & Natur, Geist & Meditation + +--- + +### 23. Admin Training Types (`/api/admin/training-types/*`) + +| Methode | Pfad | Auth | Parameter | Response | Beschreibung | +|---------|------|------|-----------|----------|--------------| +| `GET` | `/admin/training-types` | 🔒 Admin | – | `[{id, name, category, color, icon, abilities}, ...]` | Alle Typen (inkl. abilities JSONB) | +| `GET` | `/admin/training-types/{id}` | 🔒 Admin | – | `{id, name, ...}` | Einzelner Typ | +| `POST` | `/admin/training-types` | 🔒 Admin | `{name, category, color?, icon?}` | `{id, ...}` | Typ erstellen | +| `PUT` | `/admin/training-types/{id}` | 🔒 Admin | `{name?, category?, color?, icon?, abilities?}` | `{ok: true}` | Bearbeiten | +| `DELETE` | `/admin/training-types/{id}` | 🔒 Admin | – | `{ok: true}` | Löschen | +| `GET` | `/admin/training-types/taxonomy/abilities` | 🔒 Admin | – | `{categories: [...], abilities: [...]}` | Abilities-Taxonomie (v9f) | + +--- + +### 24. Activity Mappings (`/api/admin/activity-mappings/*`) + +| Methode | Pfad | Auth | Parameter | Response | Beschreibung | +|---------|------|------|-----------|----------|--------------| +| `GET` | `/admin/activity-mappings` | 🔒 Admin | `?profile_id=...&global_only=true` | `[{id, activity_type, training_type_id, profile_id, is_global}, ...]` | Lernendes Mapping-System | +| `GET` | `/admin/activity-mappings/{id}` | 🔒 Admin | – | `{id, ...}` | Einzelnes Mapping | +| `POST` | `/admin/activity-mappings` | 🔒 Admin | `{activity_type, training_type_id, profile_id?, is_global?}` | `{id, ...}` | Mapping erstellen | +| `PUT` | `/admin/activity-mappings/{id}` | 🔒 Admin | `{training_type_id?, is_global?}` | `{ok: true}` | Bearbeiten | +| `DELETE` | `/admin/activity-mappings/{id}` | 🔒 Admin | – | `{ok: true}` | Löschen | +| `GET` | `/admin/activity-mappings/stats/coverage` | 🔒 Admin | – | `{total_activities, mapped, unmapped, coverage_pct}` | Mapping-Coverage | + +**Auto-Learning:** Bulk-Kategorisierung in ActivityPage speichert neue Mappings automatisch + +--- + +### 25. Training Profiles (`/api/evaluation/*`) + +| Methode | Pfad | Auth | Parameter | Response | Beschreibung | +|---------|------|------|-----------|----------|--------------| +| `GET` | `/evaluation/parameters` | 🔒 Admin | – | `{categories: [...], abilities: [...]}` | Training-Parameter-Taxonomie | +| `POST` | `/evaluation/batch` | 🔒 Admin | – | `{evaluated, failed, errors}` | Batch-Evaluierung aller Aktivitäten (v9d #15) | + +--- + +## Sleep & Vitals (v9d Phase 2) + +### 26. Sleep (`/api/sleep/*`) + +| Methode | Pfad | Auth | Parameter | Response | Beschreibung | +|---------|------|------|-----------|----------|--------------| +| `GET` | `/sleep` | ✅ | `?limit=90` | `[{id, date, bedtime, wakeup, duration_min, quality, sleep_segments, notes, source}, ...]` | Schlaf-Einträge | +| `GET` | `/sleep/by-date/{date}` | ✅ | – | `{id, date, ...}` oder `null` | Eintrag für Datum | +| `POST` | `/sleep` | ✅ | `{date, bedtime, wakeup, duration_min, quality?, sleep_segments?, notes?}` | `{id, ...}` | Eintrag erstellen (Upsert) | +| `PUT` | `/sleep/{id}` | ✅ | `{...}` | `{ok: true}` | Bearbeiten | +| `DELETE` | `/sleep/{id}` | ✅ | – | `{ok: true}` | Löschen | +| `GET` | `/sleep/stats` | ✅ | `?days=7` | `{avg_duration, avg_quality, total_deep, total_rem, ...}` | Stats | +| `GET` | `/sleep/debt` | ✅ | `?days=14` | `{sleep_debt_min, avg_duration, target_duration}` | Schlafschuld | +| `GET` | `/sleep/trend` | ✅ | `?days=30` | `[{date, duration_min, quality}, ...]` | Trend | +| `GET` | `/sleep/phases` | ✅ | `?days=30` | `[{date, deep_min, rem_min, light_min, awake_min}, ...]` | Schlafphasen | +| `POST` | `/sleep/import/apple-health` | ✅ | `FormData(file)` | `{imported, skipped, failed, errors}` | Apple Health CSV-Import | + +**sleep_segments Format (JSONB):** +```json +[ + {"phase": "deep", "start": "23:30", "end": "01:15"}, + {"phase": "rem", "start": "01:15", "end": "02:45"}, + {"phase": "light", "start": "02:45", "end": "06:00"}, + {"phase": "awake", "start": "06:00", "end": "06:15"} +] +``` + +--- + +### 27. Rest Days (`/api/rest-days/*`) + +| Methode | Pfad | Auth | Parameter | Response | Beschreibung | +|---------|------|------|-----------|----------|--------------| +| `GET` | `/rest-days` | ✅ | `?limit=90` | `[{id, date, rest_type, reason, notes}, ...]` | Ruhetage | +| `GET` | `/rest-days/{id}` | ✅ | – | `{id, ...}` | Einzelner Eintrag | +| `POST` | `/rest-days` | ✅ | `{date, rest_type, reason?, notes?}` | `{id, ...}` | Ruhetag eintragen | +| `PUT` | `/rest-days/{id}` | ✅ | `{...}` | `{ok: true}` | Bearbeiten | +| `DELETE` | `/rest-days/{id}` | ✅ | – | `{ok: true}` | Löschen | +| `GET` | `/rest-days/stats` | ✅ | `?weeks=4` | `{total_rest_days, kraft_days, cardio_days, entspannung_days}` | Stats | +| `POST` | `/rest-days/validate-activity` | ✅ | `{date, activity_type}` | `{conflicts: [{rest_type, reason}, ...]}` | Validierung gegen Ruhetage | + +**rest_type:** `kraft`, `cardio`, `entspannung` (Multi-Dimensional Rest) + +--- + +### 28. Vitals Baseline (`/api/vitals/baseline/*`) + +| Methode | Pfad | Auth | Parameter | Response | Beschreibung | +|---------|------|------|-----------|----------|--------------| +| `GET` | `/vitals/baseline` | ✅ | `?limit=90` | `[{id, date, resting_hr, hrv, vo2_max, spo2, respiratory_rate, notes}, ...]` | Morgenmessungen | +| `GET` | `/vitals/baseline/by-date/{date}` | ✅ | – | `{id, ...}` oder `null` | Eintrag für Datum | +| `POST` | `/vitals/baseline` | ✅ | `{date, resting_hr?, hrv?, vo2_max?, spo2?, respiratory_rate?, notes?}` | `{id, ...}` | Eintrag erstellen (Upsert) | +| `PUT` | `/vitals/baseline/{id}` | ✅ | `{...}` | `{ok: true}` | Bearbeiten | +| `DELETE` | `/vitals/baseline/{id}` | ✅ | – | `{ok: true}` | Löschen | +| `GET` | `/vitals/baseline/stats` | ✅ | `?days=30` | `{avg_resting_hr, avg_hrv, trend_resting_hr, trend_hrv}` | Stats | +| `POST` | `/vitals/baseline/import/apple-health` | ✅ | `FormData(file)` | `{imported, skipped, failed, errors}` | Apple Health CSV-Import | + +**Messung:** 1x täglich, morgens nüchtern + +--- + +### 29. Blood Pressure (`/api/blood-pressure/*`) + +| Methode | Pfad | Auth | Parameter | Response | Beschreibung | +|---------|------|------|-----------|----------|--------------| +| `GET` | `/blood-pressure` | ✅ | `?limit=90` | `[{id, date, time, systolic, diastolic, pulse, context, irregular_heartbeat, afib_warning, notes}, ...]` | Blutdruck-Messungen | +| `GET` | `/blood-pressure/by-date/{date}` | ✅ | – | `[{id, time, ...}, ...]` | Alle Messungen für Datum | +| `POST` | `/blood-pressure` | ✅ | `{date, time, systolic, diastolic, pulse?, context?, irregular_heartbeat?, afib_warning?, notes?}` | `{id, ...}` | Messung eintragen | +| `PUT` | `/blood-pressure/{id}` | ✅ | `{...}` | `{ok: true}` | Bearbeiten | +| `DELETE` | `/blood-pressure/{id}` | ✅ | – | `{ok: true}` | Löschen | +| `GET` | `/blood-pressure/stats` | ✅ | `?days=30` | `{avg_systolic, avg_diastolic, avg_pulse, classification, trend}` | Stats | +| `POST` | `/blood-pressure/import/omron` | ✅ | `FormData(file)` | `{imported, skipped, failed, errors}` | Omron CSV-Import (Deutsch) | + +**Contexts:** `fasting`, `after_meal`, `exercise`, `stress`, `rest`, `before_sleep`, `after_sleep`, `medication` + +**WHO/ISH-Klassifizierung:** +- Optimal: <120/<80 +- Normal: 120-129/80-84 +- Hoch-Normal: 130-139/85-89 +- Hypertonie Grad 1: 140-159/90-99 +- Hypertonie Grad 2: 160-179/100-109 +- Hypertonie Grad 3: ≥180/≥110 + +--- + +## Zusammenfassung + +**Gesamt:** 29 Router-Module, ~200 Endpoints + +**Authentifizierung:** Token-basiert (X-Auth-Token Header) + +**Fehlerformat:** `{"detail": "Fehlermeldung"}` + +**Rate Limits:** Nur Auth-Endpoints (5/min Login, 3/hour Register) + +**Feature-Limits:** Membership-basiert (v9c), enforcement via HTTP 403 + +**Import-Formate:** CSV (Apple Health, Omron, FDDB), JSON (Backup) + +**Export-Formate:** CSV, JSON, ZIP (mit Fotos) + +**Besonderheiten:** +- Upsert-Logik für viele Endpoints (Date-basiert) +- Inline-Editing-Support (GET by date, PUT by id) +- CSV-Import mit Duplikat-Erkennung +- Lernendes Mapping-System (Activity Types) +- JSONB für flexible Datenstrukturen (sleep_segments, abilities) +- Auto-Migration SHA256 → bcrypt +- E-Mail-Verifizierung + Passwort-Reset diff --git a/.claude/docs/technical/ARCHITECTURE.md b/.claude/docs/technical/ARCHITECTURE.md new file mode 100644 index 0000000..e5b0c95 --- /dev/null +++ b/.claude/docs/technical/ARCHITECTURE.md @@ -0,0 +1,634 @@ +# Architektur-Übersicht – Mitai Jinkendo + +## System-Überblick + +**Mitai Jinkendo** ist eine selbst-gehostete Progressive Web App (PWA) für Körper-Tracking mit KI-gestützter Auswertung. Die Architektur folgt einem klassischen **3-Tier-Modell** mit klarer Trennung von Präsentation, Business-Logik und Datenhaltung. + +``` +Internet + ↓ +Fritz!Box (privat.stommer.com) + ↓ +Synology NAS + ↓ +Raspberry Pi 5 (192.168.2.49) + ↓ +┌────────────────────────────────────┐ +│ Docker Compose Environment │ +├────────────────────────────────────┤ +│ Frontend (React + Vite) │ Port 3002 (Prod) / 3099 (Dev) +│ Backend (FastAPI + Python) │ Port 8002 (Prod) / 8099 (Dev) +│ Database (PostgreSQL 16 Alpine) │ Port 5432 (internal) +└────────────────────────────────────┘ +``` + +--- + +## Tech-Stack + +| Komponente | Technologie | Version | Zweck | +|-----------|-------------|---------|-------| +| **Frontend** | React | 18 | UI Framework | +| | Vite | Latest | Build Tool + Dev Server | +| | React Router | 6 | Client-side Routing | +| | Lucide React | Latest | Icon Library | +| **Backend** | FastAPI | Latest | REST API Framework | +| | Python | 3.12 | Programmiersprache | +| | psycopg2-binary | Latest | PostgreSQL Driver | +| | bcrypt | Latest | Passwort-Hashing | +| | slowapi | Latest | Rate Limiting | +| **Database** | PostgreSQL | 16 Alpine | Primäre Datenbank | +| **Container** | Docker | Latest | Container Runtime | +| | Docker Compose | v2 | Multi-Container Orchestration | +| **KI** | OpenRouter API | - | Claude Sonnet 4 | +| | Anthropic API | - | Direkt-Integration (optional) | +| **Email** | SMTP | - | E-Mail-Versand (Verifikation, Reset) | +| **Infrastruktur** | Raspberry Pi 5 | - | Host-System | +| | Gitea | 3000 | Git Server + CI/CD | +| | Synology NAS | - | Reverse Proxy | + +--- + +## Deployment-Architektur + +### Umgebungen + +| Umgebung | Domain | Branch | Ports | Deployment | +|----------|--------|--------|-------|-----------| +| **Production** | mitai.jinkendo.de | `main` | 3002/8002 | Auto-Deploy via Gitea | +| **Development** | dev.mitai.jinkendo.de | `develop` | 3099/8099 | Auto-Deploy via Gitea | + +### Git-Workflow + +``` +develop → Push → Gitea Webhook → Runner → docker-compose.dev-env.yml → dev.mitai.jinkendo.de +main → Push → Gitea Webhook → Runner → docker-compose.yml → mitai.jinkendo.de +``` + +**Runner-Details:** +- Läuft auf Raspberry Pi 5 (`/home/lars/gitea-runner/`) +- Watchtower für automatische Updates +- Docker Compose Build + Restart bei neuen Commits + +### Verzeichnisstruktur auf dem Server + +``` +/home/lars/docker/ +├── bodytrack/ # Production +│ ├── docker-compose.yml +│ └── .env +└── bodytrack-dev/ # Development + ├── docker-compose.dev-env.yml + └── .env +``` + +**Externe Volumes:** +- `bodytrack_bodytrack-data` → `/app/data` (Backend JSON-Daten, Legacy) +- `bodytrack_bodytrack-photos` → `/app/photos` (Progress-Fotos) +- `mitai_postgres_data` → PostgreSQL Datenbank + +--- + +## Komponenten-Übersicht + +### Backend-Module (26 Router) + +| Router | Endpunkte | Beschreibung | +|--------|-----------|--------------| +| **auth** | `/api/auth/*` | Login, Register, Verify, Password Reset | +| **profiles** | `/api/profiles/*`, `/api/profile` | Nutzerverwaltung | +| **weight** | `/api/weight/*` | Gewichts-Tracking | +| **circumference** | `/api/circumferences/*` | Umfangsmessungen (8 Punkte) | +| **caliper** | `/api/caliper/*` | Hautfaltenmessungen (4 Methoden) | +| **activity** | `/api/activity/*` | Training & Aktivitäten | +| **nutrition** | `/api/nutrition/*` | Ernährungsdaten (Kalorien + Makros) | +| **photos** | `/api/photos/*` | Progress-Fotos | +| **insights** | `/api/insights/*`, `/api/ai/*` | KI-Auswertungen | +| **prompts** | `/api/prompts/*` | Konfigurierbare KI-Prompts | +| **admin** | `/api/admin/*` | Admin-Panel (Profile, Permissions) | +| **stats** | `/api/stats` | Statistiken für Dashboard | +| **exportdata** | `/api/export/*` | CSV/JSON/ZIP Export | +| **importdata** | `/api/import/*` | CSV Import | +| **subscription** | `/api/subscription/*` | Abo-Status (v9c) | +| **coupons** | `/api/coupons/*` | Coupon-System (v9c) | +| **features** | `/api/features/*` | Feature-Verwaltung (v9c) | +| **tiers_mgmt** | `/api/tiers/*` | Tier-Verwaltung (v9c) | +| **tier_limits** | `/api/tier-limits/*` | Tier-Limits Matrix (v9c) | +| **user_restrictions** | `/api/user-restrictions/*` | User-spezifische Limits (v9c) | +| **access_grants** | `/api/access-grants/*` | Zeitlich begrenzte Zugriffe (v9c) | +| **training_types** | `/api/training-types/*` | Trainingstypen (v9d) | +| **admin_training_types** | `/api/admin/training-types/*` | Trainingstypen-Admin (v9d) | +| **admin_activity_mappings** | `/api/admin/activity-mappings/*` | Activity Mapping Admin (v9d) | +| **sleep** | `/api/sleep/*` | Schlaf-Modul (v9d Phase 2b) | +| **rest_days** | `/api/rest-days/*` | Ruhetage (v9d Phase 2a) | +| **vitals_baseline** | `/api/vitals/baseline/*` | Morgenmessungen (RHR, HRV, VO2 Max) | +| **blood_pressure** | `/api/blood-pressure/*` | Blutdruck (mehrfach täglich) | +| **evaluation** | `/api/evaluation/*` | Training Type Profiling (v9d Phase 2 #15) | + +**Registrierung in `main.py`:** +```python +app.include_router(auth.router) +app.include_router(profiles.router) +# ... alle 26 Router +``` + +### Frontend-Struktur + +``` +frontend/src/ +├── App.jsx # Root-Komponente + Routing +├── app.css # Globales Design-System +├── context/ +│ ├── AuthContext.jsx # Session-Management +│ └── ProfileContext.jsx # Aktives Profil +├── pages/ # 30+ Seiten +│ ├── LoginScreen.jsx # Login + Auto-Migration SHA256→bcrypt +│ ├── Register.jsx # Selbst-Registrierung (v9c) +│ ├── Verify.jsx # E-Mail-Verifizierung +│ ├── Dashboard.jsx # Übersicht mit Stats + Charts +│ ├── CaptureHub.jsx # Quick-Entry-Auswahl +│ ├── WeightScreen.jsx # Gewichts-Erfassung +│ ├── CircumScreen.jsx # Umfänge +│ ├── CaliperScreen.jsx # Caliper +│ ├── ActivityPage.jsx # Training (mit Trainingstypen v9d) +│ ├── NutritionPage.jsx # Ernährung (3-Tab: Entry/Import/Charts) +│ ├── SleepPage.jsx # Schlaf (v9d Phase 2b) +│ ├── RestDaysPage.jsx # Ruhetage (v9d Phase 2a) +│ ├── VitalsPage.jsx # Vitalwerte (3-Tab: Baseline/BP/Import) +│ ├── History.jsx # Verlauf-Charts +│ ├── Analysis.jsx # KI-Analyse + Pipeline +│ ├── SettingsPage.jsx # Einstellungen +│ ├── SubscriptionPage.jsx # Membership-UI (v9c) +│ └── Admin*.jsx # 9 Admin-Seiten +└── utils/ + ├── api.js # ALLE API-Calls (285 Zeilen) + ├── calc.js # Body-Fat-Berechnungen + ├── interpret.js # Daten-Interpretation + ├── Markdown.jsx # Markdown-Renderer + └── guideData.js # Guide-Inhalte +``` + +--- + +## Datenfluss + +### 1. User Request → Auth → API → Database + +``` +User Action (Frontend) + ↓ +api.js (Token injiziert via hdrs()) + ↓ +FastAPI Endpoint + ↓ +require_auth() Dependency (auth.py) + ↓ (validiert X-Auth-Token → session) + ↓ +get_session() → SELECT FROM sessions JOIN profiles + ↓ (returns session dict mit profile_id, role, ai_enabled, ...) + ↓ +Router-Funktion + ↓ +db.py → get_db() Context Manager + ↓ +psycopg2 → PostgreSQL Query (RealDictCursor) + ↓ +Response (JSON) +``` + +### 2. Feature Access Control (v9c) + +``` +Endpoint (z.B. POST /api/insights/run) + ↓ +check_feature_access(profile_id, 'ai_calls') + ↓ +Prüf-Hierarchie: + 1. user_feature_restrictions (user-spezifisch) + 2. tier_limits (Tier-basiert) + 3. features.default_limit (Fallback) + ↓ +get_effective_tier(profile_id) + → access_grants (Coupon/Trial) ODER profiles.tier + ↓ +user_feature_usage (aktueller Zähler) + ↓ +Ergebnis: {allowed: bool, limit: int, used: int, remaining: int, reason: str} + ↓ +Falls allowed == false → HTTP 403 +Falls allowed == true → increment_feature_usage() + execute +``` + +### 3. KI-Pipeline (3-stufig) + +``` +User: "Analyse starten" + ↓ +POST /api/insights/pipeline + ↓ +check_feature_access('ai_pipeline') + ↓ +Stufe 1: Gewicht-Trend → claude-sonnet-4 (OpenRouter) +Stufe 2: Ernährung → claude-sonnet-4 +Stufe 3: Gesamtanalyse → claude-sonnet-4 (mit Ergebnissen aus 1+2) + ↓ +Jeder Call: increment_feature_usage('ai_calls') + ↓ +INSERT INTO ai_insights (scope='pipeline', content=...) + ↓ +Return: {content: "...", usage: {...}} +``` + +--- + +## Sicherheitsarchitektur + +### 1. Passwort-Sicherheit + +**Hash-Verfahren:** +- **Aktuell:** bcrypt (Salting + Work Factor) +- **Legacy:** SHA256 (wird beim Login automatisch zu bcrypt migriert) + +**Passwort-Hashing (`auth.py`):** +```python +def hash_pin(pin: str) -> str: + return bcrypt.hashpw(pin.encode(), bcrypt.gensalt()).decode() + +def verify_pin(pin: str, stored_hash: str) -> bool: + if stored_hash.startswith('$2'): # bcrypt + return bcrypt.checkpw(pin.encode(), stored_hash.encode()) + # Legacy SHA256 → auto-upgrade beim nächsten Login + return stored_hash == hashlib.sha256(pin.encode()).hexdigest() +``` + +### 2. Session-Management + +**Token-Format:** +- `secrets.token_urlsafe(32)` → 43 Zeichen Base64-URL-safe +- Gespeichert in `sessions` Tabelle mit `expires_at` +- Standard-Lebensdauer: 30 Tage (konfigurierbar pro Profil) + +**Auth-Flow:** +```python +# Backend +@router.post("/api/auth/login") +def login(email, password): + profile = SELECT ... WHERE email=... + verify_pin(password, profile.pin_hash) # ✓ + token = make_token() + INSERT INTO sessions (token, profile_id, expires_at) + return {"token": token, "profile": {...}} + +# Frontend (AuthContext.jsx) +localStorage.setItem('mitai-jinkendo_token', token) +``` + +**Auth-Middleware (`auth.py`):** +```python +def require_auth(x_auth_token: Optional[str] = Header(default=None)): + session = get_session(x_auth_token) + if not session: + raise HTTPException(401, "Nicht eingeloggt") + return session # dict mit profile_id, role, ai_enabled, ... +``` + +### 3. CORS-Konfiguration + +**Produktion (`docker-compose.yml`):** +```yaml +ALLOWED_ORIGINS: https://mitai.jinkendo.de +``` + +**Development:** +```yaml +ALLOWED_ORIGINS: https://dev.mitai.jinkendo.de,http://localhost:3099 +``` + +**FastAPI Setup (`main.py`):** +```python +app.add_middleware( + CORSMiddleware, + allow_origins=os.getenv("ALLOWED_ORIGINS", "*").split(","), + allow_credentials=True, + allow_methods=["GET","POST","PUT","DELETE","OPTIONS"], + allow_headers=["*"], +) +``` + +### 4. Rate Limiting + +**Library:** slowapi (Redis-freie In-Memory Rate Limiting) + +**Anwendung:** +```python +from slowapi import Limiter +from slowapi.util import get_remote_address + +limiter = Limiter(key_func=get_remote_address) +app.state.limiter = limiter + +@router.post("/auth/login") +@limiter.limit("5/minute") # Max 5 Login-Versuche pro Minute +def login(request: Request, ...): + ... +``` + +**Geschützte Endpoints:** +- `/api/auth/login` → 5/minute +- `/api/auth/register` → 3/minute +- KI-Endpoints → via Feature-Limits (nicht Rate Limiting) + +### 5. SQL Injection Protection + +**Parameter-Binding:** +```python +# ✅ Sicher (psycopg2 escaped automatisch): +cur.execute("SELECT * FROM profiles WHERE id = %s", (profile_id,)) + +# ❌ NIEMALS: +cur.execute(f"SELECT * FROM profiles WHERE id = '{profile_id}'") +``` + +**RealDictCursor:** +- Automatisches Escaping +- Keine String-Konkatenation + +--- + +## Versions-Historie + +### v9c (Komplett) – Production seit 21.03.2026 ✅ + +**Features:** +- Membership-System (5 Tiers: free/basic/premium/selfhosted) +- Coupon-System + Trial (14 Tage) +- Feature-Enforcement (4 Phasen): + - Phase 1: Cleanup ✅ + - Phase 2: Monitoring ✅ + - Phase 3: Frontend Display ✅ + - Phase 4: Enforcement (HTTP 403) ✅ +- Selbst-Registrierung + E-Mail-Verifizierung +- Export: CSV/JSON/ZIP +- Ernährungs-Modul erweitert (3-Tab Layout) + +### v9d – Phase 1b ✅ + +**Trainingstypen-System:** +- 29 Trainingstypen in 7 Kategorien +- Lernendes Mapping-System (DB-basiert) +- Apple Health Import (Deutsch + English) +- Bulk-Kategorisierung (selbstlernend) + +### v9d – Phase 2 ✅ (Deployed 23.03.2026) + +**Vitalwerte & Erholung:** +- **Schlaf-Modul (v9d Phase 2b):** + - Tabelle `sleep_log` mit JSONB sleep_segments + - Schlafphasen (Deep, REM, Light, Awake) + - Apple Health CSV Import +- **Ruhetage (v9d Phase 2a):** + - Multi-dimensionale Ruhetage (Kraft/Cardio/Entspannung) + - Quick Mode Presets + Custom Entry +- **Vitalwerte erweitert (v9d Phase 2d):** + - **3-Tab Architektur:** Baseline (morgens) / Blutdruck (mehrfach täglich) / Import + - **Baseline Vitals:** Ruhepuls, HRV, VO2 Max, SpO2, Atemfrequenz + - **Blutdruck:** Systolisch/Diastolisch + Puls, WHO/ISH-Klassifizierung + - **Context-Tagging:** 8 Kontexte (nüchtern, nach Essen, Training, Stress, etc.) + - **Inline-Editing:** Alle Messungen direkt in der Liste bearbeitbar + - CSV Import: Omron (Deutsch) + Apple Health (Deutsch/Englisch) + +### v9b (PostgreSQL-Migration) + +- Migration SQLite → PostgreSQL 16 +- Connection Pooling (psycopg2) +- UUID statt Integer Primary Keys +- Trigger für auto-update timestamps + +### v9a (Basis) + +- Login + Auth-Middleware +- Gewicht, Umfänge, Caliper, Ernährung, Aktivität +- KI-Analyse (6 Prompts + 3-stufige Pipeline) +- PWA + Dashboard +- Admin-Panel + +--- + +## Design-Entscheidungen + +### Warum PostgreSQL statt SQLite? + +**Vorteile:** +- Concurrent Writes (SQLite locked bei Writes) +- Native UUID Support +- JSONB für flexible Datenstrukturen (z.B. sleep_segments) +- Trigger + Stored Procedures +- Production-ready für Self-Hosting + +**Migration:** +- Automatisches Migrations-System (`db_init.py`) +- Rollback-fähig via SQL-Dateien +- Tracking in `schema_migrations` Tabelle + +### Warum FastAPI statt Flask/Django? + +**Vorteile:** +- Async Support (für zukünftige WebSocket-Features) +- Automatic OpenAPI Docs +- Type Hints → automatische Validierung +- Dependency Injection (z.B. `require_auth`) +- Performance (ASGI statt WSGI) + +### Warum React statt Vue/Svelte? + +**Entscheidung:** +- Bekanntes Ecosystem +- Context API ausreichend (kein Redux nötig) +- Gute PWA-Integration +- React Router maturity + +### Warum kein TypeScript? + +**Entscheidung (bewusst):** +- Schnellere Prototyping-Geschwindigkeit +- Weniger Build-Komplexität +- Dokumentation via JSDoc-Kommentare +- Type Safety durch Backend (Pydantic) + +### Warum Connection Pooling? + +**Problem ohne Pooling:** +- Jeder API-Call → neue DB-Verbindung → Overhead +- PostgreSQL max_connections erreicht bei vielen gleichzeitigen Requests + +**Lösung:** +```python +_pool = psycopg2.pool.SimpleConnectionPool(minconn=1, maxconn=10) + +@contextmanager +def get_db(): + conn = _pool.getconn() + try: + yield conn + conn.commit() + except: + conn.rollback() + raise + finally: + _pool.putconn(conn) +``` + +**Vorteil:** +- Max 10 gleichzeitige Verbindungen +- Automatisches Recycling +- Auto-Commit/Rollback + +--- + +## Bekannte Limitationen & Workarounds + +### 1. Docker Build auf Raspberry Pi + +**Problem:** `apt-get install postgresql-client` hängt 30+ Minuten + +**Lösung:** Reine Python-Lösung mit `psycopg2-binary` (keine System-Dependencies) + +### 2. Apple Health CSV-Import + +**Problem:** Dezimalwerte werden als String exportiert (`"65.0"` statt `65`) + +**Lösung:** +```python +def safe_float(val): + try: + return float(val) if val else None + except: + return None +``` + +### 3. Bun Crash nach langen Claude Code Sessions + +**Problem:** Bun (JS Runtime für Claude Code CLI) crashed bei >30 Min Sessions + +**Workaround:** Bei komplexen Tasks früher committen + neue Session starten + +### 4. dayjs.week() ohne Plugin + +**Problem:** `dayjs().week()` existiert nicht ohne `isoWeek`-Plugin + +**Lösung:** Native ISO-Wochenberechnung: +```javascript +const isoWeek = (d => Math.ceil(((new Date(d.setDate(d.getDate()+4-(d.getDay()||7)))- + new Date(d.getFullYear(),0,1))/86400000+1)/7))(new Date(date)) +``` + +--- + +## Performance-Optimierungen + +### 1. Frontend + +- **Code Splitting:** React.lazy() für Admin-Seiten (nicht initial geladen) +- **Image Loading:** `loading="lazy"` für Photos +- **CSS:** Inline Critical CSS, kein CSS-in-JS Overhead +- **PWA Cache:** Service Worker cached statische Assets + +### 2. Backend + +- **Connection Pooling:** Max 10 Connections statt unlimited +- **Query Optimization:** Indizes auf häufig abgefragte Spalten +- **Lazy Loading:** KI-Analysen nur auf Abruf, nicht im Dashboard + +### 3. Database + +**Indizes:** +```sql +CREATE INDEX idx_weight_log_profile_date ON weight_log(profile_id, date DESC); +CREATE INDEX idx_sessions_expires_at ON sessions(expires_at); +CREATE INDEX idx_ai_insights_profile_scope ON ai_insights(profile_id, scope, created DESC); +``` + +**Unique Constraints:** +```sql +CREATE UNIQUE INDEX idx_weight_log_profile_date_unique ON weight_log(profile_id, date); +``` + +--- + +## Monitoring & Logging + +### 1. Feature Usage Logging (v9c Phase 2) + +**Format:** JSON Lines (JSONL) + +**Speicherort:** `/app/logs/feature-usage.log` + +**Beispiel:** +```json +{"timestamp":"2026-03-23T10:15:30Z","profile_id":"uuid-here","feature":"ai_calls","allowed":true,"limit":10,"used":3,"remaining":7} +{"timestamp":"2026-03-23T10:16:00Z","profile_id":"uuid-here","feature":"data_export","allowed":false,"limit":0,"used":0,"remaining":0,"reason":"limit_exceeded"} +``` + +**Auswertung:** +```bash +cat feature-usage.log | jq -s 'group_by(.feature) | map({feature: .[0].feature, total: length})' +``` + +### 2. Health Checks + +**Database:** +```yaml +healthcheck: + test: ["CMD-SHELL", "pg_isready -U mitai_prod"] + interval: 10s +``` + +**API:** +```python +@app.get("/") +def root(): + return {"status": "ok", "service": "mitai-jinkendo", "version": "v9c-dev"} +``` + +### 3. Error Handling + +**Einheitliches Format:** +```python +raise HTTPException(status_code=404, detail="Eintrag nicht gefunden") +# → {"detail": "Eintrag nicht gefunden"} +``` + +**Frontend (`api.js`):** +```javascript +if (!res.ok) { + const err = await res.text() + try { + const parsed = JSON.parse(err) + throw new Error(parsed.detail || err) + } catch { + throw new Error(err) + } +} +``` + +--- + +## Zusammenfassung + +**Architektur-Highlights:** +- ✅ Modulare Router-Struktur (26 Module, single responsibility) +- ✅ Connection Pooling (max 10 concurrent DB connections) +- ✅ Token-basiertes Auth + bcrypt +- ✅ Feature Access Control mit Tier-System +- ✅ Docker Compose für einfaches Deployment +- ✅ Auto-Deploy via Gitea Webhooks +- ✅ PostgreSQL mit Migrations-System +- ✅ PWA mit Service Worker +- ✅ API-First Prinzip (Backend = Single Source of Truth) + +**Nächste Schritte (v9e+):** +- Ziele-Modul (Goal Tracking) +- HF-Zonen + Erholungsstatus +- Stripe-Integration +- Connectoren (Garmin, Withings, etc.) +- Meditation + Selbstwahrnehmung diff --git a/.claude/docs/technical/AUTH.md b/.claude/docs/technical/AUTH.md new file mode 100644 index 0000000..94c4417 --- /dev/null +++ b/.claude/docs/technical/AUTH.md @@ -0,0 +1,904 @@ +# Auth-Flow & Sicherheit + +## Übersicht + +Mitai Jinkendo nutzt **Token-basiertes Session-Management** mit bcrypt-Passwort-Hashing. Die Authentifizierung ist als FastAPI Dependency implementiert (`require_auth()`), die automatisch auf alle geschützten Endpoints angewendet wird. + +**Sicherheits-Features:** +- ✅ bcrypt-Hashing (Work Factor ~12 Rounds) +- ✅ Auto-Migration SHA256 → bcrypt +- ✅ Rate Limiting (slowapi) +- ✅ CORS-Konfiguration +- ✅ E-Mail-Verifizierung für Registrierung +- ✅ Passwort-Reset via E-Mail (1h Token) +- ✅ Session-Expiry (30 Tage Standard) +- ✅ Admin-Role-Check + +--- + +## Login-Flow (Schritt für Schritt) + +### 1. User-Eingabe (Frontend) + +**LoginScreen.jsx:** +```javascript +const handleLogin = async () => { + const data = await login({ email: email.trim().toLowerCase(), password }) + window.location.href = '/' // Hard-Redirect nach Login +} +``` + +### 2. POST /api/auth/login (Backend) + +**Request:** +```json +{ + "email": "user@example.com", + "password": "geheim123" +} +``` + +**Backend-Logik (`backend/routers/auth.py`):** +```python +@router.post("/login") +@limiter.limit("5/minute") # Rate Limiting +async def login(req: LoginRequest, request: Request): + # 1. E-Mail-Lookup + cur.execute("SELECT * FROM profiles WHERE email=%s", (req.email.lower().strip(),)) + prof = cur.fetchone() + if not prof: + raise HTTPException(401, "Ungültige Zugangsdaten") + + # 2. Passwort-Verifizierung + if not verify_pin(req.password, prof['pin_hash']): + raise HTTPException(401, "Ungültige Zugangsdaten") + + # 3. Auto-Migration SHA256 → bcrypt + if not prof['pin_hash'].startswith('$2'): # bcrypt-Hash startet mit $2b$ oder $2a$ + new_hash = hash_pin(req.password) + cur.execute("UPDATE profiles SET pin_hash=%s WHERE id=%s", (new_hash, prof['id'])) + + # 4. Session erstellen + token = make_token() # secrets.token_urlsafe(32) → 43 Zeichen + session_days = prof.get('session_days', 30) + expires = datetime.now() + timedelta(days=session_days) + cur.execute(""" + INSERT INTO sessions (token, profile_id, expires_at, created) + VALUES (%s, %s, %s, CURRENT_TIMESTAMP) + """, (token, prof['id'], expires)) + + return { + "token": token, + "profile_id": prof['id'], + "name": prof['name'], + "role": prof['role'], + "expires_at": expires.isoformat() + } +``` + +**Response:** +```json +{ + "token": "jT9z3xK...(43 chars)...pQ2vL", + "profile_id": "550e8400-e29b-41d4-a716-446655440000", + "name": "Max Mustermann", + "role": "user", + "expires_at": "2026-04-22T14:30:00" +} +``` + +### 3. Token-Speicherung (Frontend) + +**AuthContext.jsx:** +```javascript +const login = async (credentials) => { + const data = await api.login(credentials) + + // Token speichern + localStorage.setItem('bodytrack_token', data.token) + localStorage.setItem('bodytrack_active_profile', data.profile_id) + + // Volles Profil laden + const profile = await fetch('/api/auth/me', { + headers: { 'X-Auth-Token': data.token } + }) + + setSession({ + token: data.token, + profile_id: data.profile_id, + role: data.role, + profile: await profile.json() + }) +} +``` + +### 4. Nachfolgende Requests + +**api.js:** +```javascript +function hdrs(extra={}) { + const h = {...extra} + const token = getToken() // localStorage.getItem('bodytrack_token') + if (token) h['X-Auth-Token'] = token + return h +} + +async function req(path, opts={}) { + const res = await fetch(BASE+path, {...opts, headers:hdrs(opts.headers||{})}) + // ... +} +``` + +**Jeder API-Call sendet automatisch:** +``` +GET /api/weight +Headers: + X-Auth-Token: jT9z3xK...pQ2vL +``` + +### 5. Auth-Validierung (Backend) + +**require_auth() Dependency:** +```python +def require_auth(x_auth_token: Optional[str] = Header(default=None)): + """FastAPI Dependency - requires valid authentication.""" + session = get_session(x_auth_token) + if not session: + raise HTTPException(401, "Nicht eingeloggt") + return session + +def get_session(token: str): + """Get session data for a given token.""" + if not token: + return None + with get_db() as conn: + cur = get_cursor(conn) + cur.execute(""" + SELECT s.*, p.role, p.name, p.ai_enabled, p.ai_limit_day, p.export_enabled + FROM sessions s + JOIN profiles p ON s.profile_id=p.id + WHERE s.token=%s AND s.expires_at > CURRENT_TIMESTAMP + """, (token,)) + return cur.fetchone() +``` + +**Endpoint-Beispiel:** +```python +@router.get("/api/weight") +def list_weight(session: dict = Depends(require_auth)): + profile_id = session['profile_id'] # Immer aus Session, nie aus Header! + # ... Query mit profile_id +``` + +**Session-Dict:** +```python +{ + 'profile_id': 'uuid-string', + 'role': 'user' | 'admin', + 'name': 'Max Mustermann', + 'ai_enabled': True, + 'ai_limit_day': 10, + 'export_enabled': True, + 'expires_at': datetime(2026, 4, 22, 14, 30, 0) +} +``` + +--- + +## Registrierung-Flow (v9c) + +### 1. Selbst-Registrierung + +**POST /api/auth/register:** +```json +{ + "name": "Max Mustermann", + "email": "max@example.com", + "password": "sicheresPasswort123" +} +``` + +**Validierung:** +- E-Mail: Muss `@` enthalten +- Passwort: Mindestens 8 Zeichen +- Name: Mindestens 2 Zeichen +- E-Mail-Duplikat-Check + +**Backend:** +```python +@router.post("/register") +@limiter.limit("3/hour") # Rate Limiting +async def register(req: RegisterRequest, request: Request): + email = req.email.lower().strip() + + # Duplikat-Check + cur.execute("SELECT id FROM profiles WHERE email=%s", (email,)) + if cur.fetchone(): + raise HTTPException(400, "E-Mail-Adresse bereits registriert") + + # Profil erstellen (inaktiv bis verifiziert) + profile_id = str(secrets.token_hex(16)) + pin_hash = hash_pin(req.password) + verification_token = secrets.token_urlsafe(32) + verification_expires = datetime.now() + timedelta(hours=24) + trial_ends = datetime.now() + timedelta(days=14) # 14-Tage-Trial + + cur.execute(""" + INSERT INTO profiles ( + id, name, email, pin_hash, auth_type, role, tier, + email_verified, verification_token, verification_expires, + trial_ends_at, created + ) VALUES (%s, %s, %s, %s, 'email', 'user', 'free', FALSE, %s, %s, %s, CURRENT_TIMESTAMP) + """, (profile_id, req.name, email, pin_hash, verification_token, verification_expires, trial_ends)) + + # Verifizierungs-E-Mail senden + send_email(email, "Willkommen bei Mitai Jinkendo", f"Verify: {APP_URL}/verify?token={verification_token}") +``` + +**Response:** +```json +{ + "ok": true, + "message": "Registrierung erfolgreich! Bitte prüfe dein E-Mail-Postfach." +} +``` + +### 2. E-Mail-Verifizierung + +**User klickt Link in E-Mail:** +``` +https://mitai.jinkendo.de/verify?token=jT9z3xK...pQ2vL +``` + +**GET /api/auth/verify/{token}:** +```python +@router.get("/verify/{token}") +async def verify_email(token: str): + # Token-Lookup + cur.execute(""" + SELECT id, name, email, email_verified, verification_expires + FROM profiles + WHERE verification_token=%s + """, (token,)) + prof = cur.fetchone() + + if not prof: + raise HTTPException(400, "Verifikations-Link ungültig") + if prof['email_verified']: + raise HTTPException(400, "E-Mail bereits bestätigt") + if datetime.now() > prof['verification_expires']: + raise HTTPException(400, "Link abgelaufen") + + # Verifizierung + cur.execute(""" + UPDATE profiles + SET email_verified=TRUE, verification_token=NULL, verification_expires=NULL + WHERE id=%s + """, (prof['id'],)) + + # Auto-Login (Session erstellen) + session_token = make_token() + expires = datetime.now() + timedelta(days=30) + cur.execute(""" + INSERT INTO sessions (token, profile_id, expires_at, created) + VALUES (%s, %s, %s, CURRENT_TIMESTAMP) + """, (session_token, prof['id'], expires)) + + return { + "ok": True, + "token": session_token, + "profile": {"id": prof['id'], "name": prof['name'], "email": prof['email']} + } +``` + +**Frontend (Verify.jsx):** +```javascript +const verifyToken = urlParams.get('token') +const data = await api.verifyEmail(verifyToken) + +// Auto-Login nach Verifizierung +setAuthFromToken(data.token, data.profile) +window.location.href = '/' +``` + +### 3. Resend Verification + +**POST /api/auth/resend-verification:** +```json +{ + "email": "max@example.com" +} +``` + +**Backend:** +- Generiert neuen Token (24h Gültigkeit) +- Sendet erneut E-Mail +- Rate Limit: 3/hour + +--- + +## Passwort-Reset-Flow + +### 1. Passwort vergessen + +**POST /api/auth/forgot-password:** +```json +{ + "email": "max@example.com" +} +``` + +**Backend:** +```python +@router.post("/forgot-password") +@limiter.limit("3/minute") +async def password_reset_request(req: PasswordResetRequest, request: Request): + email = req.email.lower().strip() + + cur.execute("SELECT id, name FROM profiles WHERE email=%s", (email,)) + prof = cur.fetchone() + + if not prof: + # Don't reveal if email exists (Anti-Enumeration) + return {"ok": True, "message": "Falls die E-Mail existiert, wurde ein Link gesendet."} + + # Reset-Token erstellen + token = secrets.token_urlsafe(32) + expires = datetime.now() + timedelta(hours=1) # 1 Stunde gültig + + # In sessions-Tabelle speichern (mit Präfix "reset_") + cur.execute(""" + INSERT INTO sessions (token, profile_id, expires_at, created) + VALUES (%s, %s, %s, CURRENT_TIMESTAMP) + """, (f"reset_{token}", prof['id'], expires)) + + # E-Mail senden + send_email(prof['email'], "Passwort zurücksetzen", f"Reset: {APP_URL}/reset-password?token={token}") +``` + +### 2. Neues Passwort setzen + +**User klickt Link in E-Mail:** +``` +https://mitai.jinkendo.de/reset-password?token=jT9z3xK...pQ2vL +``` + +**POST /api/auth/reset-password:** +```json +{ + "token": "jT9z3xK...pQ2vL", + "new_password": "neuesPasswort123" +} +``` + +**Backend:** +```python +@router.post("/reset-password") +def password_reset_confirm(req: PasswordResetConfirm): + cur.execute(""" + SELECT profile_id FROM sessions + WHERE token=%s AND expires_at > CURRENT_TIMESTAMP + """, (f"reset_{req.token}",)) + sess = cur.fetchone() + + if not sess: + raise HTTPException(400, "Ungültiger oder abgelaufener Reset-Link") + + # Passwort ändern + new_hash = hash_pin(req.new_password) + cur.execute("UPDATE profiles SET pin_hash=%s WHERE id=%s", (new_hash, sess['profile_id'])) + + # Reset-Token löschen + cur.execute("DELETE FROM sessions WHERE token=%s", (f"reset_{req.token}",)) + + return {"ok": True, "message": "Passwort erfolgreich zurückgesetzt"} +``` + +--- + +## Session-Management + +### Session-Struktur (Datenbank) + +**Tabelle: `sessions`** +```sql +CREATE TABLE sessions ( + token VARCHAR(64) PRIMARY KEY, -- secrets.token_urlsafe(32) + profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + expires_at TIMESTAMP WITH TIME ZONE NOT NULL, + created TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_sessions_profile_id ON sessions(profile_id); +CREATE INDEX idx_sessions_expires_at ON sessions(expires_at); +``` + +### Token-Format + +**`secrets.token_urlsafe(32)`** +- 32 Bytes Zufallsdaten +- Base64-URL-safe-kodiert +- Resultierende Länge: 43 Zeichen +- Charset: `A-Za-z0-9_-` + +**Beispiel:** `jT9z3xKpLmN8vQrStUwXyZ1aB2cD3eF4gH5iJ6kL7mN8oP9qR` + +### Session-Lebensdauer + +**Standard:** 30 Tage (konfigurierbar pro Profil via `profiles.session_days`) + +**Nach Login:** +```python +session_days = prof.get('session_days', 30) +expires = datetime.now() + timedelta(days=session_days) +``` + +**Automatische Bereinigung:** +- Abgelaufene Sessions werden bei Login automatisch gelöscht (via `WHERE expires_at > CURRENT_TIMESTAMP`) +- Geplant: Cron-Job für Cleanup alter Sessions (v10+) + +### Logout + +**POST /api/auth/logout:** +```python +@router.post("/logout") +def logout(x_auth_token: Optional[str]=Header(default=None)): + if x_auth_token: + with get_db() as conn: + cur = get_cursor(conn) + cur.execute("DELETE FROM sessions WHERE token=%s", (x_auth_token,)) + return {"ok": True} +``` + +**Frontend:** +```javascript +const logout = async () => { + await fetch('/api/auth/logout', { + method: 'POST', + headers: { 'X-Auth-Token': token } + }) + localStorage.removeItem('bodytrack_token') + setSession(null) +} +``` + +--- + +## Passwort-Sicherheit + +### 1. bcrypt-Hashing + +**Verwendete Bibliothek:** `bcrypt` (Python) + +**Konfiguration:** +- Work Factor: Default (~12 Rounds) +- Automatisches Salting (integriert in bcrypt) + +**Hash-Format:** +``` +$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz3/yDZL1AJ.zCBU8fKWChSrW8GZF1a +│ │ │ │ │ +│ │ │ └─ Salt (22 Zeichen) └─ Hash (31 Zeichen) +│ │ └─ Cost-Faktor (12 = 2^12 = 4096 Iterationen) +│ └─ bcrypt-Variante ('a' oder 'b') +└─ Präfix +``` + +**Hashing-Funktion (`auth.py`):** +```python +import bcrypt + +def hash_pin(pin: str) -> str: + """Hash password with bcrypt.""" + return bcrypt.hashpw(pin.encode(), bcrypt.gensalt()).decode() +``` + +**Verifizierung:** +```python +def verify_pin(pin: str, stored_hash: str) -> bool: + """Verify password - supports both bcrypt and legacy SHA256.""" + if not stored_hash: + return False + + # Detect bcrypt hash (starts with $2b$ or $2a$) + if stored_hash.startswith('$2'): + try: + return bcrypt.checkpw(pin.encode(), stored_hash.encode()) + except Exception: + return False + + # Legacy SHA256 support (auto-upgrade to bcrypt on next login) + return stored_hash == hashlib.sha256(pin.encode()).hexdigest() +``` + +### 2. SHA256 → bcrypt Auto-Migration + +**Problem:** Alte Accounts hatten SHA256-Hashes (unsicher, kein Salting) + +**Lösung:** Automatische Migration beim Login + +**Logik:** +```python +# Beim Login +if prof['pin_hash'] and not prof['pin_hash'].startswith('$2'): + # Passwort verifiziert erfolgreich (via SHA256) + # → Upgrade zu bcrypt + new_hash = hash_pin(req.password) + cur.execute("UPDATE profiles SET pin_hash=%s WHERE id=%s", (new_hash, prof['id'])) +``` + +**Ablauf:** +1. User loggt sich mit altem Passwort ein +2. Backend verifiziert gegen SHA256-Hash (erfolgreich) +3. Backend erstellt bcrypt-Hash vom Klartext-Passwort +4. SHA256-Hash wird in DB durch bcrypt-Hash ersetzt +5. Nächster Login → bcrypt-Verifizierung + +**Vorteil:** Keine Passwort-Resets nötig, Migration transparent + +### 3. Passwort-Anforderungen + +**Minimum:** +- Länge: 8 Zeichen (bei Registrierung) +- Länge: 4 Zeichen (bei PIN-Change, für Abwärtskompatibilität) + +**Empfohlen (nicht erzwungen):** +- Groß- und Kleinbuchstaben +- Zahlen +- Sonderzeichen + +**Keine Komplexitäts-Prüfung:** Bewusst nicht implementiert (Fokus auf Länge statt Komplexität) + +--- + +## Rate Limiting + +**Bibliothek:** slowapi (Redis-freie In-Memory Rate Limiting) + +### Konfiguration + +**main.py:** +```python +from slowapi import Limiter, _rate_limit_exceeded_handler +from slowapi.util import get_remote_address +from slowapi.errors import RateLimitExceeded + +limiter = Limiter(key_func=get_remote_address) +app.state.limiter = limiter +app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler) +``` + +### Rate Limits + +| Endpoint | Limit | Grund | +|----------|-------|-------| +| `/api/auth/login` | 5/minute | Brute-Force-Schutz | +| `/api/auth/register` | 3/hour | Spam-Prevention | +| `/api/auth/forgot-password` | 3/minute | E-Mail-Flooding-Schutz | +| `/api/auth/resend-verification` | 3/hour | E-Mail-Flooding-Schutz | + +**Verwendung:** +```python +from slowapi import Limiter + +@router.post("/login") +@limiter.limit("5/minute") +async def login(req: LoginRequest, request: Request): + # Request-Objekt muss übergeben werden für IP-Extraktion + pass +``` + +**Response bei Überschreitung:** +``` +HTTP 429 Too Many Requests +{ + "detail": "Rate limit exceeded: 5 per 1 minute" +} +``` + +**Key-Funktion:** `get_remote_address` → IP-basiert (nicht User-basiert) + +**Hinweis:** In-Memory = Reset bei Server-Neustart + +--- + +## CORS-Konfiguration + +### Produktion + +**docker-compose.yml:** +```yaml +ALLOWED_ORIGINS: https://mitai.jinkendo.de +``` + +**main.py:** +```python +app.add_middleware( + CORSMiddleware, + allow_origins=os.getenv("ALLOWED_ORIGINS", "*").split(","), + allow_credentials=True, + allow_methods=["GET","POST","PUT","DELETE","OPTIONS"], + allow_headers=["*"], +) +``` + +### Development + +**docker-compose.dev-env.yml:** +```yaml +ALLOWED_ORIGINS: https://dev.mitai.jinkendo.de,http://localhost:3099 +``` + +**Wichtig:** Keine Wildcards (`*`) in Produktion! + +--- + +## Öffentliche vs. geschützte Endpoints + +### Öffentliche Endpoints (kein Auth) + +| Endpoint | Methode | Zweck | +|----------|---------|-------| +| `/` | GET | Health Check | +| `/api/auth/status` | GET | Status-Check (First-Run-Detection) | +| `/api/auth/login` | POST | Login | +| `/api/auth/register` | POST | Registrierung | +| `/api/auth/verify/{token}` | GET | E-Mail-Verifizierung | +| `/api/auth/forgot-password` | POST | Passwort-Reset anfordern | +| `/api/auth/reset-password` | POST | Passwort-Reset bestätigen | +| `/api/auth/resend-verification` | POST | Verifizierungs-E-Mail erneut senden | + +### Geschützte Endpoints (require_auth) + +**Alle anderen Endpoints** benötigen `X-Auth-Token` Header. + +**Beispiel:** +```python +@router.get("/api/weight") +def list_weight(session: dict = Depends(require_auth)): + profile_id = session['profile_id'] + # ... +``` + +### Admin-Only Endpoints (require_admin) + +| Endpoint | Zweck | +|----------|-------| +| `/api/admin/*` | Admin-Panel | +| `/api/features` | Feature-Verwaltung (POST/PUT/DELETE) | +| `/api/tiers` | Tier-Verwaltung (POST/PUT/DELETE) | +| `/api/tier-limits` | Tier-Limits-Matrix (PUT) | +| `/api/coupons` | Coupon-Verwaltung (POST/PUT/DELETE) | +| `/api/user-restrictions` | User-Restrictions (POST/PUT/DELETE) | +| `/api/access-grants` | Access-Grants (POST/PUT/DELETE) | +| `/api/admin/training-types` | Trainingstypen-Admin (POST/PUT/DELETE) | +| `/api/admin/activity-mappings` | Activity-Mappings (POST/PUT/DELETE) | + +**require_admin() Dependency:** +```python +def require_admin(x_auth_token: Optional[str] = Header(default=None)): + """FastAPI dependency - requires admin authentication.""" + session = get_session(x_auth_token) + if not session: + raise HTTPException(401, "Nicht eingeloggt") + if session['role'] != 'admin': + raise HTTPException(403, "Nur für Admins") + return session +``` + +--- + +## E-Mail-System + +### SMTP-Konfiguration + +**Umgebungsvariablen:** +```env +SMTP_HOST=smtp.strato.de +SMTP_PORT=587 +SMTP_USER=noreply@jinkendo.de +SMTP_PASS=***** +SMTP_FROM=noreply@jinkendo.de +APP_URL=https://mitai.jinkendo.de +``` + +### E-Mail-Versand + +**Helper-Funktion (`backend/routers/auth.py`):** +```python +def send_email(to_email: str, subject: str, body: str): + """Send email via SMTP (reusable helper).""" + try: + smtp_host = os.getenv("SMTP_HOST") + smtp_port = int(os.getenv("SMTP_PORT", 587)) + smtp_user = os.getenv("SMTP_USER") + smtp_pass = os.getenv("SMTP_PASS") + smtp_from = os.getenv("SMTP_FROM", "noreply@jinkendo.de") + + if not smtp_host or not smtp_user or not smtp_pass: + print("SMTP not configured, skipping email") + return False + + msg = MIMEText(body) + msg['Subject'] = subject + msg['From'] = smtp_from + msg['To'] = to_email + + with smtplib.SMTP(smtp_host, smtp_port) as server: + server.starttls() + server.login(smtp_user, smtp_pass) + server.send_message(msg) + + return True + except Exception as e: + print(f"Email error: {e}") + return False +``` + +### E-Mail-Templates + +**Registrierung (Verifizierung):** +``` +Betreff: Willkommen bei Mitai Jinkendo – E-Mail bestätigen + +Hallo {name}, + +willkommen bei Mitai Jinkendo! + +Bitte bestätige deine E-Mail-Adresse um die Registrierung abzuschließen: + +https://mitai.jinkendo.de/verify?token={verification_token} + +Der Link ist 24 Stunden gültig. + +Dein Mitai Jinkendo Team +``` + +**Passwort-Reset:** +``` +Betreff: Passwort zurücksetzen – Mitai Jinkendo + +Hallo {name}, + +du hast einen Passwort-Reset angefordert. + +Reset-Link: https://mitai.jinkendo.de/reset-password?token={token} + +Der Link ist 1 Stunde gültig. + +Falls du diese Anfrage nicht gestellt hast, ignoriere diese E-Mail. + +Dein Mitai Jinkendo Team +``` + +--- + +## Bekannte Sicherheitsentscheidungen + +### 1. Profile-ID aus Session, nie aus Header + +**❌ Falsch (Sicherheitslücke):** +```python +@router.get("/weight") +def list_weight(x_profile_id: str = Header(default=None), session=Depends(require_auth)): + profile_id = x_profile_id # User könnte beliebige ID senden! +``` + +**✅ Richtig:** +```python +@router.get("/weight") +def list_weight(session: dict = Depends(require_auth)): + profile_id = session['profile_id'] # Immer aus validierter Session +``` + +**Grund:** Session ist an Token gebunden → User kann nur eigene Daten abrufen + +### 2. E-Mail-Enumeration-Schutz + +**Problem:** Registrierung könnte verraten ob E-Mail bereits existiert + +**Lösung:** +```python +if cur.fetchone(): + raise HTTPException(400, "E-Mail-Adresse bereits registriert") +``` + +**Passwort-Reset:** +```python +if not prof: + # Don't reveal if email exists + return {"ok": True, "message": "Falls die E-Mail existiert, wurde ein Link gesendet."} +``` + +**Trade-off:** Registrierung gibt Info preis (UX > Sicherheit), Reset nicht (Sicherheit > UX) + +### 3. Reset-Token-Präfix + +**Problem:** Reset-Token könnte mit regulären Session-Token kollidieren + +**Lösung:** +```python +cur.execute("INSERT INTO sessions (token, ...) VALUES (%s, ...)", (f"reset_{token}", ...)) +``` + +**Vorteil:** Eindeutige Identifikation, kein Risiko von Kollisionen + +### 4. Keine Passwort-Komplexitäts-Prüfung + +**Entscheidung:** Nur Mindestlänge (8 Zeichen), keine Sonderzeichen-Pflicht + +**Grund:** +- Länge > Komplexität (NIST-Empfehlung) +- Komplexitäts-Anforderungen führen zu schlechteren Passwörtern (z.B. `Password123!`) +- Benutzerfreundlichkeit + +**Alternative:** Passwort-Strength-Meter im Frontend (geplant) + +### 5. In-Memory Rate Limiting + +**Problem:** Rate Limits resetten bei Server-Neustart + +**Akzeptiert weil:** +- Redis-Overhead für Self-Hosting zu hoch +- Server-Neustarts selten (<1x pro Woche) +- Bei Neustart = Attack-Vektoren resetten sich auch (DDoS-Protection) + +**Geplant (v10+):** Redis-Integration optional + +--- + +## Zusammenfassung: Auth-Flow + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 1. Login (POST /api/auth/login) │ +│ ↓ E-Mail + Passwort │ +│ ↓ verify_pin(password, pin_hash) │ +│ ↓ SHA256 → bcrypt Migration (falls nötig) │ +│ ↓ Token generieren (secrets.token_urlsafe(32)) │ +│ ↓ INSERT INTO sessions (token, profile_id, expires_at) │ +│ → Return {token, profile_id, role, expires_at} │ +└─────────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────────┐ +│ 2. Frontend speichert Token │ +│ localStorage.setItem('bodytrack_token', token) │ +└─────────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────────┐ +│ 3. Nachfolgende Requests │ +│ GET /api/weight │ +│ Headers: X-Auth-Token: {token} │ +└─────────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────────┐ +│ 4. Backend validiert Token (require_auth) │ +│ ↓ SELECT FROM sessions WHERE token=... AND expires_at>NOW│ +│ ↓ JOIN profiles ON profile_id │ +│ → Return session dict {profile_id, role, ai_enabled, ...}│ +└─────────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────────┐ +│ 5. Endpoint-Logik │ +│ profile_id = session['profile_id'] │ +│ data = SELECT FROM weight_log WHERE profile_id=... │ +│ → Return data │ +└─────────────────────────────────────────────────────────────┘ +``` + +**Sicherheits-Highlights:** +- ✅ bcrypt mit Auto-Salting +- ✅ SHA256 → bcrypt Migration (transparent) +- ✅ Rate Limiting (5/min Login, 3/hour Register) +- ✅ E-Mail-Verifizierung (24h Token) +- ✅ Passwort-Reset (1h Token via E-Mail) +- ✅ Session-Expiry (30 Tage) +- ✅ Admin-Role-Check (require_admin) +- ✅ Profile-ID-Isolation (immer aus Session) +- ✅ CORS-Whitelisting (Production) + +**Bekannte Limitationen:** +- In-Memory Rate Limiting (Reset bei Server-Neustart) +- Keine 2FA (geplant für v10+) +- Keine Passwort-Strength-Meter (geplant) +- Keine Session-Revocation-UI (nur via DB) diff --git a/.claude/docs/technical/CENTRAL_SUBSCRIPTION_SYSTEM.md b/.claude/docs/technical/CENTRAL_SUBSCRIPTION_SYSTEM.md new file mode 100644 index 0000000..95a55e7 --- /dev/null +++ b/.claude/docs/technical/CENTRAL_SUBSCRIPTION_SYSTEM.md @@ -0,0 +1,205 @@ +# Zentrales Abo-System (Zukunft) + +## Vision + +**Ein zentrales Abo-System für alle Jinkendo Apps:** +- mitai.jinkendo.de (Körper-Tracking) 身体 +- miken.jinkendo.de (Meditation) 眉間 +- ikigai.jinkendo.de (Lebenssinn) 生き甲斐 +- shinkan.jinkendo.de (Kampfsport) 真観 + +## Konzept + +### Zentrale Webseite: jinkendo.de +- Zentrale Landing-Page mit allen Apps +- **Zentrale Abo-Verwaltung** (Stripe-Integration) +- User-Account übergreifend für alle Apps +- Single Sign-On (SSO) zwischen Apps + +### Abo-Modelle (Ideen) + +#### Option 1: App-spezifische Abos +``` +mitai Basic: €5/Monat → Nur Mitai Premium +miken Basic: €5/Monat → Nur Miken Premium +``` + +#### Option 2: Kombinierte Abos +``` +Jinkendo Basic: €8/Monat → 2 Apps +Jinkendo Premium: €12/Monat → Alle 4 Apps +Jinkendo Family: €20/Monat → Alle Apps + 3 Profile +``` + +#### Option 3: Feature-basiert +``` +Free: Basis-Features alle Apps +Basic: Erweiterte Features (KI, Export, etc.) +Premium: Unlimited + Priority Support +``` + +--- + +## Technische Umsetzung + +### Backend + +#### Zentrale Auth-API +``` +auth.jinkendo.de + POST /register → User-Account erstellen + POST /login → JWT Token für alle Apps + POST /refresh → Token erneuern + GET /me → User-Info mit Abo-Status +``` + +#### Subscription-API +``` +subscriptions.jinkendo.de + GET /plans → Verfügbare Abos + POST /subscribe → Stripe Checkout Session + GET /my-subscription → Aktuelles Abo + Features + POST /cancel → Abo kündigen + POST /webhook → Stripe Webhook +``` + +#### App-Integration +Jede App prüft beim Start: +```javascript +const subscription = await fetch('https://subscriptions.jinkendo.de/my-subscription', { + headers: { 'Authorization': `Bearer ${jwt_token}` } +}) + +// subscription.features: ['mitai_premium', 'miken_basic', ...] +// App aktiviert entsprechende Features +``` + +### Frontend + +#### Zentrale Webseite (jinkendo.de) +- Next.js oder React + Vite +- Stripe Elements für Payment +- Dashboard: Übersicht alle Apps + Abo-Status +- Rechnung-Historie + +#### App-Anpassungen +**TrialBanner:** +```jsx + + Jetzt upgraden + +``` + +**Settings → Abo:** +- Link zu `https://jinkendo.de/account/subscription` +- Oder Embedded iFrame + +--- + +## Datenbank-Schema (zentral) + +### users +```sql +CREATE TABLE users ( + id UUID PRIMARY KEY, + email VARCHAR(255) UNIQUE NOT NULL, + password_hash VARCHAR(255) NOT NULL, + name VARCHAR(100), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); +``` + +### subscriptions +```sql +CREATE TABLE subscriptions ( + id SERIAL PRIMARY KEY, + user_id UUID REFERENCES users(id), + stripe_customer_id VARCHAR(100), + stripe_subscription_id VARCHAR(100), + plan VARCHAR(50), -- 'basic', 'premium', 'family' + status VARCHAR(20), -- 'active', 'canceled', 'past_due' + current_period_end TIMESTAMP, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + canceled_at TIMESTAMP +); +``` + +### subscription_features +```sql +CREATE TABLE subscription_features ( + subscription_id INT REFERENCES subscriptions(id), + app VARCHAR(50), -- 'mitai', 'miken', 'ikigai', 'shinkan' + tier VARCHAR(50), -- 'basic', 'premium' + PRIMARY KEY (subscription_id, app) +); +``` + +### app_access_tokens +```sql +-- Mapping: Zentrale User → App-spezifische Profile +CREATE TABLE app_access_tokens ( + user_id UUID REFERENCES users(id), + app VARCHAR(50), + app_profile_id VARCHAR(100), -- ID in der jeweiligen App-DB + PRIMARY KEY (user_id, app) +); +``` + +--- + +## Migration: Bestehende Apps + +### Schritt 1: Zentrale Auth aufbauen +1. `auth.jinkendo.de` API deployen +2. User aus `mitai` DB migrieren zu zentraler DB +3. Mapping erstellen: zentrale User ID → mitai Profile ID + +### Schritt 2: Apps auf zentrale Auth umstellen +1. Login/Register in Apps deaktivieren +2. "Mit Jinkendo anmelden" Button → SSO-Flow +3. JWT von `auth.jinkendo.de` verwenden +4. Profile-ID Mapping bei jedem Request + +### Schritt 3: Subscription-System +1. `subscriptions.jinkendo.de` API deployen +2. Stripe-Integration +3. Apps prüfen Abo-Status bei jedem Feature-Zugriff + +### Schritt 4: Zentrale Webseite +1. `jinkendo.de` Landing Page +2. Account-Dashboard +3. Abo-Verwaltung + +--- + +## Status (März 2026) + +🔲 **Noch nicht gestartet** + +**Aktuell:** +- Jede App hat eigene User-Verwaltung +- `mitai` hat Membership-System (v9c) +- TrialBanner Link → `mailto:mitai@jinkendo.de` + +**Nächste Schritte:** +1. Weitere Apps entwickeln (miken, ikigai, shinkan) +2. Zentrale Infrastruktur planen +3. Migration vorbereiten + +--- + +## Offene Fragen + +- **Pricing:** Welche Preise pro App / kombiniert? +- **Stripe vs. Paddle:** Welcher Payment Provider? +- **Single DB vs. Separate:** Eine PostgreSQL-DB für alles oder separate? +- **Hosting:** Eigener Server oder Cloud (Vercel, Railway, Fly.io)? +- **Domain-Strategie:** Subdomains (api.jinkendo.de) oder Paths (jinkendo.de/api)? + +--- + +## Related + +- `MEMBERSHIP_SYSTEM.md` - Aktuelles System in mitai (v9c) +- `FEATURE_ENFORCEMENT.md` - Feature-Limiting Mechanismus +- Backlog: v9h (Connectoren & Stripe) diff --git a/.claude/docs/technical/DASHBOARD_WIDGETS_AGENT_GUIDE.md b/.claude/docs/technical/DASHBOARD_WIDGETS_AGENT_GUIDE.md new file mode 100644 index 0000000..7fafcac --- /dev/null +++ b/.claude/docs/technical/DASHBOARD_WIDGETS_AGENT_GUIDE.md @@ -0,0 +1,163 @@ +# Dashboard-Lab-Widgets – Anleitung für Coding-Agenten + +Ziel: Ein neues Dashboard-Widget **end-to-end** korrekt einbinden (Backend-Katalog, Validierung, API-Layout, Frontend-Registrierung, optional Lab-Editor für `config`). +Kontext: **Dashboard-Lab** unter geschützten Endpoints `GET/PUT /api/app/...` (siehe `backend/routers/app_dashboard.py`). Layout liegt pro Profil in `profiles.dashboard_layout` (JSON). + +--- + +## 0. Architekturanforderung: Subscription / Feature-System vs. Widget-Katalog + +### 0.1 Ist-Stand (Verifikation) + +- **Bereits vorhanden:** Membership- und Feature-Modell (`features`, `tier_limits`, `user_feature_restrictions`, `check_feature_access` in `backend/auth.py`). Siehe `.claude/docs/architecture/FEATURE_ENFORCEMENT.md`. +- **Umgesetzt:** `GET /api/app/widgets/catalog` liefert pro Widget **`allowed`** (aus `requires_feature` im Katalog + `check_feature_access`). `GET/PUT /api/app/dashboard-layout` wendet **`apply_entitlements_to_layout_dict`** an (nicht erlaubte Einträge: `enabled: false`; Standard-Layout ebenfalls bereinigt). Implementierung: `backend/dashboard_widget_entitlements.py`. +- Optional pro Katalogzeile: **`requires_feature`** (`features.id`) in `widget_catalog.py`; fehlt der Key → Widget für alle authentifizierten Nutzer katalog-sichtbar (ohne zusätzliches Feature-Gate). + +### 0.2 Soll: eine Wahrheit für „darf angezeigt werden“ + +- **Komplexität** (Module aus, Cluster, Stufen: z. B. Ernährung an, aber bestimmte Auswertungen nur in höherem Tier) gehört in die **Feature-/Subscription-Schicht** (inkl. späterer Feature-Cluster), nicht in einzelne React-Widgets. +- **Widgets** sollen das Ergebnis nur **abrufen** (z. B. `allowed` / sichtbar im Katalog), nicht die Tier-Logik duplizieren. + +### 0.3 Bindende Anforderungen (wenn Feature-Gating umgesetzt wird) + +| Anforderung | Beschreibung | +|-------------|--------------| +| **A1 – Zentrale Auflösung** | Backend ermittelt pro Profil (effektiver Tier + Restrictions), welche Widget-IDs **erlaubt** sind – idealerweise in **einer** Stelle (Erweiterung des Katalog-Endpoints oder dedizierter Entitlements-Teil der Response). Intern: `check_feature_access` und später ggf. Mapping Widget-ID → Feature-ID(n) / Cluster. | +| **A2 – Nutzer-Konfigurator** | Im Dashboard-Lab (und jedem späteren Layout-Konfigurator): Widgets **ohne Berechtigung nicht anbieten** (ausgeblendet oder gar nicht in der Liste). Alle **erlaubten** Widgets bleiben wie heute wählbar. | +| **A3 – Layout-Persistenz** | `PUT /api/app/dashboard-layout`: Layout darf **keine** nicht erlaubten Widgets dauerhaft speichern – entweder **ablehnen** (422) oder **beim Speichern entfernen/deaktivieren** (Policy festlegen und dokumentieren). Verhindert „gespeichert, aber nie sichtbar“-Zombies. | +| **A4 – API-/Datenschutz** | Sichtbarkeit im UI reicht nicht: Endpoints, die **Inhalte** für gated Widgets liefern (Charts, KI, …), müssen weiterhin wie heute **eigenständig** über Features abgesichert sein (`check_feature_access`, 403). | + +### 0.4 Katalog-Erweiterung (Vorbereitung ohne feste Tier-Namen) + +- Tiers bleiben **in der DB konfigurierbar**; im Code keine Annahme „free vs. pro“. +- Pro Widget-Eintrag (oder separater Mapping-Layer) kann später **`required_feature_id`** (ein Key aus `features.id`) oder ein **Cluster-Key** ergänzt werden, der auf **eine oder mehrere** `check_feature_access`-Abfragen abgebildet wird – Details bei Implementierung festlegen. +- Neue Widget-Doku: Wenn ein Widget an ein Feature hängt, in `widget_catalog`-`description` und in dieser Anleitung vermerken. + +**Verweis:** Verbindliche Regel auf Projektebene: `.claude/rules/ARCHITECTURE.md` § 9. + +--- + +## 1. Datenfluss (kurz) + +1. **`backend/widget_catalog.py`** – `WIDGET_CATALOG`: erlaubte Widget-IDs, Reihenfolge, Titel/Beschreibung für API und Default-Layout. +2. **`backend/dashboard_layout_schema.py`** – `DashboardLayoutPayload`: jede Zeile hat `id`, `enabled`, optional `config`. IDs müssen in `ALLOWED_WIDGET_IDS` sein (aus dem Katalog abgeleitet). +3. **`backend/dashboard_widget_config.py`** – `validate_widget_entry_config`: **nur** Widgets in `WIDGETS_ALLOWING_CONFIG` dürfen **nicht-leere** `config` haben; Keys werden streng validiert (unbekannte Keys → Fehler). +4. **Frontend** – `ensurePilotLabWidgetsRegistered()` in `frontend/src/widgetSystem/registerPilotLabWidgets.js`: verbindet jede Katalog-ID mit einer React-Komponente und mappt `ctx.layoutEntry.config` auf Props. +5. **Dashboard-Lab-UI** – `frontend/src/pages/DashboardLabPage.jsx`: Umsortieren, Ein/Aus, Speichern; **zusätzliche** UI nur nötig, wenn das Widget konfigurierbare Felder braucht. + +--- + +## 2. Checkliste: neues Widget ohne Konfiguration + +| Schritt | Datei | Aktion | +|--------|--------|--------| +| A | `backend/widget_catalog.py` | Neuen Eintrag `{ "id", "title", "description" }` in `WIDGET_CATALOG` einfügen (Reihenfolge = Default-Reihenfolge im Layout). Optional `"requires_feature": ""` für Tarif-Gating (`dashboard_widget_entitlements`). | +| B | `backend/widget_catalog.py` | Optional: ID zu `DEFAULT_LAB_WIDGET_IDS` hinzufügen, wenn es im Standard-Lab **aktiv** sein soll. | +| C | `frontend/src/components/dashboard-widgets/MyWidget.jsx` (oder Pilot-Komponente) | React-Komponente implementieren; typischerweise `refreshTick` aus `mapProps` nutzen, um Daten neu zu laden. | +| D | `frontend/src/widgetSystem/registerPilotLabWidgets.js` | `import` + `registerDashboardWidget({ id, Component, mapProps })` – `id` **exakt** wie im Katalog. | +| E | `backend/tests/test_widget_catalog.py` | Läuft implizit mit; bei Strukturänderungen Katalog-Tests beachten. | +| F | `backend/version.py` | `MODULE_VERSIONS["app_dashboard"]` MINOR erhöhen und kurz kommentieren. | +| G | Build/Tests | `pytest` (z. B. `tests/test_dashboard_layout_schema.py`, `test_widget_catalog.py`); `npm run build` im `frontend`. | + +**Nicht nötig:** `WIDGETS_ALLOWING_CONFIG` oder `validate_widget_entry_config`-Zweig, solange `config` immer `{}` bleibt. + +**Wichtig:** Widget-IDs im Frontend-Registry **ohne** Registrierung führen im UI zu „Unbekanntes Widget“ (`dashboardWidgetRegistry.jsx`). + +--- + +## 3. Checkliste: Widget mit konfigurierbaren Einstellungen (`config`) + +### 3.1 Backend + +1. **`WIDGETS_ALLOWING_CONFIG`** in `backend/dashboard_widget_config.py` um die neue `widget_id` ergänzen. +2. In **`validate_widget_entry_config`** einen eigenen Zweig oder Aufruf einer Hilfsfunktion hinzufügen (siehe bestehende Muster unten). +3. **`MAX_WIDGET_CONFIG_JSON_BYTES`** (3072): keine großen Blobs in `config`. +4. Regeln konsistent halten: + - Unbekannte Keys **ablehnen** (wie bei `kpi_board`, `quick_capture`, `chart_days`-only). + - Leeres Objekt `{}` erlauben, wenn alle Keys optional sind (Validator entscheidet). + +**Referenz-Muster im Code:** + +| Muster | Verwendung | Implementierung | +|--------|------------|-----------------| +| Nur `chart_days` (7–90) | Chart-Kacheln | `_validate_chart_days_only(raw, label="...")` | +| KPI-Kacheln | `tiles`-Liste, max. 9 | `_validate_kpi_board_config` | +| Booleans + Mindestens eines true | Schnelleingabe-Sichtbarkeit | `_validate_quick_capture_config` | + +Neue komplexe Config: eigene `_validate_my_widget_config` schreiben, Keys als `frozenset` whitelisten, Typen prüfen, sinnvoll normalisieren/abrunden. + +5. **Tests** in `backend/tests/test_dashboard_widget_config.py`: Happy-Path, ein ungültiger Wert, unbekannter Key, ggf. Größe/Limits. + +### 3.2 Katalog-Beschreibung + +In `widget_catalog.py` bei `description` die **konfigurierbaren Keys** kurz nennen (hilft Admin/API-Nutzern). Einheitliche Benennung mit dem Backend (z. B. `chart_days 7–90`). + +### 3.3 Frontend: Props aus Layout + +`registerDashboardWidget` erhält `mapProps(ctx)`: + +- **`ctx.layoutEntry`**: `{ id, enabled, config? }` – hierher kommt die gespeicherte Konfiguration. +- **`ctx.refreshTick`** / **`ctx.requestRefresh()`**: Datenaktualisierung nach Aktionen. + +Typische Zuordnung: + +```javascript +mapProps: (ctx) => ({ + refreshTick: ctx.refreshTick, + myOption: ctx.layoutEntry?.config?.my_option, +}) +``` + +**Abgleich mit Chart-Zeitraum:** Für `chart_days` existiert `frontend/src/widgetSystem/bodyChartDays.js` (`BODY_CHART_DAYS_MIN/MAX`, `normalizeBodyChartDays`). Entweder in `mapProps` normalisieren (wie `body_overview`) oder rohen Wert durchreichen und in der Widget-Komponente normalisieren (wie `nutrition_detail_charts` / `TrendKcalWeightWidget`) – **beides** ist im Projekt vertreten; wichtig ist Konsistenz mit der Backend-Grenze 7–90. + +### 3.4 Dashboard-Lab-Editor (`DashboardLabPage.jsx`) + +Ohne UI-Änderung bleibt `config` beim Nutzer `{}` – konfigurierbare Widgets brauchen **Editor-Controls**: + +- **Einfaches Zahlfeld `chart_days`:** Eintrag in `CHART_DAYS_WIDGET_IDS` (Set oben in der Datei) + bestehendes Label/`aria-label`-Pattern für die Zeitraum-Zeile erweitern (siehe `body_overview`, `nutrition_detail_charts`). +- **Strukturierte Config (Listen, mehrere Booleans):** Eigenes Editor-Komponenten-File nach Vorbild `KpiBoardConfigEditor.jsx` / `QuickCaptureConfigEditor.jsx` einbinden und `setLayout` + `normalizeLayoutForEditor` wie bei den bestehenden Blöcken verwenden. + +Nach Speichern ruft die Seite `api.putAppDashboardLayout(layout)` auf; das Backend validiert über `DashboardLayoutPayload` → `validate_widget_entry_config`. + +--- + +## 4. Grenzen und Fehlerbilder + +| Thema | Detail | +|--------|--------| +| Erlaubte IDs | Nur IDs aus `WIDGET_CATALOG`. `ALLOWED_WIDGET_IDS` wird daraus abgeleitet – nicht manuell duplizieren. | +| Doppelte IDs | Im Layout sind **keine** doppelten `widget.id` erlaubt (`DashboardLayoutPayload`). | +| Max. Widgets | `widgets` max. 32 Einträge (`DashboardLayoutPayload`). | +| Config verboten | Widget **nicht** in `WIDGETS_ALLOWING_CONFIG` → jede nicht-leere `config` → Validierungsfehler beim Speichern. | +| Frontend ≠ Katalog | Komponente registriert, ID fehlt im Katalog → PUT schlägt fehl. | +| Katalog ohne Registry | GET Layout ok, Render zeigt „Unbekanntes Widget“. | + +--- + +## 5. API zum Prüfen + +- `GET /api/app/widgets/catalog` – Katalog inkl. `allowed` je Widget (Auth + `X-Profile-Id` wie andere App-Endpoints). +- `GET /api/app/dashboard-layout` – `layout` (effektiv, bereinigt), `custom`, `product_default_layout` (Übersichts-Standard), `lab_default_layout` (Dashboard-Lab-Standard). +- `PUT /api/app/dashboard-layout` – Body `{ "version": 1, "widgets": [ ... ] }` (unerlaubte Widgets werden auf `enabled: false` gesetzt). + +--- + +## 6. Nach getaner Arbeit + +- `pytest` für `dashboard_widget_config` und `widget_catalog` / `dashboard_layout_schema`. +- `npm run build`. +- `MODULE_VERSIONS["app_dashboard"]` in `backend/version.py` anheben. + +--- + +## 7. Verwandte Dateien (Referenz) + +| Zweck | Pfad | +|--------|------| +| Katalog | `backend/widget_catalog.py` | +| Config-Validierung | `backend/dashboard_widget_config.py` | +| Layout-Pydantic | `backend/dashboard_layout_schema.py` | +| HTTP | `backend/routers/app_dashboard.py` | +| Registry + Render | `frontend/src/widgetSystem/dashboardWidgetRegistry.jsx` | +| Pilot/Lab-Registrierung | `frontend/src/widgetSystem/registerPilotLabWidgets.js` | +| Lab-UI | `frontend/src/pages/DashboardLabPage.jsx` | diff --git a/.claude/docs/technical/DATABASE.md b/.claude/docs/technical/DATABASE.md new file mode 100644 index 0000000..94b9d95 --- /dev/null +++ b/.claude/docs/technical/DATABASE.md @@ -0,0 +1,1088 @@ +# Datenbankschema + +## Übersicht + +**Datenbank:** PostgreSQL 16 Alpine + +**Schema-Verwaltung:** Automatische Migrations (db_init.py) + +**Tabellen:** 32 Tabellen (Stand v9d Phase 2) + +**Primärschlüssel:** UUID (uuid_generate_v4()) + +**Zeitstempel:** `TIMESTAMP WITH TIME ZONE`, UTC + +--- + +## Migrations-System + +### Automatische Schema-Updates + +**Location:** `backend/migrations/*.sql` + +**Pattern:** `XXX_description.sql` (z.B. `001_initial_schema.sql`) + +**Tracking-Tabelle:** `schema_migrations` +```sql +CREATE TABLE schema_migrations ( + id SERIAL PRIMARY KEY, + migration_file VARCHAR(255) UNIQUE NOT NULL, + applied_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); +``` + +**Ablauf:** +1. Container-Start → `db_init.py` läuft +2. Alle Dateien in `backend/migrations/` werden alphabetisch sortiert +3. Noch nicht angewendete Migrationen werden ausgeführt +4. Eintrag in `schema_migrations` nach erfolgreicher Anwendung + +**Sicherheit:** +- Transaktionen pro Migration (Rollback bei Fehler) +- Skip bereits angewendeter Migrationen +- Logging aller Migrationen + +### Migration-History + +| Nr | Datei | Beschreibung | Version | +|----|-------|--------------|---------| +| 001 | `001_initial_schema.sql` | Basis-Tabellen (profiles, sessions, weight, etc.) | v9b | +| 002 | `002_membership_system.sql` | Tiers, Features, Coupons, Access Grants | v9c | +| 003 | `003_email_verification.sql` | E-Mail-Verifizierung für Registrierung | v9c | +| 004 | `004_training_types.sql` | Trainingstypen (23 Basis-Typen) | v9d Phase 1a | +| 005 | `005_extended_training_types.sql` | Extended Types (Gehen, Tanzen, Meditation) | v9d Phase 1a | +| 006 | `006_training_abilities.sql` | abilities JSONB column (Platzhalter) | v9d Phase 1b | +| 007 | `007_activity_type_mappings.sql` | Lernendes Mapping-System | v9d Phase 1b | +| 008-009 | – | Reserved für v9d Phase 2a-c | – | +| 010 | `010_sleep_log.sql` | Schlaf-Modul (JSONB segments) | v9d Phase 2b | +| 011 | `011_rest_days.sql` | Ruhetage (Kraft, Cardio, Entspannung) | v9d Phase 2a | +| 012 | `012_rest_days_unique_constraint.sql` | Unique constraint rest_days | v9d Phase 2a | +| 013 | `013_vitals_log.sql` | Vitalwerte (Ruhepuls, HRV) – **deprecated** | v9d Phase 2c | +| 014 | `014_extended_vitals.sql` | Extended vitals (BP, VO2 Max, SpO2) – **deprecated** | v9d Phase 2c | +| 015 | `015_vitals_refactoring.sql` | **Vitals Refactoring** - Trennung in vitals_baseline + blood_pressure_log | v9d Phase 2d | + +**Backup vor Migration 015:** +- `vitals_log` → `vitals_log_backup_pre_015` +- Daten nach Refactoring archiviert (nicht gelöscht) + +--- + +## Tabellen-Übersicht + +### Core-Tabellen + +| Tabelle | Zweck | Primärschlüssel | Besonderheit | +|---------|-------|-----------------|--------------| +| `profiles` | Nutzerprofile + Auth | UUID | bcrypt-Hashing, Tier-System | +| `sessions` | Auth-Tokens | VARCHAR(64) | Expires-Check via Index | +| `ai_usage` | KI-Call-Tracking | UUID | Daily Count pro Profil | + +### Tracking-Tabellen + +| Tabelle | Zweck | Primärschlüssel | Unique Constraint | +|---------|-------|-----------------|-------------------| +| `weight_log` | Gewichtsmessungen | UUID | (profile_id, date) | +| `circumference_log` | Umfangsmessungen (8 Punkte) | UUID | – | +| `caliper_log` | Hautfaltenmessungen + BF% | UUID | – | +| `nutrition_log` | Ernährungsdaten (Kalorien + Makros) | UUID | – | +| `activity_log` | Training + Aktivitäten | UUID | – | +| `photos` | Progress-Fotos | UUID | – | +| `sleep_log` | Schlaf + JSONB Phasen | UUID | (profile_id, date) | +| `rest_days` | Multi-dimensionale Ruhetage | UUID | (profile_id, date, rest_type) | +| `vitals_baseline` | Morgenmessungen (RHR, HRV, VO2 Max) | UUID | (profile_id, date) | +| `blood_pressure_log` | Blutdruck mehrfach täglich | UUID | – | + +### KI-Tabellen + +| Tabelle | Zweck | Primärschlüssel | Index | +|---------|-------|-----------------|-------| +| `ai_insights` | KI-Auswertungen | UUID | (profile_id, scope, created DESC) | +| `ai_prompts` | Konfigurierbare Prompts | UUID | slug UNIQUE | + +### Membership-System (v9c) + +| Tabelle | Zweck | Primärschlüssel | Besonderheit | +|---------|-------|-----------------|--------------| +| `subscriptions` | **deprecated** | UUID | Wurde durch access_grants ersetzt | +| `coupons` | Coupon-Codes | UUID | code UNIQUE | +| `coupon_redemptions` | Einlösungen | UUID | (profile_id, coupon_id) UNIQUE | +| `features` | Feature-Definitionen | VARCHAR(50) | id als String (z.B. 'weight_entries') | +| `tier_limits` | Tier-Feature-Matrix | UUID | (tier_id, feature_id) UNIQUE | +| `user_feature_restrictions` | User-spezifische Limits | UUID | (profile_id, feature_id) UNIQUE | +| `user_feature_usage` | Usage-Tracking | UUID | (profile_id, feature_id) UNIQUE | +| `access_grants` | Zeitlich begrenzte Tier-Zugriffe | UUID | Ersetzt subscriptions | +| `user_activity_log` | Audit-Log | UUID | – | + +### Training-System (v9d) + +| Tabelle | Zweck | Primärschlüssel | Besonderheit | +|---------|-------|-----------------|--------------| +| `training_types` | 29 Trainingstypen in 7 Kategorien | UUID | abilities JSONB | +| `activity_type_mappings` | Lernendes Mapping-System | UUID | (activity_type, profile_id) UNIQUE | + +### Infrastruktur + +| Tabelle | Zweck | Primärschlüssel | Verwendung | +|---------|-------|-----------------|------------| +| `schema_migrations` | Migrations-Tracking | SERIAL | Automatisches System | + +--- + +## Detaillierte Tabellen-Beschreibungen + +### 1. profiles + +**Beschreibung:** Nutzerprofile mit Auth, Permissions, Tier-System + +```sql +CREATE TABLE profiles ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + name VARCHAR(255) NOT NULL DEFAULT 'Nutzer', + avatar_color VARCHAR(7) DEFAULT '#1D9E75', + photo_id UUID, + sex VARCHAR(1) DEFAULT 'm' CHECK (sex IN ('m', 'w', 'd')), + dob DATE, + height NUMERIC(5,2) DEFAULT 178, + goal_weight NUMERIC(5,2), + goal_bf_pct NUMERIC(4,2), + + -- Auth & Permissions + role VARCHAR(20) DEFAULT 'user' CHECK (role IN ('user', 'admin')), + pin_hash TEXT, -- bcrypt-Hash + auth_type VARCHAR(20) DEFAULT 'pin' CHECK (auth_type IN ('pin', 'email')), + session_days INTEGER DEFAULT 30, + ai_enabled BOOLEAN DEFAULT TRUE, + ai_limit_day INTEGER, + export_enabled BOOLEAN DEFAULT TRUE, + email VARCHAR(255) UNIQUE, + + -- E-Mail-Verifizierung (v9c) + email_verified BOOLEAN DEFAULT FALSE, + verification_token VARCHAR(64), + verification_expires TIMESTAMP WITH TIME ZONE, + + -- Tier-System (v9c) + tier VARCHAR(20) DEFAULT 'free' CHECK (tier IN ('free', 'basic', 'premium', 'selfhosted')), + tier_expires_at TIMESTAMP WITH TIME ZONE, + trial_ends_at TIMESTAMP WITH TIME ZONE, + invited_by UUID REFERENCES profiles(id), + + -- Timestamps + created TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + updated TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_profiles_email ON profiles(email) WHERE email IS NOT NULL; +CREATE INDEX idx_profiles_tier ON profiles(tier); +``` + +**Wichtige Felder:** +- `pin_hash`: bcrypt-Hash (Format: `$2b$12$...`) oder Legacy SHA256 (auto-migrated) +- `tier`: Subscription-Tier (überschrieben durch `access_grants`) +- `trial_ends_at`: 14 Tage ab Registrierung +- `email_verified`: Muss `true` sein für Login (außer Legacy-Accounts) + +**Trigger:** +```sql +CREATE TRIGGER trigger_profiles_updated + BEFORE UPDATE ON profiles + FOR EACH ROW + EXECUTE FUNCTION update_updated_timestamp(); +``` + +--- + +### 2. sessions + +**Beschreibung:** Auth-Token-Management + +```sql +CREATE TABLE sessions ( + token VARCHAR(64) PRIMARY KEY, -- secrets.token_urlsafe(32) + profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + expires_at TIMESTAMP WITH TIME ZONE NOT NULL, + created TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_sessions_profile_id ON sessions(profile_id); +CREATE INDEX idx_sessions_expires_at ON sessions(expires_at); +``` + +**Besonderheiten:** +- Token-Format: 43 Zeichen Base64-URL-safe +- Password-Reset-Tokens: Präfix `reset_` (z.B. `reset_jT9z3xK...`) +- Automatische Bereinigung via `WHERE expires_at > CURRENT_TIMESTAMP` + +--- + +### 3. weight_log + +**Beschreibung:** Gewichtsmessungen + +```sql +CREATE TABLE weight_log ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + date DATE NOT NULL, + weight NUMERIC(5,2) NOT NULL, + note TEXT, + source VARCHAR(20) DEFAULT 'manual', -- 'manual' | 'apple_health' | 'garmin' + created TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_weight_log_profile_date ON weight_log(profile_id, date DESC); +CREATE UNIQUE INDEX idx_weight_log_profile_date_unique ON weight_log(profile_id, date); +``` + +**Unique Constraint:** Ein Eintrag pro Profil pro Tag + +**Upsert-Logik:** +```sql +INSERT INTO weight_log (profile_id, date, weight, note, source) +VALUES (%s, %s, %s, %s, %s) +ON CONFLICT (profile_id, date) +DO UPDATE SET weight=EXCLUDED.weight, note=EXCLUDED.note +WHERE weight_log.source != 'manual'; -- Manuelle Einträge haben Vorrang +``` + +--- + +### 4. circumference_log + +**Beschreibung:** Umfangsmessungen (8 Punkte) + +```sql +CREATE TABLE circumference_log ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + date DATE NOT NULL, + c_neck NUMERIC(5,2), + c_chest NUMERIC(5,2), + c_waist NUMERIC(5,2), + c_belly NUMERIC(5,2), + c_hip NUMERIC(5,2), + c_thigh NUMERIC(5,2), + c_calf NUMERIC(5,2), + c_arm NUMERIC(5,2), + notes TEXT, + photo_id UUID, + created TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_circumference_profile_date ON circumference_log(profile_id, date DESC); +``` + +**Kein Unique Constraint:** Mehrere Messungen pro Tag möglich + +--- + +### 5. caliper_log + +**Beschreibung:** Hautfaltenmessungen + Körperfett-Berechnung + +```sql +CREATE TABLE caliper_log ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + date DATE NOT NULL, + sf_method VARCHAR(20) DEFAULT 'jackson3', -- 'jackson3' | 'jackson7' | 'durnin' | 'parrillo' + sf_chest NUMERIC(5,2), + sf_axilla NUMERIC(5,2), + sf_triceps NUMERIC(5,2), + sf_subscap NUMERIC(5,2), + sf_suprailiac NUMERIC(5,2), + sf_abdomen NUMERIC(5,2), + sf_thigh NUMERIC(5,2), + sf_calf_med NUMERIC(5,2), + sf_lowerback NUMERIC(5,2), + sf_biceps NUMERIC(5,2), + body_fat_pct NUMERIC(4,2), -- Berechnet + lean_mass NUMERIC(5,2), -- Berechnet + fat_mass NUMERIC(5,2), -- Berechnet + notes TEXT, + created TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_caliper_profile_date ON caliper_log(profile_id, date DESC); +``` + +**Berechnungslogik:** Backend berechnet `body_fat_pct`, `lean_mass`, `fat_mass` bei POST + +**Methoden:** Siehe `frontend/src/utils/calc.js` (Jackson-Pollock 3/7, Durnin-Womersley, Parrillo) + +--- + +### 6. nutrition_log + +**Beschreibung:** Ernährungsdaten (Kalorien + Makros) + +```sql +CREATE TABLE nutrition_log ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + date DATE NOT NULL, + kcal NUMERIC(7,2), + protein_g NUMERIC(6,2), + fat_g NUMERIC(6,2), + carbs_g NUMERIC(6,2), + source VARCHAR(20) DEFAULT 'csv', -- 'csv' | 'manual' + created TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_nutrition_profile_date ON nutrition_log(profile_id, date DESC); +``` + +**Upsert-Logik:** Wie `weight_log` – ein Eintrag pro Tag + +--- + +### 7. activity_log + +**Beschreibung:** Training + Aktivitäten + +```sql +CREATE TABLE activity_log ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + date DATE NOT NULL, + start_time TIME, + end_time TIME, + activity_type VARCHAR(50) NOT NULL, -- 'Laufen' | 'Krafttraining' | etc. + training_type_id UUID REFERENCES training_types(id), -- v9d: Mapping zu Trainingstypen + duration_min NUMERIC(6,2), + kcal_active NUMERIC(7,2), + kcal_resting NUMERIC(7,2), + hr_avg NUMERIC(5,2), + hr_max NUMERIC(5,2), + distance_km NUMERIC(7,2), + rpe INTEGER CHECK (rpe >= 1 AND rpe <= 10), -- Rate of Perceived Exertion + source VARCHAR(20) DEFAULT 'manual', + notes TEXT, + created TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_activity_profile_date ON activity_log(profile_id, date DESC); +CREATE INDEX idx_activity_training_type ON activity_log(training_type_id); +``` + +**Duplikat-Erkennung:** Import prüft `(date, start_time)` bei Apple Health CSV + +--- + +### 8. photos + +**Beschreibung:** Progress-Fotos + +```sql +CREATE TABLE photos ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + meas_id UUID, -- Legacy: reference to circumference/caliper + date DATE, + path TEXT NOT NULL, -- Filesystem-Pfad (relativ zu PHOTOS_DIR) + created TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_photos_profile_date ON photos(profile_id, date DESC); +``` + +**Storage:** `/app/photos/` (Docker Volume) + +**Format:** JPEG, max 5 MB (Frontend-Validierung) + +--- + +### 9. ai_insights + +**Beschreibung:** KI-Auswertungen + +```sql +CREATE TABLE ai_insights ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + scope VARCHAR(50) NOT NULL, -- Prompt-Slug (z.B. 'weight-trend', 'pipeline') + content TEXT NOT NULL, -- Markdown-formatierter Text + created TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_ai_insights_profile_scope ON ai_insights(profile_id, scope, created DESC); +``` + +**Scopes:** `weight-trend`, `nutrition-analysis`, `training-plan`, `body-composition`, `progress-summary`, `pipeline` + +--- + +### 10. ai_prompts + +**Beschreibung:** Konfigurierbare KI-Prompts + +```sql +CREATE TABLE ai_prompts ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + name VARCHAR(255) NOT NULL, + slug VARCHAR(100) NOT NULL UNIQUE, + description TEXT, + template TEXT NOT NULL, -- Prompt-Template mit Platzhaltern + active BOOLEAN DEFAULT TRUE, + sort_order INTEGER DEFAULT 0, + created TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + updated TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_ai_prompts_slug ON ai_prompts(slug); +CREATE INDEX idx_ai_prompts_active_sort ON ai_prompts(active, sort_order); +``` + +**Template-Platzhalter:** `{weight_data}`, `{nutrition_data}`, `{activity_data}`, etc. + +**Trigger:** `update_updated_timestamp()` bei UPDATE + +--- + +### 11. ai_usage + +**Beschreibung:** KI-Call-Tracking (Daily) + +```sql +CREATE TABLE ai_usage ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + date DATE NOT NULL, + call_count INTEGER DEFAULT 0, + UNIQUE(profile_id, date) +); + +CREATE INDEX idx_ai_usage_profile_date ON ai_usage(profile_id, date); +``` + +**Upsert-Logik:** +```sql +INSERT INTO ai_usage (profile_id, date, call_count) +VALUES (%s, CURRENT_DATE, 1) +ON CONFLICT (profile_id, date) +DO UPDATE SET call_count = ai_usage.call_count + 1; +``` + +--- + +## Membership-System (v9c) + +### 12. coupons + +```sql +CREATE TABLE coupons ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + code VARCHAR(50) NOT NULL UNIQUE, + tier_id VARCHAR(20) NOT NULL, -- 'basic' | 'premium' | 'selfhosted' + valid_days INTEGER NOT NULL, -- Gültigkeitsdauer in Tagen + max_uses INTEGER, -- NULL = unlimited + uses_count INTEGER DEFAULT 0, + expires_at TIMESTAMP WITH TIME ZONE, + active BOOLEAN DEFAULT TRUE, + created TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_coupons_code ON coupons(code); +``` + +--- + +### 13. coupon_redemptions + +```sql +CREATE TABLE coupon_redemptions ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + coupon_id UUID NOT NULL REFERENCES coupons(id) ON DELETE CASCADE, + redeemed_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + UNIQUE(profile_id, coupon_id) +); + +CREATE INDEX idx_coupon_redemptions_profile ON coupon_redemptions(profile_id); +CREATE INDEX idx_coupon_redemptions_coupon ON coupon_redemptions(coupon_id); +``` + +**Logik:** Ein User kann denselben Coupon nur einmal einlösen + +--- + +### 14. features + +```sql +CREATE TABLE features ( + id VARCHAR(50) PRIMARY KEY, -- 'weight_entries', 'ai_calls', etc. + name VARCHAR(100) NOT NULL, + description TEXT, + limit_type VARCHAR(20) NOT NULL CHECK (limit_type IN ('count', 'boolean')), + reset_period VARCHAR(20) NOT NULL CHECK (reset_period IN ('never', 'daily', 'monthly')), + default_limit INTEGER, -- NULL = unlimited + active BOOLEAN DEFAULT TRUE, + created TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); +``` + +**Standard-Features:** +- `weight_entries`, `circumference_entries`, `caliper_entries`, `activity_entries`, `nutrition_entries` +- `photos`, `ai_calls`, `ai_pipeline`, `data_export`, `data_import` + +--- + +### 15. tier_limits + +```sql +CREATE TABLE tier_limits ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + tier_id VARCHAR(20) NOT NULL, -- 'free', 'basic', 'premium', 'selfhosted' + feature_id VARCHAR(50) NOT NULL REFERENCES features(id) ON DELETE CASCADE, + limit_value INTEGER, -- NULL = unlimited + created TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + UNIQUE(tier_id, feature_id) +); + +CREATE INDEX idx_tier_limits_tier ON tier_limits(tier_id); +CREATE INDEX idx_tier_limits_feature ON tier_limits(feature_id); +``` + +**Beispiel-Werte:** +```sql +INSERT INTO tier_limits (tier_id, feature_id, limit_value) VALUES +('free', 'weight_entries', 100), +('free', 'ai_calls', 10), +('premium', 'weight_entries', NULL), -- unlimited +('premium', 'ai_calls', NULL); +``` + +--- + +### 16. user_feature_restrictions + +```sql +CREATE TABLE user_feature_restrictions ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + feature_id VARCHAR(50) NOT NULL REFERENCES features(id) ON DELETE CASCADE, + limit_value INTEGER, -- Überschreibt Tier-Limit + created TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + UNIQUE(profile_id, feature_id) +); + +CREATE INDEX idx_user_restrictions_profile ON user_feature_restrictions(profile_id); +``` + +**Priorität:** User-Restriction > Tier-Limit > Feature-Default + +--- + +### 17. user_feature_usage + +```sql +CREATE TABLE user_feature_usage ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + feature_id VARCHAR(50) NOT NULL REFERENCES features(id) ON DELETE CASCADE, + usage_count INTEGER DEFAULT 0, + reset_at TIMESTAMP WITH TIME ZONE, + updated TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + UNIQUE(profile_id, feature_id) +); + +CREATE INDEX idx_user_usage_profile ON user_feature_usage(profile_id); +CREATE INDEX idx_user_usage_reset ON user_feature_usage(reset_at); +``` + +**Upsert-Logik:** +```sql +INSERT INTO user_feature_usage (profile_id, feature_id, usage_count, reset_at) +VALUES (%s, %s, 1, %s) +ON CONFLICT (profile_id, feature_id) +DO UPDATE SET usage_count = user_feature_usage.usage_count + 1, updated = CURRENT_TIMESTAMP; +``` + +--- + +### 18. access_grants + +```sql +CREATE TABLE access_grants ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + tier_id VARCHAR(20) NOT NULL, + valid_from TIMESTAMP WITH TIME ZONE NOT NULL, + valid_until TIMESTAMP WITH TIME ZONE NOT NULL, + source VARCHAR(50), -- 'coupon', 'trial', 'manual', 'gift' + is_active BOOLEAN DEFAULT TRUE, + created TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_access_grants_profile ON access_grants(profile_id); +CREATE INDEX idx_access_grants_validity ON access_grants(valid_from, valid_until); +``` + +**Logik:** Aktiver Grant überschreibt `profiles.tier` + +**Priorität:** Grant mit spätestem `valid_until` gewinnt + +--- + +### 19. user_activity_log + +```sql +CREATE TABLE user_activity_log ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + action VARCHAR(100) NOT NULL, + details TEXT, + created TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_activity_log_profile ON user_activity_log(profile_id); +CREATE INDEX idx_activity_log_created ON user_activity_log(created DESC); +``` + +**Verwendung:** Audit-Log (Login, Coupon-Einlösung, Feature-Zugriff-Denial) + +--- + +## Training-System (v9d) + +### 20. training_types + +```sql +CREATE TABLE training_types ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + name VARCHAR(100) NOT NULL, + category VARCHAR(50) NOT NULL, -- 'Kraft', 'Cardio', 'Flexibilität', etc. + color VARCHAR(7) DEFAULT '#1D9E75', + icon VARCHAR(50), -- 'dumbbell', 'running', 'yoga', etc. + abilities JSONB, -- v9f: Fähigkeiten-Matrix + sort_order INTEGER DEFAULT 0, + active BOOLEAN DEFAULT TRUE, + created TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_training_types_category ON training_types(category); +CREATE INDEX idx_training_types_active ON training_types(active); +``` + +**abilities Format (v9f):** +```json +{ + "Kraft": {"Oberkörper": 8, "Unterkörper": 2, "Core": 5}, + "Ausdauer": {"Aerob": 9, "Anaerob": 3}, + "Beweglichkeit": {"Dynamisch": 6, "Statisch": 4}, + "Koordination": 7, + "Schnelligkeit": 5, + "Gleichgewicht": 3 +} +``` + +**Kategorien:** +- Kraft (8 Typen) +- Cardio (6 Typen) +- Flexibilität (4 Typen) +- Spiel & Sport (4 Typen) +- Alltag & Bewegung (3 Typen) +- Outdoor & Natur (2 Typen) +- Geist & Meditation (2 Typen) + +**Gesamt:** 29 Trainingstypen + +--- + +### 21. activity_type_mappings + +```sql +CREATE TABLE activity_type_mappings ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + activity_type VARCHAR(100) NOT NULL, -- 'Laufen', 'Running', etc. + training_type_id UUID NOT NULL REFERENCES training_types(id) ON DELETE CASCADE, + profile_id UUID REFERENCES profiles(id) ON DELETE CASCADE, -- NULL = global + is_global BOOLEAN DEFAULT FALSE, + created TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + UNIQUE(activity_type, profile_id) +); + +CREATE INDEX idx_activity_mappings_activity ON activity_type_mappings(activity_type); +CREATE INDEX idx_activity_mappings_training ON activity_type_mappings(training_type_id); +CREATE INDEX idx_activity_mappings_profile ON activity_type_mappings(profile_id); +``` + +**Lernendes System:** +- Bulk-Kategorisierung in UI speichert neue Mappings +- User-spezifische Mappings überschreiben globale Mappings +- Admin kann globale Mappings erstellen + +**Standard-Mappings:** 40+ vordefiniert (Deutsch + Englisch) + +--- + +## Sleep & Vitals (v9d Phase 2) + +### 22. sleep_log + +```sql +CREATE TABLE sleep_log ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + date DATE NOT NULL, + bedtime TIME, + wakeup TIME, + duration_min INTEGER NOT NULL, + quality INTEGER CHECK (quality >= 1 AND quality <= 10), + sleep_segments JSONB, -- Schlafphasen (Deep, REM, Light, Awake) + notes TEXT, + source VARCHAR(20) DEFAULT 'manual', + created TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + UNIQUE(profile_id, date) +); + +CREATE INDEX idx_sleep_profile_date ON sleep_log(profile_id, date DESC); +``` + +**sleep_segments Format:** +```json +[ + {"phase": "deep", "start": "23:30", "end": "01:15"}, + {"phase": "rem", "start": "01:15", "end": "02:45"}, + {"phase": "light", "start": "02:45", "end": "06:00"}, + {"phase": "awake", "start": "06:00", "end": "06:15"} +] +``` + +--- + +### 23. rest_days + +```sql +CREATE TABLE rest_days ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + date DATE NOT NULL, + rest_type VARCHAR(20) NOT NULL CHECK (rest_type IN ('kraft', 'cardio', 'entspannung')), + reason TEXT, + notes TEXT, + created TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + UNIQUE(profile_id, date, rest_type) +); + +CREATE INDEX idx_rest_days_profile_date ON rest_days(profile_id, date DESC); +``` + +**Multi-Dimensional Rest:** Mehrere rest_types pro Tag möglich (z.B. kraft + cardio) + +--- + +### 24. vitals_baseline + +**Beschreibung:** Morgenmessungen (1x täglich) + +```sql +CREATE TABLE vitals_baseline ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + date DATE NOT NULL, + resting_hr INTEGER, -- Ruhepuls (bpm) + hrv INTEGER, -- Herzfrequenzvariabilität (ms) + vo2_max NUMERIC(5,2), -- VO2 Max (ml/kg/min) + spo2 INTEGER, -- Sauerstoffsättigung (%) + respiratory_rate INTEGER, -- Atemfrequenz (pro Minute) + notes TEXT, + source VARCHAR(20) DEFAULT 'manual', + created TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + UNIQUE(profile_id, date) +); + +CREATE INDEX idx_vitals_baseline_profile_date ON vitals_baseline(profile_id, date DESC); +``` + +**Messung:** Morgens nüchtern, direkt nach dem Aufwachen + +--- + +### 25. blood_pressure_log + +**Beschreibung:** Blutdruck (mehrfach täglich) + +```sql +CREATE TABLE blood_pressure_log ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + date DATE NOT NULL, + time TIME NOT NULL, + systolic INTEGER NOT NULL, -- Systolisch (mmHg) + diastolic INTEGER NOT NULL, -- Diastolisch (mmHg) + pulse INTEGER, -- Puls (bpm) + context VARCHAR(50), -- 'fasting', 'after_meal', 'exercise', etc. + irregular_heartbeat BOOLEAN DEFAULT FALSE, + afib_warning BOOLEAN DEFAULT FALSE, + notes TEXT, + source VARCHAR(20) DEFAULT 'manual', + created TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX idx_blood_pressure_profile_date ON blood_pressure_log(profile_id, date DESC, time DESC); +``` + +**Contexts:** +- `fasting` – Nüchtern +- `after_meal` – Nach dem Essen +- `exercise` – Nach Training +- `stress` – Unter Stress +- `rest` – In Ruhe +- `before_sleep` – Vor dem Schlafen +- `after_sleep` – Nach dem Aufwachen +- `medication` – Nach Medikation + +**WHO/ISH-Klassifizierung:** Backend berechnet Kategorie (Optimal, Normal, Hoch-Normal, Hypertonie 1/2/3) + +--- + +## Beziehungen (Foreign Keys) + +### Profil-Bezug + +**Alle Tracking-Tabellen:** +```sql +profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE +``` + +**Kaskadierendes Löschen:** Beim Löschen eines Profils werden alle zugehörigen Daten gelöscht + +### Membership-Beziehungen + +``` +profiles + ↓ (1:n) +user_feature_usage +user_feature_restrictions +access_grants +coupon_redemptions + +features + ↓ (1:n) +tier_limits +user_feature_restrictions +user_feature_usage +``` + +### Training-Beziehungen + +``` +training_types + ↓ (1:n) +activity_log (training_type_id) +activity_type_mappings (training_type_id) + +profiles + ↓ (1:n) +activity_type_mappings (profile_id, NULL = global) +``` + +--- + +## Design-Entscheidungen + +### 1. Warum UUID statt Integer? + +**Entscheidung:** UUID als Primärschlüssel + +**Gründe:** +- Keine Kollisionen bei verteilten Systemen +- Sicherheit (kein Raten von IDs) +- Vorbereitung für Multi-Server-Setup + +**Nachteil:** Größerer Index (16 Bytes vs. 4 Bytes) + +**Akzeptiert weil:** Performance-Impact minimal bei <100k Einträgen pro Tabelle + +--- + +### 2. Warum JSONB für sleep_segments? + +**Entscheidung:** JSONB statt normalisierte Tabellen + +**Gründe:** +- Flexibilität (variable Anzahl Phasen pro Nacht) +- Atomarität (gesamter Schlaf = 1 Zeile) +- Performance (kein JOIN nötig) +- Native PostgreSQL-Support für JSON-Queries + +**Beispiel-Query:** +```sql +SELECT date, sleep_segments->>0 AS first_phase +FROM sleep_log +WHERE profile_id = '...'; +``` + +--- + +### 3. Warum Unique Constraint auf (profile_id, date)? + +**Entscheidung:** Mehrere Tabellen haben `UNIQUE(profile_id, date)` + +**Tabellen:** `weight_log`, `sleep_log`, `vitals_baseline` + +**Gründe:** +- Upsert-Logik (nur ein Eintrag pro Tag) +- Vereinfacht Frontend (kein Duplikat-Handling) +- Performance (Index für schnelle Lookups) + +**Ausnahmen:** `blood_pressure_log`, `activity_log` (mehrfach täglich erlaubt) + +--- + +### 4. Warum source-Feld? + +**Entscheidung:** Alle Import-fähigen Tabellen haben `source VARCHAR(20)` + +**Werte:** `manual`, `apple_health`, `garmin`, `withings`, etc. + +**Gründe:** +- Provenance-Tracking (woher kommen Daten) +- Konflikt-Auflösung (manuelle Einträge haben Vorrang) +- Debugging (Import-Fehler tracken) + +**Upsert-Logik:** +```sql +ON CONFLICT (profile_id, date) +DO UPDATE SET ... WHERE table.source != 'manual'; +``` + +--- + +### 5. Warum String-IDs für Features? + +**Entscheidung:** `features.id` ist `VARCHAR(50)` statt UUID + +**Beispiel:** `'weight_entries'`, `'ai_calls'` + +**Gründe:** +- Lesbarkeit in Logs/Code +- Einfachere Referenzierung in Frontend +- Keine Notwendigkeit für Auto-Generated IDs + +--- + +### 6. Warum abilities als JSONB? + +**Entscheidung:** `training_types.abilities` als JSONB (v9f) + +**Gründe:** +- Flexible Schema-Evolution (neue Fähigkeiten ohne Migration) +- Nested Structure (Hierarchie: Kraft → Oberkörper → Brust) +- Native PostgreSQL-Aggregation (AVG, SUM über JSONB) + +**Alternative:** Normalisierte Tabellen (abgelehnt wegen Overhead) + +--- + +## Wichtige Queries + +### 1. Latest Weight mit 7-Tage-Durchschnitt + +```sql +SELECT + w1.date, + w1.weight, + ( + SELECT AVG(w2.weight) + FROM weight_log w2 + WHERE w2.profile_id = w1.profile_id + AND w2.date BETWEEN w1.date - INTERVAL '7 days' AND w1.date + ) AS avg_7d +FROM weight_log w1 +WHERE w1.profile_id = %s +ORDER BY w1.date DESC +LIMIT 1; +``` + +### 2. Feature-Limit-Check mit Hierarchie + +```sql +-- 1. User-Restriction (höchste Priorität) +SELECT limit_value FROM user_feature_restrictions +WHERE profile_id = %s AND feature_id = %s; + +-- 2. Tier-Limit (falls keine Restriction) +SELECT limit_value FROM tier_limits +WHERE tier_id = ( + SELECT tier FROM profiles WHERE id = %s +) AND feature_id = %s; + +-- 3. Feature-Default (falls kein Tier-Limit) +SELECT default_limit FROM features WHERE id = %s; +``` + +### 3. Aktiver Access Grant + +```sql +SELECT tier_id FROM access_grants +WHERE profile_id = %s + AND is_active = true + AND valid_from <= CURRENT_TIMESTAMP + AND valid_until > CURRENT_TIMESTAMP +ORDER BY valid_until DESC +LIMIT 1; +``` + +### 4. Training-Type-Distribution + +```sql +SELECT + tt.name, + tt.color, + COUNT(*) AS count, + SUM(al.duration_min) AS total_duration_min +FROM activity_log al +JOIN training_types tt ON al.training_type_id = tt.id +WHERE al.profile_id = %s + AND al.date >= CURRENT_DATE - INTERVAL '30 days' +GROUP BY tt.id, tt.name, tt.color +ORDER BY total_duration_min DESC; +``` + +### 5. Sleep-Schuld (Sleep Debt) + +```sql +SELECT + SUM( + CASE + WHEN duration_min < 480 THEN 480 - duration_min -- 8h = 480min Target + ELSE 0 + END + ) AS sleep_debt_min +FROM sleep_log +WHERE profile_id = %s + AND date >= CURRENT_DATE - INTERVAL '14 days'; +``` + +--- + +## Zusammenfassung + +**Tabellen:** 32 (Stand v9d Phase 2) + +**Primärschlüssel:** UUID (uuid_generate_v4()) + +**Indizes:** 60+ für Performance-Optimierung + +**Unique Constraints:** 15+ für Datenintegrität + +**JSONB-Spalten:** 2 (`sleep_segments`, `abilities`) + +**Migrations:** Automatisch via `db_init.py` + +**Design-Highlights:** +- ✅ UUID-basierte IDs (Sicherheit + Skalierbarkeit) +- ✅ JSONB für flexible Strukturen (Schlafphasen, Fähigkeiten) +- ✅ Unique Constraints für Upsert-Logik +- ✅ Source-Tracking für Import-Daten +- ✅ Kaskadierendes Löschen (ON DELETE CASCADE) +- ✅ Automatische Timestamps (created, updated) +- ✅ Check Constraints für Datenvalidierung +- ✅ Index-Optimierung für häufige Queries + +**Bekannte Limitationen:** +- Keine Soft-Deletes (physisches Löschen) +- Keine Audit-Trail für Änderungen (außer user_activity_log) +- Keine Partitionierung (akzeptabel bis >1M Einträge) diff --git a/.claude/docs/technical/DATABASE_MODEL_COMPLETE.md b/.claude/docs/technical/DATABASE_MODEL_COMPLETE.md new file mode 100644 index 0000000..e54c0fa --- /dev/null +++ b/.claude/docs/technical/DATABASE_MODEL_COMPLETE.md @@ -0,0 +1,1758 @@ +# Technisches Datenmodell – Mitai Jinkendo + +> **Quelle:** Development Database (dev.mitai.jinkendo.de) +> **Generiert:** 2026-04-02 +> **PostgreSQL Version:** 16-alpine +> **Anzahl Tabellen:** 43 + +--- + +## Inhaltsverzeichnis + +1. [Kernmodule](#1-kernmodule) +2. [Tracking-Module](#2-tracking-module) +3. [KI & Analysen](#3-ki--analysen) +4. [Membership & Features](#4-membership--features) +5. [Ziele & Training](#5-ziele--training) +6. [System-Tabellen](#6-system-tabellen) +7. [Backup-Tabellen](#7-backup-tabellen) +8. [ER-Diagramm](#8-er-diagramm) + +--- + +## 1. Kernmodule + +### 1.1 profiles + +**Beschreibung:** Nutzerstammdaten und Authentifizierung + +| Spalte | Typ | Nullable | Default | Beschreibung | +|--------|-----|----------|---------|--------------| +| id | uuid | NO | uuid_generate_v4() | PK, eindeutige Profil-ID | +| email | varchar | YES | NULL | E-Mail-Adresse (unique) | +| pin_hash | varchar(255) | NO | - | bcrypt-gehashtes Passwort | +| role | varchar(20) | YES | 'user' | Rolle (user, admin) | +| tier | text | YES | 'free' | Membership-Tier | +| created_at | timestamp | YES | now() | Erstellungsdatum | +| email_verified | boolean | YES | false | E-Mail-Verifizierung | +| verification_token | varchar(255) | YES | NULL | Token für E-Mail-Verifizierung | +| invited_by | uuid | YES | NULL | FK zu profiles.id (Einladung) | +| quality_filter_level | varchar(20) | YES | 'disabled' | Qualitätsfilter für Aktivitäten | + +**Constraints:** +- PK: `id` +- UNIQUE: `email` +- FK: `invited_by` → `profiles(id)` ON DELETE NO ACTION +- CHECK: `tier IN ('free', 'plus', 'premium', 'trial')` + +**Indizes:** +- `idx_profiles_email` (email) WHERE email IS NOT NULL +- `idx_profiles_tier` (tier) +- `idx_profiles_quality_filter` (quality_filter_level) +- `idx_profiles_verification_token` (verification_token) WHERE verification_token IS NOT NULL + +**Beziehungen:** +- 1:N zu allen Tracking-Tabellen (profile_id) +- 1:N zu sessions +- 1:N zu ai_insights, ai_usage +- 1:N zu goals, training_phases +- 1:1 zu user_stats +- Self-Reference (invited_by) + +--- + +### 1.2 sessions + +**Beschreibung:** Authentifizierungs-Sessions + +| Spalte | Typ | Nullable | Default | Beschreibung | +|--------|-----|----------|---------|--------------| +| token | varchar(255) | NO | - | PK, Session-Token | +| profile_id | uuid | NO | - | FK zu profiles.id | +| expires_at | timestamp | NO | - | Ablaufdatum | +| created_at | timestamp | YES | now() | Erstellungsdatum | + +**Constraints:** +- PK: `token` +- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE + +**Indizes:** +- `idx_sessions_profile_id` (profile_id) +- `idx_sessions_expires_at` (expires_at) + +--- + +### 1.3 user_stats + +**Beschreibung:** Aggregierte Nutzerstatistiken + +| Spalte | Typ | Nullable | Default | Beschreibung | +|--------|-----|----------|---------|--------------| +| profile_id | uuid | NO | - | PK, FK zu profiles.id | +| total_weight_entries | integer | YES | 0 | Anzahl Gewichtseinträge | +| total_circumference_entries | integer | YES | 0 | Anzahl Umfangseinträge | +| total_caliper_entries | integer | YES | 0 | Anzahl Caliper-Einträge | +| total_activity_entries | integer | YES | 0 | Anzahl Aktivitäten | +| total_nutrition_entries | integer | YES | 0 | Anzahl Ernährungseinträge | +| total_photos | integer | YES | 0 | Anzahl Fotos | +| total_ai_insights | integer | YES | 0 | Anzahl KI-Analysen | +| last_weight_date | date | YES | NULL | Letztes Gewichtsdatum | +| last_activity_date | date | YES | NULL | Letzte Aktivität | +| last_insight_date | timestamp | YES | NULL | Letzte KI-Analyse | +| updated_at | timestamp | YES | now() | Letztes Update | + +**Constraints:** +- PK: `profile_id` +- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE + +--- + +## 2. Tracking-Module + +### 2.1 weight_log + +**Beschreibung:** Gewichtsverlauf + +| Spalte | Typ | Nullable | Default | Beschreibung | +|--------|-----|----------|---------|--------------| +| id | uuid | NO | uuid_generate_v4() | PK | +| profile_id | uuid | NO | - | FK zu profiles.id | +| date | date | NO | - | Messdatum | +| weight | numeric(5,2) | NO | - | Gewicht in kg | +| source | varchar(50) | YES | 'manual' | Datenquelle | +| notes | text | YES | NULL | Notizen | +| created_at | timestamp | YES | now() | Erstellungsdatum | + +**Constraints:** +- PK: `id` +- UNIQUE: `profile_id, date` +- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE + +**Indizes:** +- `idx_weight_log_profile_date` (profile_id, date DESC) +- `idx_weight_log_profile_date_unique` UNIQUE (profile_id, date) + +--- + +### 2.2 circumference_log + +**Beschreibung:** Umfangsmessungen (8 Messpunkte) + +| Spalte | Typ | Nullable | Default | Beschreibung | +|--------|-----|----------|---------|--------------| +| id | uuid | NO | uuid_generate_v4() | PK | +| profile_id | uuid | NO | - | FK zu profiles.id | +| date | date | NO | - | Messdatum | +| c_neck | numeric(5,2) | YES | NULL | Hals (cm) | +| c_chest | numeric(5,2) | YES | NULL | Brust (cm) | +| c_waist | numeric(5,2) | YES | NULL | Taille (cm) | +| c_hips | numeric(5,2) | YES | NULL | Hüfte (cm) | +| c_thigh | numeric(5,2) | YES | NULL | Oberschenkel (cm) | +| c_calf | numeric(5,2) | YES | NULL | Wade (cm) | +| c_biceps | numeric(5,2) | YES | NULL | Bizeps (cm) | +| c_forearm | numeric(5,2) | YES | NULL | Unterarm (cm) | +| source | varchar(50) | YES | 'manual' | Datenquelle | +| notes | text | YES | NULL | Notizen | +| created_at | timestamp | YES | now() | Erstellungsdatum | + +**Constraints:** +- PK: `id` +- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE + +**Indizes:** +- `idx_circumference_profile_date` (profile_id, date DESC) + +--- + +### 2.3 caliper_log + +**Beschreibung:** Hautfaltenmessungen (Körperfettanalyse) + +| Spalte | Typ | Nullable | Default | Beschreibung | +|--------|-----|----------|---------|--------------| +| id | uuid | NO | uuid_generate_v4() | PK | +| profile_id | uuid | NO | - | FK zu profiles.id | +| date | date | NO | - | Messdatum | +| chest | numeric(5,2) | YES | NULL | Brust (mm) | +| abdominal | numeric(5,2) | YES | NULL | Bauch (mm) | +| thigh | numeric(5,2) | YES | NULL | Oberschenkel (mm) | +| triceps | numeric(5,2) | YES | NULL | Trizeps (mm) | +| subscapular | numeric(5,2) | YES | NULL | Schulterblatt (mm) | +| suprailiac | numeric(5,2) | YES | NULL | Hüfte (mm) | +| midaxillary | numeric(5,2) | YES | NULL | Mittelachsel (mm) | +| body_fat_pct | numeric(5,2) | YES | NULL | Körperfett % (berechnet) | +| formula | varchar(20) | YES | NULL | Berechnungsformel | +| source | varchar(50) | YES | 'manual' | Datenquelle | +| notes | text | YES | NULL | Notizen | +| created_at | timestamp | YES | now() | Erstellungsdatum | + +**Constraints:** +- PK: `id` +- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE + +**Indizes:** +- `idx_caliper_profile_date` (profile_id, date DESC) + +--- + +### 2.4 activity_log + +**Beschreibung:** Trainings- und Aktivitätslog + +| Spalte | Typ | Nullable | Default | Beschreibung | +|--------|-----|----------|---------|--------------| +| id | uuid | NO | uuid_generate_v4() | PK | +| profile_id | uuid | NO | - | FK zu profiles.id | +| date | date | NO | - | Aktivitätsdatum | +| start_time | time | YES | NULL | Startzeit | +| duration_minutes | integer | YES | NULL | Dauer (Minuten) | +| training_type_id | integer | YES | NULL | FK zu training_types.id | +| training_category | varchar(50) | YES | NULL | Kategorie | +| kcal | integer | YES | NULL | Kalorienverbrauch | +| avg_hr | integer | YES | NULL | Durchschnittliche HF | +| max_hr | integer | YES | NULL | Maximale HF | +| distance_km | numeric(6,2) | YES | NULL | Distanz (km) | +| description | text | YES | NULL | Beschreibung | +| source | varchar(50) | YES | 'manual' | Datenquelle | +| evaluation | jsonb | YES | NULL | Qualitätsbewertung | +| overall_score | integer | YES | NULL | Gesamtbewertung (0-100) | +| quality_label | varchar(20) | YES | NULL | Qualitätslabel | +| created_at | timestamp | YES | now() | Erstellungsdatum | + +**Constraints:** +- PK: `id` +- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE +- FK: `training_type_id` → `training_types(id)` ON DELETE NO ACTION + +**Indizes:** +- `idx_activity_profile_date` (profile_id, date DESC) +- `idx_activity_training_type` (training_type_id) +- `idx_activity_training_category` (training_category) +- `idx_activity_quality_label` (quality_label) WHERE quality_label IS NOT NULL +- `idx_activity_overall_score` (overall_score DESC) WHERE overall_score IS NOT NULL +- `idx_activity_evaluation_passed` (evaluation->'rule_set_results'->'minimum_requirements'->>'passed') WHERE evaluation IS NOT NULL + +--- + +### 2.5 nutrition_log + +**Beschreibung:** Ernährungslog (täglich) + +| Spalte | Typ | Nullable | Default | Beschreibung | +|--------|-----|----------|---------|--------------| +| id | uuid | NO | uuid_generate_v4() | PK | +| profile_id | uuid | NO | - | FK zu profiles.id | +| date | date | NO | - | Datum | +| kcal | integer | YES | NULL | Kalorien | +| protein_g | numeric(6,2) | YES | NULL | Protein (g) | +| carbs_g | numeric(6,2) | YES | NULL | Kohlenhydrate (g) | +| fat_g | numeric(6,2) | YES | NULL | Fett (g) | +| fiber_g | numeric(6,2) | YES | NULL | Ballaststoffe (g) | +| water_l | numeric(4,2) | YES | NULL | Wasser (Liter) | +| source | varchar(50) | YES | 'manual' | Datenquelle | +| notes | text | YES | NULL | Notizen | +| created_at | timestamp | YES | now() | Erstellungsdatum | + +**Constraints:** +- PK: `id` +- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE + +**Indizes:** +- `idx_nutrition_profile_date` (profile_id, date DESC) + +--- + +### 2.6 sleep_log + +**Beschreibung:** Schlaf-Tracking mit JSONB-Segmenten (Deep, REM, Light, Awake) + +| Spalte | Typ | Nullable | Default | Beschreibung | +|--------|-----|----------|---------|--------------| +| id | uuid | NO | uuid_generate_v4() | PK | +| profile_id | uuid | NO | - | FK zu profiles.id | +| date | date | NO | - | Schlafdatum | +| sleep_start | timestamp | YES | NULL | Schlafbeginn | +| sleep_end | timestamp | YES | NULL | Schlafende | +| total_duration_minutes | integer | YES | NULL | Gesamtdauer (Minuten) | +| sleep_segments | jsonb | YES | NULL | Schlafphasen-Daten | +| quality_score | integer | YES | NULL | Schlafqualität (0-100) | +| source | varchar(50) | YES | 'manual' | Datenquelle | +| notes | text | YES | NULL | Notizen | +| created_at | timestamp | YES | now() | Erstellungsdatum | + +**JSONB-Struktur `sleep_segments`:** +```json +{ + "deep": 120, // Minuten + "rem": 90, // Minuten + "light": 180, // Minuten + "awake": 15 // Minuten +} +``` + +**Constraints:** +- PK: `id` +- UNIQUE: `profile_id, date` +- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE + +**Indizes:** +- `idx_sleep_profile_date` (profile_id, date DESC) +- `unique_sleep_per_day` UNIQUE (profile_id, date) + +--- + +### 2.7 vitals_baseline + +**Beschreibung:** Morgenmessung Vitalwerte (RHR, HRV, VO2 Max, SpO2, Atemfrequenz) + +| Spalte | Typ | Nullable | Default | Beschreibung | +|--------|-----|----------|---------|--------------| +| id | uuid | NO | uuid_generate_v4() | PK | +| profile_id | uuid | NO | - | FK zu profiles.id | +| date | date | NO | - | Messdatum | +| resting_hr | integer | YES | NULL | Ruhepuls (bpm) | +| hrv | integer | YES | NULL | HRV (ms) | +| vo2_max | numeric(5,2) | YES | NULL | VO2 Max (ml/kg/min) | +| spo2 | integer | YES | NULL | Sauerstoffsättigung (%) | +| respiratory_rate | integer | YES | NULL | Atemfrequenz (bpm) | +| source | varchar(50) | YES | 'manual' | Datenquelle | +| notes | text | YES | NULL | Notizen | +| created_at | timestamp | YES | now() | Erstellungsdatum | + +**Constraints:** +- PK: `id` +- UNIQUE: `profile_id, date` +- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE + +**Indizes:** +- `idx_vitals_baseline_profile_date` (profile_id, date DESC) +- `unique_baseline_per_day` UNIQUE (profile_id, date) + +--- + +### 2.8 blood_pressure_log + +**Beschreibung:** Blutdruckmessungen (mehrfach täglich mit Context-Tagging) + +| Spalte | Typ | Nullable | Default | Beschreibung | +|--------|-----|----------|---------|--------------| +| id | uuid | NO | uuid_generate_v4() | PK | +| profile_id | uuid | NO | - | FK zu profiles.id | +| measured_at | timestamp | NO | - | Messzeitpunkt | +| systolic | integer | NO | - | Systolisch (mmHg) | +| diastolic | integer | NO | - | Diastolisch (mmHg) | +| pulse | integer | YES | NULL | Puls (bpm) | +| context | varchar(50) | YES | NULL | Kontext (nüchtern, nach Essen, etc.) | +| irregular_heartbeat | boolean | YES | false | Unregelmäßiger Herzschlag | +| afib_detected | boolean | YES | false | Vorhofflimmern erkannt | +| source | varchar(50) | YES | 'manual' | Datenquelle | +| notes | text | YES | NULL | Notizen | +| created_at | timestamp | YES | now() | Erstellungsdatum | + +**Constraints:** +- PK: `id` +- UNIQUE: `profile_id, measured_at` +- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE + +**Indizes:** +- `idx_blood_pressure_profile_datetime` (profile_id, measured_at DESC) +- `idx_blood_pressure_context` (context) WHERE context IS NOT NULL +- `unique_bp_measurement` UNIQUE (profile_id, measured_at) + +--- + +### 2.9 rest_days + +**Beschreibung:** Multi-dimensionale Ruhetage (Kraft, Cardio, Entspannung) + +| Spalte | Typ | Nullable | Default | Beschreibung | +|--------|-----|----------|---------|--------------| +| id | uuid | NO | uuid_generate_v4() | PK | +| profile_id | uuid | NO | - | FK zu profiles.id | +| date | date | NO | - | Ruhetag-Datum | +| focus | varchar(50) | NO | - | Fokus (strength, cardio, relaxation) | +| rest_config | jsonb | YES | NULL | Detailkonfiguration | +| notes | text | YES | NULL | Notizen | +| created_at | timestamp | YES | now() | Erstellungsdatum | + +**JSONB-Struktur `rest_config`:** +```json +{ + "strength_rest": true, + "cardio_rest": false, + "relaxation": true, + "preset": "full_rest" +} +``` + +**Constraints:** +- PK: `id` +- UNIQUE: `profile_id, date, focus` +- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE + +**Indizes:** +- `idx_rest_days_profile_date` (profile_id, date DESC) +- `idx_rest_days_focus` (focus) +- `idx_rest_days_config` GIN (rest_config) +- `unique_rest_day_per_focus` UNIQUE (profile_id, date, focus) + +--- + +### 2.10 photos + +**Beschreibung:** Progress-Fotos + +| Spalte | Typ | Nullable | Default | Beschreibung | +|--------|-----|----------|---------|--------------| +| id | uuid | NO | uuid_generate_v4() | PK | +| profile_id | uuid | NO | - | FK zu profiles.id | +| date | date | NO | - | Fotodatum | +| filename | varchar(255) | NO | - | Dateiname | +| pose | varchar(50) | YES | NULL | Pose (front, side, back) | +| notes | text | YES | NULL | Notizen | +| created_at | timestamp | YES | now() | Erstellungsdatum | + +**Constraints:** +- PK: `id` +- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE + +**Indizes:** +- `idx_photos_profile_date` (profile_id, date DESC) + +--- + +## 3. KI & Analysen + +### 3.1 ai_prompts + +**Beschreibung:** KI-Prompt-Definitionen (Unified Prompt System) + +| Spalte | Typ | Nullable | Default | Beschreibung | +|--------|-----|----------|---------|--------------| +| id | uuid | NO | uuid_generate_v4() | PK | +| slug | varchar(100) | NO | - | Eindeutiger Slug (unique) | +| title | varchar(255) | NO | - | Titel | +| type | varchar(20) | YES | 'base' | Typ (base, pipeline) | +| category | varchar(50) | YES | NULL | Kategorie | +| stages | jsonb | YES | NULL | Pipeline-Stages | +| output_format | varchar(20) | YES | NULL | Output-Format (json, text) | +| output_schema | jsonb | YES | NULL | JSON-Schema für Output | +| active | boolean | YES | true | Aktiv | +| sort_order | integer | YES | 0 | Sortierung | +| created_at | timestamp | YES | now() | Erstellungsdatum | +| updated_at | timestamp | YES | now() | Letztes Update | + +**JSONB-Struktur `stages`:** +```json +[ + { + "name": "Stage 1", + "prompts": [ + { + "type": "reference", + "slug": "body_summary" + }, + { + "type": "inline", + "template": "Analyse {{weight_latest}}...", + "output_format": "json", + "output_schema": {...} + } + ] + } +] +``` + +**Constraints:** +- PK: `id` +- UNIQUE: `slug` + +**Indizes:** +- `idx_ai_prompts_slug` (slug) +- `ai_prompts_slug_key` UNIQUE (slug) +- `idx_ai_prompts_type` (type) +- `idx_ai_prompts_category` (category) +- `idx_ai_prompts_active_sort` (active, sort_order) +- `idx_ai_prompts_stages` GIN (stages) + +--- + +### 3.2 ai_insights + +**Beschreibung:** Gespeicherte KI-Analysen + +| Spalte | Typ | Nullable | Default | Beschreibung | +|--------|-----|----------|---------|--------------| +| id | uuid | NO | uuid_generate_v4() | PK | +| profile_id | uuid | NO | - | FK zu profiles.id | +| scope | varchar(100) | YES | NULL | Prompt-Slug | +| content | text | NO | - | KI-Antwort | +| metadata | jsonb | YES | NULL | Platzhalter-Werte | +| created | timestamp | YES | now() | Erstellungsdatum | + +**JSONB-Struktur `metadata`:** +```json +{ + "placeholders": { + "weight_latest": 85.2, + "goal_weight": 80.0, + "activity_days": 28 + }, + "categories": { + "PROFIL": [...], + "KÖRPER": [...] + } +} +``` + +**Constraints:** +- PK: `id` +- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE + +**Indizes:** +- `idx_ai_insights_profile_scope` (profile_id, scope, created DESC) + +--- + +### 3.3 ai_usage + +**Beschreibung:** Tägliche KI-Nutzung (Rate Limiting) + +| Spalte | Typ | Nullable | Default | Beschreibung | +|--------|-----|----------|---------|--------------| +| id | uuid | NO | uuid_generate_v4() | PK | +| profile_id | uuid | NO | - | FK zu profiles.id | +| date | date | NO | - | Nutzungsdatum | +| count | integer | YES | 0 | Anzahl Calls | + +**Constraints:** +- PK: `id` +- UNIQUE: `profile_id, date` +- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE + +**Indizes:** +- `idx_ai_usage_profile_date` (profile_id, date) +- `ai_usage_profile_id_date_key` UNIQUE (profile_id, date) + +--- + +### 3.4 pipeline_configs + +**Beschreibung:** Pipeline-Konfigurationen (deprecated, ersetzt durch ai_prompts) + +| Spalte | Typ | Nullable | Default | Beschreibung | +|--------|-----|----------|---------|--------------| +| id | uuid | NO | uuid_generate_v4() | PK | +| name | varchar(100) | NO | - | Name (unique) | +| description | text | YES | NULL | Beschreibung | +| stages | jsonb | NO | - | Stage-Konfiguration | +| active | boolean | YES | true | Aktiv | +| is_default | boolean | YES | false | Standard-Pipeline | +| created_at | timestamp | YES | now() | Erstellungsdatum | + +**Constraints:** +- PK: `id` +- UNIQUE: `name` + +**Indizes:** +- `idx_pipeline_configs_active` (active) +- `idx_pipeline_configs_default` (is_default) WHERE is_default = true +- `idx_pipeline_configs_single_default` UNIQUE (is_default) WHERE is_default = true +- `pipeline_configs_name_key` UNIQUE (name) + +--- + +## 4. Membership & Features + +### 4.1 tiers + +**Beschreibung:** Membership-Tiers (free, plus, premium, trial) + +| Spalte | Typ | Nullable | Default | Beschreibung | +|--------|-----|----------|---------|--------------| +| id | text | NO | - | PK (free, plus, premium, trial) | +| name | text | NO | - | Display-Name | +| description | text | YES | NULL | Beschreibung | +| price_monthly | numeric(10,2) | YES | NULL | Preis/Monat | +| price_yearly | numeric(10,2) | YES | NULL | Preis/Jahr | +| trial_days | integer | YES | NULL | Trial-Dauer (Tage) | +| is_active | boolean | YES | true | Aktiv | +| sort_order | integer | YES | 0 | Sortierung | + +**Constraints:** +- PK: `id` + +--- + +### 4.2 features + +**Beschreibung:** Feature-Definitionen + +| Spalte | Typ | Nullable | Default | Beschreibung | +|--------|-----|----------|---------|--------------| +| id | text | NO | - | PK (feature_key) | +| name | text | NO | - | Display-Name | +| description | text | YES | NULL | Beschreibung | +| category | text | YES | NULL | Kategorie | + +**Constraints:** +- PK: `id` + +--- + +### 4.3 tier_limits + +**Beschreibung:** Feature-Limits pro Tier + +| Spalte | Typ | Nullable | Default | Beschreibung | +|--------|-----|----------|---------|--------------| +| id | uuid | NO | uuid_generate_v4() | PK | +| tier_id | text | NO | - | FK zu tiers.id | +| feature_id | text | NO | - | FK zu features.id | +| limit_value | integer | YES | NULL | Limit-Wert | +| limit_type | text | YES | NULL | Limit-Typ (daily, monthly, total) | + +**Constraints:** +- PK: `id` +- UNIQUE: `tier_id, feature_id` +- FK: `tier_id` → `tiers(id)` ON DELETE CASCADE +- FK: `feature_id` → `features(id)` ON DELETE CASCADE + +**Indizes:** +- `idx_tier_limits_tier` (tier_id) +- `idx_tier_limits_feature` (feature_id) +- `tier_limits_tier_id_feature_id_key` UNIQUE (tier_id, feature_id) + +--- + +### 4.4 coupons + +**Beschreibung:** Gutschein-Codes + +| Spalte | Typ | Nullable | Default | Beschreibung | +|--------|-----|----------|---------|--------------| +| id | uuid | NO | uuid_generate_v4() | PK | +| code | varchar(50) | NO | - | Gutschein-Code (unique) | +| tier_id | text | YES | NULL | FK zu tiers.id | +| duration_days | integer | YES | NULL | Laufzeit (Tage) | +| max_uses | integer | YES | NULL | Max. Einlösungen | +| current_uses | integer | YES | 0 | Aktuelle Einlösungen | +| valid_from | timestamp | YES | NULL | Gültig ab | +| valid_until | timestamp | YES | NULL | Gültig bis | +| is_active | boolean | YES | true | Aktiv | +| created_at | timestamp | YES | now() | Erstellungsdatum | + +**Constraints:** +- PK: `id` +- UNIQUE: `code` +- FK: `tier_id` → `tiers(id)` ON DELETE SET NULL + +**Indizes:** +- `idx_coupons_code` (code) +- `coupons_code_key` UNIQUE (code) + +--- + +### 4.5 coupon_redemptions + +**Beschreibung:** Gutschein-Einlösungen + +| Spalte | Typ | Nullable | Default | Beschreibung | +|--------|-----|----------|---------|--------------| +| id | uuid | NO | uuid_generate_v4() | PK | +| coupon_id | uuid | NO | - | FK zu coupons.id | +| profile_id | uuid | NO | - | FK zu profiles.id | +| redeemed_at | timestamp | YES | now() | Einlösungsdatum | + +**Constraints:** +- PK: `id` +- UNIQUE: `coupon_id, profile_id` +- FK: `coupon_id` → `coupons(id)` ON DELETE CASCADE +- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE + +**Indizes:** +- `idx_coupon_redemptions_profile` (profile_id) +- `coupon_redemptions_coupon_id_profile_id_key` UNIQUE (coupon_id, profile_id) + +--- + +### 4.6 access_grants + +**Beschreibung:** Zeitgebundene Tier-Zugriffe + +| Spalte | Typ | Nullable | Default | Beschreibung | +|--------|-----|----------|---------|--------------| +| id | uuid | NO | uuid_generate_v4() | PK | +| profile_id | uuid | NO | - | FK zu profiles.id | +| tier_id | text | NO | - | FK zu tiers.id | +| granted_by | text | YES | NULL | Vergeben durch | +| coupon_id | uuid | YES | NULL | FK zu coupons.id | +| valid_from | timestamp | NO | - | Gültig ab | +| valid_until | timestamp | NO | - | Gültig bis | +| is_active | boolean | YES | true | Aktiv | +| paused_by | uuid | YES | NULL | Pausiert von | +| paused_at | timestamp | YES | NULL | Pausiert am | +| remaining_days | integer | YES | NULL | Verbleibende Tage | + +**Constraints:** +- PK: `id` +- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE +- FK: `tier_id` → `tiers(id)` ON DELETE CASCADE +- FK: `coupon_id` → `coupons(id)` ON DELETE SET NULL + +**Indizes:** +- `idx_access_grants_profile` (profile_id, valid_until DESC) +- `idx_access_grants_active` (profile_id, is_active, valid_until DESC) + +--- + +### 4.7 user_feature_restrictions + +**Beschreibung:** Nutzer-spezifische Feature-Restriktionen + +| Spalte | Typ | Nullable | Default | Beschreibung | +|--------|-----|----------|---------|--------------| +| id | uuid | NO | uuid_generate_v4() | PK | +| profile_id | uuid | NO | - | FK zu profiles.id | +| feature_id | text | NO | - | FK zu features.id | +| custom_limit | integer | YES | NULL | Custom Limit | +| is_disabled | boolean | YES | false | Feature deaktiviert | + +**Constraints:** +- PK: `id` +- UNIQUE: `profile_id, feature_id` +- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE +- FK: `feature_id` → `features(id)` ON DELETE CASCADE + +**Indizes:** +- `idx_user_restrictions_profile` (profile_id) +- `user_feature_restrictions_profile_id_feature_id_key` UNIQUE (profile_id, feature_id) + +--- + +### 4.8 user_feature_usage + +**Beschreibung:** Feature-Nutzungszähler + +| Spalte | Typ | Nullable | Default | Beschreibung | +|--------|-----|----------|---------|--------------| +| id | uuid | NO | uuid_generate_v4() | PK | +| profile_id | uuid | NO | - | FK zu profiles.id | +| feature_id | text | NO | - | FK zu features.id | +| usage_count | integer | YES | 0 | Nutzungszähler | +| last_used_at | timestamp | YES | NULL | Letzte Nutzung | +| reset_at | timestamp | YES | NULL | Reset-Zeitpunkt | + +**Constraints:** +- PK: `id` +- UNIQUE: `profile_id, feature_id` +- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE +- FK: `feature_id` → `features(id)` ON DELETE CASCADE + +**Indizes:** +- `idx_user_usage_profile` (profile_id) +- `user_feature_usage_profile_id_feature_id_key` UNIQUE (profile_id, feature_id) + +--- + +### 4.9 user_activity_log + +**Beschreibung:** Audit-Log für Nutzeraktionen + +| Spalte | Typ | Nullable | Default | Beschreibung | +|--------|-----|----------|---------|--------------| +| id | uuid | NO | uuid_generate_v4() | PK | +| profile_id | uuid | NO | - | FK zu profiles.id | +| action | text | NO | - | Aktion | +| details | jsonb | YES | NULL | Details | +| created | timestamp | YES | now() | Erstellungsdatum | + +**Constraints:** +- PK: `id` +- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE + +**Indizes:** +- `idx_activity_log_profile` (profile_id, created DESC) +- `idx_activity_log_action` (action, created DESC) + +--- + +## 5. Ziele & Training + +### 5.1 goals + +**Beschreibung:** Nutzer-definierte Ziele + +| Spalte | Typ | Nullable | Default | Beschreibung | +|--------|-----|----------|---------|--------------| +| id | uuid | NO | uuid_generate_v4() | PK | +| profile_id | uuid | NO | - | FK zu profiles.id | +| title | varchar(255) | NO | - | Zieltitel | +| goal_type | varchar(50) | NO | - | Zieltyp (weight, body_fat, etc.) | +| category | varchar(50) | YES | NULL | Kategorie | +| start_date | date | YES | NULL | Startdatum | +| target_date | date | YES | NULL | Zieldatum | +| start_value | numeric(10,2) | YES | NULL | Startwert | +| target_value | numeric(10,2) | NO | - | Zielwert | +| current_value | numeric(10,2) | YES | NULL | Aktueller Wert | +| source_table | varchar(50) | YES | NULL | Datenquelle-Tabelle | +| source_column | varchar(50) | YES | NULL | Datenquelle-Spalte | +| status | varchar(20) | YES | 'active' | Status (active, completed, paused) | +| is_primary | boolean | YES | false | Primäres Ziel | +| priority | integer | YES | 0 | Priorität | +| notes | text | YES | NULL | Notizen | +| created_at | timestamp | YES | now() | Erstellungsdatum | +| updated_at | timestamp | YES | now() | Letztes Update | + +**Constraints:** +- PK: `id` +- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE + +**Indizes:** +- `idx_goals_profile` (profile_id) +- `idx_goals_status` (profile_id, status) +- `idx_goals_primary` (profile_id, is_primary) WHERE is_primary = true +- `idx_goals_category_priority` (profile_id, category, priority) + +--- + +### 5.2 goal_type_definitions + +**Beschreibung:** Zieltyp-Definitionen (weight, body_fat, vo2max, etc.) + +| Spalte | Typ | Nullable | Default | Beschreibung | +|--------|-----|----------|---------|--------------| +| id | uuid | NO | uuid_generate_v4() | PK | +| type_key | varchar(50) | NO | - | Zieltyp-Key (unique) | +| display_name | varchar(100) | NO | - | Display-Name | +| category | varchar(50) | NO | - | Kategorie | +| unit | varchar(20) | YES | NULL | Einheit | +| description | text | YES | NULL | Beschreibung | +| is_active | boolean | YES | true | Aktiv | +| sort_order | integer | YES | 0 | Sortierung | +| created_at | timestamp | YES | now() | Erstellungsdatum | + +**Constraints:** +- PK: `id` +- UNIQUE: `type_key` + +**Indizes:** +- `idx_goal_type_definitions_category` (category) +- `idx_goal_type_definitions_active` (is_active) WHERE is_active = true +- `goal_type_definitions_type_key_key` UNIQUE (type_key) + +--- + +### 5.3 goal_focus_contributions + +**Beschreibung:** M:N Relationship Goals ↔ Focus Areas + +| Spalte | Typ | Nullable | Default | Beschreibung | +|--------|-----|----------|---------|--------------| +| goal_id | uuid | NO | - | PK, FK zu goals.id | +| focus_area_id | integer | NO | - | PK, FK zu focus_area_definitions.id | +| contribution_weight | numeric(5,2) | YES | 100 | Gewichtung (%) | + +**Constraints:** +- PK: `goal_id, focus_area_id` +- FK: `goal_id` → `goals(id)` ON DELETE CASCADE +- FK: `focus_area_id` → `focus_area_definitions(id)` ON DELETE CASCADE + +**Indizes:** +- `idx_gfc_goal` (goal_id) +- `idx_gfc_focus_area` (focus_area_id) + +--- + +### 5.4 goal_progress_log + +**Beschreibung:** Tägliche Fortschrittswerte für Custom Goals + +| Spalte | Typ | Nullable | Default | Beschreibung | +|--------|-----|----------|---------|--------------| +| id | uuid | NO | uuid_generate_v4() | PK | +| goal_id | uuid | NO | - | FK zu goals.id | +| profile_id | uuid | NO | - | FK zu profiles.id | +| date | date | NO | - | Datum | +| value | numeric(10,2) | NO | - | Wert | +| notes | text | YES | NULL | Notizen | +| created_at | timestamp | YES | now() | Erstellungsdatum | + +**Constraints:** +- PK: `id` +- UNIQUE: `goal_id, date` +- FK: `goal_id` → `goals(id)` ON DELETE CASCADE +- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE + +**Indizes:** +- `idx_goal_progress_goal_date` (goal_id, date DESC) +- `idx_goal_progress_profile` (profile_id) +- `unique_progress_per_day` UNIQUE (goal_id, date) + +--- + +### 5.5 focus_area_definitions + +**Beschreibung:** Focus Area Definitionen (26 Bereiche in 7 Kategorien) + +| Spalte | Typ | Nullable | Default | Beschreibung | +|--------|-----|----------|---------|--------------| +| id | serial | NO | - | PK | +| key | varchar(50) | NO | - | Focus Area Key (unique) | +| name | varchar(100) | NO | - | Display-Name | +| category | varchar(50) | NO | - | Kategorie | +| description | text | YES | NULL | Beschreibung | +| sort_order | integer | YES | 0 | Sortierung | +| is_active | boolean | YES | true | Aktiv | + +**Constraints:** +- PK: `id` +- UNIQUE: `key` + +**Indizes:** +- `idx_focus_area_key` (key) +- `idx_focus_area_category` (category) +- `focus_area_definitions_key_key` UNIQUE (key) + +--- + +### 5.6 user_focus_area_weights + +**Beschreibung:** User-spezifische Focus Area Gewichtungen + +| Spalte | Typ | Nullable | Default | Beschreibung | +|--------|-----|----------|---------|--------------| +| profile_id | uuid | NO | - | PK, FK zu profiles.id | +| focus_area_id | integer | NO | - | PK, FK zu focus_area_definitions.id | +| weight | numeric(5,2) | YES | 0 | Gewichtung (0-100) | +| updated_at | timestamp | YES | now() | Letztes Update | + +**Constraints:** +- PK: `profile_id, focus_area_id` +- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE +- FK: `focus_area_id` → `focus_area_definitions(id)` ON DELETE CASCADE + +**Indizes:** +- `idx_user_focus_weights_profile` (profile_id) +- `idx_user_focus_weights_area` (focus_area_id) + +--- + +### 5.7 user_focus_preferences + +**Beschreibung:** User Focus Preferences (deprecated/legacy) + +| Spalte | Typ | Nullable | Default | Beschreibung | +|--------|-----|----------|---------|--------------| +| id | uuid | NO | uuid_generate_v4() | PK | +| profile_id | uuid | NO | - | FK zu profiles.id | +| focus_areas | jsonb | YES | NULL | Focus Areas (legacy) | +| active | boolean | YES | true | Aktiv | +| created_at | timestamp | YES | now() | Erstellungsdatum | +| updated_at | timestamp | YES | now() | Letztes Update | + +**Constraints:** +- PK: `id` +- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE + +**Indizes:** +- `idx_focus_areas_profile_active` UNIQUE (profile_id) WHERE active = true + +--- + +### 5.8 training_types + +**Beschreibung:** Trainingstypen (29 Typen in 7 Kategorien) + +| Spalte | Typ | Nullable | Default | Beschreibung | +|--------|-----|----------|---------|--------------| +| id | serial | NO | - | PK | +| name | varchar(100) | NO | - | Typname | +| category | varchar(50) | NO | - | Kategorie | +| color | varchar(7) | YES | NULL | Farbe (Hex) | +| icon | varchar(50) | YES | NULL | Icon-Name | +| description | text | YES | NULL | Beschreibung | +| abilities | jsonb | YES | NULL | Abilities-Matrix | +| profile | jsonb | YES | NULL | Profil-Einstellungen | + +**JSONB-Struktur `abilities`:** +```json +{ + "kraft": 80, + "ausdauer": 20, + "beweglichkeit": 40, + "schnelligkeit": 10, + "koordination": 30, + "balance": 20, + "geist": 10 +} +``` + +**Constraints:** +- PK: `id` + +**Indizes:** +- `idx_training_types_category` (category) +- `idx_training_types_abilities` GIN (abilities) +- `idx_training_types_profile_enabled` (profile->'rule_sets'->'minimum_requirements'->>'enabled') WHERE profile IS NOT NULL + +--- + +### 5.9 activity_type_mappings + +**Beschreibung:** Lernendes Mapping-System (Workout-Name → Training Type) + +| Spalte | Typ | Nullable | Default | Beschreibung | +|--------|-----|----------|---------|--------------| +| id | uuid | NO | uuid_generate_v4() | PK | +| activity_type | varchar(255) | NO | - | Workout-Name (unique per profile) | +| training_type_id | integer | NO | - | FK zu training_types.id | +| profile_id | uuid | YES | NULL | NULL = global, sonst user-spezifisch | +| is_global | boolean | YES | true | Global oder user-spezifisch | +| created_at | timestamp | YES | now() | Erstellungsdatum | + +**Constraints:** +- PK: `id` +- UNIQUE: `activity_type, profile_id` +- FK: `training_type_id` → `training_types(id)` ON DELETE CASCADE + +**Indizes:** +- `idx_activity_type_mappings_type` (activity_type) +- `idx_activity_type_mappings_profile` (profile_id) +- `unique_activity_type_per_profile` UNIQUE (activity_type, profile_id) + +--- + +### 5.10 training_phases + +**Beschreibung:** Trainingsphasen (Auto-Detection Framework) + +| Spalte | Typ | Nullable | Default | Beschreibung | +|--------|-----|----------|---------|--------------| +| id | uuid | NO | uuid_generate_v4() | PK | +| profile_id | uuid | NO | - | FK zu profiles.id | +| phase_type | varchar(50) | NO | - | Phasentyp | +| start_date | date | NO | - | Startdatum | +| end_date | date | YES | NULL | Enddatum | +| status | varchar(20) | YES | 'suggested' | Status | +| confidence | numeric(5,2) | YES | NULL | Confidence (0-100) | +| description | text | YES | NULL | Beschreibung | +| created_at | timestamp | YES | now() | Erstellungsdatum | + +**Constraints:** +- PK: `id` +- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE + +**Indizes:** +- `idx_training_phases_profile` (profile_id) +- `idx_training_phases_status` (profile_id, status) +- `idx_training_phases_dates` (profile_id, start_date, end_date) + +--- + +### 5.11 fitness_tests + +**Beschreibung:** Standardisierte Fitness-Tests + +| Spalte | Typ | Nullable | Default | Beschreibung | +|--------|-----|----------|---------|--------------| +| id | uuid | NO | uuid_generate_v4() | PK | +| profile_id | uuid | NO | - | FK zu profiles.id | +| test_date | date | NO | - | Testdatum | +| test_type | varchar(50) | NO | - | Testtyp | +| result_value | numeric(10,2) | NO | - | Ergebnis | +| result_unit | varchar(20) | YES | NULL | Einheit | +| norm_category | varchar(50) | YES | NULL | Norm-Kategorie | +| notes | text | YES | NULL | Notizen | +| created_at | timestamp | YES | now() | Erstellungsdatum | + +**Constraints:** +- PK: `id` +- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE + +**Indizes:** +- `idx_fitness_tests_profile` (profile_id) +- `idx_fitness_tests_type` (profile_id, test_type) +- `idx_fitness_tests_date` (profile_id, test_date) + +--- + +### 5.12 training_parameters + +**Beschreibung:** Globale Trainings-Parameter (Quality-Filter Settings) + +| Spalte | Typ | Nullable | Default | Beschreibung | +|--------|-----|----------|---------|--------------| +| id | uuid | NO | uuid_generate_v4() | PK | +| key | varchar(100) | NO | - | Parameter-Key (unique) | +| category | varchar(50) | NO | - | Kategorie | +| value | text | NO | - | Wert | +| description | text | YES | NULL | Beschreibung | +| is_active | boolean | YES | true | Aktiv | +| created_at | timestamp | YES | now() | Erstellungsdatum | +| updated_at | timestamp | YES | now() | Letztes Update | + +**Constraints:** +- PK: `id` +- UNIQUE: `key` + +**Indizes:** +- `idx_training_parameters_key` (key) WHERE is_active = true +- `idx_training_parameters_category` (category) WHERE is_active = true +- `training_parameters_key_key` UNIQUE (key) + +--- + +### 5.13 weekly_goals + +**Beschreibung:** Wöchentliche Ziele (legacy) + +| Spalte | Typ | Nullable | Default | Beschreibung | +|--------|-----|----------|---------|--------------| +| id | uuid | NO | uuid_generate_v4() | PK | +| profile_id | uuid | NO | - | FK zu profiles.id | +| week_start | date | NO | - | Wochenbeginn | +| goals | jsonb | YES | NULL | Ziele | +| created_at | timestamp | YES | now() | Erstellungsdatum | + +**Constraints:** +- PK: `id` +- UNIQUE: `profile_id, week_start` +- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE + +**Indizes:** +- `idx_weekly_goals_profile_week` (profile_id, week_start DESC) +- `unique_weekly_goal_per_profile` UNIQUE (profile_id, week_start) + +--- + +## 6. System-Tabellen + +### 6.1 schema_migrations + +**Beschreibung:** Migrations-Tracking + +| Spalte | Typ | Nullable | Default | Beschreibung | +|--------|-----|----------|---------|--------------| +| id | serial | NO | - | PK | +| filename | varchar(255) | NO | - | Migrations-Dateiname (unique) | +| applied_at | timestamp | YES | now() | Anwendungsdatum | + +**Constraints:** +- PK: `id` +- UNIQUE: `filename` + +**Indizes:** +- `schema_migrations_filename_key` UNIQUE (filename) + +--- + +### 6.2 app_settings + +**Beschreibung:** Globale App-Einstellungen + +| Spalte | Typ | Nullable | Default | Beschreibung | +|--------|-----|----------|---------|--------------| +| key | varchar(100) | NO | - | PK, Setting-Key | +| value | text | YES | NULL | Setting-Value | +| updated_at | timestamp | YES | now() | Letztes Update | + +**Constraints:** +- PK: `key` + +--- + +## 7. Backup-Tabellen + +### 7.1 pipeline_configs_backup_pre_020 + +**Beschreibung:** Backup vor Migration 020 (Unified Prompt System) + +| Spalte | Typ | Nullable | Default | Beschreibung | +|--------|-----|----------|---------|--------------| +| id | uuid | YES | - | Original ID | +| name | varchar(100) | YES | - | Name | +| description | text | YES | NULL | Beschreibung | +| stages | jsonb | YES | - | Stages | +| active | boolean | YES | true | Aktiv | +| is_default | boolean | YES | false | Default | +| created_at | timestamp | YES | now() | Erstellungsdatum | + +**Keine Constraints oder Indizes.** + +--- + +### 7.2 vitals_log_backup_pre_015 + +**Beschreibung:** Backup vor Vitals-Refactoring (Migration 015) + +| Spalte | Typ | Nullable | Default | Beschreibung | +|--------|-----|----------|---------|--------------| +| id | uuid | NO | uuid_generate_v4() | PK | +| profile_id | uuid | NO | - | FK zu profiles.id | +| date | date | NO | - | Messdatum | +| resting_hr | integer | YES | NULL | Ruhepuls | +| hrv | integer | YES | NULL | HRV | +| systolic | integer | YES | NULL | Systolisch | +| diastolic | integer | YES | NULL | Diastolisch | +| pulse | integer | YES | NULL | Puls | +| vo2_max | numeric(5,2) | YES | NULL | VO2 Max | +| spo2 | integer | YES | NULL | SpO2 | +| respiratory_rate | integer | YES | NULL | Atemfrequenz | +| source | varchar(50) | YES | 'manual' | Datenquelle | +| notes | text | YES | NULL | Notizen | +| created_at | timestamp | YES | now() | Erstellungsdatum | + +**Constraints:** +- PK: `id` +- UNIQUE: `profile_id, date` +- FK: `profile_id` → `profiles(id)` ON DELETE CASCADE + +**Indizes:** +- `unique_vitals_per_day` UNIQUE (profile_id, date) + +--- + +## 8. ER-Diagramm + +### 8.1 Zentrale Entität: profiles + +``` +profiles (1) → (N) weight_log +profiles (1) → (N) circumference_log +profiles (1) → (N) caliper_log +profiles (1) → (N) activity_log +profiles (1) → (N) nutrition_log +profiles (1) → (N) sleep_log +profiles (1) → (N) vitals_baseline +profiles (1) → (N) blood_pressure_log +profiles (1) → (N) rest_days +profiles (1) → (N) photos +profiles (1) → (N) sessions +profiles (1) → (N) ai_insights +profiles (1) → (N) ai_usage +profiles (1) → (N) goals +profiles (1) → (N) goal_progress_log +profiles (1) → (N) training_phases +profiles (1) → (N) fitness_tests +profiles (1) → (N) access_grants +profiles (1) → (N) coupon_redemptions +profiles (1) → (N) user_feature_restrictions +profiles (1) → (N) user_feature_usage +profiles (1) → (N) user_activity_log +profiles (1) → (1) user_stats +profiles (1) → (N) user_focus_area_weights +profiles (1) → (N) user_focus_preferences +profiles (1) → (N) activity_type_mappings +profiles (1) → (N) weekly_goals +profiles (self) → (N) profiles (invited_by) +``` + +### 8.2 Trainings-Typen-Beziehungen + +``` +training_types (1) → (N) activity_log +training_types (1) → (N) activity_type_mappings +``` + +### 8.3 Membership-Beziehungen + +``` +tiers (1) → (N) tier_limits +tiers (1) → (N) coupons +tiers (1) → (N) access_grants + +features (1) → (N) tier_limits +features (1) → (N) user_feature_restrictions +features (1) → (N) user_feature_usage + +coupons (1) → (N) coupon_redemptions +coupons (1) → (N) access_grants +``` + +### 8.4 Ziele-Beziehungen + +``` +goals (1) → (N) goal_progress_log +goals (N) → (M) focus_area_definitions (via goal_focus_contributions) + +focus_area_definitions (N) → (M) profiles (via user_focus_area_weights) +``` + +### 8.5 Kardinalitäten + +| Beziehung | Typ | ON DELETE | +|-----------|-----|-----------| +| profiles → sessions | 1:N | CASCADE | +| profiles → weight_log | 1:N | CASCADE | +| profiles → activity_log | 1:N | CASCADE | +| profiles → goals | 1:N | CASCADE | +| profiles → access_grants | 1:N | CASCADE | +| training_types → activity_log | 1:N | NO ACTION | +| tiers → tier_limits | 1:N | CASCADE | +| goals → goal_focus_contributions | 1:N | CASCADE | +| focus_area_definitions → goal_focus_contributions | 1:N | CASCADE | + +--- + +## 9. Datenbank-Statistiken + +### 9.1 Tabellen-Übersicht + +| Kategorie | Anzahl Tabellen | +|-----------|-----------------| +| Kernmodule | 3 (profiles, sessions, user_stats) | +| Tracking | 10 (weight, circumference, caliper, activity, nutrition, sleep, vitals_baseline, blood_pressure, rest_days, photos) | +| KI & Analysen | 4 (ai_prompts, ai_insights, ai_usage, pipeline_configs) | +| Membership | 9 (tiers, features, tier_limits, coupons, coupon_redemptions, access_grants, user_feature_restrictions, user_feature_usage, user_activity_log) | +| Ziele & Training | 13 (goals, goal_type_definitions, goal_progress_log, goal_focus_contributions, focus_area_definitions, user_focus_area_weights, user_focus_preferences, training_types, activity_type_mappings, training_phases, fitness_tests, training_parameters, weekly_goals) | +| System | 2 (schema_migrations, app_settings) | +| Backup | 2 (pipeline_configs_backup_pre_020, vitals_log_backup_pre_015) | +| **Gesamt** | **43** | + +### 9.2 Foreign Keys + +| Tabelle | Anzahl FKs | +|---------|------------| +| access_grants | 3 | +| activity_log | 2 | +| activity_type_mappings | 1 | +| ai_insights | 1 | +| ai_usage | 1 | +| blood_pressure_log | 1 | +| caliper_log | 1 | +| circumference_log | 1 | +| coupon_redemptions | 2 | +| coupons | 1 | +| fitness_tests | 1 | +| goal_focus_contributions | 2 | +| goal_progress_log | 2 | +| goals | 1 | +| nutrition_log | 1 | +| photos | 1 | +| profiles | 1 (self-reference) | +| rest_days | 1 | +| sessions | 1 | +| sleep_log | 1 | +| tier_limits | 2 | +| training_phases | 1 | +| user_activity_log | 1 | +| user_feature_restrictions | 2 | +| user_feature_usage | 2 | +| user_focus_area_weights | 2 | +| user_focus_preferences | 1 | +| user_stats | 1 | +| vitals_baseline | 1 | +| vitals_log_backup_pre_015 | 1 | +| weekly_goals | 1 | +| weight_log | 1 | +| **Gesamt** | **42** | + +### 9.3 Indizes + +| Typ | Anzahl | +|-----|--------| +| Primary Keys | 43 | +| Unique Constraints | 36 | +| B-Tree Indizes | 86 | +| GIN Indizes (JSONB) | 7 | +| Partial Indizes | 12 | +| **Gesamt** | **184** | + +--- + +## 10. Wichtige Design-Patterns + +### 10.1 Profile-ID Isolation + +**Regel:** Jede Tabelle mit Nutzerdaten hat `profile_id` als Foreign Key. + +**Vorteile:** +- Strikte Datenisolation pro Nutzer +- Kein Zugriff auf fremde Daten möglich +- Performante Queries durch Index auf profile_id + +**Beispiel Query:** +```sql +SELECT * FROM weight_log +WHERE profile_id = '...' AND date >= '2026-01-01' +ORDER BY date DESC; +``` + +### 10.2 Source-Tracking + +**Regel:** Tabellen mit Import-Daten haben `source` Spalte. + +**Werte:** +- `'manual'` – manuell erfasst +- `'apple_health'` – Apple Health Import +- `'garmin'` – Garmin Import +- `'withings'` – Withings Import + +**Regel bei Reimport:** +```sql +INSERT INTO sleep_log (...) +ON CONFLICT (profile_id, date) +DO UPDATE SET ... WHERE sleep_log.source != 'manual'; +``` + +**Schutz:** Manuelle Einträge werden niemals überschrieben. + +### 10.3 JSONB für flexible Daten + +**Verwendung:** +- `ai_prompts.stages` – dynamische Pipeline-Stages +- `ai_insights.metadata` – Platzhalter-Werte +- `activity_log.evaluation` – Qualitätsbewertungen +- `sleep_log.sleep_segments` – Schlafphasen +- `rest_days.rest_config` – Ruhetag-Konfiguration +- `training_types.abilities` – Abilities-Matrix +- `training_parameters.value` – Parameter-Werte + +**Indizes:** +- GIN-Indizes für JSONB-Suchen +- Partial-Indizes mit JSONB-Extraktion + +**Beispiel:** +```sql +-- GIN Index für vollständige JSONB-Suche +CREATE INDEX idx_ai_prompts_stages ON ai_prompts USING gin (stages); + +-- Partial Index für spezifischen JSONB-Wert +CREATE INDEX idx_training_types_profile_enabled +ON training_types (((profile->'rule_sets'->'minimum_requirements'->>'enabled'))) +WHERE profile IS NOT NULL; +``` + +### 10.4 Composite Primary Keys + +**M:N Beziehungen:** +- `goal_focus_contributions` (goal_id, focus_area_id) +- `user_focus_area_weights` (profile_id, focus_area_id) + +**Zeitbasierte Uniqueness:** +- `weight_log` (profile_id, date) +- `sleep_log` (profile_id, date) +- `vitals_baseline` (profile_id, date) +- `blood_pressure_log` (profile_id, measured_at) + +### 10.5 Soft Deletes & Status-Felder + +**Status-Felder:** +- `goals.status` (active, completed, paused) +- `training_phases.status` (suggested, accepted, active, completed) +- `ai_prompts.active` (true/false) +- `coupons.is_active` (true/false) + +**Keine Hard Deletes bei:** +- ai_insights (Historische Analysen behalten) +- goals (Abgeschlossene Ziele für Statistiken) +- training_phases (Phasen-Historie) + +### 10.6 Auto-Updated Timestamps + +**Pattern:** +```sql +created_at TIMESTAMP DEFAULT NOW() +updated_at TIMESTAMP DEFAULT NOW() +``` + +**Trigger für auto-update:** +```sql +CREATE TRIGGER update_timestamp +BEFORE UPDATE ON tabelle +FOR EACH ROW +EXECUTE FUNCTION update_updated_at_column(); +``` + +--- + +## 11. Performance-Optimierungen + +### 11.1 Index-Strategie + +**Profile-ID + Date Indizes:** +```sql +idx_weight_log_profile_date (profile_id, date DESC) +idx_activity_profile_date (profile_id, date DESC) +idx_sleep_profile_date (profile_id, date DESC) +``` + +**Grund:** Häufigste Query-Pattern = "alle Einträge für User X, neuste zuerst" + +### 11.2 Partial Indizes + +**Beispiele:** +```sql +-- Nur aktive Prompts indizieren +CREATE INDEX idx_ai_prompts_active_sort +ON ai_prompts (active, sort_order); + +-- Nur Einträge mit Quality-Label +CREATE INDEX idx_activity_quality_label +ON activity_log (quality_label) +WHERE quality_label IS NOT NULL; + +-- Nur verifizierte E-Mails +CREATE INDEX idx_profiles_verification_token +ON profiles (verification_token) +WHERE verification_token IS NOT NULL; +``` + +**Vorteil:** Kleinere Index-Größe, schnellere Updates + +### 11.3 Composite Indizes + +**Multi-Column Indizes:** +```sql +idx_access_grants_active (profile_id, is_active, valid_until DESC) +idx_goals_category_priority (profile_id, category, priority) +idx_tier_limits_tier (tier_id, feature_id) +``` + +**Regel:** Häufigste WHERE-Klauseln in Index-Spalten-Reihenfolge + +### 11.4 GIN-Indizes für JSONB + +**JSONB-Suchen beschleunigen:** +```sql +CREATE INDEX idx_ai_prompts_stages ON ai_prompts USING gin (stages); +CREATE INDEX idx_training_types_abilities ON training_types USING gin (abilities); +CREATE INDEX idx_rest_days_config ON rest_days USING gin (rest_config); +``` + +**Verwendung:** +```sql +SELECT * FROM ai_prompts +WHERE stages @> '[{"name": "Stage 1"}]'::jsonb; +``` + +--- + +## 12. Migration-Historie + +### Migrations-Log + +| Migration | Datum | Beschreibung | +|-----------|-------|--------------| +| 001-010 | 2025-2026 | Basis-Tabellen (weight, circumference, caliper, nutrition, activity, photos, ai) | +| 011-012 | 2026-02 | Membership-System (tiers, features, access_grants) | +| 013-014 | 2026-03 | Sleep + Rest Days | +| 015 | 2026-03 | Vitals-Refactoring (vitals_baseline + blood_pressure_log) | +| 016-021 | 2026-03 | Training Types + Activity Mappings | +| 022 | 2026-03 | Goal System (goals, training_phases, fitness_tests) | +| 023-028 | 2026-03 | Goal System Extensions (auto-population, time-based tracking) | +| 029-030 | 2026-03 | Dynamic Focus Areas v2.0 (focus_area_definitions, user_focus_area_weights) | +| 031-032 | 2026-03 | Goal Focus Contributions (M:N Relationship) | + +**Aktueller Stand:** Migration 032 +**Tracking-Tabelle:** `schema_migrations` + +--- + +## 13. Bekannte Deprecated-Elemente + +### 13.1 Deprecated Tabellen + +| Tabelle | Ersetzt durch | Migration | +|---------|---------------|-----------| +| vitals_log | vitals_baseline + blood_pressure_log | 015 | +| pipeline_configs | ai_prompts (Unified System) | 020 | + +**Backup-Tabellen behalten für Rollback-Szenarien.** + +### 13.2 Deprecated Felder + +| Tabelle | Feld | Grund | +|---------|------|-------| +| profiles | goal_mode | Ersetzt durch Dynamic Focus Areas | +| user_focus_preferences | focus_areas | Ersetzt durch user_focus_area_weights | + +--- + +## 14. Datenintegrität + +### 14.1 Foreign Key Constraints + +**ON DELETE CASCADE:** +- Alle Tracking-Daten → profiles +- sessions → profiles +- goals → profiles +- access_grants → profiles + +**ON DELETE SET NULL:** +- access_grants → coupons +- coupons → tiers + +**ON DELETE NO ACTION:** +- activity_log → training_types +- profiles → profiles (invited_by) + +### 14.2 Check Constraints + +**Beispiele:** +```sql +-- profiles.tier +CHECK (tier IN ('free', 'plus', 'premium', 'trial')) + +-- goals.status +CHECK (status IN ('active', 'completed', 'paused')) + +-- training_phases.phase_type +CHECK (phase_type IN ('calorie_deficit', 'calorie_surplus', 'deload', 'maintenance', 'periodization')) +``` + +### 14.3 Unique Constraints + +**Pro User + Datum:** +- `weight_log` (profile_id, date) +- `sleep_log` (profile_id, date) +- `vitals_baseline` (profile_id, date) +- `blood_pressure_log` (profile_id, measured_at) +- `rest_days` (profile_id, date, focus) + +**Pro User + Feature:** +- `user_feature_restrictions` (profile_id, feature_id) +- `user_feature_usage` (profile_id, feature_id) +- `ai_usage` (profile_id, date) + +**Global:** +- `profiles` (email) +- `ai_prompts` (slug) +- `coupons` (code) +- `focus_area_definitions` (key) + +--- + +## 15. Nächste Schritte (Roadmap) + +### Phase 0c: Data Layer & Charts ✅ COMPLETED +- ✅ Data Layer Migration (97 Funktionen) +- ✅ Chart Endpoints (20 Endpoints E1-E5, A1-A8, R1-R5, C1-C4) +- 🔲 Frontend Chart Integration (IN PROGRESS) + +### Phase 1: Advanced Goals & Tracking +- 🔲 Goal Templates System +- 🔲 Automated Training Phase Detection +- 🔲 Goal-Aware Placeholders (120+) + +### Phase 2: Integrations +- 🔲 Apple Health Connector (persistent sync) +- 🔲 Garmin Connect Integration +- 🔲 Withings API Integration +- 🔲 Oura Ring Support + +### Phase 3: Advanced Features +- 🔲 Multi-Profile Support (Trainer → Clients) +- 🔲 Team/Group Features +- 🔲 Stripe Payment Integration +- 🔲 Advanced Correlations (Lag-Analysis) + +--- + +## Anhang + +### A. Tabellen-Index (alphabetisch) + +``` +access_grants +activity_log +activity_type_mappings +ai_insights +ai_prompts +ai_usage +app_settings +blood_pressure_log +caliper_log +circumference_log +coupon_redemptions +coupons +features +fitness_tests +focus_area_definitions +goal_focus_contributions +goal_progress_log +goal_type_definitions +goals +nutrition_log +photos +pipeline_configs +pipeline_configs_backup_pre_020 +profiles +rest_days +schema_migrations +sessions +sleep_log +tier_limits +tiers +training_parameters +training_phases +training_types +user_activity_log +user_feature_restrictions +user_feature_usage +user_focus_area_weights +user_focus_preferences +user_stats +vitals_baseline +vitals_log_backup_pre_015 +weekly_goals +weight_log +``` + +### B. Query-Beispiele + +**Gewichtsverlauf (letzte 90 Tage):** +```sql +SELECT date, weight +FROM weight_log +WHERE profile_id = '...' + AND date >= CURRENT_DATE - INTERVAL '90 days' +ORDER BY date DESC; +``` + +**Aktivitäten mit Quality-Label "high":** +```sql +SELECT a.date, a.description, t.name AS training_type, a.overall_score +FROM activity_log a +JOIN training_types t ON a.training_type_id = t.id +WHERE a.profile_id = '...' + AND a.quality_label = 'high' +ORDER BY a.date DESC; +``` + +**Aktuelle Tier-Limits für User:** +```sql +SELECT f.name, tl.limit_value, tl.limit_type +FROM profiles p +JOIN tier_limits tl ON p.tier = tl.tier_id +JOIN features f ON tl.feature_id = f.id +WHERE p.id = '...'; +``` + +**Schlafqualität (7 Tage):** +```sql +SELECT date, + total_duration_minutes, + (sleep_segments->>'deep')::int AS deep_minutes, + (sleep_segments->>'rem')::int AS rem_minutes, + quality_score +FROM sleep_log +WHERE profile_id = '...' + AND date >= CURRENT_DATE - INTERVAL '7 days' +ORDER BY date DESC; +``` + +**Aktive Ziele mit Progress:** +```sql +SELECT title, goal_type, target_value, current_value, + ROUND((current_value / target_value) * 100, 1) AS progress_pct +FROM goals +WHERE profile_id = '...' + AND status = 'active' +ORDER BY priority DESC, created_at ASC; +``` + +--- + +**Ende des Datenmodells** diff --git a/.claude/docs/technical/DATA_LAYER_EXTENSION_GUIDE.md b/.claude/docs/technical/DATA_LAYER_EXTENSION_GUIDE.md new file mode 100644 index 0000000..f53880e --- /dev/null +++ b/.claude/docs/technical/DATA_LAYER_EXTENSION_GUIDE.md @@ -0,0 +1,780 @@ +# Data Layer Extension Guide + +**Version:** 1.0 +**Erstellt:** 28. März 2026 +**Zielgruppe:** Entwickler, Claude Code +**Phase:** Post Phase 0c + +--- + +## Überblick + +Dieser Guide beschreibt, wie man das Data Layer System erweitert mit: +- Neuen Modulen +- Neuen Funktionen in bestehenden Modulen +- Neuen Berechnungslogiken +- Neuen Aggregationsmethoden + +**Voraussetzung:** Phase 0c abgeschlossen (Multi-Layer Architecture implementiert) + +--- + +## Modul-Struktur + +### Bestehende Module (Phase 0c) + +``` +backend/data_layer/ +├── __init__.py # Exports all functions +├── body_metrics.py # Gewicht, FM, LBM, Umfänge +├── nutrition_metrics.py # Kalorien, Protein, Makros +├── activity_metrics.py # Training, Volumen, Abilities +├── recovery_metrics.py # Sleep, RHR, HRV, Recovery Score +├── health_metrics.py # BP, VO2Max, Health Stability +├── goals.py # Active goals, progress +├── correlations.py # Lag-analysis, plateau detection +└── utils.py # Shared: confidence, baseline, outliers +``` + +### Modul-Namenskonventionen + +- **Singular:** `body_metrics.py` (nicht `bodies_metrics.py`) +- **Domain-focused:** Ein Modul pro fachlichem Bereich +- **Max ~500 Zeilen:** Bei >500 Zeilen → Split erwägen + +--- + +## Neue Funktion hinzufügen + +### Template + +```python +# backend/data_layer/.py + +def get__data( + profile_id: str, + days: int = 28, + **kwargs +) -> dict: + """ + [Eine Zeile: Was liefert diese Funktion?] + + [Optional: Ausführliche Beschreibung der Berechnung] + + Args: + profile_id: User profile ID + days: Analysis window (default 28) + **kwargs: Additional parameters (z.B., goal_mode) + + Returns: + { + "": , # Main result + "confidence": str, # REQUIRED: "high"/"medium"/"low"/"insufficient" + "data_points": int, # REQUIRED: Number of data points used + "": # Any additional data + } + + Confidence Rules: + - "high": >= X points + - "medium": >= Y points + - "low": >= Z points + - "insufficient": < Z points + + Example: + >>> data = get__data("profile_123", days=28) + >>> print(data['']) + 42.0 + """ + with get_db() as conn: + cur = get_cursor(conn) + + # 1. DATA RETRIEVAL + cur.execute(""" + SELECT ... + FROM ... + WHERE profile_id = %s + AND date >= NOW() - INTERVAL '%s days' + ORDER BY date + """, (profile_id, days)) + rows = cur.fetchall() + + # 2. CONFIDENCE CALCULATION + from data_layer.utils import calculate_confidence + confidence = calculate_confidence( + data_points=len(rows), + days_requested=days, + metric_type="general" # or "correlation" or "trend" + ) + + # 3. EARLY RETURN IF INSUFFICIENT + if confidence == 'insufficient': + return { + "confidence": "insufficient", + "data_points": len(rows), + # Include all fields with safe defaults + "": 0.0, + } + + # 4. CALCULATION + # ... your logic here ... + + # 5. RETURN STRUCTURED DATA + return { + "": result, + "confidence": confidence, + "data_points": len(rows), + # Additional fields as needed + } +``` + +### Pflicht-Felder + +**Jede Funktion MUSS zurückgeben:** +```python +{ + "confidence": str, # "high" | "medium" | "low" | "insufficient" + "data_points": int, # Anzahl verwendeter Datenpunkte +} +``` + +**Warum?** +- Confidence: UI kann User warnen bei niedriger Datenqualität +- Data Points: Debugging + Monitoring + +### Optionale Felder (Best Practices) + +```python +{ + "first_date": date, # Ältester Datenpunkt + "last_date": date, # Neuester Datenpunkt + "avg": float, # Durchschnitt + "std_dev": float, # Standardabweichung + "min": float, # Minimum + "max": float, # Maximum + "outliers": list[int], # Indices von Ausreißern +} +``` + +--- + +## Neue Berechnungslogik hinzufügen + +### 1. Statistik-Funktionen (utils.py) + +**Wenn du eine neue statistische Berechnung brauchst:** + +```python +# backend/data_layer/utils.py + +def calculate_( + values: list[float], + **kwargs +) -> float: + """ + [Beschreibung der Statistik] + + Args: + values: List of measurements + **kwargs: Additional parameters + + Returns: + Calculated statistic (float) + + Example: + >>> calculate_([1.0, 2.0, 3.0]) + 2.0 + """ + # Implementation + ... +``` + +**Beispiele:** +```python +def calculate_median_absolute_deviation(values: list[float]) -> float: + """ + MAD = median(|xi - median(x)|) + + More robust than standard deviation for outlier detection. + """ + import statistics + median = statistics.median(values) + deviations = [abs(x - median) for x in values] + return statistics.median(deviations) + + +def calculate_coefficient_of_variation(values: list[float]) -> float: + """ + CV = (std_dev / mean) * 100 + + Measures relative variability. + """ + import statistics + mean = statistics.mean(values) + std_dev = statistics.stdev(values) + return (std_dev / mean) * 100 if mean != 0 else 0.0 + + +def calculate_z_score(value: float, mean: float, std_dev: float) -> float: + """ + Z = (x - μ) / σ + + Standardized score. + """ + return (value - mean) / std_dev if std_dev != 0 else 0.0 +``` + +### 2. Aggregations-Funktionen (utils.py) + +**Neue Aggregationsmethoden für Goal Types:** + +```python +# backend/data_layer/utils.py + +def aggregate_data( + values: list[tuple], # [(date, value), ...] + method: str, + **kwargs +) -> float: + """ + Aggregate data points using specified method. + + Args: + values: List of (date, value) tuples + method: Aggregation method (see below) + **kwargs: Method-specific parameters + + Returns: + Aggregated value (float) + + Supported Methods: + - "latest": Most recent value + - "avg_7d": Average last 7 days + - "avg_30d": Average last 30 days + - "avg_90d": Average last 90 days + - "sum_7d": Sum last 7 days + - "sum_30d": Sum last 30 days + - "count_7d": Count last 7 days + - "count_30d": Count last 30 days + - "min_30d": Minimum last 30 days + - "max_30d": Maximum last 30 days + - "median_7d": Median last 7 days + - "median_30d": Median last 30 days + - "rolling_avg": Rolling average (window from kwargs) + - "percentile": Nth percentile (n from kwargs) + + Example: + >>> values = [(date1, 85.0), (date2, 84.5), ...] + >>> aggregate_data(values, "avg_7d") + 84.7 + """ + from datetime import date, timedelta + import statistics + + if not values: + return 0.0 + + # Sort by date (most recent first) + sorted_values = sorted(values, key=lambda x: x[0], reverse=True) + + if method == "latest": + return float(sorted_values[0][1]) + + elif method.startswith("avg_"): + days = int(method.split("_")[1].replace("d", "")) + cutoff = date.today() - timedelta(days=days) + recent = [v for d, v in sorted_values if d >= cutoff] + return statistics.mean(recent) if recent else 0.0 + + elif method.startswith("sum_"): + days = int(method.split("_")[1].replace("d", "")) + cutoff = date.today() - timedelta(days=days) + recent = [v for d, v in sorted_values if d >= cutoff] + return sum(recent) + + elif method.startswith("count_"): + days = int(method.split("_")[1].replace("d", "")) + cutoff = date.today() - timedelta(days=days) + return len([v for d, v in sorted_values if d >= cutoff]) + + elif method.startswith("min_") or method.startswith("max_"): + func_name, days_str = method.split("_") + days = int(days_str.replace("d", "")) + cutoff = date.today() - timedelta(days=days) + recent = [v for d, v in sorted_values if d >= cutoff] + if not recent: + return 0.0 + return min(recent) if func_name == "min" else max(recent) + + elif method.startswith("median_"): + days = int(method.split("_")[1].replace("d", "")) + cutoff = date.today() - timedelta(days=days) + recent = [v for d, v in sorted_values if d >= cutoff] + return statistics.median(recent) if recent else 0.0 + + elif method == "rolling_avg": + window = kwargs.get("window", 7) + if len(sorted_values) < window: + return statistics.mean([v for _, v in sorted_values]) + recent = sorted_values[:window] + return statistics.mean([v for _, v in recent]) + + elif method == "percentile": + n = kwargs.get("n", 50) # Default: median + values_only = [v for _, v in sorted_values] + return statistics.quantiles(values_only, n=100)[n - 1] if len(values_only) > 1 else values_only[0] + + else: + raise ValueError(f"Unknown aggregation method: {method}") +``` + +### 3. Korrelations-Funktionen (correlations.py) + +**Neue Korrelations-Analysen:** + +```python +# backend/data_layer/correlations.py + +def get___correlation( + profile_id: str, + days: int = 90, + max_lag: int = 7 +) -> dict: + """ + Correlation between and with lag analysis. + + Args: + profile_id: User profile ID + days: Analysis window + max_lag: Maximum lag in days to test + + Returns: + { + "correlation": float, # Pearson r at best lag + "best_lag": int, # Days of lag + "p_value": float, # Statistical significance + "confidence": str, + "paired_points": int, + "interpretation": str # "strong"/"moderate"/"weak"/"none" + } + + Interpretation: + |r| > 0.7: "strong" + |r| > 0.5: "moderate" + |r| > 0.3: "weak" + |r| <= 0.3: "none" + """ + # Implementation using scipy.stats or numpy + ... +``` + +--- + +## Neues Modul erstellen + +### Wann ein neues Modul? + +**Erstelle ein neues Modul wenn:** +- ✅ Neue fachliche Domäne (z.B., `stress_metrics.py`, `hormone_metrics.py`) +- ✅ Bestehendes Modul >500 Zeilen +- ✅ Klare thematische Trennung möglich + +**KEIN neues Modul wenn:** +- ❌ Nur 1-2 Funktionen (füge zu bestehendem Modul hinzu) +- ❌ Starke Abhängigkeit zu bestehendem Modul (merge statt split) + +### Modul-Template + +```python +# backend/data_layer/.py + +""" + - + +This module provides data functions for . + +Functions: + - get__data() + - get__data() + - ... + +Usage: + from data_layer. import get__data + + data = get__data(profile_id="123", days=28) +""" + +from typing import Optional, List, Dict, Tuple +from datetime import date, timedelta +from db import get_db, get_cursor + + +# ── PUBLIC FUNCTIONS ───────────────────────────────────────────── + +def get__data( + profile_id: str, + days: int = 28, + **kwargs +) -> dict: + """ + [Docstring as per template above] + """ + ... + + +# ── PRIVATE HELPERS ────────────────────────────────────────────── + +def _calculate_(values: list[float]) -> float: + """ + Internal helper for . + + NOT exported from module. + """ + ... + + +def _validate_(data: dict) -> bool: + """ + Internal validation helper. + """ + ... +``` + +### Exports in __init__.py + +```python +# backend/data_layer/__init__.py + +# Existing modules +from .body_metrics import * +from .nutrition_metrics import * +from .activity_metrics import * +from .recovery_metrics import * +from .health_metrics import * +from .goals import * +from .correlations import * +from .utils import * + +# NEW MODULE +from . import * + +__all__ = [ + # Existing exports... + + # NEW MODULE exports + 'get__data', + 'get__data', +] +``` + +--- + +## Integration mit Goal Types + +### Goal Type mit neuer Aggregationsmethode + +**Scenario:** Du hast eine neue Aggregationsmethode `avg_per_week_30d` implementiert. + +#### 1. In utils.py implementieren + +```python +# backend/data_layer/utils.py + +def aggregate_data(values, method, **kwargs): + # ... existing methods ... + + elif method == "avg_per_week_30d": + # Group by week, calculate average per week + from collections import defaultdict + weeks = defaultdict(list) + + for d, v in values: + week_start = d - timedelta(days=d.weekday()) + weeks[week_start].append(v) + + week_avgs = [sum(vals) / len(vals) for vals in weeks.values()] + return sum(week_avgs) / len(week_avgs) if week_avgs else 0.0 + + # ... +``` + +#### 2. In goal_utils.py nutzen + +```python +# backend/goal_utils.py + +def _fetch_by_aggregation_method( + cur, + profile_id: str, + source_table: str, + source_column: str, + aggregation_method: str, + date_column: str = 'date', + filter_conditions: dict = None +) -> Optional[float]: + """ + Fetch current value using aggregation method. + + Now supports: + - latest, avg_7d, avg_30d, sum_30d, count_7d, etc. + - avg_per_week_30d (NEW) + """ + # Fetch data + cur.execute(f""" + SELECT {date_column}, {source_column} + FROM {source_table} + WHERE profile_id = %s + ORDER BY {date_column} DESC + LIMIT 100 + """, (profile_id,)) + rows = cur.fetchall() + + if not rows: + return None + + # Use aggregate_data from utils + from data_layer.utils import aggregate_data + return aggregate_data(rows, aggregation_method) +``` + +#### 3. In Frontend verfügbar machen + +```javascript +// frontend/src/pages/AdminGoalTypesPage.jsx + +const AGGREGATION_METHODS = [ + { value: 'latest', label: 'Aktuellster Wert' }, + { value: 'avg_7d', label: 'Durchschnitt 7 Tage' }, + { value: 'avg_30d', label: 'Durchschnitt 30 Tage' }, + { value: 'sum_30d', label: 'Summe 30 Tage' }, + { value: 'avg_per_week_30d', label: 'Durchschnitt pro Woche (30d)' }, // NEW + // ... +] +``` + +--- + +## Testing-Strategie + +### Unit Tests für neue Funktionen + +```python +# backend/tests/test_data_layer.py + +import pytest +from data_layer. import get__data + +@pytest.fixture +def test_profile_with_data(db_connection): + """Create test profile with sample data""" + # Setup + profile_id = "test_profile_123" + # Insert test data into relevant tables + ... + yield profile_id + # Teardown + ... + + +def test_get_metric_data_sufficient(test_profile_with_data): + """Test with sufficient data points""" + data = get__data(test_profile_with_data, days=28) + + assert data['confidence'] in ['high', 'medium', 'low'] + assert data['data_points'] >= 18 + assert '' in data + assert isinstance(data[''], float) + + +def test_get_metric_data_insufficient(): + """Test with insufficient data""" + data = get__data("no_data_profile", days=28) + + assert data['confidence'] == 'insufficient' + assert data['data_points'] == 0 + + +def test_get_metric_data_edge_cases(test_profile_with_data): + """Test edge cases: outliers, missing values, etc.""" + # Test with extreme values + # Test with gaps in data + # Test with all same values + ... + + +def test_get_metric_data_parameters(test_profile_with_data): + """Test different parameter combinations""" + # Test different days values + for days in [7, 28, 90]: + data = get__data(test_profile_with_data, days=days) + assert data is not None + + # Test additional parameters + data = get__data(test_profile_with_data, days=28, goal_mode="strength") + assert data is not None +``` + +### Integration Tests + +```python +# backend/tests/test_charts_integration.py + +def test_chart_uses_data_layer(client, auth_token): + """Test that chart endpoint uses data layer correctly""" + response = client.get( + "/api/charts/", + headers={"X-Auth-Token": auth_token} + ) + + assert response.status_code == 200 + data = response.json() + + # Verify Chart.js structure + assert 'chart_type' in data + assert 'data' in data + assert 'metadata' in data + + # Verify metadata includes confidence + assert 'confidence' in data['metadata'] +``` + +--- + +## Performance Considerations + +### 1. Query Optimization + +**Problem:** N+1 Queries +```python +# ❌ BAD: +for goal_id in goal_ids: + cur.execute("SELECT * FROM goals WHERE id = %s", (goal_id,)) + # ... process each goal ... + +# ✅ GOOD: +cur.execute("SELECT * FROM goals WHERE id = ANY(%s)", (goal_ids,)) +``` + +**Problem:** Unindexed Columns +```sql +-- Add index if querying frequently by date range +CREATE INDEX IF NOT EXISTS idx_weight_log_profile_date +ON weight_log(profile_id, date DESC); +``` + +### 2. Caching + +**For expensive calculations:** +```python +from functools import lru_cache + +@lru_cache(maxsize=128) +def get_expensive_calculation(profile_id: str, days: int) -> dict: + """Cache results for 128 most recent calls""" + ... +``` + +**Note:** In-memory cache resets on restart. For persistent cache → Redis (later). + +### 3. Pagination + +**For large datasets:** +```python +def get__data( + profile_id: str, + days: int = 28, + limit: int = 1000, + offset: int = 0 +) -> dict: + """ + Paginated data retrieval. + """ + cur.execute(""" + SELECT ... + FROM ... + WHERE profile_id = %s + ORDER BY date DESC + LIMIT %s OFFSET %s + """, (profile_id, limit, offset)) +``` + +--- + +## Checkliste: Neue Funktion + +``` +[ ] Richtiges Modul gewählt (oder neues Modul erstellt) +[ ] Funktion implementiert mit korrekter Signatur +[ ] Docstring vollständig (Args, Returns, Example) +[ ] Confidence calculation included +[ ] Returns structured data (dict with primitives) +[ ] NO formatting (no strings with units) +[ ] Decimal → Float conversion wo nötig +[ ] Safe dict access (.get() mit defaults) +[ ] SQL parameter binding (keine String-Concatenation) +[ ] Unit tests geschrieben (sufficient/insufficient/edge cases) +[ ] Integration test geschrieben (wenn Chart/API endpoint) +[ ] Performance geprüft (< 500ms) +[ ] In __init__.py exportiert +[ ] Dokumentation aktualisiert (CLAUDE.md) +[ ] Commit mit aussagekräftiger Message +``` + +--- + +## Häufige Fehler + +### 1. Vergessen Confidence zu berechnen +```python +# ❌ WRONG: +return {"value": result} + +# ✅ CORRECT: +from data_layer.utils import calculate_confidence +confidence = calculate_confidence(len(rows), days, "general") +return {"value": result, "confidence": confidence, "data_points": len(rows)} +``` + +### 2. Formatierung im Data Layer +```python +# ❌ WRONG (Data Layer): +return {"slope": f"{slope:.2f} kg/Woche"} + +# ✅ CORRECT (Data Layer): +return {"slope": 0.23} # Just the number + +# ✅ FORMATTING (KI Layer): +return f"{data['slope']:.2f} kg/Woche" +``` + +### 3. Hardcoded Thresholds +```python +# ❌ WRONG: +if len(rows) < 18: # Magic number + return {"confidence": "insufficient"} + +# ✅ CORRECT: +confidence = calculate_confidence(len(rows), days, "general") +if confidence == "insufficient": + return {"confidence": "insufficient", ...} +``` + +--- + +## Support & Hilfe + +**Bei Fragen:** +1. Lies PLACEHOLDER_DEVELOPMENT_GUIDE.md +2. Prüfe bestehende Funktionen als Beispiel +3. Frag im Team oder erstelle Gitea Issue + +**Debugging:** +1. Unit Test schreiben +2. Print intermediate results +3. Check SQL query mit `EXPLAIN ANALYZE` +4. Profile mit `cProfile` wenn Performance-Problem + +--- + +**Autor:** Claude Sonnet 4.5 +**Version:** 1.0 +**Letzte Aktualisierung:** 28. März 2026 diff --git a/.claude/docs/technical/FEATURE_ENFORCEMENT_MAPPING.md b/.claude/docs/technical/FEATURE_ENFORCEMENT_MAPPING.md new file mode 100644 index 0000000..ef0385a --- /dev/null +++ b/.claude/docs/technical/FEATURE_ENFORCEMENT_MAPPING.md @@ -0,0 +1,337 @@ +# Feature Enforcement Mapping + +**Version:** v9c Phase 2 +**Status:** Planning +**Datum:** 20. März 2026 + +--- + +## Übersicht + +Dieses Dokument definiert, welche API-Endpoints welche Features prüfen müssen. + +--- + +## Feature-Katalog (nach Cleanup) + +### Data Features (count, never) +1. `weight_entries` - Gewichtseinträge +2. `circumference_entries` - Umfangsmessungen +3. `caliper_entries` - Hautfaltenmessungen +4. `nutrition_entries` - Ernährungseinträge +5. `activity_entries` - Trainingseinträge +6. `photos` - Progress-Fotos + +### AI Features +7. `ai_calls` - KI-Einzelanalysen (count, monthly) +8. `ai_pipeline` - KI-Pipeline-Analyse (boolean, never) + +### Export/Import Features +9. `data_export` - Daten exportieren (count, monthly) +10. `data_import` - Daten importieren (count, monthly) + +--- + +## Endpoint → Feature Mapping + +### Weight Router (`/api/weight`) + +| Endpoint | Method | Feature | Action | +|----------|--------|---------|--------| +| `/api/weight` | POST | `weight_entries` | Check before create, increment after | +| `/api/weight` | GET | - | No check (reading is always allowed) | +| `/api/weight/{id}` | PUT | - | No check (editing existing is allowed) | +| `/api/weight/{id}` | DELETE | - | No check (deleting is allowed) | + +**Rationale:** Limit bezieht sich auf Gesamtanzahl Einträge (COUNT), nicht auf API-Calls. + +--- + +### Circumference Router (`/api/circumference`) + +| Endpoint | Method | Feature | Action | +|----------|--------|---------|--------| +| `/api/circumference` | POST | `circumference_entries` | Check before, increment after | +| `/api/circumference` | GET | - | No check | +| `/api/circumference/{id}` | PUT | - | No check | +| `/api/circumference/{id}` | DELETE | - | No check | + +--- + +### Caliper Router (`/api/caliper`) + +| Endpoint | Method | Feature | Action | +|----------|--------|---------|--------| +| `/api/caliper` | POST | `caliper_entries` | Check before, increment after | +| `/api/caliper` | GET | - | No check | +| `/api/caliper/{id}` | PUT | - | No check | +| `/api/caliper/{id}` | DELETE | - | No check | + +--- + +### Nutrition Router (`/api/nutrition`) + +| Endpoint | Method | Feature | Action | +|----------|--------|---------|--------| +| `/api/nutrition` | POST | `nutrition_entries` | Check before, increment after | +| `/api/nutrition` | GET | - | No check | +| `/api/nutrition/{id}` | PUT | - | No check | +| `/api/nutrition/{id}` | DELETE | - | No check | + +--- + +### Activity Router (`/api/activity`) + +| Endpoint | Method | Feature | Action | +|----------|--------|---------|--------| +| `/api/activity` | POST | `activity_entries` | Check before, increment after | +| `/api/activity` | GET | - | No check | +| `/api/activity/{id}` | PUT | - | No check | +| `/api/activity/{id}` | DELETE | - | No check | + +--- + +### Photos Router (`/api/photos`) + +| Endpoint | Method | Feature | Action | +|----------|--------|---------|--------| +| `/api/photos/upload` | POST | `photos` | Check before, increment after | +| `/api/photos` | GET | - | No check | +| `/api/photos/{id}` | DELETE | - | No check (deleting is allowed) | + +--- + +### Insights Router (`/api/insights`) + +| Endpoint | Method | Feature | Action | +|----------|--------|---------|--------| +| `/api/insights/run/{slug}` | POST | `ai_calls` | Check before, increment after | +| `/api/insights/pipeline` | POST | `ai_pipeline` (boolean) | Check before (no increment for boolean) | +| `/api/insights` | GET | - | No check | +| `/api/insights/{id}` | GET | - | No check | + +**Rationale:** +- `ai_calls` = count-based, monthly reset +- `ai_pipeline` = boolean (enabled/disabled), no usage tracking + +--- + +### Export Router (`/api/export`) + +| Endpoint | Method | Feature | Action | +|----------|--------|---------|--------| +| `/api/export/csv` | GET | `data_export` | Check before, increment after | +| `/api/export/json` | GET | `data_export` | Check before, increment after | +| `/api/export/zip` | GET | `data_export` | Check before, increment after | + +**Rationale:** Ein Feature für alle 3 Export-Typen (konsolidiert). + +--- + +### Import Router (`/api/import`) + +| Endpoint | Method | Feature | Action | +|----------|--------|---------|--------| +| `/api/nutrition/import/fddb` | POST | `data_import` | Check before, increment after | +| `/api/activity/import/csv` | POST | `data_import` | Check before, increment after | +| `/api/import/zip` | POST | `data_import` | Check before, increment after | + +**Rationale:** Ein Feature für alle Import-Typen. + +--- + +## Implementation Pattern (Phase 2: Non-Blocking Logging) + +### Pattern für count-based Features + +```python +from auth import require_auth, check_feature_access, increment_feature_usage +import logging + +logger = logging.getLogger(__name__) + +@router.post("/api/weight") +def create_weight(data: dict, session: dict = Depends(require_auth)): + profile_id = session['profile_id'] + + # Phase 2: Check access (log only, don't block) + access = check_feature_access(profile_id, 'weight_entries') + if not access['allowed']: + logger.warning( + f"[FEATURE-LIMIT] User {profile_id} would be blocked: " + f"weight_entries limit_exceeded ({access['used']}/{access['limit']})" + ) + # NOTE: Phase 2 does NOT raise HTTPException - just logs! + + # Actual logic + # ... create weight entry ... + + # Phase 2: Increment usage (even if limit would be exceeded) + increment_feature_usage(profile_id, 'weight_entries') + + return {"ok": True, "id": entry_id} +``` + +### Pattern für boolean Features + +```python +@router.post("/api/insights/pipeline") +def run_pipeline(session: dict = Depends(require_auth)): + profile_id = session['profile_id'] + + # Phase 2: Check access (log only) + access = check_feature_access(profile_id, 'ai_pipeline') + if not access['allowed']: + logger.warning( + f"[FEATURE-LIMIT] User {profile_id} would be blocked: " + f"ai_pipeline disabled" + ) + # NOTE: Phase 2 does NOT raise HTTPException! + + # Actual logic + # ... run pipeline ... + + # No increment for boolean features + + return {"ok": True} +``` + +--- + +## Phase 3: Frontend Display (ohne Gates) + +### Usage-Counter anzeigen + +```jsx +// Example: WeightPage.jsx +import { useEffect, useState } from 'react' +import api from '../utils/api' + +function WeightPage() { + const [usage, setUsage] = useState(null) + + useEffect(() => { + // Fetch usage info + api.get('/api/features/weight_entries/check-access') + .then(res => setUsage(res)) + }, []) + + return ( +
+

Gewicht

+ + {/* Phase 3: Display usage (non-blocking) */} + {usage && usage.limit !== null && ( +
+ {usage.used} / {usage.limit} Einträge + {usage.remaining !== null && usage.remaining < 5 && ( + + Nur noch {usage.remaining} Einträge verfügbar + + )} +
+ )} + + {/* Button is NOT disabled in Phase 3 */} + +
+ ) +} +``` + +--- + +## Phase 4: Enforcement aktivieren (opt-in) + +### Feature-Flag System + +```python +# In app_settings table +INSERT INTO app_settings (key, value, description) +VALUES ('feature_enforcement_enabled', 'false', 'Enable/disable feature limit enforcement'); +``` + +### Modified Pattern (mit Enforcement) + +```python +def create_weight(data: dict, session: dict = Depends(require_auth)): + profile_id = session['profile_id'] + + # Check if enforcement is enabled + enforcement_enabled = get_app_setting('feature_enforcement_enabled', False) + + # Check access + access = check_feature_access(profile_id, 'weight_entries') + if not access['allowed']: + if enforcement_enabled: + # Phase 4: BLOCK + raise HTTPException( + status_code=429, + detail=f"Limit erreicht: {access['used']}/{access['limit']} Gewichtseinträge. Upgrade für mehr." + ) + else: + # Phase 2/3: LOG ONLY + logger.warning( + f"[FEATURE-LIMIT] User {profile_id} would be blocked: " + f"weight_entries limit_exceeded ({access['used']}/{access['limit']})" + ) + + # Actual logic + # ... +``` + +--- + +## Rollout-Strategie + +### Phase 2: Log-Only (1-2 Wochen) +- Alle Checks implementiert +- Nur Logging, keine Blocks +- **Monitoring**: Wie oft würde blockiert? +- **Analyse**: Gibt es falsche Limits? + +### Phase 3: Display-Only (1 Woche) +- Frontend zeigt Usage an +- Buttons NICHT disabled +- **User-Feedback**: Ist Usage-Anzeige klar? +- **Testing**: Funktioniert Counter korrekt? + +### Phase 4: Enforcement (schrittweise) +1. Admin-Account testen (enforcement=true nur für Admin) +2. Test-User (1-2 Accounts) +3. Rollout an alle (feature_enforcement_enabled=true) + +### Rollback-Plan +- `UPDATE app_settings SET value='false' WHERE key='feature_enforcement_enabled'` +- Sofortiger Rollback ohne Code-Deploy + +--- + +## Testing-Checklist + +### Unit-Tests (Backend) +- [ ] `check_feature_access()` mit allen Hierarchien +- [ ] `increment_feature_usage()` mit Reset-Logik +- [ ] Count-based Features (limit erreicht) +- [ ] Boolean Features (enabled/disabled) +- [ ] Monthly reset funktioniert + +### Integration-Tests +- [ ] POST weight-entry bis Limit erreicht +- [ ] Limit wird korrekt in Response angezeigt +- [ ] Reset nach Monatswechsel +- [ ] User-Override überschreibt Tier-Limit +- [ ] Access-Grant überschreibt Base-Tier + +### Frontend-Tests +- [ ] Usage-Counter aktualisiert nach Create +- [ ] Warning bei < 5 remaining +- [ ] Unlimited zeigt "∞" +- [ ] Disabled-Features zeigen Upgrade-Hinweis + +--- + +**Letzte Aktualisierung:** 20. März 2026 +**Autor:** Lars Stommer + Claude Opus 4.6 diff --git a/.claude/docs/technical/FRONTEND.md b/.claude/docs/technical/FRONTEND.md new file mode 100644 index 0000000..ec55843 --- /dev/null +++ b/.claude/docs/technical/FRONTEND.md @@ -0,0 +1,923 @@ +# Frontend-Dokumentation + +## Übersicht + +Das Frontend ist eine **Progressive Web App (PWA)** gebaut mit React 18, Vite und React Router. Die Architektur folgt einem **Component-based Pattern** mit Context-basiertem State Management (kein Redux). + +**Technologien:** +- React 18 (ohne TypeScript) +- Vite (Build Tool + Dev Server) +- React Router v6 (Client-side Routing) +- Recharts (Chart-Bibliothek) +- Lucide React (Icon Library) +- Day.js (Datum-Handling) + +**Bundle-Größe:** ~450 KB (gzip), PWA-Cache für Offline-Nutzung + +--- + +## Seiten-Übersicht + +| Seite | Route | Beschreibung | Auth | Admin | +|-------|-------|--------------|------|-------| +| **LoginScreen** | `/` (ohne Auth) | E-Mail + Passwort Login, SHA256→bcrypt Auto-Migration | ❌ | ❌ | +| **Register** | `/register` | Selbst-Registrierung + E-Mail-Verifizierung | ❌ | ❌ | +| **Verify** | `/verify?token=...` | E-Mail-Verifizierung nach Registrierung | ❌ | ❌ | +| **SetupScreen** | `/` (First Run) | Initiales Setup (erster Admin-Account) | ❌ | ❌ | +| **Dashboard** | `/` | Übersicht: Quick Weight, Stats, Charts, Widgets | ✅ | ❌ | +| **CaptureHub** | `/capture` | Quick-Entry-Auswahl (Gewicht/Umfänge/Caliper/Fotos/Aktivität/Schlaf) | ✅ | ❌ | +| **WeightScreen** | `/weight` | Gewichts-Tracking mit Inline-Edit | ✅ | ❌ | +| **CircumScreen** | `/circum` | Umfänge (8 Punkte) | ✅ | ❌ | +| **CaliperScreen** | `/caliper` | Hautfaltenmessungen (4 Methoden) | ✅ | ❌ | +| **MeasureWizard** | `/wizard` | Geführte Messung (Schritt-für-Schritt) | ✅ | ❌ | +| **ActivityPage** | `/activity` | Training + Trainingstypen (v9d) | ✅ | ❌ | +| **NutritionPage** | `/nutrition` | 3-Tab Layout: Entry / Import / Charts | ✅ | ❌ | +| **SleepPage** | `/sleep` | Schlaf-Tracking + Phasen + Apple Health Import | ✅ | ❌ | +| **RestDaysPage** | `/rest-days` | Ruhetage (Kraft/Cardio/Entspannung) | ✅ | ❌ | +| **VitalsPage** | `/vitals` | 3-Tab: Baseline / Blutdruck / Import | ✅ | ❌ | +| **History** | `/history` | Verlauf mit Charts (Gewicht, KF%, Umfänge, etc.) | ✅ | ❌ | +| **Analysis** | `/analysis` | KI-Auswertung + Pipeline | ✅ | ❌ | +| **SettingsPage** | `/settings` | Profil, PIN-Change, Export, Feature-Usage-Übersicht | ✅ | ❌ | +| **SubscriptionPage** | `/subscription` | Membership-Status (v9c) | ✅ | ❌ | +| **GuidePage** | `/guide` | Anleitungen (Caliper, Umfänge) | ✅ | ❌ | +| **AdminPanel** | `/admin/*` (in Settings) | Admin-Übersicht | ✅ | ✅ | +| **AdminTierLimitsPage** | `/admin/tier-limits` | Tier-Limits Matrix (v9c) | ✅ | ✅ | +| **AdminFeaturesPage** | `/admin/features` | Feature-Verwaltung (v9c) | ✅ | ✅ | +| **AdminTiersPage** | `/admin/tiers` | Tier-Verwaltung (v9c) | ✅ | ✅ | +| **AdminCouponsPage** | `/admin/coupons` | Coupon-System (v9c) | ✅ | ✅ | +| **AdminUserRestrictionsPage** | `/admin/user-restrictions` | User-spezifische Limits (v9c) | ✅ | ✅ | +| **AdminTrainingTypesPage** | `/admin/training-types` | Trainingstypen-CRUD (v9d) | ✅ | ✅ | +| **AdminActivityMappingsPage** | `/admin/activity-mappings` | Activity Mapping-Verwaltung (v9d) | ✅ | ✅ | +| **AdminTrainingProfiles** | `/admin/training-profiles` | Training Type Profiling (v9d #15) | ✅ | ✅ | + +**Gesamt:** 31 Seiten (22 User-facing, 9 Admin) + +--- + +## Komponenten + +### Wiederverwendbare Komponenten + +| Komponente | Props | Beschreibung | +|-----------|-------|--------------| +| **Avatar** | `profile, size` | Runder Avatar mit Initialen + Farbe | +| **Markdown** | `text` | Lightweight Markdown-Renderer (## Headings, **bold**, Listen) | +| **TrialBanner** | – | Trial-Countdown-Banner (3 Urgency-Level) | +| **EmailVerificationBanner** | – | E-Mail-Verifizierungs-Hinweis | +| **FeatureUsageOverview** | – | Tabelle mit allen Feature-Limits + Usage (v9c Phase 3) | +| **UsageBadge** | `feature` | Inline-Badge mit Limit-Status (z.B. "3/10") | +| **TrainingTypeDistribution** | `days` | Pie-Chart für Trainingstypen-Verteilung | +| **SleepWidget** | `days` | Dashboard-Widget mit Schlaf-Stats | +| **RestDaysWidget** | `weeks` | Dashboard-Widget mit aktuellen Ruhetagen | + +**Location:** `frontend/src/components/` + +### Inline-Komponenten (in Seiten definiert) + +**Dashboard.jsx:** +- `QuickWeight` – Schnelle Gewichts-Eingabe mit Feature-Limit-Check +- `StatCard` – Statistik-Karte mit Delta-Anzeige +- `Pill` – Status-Pill mit Tooltip (WHR, WHtR, KF, Protein Ø7T) + +**SettingsPage.jsx:** +- `ProfileForm` – Formular für Profil-Bearbeitung + +**NutritionPage.jsx:** +- `EntryTab` – Manuelle Eingabe + CSV-Import +- `ImportHistoryTab` – Import-Historie mit Gruppierung +- `ChartsTab` – Korrelationen + Wochendaten + +**VitalsPage.jsx:** +- `BaselineTab` – Morgenmessungen (RHR, HRV, VO2 Max, SpO2) +- `BloodPressureTab` – Blutdruck mehrfach täglich + Context-Tagging +- `ImportTab` – CSV-Import (Omron Deutsch, Apple Health) + +--- + +## Context / State Management + +### 1. AuthContext (`frontend/src/context/AuthContext.jsx`) + +**Verantwortlichkeit:** Session-Management + Login/Logout + +**State:** +```javascript +{ + session: { + token: string, + profile_id: string, + role: 'user' | 'admin', + profile: { id, name, email, tier, ... } + }, + loading: boolean, + needsSetup: boolean, // First-run detection +} +``` + +**Methods:** +- `login(credentials)` – Login mit E-Mail + Passwort (oder Legacy profile_id + PIN) +- `setup(formData)` – Initial Setup (First Run) +- `logout()` – Logout + Token-Löschung +- `setAuthFromToken(token, profile)` – Direkt-Login (für E-Mail-Verifizierung) +- `checkStatus()` – Auth-Status prüfen (beim App-Start) + +**Computed:** +- `isAdmin` – `session.role === 'admin'` +- `canUseAI` – `session.profile.ai_enabled !== 0` +- `canExport` – `session.profile.export_enabled !== 0` + +**Storage:** +- `localStorage.bodytrack_token` – Auth-Token +- `localStorage.bodytrack_active_profile` – Aktive Profile-ID + +**Flow:** +``` +App-Start → checkStatus() + ↓ +GET /api/auth/status → {needs_setup: true/false} + ↓ (wenn needs_setup = false) +GET /api/auth/me (mit Token aus localStorage) + ↓ +Session gesetzt → App.jsx zeigt Dashboard +``` + +### 2. ProfileContext (`frontend/src/context/ProfileContext.jsx`) + +**Verantwortlichkeit:** Aktives Profil + Profil-Liste + +**State:** +```javascript +{ + profiles: Array, // Alle Profile + activeProfile: Profile, // Aktuelles Profil + loading: boolean, +} +``` + +**Methods:** +- `setActiveProfile(profile)` – Profil wechseln (speichert in localStorage) +- `refreshProfiles()` – Profile neu laden (nach Update) + +**Flow:** +``` +session.profile_id ändert sich + ↓ +GET /api/profiles (mit X-Auth-Token) + ↓ +profiles gesetzt, activeProfile = match(session.profile_id) +``` + +**Hinweis:** Profile-Wechsel ist derzeit Single-User-optimiert (Multi-User-Support in Planung). + +--- + +## API-Integration (`frontend/src/utils/api.js`) + +**Zweck:** Zentrale API-Schnittstelle – **ALLE** API-Calls gehen über `api.js` + +**Features:** +- Automatisches Token-Injection (`X-Auth-Token` Header) +- Automatisches Profile-ID-Injection (`X-Profile-Id` Header, derzeit deprecated) +- Einheitliche Fehlerbehandlung (parst `{detail: "..."}` aus Backend) +- Typed-like API (alle Methoden dokumentiert) + +**Beispiel:** +```javascript +import { api } from '../utils/api' + +// GET-Request +const weights = await api.listWeight(365) // limit=365 + +// POST-Request +await api.upsertWeight('2026-03-23', 75.5, 'Morgens nüchtern') + +// DELETE-Request +await api.deleteWeight(entryId) + +// File-Upload +const result = await api.importCsv(file) +``` + +**Headers-Injection:** +```javascript +function hdrs(extra={}) { + const h = {...extra} + if (_profileId) h['X-Profile-Id'] = _profileId // Deprecated, bleibt für Legacy + const token = getToken() + if (token) h['X-Auth-Token'] = token + return h +} +``` + +**Error-Handling:** +```javascript +if (!res.ok) { + const err = await res.text() + try { + const parsed = JSON.parse(err) + throw new Error(parsed.detail || err) + } catch { + throw new Error(err) + } +} +``` + +**API-Methoden (285 Zeilen):** +- **Profiles:** `getActiveProfile, listProfiles, createProfile, updateProfile, deleteProfile` +- **Weight:** `listWeight, upsertWeight, updateWeight, deleteWeight, weightStats` +- **Circumferences:** `listCirc, upsertCirc, updateCirc, deleteCirc` +- **Caliper:** `listCaliper, upsertCaliper, updateCaliper, deleteCaliper` +- **Activity:** `listActivity, createActivity, updateActivity, deleteActivity, activityStats, bulkCategorizeActivities, importActivityCsv` +- **Nutrition:** `importCsv, listNutrition, nutritionCorrelations, nutritionWeekly, nutritionImportHistory, createNutrition, updateNutrition, deleteNutrition` +- **Photos:** `uploadPhoto, listPhotos, photoUrl` +- **AI:** `insightTrend, listPrompts, runInsight, insightPipeline, listInsights, latestInsights` +- **Export:** `exportZip, exportJson, exportCsv` (Download-Handling inkludiert) +- **Admin:** `adminListProfiles, adminCreateProfile, adminDeleteProfile, adminSetPermissions, changePin` +- **Auth:** `register, verifyEmail, resendVerification` +- **Subscription (v9c):** `getMySubscription, getMyUsage, getMyLimits, redeemCoupon, getFeatureUsage` +- **Admin Features (v9c):** `listFeatures, createFeature, updateFeature, deleteFeature` +- **Admin Tiers (v9c):** `listTiers, createTier, updateTier, deleteTier, getTierLimitsMatrix, updateTierLimit, updateTierLimitsBatch` +- **Admin User Restrictions (v9c):** `listUserRestrictions, createUserRestriction, updateUserRestriction, deleteUserRestriction` +- **Admin Coupons (v9c):** `listCoupons, createCoupon, updateCoupon, deleteCoupon, getCouponRedemptions` +- **Admin Access Grants (v9c):** `listAccessGrants, createAccessGrant, updateAccessGrant, revokeAccessGrant` +- **Training Types (v9d):** `listTrainingTypes, listTrainingTypesFlat, getTrainingCategories, adminListTrainingTypes, adminCreateTrainingType, adminUpdateTrainingType, adminDeleteTrainingType, getAbilitiesTaxonomy` +- **Training Profiles (v9d #15):** `getProfileStats, getProfileTemplates, getProfileTemplate, applyProfileTemplate, getTrainingParameters, batchEvaluateActivities` +- **Activity Mappings (v9d):** `adminListActivityMappings, adminCreateActivityMapping, adminUpdateActivityMapping, adminDeleteActivityMapping, adminGetMappingCoverage` +- **Sleep (v9d):** `listSleep, getSleepByDate, createSleep, updateSleep, deleteSleep, getSleepStats, getSleepDebt, getSleepTrend, getSleepPhases, importAppleHealthSleep` +- **Rest Days (v9d):** `listRestDays, createRestDay, getRestDay, updateRestDay, deleteRestDay, getRestDaysStats, validateActivity` +- **Vitals Baseline (v9d):** `listBaseline, getBaselineByDate, createBaseline, updateBaseline, deleteBaseline, getBaselineStats, importBaselineAppleHealth` +- **Blood Pressure (v9d):** `listBloodPressure, getBPByDate, createBloodPressure, updateBloodPressure, deleteBloodPressure, getBPStats, importBPOmron` + +--- + +## Berechnungs-Utils + +### 1. calc.js (`frontend/src/utils/calc.js`) + +**Zweck:** Körperfett-Berechnungen + Derived Metrics + +**Funktionen:** + +**`calcBodyFat(method, skinfolds, sex, age)`** +- Berechnet Körperfett-% nach 4 Methoden: + - `jackson3` – Jackson-Pollock 3-Punkt (Standard) + - `jackson7` – Jackson-Pollock 7-Punkt + - `durnin` – Durnin-Womersley 4-Punkt + - `parrillo` – Parrillo 9-Punkt (linear) +- Nutzt Siri-Formel: `BF% = (495 / D) - 450` +- Parameter: + - `method`: String ('jackson3', 'jackson7', 'durnin', 'parrillo') + - `skinfolds`: Object mit Hautfalten in mm (z.B. `{chest: 12, abdomen: 24, thigh: 18}`) + - `sex`: 'm' | 'f' + - `age`: Number + +**`getBfCategory(pct, sex)`** +- Kategorisiert Körperfett-% in Bereiche: + - Männer: Essenziell (<6%), Athletisch (6-14%), Fit (14-18%), Durchschnitt (18-25%), Übergewicht (>25%) + - Frauen: Essenziell (<14%), Athletisch (14-21%), Fit (21-25%), Durchschnitt (25-32%), Übergewicht (>32%) +- Returns: `{max, label, color, desc}` + +**`calcDerived(measurement, height)`** +- Berechnet abgeleitete Metriken: + - **WHR** (Waist-Hip-Ratio): `waist / hip` (Ziel: <0.90 M / <0.85 F) + - **WHtR** (Waist-to-Height-Ratio): `waist / height` (Ziel: <0.50) + - **FFMI** (Fat-Free Mass Index): `lean_mass / (height_m²)` (Natural Limit: ~25 M / ~22 F) +- Returns: `{whr, whtr, ffmi}` + +**`getRuleBasedAssessment(current, previous, profile)`** +- Generiert automatische Interpretationen basierend auf: + - Körperfett-Kategorie + - Änderungen seit letzter Messung + - FFMI (Muskel-Index) + - WHR / WHtR (Fettverteilung) + - Taillenumfang (WHO-Grenzwerte) +- Returns: `{findings: Array, summary: string, summaryType: 'good'|'warn'|'bad'}` + +**Guide-Daten:** +- `CIRCUMFERENCE_GUIDE` – Messanleitung für 8 Umfangspunkte (wo, wie, Posture, Tipps) +- `CALIPER_GUIDE` – Messanleitung für Hautfalten-Punkte + +### 2. interpret.js (`frontend/src/utils/interpret.js`) + +**Zweck:** Interpretation von Messwerten + +**`getInterpretation(measurement, profile, prevMeasurement)`** +- Analysiert Messung und generiert strukturierte Interpretation: + - Körperfett-Status (mit Kategorie + Farbe) + - WHR-Status + - WHtR-Status + - FFMI-Status + - BMI-Status + - Vergleich zur letzten Messung (Deltas) +- Returns: Array von Interpretations-Objects: + ```javascript + { + category: 'Körperfett', + icon: '🫧', + status: 'good' | 'warn' | 'bad', + title: 'Athletischer Körperfettanteil', + detail: 'Ausgezeichnet. Typisch für aktive Sportler...', + value: '12.5%', + badge: 'Athletisch', + color: '#1D9E75', + } + ``` + +**`getStatusColor(status)`** – Farbe für Status ('good'→Grün, 'warn'→Orange, 'bad'→Rot) + +**`getStatusBg(status)`** – Background-Farbe für Status + +### 3. Markdown.jsx (`frontend/src/utils/Markdown.jsx`) + +**Zweck:** Leichtgewichtiger Markdown-Renderer für KI-Texte + +**Unterstützte Syntax:** +- `# Heading 1`, `## Heading 2`, `### Heading 3` +- `**bold**`, `*italic*` +- `- Bullet List`, `1. Numbered List` +- `---` (Horizontal Rule) +- Line Breaks + +**Verwendung:** +```jsx + +``` + +**Vorteil:** Kein remark/rehype-Dependency – nur 134 Zeilen pures React + +--- + +## CSS-System (`frontend/src/app.css`) + +### CSS-Variablen (Light + Dark Mode) + +**Farben:** +```css +:root { + --bg: #f4f3ef; /* Hintergrund */ + --surface: #ffffff; /* Cards */ + --surface2: #f9f8f5; /* Inputs, Secondary */ + --border: rgba(0,0,0,0.09); /* Standard-Border */ + --border2: rgba(0,0,0,0.16);/* Input-Border */ + --text1: #1c1b18; /* Primär-Text */ + --text2: #5a5955; /* Sekundär-Text */ + --text3: #9a9892; /* Muted */ + --accent: #1D9E75; /* Primär-Farbe */ + --accent-light: #E1F5EE; /* Accent-Background */ + --accent-dark: #0a5c43; /* Hover */ + --danger: #D85A30; /* Fehler/Löschen */ + --warn: #EF9F27; /* Warnung */ +} + +@media (prefers-color-scheme: dark) { + :root { + --bg: #181816; + --surface: #222220; + --surface2: #1e1e1c; + --border: rgba(255,255,255,0.08); + --text1: #eeecea; + --text2: #aaa9a4; + --text3: #686762; + --accent-light: #04342C; + --accent-dark: #5DCAA5; + } +} +``` + +**Layout:** +```css +--nav-h: 64px; /* Bottom Navigation Höhe */ +--header-h: 52px; /* App-Header Höhe */ +``` + +### Utility Classes + +**Cards:** +```css +.card /* Standard-Card (white background, border, rounded) */ +.card-title /* Card-Überschrift (uppercase, small, muted) */ +``` + +**Stats:** +```css +.stats-grid /* 2-Column Grid für Stats */ +.stat-card /* Einzelne Stat-Card */ +.stat-val /* Wert (groß, bold) */ +.stat-label /* Label (klein, muted) */ +.stat-delta /* Delta (z.B. "+2.5 kg") */ +.delta-pos /* Positive Änderung (grün) */ +.delta-neg /* Negative Änderung (rot) */ +``` + +**Forms:** +```css +.form-section /* Formular-Sektion mit Abstand */ +.form-section-title /* Sektions-Titel (uppercase, border-bottom) */ +.form-row /* Zeile mit Label + Input + Unit */ +.form-label /* Label (links, flex:1) */ +.form-input /* Input (90px breit, text-align:right) */ +.form-unit /* Einheit (z.B. "kg", 24px breit) */ +.form-select /* Select-Dropdown */ +.form-sub /* Sub-Label (klein, muted) */ +``` + +**Buttons:** +```css +.btn /* Base Button */ +.btn-primary /* Primär-Button (accent) */ +.btn-secondary /* Sekundär-Button (grau) */ +.btn-danger /* Löschen-Button (rot) */ +.btn-full /* Full-Width Button */ +``` + +**Tabs:** +```css +.tabs /* Tab-Container (segmented control) */ +.tab /* Einzelner Tab */ +.tab.active /* Aktiver Tab (white background, shadow) */ +``` + +**Misc:** +```css +.badge /* Inline-Badge (klein, rounded) */ +.spinner /* Loading-Spinner (CSS-Animation) */ +.empty-state /* Leerer Zustand (zentriert, muted) */ +.muted /* Muted-Text (text3) */ +``` + +### Responsive Design + +**Mobile-First Approach:** +- Standard-Layout für 375px–600px (Mobile) +- Max-Width: 600px (zentriert auf Desktop) +- Bottom-Navigation für Mobile (64px hoch) +- Touch-optimierte Button-Größen (min 44px) + +**Bottom Navigation:** +```css +.bottom-nav { + position: fixed; + bottom: 0; + left: 50%; + transform: translateX(-50%); + width: 100%; + max-width: 600px; + height: var(--nav-h); + z-index: 20; +} +``` + +**Safe Area (iPhone):** +```css +padding-bottom: env(safe-area-inset-bottom, 0); /* Notch-Handling */ +``` + +**Desktop-Optimierung:** +- App zentriert mit max-width: 600px +- Kein responsives Layout für >600px (bewusst Mobile-optimiert) + +--- + +## PWA-Konfiguration + +### Service Worker + +**Location:** `frontend/public/service-worker.js` + +**Cache-Strategie:** +- **Static Assets:** Cache-First (HTML, CSS, JS, Icons) +- **API-Calls:** Network-First mit Fallback +- **Photos:** Cache-First mit Expiry + +**Registrierung:** +```javascript +// frontend/src/main.jsx +if ('serviceWorker' in navigator) { + navigator.serviceWorker.register('/service-worker.js') +} +``` + +### Manifest + +**Location:** `frontend/public/manifest.json` + +**Wichtige Felder:** +```json +{ + "name": "Mitai Jinkendo", + "short_name": "Mitai", + "theme_color": "#1D9E75", + "background_color": "#f4f3ef", + "display": "standalone", + "icons": [ + { "src": "/icon-192.png", "sizes": "192x192", "type": "image/png" }, + { "src": "/icon-512.png", "sizes": "512x512", "type": "image/png" } + ] +} +``` + +**Installation:** +- iOS: "Zum Home-Bildschirm" +- Android: "Installieren"-Prompt +- Desktop: Chrome/Edge Install-Button + +--- + +## Chart-Bibliothek (Recharts) + +**Verwendete Charts:** + +| Chart-Typ | Verwendung | Seite | +|-----------|-----------|-------| +| **LineChart** | Gewicht, Körperfett, Umfänge, Vitalwerte | Dashboard, History | +| **BarChart** | Wöchentliche Ernährung, Aktivität | NutritionPage, History | +| **PieChart** | Trainingstypen-Verteilung | Dashboard, ActivityPage | +| **ScatterChart** | Korrelationen (Gewicht vs. Kalorien) | NutritionPage | +| **ComposedChart** | Multi-Axis (Gewicht + KF% kombiniert) | History | + +**Standard-Konfiguration:** +```jsx + + + + + + + + + +``` + +**Farb-Schema:** +- Gewicht: `var(--accent)` (#1D9E75) +- Körperfett: `#D85A30` (Danger) +- Umfänge: Individual Colors (siehe `CIRCUMFERENCE_GUIDE`) +- 7-Tage-Durchschnitt: `var(--accent-dark)` (#0a5c43, gestrichelt) + +--- + +## Feature Usage Badges (v9c Phase 3) + +**Zweck:** Sichtbarkeit der Feature-Limits direkt in der UI + +**Komponenten:** + +### 1. UsageBadge (`components/UsageBadge.jsx`) + +**Inline-Badge mit Limit-Status:** +```jsx + +// Rendert: "5/10" (grün) oder "10/10 🔒" (rot) +``` + +**Logik:** +```javascript +const { allowed, used, limit, remaining } = await api.getFeatureUsage() +const color = allowed ? 'var(--accent)' : 'var(--danger)' +const text = limit === null ? '∞' : `${used}/${limit}` +``` + +**Verwendung:** In Buttons (z.B. "Speichern 5/10") + +### 2. FeatureUsageOverview (`components/FeatureUsageOverview.jsx`) + +**Tabelle mit allen Features:** +```jsx + +``` + +**Darstellung:** +| Feature | Genutzt | Limit | Verbleibend | Status | +|---------|---------|-------|-------------|--------| +| Gewichtseinträge | 45 | 100 | 55 | ✓ OK | +| KI-Aufrufe | 10 | 10 | 0 | 🔒 Limit erreicht | +| Daten-Export | 1 | 5 | 4 | ✓ OK | + +**Farbcodierung:** +- Grün: `allowed === true` +- Rot: `allowed === false` +- Gelb: `remaining < 10% && allowed` + +**Location:** Settings-Seite (Tab "Quota") + +--- + +## Routing-Architektur + +### App-Struktur (`App.jsx`) + +``` +AuthProvider + ↓ +ProfileProvider + ↓ +BrowserRouter + ↓ +AppShell + ├── Public Routes (ohne Auth) + │ ├── /register → Register + │ ├── /verify?token=... → Verify + │ └── /reset-password?token=... → ResetPassword + │ + ├── Auth Gates + │ ├── authLoading → Spinner + │ ├── needsSetup → SetupScreen + │ └── !session → LoginScreen + │ + └── Authenticated Routes + ├── Header (Logo + Logout + Avatar) + ├── Main (Scrollable Content) + │ └── Routes (31 Seiten) + └── Nav (Bottom Navigation, 5 Items) +``` + +### Navigation-Items + +```javascript +const links = [ + { to: '/', icon: , label: 'Übersicht' }, + { to: '/capture', icon: , label: 'Erfassen' }, + { to: '/history', icon: , label: 'Verlauf' }, + { to: '/analysis', icon: , label: 'Analyse' }, + { to: '/settings', icon: , label: 'Einst.' }, +] +``` + +**Besonderheit:** Active-State via React Router `NavLink` (`isActive` prop) + +### Route-Guards + +**Auth-Schutz:** +```jsx +if (!session) return +``` + +**Admin-Schutz:** +```jsx +// In AdminPanel-Seiten: +const { isAdmin } = useAuth() +if (!isAdmin) return
Nur für Admins
+``` + +**Setup-Check:** +```jsx +if (needsSetup) return +``` + +--- + +## Performance-Optimierungen + +### 1. Code Splitting + +**React.lazy() für Admin-Seiten:** +```javascript +const AdminPanel = React.lazy(() => import('./pages/AdminPanel')) +``` + +**Vorteil:** Admin-Code nicht im Initial Bundle (~80 KB gespart) + +### 2. Memoization + +**useMemo für teure Berechnungen:** +```javascript +const stats = useMemo(() => { + return calculateStats(data) +}, [data]) +``` + +**Verwendung:** Chart-Daten-Transformation, Aggregationen + +### 3. Lazy Loading + +**Images:** +```jsx + +``` + +**Charts:** +- Nur sichtbare Charts rendern (Intersection Observer in Planung) + +### 4. API-Call-Batching + +**Parallel-Loading:** +```javascript +const [stats, insights, weights] = await Promise.all([ + api.getStats(), + api.latestInsights(), + api.listWeight(30), +]) +``` + +**Verwendung:** Dashboard initial load + +--- + +## Error-Handling + +### 1. API-Fehler + +**Pattern in allen Seiten:** +```javascript +const [error, setError] = useState(null) + +try { + const data = await api.someEndpoint() + setData(data) +} catch(e) { + setError(e.message) // api.js parsed bereits {detail: "..."} +} finally { + setLoading(false) +} +``` + +**Anzeige:** +```jsx +{error && ( +
+ {error} +
+)} +``` + +### 2. Network-Fehler + +**Offline-Detection:** +```javascript +useEffect(() => { + const handleOnline = () => setOnline(true) + const handleOffline = () => setOnline(false) + window.addEventListener('online', handleOnline) + window.addEventListener('offline', handleOffline) + return () => { + window.removeEventListener('online', handleOnline) + window.removeEventListener('offline', handleOffline) + } +}, []) +``` + +**Anzeige:** Banner "Keine Internetverbindung – Änderungen werden gespeichert sobald Online" + +### 3. Form-Validierung + +**Client-Side:** +```javascript +if (!weight || weight < 20 || weight > 300) { + setError('Gewicht zwischen 20 und 300 kg') + return +} +``` + +**Server-Side:** +- Backend wirft `HTTPException(400, detail="...")` → Frontend zeigt `detail` + +--- + +## Besonderheiten & Design-Entscheidungen + +### 1. Warum kein TypeScript? + +**Entscheidung:** Bewusst auf TypeScript verzichtet + +**Gründe:** +- Schnellere Prototyping-Geschwindigkeit +- Weniger Build-Komplexität +- JSDoc-Kommentare für Dokumentation ausreichend +- Type Safety durch Backend (Pydantic validiert alle Inputs) + +### 2. Warum Context statt Redux? + +**Entscheidung:** Context API ausreichend für diesen Use-Case + +**Gründe:** +- Nur 2 globale States (Auth + Profile) +- Kein komplexes State-Update-Pattern nötig +- Weniger Boilerplate +- Performance ausreichend (keine häufigen Re-Renders) + +**Hinweis:** Bei >5 Contexts würde Redux Sinn machen + +### 3. Warum Custom Markdown statt remark/rehype? + +**Entscheidung:** Eigener Markdown-Renderer (134 Zeilen) + +**Gründe:** +- Nur Subset von Markdown benötigt (Headings, Bold, Listen) +- remark + rehype + plugins = ~200 KB Bundle-Size +- Custom-Renderer = 0 Dependencies +- Full Control über Styling + +### 4. Warum Recharts statt Chart.js? + +**Entscheidung:** Recharts für alle Charts + +**Gründe:** +- React-native (kein Canvas, sondern SVG) +- Declarative API passt zu React +- Responsive by default +- Kleineres Bundle als Chart.js + +### 5. Inline-Editing statt Modal-Forms + +**Entscheidung:** Inline-Edit für alle Listen (Gewicht, Ernährung, Vitalwerte) + +**Pattern:** +```javascript +const [editingId, setEditingId] = useState(null) + +{entries.map(e => ( + editingId === e.id + ? + : setEditingId(e.id)} /> +))} +``` + +**Vorteil:** +- Keine Modal-Komponente nötig +- Besserer Mobile-UX (kein Overlay) +- Schnelleres Editing (kein Dialog öffnen) + +--- + +## Bekannte Limitationen + +### 1. Desktop-Optimierung + +**Problem:** App ist Mobile-First, Desktop-Layout nicht optimiert + +**Aktuell:** Max-Width 600px, zentriert auf Desktop + +**Geplant (v10+):** Responsive Grid-Layout für Desktop (Sidebar + Multi-Column) + +### 2. Offline-Modus + +**Problem:** Service Worker cached nur Static Assets, nicht API-Responses + +**Aktuell:** Offline = Keine Daten-Eingabe möglich + +**Geplant (v10+):** IndexedDB für Offline-Queue + +### 3. Multi-Profil-Support + +**Problem:** Profile-Wechsel funktioniert, aber Session ist Single-User + +**Aktuell:** Logout + Login für Profil-Wechsel + +**Geplant (v9f+):** Multi-Session-Support (Switch ohne Logout) + +### 4. Accessibility + +**Problem:** ARIA-Labels fehlen, Keyboard-Navigation unvollständig + +**Aktuell:** Maus/Touch-optimiert + +**Geplant (v10+):** WCAG 2.1 AA Compliance + +--- + +## Testing + +**Aktueller Stand:** Kein automatisiertes Testing implementiert + +**Geplant (v10+):** +- Unit-Tests: Vitest für utils (calc.js, interpret.js) +- Component-Tests: React Testing Library +- E2E-Tests: Playwright für kritische Flows (Login, Gewicht-Eingabe, KI-Analyse) + +**Manual Testing:** +- Alle Features manuell auf iOS Safari, Android Chrome, Desktop Firefox getestet +- Regression-Tests bei jedem Deploy + +--- + +## Zusammenfassung + +**Architektur-Highlights:** +- ✅ 31 Seiten (22 User, 9 Admin) +- ✅ Context-basiertes State Management (Auth + Profile) +- ✅ Zentrale API-Schnittstelle (api.js) +- ✅ Berechnungs-Utils für Body-Metrics (calc.js, interpret.js) +- ✅ CSS-Variablen für Light/Dark Mode +- ✅ PWA mit Service Worker +- ✅ Recharts für alle Charts +- ✅ Feature Usage Badges (v9c Phase 3) +- ✅ Mobile-First Design (max-width 600px) +- ✅ Inline-Editing statt Modals +- ✅ Custom Markdown-Renderer (0 Dependencies) + +**Performance:** +- Initial Bundle: ~450 KB (gzip) +- Code Splitting: Admin-Seiten lazy-loaded +- API-Call-Batching: Parallel-Loading für Dashboard + +**Nächste Schritte:** +- Testing (Vitest + React Testing Library) +- Desktop-Responsive-Layout +- Offline-Modus (IndexedDB) +- Accessibility (WCAG 2.1 AA) diff --git a/.claude/docs/technical/INTERNAL_API_REFERENCE.md b/.claude/docs/technical/INTERNAL_API_REFERENCE.md new file mode 100644 index 0000000..783d7be --- /dev/null +++ b/.claude/docs/technical/INTERNAL_API_REFERENCE.md @@ -0,0 +1,212 @@ +# Internal API Reference + +**Purpose:** Prevent guessing function signatures and module exports. + +Last updated: 2026-03-28 + +--- + +## goal_utils.py + +### Focus Area Functions + +```python +def get_focus_weights(conn, profile_id: str) -> Dict[str, float] +``` +Returns user's focus area weights as `{area_key: weight_percent}`. +Keys are **English** (weight_loss, muscle_gain, etc.). + +```python +def get_focus_weights_v2(conn, profile_id: str) -> Dict[str, float] +``` +Newer version with auto-normalization. Use this for new code. + +```python +def get_primary_focus(conn, profile_id: str) -> str +``` +Returns area_key of highest weighted focus area. + +```python +def get_focus_description(focus_area: str) -> str +``` +Returns German description for a focus area key. + +--- + +### Goal Functions + +```python +def get_active_goals(profile_id: str) -> List[Dict] +``` +Returns ALL active goals for a profile. +Each dict has: id, type_key, current_value, target_value, is_primary, etc. + +**To filter by type:** +```python +goals = get_active_goals(profile_id) +weight_goals = [g for g in goals if g.get('type_key') == 'weight'] +``` + +```python +def get_goal_by_id(goal_id: str) -> Optional[Dict] +``` +Returns single goal by UUID. + +```python +def get_goal_type_config(conn, type_key: str) -> Optional[Dict[str, Any]] +``` +Returns goal type metadata from `goal_type_definitions` table: +- source_table, source_column, aggregation_method, unit + +```python +def get_current_value_for_goal(conn, profile_id: str, goal_type: str) -> Optional[float] +``` +Calculates current value for a goal type using its aggregation method. + +--- + +### Aggregation Functions + +```python +def calculate_current_value( + profile_id: str, + table: str, + column: str, + method: str, + date_column: str = 'date', + filter_conditions: Optional[List[Tuple[str, Any]]] = None +) -> Optional[float] +``` + +**Available methods:** +- `latest` - Most recent value +- `avg_7d` - 7-day average (numeric values only) +- `avg_30d` - 30-day average +- `max_30d` - Maximum in 30 days +- `avg_per_week_30d` - Count per week over 30 days (for frequency tracking) + +**Filter conditions format:** +```python +[("training_type", "strength"), ("quality", "good")] +``` + +--- + +## calculations/body_metrics.py + +### Score Functions + +```python +def calculate_body_progress_score(profile_id: str, focus_weights: Dict[str, float] = None) -> Optional[int] +``` +Returns 0-100 score for body composition progress. +Weighted by focus areas: weight_loss, muscle_gain, body_recomposition. + +**Sub-functions (private):** +- `_score_weight_trend(profile_id)` - Alignment with weight goal +- `_score_body_composition(profile_id)` - BF% + lean mass progress + +--- + +## calculations/nutrition_metrics.py + +### Score Functions + +```python +def calculate_nutrition_score(profile_id: str, focus_weights: Dict[str, float] = None) -> Optional[int] +``` +Returns 0-100 score for nutrition adherence. +Weighted by: protein_intake, calorie_balance, macro_consistency, meal_timing, hydration. + +**Sub-functions (private):** +- `_score_calorie_adherence(profile_id)` - Energy balance vs goal +- `_score_protein_adequacy(profile_id)` - Protein target adherence + +--- + +## calculations/activity_metrics.py + +### Score Functions + +```python +def calculate_activity_score(profile_id: str, focus_weights: Dict[str, float] = None) -> Optional[int] +``` +Returns 0-100 score for training quality. +Weighted by: strength, endurance (aerobic+anaerobic+cardiovascular), mobility/coordination. + +**Sub-functions (private):** +- `_score_training_quality(profile_id)` - Session quality % +- `_score_training_consistency(profile_id)` - Frequency adherence + +--- + +## calculations/scores.py + +### Main Score Aggregator + +```python +def calculate_goal_progress_score(profile_id: str) -> Optional[int] +``` +Master score aggregating body, nutrition, activity progress. +Weighted by user's category weights from focus areas. + +```python +def calculate_category_progress(profile_id: str, category: str) -> Optional[int] +``` +Maps category name to appropriate score function. +Categories: körper, ernährung, aktivität, erholung, vitalwerte, mental, lebensstil + +--- + +## Common Patterns + +### Getting Goals for a Specific Type + +```python +# ❌ DON'T (function doesn't exist): +from goal_utils import get_goals_by_type +goals = get_goals_by_type(profile_id, 'weight') + +# ✅ DO: +from goal_utils import get_active_goals +goals = get_active_goals(profile_id) +weight_goals = [g for g in goals if g.get('type_key') == 'weight'] +``` + +### Accessing Focus Area Weights + +```python +# ❌ DON'T (German keys don't exist in DB): +weight_focus = focus_weights.get('körpergewicht', 0) + +# ✅ DO (English keys from Migration 031): +weight_loss = focus_weights.get('weight_loss', 0) +muscle_gain = focus_weights.get('muscle_gain', 0) +``` + +### Querying Sleep Data + +```python +# ❌ DON'T: +SELECT duration FROM sleep_log + +# ✅ DO: +SELECT duration_minutes, deep_minutes, rem_minutes FROM sleep_log +``` + +--- + +## Verification Checklist + +Before using any function: + +1. ✅ Verify function exists: `grep "^def function_name" backend/module.py` +2. ✅ Check signature: `Read module.py` to see parameters +3. ✅ Check what it returns: Read docstring or implementation +4. ✅ Verify imported correctly: Check module path + +Before writing SQL: + +1. ✅ Check table schema: `grep "CREATE TABLE" backend/schema.sql` +2. ✅ Verify column names: Check migration files +3. ✅ Test query logic: Use actual data, not assumptions diff --git a/.claude/docs/technical/MEMBERSHIP_SYSTEM.md b/.claude/docs/technical/MEMBERSHIP_SYSTEM.md new file mode 100644 index 0000000..b48b4fa --- /dev/null +++ b/.claude/docs/technical/MEMBERSHIP_SYSTEM.md @@ -0,0 +1,1058 @@ +# Mitai Jinkendo - Membership & Subscription System (v9c) + +**Version:** v9c-dev +**Status:** Backend & Admin-UI komplett, Enforcement deaktiviert +**Letzte Aktualisierung:** 20. März 2026 + +--- + +## Inhaltsverzeichnis + +1. [Überblick](#überblick) +2. [Architektur-Entscheidungen](#architektur-entscheidungen) +3. [Datenbank-Schema](#datenbank-schema) +4. [Backend-API](#backend-api) +5. [Frontend-Komponenten](#frontend-komponenten) +6. [Feature-Enforcement-System](#feature-enforcement-system) +7. [Lessons Learned](#lessons-learned) +8. [Roadmap](#roadmap) + +--- + +## Überblick + +Das Mitai Jinkendo Membership-System (v9c) ist ein flexibles, tier-basiertes Subscription-System mit folgenden Kern-Features: + +### Implementierte Features ✅ + +- **4 Tier-Stufen**: free, basic, premium, selfhosted +- **Feature-Registry-Pattern**: Zentrale Definition aller limitierbaren Features +- **Flexible Limit-Matrix**: Admin kann Tier × Feature Limits konfigurieren +- **User-Override-System**: Individuelle Limits pro User +- **Coupon-System**: 3 Typen (single_use, multi_use_period, gift) +- **Coupon-Stacking**: Intelligente Pause/Resume-Logik bei temporären Zugriffen +- **Access-Grants**: Zeitlich begrenzte Tier-Zugriffe mit Quelle-Tracking +- **User-Activity-Log**: JSONB-basierte Aktivitätsverfolgung +- **Admin-UI**: Vollständige Verwaltungsoberfläche für alle Aspekte + +### Geplante Features 🔲 + +- Feature-Enforcement in Endpoints (needs redesign) +- Selbst-Registrierung mit E-Mail-Verifizierung +- Trial-System mit automatischem Downgrade +- Bonus-System (Login-Streaks) +- Stripe-Integration +- Partner-Integration (Wellpass, Hansefit) + +--- + +## Architektur-Entscheidungen + +### 1. Feature-Registry-Pattern + +**Entscheidung:** Alle limitierbaren Features werden zentral in einer `features` Tabelle definiert. + +**Rationale:** +- Neue Features können ohne Schema-Migration hinzugefügt werden +- Metadaten (name, description, category, unit) sind direkt verfügbar +- Konsistenz zwischen Backend-Checks und Frontend-Display +- Admin-UI kann automatisch generiert werden + +**Schema:** +```sql +CREATE TABLE features ( + id TEXT PRIMARY KEY, -- 'ai_calls', 'data_export', etc. + name TEXT NOT NULL, -- 'KI-Analysen', 'Daten exportieren' + description TEXT, + category TEXT, -- 'ai', 'export', 'data', 'integration' + limit_type TEXT DEFAULT 'count', -- 'count' oder 'boolean' + reset_period TEXT DEFAULT 'never', -- 'never', 'daily', 'monthly' + default_limit INTEGER, -- NULL = unbegrenzt + active BOOLEAN DEFAULT true, + sort_order INTEGER DEFAULT 0, + created TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated TIMESTAMP +); +``` + +**Beispiel-Features:** +```sql +-- Count-based mit monatlichem Reset +('ai_calls', 'KI-Analysen', 'KI-Auswertungen pro Monat', 'ai', 'count', 'monthly', 0, true) + +-- Boolean-Feature (an/aus) +('ai_pipeline', 'KI-Pipeline', 'Vollständige Pipeline-Analyse', 'ai', 'boolean', 'never', 0, true) + +-- Count-based ohne Reset (Gesamt-Limit) +('weight_entries', 'Gewichtseinträge', 'Anzahl Gewichtsmessungen', 'data', 'count', 'never', NULL, true) +``` + +--- + +### 2. Tier-System + +**Entscheidung:** Vereinfachte Tier-Tabelle ohne hart-codierte Limits. + +**Rationale:** +- Limits werden in separate `tier_limits` Tabelle ausgelagert +- Tiers können dynamisch hinzugefügt werden +- Pricing-Informationen zentral verwaltet +- Flexibilität für zukünftige Tier-Erweiterungen + +**Schema:** +```sql +CREATE TABLE tiers ( + id TEXT PRIMARY KEY, -- 'free', 'basic', 'premium', 'selfhosted' + slug TEXT UNIQUE NOT NULL, + name TEXT NOT NULL, -- 'Free', 'Basic', 'Premium', 'Self-Hosted' + description TEXT, + price_monthly DECIMAL(10,2), + price_yearly DECIMAL(10,2), + sort_order INTEGER DEFAULT 0, + active BOOLEAN DEFAULT true, + created TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated TIMESTAMP +); +``` + +**Initial Tiers:** +- **free**: Eingeschränkt (30 Daten-Einträge, 0 KI-Calls, kein Export) +- **basic**: Kernfunktionen (unbegrenzte Daten, 3 KI-Calls/Monat, Export erlaubt) +- **premium**: Alles unbegrenzt (inkl. KI-Pipeline, Connectoren) +- **selfhosted**: Admin-Tier für selbst-gehostete Installationen + +--- + +### 3. Zugriffs-Hierarchie + +**Entscheidung:** Drei-stufige Priorität für effektive Tier-Ermittlung. + +**Priorität (höchste zuerst):** +1. **Admin-Override**: `profiles.tier_locked = true` → nutzt `profiles.tier` +2. **Access-Grant**: Aktiver, nicht-pausierter Grant → nutzt `access_grants.granted_tier` +3. **Trial**: `profiles.trial_ends_at > NOW()` → nutzt trial tier +4. **Base Tier**: `profiles.tier` + +**Implementierung:** +```python +def get_effective_tier(profile_id: str) -> str: + """Get effective tier considering all overrides.""" + with get_db() as conn: + cur = get_cursor(conn) + + # 1. Check if tier is locked by admin + cur.execute("SELECT tier, tier_locked FROM profiles WHERE id = %s", (profile_id,)) + profile = cur.fetchone() + if profile['tier_locked']: + return profile['tier'] + + # 2. Check for active access grant + cur.execute(""" + SELECT granted_tier FROM access_grants + WHERE profile_id = %s + AND is_active = true + AND valid_from <= CURRENT_TIMESTAMP + AND (valid_until IS NULL OR valid_until > CURRENT_TIMESTAMP) + ORDER BY created DESC LIMIT 1 + """, (profile_id,)) + grant = cur.fetchone() + if grant: + return grant['granted_tier'] + + # 3. Check trial + cur.execute(""" + SELECT tier FROM profiles + WHERE id = %s AND trial_ends_at > CURRENT_TIMESTAMP + """, (profile_id,)) + trial = cur.fetchone() + if trial: + return 'premium' # or configurable trial tier + + # 4. Base tier + return profile['tier'] +``` + +**Rationale:** +- Admin kann User dauerhaft einem Tier zuweisen (Support-Fälle) +- Temporäre Zugriffe (Coupons, Wellpass) haben Vorrang vor Base-Tier +- Trial-Logik ist transparent und automatisch +- Base-Tier ist Fallback + +--- + +### 4. Coupon-System + +**Entscheidung:** 3 Coupon-Typen mit unterschiedlicher Stacking-Logik. + +**Typen:** + +#### 4.1 Single-Use Coupon +- **Verwendung:** Einmalig einlösbar (z.B. Geschenk-Coupon) +- **Verhalten:** Erstellt `access_grant` mit fester Laufzeit +- **Stacking:** Zeitlich sequenziell (startet nach Ablauf vorheriger Grants) +- **Beispiel:** "30 Tage Premium geschenkt" + +```sql +INSERT INTO coupons (code, type, grants_tier, duration_days, max_redemptions) VALUES +('FRIEND-GIFT-ABC', 'single_use', 'premium', 30, 1); +``` + +#### 4.2 Multi-Use Period Coupon +- **Verwendung:** Unbegrenzt einlösbar während Gültigkeitszeitraum +- **Verhalten:** Pausiert andere Grants, reaktiviert sie nach Ablauf +- **Stacking:** Override mit Pause/Resume +- **Beispiel:** "Wellpass-Monatszugang März 2026" + +```sql +INSERT INTO coupons (code, type, grants_tier, valid_from, valid_until, max_redemptions) VALUES +('WELLPASS-2026-03', 'multi_use_period', 'premium', '2026-03-01', '2026-03-31', NULL); +``` + +**Stacking-Logik:** +```python +# User hat Single-Use Grant (20 Tage verbleibend) +# User löst Wellpass-Coupon ein +# → Single-Use Grant wird pausiert (is_active=false, paused_by=wellpass_grant_id) +# → Nach Wellpass-Ablauf: Single-Use wird reaktiviert (noch 20 Tage) +``` + +#### 4.3 Gift Coupon +- **Verwendung:** Vom System generiert als Bonus +- **Verhalten:** Wie Single-Use, aber spezielles Tracking +- **Beispiel:** "Login-Streak Belohnung" + +--- + +### 5. User-Restrictions (Override-System) + +**Entscheidung:** User-spezifische Overrides haben höchste Priorität. + +**Schema:** +```sql +CREATE TABLE user_feature_restrictions ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + feature_id TEXT NOT NULL REFERENCES features(id) ON DELETE CASCADE, + limit_value INTEGER, -- NULL = unbegrenzt, überschreibt Tier-Limit + enabled BOOLEAN DEFAULT true, + reason TEXT, -- Warum wurde Override gesetzt? + set_by UUID REFERENCES profiles(id), -- Welcher Admin? + created TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated TIMESTAMP, + UNIQUE(profile_id, feature_id) +); +``` + +**Anwendungsfälle:** +- Admin gewährt einzelnem User mehr/weniger Zugriff +- Support-Fall: User bekommt temporär mehr KI-Calls +- Beta-Tester: Zugriff auf experimentelle Features +- Problem-User: Einschränkung bestimmter Features + +**Beispiel:** +```sql +-- User bekommt 100 KI-Calls/Monat statt Tier-Standard +INSERT INTO user_feature_restrictions (profile_id, feature_id, limit_value, reason, set_by) +VALUES ('user-uuid', 'ai_calls', 100, 'Beta-Tester', 'admin-uuid'); +``` + +--- + +## Datenbank-Schema + +### Übersicht aller v9c Tabellen + +``` +v9c Subscription System (11 neue Tabellen): +├── app_settings - Globale App-Konfiguration +├── tiers - Tier-Definitionen (free/basic/premium/selfhosted) +├── features - Feature-Registry (zentrale Feature-Definition) +├── tier_limits - Tier × Feature Matrix (Limits pro Tier) +├── user_feature_restrictions - User-spezifische Overrides +├── user_feature_usage - Usage-Tracking (für reset_period) +├── coupons - Coupon-Verwaltung (3 Typen) +├── coupon_redemptions - Einlösungs-Historie +├── access_grants - Zeitlich begrenzte Zugriffe +├── user_activity_log - Aktivitäts-Tracking (JSONB) +└── user_stats - Aggregierte Statistiken + +Erweiterte Tabellen: +└── profiles - Neue Spalten: tier, tier_locked, trial_ends_at, + email_verified, invited_by, contract_type +``` + +### Detaillierte Schema-Definitionen + +#### app_settings +```sql +CREATE TABLE app_settings ( + key TEXT PRIMARY KEY, + value TEXT, + value_type TEXT DEFAULT 'string', -- 'string', 'integer', 'boolean', 'json' + description TEXT, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_by UUID REFERENCES profiles(id) +); + +-- Beispiel-Einstellungen: +INSERT INTO app_settings (key, value, value_type, description) VALUES +('trial_days', '14', 'integer', 'Anzahl Tage Trial-Zugang'), +('trial_behavior', 'downgrade', 'string', 'downgrade|lock nach Trial-Ende'), +('allow_registration', 'false', 'boolean', 'Selbst-Registrierung erlaubt?'), +('default_tier_trial', 'premium', 'string', 'Tier während Trial'), +('gift_coupons_per_month', '3', 'integer', 'Max Geschenk-Coupons pro User/Monat'); +``` + +#### tier_limits (Kern der Matrix) +```sql +CREATE TABLE tier_limits ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + tier_id TEXT NOT NULL REFERENCES tiers(id) ON DELETE CASCADE, + feature_id TEXT NOT NULL REFERENCES features(id) ON DELETE CASCADE, + limit_value INTEGER, -- NULL = unbegrenzt, 0 = deaktiviert + enabled BOOLEAN DEFAULT true, + created TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated TIMESTAMP, + UNIQUE(tier_id, feature_id) +); + +-- Beispiel Free Tier: +INSERT INTO tier_limits (tier_id, feature_id, limit_value) VALUES +('free', 'weight_entries', 30), +('free', 'ai_calls', 0), -- Deaktiviert +('free', 'data_export', 0); -- Deaktiviert + +-- Beispiel Premium Tier: +INSERT INTO tier_limits (tier_id, feature_id, limit_value) VALUES +('premium', 'weight_entries', NULL), -- Unbegrenzt +('premium', 'ai_calls', NULL), -- Unbegrenzt +('premium', 'ai_pipeline', 1); -- Boolean: Aktiviert +``` + +#### access_grants (Zeitliche Zugriffe) +```sql +CREATE TABLE access_grants ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + granted_tier TEXT NOT NULL REFERENCES tiers(id), + valid_from TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + valid_until TIMESTAMP, -- NULL = unbegrenzt + source TEXT, -- 'coupon', 'admin_grant', 'trial' + source_reference TEXT, -- Coupon-Code oder Admin-Notiz + is_active BOOLEAN DEFAULT true, -- Kann pausiert werden + paused_at TIMESTAMP, + paused_by UUID REFERENCES access_grants(id), -- Welcher Grant hat pausiert? + created TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + created_by UUID REFERENCES profiles(id) +); + +-- Index für Performance +CREATE INDEX idx_access_grants_active ON access_grants(profile_id, is_active, valid_until DESC); +``` + +#### user_activity_log +```sql +CREATE TABLE user_activity_log ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + activity_type TEXT NOT NULL, -- 'login', 'coupon_redeemed', 'tier_change', etc. + details JSONB, -- Flexible Details + ip_address TEXT, + user_agent TEXT, + created TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- Index für Abfragen +CREATE INDEX idx_activity_log_profile ON user_activity_log(profile_id, created DESC); + +-- Beispiel-Einträge: +-- Login +INSERT INTO user_activity_log (profile_id, activity_type, details) VALUES +('uuid', 'login', '{"ip": "192.168.1.1", "device": "Chrome/Mac"}'); + +-- Coupon eingelöst +INSERT INTO user_activity_log (profile_id, activity_type, details) VALUES +('uuid', 'coupon_redeemed', '{"code": "FRIEND-GIFT-ABC", "tier": "premium", "days": 30}'); + +-- Tier-Änderung +INSERT INTO user_activity_log (profile_id, activity_type, details) VALUES +('uuid', 'tier_change', '{"from": "free", "to": "basic", "reason": "admin_grant"}'); +``` + +--- + +## Backend-API + +### Router-Übersicht + +``` +v9c Backend-Router (7 neue): +├── /api/subscription - User-facing: Abo-Status, Usage, Limits +├── /api/coupons - User: redeem; Admin: CRUD +├── /api/features - Admin: Feature-Registry CRUD + check-access Endpoint +├── /api/tiers - Admin: Tier-Verwaltung CRUD +├── /api/tier-limits - Admin: Matrix-Editor (Tier × Feature) +├── /api/user-restrictions- Admin: User-Override-System +└── /api/access-grants - Admin: Grant-Verwaltung (create, list, revoke) +``` + +### Wichtige Endpoints + +#### User-Facing + +**GET /api/subscription/me** +```json +{ + "tier": "basic", + "tier_source": "base", // 'base', 'trial', 'access_grant', 'admin_locked' + "trial_ends_at": null, + "access_grants": [ + { + "granted_tier": "premium", + "valid_until": "2026-04-15", + "source": "coupon", + "days_remaining": 25 + } + ] +} +``` + +**GET /api/subscription/usage** +```json +{ + "ai_calls": { + "limit": 3, + "used": 2, + "remaining": 1, + "reset_at": "2026-04-01T00:00:00" + }, + "data_export": { + "limit": 5, + "used": 0, + "remaining": 5, + "reset_at": "2026-04-01T00:00:00" + } +} +``` + +**POST /api/coupons/redeem** +```json +{ + "code": "FRIEND-GIFT-ABC" +} +``` +Response: +```json +{ + "success": true, + "granted_tier": "premium", + "valid_until": "2026-04-19", + "message": "30 Tage Premium-Zugang aktiviert!" +} +``` + +#### Admin-Only + +**GET /api/tier-limits** +```json +[ + { + "tier_id": "free", + "feature_id": "ai_calls", + "limit_value": 0, + "enabled": false + }, + { + "tier_id": "basic", + "feature_id": "ai_calls", + "limit_value": 3, + "enabled": true + } +] +``` + +**PUT /api/tier-limits** +```json +{ + "tier_id": "basic", + "feature_id": "ai_calls", + "limit_value": 5 +} +``` + +**POST /api/access-grants** +```json +{ + "profile_id": "user-uuid", + "granted_tier": "premium", + "valid_until": "2026-12-31", + "source": "admin_grant", + "source_reference": "Support-Fall #123" +} +``` + +--- + +## Frontend-Komponenten + +### Admin-UI (vollständig implementiert) + +#### 1. AdminFeaturesPage +**Route:** `/admin/features` + +**Funktionen:** +- Feature-Liste (sortierbar, filterbar) +- Neues Feature hinzufügen +- Feature bearbeiten (Name, Beschreibung, Limits, Reset-Period) +- Feature deaktivieren (soft-delete) + +**UI-Elemente:** +- Feature-Tabelle mit Spalten: Name, Kategorie, Limit-Typ, Reset-Period, Default-Limit +- Modal für Feature-Bearbeitung +- Kategorie-Filter (ai, export, data, integration) + +#### 2. AdminTiersPage +**Route:** `/admin/tiers` + +**Funktionen:** +- Tier-Liste mit CRUD +- Pricing (monatlich/jährlich) konfigurierbar +- Sort-Order für Anzeige-Reihenfolge +- Tier aktivieren/deaktivieren + +#### 3. AdminTierLimitsPage +**Route:** `/admin/tier-limits` + +**Funktionen:** +- **Matrix-Editor**: Tiers (Spalten) × Features (Zeilen) +- Responsive: Desktop = Tabelle, Mobile = Cards +- Inline-Editing mit Auto-Save +- Checkbox für Boolean-Features +- Number-Input für Count-Features +- NULL/∞ für unbegrenzt + +**UI-Konzept:** +``` +┌────────────────┬──────┬───────┬─────────┬────────────┐ +│ Feature │ Free │ Basic │ Premium │ Selfhosted │ +├────────────────┼──────┼───────┼─────────┼────────────┤ +│ Gewicht │ 30 │ ∞ │ ∞ │ ∞ │ +│ KI-Calls/Mon │ ☐ 0 │ ☑ 3 │ ☑ ∞ │ ☑ ∞ │ +│ KI-Pipeline │ ☐ │ ☐ │ ☑ │ ☑ │ +│ Export/Mon │ ☐ 0 │ ☑ 5 │ ☑ ∞ │ ☑ ∞ │ +└────────────────┴──────┴───────┴─────────┴────────────┘ +``` + +#### 4. AdminCouponsPage +**Route:** `/admin/coupons` + +**Funktionen:** +- Coupon-Liste (Code, Typ, Tier, Gültigkeit, Einlösungen) +- Neuer Coupon (3 Typen) +- Auto-Generate Code (Button) +- Redemption-Historie ansehen +- Coupon deaktivieren + +**Coupon-Typen-UI:** +``` +[ Single-Use ] [ Multi-Use Period ] [ Gift ] + +Single-Use: + Code: [FRIEND-GIFT-___] [Generate] + Tier: [Premium ▼] + Dauer: [30] Tage + Max Einlösungen: [1] + +Multi-Use Period: + Code: [WELLPASS-2026-__] [Generate] + Tier: [Premium ▼] + Gültig von: [2026-03-01] + Gültig bis: [2026-03-31] + Max Einlösungen: [∞] +``` + +#### 5. AdminUserRestrictionsPage +**Route:** `/admin/user-restrictions` + +**Funktionen:** +- User auswählen (Dropdown) +- Aktueller Tier anzeigen +- Feature-Liste mit: + - Tier-Limit (readonly, grau) + - Aktuelle Nutzung + - Override-Eingabe (leer = Tier-Standard) + - Save-Button pro Feature +- "Alle Overrides entfernen" Button + +**UI-Konzept:** +``` +User: [Lars Stommer ▼] Tier: selfhosted + +┌─────────────┬────────────┬─────────┬──────────┬────────┐ +│ Feature │ Tier-Limit │ Genutzt │ Override │ Aktion │ +├─────────────┼────────────┼─────────┼──────────┼────────┤ +│ KI-Calls │ ∞ │ 5/∞ │ [100] │ [Save] │ +│ Export │ ∞ │ 2/∞ │ [ ] │ [Save] │ +│ Gewicht │ ∞ │ 50/∞ │ [ ] │ [Save] │ +└─────────────┴────────────┴─────────┴──────────┴────────┘ + +[Alle Overrides entfernen] +``` + +#### 6. SubscriptionPage (User-facing) +**Route:** `/subscription` + +**Funktionen:** +- Aktueller Tier + Quelle +- Tier-Badge mit Icon +- Feature-Liste mit Limits +- Usage-Progress-Bars +- Coupon-Einlösung +- Access-Grant-Historie + +**UI-Konzept:** +``` +┌─────────────────────────────────────┐ +│ Dein Abo: [🟢 PREMIUM] │ +│ Quelle: Coupon (noch 25 Tage) │ +└─────────────────────────────────────┘ + +Features & Limits: +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +KI-Analysen 5/10 +[████████░░] 50% Reset: 1.4.2026 + +Daten-Export 2/5 +[████░░░░░░] 40% Reset: 1.4.2026 + +Gewichtseinträge 120/∞ +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +Coupon einlösen: +[_________________] [Einlösen] +``` + +--- + +## Feature-Enforcement-System + +### Status: ⚠️ DEAKTIVIERT (Rollback 20.03.2026) + +**Versuch-Implementation:** Commits 3745ebd, cbad50a, cd4d912, 8415509 +**Rollback:** Commit 4fcde4a + +### Was war geplant + +**Backend:** +```python +# In jedem limitierten Endpoint +def some_endpoint(session = Depends(require_auth)): + pid = session['profile_id'] + + # 1. Feature-Check + access = check_feature_access(pid, 'feature_slug') + if not access['allowed']: + if access['reason'] == 'feature_disabled': + raise HTTPException(403, "Feature nicht verfügbar") + elif access['reason'] == 'limit_exceeded': + raise HTTPException(429, f"Limit erreicht ({access['limit']})") + + # 2. Funktion ausführen + result = do_something() + + # 3. Usage inkrementieren + increment_feature_usage(pid, 'feature_slug') + + return result +``` + +**Frontend:** +```jsx +import { FeatureGate, FeatureBadge } from '../components/FeatureGate' + +function MyComponent() { + return ( + + + + ) +} +``` + +### Was schief ging + +1. **Frontend-Cache-Problem** + - FeatureGate cached Feature-Status + - Änderungen im Admin-Panel nicht sofort sichtbar + - Optimistic rendering zeigte Features kurz an bevor Block + +2. **Backend-Inkonsistenzen** + - Feature-IDs stimmten nicht mit DB überein (data_export vs export_csv) + - Manche Features fehlten komplett (csv_import) + - increment_feature_usage() hatte silent failures + +3. **Analyse-History zerstört** + - DELETE vor INSERT entfernt, aber das war GEWOLLT im Original + - Führte zu "keine Historisierung"-Beschwerde + - War Missverständnis der Original-Logik + +4. **Export-Buttons verschwunden** + - FeatureGate blockierte sofort + - Migration nicht gelaufen → Features existierten nicht + - canExport-Flag wurde überschrieben + +5. **Pipeline-Duplikate** + - Filter ließ 'pipeline' Prompt durch + - Scope-Bug: speicherte als 'gesamt' statt 'pipeline' + +### Lessons Learned + +1. **Nie ohne vollständiges Verständnis refactorn** + - DELETE-Logik war absichtlich (1 Analyse pro Scope) + - User wollte aber History → Requirements unklar + +2. **Migrations müssen atomar laufen** + - Features müssen VOR Enforcement existieren + - Auto-Migration muss zuverlässig sein + - Test-Daten für lokale Entwicklung + +3. **Frontend-Gates brauchen Refresh-Mechanismus** + - WebSocket oder Polling nach Admin-Änderungen + - Oder: "Neu laden" Button prominent anzeigen + - Optimistic rendering ist riskant + +4. **Feature-IDs konsolidieren** + - Ein Feature für Export, nicht drei (csv/json/zip) + - Konsistent zwischen DB, Backend-Code und Frontend + +5. **Inkrementelle Einführung** + - Erst Backend-Checks als Logs (nicht blockierend) + - Dann Frontend-Display (aber funktional) + - Dann Enforcement aktivieren nach Tests + +### Nächste Schritte für Re-Implementation + +1. **Phase 1: Cleanup** + - Feature-Definitionen konsolidieren + - Migration auf Idempotenz prüfen + - Test-Daten-Script erstellen + +2. **Phase 2: Backend Non-Blocking** + - Feature-Checks in Endpoints einbauen + - Aber nur loggen, nicht blockieren + - Monitoring: Wie oft würde blockiert? + +3. **Phase 3: Frontend Display** + - Usage-Counter anzeigen (ohne Gates) + - Admin kann sehen was genutzt wird + - Validierung gegen tatsächliche API-Calls + +4. **Phase 4: Enforcement (opt-in)** + - Per Feature-Flag aktivierbar + - Erst für Admin-Accounts testen + - Dann für Test-User + - Dann Rollout + +--- + +## Roadmap + +### v9c - Fertigstellung (Q2 2026) + +**Prio 1: Feature-Enforcement (Redesign)** +- ✅ Backend-Checks als Log-Only implementieren +- ✅ Frontend Usage-Display ohne Gates +- ✅ Feature-ID Konsolidierung +- ✅ Test-Suite für alle Limits +- ⏳ Opt-in Enforcement per Feature-Flag +- ⏳ Rollout-Plan mit Rollback-Option + +**Prio 2: Registrierung & Trial** +- Self-Registration mit E-Mail-Verifizierung +- Automatischer Trial-Start (14 Tage Premium) +- Trial-Countdown-Banner +- Auto-Downgrade nach Trial-Ende + +**Prio 3: App-Settings UI** +- Admin-Panel für globale Konfiguration +- Trial-Einstellungen (Dauer, Verhalten) +- Registrierungs-Toggle +- E-Mail-Template-Editor + +### v9d - Monetarisierung (Q3 2026) + +**Prio 1: Stripe-Integration** +- Self-Service Upgrade (Premium) +- Subscription-Management +- Webhook-Handler +- Rechnungs-E-Mails + +**Prio 2: Bonus-System** +- Login-Streak-Tracking +- Punkte-System +- Geschenk-Coupons (automatisch) +- Achievements + +**Prio 3: Fitness-Connectoren** +- OAuth2-Framework +- Strava-Connector +- Withings-Connector (Waage) +- Garmin-Connector + +### v9e - Partner & Enterprise (Q4 2026) + +**Prio 1: Partner-Integration** +- Wellpass-Authentifizierung +- Hansefit-Authentifizierung +- Partner-Admin-UI +- Usage-Reporting für Partner + +**Prio 2: Enterprise Features** +- Multi-Tenant-Support +- White-Label-Option +- SAML/SSO +- API-Keys für Drittanbieter + +--- + +## Technische Details + +### Performance-Überlegungen + +**check_feature_access() Caching:** +- Cache auf Request-Level (nicht global) +- Verhindert multiple DB-Calls pro Request +- TTL: 5 Minuten für Frontend-Checks + +**Database Indices:** +```sql +-- Kritische Indices für Performance +CREATE INDEX idx_tier_limits_lookup ON tier_limits(tier_id, feature_id); +CREATE INDEX idx_user_restrictions_lookup ON user_feature_restrictions(profile_id, feature_id); +CREATE INDEX idx_feature_usage_lookup ON user_feature_usage(profile_id, feature_id, reset_at); +CREATE INDEX idx_access_grants_active ON access_grants(profile_id, is_active, valid_until DESC); +``` + +### Security-Considerations + +**Coupon-Code-Generation:** +- Kryptografisch sicherer Zufallsgenerator +- 12 Zeichen: XXXXX-YYYYY-ZZ +- Kollisions-Check vor INSERT + +**Access-Grant-Validation:** +- Zeitstempel-Checks auf DB-Ebene (PostgreSQL TIMESTAMP) +- Keine Client-side Validierung für Enforcement +- is_active Flag für sofortigen Widerruf + +**User-Restrictions:** +- Nur Admins können setzen +- Audit-Log (set_by, reason) +- Kann nicht via User-API manipuliert werden + +--- + +## Testing-Strategie + +### Unit-Tests (Backend) + +```python +def test_check_feature_access_user_override(): + # Setup: User mit Free-Tier + Override für ai_calls=100 + profile_id = create_test_profile(tier='free') + set_user_restriction(profile_id, 'ai_calls', 100) + + # Test: Override hat Vorrang vor Tier-Limit + access = check_feature_access(profile_id, 'ai_calls') + assert access['allowed'] == True + assert access['limit'] == 100 + +def test_coupon_stacking_pause_resume(): + # Setup: Single-Use Grant aktiv + profile_id = create_test_profile() + grant1 = create_access_grant(profile_id, 'premium', days=30) + + # Test: Multi-Use Coupon pausiert Single-Use + redeem_coupon(profile_id, 'WELLPASS-CODE') + grant1_reloaded = get_access_grant(grant1.id) + assert grant1_reloaded['is_active'] == False + + # Test: Nach Wellpass-Ablauf wird Single-Use reaktiviert + expire_wellpass_grant(profile_id) + grant1_reloaded = get_access_grant(grant1.id) + assert grant1_reloaded['is_active'] == True +``` + +### Integration-Tests (API) + +```bash +# Scenario: Free User versucht KI-Analyse +curl -X POST /api/insights/run/gesamt \ + -H "X-Auth-Token: $TOKEN" \ + -H "X-Profile-Id: $FREE_USER_ID" + +# Expected: HTTP 403 "KI nicht verfügbar" + +# Scenario: User löst Coupon ein +curl -X POST /api/coupons/redeem \ + -H "X-Auth-Token: $TOKEN" \ + -d '{"code": "FRIEND-GIFT-ABC"}' + +# Expected: HTTP 200 + Access-Grant erstellt + +# Scenario: User mit Premium-Grant kann KI nutzen +curl -X POST /api/insights/run/gesamt \ + -H "X-Auth-Token: $TOKEN" \ + -H "X-Profile-Id: $FREE_USER_ID" + +# Expected: HTTP 200 + Analyse-Ergebnis +``` + +--- + +## Deployment-Notes + +### Migration-Reihenfolge + +```bash +# 1. Backup +pg_dump mitai_prod > backup_before_v9c.sql + +# 2. v9c Schema-Migration +psql mitai_prod < backend/migrations/v9c_subscription_system.sql + +# 3. Feature-Fixes (falls nötig) +psql mitai_prod < backend/migrations/v9c_fix_features.sql + +# 4. Backend neu starten (Auto-Migration läuft) +docker compose restart backend + +# 5. Verifizieren +docker logs mitai-api | grep "v9c Migration" +# Expected: "✅ Migration completed successfully!" +``` + +### Rollback-Plan + +```sql +-- Emergency Rollback v9c +DROP TABLE IF EXISTS user_stats CASCADE; +DROP TABLE IF EXISTS user_activity_log CASCADE; +DROP TABLE IF EXISTS coupon_redemptions CASCADE; +DROP TABLE IF EXISTS coupons CASCADE; +DROP TABLE IF EXISTS access_grants CASCADE; +DROP TABLE IF EXISTS user_feature_usage CASCADE; +DROP TABLE IF EXISTS user_feature_restrictions CASCADE; +DROP TABLE IF EXISTS tier_limits CASCADE; +DROP TABLE IF EXISTS features CASCADE; +DROP TABLE IF EXISTS tiers CASCADE; +DROP TABLE IF EXISTS app_settings CASCADE; + +-- Profiles-Spalten entfernen +ALTER TABLE profiles + DROP COLUMN tier, + DROP COLUMN tier_locked, + DROP COLUMN trial_ends_at, + DROP COLUMN email_verified, + DROP COLUMN email_verify_token, + DROP COLUMN invited_by, + DROP COLUMN contract_type, + DROP COLUMN contract_valid_until, + DROP COLUMN stripe_customer_id; +``` + +--- + +## Support & Troubleshooting + +### Häufige Probleme + +**Problem: User kann Feature nicht nutzen** +```sql +-- Diagnose: Effektiven Tier prüfen +SELECT tier, tier_locked, trial_ends_at FROM profiles WHERE id = 'user-uuid'; + +-- Diagnose: Access-Grants prüfen +SELECT * FROM access_grants +WHERE profile_id = 'user-uuid' + AND is_active = true +ORDER BY created DESC; + +-- Diagnose: Feature-Limit prüfen +SELECT tl.* FROM tier_limits tl +WHERE tier_id = (SELECT tier FROM profiles WHERE id = 'user-uuid') + AND feature_id = 'ai_calls'; + +-- Diagnose: User-Override prüfen +SELECT * FROM user_feature_restrictions +WHERE profile_id = 'user-uuid' AND feature_id = 'ai_calls'; +``` + +**Problem: Coupon lässt sich nicht einlösen** +```sql +-- Diagnose: Coupon gültig? +SELECT * FROM coupons WHERE code = 'COUPON-CODE'; + +-- Check: Bereits eingelöst? +SELECT * FROM coupon_redemptions +WHERE coupon_id = (SELECT id FROM coupons WHERE code = 'COUPON-CODE') + AND profile_id = 'user-uuid'; + +-- Check: Max Einlösungen erreicht? +SELECT c.max_redemptions, COUNT(cr.*) as current_redemptions +FROM coupons c +LEFT JOIN coupon_redemptions cr ON cr.coupon_id = c.id +WHERE c.code = 'COUPON-CODE' +GROUP BY c.id, c.max_redemptions; +``` + +### Admin-Tools + +**User-Tier manuell ändern:** +```sql +-- Tier setzen und locken +UPDATE profiles SET tier = 'premium', tier_locked = true WHERE id = 'user-uuid'; + +-- Access-Grant manuell erstellen +INSERT INTO access_grants (profile_id, granted_tier, valid_until, source, source_reference) +VALUES ('user-uuid', 'premium', '2026-12-31', 'admin_grant', 'Support-Fall #123'); +``` + +**Feature-Usage zurücksetzen:** +```sql +-- Alle Usage für User zurücksetzen +DELETE FROM user_feature_usage WHERE profile_id = 'user-uuid'; + +-- Nur bestimmtes Feature zurücksetzen +DELETE FROM user_feature_usage +WHERE profile_id = 'user-uuid' AND feature_id = 'ai_calls'; +``` + +--- + +## Anhang + +### Glossar + +- **Tier**: Subscription-Stufe (free, basic, premium, selfhosted) +- **Feature**: Limitierbare Funktionalität (ai_calls, data_export, etc.) +- **Limit**: Maximale Anzahl Nutzungen (count) oder an/aus (boolean) +- **Access-Grant**: Zeitlich begrenzte Tier-Berechtigung +- **Coupon**: Einlösbarer Code für Tier-Zugang +- **Override**: User-spezifische Abweichung vom Tier-Limit +- **Reset-Period**: Zeitraum nach dem Limit zurückgesetzt wird (never, daily, monthly) + +### Kontakt & Fragen + +- **Repository**: http://192.168.2.144:3000/Lars/mitai-jinkendo +- **Dokumentation**: `/docs/` im Repository +- **Issues**: Gitea Issues oder direkt an Lars + +--- + +**Letzte Aktualisierung:** 20. März 2026 +**Autor:** Lars Stommer + Claude Opus 4.6 +**Version:** v9c-dev diff --git a/.claude/docs/technical/MIGRATIONS.md b/.claude/docs/technical/MIGRATIONS.md new file mode 100644 index 0000000..e8d8ffb --- /dev/null +++ b/.claude/docs/technical/MIGRATIONS.md @@ -0,0 +1,392 @@ +# Database Migrations System + +**Version:** v9c +**Implementiert:** 2026-03-21 +**Dokumentiert:** 2026-03-21 + +--- + +## Übersicht + +Mitai Jinkendo verwendet ein automatisches Migrations-System für strukturierte Schema-Änderungen. Alle Migrationen werden beim Container-Start automatisch ausgeführt. + +## Architektur + +### Migration-Tracking + +**Tabelle:** `schema_migrations` +```sql +CREATE TABLE schema_migrations ( + id SERIAL PRIMARY KEY, + filename VARCHAR(255) UNIQUE NOT NULL, + applied_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +) +``` + +Diese Tabelle trackt, welche Migrationen bereits angewendet wurden. + +### Ablauf beim Container-Start + +1. **PostgreSQL Connection Check** (`db_init.py`) +2. **Schema-Initialisierung** (falls `profiles` Tabelle nicht existiert) + - Lädt `backend/schema.sql` (Basis-Schema) +3. **Migrations-System** (`run_migrations()`) + - Erstellt `schema_migrations` Tabelle (falls nicht vorhanden) + - Scannt `backend/migrations/` nach `.sql` Dateien + - Filtert nur nummerierte Dateien: `\d{3}_*.sql` (z.B. `001_feature.sql`) + - Sortiert alphabetisch (aufsteigende Reihenfolge) + - Wendet nur noch nicht angewendete Migrationen an + - Trackt jede erfolgreich angewendete Migration +4. **SQLite-zu-PostgreSQL Migration** (falls vorhanden) + +## Datei-Konventionen + +### Naming-Pattern + +**Format:** `XXX_descriptive_name.sql` + +- `XXX` = Dreistellige Nummer (001, 002, 003, ...) +- Unterstrich `_` als Trennzeichen +- Kleinbuchstaben, keine Leerzeichen +- `.sql` Extension + +**Beispiele:** +``` +✅ 001_subscription_system.sql +✅ 002_fix_features.sql +✅ 003_add_email_verification.sql + +❌ v9c_subscription_system.sql # Keine Nummer +❌ check_features.sql # Keine Nummer +❌ 1_feature.sql # Nur eine Ziffer +❌ 001-feature.sql # Bindestrich statt Unterstrich +``` + +### Datei-Struktur + +Jede Migration sollte folgende Struktur haben: + +```sql +-- ================================================================ +-- Migration XXX: Beschreibung +-- Version: vX.X +-- Date: YYYY-MM-DD +-- ================================================================ + +-- Beschreibung der Änderung +ALTER TABLE table_name ... + +-- Weitere SQL-Statements + +-- Kommentare für Dokumentation +COMMENT ON COLUMN table.column IS 'Beschreibung'; +``` + +### SQL-Einschränkungen + +**Erlaubt:** +- Standard SQL DDL (CREATE, ALTER, DROP) +- Standard SQL DML (INSERT, UPDATE, DELETE) +- CREATE INDEX, CREATE FUNCTION, etc. +- Multi-Statement-Scripts (durch `;` getrennt) + +**Nicht erlaubt:** +- psql Meta-Kommandos (`\echo`, `\set`, `\connect`, etc.) +- Interactive Commands (`\i`, `\include`) +- Transaktions-Kontrolle (automatisch gehandhabt) + +## Anwendung + +### Automatisch (Production/Dev) + +Migrationen werden **automatisch** beim Container-Start angewendet: + +```bash +# Container startet +docker compose up -d + +# db_init.py wird ausgeführt: +# 1. PostgreSQL ready check +# 2. Schema initialisiert (falls nötig) +# 3. Migrationen ausgeführt +# 4. SQLite-Migration (falls nötig) +``` + +**Log-Output:** +``` +═══════════════════════════════════════════════════════════ +MITAI JINKENDO - Database Initialization (v9c) +═══════════════════════════════════════════════════════════ + +Checking PostgreSQL connection... +✓ PostgreSQL ready + +Checking database schema... +✓ Schema already exists + +Running database migrations... + Found 2 pending migration(s)... + ✓ Applied: 001_subscription_system.sql + ✓ Applied: 003_add_email_verification.sql + +✓ Database initialization complete +``` + +### Manuell (Entwicklung) + +Für Testing während der Entwicklung: + +```bash +# Im laufenden Container +docker exec -it dev-mitai-api python3 /app/db_init.py + +# Oder direkt mit psql (für einzelne Migrationen) +docker exec -it dev-mitai-db psql -U mitai_dev -d mitai_dev \ + -f /path/to/migration.sql +``` + +## Migration erstellen + +### Schritt 1: Datei erstellen + +```bash +# Nächste freie Nummer ermitteln +ls backend/migrations/ | grep -E '^\d{3}_' | sort | tail -1 +# → 003_add_email_verification.sql + +# Neue Migration mit Nummer 004 +touch backend/migrations/004_add_new_feature.sql +``` + +### Schritt 2: SQL schreiben + +```sql +-- ================================================================ +-- Migration 004: Add New Feature +-- Version: v9d +-- Date: 2026-03-22 +-- ================================================================ + +-- Add new column +ALTER TABLE profiles +ADD COLUMN IF NOT EXISTS new_feature_enabled BOOLEAN DEFAULT TRUE; + +-- Create index if needed +CREATE INDEX IF NOT EXISTS idx_profiles_new_feature +ON profiles(new_feature_enabled) +WHERE new_feature_enabled = TRUE; + +-- Update existing data if needed +UPDATE profiles +SET new_feature_enabled = TRUE +WHERE tier = 'premium'; + +COMMENT ON COLUMN profiles.new_feature_enabled IS 'Feature flag for new feature'; +``` + +### Schritt 3: Testen + +```bash +# Lokal testen (Dev-Container) +git add backend/migrations/004_add_new_feature.sql +git commit -m "feat: add migration for new feature" +git push origin develop + +# Container wird neu gebaut und Migration automatisch angewendet +``` + +### Schritt 4: Verifizieren + +```bash +# Prüfen ob Migration angewendet wurde +docker exec -it dev-mitai-db psql -U mitai_dev -d mitai_dev \ + -c "SELECT * FROM schema_migrations ORDER BY applied_at DESC LIMIT 5;" + +# Output: +# id | filename | applied_at +# ---+------------------------------+------------------------ +# 4 | 004_add_new_feature.sql | 2026-03-22 10:30:15+00 +# 3 | 003_add_email_verification.sql| 2026-03-21 15:20:10+00 +# 2 | 002_fix_features.sql | 2026-03-20 12:10:05+00 +# 1 | 001_subscription_system.sql | 2026-03-20 12:10:00+00 +``` + +## Rollback-Strategie + +**Automatischer Rollback:** Nicht implementiert + +**Manueller Rollback:** + +1. **Identifizieren der problematischen Migration:** + ```bash + docker logs dev-mitai-api | grep "Failed to apply" + ``` + +2. **Migration aus Tracking entfernen:** + ```sql + DELETE FROM schema_migrations + WHERE filename = '004_broken_migration.sql'; + ``` + +3. **Änderungen manuell rückgängig machen:** + ```sql + -- Beispiel: Spalte entfernen + ALTER TABLE profiles DROP COLUMN new_feature_enabled; + ``` + +4. **Migration-Datei korrigieren** + ```bash + vim backend/migrations/004_broken_migration.sql + ``` + +5. **Container neu starten** (Migration wird erneut ausgeführt) + ```bash + docker compose restart api + ``` + +## Best Practices + +### ✅ DO + +- **Immer `IF NOT EXISTS` / `IF EXISTS` verwenden:** + ```sql + ALTER TABLE profiles ADD COLUMN IF NOT EXISTS email_verified BOOLEAN; + CREATE INDEX IF NOT EXISTS idx_profiles_email ON profiles(email); + ``` + +- **Idempotente Migrationen schreiben:** Migration kann mehrfach ausgeführt werden ohne Fehler + +- **Daten-Migrationen mit Bedacht:** + ```sql + UPDATE profiles + SET email_verified = TRUE + WHERE email IS NOT NULL AND email_verified IS NULL; + ``` + +- **Kommentare für Dokumentation:** + ```sql + COMMENT ON COLUMN profiles.email_verified IS 'Whether email has been verified'; + ``` + +- **Indices für Performance:** + ```sql + CREATE INDEX IF NOT EXISTS idx_profiles_verification_token + ON profiles(verification_token) + WHERE verification_token IS NOT NULL; -- Partial index + ``` + +### ❌ DON'T + +- **Keine psql Meta-Kommandos:** + ```sql + \echo "Starting migration" -- ❌ Funktioniert nicht + SELECT 'Starting migration'; -- ✅ Funktioniert + ``` + +- **Keine Breaking Changes ohne Staging:** + ```sql + ALTER TABLE profiles DROP COLUMN tier; -- ❌ App wird brechen + ``` + +- **Keine Hardcoded Values für produktive Daten:** + ```sql + INSERT INTO profiles (id, email, ...) VALUES (1, 'admin@example.com', ...); -- ❌ + ``` + +- **Keine Foreign Keys ohne Fallback:** + ```sql + -- ❌ Ohne ON DELETE Behavior + ALTER TABLE subscriptions + ADD CONSTRAINT fk_profile FOREIGN KEY (profile_id) REFERENCES profiles(id); + + -- ✅ Mit Cascade + ALTER TABLE subscriptions + ADD CONSTRAINT fk_profile FOREIGN KEY (profile_id) + REFERENCES profiles(id) ON DELETE CASCADE; + ``` + +## Troubleshooting + +### Migration schlägt fehl + +**Symptom:** Container startet nicht, Logs zeigen `✗ Failed to apply XXX.sql` + +**Lösung:** +1. Logs prüfen: `docker logs dev-mitai-api | tail -50` +2. Fehlerhafte Migration identifizieren +3. Aus Tracking entfernen + Schema manuell korrigieren +4. Migration-Datei fixen +5. Container neu starten + +### Migration wurde nicht angewendet + +**Symptom:** Neue Migration-Datei wird ignoriert + +**Ursachen:** +- Datei entspricht nicht dem Pattern `\d{3}_*.sql` +- Datei wurde bereits in `schema_migrations` getrackt +- Datei ist nicht im Container (`backend/migrations/` Volume gemountet?) + +**Lösung:** +```bash +# Prüfen ob Datei gemountet ist +docker exec -it dev-mitai-api ls -la /app/migrations/ + +# Prüfen ob bereits getrackt +docker exec -it dev-mitai-db psql -U mitai_dev -d mitai_dev \ + -c "SELECT * FROM schema_migrations WHERE filename = '004_feature.sql';" + +# Falls fälschlich getrackt, entfernen +docker exec -it dev-mitai-db psql -U mitai_dev -d mitai_dev \ + -c "DELETE FROM schema_migrations WHERE filename = '004_feature.sql';" + +# Container neu starten +docker compose restart api +``` + +### Datenbank-Schema inkonsistent + +**Symptom:** `schema_migrations` zeigt Migration als angewendet, aber Änderungen fehlen + +**Ursachen:** +- Migration wurde manuell ausgeführt, aber Tracking war kaputt +- Datenbank wurde zurückgesetzt, aber Tracking-Tabelle nicht + +**Lösung:** +```bash +# Schema neu aufbauen (ACHTUNG: Datenverlust!) +docker compose down -v # Löscht Volumes +docker compose up -d # Baut alles neu auf + +# ODER: Tracking-Tabelle manuell korrigieren +docker exec -it dev-mitai-db psql -U mitai_dev -d mitai_dev \ + -c "TRUNCATE schema_migrations; -- Alle Tracking-Einträge löschen" + +# Container neu starten → Alle Migrationen werden erneut angewendet +docker compose restart api +``` + +## Migrations-Historie + +| Nr. | Datei | Version | Datum | Beschreibung | +|-----|-------|---------|-------|--------------| +| 003 | `003_add_email_verification.sql` | v9c | 2026-03-21 | Email-Verifizierung (verification_token, email_verified, verification_expires) | +| 002 | `002_fix_features.sql` (manuell) | v9c | 2026-03-20 | Feature-System Fixes | +| 001 | `001_subscription_system.sql` (manuell) | v9c | 2026-03-20 | Membership-System (tiers, subscriptions, coupons, access_grants) | + +**Hinweis:** Migrationen 001 und 002 wurden vor Einführung des automatischen Systems manuell angewendet und sind nicht nummeriert. Ab Migration 003 läuft alles automatisch. + +--- + +## Referenzen + +- **Code:** `backend/db_init.py` (Zeilen 94-200) +- **Migrations-Ordner:** `backend/migrations/` +- **Tracking-Tabelle:** `schema_migrations` +- **Startup-Script:** `backend/startup.sh` (ruft `db_init.py` auf) +- **Dokumentation:** `.claude/docs/technical/MIGRATIONS.md` (diese Datei) + +--- + +**Dokumentiert:** 2026-03-21 +**Letzte Änderung:** 2026-03-21 diff --git a/.claude/docs/technical/PLACEHOLDER_DEVELOPMENT_GUIDE.md b/.claude/docs/technical/PLACEHOLDER_DEVELOPMENT_GUIDE.md new file mode 100644 index 0000000..7d65df7 --- /dev/null +++ b/.claude/docs/technical/PLACEHOLDER_DEVELOPMENT_GUIDE.md @@ -0,0 +1,799 @@ +# Placeholder Development Guide + +**Version:** 1.0 +**Erstellt:** 28. März 2026 +**Zielgruppe:** Entwickler, Claude Code + +--- + +## Überblick + +Dieses Dokument beschreibt, wie neue KI-Platzhalter hinzugefügt, getestet und dokumentiert werden. + +**Wichtig für Phase 0c:** Nach dem Refactoring zu Multi-Layer Architecture nutzen alle Platzhalter das Data Layer. Dieser Guide beschreibt beide Architekturen. + +--- + +## Phase 0b Architektur (Aktuell - bis Phase 0c) + +### Anatomie eines Platzhalters + +```python +# backend/placeholder_resolver.py + +def resolve_weight_28d_trend_slope(profile_id: str) -> str: + """ + Returns kg/week slope for 28-day weight trend. + + This function: + 1. Retrieves data from database + 2. Performs calculation + 3. Formats result for KI consumption + + Args: + profile_id: User profile ID + + Returns: + Formatted string (e.g., "0.23 kg/Woche") + or "Nicht genug Daten" if insufficient data + """ + with get_db() as conn: + cur = get_cursor(conn) + + # 1. DATA RETRIEVAL + cur.execute(""" + SELECT date, weight + FROM weight_log + WHERE profile_id = %s + AND date >= NOW() - INTERVAL '28 days' + ORDER BY date + """, (profile_id,)) + rows = cur.fetchall() + + # 2. VALIDATION + if len(rows) < 18: # Confidence threshold + return "Nicht genug Daten" + + # 3. CALCULATION + x = [(row[0] - rows[0][0]).days for row in rows] + y = [row[1] for row in rows] + + # Linear regression + n = len(x) + sum_x = sum(x) + sum_y = sum(y) + sum_xy = sum(xi * yi for xi, yi in zip(x, y)) + sum_x2 = sum(xi ** 2 for xi in x) + + slope = (n * sum_xy - sum_x * sum_y) / (n * sum_x2 - sum_x ** 2) + slope_per_week = slope * 7 + + # 4. FORMATTING + return f"{slope_per_week:.2f} kg/Woche" +``` + +### Schritte zum Hinzufügen eines neuen Platzhalters (Phase 0b) + +#### 1. Funktion implementieren + +**Datei:** `backend/placeholder_resolver.py` + +**Namenskonvention:** +- `resolve_(profile_id: str) -> str` +- Snake_case +- Immer `profile_id` als Parameter +- Immer `str` als Return-Type + +**Template:** +```python +def resolve_my_new_metric(profile_id: str) -> str: + """ + [Beschreibung was der Platzhalter zurückgibt] + + Args: + profile_id: User profile ID + + Returns: + [Beschreibung des Return-Formats] + """ + with get_db() as conn: + cur = get_cursor(conn) + + # 1. DATA RETRIEVAL + cur.execute(""" + SELECT ... + FROM ... + WHERE profile_id = %s + """, (profile_id,)) + + # 2. VALIDATION + if : + return "Nicht genug Daten" + + # 3. CALCULATION + result = ... + + # 4. FORMATTING + return f"{result}" +``` + +#### 2. In Mapping registrieren + +**Datei:** `backend/placeholder_resolver.py` + +**Finde `PLACEHOLDER_FUNCTIONS` Dictionary:** +```python +PLACEHOLDER_FUNCTIONS = { + # ... existing placeholders ... + + # Add your new placeholder: + "my_new_metric": resolve_my_new_metric, +} +``` + +**Naming:** +- Key = Platzhalter-Name (snake_case) +- Value = Funktions-Referenz (ohne Klammern!) + +#### 3. In Katalog dokumentieren + +**Datei:** `backend/placeholder_resolver.py` + +**Finde `get_placeholder_catalog()` Funktion:** +```python +def get_placeholder_catalog(profile_id: str) -> Dict[str, List[Dict[str, str]]]: + placeholders = { + 'Körper': [ + # ... existing ... + ('my_new_metric', 'Beschreibung des Platzhalters'), + ], + # ... + } +``` + +**Kategorien:** +- `Profil` +- `Körper` +- `Ernährung` +- `Training` +- `Schlaf & Erholung` +- `Vitalwerte` +- `Scores (Phase 0b)` +- `Focus Areas` +- `Zeitraum` + +#### 4. Testen + +**Manueller Test:** +```python +# In Python REPL oder test script: +from placeholder_resolver import resolve_my_new_metric + +result = resolve_my_new_metric("test_profile_id") +print(result) # Should return formatted string +``` + +**Integration Test:** +```python +# Test in actual prompt +from placeholder_resolver import resolve_placeholders + +template = "Dein {{my_new_metric}} ist ..." +result = resolve_placeholders(template, "test_profile_id") +print(result) # Should have placeholder replaced +``` + +--- + +## Phase 0c Architektur (Nach Refactoring) + +### Anatomie eines Platzhalters (3-Layer) + +```python +# Layer 1: DATA LAYER +# backend/data_layer/body_metrics.py + +def get_weight_trend_data(profile_id: str, days: int = 90) -> dict: + """ + Returns weight trend data with slopes and projections. + + This is pure data retrieval and calculation. + NO FORMATTING. NO STRINGS. + + Args: + profile_id: User profile ID + days: Analysis window + + Returns: + { + "raw_values": [(date, weight), ...], + "rolling_median_7d": [(date, value), ...], + "slope_7d": float, + "slope_28d": float, + "slope_90d": float, + "confidence": str, + ... + } + """ + with get_db() as conn: + cur = get_cursor(conn) + + # DATA RETRIEVAL + cur.execute("""...""", (profile_id, days)) + rows = cur.fetchall() + + # VALIDATION + CONFIDENCE + from data_layer.utils import calculate_confidence + confidence = calculate_confidence(len(rows), days, "trend") + + if confidence == 'insufficient': + return { + "confidence": "insufficient", + "slope_28d": 0.0, + # ... minimal data + } + + # CALCULATION + # ... (same logic as before) + + # RETURN STRUCTURED DATA (not formatted!) + return { + "raw_values": rows, + "slope_7d": slope_7d, + "slope_28d": slope_28d, + "confidence": confidence, + # ... all data as dict/list/float + } + + +# Layer 2a: KI LAYER +# backend/placeholder_resolver.py + +from data_layer.body_metrics import get_weight_trend_data + +def resolve_weight_28d_trend_slope(profile_id: str) -> str: + """ + Formats weight trend slope for KI consumption. + + This function is now THIN - just calls data layer and formats. + """ + data = get_weight_trend_data(profile_id, days=28) + + if data['confidence'] == 'insufficient': + return "Nicht genug Daten" + + return f"{data['slope_28d']:.2f} kg/Woche" +``` + +### Schritte zum Hinzufügen eines neuen Platzhalters (Phase 0c) + +#### 1. Data Layer Funktion implementieren + +**Datei:** Passendes Modul in `backend/data_layer/` +- Body metrics → `body_metrics.py` +- Nutrition → `nutrition_metrics.py` +- Activity → `activity_metrics.py` +- Recovery → `recovery_metrics.py` +- Health → `health_metrics.py` +- Goals → `goals.py` +- Correlations → `correlations.py` + +**Template:** +```python +# backend/data_layer/.py + +def get__data( + profile_id: str, + days: int = 28, + **kwargs +) -> dict: + """ + [Beschreibung der Daten] + + Args: + profile_id: User profile ID + days: Analysis window + **kwargs: Additional parameters + + Returns: + { + "": , + "confidence": str, # ALWAYS include! + "data_points": int, # ALWAYS include! + } + """ + with get_db() as conn: + cur = get_cursor(conn) + + # 1. DATA RETRIEVAL + cur.execute("""...""", (profile_id,)) + rows = cur.fetchall() + + # 2. CONFIDENCE CALCULATION + from data_layer.utils import calculate_confidence + confidence = calculate_confidence( + len(rows), + days, + "general" # or "correlation" or "trend" + ) + + # 3. VALIDATION + if confidence == 'insufficient': + return { + "confidence": "insufficient", + "data_points": len(rows), + # Return minimal safe data + } + + # 4. CALCULATION + # ... your logic here ... + + # 5. RETURN STRUCTURED DATA + return { + # All data as primitives: dict, list, float, int, str, bool + # NO FORMATTING (no "0.23 kg/Woche" - just 0.23) + "result": result_value, + "confidence": confidence, + "data_points": len(rows), + } +``` + +**WICHTIG:** +- ❌ Keine Strings mit Einheiten: `"0.23 kg/Woche"` +- ✅ Nur Zahlen: `0.23` +- ❌ Keine Formatierung für Menschen +- ✅ Strukturierte Daten für Maschinen + +#### 2. KI Layer Wrapper erstellen + +**Datei:** `backend/placeholder_resolver.py` + +```python +from data_layer. import get__data + +def resolve_(profile_id: str) -> str: + """ + [Beschreibung was zurückgegeben wird] + + Phase 0c: Uses data_layer..get__data() + """ + data = get__data(profile_id) + + if data['confidence'] == 'insufficient': + return "Nicht genug Daten" + + # FORMAT for KI consumption + return f"{data['']:.2f} " +``` + +#### 3. In Mapping registrieren + +**UNVERÄNDERT - gleich wie Phase 0b:** +```python +PLACEHOLDER_FUNCTIONS = { + "my_new_metric": resolve_my_new_metric, +} +``` + +#### 4. In Katalog dokumentieren + +**UNVERÄNDERT - gleich wie Phase 0b:** +```python +def get_placeholder_catalog(profile_id: str): + placeholders = { + 'Körper': [ + ('my_new_metric', 'Beschreibung'), + ], + } +``` + +#### 5. Testen + +**Unit Test für Data Layer:** +```python +# backend/tests/test_data_layer.py + +def test_get_metric_data_sufficient(): + data = get__data("test_profile_1", days=28) + + assert data['confidence'] in ['high', 'medium', 'low', 'insufficient'] + assert 'data_points' in data + assert isinstance(data[''], float) + +def test_get_metric_data_insufficient(): + data = get__data("profile_no_data", days=28) + + assert data['confidence'] == 'insufficient' +``` + +**Integration Test für KI Layer:** +```python +# backend/tests/test_placeholders.py + +def test_resolve_placeholder(): + result = resolve_("test_profile_1") + + assert isinstance(result, str) + assert result != "Nicht genug Daten" +``` + +--- + +## Best Practices + +### 1. Confidence Scoring + +**IMMER `calculate_confidence()` verwenden:** +```python +from data_layer.utils import calculate_confidence + +confidence = calculate_confidence( + data_points=len(rows), + days_requested=days, + metric_type="general" # or "correlation" or "trend" +) +``` + +**Confidence Thresholds:** +- General (28d): high >= 18, medium >= 12, low >= 8 +- Correlation: high >= 28, medium >= 21, low >= 14 +- Trend: high >= (days * 0.7), medium >= (days * 0.5) + +### 2. Decimal → Float Conversion + +**PostgreSQL gibt Decimal zurück - immer zu float konvertieren:** +```python +# ❌ WRONG: +value = row['column'] + +# ✅ CORRECT: +value = float(row['column']) if row['column'] else 0.0 +``` + +### 3. Safe Dict Access + +**Nie direkter Key-Zugriff ohne Fallback:** +```python +# ❌ WRONG: +value = data['key'] # KeyError if missing + +# ✅ CORRECT: +value = data.get('key', default_value) +``` + +### 4. Date Serialization + +**Python date objects sind nicht JSON-serializable:** +```python +from data_layer.utils import serialize_dates + +data = { + "date": date(2026, 3, 28), + "values": [...] +} + +# Serialize before returning from API +return serialize_dates(data) +``` + +### 5. SQL Parameter Binding + +**IMMER Parameter-Binding, NIE String-Concatenation:** +```python +# ✅ CORRECT: +cur.execute("SELECT * FROM t WHERE id = %s", (id,)) + +# ❌ WRONG (SQL Injection Risk): +cur.execute(f"SELECT * FROM t WHERE id = {id}") +``` + +### 6. Column Name Consistency + +**Prüfe Schema BEVOR du Column-Namen verwendest:** +```python +# ❌ WRONG (assumed name): +SELECT bf_jpl FROM caliper_log + +# ✅ CORRECT (check schema first): +SELECT body_fat_pct FROM caliper_log +``` + +**Schema prüfen:** +```sql +\d caliper_log -- in psql +-- oder +SELECT column_name FROM information_schema.columns +WHERE table_name = 'caliper_log'; +``` + +--- + +## Fehler-Handling + +### 1. Insufficient Data + +**Return-Value bei zu wenig Daten:** +```python +# Data Layer: +return { + "confidence": "insufficient", + "data_points": 0, + # Alle anderen Felder mit safe defaults (0.0, [], etc.) +} + +# KI Layer: +if data['confidence'] == 'insufficient': + return "Nicht genug Daten" +``` + +### 2. Missing Optional Data + +**Wenn optionale Daten fehlen (z.B. keine Vitals):** +```python +# Data Layer: +return { + "hrv": None, # or 0.0, depending on semantic + "confidence": "low", # downgrade confidence +} + +# KI Layer: +if data['hrv'] is None: + return "Keine HRV-Daten verfügbar" +``` + +### 3. Calculation Errors + +**Bei Math-Errors (Division by Zero, etc.):** +```python +try: + result = numerator / denominator +except ZeroDivisionError: + result = 0.0 # or None, depending on semantic +``` + +--- + +## Dokumentations-Pflicht + +### 1. Docstring + +**Jede Funktion braucht Docstring:** +```python +def get_metric_data(profile_id: str, days: int = 28) -> dict: + """ + [Eine Zeile Zusammenfassung] + + [Ausführliche Beschreibung wenn nötig] + + Args: + profile_id: User profile ID + days: Analysis window (default 28) + + Returns: + { + "field": value, + "confidence": str, + "data_points": int + } + + Confidence Rules: + - high: >= X points + - medium: >= Y points + - low: >= Z points + - insufficient: < Z points + """ +``` + +### 2. Inline Comments + +**Nur bei nicht-offensichtlicher Logik:** +```python +# Calculate trimmed mean (remove top/bottom 10%) +sorted_values = sorted(values) +trim_count = len(values) // 10 +trimmed = sorted_values[trim_count:-trim_count] +result = sum(trimmed) / len(trimmed) +``` + +### 3. Type Hints + +**IMMER Type Hints verwenden:** +```python +from typing import Optional, List, Dict, Tuple + +def get_data( + profile_id: str, + days: int = 28, + include_raw: bool = False +) -> Dict[str, any]: + ... +``` + +--- + +## Testing-Strategie + +### 1. Unit Tests (Data Layer) + +**Teste jede Data Layer Funktion isoliert:** +```python +# backend/tests/test_data_layer.py + +import pytest +from data_layer.body_metrics import get_weight_trend_data + +@pytest.fixture +def test_profile(): + # Setup test data in database + ... + yield profile_id + # Teardown + ... + +def test_weight_trend_sufficient_data(test_profile): + data = get_weight_trend_data(test_profile, days=28) + + assert data['confidence'] in ['high', 'medium'] + assert data['slope_28d'] != 0.0 + assert len(data['raw_values']) >= 18 + +def test_weight_trend_insufficient_data(): + data = get_weight_trend_data("no_data_profile", days=28) + + assert data['confidence'] == 'insufficient' +``` + +### 2. Integration Tests (KI Layer) + +**Teste Placeholder-Resolution:** +```python +# backend/tests/test_placeholders.py + +def test_placeholder_resolution(test_profile): + result = resolve_weight_28d_trend_slope(test_profile) + + assert isinstance(result, str) + assert "kg/Woche" in result or "Nicht genug Daten" in result + +def test_placeholder_in_template(test_profile): + template = "Trend: {{weight_28d_trend_slope}}" + result = resolve_placeholders(template, test_profile) + + assert "{{" not in result # All placeholders resolved + assert result.startswith("Trend:") +``` + +### 3. Manual Testing Checklist + +``` +[ ] Funktion mit verschiedenen days-Parametern testen +[ ] Mit vollständigen Daten testen +[ ] Mit unvollständigen Daten testen +[ ] Mit NO DATA testen +[ ] Edge Cases: Extreme Werte, Outliers +[ ] Performance: < 500ms für typische Queries +[ ] Memory: Kein Leak bei großen Datasets +``` + +--- + +## Checkliste: Neuer Platzhalter + +### Phase 0b (Aktuell): +``` +[ ] Funktion in placeholder_resolver.py implementiert +[ ] resolve_(profile_id: str) -> str Signatur +[ ] Docstring vollständig +[ ] Confidence-Check implementiert +[ ] In PLACEHOLDER_FUNCTIONS registriert +[ ] In get_placeholder_catalog() dokumentiert +[ ] Manuell getestet +[ ] In echtem Prompt getestet +``` + +### Phase 0c (Nach Refactoring): +``` +[ ] Data Layer Funktion implementiert + [ ] Richtiges Modul gewählt + [ ] get__data(profile_id, ...) -> dict Signatur + [ ] Returns structured data (dict/list/primitives) + [ ] NO formatting, NO strings with units + [ ] Confidence calculation included + [ ] Docstring vollständig +[ ] KI Layer Wrapper implementiert + [ ] resolve_(profile_id: str) -> str Signatur + [ ] Calls data_layer function + [ ] Formats result for KI +[ ] In PLACEHOLDER_FUNCTIONS registriert +[ ] In get_placeholder_catalog() dokumentiert +[ ] Unit Test für Data Layer geschrieben +[ ] Integration Test für KI Layer geschrieben +[ ] Manual Testing durchgeführt +``` + +--- + +## Häufige Fehler (Learnings from Phase 0b) + +### 1. Vergessen float() Conversion +```python +# SYMPTOM: "Object of type Decimal is not JSON serializable" +# FIX: +value = float(row['column']) if row['column'] else 0.0 +``` + +### 2. Hardcoded Column Names +```python +# SYMPTOM: "column bf_jpl does not exist" +# FIX: Check schema first +SELECT column_name FROM information_schema.columns +WHERE table_name = 'caliper_log'; +``` + +### 3. KeyError bei fehlenden Daten +```python +# SYMPTOM: "KeyError: 'hrv'" +# FIX: Use .get() with default +hrv = data.get('hrv', 0.0) +``` + +### 4. Confidence nicht berechnet +```python +# SYMPTOM: Platzhalter liefert Daten bei <3 Punkten +# FIX: calculate_confidence() verwenden +from data_layer.utils import calculate_confidence +confidence = calculate_confidence(len(rows), days, "general") +``` + +### 5. Date nicht serialized +```python +# SYMPTOM: "Object of type date is not JSON serializable" +# FIX: +from data_layer.utils import serialize_dates +return serialize_dates(data) +``` + +### 6. SQL Injection Risk +```python +# SYMPTOM: Security Scanner warnt +# FIX: ALWAYS use parameter binding +cur.execute("SELECT * FROM t WHERE id = %s", (id,)) +``` + +--- + +## Nächste Schritte + +### Nach Implementierung eines neuen Platzhalters: + +1. **Commit Message:** + ``` + feat: add {{my_new_metric}} placeholder + + - Implements resolve_my_new_metric() in placeholder_resolver.py + - Adds entry to PLACEHOLDER_FUNCTIONS + - Documents in get_placeholder_catalog() + - Tested with profile XYZ + + Category: + Returns: + ``` + +2. **Dokumentation aktualisieren:** + - `CLAUDE.md` - Neue Platzhalter auflisten + - `docs/api/PLACEHOLDERS.md` - API-Dokumentation + +3. **Testing:** + - Mindestens 1 manueller Test mit echtem Profil + - Optional: Unit Test hinzufügen + +4. **Review:** + - Prüfe ob Platzhalter in Prompt-Bibliothek sinnvoll + - Teste mit verschiedenen Prompts + - Performance-Check (< 500ms) + +--- + +**Autor:** Claude Sonnet 4.5 +**Version:** 1.0 +**Letzte Aktualisierung:** 28. März 2026 diff --git a/.claude/docs/technical/PLACEHOLDER_REGISTRY_FRAMEWORK.md b/.claude/docs/technical/PLACEHOLDER_REGISTRY_FRAMEWORK.md new file mode 100644 index 0000000..89438d4 --- /dev/null +++ b/.claude/docs/technical/PLACEHOLDER_REGISTRY_FRAMEWORK.md @@ -0,0 +1,504 @@ +# Placeholder Registry Framework - Verbindliche Dokumentation + +**Status:** VERBINDLICH (ab 2026-04-02) +**Version:** 1.0 +**Geltungsbereich:** Alle Placeholder/Metrics im System + +--- + +## 1. Zweck + +Das **Placeholder Registry Framework** ist die zentrale, verbindliche Metadaten-Verwaltung für alle Placeholder und Metrics im System. + +**Kernprinzip:** Single Source of Truth + +**Ziele:** +1. Einheitliche Metadaten-Struktur für alle Placeholder +2. Vermeidung von Duplikation und Inkonsistenzen +3. Zentrale Verwaltung für alle Konsumenten +4. Evidence-basierte Transparenz +5. Erweiterbarkeit und Wartbarkeit + +--- + +## 2. Verbindlichkeit + +### 2.1 Pflicht zur Nutzung + +**ALLE neuen Placeholder/Metrics MÜSSEN über das Registry Framework registriert werden.** + +Keine Ausnahmen ohne explizite technische Begründung und Freigabe. + +### 2.2 Betroffene Systeme + +Folgende Systeme MÜSSEN die Registry als Single Source of Truth nutzen: + +1. **Backend Prompt-Injektion** (Layer 2a) + - Placeholder-Resolver + - Prompt-Template-Engine + +2. **GUI Auswahllisten** + - Placeholder-Picker + - Kategorie-Filter + - Metadata-Anzeige + +3. **Extended Export** + - `/api/prompts/placeholders/export-values-extended` + - Catalog-Generierung + - ZIP-Export + +4. **Validierung** (zukünftig) + - Metadata-Completeness-Checks + - Evidence-Quality-Assurance + - Compliance-Reports + +5. **Diagramm-Zuordnung** (zukünftig) + - Chart-Metadata-Mapping + - Layer-2b-Integration + +### 2.3 Verbotene Praktiken + +**VERBOTEN:** +- Hardcoded Metadaten außerhalb der Registry +- Duplizierte Metadaten-Definitionen +- Placeholder ohne Registry-Registrierung +- Direkte Manipulation von Metadaten im Export-Code +- Inkonsistente Metadaten zwischen Systemen + +--- + +## 3. Framework-Architektur + +### 3.1 Kernkomponenten + +**Modul:** `backend/placeholder_registry.py` + +**Klassen:** +- `PlaceholderMetadata` - Metadata-Dataclass (22 Pflichtfelder) +- `MissingValuePolicy` - Strukturierte Missing-Value-Behandlung +- `PlaceholderRegistry` - Zentrale Registry (Singleton) +- `EvidenceType` - Enum für Evidenz-Tagging +- `OutputType` - Enum für Output-Typen +- `PlaceholderType` - Enum für Placeholder-Typen + +**Singleton-Instanz:** +```python +from placeholder_registry import get_registry + +registry = get_registry() +``` + +### 3.2 Registrierungs-Package + +**Package:** `backend/placeholder_registrations/` + +**Struktur:** +``` +placeholder_registrations/ +├── __init__.py # Auto-Import aller Registrations +├── nutrition_part_a.py # Nutrition Basis-Metriken (4 Placeholder) +├── nutrition_part_b.py # Protein-Ziele (5 Placeholder) - TODO +├── body_metrics.py # Körper-Metriken - TODO +├── activity_metrics.py # Aktivitäts-Metriken - TODO +└── ... # Weitere Cluster +``` + +**Auto-Registration:** +- Import des Package triggert automatische Registrierung aller Placeholder +- Keine manuelle Registrierung erforderlich + +### 3.3 Export-Integration + +**Modul:** `backend/placeholder_registry_export.py` + +**Funktionen:** +- `get_registry_metadata_for_export()` - Metadata aus Registry +- `merge_registry_with_legacy_export()` - Backward-Compatibility +- `get_enhanced_export_with_registry()` - Vollständiger Export + +**Endpoint-Integration:** +```python +# backend/routers/prompts.py +import placeholder_registrations # Auto-registers +from placeholder_registry_export import get_registry_metadata_for_export + +registry_data = get_registry_metadata_for_export(profile_id) +export_data['registry_metadata'] = registry_data +``` + +--- + +## 4. Metadata-Schema + +### 4.1 Pflichtfelder (22 Felder) + +**Core Identification (3):** +- `key` - Placeholder-Schlüssel (z.B. "kcal_avg") +- `category` - Kategorie (z.B. "Ernährung") +- `description` - Kurzbeschreibung + +**Technical (6):** +- `resolver_module` - Modul-Pfad des Resolvers +- `resolver_function` - Funktionsname des Resolvers +- `data_layer_module` - Data Layer Modul (optional) +- `data_layer_function` - Data Layer Funktion (optional) +- `source_tables` - Liste der Quelltabellen +- `_resolver_func` - Runtime-Resolver (nicht exportiert) + +**Semantic (8):** +- `semantic_contract` - Semantische Definition +- `business_meaning` - Fachliche Bedeutung +- `unit` - Einheit (z.B. "kcal/day") +- `time_window` - Zeitfenster (z.B. "30d") +- `output_type` - Output-Typ (Enum) +- `placeholder_type` - Placeholder-Typ (Enum) +- `format_hint` - Format-Information +- `example_output` - Beispiel-Ausgabe + +**Quality (5):** +- `minimum_data_requirements` - Mindestanforderungen (optional) +- `quality_filter_policy` - Qualitätsfilter (optional) +- `confidence_logic` - Confidence-Berechnung (optional) +- `missing_value_policy` - Missing-Value-Handling +- `known_limitations` - Bekannte Einschränkungen (optional) + +**Architecture (5):** +- `layer_1_decision` - Layer-1-Zuordnung (optional) +- `layer_2a_decision` - Layer-2a-Zuordnung (optional) +- `layer_2b_reuse_possible` - Chart-Reuse möglich (optional) +- `architecture_alignment` - Architektur-Konformität (optional) +- `issue_53_alignment` - Issue #53 Konformität (optional) + +**Evidence Tracking (1):** +- `evidence` - Dict mapping Feldname → EvidenceType + +### 4.2 Evidence-Typen + +**Enum:** `EvidenceType` + +**Werte:** +- `CODE_DERIVED` - Aus Code belegt (z.B. aus Import-Statement, SQL-Query) +- `DRAFT_DERIVED` - Aus Canonical Requirements Draft übernommen +- `MIXED` - Teilweise Code, teilweise Draft/abgeleitet +- `UNRESOLVED` - Nicht explizit dokumentiert, offen +- `TO_VERIFY` - Behauptung, muss noch verifiziert werden + +**Verwendung:** +```python +metadata.set_evidence("unit", EvidenceType.CODE_DERIVED) +metadata.set_evidence("semantic_contract", EvidenceType.DRAFT_DERIVED) +metadata.set_evidence("layer_2b_reuse_possible", EvidenceType.TO_VERIFY) +``` + +--- + +## 5. Registrierungs-Workflow + +### 5.1 Neuen Placeholder registrieren + +**Schritt 1: Metadata-Objekt erstellen** + +```python +# backend/placeholder_registrations/my_cluster.py + +from placeholder_registry import ( + PlaceholderMetadata, + MissingValuePolicy, + EvidenceType, + OutputType, + PlaceholderType, + register_placeholder +) + +metadata = PlaceholderMetadata( + key="my_placeholder", + category="Meine Kategorie", + description="Kurzbeschreibung", + + # Technical (CODE_DERIVED) + resolver_module="backend/placeholder_resolver.py", + resolver_function="get_my_placeholder", + data_layer_module="backend/data_layer/my_metrics.py", + data_layer_function="get_my_data", + source_tables=["my_table"], + + # Semantic + semantic_contract="Was liefert dieser Placeholder?", + business_meaning="Fachliche Bedeutung", + unit="einheit", + time_window="30d", + output_type=OutputType.NUMERIC, + placeholder_type=PlaceholderType.INTERPRETED, + format_hint="Ganzzahl", + example_output="42", + + # Quality + confidence_logic="Wie wird Verlässlichkeit berechnet?", + missing_value_policy=MissingValuePolicy( + available=False, + value_raw=None, + missing_reason="insufficient_data", + legacy_display="nicht genug Daten" + ), + known_limitations="Einschränkungen dokumentieren", + + # Architecture + layer_1_decision="Data Layer (my_metrics.get_my_data)", + layer_2a_decision="Placeholder Resolver (formatting only)", + architecture_alignment="Phase 0c conform" +) +``` + +**Schritt 2: Evidence setzen** + +```python +# Code-derived Felder +metadata.set_evidence("resolver_module", EvidenceType.CODE_DERIVED) +metadata.set_evidence("resolver_function", EvidenceType.CODE_DERIVED) +metadata.set_evidence("data_layer_module", EvidenceType.CODE_DERIVED) +metadata.set_evidence("source_tables", EvidenceType.CODE_DERIVED) +metadata.set_evidence("unit", EvidenceType.CODE_DERIVED) +metadata.set_evidence("time_window", EvidenceType.CODE_DERIVED) + +# Draft-derived Felder +metadata.set_evidence("semantic_contract", EvidenceType.DRAFT_DERIVED) +metadata.set_evidence("business_meaning", EvidenceType.DRAFT_DERIVED) +metadata.set_evidence("known_limitations", EvidenceType.DRAFT_DERIVED) + +# Unresolved Felder +metadata.set_evidence("minimum_data_requirements", EvidenceType.UNRESOLVED) +``` + +**Schritt 3: Registrieren** + +```python +register_placeholder(metadata) +``` + +**Schritt 4: Auto-Import sicherstellen** + +```python +# backend/placeholder_registrations/__init__.py +from . import my_cluster + +__all__ = ['nutrition_part_a', 'my_cluster'] +``` + +### 5.2 Resolver-Funktion bereitstellen (optional) + +Wenn der Placeholder runtime-resolved werden soll: + +```python +from placeholder_resolver import get_my_placeholder + +register_placeholder( + metadata, + resolver_func=lambda pid: get_my_placeholder(pid) +) +``` + +--- + +## 6. API-Nutzung + +### 6.1 Metadata abrufen + +```python +from placeholder_registry import get_registry + +registry = get_registry() + +# Einzelner Placeholder +meta = registry.get("kcal_avg") +print(meta.unit) # "kcal/day" +print(meta.time_window) # "30d" + +# Alle Placeholder +all_metadata = registry.get_all() # Dict[str, PlaceholderMetadata] + +# Nach Kategorie +ernaehrung = registry.get_by_category("Ernährung") # List[PlaceholderMetadata] +``` + +### 6.2 Export + +```python +# Für Extended Export +export_data = registry.get_all_for_export() # List[Dict] + +# Mit Runtime-Werten +from placeholder_registry_export import get_registry_metadata_for_export + +registry_data = get_registry_metadata_for_export(profile_id) +# Returns: {flat, by_category, evidence_report, validation_report} +``` + +### 6.3 Validierung + +```python +# Alle Placeholder validieren +issues = registry.validate_all() # Dict[str, List[str]] + +if issues: + for key, problems in issues.items(): + print(f"{key}: {problems}") +``` + +### 6.4 QA / Evidence-Tracking + +```python +# Placeholder mit unresolved Fields finden +unresolved = registry.get_by_evidence_type(EvidenceType.UNRESOLVED) +# Returns: Dict[placeholder_key, List[field_names]] + +# Placeholder die verifiziert werden müssen +to_verify = registry.get_by_evidence_type(EvidenceType.TO_VERIFY) +``` + +--- + +## 7. Best Practices + +### 7.1 Evidence-Tagging + +**DO:** +- Jedes Feld mit Evidence-Tag versehen +- `CODE_DERIVED` nur wenn direkt aus Code ableitbar +- `TO_VERIFY` für Behauptungen, die noch geprüft werden müssen +- `UNRESOLVED` für fehlende/unklare Informationen + +**DON'T:** +- Felder ohne Evidence lassen +- Evidence halluzinieren (wenn nicht belegt, `UNRESOLVED` nutzen) +- `CODE_DERIVED` für Draft-Informationen nutzen + +### 7.2 Metadata-Vollständigkeit + +**Minimum Required:** +- `key`, `category`, `description` +- `resolver_module`, `resolver_function` +- `semantic_contract` +- `unit`, `time_window` +- `output_type`, `placeholder_type` + +**Optional but Recommended:** +- `data_layer_module`, `data_layer_function` +- `source_tables` +- `confidence_logic`, `missing_value_policy` +- `layer_1_decision`, `layer_2a_decision` + +### 7.3 Modularisierung + +**Registrations nach Cluster gruppieren:** +- `nutrition_part_a.py` - Nutrition Basis (4 Placeholder) +- `nutrition_part_b.py` - Nutrition Protein (5 Placeholder) +- `body_metrics.py` - Körper-Metriken (N Placeholder) + +**Nicht:** +- Alle Placeholder in eine riesige Datei +- Placeholder ohne thematische Gruppierung + +### 7.4 Backward-Compatibility + +**Export-Endpoint MUSS:** +- Legacy-Export beibehalten (`export_data['legacy']`) +- Graceful degradation bei Registry-Fehler +- Registry-Metadata als separate Sektion (`export_data['registry_metadata']`) + +--- + +## 8. Migration bestehender Placeholder + +### 8.1 Priorität + +**Part A (erledigt):** Nutrition Basis (kcal_avg, protein_avg, carb_avg, fat_avg) + +**Nächste Priorität:** +1. Part B - Nutrition Protein (5 Placeholder) +2. Part C - Nutrition Balance (4 Placeholder) +3. Part D - Nutrition Meta (1 Placeholder) +4. Body Metrics (ca. 15 Placeholder) +5. Activity Metrics (ca. 20 Placeholder) + +### 8.2 Migration-Workflow + +**Für jeden Placeholder:** +1. Code inspizieren (Resolver, Data Layer, SQL) +2. Evidence ableiten (was ist code-derived, was draft-derived?) +3. Metadata-Objekt erstellen +4. Registrieren +5. Export testen +6. Werte-Identität bestätigen + +**Keine Logikänderung während Migration!** + +--- + +## 9. Compliance & Enforcement + +### 9.1 Code-Review-Checkliste + +**Für neue Placeholder:** +- [ ] Registry-Registrierung vorhanden? +- [ ] Evidence-Tags gesetzt? +- [ ] Metadata-Vollständigkeit (minimum required)? +- [ ] Auto-Import in `__init__.py`? +- [ ] Export getestet? + +### 9.2 CI/CD-Integration (zukünftig) + +**Geplante Checks:** +- Alle Placeholder in PLACEHOLDER_MAP sind in Registry registriert +- Keine Placeholder ohne Evidence-Tags +- Keine doppelten Registrierungen +- Metadata-Vollständigkeit für production-ready Placeholder + +--- + +## 10. Support & Weiterentwicklung + +### 10.1 Fragen & Issues + +**Bei Unklarheiten:** +1. Diese Dokumentation prüfen +2. Bestehende Registrations als Vorlage nutzen (`nutrition_part_a.py`) +3. Code-Review anfragen + +### 10.2 Framework-Erweiterungen + +**Geplante Features:** +- GUI-Integration (Placeholder-Picker mit Registry-Metadata) +- Validation-Dashboard (QA-Monitoring) +- Evidence-Report-Endpoint (Metadata-Qualität) +- Resolver-Test-Framework (Automatisierte Werteänderungs-Detektion) +- Chart-Metadata-Mapping (Layer-2b-Integration) + +### 10.3 Versions-History + +**v1.0 (2026-04-02):** +- Initial Release +- Part A Implementation (4 Nutrition Placeholders) +- Core Framework + Export-Integration + +--- + +## 11. Referenzen + +**Code:** +- `backend/placeholder_registry.py` - Core Framework +- `backend/placeholder_registrations/nutrition_part_a.py` - Part A Implementation +- `backend/placeholder_registry_export.py` - Export-Integration +- `backend/routers/prompts.py` - Export-Endpoint + +**Dokumentation:** +- `.claude/task/rework_0b_placeholder/NUTRITION_PART_A_CHANGE_PLAN.md` +- `.claude/task/rework_0b_placeholder/NUTRITION_PART_A_IMPLEMENTATION_REPORT.md` + +**Beispiel-Export:** +```bash +curl "https://dev.mitai.jinkendo.de/api/prompts/placeholders/export-values-extended?token=XXX" +``` + +--- + +**Ende Verbindliche Dokumentation** diff --git a/.claude/docs/technical/PROFILE_REFERENCE_VALUES.md b/.claude/docs/technical/PROFILE_REFERENCE_VALUES.md new file mode 100644 index 0000000..654f21c --- /dev/null +++ b/.claude/docs/technical/PROFILE_REFERENCE_VALUES.md @@ -0,0 +1,53 @@ +# Persönliche Referenzwerte (Profil) + +## Überblick + +Nutzer-spezifische, **historische** Kennwerte (z. B. HF-Schwellen, Trainingshäufigkeit), die **nicht** zur Admin-/Focus-Area-Konfiguration gehören, sondern zum **aktiven Profil** wie Größe oder Ziele. + +## Tabellen + +| Tabelle | Zweck | +|--------|--------| +| `reference_value_types` | System-seedete Typdefinitionen: stabiler `key`, Anzeige-`label`, optional `default_unit`, `sort_order`, `active`, `metadata` (JSONB). | +| `profile_reference_values` | Historische Einträge: `profile_id`, `reference_value_type_id`, `effective_date`, `value_numeric` und/oder `value_text`, `unit`, optionale Felder `source`, `confidence`, `method`, `notes`, `extra` (JSONB). | + +**Kein** Überschreiben eines einzelnen „aktuellen“ Werts: jede Messung ist eine eigene Zeile. + +## Seed-Typen (Migration 037) + +- `max_heart_rate` (bpm) +- `resting_heart_rate` (bpm) +- `anaerobic_threshold_hr` (bpm) +- `aerobic_threshold_hr` (bpm) +- `training_frequency_weekly` (Sessions/Woche) +- `fitness_level` (Stufe) + +Es werden **keine** Benutzerwerte automatisch angelegt. + +## Admin (nur Rolle `admin`) + +- `GET/POST/PUT/DELETE /api/admin/reference-value-types` — vollständiger CRUD auf `reference_value_types` (inkl. inaktiver Typen). +- Löschen nur, wenn keine Zeilen in `profile_reference_values` zu diesem Typ existieren (sonst HTTP 409). +- UI: **Admin → Ziele & Fokus → Referenz-Kennwerte** (`/admin/reference-value-types`). + +## API (Prefix `/api`) + +- `GET /reference-value-types` — aktive Typen (dynamische UI) +- `GET /profile-reference-values?type_key=…` — Liste pro Typ, neueste zuerst +- `POST /profile-reference-values` — neuer Eintrag +- `PUT /profile-reference-values/{id}` — Aktualisierung +- `DELETE /profile-reference-values/{id}` — Löschen + +Authentifizierung wie üblich; Profil über `X-Profile-Id` / `get_pid` wie andere Module. + +## UI + +**Einstellungen → Karte „Referenzwerte“ → „Referenzwerte verwalten“** (`/settings/reference-values`). + +Typauswahl per Dropdown (aus API); Verlauf als Tabelle mit Bearbeiten/Löschen; Formular für neue Einträge bzw. Bearbeitung. + +## Erweiterbarkeit + +- Neue Messgrößen: nur **INSERT** in `reference_value_types` (Migration oder Admin-Skript), kein Schema-Wechsel für Nutzerdaten. +- Zusatzmetadaten: Spalten `source`, `confidence`, `method`, `notes`, `extra` bereits vorhanden; UI kann später erweitert werden. +- Platzierung **profilorientiert**, damit klar ist: Nutzerdaten, keine systemweite Semantik wie Focus Areas. diff --git a/.claude/docs/technical/TRAINING_PROFILE_RESOLVER_LAYER1.md b/.claude/docs/technical/TRAINING_PROFILE_RESOLVER_LAYER1.md new file mode 100644 index 0000000..4eea637 --- /dev/null +++ b/.claude/docs/technical/TRAINING_PROFILE_RESOLVER_LAYER1.md @@ -0,0 +1,148 @@ +# Training Profile Resolver (Layer 1) — technisches Scaffold + +**Stand:** 2026-04-06 +**Zweck:** Erweiterbare technische Basis für spätere trainingsprofil-basierte Auswertungen, ohne freie Formel-/Skript-Engine und ohne Kopplung an KI oder Charts. + +--- + +## 1. Einordnung in Layer 1 + +| Aspekt | Beschreibung | +|--------|----------------| +| Ort | `backend/data_layer/training_profile/` | +| Rolle | Reine Orchestrierung: Templates → registrierte Built-in-Algorithmen → strukturiertes Ergebnis inkl. Focus-Area-Beiträge | +| Single Source of Truth | Berechnungslogik der Algorithmen lebt in Python-Modulen; Templates wählen nur **Algorithmus-ID + Parameter + Dimensionen + FA-Mapping** | +| Rückwärtskompatibel | Keine Änderungen an bestehenden Data-Layer-Metriken, Placeholdern oder Chart-Routern | + +--- + +## 2. Modulübersicht + +| Pfad | Inhalt | +|------|--------| +| `models.py` | `CalculationTemplate`, `DimensionSpec`, `FocusAreaMapping`, `TrainingBaseProfile`, `TrainingEvaluationResult`, `AlgorithmRunResult` | +| `resolver.py` | `resolve_training_evaluation()`, `resolve_for_base_profile()` | +| `algorithms/registry.py` | `register_algorithm`, `get_algorithm`, `list_algorithm_ids` | +| `algorithms/builtin/threshold_band.py` | Beispiel: Schwellen-Bänder → Score 0–1 | +| `algorithms/builtin/linear_range.py` | Beispiel: lineare Abbildung [min,max] → [0,1] | +| `templates/registry.py` | Beispiel-Templates (deklarativ, in-code) | +| `profiles/registry.py` | Beispiel-Trainings-Basisprofile (Default-Template, optionale Dimensions-Whitelist) | + +--- + +## 3. Built-in-Algorithmen + +- Algorithmen sind **fest im Code** implementiert und über eine **ID** referenzierbar. +- Neue Algorithmen: Funktion mit Signatur `(*, inputs, params) -> AlgorithmRunResult` und `register_algorithm(id, fn)` (Start-up-Registrierung in `registry.py` oder Import eines Moduls, das registriert). +- **Nicht** vorgesehen: Nutzerdefinierte Ausdrücke, DSL, `eval`, externe Skripte. + +Implementiert (Beispiele): + +- `threshold_band` — `params.value_key`, `params.bands` (Liste mit `max` / `score`) +- `linear_range` — `params.value_key`, `min_value`, `max_value`, optional `invert` + +--- + +## 4. Templates (deklarativ) + +Ein `CalculationTemplate` besteht aus: + +- `id`, `version`, `label` +- `dimensions`: Liste von `DimensionSpec` mit: + - `key` — Dimensionsname + - `algorithm_id` — referenziert registrierten Algorithmus + - `inputs` — erwartete Schlüssel im flachen `activity_inputs`-Dict des Aufrufers + - `params` — JSON-serialisierbare Parameter für den Algorithmus + - `maps_to` — Tupel `(focus_area_key, weight)` — gewichteter Anteil der **normalisierten** Dimension am jeweiligen Focus Area + +Aggregation: Pro Dimension wird `normalized_score * weight` pro Focus Area addiert (`focus_area_contributions`). + +--- + +## 5. Trainings-Basisprofile (Scaffold) + +`TrainingBaseProfile`: + +- `key`, `label` +- `default_template_id` — verweist auf ein `CalculationTemplate` +- optional `allowed_dimension_keys` — nur diese Dimensionen aus dem Template werden ausgeführt (Filter) + +Aktuell **nur In-Code-Registry**; später denkbar: DB-Verknüpfung Trainingstyp → Profil-Key. + +--- + +## 6. Ergebnisstruktur (`TrainingEvaluationResult`) + +- `template_id`, `template_version`, `base_profile_key` +- `dimension_results[]` — pro Dimension: Scores, fehlende Inputs, Evidence +- `focus_area_contributions` — `dict[str, float]` (Focus-Area-Key → aggregierter Beitrag) +- `confidence` — `high` | `medium` | `low` | `insufficient` (heuristisch aus fehlenden Pflicht-Inputs) +- `evidence` — Metadaten (z. B. Anzahl Dimensionen, Input-Keys) +- optional `trace` — bei `include_trace=True` für Debugging + +`to_serializable()` liefert ein JSON-taugliches Dict für APIs/Persistenz. + +--- + +## 7. Öffentliche API (Import) + +```python +from data_layer.training_profile import ( + resolve_training_evaluation, + resolve_for_base_profile, + TrainingEvaluationResult, + CalculationTemplate, +) +``` + +Registries: + +```python +from data_layer.training_profile.templates.registry import get_calculation_template +from data_layer.training_profile.profiles.registry import get_training_base_profile +from data_layer.training_profile.algorithms.registry import get_algorithm, list_algorithm_ids +``` + +--- + +## 8. Erweiterungspunkte (für spätere Produktlogik) + +1. **Neue Algorithmen** — neue Datei unter `algorithms/builtin/`, in `registry.py` registrieren. +2. **Templates** — weitere `CalculationTemplate`-Instanzen in `templates/registry.py` oder später aus DB laden (Loader baut dieselben Dataclasses). +3. **Profile** — weitere `TrainingBaseProfile`-Einträge; Anbindung an `training_types` / Aktivität. +4. **Confidence/Evidence** — feinere Regeln (Datenqualität, Mindestkriterien) im Resolver oder in den Algorithmen. +5. **Normalisierung der FA-Beiträge** — optional globale Skalierung/Cap (aktuell rein additive Gewichtung). + +--- + +## 9. Was absichtlich offen ist + +- Finale Domänenregeln (welche Dimensionen für welchen Sport) +- Vollständige Liste Basisprofile und Templates +- Persistenz von Evaluationsergebnissen +- Integration in Placeholder-Resolver, Charts, Admin-UI +- Validierung gegen `focus_area_definitions` (DB-Keys) + +--- + +## 10. Kritische Einschätzung + +| Bereich | Bewertung | +|---------|-----------| +| Registry + Algorithmus-Schnittstelle | Robust, testbar, erweiterbar | +| Template-Modelle (frozen dataclasses) | Stabil, typsicher, serialisierbar | +| Beispiel-Algorithmen | Minimal, nur zur Demonstration der Pipeline | +| Confidence | Heuristik — für Produktion noch abzustimmen | +| Focus-Area-Gewichte | Additiv, nicht normiert — Domänenentscheidung für später | + +--- + +## 11. Tests + +`backend/tests/test_training_profile_resolver.py` — Registry, Resolver, Profil-Filter, Serialisierung, unbekannter Algorithmus. + +Ausführen (aus `backend/`): + +```bash +python -m pytest tests/test_training_profile_resolver.py -v +``` diff --git a/.claude/docs/technical/TRAINING_TYPE_PROFILES_TECHNICAL.md b/.claude/docs/technical/TRAINING_TYPE_PROFILES_TECHNICAL.md new file mode 100644 index 0000000..b65db44 --- /dev/null +++ b/.claude/docs/technical/TRAINING_TYPE_PROFILES_TECHNICAL.md @@ -0,0 +1,1097 @@ +# Training Type Profiles – Technisches Design + +**Issue:** #15 +**Status:** Technical Design (Final) +**Erstellt:** 2026-03-23 + +--- + +## Design-Prinzipien + +1. **Maximale Flexibilität:** Neue Parameter ohne Code-Änderung hinzufügbar +2. **Regel-basiert:** Verschiedene Operator-Typen (`>=`, `<=`, `between`, `in`) +3. **Optional:** Kein Parameter ist Pflicht - jeder Trainingstyp wählt relevante Parameter +4. **Erweiterbar:** Neue Regel-Typen einfach implementierbar +5. **Typsicher:** Validierung auf Parameter-Typ-Ebene + +--- + +## 1. Parameter-Registry (Zentrale Definition) + +### DB-Tabelle: `training_parameters` + +```sql +CREATE TABLE training_parameters ( + id SERIAL PRIMARY KEY, + key VARCHAR(50) UNIQUE NOT NULL, -- z.B. "duration_min", "avg_hr" + name_de VARCHAR(100) NOT NULL, + name_en VARCHAR(100) NOT NULL, + category VARCHAR(50) NOT NULL, -- "physical", "physiological", "subjective" + data_type VARCHAR(20) NOT NULL, -- "integer", "float", "string", "boolean" + unit VARCHAR(20), -- "min", "bpm", "km", "kcal", etc. + description_de TEXT, + description_en TEXT, + source_field VARCHAR(100), -- Mapping zu activity_log Spalte + validation_rules JSONB, -- Min/Max/Enum für Validierung + is_active BOOLEAN DEFAULT true, + created_at TIMESTAMP DEFAULT NOW() +); + +-- Beispiel-Daten: +INSERT INTO training_parameters (key, name_de, name_en, category, data_type, unit, source_field, validation_rules) VALUES +('duration_min', 'Dauer', 'Duration', 'physical', 'integer', 'min', 'duration_min', '{"min": 0, "max": 600}'), +('avg_hr', 'Durchschnittspuls', 'Average Heart Rate', 'physiological', 'integer', 'bpm', 'hr_avg', '{"min": 30, "max": 220}'), +('max_hr', 'Maximalpuls', 'Max Heart Rate', 'physiological', 'integer', 'bpm', 'hr_max', '{"min": 40, "max": 220}'), +('min_hr', 'Minimalpuls', 'Min Heart Rate', 'physiological', 'integer', 'bpm', 'hr_min', '{"min": 30, "max": 200}'), +('distance_km', 'Distanz', 'Distance', 'physical', 'float', 'km', 'distance_km', '{"min": 0, "max": 200}'), +('kcal_active', 'Aktive Kalorien', 'Active Calories', 'physical', 'integer', 'kcal', 'kcal_active', '{"min": 0, "max": 5000}'), +('rpe', 'RPE (Anstrengung)', 'RPE (Perceived Exertion)', 'subjective', 'integer', 'scale', 'rpe', '{"min": 1, "max": 10}'), +('pace_min_per_km', 'Pace', 'Pace', 'physical', 'float', 'min/km', 'pace_min_per_km', '{"min": 2, "max": 20}'), +('elevation_gain', 'Höhenmeter', 'Elevation Gain', 'physical', 'integer', 'm', 'elevation_gain', '{"min": 0, "max": 5000}'), +('avg_power', 'Durchschnittsleistung', 'Average Power', 'physiological', 'integer', 'W', 'avg_power', '{"min": 0, "max": 1000}'), +('cadence', 'Trittfrequenz', 'Cadence', 'physical', 'integer', 'rpm', 'cadence', '{"min": 0, "max": 200}'), +('temperature_celsius', 'Temperatur', 'Temperature', 'environmental', 'float', '°C', 'temperature_celsius', '{"min": -30, "max": 50}'), +('humidity_percent', 'Luftfeuchtigkeit', 'Humidity', 'environmental', 'integer', '%', 'humidity_percent', '{"min": 0, "max": 100}'); +``` + +**Vorteile:** +- Neue Parameter einfach per INSERT hinzufügbar +- Zentrale Validierung +- UI kann dynamisch generiert werden (Admin-Interface) +- Mehrsprachig + +--- + +## 2. Regel-System (Flexible Operators) + +### Operator-Typen + +```python +OPERATORS = { + "gte": {"symbol": "≥", "name": "Größer oder gleich", "applies_to": ["integer", "float"]}, + "lte": {"symbol": "≤", "name": "Kleiner oder gleich", "applies_to": ["integer", "float"]}, + "gt": {"symbol": ">", "name": "Größer als", "applies_to": ["integer", "float"]}, + "lt": {"symbol": "<", "name": "Kleiner als", "applies_to": ["integer", "float"]}, + "eq": {"symbol": "=", "name": "Gleich", "applies_to": ["integer", "float", "string", "boolean"]}, + "neq": {"symbol": "≠", "name": "Ungleich", "applies_to": ["integer", "float", "string", "boolean"]}, + "between": {"symbol": "⟷", "name": "Zwischen", "applies_to": ["integer", "float"]}, + "in": {"symbol": "∈", "name": "Enthalten in", "applies_to": ["string", "integer"]}, + "not_in": {"symbol": "∉", "name": "Nicht enthalten in", "applies_to": ["string", "integer"]}, +} +``` + +### Regel-Definition (JSONB in training_types.profile) + +```json +{ + "version": "2.0", + "created_at": "2026-03-23T10:00:00Z", + "updated_at": "2026-03-23T10:00:00Z", + + // ━━━ REGEL-SETS (Verschiedene Dimensionen) ━━━ + "rule_sets": { + + // 1. MINIMUM REQUIREMENTS (Quality Gates) + "minimum_requirements": { + "enabled": true, + "pass_strategy": "weighted_score", // "all_must_pass" | "weighted_score" | "at_least_n" + "pass_threshold": 0.6, + "rules": [ + { + "parameter": "duration_min", + "operator": "gte", + "value": 15, + "weight": 5, + "optional": false, + "reason": "Mindestdauer für signifikante Trainingswirkung" + }, + { + "parameter": "avg_hr", + "operator": "gte", + "value": 100, + "weight": 3, + "optional": false, + "reason": "Puls muss für Ausdauerreiz erhöht sein" + }, + { + "parameter": "max_hr", + "operator": "gte", + "value": 120, + "weight": 1, + "optional": true, + "reason": "Maximalpuls zeigt echte Belastungsspitze" + } + ] + }, + + // 2. INTENSITY ZONES (HF-Zonen) + "intensity_zones": { + "enabled": true, + "method": "percentage_max_hr", + "zones": [ + { + "id": "zone_1_regen", + "name": "Regeneration", + "rules": [ + { + "parameter": "avg_hr_percent", // Berechnet als (avg_hr / user_max_hr) * 100 + "operator": "between", + "value": [50, 60], + "weight": 1 + } + ], + "effect": "Aktive Erholung, Grundlagenausdauer 1", + "color": "#4CAF50", + "target_duration_min": 30, + "metabolic_focus": ["aerobic_oxidation", "fat_metabolism"], + "lactate_range": [0.5, 2.0] + }, + { + "id": "zone_2_ga1", + "name": "Grundlagenausdauer", + "rules": [ + { + "parameter": "avg_hr_percent", + "operator": "between", + "value": [60, 70], + "weight": 1 + } + ], + "effect": "Aerobe Kapazität, Fettstoffwechsel optimieren", + "color": "#2196F3", + "target_duration_min": 45, + "metabolic_focus": ["aerobic_oxidation", "mitochondrial_density"], + "lactate_range": [2.0, 3.5] + }, + { + "id": "zone_3_tempo", + "name": "Tempo/Entwicklung", + "rules": [ + { + "parameter": "avg_hr_percent", + "operator": "between", + "value": [70, 80], + "weight": 1 + } + ], + "effect": "VO2max-Training, Laktattoleranz", + "color": "#FF9800", + "target_duration_min": 20, + "metabolic_focus": ["aerobic_glycolytic", "vo2max_improvement"], + "lactate_range": [3.5, 5.5] + }, + { + "id": "zone_4_threshold", + "name": "Schwellentraining", + "rules": [ + { + "parameter": "avg_hr_percent", + "operator": "between", + "value": [80, 90], + "weight": 1 + } + ], + "effect": "Anaerobe Schwelle verschieben", + "color": "#F44336", + "target_duration_min": 10, + "metabolic_focus": ["anaerobic_lactic", "lactate_threshold"], + "lactate_range": [5.5, 8.0] + }, + { + "id": "zone_5_max", + "name": "Maximale Intensität", + "rules": [ + { + "parameter": "avg_hr_percent", + "operator": "between", + "value": [90, 100], + "weight": 1 + } + ], + "effect": "Maximalkraft, Sprint, HIIT", + "color": "#9C27B0", + "target_duration_min": 5, + "metabolic_focus": ["anaerobic_alactic", "phosphocreatine"], + "lactate_range": [8.0, 15.0] + } + ], + "zone_evaluation": { + "require_min_time_in_zone_percent": 70, + "allow_multiple_zones": true, + "primary_zone_weight": 1.0, + "secondary_zone_weight": 0.5 + } + }, + + // 3. TRAINING EFFECTS (Fähigkeiten-Entwicklung) + "training_effects": { + "enabled": true, + "conditional_effects": [ + { + "condition": { + "parameter": "avg_hr_percent", + "operator": "between", + "value": [60, 80] + }, + "effects": { + "primary_abilities": [ + {"category": "konditionell", "ability": "ausdauer", "intensity": 5} + ], + "secondary_abilities": [ + {"category": "psychisch", "ability": "willenskraft", "intensity": 3} + ] + } + }, + { + "condition": { + "parameter": "avg_hr_percent", + "operator": "gte", + "value": 80 + }, + "effects": { + "primary_abilities": [ + {"category": "konditionell", "ability": "schnelligkeit", "intensity": 5}, + {"category": "konditionell", "ability": "ausdauer", "intensity": 3} + ], + "secondary_abilities": [ + {"category": "psychisch", "ability": "stressresistenz", "intensity": 4} + ] + } + } + ], + "default_effects": { + "primary_abilities": [ + {"category": "konditionell", "ability": "ausdauer", "intensity": 4} + ], + "secondary_abilities": [ + {"category": "koordinativ", "ability": "rhythmus", "intensity": 2} + ] + }, + "muscle_groups": ["legs_posterior", "legs_anterior", "core"], + "energy_systems": ["aerobic_oxidative", "anaerobic_lactic"] + }, + + // 4. PERIODIZATION (Frequenz & Erholung) + "periodization": { + "enabled": true, + "frequency": { + "per_week_min": 2, + "per_week_max": 5, + "per_week_optimal": 3, + "consecutive_days_max": 2 + }, + "recovery": { + "hours_before_same_type": 48, + "hours_before_high_intensity": 24, + "conditional_recovery": [ + { + "condition": { + "parameter": "rpe", + "operator": "gte", + "value": 8 + }, + "recovery_hours": 72 + } + ] + }, + "progression": { + "beginner_duration_min": 20, + "intermediate_duration_min": 30, + "advanced_duration_min": 45, + "volume_increase_percent_per_week": 10, + "intensity_increase_percent_per_week": 5 + }, + "deload": { + "every_n_weeks": 4, + "volume_reduction_percent": 40, + "intensity_reduction_percent": 20 + } + }, + + // 5. PERFORMANCE INDICATORS (KPIs & Benchmarks) + "performance_indicators": { + "enabled": true, + "primary_metrics": ["pace_min_per_km", "avg_hr", "distance_km"], + "secondary_metrics": ["kcal_per_km", "cadence", "elevation_gain"], + "benchmarks": { + "beginner": { + "rules": [ + {"parameter": "pace_min_per_km", "operator": "gte", "value": 7.0}, + {"parameter": "distance_km", "operator": "lte", "value": 5.0} + ] + }, + "intermediate": { + "rules": [ + {"parameter": "pace_min_per_km", "operator": "between", "value": [5.5, 7.0]}, + {"parameter": "distance_km", "operator": "between", "value": [5.0, 10.0]} + ] + }, + "advanced": { + "rules": [ + {"parameter": "pace_min_per_km", "operator": "lte", "value": 5.5}, + {"parameter": "distance_km", "operator": "gte", "value": 10.0} + ] + } + }, + "trend_analysis": { + "lookback_activities": 12, + "improvement_threshold_percent": 5, + "decline_threshold_percent": -5 + } + }, + + // 6. SAFETY & WARNINGS + "safety": { + "enabled": true, + "warnings": [ + { + "parameter": "max_hr", + "operator": "gte", + "value": 180, + "severity": "high", + "message": "Maximalpuls sehr hoch - prüfe Belastung" + }, + { + "parameter": "duration_min", + "operator": "gte", + "value": 120, + "severity": "medium", + "message": "Sehr lange Einheit - ausreichend Erholung einplanen" + }, + { + "parameter": "rpe", + "operator": "gte", + "value": 9, + "severity": "medium", + "message": "Sehr hohe subjektive Anstrengung - 72h Pause empfohlen" + } + ], + "contraindications": [ + "acute_injury_lower_body", + "cardiovascular_episode_recent" + ], + "environmental_limits": { + "temperature_celsius_min": -5, + "temperature_celsius_max": 32, + "humidity_percent_max": 85 + } + }, + + // 7. AI CONTEXT (für KI-Prompts) + "ai_context": { + "enabled": true, + "evaluation_focus": [ + "Pace-Entwicklung über Distanz", + "Herzfrequenz-Stabilität während Training", + "Subjektive Anstrengung vs. objektive Daten" + ], + "questions_to_ask": [ + "Wie fühlte sich dein Tempo an?", + "Hattest du Atembeschwerden?", + "Wie schnell hat sich dein Puls nach dem Training normalisiert?" + ], + "comparison_metrics": [ + "pace_min_per_km", + "avg_hr_at_same_pace", + "rpe_at_same_distance" + ] + } + } +} +``` + +--- + +## 3. Beispiel: Meditation (Andere Regel-Richtung) + +```json +{ + "version": "2.0", + "rule_sets": { + "minimum_requirements": { + "enabled": true, + "pass_strategy": "all_must_pass", + "rules": [ + { + "parameter": "duration_min", + "operator": "gte", + "value": 10, + "weight": 5, + "reason": "Mindestens 10 Minuten für mentale Wirkung" + }, + { + "parameter": "avg_hr", + "operator": "lte", // ⬅ UMGEKEHRT: Kleiner oder gleich + "value": 70, + "weight": 4, + "reason": "Puls sollte im Ruhezustand sein" + }, + { + "parameter": "max_hr", + "operator": "lte", + "value": 80, + "weight": 2, + "reason": "Keine Belastungsspitzen während Meditation" + }, + { + "parameter": "rpe", + "operator": "lte", + "value": 3, + "weight": 3, + "reason": "Meditation sollte nicht anstrengend sein" + } + ] + }, + "intensity_zones": { + "enabled": false // Meditation hat keine HF-Zonen + }, + "training_effects": { + "enabled": true, + "default_effects": { + "primary_abilities": [ + {"category": "psychisch", "ability": "konzentration", "intensity": 5}, + {"category": "psychisch", "ability": "stressresistenz", "intensity": 4} + ], + "secondary_abilities": [ + {"category": "kognitiv", "ability": "aufmerksamkeit", "intensity": 4}, + {"category": "psychisch", "ability": "selbstvertrauen", "intensity": 3} + ] + } + }, + "periodization": { + "enabled": true, + "frequency": { + "per_week_min": 3, + "per_week_max": 7, + "per_week_optimal": 5, + "consecutive_days_max": 7 // Meditation kann täglich + }, + "recovery": { + "hours_before_same_type": 0 // Keine Erholungszeit nötig + } + } + } +} +``` + +--- + +## 4. Backend: Rule Engine (Flexibel & Erweiterbar) + +### 4.1 Core: Rule Evaluator + +```python +from typing import Any, Dict, List, Optional +from datetime import datetime + +class RuleEvaluator: + """ + Generischer Regel-Evaluator für beliebige Parameter und Operatoren. + """ + + OPERATORS = { + "gte": lambda actual, expected: actual >= expected, + "lte": lambda actual, expected: actual <= expected, + "gt": lambda actual, expected: actual > expected, + "lt": lambda actual, expected: actual < expected, + "eq": lambda actual, expected: actual == expected, + "neq": lambda actual, expected: actual != expected, + "between": lambda actual, expected: expected[0] <= actual <= expected[1], + "in": lambda actual, expected: actual in expected, + "not_in": lambda actual, expected: actual not in expected, + } + + @classmethod + def evaluate_rule(cls, rule: Dict, activity: Dict, parameters_registry: Dict) -> Dict: + """ + Evaluiert eine einzelne Regel gegen eine Aktivität. + + Args: + rule: {"parameter": str, "operator": str, "value": Any, "weight": int, ...} + activity: Die Aktivitäts-Daten + parameters_registry: Mapping parameter_key -> config + + Returns: + { + "passed": bool, + "actual_value": Any, + "expected_value": Any, + "parameter": str, + "operator": str, + "reason": str, + "weight": int + } + """ + param_key = rule["parameter"] + operator = rule["operator"] + expected_value = rule["value"] + weight = rule.get("weight", 1) + reason = rule.get("reason", "") + optional = rule.get("optional", False) + + # Parameter aus Registry holen + param_config = parameters_registry.get(param_key) + if not param_config: + return { + "passed": False, + "error": f"Unknown parameter: {param_key}" + } + + # Wert aus Aktivität extrahieren + source_field = param_config.get("source_field", param_key) + actual_value = activity.get(source_field) + + # Optional und nicht vorhanden? → Pass + if optional and actual_value is None: + return { + "passed": True, + "actual_value": None, + "expected_value": expected_value, + "parameter": param_key, + "operator": operator, + "reason": "Optional parameter, not provided", + "weight": weight, + "skipped": True + } + + # Required aber nicht vorhanden? → Fail + if actual_value is None: + return { + "passed": False, + "actual_value": None, + "expected_value": expected_value, + "parameter": param_key, + "operator": operator, + "reason": reason or "Required parameter missing", + "weight": weight + } + + # Operator anwenden + operator_func = cls.OPERATORS.get(operator) + if not operator_func: + return { + "passed": False, + "error": f"Unknown operator: {operator}" + } + + try: + passed = operator_func(actual_value, expected_value) + except Exception as e: + return { + "passed": False, + "error": f"Evaluation error: {str(e)}" + } + + return { + "passed": passed, + "actual_value": actual_value, + "expected_value": expected_value, + "parameter": param_key, + "operator": operator, + "reason": reason, + "weight": weight + } + + @classmethod + def evaluate_rule_set( + cls, + rule_set: Dict, + activity: Dict, + parameters_registry: Dict + ) -> Dict: + """ + Evaluiert ein komplettes Regel-Set (z.B. minimum_requirements). + + Returns: + { + "enabled": bool, + "passed": bool, + "score": float (0-1), + "rule_results": [...] + "summary": str + } + """ + if not rule_set.get("enabled", False): + return { + "enabled": False, + "passed": True, + "score": 1.0, + "rule_results": [] + } + + rules = rule_set.get("rules", []) + pass_strategy = rule_set.get("pass_strategy", "weighted_score") + pass_threshold = rule_set.get("pass_threshold", 0.6) + + rule_results = [] + total_weight = 0 + passed_weight = 0 + + for rule in rules: + result = cls.evaluate_rule(rule, activity, parameters_registry) + rule_results.append(result) + + if result.get("skipped"): + continue + + weight = result.get("weight", 1) + total_weight += weight + + if result["passed"]: + passed_weight += weight + + # Score berechnen + score = passed_weight / total_weight if total_weight > 0 else 1.0 + + # Pass-Strategie anwenden + if pass_strategy == "all_must_pass": + passed = all(r["passed"] for r in rule_results if not r.get("skipped")) + elif pass_strategy == "weighted_score": + passed = score >= pass_threshold + else: + passed = False + + return { + "enabled": True, + "passed": passed, + "score": round(score, 2), + "rule_results": rule_results, + "pass_strategy": pass_strategy, + "pass_threshold": pass_threshold + } +``` + +### 4.2 Master Evaluator + +```python +class TrainingProfileEvaluator: + """ + Hauptklasse für vollständige Aktivitäts-Bewertung. + """ + + def __init__(self, parameters_registry: Dict): + self.parameters_registry = parameters_registry + self.rule_evaluator = RuleEvaluator() + + def evaluate_activity( + self, + activity: Dict, + training_type_profile: Dict, + context: Optional[Dict] = None + ) -> Dict: + """ + Vollständige Bewertung einer Aktivität gegen Trainingstyp-Profil. + + Args: + activity: Aktivitäts-Daten + training_type_profile: Das JSONB-Profil des Trainingstyps + context: { + "user_profile": {...}, + "recent_activities": [...], + "historical_activities": [...] + } + + Returns: + { + "evaluated_at": ISO timestamp, + "profile_version": str, + "rule_set_results": { + "minimum_requirements": {...}, + "intensity_zones": {...}, + "training_effects": {...}, + "periodization": {...}, + "performance_indicators": {...}, + "safety": {...} + }, + "overall_score": float (0-1), + "quality_label": str, + "recommendations": [str], + "warnings": [str] + } + """ + if not training_type_profile: + return self._create_unvalidated_result() + + rule_sets = training_type_profile.get("rule_sets", {}) + context = context or {} + + results = { + "evaluated_at": datetime.now().isoformat(), + "profile_version": training_type_profile.get("version", "unknown"), + "rule_set_results": {} + } + + # 1. Minimum Requirements + if "minimum_requirements" in rule_sets: + results["rule_set_results"]["minimum_requirements"] = \ + self.rule_evaluator.evaluate_rule_set( + rule_sets["minimum_requirements"], + activity, + self.parameters_registry + ) + + # 2. Intensity Zones + if "intensity_zones" in rule_sets: + results["rule_set_results"]["intensity_zones"] = \ + self._evaluate_intensity_zones( + rule_sets["intensity_zones"], + activity, + context.get("user_profile", {}) + ) + + # 3. Training Effects + if "training_effects" in rule_sets: + results["rule_set_results"]["training_effects"] = \ + self._evaluate_training_effects( + rule_sets["training_effects"], + activity, + results["rule_set_results"].get("intensity_zones") + ) + + # 4. Periodization + if "periodization" in rule_sets: + results["rule_set_results"]["periodization"] = \ + self._evaluate_periodization( + rule_sets["periodization"], + activity, + context.get("recent_activities", []) + ) + + # 5. Performance Indicators + if "performance_indicators" in rule_sets: + results["rule_set_results"]["performance_indicators"] = \ + self._evaluate_performance( + rule_sets["performance_indicators"], + activity, + context.get("historical_activities", []) + ) + + # 6. Safety Warnings + if "safety" in rule_sets: + results["rule_set_results"]["safety"] = \ + self._evaluate_safety( + rule_sets["safety"], + activity + ) + + # Overall Score & Quality Label + overall_score = self._calculate_overall_score(results["rule_set_results"]) + results["overall_score"] = overall_score + results["quality_label"] = self._get_quality_label(overall_score) + + # Recommendations & Warnings + results["recommendations"] = self._generate_recommendations(results) + results["warnings"] = self._collect_warnings(results) + + return results + + def _calculate_overall_score(self, rule_set_results: Dict) -> float: + """Berechnet gewichteten Gesamt-Score.""" + weights = { + "minimum_requirements": 0.4, + "intensity_zones": 0.2, + "training_effects": 0.1, + "periodization": 0.2, + "performance_indicators": 0.1 + } + + total_score = 0.0 + total_weight = 0.0 + + for rule_set_name, weight in weights.items(): + result = rule_set_results.get(rule_set_name) + if result and result.get("enabled"): + score = result.get("score", 0.5) + total_score += score * weight + total_weight += weight + + return round(total_score / total_weight, 2) if total_weight > 0 else 0.5 + + def _get_quality_label(self, score: float) -> str: + """Konvertiert Score zu Label.""" + if score >= 0.9: + return "excellent" + elif score >= 0.7: + return "good" + elif score >= 0.5: + return "acceptable" + else: + return "poor" + + # ... Weitere Helper-Methoden für Intensity, Effects, etc. ... +``` + +--- + +## 5. DB-Schema + +```sql +-- Training Parameters Registry +CREATE TABLE training_parameters ( + id SERIAL PRIMARY KEY, + key VARCHAR(50) UNIQUE NOT NULL, + name_de VARCHAR(100) NOT NULL, + name_en VARCHAR(100) NOT NULL, + category VARCHAR(50) NOT NULL, + data_type VARCHAR(20) NOT NULL, + unit VARCHAR(20), + description_de TEXT, + description_en TEXT, + source_field VARCHAR(100), + validation_rules JSONB, + is_active BOOLEAN DEFAULT true, + created_at TIMESTAMP DEFAULT NOW() +); + +-- Training Types (Erweitert) +ALTER TABLE training_types ADD COLUMN profile JSONB DEFAULT NULL; + +-- Activity Log (Erweitert) +ALTER TABLE activity_log ADD COLUMN evaluation JSONB DEFAULT NULL; +ALTER TABLE activity_log ADD COLUMN quality_label VARCHAR(20); -- Index für schnelle Queries +ALTER TABLE activity_log ADD COLUMN overall_score FLOAT; -- Index für Sortierung + +CREATE INDEX idx_activity_quality ON activity_log(quality_label) WHERE quality_label IS NOT NULL; +CREATE INDEX idx_activity_score ON activity_log(overall_score) WHERE overall_score IS NOT NULL; +``` + +--- + +## 6. API-Endpoints + +### Admin-Endpoints (Parameter-Verwaltung) + +```python +@router.get("/api/admin/training-parameters") +def list_training_parameters(session: dict = Depends(require_admin)): + """Liste aller verfügbaren Parameter.""" + with get_db() as conn: + cur = get_cursor(conn) + cur.execute("SELECT * FROM training_parameters WHERE is_active = true ORDER BY category, key") + return [r2d(r) for r in cur.fetchall()] + +@router.post("/api/admin/training-parameters") +def create_training_parameter(data: dict, session: dict = Depends(require_admin)): + """Neuen Parameter anlegen.""" + # Validierung + INSERT + pass +``` + +### Evaluation-Endpoints + +```python +@router.post("/api/activity/{activity_id}/evaluate") +def evaluate_activity_endpoint( + activity_id: str, + session: dict = Depends(require_auth) +): + """ + Evaluiert eine Aktivität gegen ihr Trainingstyp-Profil. + """ + pid = session['profile_id'] + + with get_db() as conn: + cur = get_cursor(conn) + + # Activity laden + cur.execute("SELECT * FROM activity_log WHERE id = %s AND profile_id = %s", (activity_id, pid)) + activity = r2d(cur.fetchone()) + + if not activity: + raise HTTPException(404, "Activity not found") + + # Training Type Profil laden + cur.execute("SELECT profile FROM training_types WHERE id = %s", (activity['training_type_id'],)) + row = cur.fetchone() + profile = row['profile'] if row else None + + if not profile: + raise HTTPException(400, "Training type has no profile configured") + + # Context laden (recent + historical activities) + context = load_evaluation_context(pid, activity['date'], cur) + + # Parameters Registry laden + cur.execute("SELECT * FROM training_parameters WHERE is_active = true") + parameters = {r['key']: r2d(r) for r in cur.fetchall()} + + # Evaluieren + evaluator = TrainingProfileEvaluator(parameters) + evaluation_result = evaluator.evaluate_activity(activity, profile, context) + + # Evaluation speichern + cur.execute(""" + UPDATE activity_log + SET evaluation = %s, + quality_label = %s, + overall_score = %s + WHERE id = %s + """, ( + Json(evaluation_result), + evaluation_result['quality_label'], + evaluation_result['overall_score'], + activity_id + )) + + logger.info(f"[EVALUATION] Activity {activity_id}: {evaluation_result['quality_label']} ({evaluation_result['overall_score']})") + + return evaluation_result + + +@router.post("/api/activity/import-csv") +def import_activity_csv(...): + """ + CSV-Import mit automatischer Evaluation. + """ + # ... Import-Logik ... + + for entry in imported_entries: + # INSERT Activity + activity_id = insert_activity(entry) + + # Auto-Evaluate wenn Training Type Profil existiert + try: + evaluate_activity_endpoint(activity_id, session) + except Exception as e: + logger.warning(f"[EVALUATION] Failed for {activity_id}: {e}") + # Nicht-blockierend: Import erfolgreich auch ohne Evaluation + + return {"imported": len(imported_entries)} +``` + +--- + +## 7. Erweiterbarkeit: Neue Parameter hinzufügen + +### Beispiel: Neue Parameter "Cadence" und "Stride Length" + +```sql +-- 1. Parameter registrieren +INSERT INTO training_parameters (key, name_de, name_en, category, data_type, unit, source_field, validation_rules) VALUES +('cadence', 'Schrittfrequenz', 'Cadence', 'physical', 'integer', 'spm', 'cadence', '{"min": 120, "max": 220}'), +('stride_length', 'Schrittlänge', 'Stride Length', 'physical', 'float', 'm', 'stride_length', '{"min": 0.5, "max": 2.5}'); + +-- 2. Activity Log Spalten hinzufügen (optional, wenn nicht schon vorhanden) +ALTER TABLE activity_log ADD COLUMN cadence INTEGER; +ALTER TABLE activity_log ADD COLUMN stride_length FLOAT; + +-- 3. Training Type Profil erweitern (per Admin-UI oder SQL) +UPDATE training_types SET profile = jsonb_set( + profile, + '{rule_sets,minimum_requirements,rules}', + profile->'rule_sets'->'minimum_requirements'->'rules' || '[ + { + "parameter": "cadence", + "operator": "gte", + "value": 160, + "weight": 2, + "optional": true, + "reason": "Optimale Laufökonomie bei 160+ spm" + } + ]'::jsonb +) +WHERE name_de = 'Laufen'; +``` + +**Fertig!** Kein Backend-Code-Change nötig. Rule Engine erkennt neuen Parameter automatisch. + +--- + +## 8. Future: Automatische Trainingstyp-Klassifizierung + +**Feature Request (nach Implementierung):** + +Statt nur Namen-Matching: Klassifizierung basierend auf erreichten Parametern. + +```python +def classify_activity_by_profile(activity: Dict, training_types: List[Dict]) -> str: + """ + Findet passenden Trainingstyp basierend auf Parametern. + + Beispiel: + Laufen mit avg_hr_percent 60-70% → "Laufen: Grundlagenausdauer" + Laufen mit avg_hr_percent 80-90% → "Laufen: Schwellentraining" + """ + best_match = None + best_score = 0 + + for training_type in training_types: + profile = training_type.get('profile') + if not profile: + continue + + # Prüfe Minimum Requirements + min_req_result = evaluate_rule_set(profile['rule_sets']['minimum_requirements'], activity) + + # Prüfe Intensity Zone + zone_result = evaluate_intensity_zones(profile['rule_sets']['intensity_zones'], activity) + + # Score: Wie gut passt die Aktivität zu diesem Profil? + match_score = (min_req_result['score'] * 0.6) + (zone_result['score'] * 0.4) + + if match_score > best_score: + best_score = match_score + best_match = { + "training_type_id": training_type['id'], + "training_type_name": training_type['name_de'], + "variant": zone_result.get('dominant_zone'), + "match_score": match_score + } + + return best_match +``` + +**UI:** +``` +Importierte Aktivität: "Outdoor Run" + +Automatische Klassifizierung: +✅ Laufen: Grundlagenausdauer (Match: 92%) + - Dauer: 45min ✓ + - Ø HF: 135 bpm (68% Max-HF) ✓ + - Zone: Grundlagenausdauer ✓ + +Alternative Matches: + Laufen: Tempo (Match: 45%) + +[Übernehmen] [Manual ändern] +``` + +--- + +## Zusammenfassung + +### Flexibilität erreicht durch: + +1. **Parameter-Registry** - Zentrale, erweiterbare Parameter-Definition +2. **Operator-System** - Verschiedene Vergleichsoperatoren (>=, <=, between, in) +3. **Regel-Strategie** - all_must_pass, weighted_score, at_least_n +4. **Optional-Flags** - Nicht jeder Parameter ist Pflicht +5. **Konditionale Regeln** - "Wenn X, dann Y" +6. **JSONB-Storage** - Beliebige Struktur ohne Schema-Changes + +### Neue Parameter hinzufügen: + +```sql +-- Nur 1 INSERT nötig, kein Code-Change! +INSERT INTO training_parameters (...) VALUES (...); +``` + +### Neue Trainingstypen anlegen: + +```json +// Profile einfach kopieren & anpassen +// Meditation: avg_hr <= 70 +// HIIT: avg_hr >= 160, duration_min <= 30 +// Yoga: rpe <= 4, duration_min >= 30 +``` + +--- + +**Bereit für Implementierung?** 🚀 + +**Geschätzter Aufwand:** +- Phase 1 (Foundation): 6-8h +- Phase 2 (Admin-UI): 4-6h +- Phase 3 (User-UI): 4-6h +- **Total: 14-20h** diff --git a/.claude/docs/technical/V9D_PHASE2_VITALS_SLEEP.md b/.claude/docs/technical/V9D_PHASE2_VITALS_SLEEP.md new file mode 100644 index 0000000..d5152b4 --- /dev/null +++ b/.claude/docs/technical/V9D_PHASE2_VITALS_SLEEP.md @@ -0,0 +1,1257 @@ +# Technische Spezifikation: v9d Phase 2 – Vitalwerte & Schlaf + +**Version:** 1.1 +**Status:** Review-Änderungen eingearbeitet +**Datum:** 22.03.2026 +**Basis:** TRAINING_TYPES.md + SLEEP_MODULE.md + +**Änderungen v1.1:** +- Wochenplanung → DB-persistiert (weekly_goals Tabelle) +- Schlafphasen-Segmente → JSONB-Spalte hinzugefügt (sleep_segments) +- Erholungsstatus-Gewichtung → 50% HRV / 30% Ruhepuls / 20% Training +- Schlafschulden-Zeitraum → 14 Tage (statt 7) +- Ruhepuls-Warnung → beide Baselines (7d + 30d) +- Vitalwerte-Seite → eigener Nav-Eintrag +- Implementierungs-Reihenfolge → Schlaf zuerst (2b → 2c → 2d → 2a → 2e) + +--- + +## 1. Entscheidungen zu offenen Fragen + +### 1.1 TRAINING_TYPES.md + +**Q1: HRV-Erfassung – eigene Tabelle oder Spalte in vitals_log?** +✅ **Entscheidung:** Spalte in `vitals_log` (nullable) +**Begründung:** HRV und Ruhepuls werden beide morgens gemessen, gehören konzeptionell zusammen. Separate Tabelle würde Joins und Korrelationen komplizieren. + +**Q2: Wochenplanung – DB persistieren oder Session-Only?** +✅ **Entscheidung:** DB-persistiert (Tabelle `weekly_goals`) +**Begründung:** localStorage geht bei Browser-Wechsel verloren. Einfaches Soll/Ist-Tracking für Wochenplanung wird in Phase 2 implementiert. Erweiterte Ziele-Features kommen in v9f. + +**Q3: HF-Kurven aus Apple Health oder nur Avg/Max?** +✅ **Entscheidung:** Nur Avg/Max für Phase 2 +**Begründung:** HF-Kurven (Zeit × HF) benötigen komplexe Zeitserien-Struktur (JSONB oder separate Tabelle). Avg/Max deckt 80% der Use Cases ab. Kurven kommen in v9h (Connectoren). + +### 1.2 SLEEP_MODULE.md + +**Q4: sleep_log oder Erweiterung von activity_log?** +✅ **Entscheidung:** Separate Tabelle `sleep_log` +**Begründung:** Schlaf ist konzeptionell keine Aktivität. Unterschiedliche Felder (bedtime, wake_time, quality, phases) passen nicht in activity_log Schema. Separate Tabelle ermöglicht saubere Korrelations-Queries. + +**Q5: Schlafphasen – separate Spalten oder JSONB?** +✅ **Entscheidung:** Beides – Summen-Spalten + JSONB für Segmente +**Begründung:** +- **Summen-Spalten** (`deep_minutes`, `rem_minutes`, `light_minutes`, `awake_minutes`): Einfach zu querien, performant für Aggregationen +- **JSONB-Spalte** (`sleep_segments`, nullable): Rohdaten der Phasen-Übergänge aus Apple Health Import + - Format: `[{"phase": "deep", "start": "23:44", "duration_min": 42}, ...]` + - Nur bei Import befüllt, bei manueller Eingabe NULL + - Basis für spätere Analyse (Zykluslänge, Unterbrechungsmuster) in v9e/f + +**Q6: Morgendlicher Check-in – Frontend oder Backend?** +✅ **Entscheidung:** Frontend-State +**Begründung:** Frontend prüft beim Dashboard-Load ob Schlaf-Eintrag für letzte Nacht existiert (`GET /api/sleep?date={yesterday}`). Kein Backend-State nötig, keine zusätzliche Komplexität. + +**Q7: Korrelationsberechnung – Backend oder Frontend?** +✅ **Entscheidung:** Backend (Python) +**Begründung:** Konsistent mit bestehenden Correlation-Endpoints (`/api/nutrition/correlations`). Python ist besser für statistisches Computing (numpy). Frontend rendert nur Ergebnis. + +**Q8: Apple Health CSV-Format für Schlaf?** +✅ **Analysiert:** Apple Health Schlaf-Export hat folgendes Format: +```csv +Start,End,Duration (hr),Value,Source +2026-03-14 22:44:23,2026-03-14 23:00:19,0.266,Kern,Apple Watch von Lars +2026-03-14 23:00:19,2026-03-14 23:12:16,0.199,REM,Apple Watch von Lars +2026-03-14 23:12:16,2026-03-14 23:34:40,0.373,Kern,Apple Watch von Lars +2026-03-14 23:34:40,2026-03-14 23:44:38,0.166,Tief,Apple Watch von Lars +``` + +**Import-Logik:** +- Value-Mapping: `Kern` → light_minutes, `REM` → rem_minutes, `Tief` → deep_minutes, `Wach` → awake_minutes +- `Schlafend` (initial entry) wird ignoriert, nur Phasen werden gezählt +- Aggregation nach Datum: Alle Segmente einer Nacht gruppieren (Einschlafzeit = erster Start, Aufwachzeit = letztes End) +- Duration: Summe aller Phasen in Minuten (`Duration (hr) * 60`) +- Datum-Zuordnung: Wenn Schlaf über Mitternacht geht → Datum der Aufwachzeit verwenden +- **Segmente:** Raw-Daten werden zusätzlich in `sleep_segments` JSONB gespeichert für spätere Analyse +- Qualität: Bei Import nicht verfügbar (NULL), User kann nachträglich setzen + +--- + +## 2. Datenbankschema + +### 2.1 Neue Tabellen + +#### `rest_days` +```sql +CREATE TABLE rest_days ( + id SERIAL PRIMARY KEY, + profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + date DATE NOT NULL, + rest_config JSONB NOT NULL, + note TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT unique_rest_day_per_profile UNIQUE(profile_id, date) +); + +CREATE INDEX idx_rest_days_profile_date ON rest_days(profile_id, date DESC); +``` + +**JSONB Schema (`rest_config`):** +```typescript +interface RestConfig { + focus: 'muscle_recovery' | 'cardio_recovery' | 'mental_rest' | 'deload' | 'injury' + rest_from: string[] // Training type IDs (z.B. ['strength', 'hiit']) + allows: string[] // Erlaubte Aktivitäten (z.B. ['cardio_low', 'meditation']) + intensity_max?: number // Max HF% (1-100), optional + note?: string // Zusätzliche Info, optional +} +``` + +**Beispiele:** +```json +// Krafttraining-Ruhetag +{ + "focus": "muscle_recovery", + "rest_from": ["strength", "hiit"], + "allows": ["cardio_low", "meditation", "mobility"], + "intensity_max": 60 +} + +// Kompletter Entspannungstag +{ + "focus": "mental_rest", + "rest_from": ["strength", "cardio", "hiit", "power"], + "allows": ["meditation", "walk"], + "intensity_max": 50 +} + +// Deload-Woche +{ + "focus": "deload", + "rest_from": [], + "allows": ["strength", "cardio", "mobility"], + "intensity_max": 70, + "note": "Deload Week 4 - 70% Volumen" +} +``` + +**Erklärung:** +- **Flexibles Modell** statt binärem `full_rest`/`active_recovery` +- **Kontext-spezifische Ruhetage**: Ruhe von Kraft, aber Cardio erlaubt +- **Integration mit Wochenplanung**: Regelbasierte Validierung möglich +- **Intensitätsbegrenzung**: Max HF% für erlaubte Aktivitäten +- UNIQUE Constraint verhindert Duplikate pro Tag + +#### `vitals_log` +```sql +CREATE TABLE vitals_log ( + id SERIAL PRIMARY KEY, + profile_id VARCHAR(36) NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + date DATE NOT NULL, + resting_hr INTEGER CHECK (resting_hr > 0 AND resting_hr < 200), + hrv INTEGER CHECK (hrv > 0), -- nullable, in Millisekunden + note TEXT, + source VARCHAR(20) DEFAULT 'manual' CHECK (source IN ('manual', 'apple_health', 'garmin')), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT unique_vitals_per_day UNIQUE(profile_id, date) +); + +CREATE INDEX idx_vitals_profile_date ON vitals_log(profile_id, date DESC); +``` + +**Erklärung:** +- `resting_hr` = Ruhepuls in bpm (Schläge pro Minute) +- `hrv` = Herzratenvariabilität in ms (optional, nicht alle Geräte messen das) +- Upsert-Logik: Bei Reimport überschreiben wenn source = 'manual' Vorrang hat + +#### `weekly_goals` +```sql +CREATE TABLE weekly_goals ( + id SERIAL PRIMARY KEY, + profile_id VARCHAR(36) NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + week_start DATE NOT NULL, -- Montag der Woche (ISO Week) + goals JSONB NOT NULL, -- {"strength": 3, "cardio": 2, "mobility": 1, "rest": 1} + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT unique_weekly_goal_per_profile UNIQUE(profile_id, week_start) +); + +CREATE INDEX idx_weekly_goals_profile_week ON weekly_goals(profile_id, week_start DESC); +``` + +**Erklärung:** +- `week_start` = Montag der Woche (z.B. 2026-03-17 für KW 12) +- `goals` = JSONB mit Soll-Werten pro Trainingstyp-Kategorie + - Beispiel: `{"strength": 3, "cardio": 2, "mobility": 1, "rest": 1}` = 3x Kraft, 2x Cardio, 1x Mobility, 1 Ruhetag + - Flexible Struktur, kann um weitere Kategorien erweitert werden +- Dashboard zeigt Soll/Ist-Vergleich für aktuelle Woche + +#### `sleep_log` +```sql +CREATE TABLE sleep_log ( + id SERIAL PRIMARY KEY, + profile_id VARCHAR(36) NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + date DATE NOT NULL, -- Datum der Nacht (= Aufwachdatum) + bedtime TIME, -- Einschlafzeit (nullable, z.B. 23:15) + wake_time TIME, -- Aufwachzeit (nullable, z.B. 06:45) + duration_minutes INTEGER NOT NULL CHECK (duration_minutes > 0), -- Gesamtschlafdauer + quality INTEGER CHECK (quality >= 1 AND quality <= 5), -- nullable, subjektiv 1-5 + wake_count INTEGER CHECK (wake_count >= 0), -- nullable, wie oft aufgewacht + deep_minutes INTEGER CHECK (deep_minutes >= 0), -- Tiefschlaf + rem_minutes INTEGER CHECK (rem_minutes >= 0), -- REM-Schlaf + light_minutes INTEGER CHECK (light_minutes >= 0), -- Leichtschlaf (Kern) + awake_minutes INTEGER CHECK (awake_minutes >= 0), -- Wachphasen im Bett + sleep_segments JSONB, -- Rohdaten: [{"phase": "deep", "start": "23:44", "duration_min": 42}, ...] + note TEXT, + source VARCHAR(20) DEFAULT 'manual' CHECK (source IN ('manual', 'apple_health', 'garmin')), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT unique_sleep_per_day UNIQUE(profile_id, date) +); + +CREATE INDEX idx_sleep_profile_date ON sleep_log(profile_id, date DESC); +``` + +**Erklärung:** +- `date` = Datum der Nacht (wichtig: Aufwachdatum, nicht Einschlafdatum!) +- `bedtime` + `wake_time` = nullable, nur wenn bekannt +- `duration_minutes` = Pflichtfeld, kann aus bedtime/wake_time berechnet oder direkt eingegeben werden +- Phasen-Summen (deep/rem/light/awake_minutes) = nullable (nur wenn Smartwatch-Daten vorhanden) +- `sleep_segments` = JSONB nullable, enthält Rohdaten der Phasen-Übergänge aus Import + - Format: `[{"phase": "deep|rem|light|awake", "start": "HH:MM", "duration_min": N}, ...]` + - Nur bei Import befüllt, bei manueller Eingabe NULL + - Basis für spätere Zyklen-Analyse (v9e/f) + +### 2.2 Erweiterte Tabellen + +#### `activity_log` – Neue Spalten für Herzfrequenz +```sql +ALTER TABLE activity_log +ADD COLUMN avg_hr INTEGER CHECK (avg_hr > 0 AND avg_hr < 250), +ADD COLUMN max_hr INTEGER CHECK (max_hr > 0 AND max_hr < 250); +``` + +**Erklärung:** +- `avg_hr` = Durchschnittliche Herzfrequenz während Training (bpm) +- `max_hr` = Maximale Herzfrequenz während Training (bpm) +- Beide nullable (manuelle Einträge haben oft keine HF-Daten) + +#### `profiles` – HF-Max für Zonen-Berechnung +```sql +ALTER TABLE profiles +ADD COLUMN hf_max INTEGER CHECK (hf_max > 0 AND hf_max < 250), +ADD COLUMN sleep_goal_minutes INTEGER DEFAULT 450 CHECK (sleep_goal_minutes > 0); -- 7h 30min +``` + +**Erklärung:** +- `hf_max` = Maximale Herzfrequenz (nullable, Standard: 220 - Alter) +- `sleep_goal_minutes` = Schlafziel in Minuten (Standard: 450 = 7h 30min) + +--- + +## 3. Migrationen + +### Migration 008: Vitalwerte & Ruhetage (initial) +**Datei:** `backend/migrations/008_vitals_rest_days.sql` +**Status:** Deployed to production (mit einfachem type-Schema) + +```sql +-- Rest Days (initial simple schema) +CREATE TABLE IF NOT EXISTS rest_days ( + id SERIAL PRIMARY KEY, + profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + date DATE NOT NULL, + type VARCHAR(20) NOT NULL CHECK (type IN ('full_rest', 'active_recovery')), + note TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT unique_rest_day_per_profile UNIQUE(profile_id, date) +); +CREATE INDEX idx_rest_days_profile_date ON rest_days(profile_id, date DESC); +``` + +### Migration 010: Rest Days Refactoring zu JSONB +**Datei:** `backend/migrations/010_rest_days_jsonb.sql` +**Status:** In Entwicklung + +```sql +-- Refactor rest_days to JSONB config for flexible rest day types +ALTER TABLE rest_days +DROP COLUMN IF EXISTS type; + +ALTER TABLE rest_days +ADD COLUMN rest_config JSONB NOT NULL DEFAULT '{}'::jsonb; + +-- Validation function for rest_config +CREATE OR REPLACE FUNCTION validate_rest_config(config JSONB) RETURNS BOOLEAN AS $$ +BEGIN + -- Must have focus field + IF NOT (config ? 'focus') THEN + RETURN FALSE; + END IF; + + -- rest_from must be array if present + IF (config ? 'rest_from') AND jsonb_typeof(config->'rest_from') != 'array' THEN + RETURN FALSE; + END IF; + + -- allows must be array if present + IF (config ? 'allows') AND jsonb_typeof(config->'allows') != 'array' THEN + RETURN FALSE; + END IF; + + RETURN TRUE; +END; +$$ LANGUAGE plpgsql; + +-- Add check constraint +ALTER TABLE rest_days +ADD CONSTRAINT valid_rest_config CHECK (validate_rest_config(rest_config)); + +COMMENT ON COLUMN rest_days.rest_config IS 'JSONB: {focus, rest_from[], allows[], intensity_max, note}'; +``` + +-- Vitals (Resting HR + HRV) +CREATE TABLE IF NOT EXISTS vitals_log ( + id SERIAL PRIMARY KEY, + profile_id VARCHAR(36) NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + date DATE NOT NULL, + resting_hr INTEGER CHECK (resting_hr > 0 AND resting_hr < 200), + hrv INTEGER CHECK (hrv > 0), + note TEXT, + source VARCHAR(20) DEFAULT 'manual' CHECK (source IN ('manual', 'apple_health', 'garmin')), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT unique_vitals_per_day UNIQUE(profile_id, date) +); +CREATE INDEX idx_vitals_profile_date ON vitals_log(profile_id, date DESC); + +-- Extend activity_log for heart rate data +ALTER TABLE activity_log +ADD COLUMN IF NOT EXISTS avg_hr INTEGER CHECK (avg_hr > 0 AND avg_hr < 250), +ADD COLUMN IF NOT EXISTS max_hr INTEGER CHECK (max_hr > 0 AND max_hr < 250); + +-- Extend profiles for HF max and sleep goal +ALTER TABLE profiles +ADD COLUMN IF NOT EXISTS hf_max INTEGER CHECK (hf_max > 0 AND hf_max < 250), +ADD COLUMN IF NOT EXISTS sleep_goal_minutes INTEGER DEFAULT 450 CHECK (sleep_goal_minutes > 0); + +-- Weekly Goals (Soll/Ist Wochenplanung) +CREATE TABLE IF NOT EXISTS weekly_goals ( + id SERIAL PRIMARY KEY, + profile_id VARCHAR(36) NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + week_start DATE NOT NULL, + goals JSONB NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT unique_weekly_goal_per_profile UNIQUE(profile_id, week_start) +); +CREATE INDEX idx_weekly_goals_profile_week ON weekly_goals(profile_id, week_start DESC); +``` + +### Migration 009: Schlaf-Modul +**Datei:** `backend/migrations/009_sleep_log.sql` + +```sql +CREATE TABLE IF NOT EXISTS sleep_log ( + id SERIAL PRIMARY KEY, + profile_id VARCHAR(36) NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + date DATE NOT NULL, + bedtime TIME, + wake_time TIME, + duration_minutes INTEGER NOT NULL CHECK (duration_minutes > 0), + quality INTEGER CHECK (quality >= 1 AND quality <= 5), + wake_count INTEGER CHECK (wake_count >= 0), + deep_minutes INTEGER CHECK (deep_minutes >= 0), + rem_minutes INTEGER CHECK (rem_minutes >= 0), + light_minutes INTEGER CHECK (light_minutes >= 0), + awake_minutes INTEGER CHECK (awake_minutes >= 0), + sleep_segments JSONB, -- Rohdaten: [{"phase": "deep", "start": "23:44", "duration_min": 42}, ...] + note TEXT, + source VARCHAR(20) DEFAULT 'manual' CHECK (source IN ('manual', 'apple_health', 'garmin')), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT unique_sleep_per_day UNIQUE(profile_id, date) +); + +CREATE INDEX idx_sleep_profile_date ON sleep_log(profile_id, date DESC); +``` + +--- + +## 4. Backend API-Endpoints + +### 4.1 Rest Days (`routers/rest_days.py`) + +```python +router = APIRouter(prefix="/api/rest-days", tags=["rest-days"]) + +# CRUD +GET /api/rest-days?limit=90 # List rest days (last 90 days) +POST /api/rest-days # Create rest day (with config validation) +PUT /api/rest-days/{id} # Update rest day +DELETE /api/rest-days/{id} # Delete rest day + +# Stats & Validation +GET /api/rest-days/stats # Count per week, last 4 weeks +POST /api/rest-days/validate-activity # Check if activity conflicts with rest day +``` + +**Request/Response Models:** +```python +class RestConfig(BaseModel): + focus: Literal['muscle_recovery', 'cardio_recovery', 'mental_rest', 'deload', 'injury'] + rest_from: list[str] # Training type IDs + allows: list[str] # Allowed activity type IDs + intensity_max: int | None = None # Max HF% (1-100) + note: str = "" + +class RestDayCreate(BaseModel): + date: str # YYYY-MM-DD + rest_config: RestConfig + note: str = "" + +class RestDayResponse(BaseModel): + id: int + profile_id: str + date: str + rest_config: dict # JSONB as dict + note: str + created_at: str + +class ActivityConflictResponse(BaseModel): + conflict: bool + severity: Literal['warning', 'info', 'none'] + message: str +``` + +**Validation Logic:** +```python +def check_rest_day_conflict(profile_id: str, date: str, activity_type: str) -> ActivityConflictResponse: + """ + Check if activity conflicts with rest day configuration. + + Returns: + - conflict=True, severity='warning': Activity is in rest_from + - conflict=True, severity='info': Activity not in allows list + - conflict=False: No conflict + """ + rest_day = get_rest_day(profile_id, date) + if not rest_day: + return {"conflict": False, "severity": "none", "message": ""} + + config = rest_day["rest_config"] + + # Check if activity is in rest_from + if activity_type in config.get("rest_from", []): + return { + "conflict": True, + "severity": "warning", + "message": f"Ruhetag für {activity_type}. Trotzdem erfassen?" + } + + # Check if activity is allowed + if config.get("allows") and activity_type not in config.get("allows", []): + return { + "conflict": True, + "severity": "info", + "message": "Aktivität nicht in erlaubten Aktivitäten." + } + + return {"conflict": False, "severity": "none", "message": ""} +``` + +### 4.2 Vitals (`routers/vitals.py`) + +```python +router = APIRouter(prefix="/api/vitals", tags=["vitals"]) + +# CRUD +GET /api/vitals?limit=90 # List vitals (last 90 days) +POST /api/vitals # Upsert (by date) +PUT /api/vitals/{id} # Update +DELETE /api/vitals/{id} # Delete +GET /api/vitals/by-date/{date} # Get entry for specific date + +# Stats & Trends +GET /api/vitals/trend?days=30 # Resting HR trend (avg, baseline, warnings) +GET /api/vitals/hrv-baseline?days=30 # HRV baseline + deviation +``` + +**Request/Response Models:** +```python +class VitalsCreate(BaseModel): + date: str # YYYY-MM-DD + resting_hr: int + hrv: int | None = None + note: str = "" + source: Literal['manual', 'apple_health', 'garmin'] = 'manual' + +class VitalsResponse(BaseModel): + id: int + profile_id: str + date: str + resting_hr: int + hrv: int | None + note: str + source: str + created_at: str + updated_at: str + +class VitalsTrendResponse(BaseModel): + current_hr: int | None # letzter Eintrag + avg_hr_7d: float # 7-Tage-Durchschnitt + baseline_hr_30d: float # 30-Tage-Baseline + is_elevated_7d: bool # >+5 bpm über 7d-Baseline (akut) + is_elevated_30d: bool # >+5 bpm über 30d-Baseline (Trend) + warning_7d: str | None # "Ruhepuls heute erhöht – Ruhetag?" + warning_30d: str | None # "Ruhepuls im Aufwärtstrend – Übertraining?" +``` + +### 4.3 Sleep (`routers/sleep.py`) + +```python +router = APIRouter(prefix="/api/sleep", tags=["sleep"]) + +# CRUD +GET /api/sleep?limit=90 # List sleep entries +POST /api/sleep # Create/Upsert sleep entry +PUT /api/sleep/{id} # Update +DELETE /api/sleep/{id} # Delete +GET /api/sleep/by-date/{date} # Get entry for specific date + +# Import +POST /api/sleep/import-csv # Apple Health sleep CSV import + +# Stats & Trends +GET /api/sleep/stats?days=7 # 7-day average (duration, quality) +GET /api/sleep/trend?days=30 # Duration + quality trend +GET /api/sleep/phases?days=30 # Phase distribution (deep, REM, light, awake) +GET /api/sleep/debt?days=14 # Sleep debt calculation (14 days default) +GET /api/sleep/optimal-window # Optimal bedtime window (min 14 entries) + +# Correlations +GET /api/sleep/correlations/resting-hr # Sleep ↔ Resting HR correlation +GET /api/sleep/correlations/training # Sleep ↔ Training performance +GET /api/sleep/correlations/weight # Sleep ↔ Weight (weekly) +``` + +**Request/Response Models:** +```python +class SleepCreate(BaseModel): + date: str # YYYY-MM-DD + bedtime: str | None = None # HH:MM + wake_time: str | None = None # HH:MM + duration_minutes: int + quality: int | None = None # 1-5 + wake_count: int | None = None + deep_minutes: int | None = None + rem_minutes: int | None = None + light_minutes: int | None = None + awake_minutes: int | None = None + note: str = "" + source: Literal['manual', 'apple_health', 'garmin'] = 'manual' + +class SleepResponse(BaseModel): + id: int + profile_id: str + date: str + bedtime: str | None + wake_time: str | None + duration_minutes: int + duration_formatted: str # "7h 30min" + quality: int | None + wake_count: int | None + deep_minutes: int | None + rem_minutes: int | None + light_minutes: int | None + awake_minutes: int | None + sleep_segments: list[dict] | None # [{"phase": "deep", "start": "23:44", "duration_min": 42}, ...] + sleep_efficiency: float | None # calculated if phases present + deep_percent: float | None + rem_percent: float | None + note: str + source: str + created_at: str + +class SleepStatsResponse(BaseModel): + avg_duration_minutes: float + avg_quality: float | None + total_nights: int + nights_below_goal: int + sleep_goal_minutes: int # from profile + +class SleepDebtResponse(BaseModel): + sleep_debt_minutes: int # positive = deficit, negative = surplus + sleep_debt_formatted: str # "+2h 15min" or "0 – kein Defizit" + days_analyzed: int + sleep_goal_minutes: int +``` + +**CSV Import Logic (`/api/sleep/import-csv`):** +```python +def import_sleep_csv(file: UploadFile, profile_id: str): + """ + Import Apple Health sleep CSV with phases. + + CSV Format: + Start,End,Duration (hr),Value,Source + 2026-03-14 22:44:23,2026-03-14 23:00:19,0.266,Kern,Apple Watch + + Logic: + 1. Group by date (use wake_time date as date key) + 2. Sum durations per phase type + 3. Map German labels: Kern→light, REM→rem, Tief→deep, Wach→awake + 4. Ignore "Schlafend" entries (initial marker, no phase info) + 5. Calculate total duration_minutes + 6. bedtime = min(Start), wake_time = max(End) + 7. Build sleep_segments JSONB: [{"phase": "deep", "start": "23:44", "duration_min": 42}, ...] + 8. Upsert by (profile_id, date) + """ + phases_map = { + 'Kern': 'light', + 'REM': 'rem', + 'Tief': 'deep', + 'Wach': 'awake', + 'Schlafend': None # ignore + } + + # Parse CSV, group by date + # For each segment (not "Schlafend"): + # - Extract start time (HH:MM from Start timestamp) + # - Calculate duration_min (Duration (hr) * 60) + # - Map phase using phases_map + # - Append to segments list: {"phase": phase, "start": start_time, "duration_min": duration} + # + # Sum durations by phase for aggregated columns (deep_minutes, etc.) + # Store segments array in sleep_segments JSONB column + # + # Upsert: + # INSERT ... (sleep_segments = json_segments) + # ON CONFLICT (profile_id, date) DO UPDATE ... + + return {"imported": count, "updated": updated_count} +``` + +### 4.4 Activity Extension (HF Zones) + +**Extend:** `routers/activity.py` + +```python +# New endpoint +GET /api/activity/hr-zones?days=30 # HF zones distribution + +class ActivityCreateExtended(BaseModel): + # ... existing fields + avg_hr: int | None = None + max_hr: int | None = None + +def calculate_hr_zone(avg_hr: int, hf_max: int) -> int: + """ + Calculate HR zone (1-5) based on avg_hr and hf_max. + + Zones: + 1: 50-60% HFmax (Regeneration) + 2: 60-70% HFmax (Grundlagenausdauer) + 3: 70-80% HFmax (Aerobe Ausdauer) + 4: 80-90% HFmax (Anaerobe Schwelle) + 5: 90-100% HFmax (Maximale Intensität) + """ + percent = (avg_hr / hf_max) * 100 + if percent < 60: return 1 + elif percent < 70: return 2 + elif percent < 80: return 3 + elif percent < 90: return 4 + else: return 5 + +class HRZonesResponse(BaseModel): + zone_1_count: int + zone_2_count: int + zone_3_count: int + zone_4_count: int + zone_5_count: int + total_activities: int + hf_max_used: int # which hf_max was used for calculation +``` + +### 4.5 Recovery Status + +**New endpoint in:** `routers/stats.py` + +```python +GET /api/stats/recovery-status # Recovery status (traffic light) + +class RecoveryStatusResponse(BaseModel): + status: Literal['good', 'partial', 'poor'] # 🟢 🟡 🔴 + color: str # '#1D9E75', '#E67E22', '#D85A30' + recommendation: str # "Intensives Training möglich" / "Leicht" / "Ruhetag" + factors: dict # breakdown: hrv_status, resting_hr_status, training_load + +def calculate_recovery_status(profile_id: str) -> RecoveryStatusResponse: + """ + Calculate recovery status based on: + 1. HRV deviation from baseline (30d) - weight: 50% + 2. Resting HR trend (7d vs 30d baseline) - weight: 30% + 3. Training load last 3 days - weight: 20% + + Logic: + - HRV < 10% below baseline → poor + - Resting HR > +5 bpm above baseline → poor + - >3 intense trainings in last 3 days → poor + - Weighted score calculation: + score = (hrv_score * 0.5) + (hr_score * 0.3) + (training_score * 0.2) + - score >= 0.7 → good (🟢) + - score >= 0.4 → partial (🟡) + - score < 0.4 → poor (🔴) + """ +``` + +--- + +## 5. Frontend-Komponenten + +### 5.1 Navigation + +**Erweitere:** `frontend/src/App.jsx` + +```javascript +// Add to nav links +const links = [ + { to: '/', icon: , label: 'Übersicht' }, + { to: '/capture', icon: , label: 'Erfassen' }, + { to: '/history', icon: , label: 'Verlauf' }, + { to: '/sleep', icon: , label: 'Schlaf' }, // NEU + { to: '/vitals', icon: , label: 'Vital' }, // NEU + { to: '/analysis', icon: , label: 'Analyse' }, + { to: '/settings', icon: , label: 'Einst.' }, +] + +// Add routes +}/> +}/> +}/> +``` + +### 5.2 Neue Seiten + +#### `frontend/src/pages/SleepPage.jsx` +**Struktur:** +``` +┌─────────────────────────────────────┐ +│ Schlaf 🌙 │ +├─────────────────────────────────────┤ +│ [ Letzte Nacht erfassen ] (Button) │ +├─────────────────────────────────────┤ +│ 📊 Übersicht (letzte 7 Tage) │ +│ Ø 7h 12min | Qualität 3.8/5 │ +├─────────────────────────────────────┤ +│ 📈 Schlafdauer-Trend (Chart) │ +│ - Linie: Tägliche Dauer │ +│ - Referenzlinie: Schlafziel │ +│ - 7-Tage-Durchschnitt │ +├─────────────────────────────────────┤ +│ 📊 Schlafphasen (Stacked Bar) │ +│ - Tief | REM | Leicht | Wach │ +├─────────────────────────────────────┤ +│ 💤 Schlafschulden │ +│ +2h 15min (letzte 7 Tage) │ +├─────────────────────────────────────┤ +│ 🔗 Korrelationen │ +│ - Schlaf ↔ Ruhepuls │ +│ - Schlaf ↔ Training │ +│ - Schlaf ↔ Gewicht │ +└─────────────────────────────────────┘ + +Modal: Schlaf erfassen +┌─────────────────────────────────────┐ +│ Schlaf erfassen │ +├─────────────────────────────────────┤ +│ Datum: [22.03.2026] │ +│ Schlafdauer: [7] h [30] min │ +│ Qualität: ★★★★☆ │ +│ Notiz: [...] │ +├─────────────────────────────────────┤ +│ [ Detailansicht ] (Link) │ +├─────────────────────────────────────┤ +│ [ Speichern ] [ Abbrechen ] │ +└─────────────────────────────────────┘ + +Detailansicht: +│ Eingeschlafen: [23:15] │ +│ Aufgewacht: [06:45] │ +│ Aufwachungen: [1] │ +│ Tiefschlaf: [95] min │ +│ REM-Schlaf: [110] min │ +│ Leichtschlaf: [230] min │ +│ Wach im Bett: [15] min │ +``` + +**Features:** +- Schnelleingabe (3 Felder) vs. Detaileingabe (11 Felder) +- Chart-Zeitraum wählbar: 7 / 30 / 90 Tage +- CSV-Import-Button (Apple Health) +- Inline-Edit bestehender Einträge + +#### `frontend/src/pages/VitalsPage.jsx` +**Struktur:** +``` +┌─────────────────────────────────────┐ +│ Vitalwerte 💓 │ +├─────────────────────────────────────┤ +│ [ Heute erfassen ] (Button) │ +├─────────────────────────────────────┤ +│ 📊 Aktuell │ +│ Ruhepuls: 58 bpm │ +│ HRV: 52 ms │ +│ Trend: ✅ Stabil │ +├─────────────────────────────────────┤ +│ 📈 Ruhepuls-Trend (Chart) │ +│ - Linie: Täglicher Wert │ +│ - Baseline: 30-Tage-Ø │ +│ - Warnzone: >+5 bpm │ +├─────────────────────────────────────┤ +│ 📊 HRV-Trend (Chart) │ +│ - Linie: Täglicher Wert │ +│ - Baseline: 30-Tage-Ø │ +│ - Warnzone: <-10% │ +└─────────────────────────────────────┘ + +Modal: Vitalwerte erfassen +┌─────────────────────────────────────┐ +│ Vitalwerte erfassen │ +├─────────────────────────────────────┤ +│ Datum: [22.03.2026] │ +│ Ruhepuls (bpm): [58] │ +│ HRV (ms): [52] (optional) │ +│ Notiz: [...] │ +├─────────────────────────────────────┤ +│ [ Speichern ] [ Abbrechen ] │ +└─────────────────────────────────────┘ +``` + +#### `frontend/src/pages/RestDaysPage.jsx` +**Oder Integration in ActivityPage:** +``` +┌─────────────────────────────────────┐ +│ Aktivitäten & Ruhetage │ +├─────────────────────────────────────┤ +│ [ Tabs: Training | Ruhetage ] │ +├─────────────────────────────────────┤ +│ Ruhetag Tab: │ +│ [ + Ruhetag erfassen ] │ +│ │ +│ 22.03.2026 - Vollständige Ruhe │ +│ "Muskelkater Beine" │ +│ │ +│ 20.03.2026 - Aktive Erholung │ +│ "Spaziergang" │ +└─────────────────────────────────────┘ +``` + +### 5.3 Dashboard-Widgets + +**Erweitere:** `frontend/src/pages/Dashboard.jsx` + +```javascript +// New widgets + // Letzte Nacht + 7-Tage-Ø + // Ruhepuls + Trend + // 🟢 Gut erholt | Empfehlung +``` + +**SleepWidget:** +``` +┌─────────────────────────────────────┐ +│ 🌙 Schlaf │ +│ Letzte Nacht: 7h 30min | ★★★★☆ │ +│ Ø 7 Tage: 7h 12min | ★★★★☆ │ +│ [ Erfassen ] │ +└─────────────────────────────────────┘ +``` + +**RecoveryWidget:** +``` +┌─────────────────────────────────────┐ +│ 🔋 Erholungsstatus │ +│ 🟢 Gut erholt │ +│ "Intensives Training möglich" │ +│ [ Details ] │ +└─────────────────────────────────────┘ +``` + +### 5.4 Activity-Liste Erweiterung + +**In:** `frontend/src/pages/ActivityPage.jsx` + +```javascript +// Add HR zone badge +{e.avg_hr && ( +
+ + Zone {e.hr_zone} +
+)} + +function getZoneColor(zone) { + const colors = { + 1: '#1D9E75', // Regeneration (grün) + 2: '#3498DB', // Grundlage (blau) + 3: '#E67E22', // Aerob (orange) + 4: '#E74C3C', // Anaerob (rot) + 5: '#8E44AD', // Maximal (lila) + } + return colors[zone] || '#95A5A6' +} +``` + +--- + +## 6. Implementierungs-Phasen + +**Reihenfolge:** 2b → 2c → 2d → 2a → 2e → 2f (Schlaf-Modul zuerst, dann Vitalwerte) + +### Phase 2b – Schlaf-Modul Kern (3-4h) ⭐ START HIER +**Ziel:** Schlaf erfassen + Trends sehen + +1. ✅ Migration 009 erstellen + deployen (sleep_log + sleep_segments) +2. ✅ Backend Router: `sleep.py` (CRUD + Stats) +3. ✅ Frontend: `SleepPage.jsx` (Schnell + Detail-Eingabe) +4. ✅ Charts: Schlafdauer-Trend, Qualität-Trend +5. ✅ Dashboard: `SleepWidget.jsx` +6. ✅ Navigation: Schlaf-Icon 🌙 hinzufügen +7. ✅ Test: Manuelle Eingabe + Charts funktionieren + +**Deliverable:** User kann Schlaf erfassen, sieht 7-Tage-Trends. + +### Phase 2c – Apple Health Import (2h) +**Ziel:** Automatischer Schlaf-Import aus CSV + +1. ✅ Backend: CSV-Parser für Apple Health Schlaf (inkl. sleep_segments JSONB) +2. ✅ Frontend: Import-Button auf SleepPage +3. ✅ Test: CSV-Import aggregiert Phasen korrekt, speichert Segmente +4. ✅ Validierung: Mehrfach-Import überschreibt nicht manual-Einträge + +**Deliverable:** User kann Apple Health Schlaf-CSV importieren, Phasen werden gespeichert. + +### Phase 2d – HF-Zonen & Erholung (2-3h) +**Ziel:** HF-Zonen-Badges + Erholungsampel + +1. ✅ Migration 008 anwenden (`activity_log` avg_hr/max_hr erweitern) +2. ✅ Backend: HF-Zonen-Berechnung (Endpoint + Badge-Logik) +3. ✅ Backend: Recovery-Status-Berechnung (50% HRV / 30% Ruhepuls / 20% Training) +4. ✅ Frontend: HF-Zonen-Badge in ActivityPage +5. ✅ Frontend: `RecoveryWidget.jsx` im Dashboard +6. ✅ Test: Zonen korrekt berechnet, Erholungsampel funktioniert + +**Deliverable:** User sieht HF-Zonen in Aktivitäten, Erholungsstatus im Dashboard. + +### Phase 2a – Basis-Vitalwerte (1-2h) +**Ziel:** Ruhetage + Ruhepuls erfassen können + +1. ✅ Migration 008 komplett (rest_days, vitals_log, weekly_goals) +2. ✅ Backend Router: `rest_days.py`, `vitals.py`, `weekly_goals.py` (CRUD) +3. ✅ Frontend: `VitalsPage.jsx` (mit 7d + 30d Warnungen) +4. ✅ Frontend: Ruhetage-Tab in ActivityPage integrieren +5. ✅ Dashboard: `VitalsWidget.jsx` +6. ✅ Navigation: Vital-Icon ❤️ hinzufügen +7. ✅ Test: Manuelle Eingabe funktioniert, beide Warnungen angezeigt + +**Deliverable:** User kann Ruhepuls + Ruhetage erfassen, sieht Trend-Grafik mit Warnungen. + +### Phase 2e – Korrelationen (1-2h) +**Ziel:** Schlaf ↔ Ruhepuls, Training, Gewicht + +1. ✅ Backend: Korrelations-Endpoints (`/api/sleep/correlations/*`) +2. ✅ Frontend: Korrelations-Section auf SleepPage +3. ✅ Charts: Streudiagramme + narrative KI-Aussagen +4. ✅ Test: Mindestens 14 Datenpunkte erforderlich + +**Deliverable:** User sieht Korrelationen zwischen Schlaf und anderen Metriken. + +### Phase 2f – KI-Integration (1-2h, optional) +**Ziel:** Neue KI-Platzhalter + Analyse-Funktionen + +1. ✅ Backend: KI-Platzhalter erweitern (21 neue, siehe Abschnitt 7) +2. ✅ Backend: Analyse-Funktionen (Schlafmangel, Übertraining) +3. ✅ Test: KI-Prompts nutzen neue Platzhalter + +**Deliverable:** KI-Analyse berücksichtigt Schlaf + Vitalwerte. + +--- + +## 7. KI-Integration (neue Platzhalter) + +### 7.1 Training-Platzhalter (aus TRAINING_TYPES.md) +```python +{{trainingstyp_verteilung}} # "60% Kraft, 30% Cardio, 10% Mobility (letzte 4 Wochen)" +{{ruhetage_letzte_woche}} # Anzahl Ruhetage letzte 7 Tage +{{ruhepuls_aktuell}} # heutiger / letzter Ruhepuls +{{ruhepuls_trend}} # "sinkend / stabil / steigend" +{{hrv_aktuell}} # letzter HRV-Wert +{{hrv_baseline}} # persönlicher HRV-Durchschnitt (30 Tage) +{{erholungsstatus}} # "gut / teilweise / schlecht" +{{vo2max}} # aktueller VO2Max-Wert (optional, später) +{{trainingsphase}} # "Aufbau / Erholung / Plateau / unbekannt" +{{hf_zonen_verteilung}} # "Zone 2: 45%, Zone 3: 35%, Zone 4: 20%" +``` + +### 7.2 Schlaf-Platzhalter (aus SLEEP_MODULE.md) +```python +{{schlaf_avg_dauer}} # "7h 12min (Ø 7 Tage)" +{{schlaf_avg_qualitaet}} # "3,8/5 (Ø 7 Tage)" +{{schlaf_trend_dauer}} # "sinkend / stabil / steigend" +{{schlaf_trend_qualitaet}} # "sinkend / stabil / steigend" +{{schlaf_schulden}} # "+2h 15min (letzte 14 Tage)" +{{schlaf_ziel}} # "7h 30min" +{{schlaf_optimum_fenster}} # "22:00–23:00 Uhr (beste Qualität)" +{{schlaf_tiefschlaf_anteil}} # "18% (Ø letzte 14 Tage)" +{{schlaf_rem_anteil}} # "22% (Ø letzte 14 Tage)" +{{schlaf_aufwachungen}} # "1,2x pro Nacht (Ø 7 Tage)" +{{schlaf_korrelation_puls}} # "Ruhepuls +4 bpm nach <7h Schlaf" +{{schlaf_detail}} # tabellarische Übersicht letzte 7 Nächte +``` + +### 7.3 KI-Analyse-Funktionen (neu) + +**Übertraining erkennen:** +```python +def detect_overtraining(profile_id): + # Check: + # - >5 Krafteinheiten in 7 Tagen ohne Ruhetag + # - Ruhepuls >+7 bpm über Baseline + hohe Trainingsbelastung + # - HRV <-15% unter Baseline + # Return: warning message or None +``` + +**Schlafmangel warnen:** +```python +def detect_sleep_deprivation(profile_id): + # Check: + # - Schlafdurchschnitt letzte 3 Tage < (Schlafziel - 60min) + # - Schlafschulden >2h (letzte 7 Tage) + # Return: warning + recommendation +``` + +**Ruhetag empfehlen:** +```python +def recommend_rest_day(profile_id): + # Based on: + # - Erholungsstatus = poor + # - Kein Ruhetag in letzten 5 Tagen + # - Trainingsbelastung hoch + # Return: recommendation or None +``` + +--- + +## 8. Testing-Strategie + +### 8.1 Unit Tests (Backend) + +**Test:** `backend/tests/test_vitals.py` +```python +def test_upsert_vitals_by_date(): + # Create entry for 2026-03-22 + # Upsert same date with different HR + # Assert: updated, not duplicated + +def test_resting_hr_trend(): + # Insert 30 days of data + # Call /api/vitals/trend + # Assert: baseline calculated, warning if elevated +``` + +**Test:** `backend/tests/test_sleep.py` +```python +def test_apple_health_csv_import(): + # Import sample CSV (from user) + # Assert: phases aggregated correctly + # Assert: date = wake date, not sleep date + # Assert: German labels mapped + +def test_sleep_debt_calculation(): + # Insert 7 days with varying durations + # Set sleep_goal_minutes = 450 + # Call /api/sleep/debt + # Assert: debt calculated correctly +``` + +**Test:** `backend/tests/test_recovery_status.py` +```python +def test_recovery_status_calculation(): + # Create mock data: low HRV, high resting HR, high training load + # Call /api/stats/recovery-status + # Assert: status = 'poor', recommendation includes "Ruhetag" +``` + +### 8.2 Integration Tests (Frontend) + +**Test:** Manual testing checklist +- [ ] Vitalwerte erfassen → sichtbar in Trend-Chart +- [ ] Ruhetag erfassen → sichtbar in Liste +- [ ] Schlaf erfassen (Schnell) → Dashboard-Widget aktualisiert +- [ ] Schlaf erfassen (Detail) → Phasen-Chart zeigt Daten +- [ ] Apple Health CSV Import → Einträge korrekt aggregiert +- [ ] HF-Zonen-Badge → korrekte Farbe basierend auf avg_hr +- [ ] Erholungsampel → ändert Farbe bei schlechten Werten +- [ ] Korrelationen → Charts zeigen bei >14 Datenpunkten + +### 8.3 End-to-End Test + +**Scenario:** Kompletter User-Flow +1. User importiert Apple Health Schlaf-CSV (7 Tage) +2. User erfasst Ruhepuls für 7 Tage +3. User trainiert 3x intensiv (avg_hr >80% HFmax) +4. User schläft schlecht (quality 2/5, <6h) +5. Dashboard zeigt Erholungsampel 🔴 "Erholung nötig" +6. KI-Analyse empfiehlt Ruhetag + +**Validierung:** +- Schlaf korrekt importiert und aggregiert +- Ruhepuls-Trend zeigt Erhöhung +- HF-Zonen korrekt berechnet +- Erholungsstatus = poor +- KI-Platzhalter befüllt, Empfehlung korrekt + +--- + +## 9. Rollout-Plan + +### Development (develop Branch) +1. **Phase 2a:** Vitalwerte-Basis → Test auf dev.mitai.jinkendo.de +2. **Phase 2b:** Schlaf-Modul Kern → Test auf dev +3. **Phase 2c:** Apple Health Import → Test mit echten CSV-Daten +4. **Phase 2d:** HF-Zonen + Erholung → Validierung Berechnungslogik +5. **Phase 2e:** Korrelationen → Test mit Mindestdatenbasis +6. **(Optional) Phase 2f:** KI-Integration + +**Validierung:** Jede Phase einzeln testen, Bug-Fixes vor nächster Phase. + +### Production (main Branch) +**Merge:** Nach erfolgreicher End-to-End-Validierung auf develop +**Deployment:** Gitea Action deploy-prod.yml → mitai.jinkendo.de +**Post-Deployment:** +- CLAUDE.md aktualisieren: v9d Phase 2 ✅ +- Nutzer-Kommunikation: "Neue Features: Schlaf, Vitalwerte, Erholungsstatus" + +--- + +## 10. Offene TODOs (für spätere Versionen) + +### Nicht in Phase 2: +- [ ] VO2Max-Schätzung (v9f) +- [ ] Wochenplanung persistieren (v9f, mit Ziele-Modul) +- [ ] HF-Kurven (Zeit × HF) aus Apple Health (v9h) +- [ ] Live-Sync Garmin/Apple Watch (v9h, Connectoren) +- [ ] Push-Notifications "Schlafenszeit" (optional) +- [ ] Schlaf-Coaching / Tipps (optional) +- [ ] SpO2 / Schnarchen (v9e, Vitalwerte erweitert) + +### Edge Cases / Nice-to-haves: +- [ ] Schlaf-Import: Mittagsschlaf (Naps) erkennen und separieren +- [ ] Ruhepuls: Automatische Benachrichtigung bei 3+ Tagen Erhöhung +- [ ] HRV: Baseline-Berechnung nach Alter/Geschlecht normiert +- [ ] Schlafphasen: Referenzwerte nach Altersgruppe + +--- + +## 11. Anhang + +### 11.1 Apple Health CSV-Struktur (Schlaf) + +**Beispiel:** Siehe User-Bereitstellung oben + +**Mapping:** +``` +Value (Deutsch) → DB-Spalte +Kern → light_minutes +REM → rem_minutes +Tief → deep_minutes +Wach → awake_minutes +Schlafend → (ignorieren) +``` + +**Aggregations-Logik:** +- Eine Nacht = alle Einträge zwischen erstem "Schlafend"/Start und letztem End derselben Schlafperiode +- Datum = Aufwachdatum (End des letzten Segments) +- Wenn Nacht über 2 Tage geht (z.B. 14.03. 22:00 bis 15.03. 06:00) → Datum = 15.03. + +### 11.2 Berechnungsformeln + +**Schlafeffizienz:** +``` +efficiency = (duration_minutes / (duration_minutes + awake_minutes)) * 100 +Nur berechnen wenn awake_minutes vorhanden. +``` + +**Tiefschlaf-Anteil:** +``` +deep_percent = (deep_minutes / duration_minutes) * 100 +``` + +**REM-Anteil:** +``` +rem_percent = (rem_minutes / duration_minutes) * 100 +``` + +**Schlafschulden:** +``` +sleep_debt = sum(sleep_goal - actual_duration for each day in last 14 days) +Positiv = Defizit, Negativ = Überschuss +Zeitraum: 14 Tage (weniger anfällig für Ausreißer als 7 Tage) +``` + +**HFmax Standard:** +``` +hf_max = 220 - age +Überschreibbar durch profiles.hf_max +``` + +**HF-Zone:** +``` +zone = calculate_hr_zone(avg_hr, hf_max) +Siehe Abschnitt 4.4 +``` + +**VO2Max-Schätzung (optional, später):** +``` +vo2max ≈ 15 × (hf_max / resting_hr) +Cooper-Formel, grobe Schätzung +``` + +--- + +## 12. Datenbankgröße & Performance + +**Schätzung:** +- `rest_days`: ~50 Einträge/Jahr/User → ~200 Bytes/Eintrag → 10 KB/Jahr +- `vitals_log`: ~250 Einträge/Jahr/User → ~200 Bytes/Eintrag → 50 KB/Jahr +- `sleep_log`: ~300 Einträge/Jahr/User → ~400 Bytes/Eintrag → 120 KB/Jahr + +**Total Phase 2:** ~180 KB/Jahr/User + +**Performance:** +- Indizes auf `(profile_id, date DESC)` → schnelle Bereichsabfragen +- Korrelations-Queries: JOIN über date → <100ms bei <5000 Einträgen +- Trend-Berechnung: AVG() über 7-30 Tage → <50ms + +**Empfehlung:** Keine zusätzlichen Optimierungen nötig für <10.000 User. + +--- + +**Ende der Spezifikation** + +**Nächste Schritte:** +1. Spec-Review mit User +2. Start Phase 2a Implementierung +3. Iteratives Deployment (develop → test → main) diff --git a/docs/DOCUMENTATION_COMPLETE_2026-03-27.md b/.claude/docs/working/DOCUMENTATION_COMPLETE_2026-03-27.md similarity index 100% rename from docs/DOCUMENTATION_COMPLETE_2026-03-27.md rename to .claude/docs/working/DOCUMENTATION_COMPLETE_2026-03-27.md diff --git a/docs/GOALS_SYSTEM_UNIFIED_ANALYSIS.md b/.claude/docs/working/GOALS_SYSTEM_UNIFIED_ANALYSIS.md similarity index 100% rename from docs/GOALS_SYSTEM_UNIFIED_ANALYSIS.md rename to .claude/docs/working/GOALS_SYSTEM_UNIFIED_ANALYSIS.md diff --git a/docs/GOAL_SYSTEM_PRIORITY_ANALYSIS.md b/.claude/docs/working/GOAL_SYSTEM_PRIORITY_ANALYSIS.md similarity index 100% rename from docs/GOAL_SYSTEM_PRIORITY_ANALYSIS.md rename to .claude/docs/working/GOAL_SYSTEM_PRIORITY_ANALYSIS.md diff --git a/docs/GOAL_SYSTEM_REDESIGN_v2.md b/.claude/docs/working/GOAL_SYSTEM_REDESIGN_v2.md similarity index 100% rename from docs/GOAL_SYSTEM_REDESIGN_v2.md rename to .claude/docs/working/GOAL_SYSTEM_REDESIGN_v2.md diff --git a/docs/KONZEPT_ANALYSE_2026-03-26.md b/.claude/docs/working/KONZEPT_ANALYSE_2026-03-26.md similarity index 100% rename from docs/KONZEPT_ANALYSE_2026-03-26.md rename to .claude/docs/working/KONZEPT_ANALYSE_2026-03-26.md diff --git a/docs/NEXT_STEPS_2026-03-26.md b/.claude/docs/working/NEXT_STEPS_2026-03-26.md similarity index 100% rename from docs/NEXT_STEPS_2026-03-26.md rename to .claude/docs/working/NEXT_STEPS_2026-03-26.md diff --git a/.claude/docs/working/PHASE_0C_TASKS.md b/.claude/docs/working/PHASE_0C_TASKS.md new file mode 100644 index 0000000..e0383ba --- /dev/null +++ b/.claude/docs/working/PHASE_0C_TASKS.md @@ -0,0 +1,276 @@ +# Phase 0c – Architecture Cleanup & Refactoring + +**Datum:** 2026-03-28 +**Voraussetzung:** Phase 0b abgeschlossen +**Geschätzter Aufwand:** 12-16 Stunden + +--- + +## Ziele + +Phase 0c fokussiert auf **Architektur-Verbesserungen** und **Structured Data**, ohne neue Features hinzuzufügen. + +**Kernprinzip:** Von Formatted Strings zu Structured Data → Basis für Phase 0c Refactoring (Charts/Diagramme). + +--- + +## Task 1: Dynamic Aggregation Methods (3-4h) + +**Problem:** Aggregationsmethoden sind im Frontend hardcoded. + +**Lösung:** + +### Backend +```python +# backend/routers/goal_types.py +@router.get("/aggregation-methods") +def get_aggregation_methods(): + """Return all available aggregation methods with metadata""" + return [ + { + "value": "latest", + "label_de": "Letzter Wert", + "label_en": "Latest", + "description": "Aktuellster Wert aus Tabelle", + "compatible_types": ["DECIMAL", "INTEGER", "FLOAT"], + "requires_numeric": False + }, + { + "value": "avg_7d", + "label_de": "Durchschnitt 7 Tage", + "label_en": "7-day average", + "description": "Mittelwert über 7 Tage", + "compatible_types": ["DECIMAL", "INTEGER", "FLOAT"], + "requires_numeric": True + }, + # ... alle Methoden aus goal_utils.py + ] +``` + +**Implementierung:** +1. Endpoint erstellen (goal_types.py) +2. Auto-Discovery aus goal_utils.py Docstrings oder Introspection +3. Frontend anpassen (AdminGoalTypesPage.jsx) +4. Migration für Backfill (falls nötig) + +**Dokumentation:** `.claude/docs/technical/AGGREGATION_METHODS.md` (exists) + +--- + +## Task 2: Dynamic Placeholder Catalog (4-5h) + +**Problem:** Platzhalter sind im Frontend nicht sichtbar/suchbar. + +**Lösung:** + +### Backend +```python +# backend/routers/prompts.py +@router.get("/placeholders/catalog") +def get_placeholder_catalog(session: dict = Depends(require_auth)): + """ + Return all registered placeholders with metadata. + Enables auto-complete and placeholder selector in frontend. + """ + return { + "categories": [ + { + "name": "Profil", + "placeholders": [ + { + "key": "name", + "placeholder": "{{name}}", + "description": "Name des Nutzers", + "example": "Lars", + "data_type": "string" + }, + # ... + ] + }, + # ... weitere Kategorien + ], + "total_count": 111 + } +``` + +### Frontend +```javascript +// PlaceholderSelector Component + insertAtCursor(placeholder)} + categories={fetchedCatalog.categories} + searchable={true} + showExamples={true} +/> +``` + +**Implementierung:** +1. Endpoint erstellen (nutzt PLACEHOLDER_CATALOG aus placeholder_resolver.py) +2. React Component: PlaceholderSelector +3. Integration in UnifiedPromptModal (Prompt-Editor) +4. Keyboard-Shortcuts (Strg+Space für Auto-Complete) + +**Dokumentation:** `.claude/docs/technical/PLACEHOLDER_SYSTEM.md` (neu erstellen) + +--- + +## Task 3: Calculation Functions → Structured Return (5-7h) + +**Problem:** Calculation Functions returnen formatted strings statt strukturierte Daten. + +**Lösung:** Refactoring aller Calculation Functions zu structured return. + +### Beispiel-Refactoring + +**Vorher (Phase 0b):** +```python +def calculate_weight_trend(profile_id: str) -> str: + # ... calculation ... + return f"sinkend (-0.9 kg in 28 Tagen)" +``` + +**Nachher (Phase 0c):** +```python +def calculate_weight_trend(profile_id: str) -> Dict: + # ... calculation ... + return { + "raw_value": -0.9, + "unit": "kg", + "period_days": 28, + "direction": "decreasing", # "increasing", "stable" + "confidence": "high", # "high", "medium", "low" + "data_points": 15, + "slope_per_day": -0.032 + } +``` + +### Formatting Layer +```python +# placeholder_resolver.py +def format_weight_trend(data: Dict) -> str: + """Format structured weight trend for AI prompts""" + direction_de = {"decreasing": "sinkend", "increasing": "steigend", "stable": "stabil"} + return f"{direction_de[data['direction']]} ({data['raw_value']} {data['unit']} in {data['period_days']} Tagen)" +``` + +### Betroffene Module +- `calculations/body_metrics.py` (~15 Funktionen) +- `calculations/nutrition_metrics.py` (~12 Funktionen) +- `calculations/activity_metrics.py` (~18 Funktionen) +- `calculations/recovery_metrics.py` (~10 Funktionen) +- `calculations/scores.py` (~8 Funktionen) + +**Gesamt:** ~63 Funktionen zu refactoren + +### Strategie +1. **Phase 0c/1:** Body Metrics (Proof of Concept) +2. **Phase 0c/2:** Nutrition + Activity +3. **Phase 0c/3:** Recovery + Scores +4. **Phase 0c/4:** Placeholder Resolver anpassen (Formatter) + +**Dokumentation:** +- `.claude/docs/technical/CALCULATION_FUNCTIONS.md` (neu) +- `.claude/docs/functional/mitai_jinkendo_konzept_diagramme_auswertungen_v2.md` (exists, Kontext für Chart-System) + +--- + +## Task 4: Stats/Chart Endpoints (Optional, 3-4h) + +**Voraussetzung:** Task 3 abgeschlossen + +**Lösung:** Separate Endpoints für Chart-Daten (nutzen structured calculation returns) + +```python +# backend/routers/stats.py (new) +@router.get("/stats/body/weight-trend") +def get_weight_trend_stats( + days: int = 30, + session: dict = Depends(require_auth) +): + """Return structured weight trend data for charts""" + trend_data = calculate_weight_trend(session['profile_id']) + return { + "trend": trend_data, + "timeseries": [...], # Daily data points + "projections": {...} # Linear projection + } +``` + +**Implementierung:** +1. Neuer Router: `stats.py` +2. Endpoints für alle Chart-relevanten Metriken +3. OpenAPI Schema (für Frontend Type-Generation) + +--- + +## Abhängigkeiten + +``` +Task 1 (Aggregation Methods) ─┐ + ├─> Unabhängig, parallel möglich +Task 2 (Placeholder Catalog) ─┘ + +Task 3 (Structured Returns) ──> Voraussetzung für Task 4 +Task 4 (Stats Endpoints) ──> Optional, nutzt Task 3 Results +``` + +--- + +## Akzeptanzkriterien Phase 0c + +**Muss (Must-Have):** +- ✅ Aggregation Methods API-gesteuert +- ✅ Placeholder Catalog API verfügbar +- ✅ Mindestens 1 Modul (Body Metrics) zu structured return refactored +- ✅ Dokumentation für alle 3 Tasks + +**Sollte (Should-Have):** +- ✅ Alle Calculation Functions refactored +- ✅ Frontend Placeholder Selector implementiert +- ✅ Stats Endpoints für Body + Nutrition + +**Kann (Nice-to-Have):** +- ⚪ Stats Endpoints für alle Module +- ⚪ Chart-Preview in Admin-Panel +- ⚪ TypeScript Type-Generation aus OpenAPI + +--- + +## Rollback-Plan + +Falls Task 3 (Structured Returns) Probleme verursacht: +1. Calculation Functions returnen **beide** Formate: + ```python + return { + "formatted": "sinkend (-0.9 kg)", # Backward-compat + "structured": {...} # New format + } + ``` +2. Placeholder Resolver nutzt `formatted` weiterhin +3. Stats Endpoints nutzen `structured` +4. Schrittweise Migration ohne Breaking Changes + +--- + +## Timeline (geschätzt) + +**Option A (Serial):** 12-16 Stunden über 2-3 Tage +**Option B (Parallel):** 8-10 Stunden mit 2 Personen + +**Empfehlung:** Option A, da Tasks eng verzahnt sind. + +--- + +## Nach Phase 0c + +**Enabler für:** +- ✅ Chart/Diagram System (Phase 1) +- ✅ Goal Progress Visualizations +- ✅ Custom Dashboards +- ✅ PDF/Excel Export mit strukturierten Daten +- ✅ API für externe Tools (z.B. Home Assistant) + +**Nächste Phase (Phase 1):** +- Chart Library Integration (Recharts/Victory) +- Interactive Dashboards +- Goal Progress Tracking UI diff --git a/.claude/docs/working/README.md b/.claude/docs/working/README.md new file mode 100644 index 0000000..bc6ba37 --- /dev/null +++ b/.claude/docs/working/README.md @@ -0,0 +1,10 @@ +# Arbeitspapiere & Zwischenstände (`working/`) + +**Zweck:** Analysen, Migrations-Notizen, Session-Zusammenfassungen und andere **zeitlich begrenzte** Texte. Sie ergänzen **nicht** die kanonischen Spezifikationen in `../functional/` und `../technical/`. + +**Regeln:** Siehe `.claude/rules/DOCUMENTATION.md`. + +- Bei Widerspruch gewinnt die **aktuelle Spec** in `functional/` / `technical/` bzw. der **Code**. +- Dateien mit Datum im Namen oder Kopf bevorzugen; veraltete Inhalte nicht löschen ohne Prüfung – ggf. als „archiviert“ markieren. + +**Herkunft:** Mehrere Dateien wurden aus `docs/` (Projektroot) hierher verschoben, damit `docs/issues/` und ausgewiesene Governance-Dateien klarer bleiben. diff --git a/docs/STATUS_2026-03-27.md b/.claude/docs/working/STATUS_2026-03-27.md similarity index 100% rename from docs/STATUS_2026-03-27.md rename to .claude/docs/working/STATUS_2026-03-27.md diff --git a/docs/STATUS_REPORT_2026-03-26.md b/.claude/docs/working/STATUS_REPORT_2026-03-26.md similarity index 100% rename from docs/STATUS_REPORT_2026-03-26.md rename to .claude/docs/working/STATUS_REPORT_2026-03-26.md diff --git a/docs/TODO_GOAL_SYSTEM.md b/.claude/docs/working/TODO_GOAL_SYSTEM.md similarity index 100% rename from docs/TODO_GOAL_SYSTEM.md rename to .claude/docs/working/TODO_GOAL_SYSTEM.md diff --git a/docs/phase-0c-placeholder-migration-analysis.md b/.claude/docs/working/phase-0c-placeholder-migration-analysis.md similarity index 100% rename from docs/phase-0c-placeholder-migration-analysis.md rename to .claude/docs/working/phase-0c-placeholder-migration-analysis.md diff --git a/docs/test-prompt-phase-0b.md b/.claude/docs/working/test-prompt-phase-0b.md similarity index 100% rename from docs/test-prompt-phase-0b.md rename to .claude/docs/working/test-prompt-phase-0b.md diff --git a/.claude/rules/ARCHITECTURE.md b/.claude/rules/ARCHITECTURE.md new file mode 100644 index 0000000..1203883 --- /dev/null +++ b/.claude/rules/ARCHITECTURE.md @@ -0,0 +1,438 @@ +# Architektur-Regeln – Mitai Jinkendo + +> **PFLICHTLEKTÜRE für Claude Code vor jeder Implementierung.** +> Diese Regeln sind verbindlich und dürfen nicht ohne explizite +> Genehmigung des Nutzers abgeändert werden. +> +> **Dokumentationsablage:** siehe **`DOCUMENTATION.md`** (gleicher Ordner) – fachlich/technisch/working/issues. + +--- + +## 1. Router-Architektur + +### 1.1 Ein Modul = Ein Router +Jedes fachliche Modul hat genau eine Router-Datei in `backend/routers/`. + +``` +backend/routers/ +├── auth.py # Authentifizierung +├── profiles.py # Nutzerprofile +├── weight.py # Gewichts-Tracking +├── sleep.py # Schlaf-Modul +├── training_types.py # Trainingstypen + HF +└── ... # je neues Modul = neue Datei +``` + +**Regeln:** +- Kein Endpoint darf außerhalb seines thematischen Routers definiert werden +- Neue Module immer als neue Router-Datei anlegen, nie in bestehende einfügen +- Router in `main.py` registrieren: `app.include_router(modul.router, prefix="/api")` +- Router-Datei-Name = Modul-Name in `version.py` MODULE_VERSIONS + +### 1.2 API-First Prinzip +Jede Funktion ist zuerst als API-Endpoint implementiert – die UI nutzt ausschließlich +diese Endpoints über `api.js`. Keine Business-Logik im Frontend. + +```python +# ✅ Richtig: Logik im Backend-Endpoint +@router.get("/sleep/stats") +def get_sleep_stats(session=Depends(require_auth)): + # Berechnung hier + return {"avg_duration": ..., "sleep_debt": ...} + +# ❌ Falsch: Berechnung im Frontend +const sleepDebt = entries.reduce((sum, e) => sum + (goal - e.duration), 0) +``` + +### 1.3 Einheitliche Fehlerbehandlung +```python +# ✅ Immer dieses Format: +raise HTTPException(status_code=404, detail="Eintrag nicht gefunden") +# Response: {"detail": "Eintrag nicht gefunden"} + +# ❌ Nie eigene Formate: +return {"error": "not found"} +return {"message": "Fehler", "success": False} +``` + +--- + +## 2. Versionskontrollsystem + +### 2.1 Versionierungsschema +**Semantic Versioning: `MAJOR.MINOR.PATCH`** + +| Typ | Wann | Beispiel | +|-----|------|---------| +| MAJOR | Breaking Change, DB-Migration inkompatibel | 9.0.0 → 10.0.0 | +| MINOR | Neues Feature, neues Modul | 9.2.0 → 9.3.0 | +| PATCH | Bugfix, kleine Änderung, Refactor | 9.3.0 → 9.3.1 | + +### 2.2 Versions-Dateien + +**Backend: `backend/version.py`** +```python +APP_VERSION = "9.3.0" +BUILD_DATE = "2026-03-22" + +MODULE_VERSIONS = { + "auth": "1.2.0", + "profiles": "1.1.0", + "weight": "1.0.3", + "circumference": "1.0.1", + "caliper": "1.0.1", + "activity": "1.1.0", + "nutrition": "1.0.2", + "photos": "1.0.0", + "insights": "1.3.0", + "prompts": "1.1.0", + "admin": "1.2.0", + "stats": "1.0.1", + "exportdata": "1.1.0", + "importdata": "1.0.0", + "membership": "2.1.0", +} + +CHANGELOG = [ + { + "version": "9.3.0", + "date": "2026-03-22", + "changes": [ + "Feature: Sleep Module (sleep_log, JSONB-Segmente)", + "Feature: Vitalwerte-Seite in Navigation", + "Feature: Trainingstypen-Kategorisierung", + ] + }, + { + "version": "9.2.1", + "date": "2026-03-20", + "changes": [ + "Fix: Feature-Enforcement Rollback", + "Fix: Erholungsstatus-Gewichtung korrigiert", + ] + }, +] +``` + +**Frontend: `frontend/src/version.js`** +```javascript +export const APP_VERSION = "9.3.0" +export const BUILD_DATE = "2026-03-22" + +export const PAGE_VERSIONS = { + Dashboard: "1.3.0", + LoginScreen: "1.1.0", + WeightPage: "1.0.3", + ActivityPage: "1.2.0", + NutritionPage: "1.1.0", + AnalysisPage: "1.3.0", + SettingsPage: "1.4.0", + AdminPanel: "1.2.0", + SubscriptionPage: "1.0.0", + // Neue Seiten hier eintragen +} +``` + +### 2.3 Versions-Endpoint + +**`GET /api/version`** – öffentlich (kein Auth erforderlich) + +```json +{ + "app_version": "9.3.0", + "build_date": "2026-03-22", + "backend_version": "9.3.0", + "modules": { + "auth": "1.2.0", + "sleep": "1.0.0" + }, + "db_schema_version": "20260322", + "environment": "production" +} +``` + +Dieser Endpoint wird in `backend/routers/version.py` implementiert und liest +direkt aus `version.py`. + +### 2.4 Versions-Anzeige in der App + +**Settings-Seite – Versions-Panel:** +``` +System-Versionen +───────────────────────────────────── +App (gesamt) 9.3.0 +Backend 9.3.0 ✓ erreichbar +Frontend 9.3.0 ✓ geladen +DB-Schema 20260322 +Umgebung production +───────────────────────────────────── +Module +auth 1.2.0 +sleep 1.0.0 +membership 2.1.0 +[alle Module...] +───────────────────────────────────── +[Changelog] [Cache leeren] +``` + +Frontend ruft beim Laden der Settings-Seite `/api/version` ab und vergleicht +mit der eigenen `APP_VERSION` aus `version.js`. Bei Abweichung: Warnung anzeigen. + +### 2.5 Pflicht-Regel: Versions-Bump bei jedem Commit + +**Jede Code-Änderung erfordert:** +1. Versions-Bump in `backend/version.py` (APP_VERSION + betroffenes MODULE_VERSION) +2. Versions-Bump in `frontend/src/version.js` (APP_VERSION + betroffene PAGE_VERSION) +3. Changelog-Eintrag in `backend/version.py` CHANGELOG + +**Claude Code prüft das im `/deploy` Command automatisch.** + +Kein Commit ohne Versions-Bump – keine Ausnahme. + +### 2.6 DB-Schema-Version + +Format: `YYYYMMDD` (Datum der letzten Migration) + +Gespeichert in `backend/version.py`: +```python +DB_SCHEMA_VERSION = "20260322" +``` + +Bei jeder Schema-Änderung (ALTER TABLE, neue Tabelle) → DB_SCHEMA_VERSION aktualisieren. + +--- + +## 3. Datenbankregeln + +### 3.1 Pflichtfelder für neue Tabellen +```sql +-- Jede neue Tabelle braucht: +id SERIAL PRIMARY KEY, +created_at TIMESTAMP DEFAULT NOW(), +updated_at TIMESTAMP DEFAULT NOW() +``` + +### 3.2 Source-Tracking bei Import-Daten +Tabellen die Daten aus externen Quellen empfangen brauchen: +```sql +source VARCHAR(50) DEFAULT 'manual' +-- Werte: 'manual' | 'apple_health' | 'garmin' | 'withings' +``` + +Manuelle Einträge (`source = 'manual'`) haben IMMER Vorrang bei Reimport: +```sql +-- Reimport überschreibt nur nicht-manuelle Einträge: +INSERT INTO sleep_log (...) ON CONFLICT (profile_id, date) +DO UPDATE SET ... WHERE sleep_log.source != 'manual' +``` + +### 3.3 Profile-ID Isolation +Jede Tabelle mit Nutzerdaten hat `profile_id` als Foreign Key. +Kein Endpoint gibt Daten eines anderen Profils zurück. +Profile-ID kommt IMMER aus der Session, nie aus Request-Parametern. + +### 3.4 Boolean-Werte +```sql +-- PostgreSQL Boolean (nicht SQLite 0/1): +WHERE active = true ✓ +WHERE active = 1 ✗ +``` + +--- + +## 4. Frontend-Regeln + +### 4.1 Alle API-Calls über api.js +```javascript +// ✅ Richtig: +import { api } from '../utils/api' +const data = await api.listSleep() + +// ❌ Falsch: +const r = await fetch('/api/sleep') +``` + +### 4.2 Neue Seite = Eintrag in PAGE_VERSIONS +Jede neue Seite in `frontend/src/version.js` registrieren. + +### 4.3 CSS-Variablen statt Hardcoded-Farben +```javascript +// ✅ Richtig: +style={{color: 'var(--accent)'}} + +// ❌ Falsch: +style={{color: '#1D9E75'}} +``` + +### 4.4 Fehlerbehandlung in allen async Funktionen +```javascript +try { + const data = await api.meinEndpoint() + setData(data) +} catch(e) { + setError(e.message) +} finally { + setLoading(false) +} +``` + +--- + +## 5. Git & Deployment-Regeln + +### 5.1 Nie direkt auf main pushen +Immer über Pull Request in Gitea: develop → main. +develop Branch niemals löschen. + +### 5.2 Commit-Message Format +``` +feat: neues Feature oder Modul +fix: Bugfix +refactor: Umbau ohne Funktionsänderung +docs: Dokumentation +version: Versions-Bump +ci: CI/CD Änderungen +chore: Maintenance +``` + +### 5.3 Versions-Bump im Commit +``` +feat: Sleep Module v1.0.0 + +- sleep_log Tabelle mit JSONB-Segmenten +- Import aus Apple Health CSV +- Korrelationen Schlaf <-> Ruhepuls + +version: 9.3.0 (backend + frontend) +module: sleep 1.0.0 +``` + +--- + +## 6. Dokumentations-Regeln + +### 6.1 Neue Module dokumentieren +Bei jedem neuen Modul: +1. Fachliche Spec: `.claude/docs/functional/MODUL_NAME.md` +2. Technische Spec: `.claude/docs/technical/MODUL_NAME.md` +3. Nach Fertigstellung: `.claude/library/` aktualisieren + +### 6.2 CLAUDE.md aktuell halten +Nach größeren Änderungen CLAUDE.md Versions-Tabelle aktualisieren. + +### 6.3 Lessons Learned dokumentieren +Jeder Rollback oder schwerer Bug → Eintrag in `.claude/rules/LESSONS_LEARNED.md` + +--- + +## Zusammenfassung: Checkliste vor jedem Commit + +``` +[ ] Versions-Bump in backend/version.py (APP_VERSION + MODULE) +[ ] Versions-Bump in frontend/src/version.js (APP_VERSION + PAGE) +[ ] Changelog-Eintrag in backend/version.py +[ ] DB_SCHEMA_VERSION aktualisiert (wenn Schema geändert) +[ ] Neues Modul in PAGE_VERSIONS / MODULE_VERSIONS eingetragen +[ ] Auth auf alle neuen Endpoints (require_auth) +[ ] Fehlerformat einheitlich (HTTPException mit detail) +[ ] Neue Tabellen haben created_at + updated_at +[ ] Import-Tabellen haben source-Feld +[ ] api.js für alle Frontend API-Calls +``` + +--- + +## 7. Prod-Schutz & Dev-Zugriff + +### 7.1 Absoluter Prod-Schutz +Claude Code darf auf dem Prod-System (mitai.jinkendo.de) NIEMALS: +- Container neustarten (`docker restart mitai-*`) +- Schreibend in Container ausführen (`docker exec mitai-api ...`) +- Dateien direkt ändern (`/home/lars/docker/bodytrack/`) +- Prod-Datenbank schreiben (nur SELECT erlaubt) + +**Prod-Änderungen ausschließlich über:** +``` +git push origin develop → Gitea PR → Merge → deploy-prod.yml +``` + +### 7.2 Dev-System – voller Zugriff erlaubt +Claude Code darf auf dem Dev-System (dev.mitai.jinkendo.de): +- Container neustarten (`docker restart dev-mitai-*`) +- Logs lesen und filtern +- DB lesen und schreiben (für Tests) +- Container neu bauen + +### 7.3 Test-Umgebung +- API-Tests: gegen http://dev.mitai.jinkendo.de +- Playwright-Tests: gegen https://dev.mitai.jinkendo.de +- Screenshots: in `screenshots/` Ordner (in .gitignore) +- Test-Credentials: in Umgebungsvariablen (TEST_EMAIL, TEST_PASSWORD) +- NIEMALS Test-Credentials in Code committen + +### 7.4 Erkennungsmerkmale Prod vs. Dev +``` +Prod-Container: mitai-api, mitai-ui, mitai-db-prod +Dev-Container: dev-mitai-api, dev-mitai-ui, dev-mitai-postgres + +Prod-Ports: 8002 (Backend), 3002 (Frontend) +Dev-Ports: 8099 (Backend), 3099 (Frontend) + +Prod-URL: mitai.jinkendo.de +Dev-URL: dev.mitai.jinkendo.de +``` + +--- + +## 8. Test-Regeln + +### 8.1 Tests schreiben ist Pflicht +Jedes neue Feature bekommt mindestens einen Playwright-Test in +`tests/dev-smoke-test.spec.js`. + +### 8.2 Reihenfolge: Test vor Commit +``` +Implementieren → Tests schreiben → Tests grün → Committen +NIEMALS: Implementieren → Committen → Tests später +``` + +### 8.3 Claude Code schreibt Tests selbst +Nach jeder Implementierung: +1. Passende Tests in dev-smoke-test.spec.js ergänzen +2. `npx playwright test` ausführen +3. Fehler korrigieren bis alle Tests grün +4. Erst dann committen + +### 8.4 Test-Kategorien +```javascript +// UI-Test (Playwright) +test('FEATURE: Beschreibung', async ({ page }) => { ... }) + +// API-Test (Playwright request) +test('API: Endpoint', async ({ request }) => { ... }) +``` + +### 8.5 Screenshots bei Fehlern +Fehlgeschlagene Tests erzeugen automatisch Screenshots in: +`test-results/TESTNAME/test-failed-1.png` +→ Immer ansehen bevor Code geändert wird + +### 8.6 Prod nie testen +Tests laufen IMMER gegen dev.mitai.jinkendo.de +NIEMALS gegen mitai.jinkendo.de + +--- + +## 9. Dashboard-Lab-Widgets und Feature-System + +**Kontext:** Dashboard-Widgets (`backend/widget_catalog.py`, Lab unter `/api/app/...`) und das **Subscription-/Feature-Modell** (`features`, `tier_limits`, `check_feature_access` in `backend/auth.py`) sind **getrennte Schichten**, müssen aber bei tariffrelevanten Widgets **verknüpft** werden. + +**Bindend:** + +1. **Keine fest codierten Tier-Namen** für Widget-Rechte – Tiers und Limits kommen aus der DB. +2. **Komplexität** (Module aus, Unter-Stufen, KI vs. Standard) liegt in der **Feature-/Subscription-Logik**, nicht verteilt in Widget-Komponenten. +3. **Nutzer-Konfigurator** (z. B. Dashboard-Lab): Widgets **ohne** passende Berechtigung **nicht anzeigen**; alle erlaubten Widgets bleiben verfügbar. +4. **Backend** liefert die effektive Erlaubnis (z. B. über erweiterten Katalog oder Entitlements), und **validiert beim Speichern** des Layouts, dass keine unerlaubten Widget-IDs persistiert werden (Policy: ablehnen oder strippen – einheitlich halten). +5. **Daten/API:** Zusätzlich zur UI-Filterung müssen die **inhaltsliefernden Endpoints** weiterhin über `check_feature_access` geschützt sein (kein Leck über direkte API-Aufrufe). + +**Detail-Doku (Checklisten, Dateipfade):** `.claude/docs/technical/DASHBOARD_WIDGETS_AGENT_GUIDE.md` § 0. diff --git a/.claude/rules/CODING_RULES.md b/.claude/rules/CODING_RULES.md new file mode 100644 index 0000000..a703ec1 --- /dev/null +++ b/.claude/rules/CODING_RULES.md @@ -0,0 +1,93 @@ +# Coding Rules – Mitai Jinkendo + +Diese Regeln IMMER befolgen. Sie basieren auf Erfahrungen aus der Entwicklung. + +## Backend + +### 1. Auth auf jeden Endpoint +```python +# Jeder neue Endpoint braucht Auth: +@router.get("/neuer-endpoint") +def neuer_endpoint(session: dict = Depends(require_auth)): + pid = session['profile_id'] +``` + +### 2. Profile-ID aus Session – nie aus Header +```python +pid = session['profile_id'] # ✅ +# Nicht: request.headers.get('X-Profile-Id') ❌ +``` + +### 3. bcrypt für Passwörter +```python +from auth import hash_pin, verify_pin +hashed = hash_pin(plain_password) # ✅ +# Nicht: hashlib.sha256(...) ❌ +``` + +### 4. PostgreSQL-Syntax +```python +cur.execute("SELECT * FROM t WHERE id = %s AND active = true", (id,)) +# Nicht: ? und active = 1 (SQLite-Syntax) +``` + +### 5. Rate Limiting für sensitive Endpoints +```python +from slowapi import Limiter +@router.post("/sensitive") +@limiter.limit("5/minute") +def sensitive(request: Request, ...): +``` + +## Frontend + +### 1. api.js für alle API-Calls +```javascript +await api.listWeight() // ✅ +await fetch('/api/weight') // ❌ kein Token +``` + +### 2. Fehlerbehandlung in async Funktionen +```javascript +try { + const data = await api.meinEndpoint() +} catch(e) { + setError(e.message) // api.js wirft bereits Error mit detail-Text +} +``` + +### 3. Kein TypeScript +Das Projekt nutzt bewusst kein TypeScript – keine .ts/.tsx Dateien erstellen. + +### 4. Keine neuen npm-Pakete ohne Absprache +Erst fragen, dann installieren. + +### 5. CSS-Variablen statt Hardcoded-Farben +```javascript +// ✅ Richtig: +style={{color: 'var(--accent)'}} + +// ❌ Falsch: +style={{color: '#1D9E75'}} +``` + +## Git & Deployment + +### 1. Nie direkt auf main pushen +Immer über Pull Request in Gitea: develop → main + +### 2. develop Branch nie löschen +Er ist permanent – nicht nach Merge löschen. + +### 3. .env nie committen +Steht in .gitignore – nie entfernen. + +### 4. Commit-Message Format +``` +feat: neues Feature +fix: Bugfix +refactor: Umbau ohne Funktionsänderung +docs: Dokumentation +ci: CI/CD Änderungen +chore: Maintenance +``` diff --git a/.claude/rules/DOCUMENTATION.md b/.claude/rules/DOCUMENTATION.md new file mode 100644 index 0000000..f760324 --- /dev/null +++ b/.claude/rules/DOCUMENTATION.md @@ -0,0 +1,77 @@ +# Dokumentation – verbindliche Regeln + +> **PFLICHTLEKTÜRE** für alle Agenten vor größeren Änderungen (neben `ARCHITECTURE.md`, `CODING_RULES.md`, `LESSONS_LEARNED.md`). +> Ziel: **ein** nachvollziehbarer Einstieg unter `.claude/`, klare Trennung **fachlich / technisch / Arbeitspapier / Issue**. + +--- + +## 1. Einstiegspunkte (Reihenfolge) + +1. Repo-Root: `CLAUDE.md` (Kontext, Links, Pflicht-Dokus) +2. Agent-Übersicht: **`.claude/README.md`** (Baum, wo was liegt) +3. Spez-Index: **`.claude/docs/README.md`** +4. Aufgaben-Tracking: **Gitea** – Übersicht lokal: **`.claude/docs/GITEA_ISSUES_INDEX.md`** (regelmäßig refreshen nach Bedarf) + +--- + +## 2. Ablagepflicht nach Dokumententyp + +| Typ | Pfad | Inhalt / Regel | +|-----|------|----------------| +| **Fachliche Spec (WAS)** | `.claude/docs/functional/` | Domäne, Use Cases, UX-Ziele, fachliche Datenarchitektur. **Keine** reine API-Parameterliste (→ technical). | +| **Technische Spec (WIE)** | `.claude/docs/technical/` | API-, DB-, Implementierungsmuster, Agent-Guides, Migrationen. | +| **Architektur-Querschnitt** | `.claude/docs/architecture/` | Kurze Überblicke (z. B. Frontend-Baum), ergänzend zu technical. | +| **Arbeitspapier / Zwischenstand** | `.claude/docs/working/` | Analysen, Sessions, Migration-Notizen, **keine** langfristige Norm. Kann veraltet sein → Datum im Dokument. **Nicht** als alleinige „Wahrheit“ für Produkt zitieren. | +| **Audits & Matrizen** | `.claude/docs/audit/` | Zeitlich begrenzte Reviews, Reconciliation, Gitea-Vorlagen. | +| **Issue-Begleitung (lang, versioniert im Repo)** | `docs/issues/` | Epics, detaillierte Issue-Ausarbeitungen, Abnahme-Dokus, die mit Gitea-Nummern korrespondieren. **Dateiname:** sinnvoller Slug, z. B. `issue-50-phase-0a-goal-system.md`. | +| **Governance in `docs/` (aktueller Ausnahmebereich)** | `docs/PLACEHOLDER_*.md` | Platzhalter-Governance & Deployment-Hinweise, solange Pfade in Skripten/Docker auf `docs/` zeigen. Bei Umzug nach `technical/` **alle** Referenzen und Deploy-Pfade anpassen. | + +--- + +## 3. Verboten / vermeiden + +- **Keine** vollständige Duplikation derselben Spec an zwei Pflegen (außer kurzer Stub mit Verweis auf Kanon). +- **Keine** normativen Regeln nur in Chat; Regeln hier oder in `ARCHITECTURE.md` / `CODING_RULES.md` festhalten. +- **Keine** Mischung „Issue-Checkliste + langfristige Spec“ in einer Datei ohne Überschrift – lieber Spec in `functional|technical`, Checkliste in Gitea oder `docs/issues/`. + +--- + +## 4. Pflege nach Änderungen + +| Änderung | Pflege | +|----------|--------| +| Neues Feature (> 1–2 Tage) | Spec in `functional/` und ggf. `technical/`; Issue in Gitea; bei Bedarf `docs/issues/issue-NN-….md`. | +| Neue Endpoints / Tabellen | `technical/API_REFERENCE.md`, `technical/DATABASE.md` bzw. `.claude/library/*` nach Prozess. | +| Platzhalter (Registry-Pflicht) | `.claude/docs/technical/PLACEHOLDER_REGISTRY_FRAMEWORK.md`, Registrierung unter `backend/placeholder_registrations/`. | +| Mehrere offene Gitea-Themen | `GITEA_ISSUES_INDEX.md` aktualisieren (Kategorien, Dubletten-Hinweis). | + +--- + +## 5. Lokale Agent-Artefakte (nicht zwingend im Git) + +Bleiben unter `.claude/` für Kontinuität der Agenten, werden **standardmäßig nicht** versioniert: + +- `.claude/task/` – Arbeitspakete pro Thema +- `.claude/handover/` – Session-Dateien (optional: nur `NEXT_SESSION_PROMPT.md` nach Bedarf versionieren) + +Diese Ordner sind **kein** Ersatz für `working/` oder `docs/issues/`, wenn das Ergebnis für das Team festgehalten werden soll. + +--- + +## 6. Kurzreferenz Pfade + +``` +.claude/README.md ← Einstieg Agent/Human +.claude/docs/README.md ← Spec-Katalog +.claude/docs/functional/ ← WAS +.claude/docs/technical/ ← WIE +.claude/docs/working/ ← Arbeitspapiere / Analysen +.claude/docs/audit/ ← Audits +.claude/docs/GITEA_ISSUES_INDEX.md ← Issue-Landkarte (lokal gepflegt) +docs/issues/ ← Issue-Epics (Repo) +docs/PLACEHOLDER_*.md ← Platzhalter (bis Migration der Pfade) +``` + +--- + +**Version:** 1.0 · **Stand:** 2026-04-08 diff --git a/.claude/rules/IMPLEMENTATION_RULES.md b/.claude/rules/IMPLEMENTATION_RULES.md new file mode 100644 index 0000000..364e0c9 --- /dev/null +++ b/.claude/rules/IMPLEMENTATION_RULES.md @@ -0,0 +1,249 @@ +# Implementation Rules – Mitai Jinkendo + +> **PFLICHTLEKTÜRE für Claude Code vor jeder Feature-Implementierung.** +> Diese Regeln sind verbindlich und dürfen nicht ohne explizite +> Genehmigung des Nutzers übersprungen werden. + +--- + +## 1. Konzept-basierte Implementierung (MANDATORY) + +### 1.1 Wann gilt dieser Prozess? + +**PFLICHT bei:** +- Feature-Requests mit verlinktem Konzept/Spec-Dokument +- Gitea Issues mit "Konzept" Label +- User sagt "laut Konzept", "wie im Konzept beschrieben" +- Komplexe Features mit >3 Datenquellen oder >5 Funktionen +- Neue Chart-Endpoints mit spezifischen Anforderungen + +**OPTIONAL bei:** +- Einfache Bugfixes (1-2 Zeilen) +- Triviale UI-Änderungen (Text, Farbe, Spacing) +- Code-Cleanup ohne Funktionsänderung + +**Bei Unsicherheit:** Prozess anwenden. Lieber einmal zu viel als einmal zu wenig. + +--- + +## 2. Der 5-Stufen-Prozess + +### Stufe 1: Anforderungsanalyse (BEFORE ANY CODE) + +``` +□ Konzept/Spec-Dokument VOLLSTÄNDIG lesen +□ Pro Feature: Checkliste mit ALLEN geforderten Elementen erstellen +□ Datenquellen identifizieren (welche Tabellen, data_layer Funktionen) +□ Fehlende Funktionen dokumentieren (was muss neu gebaut werden) +□ Unklarheiten als Fragen dokumentieren +□ Gap-Analyse: Was existiert bereits? Was fehlt komplett? +``` + +**Output:** Anforderungs-Matrix (Tabelle oder Liste) + +**Beispiel für E1 (Energiebilanz Chart):** +``` +Gefordert im Konzept: +✓ Kalorienaufnahme täglich (nutrition_log.kcal) +✓ 7d Durchschnitt Aufnahme (berechnen) +✗ Trainingskalorien (activity_log.kcal) → FEHLT +✗ Gewichtstrend 7d geglättet (weight_log) → FEHLT +✗ Lagged comparison 3d/7d/14d → FEHLT +✓ TDEE geschätzt (Profile-basiert oder Formel) +✗ Energiebilanz als Balken → Chart-Typ ändern + +Offene Fragen: +- Trainingskalorien: activity_log.kcal oder estimated_kcal? +- TDEE: Aus Profil oder Harris-Benedict berechnen? +- Lag-Korrelation: Pearson oder andere Methode? +``` + +### Stufe 2: Umsetzungskonzept erstellen + +``` +□ Backend: Welche Endpoints? Welche Parameter? +□ Backend: Welche data_layer Funktionen? (existierend + neu) +□ Backend: Welche Berechnungen? (Formeln dokumentieren) +□ Frontend: Welche Komponenten? Welche Chart-Typen? +□ Frontend: Welche API-Calls? +□ Dependencies: Müssen andere Module angepasst werden? +``` + +**Output:** Strukturiertes Umsetzungskonzept als Markdown + +**Beispiel:** +```markdown +## E1: Energiebilanz Chart - Umsetzungskonzept + +### Backend +**Endpoint:** `GET /api/charts/energy-balance?days=28` + +**Datenquellen:** +- `nutrition_log` (kcal, date) +- `activity_log` (kcal, date) → aggregiert nach Tag +- `weight_log` (weight, date) → 7d gleitend + +**Neue data_layer Funktionen:** +- `get_daily_training_calories(profile_id, days)` → Summe kcal pro Tag +- `get_weight_trend_7d(profile_id, days)` → 7d gleitender Durchschnitt +- `calculate_lag_correlation(energy_balance, weight_change, lag_days)` → Pearson + +**Berechnungen:** +1. Energie-Bilanz = kcal_intake - (TDEE + training_kcal) +2. 7d avg intake = rolling_mean(kcal_intake, 7) +3. 7d avg bilanz = rolling_mean(bilanz, 7) +4. Lag-Korrelation: bilanz[t] vs. weight_change[t+3/7/14] + +**Response Format:** +```json +{ + "chart_type": "mixed", // Line + Bar kombiniert + "data": { + "labels": ["2026-01-01", ...], + "datasets": [ + {"type": "line", "label": "Kalorien", "data": [...]}, + {"type": "line", "label": "7d Ø Kalorien", "data": [...]}, + {"type": "line", "label": "Training kcal", "data": [...]}, + {"type": "line", "label": "TDEE", "data": [...], "borderDash": [5,5]}, + {"type": "bar", "label": "Bilanz", "data": [...], "yAxisID": "y1"}, + {"type": "line", "label": "Gewicht (7d)", "data": [...], "yAxisID": "y2"} + ] + }, + "metadata": { + "avg_intake": 2100, + "avg_balance": -300, + "weight_change_7d": -0.5, + "lag_correlation": { + "3d": 0.42, + "7d": 0.68, + "14d": 0.75 + } + } +} +``` + +### Frontend +**Component:** `NutritionCharts.jsx` → `renderEnergyBalance()` + +**Chart-Typ:** Recharts `ComposedChart` (Line + Bar kombiniert) + +**API-Call:** `api.getEnergyBalanceChart(days)` + +**Features:** +- Dual Y-Axis (links: kcal, rechts: kg) +- Legende mit Hover-Details +- Tooltips zeigen alle Werte +- Lag-Korrelation in Metadata-Box unter Chart +``` + +### Stufe 3: User-Approval einholen (MANDATORY) + +``` +□ Umsetzungskonzept dem User zeigen +□ Offene Fragen klären +□ Auf explizites OK warten +□ Bei Änderungswünschen: Konzept anpassen, erneut zeigen +``` + +**Niemals** mit der Implementierung beginnen ohne User-Approval! + +### Stufe 4: Implementierung + +``` +□ Exakt nach genehmigtem Konzept implementieren +□ Niemals "ähnliches" bauen oder Abkürzungen nehmen +□ Bei unvorhergesehenen Problemen: User informieren, Konzept anpassen +□ Commits: Referenz zum Konzept im Commit-Message +``` + +**Commit-Message Format:** +``` +feat: E1 Energiebilanz Chart (konzept-konform) + +Backend: +- Neue data_layer Funktionen: get_daily_training_calories, get_weight_trend_7d +- Endpoint: /api/charts/energy-balance mit Lag-Korrelation +- Chart-Type: mixed (Line + Bar kombiniert) + +Frontend: +- ComposedChart mit Dual Y-Axis (kcal + kg) +- Lag-Korrelation Metadata-Display + +Konzept: .claude/docs/functional/mitai_jinkendo_konzept_diagramme_auswertungen_v2.md (E1) +``` + +### Stufe 5: Compliance-Check (BEFORE COMMIT) + +``` +□ Jedes Feature gegen Konzept prüfen (Checkliste abhaken) +□ Alle geforderten Elemente vorhanden? +□ Berechnungen korrekt nach Konzept? +□ Chart-Typ und Darstellung wie gefordert? +□ Metadata vollständig? +``` + +**Erst nach 100% Compliance: Commit + Push** + +--- + +## 3. Warnsignale für Fehlverhalten + +**Wenn der User sagt:** +- "Das steht aber im Konzept" +- "Es fehlt X" (nach Deploy) +- "Überprüfe das Konzept" +- "Das ist nicht wie gefordert" + +**→ SOFORT STOPPEN** +- Konzept nochmal vollständig lesen +- Gap-Analyse machen: Was fehlt? +- Nachfragen, nicht raten +- Konzept-konform überarbeiten + +--- + +## 4. Skill-Integration + +Für komplexe Features (>10 Funktionen, >3 Module): + +```bash +/implement-feature +``` + +Der Skill führt automatisch Stufe 1-3 aus und wartet auf Approval. + +--- + +## 5. Ausnahmen + +**Einzige erlaubte Ausnahme:** +User sagt explizit: "Ignoriere das Konzept" oder "Mach es anders als im Konzept" + +**Alle anderen Fälle:** Prozess anwenden. + +--- + +## 6. Eskalation bei Unklarheiten + +**Bei Unklarheiten im Konzept:** + +1. **Niemals raten oder "ähnliches" bauen** +2. **Immer nachfragen:** + - "Im Konzept steht X, aber unklar ist Y. Wie soll ich vorgehen?" + - "Option A oder Option B?" +3. **Warten auf Antwort** +4. **Konzept mit Antwort aktualisieren** + +--- + +## Zusammenfassung: 5-Stufen-Checkliste + +``` +□ 1. Anforderungsanalyse (Konzept vollständig lesen, Checkliste erstellen) +□ 2. Umsetzungskonzept (Backend + Frontend + Datenquellen dokumentieren) +□ 3. User-Approval (Konzept zeigen, auf OK warten) +□ 4. Implementierung (Exakt nach Konzept, keine Abkürzungen) +□ 5. Compliance-Check (100% Checkliste abhaken vor Commit) +``` + +**Kein Schritt darf übersprungen werden.** diff --git a/.claude/rules/LESSONS_LEARNED.md b/.claude/rules/LESSONS_LEARNED.md new file mode 100644 index 0000000..1b01cfd --- /dev/null +++ b/.claude/rules/LESSONS_LEARNED.md @@ -0,0 +1,186 @@ +# Lessons Learned + +Fehler die gemacht wurden – damit sie nicht wiederholt werden. + +## 1. Feature-Enforcement Rollback (20.03.2026) + +**Was:** Membership-System Feature-Enforcement implementiert +**Problem:** Brach Analyse-Verlauf, Export-Sichtbarkeit und Zähler +**Rollback:** Commit 4fcde4a +**Lösung:** Einfaches ai_enabled + ai_limit_day System aktiv + +**Regel:** Feature-Enforcement nie ohne vollständige Test-Suite aktivieren. +Zuerst Shadow-Mode (loggen aber nicht blockieren), dann schrittweise aktivieren. + +## 2. session=Depends(require_auth) innerhalb Header() + +**Was:** Automatisches Einfügen von Auth in bestehende Endpoints +**Problem:** `session` wurde innerhalb `Header(default=None, session=...)` eingebettet +**Folge:** FastAPI ignorierte Auth stillschweigend – Endpoint ungeschützt +**Lösung:** session immer als separater Parameter + +```python +# ❌ Was passiert ist: +def endpoint(x: str = Header(default=None, session=Depends(require_auth))): + +# ✅ Korrekt: +def endpoint(x: str = Header(default=None), session: dict = Depends(require_auth)): +``` + +## 3. PostgreSQL Migration – apt-get Probleme + +**Was:** Docker Build mit apt-get postgresql-client +**Problem:** Build hing 30+ Minuten +**Lösung:** Reine Python-Lösung mit psycopg2-binary, kein apt-get + +## 4. SQLite → PostgreSQL Datenmigration + +**Probleme:** +- Leere date-Strings (`''`) → PostgreSQL wirft Fehler → zu NULL konvertieren +- Boolean: SQLite `0/1` → PostgreSQL `true/false` +- `?` Platzhalter → `%s` + +## 5. dayjs.week() Plugin fehlt + +**Was:** dayjs().week() ohne isoWeek-Plugin aufgerufen +**Problem:** Weißer Screen auf Verlauf/Ernährung +**Lösung:** Native ISO-Wochenberechnung (siehe FRONTEND.md) + +## 6. Bun Crash bei langen Claude Code Sessions + +**Was:** Claude Code CLI lief >30 Minuten +**Problem:** Bun (JS Runtime) crashed mit "Illegal instruction" +**Lösung:** Bei komplexen Tasks früher committen und neue Session starten + +## 7. Docker Cache nach Dateiänderung + +**Was:** main.py auf Pi kopiert, Container neu gestartet +**Problem:** Docker nutzte gecachten Layer – alte Datei im Container +**Lösung:** Immer `--no-cache` bei Änderungen am Code: +```bash +docker compose build --no-cache backend +``` + +## 8. Placeholder Registry Framework – Kritische Learnings (02.04.2026) + +**Was:** Implementation von 14 Nutrition Placeholders mit neuem Registry Framework + +### 8.1 OutputType.TEXT existiert nicht +**Problem:** `OutputType.TEXT` verursachte AttributeError +**Richtig:** `OutputType.STRING` für Text-Outputs +**Verfügbar:** NUMERIC, STRING, BOOLEAN, JSON, LIST, TEXT_SUMMARY + +```python +# ❌ Falsch: +output_type=OutputType.TEXT + +# ✅ Richtig: +output_type=OutputType.STRING +``` + +### 8.2 Time Window "mixed" ist problematisch +**Problem:** `time_window="mixed"` unklar für Export und Consumers +**Lösung:** Dominante Zeitkomponente wählen, Rest als Kommentar +```python +# ❌ Unklar: +time_window="mixed" + +# ✅ Klar: +time_window="7d" # protein 7d avg; weight ist snapshot (secondary) +``` + +### 8.3 Units müssen präzise sein +**Problem:** Unpräzise Units führen zu Interpretationsproblemen +```python +# ❌ Unpräzise: +unit="score" +unit="kcal" + +# ✅ Präzise: +unit="score (0-100)" +unit="kcal/day" +``` + +### 8.4 Date Aggregation bei CSV-Imports +**Problem:** Mehrere Einträge pro Tag → falsche "Tage"-Zählung +**Lösung:** Immer `GROUP BY date, SUM()` für Tages-Aggregation + +```python +# ❌ Falsch (zählt Einträge): +SELECT protein_g FROM nutrition_log WHERE date >= ... + +# ✅ Richtig (zählt Tage): +SELECT date, SUM(protein_g) as daily_protein +FROM nutrition_log +WHERE date >= ... +GROUP BY date +``` + +**Betroffen:** Alle Funktionen die "Tage" zählen oder daily averages berechnen + +### 8.5 Evidence-Based = Nie Raten +**Problem:** CODE_DERIVED falsch gesetzt ohne Code-Inspektion +**Lösung:** Bei Unsicherheit UNRESOLVED oder TO_VERIFY nutzen + +```python +# Wenn unklar: +metadata.set_evidence("field", EvidenceType.UNRESOLVED) # ehrlich +# Nicht: +metadata.set_evidence("field", EvidenceType.CODE_DERIVED) # halluziniert +``` + +### 8.6 Known Limitations = Dokumentations-Gold +**Problem:** Inkonsistenzen/Bugs verschwiegen +**Erfolg:** Transparent dokumentiert in `known_limitations` + +**Beispiel:** +```python +known_limitations=( + "KRITISCHE INKONSISTENZ: Protein ist geglättet (7d average), " + "Gewicht ist single-point (latest). Anfällig für Gewichts-Outlier." +) +``` + +**Regel:** Probleme dokumentieren statt verstecken. User entscheidet über Fixes. + +### 8.7 Formeln explizit dokumentieren +**Problem:** "Berechnet Score" zu vage, nicht reproduzierbar +**Erfolg:** Alle Formeln, Thresholds, TDEE-Modelle explizit dokumentiert + +**Beispiel:** +```python +known_limitations=( + "TDEE-MODELL: weight_kg × 32.5 (vereinfacht). " + "NICHT berücksichtigt: Aktivitätslevel, Alter, Geschlecht." +) +``` + +### 8.8 No-Change Requirement ist absolut +**Problem:** Versuchung, offensichtliche Bugs zu fixen +**Regel:** NUR dokumentieren, User entscheidet + +**Beispiel:** `protein_g_per_kg` hat Zeitfenster-Inkonsistenz (7d protein / latest weight) +→ Dokumentiert in known_limitations, NICHT gefixed + +### 8.9 Testing nach jedem Deploy +**Problem:** Fehler erst nach komplettem Cluster entdeckt +**Erfolg:** Testing nach jedem Part (A, B, C) → frühe Fehlerkennung + +**Workflow:** +1. Deploy Part A +2. Test Export +3. Werte verifizieren +4. Erst dann Part B + +### 8.10 .claude in .gitignore +**Problem:** Versuch, `.claude/task/` Files zu committen +**Lösung:** Nur `backend/` Code committen, `.claude/` ist local docs + +--- + +**Zusammenfassung Nutrition Cluster:** +- 14 Placeholders erfolgreich implementiert +- 2 Bugs gefunden und behoben (OutputType, Date Aggregation) +- 2 Metadaten-Inkonsistenzen korrigiert (time_window, unit) +- Alle kritischen Formeln dokumentiert +- Framework bewährt und skalierbar diff --git a/.gitignore b/.gitignore index e105a20..55e7229 100644 --- a/.gitignore +++ b/.gitignore @@ -59,10 +59,18 @@ coverage/ tmp/ *.tmp -#.claude Konfiguration -.claude/ +# Claude: nur ausgewählte Bereiche versionieren (siehe .claude/rules/DOCUMENTATION.md) +.claude/** +!.claude/README.md +!.claude/docs/ +!.claude/docs/**/* +!.claude/rules/ +!.claude/rules/**/* +!.claude/commands/ +!.claude/commands/**/* +# Lokale Secrets / Editor +.claude/settings.local.json # Cursor MCP mit Secrets (Example: .cursor/mcp.json.example) .cursor/mcp.json -.claude/settings.local.json frontend/package-lock.json diff --git a/CLAUDE.md b/CLAUDE.md index aa859e2..f234606 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,10 +4,13 @@ > VOR jeder Implementierung lesen: > | Architektur-Regeln | `.claude/rules/ARCHITECTURE.md` | +> | **Dokumentationsablage** | **`.claude/rules/DOCUMENTATION.md`** | > | Coding-Regeln | `.claude/rules/CODING_RULES.md` | > | Lessons Learned | `.claude/rules/LESSONS_LEARNED.md` | +> | **Gitea-Landkarte (lokal gepflegt)** | **`.claude/docs/GITEA_ISSUES_INDEX.md`** | > | **GUI / IA / Admin / Nav / PWA-Leiste** | **`docs/issues/GUI_IA_ADMIN_NAV_2026-04-05.md`** | > | **Dashboard-Lab-Widgets** (Katalog, Registrierung, `config`) | **`.claude/docs/technical/DASHBOARD_WIDGETS_AGENT_GUIDE.md`** | +> | **Agent-Einstieg** | **`.claude/README.md`** | ## Claude Code Verantwortlichkeiten @@ -276,7 +279,7 @@ ffa99f1 fix: correct confidence thresholds for 30-89 day range - ✅ **Frontend:** GoalsPage mit mobile-friendly Design (570 Zeilen) - ✅ **Navigation:** Goals Preview (Dashboard) + Ziele Button (Analysis) - ✅ **Basis geschaffen:** Für 120+ goal-aware Platzhalter (Phase 0b) -- ✅ **Dokumentation:** issue-50, NEXT_STEPS_2026-03-26.md, GOALS_SYSTEM_UNIFIED_ANALYSIS.md +- ✅ **Dokumentation:** issue-50, `.claude/docs/working/NEXT_STEPS_2026-03-26.md`, `.claude/docs/working/GOALS_SYSTEM_UNIFIED_ANALYSIS.md` ### Frühere Updates (26.03.2026 - Vormittag) - ✅ **circ_summary erweitert:** Best-of-Each Strategie mit Altersangaben @@ -583,7 +586,7 @@ ffa99f1 fix: correct confidence thresholds for 30-89 day range ### Phase 0a: Minimal Goal System ✅ (Completed 26.03.2026) > **Gitea:** Issue #50 (zu erstellen) - COMPLETED -> **Dokumentation:** `docs/issues/issue-50-phase-0a-goal-system.md`, `docs/GOALS_SYSTEM_UNIFIED_ANALYSIS.md` +> **Dokumentation:** `docs/issues/issue-50-phase-0a-goal-system.md`, `.claude/docs/working/GOALS_SYSTEM_UNIFIED_ANALYSIS.md` **Zwei-Ebenen-Ziel-Architektur für goal-aware KI-Analysen:** @@ -638,7 +641,7 @@ ffa99f1 fix: correct confidence thresholds for 30-89 day range - Option A: Issue #49 - Prompt Page Assignment (6-8h, Quick Win) - Option B: Phase 0b - Goal-Aware Placeholders (16-20h, Strategic) -📚 Details: `docs/NEXT_STEPS_2026-03-26.md` +📚 Details: `.claude/docs/working/NEXT_STEPS_2026-03-26.md` ## Feature-Roadmap @@ -825,6 +828,12 @@ Bottom-Padding Mobile: 80px (Navigation) ## Dokumentations-Struktur +**Agent-Einstieg & Ablage:** [`.claude/README.md`](.claude/README.md) · **Regeln:** [`.claude/rules/DOCUMENTATION.md`](.claude/rules/DOCUMENTATION.md) + +**Index Projekt-`docs/`** (Issues, Placeholder-Governance): [`docs/README.md`](docs/README.md) + +**Spec-Katalog:** [`.claude/docs/README.md`](.claude/docs/README.md) + ``` .claude/ ├── BACKLOG.md ← Feature-Übersicht @@ -844,6 +853,7 @@ Bottom-Padding Mobile: 80px (Navigation) |API-Referenz|`.claude/library/API\_REFERENCE.md`|Alle Endpoints| |Datenbankschema|`.claude/library/DATABASE.md`|Tabellen + Beziehungen| |Dashboard-Lab-Widgets|`.claude/docs/technical/DASHBOARD_WIDGETS_AGENT_GUIDE.md`|Katalog, Validierung, Frontend-Registry, konfigurierbare `config`| +|Projekt-Doku (Git)|`docs/README.md` + `docs/issues/`|Issue-Specs, Reviews, Platzhalter-Governance, Status-Snapshots| > Library-Dateien werden mit `/document` generiert und nach größeren > Änderungen aktualisiert. diff --git a/docs/MEMBERSHIP_SYSTEM.md b/docs/MEMBERSHIP_SYSTEM.md index b48b4fa..521dfb0 100644 --- a/docs/MEMBERSHIP_SYSTEM.md +++ b/docs/MEMBERSHIP_SYSTEM.md @@ -1,1058 +1,7 @@ -# Mitai Jinkendo - Membership & Subscription System (v9c) +# Membership-System – Verweis -**Version:** v9c-dev -**Status:** Backend & Admin-UI komplett, Enforcement deaktiviert -**Letzte Aktualisierung:** 20. März 2026 +Die **kanonische** v9c-Dokumentation liegt unter: ---- +**[`.claude/docs/technical/MEMBERSHIP_SYSTEM.md`](../.claude/docs/technical/MEMBERSHIP_SYSTEM.md)** -## Inhaltsverzeichnis - -1. [Überblick](#überblick) -2. [Architektur-Entscheidungen](#architektur-entscheidungen) -3. [Datenbank-Schema](#datenbank-schema) -4. [Backend-API](#backend-api) -5. [Frontend-Komponenten](#frontend-komponenten) -6. [Feature-Enforcement-System](#feature-enforcement-system) -7. [Lessons Learned](#lessons-learned) -8. [Roadmap](#roadmap) - ---- - -## Überblick - -Das Mitai Jinkendo Membership-System (v9c) ist ein flexibles, tier-basiertes Subscription-System mit folgenden Kern-Features: - -### Implementierte Features ✅ - -- **4 Tier-Stufen**: free, basic, premium, selfhosted -- **Feature-Registry-Pattern**: Zentrale Definition aller limitierbaren Features -- **Flexible Limit-Matrix**: Admin kann Tier × Feature Limits konfigurieren -- **User-Override-System**: Individuelle Limits pro User -- **Coupon-System**: 3 Typen (single_use, multi_use_period, gift) -- **Coupon-Stacking**: Intelligente Pause/Resume-Logik bei temporären Zugriffen -- **Access-Grants**: Zeitlich begrenzte Tier-Zugriffe mit Quelle-Tracking -- **User-Activity-Log**: JSONB-basierte Aktivitätsverfolgung -- **Admin-UI**: Vollständige Verwaltungsoberfläche für alle Aspekte - -### Geplante Features 🔲 - -- Feature-Enforcement in Endpoints (needs redesign) -- Selbst-Registrierung mit E-Mail-Verifizierung -- Trial-System mit automatischem Downgrade -- Bonus-System (Login-Streaks) -- Stripe-Integration -- Partner-Integration (Wellpass, Hansefit) - ---- - -## Architektur-Entscheidungen - -### 1. Feature-Registry-Pattern - -**Entscheidung:** Alle limitierbaren Features werden zentral in einer `features` Tabelle definiert. - -**Rationale:** -- Neue Features können ohne Schema-Migration hinzugefügt werden -- Metadaten (name, description, category, unit) sind direkt verfügbar -- Konsistenz zwischen Backend-Checks und Frontend-Display -- Admin-UI kann automatisch generiert werden - -**Schema:** -```sql -CREATE TABLE features ( - id TEXT PRIMARY KEY, -- 'ai_calls', 'data_export', etc. - name TEXT NOT NULL, -- 'KI-Analysen', 'Daten exportieren' - description TEXT, - category TEXT, -- 'ai', 'export', 'data', 'integration' - limit_type TEXT DEFAULT 'count', -- 'count' oder 'boolean' - reset_period TEXT DEFAULT 'never', -- 'never', 'daily', 'monthly' - default_limit INTEGER, -- NULL = unbegrenzt - active BOOLEAN DEFAULT true, - sort_order INTEGER DEFAULT 0, - created TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated TIMESTAMP -); -``` - -**Beispiel-Features:** -```sql --- Count-based mit monatlichem Reset -('ai_calls', 'KI-Analysen', 'KI-Auswertungen pro Monat', 'ai', 'count', 'monthly', 0, true) - --- Boolean-Feature (an/aus) -('ai_pipeline', 'KI-Pipeline', 'Vollständige Pipeline-Analyse', 'ai', 'boolean', 'never', 0, true) - --- Count-based ohne Reset (Gesamt-Limit) -('weight_entries', 'Gewichtseinträge', 'Anzahl Gewichtsmessungen', 'data', 'count', 'never', NULL, true) -``` - ---- - -### 2. Tier-System - -**Entscheidung:** Vereinfachte Tier-Tabelle ohne hart-codierte Limits. - -**Rationale:** -- Limits werden in separate `tier_limits` Tabelle ausgelagert -- Tiers können dynamisch hinzugefügt werden -- Pricing-Informationen zentral verwaltet -- Flexibilität für zukünftige Tier-Erweiterungen - -**Schema:** -```sql -CREATE TABLE tiers ( - id TEXT PRIMARY KEY, -- 'free', 'basic', 'premium', 'selfhosted' - slug TEXT UNIQUE NOT NULL, - name TEXT NOT NULL, -- 'Free', 'Basic', 'Premium', 'Self-Hosted' - description TEXT, - price_monthly DECIMAL(10,2), - price_yearly DECIMAL(10,2), - sort_order INTEGER DEFAULT 0, - active BOOLEAN DEFAULT true, - created TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated TIMESTAMP -); -``` - -**Initial Tiers:** -- **free**: Eingeschränkt (30 Daten-Einträge, 0 KI-Calls, kein Export) -- **basic**: Kernfunktionen (unbegrenzte Daten, 3 KI-Calls/Monat, Export erlaubt) -- **premium**: Alles unbegrenzt (inkl. KI-Pipeline, Connectoren) -- **selfhosted**: Admin-Tier für selbst-gehostete Installationen - ---- - -### 3. Zugriffs-Hierarchie - -**Entscheidung:** Drei-stufige Priorität für effektive Tier-Ermittlung. - -**Priorität (höchste zuerst):** -1. **Admin-Override**: `profiles.tier_locked = true` → nutzt `profiles.tier` -2. **Access-Grant**: Aktiver, nicht-pausierter Grant → nutzt `access_grants.granted_tier` -3. **Trial**: `profiles.trial_ends_at > NOW()` → nutzt trial tier -4. **Base Tier**: `profiles.tier` - -**Implementierung:** -```python -def get_effective_tier(profile_id: str) -> str: - """Get effective tier considering all overrides.""" - with get_db() as conn: - cur = get_cursor(conn) - - # 1. Check if tier is locked by admin - cur.execute("SELECT tier, tier_locked FROM profiles WHERE id = %s", (profile_id,)) - profile = cur.fetchone() - if profile['tier_locked']: - return profile['tier'] - - # 2. Check for active access grant - cur.execute(""" - SELECT granted_tier FROM access_grants - WHERE profile_id = %s - AND is_active = true - AND valid_from <= CURRENT_TIMESTAMP - AND (valid_until IS NULL OR valid_until > CURRENT_TIMESTAMP) - ORDER BY created DESC LIMIT 1 - """, (profile_id,)) - grant = cur.fetchone() - if grant: - return grant['granted_tier'] - - # 3. Check trial - cur.execute(""" - SELECT tier FROM profiles - WHERE id = %s AND trial_ends_at > CURRENT_TIMESTAMP - """, (profile_id,)) - trial = cur.fetchone() - if trial: - return 'premium' # or configurable trial tier - - # 4. Base tier - return profile['tier'] -``` - -**Rationale:** -- Admin kann User dauerhaft einem Tier zuweisen (Support-Fälle) -- Temporäre Zugriffe (Coupons, Wellpass) haben Vorrang vor Base-Tier -- Trial-Logik ist transparent und automatisch -- Base-Tier ist Fallback - ---- - -### 4. Coupon-System - -**Entscheidung:** 3 Coupon-Typen mit unterschiedlicher Stacking-Logik. - -**Typen:** - -#### 4.1 Single-Use Coupon -- **Verwendung:** Einmalig einlösbar (z.B. Geschenk-Coupon) -- **Verhalten:** Erstellt `access_grant` mit fester Laufzeit -- **Stacking:** Zeitlich sequenziell (startet nach Ablauf vorheriger Grants) -- **Beispiel:** "30 Tage Premium geschenkt" - -```sql -INSERT INTO coupons (code, type, grants_tier, duration_days, max_redemptions) VALUES -('FRIEND-GIFT-ABC', 'single_use', 'premium', 30, 1); -``` - -#### 4.2 Multi-Use Period Coupon -- **Verwendung:** Unbegrenzt einlösbar während Gültigkeitszeitraum -- **Verhalten:** Pausiert andere Grants, reaktiviert sie nach Ablauf -- **Stacking:** Override mit Pause/Resume -- **Beispiel:** "Wellpass-Monatszugang März 2026" - -```sql -INSERT INTO coupons (code, type, grants_tier, valid_from, valid_until, max_redemptions) VALUES -('WELLPASS-2026-03', 'multi_use_period', 'premium', '2026-03-01', '2026-03-31', NULL); -``` - -**Stacking-Logik:** -```python -# User hat Single-Use Grant (20 Tage verbleibend) -# User löst Wellpass-Coupon ein -# → Single-Use Grant wird pausiert (is_active=false, paused_by=wellpass_grant_id) -# → Nach Wellpass-Ablauf: Single-Use wird reaktiviert (noch 20 Tage) -``` - -#### 4.3 Gift Coupon -- **Verwendung:** Vom System generiert als Bonus -- **Verhalten:** Wie Single-Use, aber spezielles Tracking -- **Beispiel:** "Login-Streak Belohnung" - ---- - -### 5. User-Restrictions (Override-System) - -**Entscheidung:** User-spezifische Overrides haben höchste Priorität. - -**Schema:** -```sql -CREATE TABLE user_feature_restrictions ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, - feature_id TEXT NOT NULL REFERENCES features(id) ON DELETE CASCADE, - limit_value INTEGER, -- NULL = unbegrenzt, überschreibt Tier-Limit - enabled BOOLEAN DEFAULT true, - reason TEXT, -- Warum wurde Override gesetzt? - set_by UUID REFERENCES profiles(id), -- Welcher Admin? - created TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated TIMESTAMP, - UNIQUE(profile_id, feature_id) -); -``` - -**Anwendungsfälle:** -- Admin gewährt einzelnem User mehr/weniger Zugriff -- Support-Fall: User bekommt temporär mehr KI-Calls -- Beta-Tester: Zugriff auf experimentelle Features -- Problem-User: Einschränkung bestimmter Features - -**Beispiel:** -```sql --- User bekommt 100 KI-Calls/Monat statt Tier-Standard -INSERT INTO user_feature_restrictions (profile_id, feature_id, limit_value, reason, set_by) -VALUES ('user-uuid', 'ai_calls', 100, 'Beta-Tester', 'admin-uuid'); -``` - ---- - -## Datenbank-Schema - -### Übersicht aller v9c Tabellen - -``` -v9c Subscription System (11 neue Tabellen): -├── app_settings - Globale App-Konfiguration -├── tiers - Tier-Definitionen (free/basic/premium/selfhosted) -├── features - Feature-Registry (zentrale Feature-Definition) -├── tier_limits - Tier × Feature Matrix (Limits pro Tier) -├── user_feature_restrictions - User-spezifische Overrides -├── user_feature_usage - Usage-Tracking (für reset_period) -├── coupons - Coupon-Verwaltung (3 Typen) -├── coupon_redemptions - Einlösungs-Historie -├── access_grants - Zeitlich begrenzte Zugriffe -├── user_activity_log - Aktivitäts-Tracking (JSONB) -└── user_stats - Aggregierte Statistiken - -Erweiterte Tabellen: -└── profiles - Neue Spalten: tier, tier_locked, trial_ends_at, - email_verified, invited_by, contract_type -``` - -### Detaillierte Schema-Definitionen - -#### app_settings -```sql -CREATE TABLE app_settings ( - key TEXT PRIMARY KEY, - value TEXT, - value_type TEXT DEFAULT 'string', -- 'string', 'integer', 'boolean', 'json' - description TEXT, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_by UUID REFERENCES profiles(id) -); - --- Beispiel-Einstellungen: -INSERT INTO app_settings (key, value, value_type, description) VALUES -('trial_days', '14', 'integer', 'Anzahl Tage Trial-Zugang'), -('trial_behavior', 'downgrade', 'string', 'downgrade|lock nach Trial-Ende'), -('allow_registration', 'false', 'boolean', 'Selbst-Registrierung erlaubt?'), -('default_tier_trial', 'premium', 'string', 'Tier während Trial'), -('gift_coupons_per_month', '3', 'integer', 'Max Geschenk-Coupons pro User/Monat'); -``` - -#### tier_limits (Kern der Matrix) -```sql -CREATE TABLE tier_limits ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - tier_id TEXT NOT NULL REFERENCES tiers(id) ON DELETE CASCADE, - feature_id TEXT NOT NULL REFERENCES features(id) ON DELETE CASCADE, - limit_value INTEGER, -- NULL = unbegrenzt, 0 = deaktiviert - enabled BOOLEAN DEFAULT true, - created TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated TIMESTAMP, - UNIQUE(tier_id, feature_id) -); - --- Beispiel Free Tier: -INSERT INTO tier_limits (tier_id, feature_id, limit_value) VALUES -('free', 'weight_entries', 30), -('free', 'ai_calls', 0), -- Deaktiviert -('free', 'data_export', 0); -- Deaktiviert - --- Beispiel Premium Tier: -INSERT INTO tier_limits (tier_id, feature_id, limit_value) VALUES -('premium', 'weight_entries', NULL), -- Unbegrenzt -('premium', 'ai_calls', NULL), -- Unbegrenzt -('premium', 'ai_pipeline', 1); -- Boolean: Aktiviert -``` - -#### access_grants (Zeitliche Zugriffe) -```sql -CREATE TABLE access_grants ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, - granted_tier TEXT NOT NULL REFERENCES tiers(id), - valid_from TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - valid_until TIMESTAMP, -- NULL = unbegrenzt - source TEXT, -- 'coupon', 'admin_grant', 'trial' - source_reference TEXT, -- Coupon-Code oder Admin-Notiz - is_active BOOLEAN DEFAULT true, -- Kann pausiert werden - paused_at TIMESTAMP, - paused_by UUID REFERENCES access_grants(id), -- Welcher Grant hat pausiert? - created TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - created_by UUID REFERENCES profiles(id) -); - --- Index für Performance -CREATE INDEX idx_access_grants_active ON access_grants(profile_id, is_active, valid_until DESC); -``` - -#### user_activity_log -```sql -CREATE TABLE user_activity_log ( - id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), - profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, - activity_type TEXT NOT NULL, -- 'login', 'coupon_redeemed', 'tier_change', etc. - details JSONB, -- Flexible Details - ip_address TEXT, - user_agent TEXT, - created TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); - --- Index für Abfragen -CREATE INDEX idx_activity_log_profile ON user_activity_log(profile_id, created DESC); - --- Beispiel-Einträge: --- Login -INSERT INTO user_activity_log (profile_id, activity_type, details) VALUES -('uuid', 'login', '{"ip": "192.168.1.1", "device": "Chrome/Mac"}'); - --- Coupon eingelöst -INSERT INTO user_activity_log (profile_id, activity_type, details) VALUES -('uuid', 'coupon_redeemed', '{"code": "FRIEND-GIFT-ABC", "tier": "premium", "days": 30}'); - --- Tier-Änderung -INSERT INTO user_activity_log (profile_id, activity_type, details) VALUES -('uuid', 'tier_change', '{"from": "free", "to": "basic", "reason": "admin_grant"}'); -``` - ---- - -## Backend-API - -### Router-Übersicht - -``` -v9c Backend-Router (7 neue): -├── /api/subscription - User-facing: Abo-Status, Usage, Limits -├── /api/coupons - User: redeem; Admin: CRUD -├── /api/features - Admin: Feature-Registry CRUD + check-access Endpoint -├── /api/tiers - Admin: Tier-Verwaltung CRUD -├── /api/tier-limits - Admin: Matrix-Editor (Tier × Feature) -├── /api/user-restrictions- Admin: User-Override-System -└── /api/access-grants - Admin: Grant-Verwaltung (create, list, revoke) -``` - -### Wichtige Endpoints - -#### User-Facing - -**GET /api/subscription/me** -```json -{ - "tier": "basic", - "tier_source": "base", // 'base', 'trial', 'access_grant', 'admin_locked' - "trial_ends_at": null, - "access_grants": [ - { - "granted_tier": "premium", - "valid_until": "2026-04-15", - "source": "coupon", - "days_remaining": 25 - } - ] -} -``` - -**GET /api/subscription/usage** -```json -{ - "ai_calls": { - "limit": 3, - "used": 2, - "remaining": 1, - "reset_at": "2026-04-01T00:00:00" - }, - "data_export": { - "limit": 5, - "used": 0, - "remaining": 5, - "reset_at": "2026-04-01T00:00:00" - } -} -``` - -**POST /api/coupons/redeem** -```json -{ - "code": "FRIEND-GIFT-ABC" -} -``` -Response: -```json -{ - "success": true, - "granted_tier": "premium", - "valid_until": "2026-04-19", - "message": "30 Tage Premium-Zugang aktiviert!" -} -``` - -#### Admin-Only - -**GET /api/tier-limits** -```json -[ - { - "tier_id": "free", - "feature_id": "ai_calls", - "limit_value": 0, - "enabled": false - }, - { - "tier_id": "basic", - "feature_id": "ai_calls", - "limit_value": 3, - "enabled": true - } -] -``` - -**PUT /api/tier-limits** -```json -{ - "tier_id": "basic", - "feature_id": "ai_calls", - "limit_value": 5 -} -``` - -**POST /api/access-grants** -```json -{ - "profile_id": "user-uuid", - "granted_tier": "premium", - "valid_until": "2026-12-31", - "source": "admin_grant", - "source_reference": "Support-Fall #123" -} -``` - ---- - -## Frontend-Komponenten - -### Admin-UI (vollständig implementiert) - -#### 1. AdminFeaturesPage -**Route:** `/admin/features` - -**Funktionen:** -- Feature-Liste (sortierbar, filterbar) -- Neues Feature hinzufügen -- Feature bearbeiten (Name, Beschreibung, Limits, Reset-Period) -- Feature deaktivieren (soft-delete) - -**UI-Elemente:** -- Feature-Tabelle mit Spalten: Name, Kategorie, Limit-Typ, Reset-Period, Default-Limit -- Modal für Feature-Bearbeitung -- Kategorie-Filter (ai, export, data, integration) - -#### 2. AdminTiersPage -**Route:** `/admin/tiers` - -**Funktionen:** -- Tier-Liste mit CRUD -- Pricing (monatlich/jährlich) konfigurierbar -- Sort-Order für Anzeige-Reihenfolge -- Tier aktivieren/deaktivieren - -#### 3. AdminTierLimitsPage -**Route:** `/admin/tier-limits` - -**Funktionen:** -- **Matrix-Editor**: Tiers (Spalten) × Features (Zeilen) -- Responsive: Desktop = Tabelle, Mobile = Cards -- Inline-Editing mit Auto-Save -- Checkbox für Boolean-Features -- Number-Input für Count-Features -- NULL/∞ für unbegrenzt - -**UI-Konzept:** -``` -┌────────────────┬──────┬───────┬─────────┬────────────┐ -│ Feature │ Free │ Basic │ Premium │ Selfhosted │ -├────────────────┼──────┼───────┼─────────┼────────────┤ -│ Gewicht │ 30 │ ∞ │ ∞ │ ∞ │ -│ KI-Calls/Mon │ ☐ 0 │ ☑ 3 │ ☑ ∞ │ ☑ ∞ │ -│ KI-Pipeline │ ☐ │ ☐ │ ☑ │ ☑ │ -│ Export/Mon │ ☐ 0 │ ☑ 5 │ ☑ ∞ │ ☑ ∞ │ -└────────────────┴──────┴───────┴─────────┴────────────┘ -``` - -#### 4. AdminCouponsPage -**Route:** `/admin/coupons` - -**Funktionen:** -- Coupon-Liste (Code, Typ, Tier, Gültigkeit, Einlösungen) -- Neuer Coupon (3 Typen) -- Auto-Generate Code (Button) -- Redemption-Historie ansehen -- Coupon deaktivieren - -**Coupon-Typen-UI:** -``` -[ Single-Use ] [ Multi-Use Period ] [ Gift ] - -Single-Use: - Code: [FRIEND-GIFT-___] [Generate] - Tier: [Premium ▼] - Dauer: [30] Tage - Max Einlösungen: [1] - -Multi-Use Period: - Code: [WELLPASS-2026-__] [Generate] - Tier: [Premium ▼] - Gültig von: [2026-03-01] - Gültig bis: [2026-03-31] - Max Einlösungen: [∞] -``` - -#### 5. AdminUserRestrictionsPage -**Route:** `/admin/user-restrictions` - -**Funktionen:** -- User auswählen (Dropdown) -- Aktueller Tier anzeigen -- Feature-Liste mit: - - Tier-Limit (readonly, grau) - - Aktuelle Nutzung - - Override-Eingabe (leer = Tier-Standard) - - Save-Button pro Feature -- "Alle Overrides entfernen" Button - -**UI-Konzept:** -``` -User: [Lars Stommer ▼] Tier: selfhosted - -┌─────────────┬────────────┬─────────┬──────────┬────────┐ -│ Feature │ Tier-Limit │ Genutzt │ Override │ Aktion │ -├─────────────┼────────────┼─────────┼──────────┼────────┤ -│ KI-Calls │ ∞ │ 5/∞ │ [100] │ [Save] │ -│ Export │ ∞ │ 2/∞ │ [ ] │ [Save] │ -│ Gewicht │ ∞ │ 50/∞ │ [ ] │ [Save] │ -└─────────────┴────────────┴─────────┴──────────┴────────┘ - -[Alle Overrides entfernen] -``` - -#### 6. SubscriptionPage (User-facing) -**Route:** `/subscription` - -**Funktionen:** -- Aktueller Tier + Quelle -- Tier-Badge mit Icon -- Feature-Liste mit Limits -- Usage-Progress-Bars -- Coupon-Einlösung -- Access-Grant-Historie - -**UI-Konzept:** -``` -┌─────────────────────────────────────┐ -│ Dein Abo: [🟢 PREMIUM] │ -│ Quelle: Coupon (noch 25 Tage) │ -└─────────────────────────────────────┘ - -Features & Limits: -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -KI-Analysen 5/10 -[████████░░] 50% Reset: 1.4.2026 - -Daten-Export 2/5 -[████░░░░░░] 40% Reset: 1.4.2026 - -Gewichtseinträge 120/∞ -━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - -Coupon einlösen: -[_________________] [Einlösen] -``` - ---- - -## Feature-Enforcement-System - -### Status: ⚠️ DEAKTIVIERT (Rollback 20.03.2026) - -**Versuch-Implementation:** Commits 3745ebd, cbad50a, cd4d912, 8415509 -**Rollback:** Commit 4fcde4a - -### Was war geplant - -**Backend:** -```python -# In jedem limitierten Endpoint -def some_endpoint(session = Depends(require_auth)): - pid = session['profile_id'] - - # 1. Feature-Check - access = check_feature_access(pid, 'feature_slug') - if not access['allowed']: - if access['reason'] == 'feature_disabled': - raise HTTPException(403, "Feature nicht verfügbar") - elif access['reason'] == 'limit_exceeded': - raise HTTPException(429, f"Limit erreicht ({access['limit']})") - - # 2. Funktion ausführen - result = do_something() - - # 3. Usage inkrementieren - increment_feature_usage(pid, 'feature_slug') - - return result -``` - -**Frontend:** -```jsx -import { FeatureGate, FeatureBadge } from '../components/FeatureGate' - -function MyComponent() { - return ( - - - - ) -} -``` - -### Was schief ging - -1. **Frontend-Cache-Problem** - - FeatureGate cached Feature-Status - - Änderungen im Admin-Panel nicht sofort sichtbar - - Optimistic rendering zeigte Features kurz an bevor Block - -2. **Backend-Inkonsistenzen** - - Feature-IDs stimmten nicht mit DB überein (data_export vs export_csv) - - Manche Features fehlten komplett (csv_import) - - increment_feature_usage() hatte silent failures - -3. **Analyse-History zerstört** - - DELETE vor INSERT entfernt, aber das war GEWOLLT im Original - - Führte zu "keine Historisierung"-Beschwerde - - War Missverständnis der Original-Logik - -4. **Export-Buttons verschwunden** - - FeatureGate blockierte sofort - - Migration nicht gelaufen → Features existierten nicht - - canExport-Flag wurde überschrieben - -5. **Pipeline-Duplikate** - - Filter ließ 'pipeline' Prompt durch - - Scope-Bug: speicherte als 'gesamt' statt 'pipeline' - -### Lessons Learned - -1. **Nie ohne vollständiges Verständnis refactorn** - - DELETE-Logik war absichtlich (1 Analyse pro Scope) - - User wollte aber History → Requirements unklar - -2. **Migrations müssen atomar laufen** - - Features müssen VOR Enforcement existieren - - Auto-Migration muss zuverlässig sein - - Test-Daten für lokale Entwicklung - -3. **Frontend-Gates brauchen Refresh-Mechanismus** - - WebSocket oder Polling nach Admin-Änderungen - - Oder: "Neu laden" Button prominent anzeigen - - Optimistic rendering ist riskant - -4. **Feature-IDs konsolidieren** - - Ein Feature für Export, nicht drei (csv/json/zip) - - Konsistent zwischen DB, Backend-Code und Frontend - -5. **Inkrementelle Einführung** - - Erst Backend-Checks als Logs (nicht blockierend) - - Dann Frontend-Display (aber funktional) - - Dann Enforcement aktivieren nach Tests - -### Nächste Schritte für Re-Implementation - -1. **Phase 1: Cleanup** - - Feature-Definitionen konsolidieren - - Migration auf Idempotenz prüfen - - Test-Daten-Script erstellen - -2. **Phase 2: Backend Non-Blocking** - - Feature-Checks in Endpoints einbauen - - Aber nur loggen, nicht blockieren - - Monitoring: Wie oft würde blockiert? - -3. **Phase 3: Frontend Display** - - Usage-Counter anzeigen (ohne Gates) - - Admin kann sehen was genutzt wird - - Validierung gegen tatsächliche API-Calls - -4. **Phase 4: Enforcement (opt-in)** - - Per Feature-Flag aktivierbar - - Erst für Admin-Accounts testen - - Dann für Test-User - - Dann Rollout - ---- - -## Roadmap - -### v9c - Fertigstellung (Q2 2026) - -**Prio 1: Feature-Enforcement (Redesign)** -- ✅ Backend-Checks als Log-Only implementieren -- ✅ Frontend Usage-Display ohne Gates -- ✅ Feature-ID Konsolidierung -- ✅ Test-Suite für alle Limits -- ⏳ Opt-in Enforcement per Feature-Flag -- ⏳ Rollout-Plan mit Rollback-Option - -**Prio 2: Registrierung & Trial** -- Self-Registration mit E-Mail-Verifizierung -- Automatischer Trial-Start (14 Tage Premium) -- Trial-Countdown-Banner -- Auto-Downgrade nach Trial-Ende - -**Prio 3: App-Settings UI** -- Admin-Panel für globale Konfiguration -- Trial-Einstellungen (Dauer, Verhalten) -- Registrierungs-Toggle -- E-Mail-Template-Editor - -### v9d - Monetarisierung (Q3 2026) - -**Prio 1: Stripe-Integration** -- Self-Service Upgrade (Premium) -- Subscription-Management -- Webhook-Handler -- Rechnungs-E-Mails - -**Prio 2: Bonus-System** -- Login-Streak-Tracking -- Punkte-System -- Geschenk-Coupons (automatisch) -- Achievements - -**Prio 3: Fitness-Connectoren** -- OAuth2-Framework -- Strava-Connector -- Withings-Connector (Waage) -- Garmin-Connector - -### v9e - Partner & Enterprise (Q4 2026) - -**Prio 1: Partner-Integration** -- Wellpass-Authentifizierung -- Hansefit-Authentifizierung -- Partner-Admin-UI -- Usage-Reporting für Partner - -**Prio 2: Enterprise Features** -- Multi-Tenant-Support -- White-Label-Option -- SAML/SSO -- API-Keys für Drittanbieter - ---- - -## Technische Details - -### Performance-Überlegungen - -**check_feature_access() Caching:** -- Cache auf Request-Level (nicht global) -- Verhindert multiple DB-Calls pro Request -- TTL: 5 Minuten für Frontend-Checks - -**Database Indices:** -```sql --- Kritische Indices für Performance -CREATE INDEX idx_tier_limits_lookup ON tier_limits(tier_id, feature_id); -CREATE INDEX idx_user_restrictions_lookup ON user_feature_restrictions(profile_id, feature_id); -CREATE INDEX idx_feature_usage_lookup ON user_feature_usage(profile_id, feature_id, reset_at); -CREATE INDEX idx_access_grants_active ON access_grants(profile_id, is_active, valid_until DESC); -``` - -### Security-Considerations - -**Coupon-Code-Generation:** -- Kryptografisch sicherer Zufallsgenerator -- 12 Zeichen: XXXXX-YYYYY-ZZ -- Kollisions-Check vor INSERT - -**Access-Grant-Validation:** -- Zeitstempel-Checks auf DB-Ebene (PostgreSQL TIMESTAMP) -- Keine Client-side Validierung für Enforcement -- is_active Flag für sofortigen Widerruf - -**User-Restrictions:** -- Nur Admins können setzen -- Audit-Log (set_by, reason) -- Kann nicht via User-API manipuliert werden - ---- - -## Testing-Strategie - -### Unit-Tests (Backend) - -```python -def test_check_feature_access_user_override(): - # Setup: User mit Free-Tier + Override für ai_calls=100 - profile_id = create_test_profile(tier='free') - set_user_restriction(profile_id, 'ai_calls', 100) - - # Test: Override hat Vorrang vor Tier-Limit - access = check_feature_access(profile_id, 'ai_calls') - assert access['allowed'] == True - assert access['limit'] == 100 - -def test_coupon_stacking_pause_resume(): - # Setup: Single-Use Grant aktiv - profile_id = create_test_profile() - grant1 = create_access_grant(profile_id, 'premium', days=30) - - # Test: Multi-Use Coupon pausiert Single-Use - redeem_coupon(profile_id, 'WELLPASS-CODE') - grant1_reloaded = get_access_grant(grant1.id) - assert grant1_reloaded['is_active'] == False - - # Test: Nach Wellpass-Ablauf wird Single-Use reaktiviert - expire_wellpass_grant(profile_id) - grant1_reloaded = get_access_grant(grant1.id) - assert grant1_reloaded['is_active'] == True -``` - -### Integration-Tests (API) - -```bash -# Scenario: Free User versucht KI-Analyse -curl -X POST /api/insights/run/gesamt \ - -H "X-Auth-Token: $TOKEN" \ - -H "X-Profile-Id: $FREE_USER_ID" - -# Expected: HTTP 403 "KI nicht verfügbar" - -# Scenario: User löst Coupon ein -curl -X POST /api/coupons/redeem \ - -H "X-Auth-Token: $TOKEN" \ - -d '{"code": "FRIEND-GIFT-ABC"}' - -# Expected: HTTP 200 + Access-Grant erstellt - -# Scenario: User mit Premium-Grant kann KI nutzen -curl -X POST /api/insights/run/gesamt \ - -H "X-Auth-Token: $TOKEN" \ - -H "X-Profile-Id: $FREE_USER_ID" - -# Expected: HTTP 200 + Analyse-Ergebnis -``` - ---- - -## Deployment-Notes - -### Migration-Reihenfolge - -```bash -# 1. Backup -pg_dump mitai_prod > backup_before_v9c.sql - -# 2. v9c Schema-Migration -psql mitai_prod < backend/migrations/v9c_subscription_system.sql - -# 3. Feature-Fixes (falls nötig) -psql mitai_prod < backend/migrations/v9c_fix_features.sql - -# 4. Backend neu starten (Auto-Migration läuft) -docker compose restart backend - -# 5. Verifizieren -docker logs mitai-api | grep "v9c Migration" -# Expected: "✅ Migration completed successfully!" -``` - -### Rollback-Plan - -```sql --- Emergency Rollback v9c -DROP TABLE IF EXISTS user_stats CASCADE; -DROP TABLE IF EXISTS user_activity_log CASCADE; -DROP TABLE IF EXISTS coupon_redemptions CASCADE; -DROP TABLE IF EXISTS coupons CASCADE; -DROP TABLE IF EXISTS access_grants CASCADE; -DROP TABLE IF EXISTS user_feature_usage CASCADE; -DROP TABLE IF EXISTS user_feature_restrictions CASCADE; -DROP TABLE IF EXISTS tier_limits CASCADE; -DROP TABLE IF EXISTS features CASCADE; -DROP TABLE IF EXISTS tiers CASCADE; -DROP TABLE IF EXISTS app_settings CASCADE; - --- Profiles-Spalten entfernen -ALTER TABLE profiles - DROP COLUMN tier, - DROP COLUMN tier_locked, - DROP COLUMN trial_ends_at, - DROP COLUMN email_verified, - DROP COLUMN email_verify_token, - DROP COLUMN invited_by, - DROP COLUMN contract_type, - DROP COLUMN contract_valid_until, - DROP COLUMN stripe_customer_id; -``` - ---- - -## Support & Troubleshooting - -### Häufige Probleme - -**Problem: User kann Feature nicht nutzen** -```sql --- Diagnose: Effektiven Tier prüfen -SELECT tier, tier_locked, trial_ends_at FROM profiles WHERE id = 'user-uuid'; - --- Diagnose: Access-Grants prüfen -SELECT * FROM access_grants -WHERE profile_id = 'user-uuid' - AND is_active = true -ORDER BY created DESC; - --- Diagnose: Feature-Limit prüfen -SELECT tl.* FROM tier_limits tl -WHERE tier_id = (SELECT tier FROM profiles WHERE id = 'user-uuid') - AND feature_id = 'ai_calls'; - --- Diagnose: User-Override prüfen -SELECT * FROM user_feature_restrictions -WHERE profile_id = 'user-uuid' AND feature_id = 'ai_calls'; -``` - -**Problem: Coupon lässt sich nicht einlösen** -```sql --- Diagnose: Coupon gültig? -SELECT * FROM coupons WHERE code = 'COUPON-CODE'; - --- Check: Bereits eingelöst? -SELECT * FROM coupon_redemptions -WHERE coupon_id = (SELECT id FROM coupons WHERE code = 'COUPON-CODE') - AND profile_id = 'user-uuid'; - --- Check: Max Einlösungen erreicht? -SELECT c.max_redemptions, COUNT(cr.*) as current_redemptions -FROM coupons c -LEFT JOIN coupon_redemptions cr ON cr.coupon_id = c.id -WHERE c.code = 'COUPON-CODE' -GROUP BY c.id, c.max_redemptions; -``` - -### Admin-Tools - -**User-Tier manuell ändern:** -```sql --- Tier setzen und locken -UPDATE profiles SET tier = 'premium', tier_locked = true WHERE id = 'user-uuid'; - --- Access-Grant manuell erstellen -INSERT INTO access_grants (profile_id, granted_tier, valid_until, source, source_reference) -VALUES ('user-uuid', 'premium', '2026-12-31', 'admin_grant', 'Support-Fall #123'); -``` - -**Feature-Usage zurücksetzen:** -```sql --- Alle Usage für User zurücksetzen -DELETE FROM user_feature_usage WHERE profile_id = 'user-uuid'; - --- Nur bestimmtes Feature zurücksetzen -DELETE FROM user_feature_usage -WHERE profile_id = 'user-uuid' AND feature_id = 'ai_calls'; -``` - ---- - -## Anhang - -### Glossar - -- **Tier**: Subscription-Stufe (free, basic, premium, selfhosted) -- **Feature**: Limitierbare Funktionalität (ai_calls, data_export, etc.) -- **Limit**: Maximale Anzahl Nutzungen (count) oder an/aus (boolean) -- **Access-Grant**: Zeitlich begrenzte Tier-Berechtigung -- **Coupon**: Einlösbarer Code für Tier-Zugang -- **Override**: User-spezifische Abweichung vom Tier-Limit -- **Reset-Period**: Zeitraum nach dem Limit zurückgesetzt wird (never, daily, monthly) - -### Kontakt & Fragen - -- **Repository**: http://192.168.2.144:3000/Lars/mitai-jinkendo -- **Dokumentation**: `/docs/` im Repository -- **Issues**: Gitea Issues oder direkt an Lars - ---- - -**Letzte Aktualisierung:** 20. März 2026 -**Autor:** Lars Stommer + Claude Opus 4.6 -**Version:** v9c-dev +Diese Datei im Projekt-`docs/` dient nur noch als **Einstieg für alte Links**. Inhalt bitte nur dort pflegen. diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..9f39c3f --- /dev/null +++ b/docs/README.md @@ -0,0 +1,58 @@ +# Projekt-Dokumentation (`docs/` im Repository-Root) + +Dieser Ordner ist **immer mit Git versioniert**. Er ergänzt **`.claude/docs/`** (Specs, Landkarten) – siehe [`.claude/README.md`](../.claude/README.md) und [`.claude/rules/DOCUMENTATION.md`](../.claude/rules/DOCUMENTATION.md). + +--- + +## Was gehört wohin? (Kurz) + +| Inhalt | Ort | +|--------|-----| +| Kanonische **fachliche** Spec (WAS) | `.claude/docs/functional/` | +| Kanonische **technische** Spec (WIE) | `.claude/docs/technical/` | +| **Arbeitspapiere**, Status, Analysen | `.claude/docs/working/` | +| **Issue-Epics**, Abnahme-Dokus, große Plan-Markdowns | **`docs/issues/`** (hier) | +| Platzhalter **Governance** (Deploy-Pfade noch `docs/`) | **`docs/PLACEHOLDER_*.md`** (hier) | +| Membership v9c (Inhalt) | **`.claude/docs/technical/MEMBERSHIP_SYSTEM.md`** · Stub: [`MEMBERSHIP_SYSTEM.md`](./MEMBERSHIP_SYSTEM.md) | + +--- + +## `docs/issues/` – Index + +| Datei | +|-------| +| `GUI_IA_ADMIN_NAV_2026-04-05.md` | +| `issue-50-phase-0a-goal-system.md` | +| `issue-50-value-table-refinement.md` | +| `issue-51-prompt-page-assignment.md` | +| `issue-52-blood-pressure-dual-targets.md` | +| `issue-53-phase-0c-multi-layer-architecture.md` | +| `issue-54-dynamic-placeholder-system.md` | +| `issue-55-dynamic-aggregation-methods.md` | +| `PHASE_PLAN_RESPONSIVE_UI.md` | +| `REVIEW_OPEN_ISSUES_2026-04-04.md` | +| `UMSETZUNGSPLAN_TRAININGSPROFILE_SPORTSPEZIFISCH_2026-04-05.md` | + +--- + +## Root in `docs/` (Governance & Verweise) + +| Datei | Rolle | +|-------|--------| +| `PLACEHOLDER_GOVERNANCE.md` | Normen Platzhalter | +| `PLACEHOLDER_METADATA_*.md` | Validierung, Deployment, Summary | +| `MEMBERSHIP_SYSTEM.md` | Nur Verweis auf `.claude/docs/technical/` | + +Arbeitspapier, die früher hier lagen (`STATUS_*`, `GOALS_*`, `NEXT_STEPS_*`, `phase-0c-placeholder-…`), sind nach **`.claude/docs/working/`** verschoben. + +--- + +## Gitea + +Issues: http://192.168.2.144:3000/Lars/mitai-jinkendo/issues + +Themen-Übersicht (lokal): **`.claude/docs/GITEA_ISSUES_INDEX.md`** + +--- + +**Stand:** 2026-04-08 diff --git a/docs/issues/issue-50-phase-0a-goal-system.md b/docs/issues/issue-50-phase-0a-goal-system.md index 11cf137..1ae5ab5 100644 --- a/docs/issues/issue-50-phase-0a-goal-system.md +++ b/docs/issues/issue-50-phase-0a-goal-system.md @@ -133,7 +133,7 @@ listFitnessTests(), createFitnessTest(data) ## Dokumentation -- ✅ `docs/GOALS_SYSTEM_UNIFIED_ANALYSIS.md` (538 Zeilen) +- ✅ `.claude/docs/working/GOALS_SYSTEM_UNIFIED_ANALYSIS.md` (538 Zeilen) - Analyse beider Fachkonzepte (Konzept v2 + GOALS_VITALS.md) - Zwei-Ebenen-Architektur erklärt - 120+ Placeholder-Kategorisierung für Phase 0b @@ -221,7 +221,7 @@ goal_mode = "health" - Score-Berechnungen mit goal_mode - Größter strategischer Impact -**Siehe:** `docs/NEXT_STEPS_2026-03-26.md` für detaillierte Planung +**Siehe:** `.claude/docs/working/NEXT_STEPS_2026-03-26.md` für detaillierte Planung ---