Food Planner Quantity Standardization Plan Problem Statement The application has inconsistent handling of food quantities throughout the codebase: Current Issue: MealFood.quantity is being used sometimes as a multiplier of serving_size and sometimes as grams directly Impact: Confusing calculations in nutrition functions and unclear user interface expectations Goal: Standardize so MealFood.quantity always represents grams of the food item Core Data Model Definition Standard to Adopt Food.serving_size = base serving size in grams (e.g., 100) Food.[nutrients] = nutritional values per serving_size grams MealFood.quantity = actual grams to use (e.g., 150g) TrackedMealFood.quantity = actual grams to use (e.g., 200g) Calculation: multiplier = quantity / serving_size Implementation Plan Phase 1: Audit & Document (Non-Breaking) Task 1.1: Add documentation header to app/database.py python""" QUANTITY CONVENTION: All quantity fields in this application represent GRAMS. - Food.serving_size: base serving size in grams (e.g., 100.0) - Food nutrition values: per serving_size grams - MealFood.quantity: grams of this food in the meal (e.g., 150.0) - TrackedMealFood.quantity: grams of this food as tracked (e.g., 200.0) To calculate nutrition: multiplier = quantity / serving_size """ Task 1.2: Audit all locations where quantity is read/written app/database.py - calculation functions app/api/routes/meals.py - meal food operations app/api/routes/tracker.py - tracked meal operations app/api/routes/plans.py - detailed view Templates using quantity values Phase 2: Fix Core Calculation Functions Task 2.1: Fix calculate_meal_nutrition() in app/database.py Current behavior: Assumes quantity is already a multiplier New behavior: Calculate multiplier from grams pythondef calculate_meal_nutrition(meal, db: Session): """ Calculate total nutrition for a meal. MealFood.quantity is in GRAMS. """ totals = { 'calories': 0, 'protein': 0, 'carbs': 0, 'fat': 0, 'fiber': 0, 'sugar': 0, 'sodium': 0, 'calcium': 0 } for meal_food in meal.meal_foods: food = meal_food.food grams = meal_food.quantity # Convert grams to multiplier based on serving size try: serving_size = float(food.serving_size) multiplier = grams / serving_size if serving_size > 0 else 0 except (ValueError, TypeError): multiplier = 0 totals['calories'] += food.calories * multiplier totals['protein'] += food.protein * multiplier totals['carbs'] += food.carbs * multiplier totals['fat'] += food.fat * multiplier totals['fiber'] += (food.fiber or 0) * multiplier totals['sugar'] += (food.sugar or 0) * multiplier totals['sodium'] += (food.sodium or 0) * multiplier totals['calcium'] += (food.calcium or 0) * multiplier # Calculate percentages (unchanged) total_cals = totals['calories'] if total_cals > 0: totals['protein_pct'] = round((totals['protein'] * 4 / total_cals) * 100, 1) totals['carbs_pct'] = round((totals['carbs'] * 4 / total_cals) * 100, 1) totals['fat_pct'] = round((totals['fat'] * 9 / total_cals) * 100, 1) totals['net_carbs'] = totals['carbs'] - totals['fiber'] else: totals['protein_pct'] = 0 totals['carbs_pct'] = 0 totals['fat_pct'] = 0 totals['net_carbs'] = 0 return totals Task 2.2: Fix calculate_tracked_meal_nutrition() in app/database.py Apply the same pattern to handle TrackedMealFood.quantity as grams. Task 2.3: Remove or fix convert_grams_to_quantity() function This function appears to be unused but creates confusion. Either: Remove it entirely, OR Rename to calculate_multiplier_from_grams() and update documentation Phase 3: Fix API Routes Task 3.1: Fix app/api/routes/meals.py Location: POST /meals/{meal_id}/add_food python@router.post("/meals/{meal_id}/add_food") async def add_food_to_meal( meal_id: int, food_id: int = Form(...), grams: float = Form(...), # Changed from 'quantity' to be explicit db: Session = Depends(get_db) ): try: # Store grams directly - no conversion needed meal_food = MealFood( meal_id=meal_id, food_id=food_id, quantity=grams # This is grams ) db.add(meal_food) db.commit() return {"status": "success"} except Exception as e: db.rollback() return {"status": "error", "message": str(e)} Location: POST /meals/update_food_quantity python@router.post("/meals/update_food_quantity") async def update_meal_food_quantity( meal_food_id: int = Form(...), grams: float = Form(...), # Changed from 'quantity' db: Session = Depends(get_db) ): try: meal_food = db.query(MealFood).filter(MealFood.id == meal_food_id).first() if not meal_food: return {"status": "error", "message": "meal food not found"} meal_food.quantity = grams # Store grams directly db.commit() return {"status": "success"} except Exception as e: db.rollback() return {"status": "error", "message": str(e)} Task 3.2: Fix app/api/routes/tracker.py Review all tracked meal operations to ensure they handle grams correctly. Task 3.3: Fix app/api/routes/plans.py detailed view The detailed view calculates nutrition per food item. Update to show grams clearly: pythonfor mf in tm.meal.meal_foods: try: serving_size_value = float(mf.food.serving_size) num_servings = mf.quantity / serving_size_value if serving_size_value != 0 else 0 except (ValueError, TypeError): num_servings = 0 foods.append({ 'name': mf.food.name, 'total_grams': mf.quantity, # Explicitly show it's grams 'num_servings': round(num_servings, 2), 'serving_size': mf.food.serving_size, 'serving_unit': mf.food.serving_unit, # Don't recalculate nutrition here - it's done in calculate_meal_nutrition }) Phase 4: Fix CSV Import Functions Task 4.1: Fix app/api/routes/meals.py - POST /meals/upload Currently processes ingredient pairs as (food_name, grams). Ensure it stores grams directly: pythonfor i in range(1, len(row), 2): if i+1 >= len(row) or not row[i].strip(): continue food_name = row[i].strip() grams = float(row[i+1].strip()) # This is grams # ... find food ... ingredients.append((food.id, grams)) # Store grams directly # Later when creating MealFood: for food_id, grams in ingredients: meal_food = MealFood( meal_id=existing.id, food_id=food_id, quantity=grams # Store grams directly ) Phase 5: Update Templates & UI Task 5.1: Update templates/detailed.html Ensure the food breakdown clearly shows grams: html