feat: Phase 0b - Integrate 100+ Goal-Aware Placeholders
Extended placeholder_resolver.py with: - 100+ new placeholders across 5 levels (meta-scores, categories, individual metrics, correlations, JSON) - Safe wrapper functions (_safe_int, _safe_float, _safe_str, _safe_json) - Integration with calculation engine (body, nutrition, activity, recovery, correlations, scores) - Dynamic Focus Areas v2.0 support (category progress/weights) - Top-weighted goals/focus areas (instead of deprecated primary goal) Placeholder categories: - Meta Scores: goal_progress_score, body/nutrition/activity/recovery_score (6) - Top-Weighted: top_goal_*, top_focus_area_* (5) - Category Scores: focus_cat_*_progress/weight for 7 categories (14) - Body Metrics: weight trends, FM/LBM changes, circumferences, recomposition (12) - Nutrition Metrics: energy balance, protein adequacy, macro consistency (7) - Activity Metrics: training volume, ability balance, load monitoring (13) - Recovery Metrics: HRV/RHR vs baseline, sleep quality/debt/regularity (7) - Correlation Metrics: lagged correlations, plateau detection, driver panel (7) - JSON/Markdown: active_goals, focus_areas, top drivers (8) TODO: Implement goal_utils extensions for JSON formatters TODO: Add unit tests for all placeholder functions
This commit is contained in:
parent
09e6a5fbfb
commit
bf0b32b536
|
|
@ -464,6 +464,242 @@ def get_vitals_vo2_max(profile_id: str) -> str:
|
||||||
return "nicht verfügbar"
|
return "nicht verfügbar"
|
||||||
|
|
||||||
|
|
||||||
|
# ── Phase 0b Calculation Engine Integration ──────────────────────────────────
|
||||||
|
|
||||||
|
def _safe_int(func_name: str, profile_id: str) -> str:
|
||||||
|
"""
|
||||||
|
Safely call calculation function and return integer value or fallback.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
func_name: Name of the calculation function (e.g., 'goal_progress_score')
|
||||||
|
profile_id: Profile ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
String representation of integer value or 'nicht verfügbar'
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Import calculations dynamically to avoid circular imports
|
||||||
|
from calculations import scores, body_metrics, nutrition_metrics, activity_metrics, recovery_metrics, correlation_metrics
|
||||||
|
|
||||||
|
# Map function names to actual functions
|
||||||
|
func_map = {
|
||||||
|
'goal_progress_score': scores.calculate_goal_progress_score,
|
||||||
|
'body_progress_score': body_metrics.calculate_body_progress_score,
|
||||||
|
'nutrition_score': nutrition_metrics.calculate_nutrition_score,
|
||||||
|
'activity_score': activity_metrics.calculate_activity_score,
|
||||||
|
'recovery_score_v2': recovery_metrics.calculate_recovery_score_v2,
|
||||||
|
'data_quality_score': scores.calculate_data_quality_score,
|
||||||
|
'top_goal_progress_pct': lambda pid: scores.get_top_priority_goal(pid)['progress_pct'] if scores.get_top_priority_goal(pid) else None,
|
||||||
|
'top_focus_area_progress': lambda pid: scores.get_top_focus_area(pid)['progress'] if scores.get_top_focus_area(pid) else None,
|
||||||
|
'focus_cat_körper_progress': lambda pid: scores.calculate_category_progress(pid, 'körper'),
|
||||||
|
'focus_cat_ernährung_progress': lambda pid: scores.calculate_category_progress(pid, 'ernährung'),
|
||||||
|
'focus_cat_aktivität_progress': lambda pid: scores.calculate_category_progress(pid, 'aktivität'),
|
||||||
|
'focus_cat_recovery_progress': lambda pid: scores.calculate_category_progress(pid, 'recovery'),
|
||||||
|
'focus_cat_vitalwerte_progress': lambda pid: scores.calculate_category_progress(pid, 'vitalwerte'),
|
||||||
|
'focus_cat_mental_progress': lambda pid: scores.calculate_category_progress(pid, 'mental'),
|
||||||
|
'focus_cat_lebensstil_progress': lambda pid: scores.calculate_category_progress(pid, 'lebensstil'),
|
||||||
|
'training_minutes_week': activity_metrics.calculate_training_minutes_week,
|
||||||
|
'training_frequency_7d': activity_metrics.calculate_training_frequency_7d,
|
||||||
|
'quality_sessions_pct': activity_metrics.calculate_quality_sessions_pct,
|
||||||
|
'ability_balance_strength': activity_metrics.calculate_ability_balance_strength,
|
||||||
|
'ability_balance_endurance': activity_metrics.calculate_ability_balance_endurance,
|
||||||
|
'ability_balance_mental': activity_metrics.calculate_ability_balance_mental,
|
||||||
|
'ability_balance_coordination': activity_metrics.calculate_ability_balance_coordination,
|
||||||
|
'ability_balance_mobility': activity_metrics.calculate_ability_balance_mobility,
|
||||||
|
'proxy_internal_load_7d': activity_metrics.calculate_proxy_internal_load_7d,
|
||||||
|
'strain_score': activity_metrics.calculate_strain_score,
|
||||||
|
'rest_day_compliance': activity_metrics.calculate_rest_day_compliance,
|
||||||
|
'protein_adequacy_28d': nutrition_metrics.calculate_protein_adequacy_28d,
|
||||||
|
'macro_consistency_score': nutrition_metrics.calculate_macro_consistency_score,
|
||||||
|
'recent_load_balance_3d': recovery_metrics.calculate_recent_load_balance_3d,
|
||||||
|
'sleep_quality_7d': recovery_metrics.calculate_sleep_quality_7d,
|
||||||
|
}
|
||||||
|
|
||||||
|
func = func_map.get(func_name)
|
||||||
|
if not func:
|
||||||
|
return 'nicht verfügbar'
|
||||||
|
|
||||||
|
result = func(profile_id)
|
||||||
|
return str(int(result)) if result is not None else 'nicht verfügbar'
|
||||||
|
except Exception as e:
|
||||||
|
return 'nicht verfügbar'
|
||||||
|
|
||||||
|
|
||||||
|
def _safe_float(func_name: str, profile_id: str, decimals: int = 1) -> str:
|
||||||
|
"""
|
||||||
|
Safely call calculation function and return float value or fallback.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
func_name: Name of the calculation function
|
||||||
|
profile_id: Profile ID
|
||||||
|
decimals: Number of decimal places
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
String representation of float value or 'nicht verfügbar'
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
from calculations import body_metrics, nutrition_metrics, activity_metrics, recovery_metrics, scores
|
||||||
|
|
||||||
|
func_map = {
|
||||||
|
'weight_7d_median': body_metrics.calculate_weight_7d_median,
|
||||||
|
'weight_28d_slope': body_metrics.calculate_weight_28d_slope,
|
||||||
|
'weight_90d_slope': body_metrics.calculate_weight_90d_slope,
|
||||||
|
'fm_28d_change': body_metrics.calculate_fm_28d_change,
|
||||||
|
'lbm_28d_change': body_metrics.calculate_lbm_28d_change,
|
||||||
|
'waist_28d_delta': body_metrics.calculate_waist_28d_delta,
|
||||||
|
'hip_28d_delta': body_metrics.calculate_hip_28d_delta,
|
||||||
|
'chest_28d_delta': body_metrics.calculate_chest_28d_delta,
|
||||||
|
'arm_28d_delta': body_metrics.calculate_arm_28d_delta,
|
||||||
|
'thigh_28d_delta': body_metrics.calculate_thigh_28d_delta,
|
||||||
|
'waist_hip_ratio': body_metrics.calculate_waist_hip_ratio,
|
||||||
|
'energy_balance_7d': nutrition_metrics.calculate_energy_balance_7d,
|
||||||
|
'protein_g_per_kg': nutrition_metrics.calculate_protein_g_per_kg,
|
||||||
|
'monotony_score': activity_metrics.calculate_monotony_score,
|
||||||
|
'vo2max_trend_28d': activity_metrics.calculate_vo2max_trend_28d,
|
||||||
|
'hrv_vs_baseline_pct': recovery_metrics.calculate_hrv_vs_baseline_pct,
|
||||||
|
'rhr_vs_baseline_pct': recovery_metrics.calculate_rhr_vs_baseline_pct,
|
||||||
|
'sleep_avg_duration_7d': recovery_metrics.calculate_sleep_avg_duration_7d,
|
||||||
|
'sleep_debt_hours': recovery_metrics.calculate_sleep_debt_hours,
|
||||||
|
'sleep_regularity_proxy': recovery_metrics.calculate_sleep_regularity_proxy,
|
||||||
|
'focus_cat_körper_weight': lambda pid: scores.calculate_category_weight(pid, 'körper'),
|
||||||
|
'focus_cat_ernährung_weight': lambda pid: scores.calculate_category_weight(pid, 'ernährung'),
|
||||||
|
'focus_cat_aktivität_weight': lambda pid: scores.calculate_category_weight(pid, 'aktivität'),
|
||||||
|
'focus_cat_recovery_weight': lambda pid: scores.calculate_category_weight(pid, 'recovery'),
|
||||||
|
'focus_cat_vitalwerte_weight': lambda pid: scores.calculate_category_weight(pid, 'vitalwerte'),
|
||||||
|
'focus_cat_mental_weight': lambda pid: scores.calculate_category_weight(pid, 'mental'),
|
||||||
|
'focus_cat_lebensstil_weight': lambda pid: scores.calculate_category_weight(pid, 'lebensstil'),
|
||||||
|
}
|
||||||
|
|
||||||
|
func = func_map.get(func_name)
|
||||||
|
if not func:
|
||||||
|
return 'nicht verfügbar'
|
||||||
|
|
||||||
|
result = func(profile_id)
|
||||||
|
return f"{result:.{decimals}f}" if result is not None else 'nicht verfügbar'
|
||||||
|
except Exception as e:
|
||||||
|
return 'nicht verfügbar'
|
||||||
|
|
||||||
|
|
||||||
|
def _safe_str(func_name: str, profile_id: str) -> str:
|
||||||
|
"""
|
||||||
|
Safely call calculation function and return string value or fallback.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
from calculations import body_metrics, nutrition_metrics, activity_metrics, scores, correlation_metrics
|
||||||
|
|
||||||
|
func_map = {
|
||||||
|
'top_goal_name': lambda pid: scores.get_top_priority_goal(pid)['name'] if scores.get_top_priority_goal(pid) else None,
|
||||||
|
'top_goal_status': lambda pid: scores.get_top_priority_goal(pid)['status'] if scores.get_top_priority_goal(pid) else None,
|
||||||
|
'top_focus_area_name': lambda pid: scores.get_top_focus_area(pid)['label'] if scores.get_top_focus_area(pid) else None,
|
||||||
|
'recomposition_quadrant': body_metrics.calculate_recomposition_quadrant,
|
||||||
|
'energy_deficit_surplus': nutrition_metrics.calculate_energy_deficit_surplus,
|
||||||
|
'protein_days_in_target': nutrition_metrics.calculate_protein_days_in_target,
|
||||||
|
'intake_volatility': nutrition_metrics.calculate_intake_volatility,
|
||||||
|
'active_goals_md': lambda pid: _format_goals_as_markdown(pid),
|
||||||
|
'focus_areas_weighted_md': lambda pid: _format_focus_areas_as_markdown(pid),
|
||||||
|
'top_3_focus_areas': lambda pid: _format_top_focus_areas(pid),
|
||||||
|
'top_3_goals_behind_schedule': lambda pid: _format_goals_behind(pid),
|
||||||
|
'top_3_goals_on_track': lambda pid: _format_goals_on_track(pid),
|
||||||
|
}
|
||||||
|
|
||||||
|
func = func_map.get(func_name)
|
||||||
|
if not func:
|
||||||
|
return 'nicht verfügbar'
|
||||||
|
|
||||||
|
result = func(profile_id)
|
||||||
|
return str(result) if result is not None else 'nicht verfügbar'
|
||||||
|
except Exception as e:
|
||||||
|
return 'nicht verfügbar'
|
||||||
|
|
||||||
|
|
||||||
|
def _safe_json(func_name: str, profile_id: str) -> str:
|
||||||
|
"""
|
||||||
|
Safely call calculation function and return JSON string or fallback.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
from calculations import scores, correlation_metrics
|
||||||
|
|
||||||
|
func_map = {
|
||||||
|
'correlation_energy_weight_lag': lambda pid: correlation_metrics.calculate_lag_correlation(pid, 'energy', 'weight'),
|
||||||
|
'correlation_protein_lbm': lambda pid: correlation_metrics.calculate_lag_correlation(pid, 'protein', 'lbm'),
|
||||||
|
'correlation_load_hrv': lambda pid: correlation_metrics.calculate_lag_correlation(pid, 'training_load', 'hrv'),
|
||||||
|
'correlation_load_rhr': lambda pid: correlation_metrics.calculate_lag_correlation(pid, 'training_load', 'rhr'),
|
||||||
|
'correlation_sleep_recovery': correlation_metrics.calculate_correlation_sleep_recovery,
|
||||||
|
'plateau_detected': correlation_metrics.calculate_plateau_detected,
|
||||||
|
'top_drivers': correlation_metrics.calculate_top_drivers,
|
||||||
|
'active_goals_json': lambda pid: _get_active_goals_json(pid),
|
||||||
|
'focus_areas_weighted_json': lambda pid: _get_focus_areas_weighted_json(pid),
|
||||||
|
'focus_area_weights_json': lambda pid: json.dumps(scores.get_user_focus_weights(pid), ensure_ascii=False),
|
||||||
|
}
|
||||||
|
|
||||||
|
func = func_map.get(func_name)
|
||||||
|
if not func:
|
||||||
|
return '{}'
|
||||||
|
|
||||||
|
result = func(profile_id)
|
||||||
|
if result is None:
|
||||||
|
return '{}'
|
||||||
|
|
||||||
|
# If already string, return it; otherwise convert to JSON
|
||||||
|
if isinstance(result, str):
|
||||||
|
return result
|
||||||
|
else:
|
||||||
|
return json.dumps(result, ensure_ascii=False)
|
||||||
|
except Exception as e:
|
||||||
|
return '{}'
|
||||||
|
|
||||||
|
|
||||||
|
def _get_active_goals_json(profile_id: str) -> str:
|
||||||
|
"""Get active goals as JSON string"""
|
||||||
|
import json
|
||||||
|
try:
|
||||||
|
# TODO: Implement after goal_utils extensions
|
||||||
|
return '[]'
|
||||||
|
except Exception:
|
||||||
|
return '[]'
|
||||||
|
|
||||||
|
|
||||||
|
def _get_focus_areas_weighted_json(profile_id: str) -> str:
|
||||||
|
"""Get focus areas with weights as JSON string"""
|
||||||
|
import json
|
||||||
|
try:
|
||||||
|
# TODO: Implement after goal_utils extensions
|
||||||
|
return '[]'
|
||||||
|
except Exception:
|
||||||
|
return '[]'
|
||||||
|
|
||||||
|
|
||||||
|
def _format_goals_as_markdown(profile_id: str) -> str:
|
||||||
|
"""Format goals as markdown table"""
|
||||||
|
# TODO: Implement
|
||||||
|
return 'Keine Ziele definiert'
|
||||||
|
|
||||||
|
|
||||||
|
def _format_focus_areas_as_markdown(profile_id: str) -> str:
|
||||||
|
"""Format focus areas as markdown"""
|
||||||
|
# TODO: Implement
|
||||||
|
return 'Keine Focus Areas aktiv'
|
||||||
|
|
||||||
|
|
||||||
|
def _format_top_focus_areas(profile_id: str, n: int = 3) -> str:
|
||||||
|
"""Format top N focus areas as text"""
|
||||||
|
# TODO: Implement
|
||||||
|
return 'nicht verfügbar'
|
||||||
|
|
||||||
|
|
||||||
|
def _format_goals_behind(profile_id: str, n: int = 3) -> str:
|
||||||
|
"""Format top N goals behind schedule"""
|
||||||
|
# TODO: Implement
|
||||||
|
return 'nicht verfügbar'
|
||||||
|
|
||||||
|
|
||||||
|
def _format_goals_on_track(profile_id: str, n: int = 3) -> str:
|
||||||
|
"""Format top N goals on track"""
|
||||||
|
# TODO: Implement
|
||||||
|
return 'nicht verfügbar'
|
||||||
|
|
||||||
|
|
||||||
# ── Placeholder Registry ──────────────────────────────────────────────────────
|
# ── Placeholder Registry ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
PLACEHOLDER_MAP: Dict[str, Callable[[str], str]] = {
|
PLACEHOLDER_MAP: Dict[str, Callable[[str], str]] = {
|
||||||
|
|
@ -512,6 +748,107 @@ PLACEHOLDER_MAP: Dict[str, Callable[[str], str]] = {
|
||||||
'{{zeitraum_7d}}': lambda pid: 'letzte 7 Tage',
|
'{{zeitraum_7d}}': lambda pid: 'letzte 7 Tage',
|
||||||
'{{zeitraum_30d}}': lambda pid: 'letzte 30 Tage',
|
'{{zeitraum_30d}}': lambda pid: 'letzte 30 Tage',
|
||||||
'{{zeitraum_90d}}': lambda pid: 'letzte 90 Tage',
|
'{{zeitraum_90d}}': lambda pid: 'letzte 90 Tage',
|
||||||
|
|
||||||
|
# ========================================================================
|
||||||
|
# PHASE 0b: Goal-Aware Placeholders (Dynamic Focus Areas v2.0)
|
||||||
|
# ========================================================================
|
||||||
|
|
||||||
|
# --- Meta Scores (Ebene 1: Aggregierte Scores) ---
|
||||||
|
'{{goal_progress_score}}': lambda pid: _safe_int('goal_progress_score', pid),
|
||||||
|
'{{body_progress_score}}': lambda pid: _safe_int('body_progress_score', pid),
|
||||||
|
'{{nutrition_score}}': lambda pid: _safe_int('nutrition_score', pid),
|
||||||
|
'{{activity_score}}': lambda pid: _safe_int('activity_score', pid),
|
||||||
|
'{{recovery_score}}': lambda pid: _safe_int('recovery_score_v2', pid),
|
||||||
|
'{{data_quality_score}}': lambda pid: _safe_int('data_quality_score', pid),
|
||||||
|
|
||||||
|
# --- Top-Weighted Goals/Focus Areas (Ebene 2: statt Primary) ---
|
||||||
|
'{{top_goal_name}}': lambda pid: _safe_str('top_goal_name', pid),
|
||||||
|
'{{top_goal_progress_pct}}': lambda pid: _safe_str('top_goal_progress_pct', pid),
|
||||||
|
'{{top_goal_status}}': lambda pid: _safe_str('top_goal_status', pid),
|
||||||
|
'{{top_focus_area_name}}': lambda pid: _safe_str('top_focus_area_name', pid),
|
||||||
|
'{{top_focus_area_progress}}': lambda pid: _safe_int('top_focus_area_progress', pid),
|
||||||
|
|
||||||
|
# --- Category Scores (Ebene 3: 7 Kategorien) ---
|
||||||
|
'{{focus_cat_körper_progress}}': lambda pid: _safe_int('focus_cat_körper_progress', pid),
|
||||||
|
'{{focus_cat_körper_weight}}': lambda pid: _safe_float('focus_cat_körper_weight', pid),
|
||||||
|
'{{focus_cat_ernährung_progress}}': lambda pid: _safe_int('focus_cat_ernährung_progress', pid),
|
||||||
|
'{{focus_cat_ernährung_weight}}': lambda pid: _safe_float('focus_cat_ernährung_weight', pid),
|
||||||
|
'{{focus_cat_aktivität_progress}}': lambda pid: _safe_int('focus_cat_aktivität_progress', pid),
|
||||||
|
'{{focus_cat_aktivität_weight}}': lambda pid: _safe_float('focus_cat_aktivität_weight', pid),
|
||||||
|
'{{focus_cat_recovery_progress}}': lambda pid: _safe_int('focus_cat_recovery_progress', pid),
|
||||||
|
'{{focus_cat_recovery_weight}}': lambda pid: _safe_float('focus_cat_recovery_weight', pid),
|
||||||
|
'{{focus_cat_vitalwerte_progress}}': lambda pid: _safe_int('focus_cat_vitalwerte_progress', pid),
|
||||||
|
'{{focus_cat_vitalwerte_weight}}': lambda pid: _safe_float('focus_cat_vitalwerte_weight', pid),
|
||||||
|
'{{focus_cat_mental_progress}}': lambda pid: _safe_int('focus_cat_mental_progress', pid),
|
||||||
|
'{{focus_cat_mental_weight}}': lambda pid: _safe_float('focus_cat_mental_weight', pid),
|
||||||
|
'{{focus_cat_lebensstil_progress}}': lambda pid: _safe_int('focus_cat_lebensstil_progress', pid),
|
||||||
|
'{{focus_cat_lebensstil_weight}}': lambda pid: _safe_float('focus_cat_lebensstil_weight', pid),
|
||||||
|
|
||||||
|
# --- Body Metrics (Ebene 4: Einzelmetriken K1-K5) ---
|
||||||
|
'{{weight_7d_median}}': lambda pid: _safe_float('weight_7d_median', pid),
|
||||||
|
'{{weight_28d_slope}}': lambda pid: _safe_float('weight_28d_slope', pid, decimals=4),
|
||||||
|
'{{weight_90d_slope}}': lambda pid: _safe_float('weight_90d_slope', pid, decimals=4),
|
||||||
|
'{{fm_28d_change}}': lambda pid: _safe_float('fm_28d_change', pid),
|
||||||
|
'{{lbm_28d_change}}': lambda pid: _safe_float('lbm_28d_change', pid),
|
||||||
|
'{{waist_28d_delta}}': lambda pid: _safe_float('waist_28d_delta', pid),
|
||||||
|
'{{hip_28d_delta}}': lambda pid: _safe_float('hip_28d_delta', pid),
|
||||||
|
'{{chest_28d_delta}}': lambda pid: _safe_float('chest_28d_delta', pid),
|
||||||
|
'{{arm_28d_delta}}': lambda pid: _safe_float('arm_28d_delta', pid),
|
||||||
|
'{{thigh_28d_delta}}': lambda pid: _safe_float('thigh_28d_delta', pid),
|
||||||
|
'{{waist_hip_ratio}}': lambda pid: _safe_float('waist_hip_ratio', pid, decimals=3),
|
||||||
|
'{{recomposition_quadrant}}': lambda pid: _safe_str('recomposition_quadrant', pid),
|
||||||
|
|
||||||
|
# --- Nutrition Metrics (E1-E5) ---
|
||||||
|
'{{energy_balance_7d}}': lambda pid: _safe_float('energy_balance_7d', pid, decimals=0),
|
||||||
|
'{{energy_deficit_surplus}}': lambda pid: _safe_str('energy_deficit_surplus', pid),
|
||||||
|
'{{protein_g_per_kg}}': lambda pid: _safe_float('protein_g_per_kg', pid),
|
||||||
|
'{{protein_days_in_target}}': lambda pid: _safe_str('protein_days_in_target', pid),
|
||||||
|
'{{protein_adequacy_28d}}': lambda pid: _safe_int('protein_adequacy_28d', pid),
|
||||||
|
'{{macro_consistency_score}}': lambda pid: _safe_int('macro_consistency_score', pid),
|
||||||
|
'{{intake_volatility}}': lambda pid: _safe_str('intake_volatility', pid),
|
||||||
|
|
||||||
|
# --- Activity Metrics (A1-A8) ---
|
||||||
|
'{{training_minutes_week}}': lambda pid: _safe_int('training_minutes_week', pid),
|
||||||
|
'{{training_frequency_7d}}': lambda pid: _safe_int('training_frequency_7d', pid),
|
||||||
|
'{{quality_sessions_pct}}': lambda pid: _safe_int('quality_sessions_pct', pid),
|
||||||
|
'{{ability_balance_strength}}': lambda pid: _safe_int('ability_balance_strength', pid),
|
||||||
|
'{{ability_balance_endurance}}': lambda pid: _safe_int('ability_balance_endurance', pid),
|
||||||
|
'{{ability_balance_mental}}': lambda pid: _safe_int('ability_balance_mental', pid),
|
||||||
|
'{{ability_balance_coordination}}': lambda pid: _safe_int('ability_balance_coordination', pid),
|
||||||
|
'{{ability_balance_mobility}}': lambda pid: _safe_int('ability_balance_mobility', pid),
|
||||||
|
'{{proxy_internal_load_7d}}': lambda pid: _safe_int('proxy_internal_load_7d', pid),
|
||||||
|
'{{monotony_score}}': lambda pid: _safe_float('monotony_score', pid),
|
||||||
|
'{{strain_score}}': lambda pid: _safe_int('strain_score', pid),
|
||||||
|
'{{rest_day_compliance}}': lambda pid: _safe_int('rest_day_compliance', pid),
|
||||||
|
'{{vo2max_trend_28d}}': lambda pid: _safe_float('vo2max_trend_28d', pid),
|
||||||
|
|
||||||
|
# --- Recovery Metrics (Recovery Score v2) ---
|
||||||
|
'{{hrv_vs_baseline_pct}}': lambda pid: _safe_float('hrv_vs_baseline_pct', pid),
|
||||||
|
'{{rhr_vs_baseline_pct}}': lambda pid: _safe_float('rhr_vs_baseline_pct', pid),
|
||||||
|
'{{sleep_avg_duration_7d}}': lambda pid: _safe_float('sleep_avg_duration_7d', pid),
|
||||||
|
'{{sleep_debt_hours}}': lambda pid: _safe_float('sleep_debt_hours', pid),
|
||||||
|
'{{sleep_regularity_proxy}}': lambda pid: _safe_float('sleep_regularity_proxy', pid),
|
||||||
|
'{{recent_load_balance_3d}}': lambda pid: _safe_int('recent_load_balance_3d', pid),
|
||||||
|
'{{sleep_quality_7d}}': lambda pid: _safe_int('sleep_quality_7d', pid),
|
||||||
|
|
||||||
|
# --- Correlation Metrics (C1-C7) ---
|
||||||
|
'{{correlation_energy_weight_lag}}': lambda pid: _safe_json('correlation_energy_weight_lag', pid),
|
||||||
|
'{{correlation_protein_lbm}}': lambda pid: _safe_json('correlation_protein_lbm', pid),
|
||||||
|
'{{correlation_load_hrv}}': lambda pid: _safe_json('correlation_load_hrv', pid),
|
||||||
|
'{{correlation_load_rhr}}': lambda pid: _safe_json('correlation_load_rhr', pid),
|
||||||
|
'{{correlation_sleep_recovery}}': lambda pid: _safe_json('correlation_sleep_recovery', pid),
|
||||||
|
'{{plateau_detected}}': lambda pid: _safe_json('plateau_detected', pid),
|
||||||
|
'{{top_drivers}}': lambda pid: _safe_json('top_drivers', pid),
|
||||||
|
|
||||||
|
# --- JSON/Markdown Structured Data (Ebene 5) ---
|
||||||
|
'{{active_goals_json}}': lambda pid: _safe_json('active_goals_json', pid),
|
||||||
|
'{{active_goals_md}}': lambda pid: _safe_str('active_goals_md', pid),
|
||||||
|
'{{focus_areas_weighted_json}}': lambda pid: _safe_json('focus_areas_weighted_json', pid),
|
||||||
|
'{{focus_areas_weighted_md}}': lambda pid: _safe_str('focus_areas_weighted_md', pid),
|
||||||
|
'{{focus_area_weights_json}}': lambda pid: _safe_json('focus_area_weights_json', pid),
|
||||||
|
'{{top_3_focus_areas}}': lambda pid: _safe_str('top_3_focus_areas', pid),
|
||||||
|
'{{top_3_goals_behind_schedule}}': lambda pid: _safe_str('top_3_goals_behind_schedule', pid),
|
||||||
|
'{{top_3_goals_on_track}}': lambda pid: _safe_str('top_3_goals_on_track', pid),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user