mitai-jinkendo/.claude/docs/working/phase-0c-placeholder-migration-analysis.md

423 lines
16 KiB
Markdown
Raw Normal View History

2026-03-28 21:47:35 +01:00
# 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)
2026-03-28 21:47:35 +01:00
**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)