Files
FT546-WCS/docs/superpowers/plans/2026-08-05-javaTest-dispatch-visualizer.md

451 lines
19 KiB
Markdown

# javaTest Dispatch Visualizer Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Build a localhost-only HTML audit console that runs the existing Java scheduler for arbitrary A-to-B tasks and the complete 240-task historical workload, then renders selected vehicles, planned paths, tick movement, failures, and safety evidence.
**Architecture:** Add a focused point-to-point input factory beside the simulation domain, a visualizer service that invokes the existing simulator, a deterministic map-to-JSON mapper, and a Java 8 `HttpServer` that serves same-origin static assets and APIs. The browser never replans routes; it draws `plannedPath`, map coordinates, and `MOVE` events returned by Java.
**Tech Stack:** Java 8, Maven, Jackson 2.17.2, JUnit 5.10.2, JDK `HttpServer`, HTML5, CSS, vanilla JavaScript, SVG, Playwright for browser QA.
## Global Constraints
- Work only under `javaTest` plus the approved design/plan documents.
- Reuse `HistoricalDispatchSimulator`, `GridPathPlanner`, `VehicleAssignmentService`, traffic arbitration, PLC simulation, archive, and WMS idempotency logic.
- Do not add Spring, Node build dependencies, database connections, PLC connections, or WMS connections.
- Bind the visualizer to `127.0.0.1`; default port is `18084`.
- Do not accept arbitrary filesystem paths or execute user-supplied commands.
- Historical `agv_id` and `path_info` are comparison inputs only.
- The UI must distinguish approach path, service path, and traversed path.
- The UI must remain an offline audit tool and must not claim to guarantee production safety.
- Preserve unrelated worktree changes.
---
### Task 1: Point-to-Point Simulation Input and Fault Presets
**Files:**
- Create: `javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/PointToPointSimulationInputFactory.java`
- Create: `javaTest/src/main/java/com/ft546/wcs/shadow/history/visualizer/PointToPointSimulationRequest.java`
- Create: `javaTest/src/main/java/com/ft546/wcs/shadow/history/visualizer/PointToPointSimulationService.java`
- Test: `javaTest/src/test/java/com/ft546/wcs/shadow/history/visualizer/PointToPointSimulationServiceTest.java`
**Interfaces:**
- Consumes: `HistoricalSimulationInput`, `HistoricalDispatchSimulator`, `SimulationConfig`, and `DeterministicFaultPlan`.
- Produces: `PointToPointSimulationInputFactory.create(HistoricalSimulationInput, String, String, DispatchTask.TaskType, int, String, List<String>)` and `PointToPointSimulationService.run(PointToPointSimulationRequest)`.
- [ ] **Step 1: Write the failing service tests**
```java
@Test
void runsOneTaskWithTheRealPlannerAndIgnoresHistoricalPath() {
HistoricalSimulationInput base = loadBaseInput();
HistoricalSimulationTask sample = base.getTasks().get(0);
PointToPointSimulationRequest request = PointToPointSimulationRequest.valid(
sample.getPhysicalSource(), sample.getPhysicalTarget(), "INBOUND", 1, 100,
"HISTORY-CAR", Arrays.asList("WRONG", "REFERENCE"), "NONE");
HistoricalDispatchSimulationResult result = service(base).run(request);
SimulationTaskResult task = result.getTaskResults().get(0);
assertEquals(SimulationTaskStatus.COMPLETED, task.getStatus());
assertEquals(sample.getPhysicalSource(), task.getPlannedPath().get(0));
assertEquals(sample.getPhysicalTarget(),
task.getPlannedPath().get(task.getPlannedPath().size() - 1));
assertFalse(task.getPlannedPath().equals(Arrays.asList("WRONG", "REFERENCE")));
assertEquals(0, result.getInvariants().totalSafetyViolations());
}
@Test
void rejectsUnknownAndIdenticalEndpointsBeforeSimulation() {
assertThrows(IllegalArgumentException.class, () -> service(base).run(
PointToPointSimulationRequest.valid("UNKNOWN", validSite, "INBOUND", 1,
100, null, Collections.emptyList(), "NONE")));
assertThrows(IllegalArgumentException.class, () -> service(base).run(
PointToPointSimulationRequest.valid(validSite, validSite, "INBOUND", 1,
100, null, Collections.emptyList(), "NONE")));
}
```
- [ ] **Step 2: Run the focused test and confirm it fails**
Run:
```bash
mvn "-Dtest=PointToPointSimulationServiceTest" test
```
Expected: test compilation fails because the visualizer request/service/factory do not exist.
- [ ] **Step 3: Implement the request and package-local input factory**
The factory creates one `HistoricalQueueRecord` and one `HistoricalSimulationTask` with ID
`VISUALIZER-TASK-1`, current fixed simulation time, request business fields, and the base input's map,
traffic map, and initial vehicles. It validates that both sites exist and differ.
```java
public HistoricalSimulationInput create(HistoricalSimulationInput base,
String sourceSite,
String targetSite,
DispatchTask.TaskType taskType,
int priority,
String historicalVehicleId,
List<String> historicalPath) {
// Construct the comparison record and one simulation task here, inside the
// simulation package where package-private constructors are intentionally visible.
}
```
`PointToPointSimulationRequest` normalizes task type, acceleration, empty comparison fields, and
fault preset. It rejects priority outside `0..9`, acceleration other than `10/100`, and unknown enums.
- [ ] **Step 4: Implement fault presets and run the real simulator**
```java
public HistoricalDispatchSimulationResult run(PointToPointSimulationRequest request) {
HistoricalSimulationInput input = inputFactory.create(/* normalized request fields */);
return simulator.simulate(input,
new SimulationConfig(request.getAcceleration(), 30_000L, 3, 200, 5_000),
faultPlanFor(request.getFaultPreset()));
}
```
`PLC_FIRST_SEND_FAILURE` injects `SEND_FAILURE` on attempt 1 for `VISUALIZER-TASK-1`.
Vehicle presets affect all initial vehicles at tick 0 and restore/connect/synchronize at tick 2 so the
selected task actually demonstrates the requested recovery path.
- [ ] **Step 5: Run focused tests and commit**
Run:
```bash
mvn "-Dtest=PointToPointSimulationServiceTest" test
```
Expected: all focused tests pass.
Commit:
```bash
git add javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/PointToPointSimulationInputFactory.java javaTest/src/main/java/com/ft546/wcs/shadow/history/visualizer javaTest/src/test/java/com/ft546/wcs/shadow/history/visualizer/PointToPointSimulationServiceTest.java
git commit -m "feat(javaTest): add point-to-point simulation service"
```
---
### Task 2: Visualization DTO Mapping
**Files:**
- Create: `javaTest/src/main/java/com/ft546/wcs/shadow/history/visualizer/SimulationVisualizationMapper.java`
- Test: `javaTest/src/test/java/com/ft546/wcs/shadow/history/visualizer/SimulationVisualizationMapperTest.java`
**Interfaces:**
- Consumes: `HistoricalSimulationInput` and `HistoricalDispatchSimulationResult`.
- Produces: `Map<String, Object> bootstrap(HistoricalSimulationInput)` and `Map<String, Object> result(HistoricalSimulationInput, HistoricalDispatchSimulationResult)`.
- [ ] **Step 1: Write failing mapping tests**
```java
@Test
void mapsSitesTasksEventsAndSafetyEvidenceWithoutReplanning() {
Map<String, Object> payload = mapper.result(input, result);
List<Map<String, Object>> tasks = castList(payload.get("tasks"));
List<Map<String, Object>> events = castList(payload.get("events"));
assertEquals(result.getTaskResults().get(0).getPlannedPath(), tasks.get(0).get("plannedPath"));
assertTrue(events.stream().anyMatch(event -> "ASSIGN".equals(event.get("eventType"))));
assertTrue(events.stream().anyMatch(event -> "MOVE".equals(event.get("eventType"))));
assertEquals(0, castMap(payload.get("invariants")).get("totalSafetyViolations"));
}
@Test
void bootstrapReturnsSortedSearchableSitesAndInitialVehicles() {
Map<String, Object> payload = mapper.bootstrap(input);
assertFalse(castList(payload.get("sites")).isEmpty());
assertEquals("OFFLINE_IN_MEMORY_SIMULATION", payload.get("mode"));
}
```
- [ ] **Step 2: Run the mapping tests and confirm failure**
Run `mvn "-Dtest=SimulationVisualizationMapperTest" test`.
Expected: compilation failure because `SimulationVisualizationMapper` does not exist.
- [ ] **Step 3: Implement stable ordered JSON maps**
Use `LinkedHashMap` and sorted task/site/event lists. Include:
```text
bootstrap: mode, boundary, plannerVersion, mapSha256, floors, sites, vehicles
result: summary, tasks, events, invariants, archive, wms
task: identity, endpoints, vehicles, historicalPath, plannedPath, status, timing, holds, retries
event: tick, simulationMillis, eventType, taskId, vehicleId, reason, fromSite, toSite
site: siteId, floor, x, y, channel
```
Do not call `GridPathPlanner` from the mapper. The mapper copies the simulator result unchanged.
- [ ] **Step 4: Run mapper and existing simulator tests**
```bash
mvn "-Dtest=SimulationVisualizationMapperTest,HistoricalDispatchSimulatorTest" test
```
Expected: all selected tests pass.
- [ ] **Step 5: Commit**
```bash
git add javaTest/src/main/java/com/ft546/wcs/shadow/history/visualizer/SimulationVisualizationMapper.java javaTest/src/test/java/com/ft546/wcs/shadow/history/visualizer/SimulationVisualizationMapperTest.java
git commit -m "feat(javaTest): map simulation evidence for visualization"
```
---
### Task 3: Local HTTP Server and APIs
**Files:**
- Create: `javaTest/src/main/java/com/ft546/wcs/shadow/history/visualizer/HistoricalDispatchVisualizerApplication.java`
- Create: `javaTest/src/main/java/com/ft546/wcs/shadow/history/visualizer/HistoricalDispatchVisualizerServer.java`
- Create: `javaTest/src/main/java/com/ft546/wcs/shadow/history/visualizer/VisualizerHttpException.java`
- Test: `javaTest/src/test/java/com/ft546/wcs/shadow/history/visualizer/HistoricalDispatchVisualizerServerTest.java`
**Interfaces:**
- Consumes: Task 1 services and Task 2 mapper.
- Produces: `start()`, `stop()`, `getPort()`, `/api/health`, `/api/bootstrap`, `/api/simulations/point-to-point`, `/api/simulations/historical`, and static asset responses.
- [ ] **Step 1: Write failing HTTP contract tests**
```java
@Test
void servesHealthBootstrapAndPointToPointSimulation() throws Exception {
server = fixture.startOnPort(0);
assertEquals(200, get("/api/health").status);
assertTrue(get("/api/bootstrap").body.contains("OFFLINE_IN_MEMORY_SIMULATION"));
HttpResult response = postJson("/api/simulations/point-to-point", validRequestJson());
assertEquals(200, response.status);
assertTrue(response.body.contains("plannedPath"));
assertTrue(response.body.contains("MOVE"));
}
@Test
void rejectsUnknownRoutesMethodsOversizedBodiesAndConcurrentRuns() {
assertEquals(404, get("/api/unknown").status);
assertEquals(405, postJson("/api/health", "{}").status);
assertEquals(413, postJson("/api/simulations/point-to-point", oversizedBody()).status);
}
```
- [ ] **Step 2: Run the server tests and confirm failure**
Run `mvn "-Dtest=HistoricalDispatchVisualizerServerTest" test`.
Expected: compilation failure because the server classes do not exist.
- [ ] **Step 3: Implement same-origin JSON handlers**
Use a fixed thread pool with a single simulation executor and an `AtomicBoolean` busy gate. Parse and
write JSON only through the existing Jackson `ObjectMapper`. Limit request bodies before parsing.
```java
private void writeJson(HttpExchange exchange, int status, Object payload) throws IOException {
byte[] body = objectMapper.writeValueAsBytes(payload);
exchange.getResponseHeaders().set("Content-Type", "application/json; charset=utf-8");
exchange.getResponseHeaders().set("Cache-Control", "no-store");
exchange.sendResponseHeaders(status, body.length);
exchange.getResponseBody().write(body);
}
```
Map request failures to stable codes and never return stack traces or absolute paths.
- [ ] **Step 4: Implement the application bootstrap**
Load `shadow/history` with `HistoricalQueueLoader`, create the base input once, create services and mapper,
bind `127.0.0.1:<port>`, print the exact URL, and add a shutdown hook.
- [ ] **Step 5: Run HTTP tests and commit**
Run `mvn "-Dtest=HistoricalDispatchVisualizerServerTest" test`.
Expected: all HTTP contract tests pass and test server threads terminate.
Commit:
```bash
git add javaTest/src/main/java/com/ft546/wcs/shadow/history/visualizer javaTest/src/test/java/com/ft546/wcs/shadow/history/visualizer/HistoricalDispatchVisualizerServerTest.java
git commit -m "feat(javaTest): expose local dispatch visualizer API"
```
---
### Task 4: Operational HTML Shell and Route Playback
**Files:**
- Create: `javaTest/src/main/resources/visualizer/index.html`
- Create: `javaTest/src/main/resources/visualizer/assets/app.css`
- Create: `javaTest/src/main/resources/visualizer/assets/app.js`
- Modify: `javaTest/src/main/java/com/ft546/wcs/shadow/history/visualizer/HistoricalDispatchVisualizerServer.java`
- Test: `javaTest/src/test/java/com/ft546/wcs/shadow/history/visualizer/HistoricalDispatchVisualizerServerTest.java`
**Interfaces:**
- Consumes: `/api/bootstrap` and both simulation APIs.
- Produces: a responsive first-screen audit console with stable DOM IDs used by browser QA.
- [ ] **Step 1: Extend static resource tests and confirm failure**
```java
@Test
void servesTheVisualizerShellAndAssets() throws Exception {
assertEquals(200, get("/").status);
assertTrue(get("/").body.contains("调度路径审计台"));
assertEquals("text/css; charset=utf-8", get("/assets/app.css").contentType);
assertEquals("application/javascript; charset=utf-8", get("/assets/app.js").contentType);
}
```
Run `mvn "-Dtest=HistoricalDispatchVisualizerServerTest#servesTheVisualizerShellAndAssets" test`.
Expected: 404 or missing resource failure.
- [ ] **Step 2: Build the semantic HTML shell**
Create fixed sections with IDs:
```html
<header id="app-header">...</header>
<nav id="mode-tabs" aria-label="审计模式">...</nav>
<section id="point-controls">...</section>
<main id="workspace">
<section id="route-stage"><svg id="route-map" role="img"></svg></section>
<aside id="task-inspector"></aside>
</main>
<section id="timeline-panel">...</section>
<section id="history-view" hidden>...</section>
<section id="safety-view" hidden>...</section>
```
The page starts as the usable tool, with no hero, marketing copy, nested cards, or decorative illustration.
- [ ] **Step 3: Implement responsive operational styling**
Use stable grid tracks, maximum 8px radius, zero negative letter spacing, and no viewport-scaled font size.
Desktop keeps the route stage dominant; under 900px it stacks controls, route, inspector, and timeline.
Use neutral surfaces plus green, blue, amber, and red semantics.
- [ ] **Step 4: Implement API state, route drawing, and tick playback**
`app.js` must:
```text
bootstrap -> populate searchable A/B datalists and metadata
run point task -> store result -> select task -> render path -> reset tick to 0
render path -> use returned site x/y/floor; draw approach, planned, and traversed layers
advance tick -> update vehicle marker, event focus, traversed edges, inspector, slider
play/pause/step -> use fixed timer and never mutate result data
```
Path coordinates use deterministic normalization with fixed SVG padding. Empty/unknown paths render an
explicit status panel instead of a blank SVG.
- [ ] **Step 5: Implement historical and safety modes**
Historical mode runs the 240-task API on command, renders KPI bands and a searchable/filterable task table,
and opens the selected task in the shared path/timeline renderer. Safety mode renders every invariant,
HOLD/retry reason distribution, archive count, and WMS idempotency. Any non-zero safety invariant changes the
global gate to `验收不通过`.
- [ ] **Step 6: Run server tests and commit**
Run:
```bash
mvn "-Dtest=HistoricalDispatchVisualizerServerTest" test
```
Expected: all static and API tests pass.
Commit:
```bash
git add javaTest/src/main/resources/visualizer javaTest/src/main/java/com/ft546/wcs/shadow/history/visualizer/HistoricalDispatchVisualizerServer.java javaTest/src/test/java/com/ft546/wcs/shadow/history/visualizer/HistoricalDispatchVisualizerServerTest.java
git commit -m "feat(javaTest): add interactive dispatch audit console"
```
---
### Task 5: Documentation, Full Regression, and Browser Acceptance
**Files:**
- Modify: `javaTest/README.md`
- Create: `javaTest/src/test/resources/visualizer/point-to-point-request.json`
- Create: `javaTest/visualizer-playwright.spec.js` only if the workspace Playwright runner requires a checked-in script; otherwise keep QA scripts in a temporary workspace path.
**Interfaces:**
- Consumes: completed visualizer application.
- Produces: reproducible launch instructions, Java regression evidence, and desktop/mobile browser evidence.
- [ ] **Step 1: Document launch and safety boundary**
Add the exact Maven command, URL, three modes, route color legend, and statement that this tool is offline
evidence rather than production/HIL acceptance.
- [ ] **Step 2: Run all Java tests**
```bash
mvn clean test
```
Expected: Surefire discovers the existing 630 tests plus all new tests; Failures `0`, Errors `0`.
- [ ] **Step 3: Start the visualizer server**
```bash
mvn compile exec:java \
-Dexec.mainClass=com.ft546.wcs.shadow.history.visualizer.HistoricalDispatchVisualizerApplication \
-Dexec.args="18084"
```
Expected: process remains running and prints `http://127.0.0.1:18084`.
- [ ] **Step 4: Run Playwright desktop acceptance**
At `1440x900`:
```text
open page -> bootstrap succeeds -> select known source/target -> run
assert selected vehicle and planned path are visible
assert SVG has path segments and a vehicle marker
step tick -> assert marker/traversed path changes
run 240 history -> assert 240 completed, 0 waiting, 0 failed
open first task -> assert its route and events render
open safety -> assert totalSafetyViolations is 0 and WMS idempotent is true
assert browser console has no errors
save screenshot
```
- [ ] **Step 5: Run Playwright mobile and canvas-pixel acceptance**
At `390x844`, verify no overlap or horizontal overflow and save a screenshot. Sample pixels in the SVG route
area to prove the visualization is nonblank and contains multiple semantic colors.
- [ ] **Step 6: Run formatting and repository checks**
```bash
git diff --check
git status --short
```
Expected: no whitespace errors; only intentional visualizer, test, README, spec, and plan changes are present.
- [ ] **Step 7: Commit the verified result**
```bash
git add javaTest/README.md javaTest/src/main javaTest/src/test javaTest/src/main/resources/visualizer
git commit -m "docs(javaTest): document dispatch visualizer workflow"
```
Do not stage unrelated `ruoyi-admin` or `docs/*.html` changes.