Case 21: Household & purchase qualification
This case corresponds to USE-CASES scenario 21, “Household / home purchase / car purchase qualification under multiple policies (requirement 16)”. It belongs to the government and enterprise services domain, targeting a unified citizen qualification application portal for human resources, public security, housing, and other departments. It automatically performs five-dimension points tiering for household registration, contribution-year threshold checks for home purchase and the new-energy passenger car quota, and applies a one-vote veto to subjects with serious dishonesty records.
Industry scenario
Section titled “Industry scenario”Citizens apply for three types of public service qualifications through a unified government service portal: household registration (SETTLE), home purchase (HOUSE_PURCHASE), and the new-energy passenger car quota (CAR_PURCHASE). All three share the same set of application elements, sourced from human resources, tax, public security, housing, and other departments: age, education (doctor / master / bachelor / college / other), consecutive months of local social security contributions, consecutive months of local individual income tax payments, whether the applicant already owns a home in the city, whether the applicant already has a local passenger car registered, and whether there is a record of serious dishonesty. Household registration additionally requires a declaration channel (employment EMPLOYEE / investment INVESTMENT / talent introduction TALENT), years since founding the enterprise and last year’s tax payments, and a talent title (high-level talent TOP_TALENT / urgently needed talent ELITE / none).
Household registration uses a points system, scored across five dimensions: education, social security, age, talent title, and investment tax payments, with maximum scores of 40 / 40 / 15 / 30 / 20 respectively; the investment tax score is only counted for the investment channel. The points conclusion has three tiers: 80 points or above approves the registration (APPROVE), 60-79 points goes to manual review with public notice (REVIEW), and anything else is rejected (REJECT). The three household registration channels also have prerequisite conditions: the employment channel requires at least 6 months of social security, the investment channel requires the enterprise to have been founded for at least 1 year with annual tax payments of no less than 100,000 yuan, and the talent introduction channel requires a high-level or urgently needed talent certification.
Home purchase and car purchase do not use points; they set thresholds on consecutive months of social security or income tax contributions, and meeting either one is sufficient: under the standard policy, a non-local-hukou family buying a home needs at least 36 months, relaxed to 3 months for high-level or urgently needed talent; the new-energy passenger car quota requires at least 24 months under the standard policy, relaxed to 6 months for talent, and the applicant must not already have a local passenger car registered; those who do not meet the direct application conditions must join the regular quota lottery. Home purchase is additionally subject to purchase restrictions: non-local-hukou families that already own a home are rejected outright.
Decision results are unified into four conclusions: ALLOW (qualification granted) / APPROVE (household registration approved) / REVIEW (manual review with public notice) / REJECT (not approved), carrying per-dimension scores and threshold check details (details) plus policy explanations (reasons), directly supporting the generation of service window notices and case filing records; a record of serious dishonesty triggers a one-vote veto at the unified entry point for all three matters.
Business pain points
Section titled “Business pain points”- Scattered and frequently changing policy rules: the approval lines 80/60, the segmented social security scoring, the 36-month home purchase and 24-month car purchase thresholds, and the relaxed talent months may all be revised annually with urban population policy; once hard-coded into government systems, every adjustment requires a new release and a new security compliance assessment.
- Three matters share the same elements but use different decision logic: household registration uses multi-dimensional points, home and car purchases use contribution-year thresholds, plus hard constraints such as purchase restrictions and already owning a car; hard-coding the branches into the case workflow makes it easy to mix up policy rules.
- Channel prerequisites must intercept before points calculation: applicants with less than 6 months of employment social security, insufficient enterprise years or tax payments for investment, or no certification for talent must not enter the points calculation; a missed check causes wrongful approvals.
- Hard veto items must be front-loaded and traceable: the one-vote veto for serious dishonesty, the purchase restriction for existing homeowners, and the ban on re-applying for a car quota when already owning a local car must prevent accidents like “approved because the points are enough”.
- Review conclusions must be explainable to citizens item by item: every score, the social security and tax contribution months, and the basis for relaxed thresholds must be traceable and open to reconsideration; black-box decisions cannot meet government disclosure requirements.
- Legacy government systems with domestic-stack and JDK 8 constraints: government clouds mostly run on domestically produced environments, so a heavy standalone rule platform cannot be introduced just for qualification decisions.
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 groups of tests based on real business scenarios.
| Layer | Responsibility | Files |
|---|---|---|
| Domain model | Citizen qualification application elements and review decision result (conclusion / points / details / policy reasons) | CityQualificationApplication.java, CityQualificationDecision.java |
| Rule set and XML | Five-dimension points rules, household registration tiers, home/car contribution-month thresholds and talent relaxations | CityQualificationRules.java, city-qualification.xml |
| Orchestration service | Matter routing, dishonesty one-vote veto, channel thresholds, purchase restriction checks, points aggregation and explanation assembly | CityQualificationService.java |
| Tests | 6 policy scenarios (talent high-score approval / employment low-score rejection / investment borderline review / thresholds and dishonesty / home purchase / car purchase) | CityQualificationServiceTest.java |
Rule method breakdown
Section titled “Rule method breakdown”| Rule method | Rule | Score / output |
|---|---|---|
educationScore |
Education points: doctor 40 / master 30 / bachelor 20 / college 10 / other 0 | 0-40 |
securityScore |
Social security: 10 points per full 12 months: 40 for >=48 months, 30 for >=36, 20 for >=24, 10 for >=12, otherwise 0 | 0-40 |
ageSettleScore |
Age points: 15 for ≤35, 10 for ≤45, 5 for ≤55, otherwise 0 | 0-15 |
titleScore |
Talent title points: TOP_TALENT 30, ELITE 15, otherwise 0 | 0-30 |
investTaxScore |
Enterprise tax paid last year under investment registration: 20 for >=1 million yuan, 10 for >=500k, 5 for >=100k, otherwise 0 | 0-20 |
settleResult |
Household registration tiers: points >=80 APPROVE, >=60 REVIEW, otherwise REJECT | Three conclusions |
houseEligible |
Home purchase eligibility: social security or tax >=36 months; TOP_TALENT / ELITE relaxed to >=3 months | boolean |
carEligible |
New-energy quota: provided no local car is registered, social security or tax >=24 months; talent relaxed to >=6 months | boolean |
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 into src/test/java/com/github/paohaijiao/demo/model/, the rule interface and test class into src/test/java/com/github/paohaijiao/demo/, the Service into src/test/java/com/github/paohaijiao/demo/service/, and the XML script into src/test/resources/demo/, keeping the original package names (com.github.paohaijiao.demo / .model / .service). Then run mvn test -Dtest=CityQualificationServiceTest to execute all 6 test cases.
CityQualificationApplication.java
Section titled “CityQualificationApplication.java”package com.github.paohaijiao.demo.model;
import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;
/** * 市民资格申请要素(落户 / 购房 / 购车三类政策入口共用) */@Data@NoArgsConstructor@AllArgsConstructorpublic class CityQualificationApplication {
/** * 申请单号 */ private String applicationId;
/** * 申请人姓名 */ private String applicantName;
/** * 申请事项:SETTLE-落户 / HOUSE_PURCHASE-购房 / CAR_PURCHASE-购车 */ private String benefit;
/** * 年龄 */ private int age;
/** * 学历:DOCTOR / MASTER / BACHELOR / COLLEGE / OTHER */ private String education;
/** * 本市社保连续缴纳月数 */ private int socialSecurityMonths;
/** * 本市个税连续缴纳月数 */ private int taxPaymentMonths;
/** * 在本市是否已拥有住房 */ private boolean hasPropertyInCity;
/** * 名下是否已有本市小客车 */ private boolean hasCarInCity;
/** * 落户通道:EMPLOYEE-就业 / INVESTMENT-投资 / TALENT-人才引进 */ private String channel;
/** * 创办企业年限(投资落户) */ private int companyYears;
/** * 创办企业上年纳税额(万元,投资落户) */ private double enterpriseTaxWan;
/** * 人才称号:TOP_TALENT-高层次人才 / ELITE-紧缺人才 / NONE-无 */ private String talentTitle;
/** * 是否存在严重失信记录 */ private boolean badCreditRecord;}CityQualificationDecision.java
Section titled “CityQualificationDecision.java”package com.github.paohaijiao.demo.model;
import lombok.Data;
import java.util.ArrayList;import java.util.List;
/** * 市民资格审核决策结果(落户积分 / 购房购车门槛共用) */@Datapublic class CityQualificationDecision {
/** * 申请单号 */ private String applicationId;
/** * 申请事项 */ private String benefit;
/** * 审核结论:ALLOW-资格通过 / APPROVE-核准落户 / REVIEW-复核公示 / REJECT-不通过 */ private String result;
/** * 落户积分(仅落户事项计分,其余事项为 0) */ private int points;
/** * 各项得分与门槛核验明细 */ private List<String> details = new ArrayList<String>();
/** * 不通过原因与政策说明 */ private List<String> reasons = new ArrayList<String>();
public void addDetail(String detail) { this.details.add(detail); }
public void addReason(String reason) { this.reasons.add(reason); }}CityQualificationRules.java
Section titled “CityQualificationRules.java”package com.github.paohaijiao.demo;
import com.github.paohaijiao.xml.param.Param;
/** * 场景 21:落户 / 购房 / 购车资格多政策判定规则集 * <p> * 落户按学历、社保、年龄、人才称号、投资纳税五维积分(80 核准 / 60 复核); * 购房购车按社保或个税连续缴纳月数设置门槛,人才通道缩短年限。 * 规则脚本见 {@code src/test/resources/demo/city-qualification.xml}。 */public interface CityQualificationRules {
/** 学历积分:博士 40 / 硕士 30 / 本科 20 / 大专 10 / 其他 0 */ int educationScore(@Param("education") String education);
/** 社保积分:每满 12 个月 10 分,48 个月封顶 40 分 */ int securityScore(@Param("months") int months);
/** 年龄积分:35 岁以下 15 / 45 岁以下 10 / 55 岁以下 5 */ int ageSettleScore(@Param("age") int age);
/** 人才称号积分:高层次 30 / 紧缺 15 / 无 0 */ int titleScore(@Param("talentTitle") String talentTitle);
/** 投资纳税积分:年纳税 100/50/10 万对应 20/10/5 分 */ int investTaxScore(@Param("enterpriseTaxWan") double enterpriseTaxWan);
/** 落户结论:80 分以上核准,60 分以上复核,其余不通过 */ String settleResult(@Param("points") int points);
/** 购房资格:社保或个税满 36 个月;高层次/紧缺人才放宽至 3 个月 */ boolean houseEligible(@Param("ssMonths") int ssMonths, @Param("taxMonths") int taxMonths, @Param("talentTitle") String talentTitle);
/** 购车(新能源指标)资格:普通满 24 个月、人才满 6 个月,且名下无本市小客车 */ boolean carEligible(@Param("ssMonths") int ssMonths, @Param("taxMonths") int taxMonths, @Param("noCarInCity") boolean noCarInCity, @Param("talentTitle") String talentTitle);}city-qualification.xml
Section titled “city-qualification.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.CityQualificationRules">
<!-- 学历积分 --> <java name="educationScore" returnClass="int"> <![CDATA[ int def educationScore(str:education) { if (education == "DOCTOR") { return 40; } else if (education == "MASTER") { return 30; } else if (education == "BACHELOR") { return 20; } else if (education == "COLLEGE") { return 10; } else { return 0; } } ]]> </java>
<!-- 社保积分:分段等价于每满 12 个月 10 分,48 月封顶 --> <java name="securityScore" returnClass="int"> <![CDATA[ int def securityScore(int:months) { if (months >= 48) { return 40; } else if (months >= 36) { return 30; } else if (months >= 24) { return 20; } else if (months >= 12) { return 10; } else { return 0; } } ]]> </java>
<!-- 年龄积分 --> <java name="ageSettleScore" returnClass="int"> <![CDATA[ int def ageSettleScore(int:age) { if (age <= 35) { return 15; } else if (age <= 45) { return 10; } else if (age <= 55) { return 5; } else { return 0; } } ]]> </java>
<!-- 人才称号积分 --> <java name="titleScore" returnClass="int"> <![CDATA[ int def titleScore(str:talentTitle) { if (talentTitle == "TOP_TALENT") { return 30; } else if (talentTitle == "ELITE") { return 15; } else { return 0; } } ]]> </java>
<!-- 投资纳税积分 --> <java name="investTaxScore" returnClass="int"> <![CDATA[ int def investTaxScore(double:enterpriseTaxWan) { if (enterpriseTaxWan >= 100.0) { return 20; } else if (enterpriseTaxWan >= 50.0) { return 10; } else if (enterpriseTaxWan >= 10.0) { return 5; } else { return 0; } } ]]> </java>
<!-- 落户结论分档 --> <java name="settleResult" returnClass="java.lang.String"> <![CDATA[ str def settleResult(int:points) { if (points >= 80) { return "APPROVE"; } else if (points >= 60) { return "REVIEW"; } else { return "REJECT"; } } ]]> </java>
<!-- 购房资格:普通 36 月,人才 3 月(社保、个税满足其一即可) --> <java name="houseEligible" returnClass="boolean"> <![CDATA[ boolean def houseEligible(int:ssMonths, int:taxMonths, str:talentTitle) { boolean eligible = false; if (ssMonths >= 36 || taxMonths >= 36) { eligible = true; } if (talentTitle == "TOP_TALENT") { if (ssMonths >= 3 || taxMonths >= 3) { eligible = true; } } if (talentTitle == "ELITE") { if (ssMonths >= 3 || taxMonths >= 3) { eligible = true; } } return eligible; } ]]> </java>
<!-- 购车新能源指标:普通 24 月,人才 6 月,且名下无车 --> <java name="carEligible" returnClass="boolean"> <![CDATA[ boolean def carEligible(int:ssMonths, int:taxMonths, boolean:noCarInCity, str:talentTitle) { boolean eligible = false; if (noCarInCity) { if (ssMonths >= 24 || taxMonths >= 24) { eligible = true; } if (talentTitle == "TOP_TALENT") { if (ssMonths >= 6 || taxMonths >= 6) { eligible = true; } } if (talentTitle == "ELITE") { if (ssMonths >= 6 || taxMonths >= 6) { eligible = true; } } } return eligible; } ]]> </java>
</javas>CityQualificationService.java
Section titled “CityQualificationService.java”package com.github.paohaijiao.demo.service;
import com.github.paohaijiao.demo.CityQualificationRules;import com.github.paohaijiao.demo.model.CityQualificationApplication;import com.github.paohaijiao.demo.model.CityQualificationDecision;
/** * 场景 21:落户 / 购房 / 购车资格多政策判定服务 * <p> * 职责划分: * <ul> * <li><b>Java 侧</b>:事项路由、失信一票否决、落户通道门槛、限购核验、积分汇总</li> * <li><b>规则侧</b>:五维积分口径、落户分档线、购房购车缴纳月数门槛,全部外置在 XML 中</li> * </ul> */public class CityQualificationService {
private final CityQualificationRules rules;
public CityQualificationService(CityQualificationRules rules) { this.rules = rules; }
public CityQualificationDecision evaluate(CityQualificationApplication app) { CityQualificationDecision decision = new CityQualificationDecision(); decision.setApplicationId(app.getApplicationId()); decision.setBenefit(app.getBenefit());
if (app.isBadCreditRecord()) { decision.setResult("REJECT"); decision.addReason("申请人存在严重失信记录,触发一票否决"); return decision; }
if ("SETTLE".equals(app.getBenefit())) { evaluateSettle(app, decision); } else if ("HOUSE_PURCHASE".equals(app.getBenefit())) { evaluateHouse(app, decision); } else { evaluateCar(app, decision); } return decision; }
private void evaluateSettle(CityQualificationApplication app, CityQualificationDecision decision) { if ("EMPLOYEE".equals(app.getChannel()) && app.getSocialSecurityMonths() < 6) { decision.setResult("REJECT"); decision.addReason("就业落户人员在本市连续缴纳社保不满 6 个月,不符合通道基本条件"); return; } if ("INVESTMENT".equals(app.getChannel()) && (app.getCompanyYears() < 1 || app.getEnterpriseTaxWan() < 10.0)) { decision.setResult("REJECT"); decision.addReason("投资落户须创办企业满 1 年且年纳税不低于 10 万元"); return; } if ("TALENT".equals(app.getChannel()) && "NONE".equals(app.getTalentTitle())) { decision.setResult("REJECT"); decision.addReason("人才引进通道须持有高层次或紧缺人才认定证明"); return; }
int edu = rules.educationScore(app.getEducation()); int security = rules.securityScore(app.getSocialSecurityMonths()); int age = rules.ageSettleScore(app.getAge()); int title = rules.titleScore(app.getTalentTitle()); int invest = "INVESTMENT".equals(app.getChannel()) ? rules.investTaxScore(app.getEnterpriseTaxWan()) : 0; int points = edu + security + age + title + invest;
decision.addDetail("学历积分: " + edu + " / 40"); decision.addDetail("社保积分: " + security + " / 40"); decision.addDetail("年龄积分: " + age + " / 15"); decision.addDetail("人才称号积分: " + title + " / 30"); decision.addDetail("投资纳税积分: " + invest + " / 20"); decision.addDetail("落户总积分: " + points); decision.setPoints(points);
String result = rules.settleResult(points); decision.setResult(result); if ("APPROVE".equals(result)) { decision.addReason("积分 " + points + " 分达到核准线(80 分),准予落户"); } else if ("REVIEW".equals(result)) { decision.addReason("积分 " + points + " 分处于复核公示区间(60-79 分),转人工复核"); } else { decision.addReason("积分 " + points + " 分未达落户标准(60 分)"); } }
private void evaluateHouse(CityQualificationApplication app, CityQualificationDecision decision) { if (app.isHasPropertyInCity()) { decision.setResult("REJECT"); decision.addReason("非本市户籍家庭在本市已拥有 1 套住房,受限购政策约束"); return; } boolean eligible = rules.houseEligible(app.getSocialSecurityMonths(), app.getTaxPaymentMonths(), app.getTalentTitle()); decision.addDetail("社保连续缴纳: " + app.getSocialSecurityMonths() + " 个月"); decision.addDetail("个税连续缴纳: " + app.getTaxPaymentMonths() + " 个月"); if (eligible) { decision.setResult("ALLOW"); decision.addReason("满足非本市户籍家庭购房缴纳年限要求,具备购房资格"); } else { decision.setResult("REJECT"); decision.addReason("社保或个税连续缴纳不满 36 个月,且不符合人才放宽条件"); } }
private void evaluateCar(CityQualificationApplication app, CityQualificationDecision decision) { if (app.isHasCarInCity()) { decision.setResult("REJECT"); decision.addReason("申请人名下已登记本市小客车,不能重复申领新能源指标"); return; } boolean eligible = rules.carEligible(app.getSocialSecurityMonths(), app.getTaxPaymentMonths(), true, app.getTalentTitle()); decision.addDetail("社保连续缴纳: " + app.getSocialSecurityMonths() + " 个月"); decision.addDetail("个税连续缴纳: " + app.getTaxPaymentMonths() + " 个月"); if (eligible) { decision.setResult("ALLOW"); decision.addReason("满足新能源小客车指标申请条件,准予核发指标"); } else { decision.setResult("REJECT"); decision.addReason("不满足新能源指标直接申请条件,需参与普通指标摇号"); } }}CityQualificationServiceTest.java
Section titled “CityQualificationServiceTest.java”package com.github.paohaijiao.demo;
import com.github.paohaijiao.demo.model.CityQualificationApplication;import com.github.paohaijiao.demo.model.CityQualificationDecision;import com.github.paohaijiao.demo.service.CityQualificationService;import com.github.paohaijiao.xml.JQuickJava;import org.junit.Assert;import org.junit.Before;import org.junit.Test;
/** * 场景 21:落户 / 购房 / 购车资格多政策判定 * <p> * 覆盖 6 组政策形态:人才通道高分落户、就业通道积分不足、投资通道临界复核、 * 通道门槛与失信否决、购房年限与限购、购车普通与人才通道。 */public class CityQualificationServiceTest {
private CityQualificationService service;
@Before public void setUp() { CityQualificationRules rules = JQuickJava.create() .createApi(CityQualificationRules.class, "demo/city-qualification.xml"); this.service = new CityQualificationService(rules); }
/** * 博士高层次人才走人才引进通道,30 岁社保 12 月 → 95 分核准落户 */ @Test public void shouldApproveTopTalentSettlement() { CityQualificationApplication app = settleApplication("CQ2026001", "TALENT"); app.setAge(30); app.setEducation("DOCTOR"); app.setSocialSecurityMonths(12); app.setTalentTitle("TOP_TALENT");
CityQualificationDecision decision = service.evaluate(app);
Assert.assertEquals(95, decision.getPoints()); Assert.assertEquals("APPROVE", decision.getResult()); Assert.assertTrue(decision.getDetails().contains("人才称号积分: 30 / 30")); Assert.assertTrue(decision.getReasons().contains( "积分 95 分达到核准线(80 分),准予落户")); }
/** * 本科就业人员 40 岁社保 24 月无称号 → 50 分,不满足落户线 */ @Test public void shouldRejectLowPointsEmployeeSettlement() { CityQualificationApplication app = settleApplication("CQ2026002", "EMPLOYEE"); app.setAge(40); app.setEducation("BACHELOR"); app.setSocialSecurityMonths(24); app.setTalentTitle("NONE");
CityQualificationDecision decision = service.evaluate(app);
Assert.assertEquals(50, decision.getPoints()); Assert.assertEquals("REJECT", decision.getResult()); Assert.assertTrue(decision.getReasons().contains("积分 50 分未达落户标准(60 分)")); }
/** * 硕士企业主 42 岁,企业 3 年纳税 80 万 → 60 分恰入复核区间 */ @Test public void shouldReviewInvestmentSettlementAtThreshold() { CityQualificationApplication app = settleApplication("CQ2026003", "INVESTMENT"); app.setAge(42); app.setEducation("MASTER"); app.setSocialSecurityMonths(12); app.setTalentTitle("NONE"); app.setCompanyYears(3); app.setEnterpriseTaxWan(80.0);
CityQualificationDecision decision = service.evaluate(app);
Assert.assertEquals(60, decision.getPoints()); Assert.assertEquals("REVIEW", decision.getResult()); Assert.assertTrue(decision.getDetails().contains("投资纳税积分: 10 / 20")); Assert.assertTrue(decision.getReasons().contains( "积分 60 分处于复核公示区间(60-79 分),转人工复核")); }
/** * 通道门槛核验与失信一票否决 */ @Test public void shouldRejectChannelThresholdAndBadCredit() { CityQualificationApplication shortSocialSecurity = settleApplication("CQ2026004", "EMPLOYEE"); shortSocialSecurity.setAge(30); shortSocialSecurity.setEducation("MASTER"); shortSocialSecurity.setSocialSecurityMonths(3); CityQualificationDecision d1 = service.evaluate(shortSocialSecurity); Assert.assertEquals("REJECT", d1.getResult()); Assert.assertTrue(d1.getReasons().contains( "就业落户人员在本市连续缴纳社保不满 6 个月,不符合通道基本条件"));
CityQualificationApplication badCredit = settleApplication("CQ2026005", "TALENT"); badCredit.setAge(32); badCredit.setEducation("DOCTOR"); badCredit.setSocialSecurityMonths(48); badCredit.setTalentTitle("TOP_TALENT"); badCredit.setBadCreditRecord(true); CityQualificationDecision d2 = service.evaluate(badCredit); Assert.assertEquals("REJECT", d2.getResult()); Assert.assertTrue(d2.getReasons().contains("申请人存在严重失信记录,触发一票否决"));
CityQualificationApplication noTitle = settleApplication("CQ2026006", "TALENT"); noTitle.setAge(28); noTitle.setEducation("DOCTOR"); noTitle.setSocialSecurityMonths(36); noTitle.setTalentTitle("NONE"); CityQualificationDecision d3 = service.evaluate(noTitle); Assert.assertEquals("REJECT", d3.getResult()); Assert.assertTrue(d3.getReasons().contains("人才引进通道须持有高层次或紧缺人才认定证明")); }
/** * 购房资格:社保满 36 月无房通过;已有房产被限购;缴纳不足被拒 */ @Test public void shouldCheckHousePurchaseEligibilityAndLimit() { CityQualificationApplication eligible = houseApplication("CQ2026007"); eligible.setSocialSecurityMonths(40); CityQualificationDecision d1 = service.evaluate(eligible); Assert.assertEquals("ALLOW", d1.getResult()); Assert.assertTrue(d1.getReasons().contains("满足非本市户籍家庭购房缴纳年限要求,具备购房资格"));
CityQualificationApplication ownsProperty = houseApplication("CQ2026008"); ownsProperty.setSocialSecurityMonths(60); ownsProperty.setHasPropertyInCity(true); CityQualificationDecision d2 = service.evaluate(ownsProperty); Assert.assertEquals("REJECT", d2.getResult()); Assert.assertTrue(d2.getReasons().contains( "非本市户籍家庭在本市已拥有 1 套住房,受限购政策约束"));
CityQualificationApplication insufficient = houseApplication("CQ2026009"); insufficient.setSocialSecurityMonths(12); CityQualificationDecision d3 = service.evaluate(insufficient); Assert.assertEquals("REJECT", d3.getResult()); Assert.assertTrue(d3.getReasons().contains( "社保或个税连续缴纳不满 36 个月,且不符合人才放宽条件")); }
/** * 购车指标:普通通道 24 月通过、12 月进入摇号;紧缺人才 8 月走快速通道 */ @Test public void shouldCheckCarQuotaForNormalAndTalentChannels() { CityQualificationApplication normal = carApplication("CQ2026010"); normal.setSocialSecurityMonths(24); Assert.assertEquals("ALLOW", service.evaluate(normal).getResult());
CityQualificationApplication tooShort = carApplication("CQ2026011"); tooShort.setSocialSecurityMonths(12); CityQualificationDecision d2 = service.evaluate(tooShort); Assert.assertEquals("REJECT", d2.getResult()); Assert.assertTrue(d2.getReasons().contains("不满足新能源指标直接申请条件,需参与普通指标摇号"));
CityQualificationApplication elite = carApplication("CQ2026012"); elite.setSocialSecurityMonths(8); elite.setTalentTitle("ELITE"); Assert.assertEquals("ALLOW", service.evaluate(elite).getResult());
CityQualificationApplication alreadyHasCar = carApplication("CQ2026013"); alreadyHasCar.setSocialSecurityMonths(36); alreadyHasCar.setHasCarInCity(true); CityQualificationDecision d4 = service.evaluate(alreadyHasCar); Assert.assertEquals("REJECT", d4.getResult()); Assert.assertTrue(d4.getReasons().contains("申请人名下已登记本市小客车,不能重复申领新能源指标")); }
private CityQualificationApplication settleApplication(String id, String channel) { CityQualificationApplication app = new CityQualificationApplication(); app.setApplicationId(id); app.setApplicantName("申请人"); app.setBenefit("SETTLE"); app.setChannel(channel); app.setAge(30); app.setEducation("OTHER"); app.setSocialSecurityMonths(0); app.setTaxPaymentMonths(0); app.setHasPropertyInCity(false); app.setHasCarInCity(false); app.setCompanyYears(0); app.setEnterpriseTaxWan(0.0); app.setTalentTitle("NONE"); app.setBadCreditRecord(false); return app; }
private CityQualificationApplication houseApplication(String id) { CityQualificationApplication app = settleApplication(id, "EMPLOYEE"); app.setBenefit("HOUSE_PURCHASE"); return app; }
private CityQualificationApplication carApplication(String id) { CityQualificationApplication app = settleApplication(id, "EMPLOYEE"); app.setBenefit("CAR_PURCHASE"); return app; }}Run & verified results
Section titled “Run & verified results”Run from the project root:
mvn test -Dtest=CityQualificationServiceTestTest coverage
Section titled “Test coverage”| Test method | Business scenario constructed | Key assertions / expected decision |
|---|---|---|
shouldApproveTopTalentSettlement |
Doctorate, age 30, 12 months of social security, TOP_TALENT via the talent channel | 95 points APPROVE; details contain “人才称号积分: 30 / 30”; reasons contain “积分 95 分达到核准线(80 分),准予落户” |
shouldRejectLowPointsEmployeeSettlement |
Bachelor’s degree, age 40, 24 months of social security, no title, employment channel | 50 points REJECT; reasons contain “积分 50 分未达落户标准(60 分)” |
shouldReviewInvestmentSettlementAtThreshold |
Master’s business owner, age 42, 12 months of social security, 3-year-old company, 800k yuan tax paid, investment channel | 60 points, exactly in the review range (REVIEW); details contain “投资纳税积分: 10 / 20”; reasons contain “60-79 分,转人工复核” |
shouldRejectChannelThresholdAndBadCredit |
Three sub-scenarios: master’s employment with only 3 months of social security; doctorate talent with 48 months but a dishonesty record; doctorate talent with 36 months but no title | All REJECT; hitting respectively the 6-month employment channel threshold, the “严重失信记录,触发一票否决” veto, and the talent channel certification threshold |
shouldCheckHousePurchaseEligibilityAndLimit |
Three sub-scenarios: 40 months of social security with no home; 60 months but already owns a home; 12 months | ALLOW / REJECT (purchase restriction) / REJECT (less than 36 months and not talent) respectively |
shouldCheckCarQuotaForNormalAndTalentChannels |
Four sub-scenarios: 24 months standard; 12 months standard; ELITE with 8 months; 36 months but already owns a car | ALLOW / REJECT (join the lottery) / ALLOW (talent relaxed to 6 months) / REJECT (no repeat applications) respectively |
Technical highlights
Section titled “Technical highlights”- All policy thresholds externalized to XML: education scores, social security tiers, age brackets, tax brackets, the 80/60 tier lines, and the 36/24/6/3-month thresholds all live in the script; a city policy revision only requires adjusting the XML and rebuilding the proxy, with no release or renewed security assessment for the main government system.
- One method per policy rule, independently tunable and auditable: the 8 rule methods each encapsulate a single policy rule; whether the investment tax score is counted is decided by the Service per channel, and the script has no notion of channels, keeping responsibilities cleanly separated.
- Hard vetoes and channel thresholds short-circuit on the Java side: the dishonesty one-vote veto runs first at the unified entry; the three channel prerequisites, the purchase restriction, and the existing-car check all short-circuit before points or threshold evaluation, eliminating “high-score but non-compliant approvals”.
- Boolean flag accumulation fits the script syntax:
houseEligible/carEligiblestart withboolean eligible = falseand progressively set it to true across the standard rule and the two talent tiers, closing the “either social security or tax” plus talent relaxation logic inside a single script method. - Decision details and policy reasons recorded per case: the five-dimension scores (with their maximum denominators), contribution months, and approval/review/rejection rules are assembled uniformly by the Service and can be printed directly on the service window notice, supporting administrative reconsideration and audits.
@Paramname binding + ASM dynamic proxy: the 8 rule methods share a single proxy instance; Java 8 with no heavy dependencies, embeddable into legacy government case-handling systems and domestically produced JDK environments.