案例 8:税务合规风险打分
本案例对应 JQuick-Java 仓库 USE-CASES.md 的场景 8:税务合规风险打分,属于财税合规领域,解决按申报所属期对企业做「六维风险指标独立打分 → 风险分级 → 稽查处置路由 → 命中指标清单」的纳税合规评估问题。
税务机关或财税 SaaS 平台需要按税款所属期对企业进行批量纳税合规评估。企业税务画像包含:逾期申报次数、缺失发票次数、实际税负率与行业平均税负率、本期营业收入、进项占比、是否存在异常发票、注册年限以及纳税信用等级(A/B/M/C/D)等字段。
评估从六个维度独立打分:逾期申报每次 5 分、单项封顶 25 分;缺失发票每次 8 分、单项封顶 24 分;税负率低于行业均值一半得 20 分、低于八成得 12 分;进项占比超过 0.95 得 15 分、超过 0.85 得 8 分;存在异常发票一票记 20 分;成立不足 1 年得 10 分、不足 3 年得 5 分。六维汇总后按 70/40 两条线映射 HIGH/MEDIUM/LOW。
处置路由分为三路:存在异常发票或总分达到 70 分的,直接进入税务稽查(TAX_AUDIT)并立案调查;40 分以上的安排实地走访(FIELD_VISIT);其余企业常规自查(SELF_CHECK)。异常发票是独立于总分的「一票路由」——即使总分只有 20 分、等级 LOW,也照样移交稽查。
决策结果同时输出 7 条逐项指标清单(六个维度得分加风险总分)与关注备注(如「税负率显著低于行业均值,关注收入完整性」「进项占比过高,关注虚增进项风险」「新设企业,处于重点监控期」),支撑风险说明、约谈与事后审计回溯。场景 8 覆盖 6 组企业形态:多维全中的高风险企业(100 分)、合规良好企业(0 分自查)、中风险 48 分实地走访、异常发票单独触发稽查、进项异常 32 分、新设企业 72 分。
- 六个维度的扣分口径(每次 5 分/8 分、封顶 25/24、0.5/0.8 倍行业均值、0.95/0.85 进项占比)散落在风控脚本与报表逻辑中,各分局或行业调整阈值都要改代码发版。
- 税负率与行业均值比较若写成除法形式,既存在浮点精度问题又要防行业均值为零;硬编码时各处写法不一。
- 六个维度分数叠加可能超过 100,分级阈值(70/40)与处置路由需要统一收口,否则不同入口算出的等级不一致。
- 「异常发票一票稽查」与「按总分分级」是两条决策路径,写成普通 if-else 时容易退化为「必须高分才稽查」,放过低分高危企业。
- 企业对评定结果有异议时,需要逐维度的得分明细作为解释材料,黑盒总分无法满足税务审计的可回溯要求。
- 税务系统变更审批严格、发布窗口少,且大量配套系统运行在 JDK 8 与信创环境,需要轻量、可热更新、无重型中间件的规则方案。
本案例采用标准「六件套」:入参模型承载企业申报与经营快照,决策结果模型承载分数、等级、路由与指标清单,规则接口声明 10 个规则方法,XML 脚本外置全部扣分口径与阈值,Service 负责六维汇总、0-100 封顶与清单组装,JUnit 以 6 组企业形态回归。
| 层次 | 职责 | 对应文件 |
|---|---|---|
| 领域模型 | 企业税务画像、税务风险决策结果 | TaxProfile.java、TaxRiskDecision.java |
| 规则集与 XML | 六维评分 + 风险等级 + 稽查/预警/立案路由共 10 个方法 | TaxComplianceRules.java、tax-compliance.xml |
| 编排服务 | 六维汇总并 clamp 到 0-100、7 条指标清单与关注备注组装 | TaxComplianceService.java |
| 测试 | 6 组企业形态(高风险/合规/中风险/异常发票/进项异常/新设) | TaxComplianceServiceTest.java |
规则方法拆分
Section titled “规则方法拆分”| 规则方法 | 口径 | 分值/输出 |
|---|---|---|
scoreLate |
逾期申报每次 5 分,单项封顶 25 | 0-25 |
scoreMissingInvoice |
缺失发票每次 8 分,单项封顶 24 | 0-24 |
scoreBurden |
税负率低于行业均值×0.5 得 20、低于×0.8 得 12(交叉相乘比较,不用除法) | 0/12/20 |
scoreInputRatio |
进项占比超过 0.95 得 15、超过 0.85 得 8 | 0/8/15 |
scoreAbnormal |
存在异常发票一票记 20 分 | 0/20 |
scoreAge |
成立不足 1 年得 10、不足 3 年得 5 | 0/5/10 |
riskLevel |
≥70 HIGH / ≥40 MEDIUM / 否则 LOW | HIGH/MEDIUM/LOW |
auditLevel |
异常发票或 ≥70 → TAX_AUDIT;≥40 → FIELD_VISIT;否则 SELF_CHECK | TAX_AUDIT/FIELD_VISIT/SELF_CHECK |
needWarning |
总分 ≥40 推送风险提醒 | true / false |
needInvestigation |
总分 ≥70 或存在异常发票 → 立案调查 | true / false |
代码实现(完整可运行)
Section titled “代码实现(完整可运行)”将六个文件按仓库原有包结构放入 Maven 工程:两个模型放入 src/test/java/com/github/paohaijiao/demo/model/,规则接口与测试放入 com.github.paohaijiao.demo 包,编排服务放入 com.github.paohaijiao.demo.service 包,XML 脚本放入 src/test/resources/demo/tax-compliance.xml,包名全部保持源码原样。工程根目录执行 mvn test -Dtest=TaxComplianceServiceTest 即可运行。
TaxProfile.java
Section titled “TaxProfile.java”package com.github.paohaijiao.demo.model;
import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;
/** * 企业税务画像(税务风险打分所需的申报与经营快照) */@Data@NoArgsConstructor@AllArgsConstructorpublic class TaxProfile {
/** * 企业税号 */ private String companyId;
/** * 企业名称 */ private String companyName;
/** * 纳税人类型:GENERAL-一般纳税人 / SMALL_MICRO-小规模 */ private String taxpayerType;
/** * 申报所属期 */ private String taxPeriod;
/** * 逾期申报次数 */ private int lateCount;
/** * 缺失发票次数 */ private int missingInvoice;
/** * 实际税负率 */ private double taxBurdenRate;
/** * 行业平均税负率 */ private double industryAvgBurden;
/** * 本期营业收入(元) */ private double revenue;
/** * 进项占比 */ private double inputTaxRatio;
/** * 是否存在异常发票 */ private boolean hasAbnormalInvoice;
/** * 注册年限 */ private int yearsSinceRegister;
/** * 纳税信用等级:A / B / M / C / D */ private String creditRating;}TaxRiskDecision.java
Section titled “TaxRiskDecision.java”package com.github.paohaijiao.demo.model;
import lombok.Data;
import java.util.ArrayList;import java.util.List;
/** * 税务合规风险决策结果 */@Datapublic class TaxRiskDecision {
/** * 企业税号 */ private String companyId;
/** * 风险总分(0-100) */ private int riskScore;
/** * 风险等级:HIGH / MEDIUM / LOW */ private String riskLevel;
/** * 处置方式:SELF_CHECK-自查 / FIELD_VISIT-实地核查 / TAX_AUDIT-税务稽查 */ private String auditLevel;
/** * 是否推送风险提醒 */ private boolean needWarning;
/** * 是否移交稽查 */ private boolean needInvestigation;
/** * 风险指标明细 */ private List<String> indicators = new ArrayList<String>();
/** * 处置建议 */ private List<String> notes = new ArrayList<String>();
public void addIndicator(String indicator) { this.indicators.add(indicator); }
public void addNote(String note) { this.notes.add(note); }}TaxComplianceRules.java
Section titled “TaxComplianceRules.java”package com.github.paohaijiao.demo;
import com.github.paohaijiao.xml.param.Param;
/** * 场景 8(生产级样板):税务合规风险打分规则集 * <p> * 规则按「多维风险指标 → 分级 → 处置路由」拆分,六个维度独立计分: * 逾期申报、缺失发票、税负率异常、进项异常、异常发票、成立年限。 * 规则脚本见 {@code src/test/resources/demo/tax-compliance.xml}。 */public interface TaxComplianceRules {
/** * 逾期申报扣分(每次 5 分,封顶 25) */ int scoreLate(@Param("lateCount") int lateCount);
/** * 缺失发票扣分(每次 8 分,封顶 24) */ int scoreMissingInvoice(@Param("missingInvoice") int missingInvoice);
/** * 税负率异常扣分:明显低于行业均值触发 */ int scoreBurden(@Param("taxBurdenRate") double taxBurdenRate, @Param("industryAvgBurden") double industryAvgBurden);
/** * 进项占比异常扣分 */ int scoreInputRatio(@Param("inputTaxRatio") double inputTaxRatio);
/** * 异常发票扣分 */ int scoreAbnormal(@Param("hasAbnormalInvoice") boolean hasAbnormalInvoice);
/** * 新设企业扣分(成立时间越短风险越高) */ int scoreAge(@Param("yearsSinceRegister") int yearsSinceRegister);
/** * 风险等级:HIGH / MEDIUM / LOW */ String riskLevel(@Param("riskScore") int riskScore);
/** * 处置方式:SELF_CHECK / FIELD_VISIT / TAX_AUDIT */ String auditLevel(@Param("riskScore") int riskScore, @Param("hasAbnormalInvoice") boolean hasAbnormalInvoice);
/** * 是否推送风险提醒 */ boolean needWarning(@Param("riskScore") int riskScore);
/** * 是否移交稽查 */ boolean needInvestigation(@Param("riskScore") int riskScore, @Param("hasAbnormalInvoice") boolean hasAbnormalInvoice);}tax-compliance.xml
Section titled “tax-compliance.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.TaxComplianceRules">
<!-- 逾期申报扣分 --> <java name="scoreLate" returnClass="int"> <![CDATA[ int def scoreLate(int:lateCount) { int score = lateCount * 5; if (score > 25) { score = 25; } return score; } ]]> </java>
<!-- 缺失发票扣分 --> <java name="scoreMissingInvoice" returnClass="int"> <![CDATA[ int def scoreMissingInvoice(int:missingInvoice) { int score = missingInvoice * 8; if (score > 24) { score = 24; } return score; } ]]> </java>
<!-- 税负率异常扣分 --> <java name="scoreBurden" returnClass="int"> <![CDATA[ int def scoreBurden(double:taxBurdenRate, double:industryAvgBurden) { if (taxBurdenRate < industryAvgBurden * 0.5) { return 20; } else if (taxBurdenRate < industryAvgBurden * 0.8) { return 12; } else { return 0; } } ]]> </java>
<!-- 进项占比异常扣分 --> <java name="scoreInputRatio" returnClass="int"> <![CDATA[ int def scoreInputRatio(double:inputTaxRatio) { if (inputTaxRatio > 0.95) { return 15; } else if (inputTaxRatio > 0.85) { return 8; } else { return 0; } } ]]> </java>
<!-- 异常发票扣分 --> <java name="scoreAbnormal" returnClass="int"> <![CDATA[ int def scoreAbnormal(boolean:hasAbnormalInvoice) { if (hasAbnormalInvoice) { return 20; } else { return 0; } } ]]> </java>
<!-- 新设企业扣分 --> <java name="scoreAge" returnClass="int"> <![CDATA[ int def scoreAge(int:yearsSinceRegister) { if (yearsSinceRegister < 1) { return 10; } else if (yearsSinceRegister < 3) { return 5; } else { return 0; } } ]]> </java>
<!-- 风险等级 --> <java name="riskLevel" returnClass="java.lang.String"> <![CDATA[ str def riskLevel(int:riskScore) { if (riskScore >= 70) { return "HIGH"; } else if (riskScore >= 40) { return "MEDIUM"; } else { return "LOW"; } } ]]> </java>
<!-- 处置方式 --> <java name="auditLevel" returnClass="java.lang.String"> <![CDATA[ str def auditLevel(int:riskScore, boolean:hasAbnormalInvoice) { if (hasAbnormalInvoice) { return "TAX_AUDIT"; } else if (riskScore >= 70) { return "TAX_AUDIT"; } else if (riskScore >= 40) { return "FIELD_VISIT"; } else { return "SELF_CHECK"; } } ]]> </java>
<!-- 是否推送提醒 --> <java name="needWarning" returnClass="boolean"> <![CDATA[ boolean def needWarning(int:riskScore) { if (riskScore >= 40) { return true; } else { return false; } } ]]> </java>
<!-- 是否移交稽查 --> <java name="needInvestigation" returnClass="boolean"> <![CDATA[ boolean def needInvestigation(int:riskScore, boolean:hasAbnormalInvoice) { if (hasAbnormalInvoice) { return true; } else if (riskScore >= 70) { return true; } else { return false; } } ]]> </java>
</javas>TaxComplianceService.java
Section titled “TaxComplianceService.java”package com.github.paohaijiao.demo.service;
import com.github.paohaijiao.demo.TaxComplianceRules;import com.github.paohaijiao.demo.model.TaxProfile;import com.github.paohaijiao.demo.model.TaxRiskDecision;
/** * 场景 8:税务合规风险打分服务 * <p> * 职责划分: * <ul> * <li><b>Java 侧</b>:六维指标汇总、封顶、处置建议组装</li> * <li><b>规则侧</b>:各维度扣分口径、风险分级阈值、处置路由,全部外置在 XML 中</li> * </ul> */public class TaxComplianceService {
private final TaxComplianceRules rules;
public TaxComplianceService(TaxComplianceRules rules) { this.rules = rules; }
/** * 评估税务合规风险 * * @param profile 企业税务画像 * @return 风险决策 */ public TaxRiskDecision evaluate(TaxProfile profile) { TaxRiskDecision decision = new TaxRiskDecision(); decision.setCompanyId(profile.getCompanyId());
int lateScore = rules.scoreLate(profile.getLateCount()); int missingScore = rules.scoreMissingInvoice(profile.getMissingInvoice()); int burdenScore = rules.scoreBurden(profile.getTaxBurdenRate(), profile.getIndustryAvgBurden()); int inputScore = rules.scoreInputRatio(profile.getInputTaxRatio()); int abnormalScore = rules.scoreAbnormal(profile.isHasAbnormalInvoice()); int ageScore = rules.scoreAge(profile.getYearsSinceRegister());
int total = lateScore + missingScore + burdenScore + inputScore + abnormalScore + ageScore; if (total > 100) { total = 100; } else if (total < 0) { total = 0; }
decision.setRiskScore(total); decision.setRiskLevel(rules.riskLevel(total)); decision.setAuditLevel(rules.auditLevel(total, profile.isHasAbnormalInvoice())); decision.setNeedWarning(rules.needWarning(total)); decision.setNeedInvestigation(rules.needInvestigation(total, profile.isHasAbnormalInvoice()));
decision.addIndicator("逾期申报扣分: " + lateScore); decision.addIndicator("缺失发票扣分: " + missingScore); decision.addIndicator("税负率异常扣分: " + burdenScore); decision.addIndicator("进项异常扣分: " + inputScore); decision.addIndicator("异常发票扣分: " + abnormalScore); decision.addIndicator("成立年限扣分: " + ageScore); decision.addIndicator("风险总分: " + total);
buildNotes(profile, decision); return decision; }
private void buildNotes(TaxProfile profile, TaxRiskDecision decision) { if (profile.isHasAbnormalInvoice()) { decision.addNote("存在异常发票,直接移交税务稽查"); } if (profile.getLateCount() >= 3) { decision.addNote("逾期申报 " + profile.getLateCount() + " 次,需督促按期申报"); } if (profile.getTaxBurdenRate() < profile.getIndustryAvgBurden() * 0.5) { decision.addNote("税负率显著低于行业均值,关注收入完整性"); } if (profile.getInputTaxRatio() > 0.95) { decision.addNote("进项占比过高,关注虚增进项风险"); } if (profile.getYearsSinceRegister() < 1) { decision.addNote("新设企业,处于重点监控期"); } if ("D".equals(profile.getCreditRating())) { decision.addNote("纳税信用等级 D,按最高风险级别管理"); } if (decision.getRiskScore() < 40) { decision.addNote("风险较低,纳入常规自查"); } }}TaxComplianceServiceTest.java
Section titled “TaxComplianceServiceTest.java”package com.github.paohaijiao.demo;
import com.github.paohaijiao.demo.model.TaxProfile;import com.github.paohaijiao.demo.model.TaxRiskDecision;import com.github.paohaijiao.demo.service.TaxComplianceService;import com.github.paohaijiao.xml.JQuickJava;import org.junit.Assert;import org.junit.Before;import org.junit.Test;
/** * 场景 8 生产级样板:税务合规风险打分 * <p> * 覆盖 6 组企业形态:高风险异常企业、合规良好企业、中风险临界、 * 异常发票触发稽查、进项异常、新设企业。 */public class TaxComplianceServiceTest {
private TaxComplianceService service;
@Before public void setUp() { TaxComplianceRules rules = JQuickJava.create() .createApi(TaxComplianceRules.class, "demo/tax-compliance.xml"); this.service = new TaxComplianceService(rules); }
/** * 高风险异常企业:逾期 5 次、缺票 3 次、税负率极低、进项异常、异常发票 → 100 分、稽查 */ @Test public void shouldFlagHighRiskEnterprise() { TaxProfile profile = new TaxProfile(); profile.setCompanyId("91310000MA1K0001X"); profile.setCompanyName("某贸易有限公司"); profile.setTaxpayerType("GENERAL"); profile.setTaxPeriod("2026Q2"); profile.setLateCount(5); profile.setMissingInvoice(3); profile.setTaxBurdenRate(0.01); profile.setIndustryAvgBurden(0.03); profile.setRevenue(8000000.0); profile.setInputTaxRatio(0.97); profile.setHasAbnormalInvoice(true); profile.setYearsSinceRegister(1); profile.setCreditRating("C");
TaxRiskDecision decision = service.evaluate(profile);
Assert.assertEquals(100, decision.getRiskScore()); Assert.assertEquals("HIGH", decision.getRiskLevel()); Assert.assertEquals("TAX_AUDIT", decision.getAuditLevel()); Assert.assertTrue(decision.isNeedWarning()); Assert.assertTrue(decision.isNeedInvestigation()); Assert.assertEquals(7, decision.getIndicators().size()); }
/** * 合规良好企业:各项指标正常 → 0 分、自查 */ @Test public void shouldPassCompliantEnterprise() { TaxProfile profile = new TaxProfile(); profile.setCompanyId("91310000MA1K0002X"); profile.setCompanyName("某科技股份有限公司"); profile.setTaxpayerType("GENERAL"); profile.setTaxPeriod("2026Q2"); profile.setLateCount(0); profile.setMissingInvoice(0); profile.setTaxBurdenRate(0.05); profile.setIndustryAvgBurden(0.04); profile.setRevenue(20000000.0); profile.setInputTaxRatio(0.70); profile.setHasAbnormalInvoice(false); profile.setYearsSinceRegister(5); profile.setCreditRating("A");
TaxRiskDecision decision = service.evaluate(profile);
Assert.assertEquals(0, decision.getRiskScore()); Assert.assertEquals("LOW", decision.getRiskLevel()); Assert.assertEquals("SELF_CHECK", decision.getAuditLevel()); Assert.assertFalse(decision.isNeedWarning()); Assert.assertFalse(decision.isNeedInvestigation()); Assert.assertTrue(decision.getNotes().contains("风险较低,纳入常规自查")); }
/** * 中风险临界:逾期 3 次、缺票 1 次、税负率偏低、进项偏高 → 48 分、实地核查 */ @Test public void shouldAssessMediumRiskEnterprise() { TaxProfile profile = new TaxProfile(); profile.setCompanyId("91310000MA1K0003X"); profile.setCompanyName("某制造有限公司"); profile.setTaxpayerType("GENERAL"); profile.setTaxPeriod("2026Q2"); profile.setLateCount(3); profile.setMissingInvoice(1); profile.setTaxBurdenRate(0.03); profile.setIndustryAvgBurden(0.04); profile.setRevenue(5000000.0); profile.setInputTaxRatio(0.90); profile.setHasAbnormalInvoice(false); profile.setYearsSinceRegister(2); profile.setCreditRating("B");
TaxRiskDecision decision = service.evaluate(profile);
Assert.assertEquals(48, decision.getRiskScore()); Assert.assertEquals("MEDIUM", decision.getRiskLevel()); Assert.assertEquals("FIELD_VISIT", decision.getAuditLevel()); Assert.assertTrue(decision.isNeedWarning()); Assert.assertFalse(decision.isNeedInvestigation()); }
/** * 异常发票触发稽查:总分仅 20(LOW)但存在异常发票 → 仍移交稽查 */ @Test public void shouldInvestigateDueToAbnormalInvoice() { TaxProfile profile = new TaxProfile(); profile.setCompanyId("91310000MA1K0004X"); profile.setCompanyName("某服务有限公司"); profile.setTaxpayerType("SMALL_MICRO"); profile.setTaxPeriod("2026Q2"); profile.setLateCount(0); profile.setMissingInvoice(0); profile.setTaxBurdenRate(0.05); profile.setIndustryAvgBurden(0.04); profile.setRevenue(1000000.0); profile.setInputTaxRatio(0.70); profile.setHasAbnormalInvoice(true); profile.setYearsSinceRegister(5); profile.setCreditRating("B");
TaxRiskDecision decision = service.evaluate(profile);
Assert.assertEquals(20, decision.getRiskScore()); Assert.assertEquals("LOW", decision.getRiskLevel()); Assert.assertEquals("TAX_AUDIT", decision.getAuditLevel()); Assert.assertFalse(decision.isNeedWarning()); Assert.assertTrue(decision.isNeedInvestigation()); Assert.assertTrue(decision.getNotes().contains("存在异常发票,直接移交税务稽查")); }
/** * 进项异常:逾期 1 次、税负率偏低、进项占比 96% → 32 分、自查 */ @Test public void shouldWatchInputTaxRatio() { TaxProfile profile = new TaxProfile(); profile.setCompanyId("91310000MA1K0005X"); profile.setCompanyName("某供应链有限公司"); profile.setTaxpayerType("GENERAL"); profile.setTaxPeriod("2026Q2"); profile.setLateCount(1); profile.setMissingInvoice(0); profile.setTaxBurdenRate(0.02); profile.setIndustryAvgBurden(0.04); profile.setRevenue(6000000.0); profile.setInputTaxRatio(0.96); profile.setHasAbnormalInvoice(false); profile.setYearsSinceRegister(4); profile.setCreditRating("B");
TaxRiskDecision decision = service.evaluate(profile);
Assert.assertEquals(32, decision.getRiskScore()); Assert.assertEquals("LOW", decision.getRiskLevel()); Assert.assertEquals("SELF_CHECK", decision.getAuditLevel()); Assert.assertFalse(decision.isNeedWarning()); Assert.assertFalse(decision.isNeedInvestigation()); Assert.assertTrue(decision.getNotes().contains("进项占比过高,关注虚增进项风险")); }
/** * 新设企业:逾期 2 次、缺票 4 次、税负率极低、进项偏高、成立不足 1 年 → 72 分、稽查 */ @Test public void shouldMonitorNewlyRegisteredEnterprise() { TaxProfile profile = new TaxProfile(); profile.setCompanyId("91310000MA1K0006X"); profile.setCompanyName("某新设电商有限公司"); profile.setTaxpayerType("SMALL_MICRO"); profile.setTaxPeriod("2026Q2"); profile.setLateCount(2); profile.setMissingInvoice(4); profile.setTaxBurdenRate(0.02); profile.setIndustryAvgBurden(0.05); profile.setRevenue(3000000.0); profile.setInputTaxRatio(0.88); profile.setHasAbnormalInvoice(false); profile.setYearsSinceRegister(0); profile.setCreditRating("M");
TaxRiskDecision decision = service.evaluate(profile);
Assert.assertEquals(72, decision.getRiskScore()); Assert.assertEquals("HIGH", decision.getRiskLevel()); Assert.assertEquals("TAX_AUDIT", decision.getAuditLevel()); Assert.assertTrue(decision.isNeedWarning()); Assert.assertTrue(decision.isNeedInvestigation()); Assert.assertTrue(decision.getNotes().contains("新设企业,处于重点监控期")); }}在工程根目录执行:
mvn test -Dtest=TaxComplianceServiceTest测试覆盖形态
Section titled “测试覆盖形态”| 测试方法 | 构造的业务形态 | 关键断言/期望决策 |
|---|---|---|
shouldFlagHighRiskEnterprise |
贸易公司:逾期 5、缺票 3、税负 0.01/均值 0.03、进项 0.97、异常发票、成立 1 年 | 总分 100(封顶)、HIGH、TAX_AUDIT、需预警、需立案、指标 7 条 |
shouldPassCompliantEnterprise |
科技公司:零逾期零缺票、税负 0.05/均值 0.04、进项 0.70、A 级信用 | 总分 0、LOW、SELF_CHECK、不预警、不立案、含常规自查备注 |
shouldAssessMediumRiskEnterprise |
制造公司:逾期 3、缺票 1、税负 0.03/0.04、进项 0.90、成立 2 年 | 总分 48(15+8+12+8+5)、MEDIUM、FIELD_VISIT、需预警、不立案 |
shouldInvestigateDueToAbnormalInvoice |
服务公司:仅异常发票,其余正常 | 总分 20、LOW,但 auditLevel 为 TAX_AUDIT、不预警却需立案、含异常发票稽查备注 |
shouldWatchInputTaxRatio |
供应链公司:逾期 1、税负 0.02/0.04、进项 0.96、成立 4 年 | 总分 32(5+12+15)、LOW、SELF_CHECK、不预警、不立案、含进项过高备注 |
shouldMonitorNewlyRegisteredEnterprise |
新设电商:逾期 2、缺票 4、税负 0.02/0.05、进项 0.88、成立 0 年 | 总分 72(10+24+20+8+10)、HIGH、TAX_AUDIT、需预警、需立案、含重点监控期备注 |
USE-CASES.md 场景 8 收录的两组实测输出:
高风险企业(多维度全部命中):
风险分: 100 (HIGH) 稽查方式: TAX_AUDIT 立案调查: true命中指标: 7 条(逾期申报、缺票、税负率显著偏低、进项异常、异常发票、新设企业……)异常发票但总分不高(分数 20,LOW):
风险分: 20 (LOW) 稽查方式: TAX_AUDIT(异常发票一票路由,不受等级限制)- 六个维度每个是一个独立规则方法,单次分值、封顶值(25/24/20/15/10)与分级阈值全部外置 XML,税务口径调整改脚本即可热更新,适合审批严格、发布窗口少的税务系统。
- 税负率比较采用交叉相乘写法(脚本中以实际税负率与「行业均值 × 0.5」直接比较大小),规避浮点除法的精度与除零风险,语义仍是「低于行业均值一半」。
- Service 在 Java 侧把六维总分统一 clamp 到 0-100,并固定组装 7 条逐维得分指标,每家企业的评定依据天然可解释、可审计。
- 异常发票「一票路由」在
auditLevel与needInvestigation两个脚本方法中均前置判断,20 分 LOW 企业也直接稽查立案,不受分级结果限制。 - 规则接口通过
@Param按名绑定参数,代理实例一次创建即可在批量评估任务中复用,底层为 ASM 调用链,高并发批量打分无额外进程开销。 - Java 8 即可运行、无重型规则平台依赖,可直接嵌入金税配套系统与信创 JDK 环境。