案例 33:体检指标异常筛查与风险标注
本案例对应 JQuick-Java USE-CASES 场景 33(诉求 40:体检指标异常筛查与风险标注),属于医疗公卫/健康管理领域,决策内容是对体检报告中的八项生理指标逐项做医学分级,聚合输出整体风险层级、异常计数、组合风险标签、复查建议与首诊科室。
体检中心与公共卫生机构在批量出具体检报告时,需要把检验数值自动翻译成统一的医学风险语言。入参模型 HealthScreeningReport 承载报告号、受检人、性别(M/F)、年龄,以及八项指标:收缩压/舒张压、空腹血糖、总胆固醇、体质指数 BMI、静息心率、谷丙转氨酶 ALT、血肌酐,并附带吸烟史与糖尿病/高血压家族史两个流行病学因子。
规则层为每个指标提供独立分级方法,统一输出 NORMAL/ATTENTION/HIGH/CRITICAL 四档(ALT 用 0/1/2 秩返回,由 Service 映射)。血压双值在编排侧取较高档作为“血压”一项;血肌酐执行男女差异化阈值(男性 97/115、女性 70/80 两道关注线);BMI 同时覆盖肥胖与低于 18.5 的消瘦提示;心率同时覆盖心动过速与低于 50 次/分的心动过缓。编排层遍历七项(血压合并后)统计异常数、HIGH 数、CRITICAL 数并取最高级,再按组合因子贴风险标签:血压 HIGH 及以上合并吸烟史标注“心血管高风险”,血糖 HIGH 及以上合并家族史标注“糖尿病高危人群”。
决策结果由 HealthScreeningDecision 承载:整体最高级别、三类计数、风险标签 riskTags、逐项带【危急】/【异常】/【关注】前缀的建议清单 advice,以及建议首诊科室与复查时限——危急值 3 日内就诊、两项 HIGH 两周内、单项 HIGH 一个月内、临界项 6 个月生活方式干预复评、全正常按年度体检。科室按优先级路由心血管内科、内分泌科、肾内科、消化内科或全科/健康管理科。
- 医学阈值随指南与人群更新:血压、血糖、血脂的临界值在不同指南版本和体检套餐中存在差异,写死在 HIS/体检系统代码里,每次调整都要厂商配合发版。
- 八项指标口径必须独立可调:不同指标的分档线、性别差异(血肌酐)、双向异常(BMI 消瘦/肥胖、心率过缓/过速)混在一个大方法里,维护时极易改错档位。
- 组合风险因子难以表达:单看血压或血糖值并不触发高危结论,必须叠加吸烟史、家族史等因子形成标签,传统硬编码分支又多又散。
- 异常结果缺少可执行的就诊指引:只标红数值没有科室与时限,受检人仍需人工解读,体检机构电话回访与健康干预工作量大。
- 医疗遗留系统与信创环境约束:区域公卫平台、医院体检系统大量运行在 JDK 8 与国产 JDK 上,不适合引入需要独立部署的重型临床规则平台。
本案例采用 JQuick-Java 标准六件套:入参模型、决策结果模型、Rules 规则接口、XML 规则脚本、Service 编排服务、JUnit 测试,全部代码在 Oracle JDK 1.8 下实测通过。
| 层次 | 职责 | 对应文件 |
|---|---|---|
| 领域模型 | 承载体检八项指标要素与异常筛查、风险分层决策 | HealthScreeningReport.java、HealthScreeningDecision.java |
| 规则层 | 八项指标各自医学分级,阈值与性别差异全部外置 | HealthScreeningRules.java、health-screening.xml |
| 编排层 | 血压合并取高、异常计数、组合风险标签、科室与复查时限路由 | HealthScreeningService.java |
| 测试 | 6 组体检真实形态 | HealthScreeningServiceTest.java |
规则方法拆分
Section titled “规则方法拆分”| 规则方法 | 口径 | 分值/输出 |
|---|---|---|
systolicLevel |
收缩压 ≥180 CRITICAL;≥140 HIGH;≥130 ATTENTION;否则 NORMAL | NORMAL/ATTENTION/HIGH/CRITICAL |
diastolicLevel |
舒张压 ≥110 CRITICAL;≥90 HIGH;≥85 ATTENTION;否则 NORMAL | NORMAL/ATTENTION/HIGH/CRITICAL |
glucoseLevel |
空腹血糖 ≥7.0 CRITICAL;≥6.1 HIGH;≥5.6 ATTENTION;否则 NORMAL | NORMAL/ATTENTION/HIGH/CRITICAL |
cholesterolLevel |
总胆固醇 ≥6.2 HIGH;≥5.2 ATTENTION;否则 NORMAL(无 CRITICAL 档) | NORMAL/ATTENTION/HIGH |
bmiLevel |
BMI ≥32 CRITICAL;≥28 HIGH;≥24 ATTENTION;低于 18.5 亦为 ATTENTION(消瘦);否则 NORMAL | NORMAL/ATTENTION/HIGH/CRITICAL |
heartRateLevel |
静息心率 ≥120 CRITICAL;≥100 HIGH;低于 50 HIGH;≤55 ATTENTION;否则 NORMAL | NORMAL/ATTENTION/HIGH/CRITICAL |
altLevelRank |
谷丙转氨酶大于 120 返回 2;大于 40 返回 1;否则 0(Service 再映射 HIGH/ATTENTION/NORMAL) | 0/1/2 |
creatinineLevel |
血肌酐男性大于 115 HIGH、大于 97 ATTENTION;女性大于 80 HIGH、大于 70 ATTENTION;否则 NORMAL | NORMAL/ATTENTION/HIGH |
代码实现(完整可运行)
Section titled “代码实现(完整可运行)”将两个模型类放入 src/test/java/com/github/paohaijiao/demo/model/,规则接口放入 demo/,Service 放入 demo/service/,XML 脚本放入 src/test/resources/demo/,测试类放入 demo/,包名保持源码原样。工程根目录执行 mvn test -Dtest=HealthScreeningServiceTest 即可复现全部 6 个用例。
HealthScreeningReport.java
Section titled “HealthScreeningReport.java”package com.github.paohaijiao.demo.model;
import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;
/** * 体检指标要素 */@Data@NoArgsConstructor@AllArgsConstructorpublic class HealthScreeningReport {
/** * 体检报告号 */ private String reportId;
/** * 受检人 */ private String personName;
/** * 性别:M 男 / F 女 */ private String gender;
/** * 年龄 */ private int age;
/** * 收缩压(mmHg) */ private int systolicBp;
/** * 舒张压(mmHg) */ private int diastolicBp;
/** * 空腹血糖(mmol/L) */ private double fastingGlucose;
/** * 总胆固醇(mmol/L) */ private double totalCholesterol;
/** * 体质指数 */ private double bmi;
/** * 静息心率(次/分) */ private int heartRate;
/** * 谷丙转氨酶(U/L) */ private int alt;
/** * 血肌酐(μmol/L) */ private int creatinine;
/** * 是否有吸烟史 */ private boolean smokingHistory;
/** * 是否有糖尿病/高血压家族史 */ private boolean familyHistory;}HealthScreeningDecision.java
Section titled “HealthScreeningDecision.java”package com.github.paohaijiao.demo.model;
import lombok.Data;
import java.util.ArrayList;import java.util.List;
/** * 体检异常筛查与风险分层决策 */@Datapublic class HealthScreeningDecision {
/** * 报告号 */ private String reportId;
/** * 整体风险级别:NORMAL / ATTENTION / HIGH / CRITICAL */ private String highestLevel;
/** * 异常指标数(ATTENTION 及以上) */ private int abnormalCount;
/** * CRITICAL 指标数 */ private int criticalCount;
/** * HIGH 指标数 */ private int highCount;
/** * 风险标签 */ private List<String> riskTags = new ArrayList<String>();
/** * 逐项异常说明与复查建议 */ private List<String> advice = new ArrayList<String>();
/** * 建议首诊科室 */ private String followUpDepartment;
public void addRiskTag(String tag) { this.riskTags.add(tag); }
public void addAdvice(String line) { this.advice.add(line); }}HealthScreeningRules.java
Section titled “HealthScreeningRules.java”package com.github.paohaijiao.demo;
import com.github.paohaijiao.xml.param.Param;
/** * 场景 33:体检各指标医学分级规则(NORMAL/ATTENTION/HIGH/CRITICAL) */public interface HealthScreeningRules {
/** * 收缩压分级 */ String systolicLevel(@Param("systolic") int systolic);
/** * 舒张压分级 */ String diastolicLevel(@Param("diastolic") int diastolic);
/** * 空腹血糖分级 */ String glucoseLevel(@Param("glucose") double glucose);
/** * 总胆固醇分级 */ String cholesterolLevel(@Param("cholesterol") double cholesterol);
/** * BMI 分级(含消瘦提示) */ String bmiLevel(@Param("bmi") double bmi);
/** * 静息心率分级 */ String heartRateLevel(@Param("heartRate") int heartRate);
/** * 谷丙转氨酶分级 */ int altLevelRank(@Param("alt") int alt);
/** * 血肌酐分级(男女阈值不同) */ String creatinineLevel(@Param("creatinine") int creatinine, @Param("gender") String gender);}health-screening.xml
Section titled “health-screening.xml”<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE javas PUBLIC "-//PAOHAIJIAO//DTD API JAVA 1.0//EN" "classpath:paohaijiao/dtd/Jquick-java.dtd"><javas namespace="com.github.paohaijiao.demo.HealthScreeningRules">
<java name="systolicLevel" returnClass="java.lang.String"> <![CDATA[ str def systolicLevel(int:systolic) { if (systolic >= 180) { return "CRITICAL"; } else if (systolic >= 140) { return "HIGH"; } else if (systolic >= 130) { return "ATTENTION"; } else { return "NORMAL"; } } ]]> </java>
<java name="diastolicLevel" returnClass="java.lang.String"> <![CDATA[ str def diastolicLevel(int:diastolic) { if (diastolic >= 110) { return "CRITICAL"; } else if (diastolic >= 90) { return "HIGH"; } else if (diastolic >= 85) { return "ATTENTION"; } else { return "NORMAL"; } } ]]> </java>
<java name="glucoseLevel" returnClass="java.lang.String"> <![CDATA[ str def glucoseLevel(double:glucose) { if (glucose >= 7.0) { return "CRITICAL"; } else if (glucose >= 6.1) { return "HIGH"; } else if (glucose >= 5.6) { return "ATTENTION"; } else { return "NORMAL"; } } ]]> </java>
<java name="cholesterolLevel" returnClass="java.lang.String"> <![CDATA[ str def cholesterolLevel(double:cholesterol) { if (cholesterol >= 6.2) { return "HIGH"; } else if (cholesterol >= 5.2) { return "ATTENTION"; } else { return "NORMAL"; } } ]]> </java>
<java name="bmiLevel" returnClass="java.lang.String"> <![CDATA[ str def bmiLevel(double:bmi) { if (bmi >= 32) { return "CRITICAL"; } else if (bmi >= 28) { return "HIGH"; } else if (bmi >= 24) { return "ATTENTION"; } else if (bmi < 18.5) { return "ATTENTION"; } else { return "NORMAL"; } } ]]> </java>
<java name="heartRateLevel" returnClass="java.lang.String"> <![CDATA[ str def heartRateLevel(int:heartRate) { if (heartRate >= 120) { return "CRITICAL"; } else if (heartRate >= 100) { return "HIGH"; } else if (heartRate < 50) { return "HIGH"; } else if (heartRate <= 55) { return "ATTENTION"; } else { return "NORMAL"; } } ]]> </java>
<java name="altLevelRank" returnClass="int"> <![CDATA[ int def altLevelRank(int:alt) { if (alt > 120) { return 2; } else if (alt > 40) { return 1; } else { return 0; } } ]]> </java>
<java name="creatinineLevel" returnClass="java.lang.String"> <![CDATA[ str def creatinineLevel(int:creatinine, str:gender) { if (gender == "M") { if (creatinine > 115) { return "HIGH"; } else if (creatinine > 97) { return "ATTENTION"; } else { return "NORMAL"; } } else { if (creatinine > 80) { return "HIGH"; } else if (creatinine > 70) { return "ATTENTION"; } else { return "NORMAL"; } } } ]]> </java>
</javas>HealthScreeningService.java
Section titled “HealthScreeningService.java”package com.github.paohaijiao.demo.service;
import com.github.paohaijiao.demo.HealthScreeningRules;import com.github.paohaijiao.demo.model.HealthScreeningDecision;import com.github.paohaijiao.demo.model.HealthScreeningReport;
/** * 场景 33:体检多指标异常筛查、风险标注与就诊指引 */public class HealthScreeningService {
private final HealthScreeningRules rules;
public HealthScreeningService(HealthScreeningRules rules) { this.rules = rules; }
public HealthScreeningDecision screen(HealthScreeningReport r) { HealthScreeningDecision d = new HealthScreeningDecision(); d.setReportId(r.getReportId());
String bp = higher(rules.systolicLevel(r.getSystolicBp()), rules.diastolicLevel(r.getDiastolicBp())); String glucose = rules.glucoseLevel(r.getFastingGlucose()); String cholesterol = rules.cholesterolLevel(r.getTotalCholesterol()); String bmi = rules.bmiLevel(r.getBmi()); String heartRate = rules.heartRateLevel(r.getHeartRate()); int altRank = rules.altLevelRank(r.getAlt()); String alt = rankToLevel(altRank); String creatinine = rules.creatinineLevel(r.getCreatinine(), r.getGender());
addItem(d, "血压 " + r.getSystolicBp() + "/" + r.getDiastolicBp() + "mmHg", bp, "家庭监测血压,低盐饮食并控制体重", "立即急诊排查高血压急症"); addItem(d, "空腹血糖 " + r.getFastingGlucose() + "mmol/L", glucose, "控制精制糖摄入并增加有氧运动", "尽快到内分泌科明确糖尿病诊断"); addItem(d, "总胆固醇 " + r.getTotalCholesterol() + "mmol/L", cholesterol, "减少饱和脂肪摄入,3 个月后复查血脂", "低脂饮食并到心内科评估调脂治疗"); addItem(d, "体质指数 " + r.getBmi(), bmi, "通过膳食与运动把 BMI 控制在 18.5-24", "到营养/减重门诊干预肥胖"); addItem(d, "静息心率 " + r.getHeartRate() + " 次/分", heartRate, "规律作息,避免浓茶咖啡后复查心电图", "尽快完善动态心电图检查"); addItem(d, "谷丙转氨酶 " + r.getAlt() + "U/L", alt, "避免饮酒与熬夜,复查肝功能", "消化/肝病科排查肝炎、脂肪肝等病因"); addItem(d, "血肌酐 " + r.getCreatinine() + "μmol/L", creatinine, "多饮水避免滥用止痛药,复查肾功能", "肾内科评估肾功能损伤");
if (rank(bp) >= 2 && r.isSmokingHistory()) { d.addRiskTag("心血管高风险(血压异常合并吸烟史)"); } if (rank(glucose) >= 2 && r.isFamilyHistory()) { d.addRiskTag("糖尿病高危人群(空腹血糖异常合并家族史)"); }
String highest = "NORMAL"; for (String level : new String[]{bp, glucose, cholesterol, bmi, heartRate, alt, creatinine}) { if (rank(level) > rank(highest)) { highest = level; } if (!"NORMAL".equals(level)) { d.setAbnormalCount(d.getAbnormalCount() + 1); } if ("CRITICAL".equals(level)) { d.setCriticalCount(d.getCriticalCount() + 1); } if ("HIGH".equals(level)) { d.setHighCount(d.getHighCount() + 1); } } d.setHighestLevel(highest);
d.setFollowUpDepartment(resolveDepartment(bp, heartRate, glucose, creatinine, alt, highest)); appendFollowUpAdvice(d); return d; }
private void appendFollowUpAdvice(HealthScreeningDecision d) { if (d.getCriticalCount() >= 1) { d.addAdvice("存在危急值指标,请于 3 日内到 " + d.getFollowUpDepartment() + " 就诊"); } else if (d.getHighCount() >= 2) { d.addAdvice("多项指标明显异常,请于 2 周内到 " + d.getFollowUpDepartment() + " 复查"); } else if (d.getHighCount() == 1) { d.addAdvice("单项指标明显异常,请于 1 个月内到 " + d.getFollowUpDepartment() + " 复查"); } else if (d.getAbnormalCount() > 0) { d.addAdvice("指标处于临界区间,以生活方式干预为主,6 个月后复评"); } else { d.addAdvice("本次体检各项指标正常,请保持良好生活习惯,按年度常规体检即可"); } }
private String resolveDepartment(String bp, String heartRate, String glucose, String creatinine, String alt, String highest) { if ("NORMAL".equals(highest)) { return "全科/健康管理科"; } if (rank(bp) >= 2) { return "心血管内科"; } if (rank(heartRate) >= 2) { return "心血管内科"; } if (rank(glucose) >= 2) { return "内分泌科"; } if ("HIGH".equals(creatinine)) { return "肾内科"; } if ("HIGH".equals(alt)) { return "消化内科"; } return "全科/健康管理科"; }
private void addItem(HealthScreeningDecision d, String label, String level, String attentionAdvice, String highAdvice) { if ("NORMAL".equals(level)) { return; } String prefix; if ("CRITICAL".equals(level)) { prefix = "【危急】"; } else if ("HIGH".equals(level)) { prefix = "【异常】"; } else { prefix = "【关注】"; } String body = "CRITICAL".equals(level) || "HIGH".equals(level) ? highAdvice : attentionAdvice; d.addAdvice(prefix + label + ":" + level + "。" + body); }
private String higher(String a, String b) { return rank(a) >= rank(b) ? a : b; }
private String rankToLevel(int rank) { if (rank >= 2) { return "HIGH"; } if (rank == 1) { return "ATTENTION"; } return "NORMAL"; }
private int rank(String level) { if ("CRITICAL".equals(level)) { return 3; } if ("HIGH".equals(level)) { return 2; } if ("ATTENTION".equals(level)) { return 1; } return 0; }}HealthScreeningServiceTest.java
Section titled “HealthScreeningServiceTest.java”package com.github.paohaijiao.demo;
import com.github.paohaijiao.demo.model.HealthScreeningDecision;import com.github.paohaijiao.demo.model.HealthScreeningReport;import com.github.paohaijiao.demo.service.HealthScreeningService;import com.github.paohaijiao.xml.JQuickJava;import org.junit.Assert;import org.junit.Before;import org.junit.Test;
/** * 场景 33:体检指标异常筛查与风险标注 * <p> * 6 组形态:全指标正常、四项临界关注、高血压合并吸烟、糖尿病危急合并家族史、 * 多器官危急值、心动过缓与临界恢复分叉。 */public class HealthScreeningServiceTest {
private HealthScreeningService service;
@Before public void setUp() { HealthScreeningRules rules = JQuickJava.create() .createApi(HealthScreeningRules.class, "demo/health-screening.xml"); this.service = new HealthScreeningService(rules); }
/** * 年轻男性全部指标正常 → 年度常规体检 */ @Test public void shouldReturnNormalForHealthyYoungMan() { HealthScreeningReport r = base("RPT001", "M", 28); r.setSystolicBp(115); r.setDiastolicBp(75); r.setFastingGlucose(5.0); r.setTotalCholesterol(4.5); r.setBmi(22); r.setHeartRate(70); r.setAlt(25); r.setCreatinine(70);
HealthScreeningDecision d = service.screen(r);
Assert.assertEquals("NORMAL", d.getHighestLevel()); Assert.assertEquals(0, d.getAbnormalCount()); Assert.assertEquals("全科/健康管理科", d.getFollowUpDepartment()); Assert.assertTrue(d.getAdvice().get(0).contains("各项指标正常")); }
/** * 血压、血糖、胆固醇、BMI 四项临界 → ATTENTION,生活方式干预 */ @Test public void shouldMarkFourBorderlineMetrics() { HealthScreeningReport r = base("RPT002", "M", 42); r.setSystolicBp(132); r.setDiastolicBp(86); r.setFastingGlucose(5.8); r.setTotalCholesterol(5.5); r.setBmi(24.5); r.setHeartRate(72); r.setAlt(30); r.setCreatinine(85);
HealthScreeningDecision d = service.screen(r);
Assert.assertEquals("ATTENTION", d.getHighestLevel()); Assert.assertEquals(4, d.getAbnormalCount()); Assert.assertEquals(0, d.getHighCount()); Assert.assertTrue(d.getAdvice().get(0).startsWith("【关注】血压")); Assert.assertTrue(d.getAdvice().get(d.getAdvice().size() - 1).contains("生活方式干预")); }
/** * 血压 152/96、胆固醇 6.5、吸烟史 → 心血管高风险,2 周内心内科 */ @Test public void shouldTagCardioRiskForHypertensiveSmoker() { HealthScreeningReport r = base("RPT003", "M", 51); r.setSystolicBp(152); r.setDiastolicBp(96); r.setFastingGlucose(5.2); r.setTotalCholesterol(6.5); r.setBmi(27); r.setHeartRate(82); r.setAlt(35); r.setCreatinine(90); r.setSmokingHistory(true);
HealthScreeningDecision d = service.screen(r);
Assert.assertEquals("HIGH", d.getHighestLevel()); Assert.assertEquals(3, d.getAbnormalCount()); Assert.assertEquals(2, d.getHighCount()); Assert.assertTrue(d.getRiskTags().contains("心血管高风险(血压异常合并吸烟史)")); Assert.assertEquals("心血管内科", d.getFollowUpDepartment()); Assert.assertTrue(d.getAdvice().get(d.getAdvice().size() - 1).contains("2 周内")); }
/** * 空腹血糖 7.2 危急 + 家族史 → 糖尿病高危,3 日内内分泌科 */ @Test public void shouldFlagCriticalGlucoseWithFamilyHistory() { HealthScreeningReport r = base("RPT004", "F", 47); r.setSystolicBp(125); r.setDiastolicBp(80); r.setFastingGlucose(7.2); r.setTotalCholesterol(5.0); r.setBmi(25.5); r.setHeartRate(75); r.setAlt(30); r.setCreatinine(72); r.setFamilyHistory(true);
HealthScreeningDecision d = service.screen(r);
Assert.assertEquals("CRITICAL", d.getHighestLevel()); Assert.assertEquals(1, d.getCriticalCount()); Assert.assertTrue(d.getRiskTags().contains("糖尿病高危人群(空腹血糖异常合并家族史)")); Assert.assertEquals("内分泌科", d.getFollowUpDepartment()); Assert.assertTrue(d.getAdvice().get(d.getAdvice().size() - 1).contains("3 日内")); }
/** * 血压 182/112、血糖 8.1、ALT 130、肌酐 130、心率 105 → 多器官危急,首诊心内科 */ @Test public void shouldPrioritizeCardiologyForMultipleCriticalValues() { HealthScreeningReport r = base("RPT005", "M", 58); r.setSystolicBp(182); r.setDiastolicBp(112); r.setFastingGlucose(8.1); r.setTotalCholesterol(4.8); r.setBmi(26); r.setHeartRate(105); r.setAlt(130); r.setCreatinine(130);
HealthScreeningDecision d = service.screen(r);
Assert.assertEquals("CRITICAL", d.getHighestLevel()); Assert.assertEquals(2, d.getCriticalCount()); Assert.assertEquals(3, d.getHighCount()); Assert.assertEquals("心血管内科", d.getFollowUpDepartment()); Assert.assertTrue(d.getAdvice().get(0).contains("立即急诊排查高血压急症")); Assert.assertTrue(d.getAdvice().contains("【异常】谷丙转氨酶 130U/L:HIGH。消化/肝病科排查肝炎、脂肪肝等病因")); }
/** * 心率 46 为 HIGH 首诊心内科;恢复到 54 后整体仅 BMI 消瘦关注 */ @Test public void shouldHandleBradycardiaAndRecovery() { HealthScreeningReport r = base("RPT006", "F", 34); r.setSystolicBp(118); r.setDiastolicBp(72); r.setFastingGlucose(4.9); r.setTotalCholesterol(4.6); r.setBmi(17.8); r.setHeartRate(46); r.setAlt(22); r.setCreatinine(65);
HealthScreeningDecision d1 = service.screen(r); Assert.assertEquals("HIGH", d1.getHighestLevel()); Assert.assertEquals(1, d1.getHighCount()); Assert.assertEquals("心血管内科", d1.getFollowUpDepartment()); Assert.assertTrue(d1.getAdvice().get(1).contains("尽快完善动态心电图检查"));
r.setHeartRate(54); HealthScreeningDecision d2 = service.screen(r); Assert.assertEquals("ATTENTION", d2.getHighestLevel()); Assert.assertTrue(d2.getAdvice().get(d2.getAdvice().size() - 1).contains("生活方式干预")); }
private HealthScreeningReport base(String id, String gender, int age) { HealthScreeningReport r = new HealthScreeningReport(); r.setReportId(id); r.setPersonName("测试受检人"); r.setGender(gender); r.setAge(age); r.setSystolicBp(120); r.setDiastolicBp(80); r.setFastingGlucose(5.0); r.setTotalCholesterol(4.5); r.setBmi(22); r.setHeartRate(72); r.setAlt(25); r.setCreatinine(gender.equals("M") ? 80 : 65); r.setSmokingHistory(false); r.setFamilyHistory(false); return r; }}在工程根目录执行:
mvn test -Dtest=HealthScreeningServiceTest测试覆盖形态(6 个 @Test 方法,覆盖全正常、临界簇、组合标签、危急值、多器官危急、恢复分叉):
| 测试方法 | 业务形态 | 关键断言/期望决策 |
|---|---|---|
shouldReturnNormalForHealthyYoungMan |
RPT001 28 岁男性:血压 115/75、血糖 5.0、胆固醇 4.5、BMI 22、心率 70、ALT 25、肌酐 70 | 最高级 NORMAL、异常数 0、科室“全科/健康管理科”,首条建议含“各项指标正常” |
shouldMarkFourBorderlineMetrics |
RPT002 42 岁男性:132/86、血糖 5.8、胆固醇 5.5、BMI 24.5(肌酐 85 仍在男性 NORMAL 档) | ATTENTION、异常数 4、HIGH 数 0,首条建议以“【关注】血压”开头,末条含“生活方式干预” |
shouldTagCardioRiskForHypertensiveSmoker |
RPT003 51 岁男性:血压 152/96 HIGH、胆固醇 6.5 HIGH、BMI 27 关注、吸烟史 | HIGH、异常数 3、HIGH 数 2,含标签“心血管高风险(血压异常合并吸烟史)”、科室心血管内科、末条含“2 周内” |
shouldFlagCriticalGlucoseWithFamilyHistory |
RPT004 47 岁女性:空腹血糖 7.2 CRITICAL、BMI 25.5 关注、糖尿病家族史 | CRITICAL、CRITICAL 数 1,含标签“糖尿病高危人群(空腹血糖异常合并家族史)”、科室内分泌科、末条含“3 日内” |
shouldPrioritizeCardiologyForMultipleCriticalValues |
RPT005 58 岁男性:血压 182/112、血糖 8.1 双 CRITICAL;心率 105、ALT 130、肌酐 130 三项 HIGH;BMI 26 关注 | CRITICAL、CRITICAL 数 2、HIGH 数 3、科室心血管内科,首条含“立即急诊排查高血压急症”,并含“【异常】谷丙转氨酶 130U/L:HIGH。消化/肝病科排查肝炎、脂肪肝等病因” |
shouldHandleBradycardiaAndRecovery |
RPT006 34 岁女性:心率 46(低于 50)HIGH、BMI 17.8 消瘦关注;同份报告心率改 54 | 首次 HIGH、HIGH 数 1、心血管内科,建议第 2 条含“尽快完善动态心电图检查”;恢复后最高级 ATTENTION、末条含“生活方式干预” |
- 医学阈值 XML 外置、随指南热更新:八项指标的分档线(含血糖 5.6/6.1/7.0、BMI 18.5/24/28/32 等)全部写在
health-screening.xml,指南改版或体检套餐调整时改 XML 重建代理即可,不动体检系统主程序。 - 一指标一方法、双向异常独立表达:BMI 同时覆盖肥胖与消瘦、心率同时覆盖过速与过缓,各自独立成方法;ALT 以整数秩 0/1/2 返回再由 Java 映射,脚本只负责客观分档,可逐项调参与审计。
- 组合风险因子在编排层贴标签:血压异常合并吸烟史、血糖异常合并家族史两条交叉规则由 Service 持有,风险标签与指标分级解耦,新增流行病学因子无需改动任何指标脚本。
- 血压双值取高、科室按优先级短路:收缩压/舒张压经
higher合并为一项计数;科室路由按心血管、内分泌、肾内、消化顺序短路,危急值多器官异常时首诊科室稳定可预期。 - 整数秩比较与性别阈值参数化:Service 内用 0-3 秩比较四档级别;血肌酐通过
@Param("gender")把性别按名注入脚本,男女两套阈值在同一方法内分支,无重复接口。 - ASM 代理、Java 8 无重型依赖:接口动态代理单例复用、无外部进程与数据库,可直接嵌入区域公卫平台、医院体检系统等 JDK 8 与信创 JDK 环境。