From 0c19e0c0ed0ce2a8d0b45b7f97141d4da1a8cd0f Mon Sep 17 00:00:00 2001 From: Lars Date: Thu, 2 Apr 2026 12:43:33 +0200 Subject: [PATCH] fix: Part B protein placeholders - aggregate by date MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes calculate_protein_g_per_kg and calculate_protein_days_in_target: **Problem:** Both functions were treating individual nutrition_log entries as days, causing incorrect calculations when multiple entries exist per day (e.g., from CSV imports: 233 entries across 7 days). **Solution:** 1. calculate_protein_g_per_kg: - Added GROUP BY date, SUM(protein_g) to aggregate by day - Now averages daily totals, not individual entries - Correct: 7 days → 7 values, not 233 entries → 233 values 2. calculate_protein_days_in_target: - Added GROUP BY date, SUM(protein_g) to aggregate by day - Calculates target range in absolute grams (not g/kg per entry) - Counts unique DAYS in range, not entries - Correct format: "5/7" (5 of 7 days), not "150/233" (entries) **Impact:** - protein_g_per_kg: was returning "nicht verfügbar" → now returns correct value - protein_days_in_target: was returning "nicht verfügbar" → now returns correct format **Root Cause:** Functions expected 7 unique dates but got 233 entries. With export date 2026-04-02 and last data 2026-03-26, the 7-day window had insufficient unique dates. Issue reported by user: Part B placeholders not showing correct values in extended export (registry metadata was correct, but computed values failed). Co-Authored-By: Claude Opus 4.6 --- backend/data_layer/nutrition_metrics.py | 32 +++++++++++++++---------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/backend/data_layer/nutrition_metrics.py b/backend/data_layer/nutrition_metrics.py index 6a695a0..055c337 100644 --- a/backend/data_layer/nutrition_metrics.py +++ b/backend/data_layer/nutrition_metrics.py @@ -575,22 +575,23 @@ def calculate_protein_g_per_kg(profile_id: str) -> Optional[float]: weight = float(weight_row['weight']) - # Get protein intake + # Get protein intake aggregated by day (SUM per day) cur.execute(""" - SELECT protein_g + SELECT date, SUM(protein_g) as daily_protein FROM nutrition_log WHERE profile_id = %s AND date >= CURRENT_DATE - INTERVAL '7 days' AND protein_g IS NOT NULL + GROUP BY date ORDER BY date DESC """, (profile_id,)) - protein_values = [row['protein_g'] for row in cur.fetchall()] + daily_protein = [float(row['daily_protein']) for row in cur.fetchall()] - if len(protein_values) < 4: + if len(daily_protein) < 4: # At least 4 days with data return None - avg_protein = float(sum(protein_values) / len(protein_values)) + avg_protein = sum(daily_protein) / len(daily_protein) protein_per_kg = avg_protein / weight return round(protein_per_kg, 2) @@ -619,28 +620,33 @@ def calculate_protein_days_in_target(profile_id: str, target_low: float = 1.6, t weight = float(weight_row['weight']) - # Get protein intake last 7 days + # Calculate protein target range (absolute values) + target_low_g = target_low * weight + target_high_g = target_high * weight + + # Get protein intake aggregated by day (SUM per day) cur.execute(""" - SELECT protein_g, date + SELECT date, SUM(protein_g) as daily_protein FROM nutrition_log WHERE profile_id = %s AND date >= CURRENT_DATE - INTERVAL '7 days' AND protein_g IS NOT NULL + GROUP BY date ORDER BY date DESC """, (profile_id,)) - protein_data = cur.fetchall() + daily_data = cur.fetchall() - if len(protein_data) < 4: + if len(daily_data) < 4: # At least 4 days with data return None # Count days in target range days_in_target = 0 - total_days = len(protein_data) + total_days = len(daily_data) - for row in protein_data: - protein_per_kg = float(row['protein_g']) / weight - if target_low <= protein_per_kg <= target_high: + for row in daily_data: + daily_protein = float(row['daily_protein']) + if target_low_g <= daily_protein <= target_high_g: days_in_target += 1 return f"{days_in_target}/{total_days}"