feat(api): 添加营养建议端点 /api/nutrition/recommendations

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-27 16:19:56 +08:00
parent a8c9c87540
commit e1cef3ab4b
2 changed files with 124 additions and 0 deletions

View File

@@ -1264,6 +1264,119 @@ async def get_bmi_analysis():
}
@app.get("/api/nutrition/recommendations")
async def get_nutrition_recommendations():
"""获取营养建议数据"""
active_user = db.get_active_user()
if not active_user:
raise HTTPException(status_code=400, detail="没有激活的用户")
config = db.get_config(active_user.id)
tdee = config.tdee or 1800
# 根据目标调整热量目标
goal_multipliers = {
"lose": 0.8, # 减重: 80% TDEE
"maintain": 1.0, # 维持: 100% TDEE
"gain": 1.15 # 增重: 115% TDEE
}
calorie_target = int(tdee * goal_multipliers.get(config.goal, 1.0))
# 营养素目标 (基于 TDEE)
# 蛋白质 25%, 碳水 50%, 脂肪 25%
daily_targets = {
"calories": calorie_target,
"protein": round(calorie_target * 0.25 / 4), # 每克蛋白质 4 卡
"carbs": round(calorie_target * 0.50 / 4), # 每克碳水 4 卡
"fat": round(calorie_target * 0.25 / 9), # 每克脂肪 9 卡
}
# 建议区间
recommended_ranges = {
"protein": {"min": round(calorie_target * 0.20 / 4), "max": round(calorie_target * 0.30 / 4)},
"carbs": {"min": round(calorie_target * 0.45 / 4), "max": round(calorie_target * 0.55 / 4)},
"fat": {"min": round(calorie_target * 0.20 / 9), "max": round(calorie_target * 0.30 / 9)},
}
# 今日摄入
today = date.today()
today_meals = db.get_meals(start_date=today, end_date=today, user_id=active_user.id)
today_intake = {
"calories": sum(m.calories for m in today_meals),
"protein": sum(m.protein or 0 for m in today_meals),
"carbs": sum(m.carbs or 0 for m in today_meals),
"fat": sum(m.fat or 0 for m in today_meals),
}
# 计算差距
gaps = {
"calories": daily_targets["calories"] - today_intake["calories"],
"protein": daily_targets["protein"] - today_intake["protein"],
"carbs": daily_targets["carbs"] - today_intake["carbs"],
"fat": daily_targets["fat"] - today_intake["fat"],
}
# 餐次分布
meal_type_calories = {}
for m in today_meals:
meal_type_calories[m.meal_type] = meal_type_calories.get(m.meal_type, 0) + m.calories
meal_distribution = {
"早餐": {"actual": meal_type_calories.get("早餐", 0), "recommended_pct": "25-30%"},
"午餐": {"actual": meal_type_calories.get("午餐", 0), "recommended_pct": "35-40%"},
"晚餐": {"actual": meal_type_calories.get("晚餐", 0), "recommended_pct": "25-30%"},
"加餐": {"actual": meal_type_calories.get("加餐", 0), "recommended_pct": "0-10%"},
}
# 生成建议
suggestions = []
if gaps["protein"] > 20:
suggestions.append({
"type": "protein",
"message": f"蛋白质缺口较大 ({gaps['protein']}g),建议增加:",
"options": [
"鸡胸肉 150g (+46g 蛋白质, 165 卡)",
"鸡蛋 3 个 (+18g 蛋白质, 210 卡)",
"豆腐 200g (+16g 蛋白质, 160 卡)",
]
})
if gaps["calories"] > 300:
suggestions.append({
"type": "calories",
"message": f"热量摄入不足 ({gaps['calories']} 卡),注意补充能量",
"options": []
})
# 近 7 天趋势
week_start = today - timedelta(days=6)
week_meals = db.get_meals(start_date=week_start, end_date=today, user_id=active_user.id)
weekly_trend = []
for i in range(7):
day = week_start + timedelta(days=i)
day_meals = [m for m in week_meals if m.date == day]
weekly_trend.append({
"date": day.isoformat(),
"calories": sum(m.calories for m in day_meals),
"protein": sum(m.protein or 0 for m in day_meals),
"carbs": sum(m.carbs or 0 for m in day_meals),
"fat": sum(m.fat or 0 for m in day_meals),
})
return {
"tdee": tdee,
"goal": config.goal,
"daily_targets": daily_targets,
"recommended_ranges": recommended_ranges,
"today_intake": today_intake,
"gaps": gaps,
"meal_distribution": meal_distribution,
"suggestions": suggestions,
"weekly_trend": weekly_trend,
}
@app.get("/api/meals", response_model=list[MealResponse])
async def get_meals(
days: int = Query(default=30, ge=1, le=365, description="查询天数"),

View File

@@ -317,3 +317,14 @@ def test_bmi_analysis_endpoint(client):
assert "bmi_status" in data
assert "target_weight" in data
assert "estimated_days" in data
def test_nutrition_recommendations_endpoint(client):
"""测试营养建议 API"""
response = client.get("/api/nutrition/recommendations")
assert response.status_code == 200
data = response.json()
assert "daily_targets" in data
assert "today_intake" in data
assert "gaps" in data
assert "suggestions" in data