fixed dynamic TDEE

This commit is contained in:
2026-06-16 08:04:15 +03:00
parent a3f01cd850
commit 0f2827030b
11 changed files with 603 additions and 18 deletions
+107
View File
@@ -21,6 +21,17 @@ PROTEIN_G_PER_KG = {
}
FAT_G_PER_KG = 1.0
EXPECTED_LOOKBACK_DAYS = 7
EXPECTED_MIN_DAYS_WITH_DATA = 3
DEFAULT_SESSION_KCAL = 350.0
ACTIVITY_LEVEL_STEPS: dict[str, int] = {
"sedentary": 5000,
"moderate": 8000,
"active": 10000,
"very_active": 12000,
}
def bmr_mifflin(*, sex: str, weight_kg: float, height_cm: float, age: int) -> float:
base = 10 * weight_kg + 6.25 * height_cm - 5 * age
@@ -166,3 +177,99 @@ def compute_targets(profile: dict[str, Any]) -> dict[str, Any]:
"carbs_g": daily["carbs_g"],
"water_l": daily["water_l"],
}
def _activity_level_steps(activity_level: str | None) -> int:
key = (activity_level or "moderate").lower().replace("-", "_")
return ACTIVITY_LEVEL_STEPS.get(key, ACTIVITY_LEVEL_STEPS["moderate"])
def _history_days_with_data(history: list[dict[str, Any]]) -> int:
return sum(
1
for row in history
if int(row.get("steps") or 0) > 0 or float(row.get("workout_kcal") or 0) > 0
)
def resolve_expected_activity(
profile: dict[str, Any],
*,
history: list[dict[str, Any]],
lookback_days: int = EXPECTED_LOOKBACK_DAYS,
) -> tuple[int, float, str, int]:
"""Return expected daily steps, workout kcal, source, and days_with_data."""
days_with_data = _history_days_with_data(history)
if days_with_data >= EXPECTED_MIN_DAYS_WITH_DATA:
steps_vals = [int(row.get("steps") or 0) for row in history]
workout_vals = [float(row.get("workout_kcal") or 0) for row in history]
expected_steps = round(sum(steps_vals) / len(steps_vals))
expected_workout_kcal = round(sum(workout_vals) / len(workout_vals), 1)
return expected_steps, expected_workout_kcal, "weekly_avg", days_with_data
baseline_steps = profile.get("baseline_steps")
baseline_workout_kcal = profile.get("baseline_workout_kcal")
if baseline_steps is not None or baseline_workout_kcal is not None:
steps = int(baseline_steps) if baseline_steps is not None else _activity_level_steps(
profile.get("activity_level")
)
workout_daily = (
round(float(baseline_workout_kcal) / 7, 1)
if baseline_workout_kcal is not None
else round(
int(profile.get("weekly_workouts") or 3) * DEFAULT_SESSION_KCAL / 7,
1,
)
)
return steps, workout_daily, "baseline", days_with_data
weekly_workouts = int(profile.get("weekly_workouts") or 3)
return (
_activity_level_steps(profile.get("activity_level")),
round(weekly_workouts * DEFAULT_SESSION_KCAL / 7, 1),
"defaults",
days_with_data,
)
def compute_expected_targets(
profile: dict[str, Any],
*,
history: list[dict[str, Any]],
lookback_days: int = EXPECTED_LOOKBACK_DAYS,
) -> dict[str, Any]:
expected_steps, expected_workout_kcal, source, days_with_data = resolve_expected_activity(
profile,
history=history,
lookback_days=lookback_days,
)
workouts = [{"active_calories": expected_workout_kcal}] if expected_workout_kcal > 0 else []
daily = compute_daily_targets(
profile,
steps_total=expected_steps,
workouts=workouts,
)
return {
**daily,
"source": source,
"lookback_days": lookback_days,
"days_with_data": days_with_data,
"expected_steps": expected_steps,
"expected_workout_kcal": expected_workout_kcal,
}
def tdee_expected_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("expected_steps", daily.get("steps", 0)),
"source": daily.get("source", "defaults"),
"lookback_days": daily.get("lookback_days", EXPECTED_LOOKBACK_DAYS),
"days_with_data": daily.get("days_with_data", 0),
}