feat(calculator): 营养计算结果返回校验规则集(test-0415 反馈2-1)

- NutritionCalculateResponse 新增 validations: List<ValidationRule>
- ValidationRule = { name, value, targetRange, status, reference }
- 当前覆盖 4 条核心规则:
  · BMI(WS/T 428-2013)
  · eGFR(KDIGO 2012 CKD 评估指南)
  · 蛋白质摄入量(KDOQI 2020,按是否透析切换 0.6-0.8 / 1.0-1.2 g/kg)
  · 能量摄入量(KDOQI 2020,30-35 kcal/kg)
- status: normal / below / above / unknown,前端可在结果页给徽标提示
- 用户/医生可据此核对推荐数值是否落在临床指南区间内

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
msh-agent
2026-05-03 03:21:29 +08:00
parent 31f909247e
commit 229181f1e0
2 changed files with 101 additions and 0 deletions

View File

@@ -644,10 +644,83 @@ public class ToolCalculatorServiceImpl extends ServiceImpl<V2CalculatorResultDao
// 重要提示
response.setImportantTips(importantTips);
// test-0415 反馈2-1校验规则集——暴露每项推荐数值的临床指南范围与依据
response.setValidations(buildValidationRules(entity));
// 创建时间
response.setCreatedAt(entity.getCreatedAt());
return response;
}
/**
* 构建校验规则集
* 让用户/医生可以在结果页直接核对推荐数值与临床指南的对应关系
*/
private List<NutritionCalculateResponse.ValidationRule> buildValidationRules(V2CalculatorResult e) {
List<NutritionCalculateResponse.ValidationRule> list = new ArrayList<>();
// 1) BMI18.523.9 为正常(中国成人)
list.add(rule("BMI",
e.getBmi().toString(),
"18.5-23.9",
bmiStatus(e.getBmi()),
"中国成人 BMI 分类标准WS/T 428-2013"));
// 2) eGFRCKD 分期参考 KDIGO 2012
list.add(rule("eGFR",
e.getEgfr().toString() + " ml/min/1.73m²",
">90 (G1) / 60-89 (G2) / 30-59 (G3) / 15-29 (G4) / <15 (G5)",
"normal", // eGFR 本身不存在「越界」概念,由分期解释
"KDIGO 2012 CKD 评估和管理临床实践指南"));
// 3) 蛋白质摄入量:根据是否透析与 CKD 分期
BigDecimal proteinPerKg = e.getProteinIntake().divide(e.getStandardWeight(), 2, BigDecimal.ROUND_HALF_UP);
boolean isDialysis = e.getHasDialysis() != null && e.getHasDialysis() == 1;
String pTarget = isDialysis ? "1.0-1.2 g/kg" : "0.6-0.8 g/kg";
BigDecimal pMin = isDialysis ? new BigDecimal("1.0") : new BigDecimal("0.6");
BigDecimal pMax = isDialysis ? new BigDecimal("1.2") : new BigDecimal("0.8");
list.add(rule("蛋白质摄入量",
proteinPerKg + " g/kg" + e.getProteinIntake() + " g",
pTarget,
rangeStatus(proteinPerKg, pMin, pMax),
isDialysis ? "KDOQI 2020 透析患者营养指南" : "KDOQI 2020 非透析 CKD 营养指南"));
// 4) 能量摄入量30-35 kcal/kg/day
BigDecimal energyPerKg = new BigDecimal(e.getEnergyIntake())
.divide(e.getStandardWeight(), 2, BigDecimal.ROUND_HALF_UP);
list.add(rule("能量摄入量",
energyPerKg + " kcal/kg" + e.getEnergyIntake() + " kcal",
"30-35 kcal/kg",
rangeStatus(energyPerKg, new BigDecimal("30"), new BigDecimal("35")),
"KDOQI 2020 营养指南"));
return list;
}
private NutritionCalculateResponse.ValidationRule rule(String name, String value, String targetRange,
String status, String reference) {
NutritionCalculateResponse.ValidationRule r = new NutritionCalculateResponse.ValidationRule();
r.setName(name);
r.setValue(value);
r.setTargetRange(targetRange);
r.setStatus(status);
r.setReference(reference);
return r;
}
private String bmiStatus(BigDecimal bmi) {
if (bmi == null) return "unknown";
if (bmi.compareTo(new BigDecimal("18.5")) < 0) return "below";
if (bmi.compareTo(new BigDecimal("23.9")) > 0) return "above";
return "normal";
}
private String rangeStatus(BigDecimal value, BigDecimal min, BigDecimal max) {
if (value == null) return "unknown";
if (value.compareTo(min) < 0) return "below";
if (value.compareTo(max) > 0) return "above";
return "normal";
}
}