// SPDX-License-Identifier: GPL-3.0-or-later /* * $Id: budget.c,v 1.1 2026/05/08 03:23:57 asm Exp $ * * budget.c - USDA Food Pattern budget system for GNUtrition * * Copyright (C) 2026 Free Software Foundation, Inc. * * Author: Jason Self * Anton McClure */ #include "budget.h" #include "i18n.h" #include #include /* USDA Healthy US-Style Eating Pattern table. Source: Dietary Guidelines for Americans, 2020-2025, Appendix 3. Columns: kcal, vegetables (cup-eq), fruits (cup-eq), grains (oz-eq), dairy (cup-eq), protein (oz-eq), oils (grams). */ static const struct daily_budget usda_table[] = { { 1000, 1.0, 1.0, 3.0, 2.0, 2.0, 13.0 }, { 1200, 1.5, 1.0, 4.0, 2.0, 2.0, 17.0 }, { 1400, 1.5, 1.5, 5.0, 2.5, 4.0, 17.0 }, { 1600, 2.0, 1.5, 5.0, 2.5, 5.0, 22.0 }, { 1800, 2.5, 1.5, 6.0, 3.0, 5.0, 24.0 }, { 2000, 2.5, 2.0, 6.0, 3.0, 5.5, 27.0 }, { 2200, 3.0, 2.0, 7.0, 3.0, 6.0, 29.0 }, { 2400, 3.0, 2.0, 8.0, 3.0, 6.5, 32.0 }, { 2600, 3.5, 2.0, 9.0, 3.0, 6.5, 34.0 }, { 2800, 3.5, 2.5, 10.0, 3.0, 7.0, 36.0 }, { 3000, 4.0, 2.5, 10.0, 3.0, 7.0, 40.0 }, { 3200, 4.0, 2.5, 10.0, 3.0, 7.0, 44.0 }, }; #define TABLE_SIZE (sizeof usda_table / sizeof usda_table[0]) /* Activity-factor multipliers (Mifflin-St Jeor convention). */ static const double activity_factors[] = { 1.2, /* ACTIVITY_SEDENTARY */ 1.375, /* ACTIVITY_LIGHT */ 1.55, /* ACTIVITY_MODERATE */ 1.725, /* ACTIVITY_VERY_ACTIVE */ 1.9 /* ACTIVITY_EXTRA_ACTIVE */ }; /* Linearly interpolate between A and B by fraction T (0.0 to 1.0). */ static double lerp (double a, double b, double t) { return a + (b - a) * t; } int budget_round_to_pattern (int kcal_raw) { int rounded; rounded = ((kcal_raw + 100) / 200) * 200; if (rounded < 1000) rounded = 1000; if (rounded > 3200) rounded = 3200; return rounded; } int budget_estimate_calories (int age_years, double height_cm, double weight_kg, enum activity_level activity, enum user_gender gender) { double bmr; double tdee; double af; double go; /* Mifflin-St Jeor with neutral "midpoint" default. Male constant is +5, female is -161; midpoint is -78. BMR = 10 * weight_kg + 6.25 * height_cm - 5 * age - 78 */ switch (gender) { default: go = -78; break; case GENDER_FEMALE: go = -161; break; case GENDER_MALE: go = 5; break; } bmr = (10.0 * weight_kg) + (6.25 * height_cm) - (5.0 * age_years) + go; if ((int) activity >= 0 && (int) activity < (int) (sizeof activity_factors / sizeof activity_factors[0])) af = activity_factors[activity]; else af = activity_factors[ACTIVITY_SEDENTARY]; tdee = bmr * af; return budget_round_to_pattern ((int) round (tdee)); } struct daily_budget budget_for_calories (int kcal) { struct daily_budget b; size_t i; double t; /* Clamp to table range. */ if (kcal <= usda_table[0].calories) return usda_table[0]; if (kcal >= usda_table[TABLE_SIZE - 1].calories) return usda_table[TABLE_SIZE - 1]; /* Find the bracketing entries and interpolate. */ for (i = 0; i < TABLE_SIZE - 1; i++) { if (kcal >= usda_table[i].calories && kcal <= usda_table[i + 1].calories) { t = (double) (kcal - usda_table[i].calories) / (double) (usda_table[i + 1].calories - usda_table[i].calories); b.calories = kcal; b.vegetables = lerp (usda_table[i].vegetables, usda_table[i + 1].vegetables, t); b.fruits = lerp (usda_table[i].fruits, usda_table[i + 1].fruits, t); b.grains = lerp (usda_table[i].grains, usda_table[i + 1].grains, t); b.dairy = lerp (usda_table[i].dairy, usda_table[i + 1].dairy, t); b.protein = lerp (usda_table[i].protein, usda_table[i + 1].protein, t); b.oils = lerp (usda_table[i].oils, usda_table[i + 1].oils, t); return b; } } /* Should not reach here; the loop above covers all cases. */ return usda_table[TABLE_SIZE - 1]; } struct daily_budget budget_get_default (void) { return budget_for_calories (2000); } void budget_print (const struct daily_budget *budget, const struct daily_budget *consumed) { printf (_("Daily budget (%d kcal USDA Healthy US-Style Eating Pattern):\n\n"), budget->calories); printf (_("%-20s %10s %10s %10s\n"), _("Food Group"), _("Budget"), _("Consumed"), _("Remaining")); printf ("%-20s %10s %10s %10s\n", "--------------------", "----------", "----------", "----------"); printf (_("%-20s %8.1f %s %8.1f %s %8.1f %s\n"), _("Vegetables"), budget->vegetables, _("c "), consumed->vegetables, _("c "), budget->vegetables - consumed->vegetables, _("c ")); printf (_("%-20s %8.1f %s %8.1f %s %8.1f %s\n"), _("Fruits"), budget->fruits, _("c "), consumed->fruits, _("c "), budget->fruits - consumed->fruits, _("c ")); printf (_("%-20s %8.1f %s %8.1f %s %8.1f %s\n"), _("Grains"), budget->grains, _("oz"), consumed->grains, _("oz"), budget->grains - consumed->grains, _("oz")); printf (_("%-20s %8.1f %s %8.1f %s %8.1f %s\n"), _("Dairy"), budget->dairy, _("c "), consumed->dairy, _("c "), budget->dairy - consumed->dairy, _("c ")); printf (_("%-20s %8.1f %s %8.1f %s %8.1f %s\n"), _("Protein Foods"), budget->protein, _("oz"), consumed->protein, _("oz"), budget->protein - consumed->protein, _("oz")); printf (_("%-20s %8.1f %s %8.1f %s %8.1f %s\n"), _("Oils"), budget->oils, _("g "), consumed->oils, _("g "), budget->oils - consumed->oils, _("g ")); }