From 78e74daea179ec568032c6baab5851c550294c46 Mon Sep 17 00:00:00 2001 From: zgw <13623675548@163.com> Date: Fri, 31 Jul 2026 11:18:49 +0800 Subject: [PATCH] feat: prepare verified history simulation inputs --- ...real-history-driven-dispatch-simulation.md | 2 +- ...story-driven-dispatch-simulation-design.md | 2 +- .../wcs/shadow/history/HistoricalMapSite.java | 26 +++ .../history/HistoricalQueueDataset.java | 16 +- .../shadow/history/HistoricalQueueLoader.java | 18 +- .../HistoricalSimulationFailure.java | 20 +++ .../simulation/HistoricalSimulationInput.java | 47 ++++++ .../HistoricalSimulationInputFactory.java | 159 ++++++++++++++++++ .../simulation/HistoricalSimulationTask.java | 51 ++++++ .../wcs/shadow/HistoricalQueueLoaderTest.java | 7 + .../HistoricalSimulationInputFactoryTest.java | 58 +++++++ 11 files changed, 395 insertions(+), 11 deletions(-) create mode 100644 javaTest/src/main/java/com/ft546/wcs/shadow/history/HistoricalMapSite.java create mode 100644 javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/HistoricalSimulationFailure.java create mode 100644 javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/HistoricalSimulationInput.java create mode 100644 javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/HistoricalSimulationInputFactory.java create mode 100644 javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/HistoricalSimulationTask.java create mode 100644 javaTest/src/test/java/com/ft546/wcs/shadow/history/simulation/HistoricalSimulationInputFactoryTest.java diff --git a/docs/superpowers/plans/2026-07-31-wcs-real-history-driven-dispatch-simulation.md b/docs/superpowers/plans/2026-07-31-wcs-real-history-driven-dispatch-simulation.md index b0b00d4..94d0592 100644 --- a/docs/superpowers/plans/2026-07-31-wcs-real-history-driven-dispatch-simulation.md +++ b/docs/superpowers/plans/2026-07-31-wcs-real-history-driven-dispatch-simulation.md @@ -55,7 +55,7 @@ Expected: compilation fails because the new input types do not exist. - [ ] **Step 3: Implement immutable map loading and task conversion** -Parse SHA-verified map details into an insertion-ordered immutable map. Map task types `1/2/3`, preserve raw business fields, assign ID `task_no@guid`, parse `create_date`, and retain `finish_date/agv_id/path_info` only in comparison fields. +Parse SHA-verified map details into an insertion-ordered immutable map. Map task types `1/2/3`, preserve raw business fields, assign stable per-row ID `task_no@guid#stableSequence` because the snapshot contains repeated task/GUID pairs, parse `create_date`, and retain `finish_date/agv_id/path_info` only in comparison fields. - [ ] **Step 4: Build replannable topology and explicit endpoint mapping** diff --git a/docs/superpowers/specs/2026-07-31-wcs-real-history-driven-dispatch-simulation-design.md b/docs/superpowers/specs/2026-07-31-wcs-real-history-driven-dispatch-simulation-design.md index f738d97..45c82ce 100644 --- a/docs/superpowers/specs/2026-07-31-wcs-real-history-driven-dispatch-simulation-design.md +++ b/docs/superpowers/specs/2026-07-31-wcs-real-history-driven-dispatch-simulation-design.md @@ -23,7 +23,7 @@ ## 3. 输入口径 - 使用已固化并经 SHA-256 校验的 240 条真实历史记录。 -- 每条数据库行都是独立任务,唯一仿真 ID 为 `task_no@guid`;相同任务号的多阶段记录不合并。 +- 每条数据库行都是独立任务。快照中 `task_no@guid` 只有 98 个唯一组合,因此唯一仿真 ID 为 `task_no@guid#stableSequence`;相同业务标识的多阶段或重复记录不合并。 - `create_date` 决定任务到达顺序和到达间隔。离散事件时钟支持 `10x`、`100x`,默认 `100x`;相同时间同时到达,超过 30 分钟的历史空闲间隔封顶为 18 仿真秒,避免测试等待数月。 - `task_type`:`1→INBOUND`、`2→OUTBOUND`、`3→RELOCATION`。 - `priority` 作为业务优先级;缺失时使用最低优先级。 diff --git a/javaTest/src/main/java/com/ft546/wcs/shadow/history/HistoricalMapSite.java b/javaTest/src/main/java/com/ft546/wcs/shadow/history/HistoricalMapSite.java new file mode 100644 index 0000000..6494295 --- /dev/null +++ b/javaTest/src/main/java/com/ft546/wcs/shadow/history/HistoricalMapSite.java @@ -0,0 +1,26 @@ +package com.ft546.wcs.shadow.history; + +public final class HistoricalMapSite { + private final String siteId; + private final int x; + private final int y; + private final int z; + private final String axy; + + public HistoricalMapSite(String siteId, int x, int y, int z, String axy) { + if (siteId == null || siteId.trim().isEmpty()) { + throw new IllegalArgumentException("site id must not be blank"); + } + this.siteId = siteId; + this.x = x; + this.y = y; + this.z = z; + this.axy = axy == null ? "" : axy; + } + + public String getSiteId() { return siteId; } + public int getX() { return x; } + public int getY() { return y; } + public int getZ() { return z; } + public String getAxy() { return axy; } +} diff --git a/javaTest/src/main/java/com/ft546/wcs/shadow/history/HistoricalQueueDataset.java b/javaTest/src/main/java/com/ft546/wcs/shadow/history/HistoricalQueueDataset.java index f1c640c..c12d7b2 100644 --- a/javaTest/src/main/java/com/ft546/wcs/shadow/history/HistoricalQueueDataset.java +++ b/javaTest/src/main/java/com/ft546/wcs/shadow/history/HistoricalQueueDataset.java @@ -3,12 +3,14 @@ package com.ft546.wcs.shadow.history; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashSet; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Set; public final class HistoricalQueueDataset { private final List records; - private final Set mapSites; + private final Map mapSiteDetails; private final int manifestSampleRows; private final int fullScanTotalRows; private final String database; @@ -22,7 +24,8 @@ public final class HistoricalQueueDataset { private final int missingIntervalRows; private final int reversedIntervalRows; - public HistoricalQueueDataset(List records, Set mapSites, + public HistoricalQueueDataset(List records, + Map mapSiteDetails, int manifestSampleRows, int fullScanTotalRows, String database, String sourceTable, String extractedAt, String minCreateDate, String maxCreateDate, @@ -30,7 +33,8 @@ public final class HistoricalQueueDataset { int missingPathRows, int missingIntervalRows, int reversedIntervalRows) { this.records = Collections.unmodifiableList(new ArrayList(records)); - this.mapSites = Collections.unmodifiableSet(new LinkedHashSet(mapSites)); + this.mapSiteDetails = Collections.unmodifiableMap( + new LinkedHashMap(mapSiteDetails)); this.manifestSampleRows = manifestSampleRows; this.fullScanTotalRows = fullScanTotalRows; this.database = database; @@ -46,7 +50,11 @@ public final class HistoricalQueueDataset { } public List getRecords() { return records; } - public Set getMapSites() { return mapSites; } + public Set getMapSites() { + return Collections.unmodifiableSet( + new LinkedHashSet(mapSiteDetails.keySet())); + } + public Map getMapSiteDetails() { return mapSiteDetails; } public int getManifestSampleRows() { return manifestSampleRows; } public int getFullScanTotalRows() { return fullScanTotalRows; } public String getDatabase() { return database; } diff --git a/javaTest/src/main/java/com/ft546/wcs/shadow/history/HistoricalQueueLoader.java b/javaTest/src/main/java/com/ft546/wcs/shadow/history/HistoricalQueueLoader.java index 69c9d86..901f8aa 100644 --- a/javaTest/src/main/java/com/ft546/wcs/shadow/history/HistoricalQueueLoader.java +++ b/javaTest/src/main/java/com/ft546/wcs/shadow/history/HistoricalQueueLoader.java @@ -15,10 +15,9 @@ import java.nio.charset.CodingErrorAction; import java.nio.file.Path; import java.security.MessageDigest; import java.net.URLClassLoader; -import java.util.ArrayList; -import java.util.LinkedHashSet; +import java.util.LinkedHashMap; import java.util.List; -import java.util.Set; +import java.util.Map; public final class HistoricalQueueLoader { private static final ObjectMapper MAPPER = new ObjectMapper(); @@ -36,10 +35,19 @@ public final class HistoricalQueueLoader { CollectionType rowsType = MAPPER.getTypeFactory() .constructCollectionType(List.class, HistoricalQueueRecord.class); List rows = MAPPER.readValue(queueBytes, rowsType); - Set sites = new LinkedHashSet(); + Map sites = + new LinkedHashMap(); for (JsonNode node : MAPPER.readTree(mapBytes)) { String site = node.path("site_id").asText(); - if (!site.isEmpty()) sites.add(site); + if (site.isEmpty()) { + throw new IllegalArgumentException("history map site id must not be blank"); + } + HistoricalMapSite detail = new HistoricalMapSite( + site, node.path("x").asInt(), node.path("y").asInt(), + node.path("z").asInt(), node.path("axy").asText()); + if (sites.put(site, detail) != null) { + throw new IllegalArgumentException("duplicate history map site " + site); + } } int sampleRows = manifest.path("sampleRows").asInt(); if (sampleRows != rows.size()) { diff --git a/javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/HistoricalSimulationFailure.java b/javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/HistoricalSimulationFailure.java new file mode 100644 index 0000000..5e84a10 --- /dev/null +++ b/javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/HistoricalSimulationFailure.java @@ -0,0 +1,20 @@ +package com.ft546.wcs.shadow.history.simulation; + +import com.ft546.wcs.shadow.history.HistoricalQueueRecord; + +public final class HistoricalSimulationFailure { + private final String simulationTaskId; + private final HistoricalQueueRecord record; + private final String reason; + + HistoricalSimulationFailure(String simulationTaskId, HistoricalQueueRecord record, + String reason) { + this.simulationTaskId = simulationTaskId; + this.record = record; + this.reason = reason; + } + + public String getSimulationTaskId() { return simulationTaskId; } + public HistoricalQueueRecord getRecord() { return record; } + public String getReason() { return reason; } +} diff --git a/javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/HistoricalSimulationInput.java b/javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/HistoricalSimulationInput.java new file mode 100644 index 0000000..5aed57d --- /dev/null +++ b/javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/HistoricalSimulationInput.java @@ -0,0 +1,47 @@ +package com.ft546.wcs.shadow.history.simulation; + +import com.ft546.wcs.demo.model.VehicleSnapshot; +import com.ft546.wcs.demo.traffic.GridMap; +import com.ft546.wcs.shadow.map.EffectiveTrafficMap; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public final class HistoricalSimulationInput { + private final List tasks; + private final List failedInputs; + private final GridMap gridMap; + private final EffectiveTrafficMap trafficMap; + private final List initialVehicles; + + HistoricalSimulationInput(List tasks, + List failedInputs, + GridMap gridMap, EffectiveTrafficMap trafficMap, + List initialVehicles) { + this.tasks = immutable(tasks); + this.failedInputs = immutable(failedInputs); + this.gridMap = gridMap; + this.trafficMap = trafficMap; + this.initialVehicles = immutable(initialVehicles); + } + + public List getTasks() { return tasks; } + public List getFailedInputs() { return failedInputs; } + public GridMap getGridMap() { return gridMap; } + public EffectiveTrafficMap getTrafficMap() { return trafficMap; } + public List getInitialVehicles() { return initialVehicles; } + + public List allSimulationIds() { + List ids = new ArrayList(); + for (HistoricalSimulationTask task : tasks) ids.add(task.getSimulationTaskId()); + for (HistoricalSimulationFailure failure : failedInputs) { + ids.add(failure.getSimulationTaskId()); + } + return Collections.unmodifiableList(ids); + } + + private static List immutable(List source) { + return Collections.unmodifiableList(new ArrayList(source)); + } +} diff --git a/javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/HistoricalSimulationInputFactory.java b/javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/HistoricalSimulationInputFactory.java new file mode 100644 index 0000000..6218f4b --- /dev/null +++ b/javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/HistoricalSimulationInputFactory.java @@ -0,0 +1,159 @@ +package com.ft546.wcs.shadow.history.simulation; + +import com.ft546.wcs.demo.model.DispatchTask; +import com.ft546.wcs.demo.model.Site; +import com.ft546.wcs.demo.model.VehicleSnapshot; +import com.ft546.wcs.demo.traffic.GridMap; +import com.ft546.wcs.shadow.history.HistoricalMapSite; +import com.ft546.wcs.shadow.history.HistoricalPath; +import com.ft546.wcs.shadow.history.HistoricalQueueDataset; +import com.ft546.wcs.shadow.history.HistoricalQueueNormalizer; +import com.ft546.wcs.shadow.history.HistoricalQueueRecord; +import com.ft546.wcs.shadow.map.EffectiveTrafficMap; +import com.ft546.wcs.shadow.map.TrafficMapCompiler; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public final class HistoricalSimulationInputFactory { + public HistoricalSimulationInput create(HistoricalQueueDataset dataset) { + if (dataset == null) throw new IllegalArgumentException("dataset is required"); + HistoricalQueueNormalizer normalizer = new HistoricalQueueNormalizer(); + List paths = new ArrayList(); + for (HistoricalQueueRecord record : dataset.getRecords()) { + paths.add(normalizer.normalize(record, dataset.getMapSites())); + } + Collections.sort(paths, Comparator + .comparing(HistoricalPath::getCreatedAt, + Comparator.nullsLast(Comparator.naturalOrder())) + .thenComparing(path -> text(path.getRecord().getTaskNo())) + .thenComparing(path -> text(path.getRecord().getGuid()))); + + List tasks = new ArrayList(); + List failures = new ArrayList(); + int sequence = 0; + for (HistoricalPath path : paths) { + HistoricalQueueRecord record = path.getRecord(); + int stableSequence = sequence++; + String id = simulationId(record, stableSequence); + DispatchTask.TaskType type = taskType(record.getTaskType()); + if (!path.isReplayable() || type == null) { + failures.add(new HistoricalSimulationFailure(id, record, failureReason(path, type))); + continue; + } + List nodes = path.getNodes(); + String source = dataset.getMapSites().contains(record.getStartSite()) + ? record.getStartSite() : nodes.get(0); + String target = dataset.getMapSites().contains(record.getEndSite()) + ? record.getEndSite() : nodes.get(nodes.size() - 1); + tasks.add(new HistoricalSimulationTask(id, record, type, + record.getPriority() == null ? Integer.MAX_VALUE : record.getPriority(), + path.getCreatedAt(), path.getFinishedAt(), source, target, nodes, stableSequence)); + } + + GridMap gridMap = buildGridMap(tasks, dataset.getMapSiteDetails()); + EffectiveTrafficMap trafficMap = buildTrafficMap(tasks); + List vehicles = initialVehicles(tasks, dataset.getMapSiteDetails()); + return new HistoricalSimulationInput(tasks, failures, gridMap, trafficMap, vehicles); + } + + private GridMap buildGridMap(List tasks, + Map details) { + LinkedHashSet nodes = new LinkedHashSet(); + LinkedHashSet edges = new LinkedHashSet(); + for (HistoricalSimulationTask task : tasks) { + List path = task.getHistoricalNodes(); + nodes.addAll(path); + for (int i = 1; i < path.size(); i++) { + edges.add(edgeId(path.get(i - 1), path.get(i))); + } + } + GridMap map = new GridMap(); + for (String node : nodes) { + HistoricalMapSite detail = details.get(node); + map.addSite(new Site(node, detail.getZ(), detail.getX(), detail.getY(), + detail.getAxy().isEmpty() ? "OFFLINE" : detail.getAxy())); + } + for (String edge : edges) { + String[] pair = edge.split("\\|", -1); + map.connect(pair[0], pair[1]); + } + return map; + } + + private EffectiveTrafficMap buildTrafficMap(List tasks) { + LinkedHashSet nodes = new LinkedHashSet(); + LinkedHashMap edges = + new LinkedHashMap(); + for (HistoricalSimulationTask task : tasks) { + List path = task.getHistoricalNodes(); + nodes.addAll(path); + for (int i = 1; i < path.size(); i++) { + String id = edgeId(path.get(i - 1), path.get(i)); + String[] pair = id.split("\\|", -1); + edges.put(id, new TrafficMapCompiler.EdgeDefinition(pair[0], pair[1])); + } + } + TrafficMapCompiler.MapDefinition definition = new TrafficMapCompiler.MapDefinition( + "FT546-HISTORY-SIM", 1L, "OFFLINE", true, + "history-snapshot-v1", "history-snapshot-v1", nodes, + new LinkedHashSet(edges.values()), + Collections.emptyMap(), Collections.emptySet(), Collections.emptySet()); + return new TrafficMapCompiler().compile(definition); + } + + private List initialVehicles(List tasks, + Map details) { + Map earliest = + new HashMap(); + for (HistoricalSimulationTask task : tasks) { + String vehicle = task.getRecord().getAgvId(); + if (vehicle != null && (!earliest.containsKey(vehicle) + || task.getCreatedAt().isBefore(earliest.get(vehicle).getCreatedAt()))) { + earliest.put(vehicle, task); + } + } + List ids = new ArrayList(earliest.keySet()); + Collections.sort(ids); + List vehicles = new ArrayList(); + for (String id : ids) { + String site = earliest.get(id).getPhysicalSource(); + vehicles.add(new VehicleSnapshot(id, details.get(site).getZ(), site, + 5, 1, 100, true, true, false)); + } + return vehicles; + } + + private String failureReason(HistoricalPath path, DispatchTask.TaskType type) { + if (type == null) return "INVALID_TASK_TYPE"; + for (String issue : path.getIssues()) { + if (issue.startsWith("UNKNOWN_MAP_SITE")) return "UNKNOWN_MAP"; + } + return "INPUT_REJECTED"; + } + + private DispatchTask.TaskType taskType(String value) { + if ("1".equals(value)) return DispatchTask.TaskType.INBOUND; + if ("2".equals(value)) return DispatchTask.TaskType.OUTBOUND; + if ("3".equals(value)) return DispatchTask.TaskType.RELOCATION; + return null; + } + + private String simulationId(HistoricalQueueRecord record, int stableSequence) { + return text(record.getTaskNo()) + "@" + text(record.getGuid()) + "#" + stableSequence; + } + + private static String edgeId(String left, String right) { + return left.compareTo(right) <= 0 ? left + "|" + right : right + "|" + left; + } + + private static String text(String value) { return value == null ? "" : value; } +} diff --git a/javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/HistoricalSimulationTask.java b/javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/HistoricalSimulationTask.java new file mode 100644 index 0000000..21dcd4c --- /dev/null +++ b/javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/HistoricalSimulationTask.java @@ -0,0 +1,51 @@ +package com.ft546.wcs.shadow.history.simulation; + +import com.ft546.wcs.demo.model.DispatchTask; +import com.ft546.wcs.shadow.history.HistoricalQueueRecord; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public final class HistoricalSimulationTask { + private final String simulationTaskId; + private final HistoricalQueueRecord record; + private final DispatchTask.TaskType taskType; + private final int priority; + private final LocalDateTime createdAt; + private final LocalDateTime historicalFinishedAt; + private final String physicalSource; + private final String physicalTarget; + private final List historicalNodes; + private final int arrivalSequence; + + HistoricalSimulationTask(String simulationTaskId, HistoricalQueueRecord record, + DispatchTask.TaskType taskType, int priority, + LocalDateTime createdAt, LocalDateTime historicalFinishedAt, + String physicalSource, String physicalTarget, + List historicalNodes, int arrivalSequence) { + this.simulationTaskId = simulationTaskId; + this.record = record; + this.taskType = taskType; + this.priority = priority; + this.createdAt = createdAt; + this.historicalFinishedAt = historicalFinishedAt; + this.physicalSource = physicalSource; + this.physicalTarget = physicalTarget; + this.historicalNodes = Collections.unmodifiableList( + new ArrayList(historicalNodes)); + this.arrivalSequence = arrivalSequence; + } + + public String getSimulationTaskId() { return simulationTaskId; } + public HistoricalQueueRecord getRecord() { return record; } + public DispatchTask.TaskType getTaskType() { return taskType; } + public int getPriority() { return priority; } + public LocalDateTime getCreatedAt() { return createdAt; } + public LocalDateTime getHistoricalFinishedAt() { return historicalFinishedAt; } + public String getPhysicalSource() { return physicalSource; } + public String getPhysicalTarget() { return physicalTarget; } + public List getHistoricalNodes() { return historicalNodes; } + public int getArrivalSequence() { return arrivalSequence; } +} diff --git a/javaTest/src/test/java/com/ft546/wcs/shadow/HistoricalQueueLoaderTest.java b/javaTest/src/test/java/com/ft546/wcs/shadow/HistoricalQueueLoaderTest.java index 1690442..312996a 100644 --- a/javaTest/src/test/java/com/ft546/wcs/shadow/HistoricalQueueLoaderTest.java +++ b/javaTest/src/test/java/com/ft546/wcs/shadow/HistoricalQueueLoaderTest.java @@ -2,6 +2,7 @@ package com.ft546.wcs.shadow; import com.ft546.wcs.shadow.history.HistoricalQueueDataset; import com.ft546.wcs.shadow.history.HistoricalQueueLoader; +import com.ft546.wcs.shadow.history.HistoricalMapSite; import org.junit.jupiter.api.Test; import java.nio.charset.StandardCharsets; @@ -22,6 +23,12 @@ class HistoricalQueueLoaderTest { assertFalse(data.getRecords().get(0).getTaskNo().isEmpty()); assertFalse(data.getRecords().get(0).getAgvId().isEmpty()); assertTrue(data.getMapSites().size() > 1000); + assertEquals(1129, data.getMapSiteDetails().size()); + HistoricalMapSite site = data.getMapSiteDetails().get("010701"); + assertEquals(15, site.getX()); + assertEquals(1, site.getY()); + assertEquals(1, site.getZ()); + assertEquals("15,1", site.getAxy()); assertEquals("FT546-wcs", data.getDatabase()); assertEquals("dbo.wcs_srgv_queue_finish", data.getSourceTable()); assertEquals("2026-03-01 10:18:24", data.getMinCreateDate()); diff --git a/javaTest/src/test/java/com/ft546/wcs/shadow/history/simulation/HistoricalSimulationInputFactoryTest.java b/javaTest/src/test/java/com/ft546/wcs/shadow/history/simulation/HistoricalSimulationInputFactoryTest.java new file mode 100644 index 0000000..408f72f --- /dev/null +++ b/javaTest/src/test/java/com/ft546/wcs/shadow/history/simulation/HistoricalSimulationInputFactoryTest.java @@ -0,0 +1,58 @@ +package com.ft546.wcs.shadow.history.simulation; + +import com.ft546.wcs.demo.model.DispatchTask; +import com.ft546.wcs.shadow.history.HistoricalQueueDataset; +import com.ft546.wcs.shadow.history.HistoricalQueueLoader; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.EnumSet; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class HistoricalSimulationInputFactoryTest { + private static HistoricalSimulationInput input; + + @BeforeAll + static void createInput() { + HistoricalQueueDataset dataset = new HistoricalQueueLoader().load( + HistoricalSimulationInputFactoryTest.class.getClassLoader(), "shadow/history"); + input = new HistoricalSimulationInputFactory().create(dataset); + } + + @Test + void convertsEveryHistoryRowToOneUniqueSchedulableOrFailedInput() { + assertEquals(238, input.getTasks().size()); + assertEquals(2, input.getFailedInputs().size()); + assertEquals(240, input.allSimulationIds().size()); + assertEquals(240, input.allSimulationIds().stream().distinct().count()); + assertTrue(input.getFailedInputs().stream() + .allMatch(failure -> "UNKNOWN_MAP".equals(failure.getReason()))); + } + + @Test + void preservesBusinessInputsWhileMappingPhysicalEndpointsExplicitly() { + HistoricalSimulationTask task = input.getTasks().stream() + .filter(item -> "tsj".equals(item.getRecord().getEndSite())) + .findFirst().get(); + assertEquals("tsj", task.getRecord().getEndSite()); + assertFalse("tsj".equals(task.getPhysicalTarget())); + assertEquals(task.getHistoricalNodes().get(task.getHistoricalNodes().size() - 1), + task.getPhysicalTarget()); + assertFalse(task.getHistoricalNodes().isEmpty()); + } + + @Test + void buildsReplannableTopologyAndInitializesTheHistoricalFleet() { + assertEquals(EnumSet.allOf(DispatchTask.TaskType.class), input.getTasks().stream() + .map(HistoricalSimulationTask::getTaskType).collect(Collectors.toSet())); + assertTrue(input.getGridMap().getSites().size() > 100); + assertTrue(input.getTrafficMap().getEdges().size() > 100); + assertEquals(Arrays.asList("1", "2", "3"), input.getInitialVehicles().stream() + .map(vehicle -> vehicle.getVehicleId()).collect(Collectors.toList())); + } +}