From 3ec6da12e62fbf9a84b351fb03647fb486171e0e Mon Sep 17 00:00:00 2001 From: zgw <13623675548@163.com> Date: Thu, 30 Jul 2026 17:15:56 +0800 Subject: [PATCH] docs: plan real queue history replay --- ...026-07-30-wcs-real-queue-history-replay.md | 745 ++++++++++++++++++ 1 file changed, 745 insertions(+) create mode 100644 docs/superpowers/plans/2026-07-30-wcs-real-queue-history-replay.md diff --git a/docs/superpowers/plans/2026-07-30-wcs-real-queue-history-replay.md b/docs/superpowers/plans/2026-07-30-wcs-real-queue-history-replay.md new file mode 100644 index 0000000..3d7df9d --- /dev/null +++ b/docs/superpowers/plans/2026-07-30-wcs-real-queue-history-replay.md @@ -0,0 +1,745 @@ +# 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 (`- [ ]`) 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`. + +## 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-/source/queue-history-full.json` +- `outputs/wcs-shadow-history-replay-/summary.json` +- `outputs/wcs-shadow-history-replay-/replay-results.csv` +- `outputs/wcs-shadow-history-replay-/data-quality-issues.csv` +- `outputs/wcs-shadow-history-replay-/readable-report.xlsx` +- `outputs/wcs-shadow-history-replay-/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: + +```sql +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: + +```sql +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: + +```sql +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-/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: + +```sql +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: + +```text +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: + +```bash +grep -RniE "password|token|jdbc:|server=" javaTest/src/test/resources/shadow/history +``` + +Expected: no matches. + +- [ ] **Step 7: Commit only the bounded immutable test snapshot** + +```bash +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`. +- `HistoricalQueueDataset.getMapSites()` returns an unmodifiable `Set`. + +- [ ] **Step 1: Write failing loader tests** + +```java +@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** + +```bash +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: + +```java +this.records = Collections.unmodifiableList(new ArrayList(records)); +this.mapSites = Collections.unmodifiableSet(new LinkedHashSet(mapSites)); +``` + +- [ ] **Step 4: Implement loader and SHA validation** + +The loader reads the four classpath resources, verifies every hash before deserializing, and checks: + +```java +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** + +```bash +mvn -f javaTest/pom.xml -Dtest=HistoricalQueueLoaderTest test +``` + +Expected: all loader tests pass. + +- [ ] **Step 6: Commit** + +```bash +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 mapSites)`. +- `HistoricalPath` exposes `getNodes()`, `getResources()`, `getCreatedAt()`, `getFinishedAt()`, `getIssues()`, and `isReplayable()`. + +- [ ] **Step 1: Write failing normalization tests** + +```java +@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** + +```bash +mvn -f javaTest/pom.xml -Dtest=HistoricalQueueNormalizerTest test +``` + +Expected: compilation failure because normalizer classes do not exist. + +- [ ] **Step 3: Implement strict timestamp parsing** + +Use: + +```java +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: + +```text +trim tokens +drop empty trailing tokens +keep original order +flag adjacent duplicate as ADJACENT_DUPLICATE_NODE: +flag missing map site as UNKNOWN_MAP_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** + +```bash +mvn -f javaTest/pom.xml -Dtest=HistoricalQueueNormalizerTest test +``` + +Expected: all normalization tests pass. + +- [ ] **Step 6: Commit** + +```bash +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 HistoricalReplayAnalyzer.analyze(List 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** + +```java +@Test +void touchingIntervalsDoNotOverlap() { + assertFalse(analyzer.overlaps(path("10:00:00", "10:01:00"), + path("10:01:00", "10:02:00"))); +} + +@Test +void detectsOppositeUseOfSameUndirectedEdge() { + List 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 first = analyzer.analyze(sample); + List second = analyzer.analyze(sample); + assertEquals(first, second); +} +``` + +- [ ] **Step 2: Run analyzer tests and confirm RED** + +```bash +mvn -f javaTest/pom.xml -Dtest=HistoricalReplayAnalyzerTest test +``` + +Expected: compilation failure because analyzer types do not exist. + +- [ ] **Step 3: Implement half-open overlap semantics** + +```java +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: + +```text +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** + +```bash +mvn -f javaTest/pom.xml -Dtest=HistoricalReplayAnalyzerTest test +``` + +Expected: all analyzer tests pass. + +- [ ] **Step 7: Commit** + +```bash +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** + +```java +@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: + +```java +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** + +```bash +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** + +```bash +mvn -f javaTest/pom.xml -Dtest=ShadowSimulationDataTest test +``` + +Expected: 60 tests, failures 0, errors 0. + +- [ ] **Step 5: Commit** + +```bash +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 paths, List results)`. +- CLI usage: `HistoricalReplayApplication `. + +- [ ] **Step 1: Write a failing writer test inside `HistoricalReplayAnalyzerTest`** + +```java +@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** + +```bash +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: + +```text +创建时间,完成时间,车辆,任务号,起点,终点,历史状态,路径, +同期车辆,同期任务,共享资源,SHADOW推演,分类,中文原因,数据口径 +``` + +`data-quality-issues.csv` columns: + +```text +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: + +```text +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** + +```java +public static void main(String[] args) throws Exception { + if (args.length != 2) { + throw new IllegalArgumentException( + "usage: HistoricalReplayApplication "); + } + // load, normalize, analyze twice, verify equality, write +} +``` + +- [ ] **Step 6: Document the workflow** + +Add to `javaTest/README.md`: + +```bash +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** + +```bash +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** + +```bash +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-/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** + +```bash +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: + +```text +测试总览!A1:O30 +真实任务明细!A1:O20 +来源与口径!A1:D20 +``` + +Scan for: + +```text +#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: + +```bash +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.