|
@@ -755,8 +755,96 @@ public class BeijingNutritionService {
|
|
|
return result.size() > 15 ? new ArrayList<>(result.subList(0, 15)) : result;
|
|
return result.size() > 15 ? new ArrayList<>(result.subList(0, 15)) : result;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private boolean isWeekend(java.time.LocalDate date) {
|
|
|
|
|
- return date.getDayOfWeek() == java.time.DayOfWeek.SATURDAY ||
|
|
|
|
|
- date.getDayOfWeek() == java.time.DayOfWeek.SUNDAY;
|
|
|
|
|
|
|
+ // ============================================================
|
|
|
|
|
+ // 均衡化评分引擎
|
|
|
|
|
+ // ============================================================
|
|
|
|
|
+
|
|
|
|
|
+ private static class ScoredIngredient {
|
|
|
|
|
+ Long foodId;
|
|
|
|
|
+ String name;
|
|
|
|
|
+ String category;
|
|
|
|
|
+ double bacteriaScore;
|
|
|
|
|
+ double nutritionScore;
|
|
|
|
|
+ double cookingScore;
|
|
|
|
|
+ double energyScore;
|
|
|
|
|
+ double compositeScore;
|
|
|
|
|
+ String balanceReason;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static class CategoryQuota {
|
|
|
|
|
+ int min;
|
|
|
|
|
+ int max;
|
|
|
|
|
+ CategoryQuota(int min, int max) { this.min = min; this.max = max; }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static final Map<String, CategoryQuota> CATEGORY_QUOTA = new LinkedHashMap<>();
|
|
|
|
|
+ static {
|
|
|
|
|
+ CATEGORY_QUOTA.put("蔬菜", new CategoryQuota(2, 3));
|
|
|
|
|
+ CATEGORY_QUOTA.put("水果", new CategoryQuota(1, 2));
|
|
|
|
|
+ CATEGORY_QUOTA.put("水产品", new CategoryQuota(1, 2));
|
|
|
|
|
+ CATEGORY_QUOTA.put("肉类", new CategoryQuota(1, 2));
|
|
|
|
|
+ CATEGORY_QUOTA.put("蛋类", new CategoryQuota(1, 1));
|
|
|
|
|
+ CATEGORY_QUOTA.put("豆类及豆制品", new CategoryQuota(1, 2));
|
|
|
|
|
+ CATEGORY_QUOTA.put("主食", new CategoryQuota(1, 2));
|
|
|
|
|
+ CATEGORY_QUOTA.put("乳制品", new CategoryQuota(0, 1));
|
|
|
|
|
+ CATEGORY_QUOTA.put("干果", new CategoryQuota(0, 1));
|
|
|
|
|
+ CATEGORY_QUOTA.put("汤", new CategoryQuota(0, 1));
|
|
|
|
|
+ CATEGORY_QUOTA.put("快餐", new CategoryQuota(0, 0));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static final Map<String, Double> NUTRITION_BASE_SCORE = new HashMap<>();
|
|
|
|
|
+ static {
|
|
|
|
|
+ NUTRITION_BASE_SCORE.put("蔬菜", 85.0);
|
|
|
|
|
+ NUTRITION_BASE_SCORE.put("水果", 80.0);
|
|
|
|
|
+ NUTRITION_BASE_SCORE.put("水产品", 80.0);
|
|
|
|
|
+ NUTRITION_BASE_SCORE.put("豆类及豆制品", 75.0);
|
|
|
|
|
+ NUTRITION_BASE_SCORE.put("蛋类", 70.0);
|
|
|
|
|
+ NUTRITION_BASE_SCORE.put("肉类", 65.0);
|
|
|
|
|
+ NUTRITION_BASE_SCORE.put("乳制品", 60.0);
|
|
|
|
|
+ NUTRITION_BASE_SCORE.put("干果", 55.0);
|
|
|
|
|
+ NUTRITION_BASE_SCORE.put("主食", 45.0);
|
|
|
|
|
+ NUTRITION_BASE_SCORE.put("汤", 35.0);
|
|
|
|
|
+ NUTRITION_BASE_SCORE.put("快餐", 20.0);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static final Set<String> QUICK_COOK_CATEGORIES = Set.of("蔬菜", "蛋类", "水果", "汤");
|
|
|
|
|
+ private static final Set<String> MEDIUM_COOK_CATEGORIES = Set.of("豆类及豆制品", "乳制品", "干果", "主食");
|
|
|
|
|
+
|
|
|
|
|
+ private double scoreBacteria(String direction, Integer recommendIndex) {
|
|
|
|
|
+ double base;
|
|
|
|
|
+ if ("recommend".equals(direction)) {
|
|
|
|
|
+ base = 85.0;
|
|
|
|
|
+ } else if ("avoid".equals(direction)) {
|
|
|
|
|
+ base = 10.0;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ base = 50.0;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (recommendIndex != null) {
|
|
|
|
|
+ double norm = (recommendIndex + 33.0) / (75.0 + 33.0) * 2.0 - 1.0;
|
|
|
|
|
+ base += norm * 10.0;
|
|
|
|
|
+ }
|
|
|
|
|
+ return Math.max(0, Math.min(100, base));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private double scoreNutrition(String category, Food food) {
|
|
|
|
|
+ double base = NUTRITION_BASE_SCORE.getOrDefault(category, 50.0);
|
|
|
|
|
+ double bonus = 0;
|
|
|
|
|
+ if (food.getProtein() != null && food.getProtein().compareTo(BigDecimal.valueOf(5)) >= 0) bonus += 5;
|
|
|
|
|
+ if (food.getFiber() != null && food.getFiber().compareTo(BigDecimal.valueOf(2)) >= 0) bonus += 4;
|
|
|
|
|
+ if (food.getFat() != null && food.getFat().compareTo(BigDecimal.valueOf(20)) > 0) bonus -= 5;
|
|
|
|
|
+ if (food.getCholesterol() != null && food.getCholesterol().compareTo(BigDecimal.valueOf(100)) > 0) bonus -= 3;
|
|
|
|
|
+ return Math.max(0, Math.min(100, base + bonus));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private double scoreCooking(String category) {
|
|
|
|
|
+ if (QUICK_COOK_CATEGORIES.contains(category)) return 90.0;
|
|
|
|
|
+ if (MEDIUM_COOK_CATEGORIES.contains(category)) return 60.0;
|
|
|
|
|
+ return 35.0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private double scoreEnergyEfficiency(double nutritionScore, Food food) {
|
|
|
|
|
+ BigDecimal energyKj = food.getEnergyKj();
|
|
|
|
|
+ if (energyKj == null || energyKj.compareTo(BigDecimal.ZERO) <= 0) return nutritionScore;
|
|
|
|
|
+ double ratio = energyKj.doubleValue() / 500.0;
|
|
|
|
|
+ return Math.max(0, Math.min(100, nutritionScore / Math.max(ratio, 0.3)));
|
|
|
}
|
|
}
|
|
|
-}
|
|
|