mitai-jinkendo/docs/GOAL_SYSTEM_PRIORITY_ANALYSIS.md
Lars ae93b9d428
All checks were successful
Deploy Development / deploy (push) Successful in 47s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 14s
docs: goal system priority analysis - hybrid approach
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)
2026-03-26 16:08:00 +01:00

539 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 (
<div className="card">
<h2>🎯 Trainingsziel</h2>
<div className="form-row">
<label>Hauptziel</label>
<select value={selectedMode} onChange={e => setSelectedMode(e.target.value)}>
{Object.entries(goalModes).map(([key, config]) => (
<option key={key} value={key}>
{config.label}
</option>
))}
</select>
<p style={{fontSize: 12, color: 'var(--text3)'}}>
{goalModes[selectedMode]?.description}
</p>
</div>
{(selectedMode === 'weight_loss' || selectedMode === 'recomposition') && (
<div className="form-row">
<label>Zielgewicht (optional)</label>
<input type="number" step="0.1" value={goalWeight} onChange={...} />
</div>
)}
<button onClick={saveGoal}>Ziel speichern</button>
</div>
)
}
```
### 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?**