Skip to content

Case 20: Tech project application review

This case corresponds to USE-CASES scenario 20, “Preliminary scoring review of tech project application materials,” in the government enterprise-services domain. It targets applications for fiscally funded projects in key core technologies, high technologies, and social development submitted to science-and-technology authorities, automatically completing five-dimension scoring, review-conclusion tiering, and fixed grant-amount determination.

Materials submitted by an applicant first go through a formal review and a scored preliminary review. The project profile fields include: project category (KEY_CORE key core technology / HIGH_TECH high technology / SOCIAL_DEV social development), technology appraisal conclusion (international leading / international advanced / domestic leading / domestic advanced / general), number of granted invention patents, industrialization stage (mass production / pilot / laboratory), prior-year revenue, team-leader title (academician-level / industry expert / ordinary), number of PhDs on the core team, R&D investment ratio, self-funded ratio, new jobs created after reaching full production, annual energy savings in standard-coal equivalent, plus two formal flags — major violations of laws or regulations in the last three years, and whether the application materials are complete.

Scoring spans five dimensions totaling 100 points: technology advancement 35 points (appraisal conclusion as the base score, with a patent bonus, capped at 35), industrialization maturity 25 points (axis on mass production / pilot / laboratory, with revenue bonuses differentiated by stage, capped at 25), team strength 15 points (leader title determines base score and tier cap, plus a PhD-count bonus), budget reasonableness 15 points (R&D intensity 0-8 + self-funded ratio 0-7), and social benefit 10 points (new jobs 0-5 + energy savings 0-5). Each dimension’s base score and bonus are capped by tier inside the script, so the five-dimension total naturally never exceeds 100.

The review conclusion has four tiers: a major violation record is a one-vote REJECT; incomplete materials are MATERIAL_PENDING for correction and resubmission; complete materials with a total score of 80 or above are APPROVE (proposed for approval), 65 or above is REVIEW (enters a defense-based re-evaluation), and everything else is rejected. Only proposed-approval projects receive a fixed fiscal grant amount from a “project category x score tier” matrix — key core technology projects get 5 million yuan at 90+ points and 3 million at 80+; high-technology projects get 3M / 1.5M; social-development projects get 1M / 0.5M. Re-evaluation and rejection cases receive no amount. The decision result carries the five-dimension score breakdown and the review-basis explanations (five categories: violation, correction, approval amount, defense interval, below standard), directly supporting generation of the preliminary review opinion and traceability in the application system.

  • Scoring rules change frequently with the annual application guide: appraisal-conclusion scores, patent bonuses, mass-production/pilot revenue lines, and grant amounts may all be revised every year; once hard-coded into the government system, every adjustment requires a new release and a new security-graded-protection assessment.
  • Multi-dimensional bonus and cap rules are complex: the technology, industrialization, and team dimensions all follow a “base score + conditional bonus + tier cap” structure; hard-coded bonuses easily break the dimension maximum and corrupt the total-score calculation.
  • The grant amount is a category-by-score matrix: the three project categories each have two amount tiers; manual table lookups easily misissue grant amounts, and re-evaluation cases must not carry a preset amount.
  • The order of formal review versus scoring must be compliant: major violations and incomplete materials must take precedence over the score-based conclusion; a high-scoring project with violations must never enter the approval announcement.
  • The preliminary-review basis must be explainable and auditable: fiscal-fund applications require that every dimension’s score, the conclusion tier lines, and the amount basis can be explained to applicants and supervisory bodies; a black-box score cannot meet disclosure requirements.
  • Domestic-platform government systems and legacy JDK 8 constraints: government clouds mostly run domestic companion JDKs, and existing application systems cannot introduce a heavy standalone platform for rule capability.

This case follows the six-piece skeleton: input model + decision result model + rule interface + XML rule script + Service orchestration + 6 test cases covering real business profiles.

Layer Responsibility Files
Domain model Project application fields, preliminary-review decision result (conclusion / amount / score breakdown / basis explanation) TechProjectApplication.java, ProjectReviewDecision.java
Rule set & XML Five-dimension scoring criteria (with bonuses and dimension caps), conclusion tiers, category-based grant amounts TechProjectReviewRules.java, tech-project-review.xml
Orchestration service Five-dimension score aggregation, conclusion assembly, grant-amount attachment, review-basis composition TechProjectReviewService.java
Tests 6 project profiles (full-score approval / defense re-evaluation / weak-project rejection / violation / correction / borderline approval) TechProjectReviewServiceTest.java
Rule method Criteria Score/output
scoreTech Base score by appraisal conclusion: international leading 35 / international advanced 30 / domestic leading 25 / domestic advanced 18 / general 8; invention patents >=5/>=2/>=1 add 3/2/1, capped at 35 0-35
scoreIndustrialization Base by stage: mass production 20 / pilot 14 / laboratory 7; revenue bonus for mass production: 50M +5, 10M +3; pilot +4/+2, capped at 25 0-25
scoreTeam Leader academician 13 (cap 15) / expert 10 (cap 12) / ordinary 6 (cap 8); PhDs >=3 add 2, >=1 add 1, capped by tier 0-15
scoreBudget R&D ratio >=15%/>=8%/>=3% yields 8/5/3 points + self-funded ratio >=60%/>=40%/>=20% yields 7/5/3 points (minimum 1) 0-15
scoreSocial New jobs >=50/>=20/>=5 yield 5/3/1 + energy savings >=1000/>=300/>=100 tons yield 5/3/1 0-10
reviewResult Major violation REJECT first; incomplete materials MATERIAL_PENDING; >=80 APPROVE; >=65 REVIEW; otherwise REJECT Four conclusions
grantAmountWan Category x score tier: key core 5M/3M, high-tech 3M/1.5M, social development 1M/0.5M; 0 when not approved Fixed amount in 10K yuan

Place the following files into the test directory of a JQuick-Java project following the source package structure: the two models go to src/test/java/com/github/paohaijiao/demo/model/, the rule interface and test class go to src/test/java/com/github/paohaijiao/demo/, the Service goes to src/test/java/com/github/paohaijiao/demo/service/, and the XML script goes to src/test/resources/demo/. Keep the package names exactly as in the source (com.github.paohaijiao.demo / .model / .service). Then run mvn test -Dtest=TechProjectReviewServiceTest to execute all 6 cases.

package com.github.paohaijiao.demo.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 科技项目申报要素(立项初审所需的项目画像)
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TechProjectApplication {
/**
* 项目编号
*/
private String projectId;
/**
* 项目名称
*/
private String projectName;
/**
* 项目类别:KEY_CORE-关键核心技术 / HIGH_TECH-高新技术 / SOCIAL_DEV-社会发展
*/
private String projectCategory;
/**
* 技术鉴定结论:INTL_LEADING-国际领先 / INTL_ADVANCED-国际先进
* / DOM_LEADING-国内领先 / DOM_ADVANCED-国内先进 / GENERAL-一般
*/
private String techLevel;
/**
* 已授权发明专利数量
*/
private int inventionPatents;
/**
* 产业化阶段:MASS-量产 / PILOT-中试 / LAB-实验室
*/
private String pilotStage;
/**
* 上年度营业收入(万元)
*/
private int annualRevenueWan;
/**
* 团队带头人称号:ACADEMICIAN-院士级 / EXPERT-行业专家 / NORMAL-普通
*/
private String leaderTitle;
/**
* 核心团队博士人数
*/
private int phdCount;
/**
* 研发投入占营业收入比例(整数百分比)
*/
private int rdRatio;
/**
* 项目自筹资金占总投资比例(整数百分比)
*/
private int selfFundedRatio;
/**
* 项目达产后新增就业人数
*/
private int employmentIncrease;
/**
* 年节能折标煤量(吨)
*/
private int energySavingTon;
/**
* 近三年是否存在重大违法违规记录
*/
private boolean majorViolation;
/**
* 申报材料是否齐全
*/
private boolean materialsComplete;
}
package com.github.paohaijiao.demo.model;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* 科技项目立项初审决策结果
*/
@Data
public class ProjectReviewDecision {
/**
* 项目编号
*/
private String projectId;
/**
* 评审总分(0-100)
*/
private int totalScore;
/**
* 评审结论:APPROVE-拟立项 / REVIEW-答辩复评 / REJECT-不予立项 / MATERIAL_PENDING-补正
*/
private String result;
/**
* 核定财政资助额度(万元,仅拟立项时大于 0)
*/
private int grantAmountWan;
/**
* 各维度评分明细
*/
private List<String> scoreDetails = new ArrayList<String>();
/**
* 审核依据与说明
*/
private List<String> reasons = new ArrayList<String>();
public void addScoreDetail(String detail) {
this.scoreDetails.add(detail);
}
public void addReason(String reason) {
this.reasons.add(reason);
}
}
package com.github.paohaijiao.demo;
import com.github.paohaijiao.xml.param.Param;
/**
* 场景 20:科技项目申报材料打分初审规则集
* <p>
* 五维评分(满分 100):技术先进性 35 / 产业化成熟度 25 / 团队实力 15
* / 经费预算合理性 15 / 社会效益 10;结论分档:拟立项 ≥80、答辩复评 ≥65、其余驳回。
* 规则脚本见 {@code src/test/resources/demo/tech-project-review.xml}。
*/
public interface TechProjectReviewRules {
/** 技术先进性 0-35:鉴定结论为轴,发明专利加成,35 封顶 */
int scoreTech(@Param("techLevel") String techLevel,
@Param("inventionPatents") int inventionPatents);
/** 产业化成熟度 0-25:量产/中试/实验室为轴,营收规模加成 */
int scoreIndustrialization(@Param("pilotStage") String pilotStage,
@Param("annualRevenueWan") int annualRevenueWan);
/** 团队实力 0-15:带头人称号为轴,博士人数加成,分档封顶 */
int scoreTeam(@Param("leaderTitle") String leaderTitle,
@Param("phdCount") int phdCount);
/** 经费预算合理性 0-15:研发占比 0-8 + 自筹比例 0-7 */
int scoreBudget(@Param("rdRatio") int rdRatio,
@Param("selfFundedRatio") int selfFundedRatio);
/** 社会效益 0-10:新增就业 0-5 + 节能降耗 0-5 */
int scoreSocial(@Param("employmentIncrease") int employmentIncrease,
@Param("energySavingTon") int energySavingTon);
/** 评审结论:重大违规与材料不齐前置,80/65 为通过、复评线 */
String reviewResult(@Param("totalScore") int totalScore,
@Param("majorViolation") boolean majorViolation,
@Param("materialsComplete") boolean materialsComplete);
/** 立项资助定额(万元):项目类别 × 评分档,未立项为 0 */
int grantAmountWan(@Param("projectCategory") String projectCategory,
@Param("totalScore") int totalScore);
}
<?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.TechProjectReviewRules">
<!-- 技术先进性:鉴定等级基础分 + 发明专利加成,35 封顶 -->
<java name="scoreTech" returnClass="int">
<![CDATA[
int def scoreTech(str:techLevel, int:inventionPatents) {
int score = 8;
if (techLevel == "INTL_LEADING") {
score = 35;
} else if (techLevel == "INTL_ADVANCED") {
score = 30;
} else if (techLevel == "DOM_LEADING") {
score = 25;
} else if (techLevel == "DOM_ADVANCED") {
score = 18;
} else {
score = 8;
}
int bonus = 0;
if (inventionPatents >= 5) {
bonus = 3;
} else if (inventionPatents >= 2) {
bonus = 2;
} else if (inventionPatents >= 1) {
bonus = 1;
} else {
bonus = 0;
}
int total = score + bonus;
if (total > 35) {
total = 35;
}
return total;
}
]]>
</java>
<!-- 产业化成熟度:阶段基础分 + 营收规模加成,25 封顶 -->
<java name="scoreIndustrialization" returnClass="int">
<![CDATA[
int def scoreIndustrialization(str:pilotStage, int:annualRevenueWan) {
int base = 7;
if (pilotStage == "MASS") {
base = 20;
} else if (pilotStage == "PILOT") {
base = 14;
} else {
base = 7;
}
int bonus = 0;
if (pilotStage == "MASS") {
if (annualRevenueWan >= 5000) {
bonus = 5;
} else if (annualRevenueWan >= 1000) {
bonus = 3;
} else {
bonus = 0;
}
}
if (pilotStage == "PILOT") {
if (annualRevenueWan >= 5000) {
bonus = 4;
} else if (annualRevenueWan >= 1000) {
bonus = 2;
} else {
bonus = 0;
}
}
int total = base + bonus;
if (total > 25) {
total = 25;
}
return total;
}
]]>
</java>
<!-- 团队实力:带头人基础分 + 博士加成,按档封顶 -->
<java name="scoreTeam" returnClass="int">
<![CDATA[
int def scoreTeam(str:leaderTitle, int:phdCount) {
int base = 6;
int cap = 8;
if (leaderTitle == "ACADEMICIAN") {
base = 13;
cap = 15;
} else if (leaderTitle == "EXPERT") {
base = 10;
cap = 12;
} else {
base = 6;
cap = 8;
}
int bonus = 0;
if (phdCount >= 3) {
bonus = 2;
} else if (phdCount >= 1) {
bonus = 1;
} else {
bonus = 0;
}
int total = base + bonus;
if (total > cap) {
total = cap;
}
return total;
}
]]>
</java>
<!-- 经费预算合理性:研发投入强度 + 自筹资金比例 -->
<java name="scoreBudget" returnClass="int">
<![CDATA[
int def scoreBudget(int:rdRatio, int:selfFundedRatio) {
int rd = 0;
if (rdRatio >= 15) {
rd = 8;
} else if (rdRatio >= 8) {
rd = 5;
} else if (rdRatio >= 3) {
rd = 3;
} else {
rd = 0;
}
int self = 1;
if (selfFundedRatio >= 60) {
self = 7;
} else if (selfFundedRatio >= 40) {
self = 5;
} else if (selfFundedRatio >= 20) {
self = 3;
} else {
self = 1;
}
return rd + self;
}
]]>
</java>
<!-- 社会效益:新增就业 + 节能降耗 -->
<java name="scoreSocial" returnClass="int">
<![CDATA[
int def scoreSocial(int:employmentIncrease, int:energySavingTon) {
int jobs = 0;
if (employmentIncrease >= 50) {
jobs = 5;
} else if (employmentIncrease >= 20) {
jobs = 3;
} else if (employmentIncrease >= 5) {
jobs = 1;
} else {
jobs = 0;
}
int energy = 0;
if (energySavingTon >= 1000) {
energy = 5;
} else if (energySavingTon >= 300) {
energy = 3;
} else if (energySavingTon >= 100) {
energy = 1;
} else {
energy = 0;
}
return jobs + energy;
}
]]>
</java>
<!-- 评审结论:违规/补正前置,其次按分数分档 -->
<java name="reviewResult" returnClass="java.lang.String">
<![CDATA[
str def reviewResult(int:totalScore, boolean:majorViolation, boolean:materialsComplete) {
str result = "REJECT";
if (majorViolation) {
result = "REJECT";
} else if (materialsComplete == false) {
result = "MATERIAL_PENDING";
} else if (totalScore >= 80) {
result = "APPROVE";
} else if (totalScore >= 65) {
result = "REVIEW";
} else {
result = "REJECT";
}
return result;
}
]]>
</java>
<!-- 资助定额:项目类别 × 评分档 -->
<java name="grantAmountWan" returnClass="int">
<![CDATA[
int def grantAmountWan(str:projectCategory, int:totalScore) {
int amount = 0;
if (projectCategory == "KEY_CORE") {
if (totalScore >= 90) {
amount = 500;
} else if (totalScore >= 80) {
amount = 300;
} else {
amount = 0;
}
}
if (projectCategory == "HIGH_TECH") {
if (totalScore >= 90) {
amount = 300;
} else if (totalScore >= 80) {
amount = 150;
} else {
amount = 0;
}
}
if (projectCategory == "SOCIAL_DEV") {
if (totalScore >= 90) {
amount = 100;
} else if (totalScore >= 80) {
amount = 50;
} else {
amount = 0;
}
}
return amount;
}
]]>
</java>
</javas>
package com.github.paohaijiao.demo.service;
import com.github.paohaijiao.demo.TechProjectReviewRules;
import com.github.paohaijiao.demo.model.ProjectReviewDecision;
import com.github.paohaijiao.demo.model.TechProjectApplication;
/**
* 场景 20:科技项目申报材料打分初审服务
* <p>
* 职责划分:
* <ul>
* <li><b>Java 侧</b>:五维评分汇总、结论装配、资助定额挂接、评审说明组装</li>
* <li><b>规则侧</b>:五维评分口径、结论阈值、分类资助定额,全部外置在 XML 中</li>
* </ul>
*/
public class TechProjectReviewService {
private final TechProjectReviewRules rules;
public TechProjectReviewService(TechProjectReviewRules rules) {
this.rules = rules;
}
public ProjectReviewDecision review(TechProjectApplication app) {
ProjectReviewDecision decision = new ProjectReviewDecision();
decision.setProjectId(app.getProjectId());
int tech = rules.scoreTech(app.getTechLevel(), app.getInventionPatents());
int industrial = rules.scoreIndustrialization(app.getPilotStage(), app.getAnnualRevenueWan());
int team = rules.scoreTeam(app.getLeaderTitle(), app.getPhdCount());
int budget = rules.scoreBudget(app.getRdRatio(), app.getSelfFundedRatio());
int social = rules.scoreSocial(app.getEmploymentIncrease(), app.getEnergySavingTon());
int total = tech + industrial + team + budget + social;
decision.addScoreDetail("技术先进性: " + tech + " / 35");
decision.addScoreDetail("产业化成熟度: " + industrial + " / 25");
decision.addScoreDetail("团队实力: " + team + " / 15");
decision.addScoreDetail("经费预算合理性: " + budget + " / 15");
decision.addScoreDetail("社会效益: " + social + " / 10");
decision.addScoreDetail("汇总评分: " + total);
decision.setTotalScore(total);
String result = rules.reviewResult(total, app.isMajorViolation(), app.isMaterialsComplete());
decision.setResult(result);
if ("APPROVE".equals(result)) {
decision.setGrantAmountWan(rules.grantAmountWan(app.getProjectCategory(), total));
}
buildReasons(app, decision);
return decision;
}
private void buildReasons(TechProjectApplication app, ProjectReviewDecision decision) {
if (app.isMajorViolation()) {
decision.addReason("申报主体近三年存在重大违法违规记录,不予立项");
return;
}
if (!app.isMaterialsComplete()) {
decision.addReason("申报材料不齐全,须补正后重新提交初审");
return;
}
if ("APPROVE".equals(decision.getResult())) {
decision.addReason("综合评分 " + decision.getTotalScore()
+ " 分达到立项线,拟立项支持,核定资助额度 "
+ decision.getGrantAmountWan() + " 万元");
} else if ("REVIEW".equals(decision.getResult())) {
decision.addReason("综合评分处于答辩复评区间(65-79 分),须组织专家答辩");
} else {
decision.addReason("综合评分未达立项标准(65 分),不予立项");
}
}
}
package com.github.paohaijiao.demo;
import com.github.paohaijiao.demo.model.ProjectReviewDecision;
import com.github.paohaijiao.demo.model.TechProjectApplication;
import com.github.paohaijiao.demo.service.TechProjectReviewService;
import com.github.paohaijiao.xml.JQuickJava;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
/**
* 场景 20:科技项目申报材料打分初审
* <p>
* 覆盖 6 组项目形态:关键核心满分立项、国内先进中试答辩、实验室弱项目驳回、
* 重大违规否决、材料不齐补正、社会发展类 80 分临界立项。
*/
public class TechProjectReviewServiceTest {
private TechProjectReviewService service;
@Before
public void setUp() {
TechProjectReviewRules rules = JQuickJava.create()
.createApi(TechProjectReviewRules.class, "demo/tech-project-review.xml");
this.service = new TechProjectReviewService(rules);
}
/**
* 国际领先、已量产、院士团队的关键核心技术项目 → 100 分,资助 500 万
*/
@Test
public void shouldApproveKeyCoreProjectWithFullScore() {
TechProjectApplication app = baseApplication("TP2026001", "KEY_CORE");
app.setTechLevel("INTL_LEADING");
app.setInventionPatents(6);
app.setPilotStage("MASS");
app.setAnnualRevenueWan(8000);
app.setLeaderTitle("ACADEMICIAN");
app.setPhdCount(4);
app.setRdRatio(18);
app.setSelfFundedRatio(70);
app.setEmploymentIncrease(80);
app.setEnergySavingTon(1500);
ProjectReviewDecision decision = service.review(app);
Assert.assertEquals(100, decision.getTotalScore());
Assert.assertEquals("APPROVE", decision.getResult());
Assert.assertEquals(500, decision.getGrantAmountWan());
Assert.assertTrue(decision.getScoreDetails().contains("技术先进性: 35 / 35"));
Assert.assertTrue(decision.getReasons().contains(
"综合评分 100 分达到立项线,拟立项支持,核定资助额度 500 万元"));
}
/**
* 国内领先、中试阶段、专家团队的高新技术项目 → 68 分,答辩复评不定额
*/
@Test
public void shouldRouteDomesticProjectToReview() {
TechProjectApplication app = baseApplication("TP2026002", "HIGH_TECH");
app.setTechLevel("DOM_LEADING");
app.setInventionPatents(0);
app.setPilotStage("PILOT");
app.setAnnualRevenueWan(1200);
app.setLeaderTitle("EXPERT");
app.setPhdCount(1);
app.setRdRatio(10);
app.setSelfFundedRatio(50);
app.setEmploymentIncrease(30);
app.setEnergySavingTon(500);
ProjectReviewDecision decision = service.review(app);
Assert.assertEquals(68, decision.getTotalScore());
Assert.assertEquals("REVIEW", decision.getResult());
Assert.assertEquals(0, decision.getGrantAmountWan());
Assert.assertTrue(decision.getScoreDetails().contains("团队实力: 11 / 15"));
Assert.assertTrue(decision.getReasons().contains(
"综合评分处于答辩复评区间(65-79 分),须组织专家答辩"));
}
/**
* 技术一般、实验室阶段、普通团队 → 22 分,不予立项
*/
@Test
public void shouldRejectWeakLabProject() {
TechProjectApplication app = baseApplication("TP2026003", "HIGH_TECH");
app.setTechLevel("GENERAL");
app.setInventionPatents(0);
app.setPilotStage("LAB");
app.setAnnualRevenueWan(0);
app.setLeaderTitle("NORMAL");
app.setPhdCount(0);
app.setRdRatio(2);
app.setSelfFundedRatio(15);
app.setEmploymentIncrease(2);
app.setEnergySavingTon(50);
ProjectReviewDecision decision = service.review(app);
Assert.assertEquals(22, decision.getTotalScore());
Assert.assertEquals("REJECT", decision.getResult());
Assert.assertEquals(0, decision.getGrantAmountWan());
Assert.assertTrue(decision.getReasons().contains("综合评分未达立项标准(65 分),不予立项"));
}
/**
* 项目本身满分但主体存在重大违法违规 → 一票否决
*/
@Test
public void shouldRejectMajorViolationRegardlessOfScore() {
TechProjectApplication app = baseApplication("TP2026004", "KEY_CORE");
app.setTechLevel("INTL_LEADING");
app.setInventionPatents(8);
app.setPilotStage("MASS");
app.setAnnualRevenueWan(6000);
app.setLeaderTitle("ACADEMICIAN");
app.setPhdCount(5);
app.setRdRatio(20);
app.setSelfFundedRatio(80);
app.setEmploymentIncrease(60);
app.setEnergySavingTon(1200);
app.setMajorViolation(true);
ProjectReviewDecision decision = service.review(app);
Assert.assertEquals("REJECT", decision.getResult());
Assert.assertEquals(0, decision.getGrantAmountWan());
Assert.assertTrue(decision.getReasons().contains(
"申报主体近三年存在重大违法违规记录,不予立项"));
}
/**
* 高分项目但申报材料不齐 → 补正暂存,不进入定额
*/
@Test
public void shouldHoldForIncompleteMaterials() {
TechProjectApplication app = baseApplication("TP2026005", "KEY_CORE");
app.setTechLevel("INTL_ADVANCED");
app.setInventionPatents(3);
app.setPilotStage("MASS");
app.setAnnualRevenueWan(5500);
app.setLeaderTitle("EXPERT");
app.setPhdCount(3);
app.setRdRatio(16);
app.setSelfFundedRatio(65);
app.setEmploymentIncrease(55);
app.setEnergySavingTon(900);
app.setMaterialsComplete(false);
ProjectReviewDecision decision = service.review(app);
Assert.assertEquals("MATERIAL_PENDING", decision.getResult());
Assert.assertEquals(0, decision.getGrantAmountWan());
Assert.assertTrue(decision.getReasons().contains(
"申报材料不齐全,须补正后重新提交初审"));
}
/**
* 社会发展类项目恰好 80 分立项线 → APPROVE,资助 50 万
*/
@Test
public void shouldApproveSocialDevelopmentAtExactThreshold() {
TechProjectApplication app = baseApplication("TP2026006", "SOCIAL_DEV");
app.setTechLevel("DOM_LEADING");
app.setInventionPatents(2);
app.setPilotStage("PILOT");
app.setAnnualRevenueWan(6000);
app.setLeaderTitle("EXPERT");
app.setPhdCount(3);
app.setRdRatio(16);
app.setSelfFundedRatio(70);
app.setEmploymentIncrease(60);
app.setEnergySavingTon(400);
ProjectReviewDecision decision = service.review(app);
Assert.assertEquals(80, decision.getTotalScore());
Assert.assertEquals("APPROVE", decision.getResult());
Assert.assertEquals(50, decision.getGrantAmountWan());
Assert.assertTrue(decision.getScoreDetails().contains("社会效益: 8 / 10"));
}
private TechProjectApplication baseApplication(String projectId, String category) {
TechProjectApplication app = new TechProjectApplication();
app.setProjectId(projectId);
app.setProjectName("测试科技项目");
app.setProjectCategory(category);
app.setTechLevel("GENERAL");
app.setInventionPatents(0);
app.setPilotStage("LAB");
app.setAnnualRevenueWan(0);
app.setLeaderTitle("NORMAL");
app.setPhdCount(0);
app.setRdRatio(0);
app.setSelfFundedRatio(0);
app.setEmploymentIncrease(0);
app.setEnergySavingTon(0);
app.setMajorViolation(false);
app.setMaterialsComplete(true);
return app;
}
}

Run from the project root:

Terminal window
mvn test -Dtest=TechProjectReviewServiceTest
Test method Constructed business profile Key assertions / expected decision
shouldApproveKeyCoreProjectWithFullScore International leading + 6 invention patents, mass production, 80M revenue, academician + 4 PhDs, R&D 18% / self-funded 70%, 80 jobs / 1500 tons 100 points APPROVE; key core category at 90+ gets a 5M grant; breakdown contains “technology advancement: 35 / 35”; basis contains “approved grant of 5 million yuan”
shouldRouteDomesticProjectToReview Domestic leading, 0 patents, pilot stage, 12M revenue, expert + 1 PhD, R&D 10% / self-funded 50%, 30 jobs / 500 tons 68 points REVIEW; amount 0; breakdown contains “team strength: 11 / 15”; basis contains “65-79 points, expert defense required”
shouldRejectWeakLabProject General technology, 0 patents, laboratory stage, 0 revenue, ordinary team, R&D 2% / self-funded 15%, 2 jobs / 50 tons 22 points REJECT; amount 0; basis contains “total score below the approval standard (65 points); not approved”
shouldRejectMajorViolationRegardlessOfScore Full-score project profile but majorViolation=true REJECT (one-vote veto takes precedence over score); amount 0; basis contains “major violations of laws or regulations in the last three years; not approved”
shouldHoldForIncompleteMaterials High-scoring project (international advanced + 3 patents, mass production, 55M revenue) but materialsComplete=false MATERIAL_PENDING; amount 0; basis contains “application materials incomplete; must be corrected and resubmitted for preliminary review”
shouldApproveSocialDevelopmentAtExactThreshold Social-development category: domestic leading + 2 patents, pilot stage, 60M, expert + 3 PhDs, 16% / 70%, 60 jobs / 400 tons Exactly 80 points APPROVE (borderline approval); social-development grant 500K yuan; breakdown contains “social benefit: 8 / 10”
  • Scoring rules fully externalized to XML: appraisal-level scores, patent bonuses, stage revenue lines, self-funded-ratio lines, the 80/65 conclusion lines, and the three-category two-tier grant amounts all live in the XML; annual application-guide revisions only require editing the script and rebuilding the proxy — the government main system needs no release.
  • “Base score + bonus + dimension cap” closes the loop inside the script: the technology, industrialization, and team methods each complete their bonus and the 35/25/tier cap inside the script, so bonuses never break the dimension maximum; the five-dimension total naturally never exceeds 100 and the Service needs no further clamp.
  • Formal-review priority scripted: reviewResult checks major violations first, then incomplete materials, and only then applies score tiers — violations and corrections take precedence over the score-based conclusion, eliminating the risk of a high-scoring project with violations entering the approval process.
  • The script does not support the ! negation operator, so positive comparisons are used instead: the incomplete-materials check is written as materialsComplete == false rather than a negation expression, conforming to the engine’s syntax constraints.
  • The amount matrix is an independent method attached only on approval: grantAmountWan outputs the amount from project category x score tier, and the Service calls it only on APPROVE; re-evaluation, correction, and rejection cases always carry 0, avoiding preset amounts.
  • Per-case auditable review basis: the five-dimension score breakdown, conclusion tier, grant amount, and the five explanation categories (violation / correction / defense / rejection / approval) are uniformly assembled by the Service and can directly generate the preliminary review opinion, meeting fiscal-fund disclosure and audit requirements.
  • @Param name binding + ASM proxy call chain: 7 rule methods share a single dynamic proxy; Java 8 with no heavy dependencies, embeddable into existing government application systems and domestic companion JDK environments.