Sort list based on time in Optaplanner constraint

189 views Asked by At

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?

enter image description here

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");
    }
1

There are 1 answers

11
Lukáš Petrovický On BEST ANSWER

You don't need to sort anything. You do need to better specify your unique pairs:

  • Select the first appointment (forEach).
  • Select the second appointment such that it starts after the first one ends (join with some lessThan/greaterThan joiners).
  • Make sure there is no third appointment between the two (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.