案例 13:医保报销合规校验
本案例对应 JQuick-Java 仓库 USE-CASES.md 中的场景 13,面向医疗医保领域的医保即时结算。系统先判定甲类、乙类、丙类药品的目录范围,目录外项目全部自费;目录内按参保类型(职工 / 居民)与医院等级(三甲 / 二级 / 社区)确定起付线与报销比例,急诊起付线减半、比例上浮 5 个百分点,乙类药先行自付 10%,最终统筹支付受年度统筹基金封顶线截断;异地未备案转人工审核,全部政策口径由 JQuick-Java 的 XML 规则代理驱动。
医保即时结算发生在医院收费窗口:参保人持医保结算单(MedicalClaim)挂号就诊后,系统采集参保类型(EMPLOYEE 职工医保 / RESIDENT 居民医保)、医院等级(THIRD_A 三甲 / SECOND_A 二级 / COMMUNITY 社区)、费用类别(DRUG_A 甲类药 / DRUG_B 乙类药 / DRUG_C 丙类自费 / TREATMENT 诊疗 / EXAMINATION 检查)、本次费用总额、是否急诊、是否异地就医及是否已备案、年度统筹基金已累计支付额。
结算的第一道闸门是目录范围判定:丙类(DRUG_C)属目录外项目,结论直接为 SELF_PAY,统筹支付为 0,全部费用由个人承担,后续起付线与比例链路不再执行。目录内项目则进入完整报销链路:先扣乙类药 10% 的先行自付,再扣起付线,剩余部分乘以报销比例得到理论统筹支付,最后与年度封顶线的剩余额度比较并截断。
起付线与比例是一个「参保类型 × 医院等级 × 急诊」的政策矩阵:普通就医时职工在三甲/二级/社区的起付线为 800/500/200 元、比例为 0.9/0.85/0.8;居民为 600/300/100 元、0.7/0.65/0.6。急诊起付线减半执行(职工三甲 400、二级 250、社区 100),报销比例在矩阵基础上上浮 0.05 且上限 1.0。年度封顶线职工 30 万、居民 20 万,支付额超过年度剩余额度时按剩余额度结算,剩余额度 ≤0 时支付 0。
合规结论分三档:目录外 SELF_PAY;异地就医且未备案 MANUAL_REVIEW,费用挂账转人工审核后再结算;其余 APPROVE 即时结算。此外,单笔统筹支付达到 5 万元或结论本身为人工审核时,needManualReview 置位。决策结果完整保留起付线、先行自付、比例、统筹支付、个人自付、封顶线、是否触顶等字段与逐条结算说明。
- 政策矩阵维度多且硬编码:参保类型 × 医院等级 × 急诊三个维度叠加出 12 个起付线档位和 6 个比例档位,写在 Java 的嵌套
if-else中极难核对,窗口人员也无法直接看到口径。 - 医保政策调整需重新发版医院系统:起付线、报销比例、封顶线、乙类先行自付比例会随统筹地区政策年度调整,HIS 系统发布窗口严格、回归成本高。
- 报销链路顺序不能错:目录外必须提前返回;目录内必须按「先行自付 → 起付线 → 比例 → 封顶截断」串联,顺序颠倒或漏扣一项都会造成基金错付。
- 起付线减半与比例上浮要防越界:急诊比例在矩阵基础上 +0.05,若不限制 1.0 上限,高档位可能出现超过 100% 的报销比例。
- 年度封顶是动态剩余额度而非固定阈值:截断依据是「年度封顶线 - 年度已累计支付」,剩余额度不足甚至为 0 时要有确定行为,不能简单按固定封顶线比较。
- 医保审计要求每笔结算可解释:目录外自费、乙类先行自付、急诊倾斜、触顶截断、异地未备案挂账都必须在结算说明中留痕,且不少统筹地区核心系统仍在 JDK 8 / 信创环境,重型规则平台难以引入。
本案例采用「入参模型 + 决策结果模型 + 规则接口 + XML 规则脚本 + Service 编排 + JUnit 测试」六件套结构,目录、起付线/比例矩阵、先行自付、封顶线、备案口径全部外置在 XML 中,Service 负责报销链路串联、负值归零与说明组装:
| 层次 | 职责 | 对应文件 |
|---|---|---|
| 领域模型 | 结算要素、报销决策(起付线/比例/支付额/结论/说明) | MedicalClaim.java、ReimbursementDecision.java |
| 规则集与 XML | 目录/起付线/比例/先行自付/封顶/结论 8 个规则方法 | MedicalReimbursementRules.java、medical-reimbursement.xml |
| 编排服务 | 目录→自付→起付线→比例→封顶链路串联、结论与说明组装 | MedicalReimbursementService.java |
| 测试 | 6 组结算形态(甲类、乙类、丙类、封顶、急诊、异地) | MedicalReimbursementServiceTest.java |
规则方法拆分
Section titled “规则方法拆分”规则接口共 8 个方法,政策查询与结论判定分离:
| 规则方法 | 口径 | 分值/输出 |
|---|---|---|
withinCatalog |
费用类别为 DRUG_C(丙类)返回 false,其余甲类、乙类、诊疗、检查在目录内 | boolean |
deductible |
职工 800/500/200、居民 600/300/100(三甲/二级/社区);急诊减半为 400/250/100 与 300/150/50 | 起付线金额 |
reimburseRatio |
职工 0.9/0.85/0.8、居民 0.7/0.65/0.6;急诊 +0.05,超过 1.0 截断为 1.0 | 报销比例 |
prepaidSelfPay |
乙类药 DRUG_B 先行自付总费用 ×10%,其余类别 0 | 先行自付金额 |
annualCap |
职工医保 300000,居民医保 200000 | 年度封顶线 |
applyCap |
剩余额度 ≤0 返回 0;理论支付超过剩余额度返回剩余额度;否则原样支付 | 截断后支付额 |
complianceResult |
目录外 SELF_PAY;异地且未备案 MANUAL_REVIEW;否则 APPROVE | 结论字符串 |
needManualReview |
结论为 MANUAL_REVIEW,或 APPROVE 且单笔统筹支付 ≥50000 返回 true | boolean |
代码实现(完整可运行)
Section titled “代码实现(完整可运行)”将以下六个文件按源码原包名放入 JQuick-Java 的测试工程:两个模型放在 com.github.paohaijiao.demo.model,规则接口与测试类放在 com.github.paohaijiao.demo,编排服务放在 com.github.paohaijiao.demo.service,XML 脚本放入 src/test/resources/demo/。保持目录与包名不变后,在工程根目录执行 mvn test -Dtest=MedicalReimbursementServiceTest 即可运行。
MedicalClaim.java
Section titled “MedicalClaim.java”package com.github.paohaijiao.demo.model;
import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;
/** * 医保结算要素(报销合规校验所需的就诊与费用快照) */@Data@NoArgsConstructor@AllArgsConstructorpublic class MedicalClaim {
/** * 结算单号 */ private String claimNo;
/** * 患者姓名 */ private String patientName;
/** * 参保类型:EMPLOYEE-职工医保 / RESIDENT-居民医保 */ private String insuranceType;
/** * 医院等级:THIRD_A-三甲 / SECOND_A-二级 / COMMUNITY-社区 */ private String hospitalLevel;
/** * 费用类别:DRUG_A-甲类药 / DRUG_B-乙类药 / DRUG_C-丙类自费 / TREATMENT-诊疗 / EXAMINATION-检查 */ private String medicalCategory;
/** * 本次费用总额 */ private double totalExpense;
/** * 是否急诊 */ private boolean emergency;
/** * 是否异地就医 */ private boolean offSite;
/** * 异地就医是否已备案 */ private boolean filed;
/** * 年度统筹基金已累计支付额 */ private double annualAccumulated;}ReimbursementDecision.java
Section titled “ReimbursementDecision.java”package com.github.paohaijiao.demo.model;
import lombok.Data;
import java.util.ArrayList;import java.util.List;
/** * 医保报销决策结果 */@Datapublic class ReimbursementDecision {
/** * 结算单号 */ private String claimNo;
/** * 是否在医保目录内 */ private boolean withinCatalog;
/** * 起付线金额 */ private double deductibleAmount;
/** * 乙类先行自付金额 */ private double prepaidSelfPay;
/** * 报销比例 */ private double reimburseRatio;
/** * 统筹支付金额 */ private double payableAmount;
/** * 个人自付金额 */ private double selfPayAmount;
/** * 年度封顶线 */ private double annualCap;
/** * 是否触及封顶线 */ private boolean capped;
/** * 结算结论:APPROVE-即时结算 / MANUAL_REVIEW-人工审核 / SELF_PAY-全自费 */ private String result;
/** * 是否需要人工审核 */ private boolean needManualReview;
/** * 结算说明 */ private List<String> notes = new ArrayList<String>();
public void addNote(String note) { this.notes.add(note); }}MedicalReimbursementRules.java
Section titled “MedicalReimbursementRules.java”package com.github.paohaijiao.demo;
import com.github.paohaijiao.xml.param.Param;
/** * 场景 13:医保报销项目与比例合规校验规则集 * <p> * 规则按「目录范围 → 起付线/比例 → 先行自付 → 封顶线 → 结算结论」拆分: * <ul> * <li>政策类:目录判定、起付线、报销比例、乙类先行自付、年度封顶线</li> * <li>决策类:结算结论、封顶截断、人工审核门槛、异地备案校验</li> * </ul> * 规则脚本见 {@code src/test/resources/demo/medical-reimbursement.xml}。 */public interface MedicalReimbursementRules {
boolean withinCatalog(@Param("medicalCategory") String medicalCategory);
double deductible(@Param("insuranceType") String insuranceType, @Param("hospitalLevel") String hospitalLevel, @Param("emergency") boolean emergency);
double reimburseRatio(@Param("insuranceType") String insuranceType, @Param("hospitalLevel") String hospitalLevel, @Param("emergency") boolean emergency);
double prepaidSelfPay(@Param("medicalCategory") String medicalCategory, @Param("totalExpense") double totalExpense);
double annualCap(@Param("insuranceType") String insuranceType);
double applyCap(@Param("payableAmount") double payableAmount, @Param("remainingCap") double remainingCap);
String complianceResult(@Param("withinCatalog") boolean withinCatalog, @Param("offSite") boolean offSite, @Param("filed") boolean filed);
boolean needManualReview(@Param("result") String result, @Param("payableAmount") double payableAmount);}medical-reimbursement.xml
Section titled “medical-reimbursement.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.MedicalReimbursementRules">
<!-- 医保目录判定:丙类全部自费 --> <java name="withinCatalog" returnClass="boolean"> <![CDATA[ boolean def withinCatalog(str:medicalCategory) { if (medicalCategory == "DRUG_C") { return false; } else { return true; } } ]]> </java>
<!-- 起付线:参保类型 × 医院等级,急诊减半执行 --> <java name="deductible" returnClass="double"> <![CDATA[ double def deductible(str:insuranceType, str:hospitalLevel, boolean:emergency) { if (insuranceType == "EMPLOYEE") { if (emergency) { if (hospitalLevel == "THIRD_A") { return 400.0; } else if (hospitalLevel == "SECOND_A") { return 250.0; } else { return 100.0; } } else { if (hospitalLevel == "THIRD_A") { return 800.0; } else if (hospitalLevel == "SECOND_A") { return 500.0; } else { return 200.0; } } } else { if (emergency) { if (hospitalLevel == "THIRD_A") { return 300.0; } else if (hospitalLevel == "SECOND_A") { return 150.0; } else { return 50.0; } } else { if (hospitalLevel == "THIRD_A") { return 600.0; } else if (hospitalLevel == "SECOND_A") { return 300.0; } else { return 100.0; } } } } ]]> </java>
<!-- 报销比例:参保类型 × 医院等级,急诊上浮 5 个百分点,上限 100% --> <java name="reimburseRatio" returnClass="double"> <![CDATA[ double def reimburseRatio(str:insuranceType, str:hospitalLevel, boolean:emergency) { double ratio = 0.6; if (insuranceType == "EMPLOYEE") { if (hospitalLevel == "THIRD_A") { ratio = 0.9; } else if (hospitalLevel == "SECOND_A") { ratio = 0.85; } else { ratio = 0.8; } } else { if (hospitalLevel == "THIRD_A") { ratio = 0.7; } else if (hospitalLevel == "SECOND_A") { ratio = 0.65; } else { ratio = 0.6; } } if (emergency) { ratio = ratio + 0.05; } if (ratio > 1.0) { ratio = 1.0; } return ratio; } ]]> </java>
<!-- 乙类药品先行自付 10%,其余类别无先行自付 --> <java name="prepaidSelfPay" returnClass="double"> <![CDATA[ double def prepaidSelfPay(str:medicalCategory, double:totalExpense) { if (medicalCategory == "DRUG_B") { return totalExpense * 0.1; } else { return 0.0; } } ]]> </java>
<!-- 年度统筹基金封顶线 --> <java name="annualCap" returnClass="double"> <![CDATA[ double def annualCap(str:insuranceType) { if (insuranceType == "EMPLOYEE") { return 300000.0; } else { return 200000.0; } } ]]> </java>
<!-- 封顶截断:支付额不得超过年度剩余额度 --> <java name="applyCap" returnClass="double"> <![CDATA[ double def applyCap(double:payableAmount, double:remainingCap) { if (remainingCap <= 0.0) { return 0.0; } else if (payableAmount > remainingCap) { return remainingCap; } else { return payableAmount; } } ]]> </java>
<!-- 结算结论:目录外全自费,异地未备案转人工,其余即时结算 --> <java name="complianceResult" returnClass="java.lang.String"> <![CDATA[ str def complianceResult(boolean:withinCatalog, boolean:offSite, boolean:filed) { if (withinCatalog == false) { return "SELF_PAY"; } else if (offSite && filed == false) { return "MANUAL_REVIEW"; } else { return "APPROVE"; } } ]]> </java>
<!-- 人工审核:异地未备案或单笔统筹支付达到 5 万 --> <java name="needManualReview" returnClass="boolean"> <![CDATA[ boolean def needManualReview(str:result, double:payableAmount) { if (result == "MANUAL_REVIEW") { return true; } else if (result == "APPROVE" && payableAmount >= 50000.0) { return true; } else { return false; } } ]]> </java>
</javas>MedicalReimbursementService.java
Section titled “MedicalReimbursementService.java”package com.github.paohaijiao.demo.service;
import com.github.paohaijiao.demo.MedicalReimbursementRules;import com.github.paohaijiao.demo.model.MedicalClaim;import com.github.paohaijiao.demo.model.ReimbursementDecision;
/** * 场景 13:医保报销合规校验服务 * <p> * 职责划分: * <ul> * <li><b>Java 侧</b>:报销链路串联(目录 → 先行自付 → 起付线 → 比例 → 封顶)、结算说明组装</li> * <li><b>规则侧</b>:目录范围、起付线与比例矩阵、乙类先行自付、封顶线、备案口径,全部外置在 XML 中</li> * </ul> */public class MedicalReimbursementService {
private final MedicalReimbursementRules rules;
public MedicalReimbursementService(MedicalReimbursementRules rules) { this.rules = rules; }
/** * 执行医保报销结算 * * @param claim 就诊费用要素 * @return 报销决策 */ public ReimbursementDecision settle(MedicalClaim claim) { ReimbursementDecision decision = new ReimbursementDecision(); decision.setClaimNo(claim.getClaimNo());
boolean withinCatalog = rules.withinCatalog(claim.getMedicalCategory()); double annualCap = rules.annualCap(claim.getInsuranceType()); decision.setWithinCatalog(withinCatalog); decision.setAnnualCap(annualCap);
if (!withinCatalog) { decision.setResult("SELF_PAY"); decision.setPayableAmount(0.0); decision.setSelfPayAmount(claim.getTotalExpense()); decision.addNote(claim.getMedicalCategory() + " 属目录外项目,全部自费"); return decision; }
double deductible = rules.deductible(claim.getInsuranceType(), claim.getHospitalLevel(), claim.isEmergency()); double ratio = rules.reimburseRatio(claim.getInsuranceType(), claim.getHospitalLevel(), claim.isEmergency()); double prepaid = rules.prepaidSelfPay(claim.getMedicalCategory(), claim.getTotalExpense());
double baseAfterPrepaid = claim.getTotalExpense() - prepaid; double baseAfterDeductible = baseAfterPrepaid - deductible; if (baseAfterDeductible < 0.0) { baseAfterDeductible = 0.0; } double rawPayable = baseAfterDeductible * ratio; double remainingCap = annualCap - claim.getAnnualAccumulated(); double payable = rules.applyCap(rawPayable, remainingCap);
decision.setDeductibleAmount(deductible); decision.setPrepaidSelfPay(prepaid); decision.setReimburseRatio(ratio); decision.setPayableAmount(payable); decision.setSelfPayAmount(claim.getTotalExpense() - payable); decision.setCapped(payable < rawPayable); decision.setResult(rules.complianceResult(true, claim.isOffSite(), claim.isFiled())); decision.setNeedManualReview(rules.needManualReview(decision.getResult(), payable));
buildNotes(claim, decision, rawPayable); return decision; }
private void buildNotes(MedicalClaim claim, ReimbursementDecision decision, double rawPayable) { if (claim.isEmergency()) { decision.addNote("急诊就诊,起付线减半、报销比例上浮 5 个百分点"); } if (decision.getPrepaidSelfPay() > 0.0) { decision.addNote("乙类药品先行自付 10%:" + decision.getPrepaidSelfPay() + " 元"); } if (decision.isCapped()) { decision.addNote("统筹支付触达年度封顶线,理论支付 " + rawPayable + " 元,按剩余额度 " + decision.getPayableAmount() + " 元结算"); } if (claim.isOffSite() && !claim.isFiled()) { decision.addNote("异地就医未备案,转人工审核后再行结算"); } }}MedicalReimbursementServiceTest.java
Section titled “MedicalReimbursementServiceTest.java”package com.github.paohaijiao.demo;
import com.github.paohaijiao.demo.model.MedicalClaim;import com.github.paohaijiao.demo.model.ReimbursementDecision;import com.github.paohaijiao.demo.service.MedicalReimbursementService;import com.github.paohaijiao.xml.JQuickJava;import org.junit.Assert;import org.junit.Before;import org.junit.Test;
/** * 场景 13 生产级样板:医保报销项目与比例合规校验 * <p> * 覆盖 6 组结算形态:职工三甲甲类、居民社区乙类、丙类全自费、 * 年度封顶截断、急诊政策倾斜、异地未备案人工审核。 */public class MedicalReimbursementServiceTest {
private MedicalReimbursementService service;
@Before public void setUp() { MedicalReimbursementRules rules = JQuickJava.create() .createApi(MedicalReimbursementRules.class, "demo/medical-reimbursement.xml"); this.service = new MedicalReimbursementService(rules); }
/** * 职工三甲甲类药,费用 5000 元 → 起付线 800,比例 90%,支付 3780 */ @Test public void shouldReimburseEmployeeThirdGradeClassA() { MedicalClaim claim = claim("MC-1301", "EMPLOYEE", "THIRD_A", "DRUG_A", 5000.0, false, false, false, 0.0);
ReimbursementDecision decision = service.settle(claim);
Assert.assertTrue(decision.isWithinCatalog()); Assert.assertEquals("APPROVE", decision.getResult()); Assert.assertEquals(800.0, decision.getDeductibleAmount(), 0.001); Assert.assertEquals(0.9, decision.getReimburseRatio(), 0.0001); Assert.assertEquals(0.0, decision.getPrepaidSelfPay(), 0.001); Assert.assertEquals(3780.0, decision.getPayableAmount(), 0.001); Assert.assertEquals(1220.0, decision.getSelfPayAmount(), 0.001); Assert.assertFalse(decision.isCapped()); Assert.assertFalse(decision.isNeedManualReview()); }
/** * 居民社区乙类药,费用 1000 元 → 先行自付 100、起付线 100、比例 60%,支付 480 */ @Test public void shouldReimburseResidentCommunityClassB() { MedicalClaim claim = claim("MC-1302", "RESIDENT", "COMMUNITY", "DRUG_B", 1000.0, false, false, false, 0.0);
ReimbursementDecision decision = service.settle(claim);
Assert.assertEquals("APPROVE", decision.getResult()); Assert.assertEquals(100.0, decision.getDeductibleAmount(), 0.001); Assert.assertEquals(100.0, decision.getPrepaidSelfPay(), 0.001); Assert.assertEquals(0.6, decision.getReimburseRatio(), 0.0001); Assert.assertEquals(480.0, decision.getPayableAmount(), 0.001); Assert.assertEquals(520.0, decision.getSelfPayAmount(), 0.001); Assert.assertTrue(decision.getNotes().contains("乙类药品先行自付 10%:100.0 元")); }
/** * 丙类药目录外 → 全部自费 800 元 */ @Test public void shouldDenyOutOfCatalogClassCDrug() { MedicalClaim claim = claim("MC-1303", "EMPLOYEE", "THIRD_A", "DRUG_C", 800.0, false, false, false, 0.0);
ReimbursementDecision decision = service.settle(claim);
Assert.assertFalse(decision.isWithinCatalog()); Assert.assertEquals("SELF_PAY", decision.getResult()); Assert.assertEquals(0.0, decision.getPayableAmount(), 0.001); Assert.assertEquals(800.0, decision.getSelfPayAmount(), 0.001); Assert.assertEquals(0.0, decision.getReimburseRatio(), 0.0001); Assert.assertTrue(decision.getNotes().contains("DRUG_C 属目录外项目,全部自费")); }
/** * 大额费用触达年度封顶线:剩余额度仅 2 万 → 支付 20000 */ @Test public void shouldTruncateAtAnnualCap() { MedicalClaim claim = claim("MC-1304", "EMPLOYEE", "THIRD_A", "DRUG_A", 600000.0, false, false, false, 280000.0);
ReimbursementDecision decision = service.settle(claim);
Assert.assertEquals(300000.0, decision.getAnnualCap(), 0.001); Assert.assertEquals(20000.0, decision.getPayableAmount(), 0.001); Assert.assertEquals(580000.0, decision.getSelfPayAmount(), 0.001); Assert.assertTrue(decision.isCapped()); Assert.assertTrue(decision.getNotes().contains( "统筹支付触达年度封顶线,理论支付 539280.0 元,按剩余额度 20000.0 元结算")); }
/** * 职工二级医院急诊,费用 3000 元 → 起付线 250、比例 90%,支付 2475 */ @Test public void shouldApplyEmergencyPreferentialPolicy() { MedicalClaim claim = claim("MC-1305", "EMPLOYEE", "SECOND_A", "DRUG_A", 3000.0, true, false, false, 0.0);
ReimbursementDecision decision = service.settle(claim);
Assert.assertEquals(250.0, decision.getDeductibleAmount(), 0.001); Assert.assertEquals(0.9, decision.getReimburseRatio(), 0.0001); Assert.assertEquals(2475.0, decision.getPayableAmount(), 0.001); Assert.assertTrue(decision.getNotes().contains("急诊就诊,起付线减半、报销比例上浮 5 个百分点")); }
/** * 异地就医未备案,费用 6000 元理论支付 4680 → 挂账转人工审核 */ @Test public void shouldHoldOffSiteClaimWithoutFiling() { MedicalClaim claim = claim("MC-1306", "EMPLOYEE", "THIRD_A", "DRUG_A", 6000.0, false, true, false, 0.0);
ReimbursementDecision decision = service.settle(claim);
Assert.assertEquals(4680.0, decision.getPayableAmount(), 0.001); Assert.assertEquals("MANUAL_REVIEW", decision.getResult()); Assert.assertTrue(decision.isNeedManualReview()); Assert.assertTrue(decision.getNotes().contains("异地就医未备案,转人工审核后再行结算")); }
private MedicalClaim claim(String claimNo, String insuranceType, String hospitalLevel, String category, double totalExpense, boolean emergency, boolean offSite, boolean filed, double annualAccumulated) { MedicalClaim claim = new MedicalClaim(); claim.setClaimNo(claimNo); claim.setPatientName("患者"); claim.setInsuranceType(insuranceType); claim.setHospitalLevel(hospitalLevel); claim.setMedicalCategory(category); claim.setTotalExpense(totalExpense); claim.setEmergency(emergency); claim.setOffSite(offSite); claim.setFiled(filed); claim.setAnnualAccumulated(annualAccumulated); return claim; }}在工程根目录执行:
mvn test -Dtest=MedicalReimbursementServiceTest测试覆盖形态
Section titled “测试覆盖形态”| 测试方法 | 构造的业务形态 | 关键断言/期望决策 |
|---|---|---|
shouldReimburseEmployeeThirdGradeClassA |
职工、三甲、甲类药、5000 元、非急诊(MC-1301) | 目录内,APPROVE,起付线 800,比例 0.9,支付 3780.0,自付 1220.0,未触顶不审核 |
shouldReimburseResidentCommunityClassB |
居民、社区、乙类药、1000 元(MC-1302) | 先行自付 100、起付线 100、比例 0.6,支付 480.0,自付 520.0,含乙类自付说明 |
shouldDenyOutOfCatalogClassCDrug |
职工、三甲、丙类药、800 元(MC-1303) | 目录外 SELF_PAY,支付 0.0、比例 0.0,自付 800.0,含全自费说明 |
shouldTruncateAtAnnualCap |
职工三甲甲类、60 万元、年度已报 28 万(MC-1304) | 封顶线 300000,理论 539280 被截断为 20000.0,自付 580000.0,capped=true |
shouldApplyEmergencyPreferentialPolicy |
职工、二级医院、急诊、甲类、3000 元(MC-1305) | 急诊起付线 250(500 减半),比例 0.9(0.85+0.05),支付 2475.0 |
shouldHoldOffSiteClaimWithoutFiling |
职工三甲甲类、6000 元、异地未备案(MC-1306) | 理论支付 4680.0 挂账,MANUAL_REVIEW,needManualReview=true |
职工三甲甲类药(费用 5000 元):
起付线: 800.0 报销比例: 0.9 统筹支付: 3780.0 个人自付: 1220.0结论: APPROVE 人工审核: false大额费用触封顶(费用 60 万、年度已报 28 万、剩余额度 2 万):
理论支付: 539280.0 实际支付: 20000.0(按年度剩余额度截断)个人自付: 580000.0 触达封顶线: true- 政策矩阵整体外置 XML,热更新不动 HIS 主程序:12 档起付线、6 档比例、乙类 10% 先行自付、30 万/20 万封顶线全部在
medical-reimbursement.xml中,统筹地区政策调整只改脚本并重建代理即可生效。 - Service 固定报销链路顺序:
settle方法严格按「目录判定 → 先行自付 → 起付线 → 比例 → 封顶截断 → 结论」串联,目录外在 Java 侧提前 return,从结构上保证丙类项目不会误走报销链路。 - 比例上浮在脚本内封顶 1.0:
reimburseRatio急诊加 0.05 后立即判断ratio > 1.0截断,杜绝任何矩阵档位叠出超过 100% 的报销比例。 - 起付线扣减在 Java 侧做负值归零,封顶按动态剩余额度截断:扣除先行自付与起付线后的基数若为负则归零;
applyCap以「封顶线 - 年度已累计」为剩余额度,剩余 ≤0 支付 0、不足则按剩余结算,并由capped标记留痕。 - 正向 return 表达取反,适配脚本语法:
complianceResult用withinCatalog == false、filed == false的正向比较表达否定条件,避开脚本不支持!取反的限制,分支语义与医保政策原文一一对应。 @Param按名绑定 + Java 8 无重型依赖:8 个规则方法通过参数名绑定多档入参,代理实例在@Before创建后六个用例复用;ASM 调用链无独立服务进程,可嵌入 JDK 8 与信创医保核心环境,每笔结算的口径与截断过程均可审计。