Skip to content

Case 8: Tax compliance risk scoring

This case corresponds to Scenario 8: Tax compliance risk scoring in the JQuick-Java repository’s USE-CASES.md. It belongs to the finance & tax compliance domain and solves the problem of batch tax compliance assessment of enterprises per filing period: “six-dimension independent risk scoring → risk grading → audit handling routing → hit indicator list”.

Tax authorities or finance & tax SaaS platforms need to assess enterprises per tax filing period. The enterprise tax profile includes: late filing count, missing invoice count, actual tax burden rate and industry average tax burden rate, current-period revenue, input tax ratio, whether abnormal invoices exist, years since registration, and tax credit rating (A/B/M/C/D).

The assessment scores six dimensions independently: each late filing scores 5 points, capped at 25; each missing invoice scores 8 points, capped at 24; tax burden rate below half the industry average scores 20, below 80% scores 12; input tax ratio above 0.95 scores 15, above 0.85 scores 8; an abnormal invoice scores a one-vote 20; registration less than 1 year scores 10, less than 3 years scores 5. The six dimensions are summed and mapped to HIGH/MEDIUM/LOW via the 70/40 lines.

Handling routes split into three paths: enterprises with abnormal invoices or a total score reaching 70 go directly into tax audit (TAX_AUDIT) with case filing; those above 40 are assigned a field visit (FIELD_VISIT); the rest undergo regular self-check (SELF_CHECK). Abnormal invoices are a “one-vote route” independent of total score—even a total of 20 points at LOW level is still routed to audit.

The decision result also outputs 7 itemized indicator entries (six dimension scores plus total risk score) and concern notes (such as “tax burden significantly below industry average, watch revenue completeness”, “input tax ratio too high, watch inflated input risk”, “newly registered enterprise, under key monitoring”), supporting risk explanations, interviews, and post-audit traceability. Scenario 8 covers 6 enterprise profiles: multi-dimension all-hit high-risk enterprise (100 points), well-compliant enterprise (0 points self-check), medium-risk 48 points field visit, abnormal invoice alone triggering audit, abnormal input tax at 32 points, newly registered enterprise at 72 points.

  • The six dimension deduction policies (5/8 per occurrence, caps 25/24, 0.5/0.8× industry average, 0.95/0.85 input ratios) are scattered across risk-control scripts and report logic; every threshold adjustment at a branch or industry level requires code changes and redeployment.
  • Comparing tax burden rate with industry average written as division has both floating-point precision issues and division-by-zero risks; hard-coded implementations differ everywhere.
  • The six dimensions can sum over 100, so grading thresholds (70/40) and handling routes need a unified clamp; otherwise different entry points produce inconsistent levels.
  • “One-vote audit for abnormal invoices” and “grade by total score” are two decision paths; written as plain if-else they easily degrade into “audit only at high score”, letting low-score high-risk enterprises slip through.
  • When enterprises dispute results, they need per-dimension score details as explanatory material; a black-box total cannot satisfy tax audit traceability requirements.
  • Tax system changes face strict approval and few release windows, and many supporting systems run on JDK 8 and IT-innovation environments, requiring a lightweight, hot-updatable rule solution without heavy middleware.

This case uses the standard “six-piece set”: the input model carries the enterprise filing and operating snapshot, the decision result model carries score, level, route, and indicator list, the rule interface declares 10 rule methods, the XML script externalizes all deduction policies and thresholds, the Service handles six-dimension aggregation, 0-100 clamp, and list assembly, and JUnit regresses with 6 enterprise profiles.

Layer Responsibility Files
Domain model Enterprise tax profile, tax risk decision result TaxProfile.java, TaxRiskDecision.java
Rule set & XML 10 methods covering six-dimension scoring + risk level + audit/warning/investigation routes TaxComplianceRules.java, tax-compliance.xml
Orchestration service Six-dimension aggregation clamped to 0-100, 7-indicator list and concern note assembly TaxComplianceService.java
Tests 6 enterprise profiles (high risk / compliant / medium risk / abnormal invoice / abnormal input / newly registered) TaxComplianceServiceTest.java
Rule method Policy Output
scoreLate Each late filing 5 points, capped at 25 0-25
scoreMissingInvoice Each missing invoice 8 points, capped at 24 0-24
scoreBurden Below industry avg ×0.5 → 20, below ×0.8 → 12 (cross-multiplication comparison, no division) 0/12/20
scoreInputRatio Input ratio >0.95 → 15, >0.85 → 8 0/8/15
scoreAbnormal Abnormal invoice one-vote 20 0/20
scoreAge Registered <1 year → 10, <3 years → 5 0/5/10
riskLevel ≥70 HIGH / ≥40 MEDIUM / otherwise LOW HIGH/MEDIUM/LOW
auditLevel Abnormal invoice or ≥70 → TAX_AUDIT; ≥40 → FIELD_VISIT; otherwise SELF_CHECK TAX_AUDIT/FIELD_VISIT/SELF_CHECK
needWarning Total ≥40 → push risk warning true / false
needInvestigation Total ≥70 or abnormal invoice → file investigation true / false

Place the six files into a Maven project following the repository’s original package structure: the two models go into src/test/java/com/github/paohaijiao/demo/model/, the rule interface and test go into the com.github.paohaijiao.demo package, the orchestration service goes into com.github.paohaijiao.demo.service, and the XML script goes into src/test/resources/demo/tax-compliance.xml; keep all package names exactly as in the source. Run mvn test -Dtest=TaxComplianceServiceTest at the project root to execute.

package com.github.paohaijiao.demo.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 企业税务画像(税务风险打分所需的申报与经营快照)
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public 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;
}
package com.github.paohaijiao.demo.model;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* 税务合规风险决策结果
*/
@Data
public 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);
}
}
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);
}
<?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>
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("风险较低,纳入常规自查");
}
}
}
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("新设企业,处于重点监控期"));
}
}

Run at the project root:

Terminal window
mvn test -Dtest=TaxComplianceServiceTest
Test method Business profile constructed Key assertions / expected decision
shouldFlagHighRiskEnterprise Trading company: 5 late filings, 3 missing invoices, burden 0.01/avg 0.03, input 0.97, abnormal invoice, registered 1 year Total 100 (capped), HIGH, TAX_AUDIT, warning true, investigation true, 7 indicators
shouldPassCompliantEnterprise Tech company: zero late/missing, burden 0.05/avg 0.04, input 0.70, credit A Total 0, LOW, SELF_CHECK, no warning, no investigation, includes self-check note
shouldAssessMediumRiskEnterprise Manufacturing company: 3 late, 1 missing, burden 0.03/0.04, input 0.90, registered 2 years Total 48 (15+8+12+8+5), MEDIUM, FIELD_VISIT, warning true, no investigation
shouldInvestigateDueToAbnormalInvoice Service company: only abnormal invoice, everything else normal Total 20, LOW, but auditLevel TAX_AUDIT, no warning yet investigation true, includes abnormal-invoice audit note
shouldWatchInputTaxRatio Supply chain company: 1 late, burden 0.02/0.04, input 0.96, registered 4 years Total 32 (5+12+15), LOW, SELF_CHECK, no warning, no investigation, includes high-input-ratio note
shouldMonitorNewlyRegisteredEnterprise New e-commerce: 2 late, 4 missing, burden 0.02/0.05, input 0.88, registered 0 years Total 72 (10+24+20+8+10), HIGH, TAX_AUDIT, warning true, investigation true, includes key-monitoring note

Two verified outputs recorded in USE-CASES.md Scenario 8:

High-risk enterprise (all dimensions hit):

风险分: 100 (HIGH) 稽查方式: TAX_AUDIT 立案调查: true
命中指标: 7 条(逾期申报、缺票、税负率显著偏低、进项异常、异常发票、新设企业……)

Abnormal invoice but low total score (20, LOW):

风险分: 20 (LOW) 稽查方式: TAX_AUDIT(异常发票一票路由,不受等级限制)
  • Each of the six dimensions is an independent rule method; per-occurrence values, caps (25/24/20/15/10), and grading thresholds are all externalized in XML, so tax policy changes only require script edits and hot updates, suiting systems with strict approval and few release windows.
  • Tax burden comparison uses cross-multiplication (the script compares the actual burden rate directly with “industry avg × 0.5”), avoiding floating-point division precision and division-by-zero risks while keeping the semantics of “below half the industry average”.
  • The Service clamps the six-dimension total to 0-100 on the Java side and assembles a fixed 7-entry per-dimension score list, making each enterprise’s assessment naturally explainable and auditable.
  • The abnormal-invoice “one-vote route” is checked first in both auditLevel and needInvestigation script methods, so even a 20-point LOW enterprise is routed directly to audit and filing, regardless of its grade.
  • The rule interface binds parameters by name via @Param; the proxy instance is created once and reused across batch assessment tasks, backed by an ASM call chain with no extra process overhead for high-concurrency batch scoring.
  • Runs on Java 8 with no heavy rule platform dependency, and can be embedded directly into Golden Tax supporting systems and IT-innovation JDK environments.