I'm working on an employee rostering system using OptaPlanner, and I'm facing a scenario where I need to ensure that priority 1 shifts are filled before priority 2 shifts. The shifts are identified by a priority field in the Shift class, which serves as the planning entity.
Here's what I'm trying to achieve:
- Priority 1 shifts must be assigned first, and they have higher importance.
- Only after all priority 1 shifts are assigned should the solver consider filling priority 2 shifts. Could someone guide me on how to set up the OptaPlanner configuration and constraints to achieve this behavior? I've tried a few approaches, but I'm not sure how to make sure the solver respects this priority order when assigning shifts.
Any help, sample code snippets, or guidance on how to structure my constraint configuration for this scenario would be greatly appreciated. Thank you in advance for your assistance!
OptaPlanner Version: 8.29.0.Final
I have tried created a constraint like this
Constraint constraint = constraintFactory.forEach(Shift.class)
.groupBy(shift -> shift.getWorksite(), ConstraintCollectors.toList())
.filter((worksite, shiftList) -> filterAlternateShifts(shiftList)).penalizeConfigurable((u, s) -> 20)
.asConstraint(CONSTRAINT_ALTERNATES_SHOULD_NOT_BE_ASSIGNED_BEFORE_SCHEDULED);
with the filter method defined as:
private boolean filterAlternateShifts(List<Shift> shiftList) {
List<Shift> alternates = shiftList.stream().filter(s -> s.getRank().equals(SchedulingConstants.ALTERNATE_SHIFT))
.collect(Collectors.toList());
List<Shift> scheduled = shiftList.stream().filter(s -> s.getRank().equals(SchedulingConstants.SCHEDULED_SHIFT))
.collect(Collectors.toList());
boolean unassignedScheduledShiftExists = scheduled.stream().anyMatch(s -> s.getClinician() == null);
boolean assignedAlternateExists = alternates.stream().anyMatch(s -> s.getClinician() != null);
return unassignedScheduledShiftExists && assignedAlternateExists;
}
But this is not working. I have tried another approach similar to the already defined assignEveryShift constraint
Constraint assignEveryShift(ConstraintFactory constraintFactory) {
LOG.trace(SchedulingConstants.METHOD_ENTRY);
Constraint constraint = constraintFactory.forEachIncludingNullVars(Shift.class)
.filter(shift -> shift.getClinician() == null).penalizeConfigurable()
.asConstraint(CONSTRAINT_ASSIGN_EVERY_SHIFT);
LOG.trace(SchedulingConstants.METHOD_EXIT);
return constraint;
}
Constraint prioritizeScheduledRankShift(ConstraintFactory constraintFactory) {
LOG.trace(SchedulingConstants.METHOD_ENTRY);
Constraint constraint = constraintFactory.forEachIncludingNullVars(Shift.class).filter(
shift -> shift.getClinician() == null && shift.getRank().equals(SchedulingConstants.SCHEDULED_SHIFT))
.penalizeConfigurable().asConstraint(CONSTRAINT_SHIFT_ALLOCATION_PREFERENCE_TO_SCHEDULED_RANK);
LOG.trace(SchedulingConstants.METHOD_EXIT);
return constraint;
}
The difference being i have used a hard score to penalize it. This is working to an extend but not on all occasions and I am scared that it can break something.