feat(javaTest): inspect bounded route decisions on full map

This commit is contained in:
zgw
2026-08-06 18:49:15 +08:00
parent 58de793143
commit bd7decf4fd
12 changed files with 728 additions and 22 deletions

View File

@@ -1064,9 +1064,12 @@ public final class HistoricalDispatchSimulator {
current.execution.pathVersion = nextVersion;
current.execution.rerouteCount++;
current.execution.planningEvidence.addAll(newEvidence);
events.add(event(tick, now, "REROUTE", taskId, vehicle.getVehicleId(),
"STRICTLY_BETTER_TRAFFIC_QUALITY", oldRemaining.get(0),
newRemaining.get(newRemaining.size() - 1)));
// 只有预约和活动路线都提交成功后才发布前后证据,页面看到的版本一定真实生效。
events.add(new SimulationTickEvent(tick, now, "REROUTE", taskId,
vehicle.getVehicleId(), "STRICTLY_BETTER_TRAFFIC_QUALITY",
oldRemaining.get(0), newRemaining.get(newRemaining.size() - 1),
new RerouteDecisionEvidence(nextVersion, oldRemaining, newRemaining,
oldQuality, newQuality)));
return true;
}

View File

@@ -0,0 +1,63 @@
package com.ft546.wcs.shadow.history.simulation;
import com.ft546.wcs.demo.traffic.RouteQuality;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/** 成功替换交通预约时保存的重规划前后剩余路线和质量证据。 */
public final class RerouteDecisionEvidence {
private final int pathVersion;
private final List<String> oldRemainingPath;
private final List<String> newRemainingPath;
private final RouteQuality oldQuality;
private final RouteQuality newQuality;
/**
* 固化一次已经提交的路线替换,供报告和地图检查器还原当时的业务判断。
*
* <p>只有预约原子替换成功后才创建该对象,因此它代表实际生效版本,而不是一次尝试候选。
* @param pathVersion 替换成功后的路线版本。
* @param oldRemainingPath 替换前从车辆当前位置开始的剩余路线。
* @param newRemainingPath 替换后从车辆当前位置开始的剩余路线。
* @param oldQuality 旧剩余路线在触发时交通快照中的质量。
* @param newQuality 新剩余路线在同一交通快照中的质量。
*/
public RerouteDecisionEvidence(int pathVersion,
List<String> oldRemainingPath,
List<String> newRemainingPath,
RouteQuality oldQuality,
RouteQuality newQuality) {
if (pathVersion <= 1 || oldRemainingPath == null || oldRemainingPath.isEmpty()
|| newRemainingPath == null || newRemainingPath.isEmpty()
|| oldQuality == null || newQuality == null) {
throw new IllegalArgumentException("complete reroute evidence is required");
}
this.pathVersion = pathVersion;
this.oldRemainingPath = immutable(oldRemainingPath);
this.newRemainingPath = immutable(newRemainingPath);
this.oldQuality = oldQuality;
this.newQuality = newQuality;
}
/** 返回原子替换成功后采用的路线版本号。 */
public int getPathVersion() { return pathVersion; }
/** 返回替换前仍待车辆执行的真实路线段。 */
public List<String> getOldRemainingPath() { return oldRemainingPath; }
/** 返回替换后已经取得交通预约的真实路线段。 */
public List<String> getNewRemainingPath() { return newRemainingPath; }
/** 返回触发重规划时旧剩余路线的质量元组。 */
public RouteQuality getOldQuality() { return oldQuality; }
/** 返回同一快照下新剩余路线的质量元组。 */
public RouteQuality getNewQuality() { return newQuality; }
/** 对外暴露防御性、不可变路径,防止报告层改写仿真事实。 */
private List<String> immutable(List<String> values) {
return Collections.unmodifiableList(new ArrayList<String>(values));
}
}

View File

@@ -9,6 +9,7 @@ public final class SimulationTickEvent {
private final String reason;
private final String fromSite;
private final String toSite;
private final RerouteDecisionEvidence rerouteDecision;
/**
* 创建 {@code SimulationTickEvent} 实例并初始化其内部状态。
@@ -26,6 +27,26 @@ public final class SimulationTickEvent {
public SimulationTickEvent(long tick, long simulationMillis, String type,
String taskId, String vehicleId, String reason,
String fromSite, String toSite) {
this(tick, simulationMillis, type, taskId, vehicleId, reason,
fromSite, toSite, null);
}
/**
* 创建带结构化重规划决定的审计事件;该扩展仅用于已经成功提交的 {@code REROUTE}。
* @param tick 事件所在仿真 tick。
* @param simulationMillis 相对仿真时间。
* @param type 事件类型。
* @param taskId 任务标识。
* @param vehicleId 执行车辆。
* @param reason 业务原因。
* @param fromSite 事件起点。
* @param toSite 事件终点。
* @param rerouteDecision 重规划前后路径与质量;普通事件为 {@code null}。
*/
public SimulationTickEvent(long tick, long simulationMillis, String type,
String taskId, String vehicleId, String reason,
String fromSite, String toSite,
RerouteDecisionEvidence rerouteDecision) {
this.tick = tick;
this.simulationMillis = simulationMillis;
this.type = type;
@@ -34,6 +55,7 @@ public final class SimulationTickEvent {
this.reason = reason;
this.fromSite = fromSite;
this.toSite = toSite;
this.rerouteDecision = rerouteDecision;
}
/**
@@ -92,4 +114,7 @@ public final class SimulationTickEvent {
* @return 本次处理产生的结果;调用方据此继续后续流程。
*/
public String getToSite() { return toSite; }
/** 返回成功重规划事件的结构化决定;非 REROUTE 事件返回 {@code null}。 */
public RerouteDecisionEvidence getRerouteDecision() { return rerouteDecision; }
}

View File

@@ -9,6 +9,7 @@ import com.ft546.wcs.shadow.history.simulation.HistoricalDispatchSimulationResul
import com.ft546.wcs.shadow.history.simulation.HistoricalSimulationInput;
import com.ft546.wcs.shadow.history.simulation.HistoricalSimulationTask;
import com.ft546.wcs.shadow.history.simulation.RoutePlanningEvidence;
import com.ft546.wcs.shadow.history.simulation.RerouteDecisionEvidence;
import com.ft546.wcs.shadow.history.simulation.SimulationInvariantSummary;
import com.ft546.wcs.shadow.history.simulation.SimulationMetrics;
import com.ft546.wcs.shadow.history.simulation.SimulationTaskResult;
@@ -108,7 +109,7 @@ public final class SimulationVisualizationMapper {
List<Map<String, Object>> tasks = new ArrayList<Map<String, Object>>();
for (SimulationTaskResult task : orderedTasks) {
tasks.add(task(task, definitions.get(task.getSimulationTaskId()),
result.getAcceleration(), input.getGridMap()));
result.getAcceleration(), input.getGridMap(), result.getEvents()));
}
List<Map<String, Object>> events = new ArrayList<Map<String, Object>>();
for (SimulationTickEvent event : result.getEvents()) events.add(event(event));
@@ -182,7 +183,8 @@ public final class SimulationVisualizationMapper {
private Map<String, Object> task(SimulationTaskResult task,
HistoricalSimulationTask definition,
int acceleration,
GridMap gridMap) {
GridMap gridMap,
List<SimulationTickEvent> allEvents) {
HistoricalQueueRecord record = task.getRecord();
List<String> historicalPath = historicalPath(record.getPathInfo());
Long historicalDuration = historicalDuration(task);
@@ -242,9 +244,27 @@ public final class SimulationVisualizationMapper {
value.put("servicePlanningEvidence", planningEvidence(task,
RoutePlanningEvidence.PlanningStage.SERVICE));
value.put("rerouteCount", task.getRerouteCount());
Set<Integer> pathVersions = new TreeSet<Integer>();
for (RoutePlanningEvidence evidence : task.getPlanningEvidence()) {
pathVersions.add(evidence.getPathVersion());
}
value.put("pathVersions", new ArrayList<Integer>(pathVersions));
value.put("rerouteEvents", rerouteEvents(task.getSimulationTaskId(), allEvents));
return value;
}
/** 返回当前任务的重规划触发、拒绝和成功事件,供前端按版本还原决策链。 */
private List<Map<String, Object>> rerouteEvents(
String taskId, List<SimulationTickEvent> allEvents) {
List<Map<String, Object>> values = new ArrayList<Map<String, Object>>();
for (SimulationTickEvent event : allEvents) {
if (!taskId.equals(event.getTaskId())
|| !event.getType().startsWith("REROUTE")) continue;
values.add(event(event));
}
return values;
}
/** 按阶段转换每次实际采用的候选、质量、距离边界和版本证据。 */
private List<Map<String, Object>> planningEvidence(
SimulationTaskResult task, RoutePlanningEvidence.PlanningStage stage) {
@@ -347,6 +367,19 @@ public final class SimulationVisualizationMapper {
value.put("reason", event.getReason());
value.put("fromSite", event.getFromSite());
value.put("toSite", event.getToSite());
value.put("rerouteDecision", rerouteDecision(event.getRerouteDecision()));
return value;
}
/** 把成功路线替换转换为明确的旧/新剩余路径和同快照质量。 */
private Map<String, Object> rerouteDecision(RerouteDecisionEvidence source) {
if (source == null) return null;
Map<String, Object> value = new LinkedHashMap<String, Object>();
value.put("pathVersion", source.getPathVersion());
value.put("oldRemainingPath", source.getOldRemainingPath());
value.put("newRemainingPath", source.getNewRemainingPath());
value.put("oldQuality", quality(source.getOldQuality()));
value.put("newQuality", quality(source.getNewQuality()));
return value;
}