optaplanner - Timefold vehicle routing problem time windowed - include lunch break in the implementation - Stack Overflow

There are nursesclinicians and there are visits. nursesclinicians will perform visits starting from ho

There are nurses/clinicians and there are visits. nurses/clinicians will perform visits starting from home location and travel from visit to visit. Visits will have time windows. Nurses/Clinicians will start from home and will return to home after performing various visits Requirement is to find optimized route for nurses/clinicians.

I have previousVisit and arrivalTime for chaining the visits, clinician starts from home location and goes from visit to visit, clinician comes back to his home location after doing visits within his working hours.

I want to also ensure that clinician does not conduct visit or travel during lunch hours.

I am thinking that i would need one more shadow variable visitStartTime. So for lunch time arrivalTime and visitStartTime will be different for visit just after lunch and for all other visits visitStartTime and arrival time will be the same. Please suggest the changes.

Below are my Visits, Providers and solution class:

@PlanningEntity
@Data
public class Visits {
    @PlanningId
    private String id;

    private Double matrixVisitId;
    private Long clientId;
    private VisitMsa msa;
    private Date[] slotPreferences;
    private String[] products;
    private VisitsMember member;
    private String slotType;
    private boolean pinned;
    private TimeWindow timeWindow;

    private ClinicianSlot assignedSlot;

    private Providers assignedClinician;
    private Visits previousVisit;
    private Visits nextVisit;

    @CascadingUpdateShadowVariable(targetMethodName = "updateArrivalTime")
    private LocalDateTime arrivalTime;

    @ValueRangeProvider(id = "timeBlockRangeForVisit")
    public List<ClinicianSlot> generatePossibleTimeBlocks() {
        if (this.timeWindow == null) {
            return Collections.emptyList();
        }
        List<ClinicianSlot> possibleTimeBlocks = new ArrayList<>();
        LocalDateTime start = timeWindow.getStart();
        while (!start.plusHours(1).isAfter(timeWindow.getEnd())) {
            possibleTimeBlocks.add(new ClinicianSlot(start, start.plusHours(1)));
            start = start.plusHours(1);
        }
        return possibleTimeBlocks;
    }

    @PlanningPin
    public boolean isPinned() {
        return pinned;
    }

    @PreviousElementShadowVariable(sourceVariableName = "visits")
    public Visits getPreviousVisit() {
        return previousVisit;
    }

    public void setPreviousVisit(Visits previousVisit) {
        this.previousVisit = previousVisit;
    }

    @NextElementShadowVariable(sourceVariableName = "visits")
    public Visits getNextVisit() {
        return nextVisit;
    }

    public void setNextVisit(Visits nextVisit) {
        this.nextVisit = nextVisit;
    }

    @InverseRelationShadowVariable(sourceVariableName = "visits")
    public Providers getAssignedClinician() {
        return assignedClinician;
    }

    public LocalDateTime getArrivalTime() {
        return arrivalTime;
    }

    public void setArrivalTime(LocalDateTime arrivalTime) {
        this.arrivalTime = arrivalTime;
    }

    @SuppressWarnings("unused")
    private void updateArrivalTime() {
        if (previousVisit == null && assignedClinician == null) {
            arrivalTime = null;
            return;
        }
        LocalDateTime departureTime = previousVisit == null ? assignedClinician.getDepartureTime() : previousVisit.getDepartureTime();
        arrivalTime = departureTime != null ? departureTime.plusMinutes(getDrivingTimeSecondsFromPreviousStandstill()) : null;
    }

    @JsonProperty(access = JsonProperty.Access.READ_ONLY)
    public LocalDateTime getDepartureTime() {
        if (arrivalTime == null) {
            return null;
        }
        return getStartServiceTime().plusMinutes(55);
    }

    @JsonProperty(access = JsonProperty.Access.READ_ONLY)
    public LocalDateTime getStartServiceTime() {
        if (arrivalTime == null) {
            return null;
        }
        return arrivalTime;
    }

    @JsonIgnore
    public long getDrivingTimeSecondsFromPreviousStandstill() {
        if (assignedClinician == null) {
            throw new IllegalStateException(
                    "This method must not be called when the shadow variables are not initialized yet.");
        }
        if (previousVisit == null) {
            return Constants.getDrivingTime(assignedClinician.getLocation().getCoordinates(), member.getMemberLocation().getCoordinates());
        }
        return Constants.getDrivingTime(previousVisit.getMember().getMemberLocation().getCoordinates(), member.getMemberLocation().getCoordinates());
    }

    @Override
    public String toString() {
        return id;
    }
}
@Data
@PlanningEntity
@AllArgsConstructor
@NoArgsConstructor
public class Providers {

    @PlanningId
    private String id;

    private Integer staffResourceId;
    private String providerId;
    private ProviderMsa[] msa;
    private Client[] clients;
    private String[] products;
    private List<ClinicianSlot> availableSlots;

    @PlanningListVariable(valueRangeProviderRefs = "visitRange", allowsUnassignedValues=true)
    private List<Visits> visits = new ArrayList<>();

    @GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE)
    private Location location;

    private LocalDateTime departureTime;
    private LocalDateTime arrivalTime;

    private Location endLocation;

    @JsonProperty(access = JsonProperty.Access.READ_ONLY)
    public int getTotalDrivingTimeSeconds() {
        if (visits.isEmpty()) {
            return 0;
        }

        int totalDrivingTime = 0;
        Location previousLocation = location;

        for (Visits visit : visits) {
            totalDrivingTime += Constants.getDrivingTime(previousLocation.getCoordinates(), visit.getMember().getMemberLocation().getCoordinates());
            previousLocation = visit.getMember().getMemberLocation();
        }
        totalDrivingTime += Constants.getDrivingTime(previousLocation.getCoordinates(), location.getCoordinates());

        return totalDrivingTime;
    }

    @JsonProperty(access = JsonProperty.Access.READ_ONLY)
    public LocalDateTime arrivalTime() {
        if (visits.isEmpty()) {
            return departureTime;
        }

        Visits lastVisit = visits.get(visits.size() - 1);
        return lastVisit.getDepartureTime().plusMinutes(Constants.getDrivingTime(lastVisit.getMember().getMemberLocation().getCoordinates(), location.getCoordinates()));
    }

    @Override
    public String toString() {
        return id;
    }
}
@PlanningSolution
@Data
public class ProviderRouteSolution {

    private List<Visits> visitList;

    @PlanningEntityCollectionProperty
    @ValueRangeProvider(id = "visitRange")
    public List<Visits> getVisitList() {
        return visitList;
    }

    private List<Providers> clinicianList;
    
    @PlanningScore
    private HardMediumSoftScore score;

    @ValueRangeProvider(id = "clinicianRange")
    @PlanningEntityCollectionProperty
    public List<Providers> getClinicianList() {
        return clinicianList;
    }
}
@Service
public class ProviderSchedulingConstraintProvider implements ConstraintProvider {

    public ProviderSchedulingConstraintProvider() {
    }

    @Override
    public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {
        return new Constraint[]{
                enforceAllHardConstraints(constraintFactory),
                assignVisitToValidSlotAndClinician(constraintFactory),
                penalizeUnassignedVisits(constraintFactory),
                minimizeChainedTravelDistance(constraintFactory),
                isProviderReachingHomeOnTime(constraintFactory)
        };
    }

    private Constraint enforceAllHardConstraints(ConstraintFactory constraintFactory) {
        return constraintFactory.forEachIncludingUnassigned(Visits.class)
                .filter(visit -> visit.getAssignedClinician() != null)
                .penalize(HardMediumSoftScore.ONE_HARD, visit -> {
                    Providers clinician = visit.getAssignedClinician();
                    boolean msaMatches = (clinician.getMsa() != null && visit.getMsa() != null) &&
                            Arrays.stream(clinician.getMsa())
                                    .anyMatch(msa -> Objects.equals(msa.getState(), visit.getMsa().getName()));
                    boolean clientMatches = (clinician.getClients() != null && visit.getClientId() != null) &&
                            Arrays.stream(clinician.getClients())
                                    .anyMatch(client -> Objects.equals(client.getClientId().longValue(), visit.getClientId()));
                    boolean productMatches = (clinician.getProducts() != null && visit.getProducts() != null) &&
                            Arrays.asList(clinician.getProducts()).contains("CHA") &&
                            Arrays.asList(visit.getProducts()).contains("CHA");
                    return (msaMatches && clientMatches && productMatches) ? 0 : 1;
                }).asConstraint("Enforce All Hard Constraints");
    }


    private Constraint penalizeUnassignedVisits(ConstraintFactory constraintFactory) {
        return constraintFactory.forEachIncludingUnassigned(Visits.class)
                .filter(visit -> visit.getAssignedClinician() == null)
                .penalize(HardMediumSoftScore.ONE_MEDIUM).asConstraint("Unassigned Visit");
    }

    protected Constraint minimizeChainedTravelDistance(ConstraintFactory factory) {
        return factory.forEachIncludingUnassigned(Providers.class)
                .penalize(HardMediumSoftScore.ONE_SOFT,
                        Providers::getTotalDrivingTimeSeconds)
                .asConstraint("minimizeTravelTime");
    }

    private Constraint assignVisitToValidSlotAndClinician(ConstraintFactory constraintFactory) {
        return constraintFactory.forEachIncludingUnassigned(Visits.class)
                .filter(visit -> visit.getAssignedClinician() != null)
                .penalize(HardMediumSoftScore.ONE_HARD, visit -> {
                    boolean fitsVisitWindow = isVisitWithinMemberTimeWindow(visit);
                    return (!fitsVisitWindow) ? 10000 : 0;
                }).asConstraint("Assign Visit to Valid Slot and Clinician");
    }

    private boolean isVisitWithinMemberTimeWindow(Visits visit) {
        LocalDateTime visitWindowStart = visit.getTimeWindow().getStart();
        LocalDateTime visitWindowEnd = visit.getTimeWindow().getEnd();
        boolean isArrivalAfterWindowStart = visit.getArrivalTime().isAfter(visitWindowStart);
        boolean isDepartureBeforeWindowEnd = visit.getArrivalTime().plusMinutes(55).isBefore(visitWindowEnd);

        return isArrivalAfterWindowStart && isDepartureBeforeWindowEnd;
    }

    private Constraint isProviderReachingHomeOnTime(ConstraintFactory constraintFactory) {
        return constraintFactory.forEachIncludingUnassigned(Visits.class)
                .filter(visit -> visit.getNextVisit() == null && visit.getAssignedClinician() != null)
                .penalize(HardMediumSoftScore.ONE_HARD, visit -> {
                    boolean isProviderReachingHomeOnTime = visit.getArrivalTime().plusMinutes(55).plusMinutes(Constants.getDrivingTime(visit.getMember().getMemberLocation().getCoordinates(), visit.getAssignedClinician().getEndLocation().getCoordinates())).isBefore(visit.getAssignedClinician().getArrivalTime());
                    return (!isProviderReachingHomeOnTime) ? 10000 : 0;
                }).asConstraint("Assign Valid Visit for Clinician to reach home on time");
    }
}

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1745673119a4639525.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信