OptaPlanner员工排班:优先填充Priority 1班次方案咨询
实现OptaPlanner中Priority 1班次优先于Priority 2班次分配的约束方案
需求明确
核心目标:
- Priority 1(对应代码中的
SCHEDULED_SHIFT)班次必须优先完成分配 - 仅当所有Priority 1班次已分配员工后,才能开始分配Priority 2(
ALTERNATE_SHIFT)班次
现有方案问题分析
第一种分组过滤方案:
按工地分组的检查逻辑本身合理,但约束惩罚权重(20)未设置为硬约束级别,导致求解器可接受违规情况;同时该逻辑仅覆盖单工地范围,跨工地存在未分配Priority 1班次时无法触发约束。第二种双硬约束方案:
assignEveryShift惩罚所有未分配班次,prioritizeScheduledRankShift额外惩罚未分配的Priority 1班次,这种设置会让求解器同时尝试填充两类班次,无法实现“完全填满Priority 1再处理Priority 2”的排他性优先级。
正确约束配置方案
方案1:硬约束强制排他优先级(推荐)
通过硬约束禁止存在未分配Priority 1班次时分配Priority 2班次,同时确保Priority 1班次必须全部分配(业务强制要求时启用),Priority 2班次分配作为软约束处理。
import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore; import org.optaplanner.core.api.score.stream.Constraint; import org.optaplanner.core.api.score.stream.ConstraintFactory; import org.optaplanner.core.api.score.stream.ConstraintProvider; public class ShiftSchedulingConstraintProvider implements ConstraintProvider { private static final String CONSTRAINT_PRIORITY1_FIRST = "priority1-shift-must-be-assigned-first"; private static final String CONSTRAINT_ASSIGN_ALL_PRIORITY1 = "assign-all-priority1-shifts"; private static final String CONSTRAINT_ASSIGN_PRIORITY2 = "assign-priority2-shifts"; @Override public Constraint[] defineConstraints(ConstraintFactory constraintFactory) { return new Constraint[]{ // 硬约束:全局范围内只要有未分配的Priority1班次,就不能分配Priority2班次 priority1First(constraintFactory), // 硬约束:所有Priority1班次必须分配员工(业务强制要求时启用) assignAllPriority1Shifts(constraintFactory), // 软约束:分配Priority2班次(可选,根据业务需求调整) assignPriority2Shifts(constraintFactory) }; } private Constraint priority1First(ConstraintFactory constraintFactory) { return constraintFactory.forEach(Shift.class) .filter(shift -> shift.getRank().equals(SchedulingConstants.SCHEDULED_SHIFT) && shift.getClinician() == null) .join(Shift.class) .filter((priority1Shift, priority2Shift) -> priority2Shift.getRank().equals(SchedulingConstants.ALTERNATE_SHIFT) && priority2Shift.getClinician() != null) .penalize(HardSoftScore.ONE_HARD) .asConstraint(CONSTRAINT_PRIORITY1_FIRST); } private Constraint assignAllPriority1Shifts(ConstraintFactory constraintFactory) { return constraintFactory.forEachIncludingNullVars(Shift.class) .filter(shift -> shift.getRank().equals(SCHEDULED_SHIFT) && shift.getClinician() == null) .penalize(HardSoftScore.ONE_HARD) .asConstraint(CONSTRAINT_ASSIGN_ALL_PRIORITY1); } private Constraint assignPriority2Shifts(ConstraintFactory constraintFactory) { return constraintFactory.forEachIncludingNullVars(Shift.class) .filter(shift -> shift.getRank().equals(ALTERNATE_SHIFT) && shift.getClinician() == null) .penalize(HardSoftScore.ONE_SOFT) .asConstraint(CONSTRAINT_ASSIGN_PRIORITY2); } }
方案2:权重差异实现优先级
若不需要严格硬约束,可通过设置极大权重差,让求解器优先填满Priority 1班次:
private Constraint prioritizePriority1Shifts(ConstraintFactory constraintFactory) { // 未分配的Priority1班次惩罚权重远高于Priority2,驱动求解器优先处理 return constraintFactory.forEachIncludingNullVars(Shift.class) .filter(shift -> shift.getClinician() == null) .penalizeConfigurable(shift -> shift.getRank().equals(SchedulingConstants.SCHEDULED_SHIFT) ? 1000 : 1) .asConstraint("prioritize-priority1-shifts"); }
求解器配置建议
在application.properties中配置合适的分数类型与终止条件:
# 使用硬软分数类型,适配硬约束+软约束的配置 optaplanner.solver.score-type=HARD_SOFT # 设置求解超时,确保有足够时间完成Priority1班次的分配 optaplanner.solver.termination.spent-limit=30s
验证方法
- 构造测试场景:创建3个未分配的Priority 1班次和2个Priority 2班次
- 运行求解器,观察初始阶段仅分配Priority 1班次
- 确认所有Priority 1班次分配完成后,求解器才开始处理Priority 2班次
- 手动修改解(给Priority 2班次分配员工但仍有未分配的Priority 1),应触发硬分数惩罚
内容的提问来源于stack exchange,提问作者sicario_23
相关产品推荐
相关产品推荐

