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