理賠計算典型特徵:
| 維度 | 規則引擎 (Drools/Easy Rules) | 動態腳本 | 決策表/決策樹 |
|---|---|---|---|
| 表達能力 | ★★★★★ 複雜邏輯建模 | ★★★★☆ 圖靈完備 | ★★★☆☆ 結構化決策 |
| 業務人員可維護 | ★★★☆☆ 需 DSL 培訓 | ★★☆☆☆ 需開發介入 | ★★★★★ Excel 直觀編輯 |
| 性能 (萬級 QPS) | ★★★★☆ Rete 網絡優化 | ★★★☆☆ 解析開銷大 | ★★★★★ 索引查找極快 |
| 版本管理/回滾 | ★★★★☆ 規則版本化 | ★★★☆☆ 代碼級管理 | ★★★★★ 表格版本對比 |
| 調試難度 | ★★★☆☆ 調試工具較弱 | ★★★★★ IDE 斷點 | ★★★★☆ 可視化追蹤 |
| 學習成本 | 高 | 低 (Groovy/JS) | 低 |
| 典型適用場景 | 核心理算、反欺詐、風控 | 個性化定價、營銷活動 | 標準化賠付、審核流程 |
// 分層架構設計
public interface CalculationEngine {
ClaimResult calculate(ClaimContext ctx);
}
// 1. 核心理算層:Drools 規則引擎
@Component
public class CoreClaimEngine implements CalculationEngine {
private KieSession kieSession;
@Override
public ClaimResult calculate(ClaimContext ctx) {
kieSession.insert(ctx);
kieSession.fireAllRules();
return ctx.getResult();
}
}
// 2. 參數配置層:決策表 (Excel/DB)
@Component
public class ParameterResolver {
public BigDecimal getReimburseRate(String productCode, String hospitalLevel) {
// 從決策表查找:產品代碼 + 醫院等級 -> 賠付比例
return decisionTable.lookup(productCode, hospitalLevel);
}
}
// 3. 複雜定價層:Groovy 動態腳本
@Component
public class PricingScriptEngine {
private GroovyShell shell = new GroovyShell();
public BigDecimal executePricing(String script, Map vars) {
Script compiled = shell.parse(script);
Binding binding = new Binding(vars);
return (BigDecimal) compiled.run(binding, binding);
}
}
// 4. 編排層:責任鏈模式串聯
@Service
public class ClaimCalculationOrchestrator {
@Autowired List engines;
public ClaimResult calculate(ClaimContext ctx) {
return engines.stream()
.reduce(ctx, (c, e) -> e.calculate(c), (a, b) -> b)
.getResult();
}
}
// 規則文件:medical_outpatient.drl
rule "門診基礎賠付計算"
when
$claim : Claim(type == ClaimType.OUTPATIENT,
validAmount > 0,
$deductible : deductible,
$rate : reimburseRate)
then
BigDecimal payable = ($claim.getValidAmount().subtract($deductible))
.multiply($rate)
.setScale(2, RoundingMode.HALF_UP);
$claim.setPayableAmount(payable.max(BigDecimal.ZERO));
update($claim);
end
rule "年度累計封頂控制"
when
$claim : Claim(annualAccumulated + payableAmount > annualLimit)
then
BigDecimal excess = $claim.getAnnualAccumulated()
.add($claim.getPayableAmount())
.subtract($claim.getAnnualLimit());
$claim.setPayableAmount($claim.getPayableAmount().subtract(excess));
update($claim);
end
@Test
public void testOutpatientCalculation() {
// Given
ClaimContext ctx = ClaimContext.builder()
.type(ClaimType.OUTPATIENT)
.validAmount(new BigDecimal("1000"))
.deductible(new BigDecimal("100"))
.reimburseRate(new BigDecimal("0.8"))
.annualLimit(new BigDecimal("5000"))
.annualAccumulated(new BigDecimal("4500"))
.build();
// When
ClaimResult result = engine.calculate(ctx);
// Then
// (1000-100)*0.8 = 720,但年度剩餘額度 500,最終賠付 500
assertEquals(new BigDecimal("500"), result.getPayableAmount());
}