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.
Industry scenario
Section titled “Industry scenario”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.
Business pain points
Section titled “Business pain points”- 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.
Solution architecture
Section titled “Solution architecture”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 breakdown
Section titled “Rule method breakdown”| 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 |
Code implementation (complete & runnable)
Section titled “Code implementation (complete & runnable)”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.
TechProjectApplication.java
Section titled “TechProjectApplication.java”package com.github.paohaijiao.demo.model;
import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;
/** * 科技项目申报要素(立项初审所需的项目画像) */@Data@NoArgsConstructor@AllArgsConstructorpublic 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;}ProjectReviewDecision.java
Section titled “ProjectReviewDecision.java”package com.github.paohaijiao.demo.model;
import lombok.Data;
import java.util.ArrayList;import java.util.List;
/** * 科技项目立项初审决策结果 */@Datapublic 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); }}TechProjectReviewRules.java
Section titled “TechProjectReviewRules.java”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);}tech-project-review.xml
Section titled “tech-project-review.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.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>TechProjectReviewService.java
Section titled “TechProjectReviewService.java”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 分),不予立项"); } }}TechProjectReviewServiceTest.java
Section titled “TechProjectReviewServiceTest.java”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 & verified results
Section titled “Run & verified results”Run from the project root:
mvn test -Dtest=TechProjectReviewServiceTestTest coverage
Section titled “Test coverage”| 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” |
Technical highlights
Section titled “Technical highlights”- 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:
reviewResultchecks 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 asmaterialsComplete == falserather than a negation expression, conforming to the engine’s syntax constraints. - The amount matrix is an independent method attached only on approval:
grantAmountWanoutputs 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.
@Paramname 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.