OptaPlanner排班硬约束失效:班次间隔不足未扣硬分问题
问题现象
使用OptaPlanner实现员工班次排班时,定义了硬约束atLeast12HoursBetweenTwoShifts,要求同一员工两次班次间隔不得少于12小时,违规时扣除12-间隔小时数的硬分。但运行后发现员工ID6的班次(2022-11-24 20:00~23:00与2022-11-25 09:00~12:00,间隔10小时)违反规则却未触发扣分,最终得分显示0hard/-105soft。
相关日志片段:
15:24:02.832 [main ] INFO Solving started: time spent (43), best score (-25init/0hard/0soft), environment mode (REPRODUCIBLE), move thread count (NONE), random (JDK with seed 0). 15:24:02.881 [main ] INFO Construction Heuristic phase (0) ended: time spent (92), best score (0hard/-109soft), score calculation speed (3682/sec), step total (25). 15:24:07.789 [main ] INFO Local Search phase (1) ended: time spent (5000), best score (0hard/-105soft), score calculation speed (136883/sec), step total (33460). 15:24:07.790 [main ] INFO Solving ended: time spent (5000), best score (0hard/-105soft), score calculation speed (134313/sec), phase total (2), environment mode (REPRODUCIBLE), move thread count (NONE).
违规班次分配:
ShiftAssignment{shiftId='3', employeeId=6, employeeRoles=[Design, Dev], roleRequired='Dev', time=2022-11-24,20:00~23:00} ShiftAssignment{shiftId='1', employeeId=6, employeeRoles=[Design, Dev], roleRequired='Design', time=2022-11-25,09:00~12:00}
原因分析
核心问题出在Employee类的equals/hashCode实现:
- 使用
@Data注解自动生成的equals和hashCode方法会比较类中所有字段(id、name、roleSet)。 - 如果数据生成时,给不同ShiftAssignment分配的Employee是不同实例(即使id相同),这两个Employee对象的equals结果为false。
- 约束
atLeast12HoursBetweenTwoShifts中使用Joiners.equal(ShiftAssignment::getEmployee)匹配同一员工的班次,由于equals不匹配,无法识别出这两个班次属于同一员工,因此约束逻辑未触发。
解决方案
1. 修正Employee类的equals和hashCode逻辑
修改Employee类,仅基于id字段生成equals和hashCode,确保同一id的员工被视为同一对象:
@Data @EqualsAndHashCode(of = "id") public class Employee { private Long id; private String name; private Set<String> roleSet; public Employee(Long id, String name, Set<String> roleSet) { this.id = id; this.name = name; this.roleSet = roleSet; } public Employee() { } }
- 使用
@EqualsAndHashCode(of = "id")指定仅以id作为equals和hashCode的判断依据,覆盖@Data自动生成的逻辑。
2. 验证约束逻辑(可选)
确认约束中的时间比较逻辑正确:
- 确保
Shift.getEndAt()和Shift.getStartAt()返回的是LocalDateTime类型,Duration.between()能正确计算时间间隔。 - 检查
forEachUniquePair的Join条件,Joiners.lessThanOrEqual确保仅匹配前一班次结束时间早于后一班次开始时间的配对,避免重复计算。
3. 重新运行求解器
修改后重新运行,此时约束会正确识别同一员工的连续班次,违规时会扣除对应的硬分。
内容的提问来源于stack exchange,提问作者Jingyu Xiang
相关产品推荐
相关产品推荐

