746 lines
27 KiB
Markdown
746 lines
27 KiB
Markdown
# 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-<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:
|
|
|
|
```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-<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:
|
|
|
|
```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<HistoricalQueueRecord>`.
|
|
- `HistoricalQueueDataset.getMapSites()` returns an unmodifiable `Set<String>`.
|
|
|
|
- [ ] **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<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:
|
|
|
|
```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<String> 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:<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**
|
|
|
|
```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<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**
|
|
|
|
```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<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**
|
|
|
|
```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<HistoricalPath> paths, List<HistoricalReplayResult> results)`.
|
|
- CLI usage: `HistoricalReplayApplication <snapshotDir> <outputDir>`.
|
|
|
|
- [ ] **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 <snapshotDir> <outputDir>");
|
|
}
|
|
// 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-<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**
|
|
|
|
```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.
|