- Adjusted the total number of placeholders from 116 to 114 across various documentation and code files to reflect the current state of the system. - Enhanced TDEE calculation logic in `nutrition_metrics.py` to prioritize Mifflin–St Jeor BMR with PAL when demographic data is available, with a fallback to a weight-based estimate. - Updated placeholder registrations to ensure consistency with the new metadata structure and improved data handling. - Revised documentation to clarify the authoritative source of placeholder metadata and the implications of the changes on existing functionalities. These updates improve the accuracy and consistency of the placeholder system and enhance the nutritional assessment capabilities within the application.
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: 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)
'{{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)