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

16 KiB
Raw Permalink Blame History

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)

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:

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:

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:

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:

-- 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:

# 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:

# 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):

// 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:
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?