fix(shadow): respect queue phases and compress input evidence
This commit is contained in:
@@ -168,6 +168,7 @@ public final class Ft546ShadowCycle {
|
||||
"INVALID_COMMAND_FACTS:" + message(failure), progress);
|
||||
}
|
||||
progress.enter("EVIDENCE_BUILD");
|
||||
progress.dispatch(batch, snapshot, result.getDecisions());
|
||||
List<ShadowCycleEvidence> evidence = evidenceCollector.collect(batch,
|
||||
result.getDecisions(), encoded, clock.instant());
|
||||
progress.enter("EVIDENCE_SAVE");
|
||||
@@ -199,7 +200,9 @@ public final class Ft546ShadowCycle {
|
||||
.getReasonCode();
|
||||
if ("EVIDENCE_KEY_COLLISION".equals(reasonCode)
|
||||
|| "EVIDENCE_RUN_COLLISION".equals(reasonCode)
|
||||
|| "EVIDENCE_BATCH_PAYLOAD_LIMIT".equals(reasonCode)) {
|
||||
|| "EVIDENCE_BATCH_PAYLOAD_LIMIT".equals(reasonCode)
|
||||
|| "EVIDENCE_LOGICAL_BATCH_LIMIT".equals(reasonCode)
|
||||
|| reasonCode.startsWith("INPUT_")) {
|
||||
return reasonCode;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ final class ShadowCycleDiagnostics {
|
||||
private final Map<String, Long> phaseNanos = new LinkedHashMap<String, Long>();
|
||||
private Map<String, Object> completed;
|
||||
private boolean cancelled;
|
||||
private Map<String, Object> dispatch = Collections.emptyMap();
|
||||
|
||||
ShadowCycleDiagnostics() { this(System::nanoTime); }
|
||||
|
||||
@@ -27,6 +28,43 @@ final class ShadowCycleDiagnostics {
|
||||
this.phaseStarted = started;
|
||||
}
|
||||
|
||||
synchronized void dispatch(com.ruoyi.shadow.facts.ShadowFactBatch batch,
|
||||
com.ruoyi.wcs.application.dispatch.DispatchSnapshot snapshot,
|
||||
com.ruoyi.wcs.application.dispatch.DispatchCycleResult result) {
|
||||
if (completed != null || cancelled) return;
|
||||
Map<String, Object> values = new LinkedHashMap<String, Object>();
|
||||
values.put("eligiblePhases", snapshot.getReadySteps().size());
|
||||
values.put("granted", result.getGranted().size());
|
||||
values.put("held", result.getHeld().size());
|
||||
values.put("yielded", result.getYielded().size());
|
||||
Map<String, Integer> observations = new LinkedHashMap<String, Integer>();
|
||||
for (com.ruoyi.shadow.facts.ShadowQueueFact queue : batch.getQueues()) {
|
||||
if (!queue.isActive()) continue;
|
||||
String reason = com.ruoyi.shadow.facts.ShadowQueueDispatchEligibility.observationReason(queue, batch);
|
||||
if (reason != null) observations.merge(reason, 1, Integer::sum);
|
||||
}
|
||||
values.put("observations", Collections.unmodifiableMap(observations));
|
||||
int safe = 0;
|
||||
for (String id : snapshot.getTopology().getNodeIds()) {
|
||||
com.ruoyi.wcs.domain.topology.TopologyNode node = snapshot.getTopology().node(id);
|
||||
if (node.isSafeWait() || node.isSafeYield()) safe++;
|
||||
}
|
||||
values.put("validatedSafePointCount", safe);
|
||||
values.put("safePointStatus", safe == 0 ? "UNCONFIGURED" : "TOPOLOGY_VALIDATED");
|
||||
List<Map<String, Object>> holds = new ArrayList<Map<String, Object>>();
|
||||
for (com.ruoyi.wcs.application.dispatch.HeldDispatch hold : result.getHeld()) {
|
||||
if (holds.size() == 20) break;
|
||||
Map<String, Object> row = new LinkedHashMap<String, Object>();
|
||||
row.put("taskId", hold.getOrderId()); row.put("vehicleId", hold.getVehicleId());
|
||||
row.put("reasonCode", hold.getReasonCode());
|
||||
List<String> blockers = new ArrayList<String>(hold.getBlockingVehicleIds());
|
||||
row.put("blockingVehicleIds", Collections.unmodifiableList(blockers.subList(0, Math.min(20, blockers.size()))));
|
||||
holds.add(Collections.unmodifiableMap(row));
|
||||
}
|
||||
values.put("holdSamples", Collections.unmodifiableList(holds));
|
||||
dispatch = Collections.unmodifiableMap(values);
|
||||
}
|
||||
|
||||
synchronized void enter(String next) {
|
||||
if (cancelled || Thread.currentThread().isInterrupted()) {
|
||||
throw new CancellationException("SHADOW_CYCLE_CANCELLED");
|
||||
@@ -49,6 +87,7 @@ final class ShadowCycleDiagnostics {
|
||||
Map<String, Long> timings = new LinkedHashMap<String, Long>();
|
||||
phaseNanos.forEach((key, value) -> timings.put(key, TimeUnit.NANOSECONDS.toMillis(value)));
|
||||
snapshot.put("phaseMillis", Collections.unmodifiableMap(timings));
|
||||
snapshot.put("dispatch", dispatch);
|
||||
if (worker != null) {
|
||||
snapshot.put("workerState", worker.getState().name());
|
||||
List<String> stack = new ArrayList<String>();
|
||||
|
||||
@@ -39,12 +39,20 @@ public final class ShadowDispatchSnapshotAssembler {
|
||||
private final long topologyVersion;
|
||||
private final Function<EffectiveTopology, ShadowTrafficState> stateLoader;
|
||||
private final Clock clock;
|
||||
private final java.util.Set<String> safeWaitSites;
|
||||
private final java.util.Set<String> safeYieldSites;
|
||||
|
||||
public ShadowDispatchSnapshotAssembler(
|
||||
String warehouseCode,
|
||||
long topologyVersion,
|
||||
Function<EffectiveTopology, ShadowTrafficState> stateLoader,
|
||||
Clock clock) {
|
||||
this(warehouseCode, topologyVersion, stateLoader, clock, Collections.emptySet(), Collections.emptySet());
|
||||
}
|
||||
|
||||
public ShadowDispatchSnapshotAssembler(String warehouseCode, long topologyVersion,
|
||||
Function<EffectiveTopology, ShadowTrafficState> stateLoader, Clock clock,
|
||||
java.util.Set<String> safeWaitSites, java.util.Set<String> safeYieldSites) {
|
||||
this.warehouseCode = required(warehouseCode, "warehouseCode");
|
||||
if (topologyVersion <= 0L) {
|
||||
throw new IllegalArgumentException(
|
||||
@@ -53,6 +61,8 @@ public final class ShadowDispatchSnapshotAssembler {
|
||||
this.topologyVersion = topologyVersion;
|
||||
this.stateLoader = Objects.requireNonNull(stateLoader, "stateLoader");
|
||||
this.clock = Objects.requireNonNull(clock, "clock");
|
||||
this.safeWaitSites = safeSites(safeWaitSites);
|
||||
this.safeYieldSites = safeSites(safeYieldSites);
|
||||
}
|
||||
|
||||
public DispatchSnapshot assemble(ShadowFactBatch batch) {
|
||||
@@ -74,7 +84,8 @@ public final class ShadowDispatchSnapshotAssembler {
|
||||
continue;
|
||||
}
|
||||
nodes.add(new TopologyNode(fact.getSiteId(), floor(fact.getZ()),
|
||||
fact.getX(), fact.getY(), 1, false, false));
|
||||
fact.getX(), fact.getY(), 1, safeWaitSites.contains(fact.getSiteId()),
|
||||
safeYieldSites.contains(fact.getSiteId())));
|
||||
String coordinate = coordinate(fact.getX(), fact.getY(), fact.getZ());
|
||||
if (coordinates.put(coordinate, fact) != null) {
|
||||
throw new IllegalArgumentException(
|
||||
@@ -96,7 +107,26 @@ public final class ShadowDispatchSnapshotAssembler {
|
||||
Collections.<ResourceDefinition>emptyList(),
|
||||
Collections.<String>emptyList(),
|
||||
Collections.<String>emptyList());
|
||||
return new TopologyCompiler().compile(definition);
|
||||
EffectiveTopology topology = new TopologyCompiler().compile(definition);
|
||||
java.util.Set<String> configured = new java.util.LinkedHashSet<String>(safeWaitSites);
|
||||
configured.addAll(safeYieldSites);
|
||||
for (String id : configured) {
|
||||
if (!topology.containsNode(id) || topology.neighbors(id).size() < 2) {
|
||||
throw new IllegalArgumentException("INVALID_CONFIGURED_SAFE_SITE:" + id);
|
||||
}
|
||||
}
|
||||
return topology;
|
||||
}
|
||||
|
||||
public static java.util.Set<String> safeSites(java.util.Set<String> values) {
|
||||
java.util.Set<String> normalized = new java.util.TreeSet<String>();
|
||||
for (String value : Objects.requireNonNull(values, "safeSites")) {
|
||||
String id = required(value, "safeSite");
|
||||
if (!id.matches("[0-9]{1,6}") || Integer.parseInt(id) == 0)
|
||||
throw new IllegalArgumentException("INVALID_CONFIGURED_SAFE_SITE:" + id);
|
||||
normalized.add(id);
|
||||
}
|
||||
return Collections.unmodifiableSet(normalized);
|
||||
}
|
||||
|
||||
private List<DispatchTask> tasks(ShadowFactBatch batch,
|
||||
@@ -104,10 +134,10 @@ public final class ShadowDispatchSnapshotAssembler {
|
||||
List<DispatchTask> tasks = new ArrayList<DispatchTask>();
|
||||
Instant now = clock.instant();
|
||||
for (ShadowQueueFact fact : batch.getQueues()) {
|
||||
if (!ACTIVE_QUEUE.equals(fact.getTableName())) {
|
||||
if (com.ruoyi.shadow.facts.ShadowQueueDispatchEligibility.observationReason(fact, batch) != null) {
|
||||
continue;
|
||||
}
|
||||
String source = required(ShadowQueueEndpoints.start(fact, batch), "startSite");
|
||||
String source = required(ShadowQueueEndpoints.dispatchStart(fact, batch), "startSite");
|
||||
String destination = required(ShadowQueueEndpoints.end(fact, batch), "endSite");
|
||||
if (!topology.containsNode(source)
|
||||
|| !topology.containsNode(destination)) {
|
||||
@@ -130,6 +160,10 @@ public final class ShadowDispatchSnapshotAssembler {
|
||||
values.put("parentTaskId", parentTaskId);
|
||||
values.put("phase", phase(required(fact.getTaskType(), "taskType")));
|
||||
values.put("routeVersion", Long.toString(topologyVersion));
|
||||
String boundVehicle = optional(fact.getAgvId());
|
||||
if (boundVehicle != null && !"0".equals(boundVehicle)) {
|
||||
values.put("shadowBoundVehicleId", boundVehicle);
|
||||
}
|
||||
values.put("taskNo", positiveNumber(parentTaskId, "taskNo"));
|
||||
short taskType = parsedShort(required(fact.getDefault02(), "default02"),
|
||||
"default02");
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.ruoyi.shadow.kernel;
|
||||
|
||||
import com.ruoyi.wcs.domain.dispatch.DispatchTask;
|
||||
import com.ruoyi.wcs.domain.dispatch.VehicleAssignmentDecision;
|
||||
import com.ruoyi.wcs.domain.dispatch.VehicleAssignmentPolicy;
|
||||
import com.ruoyi.wcs.domain.equipment.VehicleSnapshot;
|
||||
import com.ruoyi.wcs.domain.topology.EffectiveTopology;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/** Keeps the source phase's vehicle binding without relaxing the normal safety gates. */
|
||||
public final class ShadowVehicleAssignmentPolicy implements VehicleAssignmentPolicy {
|
||||
private final VehicleAssignmentPolicy delegate;
|
||||
|
||||
public ShadowVehicleAssignmentPolicy(VehicleAssignmentPolicy delegate) {
|
||||
this.delegate = Objects.requireNonNull(delegate, "delegate");
|
||||
}
|
||||
|
||||
@Override
|
||||
public VehicleAssignmentDecision assign(DispatchTask task, List<VehicleSnapshot> vehicles,
|
||||
EffectiveTopology topology) {
|
||||
String bound = task.getCommandParameters().get("shadowBoundVehicleId");
|
||||
if (bound == null) return delegate.assign(task, vehicles, topology);
|
||||
List<VehicleSnapshot> candidates = new ArrayList<VehicleSnapshot>();
|
||||
for (VehicleSnapshot vehicle : vehicles) {
|
||||
if (bound.equals(vehicle.getVehicleId())) candidates.add(vehicle);
|
||||
}
|
||||
return delegate.assign(task, candidates, topology);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user