feat(javaTest): orchestrate cross-floor dispatch

This commit is contained in:
zgw
2026-08-07 10:50:23 +08:00
parent 35e67e2455
commit 31fa643993
9 changed files with 1441 additions and 26 deletions

View File

@@ -32,6 +32,42 @@ public final class ClearanceMovePlanner {
Set<String> occupiedSites,
Set<ResourceKey> reservedResources,
GridMap map) {
return planInternal(vehicleId, currentSite, ownerTaskId, ownerRemainingRoute,
occupiedSites, reservedResources, map, false);
}
/**
* 在父业务路线尚未预约和激活之前,把占住其终点的空闲车移到路线外的安全支路。
*
* <p>与活动任务清障不同,此时父车辆尚未进入通道,所以清障路径可以暂时经过未来业务路线;但是
* 清障终点仍必须位于该路线之外,避免只把阻塞车从终点挪到前置节点后再次挡住父任务。调用方必须
* 等清障完成后才能提交父路线。
*
* @param vehicleId 占住业务终点、需要先让位的车辆。
* @param currentSite 当前被占用的业务终点。
* @param ownerTaskId 等待取得路线承诺的父任务。
* @param ownerRoute 父任务准备提交但尚未预约的完整路线。
* @param occupiedSites 全车队当前占点。
* @param reservedResources 已由其他物理动作持有的资源。
* @param map 当前项目路网。
* @return 能把车辆停到父路线外时返回逐节点清障动作,否则返回空。
*/
public Optional<ClearanceMove> planBeforeRouteCommit(
String vehicleId, String currentSite, String ownerTaskId,
List<String> ownerRoute, Set<String> occupiedSites,
Set<ResourceKey> reservedResources, GridMap map) {
return planInternal(vehicleId, currentSite, ownerTaskId, ownerRoute,
occupiedSites, reservedResources, map, true);
}
/**
* 统一选择清障终点;只有准入前清障可以穿过尚未生效的父路线。
*/
private Optional<ClearanceMove> planInternal(
String vehicleId, String currentSite, String ownerTaskId,
List<String> ownerRemainingRoute, Set<String> occupiedSites,
Set<ResourceKey> reservedResources, GridMap map,
boolean mayTraverseOwnerRoute) {
if (vehicleId == null || currentSite == null || ownerTaskId == null
|| ownerRemainingRoute == null || occupiedSites == null
|| reservedResources == null || map == null) {
@@ -47,6 +83,19 @@ public final class ClearanceMovePlanner {
}
excluded.remove(currentSite);
// 准入前父路线还没有形成物理承诺,只限制清障终点;真正的占车和节点资源仍是路径硬障碍。
Set<String> pathObstacles = new LinkedHashSet<String>(excluded);
if (mayTraverseOwnerRoute) {
pathObstacles.removeAll(ownerRemainingRoute);
pathObstacles.addAll(occupiedSites);
for (ResourceKey resource : reservedResources) {
if (resource.value().startsWith("NODE:")) {
pathObstacles.add(resource.value().substring("NODE:".length()));
}
}
pathObstacles.remove(currentSite);
}
GridPathPlanner planner = new GridPathPlanner();
List<Candidate> candidates = new ArrayList<Candidate>();
// 枚举全图而不是只看邻居,使被阻塞车辆能找到最近的可达让路站点。
@@ -54,7 +103,8 @@ public final class ClearanceMovePlanner {
Collections.sort(siteIds);
for (String target : siteIds) {
if (target.equals(currentSite) || excluded.contains(target)) continue;
List<String> route = planner.shortestPath(map, currentSite, target, excluded);
List<String> route = planner.shortestPath(
map, currentSite, target, pathObstacles);
if (!route.isEmpty()) {
candidates.add(new Candidate(target, route,
map.getNeighbors(target).size() <= 1));

View File

@@ -14,6 +14,7 @@ public final class HistoricalSimulationInput {
private final GridMap gridMap;
private final EffectiveTrafficMap trafficMap;
private final List<VehicleSnapshot> initialVehicles;
private final FloorTransferTopology floorTransferTopology;
/**
* 创建 {@code HistoricalSimulationInput} 实例并初始化其内部状态。
@@ -29,11 +30,36 @@ public final class HistoricalSimulationInput {
List<HistoricalSimulationFailure> failedInputs,
GridMap gridMap, EffectiveTrafficMap trafficMap,
List<VehicleSnapshot> initialVehicles) {
this(tasks, failedInputs, gridMap, trafficMap, initialVehicles,
new FloorTransferTopology(Collections.<Integer, String>emptyMap()));
}
/**
* 创建带项目换层端口目录的仿真输入。
*
* <p>换层端口与普通地图边分开保存。中心调度只能通过该目录生成换层作业,不能在
* {@link GridMap} 中虚构跨层边;没有配置端口的测试输入继续只支持同层调度。
* @param tasks 可按 create_date 进入调度队列的业务任务。
* @param failedInputs 输入阶段已经关闭的非法历史记录。
* @param gridMap 只包含同层物理邻接关系的完整地图。
* @param trafficMap 节点、边和交通资源容量定义。
* @param initialVehicles 本次仿真的初始车队快照。
* @param floorTransferTopology 项目楼层到换层交接站点的独立配置。
*/
HistoricalSimulationInput(List<HistoricalSimulationTask> tasks,
List<HistoricalSimulationFailure> failedInputs,
GridMap gridMap, EffectiveTrafficMap trafficMap,
List<VehicleSnapshot> initialVehicles,
FloorTransferTopology floorTransferTopology) {
this.tasks = immutable(tasks);
this.failedInputs = immutable(failedInputs);
this.gridMap = gridMap;
this.trafficMap = trafficMap;
this.initialVehicles = immutable(initialVehicles);
if (floorTransferTopology == null) {
throw new IllegalArgumentException("floor transfer topology is required");
}
this.floorTransferTopology = floorTransferTopology;
}
/**
@@ -72,6 +98,9 @@ public final class HistoricalSimulationInput {
*/
public List<VehicleSnapshot> getInitialVehicles() { return initialVehicles; }
/** 返回项目换层端口目录;该目录不向普通路径规划器暴露跨层边。 */
public FloorTransferTopology getFloorTransferTopology() { return floorTransferTopology; }
/**
* 执行“all simulation ids”所表示的处理流程。
*

View File

@@ -10,7 +10,9 @@ import com.ft546.wcs.shadow.history.HistoricalQueueRecord;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public final class HistoricalSimulationInputFactory {
/**
@@ -75,7 +77,24 @@ public final class HistoricalSimulationInputFactory {
List<VehicleSnapshot> vehicles = initialVehicles(compiled);
return new HistoricalSimulationInput(tasks, failures, compiled.getGridMap(),
compiled.getTrafficMap(), vehicles);
compiled.getTrafficMap(), vehicles, floorTransferTopology(compiled));
}
/**
* 按 FT546 现有业务别名规则,把每层 {@code 07050<floor>} 登记为换层交接站点。
*
* <p>这里只建立项目设备端口配置,不调用 {@code GridMap.connect},因此路径规划器仍然看不到
* 任何普通跨层边。某层没有该真实站点时保持未配置,让中心调度明确判定跨层不可用。
*/
private FloorTransferTopology floorTransferTopology(
HistoricalGridMapCompiler.CompiledHistoricalMap compiled) {
Map<Integer, String> ports = new LinkedHashMap<Integer, String>();
for (int floor = 1; floor <= 3; floor++) {
String siteId = "07050" + floor;
HistoricalMapCell cell = compiled.getPassableSites().get(siteId);
if (cell != null && cell.getZ() == floor) ports.put(floor, siteId);
}
return new FloorTransferTopology(ports);
}
/**

View File

@@ -51,7 +51,8 @@ public final class PointToPointSimulationInputFactory {
CREATED_AT.plusMinutes(1), sourceSite, targetSite, history, 0);
return new HistoricalSimulationInput(
Collections.singletonList(task), Collections.emptyList(),
base.getGridMap(), base.getTrafficMap(), base.getInitialVehicles());
base.getGridMap(), base.getTrafficMap(), base.getInitialVehicles(),
base.getFloorTransferTopology());
}
private String taskTypeCode(DispatchTask.TaskType taskType) {

View File

@@ -26,6 +26,8 @@ public final class SimulationTaskResult {
private final Map<String, Integer> retryReasons;
private final List<RoutePlanningEvidence> planningEvidence;
private final int rerouteCount;
private final AssignmentMode assignmentMode;
private final List<FloorTransferJob> floorTransferJobs;
/**
* 创建 {@code SimulationTaskResult} 实例并初始化其内部状态。
@@ -48,6 +50,8 @@ public final class SimulationTaskResult {
* @param retryReasons 执行该处理所需的 retry reasons 输入。
* @param planningEvidence 每个实际采用路径版本的接近段和服务段决策证据。
* @param rerouteCount 运行中成功替换剩余路线的次数。
* @param assignmentMode 中心采用的直接派车或先换层编排模式;未分配失败任务为 {@code null}。
* @param floorTransferJobs 父任务实际创建过的幂等换层作业证据。
*/
SimulationTaskResult(String simulationTaskId, HistoricalQueueRecord record,
SimulationTaskStatus status, String reason,
@@ -58,7 +62,8 @@ public final class SimulationTaskResult {
Map<String, Integer> holdReasons,
Map<String, Integer> retryReasons,
List<RoutePlanningEvidence> planningEvidence,
int rerouteCount) {
int rerouteCount, AssignmentMode assignmentMode,
List<FloorTransferJob> floorTransferJobs) {
this.simulationTaskId = simulationTaskId;
this.record = record;
this.status = status;
@@ -76,6 +81,8 @@ public final class SimulationTaskResult {
this.retryReasons = immutableMap(retryReasons);
this.planningEvidence = immutable(planningEvidence);
this.rerouteCount = rerouteCount;
this.assignmentMode = assignmentMode;
this.floorTransferJobs = immutable(floorTransferJobs);
}
/**
@@ -189,6 +196,15 @@ public final class SimulationTaskResult {
/** 返回运行中因连续交通 HOLD 成功切换路线的次数。 */
public int getRerouteCount() { return rerouteCount; }
/** 返回中心最终采用的直接派车或先换层编排模式。 */
public AssignmentMode getAssignmentMode() { return assignmentMode; }
/** 返回该父任务实际创建过的全部换层作业,供幂等和重派审计。 */
public List<FloorTransferJob> getFloorTransferJobs() { return floorTransferJobs; }
/** 返回该父任务产生的换层业务效果数;重复提交不会增加该计数。 */
public int getFloorTransferCount() { return floorTransferJobs.size(); }
/**
* 返回当前对象保存的“waiting millis”信息。
*