# Phase 0c: Placeholder Migration Analysis

**Erstellt:** 28. März 2026
**Zweck:** Analyse welche Platzhalter zu Data Layer migriert werden müssen

---

## Gesamt-Übersicht

**Aktuelle Platzhalter:** 114 (PLACEHOLDER_MAP / Registry)
**Nach Phase 0c Migration:**
- ✅ **Bleiben einfach (kein Data Layer):** 8 Platzhalter
- 🔄 **Gehen zu Data Layer:** 108 Platzhalter

---

## Kategorisierung: BLEIBEN EINFACH (8 Platzhalter)

Diese Platzhalter bleiben im KI Layer (placeholder_resolver.py) weil sie:
- Keine Berechnungen durchführen
- Keine Daten-Aggregation benötigen
- Einfache Getter oder Konstanten sind

### Zeitraum (4 Platzhalter)
```python
'{{datum_heute}}': lambda pid: datetime.now().strftime('%d.%m.%Y')
'{{zeitraum_7d}}': lambda pid: 'letzte 7 Tage'
'{{zeitraum_30d}}': lambda pid: 'letzte 30 Tage'
'{{zeitraum_90d}}': lambda pid: 'letzte 90 Tage'
```
**Begründung:** Konstanten oder einfache Datum-Formatierung. Kein Data Layer nötig.

### Profil - Basis (4 Platzhalter)
```python
'{{name}}': lambda pid: get_profile_data(pid).get('name', 'Nutzer')
'{{age}}': lambda pid: calculate_age(get_profile_data(pid).get('dob'))
'{{height}}': lambda pid: str(get_profile_data(pid).get('height', 'unbekannt'))
'{{geschlecht}}': lambda pid: 'männlich' if get_profile_data(pid).get('sex') == 'm' else 'weiblich'
```
**Begründung:** Direkte Getter aus profiles Tabelle. Keine Aggregation.

---

## GEHEN ZU DATA LAYER (108 Platzhalter)

### 1. Körper (20 Platzhalter) → `data_layer.body_metrics`

#### Basis-Metriken (8):
```python
'{{weight_aktuell}}'        → get_weight_trend_data()['last_value']
'{{weight_trend}}'          → get_weight_trend_data() (formatiert)
'{{kf_aktuell}}'            → get_body_composition_data()['body_fat_pct'][-1]
'{{bmi}}'                   → get_body_composition_data() (berechnet)
'{{caliper_summary}}'       → get_caliper_summary_data()
'{{circ_summary}}'          → get_circumference_summary()
'{{goal_weight}}'           → get_active_goals() (filtered)
'{{goal_bf_pct}}'           → get_active_goals() (filtered)
```

#### Phase 0b - Advanced Body (12):
```python
'{{weight_7d_median}}'      → get_weight_trend_data()['rolling_median_7d'][-1]
'{{weight_28d_slope}}'      → get_weight_trend_data()['slope_28d']
'{{weight_90d_slope}}'      → get_weight_trend_data()['slope_90d']
'{{fm_28d_change}}'         → get_body_composition_data()['fm_delta_28d']
'{{lbm_28d_change}}'        → get_body_composition_data()['lbm_delta_28d']
'{{waist_28d_delta}}'       → get_circumference_summary()['changes']['waist_28d']
'{{hip_28d_delta}}'         → get_circumference_summary()['changes']['hip_28d']
'{{chest_28d_delta}}'       → get_circumference_summary()['changes']['chest_28d']
'{{arm_28d_delta}}'         → get_circumference_summary()['changes']['arm_28d']
'{{thigh_28d_delta}}'       → get_circumference_summary()['changes']['thigh_28d']
'{{waist_hip_ratio}}'       → get_circumference_summary()['ratios']['waist_to_hip']
'{{recomposition_quadrant}}'→ get_body_composition_data()['recomposition_score']
```

**Data Layer Funktionen benötigt:**
- `get_weight_trend_data(profile_id, days=90)`
- `get_body_composition_data(profile_id, days=90)`
- `get_circumference_summary(profile_id, days=90)`
- `get_caliper_summary_data(profile_id, days=90)`

---

### 2. Ernährung (14 Platzhalter) → `data_layer.nutrition_metrics`

#### Basis-Metriken (7):
```python
'{{kcal_avg}}'              → get_energy_balance_data()['avg_intake']
'{{protein_avg}}'           → get_protein_adequacy_data()['avg_protein_g']
'{{carb_avg}}'              → get_macro_distribution_data()['avg_carbs_g']
'{{fat_avg}}'               → get_macro_distribution_data()['avg_fat_g']
'{{nutrition_days}}'        → get_energy_balance_data()['data_points']
'{{protein_ziel_low}}'      → get_protein_adequacy_data()['target_protein_g'] (low)
'{{protein_ziel_high}}'     → get_protein_adequacy_data()['target_protein_g'] (high)
```

#### Phase 0b - Advanced Nutrition (7):
```python
'{{energy_balance_7d}}'     → get_energy_balance_data()['avg_net']
'{{energy_deficit_surplus}}'→ get_energy_balance_data()['deficit_surplus_avg']
'{{protein_g_per_kg}}'      → get_protein_adequacy_data()['avg_protein_per_kg']
'{{protein_days_in_target}}'→ get_protein_adequacy_data()['adherence_pct']
'{{protein_adequacy_28d}}'  → get_protein_adequacy_data()['adherence_score']
'{{macro_consistency_score}}'→ get_macro_distribution_data()['balance_score']
'{{intake_volatility}}'     → get_macro_distribution_data()['variability']
```

**Data Layer Funktionen benötigt:**
- `get_protein_adequacy_data(profile_id, days=28, goal_mode=None)`
- `get_energy_balance_data(profile_id, days=28)`
- `get_macro_distribution_data(profile_id, days=28)`

---

### 3. Training (16 Platzhalter) → `data_layer.activity_metrics`

#### Basis-Metriken (3):
```python
'{{activity_summary}}'      → get_training_volume_data()['weekly_totals'] (formatted)
'{{activity_detail}}'       → get_training_volume_data()['by_type'] (formatted)
'{{trainingstyp_verteilung}}'→ get_activity_quality_distribution()
```

#### Phase 0b - Advanced Activity (13):
```python
'{{training_minutes_week}}' → get_training_volume_data()['weekly_totals'][0]['duration_min']
'{{training_frequency_7d}}' → get_training_volume_data()['weekly_totals'][0]['sessions']
'{{quality_sessions_pct}}'  → get_activity_quality_distribution()['high_quality_pct']
'{{ability_balance_strength}}' → get_ability_balance_data()['abilities']['strength']
'{{ability_balance_endurance}}'→ get_ability_balance_data()['abilities']['cardio']
'{{ability_balance_mental}}'   → get_ability_balance_data()['abilities']['mental']
'{{ability_balance_coordination}}'→ get_ability_balance_data()['abilities']['coordination']
'{{ability_balance_mobility}}'  → get_ability_balance_data()['abilities']['mobility']
'{{proxy_internal_load_7d}}'→ get_training_volume_data()['strain']
'{{monotony_score}}'        → get_training_volume_data()['monotony']
'{{strain_score}}'          → get_training_volume_data()['strain']
'{{rest_day_compliance}}'   → get_recovery_score_data()['components']['rest_compliance']['score']
'{{vo2max_trend_28d}}'      → get_vitals_baseline_data()['vo2_max']['trend']
```

**Data Layer Funktionen benötigt:**
- `get_training_volume_data(profile_id, weeks=4)`
- `get_activity_quality_distribution(profile_id, days=28)`
- `get_ability_balance_data(profile_id, weeks=4)`

---

### 4. Schlaf & Erholung (10 Platzhalter) → `data_layer.recovery_metrics`

#### Basis-Metriken (3):
```python
'{{sleep_avg_duration}}'    → get_sleep_regularity_data()['avg_duration_h']
'{{sleep_avg_quality}}'     → get_sleep_regularity_data()['avg_quality']
'{{rest_days_count}}'       → get_recovery_score_data()['components']['rest_compliance']['rest_days']
```

#### Phase 0b - Advanced Recovery (7):
```python
'{{hrv_vs_baseline_pct}}'   → get_vitals_baseline_data()['hrv']['deviation_pct']
'{{rhr_vs_baseline_pct}}'   → get_vitals_baseline_data()['rhr']['deviation_pct']
'{{sleep_avg_duration_7d}}' → get_sleep_regularity_data()['avg_duration_h']
'{{sleep_debt_hours}}'      → get_sleep_regularity_data()['sleep_debt_h']
'{{sleep_regularity_proxy}}'→ get_sleep_regularity_data()['regularity_score']
'{{recent_load_balance_3d}}'→ get_recovery_score_data()['load_balance']
'{{sleep_quality_7d}}'      → get_sleep_regularity_data()['avg_quality']
```

**Data Layer Funktionen benötigt:**
- `get_recovery_score_data(profile_id, days=7)`
- `get_sleep_regularity_data(profile_id, days=28)`
- `get_vitals_baseline_data(profile_id, days=7)`

---

### 5. Vitalwerte (3 Platzhalter) → `data_layer.health_metrics`

```python
'{{vitals_avg_hr}}'         → get_vitals_baseline_data()['rhr']['current']
'{{vitals_avg_hrv}}'        → get_vitals_baseline_data()['hrv']['current']
'{{vitals_vo2_max}}'        → get_vitals_baseline_data()['vo2_max']['current']
```

**Data Layer Funktionen benötigt:**
- `get_vitals_baseline_data(profile_id, days=7)` (bereits in recovery)

---

### 6. Scores (6 Platzhalter) → Diverse Module

```python
'{{goal_progress_score}}'   → get_goal_progress_data() → goals.py
'{{body_progress_score}}'   → get_body_composition_data() → body_metrics.py
'{{nutrition_score}}'       → get_protein_adequacy_data() → nutrition_metrics.py
'{{activity_score}}'        → get_training_volume_data() → activity_metrics.py
'{{recovery_score}}'        → get_recovery_score_data()['score'] → recovery_metrics.py
'{{data_quality_score}}'    → get_data_quality_score() → utils.py (NEW)
```

**Hinweis:** Scores nutzen bestehende Data Layer Funktionen, nur Formatierung nötig.

---

### 7. Top Goals/Focus (5 Platzhalter) → `data_layer.goals`

```python
'{{top_goal_name}}'         → get_active_goals()[0]['name']
'{{top_goal_progress_pct}}' → get_active_goals()[0]['progress_pct']
'{{top_goal_status}}'       → get_active_goals()[0]['status']
'{{top_focus_area_name}}'   → get_weighted_focus_areas()[0]['name']
'{{top_focus_area_progress}}'→ get_weighted_focus_areas()[0]['progress']
```

**Data Layer Funktionen benötigt:**
- `get_active_goals(profile_id)` (already exists from Phase 0b)
- `get_weighted_focus_areas(profile_id)` (already exists from Phase 0b)

---

### 8. Category Scores (14 Platzhalter) → Formatierung nur

```python
'{{focus_cat_körper_progress}}'     → _format_from_aggregated_data()
'{{focus_cat_körper_weight}}'       → _format_from_aggregated_data()
'{{focus_cat_ernährung_progress}}'  → _format_from_aggregated_data()
'{{focus_cat_ernährung_weight}}'    → _format_from_aggregated_data()
# ... (7 Kategorien × 2 = 14 total)
```

**Hinweis:** Diese nutzen bereits aggregierte Daten aus Phase 0b.
**Migration:** Nur KI Layer Formatierung, Data Layer nicht nötig (Daten kommen aus anderen Funktionen).

---

### 9. Korrelationen (7 Platzhalter) → `data_layer.correlations`

```python
'{{correlation_energy_weight_lag}}'  → get_correlation_data(pid, 'energy', 'weight')
'{{correlation_protein_lbm}}'        → get_correlation_data(pid, 'protein', 'lbm')
'{{correlation_load_hrv}}'           → get_correlation_data(pid, 'load', 'hrv')
'{{correlation_load_rhr}}'           → get_correlation_data(pid, 'load', 'rhr')
'{{correlation_sleep_recovery}}'     → get_correlation_data(pid, 'sleep', 'recovery')
'{{plateau_detected}}'               → detect_plateau(pid, 'weight')
'{{top_drivers}}'                    → get_top_drivers(pid)
```

**Data Layer Funktionen benötigt:**
- `get_correlation_data(profile_id, metric_a, metric_b, days=90, max_lag=7)`
- `detect_plateau(profile_id, metric, days=28)`
- `get_top_drivers(profile_id)` (NEW - identifies top correlations)

---

### 10. JSON/Markdown (8 Platzhalter) → Formatierung nur

```python
'{{active_goals_json}}'             → json.dumps(get_active_goals(pid))
'{{active_goals_md}}'               → format_as_markdown(get_active_goals(pid))
'{{focus_areas_weighted_json}}'     → json.dumps(get_weighted_focus_areas(pid))
'{{focus_areas_weighted_md}}'       → format_as_markdown(get_weighted_focus_areas(pid))
'{{focus_area_weights_json}}'       → json.dumps(get_focus_area_weights(pid))
'{{top_3_focus_areas}}'             → format_top_3(get_weighted_focus_areas(pid))
'{{top_3_goals_behind_schedule}}'   → format_goals_behind(get_active_goals(pid))
'{{top_3_goals_on_track}}'          → format_goals_on_track(get_active_goals(pid))
```

**Hinweis:** Diese nutzen bereits existierende Data Layer Funktionen.
**Migration:** Nur KI Layer Formatierung (json.dumps, markdown, etc.).

---

## Data Layer Funktionen - Zusammenfassung

### Neue Funktionen zu erstellen (Phase 0c):

#### body_metrics.py (4 Funktionen):
- ✅ `get_weight_trend_data()`
- ✅ `get_body_composition_data()`
- ✅ `get_circumference_summary()`
- ✅ `get_caliper_summary_data()`

#### nutrition_metrics.py (3 Funktionen):
- ✅ `get_protein_adequacy_data()`
- ✅ `get_energy_balance_data()`
- ✅ `get_macro_distribution_data()`

#### activity_metrics.py (3 Funktionen):
- ✅ `get_training_volume_data()`
- ✅ `get_activity_quality_distribution()`
- ✅ `get_ability_balance_data()`

#### recovery_metrics.py (2 Funktionen):
- ✅ `get_recovery_score_data()`
- ✅ `get_sleep_regularity_data()`

#### health_metrics.py (2 Funktionen):
- ✅ `get_vitals_baseline_data()`
- ✅ `get_blood_pressure_data()` (aus Spec)

#### goals.py (3 Funktionen):
- ✅ `get_active_goals()` (exists from Phase 0b)
- ✅ `get_weighted_focus_areas()` (exists from Phase 0b)
- ✅ `get_goal_progress_data()` (aus Spec)

#### correlations.py (3 Funktionen):
- ✅ `get_correlation_data()`
- ✅ `detect_plateau()`
- 🆕 `get_top_drivers()` (NEW - not in spec)

#### utils.py (Shared):
- ✅ `calculate_confidence()`
- ✅ `calculate_baseline()`
- ✅ `detect_outliers()`
- ✅ `aggregate_data()`
- ✅ `serialize_dates()`
- 🆕 `get_data_quality_score()` (NEW)

**Total neue Funktionen:** 20 (aus Spec) + 2 (zusätzlich) = **22 Data Layer Funktionen**

---

## Migration-Aufwand pro Kategorie

| Kategorie | Platzhalter | Data Layer Funcs | Aufwand | Priorität |
|-----------|-------------|------------------|---------|-----------|
| Körper | 20 | 4 | 3-4h | High |
| Ernährung | 14 | 3 | 2-3h | High |
| Training | 16 | 3 | 3-4h | Medium |
| Recovery | 10 | 2 | 2-3h | Medium |
| Vitalwerte | 3 | 1 (shared) | 0.5h | Low |
| Scores | 6 | 0 (use others) | 1h | Low |
| Goals/Focus | 5 | 0 (exists) | 0.5h | Low |
| Categories | 14 | 0 (formatting) | 1h | Low |
| Korrelationen | 7 | 3 | 2-3h | Medium |
| JSON/Markdown | 8 | 0 (formatting) | 0.5h | Low |
| **TOTAL** | **108** | **22** | **16-22h** | - |

---

## KI Layer Refactoring-Muster

**VORHER (Phase 0b):**
```python
def get_latest_weight(profile_id: str) -> str:
    """Returns latest weight with SQL + formatting"""
    with get_db() as conn:
        cur = get_cursor(conn)
        cur.execute("""
            SELECT weight FROM weight_log
            WHERE profile_id = %s
            ORDER BY date DESC LIMIT 1
        """, (profile_id,))
        row = cur.fetchone()
        if not row:
            return "nicht verfügbar"
        return f"{row['weight']:.1f} kg"

PLACEHOLDER_MAP = {
    '{{weight_aktuell}}': get_latest_weight,
}
```

**NACHHER (Phase 0c):**
```python
from data_layer.body_metrics import get_weight_trend_data

def resolve_weight_aktuell(profile_id: str) -> str:
    """Returns latest weight (formatted for KI)"""
    data = get_weight_trend_data(profile_id, days=7)

    if data['confidence'] == 'insufficient':
        return "nicht verfügbar"

    return f"{data['last_value']:.1f} kg"

PLACEHOLDER_MAP = {
    '{{weight_aktuell}}': resolve_weight_aktuell,
}
```

**Reduzierung:** Von ~15 Zeilen (SQL + Logic) zu ~7 Zeilen (Call + Format)

---

## Erwartetes Ergebnis nach Phase 0c

### Zeilen-Reduktion:
- **placeholder_resolver.py:**
  - Vorher: ~1200 Zeilen
  - Nachher: ~400 Zeilen (67% Reduktion)

### Code-Qualität:
- ✅ Keine SQL queries in placeholder_resolver.py
- ✅ Keine Berechnungslogik in placeholder_resolver.py
- ✅ Nur Formatierung für KI-Consumption

### Wiederverwendbarkeit:
- ✅ 22 Data Layer Funktionen nutzbar für:
  - KI Layer (108 Platzhalter)
  - Charts Layer (10+ Charts)
  - API Endpoints (beliebig erweiterbar)

---

## Checkliste: Migration pro Platzhalter

Für jeden der **108 Platzhalter**:

```
[ ] Data Layer Funktion existiert
[ ] KI Layer ruft Data Layer Funktion auf
[ ] Formatierung für KI korrekt
[ ] Fehlerbehandlung (insufficient data)
[ ] Test: Platzhalter liefert gleichen Output wie vorher
[ ] In PLACEHOLDER_MAP registriert
[ ] Dokumentiert
```

---

**Erstellt:** 28. März 2026
**Status:** Ready for Phase 0c Implementation
**Nächster Schritt:** Data Layer Funktionen implementieren (Start mit utils.py)
