package com.zy.core.utils.station; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.core.common.Cools; import com.zy.asrs.entity.BasDevp; import com.zy.asrs.entity.BasStation; import com.zy.asrs.entity.WrkMast; import com.zy.asrs.service.BasDevpService; import com.zy.asrs.service.BasStationService; import com.zy.asrs.service.WrkMastService; import com.zy.common.model.NavigateNode; import com.zy.common.utils.NavigateUtils; import com.zy.core.News; import com.zy.core.cache.SlaveConnection; import com.zy.core.enums.SlaveType; import com.zy.core.enums.StationCommandType; import com.zy.core.enums.WrkIoType; import com.zy.core.enums.WrkStsType; import com.zy.core.model.command.StationCommand; import com.zy.core.model.protocol.StationProtocol; import com.zy.core.move.StationMoveCoordinator; import com.zy.core.move.StationMoveDispatchMode; import com.zy.core.move.StationMoveSession; import com.zy.core.service.StationTaskLoopService; import com.zy.core.thread.StationThread; import com.zy.core.utils.station.model.CircleTargetCandidate; import com.zy.core.utils.station.model.OutOrderDispatchDecision; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; @Slf4j @Component public class StationOutboundDecisionSupport { private static final long PATH_CALC_SLOW_THRESHOLD_MS = 50L; private static final long OUT_ORDER_DECISION_SLOW_THRESHOLD_MS = 100L; private static final long CURRENT_OUT_ORDER_DECISION_SLOW_THRESHOLD_MS = 100L; private static final long CIRCLE_TARGET_EVAL_SLOW_THRESHOLD_MS = 100L; @Autowired private WrkMastService wrkMastService; @Autowired private BasDevpService basDevpService; @Autowired private BasStationService basStationService; @Autowired private NavigateUtils navigateUtils; @Autowired private StationTaskLoopService stationTaskLoopService; @Autowired private StationMoveCoordinator stationMoveCoordinator; @Autowired private StationDispatchRuntimeStateSupport stationDispatchRuntimeStateSupport; public StationCommand buildOutboundMoveCommand(StationThread stationThread, WrkMast wrkMast, Integer stationId, Integer targetStationId, Double pathLenFactor) { if (stationThread == null || wrkMast == null) { return null; } return stationThread.getCommand( StationCommandType.MOVE, wrkMast.getWrkNo(), stationId, targetStationId, 0, normalizePathLenFactor(pathLenFactor) ); } public Double resolveOutboundPathLenFactor(WrkMast wrkMast) { if (!isBatchOutboundTaskWithSeq(wrkMast)) { return 0.0d; } List activeBatchTaskList = loadActiveBatchTaskList(wrkMast.getBatch()); if (activeBatchTaskList.size() <= 1) { return 0.0d; } int activeTaskCount = 0; int predecessorCount = 0; for (WrkMast item : activeBatchTaskList) { if (!isFactorCandidateTask(item)) { continue; } activeTaskCount++; if (item.getBatchSeq() < wrkMast.getBatchSeq()) { predecessorCount++; } } if (activeTaskCount <= 1 || predecessorCount <= 0) { return 0.0d; } return normalizePathLenFactor((double) predecessorCount / (double) (activeTaskCount - 1)); } public List getAllOutOrderList() { List list = new ArrayList<>(); List basDevps = basDevpService.list(new QueryWrapper()); for (BasDevp basDevp : basDevps) { List orderList = basDevp.getOutOrderIntList(); list.addAll(orderList); } return list; } public OutOrderDispatchDecision resolveOutboundDispatchDecision(Integer currentStationId, WrkMast wrkMast, List outOrderStationIds, Double pathLenFactor) { long startMs = System.currentTimeMillis(); if (wrkMast == null || wrkMast.getStaNo() == null) { return null; } DecisionPathCache decisionPathCache = new DecisionPathCache(); if (!shouldApplyOutOrder(wrkMast, outOrderStationIds)) { return OutOrderDispatchDecision.direct(wrkMast.getStaNo()); } long resolveDispatchTargetStartMs = System.currentTimeMillis(); Integer dispatchStationId = resolveDispatchOutOrderTarget( wrkMast, wrkMast.getSourceStaNo(), wrkMast.getStaNo(), outOrderStationIds, pathLenFactor, decisionPathCache ); long resolveDispatchTargetCostMs = System.currentTimeMillis() - resolveDispatchTargetStartMs; if (dispatchStationId == null) { return null; } long currentDispatchCheckStartMs = System.currentTimeMillis(); boolean currentOutOrderDispatchStation = isCurrentOutOrderDispatchStation(currentStationId, wrkMast, outOrderStationIds, pathLenFactor, decisionPathCache); long currentDispatchCheckCostMs = System.currentTimeMillis() - currentDispatchCheckStartMs; OutOrderDispatchDecision decision; if (currentOutOrderDispatchStation) { decision = resolveCurrentOutOrderDispatchDecision(currentStationId, wrkMast, outOrderStationIds, pathLenFactor, decisionPathCache); } else if (!Objects.equals(dispatchStationId, wrkMast.getStaNo()) && isCurrentOutOrderStation(currentStationId, outOrderStationIds) && isWatchingCircleArrival(wrkMast.getWrkNo(), currentStationId)) { decision = OutOrderDispatchDecision.circle(dispatchStationId, null, false); } else { decision = OutOrderDispatchDecision.direct(dispatchStationId); } long totalCostMs = System.currentTimeMillis() - startMs; log.info("resolveOutboundDispatchDecision profile, taskNo={}, currentStationId={}, finalTargetStationId={}, dispatchStationId={}, currentOutOrderDispatchStation={}, decision={}, circle={}, resolveDispatchTargetCostMs={}ms, currentDispatchCheckCostMs={}ms, totalCostMs={}ms", wrkMast.getWrkNo(), currentStationId, wrkMast.getStaNo(), dispatchStationId, currentOutOrderDispatchStation, decision == null ? null : decision.getTargetStationId(), decision != null && decision.isCircle(), resolveDispatchTargetCostMs, currentDispatchCheckCostMs, totalCostMs); if (totalCostMs > OUT_ORDER_DECISION_SLOW_THRESHOLD_MS) { log.warn("resolveOutboundDispatchDecision slow, taskNo={}, currentStationId={}, totalCostMs={}ms, resolveDispatchTargetCostMs={}ms, currentDispatchCheckCostMs={}ms", wrkMast.getWrkNo(), currentStationId, totalCostMs, resolveDispatchTargetCostMs, currentDispatchCheckCostMs); } return decision; } public void syncOutOrderWatchState(WrkMast wrkMast, Integer currentStationId, List outOrderStationIds, OutOrderDispatchDecision dispatchDecision, StationCommand command) { if (dispatchDecision == null || command == null || !shouldApplyOutOrder(wrkMast, outOrderStationIds)) { return; } if (dispatchDecision.isCircle()) { stationDispatchRuntimeStateSupport.saveWatchCircleCommand(wrkMast.getWrkNo(), command); if (dispatchDecision.shouldCountLoopIssue() && stationTaskLoopService != null && dispatchDecision.getLoopEvaluation() != null) { stationTaskLoopService.recordLoopIssue(dispatchDecision.getLoopEvaluation(), "OUT_ORDER_CIRCLE"); } } else { stationDispatchRuntimeStateSupport.clearWatchCircleCommand(wrkMast.getWrkNo()); } } public boolean shouldSkipOutOrderDispatchForExistingRoute(Integer wrkNo, Integer stationId) { if (stationMoveCoordinator == null || wrkNo == null || wrkNo <= 0 || stationId == null) { return false; } StationMoveSession session = stationMoveCoordinator.loadSession(wrkNo); if (session == null) { return false; } if (!session.isActive() || !session.containsStation(stationId)) { return false; } if (StationMoveDispatchMode.CIRCLE == session.getDispatchMode()) { return true; } return !Objects.equals(stationId, session.getCurrentRouteTargetStationId()); } public boolean isWatchingCircleArrival(Integer wrkNo, Integer stationId) { if (stationMoveCoordinator != null) { StationMoveSession session = stationMoveCoordinator.loadSession(wrkNo); if (session != null && session.isActive() && stationId != null) { if (stationId.equals(session.getNextDecisionStationId())) { return true; } if (session.containsStation(stationId)) { return false; } } } StationCommand command = stationDispatchRuntimeStateSupport.loadWatchCircleCommand(wrkNo); return command != null && stationId != null && stationId.equals(command.getTargetStaNo()); } private List calcOutboundNavigatePath(WrkMast wrkMast, Integer sourceStationId, Integer targetStationId, Double pathLenFactor) { return calcOutboundNavigatePath(wrkMast, sourceStationId, targetStationId, pathLenFactor, null); } private List calcOutboundNavigatePath(WrkMast wrkMast, Integer sourceStationId, Integer targetStationId, Double pathLenFactor, DecisionPathCache decisionPathCache) { long startMs = System.currentTimeMillis(); Double normalizedFactor = normalizePathLenFactor(pathLenFactor); Integer currentTaskNo = wrkMast == null ? null : wrkMast.getWrkNo(); boolean cacheHit = false; List path; if (decisionPathCache == null) { path = currentTaskNo == null ? navigateUtils.calcOptimalPathByStationId(sourceStationId, targetStationId, null, normalizedFactor) : navigateUtils.calcOptimalPathByStationId(sourceStationId, targetStationId, currentTaskNo, normalizedFactor); } else { String cacheKey = buildPathCacheKey(currentTaskNo, sourceStationId, targetStationId, normalizedFactor); List cachedPath = decisionPathCache.pathMap.get(cacheKey); if (cachedPath != null) { cacheHit = true; path = cachedPath; } else { path = currentTaskNo == null ? navigateUtils.calcOptimalPathByStationId(sourceStationId, targetStationId, null, normalizedFactor) : navigateUtils.calcOptimalPathByStationId(sourceStationId, targetStationId, currentTaskNo, normalizedFactor); if (path == null) { path = Collections.emptyList(); } decisionPathCache.pathMap.put(cacheKey, path); } } if (path == null) { path = Collections.emptyList(); } long costMs = System.currentTimeMillis() - startMs; if (costMs > PATH_CALC_SLOW_THRESHOLD_MS) { log.warn("calcOutboundNavigatePath slow, taskNo={}, sourceStationId={}, targetStationId={}, pathCacheHit={}, pathNodeCount={}, pathCostMs={}ms", currentTaskNo, sourceStationId, targetStationId, cacheHit, path.size(), costMs); } return path; } private boolean isBatchOutboundTaskWithSeq(WrkMast wrkMast) { return wrkMast != null && Objects.equals(wrkMast.getIoType(), WrkIoType.OUT.id) && !Cools.isEmpty(wrkMast.getBatch()) && wrkMast.getBatchSeq() != null && wrkMast.getWrkNo() != null; } private List loadActiveBatchTaskList(String batch) { if (Cools.isEmpty(batch)) { return Collections.emptyList(); } return wrkMastService.list(new QueryWrapper() .eq("io_type", WrkIoType.OUT.id) .eq("batch", batch) .notIn("wrk_sts", WrkStsType.STATION_RUN_COMPLETE.sts, WrkStsType.COMPLETE_OUTBOUND.sts, WrkStsType.SETTLE_OUTBOUND.sts)); } private boolean isFactorCandidateTask(WrkMast wrkMast) { return wrkMast != null && Objects.equals(wrkMast.getIoType(), WrkIoType.OUT.id) && wrkMast.getBatchSeq() != null && !"taskCancel".equals(wrkMast.getMk()); } private boolean shouldApplyOutOrder(WrkMast wrkMast, List outOrderStationIds) { return wrkMast != null && wrkMast.getStaNo() != null && Objects.equals(wrkMast.getIoType(), WrkIoType.OUT.id) && !Cools.isEmpty(wrkMast.getBatch()) && wrkMast.getBatchSeq() != null && outOrderStationIds != null && !outOrderStationIds.isEmpty(); } private boolean isCurrentOutOrderDispatchStation(Integer currentStationId, WrkMast wrkMast, List outOrderStationIds, Double pathLenFactor, DecisionPathCache decisionPathCache) { if (!shouldApplyOutOrder(wrkMast, outOrderStationIds) || currentStationId == null) { return false; } Integer dispatchStationId = resolveDispatchOutOrderTarget( wrkMast, wrkMast.getSourceStaNo(), wrkMast.getStaNo(), outOrderStationIds, pathLenFactor, decisionPathCache ); return dispatchStationId != null && !Objects.equals(dispatchStationId, wrkMast.getStaNo()) && Objects.equals(currentStationId, dispatchStationId); } private boolean isCurrentOutOrderStation(Integer currentStationId, List outOrderStationIds) { return currentStationId != null && outOrderStationIds != null && outOrderStationIds.contains(currentStationId); } private OutOrderDispatchDecision resolveCurrentOutOrderDispatchDecision(Integer currentStationId, WrkMast wrkMast, List outOrderStationIds, Double pathLenFactor, DecisionPathCache decisionPathCache) { long startMs = System.currentTimeMillis(); if (!isCurrentOutOrderDispatchStation(currentStationId, wrkMast, outOrderStationIds, pathLenFactor, decisionPathCache)) { return null; } long batchQueryStartMs = System.currentTimeMillis(); List batchWrkList = wrkMastService.list(new QueryWrapper() .eq("io_type", WrkIoType.OUT.id) .notIn("wrk_sts", WrkStsType.STATION_RUN_COMPLETE.sts, WrkStsType.COMPLETE_OUTBOUND.sts, WrkStsType.SETTLE_OUTBOUND.sts) .eq("batch", wrkMast.getBatch()) .orderByAsc("batch_seq") .orderByAsc("wrk_no")); long batchQueryCostMs = System.currentTimeMillis() - batchQueryStartMs; if (batchWrkList.isEmpty()) { return OutOrderDispatchDecision.direct(wrkMast.getStaNo()); } WrkMast firstWrkMast = batchWrkList.get(0); Integer currentBatchSeq = firstWrkMast.getBatchSeq(); if (currentBatchSeq == null) { News.taskInfo(wrkMast.getWrkNo(), "批次:{} 首个未完成任务缺少批次序号,当前任务暂不放行", wrkMast.getBatch()); return null; } List initPath; long initPathStartMs = System.currentTimeMillis(); try { initPath = calcOutboundNavigatePath(wrkMast, wrkMast.getSourceStaNo(), wrkMast.getStaNo(), pathLenFactor, decisionPathCache); } catch (Exception e) { News.taskInfo(wrkMast.getWrkNo(), "批次:{} 计算排序路径失败,当前站点={}", wrkMast.getBatch(), currentStationId); return null; } long initPathCostMs = System.currentTimeMillis() - initPathStartMs; long batchSeqScanStartMs = System.currentTimeMillis(); Integer seq = getOutStationBatchSeq(initPath, currentStationId, wrkMast.getBatch()); long batchSeqScanCostMs = System.currentTimeMillis() - batchSeqScanStartMs; boolean toTarget = seq == null ? currentBatchSeq.equals(wrkMast.getBatchSeq()) : Integer.valueOf(seq + 1).equals(wrkMast.getBatchSeq()); if (toTarget) { long releaseSlotCheckStartMs = System.currentTimeMillis(); boolean hasReachableReleaseSlot = hasReachableOutReleaseSlot(wrkMast, currentStationId, wrkMast.getStaNo(), pathLenFactor, decisionPathCache); long releaseSlotCheckCostMs = System.currentTimeMillis() - releaseSlotCheckStartMs; if (hasReachableReleaseSlot) { return OutOrderDispatchDecision.direct(wrkMast.getStaNo()); } long loopEvalStartMs = System.currentTimeMillis(); StationTaskLoopService.LoopEvaluation loopEvaluation = evaluateOutOrderLoop( wrkMast.getWrkNo(), currentStationId, outOrderStationIds ); long loopEvalCostMs = System.currentTimeMillis() - loopEvalStartMs; long circleTargetEvalStartMs = System.currentTimeMillis(); Integer circleTarget = resolveNextCircleOrderTarget( wrkMast, currentStationId, outOrderStationIds, loopEvaluation.getExpectedLoopIssueCount(), pathLenFactor, decisionPathCache ); long circleTargetEvalCostMs = System.currentTimeMillis() - circleTargetEvalStartMs; long totalCostMs = System.currentTimeMillis() - startMs; log.info("resolveCurrentOutOrderDispatchDecision profile, taskNo={}, currentStationId={}, batch={}, currentBatchSeq={}, taskBatchSeq={}, toTarget={}, batchQueryCostMs={}ms, initPathCostMs={}ms, batchSeqScanCostMs={}ms, releaseSlotCheckCostMs={}ms, loopEvalCostMs={}ms, circleTargetEvalCostMs={}ms, totalCostMs={}ms, circleTarget={}", wrkMast.getWrkNo(), currentStationId, wrkMast.getBatch(), currentBatchSeq, wrkMast.getBatchSeq(), toTarget, batchQueryCostMs, initPathCostMs, batchSeqScanCostMs, releaseSlotCheckCostMs, loopEvalCostMs, circleTargetEvalCostMs, totalCostMs, circleTarget); if (totalCostMs > CURRENT_OUT_ORDER_DECISION_SLOW_THRESHOLD_MS) { log.warn("resolveCurrentOutOrderDispatchDecision slow, taskNo={}, currentStationId={}, totalCostMs={}ms, batchQueryCostMs={}ms, initPathCostMs={}ms, batchSeqScanCostMs={}ms, releaseSlotCheckCostMs={}ms, circleTargetEvalCostMs={}ms", wrkMast.getWrkNo(), currentStationId, totalCostMs, batchQueryCostMs, initPathCostMs, batchSeqScanCostMs, releaseSlotCheckCostMs, circleTargetEvalCostMs); } if (circleTarget == null) { News.taskInfo(wrkMast.getWrkNo(), "目标站当前不可进,且未找到可执行的下一排序检测点,当前站点={}", currentStationId); return null; } return OutOrderDispatchDecision.circle(circleTarget, loopEvaluation, true); } long loopEvalStartMs = System.currentTimeMillis(); StationTaskLoopService.LoopEvaluation loopEvaluation = evaluateOutOrderLoop( wrkMast.getWrkNo(), currentStationId, outOrderStationIds ); long loopEvalCostMs = System.currentTimeMillis() - loopEvalStartMs; long circleTargetEvalStartMs = System.currentTimeMillis(); Integer circleTarget = resolveNextCircleOrderTarget( wrkMast, currentStationId, outOrderStationIds, loopEvaluation.getExpectedLoopIssueCount(), pathLenFactor, decisionPathCache ); long circleTargetEvalCostMs = System.currentTimeMillis() - circleTargetEvalStartMs; long totalCostMs = System.currentTimeMillis() - startMs; log.info("resolveCurrentOutOrderDispatchDecision profile, taskNo={}, currentStationId={}, batch={}, currentBatchSeq={}, taskBatchSeq={}, toTarget={}, batchQueryCostMs={}ms, initPathCostMs={}ms, batchSeqScanCostMs={}ms, loopEvalCostMs={}ms, circleTargetEvalCostMs={}ms, totalCostMs={}ms, circleTarget={}", wrkMast.getWrkNo(), currentStationId, wrkMast.getBatch(), currentBatchSeq, wrkMast.getBatchSeq(), toTarget, batchQueryCostMs, initPathCostMs, batchSeqScanCostMs, loopEvalCostMs, circleTargetEvalCostMs, totalCostMs, circleTarget); if (totalCostMs > CURRENT_OUT_ORDER_DECISION_SLOW_THRESHOLD_MS) { log.warn("resolveCurrentOutOrderDispatchDecision slow, taskNo={}, currentStationId={}, totalCostMs={}ms, batchQueryCostMs={}ms, initPathCostMs={}ms, batchSeqScanCostMs={}ms, circleTargetEvalCostMs={}ms", wrkMast.getWrkNo(), currentStationId, totalCostMs, batchQueryCostMs, initPathCostMs, batchSeqScanCostMs, circleTargetEvalCostMs); } if (circleTarget == null) { News.taskInfo(wrkMast.getWrkNo(), "未找到可执行的下一排序检测点,当前站点={}", currentStationId); return null; } return OutOrderDispatchDecision.circle(circleTarget, loopEvaluation, true); } private StationTaskLoopService.LoopEvaluation evaluateOutOrderLoop(Integer taskNo, Integer currentStationId, List outOrderStationIds) { if (stationTaskLoopService == null) { return new StationTaskLoopService.LoopEvaluation( taskNo, currentStationId, StationTaskLoopService.LoopIdentitySnapshot.empty(), 0, 0, false ); } return stationTaskLoopService.evaluateLoop( taskNo, currentStationId, true, outOrderStationIds, "outOrderCircle" ); } private Integer resolveDispatchOutOrderTarget(WrkMast wrkMast, Integer sourceStationId, Integer finalTargetStationId, List outOrderList, Double pathLenFactor, DecisionPathCache decisionPathCache) { if (finalTargetStationId == null) { return null; } if (sourceStationId == null || outOrderList == null || outOrderList.isEmpty()) { return finalTargetStationId; } try { List nodes = calcOutboundNavigatePath(wrkMast, sourceStationId, finalTargetStationId, pathLenFactor, decisionPathCache); for (int i = nodes.size() - 1; i >= 0; i--) { Integer stationId = getStationIdFromNode(nodes.get(i)); if (stationId == null) { continue; } if (Objects.equals(stationId, finalTargetStationId)) { continue; } if (outOrderList.contains(stationId)) { return stationId; } } } catch (Exception ignore) { } return finalTargetStationId; } private boolean hasReachableOutReleaseSlot(WrkMast wrkMast, Integer currentStationId, Integer finalTargetStationId, Double pathLenFactor, DecisionPathCache decisionPathCache) { if (currentStationId == null || finalTargetStationId == null) { return true; } try { List nodes = calcOutboundNavigatePath(wrkMast, currentStationId, finalTargetStationId, pathLenFactor, decisionPathCache); if (nodes == null || nodes.isEmpty()) { return true; } for (NavigateNode node : nodes) { Integer stationId = getStationIdFromNode(node); if (stationId == null || Objects.equals(stationId, currentStationId)) { continue; } if (!isPathStationBlocked(stationId)) { return true; } } return false; } catch (Exception ignore) { return true; } } private boolean isPathStationBlocked(Integer stationId) { if (stationId == null) { return true; } BasStation basStation = basStationService.getOne(new QueryWrapper().eq("station_id", stationId)); if (basStation == null) { return true; } StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basStation.getDeviceNo()); if (stationThread == null) { return true; } StationProtocol stationProtocol = stationThread.getStatusMap().get(stationId); if (stationProtocol == null) { return true; } return !stationProtocol.isAutoing() || stationProtocol.isLoading() || (stationProtocol.getTaskNo() != null && stationProtocol.getTaskNo() > 0); } private Integer resolveNextCircleOrderTarget(WrkMast wrkMast, Integer currentStationId, List orderedOutStationList, Integer expectedLoopIssueCount, Double pathLenFactor, DecisionPathCache decisionPathCache) { long startMs = System.currentTimeMillis(); if (currentStationId == null || orderedOutStationList == null || orderedOutStationList.size() <= 1) { return null; } int startIndex = orderedOutStationList.indexOf(currentStationId); int total = orderedOutStationList.size(); List candidateList = new ArrayList<>(); int minPathLen = Integer.MAX_VALUE; int maxPathLen = 0; for (int offset = 1; offset < total; offset++) { int candidateIndex = (startIndex + offset + total) % total; Integer candidateStationId = orderedOutStationList.get(candidateIndex); if (candidateStationId == null || currentStationId.equals(candidateStationId)) { continue; } try { List path = calcOutboundNavigatePath(wrkMast, currentStationId, candidateStationId, pathLenFactor, decisionPathCache); if (path != null && !path.isEmpty()) { candidateList.add(new CircleTargetCandidate(candidateStationId, path.size(), offset)); minPathLen = Math.min(minPathLen, path.size()); maxPathLen = Math.max(maxPathLen, path.size()); } } catch (Exception ignore) { } } if (candidateList.isEmpty()) { return null; } candidateList.sort(new Comparator() { @Override public int compare(CircleTargetCandidate left, CircleTargetCandidate right) { if (left == right) { return 0; } if (left == null) { return 1; } if (right == null) { return -1; } int pathCompare = Integer.compare(left.getPathLength(), right.getPathLength()); if (pathCompare != 0) { return pathCompare; } return Integer.compare(left.getOffset(), right.getOffset()); } }); Integer circleTarget = resolveGradualCircleTargetByPathLength(expectedLoopIssueCount, candidateList, pathLenFactor); long totalCostMs = System.currentTimeMillis() - startMs; log.info("resolveNextCircleOrderTarget profile, taskNo={}, currentStationId={}, candidateCount={}, successfulCandidateCount={}, selectedTargetStationId={}, minPathLen={}, maxPathLen={}, totalCostMs={}ms", wrkMast == null ? null : wrkMast.getWrkNo(), currentStationId, Math.max(total - 1, 0), candidateList.size(), circleTarget, minPathLen == Integer.MAX_VALUE ? 0 : minPathLen, maxPathLen, totalCostMs); if (totalCostMs > CIRCLE_TARGET_EVAL_SLOW_THRESHOLD_MS) { log.warn("resolveNextCircleOrderTarget slow, taskNo={}, currentStationId={}, successfulCandidateCount={}, selectedTargetStationId={}, totalCostMs={}ms", wrkMast == null ? null : wrkMast.getWrkNo(), currentStationId, candidateList.size(), circleTarget, totalCostMs); } return circleTarget; } private Integer resolveGradualCircleTargetByPathLength(Integer expectedLoopIssueCount, List candidateList, Double pathLenFactor) { if (candidateList == null || candidateList.isEmpty()) { return null; } List tierList = new ArrayList<>(); Integer lastPathLength = null; for (CircleTargetCandidate candidate : candidateList) { if (candidate == null) { continue; } if (lastPathLength == null || !Objects.equals(lastPathLength, candidate.getPathLength())) { tierList.add(candidate); lastPathLength = candidate.getPathLength(); } } if (tierList.isEmpty()) { return candidateList.get(0).getStationId(); } int defaultTierIndex = expectedLoopIssueCount == null || expectedLoopIssueCount <= 2 ? 0 : Math.min(expectedLoopIssueCount - 2, tierList.size() - 1); int factorTierIndex = (int) Math.round(normalizePathLenFactor(pathLenFactor) * (tierList.size() - 1)); int tierIndex = Math.max(defaultTierIndex, factorTierIndex); return tierList.get(tierIndex).getStationId(); } private Integer getOutStationBatchSeq(List pathList, Integer searchStationId, String searchBatch) { if (pathList == null || pathList.isEmpty() || searchStationId == null || Cools.isEmpty(searchBatch)) { return null; } List checkList = new ArrayList<>(); for (int i = pathList.size() - 1; i >= 0; i--) { NavigateNode node = pathList.get(i); JSONObject value = JSONObject.parseObject(node.getNodeValue()); if (value == null) { continue; } Integer stationId = value.getInteger("stationId"); if (searchStationId.equals(stationId)) { break; } checkList.add(stationId); } HashMap batchMap = new HashMap<>(); for (Integer station : checkList) { BasStation basStation = basStationService.getOne(new QueryWrapper().eq("station_id", station)); if (basStation == null) { continue; } StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basStation.getDeviceNo()); if (stationThread == null) { continue; } StationProtocol checkStationProtocol = stationThread.getStatusMap().get(station); if (checkStationProtocol == null) { continue; } if (checkStationProtocol.getTaskNo() > 0) { WrkMast checkWrkMast = wrkMastService.selectByWorkNo(checkStationProtocol.getTaskNo()); if (checkWrkMast == null) { continue; } if (!Cools.isEmpty(checkWrkMast.getBatch())) { batchMap.put(checkWrkMast.getBatch(), checkWrkMast.getBatchSeq()); } } } return batchMap.get(searchBatch); } private Integer getStationIdFromNode(NavigateNode node) { if (node == null || isBlank(node.getNodeValue())) { return null; } try { JSONObject value = JSONObject.parseObject(node.getNodeValue()); return value == null ? null : value.getInteger("stationId"); } catch (Exception ignore) { return null; } } private Double normalizePathLenFactor(Double pathLenFactor) { if (pathLenFactor == null || pathLenFactor < 0.0d) { return 0.0d; } if (pathLenFactor > 1.0d) { return 1.0d; } return pathLenFactor; } private String buildPathCacheKey(Integer currentTaskNo, Integer sourceStationId, Integer targetStationId, Double normalizedFactor) { return String.valueOf(currentTaskNo) + "->" + String.valueOf(sourceStationId) + "->" + String.valueOf(targetStationId) + "@" + String.format(java.util.Locale.ROOT, "%.4f", normalizedFactor == null ? 0.0d : normalizedFactor); } private boolean isBlank(String value) { return value == null || value.trim().isEmpty(); } private static class DecisionPathCache { private final Map> pathMap = new HashMap<>(); } }