跳转到内容

案例 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

规则接口共 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

将以下六个文件按源码原包名放入 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 即可运行。

package com.github.paohaijiao.demo.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 医保结算要素(报销合规校验所需的就诊与费用快照)
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public 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;
}
package com.github.paohaijiao.demo.model;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* 医保报销决策结果
*/
@Data
public 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);
}
}
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);
}
<?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>
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("异地就医未备案,转人工审核后再行结算");
}
}
}
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;
}
}

在工程根目录执行:

Terminal window
mvn test -Dtest=MedicalReimbursementServiceTest
测试方法 构造的业务形态 关键断言/期望决策
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 与信创医保核心环境,每笔结算的口径与截断过程均可审计。