Case 38: Campus scholarship eligibility
This case corresponds to Scenario 38 in USE-CASES.md (Requirement 48: campus scholarship / merit / grant eligibility evaluation), in the education domain. One rule set handles four application types — national awards, academic scholarships, need-based grants, and individual-achievement scholarships: the rule script owns hard eligibility, three-dimensional scoring, level mapping, and fixed amounts, while the Service backstops policy checks such as hardship-student status and the grant grade floor, outputting the award level, reward amount, additional honors, and rejection reasons.
Industry scenario
Section titled “Industry scenario”Student application fields are carried by ScholarshipApplication: the application type applyType is one of NATIONAL (national awards), ACADEMIC (academic scholarship), NEED_BASED (grant), or INDIVIDUAL (individual-achievement scholarship); academic data includes the weighted average score, grade rank, and total grade size; eligibility data includes failed credits and whether there is a disciplinary record; research and performance data includes competition level (national / provincial / school / none), whether an academic paper has been published, and whether the student is a volunteer star; grant applications additionally require the family economic situation (HARDSHIP extreme hardship / DIFFICULT hardship / NORMAL).
Evaluation runs in two stages: first the hard eligibility gate — any failed credits or a disciplinary record during the assessment period immediately disqualifies the student and no scoring occurs; after passing the gate, a composite evaluation score is computed — academic score (out of 60: averages 95/90/85/80/75 map to 60/54/46/38/28), rank score (out of 25: top 10%/20%/30%/50% of the grade map to 25/20/13/7), and research/competition score (out of 15: national competition or published paper 15, provincial 10, school 5) — the three items sum to the composite total.
Level mapping branches by application type: national applications score 85+ for the National Scholarship (8,000 yuan) and 70+ for the National Endeavor Scholarship (5,000 yuan); academic applications map 85/65/50 to first (3,000), second (2,000), and third (1,000) class; grants are assigned by family economic situation to the extreme-hardship tier (3,000) or the hardship tier (2,000); individual-achievement scholarships award research innovation (national competition or paper, 1,500), provincial competition (800), or volunteer star (500). Students who published a paper and won an award receive the additional “Research Innovation Pace-setter” honor, and volunteer stars receive the eponymous honor.
The Service layer carries three policy backstops beyond the script: the National Endeavor Scholarship is only open to students with family financial hardship — a student who meets the score line but has a NORMAL family situation is re-judged as not awarded, with the reason stated; grants require a non-NORMAL family situation plus a composite score of at least 25; individual-achievement applications with no competition, paper, or volunteer achievement are rejected. The decision result ScholarshipDecision outputs whether the student is eligible, the composite score, the award level, the reward amount, the rejection reason, and the list of additional honors.
Business pain points
Section titled “Business pain points”- Many award programs with different policies: the National Scholarship, National Endeavor Scholarship, three academic tiers, two grant tiers, and three individual-achievement awards share one application entry; amount lines and level lines are complex, and manual review easily assigns the wrong tier or amount.
- Eligibility gates must be front-loaded and non-negotiable: failed courses and disciplinary records are one-vote vetoes that cannot be overridden by high scores; hard gates and scoring must be strictly separated into two stages.
- The same score yields different conclusions under different awards: the National Endeavor Scholarship is limited to hardship students; grants look at both family situation and a minimum grade line; identity conditions cross with score conditions in ways a pure lookup table cannot express.
- Rank-percentage calculation has boundary disputes: top 10% and top 20% must be compared in real time from rank and total headcount, and floating-point division easily causes policy disputes at boundary ranks.
- Policies change frequently and are time-sensitive: award amounts and score lines are adjusted yearly with school policy and fiscal documents; review season is tight, so hard-coded changes are risky.
- Results must be explainable: when a student appeals, reviewers must be able to show the composite score breakdown and the specific rejection reason, not just a final verdict.
Solution architecture
Section titled “Solution architecture”| Component | File | Responsibility |
|---|---|---|
| Input model | model/ScholarshipApplication.java |
Application type, academic data, hard-eligibility flags, competition/paper/volunteer data, and family economic situation |
| Decision model | model/ScholarshipDecision.java |
Eligibility verdict, composite score, award level, amount, rejection reason, additional honors |
| Rule interface | ScholarshipRules.java |
6 rule methods: gating, three-dimensional scoring, level, amount |
| Rule script | resources/demo/scholarship.xml |
Score tiers, rank percentiles, research points, level lines, and amount table all externalized |
| Orchestration service | service/ScholarshipService.java |
Hard-gate short circuit, three-dimension aggregation, hardship/grade-floor/individual-achievement re-checks, honor assembly |
| Tests | ScholarshipServiceTest.java |
6 evaluation shapes covering 12 students across tiers |
Rule method breakdown
Section titled “Rule method breakdown”| Rule method | Inputs (@Param) |
Policy | Returns |
|---|---|---|---|
eligibilityOk |
failedCredits, violation |
Eligible only with no failed credits and no disciplinary record | boolean |
academicScore |
average |
Average ≥95 60 / ≥90 54 / ≥85 46 / ≥80 38 / ≥75 28 / else 0 |
int academic score |
rankScore |
rank, total |
Top 10% 25 / top 20% 20 / top 30% 13 / top 50% 7 / else 0 | int rank score |
researchScore |
competition, paper |
National competition or published paper 15 / provincial competition 10 / school competition 5 / else 0 | int research score |
awardLevel |
type, score, economic, competition, paper, volunteer |
National: ≥85 NATIONAL_SPECIAL, ≥70 NATIONAL_ENDEAVOR; academic: ≥85/≥65/≥50 → first/second/third class; grant: tiered by HARDSHIP/DIFFICULT; individual: national competition or paper INNOVATION, provincial competition COMP_PROVINCIAL, volunteer star VOLUNTEER; otherwise NONE |
String level |
amountOf |
level |
Fixed amount per level: National 8000 / Endeavor 5000 / academic first 3000, second 2000, third 1000 / extreme-hardship grant 3000, hardship 2000 / research innovation 1500 / provincial competition 800 / volunteer 500 | int amount |
Code implementation (complete & runnable)
Section titled “Code implementation (complete & runnable)”Place the following files into the JQuick-Java test project by package structure: the two models into src/test/java/com/github/paohaijiao/demo/model/, the rule interface and test into src/test/java/com/github/paohaijiao/demo/, the Service into demo/service/, and the XML into src/test/resources/demo/. Then run:
mvn test -Dtest=ScholarshipServiceTestScholarshipApplication.java
Section titled “ScholarshipApplication.java”package com.github.paohaijiao.demo.model;
import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;
/** * 学生奖学金/助学金申请要素 */@Data@NoArgsConstructor@AllArgsConstructorpublic class ScholarshipApplication {
/** * 学号 */ private String studentId;
private String studentName;
/** * 申请类型:NATIONAL 国家级奖助 / ACADEMIC 学业奖学金 / * NEED_BASED 助学金 / INDIVIDUAL 单项奖学金 */ private String applyType;
/** * 加权平均分(0-100) */ private int averageScore;
/** * 年级排名 */ private int rankInGrade;
/** * 年级总人数 */ private int gradeTotal;
/** * 不及格学分数 */ private int failedCredits;
/** * 是否有纪律处分 */ private boolean disciplineViolation;
/** * 竞赛级别:NATIONAL 国家级 / PROVINCIAL 省级 / SCHOOL 校级 / NONE 无 */ private String competitionLevel;
/** * 是否发表学术论文 */ private boolean publishedPaper;
/** * 是否志愿服务之星 */ private boolean volunteerStar;
/** * 家庭经济情况:HARDSHIP 特困 / DIFFICULT 困难 / NORMAL 一般 */ private String familyEconomicLevel;}ScholarshipDecision.java
Section titled “ScholarshipDecision.java”package com.github.paohaijiao.demo.model;
import lombok.Data;
import java.util.ArrayList;import java.util.List;
/** * 奖学金评定决策 */@Datapublic class ScholarshipDecision {
/** * 学号 */ private String studentId;
/** * 是否具备参评资格 */ private boolean eligible;
/** * 综合测评得分 */ private int evaluationScore;
/** * 获奖等级: * NATIONAL_SPECIAL 国家奖学金 / NATIONAL_ENDEAVOR 国家励志奖学金 / * FIRST/SECOND/THIRD 学业一/二/三等 / * FIRST_GRANT/SECOND_GRANT 特困/困难助学金 / * INNOVATION 科研创新单项 / COMP_PROVINCIAL 省级竞赛单项 / * VOLUNTEER 志愿服务单项 / NONE 未获奖 */ private String scholarshipLevel;
/** * 奖励金额(元) */ private int rewardAmount;
/** * 退回/落选原因 */ private String rejectReason;
/** * 附加荣誉 */ private List<String> honors = new ArrayList<String>();
public void addHonor(String honor) { this.honors.add(honor); }}ScholarshipRules.java
Section titled “ScholarshipRules.java”package com.github.paohaijiao.demo;
import com.github.paohaijiao.xml.param.Param;
/** * 场景 38:硬资格门控、学业/排名/科研分项与奖助等级规则 */public interface ScholarshipRules {
/** * 参评硬资格:无不及格学分且无纪律处分 */ boolean eligibilityOk(@Param("failedCredits") int failedCredits, @Param("violation") boolean violation);
/** * 成绩分(60 分制):≥95 60 / ≥90 54 / ≥85 46 / ≥80 38 / ≥75 28 / 其余 0 */ int academicScore(@Param("average") int average);
/** * 排名分(25 分制):前 10% 25 / 前 20% 20 / 前 30% 13 / 前 50% 7 / 其余 0 */ int rankScore(@Param("rank") int rank, @Param("total") int total);
/** * 科研竞赛分(15 分制):国家级竞赛或论文 15 / 省级 10 / 校级 5 */ int researchScore(@Param("competition") String competition, @Param("paper") boolean paper);
/** * 奖助等级: * 国奖类 ≥85 国家奖学金、≥70 国家励志(困难生由 Service 二次校验); * 学业类 ≥85 一等、≥65 二等、≥50 三等; * 助学类 HARDSHIP 特困档、DIFFICULT 困难档; * 单项类国家竞赛/论文创新 1500、省级竞赛 800、志愿之星 500 */ String awardLevel(@Param("type") String type, @Param("score") int score, @Param("economic") String economic, @Param("competition") String competition, @Param("paper") boolean paper, @Param("volunteer") boolean volunteer);
/** * 各等级对应金额 */ int amountOf(@Param("level") String level);}scholarship.xml
Section titled “scholarship.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.ScholarshipRules">
<java name="eligibilityOk" returnClass="boolean"> <![CDATA[ boolean def eligibilityOk(int:failedCredits, boolean:violation) { if (failedCredits > 0) { return false; } else if (violation) { return false; } else { return true; } } ]]> </java>
<java name="academicScore" returnClass="int"> <![CDATA[ int def academicScore(int:average) { if (average >= 95) { return 60; } else if (average >= 90) { return 54; } else if (average >= 85) { return 46; } else if (average >= 80) { return 38; } else if (average >= 75) { return 28; } else { return 0; } } ]]> </java>
<java name="rankScore" returnClass="int"> <![CDATA[ int def rankScore(int:rank, int:total) { if (rank * 100 < total * 10) { return 25; } else if (rank * 100 < total * 20) { return 20; } else if (rank * 100 < total * 30) { return 13; } else if (rank * 100 < total * 50) { return 7; } else { return 0; } } ]]> </java>
<java name="researchScore" returnClass="int"> <![CDATA[ int def researchScore(str:competition, boolean:paper) { if (competition == "NATIONAL") { return 15; } else if (paper) { return 15; } else if (competition == "PROVINCIAL") { return 10; } else if (competition == "SCHOOL") { return 5; } else { return 0; } } ]]> </java>
<java name="awardLevel" returnClass="java.lang.String"> <![CDATA[ str def awardLevel(str:type, int:score, str:economic, str:competition, boolean:paper, boolean:volunteer) { if (type == "NATIONAL") { if (score >= 85) { return "NATIONAL_SPECIAL"; } else if (score >= 70) { return "NATIONAL_ENDEAVOR"; } else { return "NONE"; } } else if (type == "ACADEMIC") { if (score >= 85) { return "FIRST"; } else if (score >= 65) { return "SECOND"; } else if (score >= 50) { return "THIRD"; } else { return "NONE"; } } else if (type == "NEED_BASED") { if (economic == "HARDSHIP") { return "FIRST_GRANT"; } else if (economic == "DIFFICULT") { return "SECOND_GRANT"; } else { return "NONE"; } } else { if (competition == "NATIONAL") { return "INNOVATION"; } else if (paper) { return "INNOVATION"; } else if (competition == "PROVINCIAL") { return "COMP_PROVINCIAL"; } else if (volunteer) { return "VOLUNTEER"; } else { return "NONE"; } } } ]]> </java>
<java name="amountOf" returnClass="int"> <![CDATA[ int def amountOf(str:level) { if (level == "NATIONAL_SPECIAL") { return 8000; } else if (level == "NATIONAL_ENDEAVOR") { return 5000; } else if (level == "FIRST") { return 3000; } else if (level == "SECOND") { return 2000; } else if (level == "FIRST_GRANT") { return 3000; } else if (level == "SECOND_GRANT") { return 2000; } else if (level == "INNOVATION") { return 1500; } else if (level == "COMP_PROVINCIAL") { return 800; } else if (level == "VOLUNTEER") { return 500; } else if (level == "THIRD") { return 1000; } else { return 0; } } ]]> </java>
</javas>ScholarshipService.java
Section titled “ScholarshipService.java”package com.github.paohaijiao.demo.service;
import com.github.paohaijiao.demo.ScholarshipRules;import com.github.paohaijiao.demo.model.ScholarshipApplication;import com.github.paohaijiao.demo.model.ScholarshipDecision;
/** * 场景 38:校园奖学金/评优/助学金资格评定 */public class ScholarshipService {
/** * 助学金最低综合测评分(成绩基本要求) */ private static final int NEED_BASED_MIN_SCORE = 25;
private final ScholarshipRules rules;
public ScholarshipService(ScholarshipRules rules) { this.rules = rules; }
public ScholarshipDecision evaluate(ScholarshipApplication a) { ScholarshipDecision d = new ScholarshipDecision(); d.setStudentId(a.getStudentId());
if (!rules.eligibilityOk(a.getFailedCredits(), a.isDisciplineViolation())) { d.setEligible(false); d.setScholarshipLevel("NONE"); d.setRewardAmount(0); if (a.getFailedCredits() > 0) { d.setRejectReason("存在 " + a.getFailedCredits() + " 学分不及格课程,不具备奖助参评资格"); } else { d.setRejectReason("考核期内存在纪律处分,不具备奖助参评资格"); } return d; } d.setEligible(true);
int score = rules.academicScore(a.getAverageScore()) + rules.rankScore(a.getRankInGrade(), a.getGradeTotal()) + rules.researchScore(a.getCompetitionLevel(), a.isPublishedPaper()); d.setEvaluationScore(score);
String level = rules.awardLevel(a.getApplyType(), score, a.getFamilyEconomicLevel(), a.getCompetitionLevel(), a.isPublishedPaper(), a.isVolunteerStar());
if ("NATIONAL_ENDEAVOR".equals(level) && "NORMAL".equals(a.getFamilyEconomicLevel())) { d.setScholarshipLevel("NONE"); d.setRewardAmount(0); d.setRejectReason("综合测评达国家励志奖学金线,但该奖项仅面向家庭经济困难学生"); return d; } if ("NEED_BASED".equals(a.getApplyType())) { if ("NORMAL".equals(a.getFamilyEconomicLevel())) { d.setScholarshipLevel("NONE"); d.setRewardAmount(0); d.setRejectReason("家庭经济情况为一般,不符合助学金申请条件"); return d; } if (score < NEED_BASED_MIN_SCORE) { d.setScholarshipLevel("NONE"); d.setRewardAmount(0); d.setRejectReason("综合测评 " + score + " 分未达助学金成绩基本线 " + NEED_BASED_MIN_SCORE + " 分"); return d; } } if ("INDIVIDUAL".equals(a.getApplyType()) && "NONE".equals(level)) { d.setScholarshipLevel("NONE"); d.setRewardAmount(0); d.setRejectReason("无国家级/省级竞赛、论文或志愿服务之星等单项成果"); return d; }
d.setScholarshipLevel(level); d.setRewardAmount(rules.amountOf(level)); if (a.isPublishedPaper() && !"NONE".equals(level)) { d.addHonor("科研创新标兵"); } if (a.isVolunteerStar()) { d.addHonor("志愿服务之星"); } return d; }}ScholarshipServiceTest.java
Section titled “ScholarshipServiceTest.java”package com.github.paohaijiao.demo;
import com.github.paohaijiao.demo.model.ScholarshipApplication;import com.github.paohaijiao.demo.model.ScholarshipDecision;import com.github.paohaijiao.demo.service.ScholarshipService;import com.github.paohaijiao.xml.JQuickJava;import org.junit.Assert;import org.junit.Before;import org.junit.Test;
/** * 场景 38:校园奖学金/助学金评定 * <p> * 6 组形态:国家奖学金标杆、国家励志(困难生限定)、学业一二三档、 * 助学金困难分级与一般家庭驳回、单项奖学金、挂科/处分资格门控。 */public class ScholarshipServiceTest {
private ScholarshipService service;
@Before public void setUp() { ScholarshipRules rules = JQuickJava.create() .createApi(ScholarshipRules.class, "demo/scholarship.xml"); this.service = new ScholarshipService(rules); }
/** * 均分 96、年级前 1.5%、国赛获奖加论文 → 国家奖学金 8000 */ @Test public void shouldAwardNationalSpecialScholarship() { ScholarshipApplication a = base("S001", "NATIONAL", "NORMAL"); a.setAverageScore(96); a.setRankInGrade(3); a.setGradeTotal(200); a.setCompetitionLevel("NATIONAL"); a.setPublishedPaper(true);
ScholarshipDecision d = service.evaluate(a);
Assert.assertTrue(d.isEligible()); Assert.assertEquals(100, d.getEvaluationScore()); Assert.assertEquals("NATIONAL_SPECIAL", d.getScholarshipLevel()); Assert.assertEquals(8000, d.getRewardAmount()); Assert.assertTrue(d.getHonors().contains("科研创新标兵")); }
/** * 88 分前 12.5% 校赛 → 综合 71 分,困难生获国家励志奖学金 */ @Test public void shouldAwardNationalEndeavorToNeedyStudent() { ScholarshipApplication a = base("S002", "NATIONAL", "DIFFICULT"); a.setAverageScore(88); a.setRankInGrade(25); a.setGradeTotal(200); a.setCompetitionLevel("SCHOOL");
ScholarshipDecision d = service.evaluate(a);
Assert.assertEquals(71, d.getEvaluationScore()); Assert.assertEquals("NATIONAL_ENDEAVOR", d.getScholarshipLevel()); Assert.assertEquals(5000, d.getRewardAmount()); }
/** * 学业奖学金:95 分前 5% 一等、85 分前 15% 二等、80 分前 25% 三等 */ @Test public void shouldAwardAcademicScholarshipByThreeTiers() { ScholarshipApplication first = base("S003", "ACADEMIC", "NORMAL"); first.setAverageScore(95); first.setRankInGrade(10); first.setGradeTotal(200); ScholarshipDecision d1 = service.evaluate(first); Assert.assertEquals(85, d1.getEvaluationScore()); Assert.assertEquals("FIRST", d1.getScholarshipLevel()); Assert.assertEquals(3000, d1.getRewardAmount());
ScholarshipApplication second = base("S004", "ACADEMIC", "NORMAL"); second.setAverageScore(85); second.setRankInGrade(30); second.setGradeTotal(200); ScholarshipDecision d2 = service.evaluate(second); Assert.assertEquals(66, d2.getEvaluationScore()); Assert.assertEquals("SECOND", d2.getScholarshipLevel()); Assert.assertEquals(2000, d2.getRewardAmount());
ScholarshipApplication third = base("S005", "ACADEMIC", "NORMAL"); third.setAverageScore(80); third.setRankInGrade(50); third.setGradeTotal(200); ScholarshipDecision d3 = service.evaluate(third); Assert.assertEquals(51, d3.getEvaluationScore()); Assert.assertEquals("THIRD", d3.getScholarshipLevel()); Assert.assertEquals(1000, d3.getRewardAmount()); }
/** * 助学金:特困 3000、困难 2000、一般家庭驳回 */ @Test public void shouldGrantNeedBasedAidByEconomicLevel() { ScholarshipApplication hardship = base("S006", "NEED_BASED", "HARDSHIP"); hardship.setAverageScore(75); hardship.setRankInGrade(100); hardship.setGradeTotal(200); ScholarshipDecision d1 = service.evaluate(hardship); Assert.assertEquals(28, d1.getEvaluationScore()); Assert.assertEquals("FIRST_GRANT", d1.getScholarshipLevel()); Assert.assertEquals(3000, d1.getRewardAmount());
ScholarshipApplication difficult = base("S007", "NEED_BASED", "DIFFICULT"); difficult.setAverageScore(75); difficult.setRankInGrade(100); difficult.setGradeTotal(200); ScholarshipDecision d2 = service.evaluate(difficult); Assert.assertEquals("SECOND_GRANT", d2.getScholarshipLevel()); Assert.assertEquals(2000, d2.getRewardAmount());
ScholarshipApplication normal = base("S008", "NEED_BASED", "NORMAL"); normal.setAverageScore(90); normal.setRankInGrade(5); normal.setGradeTotal(200); ScholarshipDecision d3 = service.evaluate(normal); Assert.assertEquals("NONE", d3.getScholarshipLevel()); Assert.assertTrue(d3.getRejectReason().contains("不符合助学金申请条件")); }
/** * 单项:国赛创新奖 1500;志愿服务之星 500 */ @Test public void shouldAwardIndividualScholarship() { ScholarshipApplication comp = base("S009", "INDIVIDUAL", "NORMAL"); comp.setCompetitionLevel("NATIONAL"); ScholarshipDecision d1 = service.evaluate(comp); Assert.assertEquals("INNOVATION", d1.getScholarshipLevel()); Assert.assertEquals(1500, d1.getRewardAmount());
ScholarshipApplication volunteer = base("S010", "INDIVIDUAL", "NORMAL"); volunteer.setVolunteerStar(true); ScholarshipDecision d2 = service.evaluate(volunteer); Assert.assertEquals("VOLUNTEER", d2.getScholarshipLevel()); Assert.assertEquals(500, d2.getRewardAmount()); Assert.assertTrue(d2.getHonors().contains("志愿服务之星")); }
/** * 挂科或处分 → 直接丧失参评资格 */ @Test public void shouldRejectFailedOrDisciplinedApplicant() { ScholarshipApplication failed = base("S011", "ACADEMIC", "NORMAL"); failed.setAverageScore(96); failed.setFailedCredits(3); ScholarshipDecision d1 = service.evaluate(failed); Assert.assertFalse(d1.isEligible()); Assert.assertEquals("NONE", d1.getScholarshipLevel()); Assert.assertTrue(d1.getRejectReason().contains("3 学分不及格"));
ScholarshipApplication violated = base("S012", "ACADEMIC", "NORMAL"); violated.setAverageScore(96); violated.setDisciplineViolation(true); ScholarshipDecision d2 = service.evaluate(violated); Assert.assertFalse(d2.isEligible()); Assert.assertTrue(d2.getRejectReason().contains("纪律处分")); }
private ScholarshipApplication base(String id, String type, String economic) { ScholarshipApplication a = new ScholarshipApplication(); a.setStudentId(id); a.setStudentName("测试学生"); a.setApplyType(type); a.setAverageScore(70); a.setRankInGrade(100); a.setGradeTotal(200); a.setFailedCredits(0); a.setDisciplineViolation(false); a.setCompetitionLevel("NONE"); a.setPublishedPaper(false); a.setVolunteerStar(false); a.setFamilyEconomicLevel(economic); return a; }}Run & verified results
Section titled “Run & verified results”Run at the project root:
mvn test -Dtest=ScholarshipServiceTestThe tests cover 6 evaluation shapes with 12 student applications in total; the assertions correspond one-to-one with the source code:
| Test method | Business shape | Key assertions |
|---|---|---|
shouldAwardNationalSpecialScholarship |
Average 96, top 1.5% of grade, national competition plus a paper | Eligible; composite 100 (60+25+15); NATIONAL_SPECIAL; 8000 yuan; honors include “科研创新标兵” |
shouldAwardNationalEndeavorToNeedyStudent |
88 average, top 12.5%, school competition, hardship family | Composite 71 (46+20+5); NATIONAL_ENDEAVOR; 5000 yuan |
shouldAwardAcademicScholarshipByThreeTiers |
Three academic-scholarship students across tiers | 95 avg top 5% → composite 85 → FIRST 3000; 85 avg top 15% → composite 66 → SECOND 2000; 80 avg top 25% → composite 51 → THIRD 1000 |
shouldGrantNeedBasedAidByEconomicLevel |
Three grant applicants: extreme hardship, hardship, normal | Extreme hardship composite 28 → FIRST_GRANT 3000; hardship → SECOND_GRANT 2000; normal family (composite 79) still NONE, reason contains “不符合助学金申请条件” |
shouldAwardIndividualScholarship |
Individual awards: national competition and volunteer star | National competition → INNOVATION 1500; volunteer star → VOLUNTEER 500, honors include “志愿服务之星” |
shouldRejectFailedOrDisciplinedApplicant |
3 failed credits and a disciplinary record | Both eligible=false, level NONE; reasons contain “3 学分不及格” and “纪律处分” respectively |
Technical highlights
Section titled “Technical highlights”- Two-stage evaluation with the hard gate front-loaded: when
eligibilityOkfails, the Service returns immediately without ever computing the composite score — the one-vote veto from failed courses or disciplinary records cannot be overturned by high scores, and the rejection reason differs by which veto was hit. - One method per policy, three independent scoring dimensions: academics, ranking, and research are each a rule method with caps of 60/25/15 that do not interfere; level mapping and fixed amounts are two more methods, so review disputes can be traced dimension by dimension.
- Cross-multiplication for rank percentiles:
rankScoreusesrank * 100 < total * 10/20/30/50to test top 10%/20%/30%/50%, staying in integer comparisons throughout and avoiding boundary-rank errors from the engine’s integer division not promoting to floating point. - Service-side policy re-checks: the Endeavor scholarship’s hardship-only rule, the grant’s family-situation and 25-point grade floor, and the individual award’s no-achievement rejection are all re-judged in the Service on top of the script’s level — the rule script only carries the generic score lines and amount table.
- Externalized rules with hot update: score tiers, rank ratios, and award amounts all live in the XML; when the school adjusts aid policy for the year, editing the script and rebuilding the proxy is enough — no repackaging of the student system during review season.
- Proxy reuse, ASM, Java 8 / domestic-JDK friendly: one
ScholarshipRulesproxy created in@Beforeserves all cases; dynamic proxy + ASM has no heavy container dependencies and embeds directly into educational-administration and student-affairs systems on JDK 8 and domestic (Xinchuang) JDKs.