案例 20:科技项目申报材料打分初审
本案例对应 USE-CASES 场景 20「科技项目申报材料打分初审」,属于政务企服领域,面向科技主管部门的关键核心技术、高新技术、社会发展类财政资助项目申报,自动完成五维打分、评审结论分档与资助定额核定。
科技项目申报单位提交的材料要先经过形式审查与打分初审,项目画像要素包括:项目类别(KEY_CORE 关键核心技术 / HIGH_TECH 高新技术 / SOCIAL_DEV 社会发展)、技术鉴定结论(国际领先/国际先进/国内领先/国内先进/一般)、已授权发明专利数量、产业化阶段(量产/中试/实验室)、上年度营业收入、团队带头人称号(院士级/行业专家/普通)、核心团队博士人数、研发投入占比、自筹资金比例、达产后新增就业人数、年节能折标煤量,以及近三年重大违法违规记录、申报材料是否齐全两个形式标记。
评分按五个维度展开、满分 100:技术先进性 35 分(鉴定结论为基础分,发明专利加成,35 封顶)、产业化成熟度 25 分(量产/中试/实验室为轴,营收规模按阶段差异化加成,25 封顶)、团队实力 15 分(带头人称号定基础分与分档封顶,博士人数加成)、经费预算合理性 15 分(研发投入强度 0-8 + 自筹比例 0-7)、社会效益 10 分(新增就业 0-5 + 节能降耗 0-5)。每个维度的基础分与加分都在脚本内按档封顶,五维合计天然不超过 100。
评审结论为四档:重大违法违规记录一票否决 REJECT;材料不齐的 MATERIAL_PENDING 补正暂存;材料齐全且总分 ≥80 的 APPROVE 拟立项,≥65 的 REVIEW 进入答辩复评,其余驳回。仅拟立项项目按「项目类别 × 评分档」核定财政资助定额——关键核心类 ≥90 分 500 万、≥80 分 300 万,高新技术类 300/150 万,社会发展类 100/50 万;复评与驳回件不定额。决策结果携带五维评分明细与审核依据说明(违规、补正、立项额度、答辩区间、未达标五类口径),直接支撑初审意见书生成与申报系统留痕。
- 评分细则随年度申报指南频繁调整:鉴定结论分值、专利加分、量产/中试营收线、资助定额每年都可能修订,硬编码进政务系统后每次调整都要重新发版、重新过等保测评。
- 多维加分与封顶规则复杂:技术、产业化、团队三个维度都是「基础分 + 条件加分 + 分档封顶」结构,加分写死容易突破维度满分,导致总分口径错误。
- 资助定额是类别 × 分数的矩阵:三类项目各有两档定额,人工套表容易错发资助额度,且复评件不得预设定额。
- 形式审查与评分先后顺序要合规:重大违规、材料不齐必须优先于评分结论,不能出现高分违规项目进入立项公示的事故。
- 初审依据需要可解释、可审计:财政资金申报要求每个维度得分、结论分档线、定额依据都能向申报单位与监督部门解释,黑盒评分无法满足公示要求。
- 政务系统信创环境与存量 JDK 8 约束:政务云多为国产化配套 JDK,存量申报系统无法为规则能力引入重型独立平台。
本案例沿用六件套骨架:入参模型 + 决策结果模型 + 规则接口 + XML 规则脚本 + Service 编排 + 6 组真实业务形态测试。
| 层次 | 职责 | 对应文件 |
|---|---|---|
| 领域模型 | 项目申报要素、立项初审决策结果(结论/定额/评分明细/依据说明) | TechProjectApplication.java、ProjectReviewDecision.java |
| 规则集与 XML | 五维评分口径(含加分与维度封顶)、结论分档、分类资助定额 | TechProjectReviewRules.java、tech-project-review.xml |
| 编排服务 | 五维评分汇总、结论装配、立项定额挂接、评审说明组装 | TechProjectReviewService.java |
| 测试 | 6 组项目形态(满分立项/答辩/弱项目驳回/违规/补正/临界立项) | TechProjectReviewServiceTest.java |
规则方法拆分
Section titled “规则方法拆分”| 规则方法 | 口径 | 分值/输出 |
|---|---|---|
scoreTech |
鉴定结论基础分:国际领先 35/国际先进 30/国内领先 25/国内先进 18/一般 8;发明专利 ≥5/≥2/≥1 件加 3/2/1 分,35 封顶 | 0-35 |
scoreIndustrialization |
阶段基础分:量产 20/中试 14/实验室 7;营收加分量产 5000 万 +5、1000 万 +3,中试 +4/+2,25 封顶 | 0-25 |
scoreTeam |
带头人院士 13(封顶 15)/专家 10(封顶 12)/普通 6(封顶 8);博士 ≥3 人 +2、≥1 人 +1,按档封顶 | 0-15 |
scoreBudget |
研发占比 ≥15%/≥8%/≥3% 得 8/5/3 分 + 自筹比例 ≥60%/≥40%/≥20% 得 7/5/3 分(最低 1 分) | 0-15 |
scoreSocial |
新增就业 ≥50/≥20/≥5 人 5/3/1 分 + 节能折标煤 ≥1000/≥300/≥100 吨 5/3/1 分 | 0-10 |
reviewResult |
重大违规 REJECT 优先;材料不齐 MATERIAL_PENDING;≥80 APPROVE;≥65 REVIEW;否则 REJECT | 四种结论 |
grantAmountWan |
类别 × 分数档:关键核心 500/300 万、高新技术 300/150 万、社会发展 100/50 万,未立项为 0 | 万元定额 |
代码实现(完整可运行)
Section titled “代码实现(完整可运行)”将下列文件按源码包结构放入 JQuick-Java 工程的测试目录:两个模型放入 src/test/java/com/github/paohaijiao/demo/model/,规则接口与测试类放入 src/test/java/com/github/paohaijiao/demo/,Service 放入 src/test/java/com/github/paohaijiao/demo/service/,XML 脚本放入 src/test/resources/demo/,包名保持源码原样(com.github.paohaijiao.demo / .model / .service)。放入后执行 mvn test -Dtest=TechProjectReviewServiceTest 即可运行全部 6 个用例。
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; }}在工程根目录执行:
mvn test -Dtest=TechProjectReviewServiceTest测试覆盖形态
Section titled “测试覆盖形态”| 测试方法 | 构造的业务形态 | 关键断言/期望决策 |
|---|---|---|
shouldApproveKeyCoreProjectWithFullScore |
国际领先 + 6 件发明专利、量产、年营收 8000 万、院士 + 4 博士、研发 18%/自筹 70%、80 人/1500 吨 | 100 分 APPROVE;关键核心类 ≥90 分定额 500 万;明细含「技术先进性: 35 / 35」;依据含「核定资助额度 500 万元」 |
shouldRouteDomesticProjectToReview |
国内领先、0 专利、中试、营收 1200 万、专家 + 1 博士、研发 10%/自筹 50%、30 人/500 吨 | 68 分 REVIEW;定额 0;明细含「团队实力: 11 / 15」;依据含「65-79 分,须组织专家答辩」 |
shouldRejectWeakLabProject |
一般技术、0 专利、实验室、0 营收、普通团队、研发 2%/自筹 15%、2 人/50 吨 | 22 分 REJECT;定额 0;依据含「综合评分未达立项标准(65 分),不予立项」 |
shouldRejectMajorViolationRegardlessOfScore |
满分项目形态但 majorViolation=true |
REJECT(一票否决优先于评分);定额 0;依据含「近三年存在重大违法违规记录,不予立项」 |
shouldHoldForIncompleteMaterials |
国际先进 + 3 专利、量产、营收 5500 万的高分项目但 materialsComplete=false |
MATERIAL_PENDING;定额 0;依据含「申报材料不齐全,须补正后重新提交初审」 |
shouldApproveSocialDevelopmentAtExactThreshold |
社会发展类:国内领先 + 2 专利、中试、6000 万、专家 + 3 博士、16%/70%、60 人/400 吨 | 恰好 80 分 APPROVE(压线立项);社会发展类定额 50 万;明细含「社会效益: 8 / 10」 |
- 评分细则全量外置 XML:鉴定等级分、专利加分、阶段营收线、自筹比例线、80/65 结论线、三类两档资助定额全部在 XML 中,年度申报指南修订只需改脚本并重建代理,政务主系统免发版。
- 「基础分 + 加成 + 维度封顶」在脚本内闭环:技术、产业化、团队三个方法各自在脚本中完成加分与 35/25/分档封顶,加分不会突破维度满分,五维合计天然不超过 100,Service 侧无需再做 clamp。
- 形式审查优先级脚本化:
reviewResult内先判重大违规、再判材料不齐,之后才套分数分档,违规与补正优先于评分结论,杜绝高分违规项目进入立项流程。 - 脚本不支持
!取反,用正向比较替代:材料不齐判定写作materialsComplete == false而不是取反表达式,符合引擎语法约束。 - 定额矩阵独立方法、仅立项挂接:
grantAmountWan以项目类别 × 评分档输出定额,Service 只在 APPROVE 时调用,复评、补正、否决件定额恒为 0,避免提前预设定额。 - 评审依据逐案可审计:五维评分明细、结论分档、定额金额与违规/补正/答辩/驳回五类说明由 Service 统一组装,可直接生成初审意见书,满足财政资金公示与审计要求。
@Param按名绑定 + ASM 代理调用链:7 个规则方法共用一个动态代理;Java 8 无重型依赖,可嵌入存量政务申报系统与国产化配套 JDK 环境。