0

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");
    }
user101
  • 139
  • 10

1 Answers1

1

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.

Lukáš Petrovický
  • 3,945
  • 1
  • 11
  • 20
  • If `join with some lessThan/greaterThan joiners` with this I can get the next appointment in a sorted way, why do we need to again check with `ifNotExists` whether a thrid appointment exist for not ? – user101 Dec 04 '22 at 11:08
  • I assumed you only want appointments that have no appointment in between them. Assume appointments A, B, C; `ifNotExists` will make sure there is no C such that comes after A and before B. If you don't have that requirement, you don't need `ifNotExists`. (Although I find it hard to understand why you'd want to include such pairs.) – Lukáš Petrovický Dec 04 '22 at 12:51
  • My requirement is to get all the appointments sorted based on the start time, so that I can iterate and take decision whether reaching the next appointment is feasible or not – user101 Dec 04 '22 at 18:46
  • That requirement would apply in an imperative program, which Constraint Streams is not. Think of Constraint Streams less like a `for` loop and more like an SQL statement. In CS, you pick two appointments and penalize the distance between them in isolation; and you do that for any two appointments for which it makes sense. There is no iterating over all pairs, there is no set order; CS is an incremental API, and imperative thinking just doesn't work here. – Lukáš Petrovický Dec 04 '22 at 19:10
  • I understand that in CS we cannot code in the way I want but is there a way to get the next immediate appointment based on the start time? I guess `Joiners. greaterThanOrEqual` will give the next appointment where `start_time of A < start_time of B` but I don't think we can guarantee that it will be the next immediate one? – user101 Dec 05 '22 at 10:44
  • 1
    Indeed. Which is why you need the `ifNotExists` to follow the `join`. – Lukáš Petrovický Dec 05 '22 at 10:50
  • I have updated the question, I am getting an error while implementing `ifNotExists` logic – user101 Dec 05 '22 at 13:14
  • It's a compiler error, the methods your're using don't have the correct signatures. After a join, the argument are no longer just 1 appointment, but 2. So it's not `(appt) -> ...`, but `(appt1, appt2) -> ..., (appt3) -> ...`. – Lukáš Petrovický Dec 05 '22 at 14:47
  • I have updated the question to add the `ifNotExists` condition – user101 Dec 05 '22 at 17:43
  • Do you think this condition will give me the next immediate appointment? `.ifNotExists(Appointment.class, Joiners.greaterThan((appt1, appt2) -> appt2.getStartTime(), (appt3) -> appt3.getEndTime()))` – user101 Dec 05 '22 at 17:47
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/250169/discussion-between-lukas-petrovicky-and-roneet-shaw). – Lukáš Petrovický Dec 05 '22 at 17:57