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

19 KiB

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

@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:

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.

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
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:

mvn "-Dtest=PointToPointSimulationServiceTest" test

Expected: all focused tests pass.

Commit:

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

@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:

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
mvn "-Dtest=SimulationVisualizationMapperTest,HistoricalDispatchSimulatorTest" test

Expected: all selected tests pass.

  • Step 5: Commit
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

@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.

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:

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

@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:

<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:

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:

mvn "-Dtest=HistoricalDispatchVisualizerServerTest" test

Expected: all static and API tests pass.

Commit:

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
mvn clean test

Expected: Surefire discovers the existing 630 tests plus all new tests; Failures 0, Errors 0.

  • Step 3: Start the visualizer server
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:

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
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
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.