Skip to content

Case 6: Performance grade & bonus coefficient

This case corresponds to Scenario 6: Performance grade & bonus coefficient in the JQuick-Java repository’s USE-CASES.md. It belongs to the human resources domain and solves the integrated decision chain inside an appraisal cycle: “four-dimension weighted scoring → S/A/B/C/D grade mapping → bonus coefficient adjustment → bonus amount / promotion eligibility / PIP determination”.

In an annual or quarterly performance appraisal cycle, HR first aggregates four kinds of employee scores: KPI score, project score, teamwork score, and attendance score (all 0-100), weighted into a composite score with fixed weights of KPI 50%, project 20%, teamwork 15%, and attendance 15%.

The composite score maps to five performance grades S/A/B/C/D, each with a base bonus coefficient: S is 1.8, A is 1.4, B is 1.1, C is 0.8, D is 0.5. On top of the base coefficient, three adjustments are stacked: a manager responsibility bonus of +0.2, a sales incentive (grade S/A with annual sales reaching 500k adds 0.2; sales reaching 200k adds 0.1), and a violation deduction of -0.3. The final coefficient multiplied by the monthly base salary yields the bonus amount.

The same decision chain also produces in parallel: promotion eligibility (total score no less than 85 and no violation record), whether the employee is forced into a PIP performance improvement plan, the attendance tier (scores 95/90/80 map to tiers 3/2/1/0), and an employee-facing remark list (excellence pool entry, violation deduction explanation, attendance warning, sales incentive explanation, etc.).

These outputs directly drive bonus payout, annual promotion pool screening, and PIP follow-up. Scenario 6 covers 6 real employee profiles: excellent sales (composite 92.65, grade S, coefficient 2.0), low performer with violation (51.25, grade D, coefficient 0.2), manager grade A (coefficient 1.7), average grade B (coefficient 1.2), underperformer grade D (coefficient 0.5), and an excellent employee whose promotion is blocked by a violation.

  • Appraisal weights 50%/20%/15%/15%, S/A/B/C/D score lines, and base coefficients 1.8/1.4/1.1/0.8/0.5 are scattered across the payroll module, Excel templates, and hard-coded logic; every annual policy change requires coordinating multiple redeployments.
  • Manager bonus, sales incentive (two tiers at 500k/200k), and violation deduction are entangled in the same if-else block; changing one tier easily misses linked logic.
  • Promotion eligibility (“total score 85 and no violation”) and the PIP entry are coupled with bonus calculation in business code, so HR cannot tune parameters or run simulations independently.
  • Stacked coefficients can theoretically go negative, and each system lacks a unified floor guard; decimal conventions for amounts (two digits, half-up rounding) are also inconsistent.
  • Decisions often return only a bonus number; when employees challenge the result, it is hard to trace the composite score, grade, and every bonus or deduction.
  • Many eHR and payroll systems still run on JDK 8 or domestic IT-innovation environments, making it hard to introduce heavy rule platforms such as Drools.

This case uses the standard “six-piece set”: the input model and decision result model carry the data, the rule interface declares the calculation policies, the XML script externalizes all thresholds and coefficients, the Service handles aggregation and floor protection, and JUnit regresses all decision paths with 6 employee profiles.

Layer Responsibility Files
Domain model Performance appraisal snapshot, bonus decision result PerformanceRecord.java, BonusDecision.java
Rule set & XML 10 rule methods covering weighting, grading, coefficient, three adjustments, promotion, and PIP PerformanceRules.java, performance.xml
Orchestration service Coefficient aggregation with floor protection, two-decimal amounts, promotion line review, remark assembly PerformanceService.java
Tests 6 employee profiles (excellent / low performer with violation / manager / average / underperformer / excellent with violation) PerformanceServiceTest.java
Rule method Policy Score/output
weightedScore KPI×50% + project×20% + teamwork×15% + attendance×15% 0-100 composite score
grade ≥90 S / ≥80 A / ≥70 B / ≥60 C / otherwise D S/A/B/C/D
baseCoefficient S 1.8 / A 1.4 / B 1.1 / C 0.8 / D 0.5 Base coefficient
managerBonus Manager +0.2, otherwise 0 +0.2 / 0
salesBonus S/A with sales ≥500k +0.2; sales ≥200k +0.1; otherwise 0 +0.2 / +0.1 / 0
violationPenalty Violation -0.3, otherwise 0 -0.3 / 0
promotionEligible Eligible only when total score ≥85 and no violation (violation veto first) true / false
needPip Total score below 60, or below 70 with violation → enter PIP true / false
attendanceLevel Attendance ≥95 → 3 / ≥90 → 2 / ≥80 → 1 / otherwise 0 Tier 0-3
bonusAmount Monthly base salary × final bonus coefficient Bonus amount (CNY)

Place the six files into a Maven project following the repository’s original package structure: the two models go into src/test/java/com/github/paohaijiao/demo/model/, the rule interface, orchestration service, and test go into the com.github.paohaijiao.demo and com.github.paohaijiao.demo.service packages, and the XML script goes into src/test/resources/demo/performance.xml; keep all package names exactly as in the source. Run mvn test -Dtest=PerformanceServiceTest at the project root to execute.

package com.github.paohaijiao.demo.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 绩效考核记录(绩效与奖金核算所需的考核快照)
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PerformanceRecord {
/**
* 工号
*/
private String employeeId;
/**
* 姓名
*/
private String employeeName;
/**
* 部门
*/
private String dept;
/**
* KPI 得分(0-100)
*/
private double kpiScore;
/**
* 项目得分(0-100)
*/
private double projectScore;
/**
* 团队协作风得分(0-100)
*/
private double teamScore;
/**
* 考勤得分(0-100)
*/
private double attendanceScore;
/**
* 是否管理者
*/
private boolean manager;
/**
* 是否发生违规
*/
private boolean hasViolation;
/**
* 年度销售额(元)
*/
private double salesAmount;
/**
* 月度基本工资(元)
*/
private double baseSalary;
}
package com.github.paohaijiao.demo.model;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* 绩效奖金决策结果
*/
@Data
public class BonusDecision {
/**
* 工号
*/
private String employeeId;
/**
* 加权总分(0-100)
*/
private double totalScore;
/**
* 绩效等级:S / A / B / C / D
*/
private String grade;
/**
* 最终奖金系数
*/
private double bonusCoefficient;
/**
* 奖金金额(元)
*/
private double bonusAmount;
/**
* 考勤等级:0-3
*/
private int attendanceLevel;
/**
* 是否具备晋升资格
*/
private boolean promotionEligible;
/**
* 是否进入绩效改进计划
*/
private boolean needPip;
/**
* 说明与建议
*/
private List<String> remarks = new ArrayList<String>();
public void addRemark(String remark) {
this.remarks.add(remark);
}
}
package com.github.paohaijiao.demo;
import com.github.paohaijiao.xml.param.Param;
/**
* 场景 6(生产级样板):绩效等级与奖金系数规则集
* <p>
* 规则按「加权评分 → 等级映射 → 系数修正 → 奖金核算」拆分:
* <ul>
* <li>评分类:KPI / 项目 / 协作 / 考勤四维加权</li>
* <li>修正类:管理者加成、销售激励、违规扣减</li>
* <li>决策类:晋升资格、绩效改进、奖金金额</li>
* </ul>
* 规则脚本见 {@code src/test/resources/demo/performance.xml}。
*/
public interface PerformanceRules {
/**
* 加权总分:KPI 50% + 项目 20% + 协作 15% + 考勤 15%
*/
double weightedScore(@Param("kpiScore") double kpiScore,
@Param("projectScore") double projectScore,
@Param("teamScore") double teamScore,
@Param("attendanceScore") double attendanceScore);
/**
* 绩效等级:S / A / B / C / D
*/
String grade(@Param("totalScore") double totalScore);
/**
* 等级基准奖金系数
*/
double baseCoefficient(@Param("grade") String grade);
/**
* 管理者加成
*/
double managerBonus(@Param("manager") boolean manager);
/**
* 销售激励:高等级高销售额额外加成
*/
double salesBonus(@Param("salesAmount") double salesAmount,
@Param("grade") String grade);
/**
* 违规扣减
*/
double violationPenalty(@Param("hasViolation") boolean hasViolation);
/**
* 是否具备晋升资格
*/
boolean promotionEligible(@Param("totalScore") double totalScore,
@Param("hasViolation") boolean hasViolation);
/**
* 是否进入绩效改进计划
*/
boolean needPip(@Param("totalScore") double totalScore,
@Param("hasViolation") boolean hasViolation);
/**
* 考勤等级:>=95 为 3,>=90 为 2,>=80 为 1,否则 0
*/
int attendanceLevel(@Param("attendanceScore") double attendanceScore);
/**
* 奖金金额 = 基本工资 × 最终系数
*/
double bonusAmount(@Param("baseSalary") double baseSalary,
@Param("bonusCoefficient") double bonusCoefficient);
}
<?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.PerformanceRules">
<!-- 加权总分 -->
<java name="weightedScore" returnClass="double">
<![CDATA[
double def weightedScore(double:kpiScore, double:projectScore, double:teamScore, double:attendanceScore) {
double total = kpiScore * 0.5 + projectScore * 0.2 + teamScore * 0.15 + attendanceScore * 0.15;
return total;
}
]]>
</java>
<!-- 绩效等级 -->
<java name="grade" returnClass="java.lang.String">
<![CDATA[
str def grade(double:totalScore) {
if (totalScore >= 90.0) {
return "S";
} else if (totalScore >= 80.0) {
return "A";
} else if (totalScore >= 70.0) {
return "B";
} else if (totalScore >= 60.0) {
return "C";
} else {
return "D";
}
}
]]>
</java>
<!-- 等级基准系数 -->
<java name="baseCoefficient" returnClass="double">
<![CDATA[
double def baseCoefficient(str:grade) {
if (grade == "S") {
return 1.8;
} else if (grade == "A") {
return 1.4;
} else if (grade == "B") {
return 1.1;
} else if (grade == "C") {
return 0.8;
} else {
return 0.5;
}
}
]]>
</java>
<!-- 管理者加成 -->
<java name="managerBonus" returnClass="double">
<![CDATA[
double def managerBonus(boolean:manager) {
if (manager) {
return 0.2;
} else {
return 0.0;
}
}
]]>
</java>
<!-- 销售激励 -->
<java name="salesBonus" returnClass="double">
<![CDATA[
double def salesBonus(double:salesAmount, str:grade) {
if ((grade == "S" || grade == "A") && salesAmount >= 500000.0) {
return 0.2;
} else if (salesAmount >= 200000.0) {
return 0.1;
} else {
return 0.0;
}
}
]]>
</java>
<!-- 违规扣减 -->
<java name="violationPenalty" returnClass="double">
<![CDATA[
double def violationPenalty(boolean:hasViolation) {
if (hasViolation) {
return -0.3;
} else {
return 0.0;
}
}
]]>
</java>
<!-- 晋升资格 -->
<java name="promotionEligible" returnClass="boolean">
<![CDATA[
boolean def promotionEligible(double:totalScore, boolean:hasViolation) {
if (hasViolation) {
return false;
} else if (totalScore >= 85.0) {
return true;
} else {
return false;
}
}
]]>
</java>
<!-- 绩效改进 -->
<java name="needPip" returnClass="boolean">
<![CDATA[
boolean def needPip(double:totalScore, boolean:hasViolation) {
if (totalScore < 60.0) {
return true;
} else if (totalScore < 70.0 && hasViolation) {
return true;
} else {
return false;
}
}
]]>
</java>
<!-- 考勤等级 -->
<java name="attendanceLevel" returnClass="int">
<![CDATA[
int def attendanceLevel(double:attendanceScore) {
if (attendanceScore >= 95.0) {
return 3;
} else if (attendanceScore >= 90.0) {
return 2;
} else if (attendanceScore >= 80.0) {
return 1;
} else {
return 0;
}
}
]]>
</java>
<!-- 奖金金额 -->
<java name="bonusAmount" returnClass="double">
<![CDATA[
double def bonusAmount(double:baseSalary, double:bonusCoefficient) {
return baseSalary * bonusCoefficient;
}
]]>
</java>
</javas>
package com.github.paohaijiao.demo.service;
import com.github.paohaijiao.demo.PerformanceRules;
import com.github.paohaijiao.demo.model.BonusDecision;
import com.github.paohaijiao.demo.model.PerformanceRecord;
import java.math.BigDecimal;
import java.math.RoundingMode;
/**
* 场景 6:绩效等级与奖金核算服务
* <p>
* 职责划分:
* <ul>
* <li><b>Java 侧</b>:四维加权汇总、系数聚合与下限保护、晋升与改进判定、评语组装</li>
* <li><b>规则侧</b>:权重、等级阈值、基准系数、各类加成与扣减,全部外置在 XML 中(可随年度政策调整)</li>
* </ul>
*/
public class PerformanceService {
/**
* 晋升资格总分线
*/
private static final double PROMOTION_SCORE_LINE = 85.0;
private final PerformanceRules rules;
public PerformanceService(PerformanceRules rules) {
this.rules = rules;
}
/**
* 核算绩效与奖金
*
* @param record 考核快照
* @return 绩效奖金决策
*/
public BonusDecision evaluate(PerformanceRecord record) {
BonusDecision decision = new BonusDecision();
decision.setEmployeeId(record.getEmployeeId());
double totalScore = scale2(rules.weightedScore(record.getKpiScore(), record.getProjectScore(),
record.getTeamScore(), record.getAttendanceScore()));
String grade = rules.grade(totalScore);
double coefficient = rules.baseCoefficient(grade)
+ rules.managerBonus(record.isManager())
+ rules.salesBonus(record.getSalesAmount(), grade)
+ rules.violationPenalty(record.isHasViolation());
if (coefficient < 0.0) {
coefficient = 0.0;
}
coefficient = scale2(coefficient);
decision.setTotalScore(totalScore);
decision.setGrade(grade);
decision.setBonusCoefficient(coefficient);
decision.setBonusAmount(scale2(rules.bonusAmount(record.getBaseSalary(), coefficient)));
decision.setAttendanceLevel(rules.attendanceLevel(record.getAttendanceScore()));
decision.setPromotionEligible(rules.promotionEligible(totalScore, record.isHasViolation()));
decision.setNeedPip(rules.needPip(totalScore, record.isHasViolation()));
buildRemarks(record, decision);
return decision;
}
private void buildRemarks(PerformanceRecord record, BonusDecision decision) {
if ("S".equals(decision.getGrade())) {
decision.addRemark("绩效等级 S,优先纳入年度评优与晋升池");
}
if (decision.isPromotionEligible()) {
decision.addRemark("总分不低于 " + (int) PROMOTION_SCORE_LINE + " 且无违规,具备晋升资格");
}
if (record.isHasViolation()) {
decision.addRemark("存在违规记录,奖金系数已扣减 0.3");
}
if (decision.isNeedPip()) {
decision.addRemark("绩效不达标,需进入绩效改进计划(PIP)");
}
if (decision.getAttendanceLevel() == 0) {
decision.addRemark("考勤得分低于 80,请关注出勤情况");
}
if (record.getSalesAmount() >= 500000.0 && ("S".equals(decision.getGrade()) || "A".equals(decision.getGrade()))) {
decision.addRemark("销售额达标且绩效优秀,享受销售激励加成");
}
}
private double scale2(double value) {
return BigDecimal.valueOf(value).setScale(2, RoundingMode.HALF_UP).doubleValue();
}
}
package com.github.paohaijiao.demo;
import com.github.paohaijiao.demo.model.BonusDecision;
import com.github.paohaijiao.demo.model.PerformanceRecord;
import com.github.paohaijiao.demo.service.PerformanceService;
import com.github.paohaijiao.xml.JQuickJava;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
/**
* 场景 6 生产级样板:绩效等级与奖金核算
* <p>
* 覆盖 6 组绩效形态:优秀员工、低绩效违规、管理者达标、中等绩效、
* 不达标待改进、优秀但有违规。
*/
public class PerformanceServiceTest {
private PerformanceService service;
@Before
public void setUp() {
PerformanceRules rules = JQuickJava.create()
.createApi(PerformanceRules.class, "demo/performance.xml");
this.service = new PerformanceService(rules);
}
/**
* 优秀员工:四维均优、销售额达标 → S 级、系数 2.0、奖金翻倍
*/
@Test
public void shouldRewardTopPerformer() {
PerformanceRecord record = new PerformanceRecord();
record.setEmployeeId("EMP001");
record.setEmployeeName("张伟");
record.setDept("销售部");
record.setKpiScore(95.0);
record.setProjectScore(90.0);
record.setTeamScore(85.0);
record.setAttendanceScore(96.0);
record.setManager(false);
record.setHasViolation(false);
record.setSalesAmount(600000.0);
record.setBaseSalary(20000.0);
BonusDecision decision = service.evaluate(record);
Assert.assertEquals(92.65, decision.getTotalScore(), 0.001);
Assert.assertEquals("S", decision.getGrade());
Assert.assertEquals(2.0, decision.getBonusCoefficient(), 0.001);
Assert.assertEquals(40000.0, decision.getBonusAmount(), 0.001);
Assert.assertEquals(3, decision.getAttendanceLevel());
Assert.assertTrue(decision.isPromotionEligible());
Assert.assertFalse(decision.isNeedPip());
Assert.assertTrue(decision.getRemarks().contains("绩效等级 S,优先纳入年度评优与晋升池"));
}
/**
* 低绩效违规:总分 51.25(D 级)、违规扣减 0.3 → 系数仅 0.2、进入 PIP
*/
@Test
public void shouldPenalizeLowPerformerWithViolation() {
PerformanceRecord record = new PerformanceRecord();
record.setEmployeeId("EMP002");
record.setEmployeeName("李强");
record.setDept("运营部");
record.setKpiScore(45.0);
record.setProjectScore(50.0);
record.setTeamScore(55.0);
record.setAttendanceScore(70.0);
record.setManager(false);
record.setHasViolation(true);
record.setSalesAmount(100000.0);
record.setBaseSalary(10000.0);
BonusDecision decision = service.evaluate(record);
Assert.assertEquals(51.25, decision.getTotalScore(), 0.001);
Assert.assertEquals("D", decision.getGrade());
Assert.assertEquals(0.2, decision.getBonusCoefficient(), 0.001);
Assert.assertEquals(2000.0, decision.getBonusAmount(), 0.001);
Assert.assertEquals(0, decision.getAttendanceLevel());
Assert.assertFalse(decision.isPromotionEligible());
Assert.assertTrue(decision.isNeedPip());
}
/**
* 管理者达标:总分 85.0(A 级)、管理加成 0.2、销售激励 0.1 → 系数 1.7
*/
@Test
public void shouldApplyManagerAndSalesBonus() {
PerformanceRecord record = new PerformanceRecord();
record.setEmployeeId("EMP003");
record.setEmployeeName("王芳");
record.setDept("销售部");
record.setKpiScore(82.0);
record.setProjectScore(85.0);
record.setTeamScore(88.0);
record.setAttendanceScore(92.0);
record.setManager(true);
record.setHasViolation(false);
record.setSalesAmount(300000.0);
record.setBaseSalary(30000.0);
BonusDecision decision = service.evaluate(record);
Assert.assertEquals(85.0, decision.getTotalScore(), 0.001);
Assert.assertEquals("A", decision.getGrade());
Assert.assertEquals(1.7, decision.getBonusCoefficient(), 0.001);
Assert.assertEquals(51000.0, decision.getBonusAmount(), 0.001);
Assert.assertEquals(2, decision.getAttendanceLevel());
Assert.assertTrue(decision.isPromotionEligible());
Assert.assertFalse(decision.isNeedPip());
}
/**
* 中等绩效:总分 72.35(B 级)、销售激励 0.1 → 系数 1.2,无晋升无 PIP
*/
@Test
public void shouldSettleAveragePerformer() {
PerformanceRecord record = new PerformanceRecord();
record.setEmployeeId("EMP004");
record.setEmployeeName("赵敏");
record.setDept("市场部");
record.setKpiScore(70.0);
record.setProjectScore(72.0);
record.setTeamScore(68.0);
record.setAttendanceScore(85.0);
record.setManager(false);
record.setHasViolation(false);
record.setSalesAmount(250000.0);
record.setBaseSalary(15000.0);
BonusDecision decision = service.evaluate(record);
Assert.assertEquals(72.35, decision.getTotalScore(), 0.001);
Assert.assertEquals("B", decision.getGrade());
Assert.assertEquals(1.2, decision.getBonusCoefficient(), 0.001);
Assert.assertEquals(18000.0, decision.getBonusAmount(), 0.001);
Assert.assertEquals(1, decision.getAttendanceLevel());
Assert.assertFalse(decision.isPromotionEligible());
Assert.assertFalse(decision.isNeedPip());
}
/**
* 不达标待改进:总分 56.95(D 级)→ 系数 0.5、进入 PIP、考勤预警
*/
@Test
public void shouldRouteUnderperformerToPip() {
PerformanceRecord record = new PerformanceRecord();
record.setEmployeeId("EMP005");
record.setEmployeeName("陈静");
record.setDept("客服部");
record.setKpiScore(52.0);
record.setProjectScore(55.0);
record.setTeamScore(58.0);
record.setAttendanceScore(75.0);
record.setManager(false);
record.setHasViolation(false);
record.setSalesAmount(60000.0);
record.setBaseSalary(12000.0);
BonusDecision decision = service.evaluate(record);
Assert.assertEquals(56.95, decision.getTotalScore(), 0.001);
Assert.assertEquals("D", decision.getGrade());
Assert.assertEquals(0.5, decision.getBonusCoefficient(), 0.001);
Assert.assertEquals(6000.0, decision.getBonusAmount(), 0.001);
Assert.assertEquals(0, decision.getAttendanceLevel());
Assert.assertTrue(decision.isNeedPip());
Assert.assertTrue(decision.getRemarks().contains("考勤得分低于 80,请关注出勤情况"));
}
/**
* 优秀但有违规:总分 87.3(A 级)但违规 → 无晋升资格、系数被扣减
*/
@Test
public void shouldBlockPromotionForViolation() {
PerformanceRecord record = new PerformanceRecord();
record.setEmployeeId("EMP006");
record.setEmployeeName("周涛");
record.setDept("技术部");
record.setKpiScore(88.0);
record.setProjectScore(86.0);
record.setTeamScore(84.0);
record.setAttendanceScore(90.0);
record.setManager(false);
record.setHasViolation(true);
record.setSalesAmount(520000.0);
record.setBaseSalary(25000.0);
BonusDecision decision = service.evaluate(record);
Assert.assertEquals(87.3, decision.getTotalScore(), 0.001);
Assert.assertEquals("A", decision.getGrade());
Assert.assertEquals(1.3, decision.getBonusCoefficient(), 0.001);
Assert.assertEquals(32500.0, decision.getBonusAmount(), 0.001);
Assert.assertFalse(decision.isPromotionEligible());
Assert.assertTrue(decision.getRemarks().contains("存在违规记录,奖金系数已扣减 0.3"));
}
}

Run at the project root:

Terminal window
mvn test -Dtest=PerformanceServiceTest
Test method Business profile constructed Key assertions / expected decision
shouldRewardTopPerformer Excellent sales: four scores 95/90/85/96, sales 600k, non-manager Composite 92.65, grade S, coefficient 2.0, bonus 40000.0, attendance tier 3, promotion eligible, no PIP, includes grade-S excellence remark
shouldPenalizeLowPerformerWithViolation Low performer with violation: four scores 45/50/55/70, has violation Composite 51.25, grade D, coefficient 0.2, bonus 2000.0, attendance tier 0, not promotion eligible, needs PIP
shouldApplyManagerAndSalesBonus Manager: four scores 82/85/88/92, manager, sales 300k Composite 85.0, grade A, coefficient 1.7 after stacking manager +0.2 and sales +0.1, bonus 51000.0, attendance tier 2, promotion eligible
shouldSettleAveragePerformer Average performer: four scores 70/72/68/85, sales 250k Composite 72.35, grade B, coefficient 1.2, bonus 18000.0, attendance tier 1, not promotion eligible, no PIP
shouldRouteUnderperformerToPip Underperformer: four scores 52/55/58/75, no violation Composite 56.95, grade D, coefficient 0.5, bonus 6000.0, attendance tier 0, needs PIP, includes attendance-below-80 warning remark
shouldBlockPromotionForViolation Excellent but with violation: four scores 88/86/84/90, sales 520k, has violation Composite 87.3, grade A, coefficient 1.3, bonus 32500.0, not promotion eligible, includes violation-deduction 0.3 remark

Two verified outputs recorded in USE-CASES.md Scenario 6:

Excellent sales (composite 92.65, grade S, sales target met):

等级: S 综合分: 92.65 奖金系数: 2.0 奖金金额: 40000.0
晋升资格: true PIP: false

Low performer with violation (composite 51.25, grade D):

等级: D 综合分: 51.25 奖金系数: 0.2 PIP: true
  • Weights 0.5/0.2/0.15/0.15, the five grade cutoffs, base coefficients, and the three adjustment values are all externalized in XML; annual appraisal policy changes only require editing the script for a hot update, with no redeployment of the business system.
  • 10 rule methods, one policy per method: weighting, grading, coefficient, manager/sales/violation adjustments, promotion, PIP, attendance tier, and amount calculation are independent and can be tuned or audited separately.
  • The Service performs floor protection on the aggregated coefficient in Java (resetting negatives to zero) and normalizes amounts with BigDecimal HALF_UP at two decimal places, keeping amount conventions consistent; the promotion line is double-checked on the Java side.
  • The script does not support the ! negation operator, so promotionEligible uses a positive if to check violation first and return false early, cleanly implementing the “violation veto”.
  • The rule interface binds parameters by name via @Param (e.g. salesBonus(salesAmount, grade)), and script method signatures declare types with double:/str:/boolean:; once created, the proxy instance can be reused across appraisal cycles, backed by an ASM call chain.
  • Runs on Java 8 with no heavy rule platform dependency, and can be embedded directly into eHR/payroll systems still on JDK 8 and IT-innovation JDK environments.