smart tdee

This commit is contained in:
2026-06-16 04:38:23 +00:00
parent f2e98942ff
commit a3f01cd850
56 changed files with 2519 additions and 591 deletions
+59 -56
View File
@@ -14,13 +14,14 @@ from app.db.models import (
WaterLog,
WorkoutLog,
)
from app.fitness.activity_budget import (
build_base_targets,
compute_activity_bonus,
estimate_workout_active_kcal,
scale_targets,
from app.fitness.activity_budget import estimate_workout_active_kcal
from app.fitness.calculators import (
compute_daily_targets,
compute_targets,
one_rep_max,
targets_to_api,
tdee_breakdown_to_api,
)
from app.fitness.calculators import compute_targets, one_rep_max
from app.fitness.body_composition import compute_body_composition
DEFAULT_REMINDERS = [
@@ -45,28 +46,26 @@ class FitnessService:
return None
return self._profile_to_dict(row)
def _profile_to_dict(self, row: FitnessProfile) -> dict[str, Any]:
targets = compute_targets(
{
"sex": row.sex,
"age": row.age,
"height_cm": row.height_cm,
"weight_kg": row.weight_kg,
"activity_level": row.activity_level,
"goal": row.goal,
}
)
def _profile_params(self, row: FitnessProfile) -> dict[str, Any]:
return {
"sex": row.sex,
"age": row.age,
"height_cm": row.height_cm,
"weight_kg": row.weight_kg,
"goal": row.goal,
"neat_base_kcal": row.neat_base_kcal,
}
def _profile_to_dict(self, row: FitnessProfile) -> dict[str, Any]:
targets = compute_targets(self._profile_params(row))
return {
"sex": row.sex,
"age": row.age,
"height_cm": row.height_cm,
"weight_kg": row.weight_kg,
"activity_level": row.activity_level,
"goal": row.goal,
"target_weight_kg": row.target_weight_kg,
"weekly_workouts": row.weekly_workouts,
"baseline_steps": row.baseline_steps,
"baseline_workout_kcal": row.baseline_workout_kcal,
"neat_base_kcal": row.neat_base_kcal,
"calorie_target": row.calorie_target,
"protein_g": row.protein_g,
"fat_g": row.fat_g,
@@ -85,23 +84,13 @@ class FitnessService:
self.db.flush()
for key in (
"sex", "age", "height_cm", "weight_kg", "activity_level",
"goal", "target_weight_kg", "weekly_workouts",
"baseline_steps", "baseline_workout_kcal",
"sex", "age", "height_cm", "weight_kg",
"goal", "target_weight_kg", "neat_base_kcal",
):
if key in updates and updates[key] is not None:
setattr(row, key, updates[key])
targets = compute_targets(
{
"sex": row.sex,
"age": row.age,
"height_cm": row.height_cm,
"weight_kg": row.weight_kg,
"activity_level": row.activity_level,
"goal": row.goal,
}
)
targets = compute_targets(self._profile_params(row))
row.calorie_target = targets["calorie_target"]
row.protein_g = targets["protein_g"]
row.fat_g = targets["fat_g"]
@@ -193,14 +182,12 @@ class FitnessService:
if profile:
return profile
return {
"calorie_target": 2000,
"protein_g": 140,
"fat_g": 65,
"carbs_g": 200,
"water_l": 2.5,
"weight_kg": 70,
"activity_level": "moderate",
"weekly_workouts": 3,
"height_cm": 170,
"age": 30,
"sex": "male",
"goal": "maintain",
"neat_base_kcal": 200,
}
@@ -248,24 +235,19 @@ class FitnessService:
"steps": steps_total,
}
base_targets = build_base_targets(profile)
activity = compute_activity_bonus(
daily = compute_daily_targets(
profile,
steps_total=steps_total,
workouts=workouts,
)
effective_targets, targets_base = scale_targets(
base_targets,
activity.total_bonus_kcal,
)
targets = targets_to_api(daily)
return {
"date": (day or datetime.now(timezone.utc).date()).isoformat(),
"profile_configured": profile_row is not None,
"totals": totals,
"targets": effective_targets,
"targets_base": targets_base,
"activity": activity.to_dict(),
"targets": targets,
"tdee_breakdown": tdee_breakdown_to_api(daily),
"meals": [self._food_to_dict(f) for f in foods],
"water": [self._water_to_dict(w) for w in waters],
"workouts": workouts,
@@ -393,8 +375,8 @@ class FitnessService:
"age": profile_row.age,
"height_cm": profile_row.height_cm,
"weight_kg": weight_kg,
"activity_level": profile_row.activity_level,
"goal": profile_row.goal,
"neat_base_kcal": profile_row.neat_base_kcal,
}
)
profile_row.calorie_target = targets["calorie_target"]
@@ -428,10 +410,27 @@ class FitnessService:
active_calories: float | None = None,
total_calories: float | None = None,
steps: int | None = None,
activity_type: str | None = None,
met: float | None = None,
logged_at: datetime | str | None = None,
day: date | None = None,
days_ago: int | None = None,
) -> dict[str, Any]:
profile = self.get_profile() or {}
weight_kg = float(profile.get("weight_kg") or 70)
if active_calories is None and duration_min and met is not None:
active_calories = round(met * weight_kg * (float(duration_min) / 60.0), 1)
elif active_calories is None and duration_min:
draft = {
"title": title,
"notes": notes,
"activity_type": activity_type,
"met": met,
"duration_min": duration_min,
}
active_calories = estimate_workout_active_kcal(draft, weight_kg=weight_kg) or None
row = WorkoutLog(
user_id=self.user_id,
title=title[:255],
@@ -471,12 +470,16 @@ class FitnessService:
).all()
profile = self.get_profile() or {}
weekly_target = int(profile.get("weekly_workouts") or 3)
weight_kg = float(profile.get("weight_kg") or 70)
weekly_target = 3
count = len(rows)
duration_min = sum(r.duration_min or 0 for r in rows)
active_kcal = round(
sum(estimate_workout_active_kcal(self._workout_to_dict(r)) for r in rows),
sum(
estimate_workout_active_kcal(self._workout_to_dict(r), weight_kg=weight_kg)
for r in rows
),
1,
)
@@ -583,7 +586,7 @@ class FitnessService:
*,
days: int = 7,
end_day: date | None = None,
include_targets_base: bool = True,
include_tdee_breakdown: bool = True,
) -> dict[str, Any]:
days = max(1, min(days, 90))
end = end_day or datetime.now(timezone.utc).date()
@@ -603,8 +606,8 @@ class FitnessService:
"meal_count": len(full["meals"]),
"workout_count": len(full["workouts"]),
}
if include_targets_base:
item["targets_base"] = full.get("targets_base")
if include_tdee_breakdown:
item["tdee_breakdown"] = full.get("tdee_breakdown")
summaries.append(item)
return {