16 KiB
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)
'{{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)
'{{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):
'{{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):
'{{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):
'{{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):
'{{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):
'{{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):
'{{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):
'{{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):
'{{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
'{{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
'{{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
'{{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
'{{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
'{{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
'{{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):
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):
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)