I have a use case where I want to assign a salesperson to a list of appointments. Now, these salespeople have to travel from one point to another to reach the appointment location. I am using Optaplanner to schedule a list of salesperson to a bunch of appointments. I have a constraint defined:
Next appointment start time should be after previous appointment's end time + travel time to reach the next appointment + Grace time
Constraint nextApptConflict(ConstraintFactory constraintFactory) {
return constraintFactory
// Select each pair of 2 different appointments ...
.forEachUniquePair(Appointment.class,
Joiners.equal((appt) -> appt.getRep().getUuid()))
//Sort the result based on appointment start time
//Check for time gap is feasible or not
// ... and penalize each pair with a hard weight.
.penalize(HardSoftScore.ONE_HARD)
.asConstraint("SalesRep conflict");
}
The idea is to first get all the appointments assigned to each sales rep and then sort the result based on appointment start time and then check violations (if any) and penalize accordingly.
However, I am not sure how can we sort the appointments in constraint class and whether should I group the appointment or Joiners.equal((appt) -> appt.getRep().getUuid())
is also correct?
Edit: I have added the code as per @lukas recommendation but I am getting the following error
Constraint nextApptConflict(ConstraintFactory constraintFactory) {
// A sales-rep can accommodate at most one appointment at the same time.
return constraintFactory
// Select each pair of 2 different appointments ...
.forEachUniquePair(Appointment.class,
Joiners.equal((appt) -> appt.getRep().getUuid()),
Joiners.greaterThanOrEqual((appt) -> appt.getEndTime()))
.ifNotExists(Appointment.class, Joiners.greaterThanOrEqual((appt) -> appt.getEndTime()))
.filter((appt1, appt2) -> {
int timeInSec = Utility.getTime(Utility.distance(appt1.getPosition(), appt2.getPosition()));
Timestamp minReachTime = new Timestamp(appt1.getEndTime().getTime() + (timeInSec+GRACE_TIME_SEC) * 1000);
return appt2.getStartTime().before(minReachTime);
})
// ... and penalize each pair with a hard weight.
.penalize(HardSoftScore.ONE_HARD)
.asConstraint("SalesRep conflict");
}
Am I doing anything incorrectly?
Edit 2:
Constraint nextApptConflict(ConstraintFactory constraintFactory) {
// A sales-rep can accommodate at most one appointment at the same time.
return constraintFactory
// Select each pair of 2 different appointments ...
.forEachUniquePair(Appointment.class,
Joiners.equal((appt) -> appt.getRep().getUuid()),
Joiners.greaterThanOrEqual((appt1) -> appt1.getStartTime(), (appt2)-> appt2.getEndTime()))
.ifNotExists(Appointment.class, Joiners.greaterThan((appt1, appt2) -> appt2.getStartTime(), (appt3) -> appt3.getEndTime()))
.filter((appt1, appt2) -> {
int timeInSec = Utility.getTime(Utility.distance(appt1.getPosition(), appt2.getPosition()));
Timestamp minReachTime = new Timestamp(appt1.getEndTime().getTime() + (timeInSec+GRACE_TIME_SEC) * 1000);
return appt2.getStartTime().before(minReachTime);
})
// ... and penalize each pair with a hard weight.
.penalize(HardSoftScore.ONE_HARD)
.asConstraint("SalesRep conflict");
}
You don't need to sort anything. You do need to better specify your unique pairs:
forEach
).join
with some lessThan
/greaterThan
joiners).ifNotExists
).This will give you all pairs of consecutive appointments. What remains is to penalize based on the difference between the end of the first and the start of the next.