Files
FT546-WCS/docs/superpowers/plans/2026-07-30-wcs-real-queue-history-replay.md

27 KiB

WCS Real Queue History Replay 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 (- [x]) syntax for tracking.

Goal: Build a reproducible, read-only offline replay of FT546's real SRGV queue history, test at least 200 original records, scan the complete 17,136-row history through database aggregates, and produce a Chinese-readable report that separates database facts from SHADOW inference.

Architecture: Use the SQL Server MCP only to create versioned JSON snapshots and aggregate scan evidence; Maven tests never connect to SQL Server. A focused com.ft546.wcs.shadow.history package loads the snapshot, validates and normalizes paths, reconstructs inferred lifecycle-overlap windows, projects node/undirected-edge resources, performs deterministic replay, and writes machine-readable evidence. The final Excel workbook is generated from those verified evidence files.

Tech Stack: Java 8, Maven, Jackson 2.17.2, JUnit 5.10.2, SQL Server read-only MCP, @oai/artifact-tool for the final .xlsx.

Status (2026-07-31): Completed and independently reviewed. Final evidence run: outputs/wcs-shadow-history-replay-20260731-0945-af0b52d; Maven verification: 354 tests, 0 failures, 0 errors, 0 skipped.

Global Constraints

  • All database access is single-statement SELECT/CTE and read-only; no stored procedures or mutations.
  • Preserve original guid, task_no, agv_id, site IDs, and paths in internal test evidence.
  • Never store database credentials, IP addresses, tokens, or connection strings.
  • start_date may be null; inferred activity interval is [create_date, finish_date).
  • Empty or unknown paths produce INSUFFICIENT_DATA, never a synthetic GRANT.
  • Historical completion is a database fact; replay decisions are current-model inference and never an accuracy comparison.
  • Do not modify ruoyi-* production modules.
  • Keep the existing 60 synthetic safety cases and every existing test passing.
  • Output wording must say “真实历史队列离线回放”, not “现场 SHADOW 验收”.

File Structure

Create

  • javaTest/src/main/java/com/ft546/wcs/shadow/history/HistoricalQueueRecord.java — immutable raw snapshot row.
  • javaTest/src/main/java/com/ft546/wcs/shadow/history/HistoricalQueueDataset.java — manifest plus records and map sites.
  • javaTest/src/main/java/com/ft546/wcs/shadow/history/HistoricalQueueLoader.java — classpath JSON loading and SHA/count validation.
  • javaTest/src/main/java/com/ft546/wcs/shadow/history/HistoricalPath.java — parsed dates, ordered nodes, resource projection, and issues.
  • javaTest/src/main/java/com/ft546/wcs/shadow/history/HistoricalQueueNormalizer.java — raw-row validation and normalization.
  • javaTest/src/main/java/com/ft546/wcs/shadow/history/HistoricalReplayCategory.java — stable result enum.
  • javaTest/src/main/java/com/ft546/wcs/shadow/history/HistoricalReplayResult.java — one readable replay result.
  • javaTest/src/main/java/com/ft546/wcs/shadow/history/HistoricalReplayAnalyzer.java — overlap reconstruction and deterministic conflict decisions.
  • javaTest/src/main/java/com/ft546/wcs/shadow/history/HistoricalReplayReportWriter.java — summary JSON and CSV evidence.
  • javaTest/src/main/java/com/ft546/wcs/shadow/history/HistoricalReplayApplication.java — CLI entry point.
  • javaTest/src/test/resources/shadow/history/manifest.json — extraction metadata and hashes.
  • javaTest/src/test/resources/shadow/history/full-scan-summary.json — database-side aggregate scan of all rows.
  • javaTest/src/test/resources/shadow/history/queue-history-200.json — deterministic sample with at least 200 original rows.
  • javaTest/src/test/resources/shadow/history/map-sites.json — only map sites referenced by the sample.
  • javaTest/src/test/java/com/ft546/wcs/shadow/HistoricalQueueLoaderTest.java
  • javaTest/src/test/java/com/ft546/wcs/shadow/HistoricalQueueNormalizerTest.java
  • javaTest/src/test/java/com/ft546/wcs/shadow/HistoricalReplayAnalyzerTest.java
  • javaTest/src/test/java/com/ft546/wcs/shadow/HistoricalQueueDatabaseReplayTest.java

Modify

  • javaTest/README.md — commands, data provenance, readable field meanings, and evidence boundary.

Generated, not committed

  • outputs/wcs-shadow-history-replay-<runId>/source/queue-history-full.json
  • outputs/wcs-shadow-history-replay-<runId>/summary.json
  • outputs/wcs-shadow-history-replay-<runId>/replay-results.csv
  • outputs/wcs-shadow-history-replay-<runId>/data-quality-issues.csv
  • outputs/wcs-shadow-history-replay-<runId>/readable-report.xlsx
  • outputs/wcs-shadow-history-replay-<runId>/manifest.sha256

Task 1: Extract and Freeze Read-Only Database Evidence

Files:

  • Create: javaTest/src/test/resources/shadow/history/full-scan-summary.json
  • Create: javaTest/src/test/resources/shadow/history/queue-history-200.json
  • Create: javaTest/src/test/resources/shadow/history/map-sites.json
  • Create: javaTest/src/test/resources/shadow/history/manifest.json

Interfaces:

  • Consumes: FT546-wcs.dbo.wcs_srgv_queue_finish, FT546-wcs.dbo.wcs_srgv_map.

  • Produces: JSON arrays with original database fields and a manifest containing database, table, extractedAt, totalRows, sampleRows, queryId, and SHA-256 values.

  • Step 1: Run the complete-history aggregate scan

Use the SQL Server read-only tool with:

SELECT
  COUNT(*) AS total_rows,
  MIN(create_date) AS min_create_date,
  MAX(create_date) AS max_create_date,
  MIN(finish_date) AS min_finish_date,
  MAX(finish_date) AS max_finish_date,
  SUM(CASE WHEN path_info IS NULL OR LTRIM(RTRIM(path_info)) = '' THEN 1 ELSE 0 END) AS missing_path_rows,
  SUM(CASE WHEN create_date IS NULL OR finish_date IS NULL THEN 1 ELSE 0 END) AS missing_interval_rows,
  SUM(CASE WHEN finish_date < create_date THEN 1 ELSE 0 END) AS reversed_interval_rows,
  COUNT(DISTINCT agv_id) AS distinct_agv,
  COUNT(DISTINCT start_site) AS distinct_start_site,
  COUNT(DISTINCT end_site) AS distinct_end_site
FROM dbo.wcs_srgv_queue_finish

Expected: one row; total_rows >= 17136; query is read-only.

  • Step 2: Select a deterministic representative sample

Use one CTE query. The final ORDER BY is mandatory for reproducibility:

WITH eligible AS (
  SELECT
    guid, task_no, task_type, priority, cargo, issued, execution_status,
    cargo_pick_site, cargo_place_site, start_site, end_site, the_floor,
    trun_path, up_down_site, create_date, start_date, finish_date, agv_id,
    default02, default03, default04, default05, default06, default07, default08,
    path_info,
    ROW_NUMBER() OVER (
      PARTITION BY agv_id, task_type
      ORDER BY create_date, finish_date, guid
    ) AS stratum_seq
  FROM dbo.wcs_srgv_queue_finish
  WHERE path_info IS NOT NULL
    AND LTRIM(RTRIM(path_info)) <> ''
    AND create_date IS NOT NULL
    AND finish_date IS NOT NULL
)
SELECT TOP 240 *
FROM eligible
ORDER BY
  CASE WHEN stratum_seq <= 30 THEN 0 ELSE 1 END,
  stratum_seq,
  agv_id,
  task_type,
  create_date,
  finish_date,
  guid

Expected: 240 original rows when available, never fewer than 200.

  • Step 3: Export the complete history through deterministic read-only pages

Use pages of 500 rows and change only the two numeric bounds for each page:

WITH ordered AS (
  SELECT
    ROW_NUMBER() OVER (
      ORDER BY create_date, finish_date, guid, task_no, agv_id
    ) AS snapshot_row,
    guid, task_no, task_type, priority, cargo, issued, execution_status,
    cargo_pick_site, cargo_place_site, start_site, end_site, the_floor,
    trun_path, up_down_site, create_date, start_date, finish_date, agv_id,
    default02, default03, default04, default05, default06, default07, default08,
    path_info
  FROM dbo.wcs_srgv_queue_finish
)
SELECT *
FROM ordered
WHERE snapshot_row BETWEEN 1 AND 500
ORDER BY snapshot_row

Continue with 501 AND 1000 until the returned page is empty. Stream pages into outputs/wcs-shadow-history-replay-<runId>/source/queue-history-full.json; do not route the complete payload through chat text. The final row count must equal the aggregate scan exactly.

  • Step 4: Export referenced map sites

Read the full non-sensitive map projection, then retain only site IDs referenced by the sample and complete snapshot:

SELECT site_id, x, y, z, axy, warehouse, space_area
FROM dbo.wcs_srgv_map
WHERE site_id IS NOT NULL
ORDER BY site_id, z, y, x

Do not query credentials or device IP fields.

  • Step 5: Write snapshot files and hashes

Write JSON with UTF-8, stable property order, and two-space indentation. Compute:

SHA-256(queue-history-200.json)
SHA-256(map-sites.json)
SHA-256(full-scan-summary.json)

manifest.json must contain the exact hashes and row counts. The run-directory source manifest additionally contains the complete snapshot hash. It must not contain connection strings.

  • Step 6: Verify extraction evidence

Run:

grep -RniE "password|token|jdbc:|server=" javaTest/src/test/resources/shadow/history

Expected: no matches.

  • Step 7: Commit only the bounded immutable test snapshot
git add javaTest/src/test/resources/shadow/history
git commit -m "test: add real queue history replay snapshot"

Task 2: Load and Validate the Versioned Snapshot

Files:

  • Create: javaTest/src/main/java/com/ft546/wcs/shadow/history/HistoricalQueueRecord.java
  • Create: javaTest/src/main/java/com/ft546/wcs/shadow/history/HistoricalQueueDataset.java
  • Create: javaTest/src/main/java/com/ft546/wcs/shadow/history/HistoricalQueueLoader.java
  • Test: javaTest/src/test/java/com/ft546/wcs/shadow/HistoricalQueueLoaderTest.java

Interfaces:

  • Produces: HistoricalQueueDataset HistoricalQueueLoader.load(ClassLoader loader, String basePath).

  • Produces: HistoricalQueueDataset HistoricalQueueLoader.load(Path basePath) for the complete run-directory snapshot.

  • HistoricalQueueDataset.getRecords() returns an unmodifiable List<HistoricalQueueRecord>.

  • HistoricalQueueDataset.getMapSites() returns an unmodifiable Set<String>.

  • Step 1: Write failing loader tests

@Test
void loadsVersionedDatabaseSnapshotAndPreservesOriginalIds() {
    HistoricalQueueDataset data = new HistoricalQueueLoader().load(
            getClass().getClassLoader(), "shadow/history");
    assertTrue(data.getRecords().size() >= 200);
    assertEquals(data.getManifestSampleRows(), data.getRecords().size());
    assertFalse(data.getRecords().get(0).getTaskNo().isEmpty());
    assertFalse(data.getRecords().get(0).getAgvId().isEmpty());
}

@Test
void rejectsHashMismatch() {
    assertThrows(IllegalArgumentException.class,
            () -> HistoricalQueueLoader.verifySha256("wrong", "abc".getBytes(StandardCharsets.UTF_8)));
}
  • Step 2: Run loader tests and confirm RED
mvn -f javaTest/pom.xml -Dtest=HistoricalQueueLoaderTest test

Expected: compilation failure because history classes do not exist.

  • Step 3: Implement immutable raw records and dataset

HistoricalQueueRecord must expose exact string values for IDs/sites/dates and retain all extracted columns. Constructor arguments are JSON-mapped with Jackson @JsonCreator and @JsonProperty.

HistoricalQueueDataset must defensively copy:

this.records = Collections.unmodifiableList(new ArrayList<HistoricalQueueRecord>(records));
this.mapSites = Collections.unmodifiableSet(new LinkedHashSet<String>(mapSites));
  • Step 4: Implement loader and SHA validation

The loader reads the four classpath resources, verifies every hash before deserializing, and checks:

if (manifestSampleRows != records.size()) {
    throw new IllegalArgumentException("history sample row count mismatch");
}
if (records.size() < 200) {
    throw new IllegalArgumentException("history sample must contain at least 200 rows");
}
  • Step 5: Run loader tests and confirm GREEN
mvn -f javaTest/pom.xml -Dtest=HistoricalQueueLoaderTest test

Expected: all loader tests pass.

  • Step 6: Commit
git add javaTest/src/main/java/com/ft546/wcs/shadow/history javaTest/src/test/java/com/ft546/wcs/shadow/HistoricalQueueLoaderTest.java
git commit -m "feat: load versioned queue history snapshots"

Task 3: Normalize Dates and Real Paths Fail-Closed

Files:

  • Create: javaTest/src/main/java/com/ft546/wcs/shadow/history/HistoricalPath.java
  • Create: javaTest/src/main/java/com/ft546/wcs/shadow/history/HistoricalQueueNormalizer.java
  • Test: javaTest/src/test/java/com/ft546/wcs/shadow/HistoricalQueueNormalizerTest.java

Interfaces:

  • Produces: HistoricalPath HistoricalQueueNormalizer.normalize(HistoricalQueueRecord record, Set<String> mapSites).

  • HistoricalPath exposes getNodes(), getResources(), getCreatedAt(), getFinishedAt(), getIssues(), and isReplayable().

  • Step 1: Write failing normalization tests

@Test
void parsesTrailingCommaAndProjectsUndirectedEdges() {
    HistoricalPath path = normalizer.normalize(record("A,B,C,"), set("A", "B", "C"));
    assertEquals(Arrays.asList("A", "B", "C"), path.getNodes());
    assertTrue(path.getResources().contains(ResourceKey.node("B")));
    assertTrue(path.getResources().contains(ResourceKey.edge("B", "A")));
}

@Test
void unknownMapSiteIsNotReplayable() {
    HistoricalPath path = normalizer.normalize(record("A,UNKNOWN,"), set("A"));
    assertFalse(path.isReplayable());
    assertTrue(path.getIssues().contains("UNKNOWN_MAP_SITE:UNKNOWN"));
}

@Test
void reversedIntervalIsNotReplayable() {
    HistoricalPath path = normalizer.normalize(
            recordWithTimes("2026-07-21 14:05:00", "2026-07-21 14:04:59", "A,B,"),
            set("A", "B"));
    assertFalse(path.isReplayable());
    assertTrue(path.getIssues().contains("REVERSED_INTERVAL"));
}
  • Step 2: Run normalization tests and confirm RED
mvn -f javaTest/pom.xml -Dtest=HistoricalQueueNormalizerTest test

Expected: compilation failure because normalizer classes do not exist.

  • Step 3: Implement strict timestamp parsing

Use:

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss")
        .withResolverStyle(ResolverStyle.STRICT);

Parse into LocalDateTime. Null, blank, invalid, or reversed intervals add structured issues and prevent replay.

  • Step 4: Implement path normalization and resource projection

Rules:

trim tokens
drop empty trailing tokens
keep original order
flag adjacent duplicate as ADJACENT_DUPLICATE_NODE:<site>
flag missing map site as UNKNOWN_MAP_SITE:<site>
project every node and every adjacent undirected edge
do not create CONFLICT or lift resources

Empty paths add EMPTY_PATH and return isReplayable() == false.

  • Step 5: Run tests and confirm GREEN
mvn -f javaTest/pom.xml -Dtest=HistoricalQueueNormalizerTest test

Expected: all normalization tests pass.

  • Step 6: Commit
git add javaTest/src/main/java/com/ft546/wcs/shadow/history/HistoricalPath.java javaTest/src/main/java/com/ft546/wcs/shadow/history/HistoricalQueueNormalizer.java javaTest/src/test/java/com/ft546/wcs/shadow/HistoricalQueueNormalizerTest.java
git commit -m "feat: normalize real queue paths fail closed"

Task 4: Reconstruct Overlap Windows and Deterministic Replay

Files:

  • Create: javaTest/src/main/java/com/ft546/wcs/shadow/history/HistoricalReplayCategory.java
  • Create: javaTest/src/main/java/com/ft546/wcs/shadow/history/HistoricalReplayResult.java
  • Create: javaTest/src/main/java/com/ft546/wcs/shadow/history/HistoricalReplayAnalyzer.java
  • Test: javaTest/src/test/java/com/ft546/wcs/shadow/HistoricalReplayAnalyzerTest.java

Interfaces:

  • Produces: List<HistoricalReplayResult> HistoricalReplayAnalyzer.analyze(List<HistoricalPath> paths).

  • Produces: boolean HistoricalReplayAnalyzer.overlaps(HistoricalPath left, HistoricalPath right).

  • Stable ordering: priority ascending, createdAt, task number, AGV ID, GUID.

  • Step 1: Write failing overlap and conflict tests

@Test
void touchingIntervalsDoNotOverlap() {
    assertFalse(analyzer.overlaps(path("10:00:00", "10:01:00"),
                                  path("10:01:00", "10:02:00")));
}

@Test
void detectsOppositeUseOfSameUndirectedEdge() {
    List<HistoricalReplayResult> results = analyzer.analyze(Arrays.asList(
            path("V1", "T1", "A,B", "10:00:00", "10:02:00"),
            path("V2", "T2", "B,A", "10:00:30", "10:03:00")));
    assertTrue(results.stream().anyMatch(
            result -> result.getCategory() == HistoricalReplayCategory.POTENTIAL_OPPOSITE_EDGE));
}

@Test
void identicalInputProducesIdenticalResults() {
    List<HistoricalReplayResult> first = analyzer.analyze(sample);
    List<HistoricalReplayResult> second = analyzer.analyze(sample);
    assertEquals(first, second);
}
  • Step 2: Run analyzer tests and confirm RED
mvn -f javaTest/pom.xml -Dtest=HistoricalReplayAnalyzerTest test

Expected: compilation failure because analyzer types do not exist.

  • Step 3: Implement half-open overlap semantics
return left.getCreatedAt().isBefore(right.getFinishedAt())
        && right.getCreatedAt().isBefore(left.getFinishedAt());

Non-replayable paths return INSUFFICIENT_DATA before conflict checks.

  • Step 4: Implement conflict classification

For overlapping records:

  1. Same agv_id → SAME_VEHICLE_OVERLAP.
  2. Reverse adjacent node order on a shared normalized edge → POTENTIAL_OPPOSITE_EDGE.
  3. Shared node → POTENTIAL_NODE_CONFLICT.
  4. No resource intersection → NO_CONFLICT.

The result stores both original record IDs, shared resource keys, inferred interval, category, and Chinese explanation.

  • Step 5: Implement deterministic replay decision

Sort candidates once. Maintain an in-memory ResourceLedger with node and edge capacity 1. Replayable candidates become:

REPLAY_GRANT when every projected resource fits
REPLAY_HOLD when a projected resource is occupied in the inferred window
INSUFFICIENT_DATA when normalization blocked replay

Do not synthesize YIELD because no signed safe waiting points exist.

  • Step 6: Run analyzer tests and confirm GREEN
mvn -f javaTest/pom.xml -Dtest=HistoricalReplayAnalyzerTest test

Expected: all analyzer tests pass.

  • Step 7: Commit
git add javaTest/src/main/java/com/ft546/wcs/shadow/history javaTest/src/test/java/com/ft546/wcs/shadow/HistoricalReplayAnalyzerTest.java
git commit -m "feat: replay inferred historical traffic windows"

Task 5: Execute the Real Database Snapshot Test Matrix

Files:

  • Create: javaTest/src/test/java/com/ft546/wcs/shadow/HistoricalQueueDatabaseReplayTest.java

Interfaces:

  • Consumes: HistoricalQueueLoader, HistoricalQueueNormalizer, HistoricalReplayAnalyzer.

  • Produces: Surefire-counted parameterized tests using original task and vehicle IDs.

  • Step 1: Write the real-data parameterized test

@ParameterizedTest(name = "{index} task={0} agv={1} {2}->{3}")
@MethodSource("realHistoryCases")
void replaysRealHistoryRecord(String taskNo, String agvId,
                              String startSite, String endSite,
                              HistoricalPath path) {
    assertFalse(taskNo.trim().isEmpty());
    assertFalse(agvId.trim().isEmpty());
    assertNotNull(path);
    if (path.isReplayable()) {
        assertFalse(path.getNodes().isEmpty());
        assertFalse(path.getResources().isEmpty());
    } else {
        assertFalse(path.getIssues().isEmpty());
    }
}

realHistoryCases() must expose every snapshot row, so Surefire counts at least 200 independent real-data cases.

  • Step 2: Add aggregate acceptance tests

Assert:

assertTrue(dataset.getRecords().size() >= 200);
assertTrue(dataset.getRecords().stream().map(HistoricalQueueRecord::getAgvId).distinct().count() >= 3);
assertTrue(dataset.getRecords().stream().map(HistoricalQueueRecord::getTaskType).distinct().count() >= 3);
assertEquals(firstReplay, secondReplay);
assertEquals(17136, dataset.getFullScanTotalRows());

If the fresh extraction count differs from 17,136, use the manifest's exact count and assert it matches full-scan-summary.json; do not hardcode a stale number.

  • Step 3: Run real-data tests and confirm GREEN
mvn -f javaTest/pom.xml -Dtest=HistoricalQueueDatabaseReplayTest test

Expected: at least 200 parameterized cases plus aggregate checks; failures and errors are 0.

  • Step 4: Run the existing synthetic safety matrix
mvn -f javaTest/pom.xml -Dtest=ShadowSimulationDataTest test

Expected: 60 tests, failures 0, errors 0.

  • Step 5: Commit
git add javaTest/src/test/java/com/ft546/wcs/shadow/HistoricalQueueDatabaseReplayTest.java
git commit -m "test: replay real FT546 queue history"

Task 6: Generate Machine-Readable and Chinese-Readable Evidence

Files:

  • Create: javaTest/src/main/java/com/ft546/wcs/shadow/history/HistoricalReplayReportWriter.java
  • Create: javaTest/src/main/java/com/ft546/wcs/shadow/history/HistoricalReplayApplication.java
  • Modify: javaTest/README.md

Interfaces:

  • Produces: void HistoricalReplayReportWriter.write(Path outputDir, HistoricalQueueDataset dataset, List<HistoricalPath> paths, List<HistoricalReplayResult> results).

  • CLI usage: HistoricalReplayApplication <snapshotDir> <outputDir>.

  • Step 1: Write a failing writer test inside HistoricalReplayAnalyzerTest

@TempDir Path temp;

@Test
void writesReadableEvidenceWithOriginalIdsAndBoundaryText() throws Exception {
    writer.write(temp, dataset, paths, results);
    String csv = new String(Files.readAllBytes(temp.resolve("replay-results.csv")),
            StandardCharsets.UTF_8);
    String summary = new String(Files.readAllBytes(temp.resolve("summary.json")),
            StandardCharsets.UTF_8);
    assertTrue(csv.contains(dataset.getRecords().get(0).getTaskNo()));
    assertTrue(csv.contains(dataset.getRecords().get(0).getAgvId()));
    assertTrue(summary.contains("真实历史队列离线回放"));
    assertTrue(summary.contains("不是现场 SHADOW 验收"));
}
  • Step 2: Run the writer test and confirm RED
mvn -f javaTest/pom.xml -Dtest=HistoricalReplayAnalyzerTest#writesReadableEvidenceWithOriginalIdsAndBoundaryText test

Expected: compilation failure because writer does not exist.

  • Step 3: Implement CSV and JSON evidence

replay-results.csv columns:

创建时间,完成时间,车辆,任务号,起点,终点,历史状态,路径,
同期车辆,同期任务,共享资源,SHADOW推演,分类,中文原因,数据口径

data-quality-issues.csv columns:

GUID,车辆,任务号,问题类型,原始路径,是否影响回放,处理方式

CSV must use RFC 4180 quoting and UTF-8 BOM so Excel opens Chinese text correctly.

  • Step 4: Implement summary and SHA manifest

summary.json includes:

database/table/extractedAt/sourceRowCount/sampleRowCount/replayableRows
min/max timestamps/vehicles/taskTypes/mapSites
qualityIssueCounts/categoryCounts/replayGrant/replayHold
deterministic=true/mode=REAL_HISTORY_OFFLINE_REPLAY
boundary="不是现场 SHADOW 验收"

manifest.sha256 covers every generated file except itself, sorted by filename.

  • Step 5: Implement CLI
public static void main(String[] args) throws Exception {
    if (args.length != 2) {
        throw new IllegalArgumentException(
                "usage: HistoricalReplayApplication <snapshotDir> <outputDir>");
    }
    // load, normalize, analyze twice, verify equality, write
}
  • Step 6: Document the workflow

Add to javaTest/README.md:

mvn clean test
mvn exec:java \
  -Dexec.mainClass=com.ft546.wcs.shadow.history.HistoricalReplayApplication \
  -Dexec.args="../outputs/wcs-shadow-history-replay-local/source ../outputs/wcs-shadow-history-replay-local"

Explain original IDs are retained and the report distinguishes database facts from inference.

  • Step 7: Run writer tests and CLI
mvn -f javaTest/pom.xml -Dtest=HistoricalReplayAnalyzerTest test
mvn -f javaTest/pom.xml exec:java \
  -Dexec.mainClass=com.ft546.wcs.shadow.history.HistoricalReplayApplication \
  -Dexec.args="../outputs/wcs-shadow-history-replay-local/source ../outputs/wcs-shadow-history-replay-local"

Expected: evidence files exist, hashes verify, and CLI exits 0.

  • Step 8: Commit
git add javaTest/src/main/java/com/ft546/wcs/shadow/history/HistoricalReplayReportWriter.java javaTest/src/main/java/com/ft546/wcs/shadow/history/HistoricalReplayApplication.java javaTest/README.md
git commit -m "feat: report real history replay evidence"

Task 7: Create and Verify the Readable Excel Workbook

Files:

  • Generate: outputs/wcs-shadow-history-replay-<runId>/readable-report.xlsx

Interfaces:

  • Consumes: verified summary.json, replay-results.csv, data-quality-issues.csv.

  • Produces: one workbook with sheets 测试总览, 真实任务明细, 潜在冲突, 数据问题, 来源与口径.

  • Step 1: Run a fresh full Maven verification

mvn -f javaTest/pom.xml clean test

Expected: build success, every original test plus at least 200 real-history parameterized cases, failures 0, errors 0.

  • Step 2: Generate fresh evidence

Use a run ID containing the current date/time and Git short SHA. Never overwrite an earlier run.

  • Step 3: Build the workbook with @oai/artifact-tool

Workbook requirements:

  • KPI cards: source rows, sampled rows, replayable rows, quality issues, potential conflicts.

  • Original IDs and full paths preserved in detail.

  • Filters, frozen header rows, wrapped Chinese explanations.

  • Formula-driven category counts and pass/replay totals.

  • One compact category-distribution chart.

  • Amber boundary banner: “真实历史队列离线回放,不是现场 SHADOW 验收”.

  • Step 4: Verify workbook values and formulas

Inspect:

测试总览!A1:O30
真实任务明细!A1:O20
来源与口径!A1:D20

Scan for:

#REF! #DIV/0! #VALUE! #NAME? #N/A

Expected: no formula errors; KPI counts reconcile to JSON/CSV.

  • Step 5: Render and visually inspect every sheet

Check Chinese text, task numbers, vehicle IDs, full paths, chart, wrapped reason text, and boundary banner. Fix clipping before export.

  • Step 6: Verify manifest

Recalculate SHA-256 for all run-directory evidence except manifest.sha256 and compare line-by-line.

  • Step 7: Commit implementation and tests

Do not commit generated outputs/ evidence unless repository policy explicitly tracks evidence. Confirm:

git status --short
git log -7 --oneline

Expected: source/resources/docs changes committed; generated output remains an external artifact.


Final Verification Checklist

  • SQL operations were read-only.
  • Full database aggregate scan row count is captured.
  • Snapshot contains at least 200 original records and 3 original AGV IDs.
  • Original task, vehicle, site, and path values are visible.
  • Missing/unknown data is fail-closed.
  • Replay is deterministic.
  • Existing 60 synthetic safety cases still pass.
  • Full Maven suite passes with failures/errors 0.
  • Excel totals reconcile to machine-readable evidence.
  • Report clearly states it is not现场 SHADOW/HIL/ACTIVE evidence.
  • ruoyi-* production code is unchanged.