Case 37: Department budget overrun alert
This case corresponds to Scenario 37 in USE-CASES.md (Requirement 46: department budget compliance and overrun alerting), in the financial management domain. Around a department’s annual budget snapshot, the rule script provides five independent policies: occupancy grading, execution pace, year-end forecast, single-expense disposition, and a risk score. The Service handles percentage and overrun-amount calculation plus alert-message assembly, producing a budget alert decision that can be pushed directly to department heads and the finance director.
Industry scenario
Section titled “Industry scenario”Department budget execution status is carried by BudgetStatus: annual budget, actual spend, committed amount (signed contracts / in-flight purchase orders), current month, forecast year-end spend, plus the amount of the current single expense request, its category (CAPEX capital / OPEX operating / ADMIN administrative / MARKETING marketing), whether it is an emergency, and whether the CAPEX item has completed project initiation approval. Each month, finance uses this to decide which departments should be flagged and whether a single expense should be released.
The rule set provides three alerting threads. First, occupancy grading: the ratio of “actual spend + committed amount” to the annual budget draws the red line — 100% red, 90% orange, 70% yellow, otherwise green. Second, execution pace: spending progress is compared with time progress, and a deviation beyond ±15% is judged as over-consumption or execution lag. Third, year-end forecast: forecast spend exceeding the budget by 10% is a severe overrun (SEVERE), merely exceeding the budget is a mild overrun (MILD).
Single-expense disposition chains the budget level with expense attributes: CAPEX without initiation approval is always blocked, approved CAPEX is released; at the RED level, emergency expenses are escalated to special approval while non-emergency expenses are blocked outright; at ORANGE, a single expense exceeding 5% of the annual budget is escalated, otherwise released with a warning; YELLOW is released with a warning; GREEN is released directly. The risk score is based on the level (RED 100 / ORANGE 70 / YELLOW 40 / GREEN 10), with over-consumption and severe forecast overrun each adding 10 points, up to a maximum of 120.
The decision result BudgetAlertDecision outputs the occupancy level and percentage, execution pace, forecast level, forecast overrun amount, the single-expense action (APPROVE / APPROVE_WITH_WARNING / ESCALATE / BLOCK), the risk score, and a set of plain-language alerts such as “budget occupancy has reached 104%, entering overrun state, non-emergency spending frozen” or “budget execution severely lagging, check for stalled projects or procurement backlog”.
Business pain points
Section titled “Business pain points”- Budget control only shows up in after-the-fact reports: over-limit occupancy is often discovered only after monthly closing; there is no ability to intercept or escalate in real time at the single-expense request stage — the alarm sounds only after the money is spent.
- Committed amounts are easily missed: signed contracts and in-flight purchase orders occupy the budget even though they are unpaid; counting only actual spend makes a department look “still healthy”.
- Execution-pace imbalance goes unnoticed: spending ahead of time progress implies year-end overrun risk, while severe lag may mean stalled projects or procurement backlog — both deviations need proactive alerts.
- CAPEX and expense spending follow different control policies: capital expenditure must be initiated first; emergency spending must keep a channel open under a red budget but be escalated; the rules are scattered across various finance policies.
- Thresholds change frequently with control posture: the 70%/90%/100% flag lines, the 5% single-expense escalation line, the ±15% pace band, and the 10% severe-overrun line may all change with each year’s budget policy; hard coding is costly to maintain.
- Risk lacks a unified quantification: finance leaders need a cross-department comparable risk score to set priorities; level, pace, and forecast signals must be synthesized into one number.
Solution architecture
Section titled “Solution architecture”| Component | File | Responsibility |
|---|---|---|
| Input model | model/BudgetStatus.java |
Department budget execution snapshot and single-expense request fields |
| Decision model | model/BudgetAlertDecision.java |
Four-color level, occupancy, pace, forecast, overrun amount, disposition action, risk score, alert list |
| Rule interface | BudgetAlertRules.java |
5 rule methods: grading, pace, forecast, single-expense disposition, risk score |
| Rule script | resources/demo/budget-alert.xml |
Flag lines, deviation band, overrun lines, disposition matrix, and scores all externalized |
| Orchestration service | service/BudgetAlertService.java |
Percentage/overrun calculation, 5% determination, alert-message assembly |
| Tests | BudgetAlertServiceTest.java |
6 department budget shapes, including a two-request comparison under RED |
Rule method breakdown
Section titled “Rule method breakdown”| Rule method | Inputs (@Param) |
Policy | Returns |
|---|---|---|---|
utilizationLevel |
spent, committed, budget |
Occupancy (actual + committed) ≥100% RED / ≥90% ORANGE / ≥70% YELLOW / else GREEN |
String level |
burnStatus |
spent, budget, month |
Spending vs. time progress deviation beyond ±15% → ahead (OVER_BURN) / lagging (UNDER_BURN), otherwise NORMAL |
String pace |
forecastStatus |
forecast, budget |
Forecast ≥ budget × 110% SEVERE / > budget MILD / else OK |
String forecast level |
singleExpenseAction |
level, category, emergency, capexApproved, overFivePercent |
CAPEX without initiation BLOCK, approved APPROVE; RED emergency ESCALATE, non-emergency BLOCK; ORANGE over 5% of annual budget ESCALATE, else warn-and-release; YELLOW warn-and-release; GREEN release | String disposition action |
riskScore |
level, burn, forecast |
Base RED 100 / ORANGE 70 / YELLOW 40 / GREEN 10; OVER_BURN adds 10, SEVERE adds 10 |
int risk score |
Code implementation (complete & runnable)
Section titled “Code implementation (complete & runnable)”Place the following files into the JQuick-Java test project by package structure: the two models into src/test/java/com/github/paohaijiao/demo/model/, the rule interface and test into src/test/java/com/github/paohaijiao/demo/, the Service into demo/service/, and the XML into src/test/resources/demo/. Then run:
mvn test -Dtest=BudgetAlertServiceTestBudgetStatus.java
Section titled “BudgetStatus.java”package com.github.paohaijiao.demo.model;
import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;
/** * 部门预算执行状态与单笔支出申请 */@Data@NoArgsConstructor@AllArgsConstructorpublic class BudgetStatus {
/** * 部门编码 */ private String deptCode;
/** * 部门名称 */ private String deptName;
/** * 年度预算 */ private double annualBudget;
/** * 实际已支出 */ private double actualSpent;
/** * 已承诺金额(已签合同/在途采购单) */ private double committedAmount;
/** * 当前月份(1-12) */ private int month;
/** * 年末预测支出 */ private double forecastYearEnd;
/** * 本次单笔申请支出金额 */ private double singleExpenseAmount;
/** * 支出类别:CAPEX 资本性 / OPEX 运营 / ADMIN 行政 / MARKETING 市场 */ private String expenseCategory;
/** * 是否应急支出 */ private boolean emergency;
/** * CAPEX 是否已完成立项审批 */ private boolean capexApproved;}BudgetAlertDecision.java
Section titled “BudgetAlertDecision.java”package com.github.paohaijiao.demo.model;
import lombok.Data;
import java.util.ArrayList;import java.util.List;
/** * 预算预警与单笔支出处置决策 */@Datapublic class BudgetAlertDecision {
/** * 部门编码 */ private String deptCode;
/** * 预算占用率级别:GREEN / YELLOW / ORANGE / RED */ private String utilizationLevel;
/** * 预算占用率(百分比,含承诺金额) */ private int utilizationPercent;
/** * 执行节奏:NORMAL 正常 / OVER_BURN 超前消耗 / UNDER_BURN 执行滞后 */ private String burnStatus;
/** * 年末预测:OK / MILD 轻度超支 / SEVERE 严重超支(≥10%) */ private String forecastStatus;
/** * 预测超支金额 */ private double overrunAmount;
/** * 单笔支出处置:APPROVE 放行 / APPROVE_WITH_WARNING 警示放行 / * ESCALATE 升级审批 / BLOCK 拦截 */ private String singleExpenseAction;
/** * 风险分(0-120) */ private int riskScore;
/** * 预警信息 */ private List<String> alerts = new ArrayList<String>();
public void addAlert(String alert) { this.alerts.add(alert); }}BudgetAlertRules.java
Section titled “BudgetAlertRules.java”package com.github.paohaijiao.demo;
import com.github.paohaijiao.xml.param.Param;
/** * 场景 37:预算占用分级、执行节奏、预测超支、单笔处置与风险分 */public interface BudgetAlertRules {
/** * 预算占用率分级(实际+承诺):≥100% RED / ≥90% ORANGE / ≥70% YELLOW / 其余 GREEN */ String utilizationLevel(@Param("spent") double spent, @Param("committed") double committed, @Param("budget") double budget);
/** * 支出进度与时间进度偏差超 ±15% 为超前/滞后 */ String burnStatus(@Param("spent") double spent, @Param("budget") double budget, @Param("month") int month);
/** * 年末预测超支:超预算 10% SEVERE,超预算 MILD,其余 OK */ String forecastStatus(@Param("forecast") double forecast, @Param("budget") double budget);
/** * 单笔支出处置: * CAPEX 未立项 BLOCK;RED 级应急 ESCALATE、非应急 BLOCK; * ORANGE 超年度预算 5% ESCALATE、否则警示放行;YELLOW 警示放行;GREEN 放行 */ String singleExpenseAction(@Param("level") String level, @Param("category") String category, @Param("emergency") boolean emergency, @Param("capexApproved") boolean capexApproved, @Param("overFivePercent") boolean overFivePercent);
/** * 风险分:RED100/ORANGE70/YELLOW40/GREEN10,超前消耗与预测严重超支各加 10 */ int riskScore(@Param("level") String level, @Param("burn") String burn, @Param("forecast") String forecast);}budget-alert.xml
Section titled “budget-alert.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.BudgetAlertRules">
<java name="utilizationLevel" returnClass="java.lang.String"> <![CDATA[ str def utilizationLevel(double:spent, double:committed, double:budget) { double occupied = spent + committed; if (occupied * 100 >= budget * 100) { return "RED"; } else if (occupied * 100 >= budget * 90) { return "ORANGE"; } else if (occupied * 100 >= budget * 70) { return "YELLOW"; } else { return "GREEN"; } } ]]> </java>
<java name="burnStatus" returnClass="java.lang.String"> <![CDATA[ str def burnStatus(double:spent, double:budget, int:month) { double diff = spent * 12 - month * budget; if (diff > budget * 1.8) { return "OVER_BURN"; } else if (diff < budget * -1.8) { return "UNDER_BURN"; } else { return "NORMAL"; } } ]]> </java>
<java name="forecastStatus" returnClass="java.lang.String"> <![CDATA[ str def forecastStatus(double:forecast, double:budget) { if (forecast * 100 >= budget * 110) { return "SEVERE"; } else if (forecast > budget) { return "MILD"; } else { return "OK"; } } ]]> </java>
<java name="singleExpenseAction" returnClass="java.lang.String"> <![CDATA[ str def singleExpenseAction(str:level, str:category, boolean:emergency, boolean:capexApproved, boolean:overFivePercent) { if (category == "CAPEX") { if (capexApproved) { return "APPROVE"; } else { return "BLOCK"; } } else if (level == "RED") { if (emergency) { return "ESCALATE"; } else { return "BLOCK"; } } else if (level == "ORANGE") { if (overFivePercent) { return "ESCALATE"; } else { return "APPROVE_WITH_WARNING"; } } else if (level == "YELLOW") { return "APPROVE_WITH_WARNING"; } else { return "APPROVE"; } } ]]> </java>
<java name="riskScore" returnClass="int"> <![CDATA[ int def riskScore(str:level, str:burn, str:forecast) { int score = 10; if (level == "RED") { score = 100; } else if (level == "ORANGE") { score = 70; } else if (level == "YELLOW") { score = 40; } if (burn == "OVER_BURN") { score = score + 10; } if (forecast == "SEVERE") { score = score + 10; } return score; } ]]> </java>
</javas>BudgetAlertService.java
Section titled “BudgetAlertService.java”package com.github.paohaijiao.demo.service;
import com.github.paohaijiao.demo.BudgetAlertRules;import com.github.paohaijiao.demo.model.BudgetAlertDecision;import com.github.paohaijiao.demo.model.BudgetStatus;
import java.math.BigDecimal;import java.math.RoundingMode;
/** * 场景 37:部门预算超支预警与单笔支出处置 */public class BudgetAlertService {
private final BudgetAlertRules rules;
public BudgetAlertService(BudgetAlertRules rules) { this.rules = rules; }
public BudgetAlertDecision evaluate(BudgetStatus s) { BudgetAlertDecision d = new BudgetAlertDecision(); d.setDeptCode(s.getDeptCode());
String level = rules.utilizationLevel( s.getActualSpent(), s.getCommittedAmount(), s.getAnnualBudget()); d.setUtilizationLevel(level); int percent = (int) Math.round((s.getActualSpent() + s.getCommittedAmount()) * 100.0 / s.getAnnualBudget()); d.setUtilizationPercent(percent);
String burn = rules.burnStatus(s.getActualSpent(), s.getAnnualBudget(), s.getMonth()); d.setBurnStatus(burn);
String forecast = rules.forecastStatus(s.getForecastYearEnd(), s.getAnnualBudget()); d.setForecastStatus(forecast); double overrun = s.getForecastYearEnd() > s.getAnnualBudget() ? s.getForecastYearEnd() - s.getAnnualBudget() : 0.0; d.setOverrunAmount(BigDecimal.valueOf(overrun).setScale(2, RoundingMode.HALF_UP).doubleValue());
boolean overFivePercent = s.getSingleExpenseAmount() * 100 > s.getAnnualBudget() * 5; String action = rules.singleExpenseAction(level, s.getExpenseCategory(), s.isEmergency(), s.isCapexApproved(), overFivePercent); d.setSingleExpenseAction(action); d.setRiskScore(rules.riskScore(level, burn, forecast));
attachAlerts(s, d, level, percent, burn, forecast, overrun, action); return d; }
private void attachAlerts(BudgetStatus s, BudgetAlertDecision d, String level, int percent, String burn, String forecast, double overrun, String action) { if ("RED".equals(level)) { d.addAlert("预算占用率已达 " + percent + "%,进入超支状态,冻结非应急支出"); } else if ("ORANGE".equals(level)) { d.addAlert("预算占用率 " + percent + "%,超过 90% 橙色预警线"); } else if ("YELLOW".equals(level)) { d.addAlert("预算占用率 " + percent + "%,超过 70% 关注线,请控制支出节奏"); } if ("OVER_BURN".equals(burn)) { d.addAlert("支出进度显著快于时间进度(偏差超 15%),存在年末超支风险"); } else if ("UNDER_BURN".equals(burn)) { d.addAlert("预算执行严重滞后(偏差超 15%),请排查项目停滞或采购积压"); } if ("SEVERE".equals(forecast)) { d.addAlert("年末预测严重超支,超预算金额 " + overrun + " 元(≥10%),须上报预算调整"); } else if ("MILD".equals(forecast)) { d.addAlert("年末预测轻度超支,超预算金额 " + overrun + " 元"); } if ("BLOCK".equals(action)) { if ("CAPEX".equals(s.getExpenseCategory())) { d.addAlert("单笔 " + s.getSingleExpenseAmount() + " 元资本性支出未完成立项,系统拦截"); } else { d.addAlert("预算已超支,单笔 " + s.getSingleExpenseAmount() + " 元非应急支出系统拦截"); } } else if ("ESCALATE".equals(action)) { d.addAlert("单笔 " + s.getSingleExpenseAmount() + " 元支出升级至财务总监/分管领导专项审批"); } }}BudgetAlertServiceTest.java
Section titled “BudgetAlertServiceTest.java”package com.github.paohaijiao.demo;
import com.github.paohaijiao.demo.model.BudgetAlertDecision;import com.github.paohaijiao.demo.model.BudgetStatus;import com.github.paohaijiao.demo.service.BudgetAlertService;import com.github.paohaijiao.xml.JQuickJava;import org.junit.Assert;import org.junit.Before;import org.junit.Test;
/** * 场景 37:部门预算超支预警 * <p> * 6 组形态:健康预算直接放行、黄色关注轻度超支、橙色超前消耗升级、 * 红色超支拦截与应急升级、已立项资本支出放行、预算执行严重滞后预警。 */public class BudgetAlertServiceTest {
private BudgetAlertService service;
@Before public void setUp() { BudgetAlertRules rules = JQuickJava.create() .createApi(BudgetAlertRules.class, "demo/budget-alert.xml"); this.service = new BudgetAlertService(rules); }
/** * 8 月占用 68%、节奏正常、预测不超支 → GREEN 直接放行 */ @Test public void shouldApproveWhenBudgetHealthy() { BudgetStatus s = base("D001", 1000000, 650000, 30000, 8, 950000); s.setSingleExpenseAmount(20000); s.setExpenseCategory("OPEX");
BudgetAlertDecision d = service.evaluate(s);
Assert.assertEquals("GREEN", d.getUtilizationLevel()); Assert.assertEquals(68, d.getUtilizationPercent()); Assert.assertEquals("NORMAL", d.getBurnStatus()); Assert.assertEquals("OK", d.getForecastStatus()); Assert.assertEquals(0.0, d.getOverrunAmount(), 0.0001); Assert.assertEquals("APPROVE", d.getSingleExpenseAction()); Assert.assertEquals(10, d.getRiskScore()); Assert.assertEquals(0, d.getAlerts().size()); }
/** * 占用 75% YELLOW、预测轻度超支 5 万、单笔 3% → 警示放行 */ @Test public void shouldWarnOnYellowUtilizationAndMildOverrun() { BudgetStatus s = base("D002", 1000000, 700000, 50000, 8, 1050000); s.setSingleExpenseAmount(30000); s.setExpenseCategory("ADMIN");
BudgetAlertDecision d = service.evaluate(s);
Assert.assertEquals("YELLOW", d.getUtilizationLevel()); Assert.assertEquals(75, d.getUtilizationPercent()); Assert.assertEquals("NORMAL", d.getBurnStatus()); Assert.assertEquals("MILD", d.getForecastStatus()); Assert.assertEquals(50000.0, d.getOverrunAmount(), 0.0001); Assert.assertEquals("APPROVE_WITH_WARNING", d.getSingleExpenseAction()); Assert.assertEquals(40, d.getRiskScore()); }
/** * 8 月占用 93% ORANGE、支出超前、预测超支 8 万、单笔 6% → 升级 */ @Test public void shouldEscalateOnOrangeOverBurnLargeExpense() { BudgetStatus s = base("D003", 1000000, 880000, 50000, 8, 1080000); s.setSingleExpenseAmount(60000); s.setExpenseCategory("OPEX");
BudgetAlertDecision d = service.evaluate(s);
Assert.assertEquals("ORANGE", d.getUtilizationLevel()); Assert.assertEquals(93, d.getUtilizationPercent()); Assert.assertEquals("OVER_BURN", d.getBurnStatus()); Assert.assertEquals("MILD", d.getForecastStatus()); Assert.assertEquals(80000.0, d.getOverrunAmount(), 0.0001); Assert.assertEquals("ESCALATE", d.getSingleExpenseAction()); Assert.assertEquals(80, d.getRiskScore()); Assert.assertTrue(join(d).contains("超过 90% 橙色预警线")); }
/** * 9 月占用 104% RED、预测严重超支 15%:未立项 CAPEX 拦截,应急 OPEX 升级 */ @Test public void shouldBlockRedBudgetButEscalateEmergency() { BudgetStatus capex = base("D004", 1000000, 980000, 60000, 9, 1150000); capex.setSingleExpenseAmount(50000); capex.setExpenseCategory("CAPEX"); capex.setCapexApproved(false); BudgetAlertDecision d1 = service.evaluate(capex); Assert.assertEquals("RED", d1.getUtilizationLevel()); Assert.assertEquals(104, d1.getUtilizationPercent()); Assert.assertEquals("OVER_BURN", d1.getBurnStatus()); Assert.assertEquals("SEVERE", d1.getForecastStatus()); Assert.assertEquals(150000.0, d1.getOverrunAmount(), 0.0001); Assert.assertEquals("BLOCK", d1.getSingleExpenseAction()); Assert.assertEquals(120, d1.getRiskScore()); Assert.assertTrue(join(d1).contains("资本性支出未完成立项,系统拦截"));
BudgetStatus emergency = base("D005", 1000000, 980000, 60000, 9, 1150000); emergency.setSingleExpenseAmount(80000); emergency.setExpenseCategory("OPEX"); emergency.setEmergency(true); BudgetAlertDecision d2 = service.evaluate(emergency); Assert.assertEquals("ESCALATE", d2.getSingleExpenseAction()); Assert.assertTrue(join(d2).contains("升级至财务总监/分管领导专项审批")); }
/** * 6 月占用 40%、已立项 CAPEX 10 万 → 放行 */ @Test public void shouldApproveApprovedCapexInHealthyBudget() { BudgetStatus s = base("D006", 500000, 200000, 0, 6, 480000); s.setSingleExpenseAmount(100000); s.setExpenseCategory("CAPEX"); s.setCapexApproved(true);
BudgetAlertDecision d = service.evaluate(s);
Assert.assertEquals("GREEN", d.getUtilizationLevel()); Assert.assertEquals("NORMAL", d.getBurnStatus()); Assert.assertEquals("APPROVE", d.getSingleExpenseAction()); Assert.assertEquals(10, d.getRiskScore()); }
/** * 8 月仅支出 25% → UNDER_BURN 执行滞后预警 */ @Test public void shouldAlertUnderBurnExecutionLag() { BudgetStatus s = base("D007", 800000, 200000, 0, 8, 600000); s.setExpenseCategory("OPEX");
BudgetAlertDecision d = service.evaluate(s);
Assert.assertEquals("GREEN", d.getUtilizationLevel()); Assert.assertEquals("UNDER_BURN", d.getBurnStatus()); Assert.assertTrue(join(d).contains("预算执行严重滞后")); }
private BudgetStatus base(String dept, double budget, double spent, double committed, int month, double forecast) { BudgetStatus s = new BudgetStatus(); s.setDeptCode(dept); s.setDeptName("测试部门"); s.setAnnualBudget(budget); s.setActualSpent(spent); s.setCommittedAmount(committed); s.setMonth(month); s.setForecastYearEnd(forecast); s.setSingleExpenseAmount(0); s.setExpenseCategory("OPEX"); s.setEmergency(false); s.setCapexApproved(false); return s; }
private String join(BudgetAlertDecision d) { StringBuilder sb = new StringBuilder(); for (String a : d.getAlerts()) { sb.append(a).append(';'); } return sb.toString(); }}Run & verified results
Section titled “Run & verified results”Run at the project root:
mvn test -Dtest=BudgetAlertServiceTestThe tests cover 6 department budget shapes (under the RED level, two single-expense requests are constructed to contrast dispositions); the assertions correspond one-to-one with the source code:
| Test method | Business shape | Key assertions |
|---|---|---|
shouldApproveWhenBudgetHealthy |
August, 68% occupancy, normal pace, forecast 950k within budget, single 20k OPEX | GREEN, occupancy 68%, NORMAL, OK, overrun 0, APPROVE, risk score 10, 0 alerts |
shouldWarnOnYellowUtilizationAndMildOverrun |
75% occupancy YELLOW, forecast mild overrun of 50k, single expense at 3% of budget | YELLOW, 75%, MILD, overrun 50000, APPROVE_WITH_WARNING, risk score 40 |
shouldEscalateOnOrangeOverBurnLargeExpense |
August, 93% occupancy, over-consumption, forecast overrun 80k, single expense at 6% of budget | ORANGE, 93%, OVER_BURN, ESCALATE, risk score 80 (70 + 10 for over-burn); alert contains “超过 90% 橙色预警线” |
shouldBlockRedBudgetButEscalateEmergency |
September, 104% occupancy, forecast severe overrun 15%: unapproved CAPEX and emergency OPEX | CAPEX request: RED, 104%, SEVERE, overrun 150000, BLOCK, risk score 120, alert contains “资本性支出未完成立项,系统拦截”; emergency request: ESCALATE, alert contains “升级至财务总监/分管领导专项审批” |
shouldApproveApprovedCapexInHealthyBudget |
June, 40% occupancy, approved CAPEX of 100k | GREEN, NORMAL, APPROVE, risk score 10 |
shouldAlertUnderBurnExecutionLag |
August, only 25% spent, severe execution lag | GREEN, UNDER_BURN, alert contains “预算执行严重滞后” |
Technical highlights
Section titled “Technical highlights”- One method, one policy: occupancy, execution pace, year-end forecast, single-expense disposition, and risk score are each an independent rule method — every step of the alerting chain can be tuned and traced separately, and finance auditors can check the XML directly.
- Cross-multiplication removes floating-point disputes:
utilizationLevelexpresses percentage comparisons asoccupied * 100 >= budget * 100/90/70, andforecastStatusasforecast * 100 >= budget * 110, avoiding floating-point division and rounding errors in the script; the pace deviation converts the annualized ±15% band into an integer-multiple threshold ofbudget * 1.8(12 months × 15%). - Externalized rules with hot update: the 70%/90%/100% flag lines, the 5% single-expense escalation line, and the base risk scores all live in the XML — annual budget-policy changes only require editing the script and rebuilding the proxy, without touching the finance system’s main program.
- Service-side amount backstops: the overrun amount is truncated to non-negative and kept to two decimals with
BigDecimal+HALF_UP; “whether a single expense exceeds 5% of the annual budget” is computed as a boolean on the Java side before being passed into the rule — the rule only maps tiers and never touches amount precision. - Hard vetoes coexist with an emergency channel: unapproved CAPEX and RED non-emergency expenses are
BLOCKed directly at the rule layer, while RED emergency expenses keep theESCALATEchannel — freezing overruns without stopping firefighting procurement; alert messages are assembled item by item in the Service per hit condition. - Proxy reuse, ASM, Java 8 / domestic-JDK friendly: the proxy is created once in
@Beforeand reused throughout, so high-frequency budget checks carry no repeated loading overhead; the engine is based on dynamic proxies and ASM with no heavy dependencies, and can embed into finance systems on JDK 8 and domestic (Xinchuang) environments.