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

423 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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)