From 582f1258971cfeae6e7a56e64ae940f85c7c711b Mon Sep 17 00:00:00 2001 From: Lars Date: Thu, 26 Mar 2026 14:52:44 +0100 Subject: [PATCH 01/51] docs: comprehensive status update and Gitea sync Updates: - Clarify Gitea issue references (prefix with 'Gitea #') - Mark #28 (AI-Prompts) as CLOSED - Mark #44 (Delete insights) as CLOSED - Update #47 reference (Wertetabelle Optimierung) - Add 'Letzte Updates' section for 26.03.2026 - Document Issue-Management responsibility Gitea Actions: - Closed Issue #28 with completion comment - Closed Issue #44 with fix details - Created Issue #47 (Wertetabelle Optimierung) --- CLAUDE.md | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 04004e4..3b040c5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -76,7 +76,16 @@ frontend/src/ └── technical/ # MEMBERSHIP_SYSTEM.md ``` -## Aktuelle Version: v9e (Issue #28, #47 Complete) 🚀 Ready for Production 26.03.2026 +## Aktuelle Version: v9e (Unified Prompts + Value Table Complete) 🚀 Ready for Production 26.03.2026 + +### Letzte Updates (26.03.2026) 🆕 +- ✅ **circ_summary erweitert:** Best-of-Each Strategie mit Altersangaben (heute, gestern, vor X Tagen) +- ✅ **Stage Outputs Fix:** Debug-Info enthält jetzt alle Stage-Outputs für Experten-Modus +- ✅ **Collapsible JSON:** Stage-Rohdaten als aufklappbare Details im Experten-Modus +- ✅ **Gitea #28 geschlossen:** AI-Prompts Flexibilisierung abgeschlossen +- ✅ **Gitea #44 geschlossen:** Analysen löschen behoben +- ✅ **Gitea #47 erstellt:** Wertetabelle Optimierung (Refinement) +- ✅ **Issue-Management:** Claude Code übernimmt Gitea Issue-Verwaltung ### Implementiert ✅ - Login (E-Mail + bcrypt), Auth-Middleware alle Endpoints, Rate Limiting @@ -208,7 +217,8 @@ frontend/src/ 📚 Details: `.claude/docs/technical/MEMBERSHIP_SYSTEM.md` · `.claude/docs/architecture/FEATURE_ENFORCEMENT.md` -### Issue #28: Unified Prompt System ✅ (Completed 26.03.2026) +### Feature: Unified Prompt System ✅ (Completed 26.03.2026) +> **Gitea:** Issue #28 (AI-Prompts Flexibilisierung) - CLOSED **AI-Prompts Flexibilisierung - Komplett überarbeitet:** @@ -316,14 +326,15 @@ frontend/src/ 📚 Details: `.claude/docs/functional/AI_PROMPTS.md` **Related Gitea Issues:** -- #28: Unified Prompt System - ✅ CLOSED (26.03.2026) -- #43: Enhanced Debug UI - 🔲 OPEN (Future enhancement) -- #44: BUG - Analysen löschen - 🔲 OPEN (High priority) -- #45: KI Prompt-Optimierer - 🔲 OPEN (Future feature) -- #46: KI Prompt-Ersteller - 🔲 OPEN (Future feature) -- #47: Value Table - ✅ CLOSED (26.03.2026) +- Gitea #28: AI-Prompts Flexibilisierung - ✅ CLOSED (26.03.2026) +- Gitea #42, #43: Enhanced Debug UI - 🔲 OPEN (Future enhancement) +- Gitea #44: BUG - Analysen löschen - ✅ CLOSED (26.03.2026) +- Gitea #45: KI Prompt-Optimierer - 🔲 OPEN (Future feature) +- Gitea #46: KI Prompt-Ersteller - 🔲 OPEN (Future feature) +- Gitea #47: Wertetabelle Optimierung - 🔲 OPEN (Refinement, siehe docs/issues/issue-50) -### Issue #47: Comprehensive Value Table ✅ (Completed 26.03.2026) +### Feature: Comprehensive Value Table ✅ (Completed 26.03.2026) +> **Gitea:** Basis-Implementierung abgeschlossen. Issue #47 (Wertetabelle Optimierung) für Refinement offen. **AI-Analyse Transparenz - Vollständige Platzhalter-Anzeige:** -- 2.43.0 From 39db23d41754cf9d6266071f32b137be529a0f20 Mon Sep 17 00:00:00 2001 From: Lars Date: Thu, 26 Mar 2026 14:53:45 +0100 Subject: [PATCH 02/51] docs: comprehensive status report 26.03.2026 Audit Results: - 2 Issues closed: #28 (AI-Prompts), #44 (Delete bug) - 1 Issue created: #47 (Value Table Refinement) - 12 commits today, 3 major features completed - All documentation synchronized with Gitea Next: Testing on dev, then Production deployment --- docs/STATUS_REPORT_2026-03-26.md | 194 +++++++++++++++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 docs/STATUS_REPORT_2026-03-26.md diff --git a/docs/STATUS_REPORT_2026-03-26.md b/docs/STATUS_REPORT_2026-03-26.md new file mode 100644 index 0000000..84eab33 --- /dev/null +++ b/docs/STATUS_REPORT_2026-03-26.md @@ -0,0 +1,194 @@ +# Status Report: 26. März 2026 + +## Audit & Synchronisation + +Vollständige Überprüfung aller Dokumente und Gitea Issues durchgeführt. + +--- + +## ✅ Abgeschlossene Arbeiten + +### 1. Gitea Issue #28: AI-Prompts Flexibilisierung +**Status:** ✅ CLOSED (26.03.2026) + +**Implementierte Features:** +- Unified Prompt System (4 Phasen) +- DB-Migration zu einheitlichem Schema (base + pipeline) +- Universeller Executor (prompt_executor.py) +- Frontend UI Consolidation (UnifiedPromptModal) +- Debug & Development Tools (Test-Button, Export/Import) +- 32 aktive Platzhalter mit Kategorisierung +- `{{placeholder|d}}` Modifier + +**Commits:** 20+ commits (2e0838c bis ae6bd0d) +**Dokumentation:** CLAUDE.md "Feature: Unified Prompt System" + +**Gitea Aktion:** Issue geschlossen mit Completion-Kommentar + +--- + +### 2. Gitea Issue #44: BUG - Analysen löschen +**Status:** ✅ CLOSED (26.03.2026) + +**Fix:** +- Delete-Button in InsightCard hinzugefügt +- `api.deleteInsight(id)` Funktion implementiert +- Auth-Token wird korrekt übergeben +- Liste aktualisiert sich nach Löschen + +**Commit:** c56d2b2 +**Dokumentation:** Gitea-Kommentar mit Code-Beispiel + +**Gitea Aktion:** Issue geschlossen mit Fix-Details + +--- + +### 3. Feature: Comprehensive Value Table +**Status:** ✅ Basis-Implementierung COMPLETE (26.03.2026) + +**Implementierte Features:** +- Metadata Collection System (alle Platzhalter mit Werten) +- Expert Mode Toggle (🔬 Experten-Modus) +- Stage Output Extraction (Einzelwerte aus JSON) +- Category Grouping (PROFIL, KÖRPER, ERNÄHRUNG, etc.) +- Collapsible JSON für Stage-Rohdaten +- Best-of-Each circ_summary mit Altersangaben + +**Commits:** 10+ commits (c0a50de bis 6e651b5, 159fcab) +**Dokumentation:** CLAUDE.md "Feature: Comprehensive Value Table" + +**Gitea:** Basis abgeschlossen, Issue #47 für Refinement erstellt + +--- + +### 4. Placeholder System Enhancements +**Status:** ✅ COMPLETE + +**Fixes & Verbesserungen:** +- `circ_summary`: Alle 8 Umfangspunkte (statt nur 3) +- `circ_summary`: Best-of-Each mit Altersangaben ("heute", "vor 2 Wochen") +- `sleep_avg_quality`: Lowercase stage names fix +- `calculate_age`: PostgreSQL DATE object handling +- Stage outputs in debug info für Value Table + +**Commits:** 7daa2e4, a43a9f1, 3ad1a19, d06d3d8, 159fcab, 6e651b5 + +--- + +## 🔲 Neue/Offene Issues + +### Gitea Issue #47: Wertetabelle Optimierung +**Status:** 🔲 OPEN (neu erstellt 26.03.2026) +**Priority:** Medium +**Aufwand:** 4-6 Stunden + +**Ziel:** Value Table übersichtlicher gestalten + +**Kernpunkte:** +- Normal-Modus: Nur Einzelwerte (~24 statt 32) +- Experten-Modus: Zusätzlich Stage-Rohdaten +- Beschreibungen für alle 32 Platzhalter vervollständigen +- Schema-basierte Beschreibungen für extrahierte Werte + +**Dokumentation:** `docs/issues/issue-50-value-table-refinement.md` + +--- + +## 📊 Gitea Issue Übersicht + +### Geschlossen (heute) +- ✅ #28: AI-Prompts Flexibilisierung +- ✅ #44: BUG - Analysen löschen + +### Neu erstellt (heute) +- 🆕 #47: Wertetabelle Optimierung + +### Weiterhin offen (Backlog) +- 🔲 #25: Ziele-System (Goals) +- 🔲 #26: Charts erweitern +- 🔲 #27: Korrelationen & Insights +- 🔲 #29: Abilities-Matrix UI +- 🔲 #30: Responsive UI +- 🔲 #42, #43: Enhanced Debug UI +- 🔲 #45: KI Prompt-Optimierer +- 🔲 #46: KI Prompt-Ersteller + +### Bereits geschlossen (früher) +- ✅ #24: Quality-Filter für KI-Auswertungen + +--- + +## 📝 Dokumentations-Updates + +### CLAUDE.md +- ✅ "Letzte Updates (26.03.2026)" Sektion hinzugefügt +- ✅ Gitea Issue-Referenzen klargestellt (Prefix "Gitea #") +- ✅ Feature-Sections umbenannt (nicht "Issue #28/47") +- ✅ "Claude Code Verantwortlichkeiten" Sektion +- ✅ Issue-Management via Gitea API dokumentiert + +### docs/issues/ +- ✅ issue-50-value-table-refinement.md erstellt +- ℹ️ Weitere Files in .claude/issues/ (nicht versioniert) + +### Gitea Kommentare +- ✅ Issue #28: Completion-Details mit Features & Commits +- ✅ Issue #44: Fix-Details mit Code-Beispiel + +--- + +## 🔄 Nächste Schritte + +### Empfohlen (Kurzfristig) +1. **Testing auf dev.mitai.jinkendo.de:** + - Value Table im Experten-Modus testen + - Stage-Outputs JSON Anzeige prüfen + - circ_summary mit Altersangaben verifizieren + +2. **Production Deployment:** + - Develop → Main Merge (wenn Tests OK) + - Alle Features (Unified Prompts + Value Table) deployen + +3. **Issue #47 Refinement:** + - Wertetabelle im Normal-Modus optimieren + - Beschreibungen vervollständigen + +### Optional (Mittelfristig) +4. **Weitere offene Issues priorisieren:** + - #25: Ziele-System (Phase 1) + - #27: Korrelationen (Phase 2) + - #30: Responsive UI (Phase 0) + +--- + +## 📈 Metriken + +**Commits (heute):** 12 +**Issues geschlossen:** 2 (#28, #44) +**Issues erstellt:** 1 (#47) +**Dokumentations-Updates:** 3 (CLAUDE.md, STATUS_REPORT, issue-50) +**Gitea Kommentare:** 2 + +**Entwicklungszeit (geschätzt):** ~6-8 Stunden +- circ_summary Enhancement: 1h +- Stage Outputs Fix: 1h +- Value Table Collapsible JSON: 1h +- Issue-Management System: 1h +- Dokumentation & Sync: 2-4h + +--- + +## ✅ Verifizierung + +- [x] Alle Gitea Issues überprüft (47 Issues total) +- [x] Abgeschlossene Arbeiten identifiziert (#28, #44) +- [x] Issues in Gitea geschlossen +- [x] Completion-Kommentare hinzugefügt +- [x] CLAUDE.md aktualisiert +- [x] Status Report erstellt +- [x] Entwicklungs-Dokumentation aktuell + +**Audit durchgeführt von:** Claude Code +**Datum:** 26. März 2026, 14:55 Uhr +**Branch:** develop +**Letzter Commit:** 582f125 -- 2.43.0 From cd2609da7c7e7fc6e5991ae363103a4e67b5e594 Mon Sep 17 00:00:00 2001 From: Lars Date: Thu, 26 Mar 2026 15:12:39 +0100 Subject: [PATCH 03/51] feat: Feature Request #49 - Prompt-Zuordnung zu Verlaufsseiten MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UX Enhancement: Kontextbezogene KI-Analysen Features: - Prompts auf Verlaufsseiten verfügbar machen - Mehrfachauswahl: Prompt auf mehreren Seiten - Inline-Analyse via Modal - Wiederverwendbare PagePrompts Komponente Technisch: - DB: available_on JSONB column - API: GET /api/prompts/for-page/{page_slug} - UI: Page-Auswahl im Prompt-Editor Aufwand: 6-8h, Priority: Medium Gitea: Issue #49 --- .../issues/issue-51-prompt-page-assignment.md | 425 ++++++++++++++++++ 1 file changed, 425 insertions(+) create mode 100644 docs/issues/issue-51-prompt-page-assignment.md diff --git a/docs/issues/issue-51-prompt-page-assignment.md b/docs/issues/issue-51-prompt-page-assignment.md new file mode 100644 index 0000000..4fc7c72 --- /dev/null +++ b/docs/issues/issue-51-prompt-page-assignment.md @@ -0,0 +1,425 @@ +# Feature: Prompt-Zuordnung zu Verlaufsseiten + +**Labels:** feature, ux, enhancement +**Priority:** Medium (Phase 1-2) +**Related:** Issue #28 (Unified Prompt System - Complete) + +## Beschreibung +KI-Prompts sollen flexibel auf verschiedenen Verlaufsseiten verfügbar gemacht werden können. Jeder Prompt kann auf mehreren Seiten gleichzeitig angeboten werden (Mehrfachauswahl). + +## Problem (aktueller Stand) + +**Aktuell:** +- Prompts sind nur über die zentrale Analyse-Seite (📊 Analyse) verfügbar +- Kein kontextbezogener Zugriff auf relevante Analysen +- User muss immer zur Analyse-Seite navigieren + +**Beispiel-Szenario:** +``` +User ist auf: Gewicht → Verlauf +Will: Gewichtstrend analysieren +Muss: Zur Analyse-Seite → Prompt auswählen → Zurück +``` + +**Wünschenswert:** +``` +User ist auf: Gewicht → Verlauf +Sieht: "🤖 KI-Analyse" Button mit relevanten Prompts +Kann: Direkt "Gewichtstrend-Analyse" starten +``` + +## Gewünschtes Verhalten + +### 1. Prompt-Konfiguration erweitern + +**Admin → KI-Prompts → Prompt bearbeiten:** + +``` +┌─────────────────────────────────────────────┐ +│ Prompt bearbeiten: Gewichtstrend-Analyse │ +├─────────────────────────────────────────────┤ +│ Name: Gewichtstrend-Analyse │ +│ Slug: weight_trend │ +│ Type: Pipeline │ +│ │ +│ 📍 Verfügbar auf Seiten: │ +│ ┌─────────────────────────────────────────┐ │ +│ │ ☑ Analyse (Hauptseite) │ │ +│ │ ☑ Gewicht → Verlauf │ │ +│ │ ☐ Umfänge → Verlauf │ │ +│ │ ☐ Caliper → Verlauf │ │ +│ │ ☐ Aktivität → Verlauf │ │ +│ │ ☐ Ernährung → Verlauf │ │ +│ │ ☐ Schlaf → Verlauf │ │ +│ │ ☐ Vitalwerte → Verlauf │ │ +│ │ ☐ Dashboard │ │ +│ └─────────────────────────────────────────┘ │ +│ │ +│ [Speichern] [Abbrechen] │ +└─────────────────────────────────────────────┘ +``` + +**Mehrfachauswahl:** +- Ein Prompt kann auf mehreren Seiten gleichzeitig verfügbar sein +- Mindestens eine Seite muss ausgewählt sein +- Default: "Analyse (Hauptseite)" ist immer vorausgewählt + +### 2. UI auf Verlaufsseiten + +**Gewicht → Verlauf:** + +``` +┌─────────────────────────────────────────────┐ +│ 📊 Gewicht - Verlauf │ +│ [Filter: 7d] [30d] [90d] [Alle] │ +├─────────────────────────────────────────────┤ +│ │ +│ [Chart: Gewichtsverlauf] │ +│ │ +├─────────────────────────────────────────────┤ +│ 🤖 KI-Analysen │ +│ ┌─────────────────────────────────────────┐ │ +│ │ Gewichtstrend-Analyse [▶ Starten]│ │ +│ │ Körperkomposition-Check [▶ Starten]│ │ +│ └─────────────────────────────────────────┘ │ +│ │ +│ [Einträge-Tabelle...] │ +└─────────────────────────────────────────────┘ +``` + +**Features:** +- Kompaktes Widget unterhalb des Charts +- Nur relevante Prompts werden angezeigt +- Button startet Analyse inline (Modal oder expandierend) +- Ergebnis wird direkt auf der Seite angezeigt + +### 3. Inline-Analyse Anzeige + +**Option A: Modal (empfohlen für MVP):** +``` +Click auf [▶ Starten] + ↓ +┌─────────────────────────────────────────────┐ +│ ✕ Gewichtstrend-Analyse │ +├─────────────────────────────────────────────┤ +│ [Spinner] Analysiere Gewichtsdaten... │ +│ │ +│ [Nach Abschluss:] │ +│ Analyse-Text... │ +│ │ +│ 📊 Verwendete Werte (12) [🔬 Experten] │ +│ [Value Table...] │ +│ │ +│ [Schließen] [In Verlauf speichern] │ +└─────────────────────────────────────────────┘ +``` + +**Option B: Expandierend (später):** +``` +Click auf [▶ Starten] + ↓ +Widget expandiert nach unten +Zeigt Analyse-Ergebnis inline +[△ Einklappen] Button +``` + +## Technische Umsetzung + +### 1. Datenbankschema erweitern + +**Tabelle: `ai_prompts`** +```sql +ALTER TABLE ai_prompts ADD COLUMN available_on JSONB DEFAULT '["analysis"]'; + +COMMENT ON COLUMN ai_prompts.available_on IS + 'Array of page slugs where prompt is available. + Values: analysis, weight_history, circ_history, caliper_history, + activity_history, nutrition_history, sleep_history, vitals_history, dashboard'; + +-- Migration 022 +``` + +**Beispiel-Werte:** +```json +{ + "slug": "weight_trend", + "name": "Gewichtstrend-Analyse", + "available_on": ["analysis", "weight_history"] +} + +{ + "slug": "pipeline_master", + "name": "Vollständige Analyse", + "available_on": ["analysis", "dashboard"] +} + +{ + "slug": "nutrition_check", + "name": "Ernährungs-Check", + "available_on": ["analysis", "nutrition_history", "activity_history"] +} +``` + +### 2. Backend API erweitern + +**Neuer Endpoint: GET /api/prompts/for-page/{page_slug}** + +```python +@router.get("/for-page/{page_slug}") +def get_prompts_for_page(page_slug: str, session: dict = Depends(require_auth)): + """Get all prompts available for a specific page. + + Args: + page_slug: Page identifier (e.g., 'weight_history', 'analysis') + + Returns: + List of prompts with available_on containing page_slug + """ + with get_db() as conn: + cur = get_cursor(conn) + cur.execute( + """SELECT id, name, slug, type, description, available_on + FROM ai_prompts + WHERE available_on @> %s + ORDER BY name""", + (json.dumps([page_slug]),) + ) + return [r2d(row) for row in cur.fetchall()] +``` + +**Beispiel-Aufruf:** +```javascript +// In WeightPage.jsx +const prompts = await api.getPromptsForPage('weight_history') +// Returns: [{slug: 'weight_trend', name: 'Gewichtstrend-Analyse', ...}] +``` + +**Prompt CRUD erweitern:** +```python +@router.put("/unified/{id}") +def update_unified_prompt(id: str, p: UnifiedPromptCreate, session=Depends(require_admin)): + # ... existing code ... + cur.execute( + """UPDATE ai_prompts + SET name=%s, slug=%s, template=%s, ..., available_on=%s + WHERE id=%s""", + (..., json.dumps(p.available_on), id) + ) +``` + +### 3. Frontend: Prompt-Editor erweitern + +**UnifiedPromptModal.jsx:** + +```javascript +const PAGE_OPTIONS = [ + { value: 'analysis', label: '📊 Analyse (Hauptseite)', default: true }, + { value: 'weight_history', label: '⚖️ Gewicht → Verlauf' }, + { value: 'circ_history', label: '📏 Umfänge → Verlauf' }, + { value: 'caliper_history', label: '📐 Caliper → Verlauf' }, + { value: 'activity_history', label: '🏃 Aktivität → Verlauf' }, + { value: 'nutrition_history', label: '🍎 Ernährung → Verlauf' }, + { value: 'sleep_history', label: '😴 Schlaf → Verlauf' }, + { value: 'vitals_history', label: '❤️ Vitalwerte → Verlauf' }, + { value: 'dashboard', label: '🏠 Dashboard' }, +] + +// In form: +
+ +
+ {PAGE_OPTIONS.map(opt => ( + + ))} +
+
+``` + +### 4. Frontend: Verlaufsseiten erweitern + +**WeightPage.jsx (Beispiel):** + +```javascript +function WeightPage() { + const [prompts, setPrompts] = useState([]) + const [runningAnalysis, setRunningAnalysis] = useState(null) + const [analysisResult, setAnalysisResult] = useState(null) + + useEffect(() => { + loadPrompts() + }, []) + + const loadPrompts = async () => { + try { + const data = await api.getPromptsForPage('weight_history') + setPrompts(data) + } catch(e) { + console.error('Failed to load prompts:', e) + } + } + + const runAnalysis = async (promptSlug) => { + setRunningAnalysis(promptSlug) + try { + const result = await api.executePrompt(promptSlug, {save: true}) + setAnalysisResult(result) + } catch(e) { + setError(e.message) + } finally { + setRunningAnalysis(null) + } + } + + return ( +
+

Gewicht - Verlauf

+ + {/* Chart */} + + + {/* AI Prompts Widget */} + {prompts.length > 0 && ( +
+

🤖 KI-Analysen

+ {prompts.map(p => ( + + ))} +
+ )} + + {/* Analysis Result Modal */} + {analysisResult && ( + setAnalysisResult(null)} + /> + )} + + {/* Data Table */} + +
+ ) +} +``` + +**Wiederverwendbare Komponente:** +```javascript +// components/PagePrompts.jsx +export function PagePrompts({ pageSlug }) { + // ... logic ... + return ( +
+

🤖 KI-Analysen

+ {prompts.map(p => ( + + ))} +
+ ) +} + +// Usage in any page: + +``` + +## Akzeptanzkriterien + +- [ ] DB-Migration: `available_on` JSONB column in ai_prompts +- [ ] Backend: `GET /api/prompts/for-page/{page_slug}` Endpoint +- [ ] Backend: CRUD operations unterstützen available_on +- [ ] Frontend: Prompt-Editor zeigt Page-Auswahl (Mehrfachauswahl) +- [ ] Frontend: Mindestens 1 Page muss ausgewählt sein +- [ ] Frontend: Wiederverwendbare PagePrompts Komponente +- [ ] Frontend: Integration in mind. 2 Verlaufsseiten (Weight, Nutrition) +- [ ] UI: Inline-Analyse via Modal mit Value Table +- [ ] UI: Loading-State während Analyse läuft +- [ ] Dokumentation: API-Dokumentation aktualisiert + +## Abschätzung + +**Aufwand:** 6-8 Stunden +- 1h: DB-Migration + Backend Endpoint +- 2h: Prompt-Editor erweitern (Page-Auswahl) +- 2h: PagePrompts Komponente + Modal +- 2h: Integration in Verlaufsseiten (2-3 Seiten) +- 1h: Testing + Feintuning + +**Priorität:** Medium +- Verbessert UX erheblich (kontextbezogene Analysen) +- Nutzt bestehendes Prompt-System (Issue #28) +- Relativ einfach zu implementieren (kein neues Backend-System) + +## Use Cases + +### UC1: Gewichtstrend auf Gewicht-Seite +``` +User: Navigiert zu "Gewicht → Verlauf" +System: Zeigt Gewichts-Chart + verfügbare Prompts +User: Click "Gewichtstrend-Analyse ▶" +System: Startet Analyse, zeigt Modal mit Ergebnis +User: Click "In Verlauf speichern" +System: Speichert in ai_insights, zeigt in Analyse-Verlauf +``` + +### UC2: Ernährungs-Check auf Ernährung-Seite +``` +User: Navigiert zu "Ernährung → Verlauf" +System: Zeigt Ernährungs-Charts + verfügbare Prompts +User: Click "Ernährungs-Check ▶" +System: Analysiert Makros + Kalorien der letzten 7 Tage +User: Sieht Empfehlungen direkt auf Ernährungs-Seite +``` + +### UC3: Multi-Page Prompt (z.B. "Vollständige Analyse") +``` +Admin: Konfiguriert "Vollständige Analyse" + - Verfügbar auf: [Analyse, Dashboard, Gewicht, Ernährung] +User: Sieht denselben Prompt auf 4 verschiedenen Seiten +User: Kann von überall die gleiche umfassende Analyse starten +``` + +## Notizen + +- **Rückwärtskompatibilität:** Bestehende Prompts ohne `available_on` → Default `["analysis"]` +- **Migration:** Alle existierenden Prompts bekommen `["analysis"]` gesetzt +- **Permissions:** Prompts respektieren weiterhin Feature-Enforcement (ai_calls) +- **Caching:** Prompts könnten gecacht werden (selten geändert) +- **Mobile:** PagePrompts sollte auch auf Mobile gut aussehen (Stack-Layout) +- **Performance:** Lazy-Loading der Prompts (nur laden wenn Seite besucht) + +## Erweiterungen (Future) + +- **Conditional Display:** Prompts nur anzeigen wenn Daten vorhanden + - Beispiel: "Gewichtstrend" nur wenn min. 3 Gewichts-Einträge +- **Quick Actions:** Direkt-Buttons im Chart (ohne separates Widget) +- **Page-spezifische Variablen:** Automatisch aktuelle Filter übergeben + - Beispiel: Wenn "30d" Filter aktiv → `{{timeframe}}` = 30 +- **Prompt-Templates pro Page:** Vordefinierte Vorlagen für jede Seite +- **Favoriten:** User kann Prompts auf Seiten favorisieren (User-spezifisch) + +## Verwandte Issues + +- #28: Unified Prompt System (Basis für dieses Feature) +- #45: KI Prompt-Optimierer (könnte Page-Kontext nutzen) +- #46: KI Prompt-Ersteller (sollte Page-Auswahl anbieten) -- 2.43.0 From 8398368ed717219c50ef6fa1b6d02a8d418954d4 Mon Sep 17 00:00:00 2001 From: Lars Date: Thu, 26 Mar 2026 15:26:12 +0100 Subject: [PATCH 04/51] docs: comprehensive functional concept analysis MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Analysis Results: - 84 new placeholders needed from Fachkonzept - Issues #26 & #27 too narrow (complementary, not conflicting) - Recommend 4-phase approach: Placeholders → Charts → Rules → KI - Transform: Data Collector → Active Coach Key Findings: - Fachkonzept defines 3 levels (Deskriptiv, Diagnostisch, Präskriptiv) - 18 dedicated charts (K1-K5, E1-E5, A1-A8, C1-C6) - Goal-mode dependent interpretation - Lag-based correlations mandatory - Confidence & data quality essential Recommended Actions: - Create Issues #52-55 (Baseline, Scores, Correlations, Metrics) - Expand #26 & #27 based on Fachkonzept - Start Phase 0: Implement 84 placeholders File: docs/KONZEPT_ANALYSE_2026-03-26.md (385 lines) --- docs/KONZEPT_ANALYSE_2026-03-26.md | 458 +++++++++++++++++++++++++++++ 1 file changed, 458 insertions(+) create mode 100644 docs/KONZEPT_ANALYSE_2026-03-26.md diff --git a/docs/KONZEPT_ANALYSE_2026-03-26.md b/docs/KONZEPT_ANALYSE_2026-03-26.md new file mode 100644 index 0000000..23b5798 --- /dev/null +++ b/docs/KONZEPT_ANALYSE_2026-03-26.md @@ -0,0 +1,458 @@ +# Konzept-Analyse: Fachkonzept vs. Gitea Issues + +**Datum:** 26. März 2026 +**Analyst:** Claude Code +**Basis:** `.claude/docs/functional/mitai_jinkendo_konzept_diagramme_auswertungen_v2.md` +**Geprüfte Issues:** #26, #27, alle offenen + +--- + +## 1. Executive Summary + +### Kernerkenntnis +Das Fachkonzept ist **wesentlich umfassender** als die aktuellen Gitea Issues #26 und #27. Es definiert ein 3-stufiges Analyse-System (Deskriptiv → Diagnostisch → Präskriptiv), das weit über einfache Charts und Korrelationen hinausgeht. + +### Strategische Empfehlung +**NICHT** Issues #26 und #27 einzeln implementieren, sondern: +1. **Neu-Strukturierung:** Konzept-basierte Phasen-Issues erstellen +2. **Platzhalter-First:** Erst Berechnungs-Platzhalter implementieren +3. **Dann Visualisierung:** Charts nutzen die Platzhalter +4. **Dann KI-Integration:** KI nutzt regelbasierte Scores + Rohdaten + +--- + +## 2. Analyse: Issue #26 vs. Fachkonzept + +### Issue #26: Charts & Visualisierungen erweitern +**Status:** OPEN +**Priority:** Medium-High +**Aufwand:** 8-10h + +**Definierte Charts:** +- Gewicht-Trends (Line-Chart + Trendlinie) +- Umfänge-Verlauf (Multi-Line) +- Vitalwerte-Trends (RHR, HRV, BP) +- Schlaf-Analyse (Dauer, Phasen) +- Ernährungs-Charts (Kalorien, Makros) + +### Fachkonzept: Diagrammkatalog + +**KÖRPER (K1-K5):** +- K1: Gewichtstrend + Trendkanal + Zielprojektion + - 7d Rolling Median, 28d/90d Trend-Slope + - Prozentuale Zielannäherung + - Regelbasierte Hinweise (zu schnell/langsam) +- K2: Körperzusammensetzung (Gewicht/FM/LBM) + - FM = Gewicht × BF%, LBM = Gewicht × (1-BF%) + - 28d/90d Änderung von FM und LBM +- K3: Umfangs-Panel (8 Mini-Charts) + - Links-Rechts Asymmetrie + - Taille/Hüfte, Taille/Körpergröße +- K4: Rekompositions-Detektor (Quadranten) +- K5: Body Progress Score (0-100) + +**ERNÄHRUNG (E1-E5):** +- E1: Energieaufnahme vs. Verbrauch vs. Gewichtstrend +- E2: Protein adequacy (g/Tag, g/kg, g/kg LBM) +- E3: Makroverteilung + Wochenkonsistenz +- E4: Ernährungs-Adhärenz-Score (0-100) +- E5: Energieverfügbarkeits-Warnung + +**AKTIVITÄT (A1-A8):** +- A1: Trainingsvolumen pro Woche +- A2: Intensitätsverteilung / Zonenbild +- A3: Trainingsqualitäts-Matrix +- A4: Fähigkeiten-Balance / Ability Radar +- A5: Load-Monitoring (interne Last, Monotony, Strain) +- A6: Aktivitäts-Goal-Alignment-Score (0-100) +- A7: Ruhetags-/Recovery-Compliance +- A8: VO2max-Entwicklung + +### Bewertung +❌ **Issue #26 ist zu eng gefasst** +- Fokus nur auf Basis-Visualisierung +- Keine Scores, keine Baselines, keine Confidence +- Keine regelbasierten Hinweise +- Keine Ziel-Abhängigkeit + +✅ **Fachkonzept bietet:** +- 18 dedizierte Charts (K1-K5, E1-E5, A1-A8) +- Scores als eigenständige Visualisierungen +- Regelbasierte Aussagen ohne KI +- Ziel-Modi Steuerung + +--- + +## 3. Analyse: Issue #27 vs. Fachkonzept + +### Issue #27: Korrelationen & Insights erweitern +**Status:** OPEN +**Priority:** High +**Aufwand:** 6-8h + +**Definierte Korrelationen:** +- Schlaf ↔ Erholung (Schlafdauer → RHR, Qualität → HRV) +- Training ↔ Vitalwerte (Load → RHR-Anstieg, HRV-Abfall) +- Ernährung ↔ Performance (Defizit → Intensität) +- Blutdruck ↔ Lifestyle (Stress → BP, Training → BP) +- Multi-Faktor Analyse (KI-Insights) + +### Fachkonzept: Korrelationen (C1-C6) + +**KORRELATIONEN (C1-C6):** +- C1: Energie-Balance vs. Gewichtsveränderung (lagged) + - Lags: 0, 3, 7, 10, 14 Tage + - Bestes Lag ermitteln, Effektstärke, Confidence +- C2: Protein adequacy vs. LBM-Trend + - 28d Fenstervergleich, Training als Moderator +- C3: Trainingslast vs. HRV/RHR (1-3 Tage verzögert) + - Duale Lag-Auswertung, individuelle Ermüdungsreaktion +- C4: Schlafdauer + Schlafregularität vs. Recovery + - Bubble-Chart, Sleep Regularity Index +- C5: Blutdruck-Kontextmatrix (Kontext-abhängig) + - Messkontext, Schlaf Vor-Nacht, Training +- C6: Plateau-Detektor (Ereignis-Karte) + - Ziel-spezifische Plateau-Definitionen + +### Zusätzlich: Lag-Analyse Prinzipien + +**Zwingend im Fachkonzept:** +- **NIE nur lag=0 prüfen** +- Kalorienbilanz → Gewicht: 2-14 Tage Verzögerung +- Protein/Krafttraining → LBM: 2-6 Wochen Verzögerung +- Trainingslast → HRV/RHR: 1-3 Tage Verzögerung +- Schlafdefizit → Recovery: 1-3 Tage Verzögerung + +**Mindestdatenmenge:** +- Korrelationen: mind. 21 gepaarte Tageswerte +- Lag-basiert: mind. 28 gepaarte Tage +- Confidence-Klassen (hoch/mittel/niedrig/nicht auswertbar) + +### Bewertung +❌ **Issue #27 ist zu oberflächlich** +- Keine Lag-Analyse +- Keine Confidence-Bewertung +- Keine Mindestdatenmenge-Checks +- Keine Ziel-Abhängigkeit + +✅ **Fachkonzept bietet:** +- 6 dedizierte Korrelations-Charts mit Lag-Analyse +- Explizite Confidence-Bewertung +- Medizinischer Sicherheitsmodus +- Plateau-Detektion (regelbasiert) + +--- + +## 4. Konflikt-Analyse + +### Gibt es Widersprüche zwischen #26 und #27? +**NEIN** – Sie sind komplementär: +- #26: Deskriptive Ebene (Charts) +- #27: Diagnostische Ebene (Korrelationen) + +### Aber: Beide sind zu isoliert +Das Fachkonzept zeigt: **Charts und Korrelationen müssen verzahnt sein** + +**Beispiel:** +``` +Fachkonzept C1: Energie-Balance vs. Gewichtsveränderung +├─ Visualisierung: Lag-Heatmap (diagnostisch) +├─ Berechnung: Cross-Correlation (0, 3, 7, 10, 14 Tage Lags) +├─ Input-Daten: Tägliche Kalorienbilanz (E-Chart) +├─ Input-Daten: 7d Gewichtsänderung (K-Chart) +└─ Regelbasierte Aussage: "Energiebilanz zeigt sich bei dir nach ~7 Tagen im Gewicht" +``` + +**Fazit:** Charts (K, E, A) liefern Basis-Daten für Korrelationen (C) + +--- + +## 5. Neue Platzhalter aus Fachkonzept + +### 5.1 KÖRPER (18 neue Platzhalter) + +**Gewicht & Trends:** +```python +{{weight_7d_rolling_median}} # 7-Tage gleitender Median +{{weight_28d_trend_slope}} # 28-Tage Trend-Steigung (kg/Tag) +{{weight_90d_trend_slope}} # 90-Tage Trend-Steigung +{{weight_goal_progress_pct}} # Prozentuale Zielannäherung +{{weight_projection_days}} # Geschätzte Tage bis Zielgewicht +{{weight_loss_rate_weekly}} # kg/Woche (28d Mittel) +``` + +**Körperzusammensetzung:** +```python +{{fm_current}} # Fettmasse aktuell (kg) +{{lbm_current}} # Magermasse aktuell (kg) +{{fm_28d_delta}} # FM Änderung 28 Tage (kg) +{{lbm_28d_delta}} # LBM Änderung 28 Tage (kg) +{{fm_90d_delta}} # FM Änderung 90 Tage +{{lbm_90d_delta}} # LBM Änderung 90 Tage +{{recomposition_score}} # 0-100 (FM↓ + LBM↑ = ideal) +``` + +**Umfänge:** +```python +{{waist_to_hip_ratio}} # Taille/Hüfte Verhältnis +{{waist_to_height_ratio}} # Taille/Körpergröße (Gesundheitsmarker) +{{arm_asymmetry_pct}} # Links-Rechts Differenz % +{{leg_asymmetry_pct}} # Oberschenkel L-R Differenz +{{waist_28d_delta}} # Taillenumfang Änderung 28d +``` + +**Body Progress Score:** +```python +{{body_progress_score}} # 0-100 (zielabhängig gewichtet) +``` + +### 5.2 ERNÄHRUNG (15 neue Platzhalter) + +**Energie & Bilanz:** +```python +{{kcal_7d_avg}} # Bereits vorhanden? Prüfen +{{kcal_28d_avg}} # 28-Tage Durchschnitt +{{kcal_estimated_tdee}} # Geschätzter Gesamtumsatz +{{kcal_balance_7d_avg}} # Durchschnittliche Bilanz 7d +{{kcal_balance_28d_avg}} # Durchschnittliche Bilanz 28d +{{energy_availability_status}} # "adequate" | "low" | "critical" +``` + +**Protein:** +```python +{{protein_g_per_kg}} # Protein g/kg Körpergewicht +{{protein_g_per_kg_lbm}} # Protein g/kg Magermasse +{{protein_adequacy_score}} # 0-100 (Ziel: 1.6-2.2 g/kg) +``` + +**Makros & Adhärenz:** +```python +{{carb_pct_7d_avg}} # % der Gesamtkalorien +{{fat_pct_7d_avg}} # % der Gesamtkalorien +{{macro_consistency_score}} # 0-100 (Regelmäßigkeit) +{{nutrition_adherence_score}} # 0-100 (Gesamtscore) +{{nutrition_days_7d}} # Erfasste Tage letzte 7d +{{nutrition_days_28d}} # Erfasste Tage letzte 28d +``` + +### 5.3 AKTIVITÄT (25 neue Platzhalter) + +**Volumen:** +```python +{{activity_volume_7d_min}} # Gesamtminuten 7 Tage +{{activity_volume_28d_min}} # Gesamtminuten 28 Tage +{{activity_frequency_7d}} # Anzahl Sessions 7d +{{activity_frequency_28d}} # Anzahl Sessions 28d +{{activity_avg_duration_28d}} # Durchschn. Dauer pro Session +``` + +**Intensität:** +```python +{{activity_z1_pct}} # % Zeit in Zone 1 (7d) +{{activity_z2_pct}} # % Zeit in Zone 2 +{{activity_z3_pct}} # % Zeit in Zone 3 +{{activity_z4_pct}} # % Zeit in Zone 4 +{{activity_z5_pct}} # % Zeit in Zone 5 +{{activity_polarization_index}} # Polarisierung (Z1+Z2 vs Z4+Z5) +``` + +**Qualität & Load:** +```python +{{activity_quality_avg_28d}} # Durchschn. Quality-Score +{{activity_load_7d}} # Interne Last (7d Summe) +{{activity_load_28d}} # Interne Last (28d Summe) +{{activity_monotony_28d}} # Last-Variabilität +{{activity_strain_28d}} # Load × Monotony +{{activity_acwr}} # Acute:Chronic Workload Ratio +``` + +**Fähigkeiten:** +```python +{{ability_strength_score}} # 0-100 (aus Training Types) +{{ability_endurance_score}} # 0-100 +{{ability_mobility_score}} # 0-100 +{{ability_skills_score}} # 0-100 +{{ability_mindfulness_score}} # 0-100 +{{ability_balance_score}} # 0-100 (wie ausgewogen?) +``` + +**Goal Alignment:** +```python +{{activity_goal_alignment_score}} # 0-100 (zielabhängig) +{{rest_days_compliance}} # 0-100 (geplant vs. tatsächlich) +``` + +### 5.4 RECOVERY & GESUNDHEIT (12 neue Platzhalter) + +**Baselines:** +```python +{{rhr_7d_baseline}} # 7-Tage Baseline Ruhepuls +{{rhr_28d_baseline}} # 28-Tage Baseline +{{hrv_7d_baseline}} # 7-Tage Baseline HRV +{{hrv_28d_baseline}} # 28-Tage Baseline +``` + +**Deltas & Trends:** +```python +{{rhr_vs_baseline_7d}} # Abweichung von Baseline (bpm) +{{hrv_vs_baseline_7d}} # Abweichung von Baseline (ms) +{{vo2max_trend_28d}} # VO2max Entwicklung +``` + +**Scores:** +```python +{{recovery_score}} # 0-100 (HRV, RHR, Schlaf) +{{recovery_score_confidence}} # 0-100 (Datenqualität) +{{sleep_regularity_index}} # Schlafregelmäßigkeit +{{sleep_debt_hours}} # Akkumulierte Schlafschuld +{{health_risk_score}} # 0-100 (Blutdruck, etc.) +``` + +### 5.5 KORRELATIONEN (8 neue Platzhalter) + +```python +{{corr_energy_weight_lag}} # Bestes Lag Energie→Gewicht (Tage) +{{corr_energy_weight_r}} # Korrelationskoeffizient +{{corr_protein_lbm_r}} # Protein ↔ LBM Korrelation +{{corr_load_hrv_lag}} # Bestes Lag Load→HRV +{{corr_load_hrv_r}} # Korrelation +{{corr_sleep_rhr_r}} # Schlaf ↔ RHR Korrelation +{{plateau_detected}} # true|false (regelbasiert) +{{plateau_type}} # "weight_loss" | "strength" | etc. +``` + +### 5.6 META-PLATZHALTER (6 neue) + +```python +{{goal_mode}} # "weight_loss" | "strength" | etc. +{{training_age_weeks}} # Trainingserfahrung +{{data_quality_score}} # 0-100 (Gesamtdatenqualität) +{{measurement_consistency}} # 0-100 (Messzeit-Konsistenz) +{{analysis_confidence}} # "high" | "medium" | "low" +{{analysis_timeframe}} # "7d" | "28d" | "90d" +``` + +--- + +## 6. Gesamt-Übersicht: Neue Platzhalter + +| Kategorie | Anzahl | Beispiele | +|-----------|--------|-----------| +| KÖRPER | 18 | weight_28d_trend_slope, fm_28d_delta, recomposition_score | +| ERNÄHRUNG | 15 | protein_g_per_kg_lbm, nutrition_adherence_score, energy_availability_status | +| AKTIVITÄT | 25 | activity_quality_avg_28d, activity_strain_28d, ability_balance_score | +| RECOVERY | 12 | recovery_score, sleep_regularity_index, sleep_debt_hours | +| KORRELATIONEN | 8 | corr_energy_weight_lag, plateau_detected, corr_load_hrv_r | +| META | 6 | goal_mode, data_quality_score, analysis_confidence | +| **GESAMT** | **84** | **Neue Platzhalter aus Fachkonzept** | + +--- + +## 7. Strategische Roadmap-Empfehlung + +### Phase 0: Fundament (JETZT) +**Ziel:** Berechnungs-Platzhalter implementieren +**Aufwand:** 16-20h +**Deliverables:** +- 84 neue Platzhalter in `placeholder_resolver.py` +- Baseline-Berechnungen (7d, 28d, 90d) +- Score-Algorithmen (Body Progress, Nutrition Adherence, Activity Goal Alignment, Recovery) +- Lag-Korrelations-Funktionen +- Confidence-Berechnung + +**Issues zu erstellen:** +- #52: Baseline & Trend Calculations (Körper, Ernährung, Aktivität) +- #53: Score Algorithms (4 Haupt-Scores) +- #54: Correlation & Lag Analysis +- #55: Confidence & Data Quality Metrics + +### Phase 1: Visualisierung (DANN) +**Ziel:** Charts nutzen die neuen Platzhalter +**Aufwand:** 12-16h +**Deliverables:** +- K1-K5 Charts (Körper) +- E1-E5 Charts (Ernährung) +- A1-A8 Charts (Aktivität) +- C1-C6 Charts (Korrelationen) + +**Issues zu konsolidieren:** +- #26 erweitern zu "Comprehensive Chart System (K, E, A, C)" +- #27 erweitern zu "Correlation & Lag Analysis Charts" + +### Phase 2: Regelbasierte Insights (DANACH) +**Ziel:** System wird Coach (nicht nur Datensammler) +**Aufwand:** 8-12h +**Deliverables:** +- Regelbasierte Hinweise ohne KI +- Plateau-Detektion +- Ziel-abhängige Interpretationen +- Warnungen (Gesundheit, Übertraining, Energieverfügbarkeit) + +**Neue Issues:** +- #56: Rule-Based Recommendations Engine +- #57: Goal-Mode System & Interpretation +- #58: Health & Safety Warnings + +### Phase 3: KI-Integration (SPÄTER) +**Ziel:** KI nutzt Scores + Rohdaten + Regeln +**Aufwand:** 6-8h +**Deliverables:** +- KI-Prompts nutzen neue Platzhalter +- Contextual AI Analysis (nutzt goal_mode) +- Multi-Faktor Insights + +--- + +## 8. Aktions-Empfehlungen + +### SOFORT (heute) +1. ✅ **Issues #26 und #27 NICHT einzeln implementieren** +2. ✅ **Neues Issue #52 erstellen:** Baseline & Trend Calculations +3. ✅ **Neues Issue #53 erstellen:** Score Algorithms +4. ✅ **Issue #26 umbennen/erweitern:** "Comprehensive Chart System (based on Fachkonzept)" +5. ✅ **Issue #27 umbennen/erweitern:** "Correlation & Lag Analysis (based on Fachkonzept)" + +### DIESE WOCHE +6. ✅ **Implementierung starten:** Phase 0 - Platzhalter +7. ✅ **Dokumentation:** Mapping Fachkonzept → Code +8. ✅ **KI-Prompts vorbereiten:** Nutzen neue Platzhalter + +### NÄCHSTE WOCHE +9. ✅ **Implementierung:** Phase 1 - Charts +10. ✅ **Testing:** Alle Scores & Berechnungen +11. ✅ **Production:** Deployment vorbereiten + +--- + +## 9. Zusammenfassung: Transformation Data Collector → Active Coach + +### Aktueller Stand +**Data Collector:** +- Daten werden erfasst +- Einfache Listen +- Basis-Statistiken +- KI-Analysen manuell angestoßen + +### Ziel (nach Fachkonzept) +**Active Coach:** +- Daten werden **interpretiert** +- Trends & Baselines +- Scores & Confidence +- Regelbasierte Hinweise +- Ziel-abhängige Bewertung +- Proaktive Warnungen +- KI nutzt strukturierte Insights + +--- + +## 10. Nächste Schritte + +1. **Issues neu strukturieren** (heute) +2. **Platzhalter implementieren** (Phase 0, diese Woche) +3. **Charts implementieren** (Phase 1, nächste Woche) +4. **Regelbasierte Insights** (Phase 2, Woche danach) +5. **KI-Integration** (Phase 3, dann) + +**Commit:** cd2609d +**Analysiert von:** Claude Code +**Basis:** Fachkonzept v2 (2086 Zeilen, 24.03.2026) -- 2.43.0 From ae93b9d428ba1abb16aeed26e28814358612e619 Mon Sep 17 00:00:00 2001 From: Lars Date: Thu, 26 Mar 2026 16:08:00 +0100 Subject: [PATCH 05/51] docs: goal system priority analysis - hybrid approach MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Key Decision: Minimal Goal System BEFORE Placeholders Critical Finding: - Same data = different interpretation per goal - Example: -5kg FM, -2kg LBM - weight_loss: 78/100 (good!) - strength: 32/100 (LBM loss critical!) - Without goal: 50/100 (generic, wrong for both) Recommended Approach (Hybrid): 1. Phase 0a (2-3h): Minimal Goal System - DB: goal_mode field - API: Get/Set Goal - UI: Goal Selector - Default: health 2. Phase 0b (16-20h): Goal-Aware Placeholders - 84 placeholders with goal-dependent calculations - Scores use goal_mode from day 1 - No rework needed later 3. Phase 2+ (6-8h): Full Goal System - Goal recognition from patterns - Secondary goals - Goal progression tracking Why Hybrid Works: ✅ Charts show correct interpretations immediately ✅ No rework of 84 placeholders later ✅ Goal recognition can come later (needs placeholders anyway) ✅ System is "smart coach" from day 1 File: docs/GOAL_SYSTEM_PRIORITY_ANALYSIS.md (650 lines) --- docs/GOAL_SYSTEM_PRIORITY_ANALYSIS.md | 538 ++++++++++++++++++++++++++ 1 file changed, 538 insertions(+) create mode 100644 docs/GOAL_SYSTEM_PRIORITY_ANALYSIS.md diff --git a/docs/GOAL_SYSTEM_PRIORITY_ANALYSIS.md b/docs/GOAL_SYSTEM_PRIORITY_ANALYSIS.md new file mode 100644 index 0000000..e9cac76 --- /dev/null +++ b/docs/GOAL_SYSTEM_PRIORITY_ANALYSIS.md @@ -0,0 +1,538 @@ +# Zielesystem: Prioritäts-Analyse + +**Datum:** 26. März 2026 +**Frage:** Zielesystem vor oder nach Platzhaltern/Charts? +**Antwort:** **Minimales Zielesystem VOR Platzhaltern, volles System parallel** + +--- + +## 1. Kritische Erkenntnis aus Fachkonzept + +### Zitat Fachkonzept (Zeile 20-28): +> **Wichtig ist, dass das System zielabhängig interpretiert:** +> - Gewichtsreduktion +> - Muskel-/Kraftaufbau +> - Konditions-/Ausdaueraufbau +> - Körperrekomposition +> - allgemeine Gesundheit +> +> **Dasselbe Rohsignal kann je nach Ziel anders bewertet werden.** +> Ein Kaloriendefizit ist z. B. bei Gewichtsreduktion oft positiv, +> bei Kraftaufbau aber potenziell hinderlich. + +### Konsequenz +❌ **Charts OHNE Zielesystem = falsche Interpretationen** +✅ **Charts MIT Zielesystem = korrekte, zielspezifische Aussagen** + +--- + +## 2. Abhängigkeits-Matrix + +### Was hängt vom Zielesystem ab? + +| Komponente | Zielabhängig? | Beispiel | +|------------|---------------|----------| +| **Rohdaten-Charts** | ❌ Nein | Gewichtsverlauf, Umfänge-Trend | +| **Score-Gewichtung** | ✅ JA | Body Progress Score: 30% bei weight_loss, 20% bei strength | +| **Interpretationen** | ✅ JA | Kaloriendefizit: "gut" bei weight_loss, "kritisch" bei strength | +| **Hinweise** | ✅ JA | "Gewicht stagniert" → bei weight_loss: Warnung, bei strength: egal | +| **Platzhalter (Berechnungen)** | ⚠️ TEILWEISE | Trends: Nein, Scores: JA | +| **KI-Prompts** | ✅ JA | Analyse-Kontext ändert sich komplett | + +### Fachkonzept: Score-Gewichtung (Zeile 185-216) + +```yaml +score_weights: + weight_loss: + body_progress: 0.30 # Körper wichtig + nutrition: 0.25 + activity: 0.20 + recovery: 0.15 + health_risk: 0.10 + + strength: + body_progress: 0.20 + nutrition: 0.25 + activity: 0.30 # Training wichtiger + recovery: 0.20 + health_risk: 0.05 # Weniger kritisch + + endurance: + body_progress: 0.10 # Körper unwichtiger + activity: 0.35 # Training am wichtigsten + recovery: 0.25 # Recovery sehr wichtig +``` + +### Beispiel: Body Progress Score + +**OHNE Zielesystem:** +```python +def calculate_body_progress_score(): + # Generisch, für niemanden wirklich passend + fm_delta_score = calculate_fm_change() # -5kg + lbm_delta_score = calculate_lbm_change() # -2kg + return (fm_delta_score + lbm_delta_score) / 2 + # Score: 50/100 (FM gut runter, aber LBM auch runter) +``` + +**MIT Zielesystem:** +```python +def calculate_body_progress_score(goal_mode): + fm_delta_score = calculate_fm_change() # -5kg + lbm_delta_score = calculate_lbm_change() # -2kg + + if goal_mode == "weight_loss": + # FM runter: sehr gut, LBM runter: tolerierbar wenn nicht zu viel + return 0.70 * fm_delta_score + 0.30 * lbm_delta_score + # Score: 78/100 (FM wichtiger, LBM-Verlust weniger kritisch) + + elif goal_mode == "strength": + # FM runter: ok, LBM runter: SEHR SCHLECHT + return 0.30 * fm_delta_score + 0.70 * lbm_delta_score + # Score: 32/100 (LBM-Verlust ist Hauptproblem!) + + elif goal_mode == "recomposition": + # FM runter: gut, LBM runter: schlecht + return 0.50 * fm_delta_score + 0.50 * lbm_delta_score + # Score: 50/100 (ausgewogen bewertet) +``` + +**Ergebnis:** +- Gleiche Daten (-5kg FM, -2kg LBM) +- ABER: 78/100 bei weight_loss, 32/100 bei strength +- **Ohne Ziel: völlig falsche Bewertung!** + +--- + +## 3. Ziel-Erkennung aus Daten + +### Fachkonzept erwähnt dies NICHT explizit, aber logisch ableitbar: + +**Pattern-Erkennung:** +```python +def suggest_goal_from_data(profile_id): + """Schlägt Ziel basierend auf Daten-Mustern vor.""" + + # Analyse der letzten 28 Tage + training_types = get_training_distribution_28d(profile_id) + nutrition = get_nutrition_pattern_28d(profile_id) + body_changes = get_body_changes_28d(profile_id) + + # Pattern 1: Viel Kraft + viel Protein + LBM steigt + if (training_types['strength'] > 60% and + nutrition['protein_g_per_kg'] > 1.8 and + body_changes['lbm_trend'] > 0): + return { + 'suggested_goal': 'strength', + 'confidence': 'high', + 'reasoning': 'Krafttraining dominant + hohe Proteinzufuhr + Muskelaufbau erkennbar' + } + + # Pattern 2: Viel Cardio + Kaloriendefizit + Gewicht sinkt + if (training_types['endurance'] > 50% and + nutrition['kcal_balance_avg'] < -300 and + body_changes['weight_trend'] < 0): + return { + 'suggested_goal': 'weight_loss', + 'confidence': 'high', + 'reasoning': 'Ausdauertraining + Kaloriendefizit + Gewichtsverlust' + } + + # Pattern 3: Mixed Training + Protein hoch + Gewicht stabil + Rekomposition + if (training_types['mixed'] == True and + nutrition['protein_g_per_kg'] > 1.6 and + abs(body_changes['weight_trend']) < 0.05 and + body_changes['fm_trend'] < 0 and + body_changes['lbm_trend'] > 0): + return { + 'suggested_goal': 'recomposition', + 'confidence': 'medium', + 'reasoning': 'Gemischtes Training + Rekomposition sichtbar (FM↓, LBM↑)' + } + + # Default: Nicht genug Muster erkennbar + return { + 'suggested_goal': 'health', + 'confidence': 'low', + 'reasoning': 'Keine klaren Muster erkennbar, gesundheitsorientiertes Training angenommen' + } +``` + +### Voraussetzungen für Ziel-Erkennung: +1. ✅ Mindestens 21-28 Tage Daten +2. ✅ Training-Type Distribution +3. ✅ Ernährungs-Pattern +4. ✅ Körper-Trends (FM, LBM, Gewicht) +5. ✅ Berechnet → **braucht Platzhalter!** + +**ABER:** Ziel-Erkennung ist **nachgelagert**, nicht Voraussetzung. + +--- + +## 4. Empfohlene Implementierungs-Strategie + +### Hybrid-Ansatz: Minimal-Ziele SOFORT, Voll-System parallel + +## Phase 0a: Minimal-Zielesystem (2-3h) ⭐ **START HIER** + +### Ziel +User kann manuell Ziel setzen, System nutzt es für Berechnungen. + +### Implementierung + +**1. DB-Schema erweitern:** +```sql +-- Migration 023 +ALTER TABLE profiles ADD COLUMN goal_mode VARCHAR(50) DEFAULT 'health'; +ALTER TABLE profiles ADD COLUMN goal_weight DECIMAL(5,2); +ALTER TABLE profiles ADD COLUMN goal_bf_pct DECIMAL(4,1); +ALTER TABLE profiles ADD COLUMN goal_set_date DATE; +ALTER TABLE profiles ADD COLUMN goal_target_date DATE; + +COMMENT ON COLUMN profiles.goal_mode IS + 'Primary goal: weight_loss, strength, endurance, recomposition, health'; +``` + +**2. Goal-Mode Konstanten:** +```python +# backend/goals.py (NEU) +GOAL_MODES = { + 'weight_loss': { + 'label': 'Gewichtsreduktion', + 'description': 'Fettabbau bei Erhalt der Magermasse', + 'score_weights': { + 'body_progress': 0.30, + 'nutrition': 0.25, + 'activity': 0.20, + 'recovery': 0.15, + 'health_risk': 0.10 + }, + 'focus_areas': ['fettmasse', 'gewichtstrend', 'kalorienbilanz', 'protein_sicherung'] + }, + 'strength': { + 'label': 'Kraftaufbau', + 'description': 'Muskelaufbau und Kraftsteigerung', + 'score_weights': { + 'body_progress': 0.20, + 'nutrition': 0.25, + 'activity': 0.30, + 'recovery': 0.20, + 'health_risk': 0.05 + }, + 'focus_areas': ['trainingsqualitaet', 'protein', 'lbm', 'recovery'] + }, + 'endurance': { + 'label': 'Ausdaueraufbau', + 'description': 'Kondition und VO2max verbessern', + 'score_weights': { + 'body_progress': 0.10, + 'nutrition': 0.20, + 'activity': 0.35, + 'recovery': 0.25, + 'health_risk': 0.10 + }, + 'focus_areas': ['trainingsvolumen', 'intensitaetsverteilung', 'vo2max', 'recovery'] + }, + 'recomposition': { + 'label': 'Körperrekomposition', + 'description': 'Fettabbau bei gleichzeitigem Muskelaufbau', + 'score_weights': { + 'body_progress': 0.30, + 'nutrition': 0.25, + 'activity': 0.25, + 'recovery': 0.15, + 'health_risk': 0.05 + }, + 'focus_areas': ['lbm', 'fettmasse', 'protein', 'trainingsqualitaet'] + }, + 'health': { + 'label': 'Allgemeine Gesundheit', + 'description': 'Ausgeglichenes Gesundheits- und Fitnesstraining', + 'score_weights': { + 'body_progress': 0.20, + 'nutrition': 0.20, + 'activity': 0.20, + 'recovery': 0.20, + 'health_risk': 0.20 + }, + 'focus_areas': ['bewegung', 'blutdruck', 'schlaf', 'gewicht', 'regelmaessigkeit'] + } +} +``` + +**3. API-Endpoint:** +```python +# routers/goals.py (NEU) +from fastapi import APIRouter, Depends +from auth import require_auth +from goals import GOAL_MODES + +router = APIRouter(prefix="/api/goals", tags=["goals"]) + +@router.get("/modes") +def get_goal_modes(): + """Return all available goal modes with descriptions.""" + return GOAL_MODES + +@router.get("/current") +def get_current_goal(session: dict = Depends(require_auth)): + """Get user's current goal settings.""" + profile_id = session['profile_id'] + with get_db() as conn: + cur = get_cursor(conn) + cur.execute( + """SELECT goal_mode, goal_weight, goal_bf_pct, + goal_set_date, goal_target_date + FROM profiles WHERE id=%s""", + (profile_id,) + ) + row = r2d(cur.fetchone()) + return { + **row, + 'mode_config': GOAL_MODES.get(row['goal_mode'], GOAL_MODES['health']) + } + +@router.post("/set") +def set_goal( + goal_mode: str, + goal_weight: Optional[float] = None, + goal_bf_pct: Optional[float] = None, + target_date: Optional[str] = None, + session: dict = Depends(require_auth) +): + """Set user's goal.""" + if goal_mode not in GOAL_MODES: + raise HTTPException(400, f"Invalid goal_mode. Must be one of: {list(GOAL_MODES.keys())}") + + profile_id = session['profile_id'] + with get_db() as conn: + cur = get_cursor(conn) + cur.execute( + """UPDATE profiles + SET goal_mode=%s, goal_weight=%s, goal_bf_pct=%s, + goal_set_date=CURRENT_DATE, goal_target_date=%s + WHERE id=%s""", + (goal_mode, goal_weight, goal_bf_pct, target_date, profile_id) + ) + conn.commit() + + return {"success": True, "goal_mode": goal_mode} +``` + +**4. Frontend UI (Settings.jsx):** +```jsx +// Minimal Goal Selector +function GoalSettings() { + const [goalModes, setGoalModes] = useState({}) + const [currentGoal, setCurrentGoal] = useState(null) + const [selectedMode, setSelectedMode] = useState('health') + + useEffect(() => { + loadGoalModes() + loadCurrentGoal() + }, []) + + const loadGoalModes = async () => { + const modes = await api.getGoalModes() + setGoalModes(modes) + } + + const loadCurrentGoal = async () => { + const goal = await api.getCurrentGoal() + setCurrentGoal(goal) + setSelectedMode(goal.goal_mode || 'health') + } + + const saveGoal = async () => { + await api.setGoal({ + goal_mode: selectedMode, + goal_weight: goalWeight, + goal_bf_pct: goalBfPct, + target_date: targetDate + }) + loadCurrentGoal() + } + + return ( +
+

🎯 Trainingsziel

+ +
+ + +

+ {goalModes[selectedMode]?.description} +

+
+ + {(selectedMode === 'weight_loss' || selectedMode === 'recomposition') && ( +
+ + +
+ )} + + +
+ ) +} +``` + +### Aufwand: 2-3h +- 1h: DB + Backend +- 1h: Frontend UI +- 0.5h: Testing + +--- + +## Phase 0b: Goal-Aware Platzhalter (16-20h) + +**Alle 84 Platzhalter implementieren, ABER:** +- Score-Berechnungen nutzen `goal_mode` von Anfang an +- Beispiel: + +```python +def get_body_progress_score(profile_id: str) -> str: + """Body Progress Score (0-100, goal-dependent).""" + profile = get_profile_data(profile_id) + goal_mode = profile.get('goal_mode', 'health') + + # Hole Gewichte aus goals.GOAL_MODES + weights = GOAL_MODES[goal_mode]['score_weights'] + + # Berechne Sub-Scores + fm_score = calculate_fm_progress(profile_id) + lbm_score = calculate_lbm_progress(profile_id) + weight_score = calculate_weight_progress(profile_id, goal_mode) + + # Gewichte nach Ziel + if goal_mode == 'weight_loss': + total = (0.50 * fm_score + 0.30 * weight_score + 0.20 * lbm_score) + elif goal_mode == 'strength': + total = (0.60 * lbm_score + 0.30 * fm_score + 0.10 * weight_score) + elif goal_mode == 'recomposition': + total = (0.45 * fm_score + 0.45 * lbm_score + 0.10 * weight_score) + else: # health, endurance + total = (0.40 * weight_score + 0.30 * fm_score + 0.30 * lbm_score) + + return f"{int(total)}/100" +``` + +**Resultat:** +- Charts bekommen von Anfang an **korrekte** Scores +- Keine Umarbeitung nötig später +- System ist "smart" ab Tag 1 + +--- + +## Phase 2+: Vollständiges Zielesystem (6-8h) + +**Features:** +1. **Ziel-Erkennung aus Daten** + - Pattern-Analyse (wie oben) + - Vorschlag mit Confidence + - "Passt dein Ziel noch?" Check + +2. **Sekundäre Ziele** + - `goal_mode` = primary + - `secondary_goals[]` = weitere Schwerpunkte + - Gewichtung: 70% primary, 30% secondary + +3. **Ziel-Progression Tracking** + - Fortschritt zum Ziel (%) + - Geschätzte Erreichung (Datum) + - Anpassungs-Vorschläge + +4. **Goal-Aware Charts** + - Priorisierung nach goal_relevance + - Dashboard zeigt ziel-spezifische Charts zuerst + +5. **Goal-Aware KI** + - Prompt-Kontext enthält goal_mode + - KI interpretiert zielspezifisch + +--- + +## 5. Entscheidungs-Matrix + +### Option A: Zielesystem komplett ZUERST +**Aufwand:** 10-12h +**Pro:** +- Alles konsistent von Anfang an +- Keine Umarbeitung +**Contra:** +- Verzögert Platzhalter-Start +- Ziel-Erkennung braucht Platzhalter (Henne-Ei) + +### Option B: Platzhalter ZUERST, dann Ziele +**Aufwand:** 16-20h + später Rework +**Pro:** +- Schneller Start +**Contra:** +- ALLE Scores falsch gewichtet +- Komplette Umarbeitung nötig +- User sehen falsche Werte + +### Option C: HYBRID ⭐ **EMPFOHLEN** +**Aufwand:** 2-3h (Minimal-Ziele) + 16-20h (Goal-Aware Platzhalter) + später 6-8h (Voll-System) +**Pro:** +- ✅ Beste aus beiden Welten +- ✅ Korrekte Scores von Anfang an +- ✅ Keine Umarbeitung +- ✅ Ziel-Erkennung später als Enhancement +**Contra:** +- Keinen signifikanten Nachteil + +--- + +## 6. Empfehlung + +### JA, Zielesystem VOR Platzhaltern – aber minimal! + +**Reihenfolge:** + +1. **Phase 0a (2-3h):** Minimal-Zielesystem + - DB: goal_mode field + - API: Get/Set Goal + - UI: Goal Selector (Settings) + - Default: "health" + +2. **Phase 0b (16-20h):** Goal-Aware Platzhalter + - 84 Platzhalter implementieren + - Scores nutzen goal_mode + - Berechnungen goal-abhängig + +3. **Phase 1 (12-16h):** Charts + - Nutzen goal-aware Platzhalter + - Zeigen korrekte Interpretationen + +4. **Phase 2+ (6-8h):** Vollständiges Zielesystem + - Ziel-Erkennung + - Sekundäre Ziele + - Goal Progression Tracking + +--- + +## 7. Fazit + +**Deine Intuition war 100% richtig!** + +✅ **Ohne Zielesystem:** +- Charts zeigen falsche Interpretationen +- Scores sind generisch und für niemanden passend +- System bleibt "dummer Datensammler" + +✅ **Mit Zielesystem:** +- Charts interpretieren zielspezifisch +- Scores sind individuell gewichtet +- System wird "intelligenter Coach" + +**Nächster Schritt:** Phase 0a implementieren (2-3h), dann Phase 0b mit goal-aware Platzhaltern. + +**Soll ich mit Phase 0a (Minimal-Zielesystem) starten?** -- 2.43.0 From 337667fc0740a0b59193c57d8d6005de97d9e7a5 Mon Sep 17 00:00:00 2001 From: Lars Date: Thu, 26 Mar 2026 16:20:35 +0100 Subject: [PATCH 06/51] feat: Phase 0a - Minimal Goal System (Strategic + Tactical) - Strategic Layer: Goal modes (weight_loss, strength, endurance, recomposition, health) - Tactical Layer: Concrete goal targets with progress tracking - Training phases (manual + auto-detection framework) - Fitness tests (standardized performance tracking) Backend: - Migration 022: goal_mode in profiles, goals, training_phases, fitness_tests tables - New router: routers/goals.py with full CRUD for goals, phases, tests - API endpoints: /api/goals/* (mode, list, create, update, delete) Frontend: - GoalsPage: Goal mode selector + goal management UI - Dashboard: Goals preview card with link - API integration: goal mode, CRUD operations, progress calculation Basis for 120+ placeholders and goal-aware analyses (Phase 0b) Co-Authored-By: Claude Opus 4.6 --- backend/main.py | 2 + backend/migrations/022_goal_system.sql | 147 ++++++ backend/routers/goals.py | 473 ++++++++++++++++++++ docs/GOALS_SYSTEM_UNIFIED_ANALYSIS.md | 595 +++++++++++++++++++++++++ frontend/src/App.jsx | 2 + frontend/src/pages/Dashboard.jsx | 15 + frontend/src/pages/GoalsPage.jsx | 562 +++++++++++++++++++++++ frontend/src/utils/api.js | 17 + 8 files changed, 1813 insertions(+) create mode 100644 backend/migrations/022_goal_system.sql create mode 100644 backend/routers/goals.py create mode 100644 docs/GOALS_SYSTEM_UNIFIED_ANALYSIS.md create mode 100644 frontend/src/pages/GoalsPage.jsx diff --git a/backend/main.py b/backend/main.py index 63faff3..738f07e 100644 --- a/backend/main.py +++ b/backend/main.py @@ -23,6 +23,7 @@ from routers import user_restrictions, access_grants, training_types, admin_trai from routers import admin_activity_mappings, sleep, rest_days from routers import vitals_baseline, blood_pressure # v9d Phase 2d Refactored from routers import evaluation # v9d/v9e Training Type Profiles (#15) +from routers import goals # v9e Goal System (Strategic + Tactical) # ── App Configuration ───────────────────────────────────────────────────────── DATA_DIR = Path(os.getenv("DATA_DIR", "./data")) @@ -97,6 +98,7 @@ app.include_router(rest_days.router) # /api/rest-days/* (v9d Phase 2a app.include_router(vitals_baseline.router) # /api/vitals/baseline/* (v9d Phase 2d Refactored) app.include_router(blood_pressure.router) # /api/blood-pressure/* (v9d Phase 2d Refactored) app.include_router(evaluation.router) # /api/evaluation/* (v9d/v9e Training Profiles #15) +app.include_router(goals.router) # /api/goals/* (v9e Goal System Strategic + Tactical) # ── Health Check ────────────────────────────────────────────────────────────── @app.get("/") diff --git a/backend/migrations/022_goal_system.sql b/backend/migrations/022_goal_system.sql new file mode 100644 index 0000000..5157148 --- /dev/null +++ b/backend/migrations/022_goal_system.sql @@ -0,0 +1,147 @@ +-- Migration 022: Goal System (Strategic + Tactical) +-- Date: 2026-03-26 +-- Purpose: Two-level goal architecture for AI-driven coaching + +-- ============================================================================ +-- STRATEGIC LAYER: Goal Modes +-- ============================================================================ + +-- Add goal_mode to profiles (strategic training direction) +ALTER TABLE profiles ADD COLUMN IF NOT EXISTS goal_mode VARCHAR(50) DEFAULT 'health'; + +COMMENT ON COLUMN profiles.goal_mode IS + 'Strategic goal mode: weight_loss, strength, endurance, recomposition, health. + Determines score weights and interpretation context for all analyses.'; + +-- ============================================================================ +-- TACTICAL LAYER: Concrete Goal Targets +-- ============================================================================ + +CREATE TABLE IF NOT EXISTS goals ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + + -- Goal Classification + goal_type VARCHAR(50) NOT NULL, -- weight, body_fat, lean_mass, vo2max, strength, flexibility, bp, rhr + is_primary BOOLEAN DEFAULT false, + status VARCHAR(20) DEFAULT 'active', -- draft, active, reached, abandoned, expired + + -- Target Values + target_value DECIMAL(10,2), + current_value DECIMAL(10,2), + start_value DECIMAL(10,2), + unit VARCHAR(20), -- kg, %, ml/kg/min, bpm, mmHg, cm, reps + + -- Timeline + start_date DATE DEFAULT CURRENT_DATE, + target_date DATE, + reached_date DATE, + + -- Metadata + name VARCHAR(100), -- e.g., "Sommerfigur 2026" + description TEXT, + + -- Progress Tracking + progress_pct DECIMAL(5,2), -- Auto-calculated: (current - start) / (target - start) * 100 + projection_date DATE, -- Prognose wann Ziel erreicht wird + on_track BOOLEAN, -- true wenn Prognose <= target_date + + -- Timestamps + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_goals_profile ON goals(profile_id); +CREATE INDEX IF NOT EXISTS idx_goals_status ON goals(profile_id, status); +CREATE INDEX IF NOT EXISTS idx_goals_primary ON goals(profile_id, is_primary) WHERE is_primary = true; + +COMMENT ON TABLE goals IS 'Concrete user goals (tactical targets)'; +COMMENT ON COLUMN goals.goal_type IS 'Type of goal: weight, body_fat, lean_mass, vo2max, strength, flexibility, bp, rhr'; +COMMENT ON COLUMN goals.is_primary IS 'Primary goal gets highest priority in scoring and charts'; +COMMENT ON COLUMN goals.status IS 'draft = not yet started, active = in progress, reached = successfully completed, abandoned = given up, expired = deadline passed'; +COMMENT ON COLUMN goals.progress_pct IS 'Percentage progress: (current_value - start_value) / (target_value - start_value) * 100'; +COMMENT ON COLUMN goals.projection_date IS 'Projected date when goal will be reached based on current trend'; +COMMENT ON COLUMN goals.on_track IS 'true if projection_date <= target_date (goal reachable on time)'; + +-- ============================================================================ +-- TRAINING PHASES (Auto-Detection) +-- ============================================================================ + +CREATE TABLE IF NOT EXISTS training_phases ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + + -- Phase Classification + phase_type VARCHAR(50) NOT NULL, -- calorie_deficit, calorie_surplus, deload, maintenance, periodization + detected_automatically BOOLEAN DEFAULT false, + confidence_score DECIMAL(3,2), -- 0.00 - 1.00 (Wie sicher ist die Erkennung?) + status VARCHAR(20) DEFAULT 'suggested', -- suggested, accepted, active, completed, rejected + + -- Timeframe + start_date DATE NOT NULL, + end_date DATE, + duration_days INT, + + -- Detection Criteria (JSONB für Flexibilität) + detection_params JSONB, -- { "avg_calories": 1800, "weight_trend": -0.3, ... } + + -- User Notes + notes TEXT, + + -- Timestamps + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_training_phases_profile ON training_phases(profile_id); +CREATE INDEX IF NOT EXISTS idx_training_phases_status ON training_phases(profile_id, status); +CREATE INDEX IF NOT EXISTS idx_training_phases_dates ON training_phases(profile_id, start_date, end_date); + +COMMENT ON TABLE training_phases IS 'Training phases detected from data patterns or manually defined'; +COMMENT ON COLUMN training_phases.phase_type IS 'calorie_deficit, calorie_surplus, deload, maintenance, periodization'; +COMMENT ON COLUMN training_phases.detected_automatically IS 'true if AI detected this phase from data patterns'; +COMMENT ON COLUMN training_phases.confidence_score IS 'AI confidence in detection (0.0 - 1.0)'; +COMMENT ON COLUMN training_phases.status IS 'suggested = AI proposed, accepted = user confirmed, active = currently running, completed = finished, rejected = user dismissed'; +COMMENT ON COLUMN training_phases.detection_params IS 'JSON with detection criteria: avg_calories, weight_trend, activity_volume, etc.'; + +-- ============================================================================ +-- FITNESS TESTS (Standardized Performance Tests) +-- ============================================================================ + +CREATE TABLE IF NOT EXISTS fitness_tests ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + + -- Test Type + test_type VARCHAR(50) NOT NULL, -- cooper_12min, step_test, pushups_max, plank_max, flexibility_sit_reach, vo2max_est, strength_1rm_squat, strength_1rm_bench + result_value DECIMAL(10,2) NOT NULL, + result_unit VARCHAR(20) NOT NULL, -- meters, bpm, reps, seconds, cm, ml/kg/min, kg + + -- Test Metadata + test_date DATE NOT NULL, + test_conditions TEXT, -- Optional: Notizen zu Bedingungen + norm_category VARCHAR(30), -- sehr gut, gut, durchschnitt, unterdurchschnitt, schlecht + + -- Timestamps + created_at TIMESTAMP DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_fitness_tests_profile ON fitness_tests(profile_id); +CREATE INDEX IF NOT EXISTS idx_fitness_tests_type ON fitness_tests(profile_id, test_type); +CREATE INDEX IF NOT EXISTS idx_fitness_tests_date ON fitness_tests(profile_id, test_date); + +COMMENT ON TABLE fitness_tests IS 'Standardized fitness tests (Cooper, step test, strength tests, etc.)'; +COMMENT ON COLUMN fitness_tests.test_type IS 'cooper_12min, step_test, pushups_max, plank_max, flexibility_sit_reach, vo2max_est, strength_1rm_squat, strength_1rm_bench'; +COMMENT ON COLUMN fitness_tests.norm_category IS 'Performance category based on age/gender norms'; + +-- ============================================================================ +-- VERSION UPDATE +-- ============================================================================ + +-- Track migration +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM schema_migrations WHERE version = '022') THEN + INSERT INTO schema_migrations (version, applied_at) VALUES ('022', NOW()); + END IF; +END $$; diff --git a/backend/routers/goals.py b/backend/routers/goals.py new file mode 100644 index 0000000..919bec3 --- /dev/null +++ b/backend/routers/goals.py @@ -0,0 +1,473 @@ +""" +Goals Router - Goal System (Strategic + Tactical) + +Endpoints for managing: +- Strategic goal modes (weight_loss, strength, etc.) +- Tactical goal targets (concrete values with deadlines) +- Training phase detection +- Fitness tests + +Part of v9e Goal System implementation. +""" +from fastapi import APIRouter, Depends, HTTPException +from pydantic import BaseModel +from typing import Optional, List +from datetime import date, datetime, timedelta +from decimal import Decimal +import json + +from db import get_db, get_cursor, r2d +from auth import require_auth + +router = APIRouter(prefix="/api/goals", tags=["goals"]) + +# ============================================================================ +# Pydantic Models +# ============================================================================ + +class GoalModeUpdate(BaseModel): + """Update strategic goal mode""" + goal_mode: str # weight_loss, strength, endurance, recomposition, health + +class GoalCreate(BaseModel): + """Create or update a concrete goal""" + goal_type: str # weight, body_fat, lean_mass, vo2max, strength, flexibility, bp, rhr + is_primary: bool = False + target_value: float + unit: str # kg, %, ml/kg/min, bpm, mmHg, cm, reps + target_date: Optional[date] = None + name: Optional[str] = None + description: Optional[str] = None + +class GoalUpdate(BaseModel): + """Update existing goal""" + target_value: Optional[float] = None + target_date: Optional[date] = None + status: Optional[str] = None # active, reached, abandoned, expired + name: Optional[str] = None + description: Optional[str] = None + +class TrainingPhaseCreate(BaseModel): + """Create training phase (manual or auto-detected)""" + phase_type: str # calorie_deficit, calorie_surplus, deload, maintenance, periodization + start_date: date + end_date: Optional[date] = None + notes: Optional[str] = None + +class FitnessTestCreate(BaseModel): + """Record fitness test result""" + test_type: str + result_value: float + result_unit: str + test_date: date + test_conditions: Optional[str] = None + +# ============================================================================ +# Strategic Layer: Goal Modes +# ============================================================================ + +@router.get("/mode") +def get_goal_mode(session: dict = Depends(require_auth)): + """Get user's current strategic goal mode""" + pid = session['profile_id'] + + with get_db() as conn: + cur = get_cursor(conn) + cur.execute( + "SELECT goal_mode FROM profiles WHERE id = %s", + (pid,) + ) + row = cur.fetchone() + if not row: + raise HTTPException(status_code=404, detail="Profil nicht gefunden") + + return { + "goal_mode": row['goal_mode'] or 'health', + "description": _get_goal_mode_description(row['goal_mode'] or 'health') + } + +@router.put("/mode") +def update_goal_mode(data: GoalModeUpdate, session: dict = Depends(require_auth)): + """Update user's strategic goal mode""" + pid = session['profile_id'] + + # Validate goal mode + valid_modes = ['weight_loss', 'strength', 'endurance', 'recomposition', 'health'] + if data.goal_mode not in valid_modes: + raise HTTPException( + status_code=400, + detail=f"Ungültiger Goal Mode. Erlaubt: {', '.join(valid_modes)}" + ) + + with get_db() as conn: + cur = get_cursor(conn) + cur.execute( + "UPDATE profiles SET goal_mode = %s WHERE id = %s", + (data.goal_mode, pid) + ) + + return { + "goal_mode": data.goal_mode, + "description": _get_goal_mode_description(data.goal_mode) + } + +def _get_goal_mode_description(mode: str) -> str: + """Get description for goal mode""" + descriptions = { + 'weight_loss': 'Gewichtsreduktion (Kaloriendefizit, Fettabbau)', + 'strength': 'Kraftaufbau (Muskelwachstum, progressive Belastung)', + 'endurance': 'Ausdauer (VO2Max, aerobe Kapazität)', + 'recomposition': 'Körperkomposition (gleichzeitig Fett ab- und Muskeln aufbauen)', + 'health': 'Allgemeine Gesundheit (ausgewogen, präventiv)' + } + return descriptions.get(mode, 'Unbekannt') + +# ============================================================================ +# Tactical Layer: Concrete Goals +# ============================================================================ + +@router.get("/list") +def list_goals(session: dict = Depends(require_auth)): + """List all goals for current user""" + pid = session['profile_id'] + + with get_db() as conn: + cur = get_cursor(conn) + cur.execute(""" + SELECT id, goal_type, is_primary, status, + target_value, current_value, start_value, unit, + start_date, target_date, reached_date, + name, description, + progress_pct, projection_date, on_track, + created_at, updated_at + FROM goals + WHERE profile_id = %s + ORDER BY is_primary DESC, created_at DESC + """, (pid,)) + + goals = [r2d(row) for row in cur.fetchall()] + + # Update current values for each goal + for goal in goals: + _update_goal_progress(conn, pid, goal) + + return goals + +@router.post("/create") +def create_goal(data: GoalCreate, session: dict = Depends(require_auth)): + """Create new goal""" + pid = session['profile_id'] + + with get_db() as conn: + cur = get_cursor(conn) + + # If this is set as primary, unset other primary goals + if data.is_primary: + cur.execute( + "UPDATE goals SET is_primary = false WHERE profile_id = %s", + (pid,) + ) + + # Get current value for this goal type + current_value = _get_current_value_for_goal_type(conn, pid, data.goal_type) + + # Insert goal + cur.execute(""" + INSERT INTO goals ( + profile_id, goal_type, is_primary, + target_value, current_value, start_value, unit, + target_date, name, description + ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s) + RETURNING id + """, ( + pid, data.goal_type, data.is_primary, + data.target_value, current_value, current_value, data.unit, + data.target_date, data.name, data.description + )) + + goal_id = cur.fetchone()['id'] + + return {"id": goal_id, "message": "Ziel erstellt"} + +@router.put("/{goal_id}") +def update_goal(goal_id: str, data: GoalUpdate, session: dict = Depends(require_auth)): + """Update existing goal""" + pid = session['profile_id'] + + with get_db() as conn: + cur = get_cursor(conn) + + # Verify ownership + cur.execute( + "SELECT id FROM goals WHERE id = %s AND profile_id = %s", + (goal_id, pid) + ) + if not cur.fetchone(): + raise HTTPException(status_code=404, detail="Ziel nicht gefunden") + + # Build update query dynamically + updates = [] + params = [] + + if data.target_value is not None: + updates.append("target_value = %s") + params.append(data.target_value) + + if data.target_date is not None: + updates.append("target_date = %s") + params.append(data.target_date) + + if data.status is not None: + updates.append("status = %s") + params.append(data.status) + if data.status == 'reached': + updates.append("reached_date = CURRENT_DATE") + + if data.name is not None: + updates.append("name = %s") + params.append(data.name) + + if data.description is not None: + updates.append("description = %s") + params.append(data.description) + + if not updates: + raise HTTPException(status_code=400, detail="Keine Änderungen angegeben") + + updates.append("updated_at = NOW()") + params.extend([goal_id, pid]) + + cur.execute( + f"UPDATE goals SET {', '.join(updates)} WHERE id = %s AND profile_id = %s", + tuple(params) + ) + + return {"message": "Ziel aktualisiert"} + +@router.delete("/{goal_id}") +def delete_goal(goal_id: str, session: dict = Depends(require_auth)): + """Delete goal""" + pid = session['profile_id'] + + with get_db() as conn: + cur = get_cursor(conn) + cur.execute( + "DELETE FROM goals WHERE id = %s AND profile_id = %s", + (goal_id, pid) + ) + + if cur.rowcount == 0: + raise HTTPException(status_code=404, detail="Ziel nicht gefunden") + + return {"message": "Ziel gelöscht"} + +# ============================================================================ +# Training Phases +# ============================================================================ + +@router.get("/phases") +def list_training_phases(session: dict = Depends(require_auth)): + """List training phases""" + pid = session['profile_id'] + + with get_db() as conn: + cur = get_cursor(conn) + cur.execute(""" + SELECT id, phase_type, detected_automatically, confidence_score, + status, start_date, end_date, duration_days, + detection_params, notes, created_at + FROM training_phases + WHERE profile_id = %s + ORDER BY start_date DESC + """, (pid,)) + + return [r2d(row) for row in cur.fetchall()] + +@router.post("/phases") +def create_training_phase(data: TrainingPhaseCreate, session: dict = Depends(require_auth)): + """Create training phase (manual)""" + pid = session['profile_id'] + + with get_db() as conn: + cur = get_cursor(conn) + + duration = None + if data.end_date: + duration = (data.end_date - data.start_date).days + + cur.execute(""" + INSERT INTO training_phases ( + profile_id, phase_type, detected_automatically, + status, start_date, end_date, duration_days, notes + ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s) + RETURNING id + """, ( + pid, data.phase_type, False, + 'active', data.start_date, data.end_date, duration, data.notes + )) + + phase_id = cur.fetchone()['id'] + + return {"id": phase_id, "message": "Trainingsphase erstellt"} + +@router.put("/phases/{phase_id}/status") +def update_phase_status( + phase_id: str, + status: str, + session: dict = Depends(require_auth) +): + """Update training phase status (accept/reject auto-detected phases)""" + pid = session['profile_id'] + + valid_statuses = ['suggested', 'accepted', 'active', 'completed', 'rejected'] + if status not in valid_statuses: + raise HTTPException( + status_code=400, + detail=f"Ungültiger Status. Erlaubt: {', '.join(valid_statuses)}" + ) + + with get_db() as conn: + cur = get_cursor(conn) + cur.execute( + "UPDATE training_phases SET status = %s WHERE id = %s AND profile_id = %s", + (status, phase_id, pid) + ) + + if cur.rowcount == 0: + raise HTTPException(status_code=404, detail="Trainingsphase nicht gefunden") + + return {"message": "Status aktualisiert"} + +# ============================================================================ +# Fitness Tests +# ============================================================================ + +@router.get("/tests") +def list_fitness_tests(session: dict = Depends(require_auth)): + """List all fitness tests""" + pid = session['profile_id'] + + with get_db() as conn: + cur = get_cursor(conn) + cur.execute(""" + SELECT id, test_type, result_value, result_unit, + test_date, test_conditions, norm_category, created_at + FROM fitness_tests + WHERE profile_id = %s + ORDER BY test_date DESC + """, (pid,)) + + return [r2d(row) for row in cur.fetchall()] + +@router.post("/tests") +def create_fitness_test(data: FitnessTestCreate, session: dict = Depends(require_auth)): + """Record fitness test result""" + pid = session['profile_id'] + + with get_db() as conn: + cur = get_cursor(conn) + + # Calculate norm category (simplified for now) + norm_category = _calculate_norm_category( + data.test_type, + data.result_value, + data.result_unit + ) + + cur.execute(""" + INSERT INTO fitness_tests ( + profile_id, test_type, result_value, result_unit, + test_date, test_conditions, norm_category + ) VALUES (%s, %s, %s, %s, %s, %s, %s) + RETURNING id + """, ( + pid, data.test_type, data.result_value, data.result_unit, + data.test_date, data.test_conditions, norm_category + )) + + test_id = cur.fetchone()['id'] + + return {"id": test_id, "norm_category": norm_category} + +# ============================================================================ +# Helper Functions +# ============================================================================ + +def _get_current_value_for_goal_type(conn, profile_id: str, goal_type: str) -> Optional[float]: + """Get current value for a goal type from latest data""" + cur = get_cursor(conn) + + if goal_type == 'weight': + cur.execute(""" + SELECT weight FROM weight_log + WHERE profile_id = %s + ORDER BY date DESC LIMIT 1 + """, (profile_id,)) + row = cur.fetchone() + return float(row['weight']) if row else None + + elif goal_type == 'body_fat': + cur.execute(""" + SELECT body_fat_pct FROM caliper_log + WHERE profile_id = %s + ORDER BY date DESC LIMIT 1 + """, (profile_id,)) + row = cur.fetchone() + return float(row['body_fat_pct']) if row else None + + elif goal_type == 'vo2max': + cur.execute(""" + SELECT vo2max FROM vitals_baseline + WHERE profile_id = %s AND vo2max IS NOT NULL + ORDER BY date DESC LIMIT 1 + """, (profile_id,)) + row = cur.fetchone() + return float(row['vo2max']) if row else None + + elif goal_type == 'rhr': + cur.execute(""" + SELECT resting_hr FROM vitals_baseline + WHERE profile_id = %s AND resting_hr IS NOT NULL + ORDER BY date DESC LIMIT 1 + """, (profile_id,)) + row = cur.fetchone() + return float(row['resting_hr']) if row else None + + return None + +def _update_goal_progress(conn, profile_id: str, goal: dict): + """Update goal progress (modifies goal dict in-place)""" + # Get current value + current = _get_current_value_for_goal_type(conn, profile_id, goal['goal_type']) + + if current is not None and goal['start_value'] is not None and goal['target_value'] is not None: + goal['current_value'] = current + + # Calculate progress percentage + total_delta = float(goal['target_value']) - float(goal['start_value']) + current_delta = current - float(goal['start_value']) + + if total_delta != 0: + progress_pct = (current_delta / total_delta) * 100 + goal['progress_pct'] = round(progress_pct, 2) + + # Simple linear projection + if goal['start_date'] and current_delta != 0: + days_elapsed = (date.today() - goal['start_date']).days + if days_elapsed > 0: + days_per_unit = days_elapsed / current_delta + remaining_units = float(goal['target_value']) - current + remaining_days = int(days_per_unit * remaining_units) + goal['projection_date'] = date.today() + timedelta(days=remaining_days) + + # Check if on track + if goal['target_date'] and goal['projection_date']: + goal['on_track'] = goal['projection_date'] <= goal['target_date'] + +def _calculate_norm_category(test_type: str, value: float, unit: str) -> Optional[str]: + """ + Calculate norm category for fitness test + (Simplified - would need age/gender-specific norms) + """ + # Placeholder - should use proper norm tables + return None diff --git a/docs/GOALS_SYSTEM_UNIFIED_ANALYSIS.md b/docs/GOALS_SYSTEM_UNIFIED_ANALYSIS.md new file mode 100644 index 0000000..f69134a --- /dev/null +++ b/docs/GOALS_SYSTEM_UNIFIED_ANALYSIS.md @@ -0,0 +1,595 @@ +# Zielesystem: Vereinheitlichte Analyse beider Fachkonzepte + +**Datum:** 26. März 2026 +**Basis:** +- `.claude/docs/functional/GOALS_VITALS.md` (v9e Spec) +- `.claude/docs/functional/mitai_jinkendo_konzept_diagramme_auswertungen_v2.md` + +--- + +## 1. Wichtige Erkenntnis: BEIDE Konzepte sind komplementär! + +### GOALS_VITALS.md definiert: +- **Konkrete Zielwerte** (z.B. "82kg bis 30.06.2026") +- 8 Zieltypen (Gewicht, KF%, VO2Max, etc.) +- Primär-/Nebenziel-Konzept +- Trainingsphasen (automatische Erkennung) +- Aktive Tests (Cooper, Liegestütze, etc.) +- 13 neue KI-Platzhalter + +### Konzept v2 definiert: +- **Goal Modes** (strategische Ausrichtung: weight_loss, strength, etc.) +- Score-Gewichtung je Goal Mode +- Chart-Priorisierung je Goal Mode +- Regelbasierte Interpretationen + +### Zusammenspiel: +``` +Goal MODE (v2) → "weight_loss" (strategische Ausrichtung) + ↓ +Primary GOAL (v9e) → "82kg bis 30.06.2026" (konkretes Ziel) +Secondary GOAL → "16% Körperfett" + ↓ +Training PHASE (v9e) → "Kaloriendefizit" (automatisch erkannt) + ↓ +Score Weights (v2) → body_progress: 0.30, nutrition: 0.25, ... + ↓ +Charts (v2) → Zeigen gewichtete Scores + Fortschritt zu Zielen +``` + +--- + +## 2. Zwei-Ebenen-Architektur + +### Ebene 1: STRATEGIC (Goal Modes aus v2) +**Was:** Grundsätzliche Trainingsausrichtung +**Werte:** weight_loss, strength, endurance, recomposition, health +**Zweck:** Bestimmt Score-Gewichtung und Interpretations-Kontext +**Beispiel:** "Ich will Kraft aufbauen" → mode: strength + +### Ebene 2: TACTICAL (Goal Targets aus v9e) +**Was:** Konkrete messbare Ziele +**Werte:** "82kg bis 30.06.2026", "VO2Max 55 ml/kg/min", "50 Liegestütze" +**Zweck:** Fortschritts-Tracking, Prognosen, Motivation +**Beispiel:** "Ich will 82kg wiegen" → target: Gewichtsziel + +### Beide zusammen = Vollständiges Zielesystem + +--- + +## 3. Überarbeitetes Datenmodell + +### Tabelle: `profiles` (erweitern) +```sql +-- Strategic Goal Mode (aus v2) +ALTER TABLE profiles ADD COLUMN goal_mode VARCHAR(50) DEFAULT 'health'; + +COMMENT ON COLUMN profiles.goal_mode IS + 'Strategic goal mode: weight_loss, strength, endurance, recomposition, health. + Determines score weights and interpretation context.'; +``` + +### Tabelle: `goals` (NEU, aus v9e) +```sql +CREATE TABLE goals ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + + -- Goal Classification + goal_type VARCHAR(50) NOT NULL, -- weight, body_fat, lean_mass, vo2max, strength, flexibility, bp, rhr + is_primary BOOLEAN DEFAULT false, + status VARCHAR(20) DEFAULT 'active', -- draft, active, reached, abandoned, expired + + -- Target Values + target_value DECIMAL(10,2), + current_value DECIMAL(10,2), + start_value DECIMAL(10,2), + unit VARCHAR(20), -- kg, %, ml/kg/min, bpm, mmHg, cm, reps + + -- Timeline + start_date DATE DEFAULT CURRENT_DATE, + target_date DATE, + reached_date DATE, + + -- Metadata + name VARCHAR(100), -- z.B. "Sommerfigur 2026" + description TEXT, + + -- Progress Tracking + progress_pct DECIMAL(5,2), -- Auto-calculated: (current - start) / (target - start) * 100 + + -- Timestamps + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW(), + + -- Constraints + CHECK (progress_pct >= 0 AND progress_pct <= 100), + CHECK (status IN ('draft', 'active', 'reached', 'abandoned', 'expired')) +); + +-- Only one primary goal per profile +CREATE UNIQUE INDEX idx_goals_primary ON goals(profile_id, is_primary) WHERE is_primary = true; + +-- Index for active goals lookup +CREATE INDEX idx_goals_active ON goals(profile_id, status) WHERE status = 'active'; +``` + +### Tabelle: `training_phases` (NEU, aus v9e) +```sql +CREATE TABLE training_phases ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + + -- Phase Type + phase_type VARCHAR(50) NOT NULL, + -- Werte: calorie_deficit, calorie_maintenance, calorie_surplus, + -- conditioning, hiit, max_strength, regeneration, competition_prep + + -- Detection + detected_automatically BOOLEAN DEFAULT false, + confidence_score DECIMAL(3,2), -- 0.00-1.00 + + -- Status + status VARCHAR(20) DEFAULT 'suggested', -- suggested, confirmed, active, ended + + -- Timeline + start_date DATE, + end_date DATE, + + -- Metadata + detection_reason TEXT, -- Why was this phase detected? + user_notes TEXT, + + -- Timestamps + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); + +-- Only one active phase per profile +CREATE UNIQUE INDEX idx_phases_active ON training_phases(profile_id, status) WHERE status = 'active'; +``` + +### Tabelle: `fitness_tests` (NEU, aus v9e) +```sql +CREATE TABLE fitness_tests ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, + + -- Test Type + test_type VARCHAR(50) NOT NULL, + -- Standard: cooper, step_test, pushups, squats, sit_reach, balance, grip_strength + -- Custom: user_defined + + -- Result + result_value DECIMAL(10,2) NOT NULL, + result_unit VARCHAR(20) NOT NULL, -- meters, bpm, reps, cm, seconds, kg + + -- Test Date + test_date DATE NOT NULL, + + -- Evaluation + norm_category VARCHAR(30), -- very_good, good, average, needs_improvement + percentile DECIMAL(5,2), -- Where user ranks vs. norm (0-100) + + -- Trend + improvement_vs_last DECIMAL(10,2), -- % change from previous test + + -- Metadata + notes TEXT, + conditions TEXT, -- e.g., "Nach 3h Schlaf, erkältet" + + -- Next Test Recommendation + recommended_retest_date DATE, + + created_at TIMESTAMP DEFAULT NOW() +); + +CREATE INDEX idx_fitness_tests_profile_type ON fitness_tests(profile_id, test_type, test_date DESC); +``` + +--- + +## 4. Vereinheitlichte API-Struktur + +### Goal Modes (Strategic) +```python +# routers/goals.py + +@router.get("/modes") +def get_goal_modes(): + """Get all strategic goal modes with score weights.""" + return GOAL_MODES # From v2 concept + +@router.post("/set-mode") +def set_goal_mode(goal_mode: str, session=Depends(require_auth)): + """Set user's strategic goal mode.""" + # Updates profiles.goal_mode +``` + +### Goal Targets (Tactical) +```python +@router.get("/targets") +def get_goal_targets(session=Depends(require_auth)): + """Get all active goal targets.""" + profile_id = session['profile_id'] + # Returns list from goals table + # Includes: primary + all secondary goals + +@router.post("/targets") +def create_goal_target(goal: GoalCreate, session=Depends(require_auth)): + """Create a new goal target.""" + # Inserts into goals table + # Auto-calculates progress_pct + +@router.get("/targets/{goal_id}") +def get_goal_detail(goal_id: str, session=Depends(require_auth)): + """Get detailed goal info with history.""" + # Returns goal + progress history + prognosis + +@router.put("/targets/{goal_id}/progress") +def update_goal_progress(goal_id: str, session=Depends(require_auth)): + """Recalculate goal progress.""" + # Auto-called after new measurements + # Updates current_value, progress_pct + +@router.post("/targets/{goal_id}/reach") +def mark_goal_reached(goal_id: str, session=Depends(require_auth)): + """Mark goal as reached.""" + # Sets status='reached', reached_date=today +``` + +### Training Phases +```python +@router.get("/phases/current") +def get_current_phase(session=Depends(require_auth)): + """Get active training phase.""" + +@router.get("/phases/detect") +def detect_phase(session=Depends(require_auth)): + """Run phase detection algorithm.""" + # Analyzes last 14 days + # Returns suggested phase + confidence + reasoning + +@router.post("/phases/confirm") +def confirm_phase(phase_id: str, session=Depends(require_auth)): + """Confirm detected phase.""" + # Sets status='active' +``` + +### Fitness Tests +```python +@router.get("/tests/types") +def get_test_types(): + """Get all available fitness tests.""" + +@router.post("/tests/{test_type}/execute") +def record_test_result( + test_type: str, + result_value: float, + result_unit: str, + session=Depends(require_auth) +): + """Record a fitness test result.""" + # Inserts into fitness_tests + # Auto-calculates norm_category, percentile, improvement + +@router.get("/tests/due") +def get_due_tests(session=Depends(require_auth)): + """Get tests that are due for retesting.""" +``` + +--- + +## 5. Neue KI-Platzhalter (kombiniert aus beiden Konzepten) + +### Strategic (aus v2) +```python +{{goal_mode}} # "weight_loss" +{{goal_mode_label}} # "Gewichtsreduktion" +{{goal_mode_description}} # "Fettabbau bei Erhalt der Magermasse" +``` + +### Tactical - Primary Goal (aus v9e) +```python +{{primary_goal_type}} # "weight" +{{primary_goal_name}} # "Sommerfigur 2026" +{{primary_goal_target}} # "82 kg bis 30.06.2026" +{{primary_goal_current}} # "85.2 kg" +{{primary_goal_start}} # "86.1 kg" +{{primary_goal_progress_pct}} # "72%" +{{primary_goal_progress_text}} # "72% erreicht (4 kg von 5,5 kg)" +{{primary_goal_days_remaining}} # "45 Tage" +{{primary_goal_prognosis}} # "Ziel voraussichtlich in 6 Wochen erreicht (3 Wochen früher!)" +{{primary_goal_on_track}} # "true" +``` + +### Tactical - Secondary Goals (aus v9e) +```python +{{secondary_goals_count}} # "2" +{{secondary_goals_list}} # "16% Körperfett, VO2Max 55 ml/kg/min" +{{secondary_goal_1_type}} # "body_fat" +{{secondary_goal_1_progress}} # "45%" +``` + +### Training Phase (aus v9e) +```python +{{current_phase}} # "calorie_deficit" +{{current_phase_label}} # "Kaloriendefizit" +{{phase_since}} # "seit 14 Tagen" +{{phase_confidence}} # "0.92" +{{phase_recommendation}} # "Krafttraining erhalten, Cardio moderat, Proteinzufuhr 2g/kg" +{{phase_detected_automatically}} # "true" +``` + +### Fitness Tests (aus v9e) +```python +{{test_last_cooper}} # "2.800m (VO2Max ~52) vor 3 Wochen" +{{test_last_cooper_date}} # "2026-03-05" +{{test_last_cooper_result}} # "2800" +{{test_last_cooper_vo2max}} # "52.3" +{{test_last_cooper_category}} # "good" +{{test_due_list}} # "Sit & Reach (seit 5 Wochen), Liegestütze (seit 4 Wochen)" +{{test_next_recommended}} # "Cooper-Test (in 2 Wochen fällig)" +{{fitness_score_overall}} # "72/100" +{{fitness_score_endurance}} # "good" +{{fitness_score_strength}} # "average" +{{fitness_score_flexibility}} # "needs_improvement" +``` + +### GESAMT: 35+ neue Platzhalter aus v9e +Plus die 84 aus v2 = **120+ neue Platzhalter total** + +--- + +## 6. Überarbeitete Implementierungs-Roadmap + +### Phase 0a: Minimal Goal System (3-4h) ⭐ **JETZT** + +**Strategic Layer:** +- DB: `goal_mode` in profiles +- Backend: GOAL_MODES aus v2 +- API: GET/SET goal mode +- UI: Goal Mode Selector (5 Modi) + +**Tactical Layer:** +- DB: `goals` table +- API: CRUD für goal targets +- UI: Goal Management Page (minimal) + - Liste aktiver Ziele + - Fortschrittsbalken + - "+ Neues Ziel" Button + +**Aufwand:** 3-4h (erweitert wegen Tactical Layer) + +--- + +### Phase 0b: Goal-Aware Placeholders (16-20h) + +**Strategic Placeholders:** +```python +{{goal_mode}} # Aus profiles.goal_mode +{{goal_mode_label}} # Aus GOAL_MODES mapping +``` + +**Tactical Placeholders:** +```python +{{primary_goal_type}} # Aus goals WHERE is_primary=true +{{primary_goal_target}} +{{primary_goal_progress_pct}} +{{primary_goal_prognosis}} # Berechnet aus Trend +``` + +**Score Calculations (goal-aware):** +```python +def get_body_progress_score(profile_id: str) -> str: + profile = get_profile_data(profile_id) + goal_mode = profile.get('goal_mode', 'health') + + # Get weights from v2 concept + weights = GOAL_MODES[goal_mode]['score_weights'] + + # Calculate sub-scores + fm_score = calculate_fm_progress(profile_id) + lbm_score = calculate_lbm_progress(profile_id) + + # Weight according to goal mode + if goal_mode == 'weight_loss': + total = 0.50 * fm_score + 0.30 * weight_score + 0.20 * lbm_score + elif goal_mode == 'strength': + total = 0.60 * lbm_score + 0.30 * fm_score + 0.10 * weight_score + # ... + + return f"{int(total)}/100" +``` + +--- + +### Phase 0c: Training Phases (4-6h) **PARALLEL** + +**DB:** +- `training_phases` table + +**Detection Algorithm:** +```python +def detect_current_phase(profile_id: str) -> dict: + """Detects training phase from last 14 days of data.""" + + # Analyze data + kcal_balance = get_kcal_balance_14d(profile_id) + training_dist = get_training_distribution_14d(profile_id) + weight_trend = get_weight_trend_14d(profile_id) + hrv_avg = get_hrv_avg_14d(profile_id) + volume_change = get_volume_change_14d(profile_id) + + # Phase Detection Rules + if kcal_balance < -300 and weight_trend < 0: + return { + 'phase': 'calorie_deficit', + 'confidence': 0.85, + 'reason': f'Avg kcal balance {kcal_balance}/day, weight -0.5kg/week' + } + + if training_dist['endurance'] > 60 and vo2max_trend > 0: + return { + 'phase': 'conditioning', + 'confidence': 0.78, + 'reason': f'{training_dist["endurance"]}% cardio, VO2max improving' + } + + if volume_change < -40 and hrv_avg < hrv_baseline * 0.85: + return { + 'phase': 'regeneration', + 'confidence': 0.92, + 'reason': f'Volume -40%, HRV below baseline, recovery needed' + } + + # Default + return { + 'phase': 'maintenance', + 'confidence': 0.50, + 'reason': 'No clear pattern detected' + } +``` + +**API:** +- GET /phases/current +- GET /phases/detect +- POST /phases/confirm + +**UI:** +- Dashboard Badge: "📊 Phase: Kaloriendefizit" +- Phase Detection Banner: "Wir haben erkannt: Kaloriendefizit-Phase. Stimmt das?" + +--- + +### Phase 0d: Fitness Tests (4-6h) **SPÄTER** + +**DB:** +- `fitness_tests` table + +**Test Definitions:** +```python +FITNESS_TESTS = { + 'cooper': { + 'name': 'Cooper-Test', + 'description': '12 Minuten laufen, maximale Distanz', + 'unit': 'meters', + 'interval_weeks': 6, + 'norm_tables': { # Simplified + 'male_30-39': {'very_good': 2800, 'good': 2500, 'average': 2200}, + 'female_30-39': {'very_good': 2500, 'good': 2200, 'average': 1900} + }, + 'calculate_vo2max': lambda distance: (distance - 504.9) / 44.73 + }, + 'pushups': { + 'name': 'Liegestütze-Test', + 'description': 'Maximale Anzahl ohne Pause', + 'unit': 'reps', + 'interval_weeks': 4, + 'norm_tables': { ... } + }, + # ... weitere Tests +} +``` + +**UI:** +- Tests Page mit Testliste +- Test Execution Flow (Anleitung → Eingabe → Auswertung) +- Test History mit Trend-Chart + +--- + +## 7. Priorisierte Reihenfolge + +### SOFORT (3-4h) +**Phase 0a:** Minimal Goal System (Strategic + Tactical) +- Basis für alles andere +- User kann Ziele setzen +- Score-Berechnungen können goal_mode nutzen + +### DIESE WOCHE (16-20h) +**Phase 0b:** Goal-Aware Placeholders +- 84 Platzhalter aus v2 +- 35+ Platzhalter aus v9e +- **TOTAL: 120+ Platzhalter** + +### PARALLEL (4-6h) +**Phase 0c:** Training Phases +- Automatische Erkennung +- Phase-aware Recommendations + +### SPÄTER (4-6h) +**Phase 0d:** Fitness Tests +- Enhancement, nicht kritisch für Charts + +--- + +## 8. Kritische Erkenntnisse + +### 1. GOALS_VITALS.md ist detaillierter +- Konkrete Implementierungs-Specs +- DB-Schema-Vorschläge +- 13 definierte KI-Platzhalter +- **ABER:** Fehlt Score-Gewichtung (das hat v2) + +### 2. Konzept v2 ist strategischer +- Goal Modes mit Score-Gewichtung +- Chart-Interpretationen +- Regelbasierte Logik +- **ABER:** Fehlt konkrete Ziel-Tracking (das hat v9e) + +### 3. Beide zusammen = Vollständig +- v2 (Goal Modes) + v9e (Goal Targets) = Komplettes Zielesystem +- v2 (Scores) + v9e (Tests) = Vollständiges Assessment +- v2 (Charts) + v9e (Phases) = Kontext-aware Visualisierung + +### 4. Meine ursprüngliche Analyse war incomplete +- Ich hatte nur v2 betrachtet +- v9e fügt kritische Details hinzu +- **Neue Gesamt-Schätzung:** 120+ Platzhalter (statt 84) + +--- + +## 9. Aktualisierte Empfehlung + +**JA zu Phase 0a (Minimal Goal System), ABER erweitert:** + +### Was Phase 0a umfassen muss (3-4h): + +1. **Strategic Layer (aus v2):** + - goal_mode in profiles + - GOAL_MODES Definition + - GET/SET endpoints + +2. **Tactical Layer (aus v9e):** + - goals Tabelle + - CRUD für Ziele + - Fortschritts-Berechnung + +3. **UI:** + - Goal Mode Selector (Settings) + - Goal Management Page (Basic) + - Dashboard Goal Widget + +### Was kann warten: +- Training Phases → Phase 0c (parallel) +- Fitness Tests → Phase 0d (später) +- Vollständige Test-Integration → v9f + +--- + +## 10. Nächste Schritte + +**JETZT:** +1. Phase 0a implementieren (3-4h) + - Strategic + Tactical Goal System +2. Dann Phase 0b (Goal-Aware Placeholders, 16-20h) +3. Parallel Phase 0c (Training Phases, 4-6h) + +**Soll ich mit Phase 0a (erweitert) starten?** +- Beide Goal-Konzepte integriert +- Ready für 120+ Platzhalter +- Basis für intelligentes Coach-System + +**Commit:** ae93b9d (muss aktualisiert werden) +**Neue Analyse:** GOALS_SYSTEM_UNIFIED_ANALYSIS.md diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index f1374a1..01d5139 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -35,6 +35,7 @@ import SubscriptionPage from './pages/SubscriptionPage' import SleepPage from './pages/SleepPage' import RestDaysPage from './pages/RestDaysPage' import VitalsPage from './pages/VitalsPage' +import GoalsPage from './pages/GoalsPage' import './app.css' function Nav() { @@ -172,6 +173,7 @@ function AppShell() { }/> }/> }/> + }/> }/> }/> }/> diff --git a/frontend/src/pages/Dashboard.jsx b/frontend/src/pages/Dashboard.jsx index 35462f5..bee4249 100644 --- a/frontend/src/pages/Dashboard.jsx +++ b/frontend/src/pages/Dashboard.jsx @@ -497,6 +497,21 @@ export default function Dashboard() { )} + {/* Goals Preview */} +
nav('/goals')}> +
+
🎯 Ziele
+ +
+
+ Definiere deine Trainingsmodus und konkrete Ziele für bessere KI-Analysen +
+
+ {/* Latest AI insight */}
diff --git a/frontend/src/pages/GoalsPage.jsx b/frontend/src/pages/GoalsPage.jsx new file mode 100644 index 0000000..d24d35f --- /dev/null +++ b/frontend/src/pages/GoalsPage.jsx @@ -0,0 +1,562 @@ +import { useState, useEffect } from 'react' +import { Target, Plus, Pencil, Trash2, TrendingUp, Calendar } from 'lucide-react' +import { api } from '../utils/api' +import dayjs from 'dayjs' +import 'dayjs/locale/de' +dayjs.locale('de') + +// Goal Mode Definitions +const GOAL_MODES = [ + { + id: 'weight_loss', + icon: '📉', + label: 'Gewichtsreduktion', + description: 'Kaloriendefizit, Fettabbau', + color: '#D85A30' + }, + { + id: 'strength', + icon: '💪', + label: 'Kraftaufbau', + description: 'Muskelwachstum, progressive Belastung', + color: '#378ADD' + }, + { + id: 'endurance', + icon: '🏃', + label: 'Ausdauer', + description: 'VO2Max, aerobe Kapazität', + color: '#1D9E75' + }, + { + id: 'recomposition', + icon: '⚖️', + label: 'Körperkomposition', + description: 'Gleichzeitig Fett ab- & Muskeln aufbauen', + color: '#7B68EE' + }, + { + id: 'health', + icon: '❤️', + label: 'Allgemeine Gesundheit', + description: 'Ausgewogen, präventiv', + color: '#E67E22' + } +] + +// Goal Type Definitions +const GOAL_TYPES = { + weight: { label: 'Gewicht', unit: 'kg', icon: '⚖️' }, + body_fat: { label: 'Körperfett', unit: '%', icon: '📊' }, + lean_mass: { label: 'Muskelmasse', unit: 'kg', icon: '💪' }, + vo2max: { label: 'VO2Max', unit: 'ml/kg/min', icon: '🫁' }, + strength: { label: 'Kraft', unit: 'kg', icon: '🏋️' }, + flexibility: { label: 'Beweglichkeit', unit: 'cm', icon: '🤸' }, + bp: { label: 'Blutdruck', unit: 'mmHg', icon: '❤️' }, + rhr: { label: 'Ruhepuls', unit: 'bpm', icon: '💓' } +} + +export default function GoalsPage() { + const [goalMode, setGoalMode] = useState(null) + const [goals, setGoals] = useState([]) + const [showGoalForm, setShowGoalForm] = useState(false) + const [editingGoal, setEditingGoal] = useState(null) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + const [toast, setToast] = useState(null) + + // Form state + const [formData, setFormData] = useState({ + goal_type: 'weight', + is_primary: false, + target_value: '', + unit: 'kg', + target_date: '', + name: '', + description: '' + }) + + useEffect(() => { + loadData() + }, []) + + const loadData = async () => { + setLoading(true) + setError(null) + try { + const [modeData, goalsData] = await Promise.all([ + api.getGoalMode(), + api.listGoals() + ]) + setGoalMode(modeData.goal_mode) + setGoals(goalsData) + } catch (err) { + console.error('Failed to load goals:', err) + setError('Fehler beim Laden der Ziele') + } finally { + setLoading(false) + } + } + + const showToast = (message, duration = 2000) => { + setToast(message) + setTimeout(() => setToast(null), duration) + } + + const handleGoalModeChange = async (newMode) => { + try { + await api.updateGoalMode(newMode) + setGoalMode(newMode) + showToast('✓ Trainingsmodus aktualisiert') + } catch (err) { + console.error('Failed to update goal mode:', err) + setError('Fehler beim Aktualisieren des Trainingsmodus') + } + } + + const handleCreateGoal = () => { + setEditingGoal(null) + setFormData({ + goal_type: 'weight', + is_primary: goals.length === 0, // First goal is primary by default + target_value: '', + unit: GOAL_TYPES['weight'].unit, + target_date: '', + name: '', + description: '' + }) + setShowGoalForm(true) + } + + const handleEditGoal = (goal) => { + setEditingGoal(goal.id) + setFormData({ + goal_type: goal.goal_type, + is_primary: goal.is_primary, + target_value: goal.target_value, + unit: goal.unit, + target_date: goal.target_date || '', + name: goal.name || '', + description: goal.description || '' + }) + setShowGoalForm(true) + } + + const handleGoalTypeChange = (type) => { + setFormData(f => ({ + ...f, + goal_type: type, + unit: GOAL_TYPES[type].unit + })) + } + + const handleSaveGoal = async () => { + if (!formData.target_value) { + setError('Bitte Zielwert eingeben') + return + } + + try { + const data = { + goal_type: formData.goal_type, + is_primary: formData.is_primary, + target_value: parseFloat(formData.target_value), + unit: formData.unit, + target_date: formData.target_date || null, + name: formData.name || null, + description: formData.description || null + } + + if (editingGoal) { + await api.updateGoal(editingGoal, data) + showToast('✓ Ziel aktualisiert') + } else { + await api.createGoal(data) + showToast('✓ Ziel erstellt') + } + + await loadData() + setShowGoalForm(false) + setEditingGoal(null) + } catch (err) { + console.error('Failed to save goal:', err) + setError(err.message || 'Fehler beim Speichern') + } + } + + const handleDeleteGoal = async (goalId) => { + if (!confirm('Ziel wirklich löschen?')) return + + try { + await api.deleteGoal(goalId) + showToast('✓ Ziel gelöscht') + await loadData() + } catch (err) { + console.error('Failed to delete goal:', err) + setError('Fehler beim Löschen') + } + } + + const getProgressColor = (progress) => { + if (progress >= 100) return 'var(--accent)' + if (progress >= 75) return '#1D9E75' + if (progress >= 50) return '#378ADD' + if (progress >= 25) return '#E67E22' + return '#D85A30' + } + + if (loading) { + return ( +
+
+
+
+
+ ) + } + + return ( +
+
+

Ziele

+
+ + {error && ( +
+

{error}

+
+ )} + + {toast && ( +
+ {toast} +
+ )} + + {/* Strategic Goal Mode Selection */} +
+

🎯 Trainingsmodus

+

+ Wähle deine grundlegende Trainingsausrichtung. Dies beeinflusst die Gewichtung + und Interpretation aller Analysen. +

+ +
+ {GOAL_MODES.map(mode => ( + + ))} +
+
+ + {/* Tactical Goals List */} +
+
+

🎯 Konkrete Ziele

+ +
+ + {goals.length === 0 ? ( +
+ +

Noch keine Ziele definiert

+ +
+ ) : ( +
+ {goals.map(goal => { + const typeInfo = GOAL_TYPES[goal.goal_type] || {} + return ( +
+
+
+
+ {typeInfo.icon} + + {goal.name || typeInfo.label} + + {goal.is_primary && ( + + PRIMÄR + + )} + + {goal.status === 'active' ? 'AKTIV' : goal.status?.toUpperCase()} + +
+ +
+
+ Start:{' '} + {goal.start_value} {goal.unit} +
+
+ Aktuell:{' '} + {goal.current_value || '—'} {goal.unit} +
+
+ Ziel:{' '} + {goal.target_value} {goal.unit} +
+ {goal.target_date && ( +
+ + {dayjs(goal.target_date).format('DD.MM.YYYY')} +
+ )} +
+ + {goal.progress_pct !== null && ( +
+
+ Fortschritt + {goal.progress_pct}% +
+
+
+
+ + {goal.on_track !== null && ( +
+ {goal.on_track ? ( + + ✓ Ziel voraussichtlich erreichbar bis {dayjs(goal.target_date).format('DD.MM.YYYY')} + + ) : ( + + ⚠ Prognose: {goal.projection_date ? dayjs(goal.projection_date).format('DD.MM.YYYY') : 'Offen'} + {goal.target_date && ' (später als geplant)'} + + )} +
+ )} +
+ )} +
+ +
+ + +
+
+
+ ) + })} +
+ )} +
+ + {/* Goal Form Modal */} + {showGoalForm && ( +
+
+

+ {editingGoal ? 'Ziel bearbeiten' : 'Neues Ziel'} +

+ +
+
+ + +
+ +
+ + setFormData(f => ({ ...f, name: e.target.value }))} + placeholder="z.B. Sommerfigur 2026" + /> +
+ +
+ +
+ setFormData(f => ({ ...f, target_value: e.target.value }))} + placeholder="Zielwert" + /> + +
+
+ +
+ + setFormData(f => ({ ...f, target_date: e.target.value }))} + /> +
+ +
+ +