尝试解决临床医生/提供商的访问安排与时间段限制,在多个计划实体的配置方面存在问题。根据提供商的时段和会员的可用性,提供商将在一天内进行多次访问,并且一次访问只能有一个提供商。我已经尝试了所有可用的方法,但无法找到解决方案。非常感谢任何帮助。
用例:
临床医生/提供者一天内有空,会员也有空,提供者时段可以分为 1 小时时段。提供者将从家里开始进行访问。我们需要为提供商找到最佳的访问者。下面是示例。这是带有附加约束的车辆路径问题时间窗口。
现在我们需要检查的不是距离,而是驾驶时间和 1 小时的访问时段,所以假设提供商时段为上午 8 点至 11 点,平均访问时间为 55 分钟,会员的访问窗口为上午 8 点至 10 点,那么提供商应到达会员位置在上午 8 点至 10 点之间,并在该时间范围内完成 55 分钟的访问,所以基本上这是车辆路线问题,涉及提供商和会员的时间窗口,并考虑驾驶时间和访问时间。因此,我们将使用 Mapbox api 来计算行驶时间,并假设访问时间为 55 分钟,而不是半正矢距离。
以下示例路线供提供商澄清更多信息:
提供商时段:上午 8 点至 11 点
访问1(时间窗口上午8点至10点): 车程20分钟 早上 7:40 离开家 早上 8 点到达并参观 55 分钟,即到上午 8:55(在参观时间窗口内) 指定的提供商时段:上午 8-9 点
参观1参观2(时间窗口9-11am): 车程20分钟 上午 9:15 到达并参观 55 分钟,即到上午 10:05(在参观时间窗口内) 指定的提供商时段:上午 9-10 点
参观2参观3(时间窗口10-11am): 车程20分钟 上午 10:25 到达并访问 55 分钟,即直到上午 11:20(在访问时间窗口内),因此不应将此访问分配给提供者 未分配提供商位置
下面是我的实体类:
规划单位1
@PlanningEntity
@Data
public class Visits {
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;
@PlanningVariable(valueRangeProviderRefs = "timeBlockRangeForVisit", nullable = true)
private ClinicianSlot assignedSlot;
private Providers assignedClinician;
private Visits previousVisit;
private Visits nextVisit;
private LocalDateTime previousVisitEndTime;
@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 void setVehicle(Providers assignedClinician) {
this.assignedClinician = assignedClinician;
}
@ShadowVariable(variableListenerClass = VisitChainVariableListener.class, sourceVariableName = "previousVisit")
public LocalDateTime getPreviousVisitEndTime() {
return previousVisitEndTime;
}
public void setPreviousVisitEndTime(LocalDateTime previousVisitEndTime) {
this.previousVisitEndTime = previousVisitEndTime;
}
}
规划实体2
@Data
@PlanningEntity
@AllArgsConstructor
public class Providers {
private Integer staffResourceId;
private String providerId;
private ProviderMsa[] msa;
private Client[] clients;
private String[] products;
private List<ClinicianSlot> availableSlots;
@PlanningListVariable(valueRangeProviderRefs = "visitRange")
private List<Visits> visits;
@GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE)
private Location location;
}
解决方案类
@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 HardSoftScore score;
@PlanningEntityCollectionProperty
public List<Providers> getClinicianList() {
return clinicianList;
}
}
下面是我正在初始化求解器的服务类:
@Service
public class ProviderRouteService {
@Autowired
public ProviderRouteService(
ProvidersRepository providersRepository, VisitsRepository visitsRepository, ProviderSlotsRepository providerSlotsRepository, RoutesRepository routesRepository
) {
this.providersRepository = providersRepository;
this.visitsRepository = visitsRepository;
this.providerSlotsRepository = providerSlotsRepository;
this.routesRepository = routesRepository;
}
@PostConstruct
public void init() {
SolverConfig template = SolverConfig.createFromXmlResource("vehicleRoutingSolverConfig.xml");
SolverFactory<ProviderRouteSolution> solverFactory = SolverFactory.create(template);
this.solver = solverFactory.buildSolver();
}
...additional code here
}
这是我的vehicleRoutingSolverConfig.xml:
<?xml version="1.0" encoding="UTF-8"?>
<solver xmlns="https://www.optaplanner.org/xsd/solver" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://www.optaplanner.org/xsd/solver https://www.optaplanner.org/xsd/solver/solver.xsd">
<solutionClass>com.optaplanner.dto.ProviderRouteSolution</solutionClass>
<entityClass>com.optaplanner.model.Visits</entityClass>
<entityClass>com.optaplanner.model.Providers</entityClass>
<scoreDirectorFactory>
<constraintProviderClass>com.optaplanner.service.impl.ProviderSchedulingConstraintProvider
</constraintProviderClass>
<initializingScoreTrend>ONLY_DOWN</initializingScoreTrend>
</scoreDirectorFactory>
<termination>
<minutesSpentLimit>5</minutesSpentLimit>
</termination>
<constructionHeuristic>
</constructionHeuristic>
<localSearch>
<unionMoveSelector>
<listChangeMoveSelector>
<valueSelector id="1"/>
<destinationSelector>
<entitySelector>
<entityClass>com.optaplanner.model.Providers</entityClass>
</entitySelector>
<valueSelector variableName="visits"/>
</destinationSelector>
</listChangeMoveSelector>
</unionMoveSelector>
<acceptor>
<lateAcceptanceSize>200</lateAcceptanceSize>
</acceptor>
<forager>
<acceptedCountLimit>1</acceptedCountLimit>
</forager>
</localSearch>
</solver>
当我运行 sprint boot 应用程序时,我遇到以下错误:
Caused by: java.lang.IllegalArgumentException: The config (QueuedValuePlacerConfig(ValueSelectorConfig(visits), ListChangeMoveSelectorConfig(ValueSelectorConfig(null), null))) has no entityClass configured and because there are multiple in the entityClassSet ([class com.optaplanner.model.Visits, class com.optaplanner.model.Providers]), it cannot be deduced automatically.
at org.optaplanner.core.impl.AbstractFromConfigFactory.getTheOnlyEntityDescriptor(AbstractFromConfigFactory.java:67) ~[optaplanner-core-impl-9.44.0.Final.jar:9.44.0.Final]
at org.optaplanner.core.impl.AbstractFromConfigFactory.deduceEntityDescriptor(AbstractFromConfigFactory.java:44) ~[optaplanner-core-impl-9.44.0.Final.jar:9.44.0.Final]
at org.optaplanner.core.impl.constructionheuristic.placer.QueuedValuePlacerFactory.buildEntityPlacer(QueuedValuePlacerFactory.java:34) ~[optaplanner-core-impl-9.44.0.Final.jar:9.44.0.Final]
at org.optaplanner.core.impl.constructionheuristic.placer.QueuedValuePlacerFactory.buildEntityPlacer(QueuedValuePlacerFactory.java:20) ~[optaplanner-core-impl-9.44.0.Final.jar:9.44.0.Final]
at org.optaplanner.core.impl.constructionheuristic.DefaultConstructionHeuristicPhaseFactory.buildPhase(DefaultConstructionHeuristicPhaseFactory.java:73) ~[optaplanner-core-impl-9.44.0.Final.jar:9.44.0.Final]
at org.optaplanner.core.impl.constructionheuristic.DefaultConstructionHeuristicPhaseFactory.buildPhase(DefaultConstructionHeuristicPhaseFactory.java:43) ~[optaplanner-core-impl-9.44.0.Final.jar:9.44.0.Final]
at org.optaplanner.core.impl.phase.PhaseFactory.buildPhases(PhaseFactory.java:59) ~[optaplanner-core-impl-9.44.0.Final.jar:9.44.0.Final]
at org.optaplanner.core.impl.solver.DefaultSolverFactory.buildPhaseList(DefaultSolverFactory.java:233) ~[optaplanner-core-impl-9.44.0.Final.jar:9.44.0.Final]
at org.optaplanner.core.impl.solver.DefaultSolverFactory.buildSolver(DefaultSolverFactory.java:126) ~[optaplanner-core-impl-9.44.0.Final.jar:9.44.0.Final]
at com.optaplanner.service.impl.ProviderRouteService.init(ProviderRouteService.java:69) ~[classes/:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]