v3 增加并优化了人工评价逻辑,增加了运动量(PAL)判断
This commit is contained in:
1 parent
86567eece5
commit
2e69000c5b
22 files changed
+2250
-332
No files matched your search
@@ -1,4 +1,5 @@
|
||||
import json
|
||||
import os
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
@@ -10,23 +11,85 @@ class NutritionService:
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
self.config = json.load(f)
|
||||
|
||||
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
eer_path = os.path.join(base_dir, "config", "female_energy_eer.json")
|
||||
with open(eer_path, "r", encoding="utf-8") as f:
|
||||
self.female_eer = json.load(f)
|
||||
|
||||
def build_report_data(self, front_json):
|
||||
user = front_json.get("user", {})
|
||||
meals = front_json.get("meals", [])
|
||||
auto_evaluation = front_json.get("autoEvaluation", True)
|
||||
|
||||
merged_meals = self._merge_meals(meals)
|
||||
food_details, nutrition_total = self._calculate_nutrition(merged_meals)
|
||||
food_details, nutrition_total = self._calculate_nutrition(merged_meals, user)
|
||||
category_result = self._build_category_result_from_foods(food_details)
|
||||
suggestions = self._generate_suggestions(category_result)
|
||||
interpretation = None
|
||||
|
||||
return {
|
||||
"user": front_json.get("user", {}),
|
||||
if not auto_evaluation:
|
||||
manual = front_json.get("manualEvaluations") or {}
|
||||
category_result, suggestions, nutrition_total, interpretation = (
|
||||
self._apply_manual_evaluations(
|
||||
category_result, suggestions, nutrition_total, manual
|
||||
)
|
||||
)
|
||||
|
||||
report = {
|
||||
"user": user,
|
||||
"date": front_json.get("date"),
|
||||
"foods": food_details,
|
||||
"category_result": category_result,
|
||||
"suggestions": suggestions,
|
||||
"nutrition": nutrition_total
|
||||
"nutrition": nutrition_total,
|
||||
"autoEvaluation": auto_evaluation,
|
||||
}
|
||||
|
||||
if interpretation is not None:
|
||||
report["interpretation"] = interpretation
|
||||
|
||||
return report
|
||||
|
||||
def build_intake_preview(self, front_json):
|
||||
"""复用营养计算逻辑,返回各类摄入量与营养素合计(供手动评价参考)"""
|
||||
user = front_json.get("user", {})
|
||||
meals = front_json.get("meals", [])
|
||||
|
||||
merged_meals = self._merge_meals(meals)
|
||||
food_details, nutrition_total = self._calculate_nutrition(merged_meals, user)
|
||||
category_result = self._build_category_result_from_foods(food_details)
|
||||
|
||||
return {
|
||||
"category_result": category_result,
|
||||
"nutrition": nutrition_total,
|
||||
"foods": food_details,
|
||||
}
|
||||
|
||||
def _apply_manual_evaluations(self, category_result, suggestions, nutrition_total, manual):
|
||||
category_evals = manual.get("categoryEvaluations") or {}
|
||||
for cat, info in category_result.items():
|
||||
if cat in category_evals and str(category_evals[cat]).strip():
|
||||
info["evaluation"] = str(category_evals[cat]).strip()
|
||||
|
||||
overall = str(manual.get("overallSuggestions", "")).strip()
|
||||
suggestions = [
|
||||
line.strip()
|
||||
for line in overall.splitlines()
|
||||
if line.strip()
|
||||
]
|
||||
|
||||
energy_eval = str(manual.get("energyEvaluation", "")).strip()
|
||||
if energy_eval:
|
||||
nutrition_total["energy"]["evaluation"] = energy_eval
|
||||
|
||||
calcium_eval = str(manual.get("calciumEvaluation", "")).strip()
|
||||
if calcium_eval:
|
||||
nutrition_total["calcium"]["evaluation"] = calcium_eval
|
||||
|
||||
interpretation = str(manual.get("interpretation", "")).strip()
|
||||
|
||||
return category_result, suggestions, nutrition_total, interpretation
|
||||
|
||||
def _merge_meals(self, meals):
|
||||
meal_sum = defaultdict(float)
|
||||
|
||||
@@ -46,7 +109,7 @@ class NutritionService:
|
||||
|
||||
return meal_sum
|
||||
|
||||
def _calculate_nutrition(self, merged_meals):
|
||||
def _calculate_nutrition(self, merged_meals, user=None):
|
||||
details = []
|
||||
|
||||
total_energy = 0.0
|
||||
@@ -59,7 +122,6 @@ class NutritionService:
|
||||
food_info = self.food_db.get(food_name)
|
||||
|
||||
if not food_info:
|
||||
# 如果没匹配到食物库,可以先跳过
|
||||
continue
|
||||
|
||||
factor = grams / 100.0
|
||||
@@ -89,10 +151,18 @@ class NutritionService:
|
||||
}
|
||||
})
|
||||
|
||||
energy_standard = self._lookup_energy_eer(user or {})
|
||||
if energy_standard is not None:
|
||||
energy_eval = self._eval_energy_against_standard(total_energy, energy_standard)
|
||||
else:
|
||||
energy_standard = None
|
||||
energy_eval = self._eval_nutrition(total_energy, "energy")
|
||||
|
||||
nutrition_total = {
|
||||
"energy": {
|
||||
"value": round(total_energy, 2),
|
||||
"evaluation": self._eval_nutrition(total_energy, "energy")
|
||||
"standard": energy_standard,
|
||||
"evaluation": energy_eval
|
||||
},
|
||||
"protein": round(total_protein, 2),
|
||||
"fat": round(total_fat, 2),
|
||||
@@ -105,6 +175,66 @@ class NutritionService:
|
||||
|
||||
return details, nutrition_total
|
||||
|
||||
def _lookup_energy_eer(self, user):
|
||||
gender = str(user.get("gender", "")).strip()
|
||||
if gender != "female":
|
||||
return None
|
||||
|
||||
age = self._safe_float(user.get("age"), default=-1)
|
||||
weight = self._safe_float(user.get("weight"))
|
||||
activity = str(user.get("activityLevel", "")).strip()
|
||||
|
||||
if age < 0 or not activity:
|
||||
return None
|
||||
|
||||
age_group = None
|
||||
for group in self.female_eer.get("age_groups", []):
|
||||
if group["age_min"] <= age < group["age_max"]:
|
||||
age_group = group
|
||||
break
|
||||
|
||||
if not age_group:
|
||||
return None
|
||||
|
||||
level_data = age_group.get("levels", {}).get(activity)
|
||||
if not level_data:
|
||||
return None
|
||||
|
||||
if level_data.get("type") == "per_kg":
|
||||
if weight <= 0:
|
||||
return None
|
||||
return round(level_data["kcal"] * weight, 1)
|
||||
|
||||
standard = level_data["kcal"]
|
||||
|
||||
stage = str(user.get("physiologicalStage", "")).strip()
|
||||
stages = self.female_eer.get("physiological_stages", {})
|
||||
if stage and stage in stages:
|
||||
standard += stages[stage].get("bonus_kcal", 0)
|
||||
|
||||
return standard
|
||||
|
||||
def _eval_energy_against_standard(self, value, standard):
|
||||
rules = self.female_eer.get("evaluation_rules", {})
|
||||
under_ratio = rules.get("under_ratio", 0.9)
|
||||
over_ratio = rules.get("over_ratio", 1.1)
|
||||
|
||||
try:
|
||||
value = float(value)
|
||||
standard = float(standard)
|
||||
except Exception:
|
||||
return "-"
|
||||
|
||||
if standard <= 0:
|
||||
return "-"
|
||||
|
||||
ratio = value / standard
|
||||
if ratio < under_ratio:
|
||||
return rules.get("under_label", "不足")
|
||||
if ratio <= over_ratio:
|
||||
return rules.get("adequate_label", "适宜")
|
||||
return rules.get("over_label", "偏高")
|
||||
|
||||
def _build_category_result_from_foods(self, food_details):
|
||||
# 1. 先把配置里的所有分类都初始化为 0
|
||||
category_sum = {
|
||||
|
||||
@@ -23,4 +23,7 @@ class NutritionReportService:
|
||||
def generate_excel_from_json(self, front_json, output_path):
|
||||
report_data = self.nutrition_service.build_report_data(front_json)
|
||||
self.excel_builder.build(report_data, output_path)
|
||||
return report_data
|
||||
return report_data
|
||||
|
||||
def preview_intake(self, front_json):
|
||||
return self.nutrition_service.build_intake_preview(front_json)
|
||||
Reference in new issue
Block a user