smart tdee
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
from typing import Any
|
||||
|
||||
ACTIVITY_MULTIPLIERS = {
|
||||
"sedentary": 1.2,
|
||||
"light": 1.375,
|
||||
"moderate": 1.55,
|
||||
"active": 1.725,
|
||||
"very_active": 1.9,
|
||||
}
|
||||
from app.fitness.activity_budget import workouts_kcal_total
|
||||
|
||||
DEFAULT_NEAT_KCAL = 200.0
|
||||
NEAT_KCAL_MIN = 200.0
|
||||
NEAT_KCAL_MAX = 300.0
|
||||
KCAL_PER_STEP_REF = 0.04 / 86 # ~0.04 kcal/step at 86 kg
|
||||
WATER_ML_PER_KG = 33 # middle of 30–35 ml/kg range
|
||||
|
||||
GOAL_CALORIE_ADJUST = {
|
||||
"lose": -500,
|
||||
@@ -14,6 +14,13 @@ GOAL_CALORIE_ADJUST = {
|
||||
"gain": 300,
|
||||
}
|
||||
|
||||
PROTEIN_G_PER_KG = {
|
||||
"lose": 2.2,
|
||||
"maintain": 1.8,
|
||||
"gain": 1.8,
|
||||
}
|
||||
FAT_G_PER_KG = 1.0
|
||||
|
||||
|
||||
def bmr_mifflin(*, sex: str, weight_kg: float, height_cm: float, age: int) -> float:
|
||||
base = 10 * weight_kg + 6.25 * height_cm - 5 * age
|
||||
@@ -22,17 +29,17 @@ def bmr_mifflin(*, sex: str, weight_kg: float, height_cm: float, age: int) -> fl
|
||||
return base - 161
|
||||
|
||||
|
||||
def tdee(
|
||||
*,
|
||||
sex: str,
|
||||
weight_kg: float,
|
||||
height_cm: float,
|
||||
age: int,
|
||||
activity_level: str = "moderate",
|
||||
) -> float:
|
||||
bmr = bmr_mifflin(sex=sex, weight_kg=weight_kg, height_cm=height_cm, age=age)
|
||||
mult = ACTIVITY_MULTIPLIERS.get(activity_level, 1.55)
|
||||
return bmr * mult
|
||||
def neat_base_kcal(profile: dict[str, Any]) -> float:
|
||||
raw = profile.get("neat_base_kcal")
|
||||
if raw is not None:
|
||||
return max(NEAT_KCAL_MIN, min(NEAT_KCAL_MAX, float(raw)))
|
||||
return DEFAULT_NEAT_KCAL
|
||||
|
||||
|
||||
def steps_kcal(*, steps: int, weight_kg: float) -> float:
|
||||
if steps <= 0:
|
||||
return 0.0
|
||||
return round(steps * weight_kg * KCAL_PER_STEP_REF, 1)
|
||||
|
||||
|
||||
def bmi(weight_kg: float, height_cm: float) -> float:
|
||||
@@ -43,7 +50,7 @@ def bmi(weight_kg: float, height_cm: float) -> float:
|
||||
|
||||
|
||||
def water_target_l(weight_kg: float) -> float:
|
||||
return round(weight_kg * 0.033, 1)
|
||||
return round(weight_kg * WATER_ML_PER_KG / 1000, 1)
|
||||
|
||||
|
||||
def macro_targets(
|
||||
@@ -51,8 +58,8 @@ def macro_targets(
|
||||
weight_kg: float,
|
||||
goal: str = "maintain",
|
||||
) -> dict[str, float]:
|
||||
protein_g = round(weight_kg * (2.0 if goal == "gain" else 1.8), 0)
|
||||
fat_g = round((calorie_target * 0.25) / 9, 0)
|
||||
protein_g = round(weight_kg * PROTEIN_G_PER_KG.get(goal, 1.8), 0)
|
||||
fat_g = round(weight_kg * FAT_G_PER_KG, 0)
|
||||
protein_cal = protein_g * 4
|
||||
fat_cal = fat_g * 9
|
||||
carbs_g = max(0, round((calorie_target - protein_cal - fat_cal) / 4, 0))
|
||||
@@ -67,28 +74,95 @@ def one_rep_max(weight_kg: float, reps: int) -> float:
|
||||
return round(weight_kg * (1 + reps / 30), 1)
|
||||
|
||||
|
||||
def compute_targets(profile: dict[str, Any]) -> dict[str, Any]:
|
||||
def _profile_fields(profile: dict[str, Any]) -> tuple[float, float, int, str, str]:
|
||||
weight = float(profile.get("weight_kg") or 70)
|
||||
height = float(profile.get("height_cm") or 170)
|
||||
age = int(profile.get("age") or 30)
|
||||
sex = str(profile.get("sex") or "male")
|
||||
activity = str(profile.get("activity_level") or "moderate")
|
||||
goal = str(profile.get("goal") or "maintain")
|
||||
return weight, height, age, sex, goal
|
||||
|
||||
tdee_val = tdee(
|
||||
sex=sex, weight_kg=weight, height_cm=height, age=age, activity_level=activity
|
||||
)
|
||||
calorie_target = round(tdee_val + GOAL_CALORIE_ADJUST.get(goal, 0), 0)
|
||||
|
||||
def compute_tdee(
|
||||
profile: dict[str, Any],
|
||||
*,
|
||||
steps_total: int = 0,
|
||||
workouts: list[dict[str, Any]] | None = None,
|
||||
) -> dict[str, float]:
|
||||
weight, height, age, sex, _ = _profile_fields(profile)
|
||||
bmr = bmr_mifflin(sex=sex, weight_kg=weight, height_cm=height, age=age)
|
||||
neat = neat_base_kcal(profile)
|
||||
s_kcal = steps_kcal(steps=steps_total, weight_kg=weight)
|
||||
w_kcal = workouts_kcal_total(workouts or [], weight_kg=weight)
|
||||
tdee_val = bmr + neat + s_kcal + w_kcal
|
||||
return {
|
||||
"bmr": round(bmr, 0),
|
||||
"neat_kcal": round(neat, 0),
|
||||
"steps_kcal": s_kcal,
|
||||
"workout_kcal": w_kcal,
|
||||
"tdee": round(tdee_val, 0),
|
||||
}
|
||||
|
||||
|
||||
def compute_daily_targets(
|
||||
profile: dict[str, Any],
|
||||
*,
|
||||
steps_total: int = 0,
|
||||
workouts: list[dict[str, Any]] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
weight, height, age, sex, goal = _profile_fields(profile)
|
||||
breakdown = compute_tdee(profile, steps_total=steps_total, workouts=workouts)
|
||||
calorie_target = round(breakdown["tdee"] + GOAL_CALORIE_ADJUST.get(goal, 0), 0)
|
||||
macros = macro_targets(calorie_target, weight, goal)
|
||||
water = water_target_l(weight)
|
||||
|
||||
return {
|
||||
"bmr": round(bmr_mifflin(sex=sex, weight_kg=weight, height_cm=height, age=age), 0),
|
||||
"tdee": round(tdee_val, 0),
|
||||
"bmi": round(bmi(weight, height), 1),
|
||||
**breakdown,
|
||||
"calorie_target": calorie_target,
|
||||
"protein_g": macros["protein_g"],
|
||||
"fat_g": macros["fat_g"],
|
||||
"carbs_g": macros["carbs_g"],
|
||||
"water_l": water,
|
||||
"bmi": round(bmi(weight, height), 1),
|
||||
"steps": steps_total,
|
||||
}
|
||||
|
||||
|
||||
def targets_to_api(daily: dict[str, Any]) -> dict[str, float]:
|
||||
return {
|
||||
"calories": daily["calorie_target"],
|
||||
"protein_g": daily["protein_g"],
|
||||
"fat_g": daily["fat_g"],
|
||||
"carbs_g": daily["carbs_g"],
|
||||
"water_ml": round(daily["water_l"] * 1000),
|
||||
}
|
||||
|
||||
|
||||
def tdee_breakdown_to_api(daily: dict[str, Any]) -> dict[str, Any]:
|
||||
return {
|
||||
"bmr": daily["bmr"],
|
||||
"neat_kcal": daily["neat_kcal"],
|
||||
"steps_kcal": daily["steps_kcal"],
|
||||
"workout_kcal": daily["workout_kcal"],
|
||||
"tdee": daily["tdee"],
|
||||
"calorie_target": daily["calorie_target"],
|
||||
"steps": daily.get("steps", 0),
|
||||
}
|
||||
|
||||
|
||||
def compute_targets(profile: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Rest-day targets (BMR + NEAT, no steps/workouts) for profile storage."""
|
||||
daily = compute_daily_targets(profile, steps_total=0, workouts=[])
|
||||
return {
|
||||
"bmr": daily["bmr"],
|
||||
"tdee": daily["tdee"],
|
||||
"bmi": daily["bmi"],
|
||||
"neat_kcal": daily["neat_kcal"],
|
||||
"steps_kcal": 0,
|
||||
"workout_kcal": 0,
|
||||
"calorie_target": daily["calorie_target"],
|
||||
"protein_g": daily["protein_g"],
|
||||
"fat_g": daily["fat_g"],
|
||||
"carbs_g": daily["carbs_g"],
|
||||
"water_l": daily["water_l"],
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user