"""Nutrition math — the ONLY place nutrition is computed (spec §8.1 rule 1). Routers never compute nutrition; the frontend never re-derives it. `quantity` is interpreted by context (spec §2.1): - unit_type "weight": quantity is grams; nutrition = (quantity / 100) × per_unit - unit_type "count": quantity is item count; nutrition = quantity × per_unit - meal: quantity is a scaling factor (1.0 = one full meal) """ from models import Food def scale_to_quantity(per_unit: float | None, quantity: float, unit_type: str) -> float: """Scale a per-unit nutrition value to a logged quantity. Missing values count as 0.""" if per_unit is None: return 0.0 if unit_type == "weight": return per_unit * quantity / 100.0 if unit_type == "count": return per_unit * quantity raise ValueError(f"unknown unit_type: {unit_type!r}") def entry_calories(food: Food, quantity: float) -> float: """Calories for a single (non-meal) food at a logged quantity. TODO: handle is_meal foods by summing scaled component nutrition (recursively, with cycle detection — spec §2.2). """ return scale_to_quantity(food.calories_per_unit, quantity, food.unit_type)