feat(javaTest): load source task and aisle front fields
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
package com.ft546.wcs.shadow.history;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* 保存从生产 bas_cargo_space 只读查询导出的货位分组和通道前端事实。
|
||||
*
|
||||
* <p>仿真只读取该离线快照,不在运行时访问生产数据库,也不通过站点编号或坐标猜测前端。
|
||||
*/
|
||||
public final class HistoricalChannelFrontRecord {
|
||||
@JsonProperty("site_id") private String siteId;
|
||||
@JsonProperty("group_coding_no") private String groupCodingNo;
|
||||
@JsonProperty("group_coding") private String groupCoding;
|
||||
@JsonProperty("front_site_id") private String frontSiteId;
|
||||
|
||||
public HistoricalChannelFrontRecord() { }
|
||||
|
||||
public String getSiteId() { return siteId; }
|
||||
public String getGroupCodingNo() { return groupCodingNo; }
|
||||
public String getGroupCoding() { return groupCoding; }
|
||||
public String getFrontSiteId() { return frontSiteId; }
|
||||
}
|
||||
@@ -26,6 +26,7 @@ public final class HistoricalQueueDataset {
|
||||
private final int missingPathRows;
|
||||
private final int missingIntervalRows;
|
||||
private final int reversedIntervalRows;
|
||||
private final List<HistoricalChannelFrontRecord> channelFrontRecords;
|
||||
|
||||
/**
|
||||
* 创建 {@code HistoricalQueueDataset} 实例并初始化其内部状态。
|
||||
@@ -59,6 +60,27 @@ public final class HistoricalQueueDataset {
|
||||
String minFinishDate, String maxFinishDate,
|
||||
int missingPathRows, int missingIntervalRows,
|
||||
int reversedIntervalRows) {
|
||||
this(records, mapSiteDetails, fullMapCells, fullMapRows, fullMapSha256,
|
||||
manifestSampleRows, fullScanTotalRows, database, sourceTable, extractedAt,
|
||||
minCreateDate, maxCreateDate, minFinishDate, maxFinishDate,
|
||||
missingPathRows, missingIntervalRows, reversedIntervalRows,
|
||||
Collections.<HistoricalChannelFrontRecord>emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建带生产通道前端快照的数据集,使业务归并和地图数据来自同一份可审计离线输入。
|
||||
*/
|
||||
public HistoricalQueueDataset(List<HistoricalQueueRecord> records,
|
||||
Map<String, HistoricalMapSite> mapSiteDetails,
|
||||
List<HistoricalMapCell> fullMapCells,
|
||||
int fullMapRows, String fullMapSha256,
|
||||
int manifestSampleRows, int fullScanTotalRows,
|
||||
String database, String sourceTable, String extractedAt,
|
||||
String minCreateDate, String maxCreateDate,
|
||||
String minFinishDate, String maxFinishDate,
|
||||
int missingPathRows, int missingIntervalRows,
|
||||
int reversedIntervalRows,
|
||||
List<HistoricalChannelFrontRecord> channelFrontRecords) {
|
||||
this.records = Collections.unmodifiableList(new ArrayList<HistoricalQueueRecord>(records));
|
||||
this.mapSiteDetails = Collections.unmodifiableMap(
|
||||
new LinkedHashMap<String, HistoricalMapSite>(mapSiteDetails));
|
||||
@@ -78,6 +100,8 @@ public final class HistoricalQueueDataset {
|
||||
this.missingPathRows = missingPathRows;
|
||||
this.missingIntervalRows = missingIntervalRows;
|
||||
this.reversedIntervalRows = reversedIntervalRows;
|
||||
this.channelFrontRecords = Collections.unmodifiableList(
|
||||
new ArrayList<HistoricalChannelFrontRecord>(channelFrontRecords));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -209,4 +233,9 @@ public final class HistoricalQueueDataset {
|
||||
* @return 本次处理产生的结果;调用方据此继续后续流程。
|
||||
*/
|
||||
public int getReversedIntervalRows() { return reversedIntervalRows; }
|
||||
|
||||
/** 返回由生产货位分组查询固化的通道前端事实。 */
|
||||
public List<HistoricalChannelFrontRecord> getChannelFrontRecords() {
|
||||
return channelFrontRecords;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,17 +40,27 @@ public final class HistoricalQueueLoader {
|
||||
byte[] mapBytes = read(loader, basePath + "/map-sites.json");
|
||||
byte[] summaryBytes = read(loader, basePath + "/full-scan-summary.json");
|
||||
byte[] fullMapBytes = read(loader, basePath + "/full-map-grid.json");
|
||||
byte[] channelFrontBytes = read(loader, basePath + "/channel-fronts.json");
|
||||
verifySha256(manifest.path("sha256").path("queue-history-200.json").asText(), queueBytes);
|
||||
verifySha256(manifest.path("sha256").path("map-sites.json").asText(), mapBytes);
|
||||
verifySha256(manifest.path("sha256").path("full-scan-summary.json").asText(), summaryBytes);
|
||||
String fullMapSha256 = manifest.path("sha256").path("full-map-grid.json").asText();
|
||||
verifySha256(fullMapSha256, fullMapBytes);
|
||||
verifySha256(manifest.path("sha256").path("channel-fronts.json").asText(),
|
||||
channelFrontBytes);
|
||||
CollectionType rowsType = MAPPER.getTypeFactory()
|
||||
.constructCollectionType(List.class, HistoricalQueueRecord.class);
|
||||
List<HistoricalQueueRecord> rows = MAPPER.readValue(queueBytes, rowsType);
|
||||
CollectionType cellsType = MAPPER.getTypeFactory()
|
||||
.constructCollectionType(List.class, HistoricalMapCell.class);
|
||||
List<HistoricalMapCell> fullMapCells = MAPPER.readValue(fullMapBytes, cellsType);
|
||||
CollectionType channelFrontType = MAPPER.getTypeFactory()
|
||||
.constructCollectionType(List.class, HistoricalChannelFrontRecord.class);
|
||||
List<HistoricalChannelFrontRecord> channelFrontRecords =
|
||||
MAPPER.readValue(channelFrontBytes, channelFrontType);
|
||||
if (manifest.path("channelFrontRows").asInt() != channelFrontRecords.size()) {
|
||||
throw new IllegalArgumentException("channel front row count mismatch");
|
||||
}
|
||||
int fullMapRows = manifest.path("fullMapRows").asInt();
|
||||
if (fullMapRows != fullMapCells.size()) {
|
||||
throw new IllegalArgumentException("full map row count mismatch");
|
||||
@@ -92,7 +102,8 @@ public final class HistoricalQueueLoader {
|
||||
scanSummary.path("max_finish_date").asText(),
|
||||
scanSummary.path("missing_path_rows").asInt(),
|
||||
scanSummary.path("missing_interval_rows").asInt(),
|
||||
scanSummary.path("reversed_interval_rows").asInt());
|
||||
scanSummary.path("reversed_interval_rows").asInt(),
|
||||
channelFrontRecords);
|
||||
} catch (IOException ex) {
|
||||
throw new IllegalArgumentException("cannot load history snapshot", ex);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public final class HistoricalQueueRecord {
|
||||
@JsonProperty("snapshot_row") private String snapshotRow;
|
||||
@JsonProperty("guid") private String guid;
|
||||
@JsonProperty("task_no") private String taskNo;
|
||||
@JsonProperty("task_type") private String taskType;
|
||||
@@ -12,6 +13,8 @@ public final class HistoricalQueueRecord {
|
||||
@JsonProperty("cargo") private String cargo;
|
||||
@JsonProperty("issued") private String issued;
|
||||
@JsonProperty("execution_status") private String executionStatus;
|
||||
@JsonProperty("cargo_pick_site") private String cargoPickSite;
|
||||
@JsonProperty("cargo_place_site") private String cargoPlaceSite;
|
||||
@JsonProperty("start_site") private String startSite;
|
||||
@JsonProperty("end_site") private String endSite;
|
||||
@JsonProperty("the_floor") private String floor;
|
||||
@@ -20,6 +23,9 @@ public final class HistoricalQueueRecord {
|
||||
@JsonProperty("start_date") private String startDate;
|
||||
@JsonProperty("finish_date") private String finishDate;
|
||||
@JsonProperty("agv_id") private String agvId;
|
||||
@JsonProperty("default02") private String default02;
|
||||
@JsonProperty("default06") private String default06;
|
||||
@JsonProperty("default08") private String default08;
|
||||
|
||||
/**
|
||||
* 创建 {@code HistoricalQueueRecord} 实例并初始化其内部状态。
|
||||
@@ -58,6 +64,9 @@ public final class HistoricalQueueRecord {
|
||||
this.agvId = agvId;
|
||||
}
|
||||
|
||||
/** 返回本行在数据库扫描快照中的稳定行号,用于识别重复扫描而不是重新创建业务任务。 */
|
||||
public String getSnapshotRow() { return snapshotRow; }
|
||||
|
||||
/**
|
||||
* 返回当前对象保存的“guid”信息。
|
||||
*
|
||||
@@ -107,6 +116,10 @@ public final class HistoricalQueueRecord {
|
||||
* @return 本次处理产生的结果;调用方据此继续后续流程。
|
||||
*/
|
||||
public String getExecutionStatus() { return executionStatus; }
|
||||
/** 返回生产队列保存的业务取货点;二段任务以该点而不是队列当前 start_site 为业务源点。 */
|
||||
public String getCargoPickSite() { return cargoPickSite; }
|
||||
/** 返回生产队列保存的业务放货点;二段任务以该点而不是阶段 end_site 为业务目标。 */
|
||||
public String getCargoPlaceSite() { return cargoPlaceSite; }
|
||||
/**
|
||||
* 返回当前对象保存的“start site”信息。
|
||||
*
|
||||
@@ -163,4 +176,10 @@ public final class HistoricalQueueRecord {
|
||||
* @return 本次处理产生的结果;调用方据此继续后续流程。
|
||||
*/
|
||||
public String getAgvId() { return agvId; }
|
||||
/** 返回生产队列的业务/设备阶段编码,其中 1、2、7 分别代表入库、出库和移库。 */
|
||||
public String getDefault02() { return default02; }
|
||||
/** 返回生产多段任务保存的通道前端站点。 */
|
||||
public String getDefault06() { return default06; }
|
||||
/** 返回生产多段任务的首次/后续下发标记。 */
|
||||
public String getDefault08() { return default08; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.ft546.wcs.shadow.history.simulation;
|
||||
|
||||
import com.ft546.wcs.shadow.history.HistoricalChannelFrontRecord;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/** 根据生产货位分组快照提供权威的通道前端查询,不向普通地图增加虚构边。 */
|
||||
public final class ChannelFrontTopology {
|
||||
private final Map<String, String> frontBySite;
|
||||
|
||||
private ChannelFrontTopology(Map<String, String> frontBySite) {
|
||||
this.frontBySite = Collections.unmodifiableMap(
|
||||
new LinkedHashMap<String, String>(frontBySite));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 SQL 快照转换为站点到前端的唯一映射;同一站点出现冲突时拒绝启动仿真。
|
||||
*/
|
||||
public static ChannelFrontTopology from(List<HistoricalChannelFrontRecord> records) {
|
||||
if (records == null) throw new IllegalArgumentException("channel front records are required");
|
||||
Map<String, String> values = new LinkedHashMap<String, String>();
|
||||
for (HistoricalChannelFrontRecord record : records) {
|
||||
String site = required(record.getSiteId(), "site id");
|
||||
String front = required(record.getFrontSiteId(), "front site id");
|
||||
String previous = values.put(site, front);
|
||||
if (previous != null && !previous.equals(front)) {
|
||||
throw new IllegalArgumentException("conflicting channel front for " + site);
|
||||
}
|
||||
}
|
||||
return new ChannelFrontTopology(values);
|
||||
}
|
||||
|
||||
/** 返回货位所属通道的权威前端;非货位输送点返回空。 */
|
||||
public Optional<String> frontFor(String siteId) {
|
||||
return Optional.ofNullable(frontBySite.get(siteId));
|
||||
}
|
||||
|
||||
/** 判断业务货位与通道前端是否不同,从而是否需要生成显式前端移动阶段。 */
|
||||
public boolean requiresFrontMovement(String siteId) {
|
||||
Optional<String> front = frontFor(siteId);
|
||||
return front.isPresent() && !front.get().equals(siteId);
|
||||
}
|
||||
|
||||
public Map<String, String> asMap() { return frontBySite; }
|
||||
|
||||
private static String required(String value, String name) {
|
||||
if (value == null || value.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException(name + " is required");
|
||||
}
|
||||
return value.trim();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user