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 ( +
+ {goalModes[selectedMode]?.description} +
+