mitai-jinkendo/docs/phase-0c-placeholder-migration-analysis.md
Lars fb6d37ecfd
All checks were successful
Deploy Development / deploy (push) Successful in 43s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 13s
Neue Docs
2026-03-28 21:47:35 +01:00

16 KiB
Raw Blame History

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)