Merge pull request 'Platzhaltersystem für Ernährung, Körper und Aktivity' (#60) from develop into main
All checks were successful
Deploy Production / deploy (push) Successful in 51s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 15s

Reviewed-on: #60
This commit is contained in:
Lars 2026-04-03 09:28:13 +02:00
commit 3f5b9b8b58

View File

@ -1350,54 +1350,58 @@ def get_placeholder_catalog(profile_id: str) -> Dict[str, List[Dict[str, str]]]:
"""
Get grouped placeholder catalog with descriptions and example values.
Uses the Placeholder Registry as single source of truth.
Falls back to hardcoded legacy placeholders for non-registry items.
Args:
profile_id: User profile ID
Returns:
Dict mapping category to list of {key, description, example}
"""
# Placeholder definitions with descriptions
placeholders = {
from placeholder_registry import get_registry
catalog = {}
# Get all registered placeholders from Registry
registry = get_registry()
all_registered = registry.get_all()
# Group registry placeholders by category
for key, metadata in all_registered.items():
category = metadata.category
if category not in catalog:
catalog[category] = []
# Try to resolve value
try:
if metadata._resolver_func:
example = metadata._resolver_func(profile_id)
else:
# Fallback to PLACEHOLDER_MAP
placeholder = f'{{{{{key}}}}}'
resolver = PLACEHOLDER_MAP.get(placeholder)
if resolver:
example = resolver(profile_id)
else:
example = '[Nicht implementiert]'
except Exception as e:
example = '[Nicht verfügbar]'
catalog[category].append({
'key': key,
'description': metadata.description,
'example': str(example)
})
# Legacy placeholders (not in registry yet)
legacy_placeholders = {
'Profil': [
('name', 'Name des Nutzers'),
('age', 'Alter in Jahren'),
('height', 'Körpergröße in cm'),
('geschlecht', 'Geschlecht'),
],
'Körper': [
('weight_aktuell', 'Aktuelles Gewicht in kg'),
('weight_trend', 'Gewichtstrend (7d/30d)'),
('kf_aktuell', 'Aktueller Körperfettanteil in %'),
('bmi', 'Body Mass Index'),
('weight_7d_median', 'Gewicht 7d Median (kg)'),
('weight_28d_slope', 'Gewichtstrend 28d (kg/Tag)'),
('fm_28d_change', 'Fettmasse Änderung 28d (kg)'),
('lbm_28d_change', 'Magermasse Änderung 28d (kg)'),
('waist_28d_delta', 'Taillenumfang Änderung 28d (cm)'),
('waist_hip_ratio', 'Taille/Hüfte-Verhältnis'),
('recomposition_quadrant', 'Rekomposition-Status'),
],
'Ernährung': [
('kcal_avg', 'Durchschn. Kalorien (30d)'),
('protein_avg', 'Durchschn. Protein in g (30d)'),
('carb_avg', 'Durchschn. Kohlenhydrate in g (30d)'),
('fat_avg', 'Durchschn. Fett in g (30d)'),
('energy_balance_7d', 'Energiebilanz 7d (kcal/Tag)'),
('protein_g_per_kg', 'Protein g/kg Körpergewicht'),
('protein_adequacy_28d', 'Protein Adequacy Score (0-100)'),
('macro_consistency_score', 'Makro-Konsistenz Score (0-100)'),
],
'Training': [
('activity_summary', 'Aktivitäts-Zusammenfassung (7d)'),
('trainingstyp_verteilung', 'Verteilung nach Trainingstypen'),
('training_minutes_week', 'Trainingsminuten pro Woche'),
('training_frequency_7d', 'Trainingshäufigkeit 7d'),
('quality_sessions_pct', 'Qualitätssessions (%)'),
('ability_balance_strength', 'Ability Balance - Kraft (0-100)'),
('ability_balance_endurance', 'Ability Balance - Ausdauer (0-100)'),
('proxy_internal_load_7d', 'Proxy Load 7d'),
('rest_day_compliance', 'Ruhetags-Compliance (%)'),
],
'Schlaf & Erholung': [
('sleep_avg_duration', 'Durchschn. Schlafdauer (7d)'),
('sleep_avg_quality', 'Durchschn. Schlafqualität (7d)'),
@ -1440,13 +1444,17 @@ def get_placeholder_catalog(profile_id: str) -> Dict[str, List[Dict[str, str]]]:
],
}
catalog = {}
# Add legacy placeholders (skip if already in registry)
for category, items in legacy_placeholders.items():
if category not in catalog:
catalog[category] = []
for category, items in placeholders.items():
catalog[category] = []
for key, description in items:
# Skip if already added from registry
if any(p['key'] == key for p in catalog[category]):
continue
placeholder = f'{{{{{key}}}}}'
# Get example value if resolver exists
resolver = PLACEHOLDER_MAP.get(placeholder)
if resolver:
try: