package com.zy.ai.service.impl;
|
|
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson.JSONObject;
|
import com.zy.ai.domain.autotune.AutoTuneHotPathSegmentItem;
|
import com.zy.ai.domain.autotune.AutoTuneRoutePressureSnapshot;
|
import com.zy.ai.domain.autotune.AutoTuneRoutePressureRuleSnapshot;
|
import com.zy.ai.domain.autotune.AutoTuneStationRuntimeItem;
|
import com.zy.ai.domain.autotune.AutoTuneTargetStationRoutePressureItem;
|
import com.zy.ai.domain.autotune.AutoTuneTaskDetailItem;
|
import com.zy.ai.domain.autotune.AutoTuneTaskRouteSampleItem;
|
import com.zy.ai.domain.autotune.AutoTuneTaskSnapshot;
|
import com.zy.ai.service.RoutePressureSnapshotService;
|
import com.zy.asrs.domain.vo.StationTaskTraceVo;
|
import com.zy.asrs.entity.WrkMast;
|
import com.zy.common.model.NavigateNode;
|
import com.zy.common.utils.NavigateUtils;
|
import com.zy.core.enums.WrkIoType;
|
import com.zy.core.enums.WrkStsType;
|
import com.zy.core.trace.StationTaskTraceRegistry;
|
import com.zy.core.utils.station.StationOutboundDecisionSupport;
|
import com.zy.system.service.ConfigService;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.stereotype.Service;
|
|
import java.util.ArrayList;
|
import java.util.Collections;
|
import java.util.Comparator;
|
import java.util.HashSet;
|
import java.util.LinkedHashMap;
|
import java.util.LinkedHashSet;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.Objects;
|
import java.util.Set;
|
|
@Service("routePressureSnapshotService")
|
public class RoutePressureSnapshotServiceImpl implements RoutePressureSnapshotService {
|
|
private static final int MAX_ANALYZED_TASK_COUNT = 50;
|
private static final int DEFAULT_SEGMENT_WINDOW_SIZE = 4;
|
private static final int DEFAULT_MEDIUM_PERCENT = 50;
|
private static final int DEFAULT_HIGH_PERCENT = 75;
|
private static final int DEFAULT_PASS_WEIGHT_PERCENT = 35;
|
private static final int DEFAULT_OCCUPIED_WEIGHT_PERCENT = 25;
|
private static final int DEFAULT_BLOCKED_WEIGHT_PERCENT = 20;
|
private static final int DEFAULT_NON_AUTOING_WEIGHT_PERCENT = 10;
|
private static final int DEFAULT_RUN_BLOCK_WEIGHT_PERCENT = 10;
|
private static final int MAX_HOT_PATH_SEGMENT_COUNT = 10;
|
private static final int MAX_TARGET_MAIN_SEGMENT_COUNT = 3;
|
private static final String CONFIG_SEGMENT_WINDOW_SIZE = "aiAutoTuneRoutePressureSegmentWindowSize";
|
private static final String CONFIG_MEDIUM_PERCENT = "aiAutoTuneRoutePressureMediumPercent";
|
private static final String CONFIG_HIGH_PERCENT = "aiAutoTuneRoutePressureHighPercent";
|
private static final String CONFIG_PASS_WEIGHT_PERCENT = "aiAutoTuneRoutePressurePassWeightPercent";
|
private static final String CONFIG_OCCUPIED_WEIGHT_PERCENT = "aiAutoTuneRoutePressureOccupiedWeightPercent";
|
private static final String CONFIG_BLOCKED_WEIGHT_PERCENT = "aiAutoTuneRoutePressureBlockedWeightPercent";
|
private static final String CONFIG_NON_AUTOING_WEIGHT_PERCENT = "aiAutoTuneRoutePressureNonAutoingWeightPercent";
|
private static final String CONFIG_RUN_BLOCK_WEIGHT_PERCENT = "aiAutoTuneRoutePressureRunBlockWeightPercent";
|
private static final String PATH_SOURCE_TRACE_PENDING = "trace_pending";
|
private static final String PATH_SOURCE_TRACE_ISSUED = "trace_issued";
|
private static final String PATH_SOURCE_ESTIMATED = "estimated";
|
private static final String PATH_SOURCE_MISSING = "missing";
|
private static final String PRESSURE_HIGH = "high";
|
private static final String PRESSURE_MEDIUM = "medium";
|
private static final String PRESSURE_LOW = "low";
|
private static final String DIRECTION_INCREASE_CANDIDATE = "increase_candidate";
|
private static final String DIRECTION_OBSERVE = "observe";
|
private static final String DIRECTION_DECREASE_CANDIDATE = "decrease_candidate";
|
private static final String CONFIDENCE_HIGH = "high";
|
private static final String CONFIDENCE_MEDIUM = "medium";
|
private static final String CONFIDENCE_LOW = "low";
|
|
@Autowired
|
private StationTaskTraceRegistry stationTaskTraceRegistry;
|
|
@Autowired
|
private NavigateUtils navigateUtils;
|
|
@Autowired
|
private StationOutboundDecisionSupport stationOutboundDecisionSupport;
|
|
@Autowired
|
private ConfigService configService;
|
|
@Override
|
public AutoTuneRoutePressureSnapshot buildSnapshot(List<WrkMast> activeTasks,
|
AutoTuneTaskSnapshot taskSnapshot,
|
List<AutoTuneStationRuntimeItem> stationRuntimeSnapshot) {
|
AutoTuneRoutePressureSnapshot snapshot = new AutoTuneRoutePressureSnapshot();
|
AutoTuneRoutePressureRuleSnapshot ruleSnapshot = buildRoutePressureRuleSnapshot();
|
List<WrkMast> selectedTasks = selectTasksForAnalysis(activeTasks, taskSnapshot);
|
Map<Integer, StationTaskTraceVo> traceMap = buildTraceMap();
|
Map<String, EstimatedPathResult> estimatedPathCache = new LinkedHashMap<>();
|
List<AutoTuneTaskRouteSampleItem> routeSamples = new ArrayList<>();
|
|
for (WrkMast task : selectedTasks) {
|
AutoTuneTaskRouteSampleItem routeSample = buildRouteSample(task, traceMap, estimatedPathCache);
|
routeSamples.add(routeSample);
|
}
|
|
snapshot.setAnalyzedTaskCount(selectedTasks.size());
|
snapshot.setTracePathCount(countByPathSource(routeSamples, PATH_SOURCE_TRACE_PENDING)
|
+ countByPathSource(routeSamples, PATH_SOURCE_TRACE_ISSUED));
|
snapshot.setEstimatedPathCount(countByPathSource(routeSamples, PATH_SOURCE_ESTIMATED));
|
snapshot.setPathErrorCount(countByPathSource(routeSamples, PATH_SOURCE_MISSING));
|
snapshot.setConfidence(snapshotConfidence(snapshot.getAnalyzedTaskCount(), snapshot.getPathErrorCount()));
|
snapshot.setRoutePressureRuleSnapshot(ruleSnapshot);
|
snapshot.setTaskRouteSamples(routeSamples);
|
Map<Integer, AutoTuneStationRuntimeItem> runtimeMap = buildRuntimeMap(stationRuntimeSnapshot);
|
List<AutoTuneHotPathSegmentItem> allHotPathSegments = buildHotPathSegments(
|
routeSamples,
|
runtimeMap,
|
ruleSnapshot
|
);
|
snapshot.setHotPathSegments(topHotPathSegments(allHotPathSegments));
|
snapshot.setTargetStationRoutePressure(buildTargetStationRoutePressure(
|
routeSamples,
|
allHotPathSegments,
|
taskSnapshot,
|
ruleSnapshot
|
));
|
return snapshot;
|
}
|
|
private AutoTuneRoutePressureRuleSnapshot buildRoutePressureRuleSnapshot() {
|
AutoTuneRoutePressureRuleSnapshot snapshot = new AutoTuneRoutePressureRuleSnapshot();
|
snapshot.setSegmentWindowSize(readIntConfig(
|
CONFIG_SEGMENT_WINDOW_SIZE,
|
DEFAULT_SEGMENT_WINDOW_SIZE,
|
2,
|
20
|
));
|
snapshot.setMediumPercent(readIntConfig(CONFIG_MEDIUM_PERCENT, DEFAULT_MEDIUM_PERCENT, 1, 100));
|
snapshot.setHighPercent(readIntConfig(CONFIG_HIGH_PERCENT, DEFAULT_HIGH_PERCENT, 1, 100));
|
if (snapshot.getMediumPercent() > snapshot.getHighPercent()) {
|
snapshot.setMediumPercent(DEFAULT_MEDIUM_PERCENT);
|
snapshot.setHighPercent(DEFAULT_HIGH_PERCENT);
|
}
|
snapshot.setPassWeightPercent(readIntConfig(CONFIG_PASS_WEIGHT_PERCENT, DEFAULT_PASS_WEIGHT_PERCENT, 0, 100));
|
snapshot.setOccupiedWeightPercent(readIntConfig(
|
CONFIG_OCCUPIED_WEIGHT_PERCENT,
|
DEFAULT_OCCUPIED_WEIGHT_PERCENT,
|
0,
|
100
|
));
|
snapshot.setBlockedWeightPercent(readIntConfig(
|
CONFIG_BLOCKED_WEIGHT_PERCENT,
|
DEFAULT_BLOCKED_WEIGHT_PERCENT,
|
0,
|
100
|
));
|
snapshot.setNonAutoingWeightPercent(readIntConfig(
|
CONFIG_NON_AUTOING_WEIGHT_PERCENT,
|
DEFAULT_NON_AUTOING_WEIGHT_PERCENT,
|
0,
|
100
|
));
|
snapshot.setRunBlockWeightPercent(readIntConfig(
|
CONFIG_RUN_BLOCK_WEIGHT_PERCENT,
|
DEFAULT_RUN_BLOCK_WEIGHT_PERCENT,
|
0,
|
100
|
));
|
return snapshot;
|
}
|
|
private int readIntConfig(String code, int defaultValue, int minValue, int maxValue) {
|
if (configService == null) {
|
return defaultValue;
|
}
|
String value = configService.getConfigValue(code, String.valueOf(defaultValue));
|
try {
|
if (value == null || value.trim().isEmpty()) {
|
return defaultValue;
|
}
|
int parsedValue = Integer.parseInt(value.trim());
|
if (parsedValue < minValue || parsedValue > maxValue) {
|
return defaultValue;
|
}
|
return parsedValue;
|
} catch (Exception e) {
|
return defaultValue;
|
}
|
}
|
|
private List<WrkMast> selectTasksForAnalysis(List<WrkMast> activeTasks,
|
AutoTuneTaskSnapshot taskSnapshot) {
|
List<WrkMast> sortedOutboundTasks = sortedOutboundTasks(activeTasks);
|
Set<Integer> blockedWrkNos = collectBlockedWrkNos(taskSnapshot);
|
LinkedHashMap<Integer, WrkMast> selected = new LinkedHashMap<>();
|
|
for (WrkMast task : sortedOutboundTasks) {
|
Integer wrkNo = task.getWrkNo();
|
if (wrkNo != null && blockedWrkNos.contains(wrkNo)) {
|
selected.put(wrkNo, task);
|
}
|
if (selected.size() >= MAX_ANALYZED_TASK_COUNT) {
|
return new ArrayList<>(selected.values());
|
}
|
}
|
|
for (WrkMast task : sortedOutboundTasks) {
|
Integer wrkNo = task.getWrkNo();
|
if (wrkNo != null) {
|
selected.putIfAbsent(wrkNo, task);
|
}
|
if (selected.size() >= MAX_ANALYZED_TASK_COUNT) {
|
break;
|
}
|
}
|
return new ArrayList<>(selected.values());
|
}
|
|
private Set<Integer> collectBlockedWrkNos(AutoTuneTaskSnapshot taskSnapshot) {
|
Set<Integer> blockedWrkNos = new HashSet<>();
|
if (taskSnapshot == null || taskSnapshot.getStationLimitBlockedTasks() == null) {
|
return blockedWrkNos;
|
}
|
for (AutoTuneTaskDetailItem blockedTask : taskSnapshot.getStationLimitBlockedTasks()) {
|
if (blockedTask != null && blockedTask.getWrkNo() != null) {
|
blockedWrkNos.add(blockedTask.getWrkNo());
|
}
|
}
|
return blockedWrkNos;
|
}
|
|
private List<WrkMast> sortedOutboundTasks(List<WrkMast> activeTasks) {
|
List<WrkMast> sortedTasks = new ArrayList<>();
|
if (activeTasks == null) {
|
return sortedTasks;
|
}
|
for (WrkMast task : activeTasks) {
|
if (task != null && Objects.equals(task.getIoType(), WrkIoType.OUT.id)) {
|
sortedTasks.add(task);
|
}
|
}
|
sortedTasks.sort(Comparator
|
.comparing(WrkMast::getBatch, Comparator.nullsLast(String::compareTo))
|
.thenComparing(WrkMast::getBatchSeq, Comparator.nullsLast(Integer::compareTo))
|
.thenComparing(WrkMast::getWrkNo, Comparator.nullsLast(Integer::compareTo)));
|
return sortedTasks;
|
}
|
|
private Map<Integer, StationTaskTraceVo> buildTraceMap() {
|
Map<Integer, StationTaskTraceVo> traceMap = new LinkedHashMap<>();
|
if (stationTaskTraceRegistry == null) {
|
return traceMap;
|
}
|
List<StationTaskTraceVo> traceSnapshots;
|
try {
|
traceSnapshots = stationTaskTraceRegistry.listPlanningActiveTraceSnapshots();
|
} catch (Exception e) {
|
return traceMap;
|
}
|
if (traceSnapshots == null) {
|
return traceMap;
|
}
|
for (StationTaskTraceVo traceSnapshot : traceSnapshots) {
|
if (traceSnapshot != null && traceSnapshot.getTaskNo() != null) {
|
traceMap.putIfAbsent(traceSnapshot.getTaskNo(), traceSnapshot);
|
}
|
}
|
return traceMap;
|
}
|
|
private AutoTuneTaskRouteSampleItem buildRouteSample(WrkMast task,
|
Map<Integer, StationTaskTraceVo> traceMap,
|
Map<String, EstimatedPathResult> estimatedPathCache) {
|
AutoTuneTaskRouteSampleItem item = baseRouteSample(task);
|
if (!isRunningStationTask(task)) {
|
return buildEstimatedRouteSample(item, task, estimatedPathCache);
|
}
|
|
StationTaskTraceVo trace = task == null || task.getWrkNo() == null ? null : traceMap.get(task.getWrkNo());
|
if (trace == null) {
|
return completeRouteSample(item, PATH_SOURCE_MISSING, Collections.emptyList(),
|
"missing station trace for running station task");
|
}
|
|
List<Integer> remainingPath = traceRemainingPath(trace);
|
if (!remainingPath.isEmpty()) {
|
return completeRouteSample(item, PATH_SOURCE_TRACE_PENDING, remainingPath, null);
|
}
|
|
List<Integer> issuedPath = firstNonEmptyDistinct(
|
trace == null ? null : trace.getLatestIssuedSegmentPath(),
|
trace == null ? null : trace.getIssuedStationIds()
|
);
|
if (!issuedPath.isEmpty()) {
|
return completeRouteSample(item, PATH_SOURCE_TRACE_ISSUED, issuedPath, null);
|
}
|
|
return completeRouteSample(item, PATH_SOURCE_MISSING, Collections.emptyList(),
|
"missing usable station trace path for running station task");
|
}
|
|
private List<Integer> traceRemainingPath(StationTaskTraceVo trace) {
|
if (trace == null) {
|
return Collections.emptyList();
|
}
|
List<Integer> pendingPath = distinctPositive(trace.getPendingStationIds());
|
Integer currentStationId = trace.getCurrentStationId();
|
if (currentStationId == null || currentStationId <= 0) {
|
return pendingPath;
|
}
|
|
List<Integer> remainingPath = new ArrayList<>();
|
remainingPath.add(currentStationId);
|
Integer previousStationId = currentStationId;
|
for (Integer stationId : pendingPath) {
|
if (Objects.equals(stationId, previousStationId)) {
|
continue;
|
}
|
remainingPath.add(stationId);
|
previousStationId = stationId;
|
}
|
return remainingPath;
|
}
|
|
private boolean isRunningStationTask(WrkMast task) {
|
return task != null && Objects.equals(task.getWrkSts(), WrkStsType.STATION_RUN.sts);
|
}
|
|
private AutoTuneTaskRouteSampleItem baseRouteSample(WrkMast task) {
|
AutoTuneTaskRouteSampleItem item = new AutoTuneTaskRouteSampleItem();
|
if (task == null) {
|
return item;
|
}
|
item.setWrkNo(task.getWrkNo());
|
item.setWrkSts(task.getWrkSts());
|
item.setBatch(task.getBatch());
|
item.setBatchSeq(task.getBatchSeq());
|
item.setSourceStaNo(task.getSourceStaNo());
|
item.setTargetStaNo(task.getStaNo());
|
return item;
|
}
|
|
private AutoTuneTaskRouteSampleItem buildEstimatedRouteSample(AutoTuneTaskRouteSampleItem item,
|
WrkMast task,
|
Map<String, EstimatedPathResult> cache) {
|
if (task == null || task.getSourceStaNo() == null || task.getStaNo() == null) {
|
return completeRouteSample(item, PATH_SOURCE_MISSING, Collections.emptyList(),
|
"missing sourceStaNo or staNo");
|
}
|
|
Double pathLenFactor = resolvePathLenFactor(task);
|
String cacheKey = buildEstimatedPathCacheKey(task, pathLenFactor);
|
EstimatedPathResult pathResult = cache.get(cacheKey);
|
if (pathResult == null) {
|
pathResult = estimatePath(task, pathLenFactor);
|
cache.put(cacheKey, pathResult);
|
}
|
if (pathResult.pathStationIds.isEmpty()) {
|
return completeRouteSample(item, PATH_SOURCE_MISSING, Collections.emptyList(), pathResult.pathError);
|
}
|
return completeRouteSample(item, PATH_SOURCE_ESTIMATED, pathResult.pathStationIds, null);
|
}
|
|
private Double resolvePathLenFactor(WrkMast task) {
|
if (stationOutboundDecisionSupport == null) {
|
return 0.0d;
|
}
|
try {
|
Double pathLenFactor = stationOutboundDecisionSupport.resolveOutboundPathLenFactor(task);
|
return pathLenFactor == null ? 0.0d : pathLenFactor;
|
} catch (Exception e) {
|
return 0.0d;
|
}
|
}
|
|
private String buildEstimatedPathCacheKey(WrkMast task, Double pathLenFactor) {
|
return task.getWrkNo() + ":" + task.getSourceStaNo() + "->" + task.getStaNo() + ":" + pathLenFactor;
|
}
|
|
private EstimatedPathResult estimatePath(WrkMast task, Double pathLenFactor) {
|
List<NavigateNode> navigateNodes;
|
try {
|
navigateNodes = navigateUtils.calcOptimalPathByStationId(
|
task.getSourceStaNo(),
|
task.getStaNo(),
|
task.getWrkNo(),
|
pathLenFactor
|
);
|
} catch (Exception e) {
|
return EstimatedPathResult.error("path estimate exception: " + e.getClass().getSimpleName());
|
}
|
List<Integer> pathStationIds = stationIdsFromNavigateNodes(navigateNodes);
|
if (pathStationIds.isEmpty()) {
|
return EstimatedPathResult.error("path estimate failed");
|
}
|
return EstimatedPathResult.success(pathStationIds);
|
}
|
|
private List<Integer> stationIdsFromNavigateNodes(List<NavigateNode> navigateNodes) {
|
List<Integer> stationIds = new ArrayList<>();
|
if (navigateNodes == null) {
|
return stationIds;
|
}
|
for (NavigateNode navigateNode : navigateNodes) {
|
Integer stationId = stationIdFromNavigateNode(navigateNode);
|
if (stationId != null && stationId > 0) {
|
stationIds.add(stationId);
|
}
|
}
|
return stationIds;
|
}
|
|
private Integer stationIdFromNavigateNode(NavigateNode navigateNode) {
|
if (navigateNode == null) {
|
return null;
|
}
|
if (navigateNode.getStationId() != null) {
|
return navigateNode.getStationId();
|
}
|
String nodeValue = navigateNode.getNodeValue();
|
if (nodeValue == null || nodeValue.trim().isEmpty()) {
|
return null;
|
}
|
try {
|
JSONObject value = JSON.parseObject(nodeValue);
|
return value == null ? null : value.getInteger("stationId");
|
} catch (Exception e) {
|
return null;
|
}
|
}
|
|
@SafeVarargs
|
private final List<Integer> firstNonEmptyDistinct(List<Integer>... pathCandidates) {
|
if (pathCandidates == null) {
|
return Collections.emptyList();
|
}
|
for (List<Integer> pathCandidate : pathCandidates) {
|
List<Integer> pathStationIds = distinctPositive(pathCandidate);
|
if (!pathStationIds.isEmpty()) {
|
return pathStationIds;
|
}
|
}
|
return Collections.emptyList();
|
}
|
|
private List<Integer> distinctPositive(List<Integer> source) {
|
List<Integer> result = new ArrayList<>();
|
if (source == null) {
|
return result;
|
}
|
Integer previousStationId = null;
|
for (Integer stationId : source) {
|
if (stationId == null || stationId <= 0 || Objects.equals(stationId, previousStationId)) {
|
continue;
|
}
|
result.add(stationId);
|
previousStationId = stationId;
|
}
|
return result;
|
}
|
|
private AutoTuneTaskRouteSampleItem completeRouteSample(AutoTuneTaskRouteSampleItem item,
|
String pathSource,
|
List<Integer> pathStationIds,
|
String pathError) {
|
List<Integer> safePathStationIds = pathStationIds == null ? Collections.emptyList() : new ArrayList<>(pathStationIds);
|
item.setPathSource(pathSource);
|
item.setPathStationIds(safePathStationIds);
|
item.setPathLength(safePathStationIds.size());
|
item.setPathError(pathError);
|
return item;
|
}
|
|
private int countByPathSource(List<AutoTuneTaskRouteSampleItem> routeSamples, String pathSource) {
|
int count = 0;
|
for (AutoTuneTaskRouteSampleItem routeSample : routeSamples) {
|
if (routeSample != null && pathSource.equals(routeSample.getPathSource())) {
|
count++;
|
}
|
}
|
return count;
|
}
|
|
private Map<Integer, AutoTuneStationRuntimeItem> buildRuntimeMap(List<AutoTuneStationRuntimeItem> stationRuntimeSnapshot) {
|
Map<Integer, AutoTuneStationRuntimeItem> runtimeMap = new LinkedHashMap<>();
|
if (stationRuntimeSnapshot == null) {
|
return runtimeMap;
|
}
|
for (AutoTuneStationRuntimeItem runtimeItem : stationRuntimeSnapshot) {
|
if (runtimeItem != null && runtimeItem.getStationId() != null) {
|
runtimeMap.putIfAbsent(runtimeItem.getStationId(), runtimeItem);
|
}
|
}
|
return runtimeMap;
|
}
|
|
private List<AutoTuneHotPathSegmentItem> buildHotPathSegments(List<AutoTuneTaskRouteSampleItem> routeSamples,
|
Map<Integer, AutoTuneStationRuntimeItem> runtimeMap,
|
AutoTuneRoutePressureRuleSnapshot ruleSnapshot) {
|
LinkedHashMap<String, SegmentAggregate> aggregateMap = new LinkedHashMap<>();
|
if (routeSamples == null) {
|
return Collections.emptyList();
|
}
|
int validRouteSampleCount = 0;
|
for (AutoTuneTaskRouteSampleItem routeSample : routeSamples) {
|
if (!hasValidPath(routeSample)) {
|
continue;
|
}
|
validRouteSampleCount++;
|
Set<String> sampleSegmentKeys = new HashSet<>();
|
List<List<Integer>> routeSegments = splitRouteSegments(routeSample.getPathStationIds(), ruleSnapshot);
|
for (List<Integer> routeSegment : routeSegments) {
|
String segmentKey = buildSegmentKey(routeSegment);
|
if (segmentKey == null || !sampleSegmentKeys.add(segmentKey)) {
|
continue;
|
}
|
SegmentAggregate aggregate = aggregateMap.computeIfAbsent(
|
segmentKey,
|
key -> new SegmentAggregate(key, routeSegment)
|
);
|
aggregate.addRouteSample(routeSample);
|
}
|
}
|
List<AutoTuneHotPathSegmentItem> hotPathSegments = new ArrayList<>();
|
for (SegmentAggregate aggregate : aggregateMap.values()) {
|
hotPathSegments.add(aggregate.toItem(runtimeMap, ruleSnapshot, validRouteSampleCount));
|
}
|
hotPathSegments.sort(this::compareHotPathSegment);
|
return hotPathSegments;
|
}
|
|
private boolean hasValidPath(AutoTuneTaskRouteSampleItem routeSample) {
|
return routeSample != null
|
&& routeSample.getTargetStaNo() != null
|
&& routeSample.getPathStationIds() != null
|
&& routeSample.getPathStationIds().size() >= 2
|
&& !PATH_SOURCE_MISSING.equals(routeSample.getPathSource());
|
}
|
|
private List<List<Integer>> splitRouteSegments(List<Integer> pathStationIds,
|
AutoTuneRoutePressureRuleSnapshot ruleSnapshot) {
|
if (pathStationIds == null || pathStationIds.isEmpty()) {
|
return Collections.emptyList();
|
}
|
List<Integer> safePathStationIds = new ArrayList<>(pathStationIds);
|
int segmentWindowSize = ruleWindowSize(ruleSnapshot);
|
if (safePathStationIds.size() <= segmentWindowSize) {
|
return Collections.singletonList(safePathStationIds);
|
}
|
List<List<Integer>> routeSegments = new ArrayList<>();
|
for (int startIndex = 0; startIndex <= safePathStationIds.size() - segmentWindowSize; startIndex++) {
|
int endIndex = startIndex + segmentWindowSize;
|
routeSegments.add(new ArrayList<>(safePathStationIds.subList(startIndex, endIndex)));
|
}
|
return routeSegments;
|
}
|
|
private int ruleWindowSize(AutoTuneRoutePressureRuleSnapshot ruleSnapshot) {
|
Integer segmentWindowSize = ruleSnapshot == null ? null : ruleSnapshot.getSegmentWindowSize();
|
return segmentWindowSize == null || segmentWindowSize < 2 ? DEFAULT_SEGMENT_WINDOW_SIZE : segmentWindowSize;
|
}
|
|
private String buildSegmentKey(List<Integer> stationIds) {
|
if (stationIds == null || stationIds.isEmpty()) {
|
return null;
|
}
|
StringBuilder segmentKey = new StringBuilder();
|
for (Integer stationId : stationIds) {
|
if (stationId == null) {
|
return null;
|
}
|
if (segmentKey.length() > 0) {
|
segmentKey.append("-");
|
}
|
segmentKey.append(stationId);
|
}
|
return segmentKey.toString();
|
}
|
|
private int compareHotPathSegment(AutoTuneHotPathSegmentItem left, AutoTuneHotPathSegmentItem right) {
|
int pressureCompare = Integer.compare(
|
pressureRank(right.getPressureLevel()),
|
pressureRank(left.getPressureLevel())
|
);
|
if (pressureCompare != 0) {
|
return pressureCompare;
|
}
|
int scoreCompare = Integer.compare(nullSafe(right.getPressureScore()), nullSafe(left.getPressureScore()));
|
if (scoreCompare != 0) {
|
return scoreCompare;
|
}
|
int passCompare = Integer.compare(nullSafe(right.getPassTaskCount()), nullSafe(left.getPassTaskCount()));
|
if (passCompare != 0) {
|
return passCompare;
|
}
|
int runBlockCompare = Integer.compare(nullSafe(right.getRunBlockCount()), nullSafe(left.getRunBlockCount()));
|
if (runBlockCompare != 0) {
|
return runBlockCompare;
|
}
|
int taskHoldingCompare = Integer.compare(
|
nullSafe(right.getTaskHoldingCount()),
|
nullSafe(left.getTaskHoldingCount())
|
);
|
if (taskHoldingCompare != 0) {
|
return taskHoldingCompare;
|
}
|
int loadingCompare = Integer.compare(nullSafe(right.getLoadingCount()), nullSafe(left.getLoadingCount()));
|
if (loadingCompare != 0) {
|
return loadingCompare;
|
}
|
return nullSafeString(left.getSegmentKey()).compareTo(nullSafeString(right.getSegmentKey()));
|
}
|
|
private int pressureRank(String pressureLevel) {
|
if (PRESSURE_HIGH.equals(pressureLevel)) {
|
return 3;
|
}
|
if (PRESSURE_MEDIUM.equals(pressureLevel)) {
|
return 2;
|
}
|
return 1;
|
}
|
|
private int nullSafe(Integer value) {
|
return value == null ? 0 : value;
|
}
|
|
private String nullSafeString(String value) {
|
return value == null ? "" : value;
|
}
|
|
private List<AutoTuneHotPathSegmentItem> topHotPathSegments(List<AutoTuneHotPathSegmentItem> allHotPathSegments) {
|
if (allHotPathSegments == null || allHotPathSegments.isEmpty()) {
|
return Collections.emptyList();
|
}
|
int toIndex = Math.min(MAX_HOT_PATH_SEGMENT_COUNT, allHotPathSegments.size());
|
return new ArrayList<>(allHotPathSegments.subList(0, toIndex));
|
}
|
|
private List<AutoTuneTargetStationRoutePressureItem> buildTargetStationRoutePressure(
|
List<AutoTuneTaskRouteSampleItem> routeSamples,
|
List<AutoTuneHotPathSegmentItem> allHotPathSegments,
|
AutoTuneTaskSnapshot taskSnapshot,
|
AutoTuneRoutePressureRuleSnapshot ruleSnapshot) {
|
LinkedHashMap<Integer, TargetPressureAggregate> targetAggregateMap = new LinkedHashMap<>();
|
Set<Integer> blockedWrkNos = collectBlockedWrkNos(taskSnapshot);
|
|
if (routeSamples != null) {
|
for (AutoTuneTaskRouteSampleItem routeSample : routeSamples) {
|
if (!hasValidPath(routeSample)) {
|
continue;
|
}
|
Integer targetStaNo = routeSample.getTargetStaNo();
|
TargetPressureAggregate aggregate = targetAggregateMap.computeIfAbsent(
|
targetStaNo,
|
TargetPressureAggregate::new
|
);
|
aggregate.routeTaskCount++;
|
if (routeSample.getWrkNo() != null && blockedWrkNos.contains(routeSample.getWrkNo())) {
|
aggregate.blockedTaskCount++;
|
}
|
}
|
}
|
|
if (allHotPathSegments != null) {
|
for (AutoTuneHotPathSegmentItem hotPathSegment : allHotPathSegments) {
|
if (hotPathSegment == null || hotPathSegment.getRelatedTargetStations() == null) {
|
continue;
|
}
|
for (Integer targetStationId : hotPathSegment.getRelatedTargetStations()) {
|
TargetPressureAggregate aggregate = targetAggregateMap.get(targetStationId);
|
if (aggregate != null) {
|
aggregate.relatedHotSegments.add(hotPathSegment);
|
}
|
}
|
}
|
}
|
|
List<AutoTuneTargetStationRoutePressureItem> targetPressureItems = new ArrayList<>();
|
for (TargetPressureAggregate aggregate : targetAggregateMap.values()) {
|
targetPressureItems.add(aggregate.toItem(ruleSnapshot));
|
}
|
targetPressureItems.sort(Comparator.comparing(
|
AutoTuneTargetStationRoutePressureItem::getTargetStationId,
|
Comparator.nullsLast(Integer::compareTo)
|
));
|
return targetPressureItems;
|
}
|
|
private int calculateSegmentPressureScore(int passTaskCount,
|
int taskHoldingCount,
|
int loadingCount,
|
int runBlockCount,
|
int nonAutoingCount,
|
int segmentStationCount,
|
int validRouteSampleCount,
|
AutoTuneRoutePressureRuleSnapshot ruleSnapshot) {
|
double passRatio = ratio(passTaskCount, validRouteSampleCount);
|
double occupiedRatio = ratio(taskHoldingCount + loadingCount, segmentStationCount);
|
double nonAutoingRatio = ratio(nonAutoingCount, segmentStationCount);
|
double runBlockRatio = ratio(runBlockCount, segmentStationCount);
|
double score = passRatio * nullSafe(ruleSnapshot.getPassWeightPercent())
|
+ occupiedRatio * nullSafe(ruleSnapshot.getOccupiedWeightPercent())
|
+ nonAutoingRatio * nullSafe(ruleSnapshot.getNonAutoingWeightPercent())
|
+ runBlockRatio * nullSafe(ruleSnapshot.getRunBlockWeightPercent());
|
return clampPercent((int) Math.round(score));
|
}
|
|
private String calculatePressureLevel(int pressureScore, AutoTuneRoutePressureRuleSnapshot ruleSnapshot) {
|
if (pressureScore >= nullSafe(ruleSnapshot.getHighPercent())) {
|
return PRESSURE_HIGH;
|
}
|
if (pressureScore >= nullSafe(ruleSnapshot.getMediumPercent())) {
|
return PRESSURE_MEDIUM;
|
}
|
return PRESSURE_LOW;
|
}
|
|
private Map<String, Object> segmentPressureFactors(int passTaskCount,
|
int taskHoldingCount,
|
int loadingCount,
|
int runBlockCount,
|
int nonAutoingCount,
|
int segmentStationCount,
|
int validRouteSampleCount) {
|
Map<String, Object> factors = new LinkedHashMap<>();
|
factors.put("passRatio", percentValue(passTaskCount, validRouteSampleCount));
|
factors.put("occupiedRatio", percentValue(taskHoldingCount + loadingCount, segmentStationCount));
|
factors.put("nonAutoingRatio", percentValue(nonAutoingCount, segmentStationCount));
|
factors.put("runBlockRatio", percentValue(runBlockCount, segmentStationCount));
|
return factors;
|
}
|
|
private double ratio(int numerator, int denominator) {
|
if (numerator <= 0 || denominator <= 0) {
|
return 0.0d;
|
}
|
return Math.min(1.0d, (double) numerator / (double) denominator);
|
}
|
|
private Integer percentValue(int numerator, int denominator) {
|
return clampPercent((int) Math.round(ratio(numerator, denominator) * 100.0d));
|
}
|
|
private int clampPercent(int value) {
|
if (value < 0) {
|
return 0;
|
}
|
return Math.min(value, 100);
|
}
|
|
private String snapshotConfidence(int analyzedTaskCount, int pathErrorCount) {
|
if (analyzedTaskCount >= 10 && pathErrorCount == 0) {
|
return CONFIDENCE_HIGH;
|
}
|
if (analyzedTaskCount >= 3 && pathErrorCount < analyzedTaskCount) {
|
return CONFIDENCE_MEDIUM;
|
}
|
return CONFIDENCE_LOW;
|
}
|
|
private String targetConfidence(int routeTaskCount) {
|
if (routeTaskCount >= 10) {
|
return CONFIDENCE_HIGH;
|
}
|
if (routeTaskCount >= 3) {
|
return CONFIDENCE_MEDIUM;
|
}
|
return CONFIDENCE_LOW;
|
}
|
|
private static class EstimatedPathResult {
|
private final List<Integer> pathStationIds;
|
private final String pathError;
|
|
private EstimatedPathResult(List<Integer> pathStationIds, String pathError) {
|
this.pathStationIds = pathStationIds;
|
this.pathError = pathError;
|
}
|
|
private static EstimatedPathResult success(List<Integer> pathStationIds) {
|
return new EstimatedPathResult(new ArrayList<>(pathStationIds), null);
|
}
|
|
private static EstimatedPathResult error(String pathError) {
|
return new EstimatedPathResult(Collections.emptyList(), pathError);
|
}
|
}
|
|
private class SegmentAggregate {
|
private final String segmentKey;
|
private final List<Integer> stationIds;
|
private final Set<Integer> relatedTargetStations = new LinkedHashSet<>();
|
private final Set<Integer> sampleWrkNos = new LinkedHashSet<>();
|
private int passTaskCount;
|
|
private SegmentAggregate(String segmentKey, List<Integer> stationIds) {
|
this.segmentKey = segmentKey;
|
this.stationIds = new ArrayList<>(stationIds);
|
}
|
|
private void addRouteSample(AutoTuneTaskRouteSampleItem routeSample) {
|
passTaskCount++;
|
if (routeSample.getTargetStaNo() != null) {
|
relatedTargetStations.add(routeSample.getTargetStaNo());
|
}
|
if (routeSample.getWrkNo() != null) {
|
sampleWrkNos.add(routeSample.getWrkNo());
|
}
|
}
|
|
private AutoTuneHotPathSegmentItem toItem(Map<Integer, AutoTuneStationRuntimeItem> runtimeMap,
|
AutoTuneRoutePressureRuleSnapshot ruleSnapshot,
|
int validRouteSampleCount) {
|
int loadingCount = 0;
|
int taskHoldingCount = 0;
|
int runBlockCount = 0;
|
int nonAutoingCount = 0;
|
for (Integer stationId : stationIds) {
|
AutoTuneStationRuntimeItem runtimeItem = runtimeMap.get(stationId);
|
if (runtimeItem == null) {
|
continue;
|
}
|
if (Objects.equals(runtimeItem.getLoading(), 1)) {
|
loadingCount++;
|
}
|
if (runtimeItem.getTaskNo() != null && runtimeItem.getTaskNo() > 0) {
|
taskHoldingCount++;
|
}
|
if (Objects.equals(runtimeItem.getRunBlock(), 1)) {
|
runBlockCount++;
|
}
|
if (!Objects.equals(runtimeItem.getAutoing(), 1)) {
|
nonAutoingCount++;
|
}
|
}
|
|
int pressureScore = calculateSegmentPressureScore(
|
passTaskCount,
|
taskHoldingCount,
|
loadingCount,
|
runBlockCount,
|
nonAutoingCount,
|
stationIds.size(),
|
validRouteSampleCount,
|
ruleSnapshot
|
);
|
AutoTuneHotPathSegmentItem item = new AutoTuneHotPathSegmentItem();
|
item.setSegmentKey(segmentKey);
|
item.setStationIds(new ArrayList<>(stationIds));
|
item.setPassTaskCount(passTaskCount);
|
item.setLoadingCount(loadingCount);
|
item.setTaskHoldingCount(taskHoldingCount);
|
item.setRunBlockCount(runBlockCount);
|
item.setNonAutoingCount(nonAutoingCount);
|
item.setPressureScore(pressureScore);
|
item.setPressureLevel(calculatePressureLevel(pressureScore, ruleSnapshot));
|
item.setPressureFactors(segmentPressureFactors(
|
passTaskCount,
|
taskHoldingCount,
|
loadingCount,
|
runBlockCount,
|
nonAutoingCount,
|
stationIds.size(),
|
validRouteSampleCount
|
));
|
item.setRelatedTargetStations(new ArrayList<>(relatedTargetStations));
|
item.setSampleWrkNos(new ArrayList<>(sampleWrkNos));
|
return item;
|
}
|
}
|
|
private class TargetPressureAggregate {
|
private final Integer targetStationId;
|
private final List<AutoTuneHotPathSegmentItem> relatedHotSegments = new ArrayList<>();
|
private int blockedTaskCount;
|
private int routeTaskCount;
|
|
private TargetPressureAggregate(Integer targetStationId) {
|
this.targetStationId = targetStationId;
|
}
|
|
private AutoTuneTargetStationRoutePressureItem toItem(AutoTuneRoutePressureRuleSnapshot ruleSnapshot) {
|
int pressureScore = targetPressureScore(ruleSnapshot);
|
String pressureLevel = calculatePressureLevel(pressureScore, ruleSnapshot);
|
String heuristicDirection = heuristicDirection(pressureLevel);
|
String evidenceText = evidenceText(pressureLevel, pressureScore, heuristicDirection);
|
AutoTuneTargetStationRoutePressureItem item = new AutoTuneTargetStationRoutePressureItem();
|
item.setTargetStationId(targetStationId);
|
item.setBlockedTaskCount(blockedTaskCount);
|
item.setRouteTaskCount(routeTaskCount);
|
item.setMainHotSegments(mainHotSegmentKeys());
|
item.setPressureLevel(pressureLevel);
|
item.setPressureScore(pressureScore);
|
item.setConfidence(targetConfidence(routeTaskCount));
|
item.setPressureFactors(targetPressureFactors(ruleSnapshot));
|
item.setHeuristicDirection(heuristicDirection);
|
item.setRecommendedDirection(heuristicDirection);
|
item.setRecommendedTargets(Collections.singletonList("station/" + targetStationId + "/outTaskLimit"));
|
item.setReason(evidenceText);
|
item.setEvidenceText(evidenceText);
|
return item;
|
}
|
|
private int targetPressureScore(AutoTuneRoutePressureRuleSnapshot ruleSnapshot) {
|
int pressureScore = highestSegmentPressureScore();
|
int blockedScore = (int) Math.round(
|
ratio(blockedTaskCount, routeTaskCount) * nullSafe(ruleSnapshot.getBlockedWeightPercent())
|
);
|
return clampPercent(pressureScore + blockedScore);
|
}
|
|
private int highestSegmentPressureScore() {
|
int pressureScore = 0;
|
for (AutoTuneHotPathSegmentItem hotPathSegment : relatedHotSegments) {
|
pressureScore = Math.max(pressureScore, nullSafe(hotPathSegment.getPressureScore()));
|
}
|
return pressureScore;
|
}
|
|
private Map<String, Object> targetPressureFactors(AutoTuneRoutePressureRuleSnapshot ruleSnapshot) {
|
Map<String, Object> factors = new LinkedHashMap<>();
|
factors.put("blockedRatio", percentValue(blockedTaskCount, routeTaskCount));
|
factors.put("highestSegmentScore", highestSegmentPressureScore());
|
factors.put("blockedScore", (int) Math.round(
|
ratio(blockedTaskCount, routeTaskCount) * nullSafe(ruleSnapshot.getBlockedWeightPercent())
|
));
|
return factors;
|
}
|
|
private List<String> mainHotSegmentKeys() {
|
List<String> segmentKeys = new ArrayList<>();
|
for (AutoTuneHotPathSegmentItem hotPathSegment : relatedHotSegments) {
|
if (hotPathSegment.getSegmentKey() != null) {
|
segmentKeys.add(hotPathSegment.getSegmentKey());
|
}
|
if (segmentKeys.size() >= MAX_TARGET_MAIN_SEGMENT_COUNT) {
|
break;
|
}
|
}
|
return segmentKeys;
|
}
|
|
private String heuristicDirection(String pressureLevel) {
|
if (PRESSURE_HIGH.equals(pressureLevel)) {
|
return DIRECTION_DECREASE_CANDIDATE;
|
}
|
if (blockedTaskCount > 0) {
|
return DIRECTION_INCREASE_CANDIDATE;
|
}
|
return DIRECTION_OBSERVE;
|
}
|
|
private String evidenceText(String pressureLevel, int pressureScore, String heuristicDirection) {
|
return "目标站" + targetStationId
|
+ "路径事实:pressure=" + pressureLevel
|
+ ",score=" + pressureScore
|
+ ",heuristicDirection=" + heuristicDirection
|
+ ",blockedTaskCount=" + blockedTaskCount
|
+ ",routeTaskCount=" + routeTaskCount
|
+ ",mainHotSegments=" + mainHotSegmentKeys()
|
+ "。";
|
}
|
}
|
}
|