fix: Phase 0b - score functions use English focus area keys
All checks were successful
Deploy Development / deploy (push) Successful in 50s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 14s

Root cause: All 3 score functions returned None because they queried
German focus area keys that don't exist in database (migration 031
uses English keys).

Changes:
- body_progress_score: körpergewicht/körperfett/muskelmasse
  → weight_loss/muscle_gain/body_recomposition
- nutrition_score: ernährung_basis/proteinzufuhr/kalorienbilanz
  → protein_intake/calorie_balance/macro_consistency/meal_timing/hydration
- activity_score: kraftaufbau/cardio/bewegungsumfang/trainingsqualität
  → strength/aerobic_endurance/flexibility/rhythm/coordination (grouped)

Result: Scores now calculate correctly with existing focus area weights.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Lars 2026-03-28 10:59:37 +01:00
parent 63bd103b2c
commit cc76ae677b
3 changed files with 85 additions and 73 deletions

View File

@ -341,59 +341,72 @@ def calculate_activity_score(profile_id: str, focus_weights: Optional[Dict] = No
from calculations.scores import get_user_focus_weights
focus_weights = get_user_focus_weights(profile_id)
# Activity-related focus areas
activity_focus = {
'kraftaufbau': focus_weights.get('kraftaufbau', 0),
'cardio': focus_weights.get('cardio', 0),
'bewegungsumfang': focus_weights.get('bewegungsumfang', 0),
'trainingsqualität': focus_weights.get('trainingsqualität', 0),
'ability_balance': focus_weights.get('ability_balance', 0),
}
# Activity-related focus areas (English keys from DB)
# Strength training
strength = focus_weights.get('strength', 0)
strength_endurance = focus_weights.get('strength_endurance', 0)
power = focus_weights.get('power', 0)
total_strength = strength + strength_endurance + power
total_activity_weight = sum(activity_focus.values())
# Endurance training
aerobic = focus_weights.get('aerobic_endurance', 0)
anaerobic = focus_weights.get('anaerobic_endurance', 0)
cardiovascular = focus_weights.get('cardiovascular_health', 0)
total_cardio = aerobic + anaerobic + cardiovascular
# Mobility/Coordination
flexibility = focus_weights.get('flexibility', 0)
mobility = focus_weights.get('mobility', 0)
balance = focus_weights.get('balance', 0)
reaction = focus_weights.get('reaction', 0)
rhythm = focus_weights.get('rhythm', 0)
coordination = focus_weights.get('coordination', 0)
total_ability = flexibility + mobility + balance + reaction + rhythm + coordination
total_activity_weight = total_strength + total_cardio + total_ability
if total_activity_weight == 0:
return None # No activity goals
components = []
# 1. Weekly minutes (if bewegungsumfang goal)
if activity_focus['bewegungsumfang'] > 0:
minutes = calculate_training_minutes_week(profile_id)
if minutes is not None:
# WHO: 150-300 min/week
if 150 <= minutes <= 300:
minutes_score = 100
elif minutes < 150:
minutes_score = max(40, (minutes / 150) * 100)
else:
minutes_score = max(80, 100 - ((minutes - 300) / 10))
# 1. Weekly minutes (general activity volume)
minutes = calculate_training_minutes_week(profile_id)
if minutes is not None:
# WHO: 150-300 min/week
if 150 <= minutes <= 300:
minutes_score = 100
elif minutes < 150:
minutes_score = max(40, (minutes / 150) * 100)
else:
minutes_score = max(80, 100 - ((minutes - 300) / 10))
components.append(('minutes', minutes_score, activity_focus['bewegungsumfang']))
# Volume relevant for all activity types (20% base weight)
components.append(('minutes', minutes_score, total_activity_weight * 0.2))
# 2. Quality sessions (if trainingsqualität goal)
if activity_focus['trainingsqualität'] > 0:
quality_pct = calculate_quality_sessions_pct(profile_id)
if quality_pct is not None:
components.append(('quality', quality_pct, activity_focus['trainingsqualität']))
# 2. Quality sessions (always relevant)
quality_pct = calculate_quality_sessions_pct(profile_id)
if quality_pct is not None:
# Quality gets 10% base weight
components.append(('quality', quality_pct, total_activity_weight * 0.1))
# 3. Strength presence (if kraftaufbau goal)
if activity_focus['kraftaufbau'] > 0:
# 3. Strength presence (if strength focus active)
if total_strength > 0:
strength_score = _score_strength_presence(profile_id)
if strength_score is not None:
components.append(('strength', strength_score, activity_focus['kraftaufbau']))
components.append(('strength', strength_score, total_strength))
# 4. Cardio presence (if cardio goal)
if activity_focus['cardio'] > 0:
# 4. Cardio presence (if cardio focus active)
if total_cardio > 0:
cardio_score = _score_cardio_presence(profile_id)
if cardio_score is not None:
components.append(('cardio', cardio_score, activity_focus['cardio']))
components.append(('cardio', cardio_score, total_cardio))
# 5. Ability balance (if ability_balance goal)
if activity_focus['ability_balance'] > 0:
# 5. Ability balance (if mobility/coordination focus active)
if total_ability > 0:
balance_score = _score_ability_balance(profile_id)
if balance_score is not None:
components.append(('balance', balance_score, activity_focus['ability_balance']))
components.append(('balance', balance_score, total_ability))
if not components:
return None

View File

@ -339,12 +339,12 @@ def calculate_body_progress_score(profile_id: str, focus_weights: Optional[Dict]
from calculations.scores import get_user_focus_weights
focus_weights = get_user_focus_weights(profile_id)
# Get all body-related focus area weights
body_weight = focus_weights.get('körpergewicht', 0)
body_fat_weight = focus_weights.get('körperfett', 0)
muscle_weight = focus_weights.get('muskelmasse', 0)
# Get all body-related focus area weights (English keys from DB)
weight_loss = focus_weights.get('weight_loss', 0)
muscle_gain = focus_weights.get('muscle_gain', 0)
body_recomp = focus_weights.get('body_recomposition', 0)
total_body_weight = body_weight + body_fat_weight + muscle_weight
total_body_weight = weight_loss + muscle_gain + body_recomp
if total_body_weight == 0:
return None # No body-related goals
@ -352,23 +352,23 @@ def calculate_body_progress_score(profile_id: str, focus_weights: Optional[Dict]
# Calculate component scores (0-100)
components = []
# Weight trend component (if weight goal active)
if body_weight > 0:
# Weight trend component (if weight loss goal active)
if weight_loss > 0:
weight_score = _score_weight_trend(profile_id)
if weight_score is not None:
components.append(('weight', weight_score, body_weight))
components.append(('weight', weight_score, weight_loss))
# Body composition component (if BF% or LBM goal active)
if body_fat_weight > 0 or muscle_weight > 0:
# Body composition component (if muscle gain or recomp goal active)
if muscle_gain > 0 or body_recomp > 0:
comp_score = _score_body_composition(profile_id)
if comp_score is not None:
components.append(('composition', comp_score, body_fat_weight + muscle_weight))
components.append(('composition', comp_score, muscle_gain + body_recomp))
# Waist circumference component (proxy for health)
waist_score = _score_waist_trend(profile_id)
if waist_score is not None:
# Waist gets 20% base weight + bonus from BF% goals
waist_weight = 20 + (body_fat_weight * 0.3)
# Waist gets 20% base weight + bonus from weight loss goals
waist_weight = 20 + (weight_loss * 0.3)
components.append(('waist', waist_score, waist_weight))
if not components:

View File

@ -341,45 +341,44 @@ def calculate_nutrition_score(profile_id: str, focus_weights: Optional[Dict] = N
from calculations.scores import get_user_focus_weights
focus_weights = get_user_focus_weights(profile_id)
# Nutrition-related focus areas
nutrition_focus = {
'ernährung_basis': focus_weights.get('ernährung_basis', 0),
'ernährung_makros': focus_weights.get('ernährung_makros', 0),
'proteinzufuhr': focus_weights.get('proteinzufuhr', 0),
'kalorienbilanz': focus_weights.get('kalorienbilanz', 0),
}
# Nutrition-related focus areas (English keys from DB)
protein_intake = focus_weights.get('protein_intake', 0)
calorie_balance = focus_weights.get('calorie_balance', 0)
macro_consistency = focus_weights.get('macro_consistency', 0)
meal_timing = focus_weights.get('meal_timing', 0)
hydration = focus_weights.get('hydration', 0)
total_nutrition_weight = sum(nutrition_focus.values())
total_nutrition_weight = protein_intake + calorie_balance + macro_consistency + meal_timing + hydration
if total_nutrition_weight == 0:
return None # No nutrition goals
components = []
# 1. Calorie target adherence (if kalorienbilanz goal active)
if nutrition_focus['kalorienbilanz'] > 0:
# 1. Calorie target adherence (if calorie_balance goal active)
if calorie_balance > 0:
calorie_score = _score_calorie_adherence(profile_id)
if calorie_score is not None:
components.append(('calories', calorie_score, nutrition_focus['kalorienbilanz']))
components.append(('calories', calorie_score, calorie_balance))
# 2. Protein target adherence (always important if any nutrition goal)
protein_score = calculate_protein_adequacy_28d(profile_id)
if protein_score is not None:
# Higher weight if protein-specific goal
protein_weight = nutrition_focus['proteinzufuhr'] or (total_nutrition_weight * 0.3)
components.append(('protein', protein_score, protein_weight))
# 2. Protein target adherence (if protein_intake goal active)
if protein_intake > 0:
protein_score = calculate_protein_adequacy_28d(profile_id)
if protein_score is not None:
components.append(('protein', protein_score, protein_intake))
# 3. Intake consistency (always relevant)
consistency_score = calculate_macro_consistency_score(profile_id)
if consistency_score is not None:
consistency_weight = total_nutrition_weight * 0.2
components.append(('consistency', consistency_score, consistency_weight))
# 3. Intake consistency (if macro_consistency goal active)
if macro_consistency > 0:
consistency_score = calculate_macro_consistency_score(profile_id)
if consistency_score is not None:
components.append(('consistency', consistency_score, macro_consistency))
# 4. Macro balance (if makros goal active)
if nutrition_focus['ernährung_makros'] > 0:
# 4. Macro balance (always relevant if any nutrition goal)
if total_nutrition_weight > 0:
macro_score = _score_macro_balance(profile_id)
if macro_score is not None:
components.append(('macros', macro_score, nutrition_focus['ernährung_makros']))
# Use 20% of total weight for macro balance
components.append(('macros', macro_score, total_nutrition_weight * 0.2))
if not components:
return None