diet-guard/diet_guard/_meal.py

66 lines
2.3 KiB
Python
Raw Normal View History

"""Composite "meal" support for diet_guard.
A meal is a named group of individually-macroed items -- e.g. a dinner of
salad + chicken + rice, each entered with its own calories and macros. The
meal's nutrition is the sum of its items. Both the individual items and the
composite meal are saved to the food bank (see
:func:`diet_guard._foodbank.remember_meal`), so next time each item
autocompletes on its own and the whole meal can be picked as one summed entry.
This module is deliberately pure (no I/O): the sum is a total function of its
items, which keeps the arithmetic exhaustively unit-testable apart from the
bank persistence and the gate UI that compose it.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import TYPE_CHECKING
from diet_guard._estimator import Nutrition
if TYPE_CHECKING:
from collections.abc import Sequence
# Provenance stamped on a summed meal so the log/UI can tell a composite apart
# from a single looked-up food.
MEAL_SOURCE = "meal"
@dataclass(frozen=True)
class MealItem:
"""One named component of a composite meal, with its own nutrition.
Attributes:
name: The component's food name (e.g. ``"chicken"``).
nutrition: The component's resolved macros for the amount eaten.
"""
name: str
nutrition: Nutrition
def meal_total(items: Sequence[MealItem]) -> Nutrition:
"""Return the summed nutrition of a meal's items.
Every macro and the portion weight are added across the items and rounded to
0.1, and the result is stamped ``source=MEAL_SOURCE`` so it is
distinguishable from a single food. An empty sequence sums to an all-zero
meal rather than raising, so callers need not special-case "no items yet".
Args:
items: The meal's components.
Returns:
A :class:`~diet_guard._estimator.Nutrition` whose fields are
the per-item sums.
"""
return Nutrition(
kcal=round(sum((item.nutrition.kcal for item in items), 0.0), 1),
protein_g=round(sum((item.nutrition.protein_g for item in items), 0.0), 1),
carbs_g=round(sum((item.nutrition.carbs_g for item in items), 0.0), 1),
fat_g=round(sum((item.nutrition.fat_g for item in items), 0.0), 1),
grams=round(sum((item.nutrition.grams for item in items), 0.0), 1),
source=MEAL_SOURCE,
)