first commit 稳定版本,已知该版本存在问题,前端未对数据进行校验,没有集成导出excel
This commit is contained in:
commit
e5030abf39
40 files changed
+5186
No files matched your search
@@ -0,0 +1,174 @@
|
||||
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
|
||||
@@ -0,0 +1,19 @@
|
||||
import os
|
||||
from services.nutrition_service import NutritionService
|
||||
from pdf_builder import NutritionPDFBuilder
|
||||
|
||||
|
||||
class NutritionReportService:
|
||||
def __init__(self):
|
||||
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
food_db_path = os.path.join(base_dir, "food_db.json")
|
||||
config_path = os.path.join(base_dir, "config", "nutrition_template.json")
|
||||
|
||||
self.nutrition_service = NutritionService(food_db_path, config_path)
|
||||
self.pdf_builder = NutritionPDFBuilder()
|
||||
|
||||
def generate_report_from_json(self, front_json, output_path):
|
||||
report_data = self.nutrition_service.build_report_data(front_json)
|
||||
self.pdf_builder.build(report_data, output_path)
|
||||
return report_data
|
||||
Reference in new issue
Block a user