2026-03-28 21:47:35 +01:00
|
|
|
|
# Phase 0c: Placeholder Migration Analysis
|
|
|
|
|
|
|
|
|
|
|
|
**Erstellt:** 28. März 2026
|
|
|
|
|
|
**Zweck:** Analyse welche Platzhalter zu Data Layer migriert werden müssen
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Gesamt-Übersicht
|
|
|
|
|
|
|
2026-04-11 21:11:05 +02:00
|
|
|
|
**Aktuelle Platzhalter:** 114 (PLACEHOLDER_MAP / Registry)
|
2026-03-28 21:47:35 +01:00
|
|
|
|
**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)
|