feat(javaTest): visualize source-aligned task phases
This commit is contained in:
@@ -23,9 +23,12 @@ public final class HistoricalDispatchVisualizerApplication {
|
||||
HistoricalQueueDataset dataset = classLoader.getResource("shadow/history/manifest.json") != null
|
||||
? loader.load(classLoader, "shadow/history")
|
||||
: loader.load(Paths.get("src/test/resources/shadow/history"));
|
||||
HistoricalSimulationInput input = new HistoricalSimulationInputFactory().create(dataset);
|
||||
HistoricalSimulationInputFactory factory = new HistoricalSimulationInputFactory();
|
||||
HistoricalSimulationInput businessInput = factory.create(dataset);
|
||||
HistoricalSimulationInput rawInput = factory.createRawSnapshots(dataset);
|
||||
HistoricalDispatchVisualizerServer server =
|
||||
new HistoricalDispatchVisualizerServer(port, dataset, input);
|
||||
new HistoricalDispatchVisualizerServer(
|
||||
port, dataset, businessInput, rawInput);
|
||||
CountDownLatch shutdown = new CountDownLatch(1);
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||
server.stop();
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.ft546.wcs.shadow.history.HistoricalQueueDataset;
|
||||
import com.ft546.wcs.shadow.history.simulation.HistoricalDispatchSimulationResult;
|
||||
import com.ft546.wcs.shadow.history.simulation.HistoricalSimulationInput;
|
||||
import com.ft546.wcs.shadow.history.simulation.SimulationExecutionMode;
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpHandler;
|
||||
import com.sun.net.httpserver.HttpServer;
|
||||
@@ -35,25 +36,35 @@ public final class HistoricalDispatchVisualizerServer {
|
||||
private final HttpServer httpServer;
|
||||
private final ExecutorService executor;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final HistoricalSimulationInput input;
|
||||
private final HistoricalSimulationInput businessInput;
|
||||
private final HistoricalSimulationInput rawInput;
|
||||
private final PointToPointSimulationService pointService;
|
||||
private final HistoricalWorkloadSimulationService historicalService;
|
||||
private final HistoricalWorkloadSimulationService businessHistoricalService;
|
||||
private final HistoricalWorkloadSimulationService rawHistoricalService;
|
||||
private final SimulationVisualizationMapper mapper;
|
||||
private final AtomicBoolean simulationBusy = new AtomicBoolean(false);
|
||||
private final AtomicBoolean started = new AtomicBoolean(false);
|
||||
|
||||
public HistoricalDispatchVisualizerServer(
|
||||
int port, HistoricalQueueDataset dataset, HistoricalSimulationInput input)
|
||||
int port, HistoricalQueueDataset dataset,
|
||||
HistoricalSimulationInput businessInput, HistoricalSimulationInput rawInput)
|
||||
throws IOException {
|
||||
if (port < 0 || port > 65_535) {
|
||||
throw new IllegalArgumentException("port must be between 0 and 65535");
|
||||
}
|
||||
if (dataset == null || input == null) {
|
||||
throw new IllegalArgumentException("dataset and input are required");
|
||||
if (dataset == null || businessInput == null || rawInput == null) {
|
||||
throw new IllegalArgumentException("dataset, business input and raw input are required");
|
||||
}
|
||||
this.input = input;
|
||||
this.pointService = new PointToPointSimulationService(input);
|
||||
this.historicalService = new HistoricalWorkloadSimulationService(input);
|
||||
if (businessInput.getExecutionMode()
|
||||
!= SimulationExecutionMode.SOURCE_ALIGNED_BUSINESS
|
||||
|| rawInput.getExecutionMode() != SimulationExecutionMode.RAW_SNAPSHOT) {
|
||||
throw new IllegalArgumentException("historical inputs do not match their replay modes");
|
||||
}
|
||||
this.businessInput = businessInput;
|
||||
this.rawInput = rawInput;
|
||||
this.pointService = new PointToPointSimulationService(businessInput);
|
||||
this.businessHistoricalService = new HistoricalWorkloadSimulationService(businessInput);
|
||||
this.rawHistoricalService = new HistoricalWorkloadSimulationService(rawInput);
|
||||
this.mapper = new SimulationVisualizationMapper(dataset);
|
||||
this.objectMapper = new ObjectMapper();
|
||||
this.httpServer = HttpServer.create(new InetSocketAddress(
|
||||
@@ -131,14 +142,14 @@ public final class HistoricalDispatchVisualizerServer {
|
||||
Map<String, Object> health = new LinkedHashMap<String, Object>();
|
||||
health.put("status", "UP");
|
||||
health.put("mode", "OFFLINE_IN_MEMORY_SIMULATION");
|
||||
health.put("mapLoaded", !input.getGridMap().getSites().isEmpty());
|
||||
health.put("mapLoaded", !businessInput.getGridMap().getSites().isEmpty());
|
||||
health.put("simulationBusy", simulationBusy.get());
|
||||
writeJson(exchange, 200, health);
|
||||
return;
|
||||
}
|
||||
if ("/api/bootstrap".equals(path)) {
|
||||
requireMethod(method, "GET");
|
||||
writeJson(exchange, 200, mapper.bootstrap(input));
|
||||
writeJson(exchange, 200, mapper.bootstrap(businessInput));
|
||||
return;
|
||||
}
|
||||
if ("/api/simulations/point-to-point".equals(path)) {
|
||||
@@ -147,15 +158,19 @@ public final class HistoricalDispatchVisualizerServer {
|
||||
PointToPointSimulationRequest request = objectMapper.readValue(
|
||||
body, PointToPointSimulationRequest.class);
|
||||
writeJson(exchange, 200, runSimulation(() ->
|
||||
mapper.result(input, pointService.run(request))));
|
||||
mapper.result(businessInput, pointService.run(request))));
|
||||
return;
|
||||
}
|
||||
if ("/api/simulations/historical".equals(path)) {
|
||||
requireMethod(method, "POST");
|
||||
readBody(exchange);
|
||||
HistoricalSimulationRequest request = objectMapper.readValue(
|
||||
readBody(exchange), HistoricalSimulationRequest.class);
|
||||
writeJson(exchange, 200, runSimulation(() -> {
|
||||
HistoricalDispatchSimulationResult result = historicalService.run();
|
||||
return mapper.result(input, result);
|
||||
boolean raw = request.getMode() == SimulationExecutionMode.RAW_SNAPSHOT;
|
||||
HistoricalSimulationInput selectedInput = raw ? rawInput : businessInput;
|
||||
HistoricalDispatchSimulationResult result = raw
|
||||
? rawHistoricalService.run() : businessHistoricalService.run();
|
||||
return mapper.result(selectedInput, result);
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.ft546.wcs.shadow.history.visualizer;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.ft546.wcs.shadow.history.simulation.SimulationExecutionMode;
|
||||
|
||||
/**
|
||||
* 选择历史回放的审计口径。
|
||||
*
|
||||
* <p>业务模式把重复队列快照归并为真实父任务并执行二段式阶段;RAW 模式保留原先
|
||||
* 240 行逐条运行的兼容结果。空请求默认进入业务模式,避免页面误把快照数当成业务任务数。
|
||||
*/
|
||||
public final class HistoricalSimulationRequest {
|
||||
@JsonProperty("mode")
|
||||
private String mode;
|
||||
|
||||
/** 供 Jackson 反序列化请求体使用。 */
|
||||
public HistoricalSimulationRequest() {
|
||||
}
|
||||
|
||||
/** 返回经过校验的回放口径;未传值时默认按生产源码语义回放。 */
|
||||
public SimulationExecutionMode getMode() {
|
||||
String normalized = mode == null || mode.trim().isEmpty()
|
||||
? SimulationExecutionMode.SOURCE_ALIGNED_BUSINESS.name()
|
||||
: mode.trim().toUpperCase();
|
||||
try {
|
||||
return SimulationExecutionMode.valueOf(normalized);
|
||||
} catch (IllegalArgumentException exception) {
|
||||
throw new IllegalArgumentException(
|
||||
"INVALID_EXECUTION_MODE: mode must be SOURCE_ALIGNED_BUSINESS or RAW_SNAPSHOT",
|
||||
exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import com.ft546.wcs.shadow.history.simulation.HistoricalSimulationInput;
|
||||
import com.ft546.wcs.shadow.history.simulation.SimulatedPlcGateway;
|
||||
import com.ft546.wcs.shadow.history.simulation.SimulationConfig;
|
||||
|
||||
/** 重新运行由 240 条历史快照组装出的业务父任务,并保留既定的确定性 PLC 故障注入。 */
|
||||
/** 按调用方选择的不可变输入运行历史回放,并保留既定的确定性 PLC 故障注入。 */
|
||||
public final class HistoricalWorkloadSimulationService {
|
||||
private final HistoricalSimulationInput input;
|
||||
|
||||
@@ -17,6 +17,7 @@ public final class HistoricalWorkloadSimulationService {
|
||||
}
|
||||
|
||||
public HistoricalDispatchSimulationResult run() {
|
||||
// 每种模式都对自身任务序列稳定注入故障,保证业务/RAW 对比可重复而不共享运行状态。
|
||||
DeterministicFaultPlan.Builder faults = DeterministicFaultPlan.builder();
|
||||
for (int i = 0; i < input.getTasks().size(); i += 37) {
|
||||
faults.plcFailure(input.getTasks().get(i).getSimulationTaskId(), 1,
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.ft546.wcs.shadow.history.HistoricalQueueRecord;
|
||||
import com.ft546.wcs.shadow.history.simulation.HistoricalDispatchSimulationResult;
|
||||
import com.ft546.wcs.shadow.history.simulation.HistoricalSimulationInput;
|
||||
import com.ft546.wcs.shadow.history.simulation.HistoricalSimulationTask;
|
||||
import com.ft546.wcs.shadow.history.simulation.HistoricalTaskSnapshotEvidence;
|
||||
import com.ft546.wcs.shadow.history.simulation.FloorTransferJob;
|
||||
import com.ft546.wcs.shadow.history.simulation.RoutePlanningEvidence;
|
||||
import com.ft546.wcs.shadow.history.simulation.RerouteDecisionEvidence;
|
||||
@@ -16,6 +17,7 @@ import com.ft546.wcs.shadow.history.simulation.SimulationMetrics;
|
||||
import com.ft546.wcs.shadow.history.simulation.SimulationTaskResult;
|
||||
import com.ft546.wcs.shadow.history.simulation.SimulationTaskStatus;
|
||||
import com.ft546.wcs.shadow.history.simulation.SimulationTickEvent;
|
||||
import com.ft546.wcs.shadow.history.simulation.TaskPhaseEvidence;
|
||||
import com.ft546.wcs.demo.traffic.RouteCandidateEvaluation;
|
||||
import com.ft546.wcs.demo.traffic.RoutePlanDecision;
|
||||
import com.ft546.wcs.demo.traffic.RouteQuality;
|
||||
@@ -121,6 +123,7 @@ public final class SimulationVisualizationMapper {
|
||||
payload.put("plannerVersion", PLANNER_VERSION);
|
||||
payload.put("mapSha256", dataset.getFullMapSha256());
|
||||
payload.put("pathInfoUsedForPlanning", false);
|
||||
payload.put("executionMode", result.getExecutionMode().name());
|
||||
payload.put("summary", summary(result, definitions, input.getGridMap()));
|
||||
payload.put("tasks", tasks);
|
||||
payload.put("events", events);
|
||||
@@ -135,6 +138,13 @@ public final class SimulationVisualizationMapper {
|
||||
GridMap gridMap) {
|
||||
Map<String, Object> value = new LinkedHashMap<String, Object>();
|
||||
value.put("inputCount", result.getTaskResults().size());
|
||||
value.put("rawSnapshotCount", result.getInputAudit().getRawSnapshotCount());
|
||||
value.put("uniqueQueueCount", result.getInputAudit().getUniqueQueueCount());
|
||||
value.put("duplicateSnapshotCount", result.getInputAudit().getDuplicateSnapshotCount());
|
||||
value.put("businessTaskCount", result.getInputAudit().getBusinessTaskCount());
|
||||
value.put("operationalQueueCount", result.getInputAudit().getOperationalQueueCount());
|
||||
value.put("incompleteVehicleReleaseCount",
|
||||
result.getIncompleteVehicleReleaseCount());
|
||||
value.put("acceleration", result.getAcceleration());
|
||||
value.put("completedCount", result.getCompletedCount());
|
||||
value.put("waitingCount", result.getWaitingCount());
|
||||
@@ -199,6 +209,8 @@ public final class SimulationVisualizationMapper {
|
||||
value.put("guid", record.getGuid());
|
||||
value.put("taskType", definition == null
|
||||
? record.getTaskType() : definition.getTaskType().name());
|
||||
value.put("businessType", definition == null
|
||||
? record.getTaskType() : definition.getTaskType().name());
|
||||
value.put("priority", record.getPriority());
|
||||
value.put("businessStartSite", record.getStartSite());
|
||||
value.put("businessEndSite", record.getEndSite());
|
||||
@@ -206,6 +218,10 @@ public final class SimulationVisualizationMapper {
|
||||
? record.getStartSite() : definition.getPhysicalSource());
|
||||
value.put("physicalEndSite", definition == null
|
||||
? record.getEndSite() : definition.getPhysicalTarget());
|
||||
value.put("sourceFrontSite", definition == null
|
||||
? null : definition.getSourceFrontSite());
|
||||
value.put("targetFrontSite", definition == null
|
||||
? null : definition.getTargetFrontSite());
|
||||
value.put("historicalVehicleId", record.getAgvId());
|
||||
value.put("simulatedVehicleId", task.getSimulatedVehicleId());
|
||||
value.put("assignmentMode", task.getAssignmentMode() == null
|
||||
@@ -224,6 +240,13 @@ public final class SimulationVisualizationMapper {
|
||||
value.put("arrivalMillis", task.getArrivalMillis());
|
||||
value.put("firstAssignedMillis", task.getFirstAssignedMillis());
|
||||
value.put("completedMillis", task.getCompletedMillis());
|
||||
value.put("businessCompletedMillis", task.getBusinessCompletedMillis());
|
||||
value.put("vehicleReleasedMillis", task.getVehicleReleasedMillis());
|
||||
value.put("vehicleReleaseStatus", task.getVehicleReleaseStatus().name());
|
||||
value.put("cargoState", task.getCargoState().name());
|
||||
value.put("skippedStageReasons", task.getSkippedStageReasons());
|
||||
value.put("snapshotEvidence", snapshotEvidence(definition));
|
||||
value.put("phaseEvidence", phaseEvidence(task.getPhaseEvidence()));
|
||||
value.put("waitingMillis", task.getWaitingMillis());
|
||||
value.put("simulationDurationMillis", simulatedDuration);
|
||||
value.put("historicalDurationMillis", historicalDuration);
|
||||
@@ -259,6 +282,42 @@ public final class SimulationVisualizationMapper {
|
||||
return value;
|
||||
}
|
||||
|
||||
/** 输出父任务关联的原始空车、载货和重复扫描记录,供页面追溯 GUID,而不参与重新规划。 */
|
||||
private List<Map<String, Object>> snapshotEvidence(HistoricalSimulationTask definition) {
|
||||
List<Map<String, Object>> values = new ArrayList<Map<String, Object>>();
|
||||
if (definition == null) return values;
|
||||
for (HistoricalTaskSnapshotEvidence evidence : definition.getSnapshotEvidence()) {
|
||||
Map<String, Object> value = new LinkedHashMap<String, Object>();
|
||||
value.put("snapshotRow", evidence.getSnapshotRow());
|
||||
value.put("guid", evidence.getGuid());
|
||||
value.put("queueTaskType", evidence.getQueueTaskType());
|
||||
value.put("default02", evidence.getDefault02());
|
||||
value.put("historicalVehicleId", evidence.getHistoricalVehicleId());
|
||||
value.put("historicalPath", historicalPath(evidence.getHistoricalPath()));
|
||||
values.add(value);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
/** 输出仿真器冻结的逐阶段真实路径;前端只选择和绘制,不重新推导业务阶段。 */
|
||||
private List<Map<String, Object>> phaseEvidence(List<TaskPhaseEvidence> evidence) {
|
||||
List<Map<String, Object>> values = new ArrayList<Map<String, Object>>();
|
||||
for (TaskPhaseEvidence phase : evidence) {
|
||||
Map<String, Object> value = new LinkedHashMap<String, Object>();
|
||||
value.put("phase", phase.getPhase().name());
|
||||
value.put("fromSite", phase.getFromSite());
|
||||
value.put("toSite", phase.getToSite());
|
||||
value.put("plannedPath", phase.getPlannedPath());
|
||||
value.put("startedMillis", phase.getStartedMillis());
|
||||
value.put("completedMillis", phase.getCompletedMillis());
|
||||
value.put("holdCount", phase.getHoldCount());
|
||||
value.put("retryCount", phase.getRetryCount());
|
||||
value.put("completionReason", phase.getCompletionReason());
|
||||
values.add(value);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* 汇总父任务换层作业经过的楼层,供页面楼层选择器标记整条业务链涉及的地图。
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user