跳转到内容

案例 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
规则方法 口径 分值/输出
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 万元定额

将下列文件按源码包结构放入 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 个用例。

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;
}
}

在工程根目录执行:

Terminal window
mvn test -Dtest=TechProjectReviewServiceTest
测试方法 构造的业务形态 关键断言/期望决策
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 环境。