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 index 6218f4b..fb9ba1b 100644 --- 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 @@ -1,27 +1,16 @@ 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.HistoricalMapCell; 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) { @@ -39,107 +28,52 @@ public final class HistoricalSimulationInputFactory { List tasks = new ArrayList(); List failures = new ArrayList(); + HistoricalGridMapCompiler.CompiledHistoricalMap compiled = + new HistoricalGridMapCompiler().compile(dataset.getFullMapCells()); + PhysicalEndpointResolver endpointResolver = new PhysicalEndpointResolver(); 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))); + if (type == null) { + failures.add(new HistoricalSimulationFailure(id, record, "INVALID_TASK_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); + PhysicalEndpointResolver.ResolvedEndpoints endpoints = endpointResolver.resolve( + record, compiled.getPassableSites()); + if (compiled.getComponentId(endpoints.getSource()) + != compiled.getComponentId(endpoints.getTarget())) { + throw new IllegalArgumentException("unreachable physical endpoints for " + id); + } tasks.add(new HistoricalSimulationTask(id, record, type, record.getPriority() == null ? Integer.MAX_VALUE : record.getPriority(), - path.getCreatedAt(), path.getFinishedAt(), source, target, nodes, stableSequence)); + path.getCreatedAt(), path.getFinishedAt(), endpoints.getSource(), + endpoints.getTarget(), path.getNodes(), 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); + List vehicles = initialVehicles(compiled); + return new HistoricalSimulationInput(tasks, failures, compiled.getGridMap(), + compiled.getTrafficMap(), 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); + private List initialVehicles( + HistoricalGridMapCompiler.CompiledHistoricalMap compiled) { List vehicles = new ArrayList(); - for (String id : ids) { - String site = earliest.get(id).getPhysicalSource(); - vehicles.add(new VehicleSnapshot(id, details.get(site).getZ(), site, + for (int floor = 1; floor <= 3; floor++) { + String id = String.valueOf(floor); + String site = "01070" + floor; + HistoricalMapCell cell = compiled.getPassableSites().get(site); + if (cell == null || cell.getZ() != floor) { + throw new IllegalArgumentException("missing fleet start site " + site); + } + vehicles.add(new VehicleSnapshot(id, floor, 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; @@ -151,9 +85,5 @@ public final class HistoricalSimulationInputFactory { 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/PhysicalEndpointResolver.java b/javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/PhysicalEndpointResolver.java new file mode 100644 index 0000000..c1818a9 --- /dev/null +++ b/javaTest/src/main/java/com/ft546/wcs/shadow/history/simulation/PhysicalEndpointResolver.java @@ -0,0 +1,56 @@ +package com.ft546.wcs.shadow.history.simulation; + +import com.ft546.wcs.shadow.history.HistoricalMapCell; +import com.ft546.wcs.shadow.history.HistoricalQueueRecord; + +import java.util.Map; + +public final class PhysicalEndpointResolver { + public ResolvedEndpoints resolve(HistoricalQueueRecord record, + Map passableSites) { + if (record == null || passableSites == null) { + throw new IllegalArgumentException("record and passable sites are required"); + } + HistoricalMapCell source = passableSites.get(record.getStartSite()); + if (source == null) { + throw new IllegalArgumentException("unknown physical source " + record.getStartSite()); + } + int floor = source.getZ(); + boolean declaredFloorConsistent = true; + if (record.getFloor() != null && !record.getFloor().trim().isEmpty()) { + try { + declaredFloorConsistent = Integer.parseInt(record.getFloor().trim()) == floor; + } catch (NumberFormatException ex) { + declaredFloorConsistent = false; + } + } + String targetId = "tsj".equals(record.getEndSite()) + ? "07050" + floor : record.getEndSite(); + HistoricalMapCell target = passableSites.get(targetId); + if (target == null) { + throw new IllegalArgumentException("unknown physical target " + targetId); + } + return new ResolvedEndpoints(source.getSiteId(), target.getSiteId(), floor, + declaredFloorConsistent); + } + + public static final class ResolvedEndpoints { + private final String source; + private final String target; + private final int floor; + private final boolean declaredFloorConsistent; + + private ResolvedEndpoints(String source, String target, int floor, + boolean declaredFloorConsistent) { + this.source = source; + this.target = target; + this.floor = floor; + this.declaredFloorConsistent = declaredFloorConsistent; + } + + public String getSource() { return source; } + public String getTarget() { return target; } + public int getFloor() { return floor; } + public boolean isDeclaredFloorConsistent() { return declaredFloorConsistent; } + } +} 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 index 408f72f..d677518 100644 --- 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 @@ -3,11 +3,14 @@ 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 com.ft546.wcs.shadow.history.HistoricalQueueRecord; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import java.util.Arrays; +import java.util.ArrayList; import java.util.EnumSet; +import java.util.List; import java.util.stream.Collectors; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -26,12 +29,10 @@ class HistoricalSimulationInputFactoryTest { @Test void convertsEveryHistoryRowToOneUniqueSchedulableOrFailedInput() { - assertEquals(238, input.getTasks().size()); - assertEquals(2, input.getFailedInputs().size()); + assertEquals(240, input.getTasks().size()); + assertEquals(0, 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 @@ -41,7 +42,7 @@ class HistoricalSimulationInputFactoryTest { .findFirst().get(); assertEquals("tsj", task.getRecord().getEndSite()); assertFalse("tsj".equals(task.getPhysicalTarget())); - assertEquals(task.getHistoricalNodes().get(task.getHistoricalNodes().size() - 1), + assertEquals("07050" + input.getGridMap().getSite(task.getPhysicalSource()).getFloor(), task.getPhysicalTarget()); assertFalse(task.getHistoricalNodes().isEmpty()); } @@ -50,9 +51,43 @@ class HistoricalSimulationInputFactoryTest { 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(1175, input.getGridMap().getSites().size()); + assertTrue(input.getTrafficMap().getEdges().size() > 1000); assertEquals(Arrays.asList("1", "2", "3"), input.getInitialVehicles().stream() .map(vehicle -> vehicle.getVehicleId()).collect(Collectors.toList())); } + + @Test + void changingHistoricalPathDoesNotChangePlanningInput() { + HistoricalQueueDataset original = new HistoricalQueueLoader().load( + getClass().getClassLoader(), "shadow/history"); + List changedRecords = + new ArrayList(original.getRecords()); + HistoricalQueueRecord first = changedRecords.get(0); + changedRecords.set(0, new HistoricalQueueRecord(first.getGuid(), first.getTaskNo(), + first.getTaskType(), first.getPriority(), first.getStartSite(), first.getEndSite(), + "FAKE_A,FAKE_B", first.getCreateDate(), first.getFinishDate(), first.getAgvId())); + HistoricalQueueDataset changed = copyWithRecords(original, changedRecords); + + HistoricalSimulationInput originalInput = new HistoricalSimulationInputFactory().create(original); + HistoricalSimulationInput changedInput = new HistoricalSimulationInputFactory().create(changed); + + assertEquals(originalInput.getTasks().size(), changedInput.getTasks().size()); + assertEquals(originalInput.getFailedInputs().size(), changedInput.getFailedInputs().size()); + assertEquals(originalInput.getGridMap().getSites().keySet(), + changedInput.getGridMap().getSites().keySet()); + assertEquals(originalInput.getTrafficMap().getEdges(), changedInput.getTrafficMap().getEdges()); + } + + private HistoricalQueueDataset copyWithRecords(HistoricalQueueDataset original, + List records) { + return new HistoricalQueueDataset(records, original.getMapSiteDetails(), + original.getFullMapCells(), original.getFullMapRows(), original.getFullMapSha256(), + original.getManifestSampleRows(), original.getFullScanTotalRows(), + original.getDatabase(), original.getSourceTable(), original.getExtractedAt(), + original.getMinCreateDate(), original.getMaxCreateDate(), + original.getMinFinishDate(), original.getMaxFinishDate(), + original.getMissingPathRows(), original.getMissingIntervalRows(), + original.getReversedIntervalRows()); + } } diff --git a/javaTest/src/test/java/com/ft546/wcs/shadow/history/simulation/PhysicalEndpointResolverTest.java b/javaTest/src/test/java/com/ft546/wcs/shadow/history/simulation/PhysicalEndpointResolverTest.java new file mode 100644 index 0000000..f2e8241 --- /dev/null +++ b/javaTest/src/test/java/com/ft546/wcs/shadow/history/simulation/PhysicalEndpointResolverTest.java @@ -0,0 +1,55 @@ +package com.ft546.wcs.shadow.history.simulation; + +import com.ft546.wcs.shadow.history.HistoricalMapCell; +import com.ft546.wcs.shadow.history.HistoricalQueueDataset; +import com.ft546.wcs.shadow.history.HistoricalQueueLoader; +import com.ft546.wcs.shadow.history.HistoricalQueueRecord; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +class PhysicalEndpointResolverTest { + @Test + void usesPhysicalSourceFloorWhenTheHistoricalFloorIsStale() { + HistoricalQueueDataset dataset = new HistoricalQueueLoader().load( + getClass().getClassLoader(), "shadow/history"); + Map sites = new HistoricalGridMapCompiler() + .compile(dataset.getFullMapCells()).getPassableSites(); + HistoricalQueueRecord record = dataset.getRecords().stream() + .filter(item -> "090501".equals(item.getStartSite())) + .filter(item -> "070101".equals(item.getEndSite())) + .findFirst().get(); + + PhysicalEndpointResolver.ResolvedEndpoints endpoints = + new PhysicalEndpointResolver().resolve(record, sites); + + assertEquals(1, endpoints.getFloor()); + assertEquals("070101", endpoints.getTarget()); + assertFalse(endpoints.isDeclaredFloorConsistent()); + } + + @ParameterizedTest + @ValueSource(ints = {1, 2, 3}) + void resolvesTsjByTheProductionFloorRule(int floor) { + HistoricalQueueDataset dataset = new HistoricalQueueLoader().load( + getClass().getClassLoader(), "shadow/history"); + Map sites = new HistoricalGridMapCompiler() + .compile(dataset.getFullMapCells()).getPassableSites(); + HistoricalQueueRecord record = dataset.getRecords().stream() + .filter(item -> "tsj".equals(item.getEndSite())) + .filter(item -> sites.get(item.getStartSite()).getZ() == floor) + .findFirst().get(); + + PhysicalEndpointResolver.ResolvedEndpoints endpoints = + new PhysicalEndpointResolver().resolve(record, sites); + + assertEquals(record.getStartSite(), endpoints.getSource()); + assertEquals("07050" + floor, endpoints.getTarget()); + assertEquals(floor, endpoints.getFloor()); + } +}