"""Energy-expenditure helpers used by the inference layer. Mifflin-St Jeor is the standard modern estimator for resting metabolic rate. We use it to translate the user's profile into a personalized daily calorie target, which becomes one input to the RAG coach. """ from __future__ import annotations ACTIVITY_FACTORS: dict[str, float] = { "sedentary": 1.2, "light": 1.375, "moderate": 1.55, "active": 1.725, "very active": 1.9, } GOAL_DELTAS_KCAL: dict[str, int] = { "lose": -500, "maintain": 0, "gain": 300, } # Empirical training-data bounds for the UCI Obesity Levels dataset. # Profiles outside these ranges are extrapolations — the classifier should # not be trusted at face value there. TRAINING_BMI_RANGE: tuple[float, float] = (13.0, 50.8) TRAINING_WEIGHT_RANGE_KG: tuple[float, float] = (39.0, 173.0) TRAINING_HEIGHT_RANGE_CM: tuple[float, float] = (145.0, 198.0) TRAINING_AGE_RANGE: tuple[int, int] = (14, 61) # BMI cutoffs used to derive the rule-based "BMI band" class. The upper # bounds are anchored to WHO bands, with the Overweight split (I/II) and # the Obesity split (I/II/III) following the UCI dataset's own labeling # convention so the band matches the classifier's class names. _BMI_BAND_CUTOFFS: list[tuple[float, str]] = [ (18.5, "Insufficient_Weight"), (25.0, "Normal_Weight"), (27.5, "Overweight_Level_I"), (30.0, "Overweight_Level_II"), (35.0, "Obesity_Type_I"), (40.0, "Obesity_Type_II"), (float("inf"), "Obesity_Type_III"), ] def bmi_to_band(bmi: float) -> str: """Map a BMI value to the canonical 7-class band label. Rule-based, transparent, and immune to training-data skew — used as a sanity check against the classifier's prediction. """ for upper, label in _BMI_BAND_CUTOFFS: if bmi < upper: return label return "Obesity_Type_III" def bmr_mifflin_st_jeor(age: int, weight_kg: float, height_cm: float, sex: str = "male") -> float: s = 5 if sex.lower().startswith("m") else -161 return 10.0 * weight_kg + 6.25 * height_cm - 5.0 * age + s def tdee(bmr: float, activity_level: str) -> float: return bmr * ACTIVITY_FACTORS[activity_level] def daily_target_kcal(age: int, weight_kg: float, height_cm: float, sex: str, activity_level: str, goal: str) -> float: base = tdee(bmr_mifflin_st_jeor(age, weight_kg, height_cm, sex), activity_level) return base + GOAL_DELTAS_KCAL[goal] def macro_targets(target_kcal: float, goal: str) -> dict[str, float]: splits = { "lose": (0.35, 0.30, 0.35), "maintain": (0.25, 0.30, 0.45), "gain": (0.25, 0.25, 0.50), } p_pct, f_pct, c_pct = splits[goal] return { "protein_g": (target_kcal * p_pct) / 4.0, "fat_g": (target_kcal * f_pct) / 9.0, "carbohydrate_g": (target_kcal * c_pct) / 4.0, "sodium_mg": 2000.0, }