// 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 <jself@gnu.org>
* Anton McClure <asm@gnu.org>
*/
#include "budget.h"
#include "i18n.h"
#include <math.h>
#include <stdio.h>
/* 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 "));
}
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>