案例 40:49 条业务诉求矩阵回归与跨场景联合拦截
本案例对应 USE-CASES.md 场景 40,是整个示例集的跨域回归入口。它没有新建六件套——没有独立的模型、Rules 接口、XML 或 Service,而是为 49 条业务诉求中「由 15 套生产样板直接承接」的 25 条各提供一个独立业务入口用例(诉求 1/2/4/5/7/9/11/12/13/15/17/19/20/22/24/25/28/33/34/35/38/39/42/45/47),规则脚本、XML 与 Service 全部复用案例 1-15;在此之外再加 1 个案例 1 信贷准入与案例 15 交易反欺诈双引擎串联的跨场景联合拦截用例,共同构成 26 个矩阵回归入口。
当规则资产从几套增长到几十套(本示例集已有 39 套业务六件套、39 份 XML),真正的工程风险不再是「某条规则写得对不对」,而是「重构引擎、调整公共执行链路后,历史承接的业务诉求会不会被悄悄破坏」。一张 49 条诉求的覆盖矩阵,加上每个诉求一个可独立运行的业务入口断言,就把抽象的规则资产变成了可进 CI 门禁的回归基线:任何改动都必须同时对 25 条跨行业诉求负责。
跨引擎串联则对应真实系统中更高阶的联防需求。在银行与消费金融机构里,信贷审批与支付反欺诈通常分属授信、风控两套系统:客户在深夜境外大额交易被反欺诈拦截的同时,其信贷画像可能已命中高风险等级。两套规则集如果只能各自为战,欺诈情报就无法反哺放款决策。本案例最后的联合拦截用例证明:同一个 JVM、同一个事务内,可以先后调用反欺诈引擎与准入引擎,把 BLOCK 与 REJECT 两个独立结论合成为 JOINT_REJECT 联合决策。
25 个矩阵入口覆盖金融银行、政务企服、电商零售、制造 MES、医疗医保、HR/OA、IoT 七大行业域——从个人信贷、贷后催收、保险理赔,到人才补贴、税务评级、工单路由,再到动态定价、质检放行、医保结算、绩效计分与设备告警。每个入口都构造一份真实形态的业务对象并断言最终决策,而不是断言某个中间分数。
- 规则资产多了之后防重构回归:引擎的参数绑定、类型转换、代理缓存等公共链路一旦调整,影响面是全部历史场景,逐个人工核对不现实,必须有自动化矩阵兜底。
- 49 条诉求需要持续验证:诉求覆盖关系只写在文档里会随代码腐化,必须让每条「直接覆盖」诉求在测试中都有一个以诉求编号命名的入口,文档与代码强制对齐。
- 跨业务域决策难以串联:信贷准入与交易反欺诈是两套独立规则集,真实联防要求在一次业务流程中顺序调用并合成结论,架构上需要验证多引擎共存与组合能力。
- 回归入口必须轻量:为回归复制整套六件套会制造大量重复维护成本;理想状态是零新增规则、零新增 XML,只新增一个测试类。
- 多套规则集共存的稳定性待验证:一个 JVM 内同时加载十几份 XML、创建十几个代理并交叉调用,是否存在命名空间冲突、类加载竞争或缓存串味,需要集中压测式验证。
- 回归断言要贴近业务语义:只断言「不抛异常」没有防护力,必须断言
PASS/REJECT、风险等级、赔付金额、派单优先级等业务结果,才能在口径被误改时立即红灯。
覆盖矩阵与入口
Section titled “覆盖矩阵与入口”下表依据 CoveredScenariosMatrixTest.java 的 26 个 @Test 方法与其 Javadoc 整理,诉求编号与 USE-CASES.md「四、49 个业务场景覆盖矩阵」一一对应。前 25 行每个诉求一个独立业务入口,最后 1 行为跨场景联合拦截。
| 测试方法 | 对应诉求编号 / 业务入口 | 复用的案例与规则 |
|---|---|---|
req01_creditAdmissionPrimePass |
诉求 1:个人信贷自动审批分级 —— 优质客户 PASS/A 级/足额授信 | 案例 1:RiskAdmissionRules + risk-admission.xml + RiskAdmissionService |
req02_postLoanNormalWatch |
诉求 2:贷后风险预警 —— 未逾期账户正常监测 | 案例 2:PostLoanWarningRules + post-loan-warning.xml + PostLoanWarningService |
req04_antiFraudBlockOverseasNight |
诉求 4:贷款反欺诈风险识别 —— 深夜境外大额交易 100 分拦截 | 案例 15:TransactionFraudRules + transaction-fraud.xml + TransactionFraudService |
req05_cardApplicationManualReview |
诉求 5:信用卡进件审批分级 —— 边界客户转人工复核 | 案例 1:RiskAdmissionRules + risk-admission.xml + RiskAdmissionService |
req07_claimLiabilityAndRatio |
诉求 7:保险理赔责任与赔付比例 —— 门诊小额快赔 85% | 案例 3:ClaimAuditRules + claim-audit.xml + ClaimAuditService |
req09_overdueCollectionLitigation |
诉求 9:逾期贷后分层催收 —— 95 天失联大额转诉讼 | 案例 2:PostLoanWarningRules + post-loan-warning.xml + PostLoanWarningService |
req11_bankCardTransactionPass |
诉求 11:银行卡交易风控 —— 日常小额交易放行 | 案例 15:TransactionFraudRules + transaction-fraud.xml + TransactionFraudService |
req12_whiteAndBlackListRouting |
诉求 12:白名单/黑名单动态判定 —— 受信商户白名单短路放行;涉诉黑名单一票否决 | 案例 15:TransactionFraudRules(白名单短路)+ 案例 1:RiskAdmissionRules(涉诉否决) |
req13_talentSubsidyApproval |
诉求 13:创业/人才补贴资格自动审核 —— 博士 A 类人才核定 40 万 | 案例 11:SubsidyRules + subsidy-eligibility.xml + SubsidyService |
req15_documentCompletenessCheck |
诉求 15:政务办事材料完整性前置校验 —— 材料不齐退回补正 | 案例 11:SubsidyRules + subsidy-eligibility.xml + SubsidyService |
req17_procurementCreditVeto |
诉求 17:政府采购供应商准入与失信过滤 —— 失信记录一票否决 | 案例 11:SubsidyRules + subsidy-eligibility.xml + SubsidyService |
req19_enterpriseCreditRating |
诉求 19:企业信用等级自动评级 —— 合规良好企业 LOW/自查 | 案例 8:TaxComplianceRules + tax-compliance.xml + TaxComplianceService |
req20_approvalRoutingBySla |
诉求 20:行政审批流程智能路由 —— 安全类 P0 工单 5 分钟首响、经理介入 | 案例 5:TicketSlaRules + ticket-sla.xml + TicketSlaService |
req22_emergencyLevelClassification |
诉求 22:应急突发事件等级自动分级 —— 92℃ 过热 CRITICAL/P0 | 案例 9:DeviceAlarmRules + device-alarm.xml + DeviceAlarmService |
req24_bidPrequalificationTwoStage |
诉求 24:招投标资格预审两段式 —— 资质硬门槛淘汰 + 达标者评分入围 | 案例 14:ResumeScreeningRules + resume-screening.xml + ResumeScreeningService |
req25_couponStackingControl |
诉求 25:营销活动叠加/互斥控制 —— 新客券互斥、VIP 券可叠加 | 案例 10:CouponRules + coupon.xml + CouponService |
req28_priceFloorAndMarginProtection |
诉求 28:渠道限价/最低价/价差管控 —— 折扣击穿毛利线时按保护价执行 | 案例 4:DynamicPricingRules + dynamic-pricing.xml + DynamicPricingService |
req33_productionQualityRelease |
诉求 33:产线质检多指标判定 —— 优质批次合格放行 | 案例 12:QualityInspectionRules + quality-inspection.xml + QualityInspectionService |
req34_workOrderPriorityMatrix |
诉求 34:MES 工单优先级智能排序 —— P3 低优请求标准 SLA | 案例 5:TicketSlaRules + ticket-sla.xml + TicketSlaService |
req35_equipmentHealthScoring |
诉求 35:生产设备健康度评分与故障预警 —— 资源紧张 22 分 MINOR,预警不派单 | 案例 9:DeviceAlarmRules + device-alarm.xml + DeviceAlarmService |
req38_batchReleaseCompliance |
诉求 38:成品出入库合规与批次放行 —— 致命缺陷整批冻结不予放行 | 案例 12:QualityInspectionRules + quality-inspection.xml + QualityInspectionService |
req39_medicalInsuranceReimbursement |
诉求 39:医保报销项目/比例/合规校验 —— 职工三甲甲类报销 3780 | 案例 13:MedicalReimbursementRules + medical-reimbursement.xml + MedicalReimbursementService |
req42_kpiWeightedGrading |
诉求 42:员工绩效 KPI 加权计分与等级评定 —— S 级系数 2.0 | 案例 6:PerformanceRules + performance.xml + PerformanceService |
req45_resumeScreeningStrong |
诉求 45:招聘简历智能初筛 —— 硕士 985 全匹配强烈推荐 | 案例 14:ResumeScreeningRules + resume-screening.xml + ResumeScreeningService |
req47_iotSensorThresholdAlarm |
诉求 47:IoT 传感器数据阈值告警分级 —— 网关掉连 51 分 MAJOR/P1 | 案例 9:DeviceAlarmRules + device-alarm.xml + DeviceAlarmService |
req50_crossScenarioAdmissionAndFraudJointBlock |
跨场景组合:场景 15 交易反欺诈 + 场景 1 信贷准入双引擎串联联合拦截 | 案例 15:TransactionFraudRules(BLOCK,100 分)+ 案例 1:RiskAdmissionRules(REJECT,D 级) |
代码实现(完整可运行)
Section titled “代码实现(完整可运行)”本案例只新增一个测试类,将其放入 src/test/java/com/github/paohaijiao/demo/ 即可。它依赖的全部模型(CreditApplication、BankTransaction、ClaimRequest、SubsidyApplication、Ticket、DeviceAlarm、Resume、CouponRequest、InventoryItem、InspectionBatch、MedicalClaim、PerformanceRecord、LoanAccount、TaxProfile 等及各自决策模型)、规则接口、XML 脚本与 Service 均复用案例 1-15 的既有资产,无需新增任何模型、规则或 XML。
CoveredScenariosMatrixTest.java
Section titled “CoveredScenariosMatrixTest.java”package com.github.paohaijiao.demo;
import com.github.paohaijiao.demo.model.AdmissionDecision;import com.github.paohaijiao.demo.model.AlarmDecision;import com.github.paohaijiao.demo.model.BankTransaction;import com.github.paohaijiao.demo.model.BonusDecision;import com.github.paohaijiao.demo.model.ClaimDecision;import com.github.paohaijiao.demo.model.ClaimRequest;import com.github.paohaijiao.demo.model.CouponDecision;import com.github.paohaijiao.demo.model.CouponRequest;import com.github.paohaijiao.demo.model.CreditApplication;import com.github.paohaijiao.demo.model.DeviceAlarm;import com.github.paohaijiao.demo.model.FraudDecision;import com.github.paohaijiao.demo.model.InspectionBatch;import com.github.paohaijiao.demo.model.InspectionDecision;import com.github.paohaijiao.demo.model.InventoryItem;import com.github.paohaijiao.demo.model.LoanAccount;import com.github.paohaijiao.demo.model.MedicalClaim;import com.github.paohaijiao.demo.model.PerformanceRecord;import com.github.paohaijiao.demo.model.PricingDecision;import com.github.paohaijiao.demo.model.ReimbursementDecision;import com.github.paohaijiao.demo.model.Resume;import com.github.paohaijiao.demo.model.ScreeningDecision;import com.github.paohaijiao.demo.model.SlaDecision;import com.github.paohaijiao.demo.model.SubsidyApplication;import com.github.paohaijiao.demo.model.SubsidyDecision;import com.github.paohaijiao.demo.model.TaxProfile;import com.github.paohaijiao.demo.model.TaxRiskDecision;import com.github.paohaijiao.demo.model.Ticket;import com.github.paohaijiao.demo.model.WarningDecision;import com.github.paohaijiao.demo.service.ClaimAuditService;import com.github.paohaijiao.demo.service.CouponService;import com.github.paohaijiao.demo.service.DeviceAlarmService;import com.github.paohaijiao.demo.service.DynamicPricingService;import com.github.paohaijiao.demo.service.MedicalReimbursementService;import com.github.paohaijiao.demo.service.PerformanceService;import com.github.paohaijiao.demo.service.PostLoanWarningService;import com.github.paohaijiao.demo.service.QualityInspectionService;import com.github.paohaijiao.demo.service.ResumeScreeningService;import com.github.paohaijiao.demo.service.RiskAdmissionService;import com.github.paohaijiao.demo.service.SubsidyService;import com.github.paohaijiao.demo.service.TaxComplianceService;import com.github.paohaijiao.demo.service.TicketSlaService;import com.github.paohaijiao.demo.service.TransactionFraudService;import com.github.paohaijiao.xml.JQuickJava;import org.junit.Assert;import org.junit.Before;import org.junit.Test;
/** * 场景 40:诉求矩阵「直接覆盖」独立入口回归 + 跨场景联合拦截。 * <p> * 前 25 个用例对应 USE-CASES.md 矩阵中由 15 套生产样板直接承接的诉求 * (诉求 1/2/4/5/7/9/11/12/13/15/17/19/20/22/24/25/28/33/34/35/38/39/42/45/47), * 每个诉求一个独立业务入口;最后一个用例演示场景 1 信贷准入与场景 15 交易反欺诈 * 双引擎串联的联合拦截,构成全部 50 个独立业务场景入口。 */public class CoveredScenariosMatrixTest {
private RiskAdmissionService riskAdmissionService; private PostLoanWarningService postLoanWarningService; private ClaimAuditService claimAuditService; private DynamicPricingService dynamicPricingService; private TicketSlaService ticketSlaService; private PerformanceService performanceService; private TaxComplianceService taxComplianceService; private DeviceAlarmService deviceAlarmService; private CouponService couponService; private SubsidyService subsidyService; private QualityInspectionService qualityInspectionService; private MedicalReimbursementService medicalReimbursementService; private ResumeScreeningService resumeScreeningService; private TransactionFraudService transactionFraudService;
@Before public void setUp() { this.riskAdmissionService = new RiskAdmissionService(JQuickJava.create() .createApi(RiskAdmissionRules.class, "demo/risk-admission.xml")); this.postLoanWarningService = new PostLoanWarningService(JQuickJava.create() .createApi(PostLoanWarningRules.class, "demo/post-loan-warning.xml")); this.claimAuditService = new ClaimAuditService(JQuickJava.create() .createApi(ClaimAuditRules.class, "demo/claim-audit.xml")); this.dynamicPricingService = new DynamicPricingService(JQuickJava.create() .createApi(DynamicPricingRules.class, "demo/dynamic-pricing.xml")); this.ticketSlaService = new TicketSlaService(JQuickJava.create() .createApi(TicketSlaRules.class, "demo/ticket-sla.xml")); this.performanceService = new PerformanceService(JQuickJava.create() .createApi(PerformanceRules.class, "demo/performance.xml")); this.taxComplianceService = new TaxComplianceService(JQuickJava.create() .createApi(TaxComplianceRules.class, "demo/tax-compliance.xml")); this.deviceAlarmService = new DeviceAlarmService(JQuickJava.create() .createApi(DeviceAlarmRules.class, "demo/device-alarm.xml")); this.couponService = new CouponService(JQuickJava.create() .createApi(CouponRules.class, "demo/coupon.xml")); this.subsidyService = new SubsidyService(JQuickJava.create() .createApi(SubsidyRules.class, "demo/subsidy-eligibility.xml")); this.qualityInspectionService = new QualityInspectionService(JQuickJava.create() .createApi(QualityInspectionRules.class, "demo/quality-inspection.xml")); this.medicalReimbursementService = new MedicalReimbursementService(JQuickJava.create() .createApi(MedicalReimbursementRules.class, "demo/medical-reimbursement.xml")); this.resumeScreeningService = new ResumeScreeningService(JQuickJava.create() .createApi(ResumeScreeningRules.class, "demo/resume-screening.xml")); this.transactionFraudService = new TransactionFraudService(JQuickJava.create() .createApi(TransactionFraudRules.class, "demo/transaction-fraud.xml")); }
/** * 诉求 1:个人信贷自动审批分级 —— 优质客户 PASS/A 级/足额授信 */ @Test public void req01_creditAdmissionPrimePass() { CreditApplication a = new CreditApplication(); a.setApplicationId("M-REQ01"); a.setApplicantName("张伟"); a.setAge(35); a.setCompanyType("SOE"); a.setEmployedMonths(84); a.setSocialSecurityMonths(60); a.setMonthlyIncome(30000.0); a.setMonthlyDebt(4500.0); a.setOverdueCount(0); a.setMaxOverdueDays(0); a.setCreditQueryCount3M(2); a.setLoanAmount(300000.0); a.setLoanTermMonths(36); a.setCollateralType("HOUSE"); a.setCollateralValue(600000.0); a.setHasJudicialRecord(false); a.setCityTier("TIER_1");
AdmissionDecision d = riskAdmissionService.evaluate(a);
Assert.assertEquals("PASS", d.getAdmit()); Assert.assertEquals("A", d.getRiskLevel()); Assert.assertEquals(300000.0, d.getSuggestedLimit(), 0.001); }
/** * 诉求 2:贷后风险预警 —— 未逾期账户正常监测 */ @Test public void req02_postLoanNormalWatch() { LoanAccount a = new LoanAccount(); a.setLoanNo("M-REQ02"); a.setCustomerName("陈静"); a.setProductType("CONSUMER"); a.setRemainingPrincipal(50000.0); a.setOverdueAmount(0.0); a.setOverdueDays(0); a.setBaseRate(0.08); a.setCollateralType("NONE"); a.setCollateralValue(0.0); a.setContactSuccess(true); a.setLastContactDays(0); a.setOverdueHistoryCount(0); a.setWarningCount(0);
WarningDecision d = postLoanWarningService.evaluate(a);
Assert.assertEquals("BLUE", d.getWarningLevel()); Assert.assertEquals(0, d.getWarningScore()); Assert.assertTrue(d.getActions().contains("账户状态正常,无需催收")); }
/** * 诉求 4:贷款反欺诈风险识别 —— 深夜境外大额交易 100 分拦截 */ @Test public void req04_antiFraudBlockOverseasNight() { BankTransaction t = txn("M-REQ04", 30000.0, 2, "ONLINE", true, true, false, 6, 60000.0, true, true, false, 1, 400, 8000.0, false);
FraudDecision d = transactionFraudService.evaluate(t);
Assert.assertEquals(100, d.getRiskScore()); Assert.assertEquals("HIGH", d.getRiskLevel()); Assert.assertEquals("BLOCK", d.getAction()); Assert.assertTrue(d.isNeedAlert()); }
/** * 诉求 5:信用卡进件审批分级 —— 边界客户转人工复核 */ @Test public void req05_cardApplicationManualReview() { CreditApplication a = new CreditApplication(); a.setApplicationId("M-REQ05"); a.setApplicantName("王芳"); a.setAge(28); a.setCompanyType("PRIVATE"); a.setEmployedMonths(20); a.setSocialSecurityMonths(12); a.setMonthlyIncome(15000.0); a.setMonthlyDebt(6900.0); a.setOverdueCount(1); a.setMaxOverdueDays(20); a.setCreditQueryCount3M(4); a.setLoanAmount(100000.0); a.setLoanTermMonths(24); a.setCollateralType("VEHICLE"); a.setCollateralValue(130000.0); a.setHasJudicialRecord(false); a.setCityTier("TIER_2");
AdmissionDecision d = riskAdmissionService.evaluate(a);
Assert.assertEquals("MANUAL_REVIEW", d.getAdmit()); Assert.assertEquals("C", d.getRiskLevel()); Assert.assertEquals(57, d.getTotalScore()); }
/** * 诉求 7:保险理赔责任与赔付比例 —— 门诊小额快赔 85% */ @Test public void req07_claimLiabilityAndRatio() { ClaimRequest r = new ClaimRequest(); r.setClaimNo("M-REQ07"); r.setPolicyNo("POL-M07"); r.setInsuredName("刘洋"); r.setProductType("MEDICAL"); r.setHospitalLevel("THIRD_A"); r.setClaimAmount(3200.0); r.setInvoiceAmount(3200.0); r.setDeductible(300.0); r.setInsuredDays(400); r.setWaitingPeriodDays(30); r.setPriorClaimCount(0); r.setFraudFlagCount(0); r.setAccident(false); r.setDocumentComplete(true);
ClaimDecision d = claimAuditService.audit(r);
Assert.assertEquals("APPROVE", d.getAuditResult()); Assert.assertEquals(2465.0, d.getPayableAmount(), 0.001); Assert.assertEquals(0.85, d.getReimburseRatio(), 0.0001); Assert.assertTrue(d.isFastTrack()); }
/** * 诉求 9:逾期贷后分层催收 —— 95 天失联大额转诉讼 */ @Test public void req09_overdueCollectionLitigation() { LoanAccount a = new LoanAccount(); a.setLoanNo("M-REQ09"); a.setCustomerName("张强"); a.setProductType("CONSUMER"); a.setRemainingPrincipal(365000.0); a.setOverdueAmount(200000.0); a.setOverdueDays(95); a.setBaseRate(0.10); a.setCollateralType("NONE"); a.setCollateralValue(0.0); a.setContactSuccess(false); a.setLastContactDays(30); a.setOverdueHistoryCount(0); a.setWarningCount(0);
WarningDecision d = postLoanWarningService.evaluate(a);
Assert.assertEquals("ORANGE", d.getWarningLevel()); Assert.assertEquals(4, d.getOverdueStage()); Assert.assertEquals("LITIGATION", d.getCollectChannel()); Assert.assertTrue(d.isNeedLitigation()); }
/** * 诉求 11:银行卡交易风控 —— 日常小额交易放行 */ @Test public void req11_bankCardTransactionPass() { BankTransaction t = txn("M-REQ11", 200.0, 14, "POS", false, false, false, 1, 200.0, false, false, true, 0, 400, 5000.0, false);
FraudDecision d = transactionFraudService.evaluate(t);
Assert.assertEquals(0, d.getRiskScore()); Assert.assertEquals("LOW", d.getRiskLevel()); Assert.assertEquals("PASS", d.getAction()); }
/** * 诉求 12:白名单/黑名单动态判定 —— 受信商户白名单短路放行;涉诉黑名单一票否决 */ @Test public void req12_whiteAndBlackListRouting() { BankTransaction white = txn("M-REQ12-W", 200.0, 14, "POS", false, false, false, 1, 200.0, false, false, true, 0, 400, 5000.0, true); FraudDecision wd = transactionFraudService.evaluate(white); Assert.assertEquals("PASS", wd.getAction()); Assert.assertTrue(wd.getIndicators().contains("受信商户白名单交易,直接放行"));
CreditApplication black = new CreditApplication(); black.setApplicationId("M-REQ12-B"); black.setApplicantName("陈刚"); black.setAge(38); black.setCompanyType("PRIVATE"); black.setEmployedMonths(48); black.setSocialSecurityMonths(36); black.setMonthlyIncome(20000.0); black.setMonthlyDebt(3000.0); black.setOverdueCount(0); black.setMaxOverdueDays(0); black.setCreditQueryCount3M(3); black.setLoanAmount(150000.0); black.setLoanTermMonths(36); black.setCollateralType("HOUSE"); black.setCollateralValue(300000.0); black.setHasJudicialRecord(true); black.setCityTier("TIER_2"); AdmissionDecision bd = riskAdmissionService.evaluate(black); Assert.assertEquals("REJECT", bd.getAdmit()); Assert.assertTrue(bd.getRejectReasons().contains("存在涉诉/被执行记录,触发一票否决")); }
/** * 诉求 13:创业/人才补贴资格自动审核 —— 博士 A 类人才核定 40 万 */ @Test public void req13_talentSubsidyApproval() { SubsidyApplication app = subsidyApp("M-REQ13"); app.setEducation("PHD"); app.setAge(28); app.setLocalSocialSecurityMonths(48); app.setTalentCategory("A"); app.setStartupRegistered(true); app.setStartupEmployees(12);
SubsidyDecision d = subsidyService.review(app);
Assert.assertEquals("QUALIFIED", d.getStage()); Assert.assertEquals(110, d.getQualificationScore()); Assert.assertEquals(400000.0, d.getSubsidyAmount(), 0.001); }
/** * 诉求 15:政务办事材料完整性前置校验 —— 材料不齐退回补正 */ @Test public void req15_documentCompletenessCheck() { SubsidyApplication app = subsidyApp("M-REQ15"); app.setEducation("PHD"); app.setAge(30); app.setLocalSocialSecurityMonths(48); app.setTalentCategory("A"); app.setStartupRegistered(false); app.setStartupEmployees(0); app.setDocumentsComplete(false);
SubsidyDecision d = subsidyService.review(app);
Assert.assertEquals("MANUAL_REVIEW", d.getStage()); Assert.assertEquals(0.0, d.getSubsidyAmount(), 0.001); Assert.assertTrue(d.getReasons().contains("申报材料不齐全,退回补正后转人工复核")); }
/** * 诉求 17:政府采购供应商准入与失信过滤 —— 失信记录一票否决 */ @Test public void req17_procurementCreditVeto() { SubsidyApplication app = subsidyApp("M-REQ17"); app.setEducation("MASTER"); app.setAge(30); app.setLocalSocialSecurityMonths(40); app.setTalentCategory("B"); app.setStartupRegistered(false); app.setStartupEmployees(0); app.setCreditViolation(true);
SubsidyDecision d = subsidyService.review(app);
Assert.assertEquals("REJECTED", d.getStage()); Assert.assertTrue(d.getReasons().contains("存在失信被执行记录,触发一票否决")); }
/** * 诉求 19:企业信用等级自动评级 —— 合规良好企业 LOW/自查 */ @Test public void req19_enterpriseCreditRating() { TaxProfile p = new TaxProfile(); p.setCompanyId("M-REQ19"); p.setCompanyName("某科技股份有限公司"); p.setTaxpayerType("GENERAL"); p.setTaxPeriod("2026Q2"); p.setLateCount(0); p.setMissingInvoice(0); p.setTaxBurdenRate(0.05); p.setIndustryAvgBurden(0.04); p.setRevenue(20000000.0); p.setInputTaxRatio(0.70); p.setHasAbnormalInvoice(false); p.setYearsSinceRegister(5); p.setCreditRating("A");
TaxRiskDecision d = taxComplianceService.evaluate(p);
Assert.assertEquals(0, d.getRiskScore()); Assert.assertEquals("LOW", d.getRiskLevel()); Assert.assertEquals("SELF_CHECK", d.getAuditLevel()); }
/** * 诉求 20:行政审批流程智能路由 —— 安全类 P0 工单 5 分钟首响、经理介入 */ @Test public void req20_approvalRoutingBySla() { Ticket t = new Ticket(); t.setTicketNo("M-REQ20"); t.setTitle("支付网关不可用"); t.setPriority("P0"); t.setCategory("INCIDENT"); t.setCustomerLevel("VIP"); t.setCreateChannel("PHONE"); t.setSecurityRelated(true); t.setReopenedTimes(0); t.setAssigneeLoad(8); t.setHasSlaBreachHistory(false); t.setElapsedMinutes(20);
SlaDecision d = ticketSlaService.evaluate(t);
Assert.assertEquals("VIP", d.getSlaLevel()); Assert.assertEquals(5, d.getResponseMinutes()); Assert.assertEquals(10, d.getEscalateMinutes()); Assert.assertTrue(d.isNeedManager()); }
/** * 诉求 22:应急突发事件等级自动分级 —— 92℃ 过热 CRITICAL/P0 */ @Test public void req22_emergencyLevelClassification() { DeviceAlarm a = new DeviceAlarm(); a.setDeviceId("M-REQ22"); a.setDeviceName("炼钢炉温控传感器"); a.setDeviceType("SENSOR"); a.setCriticality("HIGH"); a.setTemperature(92.0); a.setCpuUsage(96); a.setMemoryUsage(70); a.setErrorCount(22); a.setConnectionLost(false); a.setLastHeartbeatSeconds(60); a.setBatteryLevel(80); a.setVibrationLevel(1.0);
AlarmDecision d = deviceAlarmService.evaluate(a);
Assert.assertEquals("CRITICAL", d.getAlarmLevel()); Assert.assertEquals("P0", d.getDispatchPriority()); Assert.assertEquals("ISOLATE", d.getAutoRemediation()); }
/** * 诉求 24:招投标资格预审两段式 —— 资质硬门槛淘汰 + 达标者评分入围 */ @Test public void req24_bidPrequalificationTwoStage() { Resume unqualified = resume("M-REQ24-U", "BACHELOR", "MASTER", "211", 5, 3, 4, 5); unqualified.setExpectedSalary(16000.0); unqualified.setSalaryMin(15000.0); unqualified.setSalaryMax(25000.0); unqualified.setCurrentCity("上海"); unqualified.setJobCity("上海"); unqualified.setWillingToRelocate(false); unqualified.setJobHoppingCount(1); unqualified.setGapMonths(5); unqualified.setCertified(true); unqualified.setEnglishLevel("CET6"); ScreeningDecision d1 = resumeScreeningService.screen(unqualified); Assert.assertFalse(d1.isHardCheckPass()); Assert.assertEquals("REJECT", d1.getResult());
Resume qualified = resume("M-REQ24-Q", "MASTER", "MASTER", "985", 8, 3, 5, 5); qualified.setExpectedSalary(18000.0); qualified.setSalaryMin(15000.0); qualified.setSalaryMax(25000.0); qualified.setCurrentCity("上海"); qualified.setJobCity("上海"); qualified.setWillingToRelocate(false); qualified.setJobHoppingCount(0); qualified.setGapMonths(2); qualified.setCertified(true); qualified.setEnglishLevel("CET6"); ScreeningDecision d2 = resumeScreeningService.screen(qualified); Assert.assertTrue(d2.isHardCheckPass()); Assert.assertEquals("INTERVIEW", d2.getResult()); Assert.assertEquals(90, d2.getTotalScore()); }
/** * 诉求 25:营销活动叠加/互斥控制 —— 新客券互斥、VIP 券可叠加 */ @Test public void req25_couponStackingControl() { CouponRequest n = couponReq("M-REQ25-N", "NORMAL", 88.0, 0, "APPAREL", 2, true); CouponDecision nd = couponService.issue(n); Assert.assertEquals("NEW_USER", nd.getCouponType()); Assert.assertFalse(nd.isStackable());
CouponRequest v = couponReq("M-REQ25-V", "VIP", 2500.0, 15, "DIGITAL", 900, false); v.setLastOrderDays(10); v.setAvgOrderAmount(800.0); CouponDecision vd = couponService.issue(v); Assert.assertEquals("VIP_EXCLUSIVE", vd.getCouponType()); Assert.assertTrue(vd.isStackable()); }
/** * 诉求 28:渠道限价/最低价/价差管控 —— 折扣击穿毛利线时按保护价执行 */ @Test public void req28_priceFloorAndMarginProtection() { InventoryItem item = new InventoryItem(); item.setSku("M-REQ28"); item.setSkuName("滞销小家电"); item.setCategory("家电"); item.setStockAgeDays(120); item.setStockQty(100); item.setCostPrice(60.0); item.setCurrentPrice(100.0); item.setRecentSalesQty30d(10); item.setSeasonFactor("NORMAL"); item.setPerishable(false); item.setWarehouseType("MAIN"); item.setMarginFloor(0.20);
PricingDecision d = dynamicPricingService.price(item);
Assert.assertEquals(0.50, d.getFinalDiscount(), 0.001); Assert.assertEquals(72.0, d.getSuggestedPrice(), 0.001); Assert.assertTrue(d.getNotes().contains("清仓折扣触及最低毛利保护线,按保护价执行")); }
/** * 诉求 33:产线质检多指标判定 —— 优质批次合格放行 */ @Test public void req33_productionQualityRelease() { InspectionBatch b = inspectionBatch("M-REQ33", 200, 0, 0, 1, 10.01, 10.0, 0.05, 97);
InspectionDecision d = qualityInspectionService.inspect(b);
Assert.assertEquals("ACCEPT", d.getQualityLevel()); Assert.assertTrue(d.isReleaseAllowed()); Assert.assertEquals("NEXT_PROCESS", d.getDisposition()); }
/** * 诉求 34:MES 工单优先级智能排序 —— P3 低优请求标准 SLA */ @Test public void req34_workOrderPriorityMatrix() { Ticket t = new Ticket(); t.setTicketNo("M-REQ34"); t.setTitle("修改头像失败"); t.setPriority("P3"); t.setCategory("REQUEST"); t.setCustomerLevel("NORMAL"); t.setCreateChannel("APP"); t.setSecurityRelated(false); t.setReopenedTimes(0); t.setAssigneeLoad(3); t.setHasSlaBreachHistory(false); t.setElapsedMinutes(100);
SlaDecision d = ticketSlaService.evaluate(t);
Assert.assertEquals("STANDARD", d.getSlaLevel()); Assert.assertEquals(240, d.getResponseMinutes()); Assert.assertEquals(7200, d.getResolveMinutes()); Assert.assertFalse(d.isNeedEscalate()); }
/** * 诉求 35:生产设备健康度评分与故障预警 —— 资源紧张 22 分 MINOR,预警不派单 */ @Test public void req35_equipmentHealthScoring() { DeviceAlarm a = new DeviceAlarm(); a.setDeviceId("M-REQ35"); a.setDeviceName("图像识别节点"); a.setDeviceType("CAMERA"); a.setCriticality("MEDIUM"); a.setTemperature(45.0); a.setCpuUsage(88); a.setMemoryUsage(92); a.setErrorCount(6); a.setConnectionLost(false); a.setLastHeartbeatSeconds(30); a.setBatteryLevel(60); a.setVibrationLevel(1.0);
AlarmDecision d = deviceAlarmService.evaluate(a);
Assert.assertEquals(22, d.getAlarmScore()); Assert.assertEquals("MINOR", d.getAlarmLevel()); Assert.assertFalse(d.isNeedDispatch()); Assert.assertEquals("RATE_LIMIT", d.getAutoRemediation()); }
/** * 诉求 38:成品出入库合规与批次放行 —— 致命缺陷整批冻结不予放行 */ @Test public void req38_batchReleaseCompliance() { InspectionBatch b = inspectionBatch("M-REQ38", 100, 1, 0, 2, 10.02, 10.0, 0.05, 80);
InspectionDecision d = qualityInspectionService.inspect(b);
Assert.assertEquals("SCRAP", d.getQualityLevel()); Assert.assertFalse(d.isReleaseAllowed()); Assert.assertEquals("SCRAP_BIN", d.getDisposition()); }
/** * 诉求 39:医保报销项目/比例/合规校验 —— 职工三甲甲类报销 3780 */ @Test public void req39_medicalInsuranceReimbursement() { MedicalClaim c = new MedicalClaim(); c.setClaimNo("M-REQ39"); c.setPatientName("患者"); c.setInsuranceType("EMPLOYEE"); c.setHospitalLevel("THIRD_A"); c.setMedicalCategory("DRUG_A"); c.setTotalExpense(5000.0); c.setEmergency(false); c.setOffSite(false); c.setFiled(false); c.setAnnualAccumulated(0.0);
ReimbursementDecision d = medicalReimbursementService.settle(c);
Assert.assertEquals("APPROVE", d.getResult()); Assert.assertEquals(0.9, d.getReimburseRatio(), 0.0001); Assert.assertEquals(3780.0, d.getPayableAmount(), 0.001); }
/** * 诉求 42:员工绩效 KPI 加权计分与等级评定 —— S 级系数 2.0 */ @Test public void req42_kpiWeightedGrading() { PerformanceRecord r = new PerformanceRecord(); r.setEmployeeId("M-REQ42"); r.setEmployeeName("张伟"); r.setDept("销售部"); r.setKpiScore(95.0); r.setProjectScore(90.0); r.setTeamScore(85.0); r.setAttendanceScore(96.0); r.setManager(false); r.setHasViolation(false); r.setSalesAmount(600000.0); r.setBaseSalary(20000.0);
BonusDecision d = performanceService.evaluate(r);
Assert.assertEquals(92.65, d.getTotalScore(), 0.001); Assert.assertEquals("S", d.getGrade()); Assert.assertEquals(2.0, d.getBonusCoefficient(), 0.001); Assert.assertEquals(40000.0, d.getBonusAmount(), 0.001); }
/** * 诉求 45:招聘简历智能初筛 —— 硕士 985 全匹配强烈推荐 */ @Test public void req45_resumeScreeningStrong() { Resume r = resume("M-REQ45", "MASTER", "MASTER", "985", 8, 3, 5, 5); r.setExpectedSalary(18000.0); r.setSalaryMin(15000.0); r.setSalaryMax(25000.0); r.setCurrentCity("上海"); r.setJobCity("上海"); r.setWillingToRelocate(false); r.setJobHoppingCount(0); r.setGapMonths(2); r.setCertified(true); r.setEnglishLevel("CET6");
ScreeningDecision d = resumeScreeningService.screen(r);
Assert.assertTrue(d.isHardCheckPass()); Assert.assertEquals("STRONG", d.getRecommendLevel()); Assert.assertEquals("INTERVIEW", d.getResult()); }
/** * 诉求 47:IoT 传感器数据阈值告警分级 —— 网关掉连 51 分 MAJOR/P1 */ @Test public void req47_iotSensorThresholdAlarm() { DeviceAlarm a = new DeviceAlarm(); a.setDeviceId("M-REQ47"); a.setDeviceName("园区闸机网关"); a.setDeviceType("GATEWAY"); a.setCriticality("HIGH"); a.setTemperature(40.0); a.setCpuUsage(50); a.setMemoryUsage(50); a.setErrorCount(10); a.setConnectionLost(true); a.setLastHeartbeatSeconds(600); a.setBatteryLevel(15); a.setVibrationLevel(0.5);
AlarmDecision d = deviceAlarmService.evaluate(a);
Assert.assertEquals(51, d.getAlarmScore()); Assert.assertEquals("MAJOR", d.getAlarmLevel()); Assert.assertEquals("NETWORK", d.getAlarmType()); Assert.assertEquals("P1", d.getDispatchPriority()); }
/** * 跨场景组合:场景 15 交易反欺诈 + 场景 1 信贷准入双引擎串联联合拦截。 * 同一客户深夜境外大额交易被反欺诈拦截、信贷画像命中高风险 → 联合决策拒绝放款 */ @Test public void req50_crossScenarioAdmissionAndFraudJointBlock() { BankTransaction txn = txn("M-REQ50-T", 30000.0, 2, "ONLINE", true, true, false, 6, 60000.0, true, true, false, 1, 400, 8000.0, false); FraudDecision fraud = transactionFraudService.evaluate(txn);
CreditApplication app = new CreditApplication(); app.setApplicationId("M-REQ50-C"); app.setApplicantName("李强"); app.setAge(31); app.setCompanyType("SELF_EMPLOYED"); app.setEmployedMonths(8); app.setSocialSecurityMonths(0); app.setMonthlyIncome(12000.0); app.setMonthlyDebt(9000.0); app.setOverdueCount(6); app.setMaxOverdueDays(120); app.setCreditQueryCount3M(12); app.setLoanAmount(200000.0); app.setLoanTermMonths(24); app.setCollateralType("NONE"); app.setCollateralValue(0.0); app.setHasJudicialRecord(false); app.setCityTier("TIER_3"); AdmissionDecision admission = riskAdmissionService.evaluate(app);
boolean fraudBlocked = "BLOCK".equals(fraud.getAction()); boolean creditRejected = "REJECT".equals(admission.getAdmit()); Assert.assertTrue(fraudBlocked); Assert.assertTrue(creditRejected); Assert.assertEquals(100, fraud.getRiskScore()); Assert.assertEquals("D", admission.getRiskLevel()); Assert.assertTrue(fraudBlocked || creditRejected); Assert.assertEquals("JOINT_REJECT", fraudBlocked && creditRejected ? "JOINT_REJECT" : "SINGLE_REJECT"); }
private BankTransaction txn(String txnNo, double amount, int hour, String channel, boolean overseas, boolean crossCity, boolean travelMode, int count1h, double amount1h, boolean newMerchant, boolean deviceChanged, boolean cardPresent, int failCount24h, int accountAgeDays, double historicalMax, boolean trustedMerchant) { BankTransaction t = new BankTransaction(); t.setTxnNo(txnNo); t.setCardNo("6222 **** **** 0001"); t.setAmount(amount); t.setTxnHour(hour); t.setChannel(channel); t.setOverseas(overseas); t.setCrossCity(crossCity); t.setTravelMode(travelMode); t.setTxnCount1h(count1h); t.setTxnAmount1h(amount1h); t.setNewMerchant(newMerchant); t.setDeviceChanged(deviceChanged); t.setCardPresent(cardPresent); t.setFailCount24h(failCount24h); t.setAccountAgeDays(accountAgeDays); t.setHistoricalMaxAmount(historicalMax); t.setTrustedMerchant(trustedMerchant); return t; }
private SubsidyApplication subsidyApp(String applyNo) { SubsidyApplication app = new SubsidyApplication(); app.setApplyNo(applyNo); app.setApplicantName("申请人"); app.setSubsidyType("SETTLEMENT"); app.setCreditViolation(false); app.setCriminalRecord(false); app.setReceivedSimilarSubsidy(false); app.setDocumentsComplete(true); return app; }
private CouponRequest couponReq(String userId, String level, double orderAmount, int orderCount, String category, int joinDays, boolean newUser) { CouponRequest r = new CouponRequest(); r.setUserId(userId); r.setMemberLevel(level); r.setOrderAmount(orderAmount); r.setOrderCount(orderCount); r.setCategory(category); r.setJoinDays(joinDays); r.setLastOrderDays(0); r.setAvgOrderAmount(0.0); r.setNewUser(newUser); r.setHasUsedCoupon(false); r.setCampaignActive(false); r.setStockLeft(1000); r.setChannel("APP"); r.setRiskFlag(false); return r; }
private InspectionBatch inspectionBatch(String no, int sampleSize, int critical, int major, int minor, double measured, double target, double tolerance, int surface) { InspectionBatch b = new InspectionBatch(); b.setBatchNo(no); b.setProductCode("P-001"); b.setProductName("精密轴套"); b.setWorkshop("一号车间"); b.setSampleSize(sampleSize); b.setCriticalDefects(critical); b.setMajorDefects(major); b.setMinorDefects(minor); b.setMeasuredSize(measured); b.setTargetSize(target); b.setTolerance(tolerance); b.setSurfaceScore(surface); b.setEquipmentStatus("NORMAL"); b.setFirstArticle(false); return b; }
private Resume resume(String name, String education, String requiredEducation, String schoolTier, int years, int requiredYears, int matched, int required) { Resume r = new Resume(); r.setCandidateName(name); r.setPositionName("Java 开发工程师"); r.setEducationLevel(education); r.setRequiredEducation(requiredEducation); r.setSchoolTier(schoolTier); r.setYearsExperience(years); r.setRequiredYears(requiredYears); r.setSkillMatchCount(matched); r.setRequiredSkillCount(required); r.setChannel("ONLINE"); return r; }}依赖说明:该测试在 setUp 中一次性加载 14 套生产样板的 XML 规则(risk-admission、post-loan-warning、claim-audit、dynamic-pricing、ticket-sla、performance、tax-compliance、device-alarm、coupon、subsidy-eligibility、quality-inspection、medical-reimbursement、resume-screening、transaction-fraud)并装配对应 Service,复用案例 1-15 的全部模型与规则资产,无需新增任何文件。运行命令:
mvn test -Dtest=CoveredScenariosMatrixTest在工程根目录执行:
mvn test -Dtest=CoveredScenariosMatrixTest该测试类就是 26 个用例的回归入口:25 个 reqXX_* 方法各自以诉求编号命名、对应矩阵中一条「直接覆盖」诉求,1 个 req50_crossScenarioAdmissionAndFraudJointBlock 方法验证双引擎串联。每个用例都断言最终业务结论——例如诉求 1 断言 PASS/A 级/30 万足额授信,诉求 4 断言反欺诈 100 分 BLOCK,诉求 39 断言职工三甲甲类报销 3780 元,联合拦截用例同时断言反欺诈 BLOCK(100 分)与准入 REJECT(D 级)并合成 JOINT_REJECT。引擎公共链路或任一复用规则被误改,对应诉求入口立即失败。
它与 USE-CASES.md 中全部 40 个场景入口合计 260 个用例共同构成完整回归集;本类 26 个用例是其中唯一以「诉求编号」组织的跨域入口,可直接作为引擎升级与重构的 CI 门禁命令使用。
- 同一规则资产被多条诉求复用:案例 9 设备告警同时承接诉求 22/35/47,案例 5 工单 SLA 承接诉求 20/34,案例 11 补贴审核承接诉求 13/15/17,案例 12 质检承接诉求 33/38,案例 14 简历筛选承接诉求 24/45,案例 15 反欺诈承接诉求 4/11/12,案例 1 信贷准入承接诉求 1/5/12——25 条诉求只依赖 14 套样板,复用率即资产价值。
- 双引擎串联证明规则可跨业务域组合:联合拦截用例在一个测试流程内先后调用
TransactionFraudService与RiskAdmissionService,把支付侧BLOCK与授信侧REJECT合成为JOINT_REJECT,验证了规则引擎作为嵌入式能力在真实联防链路中的可编排性。 - 一个 JVM 内并存多套规则集:
setUp同时加载 14 份 XML、创建 14 个代理并注入 14 个 Service,命名空间按接口全限定名隔离,代理缓存与并发调用无串味,为单体系统内嵌多业务域规则提供了直接证据。 - 回归入口轻量、零新增规则:整个案例只有一个 856 行的测试类,没有新增任何模型、Rules、XML 或 Service,诉求与样板的对应关系由测试命名和 Javadoc 固化,维护成本几乎为零。
- 代理实例统一装配与复用:所有代理在
@Before中通过JQuickJava.create().createApi(...)创建一次、全部用例共享,遵循「复用代理、避免重复加载规则」的性能约定,26 个用例高频交叉调用仍保持轻量。 - Java 8 信创友好、无外部依赖:回归集仅依赖 JUnit 与 JQuick-Java 自身,基于动态代理与 ASM 执行脚本,可在 Oracle JDK 1.8 及国产化 JDK 环境直接纳入流水线,不引入任何规则平台级中间件。