feat(javaTest): reconstruct historical business parent tasks
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
package com.ft546.wcs.shadow.history.simulation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/** 业务快照归并结果:可调度父任务、明确失败输入和原始数据审计。 */
|
||||
public final class HistoricalBusinessAssembly {
|
||||
private final List<HistoricalSimulationTask> tasks;
|
||||
private final List<HistoricalSimulationFailure> failures;
|
||||
private final HistoricalInputAudit audit;
|
||||
|
||||
HistoricalBusinessAssembly(List<HistoricalSimulationTask> tasks,
|
||||
List<HistoricalSimulationFailure> failures,
|
||||
HistoricalInputAudit audit) {
|
||||
this.tasks = Collections.unmodifiableList(
|
||||
new ArrayList<HistoricalSimulationTask>(tasks));
|
||||
this.failures = Collections.unmodifiableList(
|
||||
new ArrayList<HistoricalSimulationFailure>(failures));
|
||||
this.audit = audit;
|
||||
}
|
||||
|
||||
public List<HistoricalSimulationTask> getTasks() { return tasks; }
|
||||
public List<HistoricalSimulationFailure> getFailures() { return failures; }
|
||||
public HistoricalInputAudit getAudit() { return audit; }
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
package com.ft546.wcs.shadow.history.simulation;
|
||||
|
||||
import com.ft546.wcs.demo.model.DispatchTask;
|
||||
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 java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 将重复的生产队列扫描状态归并成一次可调度的 WMS 业务父任务。
|
||||
*
|
||||
* <p>{@code task_type} 只决定快照属于空车、载货还是车辆运行阶段;入库、出库、移库必须从
|
||||
* 同一业务号内唯一的 {@code default02=1/2/7} 解析。任何矛盾都形成可审计失败,不能任意取值。
|
||||
*/
|
||||
public final class HistoricalBusinessTaskAssembler {
|
||||
public HistoricalBusinessAssembly assemble(
|
||||
HistoricalQueueDataset dataset,
|
||||
HistoricalGridMapCompiler.CompiledHistoricalMap compiled,
|
||||
ChannelFrontTopology fronts) {
|
||||
if (dataset == null || compiled == null || fronts == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"dataset, compiled map and channel fronts are required");
|
||||
}
|
||||
|
||||
List<NormalizedRow> ordered = normalize(dataset);
|
||||
Map<String, List<NormalizedRow>> groups = groupBusinessRows(ordered);
|
||||
List<HistoricalSimulationTask> tasks = new ArrayList<HistoricalSimulationTask>();
|
||||
List<HistoricalSimulationFailure> failures =
|
||||
new ArrayList<HistoricalSimulationFailure>();
|
||||
int sequence = 0;
|
||||
for (Map.Entry<String, List<NormalizedRow>> entry : groups.entrySet()) {
|
||||
int stableSequence = sequence++;
|
||||
assembleGroup(entry.getKey(), entry.getValue(), stableSequence,
|
||||
compiled, fronts, tasks, failures);
|
||||
}
|
||||
return new HistoricalBusinessAssembly(tasks, failures,
|
||||
HistoricalInputAudit.from(dataset.getRecords(), groups.size()));
|
||||
}
|
||||
|
||||
/** 先规范化时间与参考路径,再按生产到达时间和稳定历史标识排序。 */
|
||||
private List<NormalizedRow> normalize(HistoricalQueueDataset dataset) {
|
||||
HistoricalQueueNormalizer normalizer = new HistoricalQueueNormalizer();
|
||||
List<NormalizedRow> rows = new ArrayList<NormalizedRow>();
|
||||
for (HistoricalQueueRecord record : dataset.getRecords()) {
|
||||
rows.add(new NormalizedRow(normalizer.normalize(record, dataset.getMapSites())));
|
||||
}
|
||||
Collections.sort(rows, Comparator
|
||||
.comparing(NormalizedRow::createdAt,
|
||||
Comparator.nullsLast(Comparator.naturalOrder()))
|
||||
.thenComparing(row -> text(row.record().getTaskNo()))
|
||||
.thenComparing(row -> text(row.record().getGuid()))
|
||||
.thenComparing(row -> text(row.record().getSnapshotRow())));
|
||||
return rows;
|
||||
}
|
||||
|
||||
/** 只有空车/载货阶段属于业务父任务;task_type=3 保留在输入审计中但不重复回调 WMS。 */
|
||||
private Map<String, List<NormalizedRow>> groupBusinessRows(List<NormalizedRow> ordered) {
|
||||
Map<String, List<NormalizedRow>> groups =
|
||||
new LinkedHashMap<String, List<NormalizedRow>>();
|
||||
for (NormalizedRow row : ordered) {
|
||||
if (!"1".equals(row.record().getTaskType())
|
||||
&& !"2".equals(row.record().getTaskType())) continue;
|
||||
String taskNo = text(row.record().getTaskNo());
|
||||
if (!groups.containsKey(taskNo)) {
|
||||
groups.put(taskNo, new ArrayList<NormalizedRow>());
|
||||
}
|
||||
groups.get(taskNo).add(row);
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
|
||||
/** 校验一个业务号内的类型、端点和优先级,并创建唯一父任务或唯一失败结果。 */
|
||||
private void assembleGroup(String taskNo, List<NormalizedRow> rows, int sequence,
|
||||
HistoricalGridMapCompiler.CompiledHistoricalMap compiled,
|
||||
ChannelFrontTopology fronts,
|
||||
List<HistoricalSimulationTask> tasks,
|
||||
List<HistoricalSimulationFailure> failures) {
|
||||
HistoricalQueueRecord fallback = rows.get(0).record();
|
||||
String id = "BUSINESS@" + taskNo + "#" + sequence;
|
||||
if (taskNo.isEmpty()) {
|
||||
failures.add(new HistoricalSimulationFailure(
|
||||
id, fallback, "CONFLICTING_HISTORY_SNAPSHOTS:TASK_NO"));
|
||||
return;
|
||||
}
|
||||
|
||||
Set<DispatchTask.TaskType> types = new LinkedHashSet<DispatchTask.TaskType>();
|
||||
Set<String> sources = new LinkedHashSet<String>();
|
||||
Set<String> targets = new LinkedHashSet<String>();
|
||||
Set<Integer> priorities = new LinkedHashSet<Integer>();
|
||||
for (NormalizedRow row : rows) {
|
||||
DispatchTask.TaskType type = businessType(row.record().getDefault02());
|
||||
if (type == null) {
|
||||
// default02=4/8 是取货或换层阶段,端点可能是中转站,只保留为阶段证据。
|
||||
continue;
|
||||
}
|
||||
// 父任务的业务类型、货物端点和优先级只能由 1/2/7 业务记录定义。
|
||||
types.add(type);
|
||||
addNonBlank(sources, row.record().getCargoPickSite());
|
||||
addNonBlank(targets, row.record().getCargoPlaceSite());
|
||||
if (row.record().getPriority() != null) priorities.add(row.record().getPriority());
|
||||
}
|
||||
if (types.size() != 1) {
|
||||
failures.add(new HistoricalSimulationFailure(
|
||||
id, fallback, "CONFLICTING_HISTORY_SNAPSHOTS:BUSINESS_TYPE"));
|
||||
return;
|
||||
}
|
||||
if (sources.size() != 1 || targets.size() != 1) {
|
||||
failures.add(new HistoricalSimulationFailure(
|
||||
id, fallback, "CONFLICTING_HISTORY_SNAPSHOTS:BUSINESS_ENDPOINT"));
|
||||
return;
|
||||
}
|
||||
if (priorities.size() > 1) {
|
||||
failures.add(new HistoricalSimulationFailure(
|
||||
id, fallback, "CONFLICTING_HISTORY_SNAPSHOTS:PRIORITY"));
|
||||
return;
|
||||
}
|
||||
|
||||
String source = sources.iterator().next();
|
||||
String target = targets.iterator().next();
|
||||
String invalidSite = missingSite(source, target, compiled);
|
||||
if (invalidSite != null) {
|
||||
failures.add(new HistoricalSimulationFailure(
|
||||
id, fallback, "UNKNOWN_MAP_SITE:" + invalidSite));
|
||||
return;
|
||||
}
|
||||
String sourceFront = front(fronts, source);
|
||||
String targetFront = front(fronts, target);
|
||||
String invalidFront = missingFront(sourceFront, targetFront, compiled);
|
||||
if (invalidFront != null) {
|
||||
failures.add(new HistoricalSimulationFailure(
|
||||
id, fallback, "UNKNOWN_MAP_SITE:CHANNEL_FRONT:" + invalidFront));
|
||||
return;
|
||||
}
|
||||
|
||||
NormalizedRow representative = representative(rows);
|
||||
LocalDateTime createdAt = earliestCreated(rows);
|
||||
if (createdAt == null) {
|
||||
failures.add(new HistoricalSimulationFailure(id, representative.record(),
|
||||
"INVALID_CREATE_DATE"));
|
||||
return;
|
||||
}
|
||||
List<HistoricalTaskSnapshotEvidence> evidence =
|
||||
new ArrayList<HistoricalTaskSnapshotEvidence>();
|
||||
for (NormalizedRow row : rows) {
|
||||
evidence.add(HistoricalTaskSnapshotEvidence.from(row.record()));
|
||||
}
|
||||
int priority = priorities.isEmpty() ? Integer.MAX_VALUE
|
||||
: priorities.iterator().next();
|
||||
tasks.add(new HistoricalSimulationTask(id, representative.record(),
|
||||
types.iterator().next(), priority, createdAt, latestFinished(rows),
|
||||
source, target, representative.path().getNodes(), sequence,
|
||||
sourceFront, targetFront, evidence));
|
||||
}
|
||||
|
||||
/** 优先使用带最终业务类型的载货阶段作为历史对照记录。 */
|
||||
private NormalizedRow representative(List<NormalizedRow> rows) {
|
||||
for (NormalizedRow row : rows) {
|
||||
if (businessType(row.record().getDefault02()) != null
|
||||
&& "2".equals(row.record().getTaskType())) return row;
|
||||
}
|
||||
for (NormalizedRow row : rows) {
|
||||
if (businessType(row.record().getDefault02()) != null) return row;
|
||||
}
|
||||
return rows.get(0);
|
||||
}
|
||||
|
||||
private DispatchTask.TaskType businessType(String value) {
|
||||
if ("1".equals(value)) return DispatchTask.TaskType.INBOUND;
|
||||
if ("2".equals(value)) return DispatchTask.TaskType.OUTBOUND;
|
||||
if ("7".equals(value)) return DispatchTask.TaskType.RELOCATION;
|
||||
return null;
|
||||
}
|
||||
|
||||
private LocalDateTime earliestCreated(List<NormalizedRow> rows) {
|
||||
LocalDateTime value = null;
|
||||
for (NormalizedRow row : rows) {
|
||||
if (row.createdAt() != null && (value == null || row.createdAt().isBefore(value))) {
|
||||
value = row.createdAt();
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private LocalDateTime latestFinished(List<NormalizedRow> rows) {
|
||||
LocalDateTime value = null;
|
||||
for (NormalizedRow row : rows) {
|
||||
LocalDateTime current = row.path().getFinishedAt();
|
||||
if (current != null && (value == null || current.isAfter(value))) value = current;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private String missingSite(String source, String target,
|
||||
HistoricalGridMapCompiler.CompiledHistoricalMap compiled) {
|
||||
if (!compiled.getPassableSites().containsKey(source)) return source;
|
||||
if (!compiled.getPassableSites().containsKey(target)) return target;
|
||||
return null;
|
||||
}
|
||||
|
||||
private String missingFront(String sourceFront, String targetFront,
|
||||
HistoricalGridMapCompiler.CompiledHistoricalMap compiled) {
|
||||
if (sourceFront != null && !compiled.getPassableSites().containsKey(sourceFront)) {
|
||||
return sourceFront;
|
||||
}
|
||||
if (targetFront != null && !compiled.getPassableSites().containsKey(targetFront)) {
|
||||
return targetFront;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String front(ChannelFrontTopology fronts, String site) {
|
||||
Optional<String> value = fronts.frontFor(site);
|
||||
return value.isPresent() ? value.get() : null;
|
||||
}
|
||||
|
||||
private void addNonBlank(Set<String> values, String value) {
|
||||
String normalized = text(value);
|
||||
if (!normalized.isEmpty()) values.add(normalized);
|
||||
}
|
||||
|
||||
private static String text(String value) {
|
||||
return value == null ? "" : value.trim();
|
||||
}
|
||||
|
||||
private static final class NormalizedRow {
|
||||
private final HistoricalPath path;
|
||||
|
||||
private NormalizedRow(HistoricalPath path) { this.path = path; }
|
||||
private HistoricalPath path() { return path; }
|
||||
private HistoricalQueueRecord record() { return path.getRecord(); }
|
||||
private LocalDateTime createdAt() { return path.getCreatedAt(); }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.ft546.wcs.shadow.history.simulation;
|
||||
|
||||
import com.ft546.wcs.shadow.history.HistoricalQueueRecord;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/** 保存从原始扫描行到唯一队列和业务父任务的双口径数据质量统计。 */
|
||||
public final class HistoricalInputAudit {
|
||||
private final int rawSnapshotCount;
|
||||
private final int uniqueQueueCount;
|
||||
private final int duplicateSnapshotCount;
|
||||
private final int businessTaskCount;
|
||||
private final int operationalQueueCount;
|
||||
|
||||
public HistoricalInputAudit(int rawSnapshotCount, int uniqueQueueCount,
|
||||
int duplicateSnapshotCount, int businessTaskCount,
|
||||
int operationalQueueCount) {
|
||||
if (rawSnapshotCount < 0 || uniqueQueueCount < 0 || duplicateSnapshotCount < 0
|
||||
|| businessTaskCount < 0 || operationalQueueCount < 0
|
||||
|| uniqueQueueCount + duplicateSnapshotCount != rawSnapshotCount) {
|
||||
throw new IllegalArgumentException("consistent non-negative audit counts are required");
|
||||
}
|
||||
this.rawSnapshotCount = rawSnapshotCount;
|
||||
this.uniqueQueueCount = uniqueQueueCount;
|
||||
this.duplicateSnapshotCount = duplicateSnapshotCount;
|
||||
this.businessTaskCount = businessTaskCount;
|
||||
this.operationalQueueCount = operationalQueueCount;
|
||||
}
|
||||
|
||||
/** 按 GUID 去重,并把 task_type=3 的唯一队列识别为车辆运行作业。 */
|
||||
static HistoricalInputAudit from(List<HistoricalQueueRecord> rows, int businessTaskCount) {
|
||||
Set<String> unique = new LinkedHashSet<String>();
|
||||
Set<String> operational = new LinkedHashSet<String>();
|
||||
for (HistoricalQueueRecord row : rows) {
|
||||
String guid = text(row.getGuid());
|
||||
if (!guid.isEmpty()) unique.add(guid);
|
||||
if ("3".equals(row.getTaskType()) && !guid.isEmpty()) operational.add(guid);
|
||||
}
|
||||
return new HistoricalInputAudit(rows.size(), unique.size(), rows.size() - unique.size(),
|
||||
businessTaskCount, operational.size());
|
||||
}
|
||||
|
||||
/** 按业务号统计空车/载货阶段,用于原始回放仍展示同一份业务口径。 */
|
||||
static HistoricalInputAudit from(List<HistoricalQueueRecord> rows) {
|
||||
Set<String> businessTaskNos = new LinkedHashSet<String>();
|
||||
for (HistoricalQueueRecord row : rows) {
|
||||
if (("1".equals(row.getTaskType()) || "2".equals(row.getTaskType()))
|
||||
&& !text(row.getTaskNo()).isEmpty()) {
|
||||
businessTaskNos.add(text(row.getTaskNo()));
|
||||
}
|
||||
}
|
||||
return from(rows, businessTaskNos.size());
|
||||
}
|
||||
|
||||
static HistoricalInputAudit syntheticRaw(int taskCount) {
|
||||
return new HistoricalInputAudit(taskCount, taskCount, 0, 0, 0);
|
||||
}
|
||||
|
||||
public int getRawSnapshotCount() { return rawSnapshotCount; }
|
||||
public int getUniqueQueueCount() { return uniqueQueueCount; }
|
||||
public int getDuplicateSnapshotCount() { return duplicateSnapshotCount; }
|
||||
public int getBusinessTaskCount() { return businessTaskCount; }
|
||||
public int getOperationalQueueCount() { return operationalQueueCount; }
|
||||
|
||||
private static String text(String value) {
|
||||
return value == null ? "" : value.trim();
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,9 @@ public final class HistoricalSimulationInput {
|
||||
private final EffectiveTrafficMap trafficMap;
|
||||
private final List<VehicleSnapshot> initialVehicles;
|
||||
private final FloorTransferTopology floorTransferTopology;
|
||||
private final SimulationExecutionMode executionMode;
|
||||
private final HistoricalInputAudit inputAudit;
|
||||
private final ChannelFrontTopology channelFrontTopology;
|
||||
|
||||
/**
|
||||
* 创建 {@code HistoricalSimulationInput} 实例并初始化其内部状态。
|
||||
@@ -51,6 +54,23 @@ public final class HistoricalSimulationInput {
|
||||
GridMap gridMap, EffectiveTrafficMap trafficMap,
|
||||
List<VehicleSnapshot> initialVehicles,
|
||||
FloorTransferTopology floorTransferTopology) {
|
||||
this(tasks, failedInputs, gridMap, trafficMap, initialVehicles,
|
||||
floorTransferTopology, SimulationExecutionMode.RAW_SNAPSHOT,
|
||||
HistoricalInputAudit.syntheticRaw(tasks.size() + failedInputs.size()),
|
||||
ChannelFrontTopology.from(
|
||||
Collections.<com.ft546.wcs.shadow.history.HistoricalChannelFrontRecord>
|
||||
emptyList()));
|
||||
}
|
||||
|
||||
/** 创建带原始审计口径和通道前端目录的明确执行模式输入。 */
|
||||
HistoricalSimulationInput(List<HistoricalSimulationTask> tasks,
|
||||
List<HistoricalSimulationFailure> failedInputs,
|
||||
GridMap gridMap, EffectiveTrafficMap trafficMap,
|
||||
List<VehicleSnapshot> initialVehicles,
|
||||
FloorTransferTopology floorTransferTopology,
|
||||
SimulationExecutionMode executionMode,
|
||||
HistoricalInputAudit inputAudit,
|
||||
ChannelFrontTopology channelFrontTopology) {
|
||||
this.tasks = immutable(tasks);
|
||||
this.failedInputs = immutable(failedInputs);
|
||||
this.gridMap = gridMap;
|
||||
@@ -60,6 +80,13 @@ public final class HistoricalSimulationInput {
|
||||
throw new IllegalArgumentException("floor transfer topology is required");
|
||||
}
|
||||
this.floorTransferTopology = floorTransferTopology;
|
||||
if (executionMode == null || inputAudit == null || channelFrontTopology == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"execution mode, input audit and channel front topology are required");
|
||||
}
|
||||
this.executionMode = executionMode;
|
||||
this.inputAudit = inputAudit;
|
||||
this.channelFrontTopology = channelFrontTopology;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,6 +128,15 @@ public final class HistoricalSimulationInput {
|
||||
/** 返回项目换层端口目录;该目录不向普通路径规划器暴露跨层边。 */
|
||||
public FloorTransferTopology getFloorTransferTopology() { return floorTransferTopology; }
|
||||
|
||||
/** 返回本次输入采用的原始快照或源码一致业务回放模式。 */
|
||||
public SimulationExecutionMode getExecutionMode() { return executionMode; }
|
||||
|
||||
/** 返回 240 原始行、唯一 GUID、重复行和业务父任务的双口径审计。 */
|
||||
public HistoricalInputAudit getInputAudit() { return inputAudit; }
|
||||
|
||||
/** 返回生产货位分组快照构建的通道前端目录。 */
|
||||
public ChannelFrontTopology getChannelFrontTopology() { return channelFrontTopology; }
|
||||
|
||||
/**
|
||||
* 执行“all simulation ids”所表示的处理流程。
|
||||
*
|
||||
|
||||
@@ -16,7 +16,25 @@ import java.util.Map;
|
||||
|
||||
public final class HistoricalSimulationInputFactory {
|
||||
/**
|
||||
* 将历史队列和完整地图转换为中心调度可直接执行的仿真输入。
|
||||
* 默认按生产源码语义把重复队列阶段归并成业务父任务,并附加权威通道前端目录。
|
||||
*/
|
||||
public HistoricalSimulationInput create(HistoricalQueueDataset dataset) {
|
||||
if (dataset == null) throw new IllegalArgumentException("dataset is required");
|
||||
HistoricalGridMapCompiler.CompiledHistoricalMap compiled =
|
||||
new HistoricalGridMapCompiler().compile(dataset.getFullMapCells());
|
||||
ChannelFrontTopology fronts = ChannelFrontTopology.from(
|
||||
dataset.getChannelFrontRecords());
|
||||
HistoricalBusinessAssembly assembly = new HistoricalBusinessTaskAssembler()
|
||||
.assemble(dataset, compiled, fronts);
|
||||
return new HistoricalSimulationInput(assembly.getTasks(), assembly.getFailures(),
|
||||
compiled.getGridMap(), compiled.getTrafficMap(), initialVehicles(compiled),
|
||||
floorTransferTopology(compiled),
|
||||
SimulationExecutionMode.SOURCE_ALIGNED_BUSINESS,
|
||||
assembly.getAudit(), fronts);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将每条历史扫描行按旧口径转换为独立任务,只用于和改造前的 240 行结果对照。
|
||||
*
|
||||
* <p>历史记录先规范化并按 {@code create_date/task_no/guid} 稳定排序;再由完整地图解析物理端点和连通性。
|
||||
* 任务号、类型、起终点、优先级保留为业务输入,历史路径只保留作对照;非法任务类型形成
|
||||
@@ -24,7 +42,7 @@ public final class HistoricalSimulationInputFactory {
|
||||
* @param dataset 执行该处理所需的 dataset 输入。
|
||||
* @return 包含可调度任务、输入失败、完整交通图和初始车队的仿真输入。
|
||||
*/
|
||||
public HistoricalSimulationInput create(HistoricalQueueDataset dataset) {
|
||||
public HistoricalSimulationInput createRawSnapshots(HistoricalQueueDataset dataset) {
|
||||
if (dataset == null) throw new IllegalArgumentException("dataset is required");
|
||||
HistoricalQueueNormalizer normalizer = new HistoricalQueueNormalizer();
|
||||
List<HistoricalPath> paths = new ArrayList<HistoricalPath>();
|
||||
@@ -76,8 +94,12 @@ public final class HistoricalSimulationInputFactory {
|
||||
}
|
||||
|
||||
List<VehicleSnapshot> vehicles = initialVehicles(compiled);
|
||||
ChannelFrontTopology fronts = ChannelFrontTopology.from(
|
||||
dataset.getChannelFrontRecords());
|
||||
return new HistoricalSimulationInput(tasks, failures, compiled.getGridMap(),
|
||||
compiled.getTrafficMap(), vehicles, floorTransferTopology(compiled));
|
||||
compiled.getTrafficMap(), vehicles, floorTransferTopology(compiled),
|
||||
SimulationExecutionMode.RAW_SNAPSHOT,
|
||||
HistoricalInputAudit.from(dataset.getRecords()), fronts);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,6 +19,9 @@ public final class HistoricalSimulationTask {
|
||||
private final String physicalTarget;
|
||||
private final List<String> historicalNodes;
|
||||
private final int arrivalSequence;
|
||||
private final String sourceFrontSite;
|
||||
private final String targetFrontSite;
|
||||
private final List<HistoricalTaskSnapshotEvidence> snapshotEvidence;
|
||||
|
||||
/**
|
||||
* 创建 {@code HistoricalSimulationTask} 实例并初始化其内部状态。
|
||||
@@ -40,6 +43,22 @@ public final class HistoricalSimulationTask {
|
||||
LocalDateTime createdAt, LocalDateTime historicalFinishedAt,
|
||||
String physicalSource, String physicalTarget,
|
||||
List<String> historicalNodes, int arrivalSequence) {
|
||||
this(simulationTaskId, record, taskType, priority, createdAt,
|
||||
historicalFinishedAt, physicalSource, physicalTarget,
|
||||
historicalNodes, arrivalSequence, null, null,
|
||||
Collections.<HistoricalTaskSnapshotEvidence>emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建源码一致的业务父任务,同时保存源/目标通道前端和全部原始阶段快照证据。
|
||||
*/
|
||||
HistoricalSimulationTask(String simulationTaskId, HistoricalQueueRecord record,
|
||||
DispatchTask.TaskType taskType, int priority,
|
||||
LocalDateTime createdAt, LocalDateTime historicalFinishedAt,
|
||||
String physicalSource, String physicalTarget,
|
||||
List<String> historicalNodes, int arrivalSequence,
|
||||
String sourceFrontSite, String targetFrontSite,
|
||||
List<HistoricalTaskSnapshotEvidence> snapshotEvidence) {
|
||||
this.simulationTaskId = simulationTaskId;
|
||||
this.record = record;
|
||||
this.taskType = taskType;
|
||||
@@ -51,6 +70,10 @@ public final class HistoricalSimulationTask {
|
||||
this.historicalNodes = Collections.unmodifiableList(
|
||||
new ArrayList<String>(historicalNodes));
|
||||
this.arrivalSequence = arrivalSequence;
|
||||
this.sourceFrontSite = sourceFrontSite;
|
||||
this.targetFrontSite = targetFrontSite;
|
||||
this.snapshotEvidence = Collections.unmodifiableList(
|
||||
new ArrayList<HistoricalTaskSnapshotEvidence>(snapshotEvidence));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -123,4 +146,23 @@ public final class HistoricalSimulationTask {
|
||||
* @return 本次处理产生的结果;调用方据此继续后续流程。
|
||||
*/
|
||||
public int getArrivalSequence() { return arrivalSequence; }
|
||||
|
||||
/** 返回源货位所属通道前端;外部输送点没有货位分组时返回 {@code null}。 */
|
||||
public String getSourceFrontSite() { return sourceFrontSite; }
|
||||
|
||||
/** 返回目标货位所属通道前端;外部输送点没有货位分组时返回 {@code null}。 */
|
||||
public String getTargetFrontSite() { return targetFrontSite; }
|
||||
|
||||
public boolean requiresSourceFrontMovement() {
|
||||
return sourceFrontSite != null && !sourceFrontSite.equals(physicalSource);
|
||||
}
|
||||
|
||||
public boolean requiresTargetFrontMovement() {
|
||||
return targetFrontSite != null && !targetFrontSite.equals(physicalTarget);
|
||||
}
|
||||
|
||||
/** 返回归并到该父任务的全部历史空车、载货和重复扫描证据。 */
|
||||
public List<HistoricalTaskSnapshotEvidence> getSnapshotEvidence() {
|
||||
return snapshotEvidence;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.ft546.wcs.shadow.history.simulation;
|
||||
|
||||
import com.ft546.wcs.shadow.history.HistoricalQueueRecord;
|
||||
|
||||
/** 保存父任务关联的每一条原始队列快照,供阶段和车辆路径对照。 */
|
||||
public final class HistoricalTaskSnapshotEvidence {
|
||||
private final String snapshotRow;
|
||||
private final String guid;
|
||||
private final String queueTaskType;
|
||||
private final String default02;
|
||||
private final String historicalVehicleId;
|
||||
private final String historicalPath;
|
||||
|
||||
private HistoricalTaskSnapshotEvidence(HistoricalQueueRecord record) {
|
||||
this.snapshotRow = record.getSnapshotRow();
|
||||
this.guid = record.getGuid();
|
||||
this.queueTaskType = record.getTaskType();
|
||||
this.default02 = record.getDefault02();
|
||||
this.historicalVehicleId = record.getAgvId();
|
||||
this.historicalPath = record.getPathInfo();
|
||||
}
|
||||
|
||||
public static HistoricalTaskSnapshotEvidence from(HistoricalQueueRecord record) {
|
||||
if (record == null) throw new IllegalArgumentException("history record is required");
|
||||
return new HistoricalTaskSnapshotEvidence(record);
|
||||
}
|
||||
|
||||
public String getSnapshotRow() { return snapshotRow; }
|
||||
public String getGuid() { return guid; }
|
||||
public String getQueueTaskType() { return queueTaskType; }
|
||||
public String getDefault02() { return default02; }
|
||||
public String getHistoricalVehicleId() { return historicalVehicleId; }
|
||||
public String getHistoricalPath() { return historicalPath; }
|
||||
}
|
||||
@@ -46,13 +46,24 @@ public final class PointToPointSimulationInputFactory {
|
||||
sourceSite, targetSite, pathInfo,
|
||||
TIME.format(CREATED_AT), TIME.format(CREATED_AT.plusMinutes(1)),
|
||||
historicalVehicleId);
|
||||
|
||||
// A/B 审计沿用生产货位分组:有分组的端点进入二段式通道作业,没有分组的端点仍是普通站点。
|
||||
ChannelFrontTopology fronts = base.getChannelFrontTopology();
|
||||
String sourceFront = fronts.frontFor(sourceSite).orElse(null);
|
||||
String targetFront = fronts.frontFor(targetSite).orElse(null);
|
||||
HistoricalSimulationTask task = new HistoricalSimulationTask(
|
||||
TASK_ID, record, taskType, priority, CREATED_AT,
|
||||
CREATED_AT.plusMinutes(1), sourceSite, targetSite, history, 0);
|
||||
CREATED_AT.plusMinutes(1), sourceSite, targetSite, history, 0,
|
||||
sourceFront, targetFront,
|
||||
Collections.<HistoricalTaskSnapshotEvidence>emptyList());
|
||||
|
||||
// 单任务审计仍按一条业务父任务报告,不能因复用旧构造器被标记成原始快照回放。
|
||||
return new HistoricalSimulationInput(
|
||||
Collections.singletonList(task), Collections.emptyList(),
|
||||
base.getGridMap(), base.getTrafficMap(), base.getInitialVehicles(),
|
||||
base.getFloorTransferTopology());
|
||||
base.getFloorTransferTopology(),
|
||||
SimulationExecutionMode.SOURCE_ALIGNED_BUSINESS,
|
||||
new HistoricalInputAudit(1, 1, 0, 1, 0), fronts);
|
||||
}
|
||||
|
||||
private String taskTypeCode(DispatchTask.TaskType taskType) {
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.ft546.wcs.shadow.history.simulation;
|
||||
|
||||
/** 区分旧的逐行快照对照回放和源码一致的业务父任务回放。 */
|
||||
public enum SimulationExecutionMode {
|
||||
RAW_SNAPSHOT,
|
||||
SOURCE_ALIGNED_BUSINESS
|
||||
}
|
||||
Reference in New Issue
Block a user