Junjie
12 小时以前 a72c3844450381a872e4f0f149210e480679984a
src/main/java/com/zy/core/utils/StationOperateProcessUtils.java
@@ -19,6 +19,8 @@
import com.zy.common.service.CommonService;
import com.zy.common.utils.NavigateUtils;
import com.zy.common.utils.RedisUtil;
import com.zy.core.move.StationMoveCoordinator;
import com.zy.core.move.StationMoveSession;
import com.zy.core.News;
import com.zy.core.cache.MessageQueue;
import com.zy.core.cache.SlaveConnection;
@@ -74,6 +76,8 @@
    private StationTaskLoopService stationTaskLoopService;
    @Autowired
    private WrkAnalysisService wrkAnalysisService;
    @Autowired
    private StationMoveCoordinator stationMoveCoordinator;
    //执行输送站点入库任务
    public synchronized void stationInExecute() {
@@ -252,7 +256,16 @@
                    wrkMast.setModiTime(now);
                    if (wrkMastService.updateById(wrkMast)) {
                        wrkAnalysisService.markOutboundStationStart(wrkMast, now);
                        offerDevpCommandWithDedup(stationObjModel.getDeviceNo(), command, "crnStationOutExecute");
                        boolean offered = offerDevpCommandWithDedup(stationObjModel.getDeviceNo(), command, "crnStationOutExecute");
                        if (offered && stationMoveCoordinator != null) {
                            stationMoveCoordinator.recordDispatch(
                                    wrkMast.getWrkNo(),
                                    stationProtocol.getStationId(),
                                    "crnStationOutExecute",
                                    command,
                                    false
                            );
                        }
                        News.info("输送站点出库命令下发成功,站点号={},工作号={},命令数据={}", stationProtocol.getStationId(), wrkMast.getWrkNo(), JSON.toJSONString(command));
                        redisUtil.set(RedisKeyType.STATION_OUT_EXECUTE_LIMIT.key + stationProtocol.getStationId(), "lock", 5);
                        redisUtil.del(RedisKeyType.CRN_OUT_TASK_COMPLETE_STATION_INFO.key + wrkMast.getWrkNo());
@@ -372,6 +385,9 @@
        if (wrkMast == null || wrkMast.getWrkNo() == null) {
            return;
        }
        if (stationMoveCoordinator != null) {
            stationMoveCoordinator.finishSession(wrkMast.getWrkNo());
        }
        Date now = new Date();
        wrkMast.setWrkSts(WrkStsType.STATION_RUN_COMPLETE.sts);
        wrkMast.setIoTime(now);
@@ -419,6 +435,9 @@
                }
                if (complete) {
                    if (stationMoveCoordinator != null) {
                        stationMoveCoordinator.finishSession(wrkNo);
                    }
                    wrkMast.setWrkSts(WrkStsType.COMPLETE_OUTBOUND.sts);
                    wrkMast.setIoTime(new Date());
                    wrkMastService.updateById(wrkMast);
@@ -466,6 +485,20 @@
                        if (wrkMast.getIoType() == WrkIoType.IN.id && runBlockReassignLocStationList.contains(stationProtocol.getStationId())) {
                            //站点处于重新分配库位区域
                            int currentTaskBufferCommandCount = countCurrentTaskBufferCommands(
                                    stationProtocol.getTaskBufferItems(),
                                    stationProtocol.getTaskNo()
                            );
                            if (currentTaskBufferCommandCount > 0) {
                                News.info("输送站点运行堵塞重分配已跳过,缓存区仍存在当前任务命令。站点号={},工作号={},当前任务命令数={}",
                                        stationProtocol.getStationId(),
                                        stationProtocol.getTaskNo(),
                                        currentTaskBufferCommandCount);
                                continue;
                            }
                            if (stationMoveCoordinator != null) {
                                stationMoveCoordinator.cancelSession(wrkMast.getWrkNo());
                            }
                            //运行堵塞,重新申请任务
                            String response = wmsOperateUtils.applyReassignTaskLocNo(wrkMast.getWrkNo(), stationProtocol.getStationId());
                            if (Cools.isEmpty(response)) {
@@ -543,7 +576,19 @@
                                }
                                if (wrkMastService.updateById(wrkMast)) {
                                    offerDevpCommandWithDedup(basDevp.getDevpNo(), command, "checkStationRunBlock_direct");
                                    boolean offered = offerDevpCommandWithDedup(basDevp.getDevpNo(), command, "checkStationRunBlock_direct");
                                    if (!offered) {
                                        continue;
                                    }
                                    if (stationMoveCoordinator != null) {
                                        stationMoveCoordinator.recordDispatch(
                                                wrkMast.getWrkNo(),
                                                stationProtocol.getStationId(),
                                                "checkStationRunBlock_direct",
                                                command,
                                                false
                                        );
                                    }
                                }
                            } else {
                                News.error("请求WMS接口失败!!!response:{}", response);
@@ -581,9 +626,24 @@
                                continue;
                            }
                            if (stationMoveCoordinator != null) {
                                stationMoveCoordinator.cancelSession(wrkMast.getWrkNo());
                            }
                            resetSegmentMoveCommandsBeforeReroute(wrkMast.getWrkNo());
                            offerDevpCommandWithDedup(basDevp.getDevpNo(), command, "checkStationRunBlock_reroute");
                            boolean offered = offerDevpCommandWithDedup(basDevp.getDevpNo(), command, "checkStationRunBlock_reroute");
                            if (!offered) {
                                continue;
                            }
                            syncOutOrderWatchState(wrkMast, stationProtocol.getStationId(), outOrderStationIds, dispatchDecision, command);
                            if (stationMoveCoordinator != null) {
                                stationMoveCoordinator.recordDispatch(
                                        wrkMast.getWrkNo(),
                                        stationProtocol.getStationId(),
                                        "checkStationRunBlock_reroute",
                                        command,
                                        dispatchDecision != null && dispatchDecision.isCircle()
                                );
                            }
                            News.info("输送站点堵塞后重新计算路径命令下发成功,站点号={},工作号={},命令数据={}", stationProtocol.getStationId(), wrkMast.getWrkNo(), JSON.toJSONString(command));
                        }
                    }
@@ -682,44 +742,21 @@
                    continue;
                }
                if (countCurrentTaskBufferCommands(stationProtocol.getTaskBufferItems(), stationProtocol.getTaskNo()) > 0) {
                    continue;
                }
                if (isWatchingCircleArrival(wrkMast.getWrkNo(), stationProtocol.getStationId())) {
                    continue;
                }
                Double pathLenFactor = resolveOutboundPathLenFactor(wrkMast);
                OutOrderDispatchDecision dispatchDecision = resolveOutboundDispatchDecision(
                        stationProtocol.getStationId(),
                RerouteContext context = RerouteContext.create(
                        RerouteSceneType.OUT_ORDER,
                        basDevp,
                        stationThread,
                        stationProtocol,
                        wrkMast,
                        outOrderStationIds,
                        pathLenFactor
                );
                Integer moveStaNo = dispatchDecision == null ? null : dispatchDecision.getTargetStationId();
                if (moveStaNo == null || Objects.equals(moveStaNo, stationProtocol.getStationId())) {
                    continue;
                }
                StationCommand command = buildOutboundMoveCommand(
                        stationThread,
                        wrkMast,
                        stationProtocol.getStationId(),
                        moveStaNo,
                        pathLenFactor
                );
                if (command == null) {
                    News.taskInfo(wrkMast.getWrkNo(), "获取输送线命令失败");
                    continue;
                }
                if (!tryAcquireOutOrderDispatchLock(wrkMast.getWrkNo(), stationProtocol.getStationId())) {
                    continue;
                }
                resetSegmentMoveCommandsBeforeReroute(wrkMast.getWrkNo());
                syncOutOrderWatchState(wrkMast, stationProtocol.getStationId(), outOrderStationIds, dispatchDecision, command);
                offerDevpCommandWithDedup(stationObjModel.getDeviceNo(), command, "checkStationOutOrder");
                News.info(dispatchDecision.isCircle() ? "{}任务进行绕圈" : "{}任务直接去目标点", wrkMast.getWrkNo());
                        pathLenFactor,
                        "checkStationOutOrder"
                ).withDispatchDeviceNo(stationObjModel.getDeviceNo())
                        .withSuppressDispatchGuard()
                        .withOutOrderDispatchLock()
                        .withResetSegmentCommandsBeforeDispatch();
                executeSharedReroute(context);
            }
        }
    }
@@ -766,39 +803,20 @@
                if (Objects.equals(stationProtocol.getStationId(), wrkMast.getStaNo())) {
                    continue;
                }
                if (countCurrentTaskBufferCommands(stationProtocol.getTaskBufferItems(), stationProtocol.getTaskNo()) > 0) {
                    continue;
                }
                Double pathLenFactor = resolveOutboundPathLenFactor(wrkMast);
                OutOrderDispatchDecision dispatchDecision = resolveOutboundDispatchDecision(
                        stationProtocol.getStationId(),
                RerouteContext context = RerouteContext.create(
                        RerouteSceneType.WATCH_CIRCLE,
                        basDevp,
                        stationThread,
                        stationProtocol,
                        wrkMast,
                        outOrderList,
                        pathLenFactor
                );
                Integer moveStaNo = dispatchDecision == null ? null : dispatchDecision.getTargetStationId();
                if (moveStaNo == null || Objects.equals(moveStaNo, stationProtocol.getStationId())) {
                    continue;
                }
                StationCommand command = buildOutboundMoveCommand(
                        stationThread,
                        wrkMast,
                        stationProtocol.getStationId(),
                        moveStaNo,
                        pathLenFactor
                );
                if (command == null) {
                    News.taskInfo(wrkMast.getWrkNo(), "获取输送线命令失败");
                    continue;
                }
                if (!tryAcquireOutOrderDispatchLock(wrkMast.getWrkNo(), stationProtocol.getStationId())) {
                    continue;
                }
                resetSegmentMoveCommandsBeforeReroute(wrkMast.getWrkNo());
                syncOutOrderWatchState(wrkMast, stationProtocol.getStationId(), outOrderList, dispatchDecision, command);
                offerDevpCommandWithDedup(basDevp.getDevpNo(), command, "watchCircleStation");
                        pathLenFactor,
                        "watchCircleStation"
                ).withSuppressDispatchGuard()
                        .withOutOrderDispatchLock()
                        .withResetSegmentCommandsBeforeDispatch();
                executeSharedReroute(context);
            }
        }
    }
@@ -819,6 +837,197 @@
                0,
                normalizePathLenFactor(pathLenFactor)
        );
    }
    RerouteCommandPlan buildRerouteCommandPlan(RerouteContext context,
                                               RerouteDecision decision) {
        if (context == null) {
            return RerouteCommandPlan.skip("missing-context");
        }
        if (decision == null) {
            return RerouteCommandPlan.skip("missing-decision");
        }
        if (decision.skip()) {
            return RerouteCommandPlan.skip(decision.skipReason());
        }
        if (context.stationThread() == null || context.stationProtocol() == null || context.wrkMast() == null) {
            return RerouteCommandPlan.skip("missing-runtime-dependency");
        }
        Integer currentStationId = context.stationProtocol().getStationId();
        Integer targetStationId = decision.targetStationId();
        if (currentStationId == null || targetStationId == null) {
            return RerouteCommandPlan.skip("missing-target-station");
        }
        if (Objects.equals(currentStationId, targetStationId)) {
            return RerouteCommandPlan.skip("same-station");
        }
        StationCommand command = context.useRunBlockCommand()
                ? context.stationThread().getRunBlockRerouteCommand(
                context.wrkMast().getWrkNo(),
                currentStationId,
                targetStationId,
                0,
                context.pathLenFactor()
        )
                : buildOutboundMoveCommand(
                context.stationThread(),
                context.wrkMast(),
                currentStationId,
                targetStationId,
                context.pathLenFactor()
        );
        if (command == null) {
            return RerouteCommandPlan.skip("missing-command");
        }
        return RerouteCommandPlan.dispatch(command, decision, context.dispatchScene());
    }
    RerouteExecutionResult executeReroutePlan(RerouteContext context,
                                              RerouteCommandPlan plan) {
        if (context == null) {
            return RerouteExecutionResult.skip("missing-context");
        }
        if (plan == null) {
            return RerouteExecutionResult.skip("missing-plan");
        }
        if (plan.skip()) {
            return RerouteExecutionResult.skip(plan.skipReason());
        }
        StationProtocol stationProtocol = context.stationProtocol();
        if (stationProtocol == null) {
            return RerouteExecutionResult.skip("missing-station-protocol");
        }
        Integer taskNo = stationProtocol.getTaskNo();
        Integer stationId = stationProtocol.getStationId();
        if (taskNo == null || taskNo <= 0 || stationId == null) {
            return RerouteExecutionResult.skip("invalid-station-task");
        }
        if (context.checkRecentDispatch()
                && shouldSkipIdleRecoverForRecentDispatch(taskNo, stationId)) {
            return RerouteExecutionResult.skip("recent-dispatch");
        }
        if (countCurrentTaskBufferCommands(stationProtocol.getTaskBufferItems(), taskNo) > 0) {
            return RerouteExecutionResult.skip("buffer-has-current-task");
        }
        if (context.checkSuppressDispatch()
                && stationMoveCoordinator != null
                && stationMoveCoordinator.shouldSuppressDispatch(taskNo, stationId, plan.command())) {
            return RerouteExecutionResult.skip("dispatch-suppressed");
        }
        if (context.requireOutOrderDispatchLock()
                && !tryAcquireOutOrderDispatchLock(taskNo, stationId)) {
            return RerouteExecutionResult.skip("out-order-lock");
        }
        if (context.cancelSessionBeforeDispatch() && stationMoveCoordinator != null) {
            stationMoveCoordinator.cancelSession(taskNo);
        }
        if (!isBlank(context.executionLockKey())) {
            Object lock = redisUtil.get(context.executionLockKey());
            if (lock != null) {
                return RerouteExecutionResult.skip("scene-lock");
            }
            redisUtil.set(context.executionLockKey(), "lock", context.executionLockSeconds());
        }
        if (context.resetSegmentCommandsBeforeDispatch()) {
            resetSegmentMoveCommandsBeforeReroute(taskNo);
        }
        int clearedCommandCount = 0;
        if (context.clearIdleIssuedCommands()) {
            clearedCommandCount = clearIssuedMoveCommandsDuringIdleStay(context.idleTrack(), taskNo, stationId);
        }
        boolean offered = offerDevpCommandWithDedup(context.dispatchDeviceNo(), plan.command(), plan.dispatchScene());
        if (!offered) {
            return RerouteExecutionResult.skip("dispatch-dedup");
        }
        applyRerouteDispatchEffects(context, plan, clearedCommandCount);
        return RerouteExecutionResult.dispatched(plan.command(), clearedCommandCount);
    }
    RerouteDecision resolveSharedRerouteDecision(RerouteContext context) {
        if (context == null || context.wrkMast() == null || context.stationProtocol() == null) {
            return RerouteDecision.skip("missing-runtime-dependency");
        }
        Integer currentStationId = context.stationProtocol().getStationId();
        if (currentStationId == null) {
            return RerouteDecision.skip("missing-current-station");
        }
        if (context.sceneType() == RerouteSceneType.IDLE_RECOVER
                && !Objects.equals(context.wrkMast().getWrkSts(), WrkStsType.STATION_RUN.sts)) {
            Integer targetStationId = context.wrkMast().getStaNo();
            return targetStationId == null || Objects.equals(targetStationId, currentStationId)
                    ? RerouteDecision.skip("same-station")
                    : RerouteDecision.proceed(targetStationId);
        }
        OutOrderDispatchDecision dispatchDecision = resolveOutboundDispatchDecision(
                currentStationId,
                context.wrkMast(),
                context.outOrderStationIds(),
                context.pathLenFactor()
        );
        Integer targetStationId = dispatchDecision == null ? null : dispatchDecision.getTargetStationId();
        if (targetStationId == null || Objects.equals(targetStationId, currentStationId)) {
            return RerouteDecision.skip("same-station");
        }
        return RerouteDecision.proceed(targetStationId, dispatchDecision);
    }
    private RerouteExecutionResult executeSharedReroute(RerouteContext context) {
        RerouteDecision decision = resolveSharedRerouteDecision(context);
        if (decision.skip()) {
            return RerouteExecutionResult.skip(decision.skipReason());
        }
        RerouteCommandPlan plan = buildRerouteCommandPlan(context, decision);
        return executeReroutePlan(context, plan);
    }
    private void applyRerouteDispatchEffects(RerouteContext context,
                                             RerouteCommandPlan plan,
                                             int clearedCommandCount) {
        if (context == null || plan == null || plan.command() == null || context.wrkMast() == null || context.stationProtocol() == null) {
            return;
        }
        WrkMast wrkMast = context.wrkMast();
        StationProtocol stationProtocol = context.stationProtocol();
        OutOrderDispatchDecision dispatchDecision = plan.decision() == null ? null : plan.decision().dispatchDecision();
        syncOutOrderWatchState(wrkMast, stationProtocol.getStationId(), context.outOrderStationIds(), dispatchDecision, plan.command());
        if (stationMoveCoordinator != null) {
            stationMoveCoordinator.recordDispatch(
                    wrkMast.getWrkNo(),
                    stationProtocol.getStationId(),
                    plan.dispatchScene(),
                    plan.command(),
                    dispatchDecision != null && dispatchDecision.isCircle()
            );
        }
        if (context.sceneType() == RerouteSceneType.IDLE_RECOVER) {
            saveStationTaskIdleTrack(new StationTaskIdleTrack(wrkMast.getWrkNo(), stationProtocol.getStationId(), System.currentTimeMillis()));
            News.info("输送站点任务停留{}秒未运行,已重新计算路径并重启运行,站点号={},目标站={},工作号={},清理旧分段命令数={},命令数据={}",
                    STATION_IDLE_RECOVER_SECONDS,
                    stationProtocol.getStationId(),
                    plan.command().getTargetStaNo(),
                    wrkMast.getWrkNo(),
                    clearedCommandCount,
                    JSON.toJSONString(plan.command()));
            return;
        }
        if (context.sceneType() == RerouteSceneType.RUN_BLOCK_REROUTE) {
            News.info("输送站点堵塞后重新计算路径命令下发成功,站点号={},工作号={},命令数据={}",
                    stationProtocol.getStationId(),
                    wrkMast.getWrkNo(),
                    JSON.toJSONString(plan.command()));
            return;
        }
        if (context.sceneType() == RerouteSceneType.OUT_ORDER) {
            News.info(dispatchDecision != null && dispatchDecision.isCircle() ? "{}任务进行绕圈" : "{}任务直接去目标点", wrkMast.getWrkNo());
        }
    }
    private List<NavigateNode> calcOutboundNavigatePath(WrkMast wrkMast,
@@ -1269,8 +1478,39 @@
    }
    private 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 = getWatchCircleCommand(wrkNo);
        return command != null && stationId != null && stationId.equals(command.getTargetStaNo());
    }
    private boolean isWatchingCircleTransit(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 false;
                }
                if (session.containsStation(stationId)) {
                    return true;
                }
            }
        }
        StationCommand command = getWatchCircleCommand(wrkNo);
        if (command == null || stationId == null || Objects.equals(stationId, command.getTargetStaNo())) {
            return false;
        }
        List<Integer> navigatePath = command.getNavigatePath();
        return navigatePath != null && navigatePath.contains(stationId);
    }
    private StationCommand getWatchCircleCommand(Integer wrkNo) {
@@ -1315,6 +1555,9 @@
        }
        StationTaskIdleTrack idleTrack = touchStationTaskIdleTrack(stationProtocol.getTaskNo(), stationProtocol.getStationId());
        if (shouldSkipIdleRecoverForRecentDispatch(stationProtocol.getTaskNo(), stationProtocol.getStationId())) {
            return;
        }
        if (idleTrack == null || !idleTrack.isTimeout(STATION_IDLE_RECOVER_SECONDS)) {
            return;
        }
@@ -1353,6 +1596,9 @@
            return;
        }
        if (stationMoveCoordinator != null) {
            stationMoveCoordinator.cancelSession(stationProtocol.getTaskNo());
        }
        redisUtil.set(RedisKeyType.CHECK_STATION_IDLE_RECOVER_LIMIT_.key + stationProtocol.getTaskNo(), "lock", STATION_IDLE_RECOVER_LIMIT_SECONDS);
        resetSegmentMoveCommandsBeforeReroute(stationProtocol.getTaskNo());
        int clearedCommandCount = clearIssuedMoveCommandsDuringIdleStay(idleTrack, stationProtocol.getTaskNo(), stationProtocol.getStationId());
@@ -1369,8 +1615,20 @@
            return;
        }
        offerDevpCommandWithDedup(basDevp.getDevpNo(), command, "checkStationIdleRecover");
        boolean offered = offerDevpCommandWithDedup(basDevp.getDevpNo(), command, "checkStationIdleRecover");
        if (!offered) {
            return;
        }
        syncOutOrderWatchState(wrkMast, stationProtocol.getStationId(), outOrderList, dispatchDecision, command);
        if (stationMoveCoordinator != null) {
            stationMoveCoordinator.recordDispatch(
                    wrkMast.getWrkNo(),
                    stationProtocol.getStationId(),
                    "checkStationIdleRecover",
                    command,
                    dispatchDecision != null && dispatchDecision.isCircle()
            );
        }
        saveStationTaskIdleTrack(new StationTaskIdleTrack(wrkMast.getWrkNo(), stationProtocol.getStationId(), System.currentTimeMillis()));
        News.info("输送站点任务停留{}秒未运行,已重新计算路径并重启运行,站点号={},目标站={},工作号={},清理旧分段命令数={},命令数据={}",
                STATION_IDLE_RECOVER_SECONDS, stationProtocol.getStationId(), moveStaNo, wrkMast.getWrkNo(), clearedCommandCount, JSON.toJSONString(command));
@@ -1385,6 +1643,29 @@
        }
        return Objects.equals(wrkMast.getWrkSts(), WrkStsType.INBOUND_STATION_RUN.sts)
                || Objects.equals(wrkMast.getWrkSts(), WrkStsType.STATION_RUN.sts);
    }
    private boolean shouldSkipIdleRecoverForRecentDispatch(Integer taskNo, Integer stationId) {
        if (stationMoveCoordinator == null || taskNo == null || taskNo <= 0 || stationId == null) {
            return false;
        }
        StationMoveSession session = stationMoveCoordinator.loadSession(taskNo);
        if (session == null || !session.isActive() || session.getLastIssuedAt() == null) {
            return false;
        }
        if (!Objects.equals(stationId, session.getCurrentStationId())
                && !Objects.equals(stationId, session.getDispatchStationId())) {
            return false;
        }
        long elapsedMs = System.currentTimeMillis() - session.getLastIssuedAt();
        long thresholdMs = STATION_IDLE_RECOVER_SECONDS * 1000L;
        if (elapsedMs >= thresholdMs) {
            return false;
        }
        saveStationTaskIdleTrack(new StationTaskIdleTrack(taskNo, stationId, System.currentTimeMillis()));
        News.info("输送站点任务刚完成命令下发,已跳过停留重算。站点号={},工作号={},距上次下发={}ms,routeVersion={}",
                stationId, taskNo, elapsedMs, session.getRouteVersion());
        return true;
    }
    private void resetSegmentMoveCommandsBeforeReroute(Integer taskNo) {
@@ -1446,8 +1727,23 @@
    private String buildStationCommandDispatchDedupKey(Integer deviceNo, StationCommand command) {
        return RedisKeyType.STATION_COMMAND_DISPATCH_DEDUP_.key
                + deviceNo + "_"
                + command.getTaskNo() + "_"
                + command.getStationId();
                + command.getStationId() + "_"
                + (stationMoveCoordinator == null ? Integer.toHexString(buildFallbackPathSignature(command).hashCode())
                : stationMoveCoordinator.buildPathSignatureHash(command));
    }
    private String buildFallbackPathSignature(StationCommand command) {
        if (command == null) {
            return "";
        }
        return String.valueOf(command.getCommandType())
                + "_" + command.getStationId()
                + "_" + command.getTargetStaNo()
                + "_" + command.getNavigatePath()
                + "_" + command.getLiftTransferPath()
                + "_" + command.getOriginalNavigatePath();
    }
    private int clearIssuedMoveCommandsDuringIdleStay(StationTaskIdleTrack idleTrack,
@@ -1765,6 +2061,333 @@
        return pathLenFactor;
    }
    enum RerouteSceneType {
        RUN_BLOCK_REROUTE,
        IDLE_RECOVER,
        OUT_ORDER,
        WATCH_CIRCLE
    }
    static final class RerouteDecision {
        private final boolean skip;
        private final String skipReason;
        private final Integer targetStationId;
        private final OutOrderDispatchDecision dispatchDecision;
        private RerouteDecision(boolean skip,
                                String skipReason,
                                Integer targetStationId,
                                OutOrderDispatchDecision dispatchDecision) {
            this.skip = skip;
            this.skipReason = skipReason;
            this.targetStationId = targetStationId;
            this.dispatchDecision = dispatchDecision;
        }
        static RerouteDecision skip(String reason) {
            return new RerouteDecision(true, reason, null, null);
        }
        static RerouteDecision proceed(Integer targetStationId) {
            return new RerouteDecision(false, null, targetStationId, null);
        }
        static RerouteDecision proceed(Integer targetStationId,
                                       OutOrderDispatchDecision dispatchDecision) {
            return new RerouteDecision(false, null, targetStationId, dispatchDecision);
        }
        boolean skip() {
            return skip;
        }
        String skipReason() {
            return skipReason;
        }
        Integer targetStationId() {
            return targetStationId;
        }
        OutOrderDispatchDecision dispatchDecision() {
            return dispatchDecision;
        }
    }
    static final class RerouteContext {
        private final RerouteSceneType sceneType;
        private final BasDevp basDevp;
        private final StationThread stationThread;
        private final StationProtocol stationProtocol;
        private final WrkMast wrkMast;
        private final List<Integer> outOrderStationIds;
        private final Double pathLenFactor;
        private final String dispatchScene;
        private Integer dispatchDeviceNo;
        private boolean useRunBlockCommand;
        private boolean checkSuppressDispatch;
        private boolean requireOutOrderDispatchLock;
        private boolean cancelSessionBeforeDispatch;
        private boolean resetSegmentCommandsBeforeDispatch;
        private boolean clearIdleIssuedCommands;
        private boolean checkRecentDispatch;
        private String executionLockKey;
        private int executionLockSeconds;
        private StationTaskIdleTrack idleTrack;
        private RerouteContext(RerouteSceneType sceneType,
                               BasDevp basDevp,
                               StationThread stationThread,
                               StationProtocol stationProtocol,
                               WrkMast wrkMast,
                               List<Integer> outOrderStationIds,
                               Double pathLenFactor,
                               String dispatchScene) {
            this.sceneType = sceneType;
            this.basDevp = basDevp;
            this.stationThread = stationThread;
            this.stationProtocol = stationProtocol;
            this.wrkMast = wrkMast;
            this.outOrderStationIds = outOrderStationIds == null ? Collections.emptyList() : outOrderStationIds;
            this.pathLenFactor = pathLenFactor;
            this.dispatchScene = dispatchScene;
            this.dispatchDeviceNo = basDevp == null ? null : basDevp.getDevpNo();
        }
        static RerouteContext create(RerouteSceneType sceneType,
                                     BasDevp basDevp,
                                     StationThread stationThread,
                                     StationProtocol stationProtocol,
                                     WrkMast wrkMast,
                                     List<Integer> outOrderStationIds,
                                     Double pathLenFactor,
                                     String dispatchScene) {
            return new RerouteContext(sceneType, basDevp, stationThread, stationProtocol, wrkMast, outOrderStationIds, pathLenFactor, dispatchScene);
        }
        RerouteContext withDispatchDeviceNo(Integer dispatchDeviceNo) {
            this.dispatchDeviceNo = dispatchDeviceNo;
            return this;
        }
        RerouteContext withRunBlockCommand() {
            this.useRunBlockCommand = true;
            return this;
        }
        RerouteContext withSuppressDispatchGuard() {
            this.checkSuppressDispatch = true;
            return this;
        }
        RerouteContext withOutOrderDispatchLock() {
            this.requireOutOrderDispatchLock = true;
            return this;
        }
        RerouteContext withCancelSessionBeforeDispatch() {
            this.cancelSessionBeforeDispatch = true;
            return this;
        }
        RerouteContext withResetSegmentCommandsBeforeDispatch() {
            this.resetSegmentCommandsBeforeDispatch = true;
            return this;
        }
        RerouteContext clearIdleIssuedCommands(StationTaskIdleTrack idleTrack) {
            this.clearIdleIssuedCommands = true;
            this.idleTrack = idleTrack;
            return this;
        }
        RerouteContext withRecentDispatchGuard() {
            this.checkRecentDispatch = true;
            return this;
        }
        RerouteContext withExecutionLock(String executionLockKey, int executionLockSeconds) {
            this.executionLockKey = executionLockKey;
            this.executionLockSeconds = executionLockSeconds;
            return this;
        }
        RerouteSceneType sceneType() {
            return sceneType;
        }
        BasDevp basDevp() {
            return basDevp;
        }
        StationThread stationThread() {
            return stationThread;
        }
        StationProtocol stationProtocol() {
            return stationProtocol;
        }
        WrkMast wrkMast() {
            return wrkMast;
        }
        List<Integer> outOrderStationIds() {
            return outOrderStationIds;
        }
        Double pathLenFactor() {
            return pathLenFactor;
        }
        String dispatchScene() {
            return dispatchScene;
        }
        Integer dispatchDeviceNo() {
            return dispatchDeviceNo;
        }
        boolean useRunBlockCommand() {
            return useRunBlockCommand;
        }
        boolean checkSuppressDispatch() {
            return checkSuppressDispatch;
        }
        boolean requireOutOrderDispatchLock() {
            return requireOutOrderDispatchLock;
        }
        boolean cancelSessionBeforeDispatch() {
            return cancelSessionBeforeDispatch;
        }
        boolean resetSegmentCommandsBeforeDispatch() {
            return resetSegmentCommandsBeforeDispatch;
        }
        boolean clearIdleIssuedCommands() {
            return clearIdleIssuedCommands;
        }
        boolean checkRecentDispatch() {
            return checkRecentDispatch;
        }
        String executionLockKey() {
            return executionLockKey;
        }
        int executionLockSeconds() {
            return executionLockSeconds;
        }
        StationTaskIdleTrack idleTrack() {
            return idleTrack;
        }
    }
    static final class RerouteCommandPlan {
        private final boolean skip;
        private final String skipReason;
        private final StationCommand command;
        private final RerouteDecision decision;
        private final String dispatchScene;
        private RerouteCommandPlan(boolean skip,
                                   String skipReason,
                                   StationCommand command,
                                   RerouteDecision decision,
                                   String dispatchScene) {
            this.skip = skip;
            this.skipReason = skipReason;
            this.command = command;
            this.decision = decision;
            this.dispatchScene = dispatchScene;
        }
        static RerouteCommandPlan skip(String reason) {
            return new RerouteCommandPlan(true, reason, null, null, null);
        }
        static RerouteCommandPlan dispatch(StationCommand command,
                                           RerouteDecision decision,
                                           String dispatchScene) {
            return new RerouteCommandPlan(false, null, command, decision, dispatchScene);
        }
        boolean skip() {
            return skip;
        }
        String skipReason() {
            return skipReason;
        }
        StationCommand command() {
            return command;
        }
        RerouteDecision decision() {
            return decision;
        }
        String dispatchScene() {
            return dispatchScene;
        }
    }
    static final class RerouteExecutionResult {
        private final boolean skipped;
        private final String skipReason;
        private final boolean dispatched;
        private final StationCommand command;
        private final int clearedCommandCount;
        private RerouteExecutionResult(boolean skipped,
                                       String skipReason,
                                       boolean dispatched,
                                       StationCommand command,
                                       int clearedCommandCount) {
            this.skipped = skipped;
            this.skipReason = skipReason;
            this.dispatched = dispatched;
            this.command = command;
            this.clearedCommandCount = clearedCommandCount;
        }
        static RerouteExecutionResult skip(String reason) {
            return new RerouteExecutionResult(true, reason, false, null, 0);
        }
        static RerouteExecutionResult dispatched(StationCommand command,
                                                 int clearedCommandCount) {
            return new RerouteExecutionResult(false, null, true, command, clearedCommandCount);
        }
        boolean skipped() {
            return skipped;
        }
        String skipReason() {
            return skipReason;
        }
        boolean dispatched() {
            return dispatched;
        }
        StationCommand command() {
            return command;
        }
        int clearedCommandCount() {
            return clearedCommandCount;
        }
    }
    private static class OutOrderDispatchDecision {
        private final Integer targetStationId;
        private final boolean circle;