174 lines
5.3 KiB
Python
174 lines
5.3 KiB
Python
import json
|
|
from collections import defaultdict
|
|
|
|
|
|
class NutritionService:
|
|
def __init__(self, food_db_path, config_path):
|
|
with open(food_db_path, "r", encoding="utf-8") as f:
|
|
self.food_db = json.load(f)
|
|
|
|
with open(config_path, "r", encoding="utf-8") as f:
|
|
self.config = json.load(f)
|
|
|
|
def build_report_data(self, front_json):
|
|
meals = front_json.get("meals", [])
|
|
|
|
merged_meals = self._merge_meals(meals)
|
|
food_details, nutrition_total = self._calculate_nutrition(merged_meals)
|
|
category_result = self._build_category_result_from_foods(food_details)
|
|
suggestions = self._generate_suggestions(category_result)
|
|
|
|
return {
|
|
"user": front_json.get("user", {}),
|
|
"date": front_json.get("date"),
|
|
"foods": food_details,
|
|
"category_result": category_result,
|
|
"suggestions": suggestions,
|
|
"nutrition": nutrition_total
|
|
}
|
|
|
|
def _merge_meals(self, meals):
|
|
meal_sum = defaultdict(float)
|
|
|
|
for item in meals:
|
|
name = str(item.get("name", "")).strip()
|
|
amount = item.get("amount", 0)
|
|
|
|
if not name:
|
|
continue
|
|
|
|
try:
|
|
amount = float(amount)
|
|
except Exception:
|
|
amount = 0
|
|
|
|
meal_sum[name] += amount
|
|
|
|
return meal_sum
|
|
|
|
def _calculate_nutrition(self, merged_meals):
|
|
details = []
|
|
|
|
total_energy = 0.0
|
|
total_protein = 0.0
|
|
total_fat = 0.0
|
|
total_carb = 0.0
|
|
total_calcium = 0.0
|
|
|
|
for food_name, grams in merged_meals.items():
|
|
food_info = self.food_db.get(food_name)
|
|
|
|
if not food_info:
|
|
# 如果没匹配到食物库,可以先跳过
|
|
continue
|
|
|
|
factor = grams / 100.0
|
|
|
|
energy = self._safe_float(food_info.get("energy")) * factor
|
|
protein = self._safe_float(food_info.get("protein")) * factor
|
|
fat = self._safe_float(food_info.get("fat")) * factor
|
|
carbohydrate = self._safe_float(food_info.get("carbohydrate")) * factor
|
|
calcium = self._safe_float(food_info.get("calcium")) * factor
|
|
|
|
total_energy += energy
|
|
total_protein += protein
|
|
total_fat += fat
|
|
total_carb += carbohydrate
|
|
total_calcium += calcium
|
|
|
|
details.append({
|
|
"name": food_name,
|
|
"amount": round(grams, 1),
|
|
"category": food_info.get("category", ""),
|
|
"nutrition": {
|
|
"energy": round(energy, 2),
|
|
"protein": round(protein, 2),
|
|
"fat": round(fat, 2),
|
|
"carbohydrate": round(carbohydrate, 2),
|
|
"calcium": round(calcium, 2)
|
|
}
|
|
})
|
|
|
|
nutrition_total = {
|
|
"energy": {
|
|
"value": round(total_energy, 2),
|
|
"evaluation": self._eval_nutrition(total_energy, "energy")
|
|
},
|
|
"protein": round(total_protein, 2),
|
|
"fat": round(total_fat, 2),
|
|
"carbohydrate": round(total_carb, 2),
|
|
"calcium": {
|
|
"value": round(total_calcium, 2),
|
|
"evaluation": self._eval_nutrition(total_calcium, "calcium")
|
|
}
|
|
}
|
|
|
|
return details, nutrition_total
|
|
|
|
def _build_category_result_from_foods(self, food_details):
|
|
# 1. 先把配置里的所有分类都初始化为 0
|
|
category_sum = {
|
|
category: 0.0
|
|
for category in self.config["food_category_map"].keys()
|
|
}
|
|
|
|
# 2. 把实际食物摄入量累加进去
|
|
for item in food_details:
|
|
category = item.get("category", "").strip()
|
|
amount = self._safe_float(item.get("amount", 0))
|
|
|
|
if category in category_sum:
|
|
category_sum[category] += amount
|
|
|
|
# 3. 所有分类都生成评价结果
|
|
result = {}
|
|
for cat, val in category_sum.items():
|
|
result[cat] = {
|
|
"value": round(val, 1),
|
|
"evaluation": self._evaluate_category(cat, val)
|
|
}
|
|
|
|
return result
|
|
|
|
def _evaluate_category(self, category, value):
|
|
rules = self.config["category_evaluation"].get(
|
|
category,
|
|
self.config["category_evaluation"].get("default", [])
|
|
)
|
|
|
|
for r in rules:
|
|
if r["min"] <= value <= r["max"]:
|
|
return r["label"]
|
|
|
|
return "-"
|
|
|
|
def _generate_suggestions(self, category_result):
|
|
rules = self.config["suggestion_rules"]
|
|
res = []
|
|
|
|
for cat, info in category_result.items():
|
|
label = info["evaluation"]
|
|
if label in rules and rules[label]:
|
|
res.append(rules[label].replace("{category}", cat))
|
|
|
|
return res
|
|
|
|
def _eval_nutrition(self, value, key):
|
|
rules = self.config["nutrition_eval"][key]
|
|
|
|
try:
|
|
value = float(value)
|
|
except Exception:
|
|
return "-"
|
|
|
|
for r in rules:
|
|
if r["min"] <= value <= r["max"]:
|
|
return r["label"]
|
|
|
|
return "-"
|
|
|
|
def _safe_float(self, value, default=0.0):
|
|
try:
|
|
return float(value)
|
|
except Exception:
|
|
return default |