From 90402710d962aa357062ecb94649e0f277c1dfb3 Mon Sep 17 00:00:00 2001
From: Junjie <fallin.jie@qq.com>
Date: 星期日, 29 三月 2026 21:03:48 +0800
Subject: [PATCH] #

---
 src/test/java/com/zy/core/utils/station/StationDispatchRuntimeStateSupportTest.java |  139 
 src/main/java/com/zy/core/utils/station/StationOutboundDecisionSupport.java         |  615 +++
 src/main/java/com/zy/core/utils/station/model/RerouteExecutionResult.java           |   52 
 src/test/java/com/zy/asrs/controller/StationControllerTest.java                     |   48 
 src/test/java/com/zy/core/utils/station/StationOutboundDispatchProcessorTest.java   |  202 +
 src/main/java/com/zy/core/utils/station/model/DispatchLimitConfig.java              |   11 
 src/main/java/com/zy/core/thread/impl/ZyStationV5Thread.java                        |  971 -----
 src/main/java/com/zy/core/plugin/XiaosongProcess.java                               |    9 
 src/main/java/com/zy/core/utils/station/StationDispatchLoadSupport.java             |  300 +
 src/test/java/com/zy/core/thread/impl/v5/StationV5RunBlockReroutePlannerTest.java   |  110 
 src/test/java/com/zy/core/utils/station/model/StationModelTypePlacementTest.java    |   49 
 src/test/java/com/zy/core/utils/StationRerouteProcessorTest.java                    |  133 
 src/main/java/com/zy/core/utils/station/model/LoadGuardState.java                   |   52 
 src/main/java/com/zy/core/thread/impl/v5/StationV5StatusReader.java                 |  127 
 src/main/java/com/zy/core/utils/station/StationOutboundDispatchProcessor.java       |  249 +
 src/main/java/com/zy/core/utils/station/model/RerouteDecision.java                  |   47 
 src/main/java/com/zy/core/plugin/FakeProcess.java                                   |   19 
 src/test/java/com/zy/core/thread/impl/ZyStationV5ThreadTest.java                    |   34 
 src/main/java/com/zy/core/utils/StationOperateProcessUtils.java                     | 2998 -------------------
 src/main/java/com/zy/core/utils/station/model/CircleTargetCandidate.java            |   25 
 src/main/java/com/zy/core/utils/station/StationRerouteProcessor.java                |  734 ++++
 src/main/java/com/zy/core/cache/MessageQueue.java                                   |   88 
 src/main/java/com/zy/core/utils/station/model/OutOrderDispatchDecision.java         |   46 
 src/main/java/com/zy/core/utils/DualCrnOperateProcessUtils.java                     |    5 
 src/main/java/com/zy/core/dispatch/StationCommandDispatcher.java                    |  121 
 src/main/java/com/zy/core/utils/station/StationDispatchRuntimeStateSupport.java     |  231 +
 /dev/null                                                                           |   96 
 src/main/java/com/zy/asrs/controller/StationController.java                         |   19 
 src/main/java/com/zy/core/plugin/NormalProcess.java                                 |    7 
 src/test/java/com/zy/core/dispatch/StationCommandDispatcherTest.java                |  100 
 src/main/java/com/zy/core/utils/station/model/RerouteSceneType.java                 |    8 
 src/main/java/com/zy/core/dispatch/StationCommandDispatchResult.java                |   27 
 src/test/java/com/zy/core/utils/station/StationRegularDispatchProcessorTest.java    |  240 +
 src/main/java/com/zy/core/utils/station/StationTaskIdleTrack.java                   |   47 
 src/main/java/com/zy/core/utils/station/model/RerouteCommandPlan.java               |   53 
 src/main/java/com/zy/core/utils/station/model/RerouteContext.java                   |  185 +
 src/test/java/com/zy/core/utils/StationOperateProcessUtilsReroutePipelineTest.java  |  382 +
 src/main/java/com/zy/core/thread/impl/v5/StationV5RunBlockReroutePlanner.java       |  375 ++
 src/main/java/com/zy/core/utils/station/model/LoopHitResult.java                    |   20 
 src/main/java/com/zy/core/plugin/GslProcess.java                                    |    9 
 src/main/java/com/zy/core/utils/station/StationRegularDispatchProcessor.java        |  284 +
 41 files changed, 5,078 insertions(+), 4,189 deletions(-)

diff --git a/src/main/java/com/zy/asrs/controller/StationController.java b/src/main/java/com/zy/asrs/controller/StationController.java
index 1d3b63d..f2fe531 100644
--- a/src/main/java/com/zy/asrs/controller/StationController.java
+++ b/src/main/java/com/zy/asrs/controller/StationController.java
@@ -7,6 +7,8 @@
 import com.zy.asrs.service.BasDevpService;
 import com.zy.asrs.service.DeviceConfigService;
 import com.zy.common.utils.RedisUtil;
+import com.zy.core.dispatch.StationCommandDispatchResult;
+import com.zy.core.dispatch.StationCommandDispatcher;
 import com.zy.core.enums.RedisKeyType;
 import com.zy.core.enums.StationCommandType;
 import com.zy.core.model.StationObjModel;
@@ -22,10 +24,8 @@
 import com.core.common.Cools;
 import com.core.common.R;
 import com.zy.asrs.domain.param.StationCommandMoveParam;
-import com.zy.core.cache.MessageQueue;
 import com.zy.core.cache.SlaveConnection;
 import com.zy.core.enums.SlaveType;
-import com.zy.core.model.Task;
 import com.zy.core.model.command.StationCommand;
 import com.zy.core.thread.StationThread;
 
@@ -45,6 +45,8 @@
     private ConfigService configService;
     @Autowired
     private DeviceConfigService deviceConfigService;
+    @Autowired
+    private StationCommandDispatcher stationCommandDispatcher;
 
     @PostMapping("/command/move")
     public R commandMove(@RequestBody StationCommandMoveParam param) {
@@ -73,7 +75,11 @@
         if (command == null) {
             return R.error("鐢熸垚杈撻�佸懡浠ゅけ璐ワ紝璺緞涓虹┖鎴栦笉鍙揪");
         }
-        MessageQueue.offer(SlaveType.Devp, devpNo, new Task(2, command));
+        StationCommandDispatchResult dispatchResult = stationCommandDispatcher
+                .dispatch(devpNo, command, "station-controller", "manual-move");
+        if (!dispatchResult.isAccepted()) {
+            return R.error("杈撻�佸懡浠や笅鍙戝け璐�:" + dispatchResult.getReason());
+        }
         return R.ok();
     }
 
@@ -118,7 +124,11 @@
 
         StationCommand command = stationThread.getCommand(StationCommandType.WRITE_INFO, 9997, stationId, stationId, 0);
         command.setBarcode(barcode.trim());
-        MessageQueue.offer(SlaveType.Devp, devpNo, new Task(2, command));
+        StationCommandDispatchResult dispatchResult = stationCommandDispatcher
+                .dispatch(devpNo, command, "station-controller", "manual-barcode");
+        if (!dispatchResult.isAccepted()) {
+            return R.error("鏉$爜鍛戒护涓嬪彂澶辫触:" + dispatchResult.getReason());
+        }
         return R.ok();
     }
 
@@ -172,5 +182,4 @@
         }
         return null;
     }
-
 }
diff --git a/src/main/java/com/zy/core/cache/MessageQueue.java b/src/main/java/com/zy/core/cache/MessageQueue.java
index 48d96cd..668e31e 100644
--- a/src/main/java/com/zy/core/cache/MessageQueue.java
+++ b/src/main/java/com/zy/core/cache/MessageQueue.java
@@ -4,6 +4,7 @@
 import com.zy.core.model.Task;
 
 import java.util.Map;
+import java.util.Queue;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.LinkedBlockingQueue;
@@ -65,21 +66,19 @@
      * 濡傛灉鍙戠幇闃熷垪宸叉弧鏃犳硶娣诲姞鐨勮瘽锛屼細鐩存帴杩斿洖false銆�
      */
     public static boolean offer(SlaveType type, Integer id, Task task) {
+        Queue<Task> queue = resolveQueue(type, id);
+        if (queue == null) {
+            return false;
+        }
         switch (type) {
             case Crn:
-                return CRN_EXCHANGE.get(id).offer(task);
             case DualCrn:
-                return DUAL_CRN_EXCHANGE.get(id).offer(task);
             case Rgv:
-                return RGV_EXCHANGE.get(id).offer(task);
             case Devp:
-                return DEVP_EXCHANGE.get(id).offer(task);
             case Barcode:
-                return BARCODE_EXCHANGE.get(id).offer(task);
             case Led:
-                return LED_EXCHANGE.get(id).offer(task);
             case Scale:
-                return SCALE_EXCHANGE.get(id).offer(task);
+                return queue.offer(task);
             default:
                 return false;
         }
@@ -90,21 +89,19 @@
      * 鑻ラ槦鍒椾负绌猴紝杩斿洖null銆�
      */
     public static Task poll(SlaveType type, Integer id) {
+        Queue<Task> queue = resolveQueue(type, id);
+        if (queue == null) {
+            return null;
+        }
         switch (type) {
             case Crn:
-                return CRN_EXCHANGE.get(id).poll();
             case DualCrn:
-                return DUAL_CRN_EXCHANGE.get(id).poll();
             case Rgv:
-                return RGV_EXCHANGE.get(id).poll();
             case Devp:
-                return DEVP_EXCHANGE.get(id).poll();
             case Barcode:
-                return BARCODE_EXCHANGE.get(id).poll();
             case Led:
-                return LED_EXCHANGE.get(id).poll();
             case Scale:
-                return SCALE_EXCHANGE.get(id).poll();
+                return queue.poll();
             default:
                 return null;
         }
@@ -114,52 +111,75 @@
      * 鍙栧嚭鍏冪礌锛屽苟涓嶅垹闄�.
      */
     public static Task peek(SlaveType type, Integer id) {
+        Queue<Task> queue = resolveQueue(type, id);
+        if (queue == null) {
+            return null;
+        }
         switch (type) {
             case Crn:
-                return CRN_EXCHANGE.get(id).peek();
             case DualCrn:
-                return DUAL_CRN_EXCHANGE.get(id).peek();
             case Rgv:
-                return RGV_EXCHANGE.get(id).peek();
             case Devp:
-                return DEVP_EXCHANGE.get(id).peek();
             case Barcode:
-                return BARCODE_EXCHANGE.get(id).peek();
             case Led:
-                return LED_EXCHANGE.get(id).peek();
             case Scale:
-                return SCALE_EXCHANGE.get(id).peek();
+                return queue.peek();
             default:
                 return null;
         }
     }
 
     public static void clear(SlaveType type, Integer id){
+        Queue<Task> queue = resolveQueue(type, id);
+        if (queue == null) {
+            return;
+        }
         switch (type) {
             case Crn:
-                CRN_EXCHANGE.get(id).clear();
-                break;
             case DualCrn:
-                DUAL_CRN_EXCHANGE.get(id).clear();
-                break;
             case Rgv:
-                RGV_EXCHANGE.get(id).clear();
-                break;
             case Devp:
-                DEVP_EXCHANGE.get(id).clear();
-                break;
             case Barcode:
-                BARCODE_EXCHANGE.get(id).clear();
-                break;
             case Led:
-                LED_EXCHANGE.get(id).clear();
-                break;
             case Scale:
-                SCALE_EXCHANGE.get(id).clear();
+                queue.clear();
                 break;
             default:
                 break;
         }
     }
 
+    public static boolean hasExchange(SlaveType type, Integer id) {
+        return resolveQueue(type, id) != null;
+    }
+
+    public static int size(SlaveType type, Integer id) {
+        Queue<Task> queue = resolveQueue(type, id);
+        return queue == null ? 0 : queue.size();
+    }
+
+    private static Queue<Task> resolveQueue(SlaveType type, Integer id) {
+        if (type == null || id == null) {
+            return null;
+        }
+        switch (type) {
+            case Crn:
+                return CRN_EXCHANGE.get(id);
+            case DualCrn:
+                return DUAL_CRN_EXCHANGE.get(id);
+            case Rgv:
+                return RGV_EXCHANGE.get(id);
+            case Devp:
+                return DEVP_EXCHANGE.get(id);
+            case Barcode:
+                return BARCODE_EXCHANGE.get(id);
+            case Led:
+                return LED_EXCHANGE.get(id);
+            case Scale:
+                return SCALE_EXCHANGE.get(id);
+            default:
+                return null;
+        }
+    }
+
 }
diff --git a/src/main/java/com/zy/core/dispatch/StationCommandDispatchResult.java b/src/main/java/com/zy/core/dispatch/StationCommandDispatchResult.java
new file mode 100644
index 0000000..7dc095e
--- /dev/null
+++ b/src/main/java/com/zy/core/dispatch/StationCommandDispatchResult.java
@@ -0,0 +1,27 @@
+package com.zy.core.dispatch;
+
+import lombok.Data;
+
+@Data
+public class StationCommandDispatchResult {
+
+    private final boolean accepted;
+    private final String reason;
+    private final int queueDepth;
+    private final String source;
+    private final String scene;
+
+    public static StationCommandDispatchResult accepted(String reason,
+                                                        int queueDepth,
+                                                        String source,
+                                                        String scene) {
+        return new StationCommandDispatchResult(true, reason, queueDepth, source, scene);
+    }
+
+    public static StationCommandDispatchResult rejected(String reason,
+                                                        int queueDepth,
+                                                        String source,
+                                                        String scene) {
+        return new StationCommandDispatchResult(false, reason, queueDepth, source, scene);
+    }
+}
diff --git a/src/main/java/com/zy/core/dispatch/StationCommandDispatcher.java b/src/main/java/com/zy/core/dispatch/StationCommandDispatcher.java
new file mode 100644
index 0000000..8255146
--- /dev/null
+++ b/src/main/java/com/zy/core/dispatch/StationCommandDispatcher.java
@@ -0,0 +1,121 @@
+package com.zy.core.dispatch;
+
+import com.alibaba.fastjson.JSON;
+import com.core.common.Cools;
+import com.zy.common.utils.RedisUtil;
+import com.zy.core.News;
+import com.zy.core.cache.MessageQueue;
+import com.zy.core.enums.RedisKeyType;
+import com.zy.core.enums.SlaveType;
+import com.zy.core.model.Task;
+import com.zy.core.model.command.StationCommand;
+import com.zy.core.move.StationMoveCoordinator;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class StationCommandDispatcher {
+
+    private static final int STATION_COMMAND_DISPATCH_DEDUP_SECONDS = 10;
+
+    @Autowired(required = false)
+    private RedisUtil redisUtil;
+    @Autowired(required = false)
+    private StationMoveCoordinator stationMoveCoordinator;
+
+    public StationCommandDispatcher() {
+    }
+
+    public StationCommandDispatcher(RedisUtil redisUtil, StationMoveCoordinator stationMoveCoordinator) {
+        this.redisUtil = redisUtil;
+        this.stationMoveCoordinator = stationMoveCoordinator;
+    }
+
+    public StationCommandDispatchResult dispatch(Integer deviceNo,
+                                                 StationCommand command,
+                                                 String source,
+                                                 String scene) {
+        String normalizedSource = Cools.isEmpty(source) ? "unknown" : source;
+        String normalizedScene = Cools.isEmpty(scene) ? "default" : scene;
+        if (deviceNo == null || command == null) {
+            return reject("invalid-argument", 0, normalizedSource, normalizedScene, null);
+        }
+        if (!MessageQueue.hasExchange(SlaveType.Devp, deviceNo)) {
+            return reject("queue-not-initialized", 0, normalizedSource, normalizedScene, command);
+        }
+
+        String dedupKey = buildDedupKey(deviceNo, command);
+        if (!Cools.isEmpty(dedupKey) && redisUtil != null && redisUtil.get(dedupKey) != null) {
+            return reject("dedup-suppressed",
+                    MessageQueue.size(SlaveType.Devp, deviceNo),
+                    normalizedSource,
+                    normalizedScene,
+                    command);
+        }
+        if (!Cools.isEmpty(dedupKey) && redisUtil != null) {
+            redisUtil.set(dedupKey, "lock", STATION_COMMAND_DISPATCH_DEDUP_SECONDS);
+        }
+
+        boolean offered = MessageQueue.offer(SlaveType.Devp, deviceNo, new Task(2, command));
+        int queueDepth = MessageQueue.size(SlaveType.Devp, deviceNo);
+        if (!offered) {
+            if (!Cools.isEmpty(dedupKey) && redisUtil != null) {
+                redisUtil.del(dedupKey);
+            }
+            return reject("queue-offer-failed", queueDepth, normalizedSource, normalizedScene, command);
+        }
+
+        News.info("杈撻�佺珯鐐瑰懡浠ゅ叆闃熸垚鍔熴�俿ource={}锛宻cene={}锛宒eviceNo={}锛宼askNo={}锛宻tationId={}锛宼argetStaNo={}锛宑ommandType={}锛宷ueueDepth={}",
+                normalizedSource,
+                normalizedScene,
+                deviceNo,
+                command.getTaskNo(),
+                command.getStationId(),
+                command.getTargetStaNo(),
+                command.getCommandType(),
+                queueDepth);
+        return StationCommandDispatchResult.accepted("accepted", queueDepth, normalizedSource, normalizedScene);
+    }
+
+    private StationCommandDispatchResult reject(String reason,
+                                                int queueDepth,
+                                                String source,
+                                                String scene,
+                                                StationCommand command) {
+        News.warn("杈撻�佺珯鐐瑰懡浠ゅ叆闃熷け璐ャ�俽eason={}锛宻ource={}锛宻cene={}锛宒eviceNo?=N/A锛宼askNo={}锛宻tationId={}锛宼argetStaNo={}锛宑ommandType={}锛宷ueueDepth={}",
+                reason,
+                source,
+                scene,
+                command == null ? null : command.getTaskNo(),
+                command == null ? null : command.getStationId(),
+                command == null ? null : command.getTargetStaNo(),
+                command == null ? null : command.getCommandType(),
+                queueDepth);
+        return StationCommandDispatchResult.rejected(reason, queueDepth, source, scene);
+    }
+
+    private String buildDedupKey(Integer deviceNo, StationCommand command) {
+        if (deviceNo == null || command == null) {
+            return "";
+        }
+        return RedisKeyType.STATION_COMMAND_DISPATCH_DEDUP_.key
+                + deviceNo + "_"
+                + command.getTaskNo() + "_"
+                + command.getStationId() + "_"
+                + buildCommandSignatureHash(command);
+    }
+
+    private String buildCommandSignatureHash(StationCommand command) {
+        if (command == null) {
+            return "";
+        }
+        if (stationMoveCoordinator != null && command.getCommandType() != null
+                && command.getCommandType().name().startsWith("MOVE")) {
+            String pathSignatureHash = stationMoveCoordinator.buildPathSignatureHash(command);
+            if (!Cools.isEmpty(pathSignatureHash)) {
+                return pathSignatureHash;
+            }
+        }
+        return Integer.toHexString(JSON.toJSONString(command).hashCode());
+    }
+}
diff --git a/src/main/java/com/zy/core/plugin/FakeProcess.java b/src/main/java/com/zy/core/plugin/FakeProcess.java
index a136dd6..9f3e374 100644
--- a/src/main/java/com/zy/core/plugin/FakeProcess.java
+++ b/src/main/java/com/zy/core/plugin/FakeProcess.java
@@ -16,6 +16,7 @@
 import com.zy.core.News;
 import com.zy.core.cache.MessageQueue;
 import com.zy.core.cache.SlaveConnection;
+import com.zy.core.dispatch.StationCommandDispatcher;
 import com.zy.core.enums.*;
 import com.zy.core.model.StationObjModel;
 import com.zy.core.model.Task;
@@ -93,6 +94,8 @@
     private DualCrnOperateProcessUtils dualCrnOperateProcessUtils;
     @Autowired
     private StoreInTaskGenerationService storeInTaskGenerationService;
+    @Autowired
+    private StationCommandDispatcher stationCommandDispatcher;
 
     /**
      * 甯﹁秴鏃朵繚鎶ゆ墽琛屾柟娉�
@@ -254,7 +257,7 @@
                     StationCommand command = stationThread.getCommand(StationCommandType.MOVE,
                             commonService.getWorkNo(WrkIoType.FAKE_TASK_NO.id), stationId,
                             entity.getBarcodeStation().getStationId(), 0);
-                    MessageQueue.offer(SlaveType.Devp, basDevp.getDevpNo(), new Task(2, command));
+                    stationCommandDispatcher.dispatch(basDevp.getDevpNo(), command, "fake-process", "fake-enable-in");
                     redisUtil.set(RedisKeyType.GENERATE_FAKE_IN_STATION_DATA_LIMIT.key + stationId, "lock", 5);
                 }
             }
@@ -349,7 +352,7 @@
                         News.taskInfo(wrkMast.getWrkNo(), "鑾峰彇杈撻�佺嚎鍛戒护澶辫触");
                         continue;
                     }
-                    MessageQueue.offer(SlaveType.Devp, basDevp.getDevpNo(), new Task(2, command));
+                    stationCommandDispatcher.dispatch(basDevp.getDevpNo(), command, "fake-process", "fake-in-task");
                     redisUtil.set(RedisKeyType.GENERATE_FAKE_IN_TASK_LIMIT.key + stationId, "lock", 5);
                 }
             }
@@ -459,7 +462,7 @@
             News.taskInfo(wrkMast.getWrkNo(), "鑾峰彇杈撻�佺嚎鍛戒护澶辫触");
             return;
         }
-        MessageQueue.offer(SlaveType.Devp, context.getBasDevp().getDevpNo(), new Task(2, command));
+        stationCommandDispatcher.dispatch(context.getBasDevp().getDevpNo(), command, "fake-process", "write-info");
     }
 
     // 璁$畻鎵�鏈夌珯鐐瑰仠鐣欐椂闂�
@@ -521,7 +524,7 @@
                         continue;
                     }
 
-                    MessageQueue.offer(SlaveType.Devp, stationObjModel.getDeviceNo(), new Task(2, command));
+                    stationCommandDispatcher.dispatch(stationObjModel.getDeviceNo(), command, "fake-process", "out-station-reset");
                     redisUtil.set(
                             RedisKeyType.CHECK_OUT_STATION_STAY_TIME_OUT_LIMIT.key + stationObjModel.getStationId(),
                             "lock", 10);
@@ -584,7 +587,7 @@
 
                 WrkMast wrkMast = wrkMastService.selectByWorkNo(stationProtocol.getTaskNo());
                 if (wrkMast == null) {
-                    MessageQueue.offer(SlaveType.Devp, stationObjModel.getDeviceNo(), new Task(2, command));
+                    stationCommandDispatcher.dispatch(stationObjModel.getDeviceNo(), command, "fake-process", "in-station-reset-task-over");
                     redisUtil.set(
                             RedisKeyType.CHECK_IN_STATION_STAY_TIME_OUT_LIMIT.key + stationObjModel.getStationId(),
                             "lock", 10);
@@ -605,7 +608,7 @@
                                 continue;
                             }
 
-                            MessageQueue.offer(SlaveType.Devp, stationObjModel.getDeviceNo(), new Task(2, command));
+                            stationCommandDispatcher.dispatch(stationObjModel.getDeviceNo(), command, "fake-process", "in-station-reset-crn-fetch");
                             redisUtil.set(RedisKeyType.CHECK_IN_STATION_STAY_TIME_OUT_LIMIT.key
                                     + stationObjModel.getStationId(), "lock", 10);
                             News.info("杈撻�佺珯鐐归噸缃懡浠や笅鍙戞垚鍔�(crn_fetch)锛岀珯鐐瑰彿={}锛屽懡浠ゆ暟鎹�={}", stationObjModel.getStationId(),
@@ -632,7 +635,7 @@
                                 continue;
                             }
 
-                            MessageQueue.offer(SlaveType.Devp, stationObjModel.getDeviceNo(), new Task(2, command));
+                            stationCommandDispatcher.dispatch(stationObjModel.getDeviceNo(), command, "fake-process", "in-station-reset-dual-crn-fetch");
                             redisUtil.set(RedisKeyType.CHECK_IN_STATION_STAY_TIME_OUT_LIMIT.key
                                     + stationObjModel.getStationId(), "lock", 10);
                             News.info("杈撻�佺珯鐐归噸缃懡浠や笅鍙戞垚鍔�(crn_fetch)锛岀珯鐐瑰彿={}锛屽懡浠ゆ暟鎹�={}", stationObjModel.getStationId(),
@@ -699,7 +702,7 @@
                         // 鐢熸垚浠跨湡绔欑偣鏁版嵁
                         StationCommand command = stationThread.getCommand(StationCommandType.WRITE_INFO, 9998,
                                 wrkMast.getSourceStaNo(), 0, 0);
-                        MessageQueue.offer(SlaveType.Devp, stationObjModel.getDeviceNo(), new Task(2, command));
+                        stationCommandDispatcher.dispatch(stationObjModel.getDeviceNo(), command, "fake-process", "crn-out-complete-write-info");
                         redisUtil.set(RedisKeyType.CRN_OUT_TASK_COMPLETE_STATION_INFO.key + wrkMast.getWrkNo(), JSON.toJSONString(stationObjModel, SerializerFeature.DisableCircularReferenceDetect), 60 * 60 * 24);
                     }
                 } else if (wrkMast.getWrkSts() == WrkStsType.LOC_MOVE_RUN.sts) {
diff --git a/src/main/java/com/zy/core/plugin/GslProcess.java b/src/main/java/com/zy/core/plugin/GslProcess.java
index 5d5d1a7..ea52095 100644
--- a/src/main/java/com/zy/core/plugin/GslProcess.java
+++ b/src/main/java/com/zy/core/plugin/GslProcess.java
@@ -8,14 +8,13 @@
 import com.zy.common.service.CommonService;
 import com.zy.common.utils.RedisUtil;
 import com.zy.core.News;
-import com.zy.core.cache.MessageQueue;
 import com.zy.core.cache.SlaveConnection;
+import com.zy.core.dispatch.StationCommandDispatcher;
 import com.zy.core.enums.RedisKeyType;
 import com.zy.core.enums.SlaveType;
 import com.zy.core.enums.StationCommandType;
 import com.zy.core.enums.WrkIoType;
 import com.zy.core.model.StationObjModel;
-import com.zy.core.model.Task;
 import com.zy.core.model.command.StationCommand;
 import com.zy.core.model.protocol.StationProtocol;
 import com.zy.core.plugin.api.MainProcessPluginApi;
@@ -49,6 +48,8 @@
     private RedisUtil redisUtil;
     @Autowired
     private StoreInTaskGenerationService storeInTaskGenerationService;
+    @Autowired
+    private StationCommandDispatcher stationCommandDispatcher;
 
     @Override
     public void run() {
@@ -115,7 +116,7 @@
             News.taskInfo(stationProtocol.getTaskNo(), "{}宸ヤ綔,鑾峰彇杈撻�佺嚎鍛戒护澶辫触", stationProtocol.getTaskNo());
             return false;
         }
-        MessageQueue.offer(SlaveType.Devp, context.getBasDevp().getDevpNo(), new Task(2, command));
+        stationCommandDispatcher.dispatch(context.getBasDevp().getDevpNo(), command, "gsl-process", "station-back");
         News.taskInfo(stationProtocol.getTaskNo(), "{}鎵爜寮傚父锛屽凡閫�鍥炶嚦{}", backStation.getStationId());
         redisUtil.set(RedisKeyType.GENERATE_STATION_BACK_LIMIT.key + stationProtocol.getStationId(), "lock", 10);
         return true;
@@ -163,7 +164,7 @@
                         && stationProtocol.isEnableIn()
                 ) {
                     StationCommand command = stationThread.getCommand(StationCommandType.MOVE, commonService.getWorkNo(WrkIoType.ENABLE_IN.id), stationId, entity.getBarcodeStation().getStationId(), 0);
-                    MessageQueue.offer(SlaveType.Devp, basDevp.getDevpNo(), new Task(2, command));
+                    stationCommandDispatcher.dispatch(basDevp.getDevpNo(), command, "gsl-process", "enable-in");
                     redisUtil.set(RedisKeyType.GENERATE_ENABLE_IN_STATION_DATA_LIMIT.key + stationId, "lock", 15);
                     News.info("{}绔欑偣鍚姩鍏ュ簱鎴愬姛锛屾暟鎹寘:{}", stationId, JSON.toJSONString(command));
                 }
diff --git a/src/main/java/com/zy/core/plugin/NormalProcess.java b/src/main/java/com/zy/core/plugin/NormalProcess.java
index 21221e3..32e3481 100644
--- a/src/main/java/com/zy/core/plugin/NormalProcess.java
+++ b/src/main/java/com/zy/core/plugin/NormalProcess.java
@@ -13,14 +13,13 @@
 import com.zy.common.service.CommonService;
 import com.zy.common.utils.RedisUtil;
 import com.zy.core.News;
-import com.zy.core.cache.MessageQueue;
 import com.zy.core.cache.SlaveConnection;
+import com.zy.core.dispatch.StationCommandDispatcher;
 import com.zy.core.enums.RedisKeyType;
 import com.zy.core.enums.SlaveType;
 import com.zy.core.enums.StationCommandType;
 import com.zy.core.enums.WrkIoType;
 import com.zy.core.model.StationObjModel;
-import com.zy.core.model.Task;
 import com.zy.core.model.command.StationCommand;
 import com.zy.core.model.protocol.StationProtocol;
 import com.zy.core.plugin.api.MainProcessPluginApi;
@@ -59,6 +58,8 @@
     private WmsOperateUtils wmsOperateUtils;
     @Autowired
     private StoreInTaskGenerationService storeInTaskGenerationService;
+    @Autowired
+    private StationCommandDispatcher stationCommandDispatcher;
 
     @Override
     public void run() {
@@ -135,7 +136,7 @@
                         && stationProtocol.isEnableIn()
                 ) {
                     StationCommand command = stationThread.getCommand(StationCommandType.MOVE, commonService.getWorkNo(WrkIoType.ENABLE_IN.id), stationId, entity.getBarcodeStation().getStationId(), 0);
-                    MessageQueue.offer(SlaveType.Devp, basDevp.getDevpNo(), new Task(2, command));
+                    stationCommandDispatcher.dispatch(basDevp.getDevpNo(), command, "normal-process", "enable-in");
                     redisUtil.set(RedisKeyType.GENERATE_ENABLE_IN_STATION_DATA_LIMIT.key + stationId, "lock", 15);
                     News.info("{}绔欑偣鍚姩鍏ュ簱鎴愬姛锛屾暟鎹寘:{}", stationId, JSON.toJSONString(command));
                 }
diff --git a/src/main/java/com/zy/core/plugin/XiaosongProcess.java b/src/main/java/com/zy/core/plugin/XiaosongProcess.java
index 5fcf9d6..cf5670f 100644
--- a/src/main/java/com/zy/core/plugin/XiaosongProcess.java
+++ b/src/main/java/com/zy/core/plugin/XiaosongProcess.java
@@ -13,14 +13,13 @@
 import com.zy.common.service.CommonService;
 import com.zy.common.utils.RedisUtil;
 import com.zy.core.News;
-import com.zy.core.cache.MessageQueue;
 import com.zy.core.cache.SlaveConnection;
+import com.zy.core.dispatch.StationCommandDispatcher;
 import com.zy.core.enums.RedisKeyType;
 import com.zy.core.enums.SlaveType;
 import com.zy.core.enums.StationCommandType;
 import com.zy.core.enums.WrkIoType;
 import com.zy.core.model.StationObjModel;
-import com.zy.core.model.Task;
 import com.zy.core.model.command.StationCommand;
 import com.zy.core.model.protocol.StationProtocol;
 import com.zy.core.plugin.api.MainProcessPluginApi;
@@ -62,6 +61,8 @@
     private DualCrnOperateProcessUtils dualCrnOperateProcessUtils;
     @Autowired
     private StoreInTaskGenerationService storeInTaskGenerationService;
+    @Autowired
+    private StationCommandDispatcher stationCommandDispatcher;
 
     @Override
     public void run() {
@@ -130,7 +131,7 @@
             News.taskInfo(stationProtocol.getTaskNo(), "{}宸ヤ綔,鑾峰彇杈撻�佺嚎鍛戒护澶辫触", stationProtocol.getTaskNo());
             return false;
         }
-        MessageQueue.offer(SlaveType.Devp, context.getBasDevp().getDevpNo(), new Task(2, command));
+        stationCommandDispatcher.dispatch(context.getBasDevp().getDevpNo(), command, "xiaosong-process", "station-back");
         News.taskInfo(stationProtocol.getTaskNo(), "{}鎵爜寮傚父锛屽凡閫�鍥炶嚦{}", backStation.getStationId());
         redisUtil.set(RedisKeyType.GENERATE_STATION_BACK_LIMIT.key + stationProtocol.getTaskNo(), "lock", 10);
         return true;
@@ -170,7 +171,7 @@
                         && stationProtocol.isEnableIn()
                 ) {
                     StationCommand command = stationThread.getCommand(StationCommandType.MOVE, commonService.getWorkNo(WrkIoType.ENABLE_IN.id), stationId, entity.getBarcodeStation().getStationId(), 0);
-                    MessageQueue.offer(SlaveType.Devp, basDevp.getDevpNo(), new Task(2, command));
+                    stationCommandDispatcher.dispatch(basDevp.getDevpNo(), command, "xiaosong-process", "enable-in");
                     redisUtil.set(RedisKeyType.GENERATE_ENABLE_IN_STATION_DATA_LIMIT.key + stationId, "lock", 15);
                     News.info("{}绔欑偣鍚姩鍏ュ簱鎴愬姛锛屾暟鎹寘:{}", stationId, JSON.toJSONString(command));
                 }
diff --git a/src/main/java/com/zy/core/thread/impl/ZyStationV5Thread.java b/src/main/java/com/zy/core/thread/impl/ZyStationV5Thread.java
index 1e8f4c4..17bd7f8 100644
--- a/src/main/java/com/zy/core/thread/impl/ZyStationV5Thread.java
+++ b/src/main/java/com/zy/core/thread/impl/ZyStationV5Thread.java
@@ -2,26 +2,14 @@
 
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
-import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
-import com.core.common.Cools;
-import com.core.common.DateUtils;
 import com.core.common.SpringUtils;
-import com.zy.asrs.domain.vo.StationCycleCapacityVo;
-import com.zy.asrs.domain.vo.StationCycleLoopVo;
-import com.zy.asrs.entity.BasDevp;
 import com.zy.asrs.entity.BasStationOpt;
 import com.zy.asrs.entity.DeviceConfig;
-import com.zy.asrs.entity.DeviceDataLog;
-import com.zy.asrs.service.BasDevpService;
 import com.zy.asrs.service.BasStationOptService;
-import com.zy.asrs.service.StationCycleCapacityService;
-import com.zy.asrs.utils.Utils;
 import com.zy.common.model.NavigateNode;
 import com.zy.common.utils.NavigateUtils;
 import com.zy.common.utils.RedisUtil;
 import com.zy.core.cache.MessageQueue;
-import com.zy.core.cache.OutputQueue;
-import com.zy.core.enums.RedisKeyType;
 import com.zy.core.enums.SlaveType;
 import com.zy.core.enums.StationCommandType;
 import com.zy.core.model.CommandResponse;
@@ -33,26 +21,19 @@
 import com.zy.core.network.ZyStationConnectDriver;
 import com.zy.core.network.entity.ZyStationStatusEntity;
 import com.zy.core.service.StationTaskLoopService;
+import com.zy.core.thread.impl.v5.StationV5RunBlockReroutePlanner;
 import com.zy.core.thread.impl.v5.StationV5SegmentExecutor;
+import com.zy.core.thread.impl.v5.StationV5StatusReader;
 import com.zy.core.thread.support.RecentStationArrivalTracker;
-import com.zy.core.trace.StationTaskTraceRegistry;
-import com.zy.core.utils.DeviceLogRedisKeyBuilder;
 import lombok.Data;
 import lombok.extern.slf4j.Slf4j;
 
-import java.text.MessageFormat;
 import java.util.ArrayList;
-import java.util.ArrayDeque;
-import java.util.Collections;
 import java.util.Date;
-import java.util.Deque;
 import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
-import java.util.Set;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 
@@ -60,42 +41,35 @@
 @Slf4j
 public class ZyStationV5Thread implements Runnable, com.zy.core.thread.StationThread {
 
-    private static final int RUN_BLOCK_REROUTE_STATE_EXPIRE_SECONDS = 60 * 60 * 24;
-    private static final int SHORT_PATH_REPEAT_AVOID_THRESHOLD = 2;
-    private static final int LOOP_REPEAT_TRIGGER_COUNT = 3;
-    private static final int LOCAL_LOOP_NEIGHBOR_HOP = 3;
+    private static final int SEGMENT_EXECUTOR_POOL_SIZE = 64;
 
-    private List<StationProtocol> statusList = new ArrayList<>();
     private DeviceConfig deviceConfig;
     private RedisUtil redisUtil;
     private ZyStationConnectDriver zyStationConnectDriver;
-    private int deviceLogCollectTime = 200;
-    private boolean initStatus = false;
-    private long deviceDataLogTime = System.currentTimeMillis();
-    private ExecutorService executor = Executors.newFixedThreadPool(9999);
+    private final ExecutorService executor = Executors.newFixedThreadPool(SEGMENT_EXECUTOR_POOL_SIZE);
     private StationV5SegmentExecutor segmentExecutor;
     private final RecentStationArrivalTracker recentArrivalTracker;
+    private final StationV5StatusReader statusReader;
+    private final StationV5RunBlockReroutePlanner runBlockReroutePlanner;
 
     public ZyStationV5Thread(DeviceConfig deviceConfig, RedisUtil redisUtil) {
         this.deviceConfig = deviceConfig;
         this.redisUtil = redisUtil;
         this.recentArrivalTracker = new RecentStationArrivalTracker(redisUtil);
         this.segmentExecutor = new StationV5SegmentExecutor(deviceConfig, redisUtil, this::sendCommand);
+        this.statusReader = new StationV5StatusReader(deviceConfig, redisUtil, recentArrivalTracker);
+        this.runBlockReroutePlanner = new StationV5RunBlockReroutePlanner(redisUtil);
     }
 
     @Override
     @SuppressWarnings("InfiniteLoopStatement")
     public void run() {
         this.connect();
-        deviceLogCollectTime = Utils.getDeviceLogCollectTime();
 
         Thread readThread = new Thread(() -> {
             while (true) {
                 try {
-                    if (initStatus) {
-                        deviceLogCollectTime = Utils.getDeviceLogCollectTime();
-                    }
-                    readStatus();
+                    statusReader.readStatus(zyStationConnectDriver);
                     Thread.sleep(100);
                 } catch (Exception e) {
                     log.error("StationV5Thread Fail", e);
@@ -107,15 +81,7 @@
         Thread processThread = new Thread(() -> {
             while (true) {
                 try {
-                    int step = 1;
-                    Task task = MessageQueue.poll(SlaveType.Devp, deviceConfig.getDeviceNo());
-                    if (task != null) {
-                        step = task.getStep();
-                    }
-                    if (step == 2) {
-                        StationCommand cmd = (StationCommand) task.getData();
-                        executor.submit(() -> segmentExecutor.execute(cmd));
-                    }
+                    pollAndDispatchQueuedCommand();
                     Thread.sleep(100);
                 } catch (Exception e) {
                     log.error("StationV5Process Fail", e);
@@ -123,84 +89,6 @@
             }
         }, "DevpProcess-" + deviceConfig.getDeviceNo());
         processThread.start();
-    }
-
-    private void readStatus() {
-        if (zyStationConnectDriver == null) {
-            return;
-        }
-
-        if (statusList.isEmpty()) {
-            BasDevpService basDevpService = null;
-            try {
-                basDevpService = SpringUtils.getBean(BasDevpService.class);
-            } catch (Exception ignore) {
-            }
-            if (basDevpService == null) {
-                return;
-            }
-
-            BasDevp basDevp = basDevpService
-                    .getOne(new QueryWrapper<BasDevp>().eq("devp_no", deviceConfig.getDeviceNo()));
-            if (basDevp == null) {
-                return;
-            }
-
-            List<ZyStationStatusEntity> list = JSONObject.parseArray(basDevp.getStationList(), ZyStationStatusEntity.class);
-            for (ZyStationStatusEntity entity : list) {
-                StationProtocol stationProtocol = new StationProtocol();
-                stationProtocol.setStationId(entity.getStationId());
-                statusList.add(stationProtocol);
-            }
-            initStatus = true;
-        }
-
-        List<ZyStationStatusEntity> zyStationStatusEntities = zyStationConnectDriver.getStatus();
-        for (ZyStationStatusEntity statusEntity : zyStationStatusEntities) {
-            for (StationProtocol stationProtocol : statusList) {
-                if (stationProtocol.getStationId().equals(statusEntity.getStationId())) {
-                    stationProtocol.setTaskNo(statusEntity.getTaskNo());
-                    stationProtocol.setTargetStaNo(statusEntity.getTargetStaNo());
-                    stationProtocol.setAutoing(statusEntity.isAutoing());
-                    stationProtocol.setLoading(statusEntity.isLoading());
-                    stationProtocol.setInEnable(statusEntity.isInEnable());
-                    stationProtocol.setOutEnable(statusEntity.isOutEnable());
-                    stationProtocol.setEmptyMk(statusEntity.isEmptyMk());
-                    stationProtocol.setFullPlt(statusEntity.isFullPlt());
-                    stationProtocol.setPalletHeight(statusEntity.getPalletHeight());
-                    stationProtocol.setError(statusEntity.getError());
-                    stationProtocol.setErrorMsg(statusEntity.getErrorMsg());
-                    stationProtocol.setBarcode(statusEntity.getBarcode());
-                    stationProtocol.setRunBlock(statusEntity.isRunBlock());
-                    stationProtocol.setEnableIn(statusEntity.isEnableIn());
-                    stationProtocol.setWeight(statusEntity.getWeight());
-                    stationProtocol.setTaskWriteIdx(statusEntity.getTaskWriteIdx());
-                    stationProtocol.setTaskBufferItems(statusEntity.getTaskBufferItems());
-                    recentArrivalTracker.observe(statusEntity.getStationId(), statusEntity.getTaskNo(), statusEntity.isLoading());
-                }
-
-                if (!Cools.isEmpty(stationProtocol.getSystemWarning())) {
-                    if (stationProtocol.isAutoing() && !stationProtocol.isLoading()) {
-                        stationProtocol.setSystemWarning("");
-                    }
-                }
-            }
-        }
-
-        OutputQueue.DEVP.offer(MessageFormat.format("銆恵0}銆慬id:{1}] <<<<< 瀹炴椂鏁版嵁鏇存柊鎴愬姛",
-                DateUtils.convert(new Date()), deviceConfig.getDeviceNo()));
-
-        if (System.currentTimeMillis() - deviceDataLogTime > deviceLogCollectTime) {
-            DeviceDataLog deviceDataLog = new DeviceDataLog();
-            deviceDataLog.setOriginData(JSON.toJSONString(zyStationStatusEntities));
-            deviceDataLog.setWcsData(JSON.toJSONString(statusList));
-            deviceDataLog.setType(String.valueOf(SlaveType.Devp));
-            deviceDataLog.setDeviceNo(deviceConfig.getDeviceNo());
-            deviceDataLog.setCreateTime(new Date());
-
-            redisUtil.set(DeviceLogRedisKeyBuilder.build(deviceDataLog), deviceDataLog, 60 * 60 * 24);
-            deviceDataLogTime = System.currentTimeMillis();
-        }
     }
 
     @Override
@@ -226,16 +114,31 @@
 
     @Override
     public List<StationProtocol> getStatus() {
-        return statusList;
+        return statusReader.getStatusList();
     }
 
     @Override
     public Map<Integer, StationProtocol> getStatusMap() {
         Map<Integer, StationProtocol> map = new HashMap<>();
-        for (StationProtocol stationProtocol : statusList) {
+        for (StationProtocol stationProtocol : statusReader.getStatusList()) {
             map.put(stationProtocol.getStationId(), stationProtocol);
         }
         return map;
+    }
+
+    private void pollAndDispatchQueuedCommand() {
+        Task task = MessageQueue.poll(SlaveType.Devp, deviceConfig.getDeviceNo());
+        if (task == null || task.getStep() == null || task.getStep() != 2) {
+            return;
+        }
+        submitSegmentCommand((StationCommand) task.getData());
+    }
+
+    private void submitSegmentCommand(StationCommand command) {
+        if (command == null || executor == null || segmentExecutor == null) {
+            return;
+        }
+        executor.submit(() -> segmentExecutor.execute(command));
     }
 
     @Override
@@ -294,7 +197,6 @@
             return getCommand(StationCommandType.MOVE, taskNo, stationId, targetStationId, palletSize, pathLenFactor);
         }
 
-        RunBlockRerouteState rerouteState = loadRunBlockRerouteState(taskNo, stationId);
         StationTaskLoopService taskLoopService = loadStationTaskLoopService();
         StationTaskLoopService.LoopEvaluation loopEvaluation = taskLoopService == null
                 ? new StationTaskLoopService.LoopEvaluation(taskNo, stationId, StationTaskLoopService.LoopIdentitySnapshot.empty(), 0, 0, false)
@@ -305,61 +207,44 @@
                 loopEvaluation.getLoopIdentity().getScopeType(),
                 loopEvaluation.getLoopIdentity().getLocalStationCount(),
                 loopEvaluation.getLoopIdentity().getSourceLoopStationCount());
-        rerouteState.setTaskNo(taskNo);
-        rerouteState.setBlockStationId(stationId);
-        rerouteState.setLastTargetStationId(targetStationId);
-        rerouteState.setPlanCount((rerouteState.getPlanCount() == null ? 0 : rerouteState.getPlanCount()) + 1);
-        rerouteState.setLastPlanTime(System.currentTimeMillis());
-
         List<List<NavigateNode>> candidatePathList = calcCandidatePathNavigateNodes(taskNo, stationId, targetStationId, pathLenFactor);
-        if (candidatePathList.isEmpty()) {
-            saveRunBlockRerouteState(rerouteState);
+        List<StationCommand> candidateCommandList = new ArrayList<>();
+        for (List<NavigateNode> candidatePath : candidatePathList) {
+            StationCommand rerouteCommand = buildMoveCommand(taskNo, stationId, targetStationId, palletSize, candidatePath);
+            if (rerouteCommand == null || rerouteCommand.getNavigatePath() == null || rerouteCommand.getNavigatePath().isEmpty()) {
+                continue;
+            }
+            candidateCommandList.add(rerouteCommand);
+        }
+
+        StationV5RunBlockReroutePlanner.PlanResult planResult = runBlockReroutePlanner.plan(
+                taskNo,
+                stationId,
+                loopEvaluation,
+                candidateCommandList
+        );
+        if (candidateCommandList.isEmpty()) {
             log.warn("杈撻�佺嚎鍫靛閲嶈鍒掑け璐ワ紝鍊欓�夎矾寰勪负绌猴紝taskNo={}, planCount={}, stationId={}, targetStationId={}",
-                    taskNo, rerouteState.getPlanCount(), stationId, targetStationId);
+                    taskNo, planResult.getPlanCount(), stationId, targetStationId);
             return null;
         }
 
-        StationCommand rerouteCommand = selectAvailableRerouteCommand(
-                rerouteState,
-                loopEvaluation,
-                candidatePathList,
-                taskNo,
-                stationId,
-                targetStationId,
-                palletSize
-        );
-        if (rerouteCommand == null) {
-            log.info("杈撻�佺嚎鍫靛閲嶈鍒掑�欓�夎矾绾垮凡鍏ㄩ儴璇曡繃锛岄噸缃矾绾垮巻鍙插悗閲嶆柊寮�濮嬶紝taskNo={}, planCount={}, stationId={}, targetStationId={}",
-                    taskNo, rerouteState.getPlanCount(), stationId, targetStationId);
-            rerouteState.resetIssuedRoutes();
-            rerouteCommand = selectAvailableRerouteCommand(
-                    rerouteState,
-                    loopEvaluation,
-                    candidatePathList,
-                    taskNo,
-                    stationId,
-                    targetStationId,
-                    palletSize
-            );
-        }
-
+        StationCommand rerouteCommand = planResult.getCommand();
         if (rerouteCommand != null) {
-            saveRunBlockRerouteState(rerouteState);
             if (taskLoopService != null) {
                 taskLoopService.recordLoopIssue(loopEvaluation, "RUN_BLOCK_REROUTE");
             }
             log.info("杈撻�佺嚎鍫靛閲嶈鍒掗�変腑鍊欓�夎矾绾匡紝taskNo={}, planCount={}, stationId={}, targetStationId={}, route={}",
-                    taskNo, rerouteState.getPlanCount(), stationId, targetStationId, JSON.toJSONString(rerouteCommand.getNavigatePath()));
+                    taskNo, planResult.getPlanCount(), stationId, targetStationId, JSON.toJSONString(rerouteCommand.getNavigatePath()));
             return rerouteCommand;
         }
 
-        saveRunBlockRerouteState(rerouteState);
         log.warn("杈撻�佺嚎鍫靛閲嶈鍒掓湭鎵惧埌鍙笅鍙戣矾绾匡紝taskNo={}, planCount={}, stationId={}, targetStationId={}, triedRoutes={}",
                 taskNo,
-                rerouteState.getPlanCount(),
+                planResult.getPlanCount(),
                 stationId,
                 targetStationId,
-                JSON.toJSONString(rerouteState.getIssuedRoutePathList()));
+                JSON.toJSONString(planResult.getIssuedRoutePathList()));
         return null;
     }
 
@@ -395,6 +280,8 @@
                             stationId, item.getSlotIdx(), item.getTaskNo());
                     continue;
                 }else {
+                    item.setTaskNo(0);
+                    item.setTargetStaNo(0);
                     success = true;
                     log.warn("杈撻�佺珯缂撳瓨鍖烘畫鐣欒矾寰勬竻鐞嗘垚鍔熴�俿tationId={}, slotIdx={}, taskNo={}",
                             stationId, item.getSlotIdx(), item.getTaskNo());
@@ -529,769 +416,11 @@
         return stationCommand;
     }
 
-    private StationCommand selectAvailableRerouteCommand(RunBlockRerouteState rerouteState,
-                                                         StationTaskLoopService.LoopEvaluation loopEvaluation,
-                                                         List<List<NavigateNode>> candidatePathList,
-                                                         Integer taskNo,
-                                                         Integer stationId,
-                                                         Integer targetStationId,
-                                                         Integer palletSize) {
-        if (rerouteState == null || candidatePathList == null || candidatePathList.isEmpty()) {
-            return null;
-        }
-
-        Set<String> issuedRouteSignatureSet = rerouteState.getIssuedRouteSignatureSet();
-        List<RerouteCandidateCommand> candidateCommandList = new ArrayList<>();
-        for (List<NavigateNode> candidatePath : candidatePathList) {
-            StationCommand rerouteCommand = buildMoveCommand(taskNo, stationId, targetStationId, palletSize, candidatePath);
-            if (rerouteCommand == null || rerouteCommand.getNavigatePath() == null || rerouteCommand.getNavigatePath().isEmpty()) {
-                continue;
-            }
-            String routeSignature = buildPathSignature(rerouteCommand.getNavigatePath());
-            if (Cools.isEmpty(routeSignature)) {
-                continue;
-            }
-            RerouteCandidateCommand candidateCommand = new RerouteCandidateCommand();
-            candidateCommand.setCommand(rerouteCommand);
-            candidateCommand.setRouteSignature(routeSignature);
-            candidateCommand.setPathLength(rerouteCommand.getNavigatePath().size());
-            candidateCommand.setIssuedCount(rerouteState.getRouteIssueCountMap().getOrDefault(routeSignature, 0));
-            candidateCommand.setLoopFingerprint(loopEvaluation.getLoopIdentity().getLoopFingerprint());
-            candidateCommand.setLoopIssuedCount(loopEvaluation.getExpectedLoopIssueCount());
-            candidateCommand.setLoopTriggered(loopEvaluation.isLargeLoopTriggered());
-            candidateCommand.setCurrentLoopHitCount(countCurrentLoopStationHit(
-                    rerouteCommand.getNavigatePath(),
-                    loopEvaluation.getLoopIdentity().getStationIdSet()
-            ));
-            candidateCommandList.add(candidateCommand);
-        }
-        if (candidateCommandList.isEmpty()) {
-            return null;
-        }
-
-        List<RerouteCandidateCommand> orderedCandidateCommandList = reorderCandidateCommandsForLoopRelease(candidateCommandList);
-        for (RerouteCandidateCommand candidateCommand : orderedCandidateCommandList) {
-            if (candidateCommand == null || candidateCommand.getCommand() == null) {
-                continue;
-            }
-            if (issuedRouteSignatureSet.contains(candidateCommand.getRouteSignature())) {
-                continue;
-            }
-
-            StationCommand rerouteCommand = candidateCommand.getCommand();
-            issuedRouteSignatureSet.add(candidateCommand.getRouteSignature());
-            rerouteState.getIssuedRoutePathList().add(new ArrayList<>(rerouteCommand.getNavigatePath()));
-            rerouteState.setLastSelectedRoute(new ArrayList<>(rerouteCommand.getNavigatePath()));
-            rerouteState.getRouteIssueCountMap().put(
-                    candidateCommand.getRouteSignature(),
-                    rerouteState.getRouteIssueCountMap().getOrDefault(candidateCommand.getRouteSignature(), 0) + 1
-            );
-            return rerouteCommand;
-        }
-        return null;
-    }
-
-    private List<RerouteCandidateCommand> reorderCandidateCommandsForLoopRelease(List<RerouteCandidateCommand> candidateCommandList) {
-        if (candidateCommandList == null || candidateCommandList.isEmpty()) {
-            return new ArrayList<>();
-        }
-
-        int shortestPathLength = Integer.MAX_VALUE;
-        int shortestPathLoopHitCount = Integer.MAX_VALUE;
-        boolean shortestPathOverused = false;
-        boolean currentLoopOverused = false;
-        boolean hasLongerCandidate = false;
-        for (RerouteCandidateCommand candidateCommand : candidateCommandList) {
-            if (candidateCommand == null || candidateCommand.getPathLength() == null || candidateCommand.getPathLength() <= 0) {
-                continue;
-            }
-            shortestPathLength = Math.min(shortestPathLength, candidateCommand.getPathLength());
-        }
-        if (shortestPathLength == Integer.MAX_VALUE) {
-            return candidateCommandList;
-        }
-
-        for (RerouteCandidateCommand candidateCommand : candidateCommandList) {
-            if (candidateCommand == null || candidateCommand.getPathLength() == null || candidateCommand.getPathLength() <= 0) {
-                continue;
-            }
-            if (candidateCommand.getPathLength() == shortestPathLength) {
-                shortestPathLoopHitCount = Math.min(shortestPathLoopHitCount, safeInt(candidateCommand.getCurrentLoopHitCount()));
-            }
-            if (candidateCommand.getPathLength() > shortestPathLength) {
-                hasLongerCandidate = true;
-            }
-            if (candidateCommand.getPathLength() == shortestPathLength
-                    && candidateCommand.getIssuedCount() != null
-                    && candidateCommand.getIssuedCount() >= SHORT_PATH_REPEAT_AVOID_THRESHOLD) {
-                shortestPathOverused = true;
-            }
-            if (!Cools.isEmpty(candidateCommand.getLoopFingerprint())
-                    && Boolean.TRUE.equals(candidateCommand.getLoopTriggered())) {
-                currentLoopOverused = true;
-            }
-        }
-        if (!shortestPathOverused && !currentLoopOverused) {
-            return candidateCommandList;
-        }
-        if (shortestPathLoopHitCount == Integer.MAX_VALUE) {
-            shortestPathLoopHitCount = 0;
-        }
-
-        boolean hasLoopExitCandidate = false;
-        for (RerouteCandidateCommand candidateCommand : candidateCommandList) {
-            if (candidateCommand == null) {
-                continue;
-            }
-            if (safeInt(candidateCommand.getCurrentLoopHitCount()) < shortestPathLoopHitCount) {
-                hasLoopExitCandidate = true;
-                break;
-            }
-        }
-        if (!hasLongerCandidate && !hasLoopExitCandidate) {
-            return candidateCommandList;
-        }
-
-        List<RerouteCandidateCommand> reorderedList = new ArrayList<>();
-        if (currentLoopOverused && hasLoopExitCandidate) {
-            for (RerouteCandidateCommand candidateCommand : candidateCommandList) {
-                if (candidateCommand == null) {
-                    continue;
-                }
-                if (safeInt(candidateCommand.getCurrentLoopHitCount()) < shortestPathLoopHitCount) {
-                    appendCandidateIfAbsent(reorderedList, candidateCommand);
-                }
-            }
-        }
-        for (RerouteCandidateCommand candidateCommand : candidateCommandList) {
-            if (candidateCommand != null
-                    && candidateCommand.getPathLength() != null
-                    && candidateCommand.getPathLength() > shortestPathLength) {
-                appendCandidateIfAbsent(reorderedList, candidateCommand);
-            }
-        }
-        for (RerouteCandidateCommand candidateCommand : candidateCommandList) {
-            if (candidateCommand == null || candidateCommand.getPathLength() == null) {
-                continue;
-            }
-            appendCandidateIfAbsent(reorderedList, candidateCommand);
-        }
-        return reorderedList;
-    }
-
-    private void appendCandidateIfAbsent(List<RerouteCandidateCommand> reorderedList,
-                                         RerouteCandidateCommand candidateCommand) {
-        if (reorderedList == null || candidateCommand == null) {
-            return;
-        }
-        if (!reorderedList.contains(candidateCommand)) {
-            reorderedList.add(candidateCommand);
-        }
-    }
-
-    private RunBlockRerouteState loadRunBlockRerouteState(Integer taskNo, Integer blockStationId) {
-        if (redisUtil == null || taskNo == null || taskNo <= 0 || blockStationId == null || blockStationId <= 0) {
-            return new RunBlockRerouteState();
-        }
-        Object stateObj = redisUtil.get(buildRunBlockRerouteStateKey(taskNo, blockStationId));
-        if (stateObj == null) {
-            return new RunBlockRerouteState();
-        }
-        try {
-            RunBlockRerouteState state = JSON.parseObject(String.valueOf(stateObj), RunBlockRerouteState.class);
-            return state == null ? new RunBlockRerouteState() : state.normalize();
-        } catch (Exception ignore) {
-            return new RunBlockRerouteState();
-        }
-    }
-
-    private void saveRunBlockRerouteState(RunBlockRerouteState rerouteState) {
-        if (redisUtil == null
-                || rerouteState == null
-                || rerouteState.getTaskNo() == null
-                || rerouteState.getTaskNo() <= 0
-                || rerouteState.getBlockStationId() == null
-                || rerouteState.getBlockStationId() <= 0) {
-            return;
-        }
-        rerouteState.normalize();
-        redisUtil.set(
-                buildRunBlockRerouteStateKey(rerouteState.getTaskNo(), rerouteState.getBlockStationId()),
-                JSON.toJSONString(rerouteState),
-                RUN_BLOCK_REROUTE_STATE_EXPIRE_SECONDS
-        );
-    }
-
-    private TaskLoopRerouteState loadTaskLoopRerouteState(Integer taskNo) {
-        if (redisUtil == null || taskNo == null || taskNo <= 0) {
-            return new TaskLoopRerouteState();
-        }
-        Object stateObj = redisUtil.get(RedisKeyType.STATION_RUN_BLOCK_TASK_LOOP_STATE_.key + taskNo);
-        if (stateObj == null) {
-            return new TaskLoopRerouteState();
-        }
-        try {
-            TaskLoopRerouteState state = JSON.parseObject(String.valueOf(stateObj), TaskLoopRerouteState.class);
-            return state == null ? new TaskLoopRerouteState() : state.normalize();
-        } catch (Exception ignore) {
-            return new TaskLoopRerouteState();
-        }
-    }
-
-    private void saveTaskLoopRerouteState(TaskLoopRerouteState taskLoopRerouteState) {
-        if (redisUtil == null
-                || taskLoopRerouteState == null
-                || taskLoopRerouteState.getTaskNo() == null
-                || taskLoopRerouteState.getTaskNo() <= 0) {
-            return;
-        }
-        taskLoopRerouteState.normalize();
-        redisUtil.set(
-                RedisKeyType.STATION_RUN_BLOCK_TASK_LOOP_STATE_.key + taskLoopRerouteState.getTaskNo(),
-                JSON.toJSONString(taskLoopRerouteState),
-                RUN_BLOCK_REROUTE_STATE_EXPIRE_SECONDS
-        );
-    }
-
-    private void touchTaskLoopRerouteState(TaskLoopRerouteState taskLoopRerouteState,
-                                           LoopIdentity currentLoopIdentity) {
-        if (taskLoopRerouteState == null || currentLoopIdentity == null || Cools.isEmpty(currentLoopIdentity.getLoopFingerprint())) {
-            return;
-        }
-        taskLoopRerouteState.getLoopIssueCountMap().put(
-                currentLoopIdentity.getLoopFingerprint(),
-                taskLoopRerouteState.getLoopIssueCountMap().getOrDefault(currentLoopIdentity.getLoopFingerprint(), 0) + 1
-        );
-        taskLoopRerouteState.setLastLoopFingerprint(currentLoopIdentity.getLoopFingerprint());
-        taskLoopRerouteState.setLastIssueTime(System.currentTimeMillis());
-    }
-
-    private int resolveCurrentLoopIssuedCount(TaskLoopRerouteState taskLoopRerouteState,
-                                              LoopIdentity currentLoopIdentity) {
-        if (taskLoopRerouteState == null || currentLoopIdentity == null || Cools.isEmpty(currentLoopIdentity.getLoopFingerprint())) {
-            return 0;
-        }
-        return taskLoopRerouteState.getLoopIssueCountMap().getOrDefault(currentLoopIdentity.getLoopFingerprint(), 0);
-    }
-
-    private int resolveExpectedLoopIssuedCount(TaskLoopRerouteState taskLoopRerouteState,
-                                               LoopIdentity currentLoopIdentity) {
-        if (currentLoopIdentity == null || Cools.isEmpty(currentLoopIdentity.getLoopFingerprint())) {
-            return 0;
-        }
-        return resolveCurrentLoopIssuedCount(taskLoopRerouteState, currentLoopIdentity) + 1;
-    }
-
-    private boolean isLoopRepeatTriggered(Integer loopIssuedCount) {
-        return loopIssuedCount != null && loopIssuedCount >= LOOP_REPEAT_TRIGGER_COUNT;
-    }
-
-    private void syncTaskTraceLoopAlert(Integer taskNo,
-                                        Integer blockedStationId,
-                                        LoopIdentity currentLoopIdentity,
-                                        int loopIssuedCount) {
-        StationTaskTraceRegistry traceRegistry;
-        try {
-            traceRegistry = SpringUtils.getBean(StationTaskTraceRegistry.class);
-        } catch (Exception ignore) {
-            return;
-        }
-        if (traceRegistry == null || taskNo == null || taskNo <= 0) {
-            return;
-        }
-
-        boolean active = currentLoopIdentity != null
-                && !Cools.isEmpty(currentLoopIdentity.getLoopFingerprint())
-                && isLoopRepeatTriggered(loopIssuedCount);
-        Map<String, Object> details = new HashMap<>();
-        details.put("blockedStationId", blockedStationId);
-        details.put("loopScopeType", currentLoopIdentity == null ? "" : currentLoopIdentity.getScopeType());
-        details.put("loopStationCount", currentLoopIdentity == null ? 0 : currentLoopIdentity.getLocalStationCount());
-        details.put("sourceLoopStationCount", currentLoopIdentity == null ? 0 : currentLoopIdentity.getSourceLoopStationCount());
-        details.put("loopRepeatCount", loopIssuedCount);
-        String loopAlertType = resolveLoopAlertType(currentLoopIdentity);
-        String loopAlertText = buildLoopAlertText(loopAlertType, currentLoopIdentity, loopIssuedCount);
-        traceRegistry.updateLoopHint(taskNo, active, loopAlertType, loopAlertText, loopIssuedCount, details);
-    }
-
-    private String resolveLoopAlertType(LoopIdentity currentLoopIdentity) {
-        if (currentLoopIdentity == null || Cools.isEmpty(currentLoopIdentity.getLoopFingerprint())) {
-            return "";
-        }
-        return "wholeLoop".equals(currentLoopIdentity.getScopeType()) ? "LARGE_LOOP" : "SMALL_LOOP";
-    }
-
-    private String buildLoopAlertText(String loopAlertType,
-                                      LoopIdentity currentLoopIdentity,
-                                      int loopIssuedCount) {
-        if (Cools.isEmpty(loopAlertType) || currentLoopIdentity == null || !isLoopRepeatTriggered(loopIssuedCount)) {
-            return "";
-        }
-        String typeLabel = "LARGE_LOOP".equals(loopAlertType) ? "澶х幆绾�" : "灏忕幆绾�";
-        return typeLabel + "缁曞湀棰勮锛岀疮璁¢噸瑙勫垝" + loopIssuedCount + "娆★紝褰撳墠璇嗗埆鑼冨洿"
-                + currentLoopIdentity.getLocalStationCount() + "绔�";
-    }
-
-    private int countCurrentLoopStationHit(List<Integer> path, Set<Integer> currentLoopStationIdSet) {
-        if (path == null || path.isEmpty() || currentLoopStationIdSet == null || currentLoopStationIdSet.isEmpty()) {
-            return 0;
-        }
-        int hitCount = 0;
-        for (Integer stationId : path) {
-            if (stationId != null && currentLoopStationIdSet.contains(stationId)) {
-                hitCount++;
-            }
-        }
-        return hitCount;
-    }
-
-    private String buildPathSignature(List<Integer> path) {
-        if (path == null || path.isEmpty()) {
-            return "";
-        }
-        StringBuilder builder = new StringBuilder();
-        for (Integer stationNo : path) {
-            if (stationNo == null) {
-                continue;
-            }
-            if (builder.length() > 0) {
-                builder.append("->");
-            }
-            builder.append(stationNo);
-        }
-        return builder.toString();
-    }
-
     private StationTaskLoopService loadStationTaskLoopService() {
         try {
             return SpringUtils.getBean(StationTaskLoopService.class);
         } catch (Exception ignore) {
             return null;
         }
-    }
-
-    private String buildRunBlockRerouteStateKey(Integer taskNo, Integer blockStationId) {
-        return RedisKeyType.STATION_RUN_BLOCK_REROUTE_STATE_.key + taskNo + "_" + blockStationId;
-    }
-
-    private LoopIdentity resolveStationLoopIdentity(Integer stationId) {
-        if (stationId == null || stationId <= 0) {
-            return LoopIdentity.empty();
-        }
-        try {
-            StationCycleCapacityService stationCycleCapacityService = SpringUtils.getBean(StationCycleCapacityService.class);
-            if (stationCycleCapacityService == null) {
-                return LoopIdentity.empty();
-            }
-            StationCycleCapacityVo capacityVo = stationCycleCapacityService.getLatestSnapshot();
-            if (capacityVo == null || capacityVo.getLoopList() == null || capacityVo.getLoopList().isEmpty()) {
-                return LoopIdentity.empty();
-            }
-            for (StationCycleLoopVo loopVo : capacityVo.getLoopList()) {
-                List<Integer> loopStationIdList = normalizeLoopStationIdList(loopVo == null ? null : loopVo.getStationIdList());
-                if (loopStationIdList.isEmpty() || !loopStationIdList.contains(stationId)) {
-                    continue;
-                }
-                Set<Integer> loopStationIdSet = new HashSet<>(loopStationIdList);
-                Map<Integer, Set<Integer>> stationGraph = loadUndirectedStationGraph();
-                List<Integer> localCycleStationIdList = resolveLocalCycleStationIdList(stationId, loopStationIdSet, stationGraph);
-                if (localCycleStationIdList.size() >= 3) {
-                    return buildLoopIdentity(localCycleStationIdList, loopStationIdList.size(), "localCycle");
-                }
-
-                List<Integer> localNeighborhoodStationIdList = resolveLocalNeighborhoodStationIdList(stationId, loopStationIdSet, stationGraph);
-                if (localNeighborhoodStationIdList.size() >= 3 && localNeighborhoodStationIdList.size() < loopStationIdList.size()) {
-                    return buildLoopIdentity(localNeighborhoodStationIdList, loopStationIdList.size(), "localNeighborhood");
-                }
-                return buildLoopIdentity(loopStationIdList, loopStationIdList.size(), "wholeLoop");
-            }
-        } catch (Exception ignore) {
-        }
-        return LoopIdentity.empty();
-    }
-
-    private LoopIdentity buildLoopIdentity(List<Integer> stationIdList,
-                                           int sourceLoopStationCount,
-                                           String scopeType) {
-        List<Integer> normalizedStationIdList = normalizeLoopStationIdList(stationIdList);
-        if (normalizedStationIdList.isEmpty()) {
-            return LoopIdentity.empty();
-        }
-        return new LoopIdentity(
-                buildLoopFingerprint(normalizedStationIdList),
-                new HashSet<>(normalizedStationIdList),
-                sourceLoopStationCount,
-                normalizedStationIdList.size(),
-                scopeType
-        );
-    }
-
-    private List<Integer> resolveLocalCycleStationIdList(Integer stationId,
-                                                         Set<Integer> loopStationIdSet,
-                                                         Map<Integer, Set<Integer>> stationGraph) {
-        if (stationId == null
-                || stationId <= 0
-                || loopStationIdSet == null
-                || loopStationIdSet.isEmpty()
-                || stationGraph == null
-                || stationGraph.isEmpty()) {
-            return new ArrayList<>();
-        }
-        Set<Integer> localNeighborhoodStationIdSet = collectLoopNeighborhoodStationIdSet(
-                stationId,
-                loopStationIdSet,
-                stationGraph,
-                LOCAL_LOOP_NEIGHBOR_HOP
-        );
-        if (localNeighborhoodStationIdSet.size() < 3) {
-            return new ArrayList<>();
-        }
-
-        Set<Integer> directNeighborStationIdSet = filterLoopNeighborStationIdSet(
-                stationGraph.getOrDefault(stationId, Collections.emptySet()),
-                localNeighborhoodStationIdSet,
-                stationId
-        );
-        if (directNeighborStationIdSet.size() < 2) {
-            return new ArrayList<>();
-        }
-
-        List<Integer> bestCycleStationIdList = new ArrayList<>();
-        List<Integer> neighborStationIdList = new ArrayList<>(directNeighborStationIdSet);
-        for (int i = 0; i < neighborStationIdList.size(); i++) {
-            Integer leftNeighborStationId = neighborStationIdList.get(i);
-            if (leftNeighborStationId == null) {
-                continue;
-            }
-            for (int j = i + 1; j < neighborStationIdList.size(); j++) {
-                Integer rightNeighborStationId = neighborStationIdList.get(j);
-                if (rightNeighborStationId == null) {
-                    continue;
-                }
-                List<Integer> pathBetweenNeighbors = findShortestScopePath(
-                        leftNeighborStationId,
-                        rightNeighborStationId,
-                        stationId,
-                        localNeighborhoodStationIdSet,
-                        stationGraph
-                );
-                if (pathBetweenNeighbors.isEmpty()) {
-                    continue;
-                }
-                List<Integer> cycleStationIdList = new ArrayList<>();
-                cycleStationIdList.add(stationId);
-                cycleStationIdList.addAll(pathBetweenNeighbors);
-                cycleStationIdList = normalizeLoopStationIdList(cycleStationIdList);
-                if (cycleStationIdList.size() < 3) {
-                    continue;
-                }
-                if (bestCycleStationIdList.isEmpty() || cycleStationIdList.size() < bestCycleStationIdList.size()) {
-                    bestCycleStationIdList = cycleStationIdList;
-                }
-            }
-        }
-        return bestCycleStationIdList;
-    }
-
-    private List<Integer> resolveLocalNeighborhoodStationIdList(Integer stationId,
-                                                                Set<Integer> loopStationIdSet,
-                                                                Map<Integer, Set<Integer>> stationGraph) {
-        return normalizeLoopStationIdList(new ArrayList<>(collectLoopNeighborhoodStationIdSet(
-                stationId,
-                loopStationIdSet,
-                stationGraph,
-                LOCAL_LOOP_NEIGHBOR_HOP
-        )));
-    }
-
-    private Set<Integer> collectLoopNeighborhoodStationIdSet(Integer stationId,
-                                                             Set<Integer> loopStationIdSet,
-                                                             Map<Integer, Set<Integer>> stationGraph,
-                                                             int maxHop) {
-        Set<Integer> neighborhoodStationIdSet = new LinkedHashSet<>();
-        if (stationId == null
-                || stationId <= 0
-                || loopStationIdSet == null
-                || loopStationIdSet.isEmpty()
-                || !loopStationIdSet.contains(stationId)
-                || stationGraph == null
-                || stationGraph.isEmpty()
-                || maxHop < 0) {
-            return neighborhoodStationIdSet;
-        }
-
-        Deque<StationHopNode> queue = new ArrayDeque<>();
-        queue.offer(new StationHopNode(stationId, 0));
-        neighborhoodStationIdSet.add(stationId);
-        while (!queue.isEmpty()) {
-            StationHopNode current = queue.poll();
-            if (current == null || current.getHop() >= maxHop) {
-                continue;
-            }
-            Set<Integer> neighborStationIdSet = filterLoopNeighborStationIdSet(
-                    stationGraph.getOrDefault(current.getStationId(), Collections.emptySet()),
-                    loopStationIdSet,
-                    null
-            );
-            for (Integer neighborStationId : neighborStationIdSet) {
-                if (neighborStationId == null || !neighborhoodStationIdSet.add(neighborStationId)) {
-                    continue;
-                }
-                queue.offer(new StationHopNode(neighborStationId, current.getHop() + 1));
-            }
-        }
-        return neighborhoodStationIdSet;
-    }
-
-    private Set<Integer> filterLoopNeighborStationIdSet(Set<Integer> candidateNeighborStationIdSet,
-                                                        Set<Integer> allowedStationIdSet,
-                                                        Integer excludedStationId) {
-        Set<Integer> result = new LinkedHashSet<>();
-        if (candidateNeighborStationIdSet == null || candidateNeighborStationIdSet.isEmpty()
-                || allowedStationIdSet == null || allowedStationIdSet.isEmpty()) {
-            return result;
-        }
-        for (Integer stationId : candidateNeighborStationIdSet) {
-            if (stationId == null
-                    || !allowedStationIdSet.contains(stationId)
-                    || (excludedStationId != null && excludedStationId.equals(stationId))) {
-                continue;
-            }
-            result.add(stationId);
-        }
-        return result;
-    }
-
-    private List<Integer> findShortestScopePath(Integer startStationId,
-                                                Integer endStationId,
-                                                Integer excludedStationId,
-                                                Set<Integer> allowedStationIdSet,
-                                                Map<Integer, Set<Integer>> stationGraph) {
-        if (startStationId == null
-                || endStationId == null
-                || Objects.equals(startStationId, excludedStationId)
-                || Objects.equals(endStationId, excludedStationId)
-                || allowedStationIdSet == null
-                || !allowedStationIdSet.contains(startStationId)
-                || !allowedStationIdSet.contains(endStationId)
-                || stationGraph == null
-                || stationGraph.isEmpty()) {
-            return new ArrayList<>();
-        }
-
-        Deque<Integer> queue = new ArrayDeque<>();
-        Map<Integer, Integer> parentMap = new HashMap<>();
-        Set<Integer> visitedStationIdSet = new HashSet<>();
-        queue.offer(startStationId);
-        visitedStationIdSet.add(startStationId);
-        while (!queue.isEmpty()) {
-            Integer currentStationId = queue.poll();
-            if (currentStationId == null) {
-                continue;
-            }
-            if (Objects.equals(currentStationId, endStationId)) {
-                break;
-            }
-            Set<Integer> neighborStationIdSet = filterLoopNeighborStationIdSet(
-                    stationGraph.getOrDefault(currentStationId, Collections.emptySet()),
-                    allowedStationIdSet,
-                    excludedStationId
-            );
-            for (Integer neighborStationId : neighborStationIdSet) {
-                if (neighborStationId == null || !visitedStationIdSet.add(neighborStationId)) {
-                    continue;
-                }
-                parentMap.put(neighborStationId, currentStationId);
-                queue.offer(neighborStationId);
-            }
-        }
-        if (!visitedStationIdSet.contains(endStationId)) {
-            return new ArrayList<>();
-        }
-
-        List<Integer> pathStationIdList = new ArrayList<>();
-        Integer cursorStationId = endStationId;
-        while (cursorStationId != null) {
-            pathStationIdList.add(cursorStationId);
-            if (Objects.equals(cursorStationId, startStationId)) {
-                break;
-            }
-            cursorStationId = parentMap.get(cursorStationId);
-        }
-        if (pathStationIdList.isEmpty()
-                || !Objects.equals(pathStationIdList.get(pathStationIdList.size() - 1), startStationId)) {
-            return new ArrayList<>();
-        }
-        Collections.reverse(pathStationIdList);
-        return pathStationIdList;
-    }
-
-    private Map<Integer, Set<Integer>> loadUndirectedStationGraph() {
-        NavigateUtils navigateUtils = SpringUtils.getBean(NavigateUtils.class);
-        if (navigateUtils == null) {
-            return new HashMap<>();
-        }
-        Map<Integer, Set<Integer>> stationGraph = navigateUtils.loadUndirectedStationGraphSnapshot();
-        return stationGraph == null ? new HashMap<>() : stationGraph;
-    }
-
-    private List<Integer> normalizeLoopStationIdList(List<Integer> stationIdList) {
-        if (stationIdList == null || stationIdList.isEmpty()) {
-            return new ArrayList<>();
-        }
-        List<Integer> normalizedList = new ArrayList<>();
-        Set<Integer> seenStationIdSet = new HashSet<>();
-        for (Integer stationId : stationIdList) {
-            if (stationId == null || stationId <= 0 || !seenStationIdSet.add(stationId)) {
-                continue;
-            }
-            normalizedList.add(stationId);
-        }
-        Collections.sort(normalizedList);
-        return normalizedList;
-    }
-
-    private String buildLoopFingerprint(List<Integer> stationIdList) {
-        if (stationIdList == null || stationIdList.isEmpty()) {
-            return "";
-        }
-        StringBuilder builder = new StringBuilder();
-        for (Integer stationId : stationIdList) {
-            if (stationId == null) {
-                continue;
-            }
-            if (builder.length() > 0) {
-                builder.append("|");
-            }
-            builder.append(stationId);
-        }
-        return builder.toString();
-    }
-
-    private int safeInt(Integer value) {
-        return value == null ? 0 : value;
-    }
-
-    @Data
-    private static class RunBlockRerouteState {
-        private Integer taskNo;
-        private Integer blockStationId;
-        private Integer planCount = 0;
-        private Integer lastTargetStationId;
-        private Long lastPlanTime;
-        private List<List<Integer>> issuedRoutePathList = new ArrayList<>();
-        private List<Integer> lastSelectedRoute = new ArrayList<>();
-        private Set<String> issuedRouteSignatureSet = new LinkedHashSet<>();
-        private Map<String, Integer> routeIssueCountMap = new HashMap<>();
-
-        private RunBlockRerouteState normalize() {
-            if (planCount == null || planCount < 0) {
-                planCount = 0;
-            }
-            if (issuedRoutePathList == null) {
-                issuedRoutePathList = new ArrayList<>();
-            }
-            if (lastSelectedRoute == null) {
-                lastSelectedRoute = new ArrayList<>();
-            }
-            if (issuedRouteSignatureSet == null) {
-                issuedRouteSignatureSet = new LinkedHashSet<>();
-            }
-            if (routeIssueCountMap == null) {
-                routeIssueCountMap = new HashMap<>();
-            }
-            for (List<Integer> routePath : issuedRoutePathList) {
-                if (routePath == null || routePath.isEmpty()) {
-                    continue;
-                }
-                String pathSignature = buildPathSignatureText(routePath);
-                if (!Cools.isEmpty(pathSignature)) {
-                    issuedRouteSignatureSet.add(pathSignature);
-                    routeIssueCountMap.putIfAbsent(pathSignature, 1);
-                }
-            }
-            return this;
-        }
-
-        private void resetIssuedRoutes() {
-            this.issuedRoutePathList = new ArrayList<>();
-            this.lastSelectedRoute = new ArrayList<>();
-            this.issuedRouteSignatureSet = new LinkedHashSet<>();
-        }
-
-        private static String buildPathSignatureText(List<Integer> routePath) {
-            if (routePath == null || routePath.isEmpty()) {
-                return "";
-            }
-            StringBuilder builder = new StringBuilder();
-            for (Integer stationId : routePath) {
-                if (stationId == null) {
-                    continue;
-                }
-                if (builder.length() > 0) {
-                    builder.append("->");
-                }
-                builder.append(stationId);
-            }
-            return builder.toString();
-        }
-    }
-
-    @Data
-    private static class RerouteCandidateCommand {
-        private StationCommand command;
-        private String routeSignature;
-        private Integer pathLength;
-        private Integer issuedCount;
-        private String loopFingerprint;
-        private Integer loopIssuedCount;
-        private Boolean loopTriggered;
-        private Integer currentLoopHitCount;
-    }
-
-    @Data
-    private static class TaskLoopRerouteState {
-        private Integer taskNo;
-        private String lastLoopFingerprint;
-        private Long lastIssueTime;
-        private Map<String, Integer> loopIssueCountMap = new HashMap<>();
-
-        private TaskLoopRerouteState normalize() {
-            if (loopIssueCountMap == null) {
-                loopIssueCountMap = new HashMap<>();
-            }
-            return this;
-        }
-    }
-
-    @Data
-    private static class LoopIdentity {
-        private String loopFingerprint;
-        private Set<Integer> stationIdSet = new HashSet<>();
-        private int sourceLoopStationCount;
-        private int localStationCount;
-        private String scopeType;
-
-        private LoopIdentity(String loopFingerprint,
-                             Set<Integer> stationIdSet,
-                             int sourceLoopStationCount,
-                             int localStationCount,
-                             String scopeType) {
-            this.loopFingerprint = loopFingerprint;
-            this.stationIdSet = stationIdSet == null ? new HashSet<>() : stationIdSet;
-            this.sourceLoopStationCount = sourceLoopStationCount;
-            this.localStationCount = localStationCount;
-            this.scopeType = scopeType == null ? "" : scopeType;
-        }
-
-        private static LoopIdentity empty() {
-            return new LoopIdentity("", new HashSet<>(), 0, 0, "none");
-        }
-    }
-
-    @Data
-    private static class StationHopNode {
-        private final Integer stationId;
-        private final int hop;
     }
 }
diff --git a/src/main/java/com/zy/core/thread/impl/v5/StationV5RunBlockReroutePlanner.java b/src/main/java/com/zy/core/thread/impl/v5/StationV5RunBlockReroutePlanner.java
new file mode 100644
index 0000000..5c1d3c3
--- /dev/null
+++ b/src/main/java/com/zy/core/thread/impl/v5/StationV5RunBlockReroutePlanner.java
@@ -0,0 +1,375 @@
+package com.zy.core.thread.impl.v5;
+
+import com.alibaba.fastjson.JSON;
+import com.core.common.Cools;
+import com.zy.common.utils.RedisUtil;
+import com.zy.core.enums.RedisKeyType;
+import com.zy.core.model.command.StationCommand;
+import com.zy.core.service.StationTaskLoopService;
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class StationV5RunBlockReroutePlanner {
+
+    private static final int RUN_BLOCK_REROUTE_STATE_EXPIRE_SECONDS = 60 * 60 * 24;
+    private static final int SHORT_PATH_REPEAT_AVOID_THRESHOLD = 2;
+
+    private final RedisUtil redisUtil;
+
+    public StationV5RunBlockReroutePlanner(RedisUtil redisUtil) {
+        this.redisUtil = redisUtil;
+    }
+
+    public PlanResult plan(Integer taskNo,
+                           Integer blockStationId,
+                           StationTaskLoopService.LoopEvaluation loopEvaluation,
+                           List<StationCommand> candidateCommands) {
+        RunBlockRerouteState rerouteState = loadRunBlockRerouteState(taskNo, blockStationId);
+        rerouteState.setTaskNo(taskNo);
+        rerouteState.setBlockStationId(blockStationId);
+        rerouteState.setPlanCount((rerouteState.getPlanCount() == null ? 0 : rerouteState.getPlanCount()) + 1);
+
+        StationCommand rerouteCommand = selectAvailableRerouteCommand(rerouteState, loopEvaluation, candidateCommands);
+        if (rerouteCommand == null && candidateCommands != null && !candidateCommands.isEmpty()) {
+            rerouteState.resetIssuedRoutes();
+            rerouteCommand = selectAvailableRerouteCommand(rerouteState, loopEvaluation, candidateCommands);
+        }
+
+        saveRunBlockRerouteState(rerouteState);
+        return new PlanResult(
+                rerouteCommand,
+                rerouteState.getPlanCount() == null ? 0 : rerouteState.getPlanCount(),
+                copyRoutePathList(rerouteState.getIssuedRoutePathList())
+        );
+    }
+
+    private StationCommand selectAvailableRerouteCommand(RunBlockRerouteState rerouteState,
+                                                         StationTaskLoopService.LoopEvaluation loopEvaluation,
+                                                         List<StationCommand> candidateCommands) {
+        if (rerouteState == null || candidateCommands == null || candidateCommands.isEmpty()) {
+            return null;
+        }
+
+        Set<String> issuedRouteSignatureSet = rerouteState.getIssuedRouteSignatureSet();
+        StationTaskLoopService.LoopIdentitySnapshot loopIdentity = loopEvaluation == null
+                ? StationTaskLoopService.LoopIdentitySnapshot.empty()
+                : loopEvaluation.getLoopIdentity();
+        if (loopIdentity == null) {
+            loopIdentity = StationTaskLoopService.LoopIdentitySnapshot.empty();
+        }
+
+        List<RerouteCandidateCommand> candidateCommandList = new ArrayList<>();
+        for (StationCommand candidateCommand : candidateCommands) {
+            if (candidateCommand == null || candidateCommand.getNavigatePath() == null || candidateCommand.getNavigatePath().isEmpty()) {
+                continue;
+            }
+            String routeSignature = buildPathSignature(candidateCommand.getNavigatePath());
+            if (Cools.isEmpty(routeSignature)) {
+                continue;
+            }
+            RerouteCandidateCommand rerouteCandidateCommand = new RerouteCandidateCommand();
+            rerouteCandidateCommand.setCommand(candidateCommand);
+            rerouteCandidateCommand.setRouteSignature(routeSignature);
+            rerouteCandidateCommand.setPathLength(candidateCommand.getNavigatePath().size());
+            rerouteCandidateCommand.setIssuedCount(rerouteState.getRouteIssueCountMap().getOrDefault(routeSignature, 0));
+            rerouteCandidateCommand.setLoopFingerprint(loopIdentity.getLoopFingerprint());
+            rerouteCandidateCommand.setLoopTriggered(loopEvaluation != null && loopEvaluation.isLargeLoopTriggered());
+            rerouteCandidateCommand.setCurrentLoopHitCount(countCurrentLoopStationHit(
+                    candidateCommand.getNavigatePath(),
+                    loopIdentity.getStationIdSet()
+            ));
+            candidateCommandList.add(rerouteCandidateCommand);
+        }
+        if (candidateCommandList.isEmpty()) {
+            return null;
+        }
+
+        List<RerouteCandidateCommand> orderedCandidateCommandList = reorderCandidateCommandsForLoopRelease(candidateCommandList);
+        for (RerouteCandidateCommand candidateCommand : orderedCandidateCommandList) {
+            if (candidateCommand == null || candidateCommand.getCommand() == null) {
+                continue;
+            }
+            if (issuedRouteSignatureSet.contains(candidateCommand.getRouteSignature())) {
+                continue;
+            }
+
+            StationCommand rerouteCommand = candidateCommand.getCommand();
+            issuedRouteSignatureSet.add(candidateCommand.getRouteSignature());
+            rerouteState.getIssuedRoutePathList().add(new ArrayList<>(rerouteCommand.getNavigatePath()));
+            rerouteState.setLastSelectedRoute(new ArrayList<>(rerouteCommand.getNavigatePath()));
+            rerouteState.getRouteIssueCountMap().put(
+                    candidateCommand.getRouteSignature(),
+                    rerouteState.getRouteIssueCountMap().getOrDefault(candidateCommand.getRouteSignature(), 0) + 1
+            );
+            return rerouteCommand;
+        }
+        return null;
+    }
+
+    private List<RerouteCandidateCommand> reorderCandidateCommandsForLoopRelease(List<RerouteCandidateCommand> candidateCommandList) {
+        if (candidateCommandList == null || candidateCommandList.isEmpty()) {
+            return new ArrayList<>();
+        }
+
+        int shortestPathLength = Integer.MAX_VALUE;
+        int shortestPathLoopHitCount = Integer.MAX_VALUE;
+        boolean shortestPathOverused = false;
+        boolean currentLoopOverused = false;
+        boolean hasLongerCandidate = false;
+        for (RerouteCandidateCommand candidateCommand : candidateCommandList) {
+            if (candidateCommand == null || candidateCommand.getPathLength() == null || candidateCommand.getPathLength() <= 0) {
+                continue;
+            }
+            shortestPathLength = Math.min(shortestPathLength, candidateCommand.getPathLength());
+        }
+        if (shortestPathLength == Integer.MAX_VALUE) {
+            return candidateCommandList;
+        }
+
+        for (RerouteCandidateCommand candidateCommand : candidateCommandList) {
+            if (candidateCommand == null || candidateCommand.getPathLength() == null || candidateCommand.getPathLength() <= 0) {
+                continue;
+            }
+            if (candidateCommand.getPathLength() == shortestPathLength) {
+                shortestPathLoopHitCount = Math.min(shortestPathLoopHitCount, safeInt(candidateCommand.getCurrentLoopHitCount()));
+            }
+            if (candidateCommand.getPathLength() > shortestPathLength) {
+                hasLongerCandidate = true;
+            }
+            if (candidateCommand.getPathLength() == shortestPathLength
+                    && candidateCommand.getIssuedCount() != null
+                    && candidateCommand.getIssuedCount() >= SHORT_PATH_REPEAT_AVOID_THRESHOLD) {
+                shortestPathOverused = true;
+            }
+            if (!Cools.isEmpty(candidateCommand.getLoopFingerprint())
+                    && Boolean.TRUE.equals(candidateCommand.getLoopTriggered())) {
+                currentLoopOverused = true;
+            }
+        }
+        if (!shortestPathOverused && !currentLoopOverused) {
+            return candidateCommandList;
+        }
+        if (shortestPathLoopHitCount == Integer.MAX_VALUE) {
+            shortestPathLoopHitCount = 0;
+        }
+
+        boolean hasLoopExitCandidate = false;
+        for (RerouteCandidateCommand candidateCommand : candidateCommandList) {
+            if (candidateCommand == null) {
+                continue;
+            }
+            if (safeInt(candidateCommand.getCurrentLoopHitCount()) < shortestPathLoopHitCount) {
+                hasLoopExitCandidate = true;
+                break;
+            }
+        }
+        if (!hasLongerCandidate && !hasLoopExitCandidate) {
+            return candidateCommandList;
+        }
+
+        List<RerouteCandidateCommand> reorderedList = new ArrayList<>();
+        if (currentLoopOverused && hasLoopExitCandidate) {
+            for (RerouteCandidateCommand candidateCommand : candidateCommandList) {
+                if (candidateCommand == null) {
+                    continue;
+                }
+                if (safeInt(candidateCommand.getCurrentLoopHitCount()) < shortestPathLoopHitCount) {
+                    appendCandidateIfAbsent(reorderedList, candidateCommand);
+                }
+            }
+        }
+        for (RerouteCandidateCommand candidateCommand : candidateCommandList) {
+            if (candidateCommand != null
+                    && candidateCommand.getPathLength() != null
+                    && candidateCommand.getPathLength() > shortestPathLength) {
+                appendCandidateIfAbsent(reorderedList, candidateCommand);
+            }
+        }
+        for (RerouteCandidateCommand candidateCommand : candidateCommandList) {
+            if (candidateCommand == null || candidateCommand.getPathLength() == null) {
+                continue;
+            }
+            appendCandidateIfAbsent(reorderedList, candidateCommand);
+        }
+        return reorderedList;
+    }
+
+    private void appendCandidateIfAbsent(List<RerouteCandidateCommand> reorderedList,
+                                         RerouteCandidateCommand candidateCommand) {
+        if (reorderedList == null || candidateCommand == null) {
+            return;
+        }
+        if (!reorderedList.contains(candidateCommand)) {
+            reorderedList.add(candidateCommand);
+        }
+    }
+
+    private RunBlockRerouteState loadRunBlockRerouteState(Integer taskNo, Integer blockStationId) {
+        if (redisUtil == null || taskNo == null || taskNo <= 0 || blockStationId == null || blockStationId <= 0) {
+            return new RunBlockRerouteState();
+        }
+        Object stateObj = redisUtil.get(buildRunBlockRerouteStateKey(taskNo, blockStationId));
+        if (stateObj == null) {
+            return new RunBlockRerouteState();
+        }
+        try {
+            RunBlockRerouteState state = JSON.parseObject(String.valueOf(stateObj), RunBlockRerouteState.class);
+            return state == null ? new RunBlockRerouteState() : state.normalize();
+        } catch (Exception ignore) {
+            return new RunBlockRerouteState();
+        }
+    }
+
+    private void saveRunBlockRerouteState(RunBlockRerouteState rerouteState) {
+        if (redisUtil == null
+                || rerouteState == null
+                || rerouteState.getTaskNo() == null
+                || rerouteState.getTaskNo() <= 0
+                || rerouteState.getBlockStationId() == null
+                || rerouteState.getBlockStationId() <= 0) {
+            return;
+        }
+        rerouteState.normalize();
+        redisUtil.set(
+                buildRunBlockRerouteStateKey(rerouteState.getTaskNo(), rerouteState.getBlockStationId()),
+                JSON.toJSONString(rerouteState),
+                RUN_BLOCK_REROUTE_STATE_EXPIRE_SECONDS
+        );
+    }
+
+    private String buildRunBlockRerouteStateKey(Integer taskNo, Integer blockStationId) {
+        return RedisKeyType.STATION_RUN_BLOCK_REROUTE_STATE_.key + taskNo + "_" + blockStationId;
+    }
+
+    private int countCurrentLoopStationHit(List<Integer> path, Set<Integer> currentLoopStationIdSet) {
+        if (path == null || path.isEmpty() || currentLoopStationIdSet == null || currentLoopStationIdSet.isEmpty()) {
+            return 0;
+        }
+        int hitCount = 0;
+        for (Integer stationId : path) {
+            if (stationId != null && currentLoopStationIdSet.contains(stationId)) {
+                hitCount++;
+            }
+        }
+        return hitCount;
+    }
+
+    private String buildPathSignature(List<Integer> path) {
+        if (path == null || path.isEmpty()) {
+            return "";
+        }
+        StringBuilder builder = new StringBuilder();
+        for (Integer stationNo : path) {
+            if (stationNo == null) {
+                continue;
+            }
+            if (builder.length() > 0) {
+                builder.append("->");
+            }
+            builder.append(stationNo);
+        }
+        return builder.toString();
+    }
+
+    private int safeInt(Integer value) {
+        return value == null ? 0 : value;
+    }
+
+    private List<List<Integer>> copyRoutePathList(List<List<Integer>> source) {
+        List<List<Integer>> copy = new ArrayList<>();
+        if (source == null || source.isEmpty()) {
+            return copy;
+        }
+        for (List<Integer> route : source) {
+            copy.add(route == null ? new ArrayList<>() : new ArrayList<>(route));
+        }
+        return copy;
+    }
+
+    @Data
+    public static class PlanResult {
+        private final StationCommand command;
+        private final int planCount;
+        private final List<List<Integer>> issuedRoutePathList;
+    }
+
+    @Data
+    private static class RunBlockRerouteState {
+        private Integer taskNo;
+        private Integer blockStationId;
+        private Integer planCount = 0;
+        private List<List<Integer>> issuedRoutePathList = new ArrayList<>();
+        private List<Integer> lastSelectedRoute = new ArrayList<>();
+        private Set<String> issuedRouteSignatureSet = new LinkedHashSet<>();
+        private Map<String, Integer> routeIssueCountMap = new HashMap<>();
+
+        private RunBlockRerouteState normalize() {
+            if (planCount == null || planCount < 0) {
+                planCount = 0;
+            }
+            if (issuedRoutePathList == null) {
+                issuedRoutePathList = new ArrayList<>();
+            }
+            if (lastSelectedRoute == null) {
+                lastSelectedRoute = new ArrayList<>();
+            }
+            if (issuedRouteSignatureSet == null) {
+                issuedRouteSignatureSet = new LinkedHashSet<>();
+            }
+            if (routeIssueCountMap == null) {
+                routeIssueCountMap = new HashMap<>();
+            }
+            for (List<Integer> routePath : issuedRoutePathList) {
+                if (routePath == null || routePath.isEmpty()) {
+                    continue;
+                }
+                String pathSignature = buildPathSignatureText(routePath);
+                if (!Cools.isEmpty(pathSignature)) {
+                    issuedRouteSignatureSet.add(pathSignature);
+                    routeIssueCountMap.putIfAbsent(pathSignature, 1);
+                }
+            }
+            return this;
+        }
+
+        private void resetIssuedRoutes() {
+            this.issuedRoutePathList = new ArrayList<>();
+            this.lastSelectedRoute = new ArrayList<>();
+            this.issuedRouteSignatureSet = new LinkedHashSet<>();
+        }
+
+        private static String buildPathSignatureText(List<Integer> routePath) {
+            if (routePath == null || routePath.isEmpty()) {
+                return "";
+            }
+            StringBuilder builder = new StringBuilder();
+            for (Integer stationId : routePath) {
+                if (stationId == null) {
+                    continue;
+                }
+                if (builder.length() > 0) {
+                    builder.append("->");
+                }
+                builder.append(stationId);
+            }
+            return builder.toString();
+        }
+    }
+
+    @Data
+    private static class RerouteCandidateCommand {
+        private StationCommand command;
+        private String routeSignature;
+        private Integer pathLength;
+        private Integer issuedCount;
+        private String loopFingerprint;
+        private Boolean loopTriggered;
+        private Integer currentLoopHitCount;
+    }
+}
diff --git a/src/main/java/com/zy/core/thread/impl/v5/StationV5SegmentExecutionPlan.java b/src/main/java/com/zy/core/thread/impl/v5/StationV5SegmentExecutionPlan.java
deleted file mode 100644
index e2f6d06..0000000
--- a/src/main/java/com/zy/core/thread/impl/v5/StationV5SegmentExecutionPlan.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.zy.core.thread.impl.v5;
-
-import com.zy.core.model.command.StationCommand;
-import lombok.Data;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@Data
-public class StationV5SegmentExecutionPlan {
-
-    private List<Integer> fullPathStationIds = new ArrayList<>();
-
-    private List<StationCommand> segmentCommands = new ArrayList<>();
-
-    public int getTotalSegmentCount() {
-        return segmentCommands == null ? 0 : segmentCommands.size();
-    }
-}
diff --git a/src/main/java/com/zy/core/thread/impl/v5/StationV5SegmentPlanner.java b/src/main/java/com/zy/core/thread/impl/v5/StationV5SegmentPlanner.java
deleted file mode 100644
index ece2fbf..0000000
--- a/src/main/java/com/zy/core/thread/impl/v5/StationV5SegmentPlanner.java
+++ /dev/null
@@ -1,96 +0,0 @@
-package com.zy.core.thread.impl.v5;
-
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.serializer.SerializerFeature;
-import com.zy.core.model.command.StationCommand;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-
-public class StationV5SegmentPlanner {
-
-    public StationV5SegmentExecutionPlan buildPlan(StationCommand original) {
-        StationV5SegmentExecutionPlan plan = new StationV5SegmentExecutionPlan();
-        if (original == null) {
-            return plan;
-        }
-
-        List<Integer> path = copyIntegerList(original.getNavigatePath());
-        List<Integer> liftTransferPath = copyIntegerList(original.getLiftTransferPath());
-        Integer startStationId = original.getStationId();
-        Integer targetStationId = original.getTargetStaNo();
-
-        if ((path == null || path.isEmpty()) && Objects.equals(startStationId, targetStationId) && startStationId != null) {
-            path = new ArrayList<>();
-            path.add(startStationId);
-        }
-
-        if (path == null || path.isEmpty()) {
-            return plan;
-        }
-
-        plan.setFullPathStationIds(copyIntegerList(path));
-
-        int total = path.size();
-        List<Integer> segmentEndIndices = new ArrayList<>();
-        if (liftTransferPath != null) {
-            for (Integer liftTransferStationId : liftTransferPath) {
-                int endIndex = path.indexOf(liftTransferStationId);
-                if (endIndex <= 0) {
-                    continue;
-                }
-                if (segmentEndIndices.isEmpty() || endIndex > segmentEndIndices.get(segmentEndIndices.size() - 1)) {
-                    segmentEndIndices.add(endIndex);
-                }
-            }
-        }
-        if (segmentEndIndices.isEmpty() || segmentEndIndices.get(segmentEndIndices.size() - 1) != total - 1) {
-            segmentEndIndices.add(total - 1);
-        }
-
-        List<StationCommand> segmentCommands = new ArrayList<>();
-        int buildStartIdx = 0;
-        for (Integer endIdx : segmentEndIndices) {
-            if (endIdx == null || endIdx < buildStartIdx) {
-                continue;
-            }
-            List<Integer> segmentPath = new ArrayList<>(path.subList(buildStartIdx, endIdx + 1));
-            if (segmentPath.isEmpty()) {
-                buildStartIdx = endIdx + 1;
-                continue;
-            }
-
-            StationCommand segmentCommand = new StationCommand();
-            segmentCommand.setTaskNo(original.getTaskNo());
-            segmentCommand.setCommandType(original.getCommandType());
-            segmentCommand.setPalletSize(original.getPalletSize());
-            segmentCommand.setBarcode(original.getBarcode());
-            segmentCommand.setOriginalNavigatePath(copyIntegerList(path));
-            segmentCommand.setNavigatePath(segmentPath);
-            segmentCommand.setStationId(segmentPath.get(0));
-            segmentCommand.setTargetStaNo(segmentPath.get(segmentPath.size() - 1));
-            segmentCommand.setSegmentStartIndex(buildStartIdx);
-            segmentCommand.setSegmentEndIndex(endIdx);
-            segmentCommands.add(segmentCommand);
-
-            buildStartIdx = endIdx;
-        }
-
-        int segmentCount = segmentCommands.size();
-        for (int i = 0; i < segmentCommands.size(); i++) {
-            StationCommand segmentCommand = segmentCommands.get(i);
-            segmentCommand.setSegmentNo(i + 1);
-            segmentCommand.setSegmentCount(segmentCount);
-        }
-        plan.setSegmentCommands(segmentCommands);
-        return plan;
-    }
-
-    private List<Integer> copyIntegerList(List<Integer> source) {
-        if (source == null) {
-            return new ArrayList<>();
-        }
-        return JSON.parseArray(JSON.toJSONString(source, SerializerFeature.DisableCircularReferenceDetect), Integer.class);
-    }
-}
diff --git a/src/main/java/com/zy/core/thread/impl/v5/StationV5StatusReader.java b/src/main/java/com/zy/core/thread/impl/v5/StationV5StatusReader.java
new file mode 100644
index 0000000..fdd6f3a
--- /dev/null
+++ b/src/main/java/com/zy/core/thread/impl/v5/StationV5StatusReader.java
@@ -0,0 +1,127 @@
+package com.zy.core.thread.impl.v5;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.core.common.Cools;
+import com.core.common.DateUtils;
+import com.core.common.SpringUtils;
+import com.zy.asrs.entity.BasDevp;
+import com.zy.asrs.entity.DeviceConfig;
+import com.zy.asrs.entity.DeviceDataLog;
+import com.zy.asrs.service.BasDevpService;
+import com.zy.asrs.utils.Utils;
+import com.zy.common.utils.RedisUtil;
+import com.zy.core.cache.OutputQueue;
+import com.zy.core.enums.SlaveType;
+import com.zy.core.model.protocol.StationProtocol;
+import com.zy.core.network.ZyStationConnectDriver;
+import com.zy.core.network.entity.ZyStationStatusEntity;
+import com.zy.core.thread.support.RecentStationArrivalTracker;
+import com.zy.core.utils.DeviceLogRedisKeyBuilder;
+
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+public class StationV5StatusReader {
+
+    private final DeviceConfig deviceConfig;
+    private final RedisUtil redisUtil;
+    private final RecentStationArrivalTracker recentArrivalTracker;
+    private final List<StationProtocol> statusList = new ArrayList<>();
+    private boolean initialized = false;
+    private long deviceDataLogTime = System.currentTimeMillis();
+
+    public StationV5StatusReader(DeviceConfig deviceConfig,
+                                 RedisUtil redisUtil,
+                                 RecentStationArrivalTracker recentArrivalTracker) {
+        this.deviceConfig = deviceConfig;
+        this.redisUtil = redisUtil;
+        this.recentArrivalTracker = recentArrivalTracker;
+    }
+
+    public void readStatus(ZyStationConnectDriver zyStationConnectDriver) {
+        if (zyStationConnectDriver == null) {
+            return;
+        }
+
+        if (statusList.isEmpty()) {
+            BasDevpService basDevpService = null;
+            try {
+                basDevpService = SpringUtils.getBean(BasDevpService.class);
+            } catch (Exception ignore) {
+            }
+            if (basDevpService == null) {
+                return;
+            }
+
+            BasDevp basDevp = basDevpService
+                    .getOne(new QueryWrapper<BasDevp>().eq("devp_no", deviceConfig.getDeviceNo()));
+            if (basDevp == null) {
+                return;
+            }
+
+            List<ZyStationStatusEntity> list = JSONObject.parseArray(basDevp.getStationList(), ZyStationStatusEntity.class);
+            for (ZyStationStatusEntity entity : list) {
+                StationProtocol stationProtocol = new StationProtocol();
+                stationProtocol.setStationId(entity.getStationId());
+                statusList.add(stationProtocol);
+            }
+            initialized = true;
+        }
+
+        int deviceLogCollectTime = initialized ? Utils.getDeviceLogCollectTime() : 200;
+        List<ZyStationStatusEntity> zyStationStatusEntities = zyStationConnectDriver.getStatus();
+        for (ZyStationStatusEntity statusEntity : zyStationStatusEntities) {
+            for (StationProtocol stationProtocol : statusList) {
+                if (stationProtocol.getStationId().equals(statusEntity.getStationId())) {
+                    stationProtocol.setTaskNo(statusEntity.getTaskNo());
+                    stationProtocol.setTargetStaNo(statusEntity.getTargetStaNo());
+                    stationProtocol.setAutoing(statusEntity.isAutoing());
+                    stationProtocol.setLoading(statusEntity.isLoading());
+                    stationProtocol.setInEnable(statusEntity.isInEnable());
+                    stationProtocol.setOutEnable(statusEntity.isOutEnable());
+                    stationProtocol.setEmptyMk(statusEntity.isEmptyMk());
+                    stationProtocol.setFullPlt(statusEntity.isFullPlt());
+                    stationProtocol.setPalletHeight(statusEntity.getPalletHeight());
+                    stationProtocol.setError(statusEntity.getError());
+                    stationProtocol.setErrorMsg(statusEntity.getErrorMsg());
+                    stationProtocol.setBarcode(statusEntity.getBarcode());
+                    stationProtocol.setRunBlock(statusEntity.isRunBlock());
+                    stationProtocol.setEnableIn(statusEntity.isEnableIn());
+                    stationProtocol.setWeight(statusEntity.getWeight());
+                    stationProtocol.setTaskWriteIdx(statusEntity.getTaskWriteIdx());
+                    stationProtocol.setTaskBufferItems(statusEntity.getTaskBufferItems());
+                    recentArrivalTracker.observe(statusEntity.getStationId(), statusEntity.getTaskNo(), statusEntity.isLoading());
+                }
+
+                if (!Cools.isEmpty(stationProtocol.getSystemWarning())) {
+                    if (stationProtocol.isAutoing() && !stationProtocol.isLoading()) {
+                        stationProtocol.setSystemWarning("");
+                    }
+                }
+            }
+        }
+
+        OutputQueue.DEVP.offer(MessageFormat.format("銆恵0}銆慬id:{1}] <<<<< 瀹炴椂鏁版嵁鏇存柊鎴愬姛",
+                DateUtils.convert(new Date()), deviceConfig.getDeviceNo()));
+
+        if (System.currentTimeMillis() - deviceDataLogTime > deviceLogCollectTime) {
+            DeviceDataLog deviceDataLog = new DeviceDataLog();
+            deviceDataLog.setOriginData(JSON.toJSONString(zyStationStatusEntities));
+            deviceDataLog.setWcsData(JSON.toJSONString(statusList));
+            deviceDataLog.setType(String.valueOf(SlaveType.Devp));
+            deviceDataLog.setDeviceNo(deviceConfig.getDeviceNo());
+            deviceDataLog.setCreateTime(new Date());
+
+            redisUtil.set(DeviceLogRedisKeyBuilder.build(deviceDataLog), deviceDataLog, 60 * 60 * 24);
+            deviceDataLogTime = System.currentTimeMillis();
+        }
+    }
+
+    public List<StationProtocol> getStatusList() {
+        return statusList;
+    }
+}
diff --git a/src/main/java/com/zy/core/utils/DualCrnOperateProcessUtils.java b/src/main/java/com/zy/core/utils/DualCrnOperateProcessUtils.java
index 951c437..834800b 100644
--- a/src/main/java/com/zy/core/utils/DualCrnOperateProcessUtils.java
+++ b/src/main/java/com/zy/core/utils/DualCrnOperateProcessUtils.java
@@ -24,6 +24,7 @@
 import com.zy.core.News;
 import com.zy.core.cache.MessageQueue;
 import com.zy.core.cache.SlaveConnection;
+import com.zy.core.dispatch.StationCommandDispatcher;
 import com.zy.core.enums.*;
 import com.zy.core.model.StationObjModel;
 import com.zy.core.model.Task;
@@ -69,6 +70,8 @@
     private StationOperateProcessUtils stationOperateProcessUtils;
     @Autowired
     private WrkAnalysisService wrkAnalysisService;
+    @Autowired
+    private StationCommandDispatcher stationCommandDispatcher;
 
     private static final String CRN_OUT_REQUIRE_STATION_OUT_ENABLE_CONFIG = "crnOutRequireStationOutEnable";
 
@@ -817,7 +820,7 @@
                         }
                         //鐢熸垚浠跨湡绔欑偣鏁版嵁
                         StationCommand command = stationThread.getCommand(StationCommandType.WRITE_INFO, 9998, wrkMast.getSourceStaNo(), 0, 0);
-                        MessageQueue.offer(SlaveType.Devp, stationObjModel.getDeviceNo(), new Task(2, command));
+                        stationCommandDispatcher.dispatch(stationObjModel.getDeviceNo(), command, "dual-crn-operate-process", "fake-out-complete-write-info");
                     }
                 }
 
diff --git a/src/main/java/com/zy/core/utils/StationOperateProcessUtils.java b/src/main/java/com/zy/core/utils/StationOperateProcessUtils.java
index e3ece21..85dc30d 100644
--- a/src/main/java/com/zy/core/utils/StationOperateProcessUtils.java
+++ b/src/main/java/com/zy/core/utils/StationOperateProcessUtils.java
@@ -1,586 +1,74 @@
 package com.zy.core.utils;
 
-import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.JSONObject;
-import com.alibaba.fastjson.serializer.SerializerFeature;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
-import com.core.common.Cools;
-import com.core.exception.CoolException;
-import com.zy.asrs.domain.enums.NotifyMsgType;
-import com.zy.asrs.domain.path.StationPathResolvedPolicy;
-import com.zy.asrs.domain.vo.StationCycleCapacityVo;
-import com.zy.asrs.domain.vo.StationCycleLoopVo;
-import com.zy.asrs.entity.*;
-import com.zy.asrs.service.*;
-import com.zy.asrs.utils.NotifyUtils;
-import com.zy.common.entity.FindCrnNoResult;
-import com.zy.common.model.NavigateNode;
-import com.zy.common.model.StartupDto;
-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.StationMoveDispatchMode;
-import com.zy.core.move.StationMoveSession;
-import com.zy.core.News;
-import com.zy.core.cache.MessageQueue;
-import com.zy.core.cache.SlaveConnection;
-import com.zy.core.enums.*;
-import com.zy.core.model.StationObjModel;
-import com.zy.core.model.Task;
-import com.zy.core.model.command.StationCommand;
-import com.zy.core.model.protocol.StationProtocol;
-import com.zy.core.model.protocol.StationTaskBufferItem;
-import com.zy.core.service.StationTaskLoopService;
-import com.zy.core.thread.StationThread;
+import com.zy.asrs.entity.WrkMast;
+import com.zy.asrs.service.WrkMastService;
+import com.zy.core.enums.WrkIoType;
+import com.zy.core.enums.WrkStsType;
+import com.zy.core.utils.station.StationDispatchLoadSupport;
+import com.zy.core.utils.station.StationOutboundDispatchProcessor;
+import com.zy.core.utils.station.StationRegularDispatchProcessor;
+import com.zy.core.utils.station.StationRerouteProcessor;
+import com.zy.core.utils.station.model.RerouteCommandPlan;
+import com.zy.core.utils.station.model.RerouteContext;
+import com.zy.core.utils.station.model.RerouteDecision;
+import com.zy.core.utils.station.model.RerouteExecutionResult;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
-import java.util.*;
+import java.util.List;
 
 @Component
 public class StationOperateProcessUtils {
-    private static final int LOOP_LOAD_RESERVE_EXPIRE_SECONDS = 120;
-    private static final int OUT_ORDER_DISPATCH_LIMIT_SECONDS = 2;
-    private static final int STATION_COMMAND_DISPATCH_DEDUP_SECONDS = 10;
-    private static final int STATION_IDLE_RECOVER_SECONDS = 10;
-    private static final int STATION_IDLE_RECOVER_LIMIT_SECONDS = 30;
-    private static final int STATION_IDLE_TRACK_EXPIRE_SECONDS = 60 * 60;
-    private static final long STATION_MOVE_RESET_WAIT_MS = 1000L;
-    private static final String IDLE_RECOVER_CLEARED_MEMO = "idleRecoverRerouteCleared";
-
-    @Autowired
-    private BasDevpService basDevpService;
     @Autowired
     private WrkMastService wrkMastService;
     @Autowired
-    private CommonService commonService;
+    private StationRegularDispatchProcessor stationRegularDispatchProcessor;
     @Autowired
-    private RedisUtil redisUtil;
+    private StationDispatchLoadSupport stationDispatchLoadSupport;
     @Autowired
-    private LocMastService locMastService;
+    private StationOutboundDispatchProcessor stationOutboundDispatchProcessor;
     @Autowired
-    private WmsOperateUtils wmsOperateUtils;
-    @Autowired
-    private NotifyUtils notifyUtils;
-    @Autowired
-    private NavigateUtils navigateUtils;
-    @Autowired
-    private BasStationService basStationService;
-    @Autowired
-    private StationCycleCapacityService stationCycleCapacityService;
-    @Autowired
-    private StationPathPolicyService stationPathPolicyService;
-    @Autowired
-    private BasStationOptService basStationOptService;
-    @Autowired
-    private StationTaskLoopService stationTaskLoopService;
-    @Autowired
-    private WrkAnalysisService wrkAnalysisService;
-    @Autowired
-    private StationMoveCoordinator stationMoveCoordinator;
+    private StationRerouteProcessor stationRerouteProcessor;
 
     //鎵ц杈撻�佺珯鐐瑰叆搴撲换鍔�
     public synchronized void stationInExecute() {
-        try {
-            DispatchLimitConfig baseLimitConfig = getDispatchLimitConfig(null, null);
-            int[] currentStationTaskCountRef = new int[]{countCurrentStationTask()};
-            LoadGuardState loadGuardState = buildLoadGuardState(baseLimitConfig);
-
-            List<BasDevp> basDevps = basDevpService.list(new QueryWrapper<>());
-            for (BasDevp basDevp : basDevps) {
-                StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo());
-                if (stationThread == null) {
-                    continue;
-                }
-
-                Map<Integer, StationProtocol> stationMap = stationThread.getStatusMap();
-
-                List<StationObjModel> list = basDevp.getBarcodeStationList$();
-                for (StationObjModel entity : list) {
-                    Integer stationId = entity.getStationId();
-                    if (!stationMap.containsKey(stationId)) {
-                        continue;
-                    }
-
-                    StationProtocol stationProtocol = stationMap.get(stationId);
-                    if (stationProtocol == null) {
-                        continue;
-                    }
-
-                    Object lock = redisUtil.get(RedisKeyType.STATION_IN_EXECUTE_LIMIT.key + stationId);
-                    if (lock != null) {
-                        continue;
-                    }
-
-                    //婊¤冻鑷姩銆佹湁鐗┿�佹湁宸ヤ綔鍙�
-                    if (stationProtocol.isAutoing()
-                            && stationProtocol.isLoading()
-                            && stationProtocol.getTaskNo() > 0
-                    ) {
-                        //妫�娴嬩换鍔℃槸鍚︾敓鎴�
-                        WrkMast wrkMast = wrkMastService.getOne(new QueryWrapper<WrkMast>().eq("barcode", stationProtocol.getBarcode()));
-                        if (wrkMast == null) {
-                            continue;
-                        }
-
-                        if (!Objects.equals(wrkMast.getWrkSts(), WrkStsType.NEW_INBOUND.sts)) {
-                            continue;
-                        }
-
-                        String locNo = wrkMast.getLocNo();
-                        FindCrnNoResult findCrnNoResult = commonService.findCrnNoByLocNo(locNo);
-                        if (findCrnNoResult == null) {
-                            News.taskInfo(wrkMast.getWrkNo(), "{}宸ヤ綔,鏈尮閰嶅埌鍫嗗灈鏈�", wrkMast.getWrkNo());
-                            continue;
-                        }
-
-                        Integer targetStationId = commonService.findInStationId(findCrnNoResult, stationId);
-                        if (targetStationId == null) {
-                            News.taskInfo(wrkMast.getWrkNo(), "{}绔欑偣,鎼滅储鍏ュ簱绔欑偣澶辫触", stationId);
-                            continue;
-                        }
-
-                        DispatchLimitConfig limitConfig = getDispatchLimitConfig(stationProtocol.getStationId(), targetStationId);
-                        LoopHitResult loopHitResult = findPathLoopHit(limitConfig, stationProtocol.getStationId(), targetStationId, loadGuardState);
-
-                        if (isDispatchBlocked(limitConfig, currentStationTaskCountRef[0], loadGuardState, loopHitResult.isThroughLoop())) {
-                            return;
-                        }
-
-                        StationCommand command = stationThread.getCommand(StationCommandType.MOVE, wrkMast.getWrkNo(), stationId, targetStationId, 0);
-                        if (command == null) {
-                            News.taskInfo(wrkMast.getWrkNo(), "{}宸ヤ綔,鑾峰彇杈撻�佺嚎鍛戒护澶辫触", wrkMast.getWrkNo());
-                            continue;
-                        }
-
-                        Date now = new Date();
-                        wrkMast.setWrkSts(WrkStsType.INBOUND_STATION_RUN.sts);
-                        wrkMast.setSourceStaNo(stationProtocol.getStationId());
-                        wrkMast.setStaNo(targetStationId);
-                        wrkMast.setSystemMsg("");
-                        wrkMast.setIoTime(now);
-                        wrkMast.setModiTime(now);
-                        if (wrkMastService.updateById(wrkMast)) {
-                            wrkAnalysisService.markInboundStationStart(wrkMast, now);
-                            boolean offered = offerDevpCommandWithDedup(basDevp.getDevpNo(), command, "stationInExecute");
-                            if (offered && stationMoveCoordinator != null) {
-                                // 鍒濆鍏ュ簱鍛戒护涔熺撼鍏� session 璺熻釜锛屽悗缁仠鐣欐仮澶�/缁曞湀/鍫靛閲嶇畻鎵嶈兘鍩轰簬鍚屼竴鏉¤矾绾跨姸鎬佸垽鏂��
-                                stationMoveCoordinator.recordDispatch(
-                                        wrkMast.getWrkNo(),
-                                        stationProtocol.getStationId(),
-                                        "stationInExecute",
-                                        command,
-                                        false
-                                );
-                            }
-                            News.info("杈撻�佺珯鐐瑰叆搴撳懡浠や笅鍙戞垚鍔燂紝绔欑偣鍙�={}锛屽伐浣滃彿={}锛屽懡浠ゆ暟鎹�={}", stationId, wrkMast.getWrkNo(), JSON.toJSONString(command));
-                            redisUtil.set(RedisKeyType.STATION_IN_EXECUTE_LIMIT.key + stationId, "lock", 5);
-                            loadGuardState.reserveLoopTask(loopHitResult.getLoopNo());
-                            saveLoopLoadReserve(wrkMast.getWrkNo(), loopHitResult);
-                        }
-                    }
-                }
-            }
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
+        stationRegularDispatchProcessor.stationInExecute();
     }
 
     //鎵ц鍫嗗灈鏈鸿緭閫佺珯鐐瑰嚭搴撲换鍔�
     public synchronized void crnStationOutExecute() {
-        try {
-            DispatchLimitConfig baseLimitConfig = getDispatchLimitConfig(null, null);
-            int[] currentStationTaskCountRef = new int[]{countCurrentStationTask()};
-            LoadGuardState loadGuardState = buildLoadGuardState(baseLimitConfig);
-
-            List<WrkMast> wrkMasts = wrkMastService.list(new QueryWrapper<WrkMast>()
-                    .eq("wrk_sts", WrkStsType.OUTBOUND_RUN_COMPLETE.sts)
-                    .isNotNull("crn_no")
-            );
-            List<Integer> outOrderList = getAllOutOrderList();
-
-            for (WrkMast wrkMast : wrkMasts) {
-                Object infoObj = redisUtil.get(RedisKeyType.CRN_OUT_TASK_COMPLETE_STATION_INFO.key + wrkMast.getWrkNo());
-                if (infoObj == null) {
-                    News.info("鍑哄簱浠诲姟{}鏁版嵁缂撳瓨涓嶅瓨鍦�", wrkMast.getWrkNo());
-                    continue;
-                }
-
-                StationObjModel stationObjModel = JSON.parseObject(infoObj.toString(), StationObjModel.class);
-                StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, stationObjModel.getDeviceNo());
-                if (stationThread == null) {
-                    continue;
-                }
-
-                Map<Integer, StationProtocol> stationMap = stationThread.getStatusMap();
-                StationProtocol stationProtocol = stationMap.get(stationObjModel.getStationId());
-                if (stationProtocol == null) {
-                    continue;
-                }
-
-                Object lock = redisUtil.get(RedisKeyType.STATION_OUT_EXECUTE_LIMIT.key + stationProtocol.getStationId());
-                if (lock != null) {
-                    continue;
-                }
-
-                //婊¤冻鑷姩銆佹湁鐗┿�佸伐浣滃彿0
-                if (stationProtocol.isAutoing()
-                        && stationProtocol.isLoading()
-                        && stationProtocol.getTaskNo() == 0
-                ) {
-                    // 鍏堢畻褰撳墠浠诲姟鍦ㄦ壒娆″嚭搴撲腑鐨勮矾寰勫�惧悜绯绘暟锛屽啀甯︾潃杩欎釜绯绘暟鍘诲喅绛栫洰鏍囩珯锛�
-                    // 杩欐牱鍚屼竴鎵规涓嶅悓搴忓彿浠诲姟鍦ㄦ帓搴忕偣銆佺粫鍦堢偣鍜屽牭濉為噸绠楁椂浼氬緱鍒颁竴鑷寸殑鐩爣瑁佸喅銆�
-                    Double pathLenFactor = resolveOutboundPathLenFactor(wrkMast);
-                    OutOrderDispatchDecision dispatchDecision = resolveOutboundDispatchDecision(
-                            stationProtocol.getStationId(),
-                            wrkMast,
-                            outOrderList,
-                            pathLenFactor
-                    );
-                    Integer moveStaNo = dispatchDecision == null ? null : dispatchDecision.getTargetStationId();
-                    if (moveStaNo == null) {
-                        continue;
-                    }
-
-                    DispatchLimitConfig limitConfig = getDispatchLimitConfig(stationProtocol.getStationId(), moveStaNo);
-                    LoopHitResult loopHitResult = findPathLoopHit(limitConfig, stationProtocol.getStationId(), moveStaNo, loadGuardState, wrkMast, pathLenFactor);
-
-                    if (isDispatchBlocked(limitConfig, currentStationTaskCountRef[0], loadGuardState, loopHitResult.isThroughLoop())) {
-                        return;
-                    }
-
-                    StationCommand command = buildOutboundMoveCommand(
-                            stationThread,
-                            wrkMast,
-                            stationProtocol.getStationId(),
-                            moveStaNo,
-                            pathLenFactor
-                    );
-                    if (command == null) {
-                        News.taskInfo(wrkMast.getWrkNo(), "鑾峰彇杈撻�佺嚎鍛戒护澶辫触");
-                        continue;
-                    }
-
-                    Date now = new Date();
-                    wrkMast.setWrkSts(WrkStsType.STATION_RUN.sts);
-                    wrkMast.setSystemMsg("");
-                    wrkMast.setIoTime(now);
-                    wrkMast.setModiTime(now);
-                    if (wrkMastService.updateById(wrkMast)) {
-                        wrkAnalysisService.markOutboundStationStart(wrkMast, now);
-                        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());
-                        currentStationTaskCountRef[0]++;
-                        loadGuardState.reserveLoopTask(loopHitResult.getLoopNo());
-                        saveLoopLoadReserve(wrkMast.getWrkNo(), loopHitResult);
-                    }
-                }
-            }
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
+        stationOutboundDispatchProcessor.crnStationOutExecute();
     }
 
     //鎵ц鍙屽伐浣嶅爢鍨涙満杈撻�佺珯鐐瑰嚭搴撲换鍔�
     public synchronized void dualCrnStationOutExecute() {
-        try {
-            List<WrkMast> wrkMasts = wrkMastService.list(new QueryWrapper<WrkMast>()
-                    .eq("wrk_sts", WrkStsType.OUTBOUND_RUN_COMPLETE.sts)
-                    .isNotNull("dual_crn_no")
-            );
-            for (WrkMast wrkMast : wrkMasts) {
-                Object infoObj = redisUtil.get(RedisKeyType.DUAL_CRN_OUT_TASK_STATION_INFO.key + wrkMast.getWrkNo());
-                if (infoObj == null) {
-                    News.info("鍑哄簱浠诲姟{}鏁版嵁缂撳瓨涓嶅瓨鍦�", wrkMast.getWrkNo());
-                    continue;
-                }
-
-                StationObjModel stationObjModel = JSON.parseObject(infoObj.toString(), StationObjModel.class);
-                StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, stationObjModel.getDeviceNo());
-                if (stationThread == null) {
-                    continue;
-                }
-
-                Map<Integer, StationProtocol> stationMap = stationThread.getStatusMap();
-                StationProtocol stationProtocol = stationMap.get(stationObjModel.getStationId());
-                if (stationProtocol == null) {
-                    continue;
-                }
-
-                Object lock = redisUtil.get(RedisKeyType.STATION_OUT_EXECUTE_LIMIT.key + stationProtocol.getStationId());
-                if (lock != null) {
-                    continue;
-                }
-
-                //婊¤冻鑷姩銆佹湁鐗┿�佸伐浣滃彿0
-                if (stationProtocol.isAutoing()
-                        && stationProtocol.isLoading()
-                        && stationProtocol.getTaskNo() == 0
-                ) {
-                    Double pathLenFactor = resolveOutboundPathLenFactor(wrkMast);
-                    StationCommand command = buildOutboundMoveCommand(
-                            stationThread,
-                            wrkMast,
-                            stationProtocol.getStationId(),
-                            wrkMast.getStaNo(),
-                            pathLenFactor
-                    );
-                    if (command == null) {
-                        News.taskInfo(wrkMast.getWrkNo(), "鑾峰彇杈撻�佺嚎鍛戒护澶辫触");
-                        continue;
-                    }
-
-                    wrkMast.setWrkSts(WrkStsType.STATION_RUN.sts);
-                    wrkMast.setSystemMsg("");
-                    wrkMast.setIoTime(new Date());
-                    if (wrkMastService.updateById(wrkMast)) {
-                        boolean offered = offerDevpCommandWithDedup(stationObjModel.getDeviceNo(), command, "dualCrnStationOutExecute");
-                        if (offered && stationMoveCoordinator != null) {
-                            // 鍙屽伐浣嶅爢鍨涙満杞叆杈撻�佺嚎鍚庡悓鏍疯鐧昏 session锛屽惁鍒欏悗缁噸绠楀彧鑳界湅鍒� PLC 鍛戒护锛岀湅涓嶅埌璺嚎璇箟銆�
-                            stationMoveCoordinator.recordDispatch(
-                                    wrkMast.getWrkNo(),
-                                    stationProtocol.getStationId(),
-                                    "dualCrnStationOutExecute",
-                                    command,
-                                    false
-                            );
-                        }
-                        notifyUtils.notify(String.valueOf(SlaveType.Devp), stationObjModel.getDeviceNo(), String.valueOf(wrkMast.getWrkNo()), wrkMast.getWmsWrkNo(), NotifyMsgType.STATION_OUT_TASK_RUN, null);
-                        News.info("杈撻�佺珯鐐瑰嚭搴撳懡浠や笅鍙戞垚鍔燂紝绔欑偣鍙�={}锛屽伐浣滃彿={}锛屽懡浠ゆ暟鎹�={}", stationProtocol.getStationId(), wrkMast.getWrkNo(), JSON.toJSONString(command));
-                        redisUtil.set(RedisKeyType.STATION_OUT_EXECUTE_LIMIT.key + stationProtocol.getStationId(), "lock", 5);
-                        redisUtil.del(RedisKeyType.DUAL_CRN_OUT_TASK_STATION_INFO.key + wrkMast.getWrkNo());
-                    }
-                }
-            }
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
+        stationOutboundDispatchProcessor.dualCrnStationOutExecute();
     }
 
     //妫�娴嬭緭閫佺珯鐐瑰嚭搴撲换鍔℃墽琛屽畬鎴�
     public synchronized void stationOutExecuteFinish() {
-        try {
-            List<WrkMast> wrkMasts = wrkMastService.list(new QueryWrapper<WrkMast>().eq("wrk_sts", WrkStsType.STATION_RUN.sts));
-            for (WrkMast wrkMast : wrkMasts) {
-                Integer wrkNo = wrkMast.getWrkNo();
-                Integer targetStaNo = wrkMast.getStaNo();
-                if (wrkNo == null || targetStaNo == null) {
-                    continue;
-                }
-
-                boolean complete = false;
-                Integer targetDeviceNo = null;
-                StationThread stationThread = null;
-                BasStation basStation = basStationService.getOne(new QueryWrapper<BasStation>().eq("station_id", targetStaNo));
-                if (basStation != null) {
-                    targetDeviceNo = basStation.getDeviceNo();
-                    stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basStation.getDeviceNo());
-                    if (stationThread != null) {
-                        Map<Integer, StationProtocol> statusMap = stationThread.getStatusMap();
-                        StationProtocol stationProtocol = statusMap.get(basStation.getStationId());
-                        if (stationProtocol != null && wrkNo.equals(stationProtocol.getTaskNo())) {
-                            complete = true;
-                        }
-                    }
-                }
-
-                if (complete) {
-                    attemptClearTaskPath(stationThread, wrkNo);
-                    completeStationRunTask(wrkMast, targetDeviceNo);
-                }
-            }
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
-    }
-
-    private void attemptClearTaskPath(StationThread stationThread, Integer taskNo) {
-        if (stationThread == null || taskNo == null || taskNo <= 0) {
-            return;
-        }
-        try {
-            boolean cleared = stationThread.clearPath(taskNo);
-            if (cleared) {
-                News.info("杈撻�佺珯鐐逛换鍔¤繍琛屽畬鎴愬悗娓呯悊娈嬬暀璺緞锛屽伐浣滃彿={}", taskNo);
-            }
-        } catch (Exception e) {
-            News.error("杈撻�佺珯鐐逛换鍔¤繍琛屽畬鎴愬悗娓呯悊娈嬬暀璺緞寮傚父锛屽伐浣滃彿={}", taskNo, e);
-        }
-    }
-
-    private void completeStationRunTask(WrkMast wrkMast, Integer deviceNo) {
-        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);
-        wrkMast.setModiTime(now);
-        wrkMastService.updateById(wrkMast);
-        wrkAnalysisService.markOutboundStationComplete(wrkMast, now);
-        if (deviceNo != null) {
-            notifyUtils.notify(String.valueOf(SlaveType.Devp), deviceNo, String.valueOf(wrkMast.getWrkNo()), wrkMast.getWmsWrkNo(), NotifyMsgType.STATION_OUT_TASK_RUN_COMPLETE, null);
-        }
-        redisUtil.set(RedisKeyType.STATION_OUT_EXECUTE_COMPLETE_LIMIT.key + wrkMast.getWrkNo(), "lock", 60);
+        stationRegularDispatchProcessor.stationOutExecuteFinish();
     }
 
     // 妫�娴嬩换鍔¤浆瀹屾垚
     public synchronized void checkTaskToComplete() {
-        try {
-            List<WrkMast> wrkMasts = wrkMastService.list(new QueryWrapper<WrkMast>().eq("wrk_sts", WrkStsType.STATION_RUN_COMPLETE.sts));
-            for (WrkMast wrkMast : wrkMasts) {
-                Integer wrkNo = wrkMast.getWrkNo();
-                Integer targetStaNo = wrkMast.getStaNo();
-
-                Object lock = redisUtil.get(RedisKeyType.STATION_OUT_EXECUTE_COMPLETE_LIMIT.key + wrkNo);
-                if (lock != null) {
-                    continue;
-                }
-
-                boolean complete = false;
-                BasStation basStation = basStationService.getOne(new QueryWrapper<BasStation>().eq("station_id", targetStaNo));
-                if (basStation == null) {
-                    continue;
-                }
-
-                StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basStation.getDeviceNo());
-                if (stationThread == null) {
-                    continue;
-                }
-
-                Map<Integer, StationProtocol> statusMap = stationThread.getStatusMap();
-                StationProtocol stationProtocol = statusMap.get(basStation.getStationId());
-                if (stationProtocol == null) {
-                    continue;
-                }
-
-                if (!stationProtocol.getTaskNo().equals(wrkNo)) {
-                    complete = true;
-                }
-
-                if (complete) {
-                    if (stationMoveCoordinator != null) {
-                        stationMoveCoordinator.finishSession(wrkNo);
-                    }
-                    wrkMast.setWrkSts(WrkStsType.COMPLETE_OUTBOUND.sts);
-                    wrkMast.setIoTime(new Date());
-                    wrkMastService.updateById(wrkMast);
-                }
-            }
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
+        stationRegularDispatchProcessor.checkTaskToComplete();
     }
 
     //妫�娴嬭緭閫佺珯鐐规槸鍚﹁繍琛屽牭濉�
     public synchronized void checkStationRunBlock() {
-        try {
-            List<BasDevp> basDevps = basDevpService.list(new QueryWrapper<>());
-            for (BasDevp basDevp : basDevps) {
-                StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo());
-                if (stationThread == null) {
-                    continue;
-                }
-
-                List<Integer> runBlockReassignLocStationList = new ArrayList<>();
-                for (StationObjModel stationObjModel : basDevp.getRunBlockReassignLocStationList$()) {
-                    runBlockReassignLocStationList.add(stationObjModel.getStationId());
-                }
-                List<Integer> outOrderStationIds = basDevp.getOutOrderIntList();
-
-                List<StationProtocol> list = stationThread.getStatus();
-                for (StationProtocol stationProtocol : list) {
-                    if (stationProtocol.isAutoing()
-                            && stationProtocol.isLoading()
-                            && stationProtocol.getTaskNo() > 0
-                            && stationProtocol.isRunBlock()
-                    ) {
-                        WrkMast wrkMast = wrkMastService.selectByWorkNo(stationProtocol.getTaskNo());
-                        if (wrkMast == null) {
-                            News.info("杈撻�佺珯鐐瑰彿={} 杩愯闃诲锛屼絾鏃犳硶鎵惧埌瀵瑰簲浠诲姟锛屽伐浣滃彿={}", stationProtocol.getStationId(), stationProtocol.getTaskNo());
-                            continue;
-                        }
-
-                        Object lock = redisUtil.get(RedisKeyType.CHECK_STATION_RUN_BLOCK_LIMIT_.key + stationProtocol.getTaskNo());
-                        if (lock != null) {
-                            continue;
-                        }
-                        redisUtil.set(RedisKeyType.CHECK_STATION_RUN_BLOCK_LIMIT_.key + stationProtocol.getTaskNo(), "lock", 15);
-
-                        if (shouldUseRunBlockDirectReassign(wrkMast, stationProtocol.getStationId(), runBlockReassignLocStationList)) {
-                            executeRunBlockDirectReassign(basDevp, stationThread, stationProtocol, wrkMast);
-                            continue;
-                        }
-
-                        Double pathLenFactor = resolveOutboundPathLenFactor(wrkMast);
-                        // 杩愯鍫靛涓嶅崟鐙喅瀹氫笟鍔$洰鏍囩珯锛屼粛鐒跺鐢ㄥ嚭搴撴帓搴�/缁曞湀鐨勭洰鏍囪鍐筹紝
-                        // 杩欓噷鍙槸瑕佹眰鐢� run-block 涓撶敤绠楄矾锛屽苟鍦ㄩ噸鍙戝墠娓呮帀鏃� session/segment 鐘舵�併��
-                        RerouteContext context = RerouteContext.create(
-                                RerouteSceneType.RUN_BLOCK_REROUTE,
-                                basDevp,
-                                stationThread,
-                                stationProtocol,
-                                wrkMast,
-                                outOrderStationIds,
-                                pathLenFactor,
-                                "checkStationRunBlock_reroute"
-                        ).withRunBlockCommand()
-                                .withSuppressDispatchGuard()
-                                .withCancelSessionBeforeDispatch()
-                                .withResetSegmentCommandsBeforeDispatch();
-                        executeSharedReroute(context);
-                    }
-                }
-            }
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
+        stationRerouteProcessor.checkStationRunBlock();
     }
 
     //妫�娴嬭緭閫佺珯鐐逛换鍔″仠鐣欒秴鏃跺悗閲嶆柊璁$畻璺緞
     public synchronized void checkStationIdleRecover() {
-        try {
-            List<BasDevp> basDevps = basDevpService.list(new QueryWrapper<>());
-            for (BasDevp basDevp : basDevps) {
-                StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo());
-                if (stationThread == null) {
-                    continue;
-                }
-
-                List<StationProtocol> list = stationThread.getStatus();
-                for (StationProtocol stationProtocol : list) {
-                    if (stationProtocol.isAutoing()
-                            && stationProtocol.isLoading()
-                            && stationProtocol.getTaskNo() > 0
-                            && !stationProtocol.isRunBlock()
-                    ) {
-                        checkStationIdleRecover(basDevp, stationThread, stationProtocol, basDevp.getOutOrderIntList());
-                    }
-                }
-            }
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
+        stationRerouteProcessor.checkStationIdleRecover();
     }
 
     //鑾峰彇杈撻�佺嚎浠诲姟鏁伴噺
     public synchronized int getCurrentStationTaskCount() {
-        return countCurrentStationTask();
+        return stationDispatchLoadSupport.countCurrentStationTask();
     }
 
     public synchronized int getCurrentOutboundTaskCountByTargetStation(Integer stationId) {
@@ -598,2457 +86,35 @@
 
     // 妫�娴嬪嚭搴撴帓搴�
     public synchronized void checkStationOutOrder() {
-        List<BasDevp> basDevps = basDevpService.list(new QueryWrapper<BasDevp>());
-        for (BasDevp basDevp : basDevps) {
-            StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo());
-            if (stationThread == null) {
-                continue;
-            }
-            Map<Integer, StationProtocol> statusMap = stationThread.getStatusMap();
-            List<StationObjModel> orderList = basDevp.getOutOrderList$();
-            List<Integer> outOrderStationIds = basDevp.getOutOrderIntList();
-            for (StationObjModel stationObjModel : orderList) {
-                StationProtocol stationProtocol = statusMap.get(stationObjModel.getStationId());
-                if (stationProtocol == null) {
-                    continue;
-                }
-
-                if (!stationProtocol.isAutoing()) {
-                    continue;
-                }
-
-                if (!stationProtocol.isLoading()) {
-                    continue;
-                }
-
-                if (stationProtocol.getTaskNo() <= 0) {
-                    continue;
-                }
-
-                // 鎺掑簭鐐规湰韬凡缁忓牭濉炴椂锛屼笉鍦� out-order 閲屽仛浜屾鍐崇瓥锛岀粺涓�浜ょ粰 run-block 閲嶈鍒掑鐞嗐��
-                if (stationProtocol.isRunBlock()) {
-                    continue;
-                }
-
-                if (!stationProtocol.getStationId().equals(stationProtocol.getTargetStaNo())) {
-                    continue;
-                }
-
-                WrkMast wrkMast = wrkMastService.selectByWorkNo(stationProtocol.getTaskNo());
-                if (wrkMast == null) {
-                    continue;
-                }
-                if (!Objects.equals(wrkMast.getWrkSts(), WrkStsType.STATION_RUN.sts)) {
-                    continue;
-                }
-                if (Objects.equals(stationProtocol.getStationId(), wrkMast.getStaNo())) {
-                    continue;
-                }
-                // 鍙湁娲诲姩涓殑鐜版湁璺嚎鎵嶄細鍘嬪埗 out-order锛汢LOCKED 璺嚎瑕佸厑璁告帓搴忕偣閲嶆柊鍚姩銆�
-                if (shouldSkipOutOrderDispatchForExistingRoute(wrkMast.getWrkNo(), stationProtocol.getStationId())) {
-                    continue;
-                }
-
-                Double pathLenFactor = resolveOutboundPathLenFactor(wrkMast);
-                RerouteContext context = RerouteContext.create(
-                        RerouteSceneType.OUT_ORDER,
-                        basDevp,
-                        stationThread,
-                        stationProtocol,
-                        wrkMast,
-                        outOrderStationIds,
-                        pathLenFactor,
-                        "checkStationOutOrder"
-                ).withDispatchDeviceNo(stationObjModel.getDeviceNo())
-                        .withSuppressDispatchGuard()
-                        .withOutOrderDispatchLock()
-                        .withResetSegmentCommandsBeforeDispatch();
-                executeSharedReroute(context);
-            }
-        }
+        stationRerouteProcessor.checkStationOutOrder();
     }
 
     // 鐩戞帶缁曞湀绔欑偣
     public synchronized void watchCircleStation() {
-        List<BasDevp> basDevps = basDevpService.list(new QueryWrapper<BasDevp>());
-        for (BasDevp basDevp : basDevps) {
-            StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo());
-            if (stationThread == null) {
-                continue;
-            }
-
-            List<Integer> outOrderList = basDevp.getOutOrderIntList();
-
-            for (StationProtocol stationProtocol : stationThread.getStatus()) {
-                if (!stationProtocol.isAutoing()) {
-                    continue;
-                }
-
-                if (!stationProtocol.isLoading()) {
-                    continue;
-                }
-
-                if (stationProtocol.getTaskNo() <= 0) {
-                    continue;
-                }
-
-                // 缁曞湀瑙﹀彂浼樺厛璇� session 鐨勪笅涓�鍐崇瓥绔欙紝legacy WATCH_CIRCLE key 鍙仛鍏煎鍥為��銆�
-                if (!isWatchingCircleArrival(stationProtocol.getTaskNo(), stationProtocol.getStationId())) {
-                    continue;
-                }
-
-                WrkMast wrkMast = wrkMastService.selectByWorkNo(stationProtocol.getTaskNo());
-                if (wrkMast == null) {
-                    continue;
-                }
-                if (!Objects.equals(wrkMast.getWrkSts(), WrkStsType.STATION_RUN.sts)) {
-                    continue;
-                }
-                if (Objects.equals(stationProtocol.getStationId(), wrkMast.getStaNo())) {
-                    continue;
-                }
-                Double pathLenFactor = resolveOutboundPathLenFactor(wrkMast);
-                RerouteContext context = RerouteContext.create(
-                        RerouteSceneType.WATCH_CIRCLE,
-                        basDevp,
-                        stationThread,
-                        stationProtocol,
-                        wrkMast,
-                        outOrderList,
-                        pathLenFactor,
-                        "watchCircleStation"
-                ).withSuppressDispatchGuard()
-                        .withOutOrderDispatchLock()
-                        .withResetSegmentCommandsBeforeDispatch();
-                executeSharedReroute(context);
-            }
-        }
-    }
-
-    private 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)
-        );
+        stationRerouteProcessor.watchCircleStation();
     }
 
     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) {
-            if (context.sceneType() == RerouteSceneType.RUN_BLOCK_REROUTE) {
-                News.taskInfo(context.wrkMast().getWrkNo(),
-                        "杈撻�佺珯鐐瑰牭濉為噸瑙勫垝鏈壘鍒板彲涓嬪彂璺嚎锛屽綋鍓嶇珯鐐�={}锛岀洰鏍囩珯鐐�={}",
-                        currentStationId,
-                        targetStationId);
-            } else if (context.sceneType() == RerouteSceneType.IDLE_RECOVER) {
-                News.taskInfo(context.wrkMast().getWrkNo(),
-                        "绔欑偣浠诲姟鍋滅暀瓒呮椂鍚庨噸绠楄矾寰勫け璐ワ紝褰撳墠绔欑偣={}锛岀洰鏍囩珯鐐�={}",
-                        currentStationId,
-                        targetStationId);
-            } else {
-                News.taskInfo(context.wrkMast().getWrkNo(), "鑾峰彇杈撻�佺嚎鍛戒护澶辫触");
-            }
-            return RerouteCommandPlan.skip("missing-command");
-        }
-        return RerouteCommandPlan.dispatch(command, decision, context.dispatchScene());
+        return stationRerouteProcessor.buildRerouteCommandPlan(context, decision);
     }
 
     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 (stationMoveCoordinator != null) {
-            return stationMoveCoordinator.withTaskDispatchLock(taskNo,
-                    () -> executeReroutePlanWithTaskLock(context, plan, stationProtocol, taskNo, stationId));
-        }
-        return executeReroutePlanWithTaskLock(context, plan, stationProtocol, taskNo, stationId);
-    }
-
-    private RerouteExecutionResult executeReroutePlanWithTaskLock(RerouteContext context,
-                                                                  RerouteCommandPlan plan,
-                                                                  StationProtocol stationProtocol,
-                                                                  Integer taskNo,
-                                                                  Integer stationId) {
-        boolean runBlockReroute = context.sceneType() == RerouteSceneType.RUN_BLOCK_REROUTE;
-        if (context.checkRecentDispatch()
-                && shouldSkipIdleRecoverForRecentDispatch(taskNo, stationId)) {
-            return RerouteExecutionResult.skip("recent-dispatch");
-        }
-        int currentTaskBufferCommandCount = countCurrentTaskBufferCommands(stationProtocol.getTaskBufferItems(), taskNo);
-        if (currentTaskBufferCommandCount > 0 && !runBlockReroute) {
-            if (context.sceneType() == RerouteSceneType.IDLE_RECOVER) {
-                News.info("杈撻�佺珯鐐逛换鍔″仠鐣欒秴鏃讹紝浣嗙紦瀛樺尯浠嶅瓨鍦ㄥ綋鍓嶄换鍔″懡浠わ紝宸茶烦杩囬噸绠椼�傜珯鐐瑰彿={}锛屽伐浣滃彿={}锛屽綋鍓嶄换鍔″懡浠ゆ暟={}",
-                        stationId,
-                        taskNo,
-                        currentTaskBufferCommandCount);
-            }
-            return RerouteExecutionResult.skip("buffer-has-current-task");
-        }
-        if (currentTaskBufferCommandCount > 0 && runBlockReroute) {
-            // 鍫靛閲嶈鍒掕鏇挎崲鐨勬鏄繖浜涙棫鍒嗘鍛戒护锛屼笉鑳藉啀鎶婃畫鐣� buffer 褰撴垚鏂扮殑鎷︽埅鏉′欢銆�
-            News.info("杈撻�佺珯鐐硅繍琛屽牭濉為噸瑙勫垝妫�娴嬪埌鏃у垎娈靛懡浠ゆ畫鐣欙紝宸插厛娓呯悊鏈湴鐘舵�佸悗缁х画閲嶅彂銆傜珯鐐瑰彿={}锛屽伐浣滃彿={}锛屽綋鍓嶄换鍔″懡浠ゆ暟={}",
-                    stationId,
-                    taskNo,
-                    currentTaskBufferCommandCount);
-        }
-        if (!runBlockReroute
-                && context.checkSuppressDispatch()
-                && stationMoveCoordinator != null
-                && stationMoveCoordinator.shouldSuppressDispatch(taskNo, stationId, plan.command())) {
-            return RerouteExecutionResult.skip("dispatch-suppressed");
-        }
-        // 杩涘叆鍫靛閲嶈鍒掑悗锛屾棫璺嚎宸茬粡琚樉寮忓彇娑堬紝鏈疆鍛戒护涓嶅啀鍙備笌 active-session suppress 鍒ゅ畾銆�
-        if (context.requireOutOrderDispatchLock()
-                && !tryAcquireOutOrderDispatchLock(taskNo, stationId)) {
-            return RerouteExecutionResult.skip("out-order-lock");
-        }
-
-        if (context.cancelSessionBeforeDispatch() && stationMoveCoordinator != null) {
-            // 鍒囪矾鍓嶅厛鎶婃棫 session 缃负 CANCEL_PENDING锛岃宸茬粡鎺掗槦涓殑鏃у垎娈电嚎绋嬪湪鏈�缁堝彂閫佸墠鍋滀笅銆�
-            stationMoveCoordinator.markCancelPending(taskNo, "reroute_pending");
-        }
-
-        if (runBlockReroute) {
-            // 绔欑偣杩涘叆鍫靛鍚庯紝璁惧渚у彲鑳藉凡缁忔妸涔嬪墠棰勪笅鍙戠殑鍒嗘鍛戒护娓呮帀浜嗐��
-            // 鍏堜綔搴熸湰鍦� session/segment 鐘舵�侊紝鍐嶆寜鏂拌矾绾块噸鍙戯紝閬垮厤琚棫鐘舵�佸弽鍚戝崱浣忋��
-            if (context.cancelSessionBeforeDispatch() && stationMoveCoordinator != null) {
-                stationMoveCoordinator.cancelSession(taskNo);
-            }
-            if (context.resetSegmentCommandsBeforeDispatch()) {
-                resetSegmentMoveCommandsBeforeReroute(taskNo);
-            }
-        }
-
-        if (!runBlockReroute
-                && 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 (!runBlockReroute && 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);
+        return stationRerouteProcessor.executeReroutePlan(context, plan);
     }
 
     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,
-                                                        Integer sourceStationId,
-                                                        Integer targetStationId,
-                                                        Double pathLenFactor) {
-        Double normalizedFactor = normalizePathLenFactor(pathLenFactor);
-        Integer currentTaskNo = wrkMast == null ? null : wrkMast.getWrkNo();
-        if (currentTaskNo == null) {
-            return navigateUtils.calcByStationId(sourceStationId, targetStationId, normalizedFactor);
-        }
-        return navigateUtils.calcByStationId(sourceStationId, targetStationId, currentTaskNo, normalizedFactor);
-    }
-
-    /**
-     * 璁$畻褰撳墠鍑哄簱浠诲姟鐨勮矾寰勫�惧悜绯绘暟銆�
-     *
-     * <p>杩欎釜绯绘暟涓嶆槸涓氬姟鐩爣绔欐湰韬紝鑰屾槸鈥滃湪澶氭潯鍙璺嚎涔嬮棿鏇村亸鍚戝摢涓�鏉♀�濈殑杈呭姪杈撳叆锛�
-     * 鐩殑鏄鍚屼竴鎵规銆佷笉鍚屽簭鍙风殑浠诲姟鍦ㄥ叡浜幆绾块噷灏介噺褰㈡垚绋冲畾銆佸彲閲嶅鐨勮矾寰勫垎甯冦��
-     *
-     * <p>杩斿洖鍊艰寖鍥村浐瀹氬湪 {@code [0, 1]}锛�
-     * 1. 闈炴壒娆″嚭搴撲换鍔★紝鐩存帴杩斿洖 {@code 0.0}锛岃〃绀轰笉寮曞叆棰濆璺緞鍋忕疆銆�
-     * 2. 褰撳墠鎵规鍙湁 1 涓湁鏁堟椿鍔ㄤ换鍔★紝杩斿洖 {@code 0.0}锛屽洜涓烘病鏈夆�滃墠鍚庨『搴忊�濆彲姣旇緝銆�
-     * 3. 鍚﹀垯鎸夆�滃綋鍓嶄换鍔″墠闈㈣繕鏈夊灏戜釜鏈夋晥鍓嶅簭浠诲姟鈥濆崰鈥滄湁鏁堟椿鍔ㄤ换鍔℃�绘暟鈥濈殑姣斾緥鏉ョ畻銆�
-     *
-     * <p>杩欓噷鐨勨�滄湁鏁堜换鍔♀�濆彧缁熻锛�
-     * 1. ioType=OUT 鐨勫嚭搴撲换鍔★紱
-     * 2. 浠嶅浜庢椿鍔ㄧ姸鎬侊紝鏈畬鎴�/鏈粨绠楋紱
-     * 3. 鏈� batchSeq锛�
-     * 4. mk != taskCancel銆�
-     *
-     * <p>缁撴灉鍚箟鍙互鐩磋鐞嗚В涓猴細
-     * 浠诲姟鎵规搴忓彿瓒婇潬鍚庯紝鍓嶉潰宸茬粡瀛樺湪鐨勬湁鏁堜换鍔¤秺澶氾紝寰楀埌鐨勭郴鏁拌秺澶э紱
-     * 鍚庣画绠楄矾鏃跺氨鏇村鏄撳拰鍓嶅簭浠诲姟褰㈡垚绋冲畾鐨勮矾寰勫垎娴侊紝鑰屼笉鏄墍鏈変换鍔¢兘璧板悓涓�鏉¢粯璁ょ煭璺��
-     *
-     * <p>娉ㄦ剰锛�
-     * 杩欎釜鏂规硶涓嶇洿鎺ュ喅瀹氱洰鏍囩珯锛屼笉璐熻矗鎺掑簭鏀捐锛屽彧鎻愪緵鈥滆矾寰勫亸濂解�濊緭鍏ャ��
-     * 鐪熸鐨勭洰鏍囩珯浠嶇敱 {@link #resolveOutboundDispatchDecision(Integer, WrkMast, List, Double)} 鍐冲畾銆�
-     */
-    private Double resolveOutboundPathLenFactor(WrkMast wrkMast) {
-        if (!isBatchOutboundTaskWithSeq(wrkMast)) {
-            return 0.0d;
-        }
-        List<WrkMast> 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));
-    }
-
-    /**
-     * 鍒ゆ柇褰撳墠浠诲姟鏄惁鍏峰鈥滄寜鎵规鍑哄簱瑙勫垯鍙備笌鎺掑簭/璺緞鍋忓ソ璁$畻鈥濈殑鍩虹鏉′欢銆�
-     *
-     * <p>杩欓噷鍙仛鏈�鍩虹鐨勮祫鏍艰繃婊わ紝涓嶅叧蹇冨綋鍓嶆槸鍚︾湡鐨勯渶瑕佹帓搴忕偣浠嬪叆銆�
-     * 鍙涓嶆槸鎵规鍑哄簱浠诲姟锛屽悗闈㈢殑璺緞鍋忓ソ绯绘暟涓庢帓搴忕洰鏍囧喅绛栭兘搴旇鐩存帴閫�鍖栦负榛樿琛屼负銆�
-     */
-    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;
-    }
-
-    /**
-     * 鍔犺浇鍚屼竴鎵规涓嬩粛澶勪簬娲诲姩涓殑鍑哄簱浠诲姟銆�
-     *
-     * <p>杩欓噷鐢ㄤ簬涓ょ被璁$畻锛�
-     * 1. 璁$畻璺緞鍋忓ソ绯绘暟鏃讹紝缁熻褰撳墠浠诲姟鍓嶉潰杩樻湁澶氬皯涓湁鏁堝墠搴忎换鍔°��
-     * 2. 褰撳墠鎺掑簭鐐归噸鏂板喅绛栨椂锛屾壘鍑鸿繖涓�鎵光�滈涓湭瀹屾垚浠诲姟鈥濈殑瀹為檯鎵规搴忓彿銆�
-     *
-     * <p>宸茬粡瀹屾垚/缁撶畻鐨勪换鍔′笉鍐嶅弬涓庡綋鍓嶆壒娆$殑鎺掑簭涓庡亸濂借绠椼��
-     */
-    private List<WrkMast> loadActiveBatchTaskList(String batch) {
-        if (Cools.isEmpty(batch)) {
-            return Collections.emptyList();
-        }
-        return wrkMastService.list(new QueryWrapper<WrkMast>()
-                .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));
-    }
-
-    /**
-     * 鍒ゆ柇鏌愭潯鎵规浠诲姟鏄惁搴旇璁″叆璺緞鍋忓ソ绯绘暟鐨勫垎姣�/鍒嗗瓙缁熻銆�
-     *
-     * <p>杩欓噷鎺掗櫎娌℃湁 batchSeq 鐨勪换鍔′互鍙婅鏄惧紡鏍囪涓� taskCancel 鐨勪换鍔★紝
-     * 閬垮厤鏃犳晥浠诲姟鎶婂悓鎵规鐨勮矾寰勫亸濂借绠楁媺鍋忋��
-     */
-    private boolean isFactorCandidateTask(WrkMast wrkMast) {
-        return wrkMast != null
-                && Objects.equals(wrkMast.getIoType(), WrkIoType.OUT.id)
-                && wrkMast.getBatchSeq() != null
-                && !"taskCancel".equals(wrkMast.getMk());
-    }
-
-    public List<Integer> getAllOutOrderList() {
-        List<Integer> list = new ArrayList<>();
-        List<BasDevp> basDevps = basDevpService.list(new QueryWrapper<BasDevp>());
-        for (BasDevp basDevp : basDevps) {
-            List<Integer> orderList = basDevp.getOutOrderIntList();
-            list.addAll(orderList);
-        }
-        return list;
-    }
-
-    /**
-     * 缁熶竴璁$畻褰撳墠浠诲姟鈥滄鍒诲簲璇ユ湞鍝釜鐩爣绔欑户缁繍琛屸�濄��
-     *
-     * <p>杩欐槸鍑哄簱鎺掑簭銆佺粫鍦堛�佸牭濉為噸瑙勫垝鍏辩敤鐨勭洰鏍囪鍐冲叆鍙c��
-     * 涓嶇瑙﹀彂鏉ユ簮鏄� OUT_ORDER銆乄ATCH_CIRCLE 杩樻槸 RUN_BLOCK_REROUTE锛�
-     * 鍙涓氬姟璇箟杩樻槸鈥滃綋鍓嶈繖绁ㄥ嚭搴撲换鍔′笅涓�姝ヨ寰�鍝噷璧扳�濓紝閮戒粠杩欓噷寰楀嚭鐩爣绔欍��
-     *
-     * <p>瀹冨仛涓夊眰鍒ゆ柇锛�
-     * 1. 濡傛灉褰撳墠浠诲姟鏍规湰涓嶉�傜敤鍑哄簱鎺掑簭锛岀洿鎺ヨ繑鍥炰换鍔′笟鍔$洰鏍囩珯 {@code wrkMast.staNo}銆�
-     * 2. 濡傛灉閫傜敤鍑哄簱鎺掑簭锛屽厛绠楀嚭鈥滃綋鍓嶆壒娆¤鍒欎笅锛屾鍒诲厑璁稿墠寰�鐨� dispatchStationId鈥濄��
-     * 3. 濡傛灉褰撳墠绔欑偣姝eソ灏辨槸鎺掑簭鍐崇瓥鐐癸紝鍐嶈繘涓�姝ュ垽鏂槸锛�
-     *    鐩存帴鍘荤洰鏍囩偣锛岃繕鏄厛杩涘叆缁曞湀鐩爣鐐癸紝鎴栬�呭洜涓轰弗鏍肩獥鍙i檺鍒惰�屾殏涓嶆斁琛屻��
-     *
-     * <p>杩斿洖鐨� {@link OutOrderDispatchDecision} 涓嶅彧鏄竴涓洰鏍囩珯锛�
-     * 杩樻惡甯︿簡杩欐鍐崇瓥鏄惁灞炰簬缁曞湀銆佹槸鍚︽潵鑷綋鍓嶆帓搴忕偣閲嶆柊瑁佸喅绛夎涔変俊鎭紝
-     * 渚涘悗缁棩蹇椼�乻ession 璁板綍鍜� watch-circle 鍒ゅ畾浣跨敤銆�
-     *
-     * <p>鍙傛暟鍚箟锛�
-     * 1. {@code currentStationId}锛氫换鍔″綋鍓嶆墍鍦ㄧ珯鐐广�傜敤浜庡垽鏂綋鍓嶆槸涓嶆槸鎺掑簭鍐崇瓥鐐广��
-     *    褰撳墠鏄笉鏄凡缁忓埌杈� watch-circle 鐨勪笅涓�鍐崇瓥绔欍��
-     * 2. {@code wrkMast}锛氬綋鍓嶄换鍔′富鐘舵�侊紝鑷冲皯瑕佹彁渚涗笟鍔$洰鏍囩珯銆佹潵婧愮珯銆佹壒娆°�佸簭鍙风瓑淇℃伅銆�
-     * 3. {@code outOrderStationIds}锛氬綋鍓嶈澶囬厤缃殑鎵�鏈夊嚭搴撴帓搴忕珯鐐瑰垪琛ㄣ��
-     * 4. {@code pathLenFactor}锛氱敱 {@link #resolveOutboundPathLenFactor(WrkMast)} 寰楀埌鐨勮矾寰勫亸濂界郴鏁帮紝
-     *    鐢ㄦ潵璁╁悓涓�鎵规浠诲姟鍦ㄩ�夋嫨 dispatch target 鏃朵繚鎸佺ǔ瀹氱殑璺緞鍊惧悜銆�
-     *
-     * <p>杩斿洖鍊艰涔夛細
-     * 1. 杩斿洖 {@code null}锛氬綋鍓嶆棤娉曞緱鍒板悎娉曠洰鏍囩珯锛岃皟鐢ㄦ柟搴旇烦杩囨湰娆℃淳鍙戙��
-     * 2. 杩斿洖 {@code targetStationId=wrkMast.staNo, circle=false}锛�
-     *    褰撳墠涓嶉渶瑕佸嚭搴撴帓搴忓共棰勶紝鎴栧凡鍏佽鐩存帴鍘讳笟鍔$洰鏍囩珯銆�
-     * 3. 杩斿洖 {@code targetStationId!=wrkMast.staNo}锛�
-     *    褰撳墠搴旇鍏堝幓涓�涓腑闂� dispatch 鐩爣绔欙紝鍚庣画鍐嶇敱鎺掑簭鐐�/缁曞湀鐐圭户缁喅绛栥��
-     * 4. 杩斿洖 {@code circle=true}锛�
-     *    褰撳墠灞炰簬缁曞湀鍐崇瓥缁撴灉锛屽悗缁� watch-circle 閫昏緫浼氭嵁姝ゆ帴绠°��
-     *
-     * <p>娉ㄦ剰锛�
-     * 杩欎釜鏂规硶鍙喅瀹氣�滅洰鏍囩珯鈥濓紝涓嶇洿鎺ョ敓鎴愯緭閫佸懡浠ゃ��
-     * 鐪熸鐨勮矾寰勭敱鏅�氱畻璺垨 run-block 涓撶敤绠楄矾鍦ㄥ悗缁楠ょ敓鎴愩��
-     */
-    private OutOrderDispatchDecision resolveOutboundDispatchDecision(Integer currentStationId,
-                                                                     WrkMast wrkMast,
-                                                                     List<Integer> outOrderStationIds,
-                                                                     Double pathLenFactor) {
-        if (wrkMast == null || wrkMast.getStaNo() == null) {
-            return null;
-        }
-        if (!shouldApplyOutOrder(wrkMast, outOrderStationIds)) {
-            return new OutOrderDispatchDecision(wrkMast.getStaNo(), false);
-        }
-        Integer dispatchStationId = resolveDispatchOutOrderTarget(
-                wrkMast,
-                wrkMast.getSourceStaNo(),
-                wrkMast.getStaNo(),
-                outOrderStationIds,
-                pathLenFactor
-        );
-        if (dispatchStationId == null) {
-            return null;
-        }
-        if (isCurrentOutOrderDispatchStation(currentStationId, wrkMast, outOrderStationIds, pathLenFactor)) {
-            return resolveCurrentOutOrderDispatchDecision(currentStationId, wrkMast, outOrderStationIds, pathLenFactor);
-        }
-        if (!Objects.equals(dispatchStationId, wrkMast.getStaNo())
-                && isCurrentOutOrderStation(currentStationId, outOrderStationIds)
-                && isWatchingCircleArrival(wrkMast.getWrkNo(), currentStationId)) {
-            return new OutOrderDispatchDecision(dispatchStationId, true, null, false);
-        }
-        return new OutOrderDispatchDecision(dispatchStationId, false);
-    }
-
-    /**
-     * 鍦ㄢ�滃綋鍓嶇珯鐐瑰氨鏄湰娆℃帓搴忓喅绛栫偣鈥濇椂锛岃绠楄繖閲屽埌搴曡鐩存帴鏀捐杩樻槸杩涘叆缁曞湀銆�
-     *
-     * <p>杩欐槸鍑哄簱鎺掑簭閲屾渶鏍稿績鐨勫眬閮ㄥ喅绛栨柟娉曘�傝繘鍏ヨ繖閲屼箣鍓嶏紝宸茬粡婊¤冻锛�
-     * 1. 褰撳墠浠诲姟閫傜敤 out-order锛�
-     * 2. 褰撳墠绔欑偣灏辨槸杩欑エ浠诲姟姝ゅ埢瀵瑰簲鐨� dispatch 鎺掑簭鐐广��
-     *
-     * <p>鍐呴儴鍐崇瓥椤哄簭锛�
-     * 1. 鍏堝彇鍑哄悓鎵规浠嶆湭瀹屾垚鐨勪换鍔★紝鎵惧嚭杩欐壒褰撳墠鈥滃簲褰撹浼樺厛鏀捐鈥濈殑搴忓彿浣嶇疆銆�
-     * 2. 鍐嶇粨鍚堝綋鍓嶄换鍔″湪鍒濆璺緞涓婄殑鎺掑簭绐楀彛浣嶇疆锛屽垽鏂嚜宸辨鍒昏兘鍚︾洿鎺ュ幓涓氬姟鐩爣绔欍��
-     * 3. 濡傛灉鐞嗚涓婅鑷繁鏀捐锛岃繕瑕侀澶栨鏌ョ洰鏍囨柟鍚戜笂鏄惁瀛樺湪鍙繘鍏ョ殑 release slot銆�
-     * 4. 濡傛灉涓嶈兘鐩磋揪锛屾垨鑰呯洿杈炬柟鍚戝綋鍓嶅叏閮ㄩ樆濉烇紝灏辫浆鎴� circle 鍐崇瓥锛屽鎵句笅涓�鎺掑簭妫�娴嬬偣銆�
-     *
-     * <p>杩斿洖鍊硷細
-     * 1. {@code circle=false} 琛ㄧず褰撳墠鎺掑簭鐐瑰凡缁忓厑璁哥洿鎺ュ幓涓氬姟鐩爣绔欍��
-     * 2. {@code circle=true} 琛ㄧず褰撳墠鍙兘鍏堝幓涓嬩竴缁曞湀鐩爣绔欙紝鍚庣画鐢� watch-circle/鎺掑簭鐐圭户缁帴鍔涘喅绛栥��
-     * 3. {@code null} 琛ㄧず褰撳墠鏃笉鑳界洿杈撅紝涔熸病鎵惧埌鍚堟硶鐨勪笅涓�缁曞湀鐐癸紝璋冪敤鏂瑰簲璺宠繃鏈娲惧彂銆�
-     */
-    private OutOrderDispatchDecision resolveCurrentOutOrderDispatchDecision(Integer currentStationId,
-                                                                            WrkMast wrkMast,
-                                                                            List<Integer> outOrderStationIds,
-                                                                            Double pathLenFactor) {
-        if (!isCurrentOutOrderDispatchStation(currentStationId, wrkMast, outOrderStationIds, pathLenFactor)) {
-            return null;
-        }
-
-        List<WrkMast> batchWrkList = wrkMastService.list(new QueryWrapper<WrkMast>()
-                .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"));
-        if (batchWrkList.isEmpty()) {
-            return new OutOrderDispatchDecision(wrkMast.getStaNo(), false);
-        }
-
-        WrkMast firstWrkMast = batchWrkList.get(0);
-        Integer currentBatchSeq = firstWrkMast.getBatchSeq();
-        if (currentBatchSeq == null) {
-            News.taskInfo(wrkMast.getWrkNo(), "鎵规:{} 棣栦釜鏈畬鎴愪换鍔$己灏戞壒娆″簭鍙凤紝褰撳墠浠诲姟鏆備笉鏀捐", wrkMast.getBatch());
-            return null;
-        }
-
-        List<NavigateNode> initPath;
-        try {
-            initPath = calcOutboundNavigatePath(wrkMast, wrkMast.getSourceStaNo(), wrkMast.getStaNo(), pathLenFactor);
-        } catch (Exception e) {
-            News.taskInfo(wrkMast.getWrkNo(), "鎵规:{} 璁$畻鎺掑簭璺緞澶辫触锛屽綋鍓嶇珯鐐�={}", wrkMast.getBatch(), currentStationId);
-            return null;
-        }
-
-        Integer seq = getOutStationBatchSeq(initPath, currentStationId, wrkMast.getBatch());
-        boolean toTarget;
-        if (seq == null) {
-            toTarget = currentBatchSeq.equals(wrkMast.getBatchSeq());
-        } else {
-            toTarget = Integer.valueOf(seq + 1).equals(wrkMast.getBatchSeq());
-        }
-        if (toTarget) {
-            if (hasReachableOutReleaseSlot(wrkMast, currentStationId, wrkMast.getStaNo(), pathLenFactor)) {
-                return new OutOrderDispatchDecision(wrkMast.getStaNo(), false);
-            }
-            StationTaskLoopService.LoopEvaluation loopEvaluation = evaluateOutOrderLoop(
-                    wrkMast.getWrkNo(),
-                    currentStationId,
-                    outOrderStationIds
-            );
-            Integer circleTarget = resolveNextCircleOrderTarget(
-                    wrkMast,
-                    currentStationId,
-                    outOrderStationIds,
-                    loopEvaluation.getExpectedLoopIssueCount(),
-                    pathLenFactor
-            );
-            if (circleTarget == null) {
-                News.taskInfo(wrkMast.getWrkNo(), "鐩爣绔欏綋鍓嶄笉鍙繘锛屼笖鏈壘鍒板彲鎵ц鐨勪笅涓�鎺掑簭妫�娴嬬偣锛屽綋鍓嶇珯鐐�={}", currentStationId);
-                return null;
-            }
-            return new OutOrderDispatchDecision(circleTarget, true, loopEvaluation, true);
-        }
-
-        StationTaskLoopService.LoopEvaluation loopEvaluation = evaluateOutOrderLoop(
-                wrkMast.getWrkNo(),
-                currentStationId,
-                outOrderStationIds
-        );
-        Integer circleTarget = resolveNextCircleOrderTarget(
-                wrkMast,
-                currentStationId,
-                outOrderStationIds,
-                loopEvaluation.getExpectedLoopIssueCount(),
-                pathLenFactor
-        );
-        if (circleTarget == null) {
-            News.taskInfo(wrkMast.getWrkNo(), "鏈壘鍒板彲鎵ц鐨勪笅涓�鎺掑簭妫�娴嬬偣锛屽綋鍓嶇珯鐐�={}", currentStationId);
-            return null;
-        }
-        return new OutOrderDispatchDecision(circleTarget, true, loopEvaluation, true);
-    }
-
-    /**
-     * 鍒ゆ柇杩欑エ浠诲姟鍦ㄥ綋鍓嶈澶囦笂鏄惁搴旇鍚敤鍑哄簱鎺掑簭閫昏緫銆�
-     *
-     * <p>鍙缂哄皯浠讳綍涓�涓帓搴忓墠鎻愶紝渚嬪涓嶆槸鍑哄簱浠诲姟銆佹病鏈夋壒娆°�佹病鏈夊簭鍙枫��
-     * 褰撳墠璁惧涔熸病鏈夐厤缃帓搴忕偣锛屽氨搴旇鐩存帴閫�鍥炩�滄櫘閫氱洰鏍囩珯鍐崇瓥鈥濄��
-     */
-    private boolean shouldApplyOutOrder(WrkMast wrkMast, List<Integer> 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();
-    }
-
-    /**
-     * 鍒ゆ柇褰撳墠鎵�鍦ㄧ珯鐐规槸鍚﹀氨鏄�滆繖绁ㄤ换鍔℃鍒诲簲璇ヨЕ鍙戞帓搴忓喅绛栫殑 dispatch 绔欑偣鈥濄��
-     *
-     * <p>娉ㄦ剰瀹冧笉鏄畝鍗曞垽鏂�滃綋鍓嶇珯鐐规槸鍚﹀睘浜庢帓搴忕偣鍒楄〃鈥濓紝
-     * 鑰屾槸鍏堟牴鎹畬鏁磋矾寰勫弽鎺ㄥ嚭褰撳墠浠诲姟瀵瑰簲鐨� dispatch 鎺掑簭鐐癸紝
-     * 鍐嶅垽鏂綋鍓嶄綅缃槸鍚︽濂界瓑浜庤繖涓� dispatch 鐐广��
-     */
-    private boolean isCurrentOutOrderDispatchStation(Integer currentStationId,
-                                                     WrkMast wrkMast,
-                                                     List<Integer> outOrderStationIds,
-                                                     Double pathLenFactor) {
-        if (!shouldApplyOutOrder(wrkMast, outOrderStationIds) || currentStationId == null) {
-            return false;
-        }
-        Integer dispatchStationId = resolveDispatchOutOrderTarget(
-                wrkMast,
-                wrkMast.getSourceStaNo(),
-                wrkMast.getStaNo(),
-                outOrderStationIds,
-                pathLenFactor
-        );
-        return dispatchStationId != null
-                && !Objects.equals(dispatchStationId, wrkMast.getStaNo())
-                && Objects.equals(currentStationId, dispatchStationId);
-    }
-
-    /**
-     * 鍒ゆ柇褰撳墠浣嶇疆鏄惁灞炰簬璁惧閰嶇疆閲岀殑浠绘剰涓�涓帓搴忕偣銆�
-     *
-     * <p>杩欎釜鍒ゆ柇姣� {@link #isCurrentOutOrderDispatchStation(Integer, WrkMast, List, Double)} 鏇村锛�
-     * 鍙洖绛斺�滃綋鍓嶄綅缃槸涓嶆槸鎺掑簭鐐光�濓紝涓嶅洖绛斺�滄槸涓嶆槸杩欑エ浠诲姟褰撳墠搴旇鍛戒腑鐨勬帓搴忕偣鈥濄��
-     */
-    private boolean isCurrentOutOrderStation(Integer currentStationId,
-                                             List<Integer> outOrderStationIds) {
-        return currentStationId != null
-                && outOrderStationIds != null
-                && outOrderStationIds.contains(currentStationId);
-    }
-
-    private void syncOutOrderWatchState(WrkMast wrkMast,
-                                        Integer currentStationId,
-                                        List<Integer> outOrderStationIds,
-                                        OutOrderDispatchDecision dispatchDecision,
-                                        StationCommand command) {
-        if (dispatchDecision == null || command == null || !shouldApplyOutOrder(wrkMast, outOrderStationIds)) {
-            return;
-        }
-        if (dispatchDecision.isCircle()) {
-            saveWatchCircleCommand(wrkMast.getWrkNo(), command);
-            if (dispatchDecision.shouldCountLoopIssue()
-                    && stationTaskLoopService != null
-                    && dispatchDecision.getLoopEvaluation() != null) {
-                stationTaskLoopService.recordLoopIssue(dispatchDecision.getLoopEvaluation(), "OUT_ORDER_CIRCLE");
-            }
-        } else {
-            clearWatchCircleCommand(wrkMast.getWrkNo());
-        }
-    }
-
-    /**
-     * 涓哄綋鍓嶆帓搴忕偣鍐崇瓥棰勫厛鍋氫竴娆$幆绾胯瘎浼般��
-     *
-     * <p>褰撴湰娆″喅绛栨渶缁堣繘鍏ョ粫鍦堟椂锛岃瘎浼扮粨鏋滀細琚甫杩� {@link OutOrderDispatchDecision}锛�
-     * 鍚庣画鐢ㄤ簬璁板綍 loop issue 缁熻锛岃�屼笉鏄湪鐪熸涓嬪彂鍚庡啀閲嶅璇勪及涓�娆°��
-     */
-    private StationTaskLoopService.LoopEvaluation evaluateOutOrderLoop(Integer taskNo,
-                                                                       Integer currentStationId,
-                                                                       List<Integer> outOrderStationIds) {
-        if (stationTaskLoopService == null) {
-            return new StationTaskLoopService.LoopEvaluation(
-                    taskNo,
-                    currentStationId,
-                    StationTaskLoopService.LoopIdentitySnapshot.empty(),
-                    0,
-                    0,
-                    false
-            );
-        }
-        return stationTaskLoopService.evaluateLoop(
-                taskNo,
-                currentStationId,
-                true,
-                outOrderStationIds,
-                "outOrderCircle"
-        );
-    }
-
-    /**
-     * 浠庘�滄簮绔欏埌涓氬姟鐩爣绔欌�濈殑瀹屾暣璺緞閲岋紝鍙嶆帹鍑哄綋鍓嶄换鍔″簲褰撳厛鍛戒腑鐨� dispatch 鎺掑簭鐐广��
-     *
-     * <p>鍋氭硶鏄細
-     * 1. 鍏堣绠椾粠 sourceStationId 鍒� finalTargetStationId 鐨勫畬鏁村鑸矾寰勶紱
-     * 2. 鍐嶄粠璺緞灏鹃儴鍚戝墠鎵弿锛�
-     * 3. 鎵惧埌绂绘渶缁堢洰鏍囨渶杩戠殑閭d釜鎺掑簭鐐癸紝浣滀负褰撳墠 dispatch 鐩爣銆�
-     *
-     * <p>濡傛灉璺緞涓婃牴鏈病鏈夋帓搴忕偣锛屾垨鑰呯己灏戞簮绔�/鎺掑簭鐐归厤缃紝
-     * 灏辩洿鎺ユ妸涓氬姟鐩爣绔欐湰韬綋鎴� dispatch 鐩爣銆�
-     */
-    private Integer resolveDispatchOutOrderTarget(WrkMast wrkMast,
-                                                  Integer sourceStationId,
-                                                  Integer finalTargetStationId,
-                                                  List<Integer> outOrderList,
-                                                  Double pathLenFactor) {
-        if (finalTargetStationId == null) {
-            return null;
-        }
-        if (sourceStationId == null || outOrderList == null || outOrderList.isEmpty()) {
-            return finalTargetStationId;
-        }
-
-        try {
-            List<NavigateNode> nodes = calcOutboundNavigatePath(wrkMast, sourceStationId, finalTargetStationId, pathLenFactor);
-            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;
-    }
-
-    /**
-     * 鍒ゆ柇浠庡綋鍓嶆帓搴忕偣缁х画鍓嶅線鏈�缁堜笟鍔$洰鏍囩珯鏃讹紝璺緞涓婃槸鍚﹁嚦灏戝瓨鍦ㄤ竴涓彲杩涘叆鐨勫悗缁珯鐐广��
-     *
-     * <p>杩欓噷涓嶆槸瑕佹眰鏁存潯璺緞瀹屽叏鐣呴�氾紝鑰屾槸鍒ゆ柇鈥滃綋鍓嶆槸鍚︽湁閲婃斁鍙e彲璧扳�濄��
-     * 鍙鍚庣画璺緞涓婂瓨鍦ㄤ竴涓湭闃诲绔欑偣锛屽氨璁や负褰撳墠浠嶅彲灏濊瘯鐩磋揪锛�
-     * 鍙湁鏁存鍚庣画璺緞鐪嬭捣鏉ラ兘琚樆濉炴椂锛屾墠浼氬己鍒惰浆鍏ョ粫鍦堛��
-     */
-    private boolean hasReachableOutReleaseSlot(WrkMast wrkMast,
-                                               Integer currentStationId,
-                                               Integer finalTargetStationId,
-                                               Double pathLenFactor) {
-        if (currentStationId == null || finalTargetStationId == null) {
-            return true;
-        }
-
-        try {
-            List<NavigateNode> nodes = calcOutboundNavigatePath(wrkMast, currentStationId, finalTargetStationId, pathLenFactor);
-            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<BasStation>().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);
-    }
-
-    /**
-     * 涓衡�滄帓搴忕偣闇�瑕佺粫鍦堚�濈殑鍦烘櫙锛屼粠鍑哄簱鎺掑簭绔欑偣鍒楄〃閲屾寫鍑轰笅涓�璺崇粫鍦堢洰鏍囥��
-     * <p>
-     * 杩欓噷鐨勮緭鍏ヤ笉鏄暣寮犲湴鍥撅紝鑰屾槸宸茬粡鎸変笟鍔¢『搴忔帓濂界殑鍑哄簱绔欑偣搴忓垪銆傛柟娉曚細浠庡綋鍓嶇珯鐐瑰湪璇ュ簭鍒椾腑鐨勫悗缁у紑濮嬶紝
-     * 渚濇灏濊瘯姣忎釜鍊欓�夌珯鐐癸紝骞惰绠椻�滃綋鍓嶇珯鐐� -> 鍊欓�夌珯鐐光�濈殑鍙璺緞锛�
-     * <p>
-     * 1. 鍙鑳界畻鍑鸿矾寰勶紝灏辨妸鍊欓�夌珯鐐硅褰曚负涓�涓彲閫夌粫鍦堢洰鏍囷紱
-     * 2. 璁板綍涓ょ被鎺掑簭淇℃伅锛�
-     *    - pathLength锛氬埌璇ュ�欓�夌偣鐨勮矾寰勯暱搴︼紝瓒婄煭璇存槑瓒婇�傚悎鍏堟嬁鏉ュ仛涓存椂缂撳啿锛�
-     *    - offset锛氳绔欑偣鍦ㄦ帓搴忓簭鍒椾腑璺濈褰撳墠绔欑偣鏈夊杩滐紝鐢ㄦ潵鍦ㄨ矾寰勯暱搴︾浉鍚岀殑鏃跺�欎繚鐣欐棦鏈夐『搴忔劅锛�
-     * 3. 鏈�鍚庢妸鍊欓�夊垪琛ㄤ氦缁� {@link #resolveGradualCircleTargetByPathLength(Integer, List, Double)}锛�
-     *    鍐嶆牴鎹�滃綋鍓嶅凡缁忕粫鍦堝灏戞鈥濅笌鈥滆矾寰勯暱搴﹀亸濂界郴鏁扳�濅粠涓嶅悓闀垮害灞傜骇閲屾寫鏈�缁堢洰鏍囥��
-     * <p>
-     * 杩欎釜鏂规硶鏈韩涓嶅垽鏂�滄槸鍚﹀簲璇ョ粫鍦堚�濓紝鍙礋璐e湪宸茬粡鍐冲畾缁曞湀鍚庯紝浠庢帓搴忕珯鐐归摼璺噷鎵句竴涓笅涓�璺崇紦鍐茬偣銆�
-     *
-     * @param wrkMast 褰撳墠浠诲姟锛屼富瑕佺敤浜庣畻璺�
-     * @param currentStationId 褰撳墠绔欑偣锛屽嵆鏈鍑嗗浠庡摢閲屽彂鍑虹粫鍦堝懡浠�
-     * @param orderedOutStationList 宸叉寜涓氬姟椤哄簭鎺掑ソ鐨勫嚭搴撶珯鐐瑰垪琛�
-     * @param expectedLoopIssueCount 棰勮宸插彂鐢熺殑缁曞湀/鍫靛杞锛岀敤浜庡喅瀹氭槸鍚﹂�愭鏀惧ぇ缁曞湀鍗婂緞
-     * @param pathLenFactor 褰撳墠浠诲姟瀵瑰簲鐨勮矾寰勫亸濂界郴鏁帮紝褰卞搷 calcOutboundNavigatePath 鐨勯�夎矾缁撴灉
-     * @return 涓嬩竴璺崇粫鍦堢洰鏍囩珯锛涘鏋滄病鏈変换浣曞彲鍒拌揪鍊欓�夊垯杩斿洖 null
-     */
-    private Integer resolveNextCircleOrderTarget(WrkMast wrkMast,
-                                                 Integer currentStationId,
-                                                 List<Integer> orderedOutStationList,
-                                                 Integer expectedLoopIssueCount,
-                                                 Double pathLenFactor) {
-        if (currentStationId == null || orderedOutStationList == null || orderedOutStationList.size() <= 1) {
-            return null;
-        }
-
-        int startIndex = orderedOutStationList.indexOf(currentStationId);
-        int total = orderedOutStationList.size();
-        List<CircleTargetCandidate> candidateList = new ArrayList<>();
-        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<NavigateNode> path = calcOutboundNavigatePath(wrkMast, currentStationId, candidateStationId, pathLenFactor);
-                if (path != null && !path.isEmpty()) {
-                    candidateList.add(new CircleTargetCandidate(candidateStationId, path.size(), offset));
-                }
-            } catch (Exception ignore) {}
-        }
-        if (candidateList.isEmpty()) {
-            return null;
-        }
-        candidateList.sort(new Comparator<CircleTargetCandidate>() {
-            @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());
-            }
-        });
-        return resolveGradualCircleTargetByPathLength(expectedLoopIssueCount, candidateList, pathLenFactor);
-    }
-
-    /**
-     * 鍦ㄢ�滃凡鎸夎矾寰勯暱搴﹀崌搴忔帓濂解�濈殑缁曞湀鍊欓�夊垪琛ㄤ腑锛屾寜灞傜骇娓愯繘鍦版寫閫夌洰鏍囩珯銆�
-     * <p>
-     * candidateList 閲屽彲鑳藉瓨鍦ㄥ涓�欓�夌偣鎷ユ湁鐩稿悓鐨� pathLength銆傚缁曞湀鍐崇瓥鏉ヨ锛�
-     * 鍚屼竴闀垮害灞傞噷鐨勫�欓�夌偣閮藉睘浜庘�滃悓涓�缁曞湀鍗婂緞鈥濓紝鐪熸闇�瑕佹帶鍒剁殑鏄細
-     * <p>
-     * 1. 鍒濇/鍓嶅嚑娆″牭濉炴椂锛屼紭鍏堥�夋嫨鏈�鐭彲杈惧眰锛屽敖閲忕敤鏈�灏忕粫琛岃窛绂绘仮澶嶆祦杞紱
-     * 2. 濡傛灉浠诲姟宸茬粡杩炵画澶氭鍦ㄥ悓涓�鍖哄煙缁曞湀锛岃鏄庣煭鍗婂緞鍊欓�夊ぇ姒傜巼宸茬粡璇曡繃鎴栨仮澶嶆晥鏋滃樊锛�
-     *    灏遍渶瑕侀�愭鏀惧ぇ鍒版洿杩滀竴灞傦紱
-     * 3. pathLenFactor 浠h〃褰撳墠浠诲姟瀵光�滅煭璺緞/闀胯矾寰勨�濈殑鍋忓ソ锛屽厑璁稿湪鐩稿悓鍫靛杞涓嬮�傚害寰�鏇磋繙灞傚亸绉汇��
-     * <p>
-     * 鍥犳杩欓噷鍏堟妸 candidateList 鍘嬬缉鎴愨�滄寜 pathLength 鍘婚噸鍚庣殑 tierList鈥濓紝姣忎釜 tier 鍙繚鐣欒闀垮害灞傜殑棣栦釜鍊欓�夈��
-     * 鐒跺悗鍚屾椂璁$畻涓や釜灞傜骇绱㈠紩锛�
-     * <p>
-     * - defaultTierIndex锛氬熀浜� expectedLoopIssueCount 鐨勯粯璁ゆ斁澶у眰绾э紱
-     * - factorTierIndex锛氬熀浜� pathLenFactor 鐨勫亸濂藉眰绾э紱
-     * <p>
-     * 鏈�缁堝彇涓よ�呰緝澶у�硷紝鍚箟鏄�滆嚦灏戞弧瓒冲綋鍓嶅牭濉炶疆娆¢渶瑕佺殑鏀惧ぇ鍗婂緞锛屽悓鏃跺厑璁歌矾寰勫亸濂芥妸鐩爣鎺ㄥ悜鏇磋繙灞傜骇鈥濄��
-     *
-     * @param expectedLoopIssueCount 棰勮宸插彂鐢熺殑缁曞湀/鍫靛杞
-     * @param candidateList 宸叉寜 pathLength銆乷ffset 鎺掑簭鐨勫�欓�夊垪琛�
-     * @param pathLenFactor 褰撳墠浠诲姟鐨勮矾寰勫亸濂界郴鏁�
-     * @return 鏈�缁堥�変腑鐨勭粫鍦堢洰鏍囩珯锛涜嫢娌℃湁鍊欓�夊垯杩斿洖 null
-     */
-    private Integer resolveGradualCircleTargetByPathLength(Integer expectedLoopIssueCount,
-                                                           List<CircleTargetCandidate> candidateList,
-                                                           Double pathLenFactor) {
-        if (candidateList == null || candidateList.isEmpty()) {
-            return null;
-        }
-
-        List<CircleTargetCandidate> 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 boolean tryAcquireOutOrderDispatchLock(Integer wrkNo, Integer stationId) {
-        if (wrkNo == null || wrkNo <= 0 || stationId == null) {
-            return true;
-        }
-        String key = RedisKeyType.STATION_OUT_ORDER_DISPATCH_LIMIT_.key + wrkNo + "_" + stationId;
-        Object lock = redisUtil.get(key);
-        if (lock != null) {
-            return false;
-        }
-        redisUtil.set(key, "lock", OUT_ORDER_DISPATCH_LIMIT_SECONDS);
-        return true;
-    }
-
-    private 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());
-    }
-
-    private boolean isWatchingCircleArrival(Integer wrkNo, Integer stationId) {
-        if (stationMoveCoordinator != null) {
-            StationMoveSession session = stationMoveCoordinator.loadSession(wrkNo);
-            if (session != null && session.isActive() && stationId != null) {
-                // nextDecisionStationId 琛ㄧず杩欐潯璺嚎鐪熸绛夊緟閲嶆柊鍐崇瓥鐨勭珯鐐癸紝鍒扮珯鎵嶈Е鍙� watch-circle銆�
-                if (stationId.equals(session.getNextDecisionStationId())) {
-                    return true;
-                }
-                // 杩樺湪 session 璺緞涓棿绔欒繍琛屾椂涓嶅簲璇Е鍙戙��
-                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) {
-        if (wrkNo == null || wrkNo <= 0) {
-            return null;
-        }
-        Object circleObj = redisUtil.get(RedisKeyType.WATCH_CIRCLE_STATION_.key + wrkNo);
-        if (circleObj == null) {
-            return null;
-        }
-        try {
-            return JSON.parseObject(circleObj.toString(), StationCommand.class);
-        } catch (Exception ignore) {
-            return null;
-        }
-    }
-
-    private void saveWatchCircleCommand(Integer wrkNo, StationCommand command) {
-        if (wrkNo == null || wrkNo <= 0 || command == null) {
-            return;
-        }
-        redisUtil.set(RedisKeyType.WATCH_CIRCLE_STATION_.key + wrkNo,
-                JSON.toJSONString(command, SerializerFeature.DisableCircularReferenceDetect), 60 * 60 * 24);
-    }
-
-    private void clearWatchCircleCommand(Integer wrkNo) {
-        if (wrkNo == null || wrkNo <= 0) {
-            return;
-        }
-        redisUtil.del(RedisKeyType.WATCH_CIRCLE_STATION_.key + wrkNo);
-    }
-
-    private void checkStationIdleRecover(BasDevp basDevp,
-                                         StationThread stationThread,
-                                         StationProtocol stationProtocol,
-                                         List<Integer> outOrderList) {
-        if (stationProtocol == null || stationProtocol.getTaskNo() == null || stationProtocol.getTaskNo() <= 0) {
-            return;
-        }
-        if (!Objects.equals(stationProtocol.getStationId(), stationProtocol.getTargetStaNo())) {
-            return;
-        }
-
-        StationTaskIdleTrack idleTrack = touchStationTaskIdleTrack(stationProtocol.getTaskNo(), stationProtocol.getStationId());
-        if (shouldSkipIdleRecoverForRecentDispatch(stationProtocol.getTaskNo(), stationProtocol.getStationId())) {
-            return;
-        }
-        if (idleTrack == null || !idleTrack.isTimeout(STATION_IDLE_RECOVER_SECONDS)) {
-            return;
-        }
-
-        WrkMast wrkMast = wrkMastService.selectByWorkNo(stationProtocol.getTaskNo());
-        if (!canRecoverIdleStationTask(wrkMast, stationProtocol.getStationId())) {
-            return;
-        }
-
-        Object lock = redisUtil.get(RedisKeyType.CHECK_STATION_IDLE_RECOVER_LIMIT_.key + stationProtocol.getTaskNo());
-        if (lock != null) {
-            return;
-        }
-        Double pathLenFactor = resolveOutboundPathLenFactor(wrkMast);
-        RerouteContext context = RerouteContext.create(
-                RerouteSceneType.IDLE_RECOVER,
-                basDevp,
-                stationThread,
-                stationProtocol,
-                wrkMast,
-                outOrderList,
-                pathLenFactor,
-                "checkStationIdleRecover"
-        ).withCancelSessionBeforeDispatch()
-                .withExecutionLock(RedisKeyType.CHECK_STATION_IDLE_RECOVER_LIMIT_.key + stationProtocol.getTaskNo(), STATION_IDLE_RECOVER_LIMIT_SECONDS)
-                .withResetSegmentCommandsBeforeDispatch()
-                .clearIdleIssuedCommands(idleTrack);
-        executeSharedReroute(context);
+        return stationRerouteProcessor.resolveSharedRerouteDecision(context);
     }
 
     boolean shouldUseRunBlockDirectReassign(WrkMast wrkMast,
                                             Integer stationId,
                                             List<Integer> runBlockReassignLocStationList) {
-        return wrkMast != null
-                && Objects.equals(wrkMast.getIoType(), WrkIoType.IN.id)
-                && stationId != null
-                && runBlockReassignLocStationList != null
-                && runBlockReassignLocStationList.contains(stationId);
-    }
-
-    private void executeRunBlockDirectReassign(BasDevp basDevp,
-                                               StationThread stationThread,
-                                               StationProtocol stationProtocol,
-                                               WrkMast wrkMast) {
-        if (basDevp == null || stationThread == null || stationProtocol == null || wrkMast == null) {
-            return;
-        }
-        int currentTaskBufferCommandCount = countCurrentTaskBufferCommands(
-                stationProtocol.getTaskBufferItems(),
-                stationProtocol.getTaskNo()
-        );
-        if (currentTaskBufferCommandCount > 0) {
-            News.info("杈撻�佺珯鐐硅繍琛屽牭濉為噸鍒嗛厤宸茶烦杩囷紝缂撳瓨鍖轰粛瀛樺湪褰撳墠浠诲姟鍛戒护銆傜珯鐐瑰彿={}锛屽伐浣滃彿={}锛屽綋鍓嶄换鍔″懡浠ゆ暟={}",
-                    stationProtocol.getStationId(),
-                    stationProtocol.getTaskNo(),
-                    currentTaskBufferCommandCount);
-            return;
-        }
-        if (stationMoveCoordinator != null) {
-            stationMoveCoordinator.cancelSession(wrkMast.getWrkNo());
-        }
-        String response = wmsOperateUtils.applyReassignTaskLocNo(wrkMast.getWrkNo(), stationProtocol.getStationId());
-        if (Cools.isEmpty(response)) {
-            News.taskError(wrkMast.getWrkNo(), "璇锋眰WMS閲嶆柊鍒嗛厤搴撲綅鎺ュ彛澶辫触锛屾帴鍙f湭鍝嶅簲锛侊紒锛乺esponse锛歿}", response);
-            return;
-        }
-        JSONObject jsonObject = JSON.parseObject(response);
-        if (!jsonObject.getInteger("code").equals(200)) {
-            News.error("璇锋眰WMS鎺ュ彛澶辫触锛侊紒锛乺esponse锛歿}", response);
-            return;
-        }
-
-        StartupDto dto = jsonObject.getObject("data", StartupDto.class);
-        String sourceLocNo = wrkMast.getLocNo();
-        String locNo = dto.getLocNo();
-
-        LocMast sourceLocMast = locMastService.queryByLoc(sourceLocNo);
-        if (sourceLocMast == null) {
-            News.taskInfo(wrkMast.getWrkNo(), "搴撲綅鍙�:{} 婧愬簱浣嶄俊鎭笉瀛樺湪", sourceLocNo);
-            return;
-        }
-        if (!sourceLocMast.getLocSts().equals("S")) {
-            News.taskInfo(wrkMast.getWrkNo(), "搴撲綅鍙�:{} 婧愬簱浣嶇姸鎬佷笉澶勪簬鍏ュ簱棰勭害", sourceLocNo);
-            return;
-        }
-
-        LocMast locMast = locMastService.queryByLoc(locNo);
-        if (locMast == null) {
-            News.taskInfo(wrkMast.getWrkNo(), "搴撲綅鍙�:{} 鐩爣搴撲綅淇℃伅涓嶅瓨鍦�", locNo);
-            return;
-        }
-        if (!locMast.getLocSts().equals("O")) {
-            News.taskInfo(wrkMast.getWrkNo(), "搴撲綅鍙�:{} 鐩爣搴撲綅鐘舵�佷笉澶勪簬绌哄簱浣�", locNo);
-            return;
-        }
-
-        FindCrnNoResult findCrnNoResult = commonService.findCrnNoByLocNo(locNo);
-        if (findCrnNoResult == null) {
-            News.taskInfo(wrkMast.getWrkNo(), "{}宸ヤ綔,鏈尮閰嶅埌鍫嗗灈鏈�", wrkMast.getWrkNo());
-            return;
-        }
-        Integer crnNo = findCrnNoResult.getCrnNo();
-
-        Integer targetStationId = commonService.findInStationId(findCrnNoResult, stationProtocol.getStationId());
-        if (targetStationId == null) {
-            News.taskInfo(wrkMast.getWrkNo(), "{}绔欑偣,鎼滅储鍏ュ簱绔欑偣澶辫触", stationProtocol.getStationId());
-            return;
-        }
-
-        StationCommand command = stationThread.getCommand(StationCommandType.MOVE, wrkMast.getWrkNo(), stationProtocol.getStationId(), targetStationId, 0);
-        if (command == null) {
-            News.taskInfo(wrkMast.getWrkNo(), "{}宸ヤ綔,鑾峰彇杈撻�佺嚎鍛戒护澶辫触", wrkMast.getWrkNo());
-            return;
-        }
-
-        sourceLocMast.setLocSts("O");
-        sourceLocMast.setModiTime(new Date());
-        locMastService.updateById(sourceLocMast);
-
-        locMast.setLocSts("S");
-        locMast.setModiTime(new Date());
-        locMastService.updateById(locMast);
-
-        wrkMast.setLocNo(locNo);
-        wrkMast.setStaNo(targetStationId);
-
-        if (findCrnNoResult.getCrnType().equals(SlaveType.Crn)) {
-            wrkMast.setCrnNo(crnNo);
-        } else if (findCrnNoResult.getCrnType().equals(SlaveType.DualCrn)) {
-            wrkMast.setDualCrnNo(crnNo);
-        } else {
-            throw new CoolException("鏈煡璁惧绫诲瀷");
-        }
-
-        if (!wrkMastService.updateById(wrkMast)) {
-            return;
-        }
-        boolean offered = offerDevpCommandWithDedup(basDevp.getDevpNo(), command, "checkStationRunBlock_direct");
-        if (!offered) {
-            return;
-        }
-        if (stationMoveCoordinator != null) {
-            stationMoveCoordinator.recordDispatch(
-                    wrkMast.getWrkNo(),
-                    stationProtocol.getStationId(),
-                    "checkStationRunBlock_direct",
-                    command,
-                    false
-            );
-        }
-    }
-
-    private boolean canRecoverIdleStationTask(WrkMast wrkMast, Integer currentStationId) {
-        if (wrkMast == null || currentStationId == null || wrkMast.getStaNo() == null) {
-            return false;
-        }
-        if (Objects.equals(currentStationId, wrkMast.getStaNo())) {
-            return false;
-        }
-        return Objects.equals(wrkMast.getWrkSts(), WrkStsType.INBOUND_STATION_RUN.sts)
-                || Objects.equals(wrkMast.getWrkSts(), WrkStsType.STATION_RUN.sts);
+        return stationRerouteProcessor.shouldUseRunBlockDirectReassign(wrkMast, stationId, runBlockReassignLocStationList);
     }
 
     private boolean shouldSkipIdleRecoverForRecentDispatch(Integer taskNo, Integer stationId) {
-        if (taskNo == null || taskNo <= 0 || stationId == null) {
-            return false;
-        }
-        long thresholdMs = STATION_IDLE_RECOVER_SECONDS * 1000L;
-        StationMoveSession session = stationMoveCoordinator == null ? null : stationMoveCoordinator.loadSession(taskNo);
-        if (session != null && session.isActive() && session.getLastIssuedAt() != null) {
-            // 鍒嗘鎵ц杩囩▼涓紝鍒氫笅鍙戜笅涓�娈靛懡浠ゆ椂锛宻ession 鐨� currentStationId/dispatchStationId
-            // 鍙兘杩樻病鏉ュ緱鍙婂拰褰撳墠瑙傚療绔欑偣瀹屽叏瀵归綈锛涘彧瑕佸綋鍓嶇珯鐐逛粛鍦ㄨ繖鏉℃椿鍔ㄨ矾绾块噷锛�
-            // 灏辫鏄庤繖娆� recent dispatch 浠嶇劧鍜屽畠鐩稿叧锛宨dle recover 涓嶅簲鍦� 10 绉掔獥鍙e唴鍐嶆浠嬪叆銆�
-            if (Objects.equals(stationId, session.getCurrentStationId())
-                    || Objects.equals(stationId, session.getDispatchStationId())
-                    || session.containsStation(stationId)) {
-                long elapsedMs = System.currentTimeMillis() - session.getLastIssuedAt();
-                if (elapsedMs < thresholdMs) {
-                    saveStationTaskIdleTrack(new StationTaskIdleTrack(taskNo, stationId, System.currentTimeMillis()));
-                    News.info("杈撻�佺珯鐐逛换鍔″垰瀹屾垚鍛戒护涓嬪彂锛屽凡璺宠繃鍋滅暀閲嶇畻銆傜珯鐐瑰彿={}锛屽伐浣滃彿={}锛岃窛涓婃涓嬪彂={}ms锛宺outeVersion={}",
-                            stationId, taskNo, elapsedMs, session.getRouteVersion());
-                    return true;
-                }
-            }
-        }
-        if (!hasRecentIssuedMoveCommand(taskNo, stationId, thresholdMs)) {
-            return false;
-        }
-        saveStationTaskIdleTrack(new StationTaskIdleTrack(taskNo, stationId, System.currentTimeMillis()));
-        News.info("杈撻�佺珯鐐逛换鍔″垰瀹屾垚鍛戒护涓嬪彂锛屽凡璺宠繃鍋滅暀閲嶇畻銆傜珯鐐瑰彿={}锛屽伐浣滃彿={}锛岃窛鏈�杩戝懡浠や笅鍙�<{}ms锛宺outeVersion={}",
-                stationId, taskNo, thresholdMs, session == null ? null : session.getRouteVersion());
-        return true;
+        return stationRerouteProcessor.shouldSkipIdleRecoverForRecentDispatch(taskNo, stationId);
     }
-
-    private boolean hasRecentIssuedMoveCommand(Integer taskNo, Integer stationId, long thresholdMs) {
-        if (taskNo == null || taskNo <= 0 || stationId == null || thresholdMs <= 0L || basStationOptService == null) {
-            return false;
-        }
-        Date thresholdTime = new Date(System.currentTimeMillis() - thresholdMs);
-        List<BasStationOpt> optList = basStationOptService.list(new QueryWrapper<BasStationOpt>()
-                .select("id")
-                .eq("task_no", taskNo)
-                .eq("station_id", stationId)
-                .eq("mode", String.valueOf(StationCommandType.MOVE))
-                .eq("send", 1)
-                .ge("send_time", thresholdTime)
-                .orderByDesc("send_time")
-                .last("limit 1"));
-        return optList != null && !optList.isEmpty();
-    }
-
-    private void resetSegmentMoveCommandsBeforeReroute(Integer taskNo) {
-        if (redisUtil == null || taskNo == null || taskNo <= 0) {
-            return;
-        }
-        String key = RedisKeyType.DEVICE_STATION_MOVE_RESET.key + taskNo;
-        redisUtil.set(key, "cancel", 3);
-        try {
-            Thread.sleep(STATION_MOVE_RESET_WAIT_MS);
-        } catch (InterruptedException e) {
-            Thread.currentThread().interrupt();
-        } catch (Exception ignore) {
-        }
-        redisUtil.del(key);
-    }
-
-    private int countCurrentTaskBufferCommands(List<StationTaskBufferItem> taskBufferItems, Integer currentTaskNo) {
-        if (taskBufferItems == null || taskBufferItems.isEmpty() || currentTaskNo == null || currentTaskNo <= 0) {
-            return 0;
-        }
-        int count = 0;
-        for (StationTaskBufferItem item : taskBufferItems) {
-            if (item == null || item.getTaskNo() == null) {
-                continue;
-            }
-            if (currentTaskNo.equals(item.getTaskNo())) {
-                count++;
-            }
-        }
-        return count;
-    }
-
-    private boolean offerDevpCommandWithDedup(Integer deviceNo, StationCommand command, String scene) {
-        if (deviceNo == null || command == null) {
-            return false;
-        }
-        String dedupKey = buildStationCommandDispatchDedupKey(deviceNo, command);
-        if (redisUtil != null) {
-            Object lock = redisUtil.get(dedupKey);
-            if (lock != null) {
-                News.info("杈撻�佺珯鐐瑰懡浠ょ煭鏃堕噸澶嶆淳鍙戯紝宸茶烦杩囥�俿cene={}锛宒eviceNo={}锛宼askNo={}锛宻tationId={}锛宼argetStaNo={}锛宑ommandType={}",
-                        scene,
-                        deviceNo,
-                        command.getTaskNo(),
-                        command.getStationId(),
-                        command.getTargetStaNo(),
-                        command.getCommandType());
-                return false;
-            }
-            redisUtil.set(dedupKey, "lock", STATION_COMMAND_DISPATCH_DEDUP_SECONDS);
-        }
-        boolean offered = MessageQueue.offer(SlaveType.Devp, deviceNo, new Task(2, command));
-        if (!offered && redisUtil != null) {
-            redisUtil.del(dedupKey);
-        }
-        return offered;
-    }
-
-    private String buildStationCommandDispatchDedupKey(Integer deviceNo, StationCommand command) {
-        return RedisKeyType.STATION_COMMAND_DISPATCH_DEDUP_.key
-                + deviceNo + "_"
-                + command.getTaskNo() + "_"
-                + 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,
-                                                      Integer taskNo,
-                                                      Integer stationId) {
-        if (basStationOptService == null) {
-            return 0;
-        }
-        List<BasStationOpt> optList;
-        try {
-            optList = listIssuedMoveCommandsDuringIdleStay(idleTrack, taskNo);
-        } catch (Exception e) {
-            return 0;
-        }
-        if (optList == null || optList.isEmpty()) {
-            return 0;
-        }
-
-        Date now = new Date();
-        String cleanupMemo = buildIdleRecoverClearedMemo(stationId);
-        int clearedCount = 0;
-        for (BasStationOpt opt : optList) {
-            if (opt == null || opt.getId() == null) {
-                continue;
-            }
-            opt.setSend(0);
-            opt.setUpdateTime(now);
-            opt.setMemo(appendCleanupMemo(opt.getMemo(), cleanupMemo));
-            clearedCount++;
-        }
-        if (clearedCount > 0) {
-            basStationOptService.updateBatchById(optList);
-        }
-        return clearedCount;
-    }
-
-    private List<BasStationOpt> listIssuedMoveCommandsDuringIdleStay(StationTaskIdleTrack idleTrack,
-                                                                     Integer taskNo) {
-        if (idleTrack == null || taskNo == null || taskNo <= 0 || idleTrack.firstSeenTime == null || basStationOptService == null) {
-            return Collections.emptyList();
-        }
-        List<BasStationOpt> optList = basStationOptService.list(new QueryWrapper<BasStationOpt>()
-                .select("id", "task_no", "send_time", "target_station_id", "memo", "send")
-                .eq("task_no", taskNo)
-                .eq("mode", String.valueOf(StationCommandType.MOVE))
-                .eq("send", 1)
-                .ge("send_time", new Date(idleTrack.firstSeenTime))
-                .orderByAsc("send_time"));
-        if (optList == null || optList.isEmpty()) {
-            return Collections.emptyList();
-        }
-        return optList;
-    }
-
-    private String buildIdleRecoverClearedMemo(Integer stationId) {
-        if (stationId == null) {
-            return IDLE_RECOVER_CLEARED_MEMO;
-        }
-        return IDLE_RECOVER_CLEARED_MEMO + "(stationId=" + stationId + ")";
-    }
-
-    private String appendCleanupMemo(String memo, String cleanupMemo) {
-        if (Cools.isEmpty(cleanupMemo)) {
-            return memo;
-        }
-        if (Cools.isEmpty(memo)) {
-            return cleanupMemo;
-        }
-        if (memo.contains(cleanupMemo)) {
-            return memo;
-        }
-        return memo + " | " + cleanupMemo;
-    }
-
-    private StationTaskIdleTrack touchStationTaskIdleTrack(Integer taskNo, Integer stationId) {
-        if (taskNo == null || taskNo <= 0 || stationId == null) {
-            return null;
-        }
-        long now = System.currentTimeMillis();
-        StationTaskIdleTrack idleTrack = getStationTaskIdleTrack(taskNo);
-        if (idleTrack == null || !Objects.equals(idleTrack.stationId, stationId)) {
-            idleTrack = new StationTaskIdleTrack(taskNo, stationId, now);
-            saveStationTaskIdleTrack(idleTrack);
-        }
-        return idleTrack;
-    }
-
-    private StationTaskIdleTrack getStationTaskIdleTrack(Integer taskNo) {
-        if (taskNo == null || taskNo <= 0) {
-            return null;
-        }
-        Object obj = redisUtil.get(RedisKeyType.STATION_TASK_IDLE_TRACK_.key + taskNo);
-        if (obj == null) {
-            return null;
-        }
-        try {
-            return JSON.parseObject(obj.toString(), StationTaskIdleTrack.class);
-        } catch (Exception e) {
-            return null;
-        }
-    }
-
-    private void saveStationTaskIdleTrack(StationTaskIdleTrack idleTrack) {
-        if (idleTrack == null || idleTrack.taskNo == null || idleTrack.taskNo <= 0) {
-            return;
-        }
-        redisUtil.set(
-                RedisKeyType.STATION_TASK_IDLE_TRACK_.key + idleTrack.taskNo,
-                JSON.toJSONString(idleTrack, SerializerFeature.DisableCircularReferenceDetect),
-                STATION_IDLE_TRACK_EXPIRE_SECONDS
-        );
-    }
-
-    /**
-     * 娌库�滄簮绔� -> 鐩爣绔欌�濈殑鐞嗚璺緞锛屼粠褰撳墠绔欑偣寰�涓嬫父鍥炵湅锛屾壘鍑哄悓鎵规浠诲姟鍦ㄥ悗缁珯鐐逛笂鐨勫凡鐭ュ簭鍙枫��
-     * <p>
-     * 涓ユ牸绐楀彛鎺у埗瑕佸洖绛旂殑闂涓嶆槸鈥滃綋鍓嶄换鍔¤嚜宸辩殑 batchSeq 鏄灏戔�濓紝鑰屾槸锛�
-     * 鈥滃湪褰撳墠绔欑偣鍚庨潰锛屾部杩欐潯鍑哄簱閾捐矾宸茬粡鎺掔潃鐨勫悓鎵规浠诲姟锛屾渶闈犺繎鐩爣绔殑搴忓彿鏄灏戯紵鈥�
-     * 鍙湁鎷垮埌杩欎釜搴忓彿锛屾帓搴忕偣鎵嶈兘鍒ゆ柇褰撳墠浠诲姟鏄惁搴旇绱ц窡鍏跺悗鏀捐銆�
-     * <p>
-     * 鍏蜂綋鍋氭硶锛�
-     * <p>
-     * 1. 鎸� pathList 浠庡熬鍒板ご鍥炴壂锛屾埅鍑� searchStationId 涔嬪悗鐨勫叏閮ㄤ笅娓哥珯鐐癸紱
-     * 2. 璇诲彇杩欎簺绔欑偣褰撳墠姝e湪鎵ц鐨勪换鍔″彿锛�
-     * 3. 濡傛灉鏌愮珯鐐逛笂鐨勪换鍔″睘浜� searchBatch锛屽氨璁板綍瀹冪殑 batchSeq锛�
-     * 4. 杩斿洖璇ユ壒娆″湪涓嬫父宸茬煡鐨勫簭鍙枫��
-     * <p>
-     * 杩欓噷杩斿洖鐨勬槸鈥滆矾寰勪笅娓哥幇鍦哄凡鍑虹幇鐨勬壒娆″簭鍙封�濓紝涓嶆槸鎵规鐞嗚鏈�灏�/鏈�澶у�笺��
-     * 濡傛灉涓嬫父娌℃湁鍚屾壒娆′换鍔★紝杩斿洖 null锛岃皟鐢ㄦ柟闇�瑕侀��鍥炲埌鈥滃綋鍓嶆壒娆¢涓湭瀹屾垚浠诲姟鏄惁灏辨槸鑷繁鈥濈殑鍒ゅ畾銆�
-     *
-     * @param pathList 浠庝换鍔℃簮绔欏埌鐩爣绔欑殑鐞嗚鍑哄簱璺緞
-     * @param searchStationId 褰撳墠姝e湪鍋氭帓搴忓喅绛栫殑绔欑偣
-     * @param searchBatch 褰撳墠浠诲姟鎵�灞炴壒娆�
-     * @return 涓嬫父鍚屾壒娆′换鍔$殑宸茬煡搴忓彿锛涜嫢璺緞涓嬫父灏氭湭鍑虹幇璇ユ壒娆″垯杩斿洖 null
-     */
-    public Integer getOutStationBatchSeq(List<NavigateNode> pathList, Integer searchStationId, String searchBatch) {
-        if (pathList == null || pathList.isEmpty() || searchStationId == null || Cools.isEmpty(searchBatch)) {
-            return null;
-        }
-        // 鍙叧蹇冨綋鍓嶇珯鐐逛箣鍚庣殑涓嬫父绔欑偣锛屽綋鍓嶇珯鐐逛箣鍓嶇殑鑺傜偣涓嶄細褰卞搷鈥滆皝搴旇鎺掑湪鎴戝墠闈⑩�濄��
-        List<Integer> checkList = new ArrayList<>();
-        for (int i = pathList.size() - 1; i >= 0; i--) {
-            NavigateNode node = pathList.get(i);
-            JSONObject v = JSONObject.parseObject(node.getNodeValue());
-            if (v != null) {
-                Integer stationId = v.getInteger("stationId");
-                if (searchStationId.equals(stationId)) {
-                    break;
-                } else {
-                    checkList.add(stationId);
-                }
-            }
-        }
-
-        // 涓嬫父绔欑偣鍙兘鍚屾椂鎸傜潃澶氫釜涓嶅悓鎵规浠诲姟锛涜繖閲屽彧鎶藉彇涓庡綋鍓嶆壒娆$浉鍏崇殑鐜板満搴忓彿蹇収銆�
-        HashMap<String, Integer> batchMap = new HashMap<>();
-        for (Integer station : checkList) {
-            BasStation basStation = basStationService.getOne(new QueryWrapper<BasStation>().eq("station_id", station));
-            if (basStation == null) {
-                continue;
-            }
-
-            StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basStation.getDeviceNo());
-            if (stationThread == null) {
-                continue;
-            }
-            Map<Integer, StationProtocol> statusMap = stationThread.getStatusMap();
-            StationProtocol checkStationProtocol = statusMap.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());
-                }
-            }
-        }
-
-        Integer seq = batchMap.get(searchBatch);
-        return seq;
-    }
-
-    private int countCurrentStationTask() {
-        int currentStationTaskCount = 0;
-        List<BasDevp> basDevps = basDevpService.list(new QueryWrapper<BasDevp>());
-        for (BasDevp basDevp : basDevps) {
-            StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo());
-            if (stationThread == null) {
-                continue;
-            }
-
-            for (StationProtocol stationProtocol : stationThread.getStatus()) {
-                if (stationProtocol.getTaskNo() > 0) {
-                    currentStationTaskCount++;
-                }
-            }
-        }
-        return currentStationTaskCount;
-    }
-
-    private boolean isDispatchBlocked(DispatchLimitConfig config,
-                                      int currentStationTaskCount,
-                                      LoadGuardState loadGuardState,
-                                      boolean needReserveLoopLoad) {
-        if (config.loopModeEnable) {
-            double currentLoad = loadGuardState.currentLoad();
-            if (currentLoad >= config.circleMaxLoadLimit) {
-                News.warn("褰撳墠鎵胯浇閲忚揪鍒颁笂闄愶紝宸插仠姝㈢珯鐐逛换鍔′笅鍙戙�傚綋鍓嶆壙杞介噺={}锛屼笂闄�={}", formatPercent(currentLoad), formatPercent(config.circleMaxLoadLimit));
-                return true;
-            }
-
-            if (needReserveLoopLoad) {
-                double reserveLoad = loadGuardState.loadAfterReserve();
-                if (reserveLoad >= config.circleMaxLoadLimit) {
-                    News.warn("棰勫崰鍚庢壙杞介噺杈惧埌涓婇檺锛屽凡鍋滄绔欑偣浠诲姟涓嬪彂銆傞鍗犲悗鎵胯浇閲�={}锛屼笂闄�={}", formatPercent(reserveLoad), formatPercent(config.circleMaxLoadLimit));
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    private LoadGuardState buildLoadGuardState(DispatchLimitConfig config) {
-        LoadGuardState state = new LoadGuardState();
-        if (!config.loopModeEnable) {
-            return state;
-        }
-
-        StationCycleCapacityVo capacityVo = stationCycleCapacityService.getLatestSnapshot();
-        if (capacityVo == null) {
-            return state;
-        }
-
-        state.totalStationCount = toNonNegative(capacityVo.getTotalStationCount());
-        Integer occupiedStationCount = capacityVo.getOccupiedStationCount();
-        state.projectedTaskStationCount = toNonNegative(occupiedStationCount != null ? occupiedStationCount : capacityVo.getTaskStationCount());
-
-        List<StationCycleLoopVo> loopList = capacityVo.getLoopList();
-        if (loopList != null) {
-            for (StationCycleLoopVo loopVo : loopList) {
-                if (loopVo == null || loopVo.getStationIdList() == null) {
-                    continue;
-                }
-                Integer loopNo = loopVo.getLoopNo();
-                for (Integer stationId : loopVo.getStationIdList()) {
-                    if (stationId != null) {
-                        if (loopNo != null) {
-                            state.stationLoopNoMap.put(stationId, loopNo);
-                        }
-                    }
-                }
-            }
-        }
-        return state;
-    }
-
-    private LoopHitResult findPathLoopHit(DispatchLimitConfig config,
-                                          Integer sourceStationId,
-                                          Integer targetStationId,
-                                          LoadGuardState loadGuardState) {
-        return findPathLoopHit(config, sourceStationId, targetStationId, loadGuardState, null, null);
-    }
-
-    private LoopHitResult findPathLoopHit(DispatchLimitConfig config,
-                                          Integer sourceStationId,
-                                          Integer targetStationId,
-                                          LoadGuardState loadGuardState,
-                                          WrkMast wrkMast,
-                                          Double pathLenFactor) {
-        if (!config.loopModeEnable) {
-            return LoopHitResult.NO_HIT;
-        }
-        if (sourceStationId == null || targetStationId == null) {
-            return LoopHitResult.NO_HIT;
-        }
-        if (loadGuardState.stationLoopNoMap.isEmpty()) {
-            return LoopHitResult.NO_HIT;
-        }
-
-        try {
-            List<NavigateNode> nodes = wrkMast == null
-                    ? navigateUtils.calcByStationId(sourceStationId, targetStationId)
-                    : calcOutboundNavigatePath(wrkMast, sourceStationId, targetStationId, pathLenFactor);
-            if (nodes == null || nodes.isEmpty()) {
-                return LoopHitResult.NO_HIT;
-            }
-
-            for (NavigateNode node : nodes) {
-                Integer stationId = getStationIdFromNode(node);
-                if (stationId == null) {
-                    continue;
-                }
-                Integer loopNo = loadGuardState.stationLoopNoMap.get(stationId);
-                if (loopNo != null) {
-                    return new LoopHitResult(true, loopNo, stationId);
-                }
-            }
-        } catch (Exception e) {
-            return LoopHitResult.NO_HIT;
-        }
-
-        return LoopHitResult.NO_HIT;
-    }
-
-    private Integer getStationIdFromNode(NavigateNode node) {
-        if (node == null || isBlank(node.getNodeValue())) {
-            return null;
-        }
-        try {
-            JSONObject v = JSONObject.parseObject(node.getNodeValue());
-            if (v == null) {
-                return null;
-            }
-            return v.getInteger("stationId");
-        } catch (Exception e) {
-            return null;
-        }
-    }
-
-    private int toNonNegative(Integer value) {
-        if (value == null || value < 0) {
-            return 0;
-        }
-        return value;
-    }
-
-    private Double normalizePathLenFactor(Double pathLenFactor) {
-        if (pathLenFactor == null || pathLenFactor < 0.0d) {
-            return 0.0d;
-        }
-        if (pathLenFactor > 1.0d) {
-            return 1.0d;
-        }
-        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;
-        }
-    }
-
-    /**
-     * 閲嶇畻/閲嶅彂閾捐矾鐨勪竴娆℃�ф墽琛屼笂涓嬫枃銆�
-     *
-     * <p>杩欎釜瀵硅薄鍙湪涓�娆� reroute 鎵ц杩囩▼涓瓨鍦紝涓嶄細钀藉簱锛屼篃涓嶄細闀挎湡缂撳瓨銆�
-     * 瀹冪殑鑱岃矗涓嶆槸琛ㄨ揪涓氬姟鐘舵�侊紝鑰屾槸鎶娾�滆繖娆′负浠�涔堣繘鏉ャ�佸綋鍓嶇敤鍝杩愯鏃跺璞°��
-     * 涓嬪彂鍓嶅悗瑕佷笉瑕佸惎鐢ㄩ澶栨帶鍒堕�昏緫鈥濋泦涓墦鍖咃紝渚涚粺涓�鎵ц閾捐矾浣跨敤銆�
-     *
-     * <p>缁熶竴鎵ц閾捐矾澶ц嚧鍒嗕笁娈碉細
-     * 1. {@code resolveSharedRerouteDecision} 鏍规嵁褰撳墠浠诲姟鍜屽満鏅厛鍐崇瓥鐩爣绔欍��
-     * 2. {@code buildRerouteCommandPlan} 鍐冲畾鐢ㄦ櫘閫氬嚭搴撶畻璺繕鏄� run-block 涓撶敤绠楄矾銆�
-     * 3. {@code executeReroutePlan} 鎸変笂涓嬫枃閲岀殑寮�鍏冲喅瀹氭槸鍚﹀仛 suppress銆佹槸鍚﹀姞閿併��
-     *    鏄惁鍏� cancel 鏃� session銆佹槸鍚﹀厛娓呮棫鍒嗘鍛戒护锛岀劧鍚庣湡姝d笅鍙戙��
-     *
-     * <p>瀛楁鍙互鍒嗘垚鍥涚粍鐞嗚В锛�
-     * 1. 鍦烘櫙涓庤繍琛屾椂瀵硅薄锛�
-     *    {@code sceneType} / {@code basDevp} / {@code stationThread} /
-     *    {@code stationProtocol} / {@code wrkMast}
-     *    琛ㄧず鈥滆皝鍦ㄤ粈涔堝満鏅笅瑙﹀彂浜嗚繖娆¢噸绠椻�濄��
-     * 2. 鐩爣鍐崇瓥杈撳叆锛�
-     *    {@code outOrderStationIds} / {@code pathLenFactor}
-     *    琛ㄧず鈥滅洰鏍囩珯濡備綍绠椼�佽矾寰勫�惧悜绯绘暟鏄灏戔�濄��
-     * 3. 涓嬪彂鐩爣淇℃伅锛�
-     *    {@code dispatchScene} / {@code dispatchDeviceNo}
-     *    琛ㄧず鈥滆繖娆″懡浠ゆ渶缁堝線鍝釜杈撻�佽澶囬槦鍒楀彂锛屼互鍙婃棩蹇�/鍘婚噸鍦烘櫙鍚嶆槸浠�涔堚�濄��
-     * 4. 鎵ц鎺у埗寮�鍏筹細
-     *    {@code useRunBlockCommand} / {@code checkSuppressDispatch} /
-     *    {@code requireOutOrderDispatchLock} / {@code cancelSessionBeforeDispatch} /
-     *    {@code resetSegmentCommandsBeforeDispatch} / {@code clearIdleIssuedCommands} /
-     *    {@code checkRecentDispatch} / {@code executionLockKey} / {@code executionLockSeconds}
-     *    琛ㄧず鈥滅湡姝f墽琛屽墠鍚庤鎵撳紑鍝簺淇濇姢鍔ㄤ綔鈥濄��
-     *
-     * <p>瀹冩湰璐ㄤ笂鏄竴涓弬鏁板璞″姞甯冨皵寮�鍏抽泦鍚堬細
-     * {@code create(...)} 鍏堟妸杩欐 reroute 鐨勫熀纭�鐜板満濉繘鏉ワ紝
-     * 鍐嶉�氳繃 {@code withXxx(...)} 閫愰」澹版槑杩欐鎵ц闇�瑕侀檮鍔犲摢浜涙帶鍒惰涔夈��
-     *
-     * <p>渚嬪锛�
-     * RUN_BLOCK_REROUTE 浼氭墦寮� {@code useRunBlockCommand}锛�
-     * 骞惰姹傚湪涓嬪彂鍓嶅厛 {@code cancelSession}銆佸厛娓呮棫鍒嗘鍛戒护锛�
-     * OUT_ORDER 浼氭墦寮� suppress guard 鍜� out-order lock锛�
-     * IDLE_RECOVER 鍒欎細鎵撳紑 recent dispatch guard锛屽苟璁板綍/娓呯悊鍋滅暀鏈熼棿宸蹭笅鍙戝懡浠ゃ��
-     */
-    static final class RerouteContext {
-        // 鏈 reroute 鐨勮Е鍙戞潵婧愩�傚喅瀹氬悗闈㈣蛋鍝被鐩爣瑁佸喅銆佹棩蹇楁枃妗堝拰鐗规畩淇濇姢鍒嗘敮銆�
-        private final RerouteSceneType sceneType;
-        // 褰撳墠杈撻�佽澶囬厤缃紝涓昏鐢ㄤ簬鎷块粯璁や笅鍙戣澶囧彿鍜岀浉鍏崇珯鐐归厤缃��
-        private final BasDevp basDevp;
-        // 褰撳墠绔欑偣绾跨▼锛屽悗闈㈡瀯閫犺緭閫佸懡浠ゆ椂鐩存帴渚濊禆瀹冨彇 command銆�
-        private final StationThread stationThread;
-        // 瑙﹀彂杩欐 reroute 鐨勭幇鍦虹珯鐐圭姸鎬佸揩鐓э紝鍖呭惈 stationId/taskNo/runBlock/targetStaNo 绛夎繍琛屾椂淇℃伅銆�
-        private final StationProtocol stationProtocol;
-        // 褰撳墠宸ヤ綔涓昏〃璁板綍锛岃〃绀鸿繖娆¢噸绠楀搴旂殑浠诲姟涓荤姸鎬併��
-        private final WrkMast wrkMast;
-        // 褰撳墠璁惧鐨勫嚭搴撴帓搴忕偣鍒楄〃銆傜洰鏍囩珯鍐崇瓥鏃堕渶瑕佺煡閬撳摢浜涚珯鐐瑰睘浜� out-order 鑺傜偣銆�
-        private final List<Integer> outOrderStationIds;
-        // 璺緞闀垮害鍊惧悜绯绘暟銆傛壒娆″嚭搴撴椂鐢ㄤ簬璁╀笉鍚屼换鍔″璺緞閫夋嫨鏈夌ǔ瀹氬亸濂姐��
-        private final Double pathLenFactor;
-        // 杩欐娲惧彂鍦ㄦ棩蹇椼�佸幓閲嶃�乻ession 璁板綍涓娇鐢ㄧ殑鍦烘櫙鍚嶏紝渚嬪 checkStationRunBlock_reroute銆�
-        private final String dispatchScene;
-        // 瀹為檯鎶曢�掑懡浠ょ殑璁惧鍙枫�傞粯璁ゅ彇 basDevp.getDevpNo()锛屾煇浜涘満鏅彲鏄惧紡瑕嗙洊銆�
-        private Integer dispatchDeviceNo;
-        // true 琛ㄧず鍛戒护鏋勯�犻樁娈垫敼璧� stationThread.getRunBlockRerouteCommand锛岃�屼笉鏄櫘閫氬嚭搴撶畻璺��
-        private boolean useRunBlockCommand;
-        // true 琛ㄧず鎵ц鍓嶈鍏堝仛 active-session suppress锛岄伩鍏嶆棫娲诲姩璺嚎琚噸澶嶆彃鍏ユ柊鍛戒护銆�
-        private boolean checkSuppressDispatch;
-        // true 琛ㄧず鎵ц鍓嶈鍔� out-order 涓撶敤鐭攣锛岄槻姝㈠悓涓�鎺掑簭鐐圭煭鏃堕棿閲嶅璁$畻/涓嬪彂銆�
-        private boolean requireOutOrderDispatchLock;
-        // true 琛ㄧず鐪熸涓嬪彂鍓嶅厛鍙栨秷鏃� session銆傞�氬父鐢ㄤ簬 reroute 鏇挎崲鏃ц矾绾裤��
-        private boolean cancelSessionBeforeDispatch;
-        // true 琛ㄧず鐪熸涓嬪彂鍓嶅厛娓呯悊鏃у垎娈佃緭閫佸懡浠わ紝閬垮厤 segment executor 杩樻寔鏈夋棫璺嚎銆�
-        private boolean resetSegmentCommandsBeforeDispatch;
-        // true 琛ㄧず瑕佹竻鐞� idle recover 鏈熼棿宸茬粡涓嬪彂杩囦絾鏈疄闄呯敓鏁堢殑鏃у懡浠ょ棔杩广��
-        private boolean clearIdleIssuedCommands;
-        // true 琛ㄧず鎵ц鍓嶈妫�鏌モ�滄渶杩戝垰涓嬪彂杩団�濓紝鐢ㄤ簬 idle recover 閬垮厤鍒氬彂瀹屽氨閲嶇畻銆�
-        private boolean checkRecentDispatch;
-        // 鍙�夋墽琛岄攣 key銆傜敤浜庣粰鏌愪釜 reroute 鍦烘櫙鍔犵煭鏃堕棿浜掓枼銆�
-        private String executionLockKey;
-        // executionLockKey 瀵瑰簲鐨勯攣绉掓暟銆�
-        private int executionLockSeconds;
-        // 浠� idle recover 闇�瑕侊紝璁板綍鍋滅暀璺熻釜涓婁笅鏂囷紝渚涙竻鐞嗘棫鍛戒护涓庢洿鏂版椂闂翠娇鐢ㄣ��
-        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) {
-            // create 鍙礋璐e~鍩虹鐜板満锛屼笉榛樿鎵撳紑浠讳綍鎵ц寮�鍏炽��
-            // 姣忎釜鍦烘櫙鍚庨潰閫氳繃 withXxx 鏄庣‘澹版槑鑷繁闇�瑕佸摢浜涢檮鍔犳帶鍒躲��
-            return new RerouteContext(sceneType, basDevp, stationThread, stationProtocol, wrkMast, outOrderStationIds, pathLenFactor, dispatchScene);
-        }
-
-        RerouteContext withDispatchDeviceNo(Integer dispatchDeviceNo) {
-            // 瑕嗙洊榛樿涓嬪彂璁惧鍙枫�傚吀鍨嬪満鏅槸 out-order 绔欑偣閰嶇疆鐨� deviceNo 涓� basDevp 榛樿鍊间笉鍚屻��
-            this.dispatchDeviceNo = dispatchDeviceNo;
-            return this;
-        }
-
-        RerouteContext withRunBlockCommand() {
-            // 鍛戒护鏋勯�犻樁娈靛垏鎹㈠埌 run-block 涓撶敤绠楄矾鍣ㄣ��
-            // 鐩爣绔欎粛鐢辩粺涓�鍐崇瓥閫昏緫鍐冲畾锛屽彧鏄�滃幓鐩爣绔欑殑璺緞鈥濇敼涓哄牭濉為噸瑙勫垝绠楁硶鐢熸垚銆�
-            this.useRunBlockCommand = true;
-            return this;
-        }
-
-        RerouteContext withSuppressDispatchGuard() {
-            // 鎵ц鍓嶅惎鐢� session suppress锛�
-            // 濡傛灉褰撳墠 task 鍦ㄥ綋鍓嶄綅缃凡缁忔湁涓�鏉℃椿鍔ㄤ腑鐨勫悓璺緞/鍚岃鐩栬寖鍥磋矾绾匡紝鍒欐湰娆′笉鍐嶉噸澶嶆淳鍙戙��
-            this.checkSuppressDispatch = true;
-            return this;
-        }
-
-        RerouteContext withOutOrderDispatchLock() {
-            // 鎵ц鍓嶅惎鐢ㄦ帓搴忕偣鐭攣銆�
-            // 涓昏闃叉鍚屼竴涓� out-order/watch-circle 瑙﹀彂鐐瑰湪鏋佺煭鏃堕棿鍐呰骞跺彂閲嶅閲嶇畻銆�
-            this.requireOutOrderDispatchLock = true;
-            return this;
-        }
-
-        RerouteContext withCancelSessionBeforeDispatch() {
-            // 鎵ц鍓嶆樉寮忓彇娑堟棫 session銆�
-            // 璇箟鏄�滄湰娆″懡浠ゅ噯澶囨浛鎹㈡棫璺嚎鈥濓紝鏃� routeVersion 涔嬪悗涓嶅簲鍐嶇户缁帹杩涖��
-            this.cancelSessionBeforeDispatch = true;
-            return this;
-        }
-
-        RerouteContext withResetSegmentCommandsBeforeDispatch() {
-            // 鎵ц鍓嶆竻鎺� segment executor 渚ф棫鍒嗘鍛戒护銆�
-            // 杩欏 run-block/idle recover 寰堝叧閿紝鍚﹀垯绯荤粺鍙兘杩樻嬁鐫�鏃� segment 鐘舵�侀樆鏂柊璺嚎銆�
-            this.resetSegmentCommandsBeforeDispatch = true;
-            return this;
-        }
-
-        RerouteContext clearIdleIssuedCommands(StationTaskIdleTrack idleTrack) {
-            // 浠� idle recover 浣跨敤锛�
-            // 琛ㄧず閲嶅惎鍓嶈鎶娾�滃仠鐣欐湡闂村凡缁忓彂杩囦絾鍙兘鏈湡姝f墽琛岀殑鍛戒护鐥曡抗鈥濇竻鐞嗘帀銆�
-            this.clearIdleIssuedCommands = true;
-            this.idleTrack = idleTrack;
-            return this;
-        }
-
-        RerouteContext withRecentDispatchGuard() {
-            // 鎵ц鍓嶆鏌ユ渶杩戞槸鍚﹀垰涓嬪彂杩囥��
-            // 閬垮厤 idle recover 鍦ㄢ�滃垰閲嶅彂瀹屸�濈殑绐楀彛鍐呭張椹笂瑙﹀彂涓�娆°��
-            this.checkRecentDispatch = true;
-            return this;
-        }
-
-        RerouteContext withExecutionLock(String executionLockKey, int executionLockSeconds) {
-            // 涓烘煇涓満鏅寕涓�涓嫭绔嬫墽琛岄攣銆�
-            // 鍜� out-order lock 涓嶅悓锛岃繖閲屾槸娉涘寲閿侊紝璋佷紶 key 璋佽礋璐e畾涔夐攣璇箟銆�
-            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;
-        private final StationTaskLoopService.LoopEvaluation loopEvaluation;
-        private final boolean countLoopIssue;
-
-        private OutOrderDispatchDecision(Integer targetStationId, boolean circle) {
-            this(targetStationId, circle, null, false);
-        }
-
-        private OutOrderDispatchDecision(Integer targetStationId,
-                                         boolean circle,
-                                         StationTaskLoopService.LoopEvaluation loopEvaluation,
-                                         boolean countLoopIssue) {
-            this.targetStationId = targetStationId;
-            this.circle = circle;
-            this.loopEvaluation = loopEvaluation;
-            this.countLoopIssue = countLoopIssue;
-        }
-
-        private Integer getTargetStationId() {
-            return targetStationId;
-        }
-
-        private boolean isCircle() {
-            return circle;
-        }
-
-        private StationTaskLoopService.LoopEvaluation getLoopEvaluation() {
-            return loopEvaluation;
-        }
-
-        private boolean shouldCountLoopIssue() {
-            return countLoopIssue;
-        }
-    }
-
-    private static class CircleTargetCandidate {
-        private final Integer stationId;
-        private final Integer pathLength;
-        private final Integer offset;
-
-        private CircleTargetCandidate(Integer stationId, Integer pathLength, Integer offset) {
-            this.stationId = stationId;
-            this.pathLength = pathLength == null ? 0 : pathLength;
-            this.offset = offset == null ? 0 : offset;
-        }
-
-        private Integer getStationId() {
-            return stationId;
-        }
-
-        private Integer getPathLength() {
-            return pathLength;
-        }
-
-        private Integer getOffset() {
-            return offset;
-        }
-    }
-
-    private void saveLoopLoadReserve(Integer wrkNo, LoopHitResult loopHitResult) {
-        if (wrkNo == null || wrkNo <= 0 || loopHitResult == null || !loopHitResult.isThroughLoop()) {
-            return;
-        }
-        JSONObject reserveJson = new JSONObject();
-        reserveJson.put("wrkNo", wrkNo);
-        reserveJson.put("loopNo", loopHitResult.getLoopNo());
-        reserveJson.put("hitStationId", loopHitResult.getHitStationId());
-        reserveJson.put("createTime", System.currentTimeMillis());
-        redisUtil.hset(RedisKeyType.STATION_CYCLE_LOAD_RESERVE.key, String.valueOf(wrkNo), reserveJson.toJSONString());
-        redisUtil.expire(RedisKeyType.STATION_CYCLE_LOAD_RESERVE.key, LOOP_LOAD_RESERVE_EXPIRE_SECONDS);
-    }
-
-    private DispatchLimitConfig getDispatchLimitConfig(Integer startStationId, Integer endStationId) {
-        DispatchLimitConfig config = new DispatchLimitConfig();
-        Object systemConfigMapObj = redisUtil.get(RedisKeyType.SYSTEM_CONFIG_MAP.key);
-        if (systemConfigMapObj instanceof Map) {
-            Map<?, ?> systemConfigMap = (Map<?, ?>) systemConfigMapObj;
-            config.circleMaxLoadLimit = parseLoadLimit(getConfigValue(systemConfigMap, "circleMaxLoadLimit"), config.circleMaxLoadLimit);
-            String loopModeValue = getConfigValue(systemConfigMap, "circleLoopModeEnable");
-            if (isBlank(loopModeValue)) {
-                loopModeValue = getConfigValue(systemConfigMap, "circleModeEnable");
-            }
-            if (isBlank(loopModeValue)) {
-                loopModeValue = getConfigValue(systemConfigMap, "isCircleMode");
-            }
-            config.loopModeEnable = parseBoolean(loopModeValue, config.loopModeEnable);
-        }
-
-        if (stationPathPolicyService != null && startStationId != null && endStationId != null) {
-            try {
-                StationPathResolvedPolicy resolvedPolicy = stationPathPolicyService.resolvePolicy(startStationId, endStationId);
-                if (resolvedPolicy != null && resolvedPolicy.getProfileConfig() != null) {
-                    config.circleMaxLoadLimit = parseLoadLimit(String.valueOf(resolvedPolicy.getProfileConfig().getCircleMaxLoadLimit()), config.circleMaxLoadLimit);
-                }
-            } catch (Exception ignore) {
-            }
-        }
-
-        return config;
-    }
-
-    private String getConfigValue(Map<?, ?> configMap, String key) {
-        Object value = configMap.get(key);
-        if (value == null) {
-            return null;
-        }
-        return String.valueOf(value).trim();
-    }
-
-    private boolean parseBoolean(String value, boolean defaultValue) {
-        if (isBlank(value)) {
-            return defaultValue;
-        }
-        String lowValue = value.toLowerCase(Locale.ROOT);
-        if ("y".equals(lowValue) || "yes".equals(lowValue) || "true".equals(lowValue)
-                || "1".equals(lowValue) || "on".equals(lowValue)) {
-            return true;
-        }
-        if ("n".equals(lowValue) || "no".equals(lowValue) || "false".equals(lowValue)
-                || "0".equals(lowValue) || "off".equals(lowValue)) {
-            return false;
-        }
-        return defaultValue;
-    }
-
-    private double parseLoadLimit(String value, double defaultValue) {
-        if (isBlank(value)) {
-            return defaultValue;
-        }
-        try {
-            String normalized = value.replace("%", "").trim();
-            double parsed = Double.parseDouble(normalized);
-            if (parsed > 1.0) {
-                parsed = parsed / 100.0;
-            }
-            if (parsed < 0.0) {
-                return 0.0;
-            }
-            if (parsed > 1.0) {
-                return 1.0;
-            }
-            return parsed;
-        } catch (Exception e) {
-            return defaultValue;
-        }
-    }
-
-    private int parseInt(String value, int defaultValue) {
-        if (isBlank(value)) {
-            return defaultValue;
-        }
-        try {
-            int parsed = Integer.parseInt(value.trim());
-            return parsed < 0 ? defaultValue : parsed;
-        } catch (Exception e) {
-            return defaultValue;
-        }
-    }
-
-    private String formatPercent(double value) {
-        return String.format(Locale.ROOT, "%.1f%%", value * 100.0);
-    }
-
-    private boolean isBlank(String value) {
-        return value == null || value.trim().isEmpty();
-    }
-
-    private static class DispatchLimitConfig {
-        // 鍦堟渶澶ф壙杞借兘鍔涳紝榛樿80%
-        private double circleMaxLoadLimit = 0.8d;
-        // 鏄惁鍚敤缁曞湀妯″紡锛堜粎鍚敤鏃舵墠鐢熸晥鎵胯浇闄愬埗锛�
-        private boolean loopModeEnable = false;
-    }
-
-    private static class LoadGuardState {
-        private int totalStationCount = 0;
-        private int projectedTaskStationCount = 0;
-        private final Map<Integer, Integer> stationLoopNoMap = new HashMap<>();
-
-        private double currentLoad() {
-            return calcLoad(this.projectedTaskStationCount, this.totalStationCount);
-        }
-
-        private double loadAfterReserve() {
-            return calcLoad(this.projectedTaskStationCount + 1, this.totalStationCount);
-        }
-
-        private void reserveLoopTask(Integer loopNo) {
-            if (loopNo == null || loopNo <= 0) {
-                return;
-            }
-            if (this.totalStationCount <= 0) {
-                return;
-            }
-            this.projectedTaskStationCount++;
-        }
-
-        private double calcLoad(int taskCount, int stationCount) {
-            if (stationCount <= 0 || taskCount <= 0) {
-                return 0.0;
-            }
-            double load = (double) taskCount / (double) stationCount;
-            if (load < 0.0) {
-                return 0.0;
-            }
-            if (load > 1.0) {
-                return 1.0;
-            }
-            return load;
-        }
-    }
-
-    private static class LoopHitResult {
-        private static final LoopHitResult NO_HIT = new LoopHitResult(false, null, null);
-        private final boolean throughLoop;
-        private final Integer loopNo;
-        private final Integer hitStationId;
-
-        private LoopHitResult(boolean throughLoop, Integer loopNo, Integer hitStationId) {
-            this.throughLoop = throughLoop;
-            this.loopNo = loopNo;
-            this.hitStationId = hitStationId;
-        }
-
-        private boolean isThroughLoop() {
-            return throughLoop;
-        }
-
-        private Integer getLoopNo() {
-            return loopNo;
-        }
-
-        private Integer getHitStationId() {
-            return hitStationId;
-        }
-    }
-
-    private static class StationTaskIdleTrack {
-        private Integer taskNo;
-        private Integer stationId;
-        private Long firstSeenTime;
-
-        private StationTaskIdleTrack() {}
-
-        private StationTaskIdleTrack(Integer taskNo, Integer stationId, Long firstSeenTime) {
-            this.taskNo = taskNo;
-            this.stationId = stationId;
-            this.firstSeenTime = firstSeenTime;
-        }
-
-        private boolean isTimeout(int seconds) {
-            if (firstSeenTime == null) {
-                return false;
-            }
-            return System.currentTimeMillis() - firstSeenTime >= seconds * 1000L;
-        }
-
-        public Integer getTaskNo() {
-            return taskNo;
-        }
-
-        public void setTaskNo(Integer taskNo) {
-            this.taskNo = taskNo;
-        }
-
-        public Integer getStationId() {
-            return stationId;
-        }
-
-        public void setStationId(Integer stationId) {
-            this.stationId = stationId;
-        }
-
-        public Long getFirstSeenTime() {
-            return firstSeenTime;
-        }
-
-        public void setFirstSeenTime(Long firstSeenTime) {
-            this.firstSeenTime = firstSeenTime;
-        }
-    }
-
 }
diff --git a/src/main/java/com/zy/core/utils/station/StationDispatchLoadSupport.java b/src/main/java/com/zy/core/utils/station/StationDispatchLoadSupport.java
new file mode 100644
index 0000000..55d951c
--- /dev/null
+++ b/src/main/java/com/zy/core/utils/station/StationDispatchLoadSupport.java
@@ -0,0 +1,300 @@
+package com.zy.core.utils.station;
+
+import com.alibaba.fastjson.JSONObject;
+import com.zy.asrs.domain.path.StationPathResolvedPolicy;
+import com.zy.asrs.domain.vo.StationCycleCapacityVo;
+import com.zy.asrs.domain.vo.StationCycleLoopVo;
+import com.zy.asrs.entity.BasDevp;
+import com.zy.asrs.entity.WrkMast;
+import com.zy.asrs.service.BasDevpService;
+import com.zy.asrs.service.StationCycleCapacityService;
+import com.zy.asrs.service.StationPathPolicyService;
+import com.zy.common.model.NavigateNode;
+import com.zy.common.utils.NavigateUtils;
+import com.zy.common.utils.RedisUtil;
+import com.zy.core.News;
+import com.zy.core.cache.SlaveConnection;
+import com.zy.core.enums.RedisKeyType;
+import com.zy.core.enums.SlaveType;
+import com.zy.core.model.protocol.StationProtocol;
+import com.zy.core.thread.StationThread;
+import com.zy.core.utils.station.model.DispatchLimitConfig;
+import com.zy.core.utils.station.model.LoadGuardState;
+import com.zy.core.utils.station.model.LoopHitResult;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+@Component
+public class StationDispatchLoadSupport {
+
+    private static final int LOOP_LOAD_RESERVE_EXPIRE_SECONDS = 120;
+
+    @Autowired
+    private RedisUtil redisUtil;
+    @Autowired
+    private BasDevpService basDevpService;
+    @Autowired
+    private StationCycleCapacityService stationCycleCapacityService;
+    @Autowired
+    private StationPathPolicyService stationPathPolicyService;
+    @Autowired
+    private NavigateUtils navigateUtils;
+
+    public int countCurrentStationTask() {
+        int currentStationTaskCount = 0;
+        List<BasDevp> basDevps = basDevpService.list();
+        for (BasDevp basDevp : basDevps) {
+            StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo());
+            if (stationThread == null) {
+                continue;
+            }
+
+            for (StationProtocol stationProtocol : stationThread.getStatus()) {
+                if (stationProtocol.getTaskNo() > 0) {
+                    currentStationTaskCount++;
+                }
+            }
+        }
+        return currentStationTaskCount;
+    }
+
+    public boolean isDispatchBlocked(DispatchLimitConfig config,
+                                     int currentStationTaskCount,
+                                     LoadGuardState loadGuardState,
+                                     boolean needReserveLoopLoad) {
+        if (config != null && config.isLoopModeEnable()) {
+            double currentLoad = loadGuardState.currentLoad();
+            if (currentLoad >= config.getCircleMaxLoadLimit()) {
+                News.warn("褰撳墠鎵胯浇閲忚揪鍒颁笂闄愶紝宸插仠姝㈢珯鐐逛换鍔′笅鍙戙�傚綋鍓嶆壙杞介噺={}锛屼笂闄�={}", formatPercent(currentLoad), formatPercent(config.getCircleMaxLoadLimit()));
+                return true;
+            }
+
+            if (needReserveLoopLoad) {
+                double reserveLoad = loadGuardState.loadAfterReserve();
+                if (reserveLoad >= config.getCircleMaxLoadLimit()) {
+                    News.warn("棰勫崰鍚庢壙杞介噺杈惧埌涓婇檺锛屽凡鍋滄绔欑偣浠诲姟涓嬪彂銆傞鍗犲悗鎵胯浇閲�={}锛屼笂闄�={}", formatPercent(reserveLoad), formatPercent(config.getCircleMaxLoadLimit()));
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    public LoadGuardState buildLoadGuardState(DispatchLimitConfig config) {
+        LoadGuardState state = new LoadGuardState();
+        if (config == null || !config.isLoopModeEnable()) {
+            return state;
+        }
+
+        StationCycleCapacityVo capacityVo = stationCycleCapacityService.getLatestSnapshot();
+        if (capacityVo == null) {
+            return state;
+        }
+
+        Integer occupiedStationCount = capacityVo.getOccupiedStationCount();
+        state.setTotalStationCount(toNonNegative(capacityVo.getTotalStationCount()));
+        state.setProjectedTaskStationCount(toNonNegative(occupiedStationCount != null ? occupiedStationCount : capacityVo.getTaskStationCount()));
+
+        List<StationCycleLoopVo> loopList = capacityVo.getLoopList();
+        if (loopList != null) {
+            for (StationCycleLoopVo loopVo : loopList) {
+                if (loopVo == null || loopVo.getStationIdList() == null) {
+                    continue;
+                }
+                Integer loopNo = loopVo.getLoopNo();
+                for (Integer stationId : loopVo.getStationIdList()) {
+                    if (stationId != null && loopNo != null) {
+                        state.putStationLoopNo(stationId, loopNo);
+                    }
+                }
+            }
+        }
+        return state;
+    }
+
+    public LoopHitResult findPathLoopHit(DispatchLimitConfig config,
+                                         Integer sourceStationId,
+                                         Integer targetStationId,
+                                         LoadGuardState loadGuardState) {
+        return findPathLoopHit(config, sourceStationId, targetStationId, loadGuardState, null, null);
+    }
+
+    public LoopHitResult findPathLoopHit(DispatchLimitConfig config,
+                                         Integer sourceStationId,
+                                         Integer targetStationId,
+                                         LoadGuardState loadGuardState,
+                                         WrkMast wrkMast,
+                                         Double pathLenFactor) {
+        if (config == null || !config.isLoopModeEnable()) {
+            return LoopHitResult.noHit();
+        }
+        if (sourceStationId == null || targetStationId == null) {
+            return LoopHitResult.noHit();
+        }
+        if (loadGuardState == null || loadGuardState.getStationLoopNoMap().isEmpty()) {
+            return LoopHitResult.noHit();
+        }
+
+        try {
+            List<NavigateNode> nodes = wrkMast == null
+                    ? navigateUtils.calcByStationId(sourceStationId, targetStationId)
+                    : calcOutboundNavigatePath(wrkMast, sourceStationId, targetStationId, pathLenFactor);
+            if (nodes == null || nodes.isEmpty()) {
+                return LoopHitResult.noHit();
+            }
+
+            for (NavigateNode node : nodes) {
+                Integer stationId = getStationIdFromNode(node);
+                if (stationId == null) {
+                    continue;
+                }
+                Integer loopNo = loadGuardState.getStationLoopNoMap().get(stationId);
+                if (loopNo != null) {
+                    return new LoopHitResult(true, loopNo, stationId);
+                }
+            }
+        } catch (Exception ignore) {
+            return LoopHitResult.noHit();
+        }
+
+        return LoopHitResult.noHit();
+    }
+
+    public void saveLoopLoadReserve(Integer wrkNo, LoopHitResult loopHitResult) {
+        if (wrkNo == null || wrkNo <= 0 || loopHitResult == null || !loopHitResult.isThroughLoop()) {
+            return;
+        }
+        JSONObject reserveJson = new JSONObject();
+        reserveJson.put("wrkNo", wrkNo);
+        reserveJson.put("loopNo", loopHitResult.getLoopNo());
+        reserveJson.put("hitStationId", loopHitResult.getHitStationId());
+        reserveJson.put("createTime", System.currentTimeMillis());
+        redisUtil.hset(RedisKeyType.STATION_CYCLE_LOAD_RESERVE.key, String.valueOf(wrkNo), reserveJson.toJSONString());
+        redisUtil.expire(RedisKeyType.STATION_CYCLE_LOAD_RESERVE.key, LOOP_LOAD_RESERVE_EXPIRE_SECONDS);
+    }
+
+    public DispatchLimitConfig getDispatchLimitConfig(Integer startStationId, Integer endStationId) {
+        DispatchLimitConfig config = new DispatchLimitConfig();
+        Object systemConfigMapObj = redisUtil.get(RedisKeyType.SYSTEM_CONFIG_MAP.key);
+        if (systemConfigMapObj instanceof Map) {
+            Map<?, ?> systemConfigMap = (Map<?, ?>) systemConfigMapObj;
+            config.setCircleMaxLoadLimit(parseLoadLimit(getConfigValue(systemConfigMap, "circleMaxLoadLimit"), config.getCircleMaxLoadLimit()));
+            String loopModeValue = getConfigValue(systemConfigMap, "circleLoopModeEnable");
+            if (isBlank(loopModeValue)) {
+                loopModeValue = getConfigValue(systemConfigMap, "circleModeEnable");
+            }
+            if (isBlank(loopModeValue)) {
+                loopModeValue = getConfigValue(systemConfigMap, "isCircleMode");
+            }
+            config.setLoopModeEnable(parseBoolean(loopModeValue, config.isLoopModeEnable()));
+        }
+
+        if (stationPathPolicyService != null && startStationId != null && endStationId != null) {
+            try {
+                StationPathResolvedPolicy resolvedPolicy = stationPathPolicyService.resolvePolicy(startStationId, endStationId);
+                if (resolvedPolicy != null && resolvedPolicy.getProfileConfig() != null) {
+                    config.setCircleMaxLoadLimit(parseLoadLimit(String.valueOf(resolvedPolicy.getProfileConfig().getCircleMaxLoadLimit()), config.getCircleMaxLoadLimit()));
+                }
+            } catch (Exception ignore) {
+            }
+        }
+
+        return config;
+    }
+
+    private List<NavigateNode> calcOutboundNavigatePath(WrkMast wrkMast,
+                                                        Integer sourceStationId,
+                                                        Integer targetStationId,
+                                                        Double pathLenFactor) {
+        Double normalizedFactor = normalizePathLenFactor(pathLenFactor);
+        Integer currentTaskNo = wrkMast == null ? null : wrkMast.getWrkNo();
+        if (currentTaskNo == null) {
+            return navigateUtils.calcByStationId(sourceStationId, targetStationId, normalizedFactor);
+        }
+        return navigateUtils.calcByStationId(sourceStationId, targetStationId, currentTaskNo, normalizedFactor);
+    }
+
+    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 int toNonNegative(Integer value) {
+        if (value == null || value < 0) {
+            return 0;
+        }
+        return value;
+    }
+
+    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 getConfigValue(Map<?, ?> configMap, String key) {
+        Object value = configMap.get(key);
+        return value == null ? null : String.valueOf(value).trim();
+    }
+
+    private boolean parseBoolean(String value, boolean defaultValue) {
+        if (isBlank(value)) {
+            return defaultValue;
+        }
+        String lowValue = value.toLowerCase(Locale.ROOT);
+        if ("y".equals(lowValue) || "yes".equals(lowValue) || "true".equals(lowValue)
+                || "1".equals(lowValue) || "on".equals(lowValue)) {
+            return true;
+        }
+        if ("n".equals(lowValue) || "no".equals(lowValue) || "false".equals(lowValue)
+                || "0".equals(lowValue) || "off".equals(lowValue)) {
+            return false;
+        }
+        return defaultValue;
+    }
+
+    private double parseLoadLimit(String value, double defaultValue) {
+        if (isBlank(value)) {
+            return defaultValue;
+        }
+        try {
+            String normalized = value.replace("%", "").trim();
+            double parsed = Double.parseDouble(normalized);
+            if (parsed > 1.0) {
+                parsed = parsed / 100.0;
+            }
+            if (parsed < 0.0) {
+                return 0.0;
+            }
+            if (parsed > 1.0) {
+                return 1.0;
+            }
+            return parsed;
+        } catch (Exception ignore) {
+            return defaultValue;
+        }
+    }
+
+    private String formatPercent(double value) {
+        return String.format(Locale.ROOT, "%.1f%%", value * 100.0);
+    }
+
+    private boolean isBlank(String value) {
+        return value == null || value.trim().isEmpty();
+    }
+}
diff --git a/src/main/java/com/zy/core/utils/station/StationDispatchRuntimeStateSupport.java b/src/main/java/com/zy/core/utils/station/StationDispatchRuntimeStateSupport.java
new file mode 100644
index 0000000..7328225
--- /dev/null
+++ b/src/main/java/com/zy/core/utils/station/StationDispatchRuntimeStateSupport.java
@@ -0,0 +1,231 @@
+package com.zy.core.utils.station;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.serializer.SerializerFeature;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.core.common.Cools;
+import com.zy.asrs.entity.BasStationOpt;
+import com.zy.asrs.service.BasStationOptService;
+import com.zy.common.utils.RedisUtil;
+import com.zy.core.enums.RedisKeyType;
+import com.zy.core.enums.StationCommandType;
+import com.zy.core.model.command.StationCommand;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Objects;
+
+@Component
+public class StationDispatchRuntimeStateSupport {
+    private static final int STATION_IDLE_TRACK_EXPIRE_SECONDS = 60 * 60;
+    private static final String IDLE_RECOVER_CLEARED_MEMO = "idleRecoverRerouteCleared";
+
+    @Autowired
+    private RedisUtil redisUtil;
+    @Autowired
+    private BasStationOptService basStationOptService;
+
+    public StationTaskIdleTrack touchIdleTrack(Integer taskNo, Integer stationId) {
+        if (taskNo == null || taskNo <= 0 || stationId == null) {
+            return null;
+        }
+        long now = System.currentTimeMillis();
+        StationTaskIdleTrack idleTrack = loadIdleTrack(taskNo);
+        if (idleTrack == null || !Objects.equals(idleTrack.getStationId(), stationId)) {
+            idleTrack = new StationTaskIdleTrack(taskNo, stationId, now);
+            saveIdleTrack(idleTrack);
+        }
+        return idleTrack;
+    }
+
+    public StationTaskIdleTrack loadIdleTrack(Integer taskNo) {
+        if (taskNo == null || taskNo <= 0 || redisUtil == null) {
+            return null;
+        }
+        Object obj = redisUtil.get(RedisKeyType.STATION_TASK_IDLE_TRACK_.key + taskNo);
+        if (obj == null) {
+            return null;
+        }
+        try {
+            return JSON.parseObject(obj.toString(), StationTaskIdleTrack.class);
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    public void saveIdleTrack(StationTaskIdleTrack idleTrack) {
+        if (idleTrack == null || idleTrack.getTaskNo() == null || idleTrack.getTaskNo() <= 0 || redisUtil == null) {
+            return;
+        }
+        redisUtil.set(
+                RedisKeyType.STATION_TASK_IDLE_TRACK_.key + idleTrack.getTaskNo(),
+                JSON.toJSONString(idleTrack, SerializerFeature.DisableCircularReferenceDetect),
+                STATION_IDLE_TRACK_EXPIRE_SECONDS
+        );
+    }
+
+    public boolean hasRecentIssuedMoveCommand(Integer taskNo, Integer stationId, long thresholdMs) {
+        if (taskNo == null || taskNo <= 0 || stationId == null || thresholdMs <= 0L || basStationOptService == null) {
+            return false;
+        }
+        Date thresholdTime = new Date(System.currentTimeMillis() - thresholdMs);
+        List<BasStationOpt> optList = basStationOptService.list(new QueryWrapper<BasStationOpt>()
+                .select("id")
+                .eq("task_no", taskNo)
+                .eq("station_id", stationId)
+                .eq("mode", String.valueOf(StationCommandType.MOVE))
+                .eq("send", 1)
+                .ge("send_time", thresholdTime)
+                .orderByDesc("send_time")
+                .last("limit 1"));
+        return optList != null && !optList.isEmpty();
+    }
+
+    public int clearIssuedMoveCommandsDuringIdleStay(StationTaskIdleTrack idleTrack,
+                                                     Integer taskNo,
+                                                     Integer stationId) {
+        if (basStationOptService == null) {
+            return 0;
+        }
+        List<BasStationOpt> optList;
+        try {
+            optList = listIssuedMoveCommandsDuringIdleStay(idleTrack, taskNo);
+        } catch (Exception e) {
+            return 0;
+        }
+        if (optList == null || optList.isEmpty()) {
+            return 0;
+        }
+
+        Date now = new Date();
+        String cleanupMemo = buildIdleRecoverClearedMemo(stationId);
+        int clearedCount = 0;
+        for (BasStationOpt opt : optList) {
+            if (opt == null || opt.getId() == null) {
+                continue;
+            }
+            opt.setSend(0);
+            opt.setUpdateTime(now);
+            opt.setMemo(appendCleanupMemo(opt.getMemo(), cleanupMemo));
+            clearedCount++;
+        }
+        if (clearedCount > 0) {
+            basStationOptService.updateBatchById(optList);
+        }
+        return clearedCount;
+    }
+
+    public boolean tryAcquireLock(String key, int seconds) {
+        if (redisUtil == null || isBlank(key)) {
+            return true;
+        }
+        Object lock = redisUtil.get(key);
+        if (lock != null) {
+            return false;
+        }
+        redisUtil.set(key, "lock", seconds);
+        return true;
+    }
+
+    public boolean tryAcquireOutOrderDispatchLock(Integer wrkNo, Integer stationId, int seconds) {
+        if (wrkNo == null || wrkNo <= 0 || stationId == null) {
+            return true;
+        }
+        return tryAcquireLock(RedisKeyType.STATION_OUT_ORDER_DISPATCH_LIMIT_.key + wrkNo + "_" + stationId, seconds);
+    }
+
+    public void signalSegmentReset(Integer taskNo, long waitMs) {
+        if (redisUtil == null || taskNo == null || taskNo <= 0) {
+            return;
+        }
+        String key = RedisKeyType.DEVICE_STATION_MOVE_RESET.key + taskNo;
+        redisUtil.set(key, "cancel", 3);
+        try {
+            if (waitMs > 0L) {
+                Thread.sleep(waitMs);
+            }
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+        } catch (Exception ignore) {
+        }
+        redisUtil.del(key);
+    }
+
+    public StationCommand loadWatchCircleCommand(Integer wrkNo) {
+        if (wrkNo == null || wrkNo <= 0 || redisUtil == null) {
+            return null;
+        }
+        Object circleObj = redisUtil.get(RedisKeyType.WATCH_CIRCLE_STATION_.key + wrkNo);
+        if (circleObj == null) {
+            return null;
+        }
+        try {
+            return JSON.parseObject(circleObj.toString(), StationCommand.class);
+        } catch (Exception ignore) {
+            return null;
+        }
+    }
+
+    public void saveWatchCircleCommand(Integer wrkNo, StationCommand command) {
+        if (wrkNo == null || wrkNo <= 0 || command == null || redisUtil == null) {
+            return;
+        }
+        redisUtil.set(
+                RedisKeyType.WATCH_CIRCLE_STATION_.key + wrkNo,
+                JSON.toJSONString(command, SerializerFeature.DisableCircularReferenceDetect),
+                60 * 60 * 24
+        );
+    }
+
+    public void clearWatchCircleCommand(Integer wrkNo) {
+        if (wrkNo == null || wrkNo <= 0 || redisUtil == null) {
+            return;
+        }
+        redisUtil.del(RedisKeyType.WATCH_CIRCLE_STATION_.key + wrkNo);
+    }
+
+    private List<BasStationOpt> listIssuedMoveCommandsDuringIdleStay(StationTaskIdleTrack idleTrack,
+                                                                     Integer taskNo) {
+        if (idleTrack == null || taskNo == null || taskNo <= 0 || idleTrack.getFirstSeenTime() == null || basStationOptService == null) {
+            return Collections.emptyList();
+        }
+        List<BasStationOpt> optList = basStationOptService.list(new QueryWrapper<BasStationOpt>()
+                .select("id", "task_no", "send_time", "target_station_id", "memo", "send")
+                .eq("task_no", taskNo)
+                .eq("mode", String.valueOf(StationCommandType.MOVE))
+                .eq("send", 1)
+                .ge("send_time", new Date(idleTrack.getFirstSeenTime()))
+                .orderByAsc("send_time"));
+        if (optList == null || optList.isEmpty()) {
+            return Collections.emptyList();
+        }
+        return optList;
+    }
+
+    private String buildIdleRecoverClearedMemo(Integer stationId) {
+        if (stationId == null) {
+            return IDLE_RECOVER_CLEARED_MEMO;
+        }
+        return IDLE_RECOVER_CLEARED_MEMO + "(stationId=" + stationId + ")";
+    }
+
+    private String appendCleanupMemo(String memo, String cleanupMemo) {
+        if (Cools.isEmpty(cleanupMemo)) {
+            return memo;
+        }
+        if (Cools.isEmpty(memo)) {
+            return cleanupMemo;
+        }
+        if (memo.contains(cleanupMemo)) {
+            return memo;
+        }
+        return memo + " | " + cleanupMemo;
+    }
+
+    private boolean isBlank(String value) {
+        return value == null || value.trim().isEmpty();
+    }
+}
diff --git a/src/main/java/com/zy/core/utils/station/StationOutboundDecisionSupport.java b/src/main/java/com/zy/core/utils/station/StationOutboundDecisionSupport.java
new file mode 100644
index 0000000..5bfc4a0
--- /dev/null
+++ b/src/main/java/com/zy/core/utils/station/StationOutboundDecisionSupport.java
@@ -0,0 +1,615 @@
+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 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;
+
+@Component
+public class StationOutboundDecisionSupport {
+
+    @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<WrkMast> 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<Integer> getAllOutOrderList() {
+        List<Integer> list = new ArrayList<>();
+        List<BasDevp> basDevps = basDevpService.list(new QueryWrapper<BasDevp>());
+        for (BasDevp basDevp : basDevps) {
+            List<Integer> orderList = basDevp.getOutOrderIntList();
+            list.addAll(orderList);
+        }
+        return list;
+    }
+
+    public OutOrderDispatchDecision resolveOutboundDispatchDecision(Integer currentStationId,
+                                                                    WrkMast wrkMast,
+                                                                    List<Integer> outOrderStationIds,
+                                                                    Double pathLenFactor) {
+        if (wrkMast == null || wrkMast.getStaNo() == null) {
+            return null;
+        }
+        if (!shouldApplyOutOrder(wrkMast, outOrderStationIds)) {
+            return OutOrderDispatchDecision.direct(wrkMast.getStaNo());
+        }
+        Integer dispatchStationId = resolveDispatchOutOrderTarget(
+                wrkMast,
+                wrkMast.getSourceStaNo(),
+                wrkMast.getStaNo(),
+                outOrderStationIds,
+                pathLenFactor
+        );
+        if (dispatchStationId == null) {
+            return null;
+        }
+        if (isCurrentOutOrderDispatchStation(currentStationId, wrkMast, outOrderStationIds, pathLenFactor)) {
+            return resolveCurrentOutOrderDispatchDecision(currentStationId, wrkMast, outOrderStationIds, pathLenFactor);
+        }
+        if (!Objects.equals(dispatchStationId, wrkMast.getStaNo())
+                && isCurrentOutOrderStation(currentStationId, outOrderStationIds)
+                && isWatchingCircleArrival(wrkMast.getWrkNo(), currentStationId)) {
+            return OutOrderDispatchDecision.circle(dispatchStationId, null, false);
+        }
+        return OutOrderDispatchDecision.direct(dispatchStationId);
+    }
+
+    public void syncOutOrderWatchState(WrkMast wrkMast,
+                                       Integer currentStationId,
+                                       List<Integer> 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<NavigateNode> calcOutboundNavigatePath(WrkMast wrkMast,
+                                                        Integer sourceStationId,
+                                                        Integer targetStationId,
+                                                        Double pathLenFactor) {
+        Double normalizedFactor = normalizePathLenFactor(pathLenFactor);
+        Integer currentTaskNo = wrkMast == null ? null : wrkMast.getWrkNo();
+        if (currentTaskNo == null) {
+            return navigateUtils.calcByStationId(sourceStationId, targetStationId, normalizedFactor);
+        }
+        return navigateUtils.calcByStationId(sourceStationId, targetStationId, currentTaskNo, normalizedFactor);
+    }
+
+    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<WrkMast> loadActiveBatchTaskList(String batch) {
+        if (Cools.isEmpty(batch)) {
+            return Collections.emptyList();
+        }
+        return wrkMastService.list(new QueryWrapper<WrkMast>()
+                .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<Integer> 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<Integer> outOrderStationIds,
+                                                     Double pathLenFactor) {
+        if (!shouldApplyOutOrder(wrkMast, outOrderStationIds) || currentStationId == null) {
+            return false;
+        }
+        Integer dispatchStationId = resolveDispatchOutOrderTarget(
+                wrkMast,
+                wrkMast.getSourceStaNo(),
+                wrkMast.getStaNo(),
+                outOrderStationIds,
+                pathLenFactor
+        );
+        return dispatchStationId != null
+                && !Objects.equals(dispatchStationId, wrkMast.getStaNo())
+                && Objects.equals(currentStationId, dispatchStationId);
+    }
+
+    private boolean isCurrentOutOrderStation(Integer currentStationId,
+                                             List<Integer> outOrderStationIds) {
+        return currentStationId != null
+                && outOrderStationIds != null
+                && outOrderStationIds.contains(currentStationId);
+    }
+
+    private OutOrderDispatchDecision resolveCurrentOutOrderDispatchDecision(Integer currentStationId,
+                                                                           WrkMast wrkMast,
+                                                                           List<Integer> outOrderStationIds,
+                                                                           Double pathLenFactor) {
+        if (!isCurrentOutOrderDispatchStation(currentStationId, wrkMast, outOrderStationIds, pathLenFactor)) {
+            return null;
+        }
+
+        List<WrkMast> batchWrkList = wrkMastService.list(new QueryWrapper<WrkMast>()
+                .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"));
+        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<NavigateNode> initPath;
+        try {
+            initPath = calcOutboundNavigatePath(wrkMast, wrkMast.getSourceStaNo(), wrkMast.getStaNo(), pathLenFactor);
+        } catch (Exception e) {
+            News.taskInfo(wrkMast.getWrkNo(), "鎵规:{} 璁$畻鎺掑簭璺緞澶辫触锛屽綋鍓嶇珯鐐�={}", wrkMast.getBatch(), currentStationId);
+            return null;
+        }
+
+        Integer seq = getOutStationBatchSeq(initPath, currentStationId, wrkMast.getBatch());
+        boolean toTarget = seq == null
+                ? currentBatchSeq.equals(wrkMast.getBatchSeq())
+                : Integer.valueOf(seq + 1).equals(wrkMast.getBatchSeq());
+        if (toTarget) {
+            if (hasReachableOutReleaseSlot(wrkMast, currentStationId, wrkMast.getStaNo(), pathLenFactor)) {
+                return OutOrderDispatchDecision.direct(wrkMast.getStaNo());
+            }
+            StationTaskLoopService.LoopEvaluation loopEvaluation = evaluateOutOrderLoop(
+                    wrkMast.getWrkNo(),
+                    currentStationId,
+                    outOrderStationIds
+            );
+            Integer circleTarget = resolveNextCircleOrderTarget(
+                    wrkMast,
+                    currentStationId,
+                    outOrderStationIds,
+                    loopEvaluation.getExpectedLoopIssueCount(),
+                    pathLenFactor
+            );
+            if (circleTarget == null) {
+                News.taskInfo(wrkMast.getWrkNo(), "鐩爣绔欏綋鍓嶄笉鍙繘锛屼笖鏈壘鍒板彲鎵ц鐨勪笅涓�鎺掑簭妫�娴嬬偣锛屽綋鍓嶇珯鐐�={}", currentStationId);
+                return null;
+            }
+            return OutOrderDispatchDecision.circle(circleTarget, loopEvaluation, true);
+        }
+
+        StationTaskLoopService.LoopEvaluation loopEvaluation = evaluateOutOrderLoop(
+                wrkMast.getWrkNo(),
+                currentStationId,
+                outOrderStationIds
+        );
+        Integer circleTarget = resolveNextCircleOrderTarget(
+                wrkMast,
+                currentStationId,
+                outOrderStationIds,
+                loopEvaluation.getExpectedLoopIssueCount(),
+                pathLenFactor
+        );
+        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<Integer> 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<Integer> outOrderList,
+                                                  Double pathLenFactor) {
+        if (finalTargetStationId == null) {
+            return null;
+        }
+        if (sourceStationId == null || outOrderList == null || outOrderList.isEmpty()) {
+            return finalTargetStationId;
+        }
+
+        try {
+            List<NavigateNode> nodes = calcOutboundNavigatePath(wrkMast, sourceStationId, finalTargetStationId, pathLenFactor);
+            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) {
+        if (currentStationId == null || finalTargetStationId == null) {
+            return true;
+        }
+
+        try {
+            List<NavigateNode> nodes = calcOutboundNavigatePath(wrkMast, currentStationId, finalTargetStationId, pathLenFactor);
+            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<BasStation>().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<Integer> orderedOutStationList,
+                                                 Integer expectedLoopIssueCount,
+                                                 Double pathLenFactor) {
+        if (currentStationId == null || orderedOutStationList == null || orderedOutStationList.size() <= 1) {
+            return null;
+        }
+
+        int startIndex = orderedOutStationList.indexOf(currentStationId);
+        int total = orderedOutStationList.size();
+        List<CircleTargetCandidate> candidateList = new ArrayList<>();
+        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<NavigateNode> path = calcOutboundNavigatePath(wrkMast, currentStationId, candidateStationId, pathLenFactor);
+                if (path != null && !path.isEmpty()) {
+                    candidateList.add(new CircleTargetCandidate(candidateStationId, path.size(), offset));
+                }
+            } catch (Exception ignore) {
+            }
+        }
+        if (candidateList.isEmpty()) {
+            return null;
+        }
+        candidateList.sort(new Comparator<CircleTargetCandidate>() {
+            @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());
+            }
+        });
+        return resolveGradualCircleTargetByPathLength(expectedLoopIssueCount, candidateList, pathLenFactor);
+    }
+
+    private Integer resolveGradualCircleTargetByPathLength(Integer expectedLoopIssueCount,
+                                                           List<CircleTargetCandidate> candidateList,
+                                                           Double pathLenFactor) {
+        if (candidateList == null || candidateList.isEmpty()) {
+            return null;
+        }
+
+        List<CircleTargetCandidate> 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<NavigateNode> pathList, Integer searchStationId, String searchBatch) {
+        if (pathList == null || pathList.isEmpty() || searchStationId == null || Cools.isEmpty(searchBatch)) {
+            return null;
+        }
+
+        List<Integer> 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<String, Integer> batchMap = new HashMap<>();
+        for (Integer station : checkList) {
+            BasStation basStation = basStationService.getOne(new QueryWrapper<BasStation>().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 boolean isBlank(String value) {
+        return value == null || value.trim().isEmpty();
+    }
+}
diff --git a/src/main/java/com/zy/core/utils/station/StationOutboundDispatchProcessor.java b/src/main/java/com/zy/core/utils/station/StationOutboundDispatchProcessor.java
new file mode 100644
index 0000000..644090a
--- /dev/null
+++ b/src/main/java/com/zy/core/utils/station/StationOutboundDispatchProcessor.java
@@ -0,0 +1,249 @@
+package com.zy.core.utils.station;
+
+import com.alibaba.fastjson.JSON;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.zy.asrs.domain.enums.NotifyMsgType;
+import com.zy.asrs.entity.WrkMast;
+import com.zy.asrs.service.WrkAnalysisService;
+import com.zy.asrs.service.WrkMastService;
+import com.zy.asrs.utils.NotifyUtils;
+import com.zy.common.utils.RedisUtil;
+import com.zy.core.News;
+import com.zy.core.cache.SlaveConnection;
+import com.zy.core.dispatch.StationCommandDispatchResult;
+import com.zy.core.dispatch.StationCommandDispatcher;
+import com.zy.core.enums.RedisKeyType;
+import com.zy.core.enums.SlaveType;
+import com.zy.core.enums.WrkStsType;
+import com.zy.core.model.StationObjModel;
+import com.zy.core.model.command.StationCommand;
+import com.zy.core.model.protocol.StationProtocol;
+import com.zy.core.move.StationMoveCoordinator;
+import com.zy.core.thread.StationThread;
+import com.zy.core.utils.station.model.DispatchLimitConfig;
+import com.zy.core.utils.station.model.LoadGuardState;
+import com.zy.core.utils.station.model.LoopHitResult;
+import com.zy.core.utils.station.model.OutOrderDispatchDecision;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+@Component
+public class StationOutboundDispatchProcessor {
+
+    @Autowired
+    private WrkMastService wrkMastService;
+    @Autowired
+    private WrkAnalysisService wrkAnalysisService;
+    @Autowired
+    private RedisUtil redisUtil;
+    @Autowired
+    private NotifyUtils notifyUtils;
+    @Autowired
+    private StationMoveCoordinator stationMoveCoordinator;
+    @Autowired(required = false)
+    private StationCommandDispatcher stationCommandDispatcher;
+    @Autowired
+    private StationDispatchLoadSupport stationDispatchLoadSupport;
+    @Autowired
+    private StationOutboundDecisionSupport stationOutboundDecisionSupport;
+
+    public void crnStationOutExecute() {
+        try {
+            DispatchLimitConfig baseLimitConfig =
+                    stationDispatchLoadSupport.getDispatchLimitConfig(null, null);
+            int[] currentStationTaskCountRef = new int[]{stationDispatchLoadSupport.countCurrentStationTask()};
+            LoadGuardState loadGuardState =
+                    stationDispatchLoadSupport.buildLoadGuardState(baseLimitConfig);
+
+            List<WrkMast> wrkMasts = wrkMastService.list(new QueryWrapper<WrkMast>()
+                    .eq("wrk_sts", WrkStsType.OUTBOUND_RUN_COMPLETE.sts)
+                    .isNotNull("crn_no"));
+            List<Integer> outOrderList = stationOutboundDecisionSupport.getAllOutOrderList();
+
+            for (WrkMast wrkMast : wrkMasts) {
+                Object infoObj = redisUtil.get(RedisKeyType.CRN_OUT_TASK_COMPLETE_STATION_INFO.key + wrkMast.getWrkNo());
+                if (infoObj == null) {
+                    News.info("鍑哄簱浠诲姟{}鏁版嵁缂撳瓨涓嶅瓨鍦�", wrkMast.getWrkNo());
+                    continue;
+                }
+
+                StationObjModel stationObjModel = JSON.parseObject(infoObj.toString(), StationObjModel.class);
+                StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, stationObjModel.getDeviceNo());
+                if (stationThread == null) {
+                    continue;
+                }
+
+                Map<Integer, StationProtocol> stationMap = stationThread.getStatusMap();
+                StationProtocol stationProtocol = stationMap.get(stationObjModel.getStationId());
+                if (stationProtocol == null) {
+                    continue;
+                }
+
+                Object lock = redisUtil.get(RedisKeyType.STATION_OUT_EXECUTE_LIMIT.key + stationProtocol.getStationId());
+                if (lock != null) {
+                    continue;
+                }
+
+                if (stationProtocol.isAutoing()
+                        && stationProtocol.isLoading()
+                        && stationProtocol.getTaskNo() == 0) {
+                    Double pathLenFactor = stationOutboundDecisionSupport.resolveOutboundPathLenFactor(wrkMast);
+                    OutOrderDispatchDecision dispatchDecision =
+                            stationOutboundDecisionSupport.resolveOutboundDispatchDecision(
+                                    stationProtocol.getStationId(),
+                                    wrkMast,
+                                    outOrderList,
+                                    pathLenFactor
+                            );
+                    Integer moveStaNo = dispatchDecision == null ? null : dispatchDecision.getTargetStationId();
+                    if (moveStaNo == null) {
+                        continue;
+                    }
+
+                    DispatchLimitConfig limitConfig =
+                            stationDispatchLoadSupport.getDispatchLimitConfig(stationProtocol.getStationId(), moveStaNo);
+                    LoopHitResult loopHitResult =
+                            stationDispatchLoadSupport.findPathLoopHit(
+                                    limitConfig,
+                                    stationProtocol.getStationId(),
+                                    moveStaNo,
+                                    loadGuardState,
+                                    wrkMast,
+                                    pathLenFactor
+                            );
+                    if (stationDispatchLoadSupport.isDispatchBlocked(
+                            limitConfig,
+                            currentStationTaskCountRef[0],
+                            loadGuardState,
+                            loopHitResult.isThroughLoop())) {
+                        return;
+                    }
+
+                    StationCommand command = stationOutboundDecisionSupport.buildOutboundMoveCommand(
+                            stationThread,
+                            wrkMast,
+                            stationProtocol.getStationId(),
+                            moveStaNo,
+                            pathLenFactor
+                    );
+                    if (command == null) {
+                        News.taskInfo(wrkMast.getWrkNo(), "鑾峰彇杈撻�佺嚎鍛戒护澶辫触");
+                        continue;
+                    }
+
+                    Date now = new Date();
+                    wrkMast.setWrkSts(WrkStsType.STATION_RUN.sts);
+                    wrkMast.setSystemMsg("");
+                    wrkMast.setIoTime(now);
+                    wrkMast.setModiTime(now);
+                    if (wrkMastService.updateById(wrkMast)) {
+                        wrkAnalysisService.markOutboundStationStart(wrkMast, now);
+                        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());
+                        currentStationTaskCountRef[0]++;
+                        loadGuardState.reserveLoopTask(loopHitResult.getLoopNo());
+                        stationDispatchLoadSupport.saveLoopLoadReserve(wrkMast.getWrkNo(), loopHitResult);
+                    }
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    public void dualCrnStationOutExecute() {
+        try {
+            List<WrkMast> wrkMasts = wrkMastService.list(new QueryWrapper<WrkMast>()
+                    .eq("wrk_sts", WrkStsType.OUTBOUND_RUN_COMPLETE.sts)
+                    .isNotNull("dual_crn_no"));
+            for (WrkMast wrkMast : wrkMasts) {
+                Object infoObj = redisUtil.get(RedisKeyType.DUAL_CRN_OUT_TASK_STATION_INFO.key + wrkMast.getWrkNo());
+                if (infoObj == null) {
+                    News.info("鍑哄簱浠诲姟{}鏁版嵁缂撳瓨涓嶅瓨鍦�", wrkMast.getWrkNo());
+                    continue;
+                }
+
+                StationObjModel stationObjModel = JSON.parseObject(infoObj.toString(), StationObjModel.class);
+                StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, stationObjModel.getDeviceNo());
+                if (stationThread == null) {
+                    continue;
+                }
+
+                Map<Integer, StationProtocol> stationMap = stationThread.getStatusMap();
+                StationProtocol stationProtocol = stationMap.get(stationObjModel.getStationId());
+                if (stationProtocol == null) {
+                    continue;
+                }
+
+                Object lock = redisUtil.get(RedisKeyType.STATION_OUT_EXECUTE_LIMIT.key + stationProtocol.getStationId());
+                if (lock != null) {
+                    continue;
+                }
+
+                if (stationProtocol.isAutoing()
+                        && stationProtocol.isLoading()
+                        && stationProtocol.getTaskNo() == 0) {
+                    Double pathLenFactor = stationOutboundDecisionSupport.resolveOutboundPathLenFactor(wrkMast);
+                    StationCommand command = stationOutboundDecisionSupport.buildOutboundMoveCommand(
+                            stationThread,
+                            wrkMast,
+                            stationProtocol.getStationId(),
+                            wrkMast.getStaNo(),
+                            pathLenFactor
+                    );
+                    if (command == null) {
+                        News.taskInfo(wrkMast.getWrkNo(), "鑾峰彇杈撻�佺嚎鍛戒护澶辫触");
+                        continue;
+                    }
+
+                    wrkMast.setWrkSts(WrkStsType.STATION_RUN.sts);
+                    wrkMast.setSystemMsg("");
+                    wrkMast.setIoTime(new Date());
+                    if (wrkMastService.updateById(wrkMast)) {
+                        boolean offered = offerDevpCommandWithDedup(stationObjModel.getDeviceNo(), command, "dualCrnStationOutExecute");
+                        if (offered && stationMoveCoordinator != null) {
+                            stationMoveCoordinator.recordDispatch(
+                                    wrkMast.getWrkNo(),
+                                    stationProtocol.getStationId(),
+                                    "dualCrnStationOutExecute",
+                                    command,
+                                    false
+                            );
+                        }
+                        notifyUtils.notify(String.valueOf(SlaveType.Devp), stationObjModel.getDeviceNo(),
+                                String.valueOf(wrkMast.getWrkNo()), wrkMast.getWmsWrkNo(),
+                                NotifyMsgType.STATION_OUT_TASK_RUN, null);
+                        News.info("杈撻�佺珯鐐瑰嚭搴撳懡浠や笅鍙戞垚鍔燂紝绔欑偣鍙�={}锛屽伐浣滃彿={}锛屽懡浠ゆ暟鎹�={}",
+                                stationProtocol.getStationId(), wrkMast.getWrkNo(), JSON.toJSONString(command));
+                        redisUtil.set(RedisKeyType.STATION_OUT_EXECUTE_LIMIT.key + stationProtocol.getStationId(), "lock", 5);
+                        redisUtil.del(RedisKeyType.DUAL_CRN_OUT_TASK_STATION_INFO.key + wrkMast.getWrkNo());
+                    }
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    private boolean offerDevpCommandWithDedup(Integer deviceNo, StationCommand command, String scene) {
+        StationCommandDispatchResult dispatchResult = stationCommandDispatcher
+                .dispatch(deviceNo, command, "station-operate-process", scene);
+        return dispatchResult.isAccepted();
+    }
+}
diff --git a/src/main/java/com/zy/core/utils/station/StationRegularDispatchProcessor.java b/src/main/java/com/zy/core/utils/station/StationRegularDispatchProcessor.java
new file mode 100644
index 0000000..a1e02ae
--- /dev/null
+++ b/src/main/java/com/zy/core/utils/station/StationRegularDispatchProcessor.java
@@ -0,0 +1,284 @@
+package com.zy.core.utils.station;
+
+import com.alibaba.fastjson.JSON;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.zy.asrs.domain.enums.NotifyMsgType;
+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.WrkAnalysisService;
+import com.zy.asrs.service.WrkMastService;
+import com.zy.asrs.utils.NotifyUtils;
+import com.zy.common.entity.FindCrnNoResult;
+import com.zy.common.service.CommonService;
+import com.zy.common.utils.RedisUtil;
+import com.zy.core.News;
+import com.zy.core.cache.SlaveConnection;
+import com.zy.core.dispatch.StationCommandDispatchResult;
+import com.zy.core.dispatch.StationCommandDispatcher;
+import com.zy.core.enums.RedisKeyType;
+import com.zy.core.enums.SlaveType;
+import com.zy.core.enums.StationCommandType;
+import com.zy.core.enums.WrkStsType;
+import com.zy.core.model.StationObjModel;
+import com.zy.core.model.command.StationCommand;
+import com.zy.core.model.protocol.StationProtocol;
+import com.zy.core.model.protocol.StationTaskBufferItem;
+import com.zy.core.move.StationMoveCoordinator;
+import com.zy.core.thread.StationThread;
+import com.zy.core.utils.station.model.DispatchLimitConfig;
+import com.zy.core.utils.station.model.LoadGuardState;
+import com.zy.core.utils.station.model.LoopHitResult;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+@Component
+public class StationRegularDispatchProcessor {
+
+    @Autowired
+    private BasDevpService basDevpService;
+    @Autowired
+    private WrkMastService wrkMastService;
+    @Autowired
+    private CommonService commonService;
+    @Autowired
+    private RedisUtil redisUtil;
+    @Autowired
+    private WrkAnalysisService wrkAnalysisService;
+    @Autowired
+    private BasStationService basStationService;
+    @Autowired
+    private NotifyUtils notifyUtils;
+    @Autowired
+    private StationMoveCoordinator stationMoveCoordinator;
+    @Autowired(required = false)
+    private StationCommandDispatcher stationCommandDispatcher;
+    @Autowired
+    private StationDispatchLoadSupport stationDispatchLoadSupport;
+
+    public void stationInExecute() {
+        try {
+            DispatchLimitConfig baseLimitConfig = stationDispatchLoadSupport.getDispatchLimitConfig(null, null);
+            int[] currentStationTaskCountRef = new int[]{stationDispatchLoadSupport.countCurrentStationTask()};
+            LoadGuardState loadGuardState = stationDispatchLoadSupport.buildLoadGuardState(baseLimitConfig);
+
+            List<BasDevp> basDevps = basDevpService.list(new QueryWrapper<>());
+            for (BasDevp basDevp : basDevps) {
+                StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo());
+                if (stationThread == null) {
+                    continue;
+                }
+
+                Map<Integer, StationProtocol> stationMap = stationThread.getStatusMap();
+                List<StationObjModel> stationList = basDevp.getBarcodeStationList$();
+                for (StationObjModel entity : stationList) {
+                    Integer stationId = entity.getStationId();
+                    if (!stationMap.containsKey(stationId)) {
+                        continue;
+                    }
+
+                    StationProtocol stationProtocol = stationMap.get(stationId);
+                    if (stationProtocol == null) {
+                        continue;
+                    }
+
+                    Object lock = redisUtil.get(RedisKeyType.STATION_IN_EXECUTE_LIMIT.key + stationId);
+                    if (lock != null) {
+                        continue;
+                    }
+
+                    if (!stationProtocol.isAutoing()
+                            || !stationProtocol.isLoading()
+                            || stationProtocol.getTaskNo() <= 0) {
+                        continue;
+                    }
+
+                    WrkMast wrkMast = wrkMastService.getOne(new QueryWrapper<WrkMast>().eq("barcode", stationProtocol.getBarcode()));
+                    if (wrkMast == null || !Objects.equals(wrkMast.getWrkSts(), WrkStsType.NEW_INBOUND.sts)) {
+                        continue;
+                    }
+
+                    String locNo = wrkMast.getLocNo();
+                    FindCrnNoResult findCrnNoResult = commonService.findCrnNoByLocNo(locNo);
+                    if (findCrnNoResult == null) {
+                        News.taskInfo(wrkMast.getWrkNo(), "{}宸ヤ綔,鏈尮閰嶅埌鍫嗗灈鏈�", wrkMast.getWrkNo());
+                        continue;
+                    }
+
+                    Integer targetStationId = commonService.findInStationId(findCrnNoResult, stationId);
+                    if (targetStationId == null) {
+                        News.taskInfo(wrkMast.getWrkNo(), "{}绔欑偣,鎼滅储鍏ュ簱绔欑偣澶辫触", stationId);
+                        continue;
+                    }
+
+                    DispatchLimitConfig limitConfig = stationDispatchLoadSupport.getDispatchLimitConfig(stationProtocol.getStationId(), targetStationId);
+                    LoopHitResult loopHitResult = stationDispatchLoadSupport.findPathLoopHit(
+                            limitConfig,
+                            stationProtocol.getStationId(),
+                            targetStationId,
+                            loadGuardState
+                    );
+                    if (stationDispatchLoadSupport.isDispatchBlocked(limitConfig, currentStationTaskCountRef[0], loadGuardState, loopHitResult.isThroughLoop())) {
+                        return;
+                    }
+
+                    StationCommand command = stationThread.getCommand(StationCommandType.MOVE, wrkMast.getWrkNo(), stationId, targetStationId, 0);
+                    if (command == null) {
+                        News.taskInfo(wrkMast.getWrkNo(), "{}宸ヤ綔,鑾峰彇杈撻�佺嚎鍛戒护澶辫触", wrkMast.getWrkNo());
+                        continue;
+                    }
+
+                    Date now = new Date();
+                    wrkMast.setWrkSts(WrkStsType.INBOUND_STATION_RUN.sts);
+                    wrkMast.setSourceStaNo(stationProtocol.getStationId());
+                    wrkMast.setStaNo(targetStationId);
+                    wrkMast.setSystemMsg("");
+                    wrkMast.setIoTime(now);
+                    wrkMast.setModiTime(now);
+                    if (wrkMastService.updateById(wrkMast)) {
+                        wrkAnalysisService.markInboundStationStart(wrkMast, now);
+                        boolean offered = offerDevpCommandWithDedup(basDevp.getDevpNo(), command, "stationInExecute");
+                        if (offered && stationMoveCoordinator != null) {
+                            stationMoveCoordinator.recordDispatch(
+                                    wrkMast.getWrkNo(),
+                                    stationProtocol.getStationId(),
+                                    "stationInExecute",
+                                    command,
+                                    false
+                            );
+                        }
+                        News.info("杈撻�佺珯鐐瑰叆搴撳懡浠や笅鍙戞垚鍔燂紝绔欑偣鍙�={}锛屽伐浣滃彿={}锛屽懡浠ゆ暟鎹�={}", stationId, wrkMast.getWrkNo(), JSON.toJSONString(command));
+                        redisUtil.set(RedisKeyType.STATION_IN_EXECUTE_LIMIT.key + stationId, "lock", 5);
+                        loadGuardState.reserveLoopTask(loopHitResult.getLoopNo());
+                        stationDispatchLoadSupport.saveLoopLoadReserve(wrkMast.getWrkNo(), loopHitResult);
+                    }
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    public void stationOutExecuteFinish() {
+        try {
+            List<WrkMast> wrkMasts = wrkMastService.list(new QueryWrapper<WrkMast>().eq("wrk_sts", WrkStsType.STATION_RUN.sts));
+            for (WrkMast wrkMast : wrkMasts) {
+                Integer wrkNo = wrkMast.getWrkNo();
+                Integer targetStaNo = wrkMast.getStaNo();
+                if (wrkNo == null || targetStaNo == null) {
+                    continue;
+                }
+
+                boolean complete = false;
+                Integer targetDeviceNo = null;
+                StationThread stationThread = null;
+                BasStation basStation = basStationService.getOne(new QueryWrapper<BasStation>().eq("station_id", targetStaNo));
+                if (basStation != null) {
+                    targetDeviceNo = basStation.getDeviceNo();
+                    stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basStation.getDeviceNo());
+                    if (stationThread != null) {
+                        Map<Integer, StationProtocol> statusMap = stationThread.getStatusMap();
+                        StationProtocol stationProtocol = statusMap.get(basStation.getStationId());
+                        if (stationProtocol != null && wrkNo.equals(stationProtocol.getTaskNo())) {
+                            complete = true;
+                        }
+                    }
+                }
+
+                if (complete) {
+                    attemptClearTaskPath(stationThread, wrkNo);
+                    completeStationRunTask(wrkMast, targetDeviceNo);
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    public void checkTaskToComplete() {
+        try {
+            List<WrkMast> wrkMasts = wrkMastService.list(new QueryWrapper<WrkMast>().eq("wrk_sts", WrkStsType.STATION_RUN_COMPLETE.sts));
+            for (WrkMast wrkMast : wrkMasts) {
+                Integer wrkNo = wrkMast.getWrkNo();
+                Integer targetStaNo = wrkMast.getStaNo();
+
+                Object lock = redisUtil.get(RedisKeyType.STATION_OUT_EXECUTE_COMPLETE_LIMIT.key + wrkNo);
+                if (lock != null) {
+                    continue;
+                }
+
+                BasStation basStation = basStationService.getOne(new QueryWrapper<BasStation>().eq("station_id", targetStaNo));
+                if (basStation == null) {
+                    continue;
+                }
+
+                StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basStation.getDeviceNo());
+                if (stationThread == null) {
+                    continue;
+                }
+
+                Map<Integer, StationProtocol> statusMap = stationThread.getStatusMap();
+                StationProtocol stationProtocol = statusMap.get(basStation.getStationId());
+                if (stationProtocol == null) {
+                    continue;
+                }
+
+                if (!Objects.equals(stationProtocol.getTaskNo(), wrkNo)) {
+                    if (stationMoveCoordinator != null) {
+                        stationMoveCoordinator.finishSession(wrkNo);
+                    }
+                    wrkMast.setWrkSts(WrkStsType.COMPLETE_OUTBOUND.sts);
+                    wrkMast.setIoTime(new Date());
+                    wrkMastService.updateById(wrkMast);
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    private void attemptClearTaskPath(StationThread stationThread, Integer taskNo) {
+        if (stationThread == null || taskNo == null || taskNo <= 0) {
+            return;
+        }
+        try {
+            boolean cleared = stationThread.clearPath(taskNo);
+            if (cleared) {
+                News.info("杈撻�佺珯鐐逛换鍔¤繍琛屽畬鎴愬悗娓呯悊娈嬬暀璺緞锛屽伐浣滃彿={}", taskNo);
+            }
+        } catch (Exception e) {
+            News.error("杈撻�佺珯鐐逛换鍔¤繍琛屽畬鎴愬悗娓呯悊娈嬬暀璺緞寮傚父锛屽伐浣滃彿={}", taskNo, e);
+        }
+    }
+
+    private void completeStationRunTask(WrkMast wrkMast, Integer deviceNo) {
+        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);
+        wrkMast.setModiTime(now);
+        wrkMastService.updateById(wrkMast);
+        wrkAnalysisService.markOutboundStationComplete(wrkMast, now);
+        if (deviceNo != null) {
+            notifyUtils.notify(String.valueOf(SlaveType.Devp), deviceNo, String.valueOf(wrkMast.getWrkNo()), wrkMast.getWmsWrkNo(), NotifyMsgType.STATION_OUT_TASK_RUN_COMPLETE, null);
+        }
+        redisUtil.set(RedisKeyType.STATION_OUT_EXECUTE_COMPLETE_LIMIT.key + wrkMast.getWrkNo(), "lock", 60);
+    }
+
+    private boolean offerDevpCommandWithDedup(Integer deviceNo, StationCommand command, String scene) {
+        StationCommandDispatchResult dispatchResult = stationCommandDispatcher.dispatch(deviceNo, command, "station-operate-process", scene);
+        return dispatchResult.isAccepted();
+    }
+}
diff --git a/src/main/java/com/zy/core/utils/station/StationRerouteProcessor.java b/src/main/java/com/zy/core/utils/station/StationRerouteProcessor.java
new file mode 100644
index 0000000..42034af
--- /dev/null
+++ b/src/main/java/com/zy/core/utils/station/StationRerouteProcessor.java
@@ -0,0 +1,734 @@
+package com.zy.core.utils.station;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.core.common.Cools;
+import com.core.exception.CoolException;
+import com.zy.asrs.entity.BasDevp;
+import com.zy.asrs.entity.LocMast;
+import com.zy.asrs.entity.WrkMast;
+import com.zy.asrs.service.BasDevpService;
+import com.zy.asrs.service.LocMastService;
+import com.zy.asrs.service.WrkMastService;
+import com.zy.common.entity.FindCrnNoResult;
+import com.zy.common.model.StartupDto;
+import com.zy.common.service.CommonService;
+import com.zy.common.utils.RedisUtil;
+import com.zy.core.News;
+import com.zy.core.cache.SlaveConnection;
+import com.zy.core.dispatch.StationCommandDispatchResult;
+import com.zy.core.dispatch.StationCommandDispatcher;
+import com.zy.core.enums.RedisKeyType;
+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.StationObjModel;
+import com.zy.core.model.command.StationCommand;
+import com.zy.core.model.protocol.StationProtocol;
+import com.zy.core.model.protocol.StationTaskBufferItem;
+import com.zy.core.move.StationMoveCoordinator;
+import com.zy.core.move.StationMoveSession;
+import com.zy.core.thread.StationThread;
+import com.zy.core.utils.station.model.OutOrderDispatchDecision;
+import com.zy.core.utils.station.model.RerouteCommandPlan;
+import com.zy.core.utils.station.model.RerouteContext;
+import com.zy.core.utils.station.model.RerouteDecision;
+import com.zy.core.utils.station.model.RerouteExecutionResult;
+import com.zy.core.utils.station.model.RerouteSceneType;
+import com.zy.core.utils.WmsOperateUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+@Component
+public class StationRerouteProcessor {
+    private static final int OUT_ORDER_DISPATCH_LIMIT_SECONDS = 2;
+    private static final int STATION_IDLE_RECOVER_SECONDS = 10;
+    private static final int STATION_IDLE_RECOVER_LIMIT_SECONDS = 30;
+    private static final long STATION_MOVE_RESET_WAIT_MS = 1000L;
+
+    @Autowired
+    private BasDevpService basDevpService;
+    @Autowired
+    private WrkMastService wrkMastService;
+    @Autowired
+    private CommonService commonService;
+    @Autowired
+    private RedisUtil redisUtil;
+    @Autowired
+    private LocMastService locMastService;
+    @Autowired
+    private WmsOperateUtils wmsOperateUtils;
+    @Autowired
+    private StationMoveCoordinator stationMoveCoordinator;
+    @Autowired
+    private StationCommandDispatcher stationCommandDispatcher;
+    @Autowired
+    private StationOutboundDecisionSupport stationOutboundDecisionSupport;
+    @Autowired
+    private StationDispatchRuntimeStateSupport stationDispatchRuntimeStateSupport;
+
+    public void checkStationRunBlock() {
+        try {
+            List<BasDevp> basDevps = basDevpService.list(new QueryWrapper<>());
+            for (BasDevp basDevp : basDevps) {
+                StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo());
+                if (stationThread == null) {
+                    continue;
+                }
+
+                List<Integer> runBlockReassignLocStationList = new ArrayList<>();
+                for (StationObjModel stationObjModel : basDevp.getRunBlockReassignLocStationList$()) {
+                    runBlockReassignLocStationList.add(stationObjModel.getStationId());
+                }
+                List<Integer> outOrderStationIds = basDevp.getOutOrderIntList();
+
+                for (StationProtocol stationProtocol : stationThread.getStatus()) {
+                    if (stationProtocol.isAutoing()
+                            && stationProtocol.isLoading()
+                            && stationProtocol.getTaskNo() > 0
+                            && stationProtocol.isRunBlock()) {
+                        WrkMast wrkMast = wrkMastService.selectByWorkNo(stationProtocol.getTaskNo());
+                        if (wrkMast == null) {
+                            News.info("杈撻�佺珯鐐瑰彿={} 杩愯闃诲锛屼絾鏃犳硶鎵惧埌瀵瑰簲浠诲姟锛屽伐浣滃彿={}", stationProtocol.getStationId(), stationProtocol.getTaskNo());
+                            continue;
+                        }
+
+                        Object lock = redisUtil.get(RedisKeyType.CHECK_STATION_RUN_BLOCK_LIMIT_.key + stationProtocol.getTaskNo());
+                        if (lock != null) {
+                            continue;
+                        }
+                        redisUtil.set(RedisKeyType.CHECK_STATION_RUN_BLOCK_LIMIT_.key + stationProtocol.getTaskNo(), "lock", 15);
+
+                        if (shouldUseRunBlockDirectReassign(wrkMast, stationProtocol.getStationId(), runBlockReassignLocStationList)) {
+                            executeRunBlockDirectReassign(basDevp, stationThread, stationProtocol, wrkMast);
+                            continue;
+                        }
+
+                        Double pathLenFactor = stationOutboundDecisionSupport.resolveOutboundPathLenFactor(wrkMast);
+                        RerouteContext context = RerouteContext.create(
+                                RerouteSceneType.RUN_BLOCK_REROUTE,
+                                basDevp,
+                                stationThread,
+                                stationProtocol,
+                                wrkMast,
+                                outOrderStationIds,
+                                pathLenFactor,
+                                "checkStationRunBlock_reroute"
+                        ).withRunBlockCommand()
+                                .withSuppressDispatchGuard()
+                                .withCancelSessionBeforeDispatch()
+                                .withResetSegmentCommandsBeforeDispatch();
+                        executeSharedReroute(context);
+                    }
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    public void checkStationIdleRecover() {
+        try {
+            List<BasDevp> basDevps = basDevpService.list(new QueryWrapper<>());
+            for (BasDevp basDevp : basDevps) {
+                StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo());
+                if (stationThread == null) {
+                    continue;
+                }
+
+                for (StationProtocol stationProtocol : stationThread.getStatus()) {
+                    if (stationProtocol.isAutoing()
+                            && stationProtocol.isLoading()
+                            && stationProtocol.getTaskNo() > 0
+                            && !stationProtocol.isRunBlock()) {
+                        checkStationIdleRecover(basDevp, stationThread, stationProtocol, basDevp.getOutOrderIntList());
+                    }
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    public void checkStationOutOrder() {
+        List<BasDevp> basDevps = basDevpService.list(new QueryWrapper<BasDevp>());
+        for (BasDevp basDevp : basDevps) {
+            StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo());
+            if (stationThread == null) {
+                continue;
+            }
+            Map<Integer, StationProtocol> statusMap = stationThread.getStatusMap();
+            List<StationObjModel> orderList = basDevp.getOutOrderList$();
+            List<Integer> outOrderStationIds = basDevp.getOutOrderIntList();
+            for (StationObjModel stationObjModel : orderList) {
+                StationProtocol stationProtocol = statusMap.get(stationObjModel.getStationId());
+                if (stationProtocol == null
+                        || !stationProtocol.isAutoing()
+                        || !stationProtocol.isLoading()
+                        || stationProtocol.getTaskNo() <= 0
+                        || stationProtocol.isRunBlock()
+                        || !stationProtocol.getStationId().equals(stationProtocol.getTargetStaNo())) {
+                    continue;
+                }
+
+                WrkMast wrkMast = wrkMastService.selectByWorkNo(stationProtocol.getTaskNo());
+                if (wrkMast == null
+                        || !Objects.equals(wrkMast.getWrkSts(), WrkStsType.STATION_RUN.sts)
+                        || Objects.equals(stationProtocol.getStationId(), wrkMast.getStaNo())) {
+                    continue;
+                }
+                if (stationOutboundDecisionSupport.shouldSkipOutOrderDispatchForExistingRoute(wrkMast.getWrkNo(), stationProtocol.getStationId())) {
+                    continue;
+                }
+
+                Double pathLenFactor = stationOutboundDecisionSupport.resolveOutboundPathLenFactor(wrkMast);
+                RerouteContext context = RerouteContext.create(
+                        RerouteSceneType.OUT_ORDER,
+                        basDevp,
+                        stationThread,
+                        stationProtocol,
+                        wrkMast,
+                        outOrderStationIds,
+                        pathLenFactor,
+                        "checkStationOutOrder"
+                ).withDispatchDeviceNo(stationObjModel.getDeviceNo())
+                        .withSuppressDispatchGuard()
+                        .withOutOrderDispatchLock()
+                        .withResetSegmentCommandsBeforeDispatch();
+                executeSharedReroute(context);
+            }
+        }
+    }
+
+    public void watchCircleStation() {
+        List<BasDevp> basDevps = basDevpService.list(new QueryWrapper<BasDevp>());
+        for (BasDevp basDevp : basDevps) {
+            StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo());
+            if (stationThread == null) {
+                continue;
+            }
+
+            List<Integer> outOrderList = basDevp.getOutOrderIntList();
+            for (StationProtocol stationProtocol : stationThread.getStatus()) {
+                if (!stationProtocol.isAutoing()
+                        || !stationProtocol.isLoading()
+                        || stationProtocol.getTaskNo() <= 0
+                        || !stationOutboundDecisionSupport.isWatchingCircleArrival(stationProtocol.getTaskNo(), stationProtocol.getStationId())) {
+                    continue;
+                }
+
+                WrkMast wrkMast = wrkMastService.selectByWorkNo(stationProtocol.getTaskNo());
+                if (wrkMast == null
+                        || !Objects.equals(wrkMast.getWrkSts(), WrkStsType.STATION_RUN.sts)
+                        || Objects.equals(stationProtocol.getStationId(), wrkMast.getStaNo())) {
+                    continue;
+                }
+
+                Double pathLenFactor = stationOutboundDecisionSupport.resolveOutboundPathLenFactor(wrkMast);
+                RerouteContext context = RerouteContext.create(
+                        RerouteSceneType.WATCH_CIRCLE,
+                        basDevp,
+                        stationThread,
+                        stationProtocol,
+                        wrkMast,
+                        outOrderList,
+                        pathLenFactor,
+                        "watchCircleStation"
+                ).withSuppressDispatchGuard()
+                        .withOutOrderDispatchLock()
+                        .withResetSegmentCommandsBeforeDispatch();
+                executeSharedReroute(context);
+            }
+        }
+    }
+
+    public 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()
+        )
+                : stationOutboundDecisionSupport.buildOutboundMoveCommand(
+                context.stationThread(),
+                context.wrkMast(),
+                currentStationId,
+                targetStationId,
+                context.pathLenFactor()
+        );
+        if (command == null) {
+            if (context.sceneType() == RerouteSceneType.RUN_BLOCK_REROUTE) {
+                News.taskInfo(context.wrkMast().getWrkNo(),
+                        "杈撻�佺珯鐐瑰牭濉為噸瑙勫垝鏈壘鍒板彲涓嬪彂璺嚎锛屽綋鍓嶇珯鐐�={}锛岀洰鏍囩珯鐐�={}",
+                        currentStationId,
+                        targetStationId);
+            } else if (context.sceneType() == RerouteSceneType.IDLE_RECOVER) {
+                News.taskInfo(context.wrkMast().getWrkNo(),
+                        "绔欑偣浠诲姟鍋滅暀瓒呮椂鍚庨噸绠楄矾寰勫け璐ワ紝褰撳墠绔欑偣={}锛岀洰鏍囩珯鐐�={}",
+                        currentStationId,
+                        targetStationId);
+            } else {
+                News.taskInfo(context.wrkMast().getWrkNo(), "鑾峰彇杈撻�佺嚎鍛戒护澶辫触");
+            }
+            return RerouteCommandPlan.skip("missing-command");
+        }
+        return RerouteCommandPlan.dispatch(command, decision, context.dispatchScene());
+    }
+
+    public 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 (stationMoveCoordinator != null) {
+            return stationMoveCoordinator.withTaskDispatchLock(taskNo,
+                    () -> executeReroutePlanWithTaskLock(context, plan, stationProtocol, taskNo, stationId));
+        }
+        return executeReroutePlanWithTaskLock(context, plan, stationProtocol, taskNo, stationId);
+    }
+
+    public 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 =
+                stationOutboundDecisionSupport.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);
+    }
+
+    public boolean shouldUseRunBlockDirectReassign(WrkMast wrkMast,
+                                                   Integer stationId,
+                                                   List<Integer> runBlockReassignLocStationList) {
+        return wrkMast != null
+                && Objects.equals(wrkMast.getIoType(), WrkIoType.IN.id)
+                && stationId != null
+                && runBlockReassignLocStationList != null
+                && runBlockReassignLocStationList.contains(stationId);
+    }
+
+    public boolean shouldSkipIdleRecoverForRecentDispatch(Integer taskNo, Integer stationId) {
+        if (taskNo == null || taskNo <= 0 || stationId == null) {
+            return false;
+        }
+        long thresholdMs = STATION_IDLE_RECOVER_SECONDS * 1000L;
+        StationMoveSession session = stationMoveCoordinator == null ? null : stationMoveCoordinator.loadSession(taskNo);
+        if (session != null && session.isActive() && session.getLastIssuedAt() != null) {
+            if (Objects.equals(stationId, session.getCurrentStationId())
+                    || Objects.equals(stationId, session.getDispatchStationId())
+                    || session.containsStation(stationId)) {
+                long elapsedMs = System.currentTimeMillis() - session.getLastIssuedAt();
+                if (elapsedMs < thresholdMs) {
+                    stationDispatchRuntimeStateSupport.saveIdleTrack(new StationTaskIdleTrack(taskNo, stationId, System.currentTimeMillis()));
+                    News.info("杈撻�佺珯鐐逛换鍔″垰瀹屾垚鍛戒护涓嬪彂锛屽凡璺宠繃鍋滅暀閲嶇畻銆傜珯鐐瑰彿={}锛屽伐浣滃彿={}锛岃窛涓婃涓嬪彂={}ms锛宺outeVersion={}",
+                            stationId, taskNo, elapsedMs, session.getRouteVersion());
+                    return true;
+                }
+            }
+        }
+        if (!stationDispatchRuntimeStateSupport.hasRecentIssuedMoveCommand(taskNo, stationId, thresholdMs)) {
+            return false;
+        }
+        stationDispatchRuntimeStateSupport.saveIdleTrack(new StationTaskIdleTrack(taskNo, stationId, System.currentTimeMillis()));
+        News.info("杈撻�佺珯鐐逛换鍔″垰瀹屾垚鍛戒护涓嬪彂锛屽凡璺宠繃鍋滅暀閲嶇畻銆傜珯鐐瑰彿={}锛屽伐浣滃彿={}锛岃窛鏈�杩戝懡浠や笅鍙�<{}ms锛宺outeVersion={}",
+                stationId, taskNo, thresholdMs, session == null ? null : session.getRouteVersion());
+        return true;
+    }
+
+    private RerouteExecutionResult executeReroutePlanWithTaskLock(RerouteContext context,
+                                                                  RerouteCommandPlan plan,
+                                                                  StationProtocol stationProtocol,
+                                                                  Integer taskNo,
+                                                                  Integer stationId) {
+        boolean runBlockReroute = context.sceneType() == RerouteSceneType.RUN_BLOCK_REROUTE;
+        if (context.checkRecentDispatch()
+                && shouldSkipIdleRecoverForRecentDispatch(taskNo, stationId)) {
+            return RerouteExecutionResult.skip("recent-dispatch");
+        }
+        int currentTaskBufferCommandCount = countCurrentTaskBufferCommands(stationProtocol.getTaskBufferItems(), taskNo);
+        if (currentTaskBufferCommandCount > 0 && !runBlockReroute) {
+            if (context.sceneType() == RerouteSceneType.IDLE_RECOVER) {
+                News.info("杈撻�佺珯鐐逛换鍔″仠鐣欒秴鏃讹紝浣嗙紦瀛樺尯浠嶅瓨鍦ㄥ綋鍓嶄换鍔″懡浠わ紝宸茶烦杩囬噸绠椼�傜珯鐐瑰彿={}锛屽伐浣滃彿={}锛屽綋鍓嶄换鍔″懡浠ゆ暟={}",
+                        stationId,
+                        taskNo,
+                        currentTaskBufferCommandCount);
+            }
+            return RerouteExecutionResult.skip("buffer-has-current-task");
+        }
+        if (currentTaskBufferCommandCount > 0 && runBlockReroute) {
+            News.info("杈撻�佺珯鐐硅繍琛屽牭濉為噸瑙勫垝妫�娴嬪埌鏃у垎娈靛懡浠ゆ畫鐣欙紝宸插厛娓呯悊鏈湴鐘舵�佸悗缁х画閲嶅彂銆傜珯鐐瑰彿={}锛屽伐浣滃彿={}锛屽綋鍓嶄换鍔″懡浠ゆ暟={}",
+                    stationId,
+                    taskNo,
+                    currentTaskBufferCommandCount);
+        }
+        if (!runBlockReroute
+                && context.checkSuppressDispatch()
+                && stationMoveCoordinator != null
+                && stationMoveCoordinator.shouldSuppressDispatch(taskNo, stationId, plan.command())) {
+            return RerouteExecutionResult.skip("dispatch-suppressed");
+        }
+        if (context.requireOutOrderDispatchLock()
+                && !stationDispatchRuntimeStateSupport.tryAcquireOutOrderDispatchLock(taskNo, stationId, OUT_ORDER_DISPATCH_LIMIT_SECONDS)) {
+            return RerouteExecutionResult.skip("out-order-lock");
+        }
+
+        if (context.cancelSessionBeforeDispatch() && stationMoveCoordinator != null) {
+            stationMoveCoordinator.markCancelPending(taskNo, "reroute_pending");
+        }
+
+        if (runBlockReroute) {
+            if (context.cancelSessionBeforeDispatch() && stationMoveCoordinator != null) {
+                stationMoveCoordinator.cancelSession(taskNo);
+            }
+            if (context.resetSegmentCommandsBeforeDispatch()) {
+                stationDispatchRuntimeStateSupport.signalSegmentReset(taskNo, STATION_MOVE_RESET_WAIT_MS);
+            }
+        }
+
+        if (!runBlockReroute
+                && context.cancelSessionBeforeDispatch() && stationMoveCoordinator != null) {
+            stationMoveCoordinator.cancelSession(taskNo);
+        }
+        if (!isBlank(context.executionLockKey())
+                && !stationDispatchRuntimeStateSupport.tryAcquireLock(context.executionLockKey(), context.executionLockSeconds())) {
+                return RerouteExecutionResult.skip("scene-lock");
+        }
+        if (!runBlockReroute && context.resetSegmentCommandsBeforeDispatch()) {
+            stationDispatchRuntimeStateSupport.signalSegmentReset(taskNo, STATION_MOVE_RESET_WAIT_MS);
+        }
+
+        int clearedCommandCount = 0;
+        if (context.clearIdleIssuedCommands()) {
+            clearedCommandCount = stationDispatchRuntimeStateSupport.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);
+    }
+
+    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();
+
+        stationOutboundDecisionSupport.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) {
+            stationDispatchRuntimeStateSupport.saveIdleTrack(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 void checkStationIdleRecover(BasDevp basDevp,
+                                         StationThread stationThread,
+                                         StationProtocol stationProtocol,
+                                         List<Integer> outOrderList) {
+        if (stationProtocol == null || stationProtocol.getTaskNo() == null || stationProtocol.getTaskNo() <= 0) {
+            return;
+        }
+        if (!Objects.equals(stationProtocol.getStationId(), stationProtocol.getTargetStaNo())) {
+            return;
+        }
+
+        StationTaskIdleTrack idleTrack = stationDispatchRuntimeStateSupport.touchIdleTrack(stationProtocol.getTaskNo(), stationProtocol.getStationId());
+        if (shouldSkipIdleRecoverForRecentDispatch(stationProtocol.getTaskNo(), stationProtocol.getStationId())) {
+            return;
+        }
+        if (idleTrack == null || !idleTrack.isTimeout(STATION_IDLE_RECOVER_SECONDS)) {
+            return;
+        }
+
+        WrkMast wrkMast = wrkMastService.selectByWorkNo(stationProtocol.getTaskNo());
+        if (!canRecoverIdleStationTask(wrkMast, stationProtocol.getStationId())) {
+            return;
+        }
+
+        Object lock = redisUtil.get(RedisKeyType.CHECK_STATION_IDLE_RECOVER_LIMIT_.key + stationProtocol.getTaskNo());
+        if (lock != null) {
+            return;
+        }
+        Double pathLenFactor = stationOutboundDecisionSupport.resolveOutboundPathLenFactor(wrkMast);
+        RerouteContext context = RerouteContext.create(
+                RerouteSceneType.IDLE_RECOVER,
+                basDevp,
+                stationThread,
+                stationProtocol,
+                wrkMast,
+                outOrderList,
+                pathLenFactor,
+                "checkStationIdleRecover"
+        ).withCancelSessionBeforeDispatch()
+                .withExecutionLock(RedisKeyType.CHECK_STATION_IDLE_RECOVER_LIMIT_.key + stationProtocol.getTaskNo(), STATION_IDLE_RECOVER_LIMIT_SECONDS)
+                .withResetSegmentCommandsBeforeDispatch()
+                .clearIdleIssuedCommands(idleTrack);
+        executeSharedReroute(context);
+    }
+
+    private void executeRunBlockDirectReassign(BasDevp basDevp,
+                                               StationThread stationThread,
+                                               StationProtocol stationProtocol,
+                                               WrkMast wrkMast) {
+        if (basDevp == null || stationThread == null || stationProtocol == null || wrkMast == null) {
+            return;
+        }
+        int currentTaskBufferCommandCount = countCurrentTaskBufferCommands(
+                stationProtocol.getTaskBufferItems(),
+                stationProtocol.getTaskNo()
+        );
+        if (currentTaskBufferCommandCount > 0) {
+            News.info("杈撻�佺珯鐐硅繍琛屽牭濉為噸鍒嗛厤宸茶烦杩囷紝缂撳瓨鍖轰粛瀛樺湪褰撳墠浠诲姟鍛戒护銆傜珯鐐瑰彿={}锛屽伐浣滃彿={}锛屽綋鍓嶄换鍔″懡浠ゆ暟={}",
+                    stationProtocol.getStationId(),
+                    stationProtocol.getTaskNo(),
+                    currentTaskBufferCommandCount);
+            return;
+        }
+        if (stationMoveCoordinator != null) {
+            stationMoveCoordinator.cancelSession(wrkMast.getWrkNo());
+        }
+        String response = wmsOperateUtils.applyReassignTaskLocNo(wrkMast.getWrkNo(), stationProtocol.getStationId());
+        if (Cools.isEmpty(response)) {
+            News.taskError(wrkMast.getWrkNo(), "璇锋眰WMS閲嶆柊鍒嗛厤搴撲綅鎺ュ彛澶辫触锛屾帴鍙f湭鍝嶅簲锛侊紒锛乺esponse锛歿}", response);
+            return;
+        }
+        JSONObject jsonObject = JSON.parseObject(response);
+        if (!jsonObject.getInteger("code").equals(200)) {
+            News.error("璇锋眰WMS鎺ュ彛澶辫触锛侊紒锛乺esponse锛歿}", response);
+            return;
+        }
+
+        StartupDto dto = jsonObject.getObject("data", StartupDto.class);
+        String sourceLocNo = wrkMast.getLocNo();
+        String locNo = dto.getLocNo();
+
+        LocMast sourceLocMast = locMastService.queryByLoc(sourceLocNo);
+        if (sourceLocMast == null) {
+            News.taskInfo(wrkMast.getWrkNo(), "搴撲綅鍙�:{} 婧愬簱浣嶄俊鎭笉瀛樺湪", sourceLocNo);
+            return;
+        }
+        if (!sourceLocMast.getLocSts().equals("S")) {
+            News.taskInfo(wrkMast.getWrkNo(), "搴撲綅鍙�:{} 婧愬簱浣嶇姸鎬佷笉澶勪簬鍏ュ簱棰勭害", sourceLocNo);
+            return;
+        }
+
+        LocMast locMast = locMastService.queryByLoc(locNo);
+        if (locMast == null) {
+            News.taskInfo(wrkMast.getWrkNo(), "搴撲綅鍙�:{} 鐩爣搴撲綅淇℃伅涓嶅瓨鍦�", locNo);
+            return;
+        }
+        if (!locMast.getLocSts().equals("O")) {
+            News.taskInfo(wrkMast.getWrkNo(), "搴撲綅鍙�:{} 鐩爣搴撲綅鐘舵�佷笉澶勪簬绌哄簱浣�", locNo);
+            return;
+        }
+
+        FindCrnNoResult findCrnNoResult = commonService.findCrnNoByLocNo(locNo);
+        if (findCrnNoResult == null) {
+            News.taskInfo(wrkMast.getWrkNo(), "{}宸ヤ綔,鏈尮閰嶅埌鍫嗗灈鏈�", wrkMast.getWrkNo());
+            return;
+        }
+        Integer crnNo = findCrnNoResult.getCrnNo();
+
+        Integer targetStationId = commonService.findInStationId(findCrnNoResult, stationProtocol.getStationId());
+        if (targetStationId == null) {
+            News.taskInfo(wrkMast.getWrkNo(), "{}绔欑偣,鎼滅储鍏ュ簱绔欑偣澶辫触", stationProtocol.getStationId());
+            return;
+        }
+
+        StationCommand command = stationThread.getCommand(StationCommandType.MOVE, wrkMast.getWrkNo(), stationProtocol.getStationId(), targetStationId, 0);
+        if (command == null) {
+            News.taskInfo(wrkMast.getWrkNo(), "{}宸ヤ綔,鑾峰彇杈撻�佺嚎鍛戒护澶辫触", wrkMast.getWrkNo());
+            return;
+        }
+
+        sourceLocMast.setLocSts("O");
+        sourceLocMast.setModiTime(new Date());
+        locMastService.updateById(sourceLocMast);
+
+        locMast.setLocSts("S");
+        locMast.setModiTime(new Date());
+        locMastService.updateById(locMast);
+
+        wrkMast.setLocNo(locNo);
+        wrkMast.setStaNo(targetStationId);
+
+        if (findCrnNoResult.getCrnType().equals(SlaveType.Crn)) {
+            wrkMast.setCrnNo(crnNo);
+        } else if (findCrnNoResult.getCrnType().equals(SlaveType.DualCrn)) {
+            wrkMast.setDualCrnNo(crnNo);
+        } else {
+            throw new CoolException("鏈煡璁惧绫诲瀷");
+        }
+
+        if (!wrkMastService.updateById(wrkMast)) {
+            return;
+        }
+        boolean offered = offerDevpCommandWithDedup(basDevp.getDevpNo(), command, "checkStationRunBlock_direct");
+        if (!offered) {
+            return;
+        }
+        if (stationMoveCoordinator != null) {
+            stationMoveCoordinator.recordDispatch(
+                    wrkMast.getWrkNo(),
+                    stationProtocol.getStationId(),
+                    "checkStationRunBlock_direct",
+                    command,
+                    false
+            );
+        }
+    }
+
+    private boolean canRecoverIdleStationTask(WrkMast wrkMast, Integer currentStationId) {
+        if (wrkMast == null || currentStationId == null || wrkMast.getStaNo() == null) {
+            return false;
+        }
+        if (Objects.equals(currentStationId, wrkMast.getStaNo())) {
+            return false;
+        }
+        return Objects.equals(wrkMast.getWrkSts(), WrkStsType.INBOUND_STATION_RUN.sts)
+                || Objects.equals(wrkMast.getWrkSts(), WrkStsType.STATION_RUN.sts);
+    }
+
+    private int countCurrentTaskBufferCommands(List<StationTaskBufferItem> taskBufferItems, Integer currentTaskNo) {
+        if (taskBufferItems == null || taskBufferItems.isEmpty() || currentTaskNo == null || currentTaskNo <= 0) {
+            return 0;
+        }
+        int count = 0;
+        for (StationTaskBufferItem item : taskBufferItems) {
+            if (item == null || item.getTaskNo() == null) {
+                continue;
+            }
+            if (currentTaskNo.equals(item.getTaskNo())) {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    private boolean offerDevpCommandWithDedup(Integer deviceNo, StationCommand command, String scene) {
+        StationCommandDispatchResult dispatchResult = stationCommandDispatcher
+                .dispatch(deviceNo, command, "station-operate-process", scene);
+        return dispatchResult.isAccepted();
+    }
+
+    private boolean isBlank(String value) {
+        return value == null || value.trim().isEmpty();
+    }
+}
diff --git a/src/main/java/com/zy/core/utils/station/StationTaskIdleTrack.java b/src/main/java/com/zy/core/utils/station/StationTaskIdleTrack.java
new file mode 100644
index 0000000..7ab5eb4
--- /dev/null
+++ b/src/main/java/com/zy/core/utils/station/StationTaskIdleTrack.java
@@ -0,0 +1,47 @@
+package com.zy.core.utils.station;
+
+public class StationTaskIdleTrack {
+    private Integer taskNo;
+    private Integer stationId;
+    private Long firstSeenTime;
+
+    public StationTaskIdleTrack() {
+    }
+
+    public StationTaskIdleTrack(Integer taskNo, Integer stationId, Long firstSeenTime) {
+        this.taskNo = taskNo;
+        this.stationId = stationId;
+        this.firstSeenTime = firstSeenTime;
+    }
+
+    public boolean isTimeout(int seconds) {
+        if (firstSeenTime == null) {
+            return false;
+        }
+        return System.currentTimeMillis() - firstSeenTime >= seconds * 1000L;
+    }
+
+    public Integer getTaskNo() {
+        return taskNo;
+    }
+
+    public void setTaskNo(Integer taskNo) {
+        this.taskNo = taskNo;
+    }
+
+    public Integer getStationId() {
+        return stationId;
+    }
+
+    public void setStationId(Integer stationId) {
+        this.stationId = stationId;
+    }
+
+    public Long getFirstSeenTime() {
+        return firstSeenTime;
+    }
+
+    public void setFirstSeenTime(Long firstSeenTime) {
+        this.firstSeenTime = firstSeenTime;
+    }
+}
diff --git a/src/main/java/com/zy/core/utils/station/model/CircleTargetCandidate.java b/src/main/java/com/zy/core/utils/station/model/CircleTargetCandidate.java
new file mode 100644
index 0000000..8aa9de4
--- /dev/null
+++ b/src/main/java/com/zy/core/utils/station/model/CircleTargetCandidate.java
@@ -0,0 +1,25 @@
+package com.zy.core.utils.station.model;
+
+public final class CircleTargetCandidate {
+    private final Integer stationId;
+    private final Integer pathLength;
+    private final Integer offset;
+
+    public CircleTargetCandidate(Integer stationId, Integer pathLength, Integer offset) {
+        this.stationId = stationId;
+        this.pathLength = pathLength == null ? 0 : pathLength;
+        this.offset = offset == null ? 0 : offset;
+    }
+
+    public Integer getStationId() {
+        return stationId;
+    }
+
+    public Integer getPathLength() {
+        return pathLength;
+    }
+
+    public Integer getOffset() {
+        return offset;
+    }
+}
diff --git a/src/main/java/com/zy/core/utils/station/model/DispatchLimitConfig.java b/src/main/java/com/zy/core/utils/station/model/DispatchLimitConfig.java
new file mode 100644
index 0000000..24367e1
--- /dev/null
+++ b/src/main/java/com/zy/core/utils/station/model/DispatchLimitConfig.java
@@ -0,0 +1,11 @@
+package com.zy.core.utils.station.model;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class DispatchLimitConfig {
+    private double circleMaxLoadLimit = 0.8d;
+    private boolean loopModeEnable = false;
+}
diff --git a/src/main/java/com/zy/core/utils/station/model/LoadGuardState.java b/src/main/java/com/zy/core/utils/station/model/LoadGuardState.java
new file mode 100644
index 0000000..5747a0f
--- /dev/null
+++ b/src/main/java/com/zy/core/utils/station/model/LoadGuardState.java
@@ -0,0 +1,52 @@
+package com.zy.core.utils.station.model;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Getter
+public class LoadGuardState {
+    @Setter
+    private int totalStationCount = 0;
+    @Setter
+    private int projectedTaskStationCount = 0;
+    private final Map<Integer, Integer> stationLoopNoMap = new HashMap<>();
+
+    public double currentLoad() {
+        return calcLoad(this.projectedTaskStationCount, this.totalStationCount);
+    }
+
+    public double loadAfterReserve() {
+        return calcLoad(this.projectedTaskStationCount + 1, this.totalStationCount);
+    }
+
+    public void reserveLoopTask(Integer loopNo) {
+        if (loopNo == null || loopNo <= 0 || this.totalStationCount <= 0) {
+            return;
+        }
+        this.projectedTaskStationCount++;
+    }
+
+    public void putStationLoopNo(Integer stationId, Integer loopNo) {
+        if (stationId == null || loopNo == null) {
+            return;
+        }
+        this.stationLoopNoMap.put(stationId, loopNo);
+    }
+
+    private double calcLoad(int taskCount, int stationCount) {
+        if (stationCount <= 0 || taskCount <= 0) {
+            return 0.0;
+        }
+        double load = (double) taskCount / (double) stationCount;
+        if (load < 0.0) {
+            return 0.0;
+        }
+        if (load > 1.0) {
+            return 1.0;
+        }
+        return load;
+    }
+}
diff --git a/src/main/java/com/zy/core/utils/station/model/LoopHitResult.java b/src/main/java/com/zy/core/utils/station/model/LoopHitResult.java
new file mode 100644
index 0000000..490a87f
--- /dev/null
+++ b/src/main/java/com/zy/core/utils/station/model/LoopHitResult.java
@@ -0,0 +1,20 @@
+package com.zy.core.utils.station.model;
+
+import lombok.Getter;
+
+@Getter
+public class LoopHitResult {
+    private final boolean throughLoop;
+    private final Integer loopNo;
+    private final Integer hitStationId;
+
+    public LoopHitResult(boolean throughLoop, Integer loopNo, Integer hitStationId) {
+        this.throughLoop = throughLoop;
+        this.loopNo = loopNo;
+        this.hitStationId = hitStationId;
+    }
+
+    public static LoopHitResult noHit() {
+        return new LoopHitResult(false, null, null);
+    }
+}
diff --git a/src/main/java/com/zy/core/utils/station/model/OutOrderDispatchDecision.java b/src/main/java/com/zy/core/utils/station/model/OutOrderDispatchDecision.java
new file mode 100644
index 0000000..eec41b5
--- /dev/null
+++ b/src/main/java/com/zy/core/utils/station/model/OutOrderDispatchDecision.java
@@ -0,0 +1,46 @@
+package com.zy.core.utils.station.model;
+
+import com.zy.core.service.StationTaskLoopService;
+
+public final class OutOrderDispatchDecision {
+    private final Integer targetStationId;
+    private final boolean circle;
+    private final StationTaskLoopService.LoopEvaluation loopEvaluation;
+    private final boolean countLoopIssue;
+
+    private OutOrderDispatchDecision(Integer targetStationId,
+                                     boolean circle,
+                                     StationTaskLoopService.LoopEvaluation loopEvaluation,
+                                     boolean countLoopIssue) {
+        this.targetStationId = targetStationId;
+        this.circle = circle;
+        this.loopEvaluation = loopEvaluation;
+        this.countLoopIssue = countLoopIssue;
+    }
+
+    public static OutOrderDispatchDecision direct(Integer targetStationId) {
+        return new OutOrderDispatchDecision(targetStationId, false, null, false);
+    }
+
+    public static OutOrderDispatchDecision circle(Integer targetStationId,
+                                                  StationTaskLoopService.LoopEvaluation loopEvaluation,
+                                                  boolean countLoopIssue) {
+        return new OutOrderDispatchDecision(targetStationId, true, loopEvaluation, countLoopIssue);
+    }
+
+    public Integer getTargetStationId() {
+        return targetStationId;
+    }
+
+    public boolean isCircle() {
+        return circle;
+    }
+
+    public StationTaskLoopService.LoopEvaluation getLoopEvaluation() {
+        return loopEvaluation;
+    }
+
+    public boolean shouldCountLoopIssue() {
+        return countLoopIssue;
+    }
+}
diff --git a/src/main/java/com/zy/core/utils/station/model/RerouteCommandPlan.java b/src/main/java/com/zy/core/utils/station/model/RerouteCommandPlan.java
new file mode 100644
index 0000000..1b70e5d
--- /dev/null
+++ b/src/main/java/com/zy/core/utils/station/model/RerouteCommandPlan.java
@@ -0,0 +1,53 @@
+package com.zy.core.utils.station.model;
+
+import com.zy.core.model.command.StationCommand;
+
+public 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;
+    }
+
+    public static RerouteCommandPlan skip(String reason) {
+        return new RerouteCommandPlan(true, reason, null, null, null);
+    }
+
+    public static RerouteCommandPlan dispatch(StationCommand command,
+                                              RerouteDecision decision,
+                                              String dispatchScene) {
+        return new RerouteCommandPlan(false, null, command, decision, dispatchScene);
+    }
+
+    public boolean skip() {
+        return skip;
+    }
+
+    public String skipReason() {
+        return skipReason;
+    }
+
+    public StationCommand command() {
+        return command;
+    }
+
+    public RerouteDecision decision() {
+        return decision;
+    }
+
+    public String dispatchScene() {
+        return dispatchScene;
+    }
+}
diff --git a/src/main/java/com/zy/core/utils/station/model/RerouteContext.java b/src/main/java/com/zy/core/utils/station/model/RerouteContext.java
new file mode 100644
index 0000000..2f39856
--- /dev/null
+++ b/src/main/java/com/zy/core/utils/station/model/RerouteContext.java
@@ -0,0 +1,185 @@
+package com.zy.core.utils.station.model;
+
+import com.zy.asrs.entity.BasDevp;
+import com.zy.asrs.entity.WrkMast;
+import com.zy.core.model.protocol.StationProtocol;
+import com.zy.core.thread.StationThread;
+import com.zy.core.utils.station.StationTaskIdleTrack;
+
+import java.util.Collections;
+import java.util.List;
+
+public 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();
+    }
+
+    public 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);
+    }
+
+    public RerouteContext withDispatchDeviceNo(Integer dispatchDeviceNo) {
+        this.dispatchDeviceNo = dispatchDeviceNo;
+        return this;
+    }
+
+    public RerouteContext withRunBlockCommand() {
+        this.useRunBlockCommand = true;
+        return this;
+    }
+
+    public RerouteContext withSuppressDispatchGuard() {
+        this.checkSuppressDispatch = true;
+        return this;
+    }
+
+    public RerouteContext withOutOrderDispatchLock() {
+        this.requireOutOrderDispatchLock = true;
+        return this;
+    }
+
+    public RerouteContext withCancelSessionBeforeDispatch() {
+        this.cancelSessionBeforeDispatch = true;
+        return this;
+    }
+
+    public RerouteContext withResetSegmentCommandsBeforeDispatch() {
+        this.resetSegmentCommandsBeforeDispatch = true;
+        return this;
+    }
+
+    public RerouteContext clearIdleIssuedCommands(StationTaskIdleTrack idleTrack) {
+        this.clearIdleIssuedCommands = true;
+        this.idleTrack = idleTrack;
+        return this;
+    }
+
+    public RerouteContext withRecentDispatchGuard() {
+        this.checkRecentDispatch = true;
+        return this;
+    }
+
+    public RerouteContext withExecutionLock(String executionLockKey, int executionLockSeconds) {
+        this.executionLockKey = executionLockKey;
+        this.executionLockSeconds = executionLockSeconds;
+        return this;
+    }
+
+    public RerouteSceneType sceneType() {
+        return sceneType;
+    }
+
+    public BasDevp basDevp() {
+        return basDevp;
+    }
+
+    public StationThread stationThread() {
+        return stationThread;
+    }
+
+    public StationProtocol stationProtocol() {
+        return stationProtocol;
+    }
+
+    public WrkMast wrkMast() {
+        return wrkMast;
+    }
+
+    public List<Integer> outOrderStationIds() {
+        return outOrderStationIds;
+    }
+
+    public Double pathLenFactor() {
+        return pathLenFactor;
+    }
+
+    public String dispatchScene() {
+        return dispatchScene;
+    }
+
+    public Integer dispatchDeviceNo() {
+        return dispatchDeviceNo;
+    }
+
+    public boolean useRunBlockCommand() {
+        return useRunBlockCommand;
+    }
+
+    public boolean checkSuppressDispatch() {
+        return checkSuppressDispatch;
+    }
+
+    public boolean requireOutOrderDispatchLock() {
+        return requireOutOrderDispatchLock;
+    }
+
+    public boolean cancelSessionBeforeDispatch() {
+        return cancelSessionBeforeDispatch;
+    }
+
+    public boolean resetSegmentCommandsBeforeDispatch() {
+        return resetSegmentCommandsBeforeDispatch;
+    }
+
+    public boolean clearIdleIssuedCommands() {
+        return clearIdleIssuedCommands;
+    }
+
+    public boolean checkRecentDispatch() {
+        return checkRecentDispatch;
+    }
+
+    public String executionLockKey() {
+        return executionLockKey;
+    }
+
+    public int executionLockSeconds() {
+        return executionLockSeconds;
+    }
+
+    public StationTaskIdleTrack idleTrack() {
+        return idleTrack;
+    }
+}
diff --git a/src/main/java/com/zy/core/utils/station/model/RerouteDecision.java b/src/main/java/com/zy/core/utils/station/model/RerouteDecision.java
new file mode 100644
index 0000000..f6a6810
--- /dev/null
+++ b/src/main/java/com/zy/core/utils/station/model/RerouteDecision.java
@@ -0,0 +1,47 @@
+package com.zy.core.utils.station.model;
+
+public 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;
+    }
+
+    public static RerouteDecision skip(String reason) {
+        return new RerouteDecision(true, reason, null, null);
+    }
+
+    public static RerouteDecision proceed(Integer targetStationId) {
+        return new RerouteDecision(false, null, targetStationId, null);
+    }
+
+    public static RerouteDecision proceed(Integer targetStationId,
+                                          OutOrderDispatchDecision dispatchDecision) {
+        return new RerouteDecision(false, null, targetStationId, dispatchDecision);
+    }
+
+    public boolean skip() {
+        return skip;
+    }
+
+    public String skipReason() {
+        return skipReason;
+    }
+
+    public Integer targetStationId() {
+        return targetStationId;
+    }
+
+    public OutOrderDispatchDecision dispatchDecision() {
+        return dispatchDecision;
+    }
+}
diff --git a/src/main/java/com/zy/core/utils/station/model/RerouteExecutionResult.java b/src/main/java/com/zy/core/utils/station/model/RerouteExecutionResult.java
new file mode 100644
index 0000000..24dba39
--- /dev/null
+++ b/src/main/java/com/zy/core/utils/station/model/RerouteExecutionResult.java
@@ -0,0 +1,52 @@
+package com.zy.core.utils.station.model;
+
+import com.zy.core.model.command.StationCommand;
+
+public 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;
+    }
+
+    public static RerouteExecutionResult skip(String reason) {
+        return new RerouteExecutionResult(true, reason, false, null, 0);
+    }
+
+    public static RerouteExecutionResult dispatched(StationCommand command,
+                                                    int clearedCommandCount) {
+        return new RerouteExecutionResult(false, null, true, command, clearedCommandCount);
+    }
+
+    public boolean skipped() {
+        return skipped;
+    }
+
+    public String skipReason() {
+        return skipReason;
+    }
+
+    public boolean dispatched() {
+        return dispatched;
+    }
+
+    public StationCommand command() {
+        return command;
+    }
+
+    public int clearedCommandCount() {
+        return clearedCommandCount;
+    }
+}
diff --git a/src/main/java/com/zy/core/utils/station/model/RerouteSceneType.java b/src/main/java/com/zy/core/utils/station/model/RerouteSceneType.java
new file mode 100644
index 0000000..3132400
--- /dev/null
+++ b/src/main/java/com/zy/core/utils/station/model/RerouteSceneType.java
@@ -0,0 +1,8 @@
+package com.zy.core.utils.station.model;
+
+public enum RerouteSceneType {
+    RUN_BLOCK_REROUTE,
+    IDLE_RECOVER,
+    OUT_ORDER,
+    WATCH_CIRCLE
+}
diff --git a/src/test/java/com/zy/asrs/controller/StationControllerTest.java b/src/test/java/com/zy/asrs/controller/StationControllerTest.java
index 5983e47..c6dcd32 100644
--- a/src/test/java/com/zy/asrs/controller/StationControllerTest.java
+++ b/src/test/java/com/zy/asrs/controller/StationControllerTest.java
@@ -4,8 +4,12 @@
 import com.zy.asrs.domain.param.StationCommandMoveParam;
 import com.zy.asrs.entity.BasDevp;
 import com.zy.asrs.service.BasDevpService;
+import com.zy.core.dispatch.StationCommandDispatchResult;
+import com.zy.core.dispatch.StationCommandDispatcher;
 import com.zy.core.cache.SlaveConnection;
 import com.zy.core.enums.SlaveType;
+import com.zy.core.enums.StationCommandType;
+import com.zy.core.model.command.StationCommand;
 import com.zy.core.thread.StationThread;
 import org.junit.jupiter.api.Test;
 import org.springframework.test.util.ReflectionTestUtils;
@@ -13,12 +17,21 @@
 import java.util.Collections;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.same;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 class StationControllerTest {
+
+    @Test
+    void controller_noLongerKeepsDispatcherFallbackHelper() {
+        assertThrows(NoSuchMethodException.class,
+                () -> StationController.class.getDeclaredMethod("getStationCommandDispatcher"));
+    }
 
     @Test
     void commandClearPath_callsThreadClearPath() {
@@ -48,4 +61,39 @@
             SlaveConnection.remove(SlaveType.Devp, 1);
         }
     }
+
+    @Test
+    void commandMove_dispatchesViaStationCommandDispatcher() {
+        StationController controller = new StationController();
+        BasDevpService basDevpService = mock(BasDevpService.class);
+        StationThread stationThread = mock(StationThread.class);
+        StationCommandDispatcher dispatcher = mock(StationCommandDispatcher.class);
+        StationCommand command = new StationCommand();
+
+        ReflectionTestUtils.setField(controller, "basDevpService", basDevpService);
+        ReflectionTestUtils.setField(controller, "stationCommandDispatcher", dispatcher);
+
+        BasDevp basDevp = new BasDevp();
+        basDevp.setStationList("[{\"deviceNo\":1,\"stationId\":145}]");
+        when(basDevpService.list(any(com.baomidou.mybatisplus.core.conditions.Wrapper.class)))
+                .thenReturn(Collections.singletonList(basDevp));
+        when(stationThread.getCommand(StationCommandType.MOVE, 10335, 145, 188, 0)).thenReturn(command);
+        when(dispatcher.dispatch(1, command, "station-controller", "manual-move"))
+                .thenReturn(StationCommandDispatchResult.accepted("accepted", 1, "station-controller", "manual-move"));
+
+        StationCommandMoveParam param = new StationCommandMoveParam();
+        param.setStationId(145);
+        param.setTaskNo(10335);
+        param.setTargetStationId(188);
+
+        SlaveConnection.put(SlaveType.Devp, 1, stationThread);
+        try {
+            R result = controller.commandMove(param);
+
+            assertEquals(200, result.get("code"));
+            verify(dispatcher).dispatch(eq(1), same(command), eq("station-controller"), eq("manual-move"));
+        } finally {
+            SlaveConnection.remove(SlaveType.Devp, 1);
+        }
+    }
 }
diff --git a/src/test/java/com/zy/core/dispatch/StationCommandDispatcherTest.java b/src/test/java/com/zy/core/dispatch/StationCommandDispatcherTest.java
new file mode 100644
index 0000000..aab4866
--- /dev/null
+++ b/src/test/java/com/zy/core/dispatch/StationCommandDispatcherTest.java
@@ -0,0 +1,100 @@
+package com.zy.core.dispatch;
+
+import com.zy.common.utils.RedisUtil;
+import com.zy.core.cache.MessageQueue;
+import com.zy.core.enums.SlaveType;
+import com.zy.core.enums.StationCommandType;
+import com.zy.core.model.command.StationCommand;
+import com.zy.core.move.StationMoveCoordinator;
+import org.junit.jupiter.api.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class StationCommandDispatcherTest {
+
+    @Test
+    void dispatch_acceptsMoveCommandAndReturnsQueueDepth() {
+        StationCommandDispatcher dispatcher = new StationCommandDispatcher();
+
+        StationCommand command = new StationCommand();
+        command.setCommandType(StationCommandType.MOVE);
+        command.setTaskNo(100);
+        command.setStationId(10);
+        command.setTargetStaNo(20);
+
+        MessageQueue.init(SlaveType.Devp, 1);
+        try {
+            StationCommandDispatchResult result = dispatcher.dispatch(1, command, "unit-test", "move");
+
+            assertTrue(result.isAccepted());
+            assertEquals("accepted", result.getReason());
+            assertEquals(1, result.getQueueDepth());
+            assertEquals("unit-test", result.getSource());
+            assertEquals("move", result.getScene());
+        } finally {
+            MessageQueue.clear(SlaveType.Devp, 1);
+        }
+    }
+
+    @Test
+    void dispatch_suppressesDuplicateMoveCommandWithinDedupWindow() {
+        RedisUtil redisUtil = mock(RedisUtil.class);
+        StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class);
+        StationCommandDispatcher dispatcher = new StationCommandDispatcher(redisUtil, coordinator);
+        Map<String, Object> dedupStore = new HashMap<>();
+
+        when(redisUtil.get(anyString())).thenAnswer(invocation -> dedupStore.get(invocation.getArgument(0)));
+        when(redisUtil.set(anyString(), eq("lock"), anyLong())).thenAnswer(invocation -> {
+            dedupStore.put(invocation.getArgument(0), invocation.getArgument(1));
+            return true;
+        });
+        when(coordinator.buildPathSignatureHash(org.mockito.ArgumentMatchers.any(StationCommand.class)))
+                .thenReturn("same-path");
+
+        StationCommand command = new StationCommand();
+        command.setCommandType(StationCommandType.MOVE);
+        command.setTaskNo(100);
+        command.setStationId(10);
+        command.setTargetStaNo(20);
+
+        MessageQueue.init(SlaveType.Devp, 1);
+        try {
+            StationCommandDispatchResult first = dispatcher.dispatch(1, command, "unit-test", "move");
+            StationCommandDispatchResult second = dispatcher.dispatch(1, command, "unit-test", "move");
+
+            assertTrue(first.isAccepted());
+            assertFalse(second.isAccepted());
+            assertEquals("dedup-suppressed", second.getReason());
+            assertEquals(1, second.getQueueDepth());
+        } finally {
+            MessageQueue.clear(SlaveType.Devp, 1);
+        }
+    }
+
+    @Test
+    void dispatch_rejectsWhenDevpQueueIsNotInitialized() {
+        StationCommandDispatcher dispatcher = new StationCommandDispatcher();
+
+        StationCommand command = new StationCommand();
+        command.setCommandType(StationCommandType.MOVE);
+        command.setTaskNo(100);
+        command.setStationId(10);
+        command.setTargetStaNo(20);
+
+        StationCommandDispatchResult result = dispatcher.dispatch(999, command, "unit-test", "move");
+
+        assertFalse(result.isAccepted());
+        assertEquals("queue-not-initialized", result.getReason());
+        assertEquals(0, result.getQueueDepth());
+    }
+}
diff --git a/src/test/java/com/zy/core/thread/impl/ZyStationV5ThreadTest.java b/src/test/java/com/zy/core/thread/impl/ZyStationV5ThreadTest.java
index bb1da07..ad2ac25 100644
--- a/src/test/java/com/zy/core/thread/impl/ZyStationV5ThreadTest.java
+++ b/src/test/java/com/zy/core/thread/impl/ZyStationV5ThreadTest.java
@@ -2,9 +2,15 @@
 
 import com.zy.asrs.entity.DeviceConfig;
 import com.zy.common.utils.RedisUtil;
+import com.zy.core.cache.MessageQueue;
+import com.zy.core.enums.SlaveType;
+import com.zy.core.model.Task;
+import com.zy.core.model.command.StationCommand;
 import com.zy.core.model.protocol.StationProtocol;
 import com.zy.core.model.protocol.StationTaskBufferItem;
 import com.zy.core.network.ZyStationConnectDriver;
+import com.zy.core.thread.impl.v5.StationV5SegmentExecutor;
+import com.zy.core.thread.impl.v5.StationV5StatusReader;
 import org.junit.jupiter.api.Test;
 import org.springframework.test.util.ReflectionTestUtils;
 
@@ -17,10 +23,35 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 class ZyStationV5ThreadTest {
+
+    @Test
+    void pollAndDispatchQueuedCommand_submitsQueuedMoveCommandToSegmentExecutor() {
+        DeviceConfig deviceConfig = new DeviceConfig();
+        deviceConfig.setDeviceNo(1);
+        RedisUtil redisUtil = mock(RedisUtil.class);
+        StationCommand command = new StationCommand();
+        StationV5SegmentExecutor segmentExecutor = mock(StationV5SegmentExecutor.class);
+
+        ZyStationV5Thread thread = new ZyStationV5Thread(deviceConfig, redisUtil);
+        ReflectionTestUtils.setField(thread, "segmentExecutor", segmentExecutor);
+
+        MessageQueue.init(SlaveType.Devp, 1);
+        try {
+            MessageQueue.offer(SlaveType.Devp, 1, new Task(2, command));
+
+            ReflectionTestUtils.invokeMethod(thread, "pollAndDispatchQueuedCommand");
+
+            verify(segmentExecutor, timeout(1000)).execute(command);
+        } finally {
+            MessageQueue.clear(SlaveType.Devp, 1);
+            thread.close();
+        }
+    }
 
     @Test
     void clearPath_delegatesPureSlotClearingToDriver() {
@@ -46,7 +77,8 @@
         station10.setStationId(10);
         station10.setTaskBufferItems(List.of(hitItem));
 
-        ReflectionTestUtils.setField(thread, "statusList", Arrays.asList(station20, station10));
+        StationV5StatusReader statusReader = (StationV5StatusReader) ReflectionTestUtils.getField(thread, "statusReader");
+        ReflectionTestUtils.setField(statusReader, "statusList", Arrays.asList(station20, station10));
 
         boolean result = thread.clearPath(100);
 
diff --git a/src/test/java/com/zy/core/thread/impl/v5/StationV5RunBlockReroutePlannerTest.java b/src/test/java/com/zy/core/thread/impl/v5/StationV5RunBlockReroutePlannerTest.java
new file mode 100644
index 0000000..be14570
--- /dev/null
+++ b/src/test/java/com/zy/core/thread/impl/v5/StationV5RunBlockReroutePlannerTest.java
@@ -0,0 +1,110 @@
+package com.zy.core.thread.impl.v5;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.zy.common.utils.RedisUtil;
+import com.zy.core.enums.RedisKeyType;
+import com.zy.core.model.command.StationCommand;
+import com.zy.core.service.StationTaskLoopService;
+import org.junit.jupiter.api.Test;
+
+import java.util.HashSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class StationV5RunBlockReroutePlannerTest {
+
+    @Test
+    void plan_prefersLongerCandidateWhenShortestPathIsOverusedInsideTriggeredLoop() {
+        RedisUtil redisUtil = mock(RedisUtil.class);
+        String stateKey = RedisKeyType.STATION_RUN_BLOCK_REROUTE_STATE_.key + "100_10";
+        when(redisUtil.get(stateKey)).thenReturn(JSON.toJSONString(seedState(
+                100,
+                10,
+                2,
+                List.of(),
+                Map.of("10->11->20", 2)
+        )));
+
+        StationV5RunBlockReroutePlanner planner = new StationV5RunBlockReroutePlanner(redisUtil);
+        StationCommand shortest = moveCommand(100, 10, 20, 10, 11, 20);
+        StationCommand longer = moveCommand(100, 10, 20, 10, 31, 32, 20);
+
+        StationTaskLoopService.LoopIdentitySnapshot loopIdentity =
+                new StationTaskLoopService.LoopIdentitySnapshot("10|11|20", new HashSet<>(List.of(10, 11, 20)), 3, 3, "wholeLoop");
+        StationTaskLoopService.LoopEvaluation loopEvaluation =
+                new StationTaskLoopService.LoopEvaluation(100, 10, loopIdentity, 2, 3, true);
+
+        StationV5RunBlockReroutePlanner.PlanResult result = planner.plan(
+                100,
+                10,
+                loopEvaluation,
+                List.of(shortest, longer)
+        );
+
+        assertSame(longer, result.getCommand());
+        assertEquals(3, result.getPlanCount());
+    }
+
+    @Test
+    void plan_resetsIssuedRoutesWhenAllCandidatesHaveBeenTried() {
+        RedisUtil redisUtil = mock(RedisUtil.class);
+        String stateKey = RedisKeyType.STATION_RUN_BLOCK_REROUTE_STATE_.key + "100_10";
+        when(redisUtil.get(stateKey)).thenReturn(JSON.toJSONString(seedState(
+                100,
+                10,
+                1,
+                List.of(List.of(10, 11, 20), List.of(10, 31, 32, 20)),
+                Map.of("10->11->20", 1, "10->31->32->20", 1)
+        )));
+
+        StationV5RunBlockReroutePlanner planner = new StationV5RunBlockReroutePlanner(redisUtil);
+        StationCommand first = moveCommand(100, 10, 20, 10, 11, 20);
+        StationCommand second = moveCommand(100, 10, 20, 10, 31, 32, 20);
+
+        StationTaskLoopService.LoopEvaluation loopEvaluation =
+                new StationTaskLoopService.LoopEvaluation(100, 10, StationTaskLoopService.LoopIdentitySnapshot.empty(), 0, 0, false);
+
+        StationV5RunBlockReroutePlanner.PlanResult result = planner.plan(
+                100,
+                10,
+                loopEvaluation,
+                List.of(first, second)
+        );
+
+        assertSame(first, result.getCommand());
+        assertEquals(2, result.getPlanCount());
+    }
+
+    private static StationCommand moveCommand(Integer taskNo,
+                                              Integer stationId,
+                                              Integer targetStationId,
+                                              Integer... path) {
+        StationCommand command = new StationCommand();
+        command.setTaskNo(taskNo);
+        command.setStationId(stationId);
+        command.setTargetStaNo(targetStationId);
+        command.setNavigatePath(List.of(path));
+        return command;
+    }
+
+    private static JSONObject seedState(Integer taskNo,
+                                        Integer blockStationId,
+                                        Integer planCount,
+                                        List<List<Integer>> issuedRoutePathList,
+                                        Map<String, Integer> routeIssueCountMap) {
+        JSONObject state = new JSONObject();
+        state.put("taskNo", taskNo);
+        state.put("blockStationId", blockStationId);
+        state.put("planCount", planCount);
+        state.put("issuedRoutePathList", issuedRoutePathList);
+        state.put("routeIssueCountMap", new HashMap<>(routeIssueCountMap));
+        return state;
+    }
+}
diff --git a/src/test/java/com/zy/core/utils/StationOperateProcessUtilsReroutePipelineTest.java b/src/test/java/com/zy/core/utils/StationOperateProcessUtilsReroutePipelineTest.java
index 8abda6e..cdc99b8 100644
--- a/src/test/java/com/zy/core/utils/StationOperateProcessUtilsReroutePipelineTest.java
+++ b/src/test/java/com/zy/core/utils/StationOperateProcessUtilsReroutePipelineTest.java
@@ -21,16 +21,34 @@
 import com.zy.core.move.StationMoveCoordinator;
 import com.zy.core.move.StationMoveDispatchMode;
 import com.zy.core.move.StationMoveSession;
+import com.zy.core.dispatch.StationCommandDispatchResult;
+import com.zy.core.dispatch.StationCommandDispatcher;
 import com.zy.core.model.command.StationCommand;
 import com.zy.core.model.protocol.StationProtocol;
 import com.zy.core.model.protocol.StationTaskBufferItem;
 import com.zy.core.thread.StationThread;
 import com.zy.common.utils.RedisUtil;
+import com.zy.core.utils.station.StationDispatchLoadSupport;
+import com.zy.core.utils.station.StationDispatchRuntimeStateSupport;
+import com.zy.core.utils.station.StationOutboundDecisionSupport;
+import com.zy.core.utils.station.StationOutboundDispatchProcessor;
+import com.zy.core.utils.station.StationRegularDispatchProcessor;
+import com.zy.core.utils.station.StationRerouteProcessor;
+import com.zy.core.utils.station.model.DispatchLimitConfig;
+import com.zy.core.utils.station.model.LoadGuardState;
+import com.zy.core.utils.station.model.LoopHitResult;
+import com.zy.core.utils.station.model.RerouteCommandPlan;
+import com.zy.core.utils.station.model.RerouteContext;
+import com.zy.core.utils.station.model.RerouteDecision;
+import com.zy.core.utils.station.model.RerouteExecutionResult;
+import com.zy.core.utils.station.model.RerouteSceneType;
 import org.junit.jupiter.api.Test;
 import org.springframework.test.util.ReflectionTestUtils;
 
 import java.util.Date;
 import java.util.Collections;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -51,6 +69,89 @@
 import static org.mockito.Mockito.when;
 
 class StationOperateProcessUtilsReroutePipelineTest {
+    private final Map<StationOperateProcessUtils, Map<String, Object>> dependencyOverrides = new IdentityHashMap<>();
+
+    private StationOperateProcessUtils newUtils() {
+        StationOperateProcessUtils utils = new StationOperateProcessUtils();
+        stash(utils, "stationOutboundDecisionSupport", new StationOutboundDecisionSupport());
+        wireRerouteProcessor(utils);
+        return utils;
+    }
+
+    private void wireOutboundSupport(StationOperateProcessUtils utils) {
+        StationOutboundDecisionSupport support = new StationOutboundDecisionSupport();
+        copyIfPresent(utils, support, "wrkMastService");
+        copyIfPresent(utils, support, "basDevpService");
+        copyIfPresent(utils, support, "basStationService");
+        copyIfPresent(utils, support, "navigateUtils");
+        copyIfPresent(utils, support, "redisUtil");
+        copyIfPresent(utils, support, "stationTaskLoopService");
+        copyIfPresent(utils, support, "stationMoveCoordinator");
+        StationDispatchRuntimeStateSupport runtimeStateSupport = new StationDispatchRuntimeStateSupport();
+        copyIfPresent(utils, runtimeStateSupport, "redisUtil");
+        copyIfPresent(utils, runtimeStateSupport, "basStationOptService");
+        ReflectionTestUtils.setField(support, "stationDispatchRuntimeStateSupport", runtimeStateSupport);
+        stash(utils, "stationOutboundDecisionSupport", support);
+    }
+
+    private void wireRerouteProcessor(StationOperateProcessUtils utils) {
+        StationRerouteProcessor processor = new StationRerouteProcessor();
+        copyIfPresent(utils, processor, "basDevpService");
+        copyIfPresent(utils, processor, "wrkMastService");
+        copyIfPresent(utils, processor, "commonService");
+        copyIfPresent(utils, processor, "redisUtil");
+        copyIfPresent(utils, processor, "locMastService");
+        copyIfPresent(utils, processor, "wmsOperateUtils");
+        copyIfPresent(utils, processor, "basStationOptService");
+        copyIfPresent(utils, processor, "stationMoveCoordinator");
+        Object dispatcher = readIfPresent(utils, "stationCommandDispatcher");
+        if (dispatcher == null) {
+            dispatcher = new StationCommandDispatcher(
+                    (RedisUtil) readIfPresent(utils, "redisUtil"),
+                    (StationMoveCoordinator) readIfPresent(utils, "stationMoveCoordinator")
+            );
+        }
+        ReflectionTestUtils.setField(processor, "stationCommandDispatcher", dispatcher);
+        Object outboundSupport = readIfPresent(utils, "stationOutboundDecisionSupport");
+        if (outboundSupport == null) {
+            wireOutboundSupport(utils);
+            outboundSupport = readIfPresent(utils, "stationOutboundDecisionSupport");
+        }
+        if (outboundSupport != null) {
+            ReflectionTestUtils.setField(processor, "stationOutboundDecisionSupport", outboundSupport);
+        }
+        StationDispatchRuntimeStateSupport runtimeStateSupport = new StationDispatchRuntimeStateSupport();
+        copyIfPresent(utils, runtimeStateSupport, "redisUtil");
+        copyIfPresent(utils, runtimeStateSupport, "basStationOptService");
+        ReflectionTestUtils.setField(processor, "stationDispatchRuntimeStateSupport", runtimeStateSupport);
+        ReflectionTestUtils.setField(utils, "stationRerouteProcessor", processor);
+    }
+
+    private void stash(StationOperateProcessUtils utils, String fieldName, Object value) {
+        dependencyOverrides.computeIfAbsent(utils, key -> new HashMap<>()).put(fieldName, value);
+    }
+
+    private Object readIfPresent(Object source, String fieldName) {
+        try {
+            return ReflectionTestUtils.getField(source, fieldName);
+        } catch (IllegalArgumentException ignore) {
+            if (source instanceof StationOperateProcessUtils) {
+                Map<String, Object> values = dependencyOverrides.get(source);
+                return values == null ? null : values.get(fieldName);
+            }
+            return null;
+        }
+    }
+
+    private void copyIfPresent(Object source, Object target, String fieldName) {
+        Object value = readIfPresent(source, fieldName);
+        if (value != null) {
+            try {
+                ReflectionTestUtils.setField(target, fieldName, value);
+            } catch (IllegalArgumentException ignore) {
+            }
+        }
+    }
 
     @SuppressWarnings("unchecked")
     private void stubTaskDispatchLock(StationMoveCoordinator coordinator) {
@@ -62,16 +163,14 @@
 
     @Test
     void choosesRunBlockCommandBuilderForRunBlockRerouteScene() {
-        StationOperateProcessUtils.RerouteSceneType scene =
-                StationOperateProcessUtils.RerouteSceneType.RUN_BLOCK_REROUTE;
+        RerouteSceneType scene = RerouteSceneType.RUN_BLOCK_REROUTE;
 
-        assertSame(StationOperateProcessUtils.RerouteSceneType.RUN_BLOCK_REROUTE, scene);
+        assertSame(RerouteSceneType.RUN_BLOCK_REROUTE, scene);
     }
 
     @Test
     void resolveExecutionTarget_skipsWhenTargetEqualsCurrentStation() {
-        StationOperateProcessUtils.RerouteDecision decision =
-                StationOperateProcessUtils.RerouteDecision.skip("same-station");
+        RerouteDecision decision = RerouteDecision.skip("same-station");
 
         assertTrue(decision.skip());
         assertEquals("same-station", decision.skipReason());
@@ -79,7 +178,7 @@
 
     @Test
     void buildCommandPlan_usesRunBlockCommandBuilderForRunBlockScene() {
-        StationOperateProcessUtils utils = new StationOperateProcessUtils();
+        StationOperateProcessUtils utils = newUtils();
         StationThread stationThread = mock(StationThread.class);
         StationCommand command = new StationCommand();
         command.setTaskNo(100);
@@ -87,8 +186,8 @@
         command.setTargetStaNo(20);
         when(stationThread.getRunBlockRerouteCommand(100, 10, 20, 0, 0.25d)).thenReturn(command);
 
-        StationOperateProcessUtils.RerouteContext context = StationOperateProcessUtils.RerouteContext.create(
-                StationOperateProcessUtils.RerouteSceneType.RUN_BLOCK_REROUTE,
+        RerouteContext context = RerouteContext.create(
+                RerouteSceneType.RUN_BLOCK_REROUTE,
                 buildBasDevp(1),
                 stationThread,
                 buildStationProtocol(10, 100, 10),
@@ -100,9 +199,9 @@
                 .withCancelSessionBeforeDispatch()
                 .withResetSegmentCommandsBeforeDispatch();
 
-        StationOperateProcessUtils.RerouteCommandPlan plan = utils.buildRerouteCommandPlan(
+        RerouteCommandPlan plan = utils.buildRerouteCommandPlan(
                 context,
-                StationOperateProcessUtils.RerouteDecision.proceed(20)
+                RerouteDecision.proceed(20)
         );
 
         verify(stationThread).getRunBlockRerouteCommand(100, 10, 20, 0, 0.25d);
@@ -111,7 +210,7 @@
 
     @Test
     void executePlan_skipsWhenCurrentTaskStillExistsInBuffer() {
-        StationOperateProcessUtils utils = new StationOperateProcessUtils();
+        StationOperateProcessUtils utils = newUtils();
         StationCommand command = new StationCommand();
         command.setTaskNo(100);
         command.setStationId(10);
@@ -120,8 +219,8 @@
         StationTaskBufferItem bufferItem = new StationTaskBufferItem();
         bufferItem.setTaskNo(100);
 
-        StationOperateProcessUtils.RerouteContext context = StationOperateProcessUtils.RerouteContext.create(
-                StationOperateProcessUtils.RerouteSceneType.OUT_ORDER,
+        RerouteContext context = RerouteContext.create(
+                RerouteSceneType.OUT_ORDER,
                 buildBasDevp(1),
                 mock(StationThread.class),
                 buildStationProtocol(10, 100, 10, Collections.singletonList(bufferItem)),
@@ -131,11 +230,11 @@
                 "checkStationOutOrder"
         );
 
-        StationOperateProcessUtils.RerouteExecutionResult result = utils.executeReroutePlan(
+        RerouteExecutionResult result = utils.executeReroutePlan(
                 context,
-                StationOperateProcessUtils.RerouteCommandPlan.dispatch(
+                RerouteCommandPlan.dispatch(
                         command,
-                        StationOperateProcessUtils.RerouteDecision.proceed(20),
+                        RerouteDecision.proceed(20),
                         "checkStationOutOrder"
                 )
         );
@@ -146,11 +245,11 @@
 
     @Test
     void outOrderAndWatchCircle_shareDecisionFlow() {
-        StationOperateProcessUtils utils = new StationOperateProcessUtils();
+        StationOperateProcessUtils utils = newUtils();
         WrkMast wrkMast = buildWrkMast(100, 20);
 
-        StationOperateProcessUtils.RerouteContext outOrderContext = StationOperateProcessUtils.RerouteContext.create(
-                StationOperateProcessUtils.RerouteSceneType.OUT_ORDER,
+        RerouteContext outOrderContext = RerouteContext.create(
+                RerouteSceneType.OUT_ORDER,
                 buildBasDevp(1),
                 mock(StationThread.class),
                 buildStationProtocol(10, 100, 10),
@@ -159,8 +258,8 @@
                 0.0d,
                 "checkStationOutOrder"
         );
-        StationOperateProcessUtils.RerouteContext watchCircleContext = StationOperateProcessUtils.RerouteContext.create(
-                StationOperateProcessUtils.RerouteSceneType.WATCH_CIRCLE,
+        RerouteContext watchCircleContext = RerouteContext.create(
+                RerouteSceneType.WATCH_CIRCLE,
                 buildBasDevp(1),
                 mock(StationThread.class),
                 buildStationProtocol(10, 100, 10),
@@ -170,8 +269,8 @@
                 "watchCircleStation"
         );
 
-        StationOperateProcessUtils.RerouteDecision outOrderDecision = utils.resolveSharedRerouteDecision(outOrderContext);
-        StationOperateProcessUtils.RerouteDecision watchCircleDecision = utils.resolveSharedRerouteDecision(watchCircleContext);
+        RerouteDecision outOrderDecision = utils.resolveSharedRerouteDecision(outOrderContext);
+        RerouteDecision watchCircleDecision = utils.resolveSharedRerouteDecision(watchCircleContext);
 
         assertEquals(20, outOrderDecision.targetStationId());
         assertEquals(20, watchCircleDecision.targetStationId());
@@ -179,7 +278,7 @@
 
     @Test
     void runBlockReroute_keepsDirectReassignAndNormalRerouteSeparate() {
-        StationOperateProcessUtils utils = new StationOperateProcessUtils();
+        StationOperateProcessUtils utils = newUtils();
         WrkMast inboundWrkMast = buildWrkMast(100, 20);
         inboundWrkMast.setIoType(WrkIoType.IN.id);
 
@@ -189,12 +288,13 @@
 
     @Test
     void idleRecover_skipsWhenLastDispatchIsTooRecent() {
-        StationOperateProcessUtils utils = new StationOperateProcessUtils();
+        StationOperateProcessUtils utils = newUtils();
         StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class);
         RedisUtil redisUtil = mock(RedisUtil.class);
         stubTaskDispatchLock(coordinator);
-        ReflectionTestUtils.setField(utils, "stationMoveCoordinator", coordinator);
-        ReflectionTestUtils.setField(utils, "redisUtil", redisUtil);
+        stash(utils, "stationMoveCoordinator", coordinator);
+        stash(utils, "redisUtil", redisUtil);
+        wireRerouteProcessor(utils);
 
         StationMoveSession session = new StationMoveSession();
         session.setStatus(StationMoveSession.STATUS_RUNNING);
@@ -208,8 +308,8 @@
         command.setStationId(10);
         command.setTargetStaNo(20);
 
-        StationOperateProcessUtils.RerouteContext context = StationOperateProcessUtils.RerouteContext.create(
-                StationOperateProcessUtils.RerouteSceneType.IDLE_RECOVER,
+        RerouteContext context = RerouteContext.create(
+                RerouteSceneType.IDLE_RECOVER,
                 buildBasDevp(1),
                 mock(StationThread.class),
                 buildStationProtocol(10, 100, 10),
@@ -219,11 +319,11 @@
                 "checkStationIdleRecover"
         ).withRecentDispatchGuard();
 
-        StationOperateProcessUtils.RerouteExecutionResult result = utils.executeReroutePlan(
+        RerouteExecutionResult result = utils.executeReroutePlan(
                 context,
-                StationOperateProcessUtils.RerouteCommandPlan.dispatch(
+                RerouteCommandPlan.dispatch(
                         command,
-                        StationOperateProcessUtils.RerouteDecision.proceed(20),
+                        RerouteDecision.proceed(20),
                         "checkStationIdleRecover"
                 )
         );
@@ -235,12 +335,13 @@
 
     @Test
     void idleRecover_skipsWhenCurrentStationIsStillInsideRecentlyIssuedActiveRoute() {
-        StationOperateProcessUtils utils = new StationOperateProcessUtils();
+        StationOperateProcessUtils utils = newUtils();
         StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class);
         RedisUtil redisUtil = mock(RedisUtil.class);
         stubTaskDispatchLock(coordinator);
-        ReflectionTestUtils.setField(utils, "stationMoveCoordinator", coordinator);
-        ReflectionTestUtils.setField(utils, "redisUtil", redisUtil);
+        stash(utils, "stationMoveCoordinator", coordinator);
+        stash(utils, "redisUtil", redisUtil);
+        wireRerouteProcessor(utils);
 
         StationMoveSession session = new StationMoveSession();
         session.setStatus(StationMoveSession.STATUS_RUNNING);
@@ -255,8 +356,8 @@
         command.setStationId(121);
         command.setTargetStaNo(124);
 
-        StationOperateProcessUtils.RerouteContext context = StationOperateProcessUtils.RerouteContext.create(
-                StationOperateProcessUtils.RerouteSceneType.IDLE_RECOVER,
+        RerouteContext context = RerouteContext.create(
+                RerouteSceneType.IDLE_RECOVER,
                 buildBasDevp(1),
                 mock(StationThread.class),
                 buildStationProtocol(121, 10510, 121),
@@ -266,11 +367,11 @@
                 "checkStationIdleRecover"
         ).withRecentDispatchGuard();
 
-        StationOperateProcessUtils.RerouteExecutionResult result = utils.executeReroutePlan(
+        RerouteExecutionResult result = utils.executeReroutePlan(
                 context,
-                StationOperateProcessUtils.RerouteCommandPlan.dispatch(
+                RerouteCommandPlan.dispatch(
                         command,
-                        StationOperateProcessUtils.RerouteDecision.proceed(124),
+                        RerouteDecision.proceed(124),
                         "checkStationIdleRecover"
                 )
         );
@@ -282,14 +383,15 @@
 
     @Test
     void idleRecover_skipsWhenStationCommandLogShowsRecentIssuedMove() {
-        StationOperateProcessUtils utils = new StationOperateProcessUtils();
+        StationOperateProcessUtils utils = newUtils();
         StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class);
         BasStationOptService basStationOptService = mock(BasStationOptService.class);
         RedisUtil redisUtil = mock(RedisUtil.class);
         stubTaskDispatchLock(coordinator);
-        ReflectionTestUtils.setField(utils, "stationMoveCoordinator", coordinator);
-        ReflectionTestUtils.setField(utils, "basStationOptService", basStationOptService);
-        ReflectionTestUtils.setField(utils, "redisUtil", redisUtil);
+        stash(utils, "stationMoveCoordinator", coordinator);
+        stash(utils, "basStationOptService", basStationOptService);
+        stash(utils, "redisUtil", redisUtil);
+        wireRerouteProcessor(utils);
 
         StationMoveSession session = new StationMoveSession();
         session.setStatus(StationMoveSession.STATUS_RUNNING);
@@ -318,7 +420,7 @@
 
     @Test
     void checkStationOutOrder_skipsWhenActiveSessionAlreadyOwnsCurrentStation() {
-        StationOperateProcessUtils utils = new StationOperateProcessUtils();
+        StationOperateProcessUtils utils = newUtils();
         BasDevpService basDevpService = mock(BasDevpService.class);
         WrkMastService wrkMastService = mock(WrkMastService.class);
         StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class);
@@ -326,10 +428,12 @@
         StationThread stationThread = mock(StationThread.class);
         stubTaskDispatchLock(coordinator);
 
-        ReflectionTestUtils.setField(utils, "basDevpService", basDevpService);
+        stash(utils, "basDevpService", basDevpService);
         ReflectionTestUtils.setField(utils, "wrkMastService", wrkMastService);
-        ReflectionTestUtils.setField(utils, "stationMoveCoordinator", coordinator);
-        ReflectionTestUtils.setField(utils, "redisUtil", redisUtil);
+        stash(utils, "stationMoveCoordinator", coordinator);
+        stash(utils, "redisUtil", redisUtil);
+        wireOutboundSupport(utils);
+        wireRerouteProcessor(utils);
 
         BasDevp basDevp = buildBasDevp(1);
         basDevp.setIsOutOrderList("[{\"deviceNo\":1,\"stationId\":145}]");
@@ -380,7 +484,7 @@
 
     @Test
     void checkStationOutOrder_restartsWhenBlockedSessionExistsButStationNoLongerRunBlock() {
-        StationOperateProcessUtils utils = new StationOperateProcessUtils();
+        StationOperateProcessUtils utils = newUtils();
         BasDevpService basDevpService = mock(BasDevpService.class);
         WrkMastService wrkMastService = mock(WrkMastService.class);
         StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class);
@@ -388,10 +492,12 @@
         StationThread stationThread = mock(StationThread.class);
         stubTaskDispatchLock(coordinator);
 
-        ReflectionTestUtils.setField(utils, "basDevpService", basDevpService);
+        stash(utils, "basDevpService", basDevpService);
         ReflectionTestUtils.setField(utils, "wrkMastService", wrkMastService);
-        ReflectionTestUtils.setField(utils, "stationMoveCoordinator", coordinator);
-        ReflectionTestUtils.setField(utils, "redisUtil", redisUtil);
+        stash(utils, "stationMoveCoordinator", coordinator);
+        stash(utils, "redisUtil", redisUtil);
+        wireOutboundSupport(utils);
+        wireRerouteProcessor(utils);
 
         BasDevp basDevp = buildBasDevp(1);
         basDevp.setIsOutOrderList("[{\"deviceNo\":1,\"stationId\":145}]");
@@ -443,15 +549,17 @@
 
     @Test
     void checkStationOutOrder_skipsRunBlockStationBeforePlanning() {
-        StationOperateProcessUtils utils = new StationOperateProcessUtils();
+        StationOperateProcessUtils utils = newUtils();
         BasDevpService basDevpService = mock(BasDevpService.class);
         WrkMastService wrkMastService = mock(WrkMastService.class);
         RedisUtil redisUtil = mock(RedisUtil.class);
         StationThread stationThread = mock(StationThread.class);
 
-        ReflectionTestUtils.setField(utils, "basDevpService", basDevpService);
+        stash(utils, "basDevpService", basDevpService);
         ReflectionTestUtils.setField(utils, "wrkMastService", wrkMastService);
-        ReflectionTestUtils.setField(utils, "redisUtil", redisUtil);
+        stash(utils, "redisUtil", redisUtil);
+        wireOutboundSupport(utils);
+        wireRerouteProcessor(utils);
 
         BasDevp basDevp = buildBasDevp(1);
         basDevp.setIsOutOrderList("[{\"deviceNo\":1,\"stationId\":145}]");
@@ -493,12 +601,13 @@
 
     @Test
     void executePlan_runBlockReroute_reissuesWhenBlockedSessionMatchesCandidatePath() {
-        StationOperateProcessUtils utils = new StationOperateProcessUtils();
+        StationOperateProcessUtils utils = newUtils();
         StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class);
         RedisUtil redisUtil = mock(RedisUtil.class);
         stubTaskDispatchLock(coordinator);
-        ReflectionTestUtils.setField(utils, "stationMoveCoordinator", coordinator);
-        ReflectionTestUtils.setField(utils, "redisUtil", redisUtil);
+        stash(utils, "stationMoveCoordinator", coordinator);
+        stash(utils, "redisUtil", redisUtil);
+        wireRerouteProcessor(utils);
 
         StationCommand command = new StationCommand();
         command.setTaskNo(100);
@@ -516,8 +625,8 @@
         when(coordinator.loadSession(100)).thenReturn(session);
         when(coordinator.buildPathSignature(command)).thenReturn("same-path");
 
-        StationOperateProcessUtils.RerouteContext context = StationOperateProcessUtils.RerouteContext.create(
-                StationOperateProcessUtils.RerouteSceneType.RUN_BLOCK_REROUTE,
+        RerouteContext context = RerouteContext.create(
+                RerouteSceneType.RUN_BLOCK_REROUTE,
                 buildBasDevp(1),
                 mock(StationThread.class),
                 buildStationProtocol(145, 100, 145),
@@ -530,11 +639,11 @@
 
         MessageQueue.init(SlaveType.Devp, 1);
         try {
-            StationOperateProcessUtils.RerouteExecutionResult result = utils.executeReroutePlan(
+            RerouteExecutionResult result = utils.executeReroutePlan(
                     context,
-                    StationOperateProcessUtils.RerouteCommandPlan.dispatch(
+                    RerouteCommandPlan.dispatch(
                             command,
-                            StationOperateProcessUtils.RerouteDecision.proceed(111),
+                            RerouteDecision.proceed(111),
                             "checkStationRunBlock_reroute"
                     )
             );
@@ -551,12 +660,13 @@
 
     @Test
     void executePlan_runBlockReroute_ignoresCurrentTaskBufferAfterReset() {
-        StationOperateProcessUtils utils = new StationOperateProcessUtils();
+        StationOperateProcessUtils utils = newUtils();
         StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class);
         RedisUtil redisUtil = mock(RedisUtil.class);
         stubTaskDispatchLock(coordinator);
-        ReflectionTestUtils.setField(utils, "stationMoveCoordinator", coordinator);
-        ReflectionTestUtils.setField(utils, "redisUtil", redisUtil);
+        stash(utils, "stationMoveCoordinator", coordinator);
+        stash(utils, "redisUtil", redisUtil);
+        wireRerouteProcessor(utils);
 
         StationCommand command = new StationCommand();
         command.setTaskNo(10388);
@@ -570,8 +680,8 @@
 
         when(redisUtil.get(anyString())).thenReturn(null);
 
-        StationOperateProcessUtils.RerouteContext context = StationOperateProcessUtils.RerouteContext.create(
-                StationOperateProcessUtils.RerouteSceneType.RUN_BLOCK_REROUTE,
+        RerouteContext context = RerouteContext.create(
+                RerouteSceneType.RUN_BLOCK_REROUTE,
                 buildBasDevp(1),
                 mock(StationThread.class),
                 buildStationProtocol(186, 10388, 189, Collections.singletonList(bufferItem)),
@@ -584,11 +694,11 @@
 
         MessageQueue.init(SlaveType.Devp, 1);
         try {
-            StationOperateProcessUtils.RerouteExecutionResult result = utils.executeReroutePlan(
+            RerouteExecutionResult result = utils.executeReroutePlan(
                     context,
-                    StationOperateProcessUtils.RerouteCommandPlan.dispatch(
+                    RerouteCommandPlan.dispatch(
                             command,
-                            StationOperateProcessUtils.RerouteDecision.proceed(124),
+                            RerouteDecision.proceed(124),
                             "checkStationRunBlock_reroute"
                     )
             );
@@ -602,12 +712,13 @@
 
     @Test
     void executePlan_runBlockReroute_bypassesSuppressDispatchAfterReset() {
-        StationOperateProcessUtils utils = new StationOperateProcessUtils();
+        StationOperateProcessUtils utils = newUtils();
         StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class);
         RedisUtil redisUtil = mock(RedisUtil.class);
         stubTaskDispatchLock(coordinator);
-        ReflectionTestUtils.setField(utils, "stationMoveCoordinator", coordinator);
-        ReflectionTestUtils.setField(utils, "redisUtil", redisUtil);
+        stash(utils, "stationMoveCoordinator", coordinator);
+        stash(utils, "redisUtil", redisUtil);
+        wireRerouteProcessor(utils);
 
         StationCommand command = new StationCommand();
         command.setTaskNo(10388);
@@ -619,8 +730,8 @@
         when(redisUtil.get(anyString())).thenReturn(null);
         when(coordinator.shouldSuppressDispatch(10388, 186, command)).thenReturn(true);
 
-        StationOperateProcessUtils.RerouteContext context = StationOperateProcessUtils.RerouteContext.create(
-                StationOperateProcessUtils.RerouteSceneType.RUN_BLOCK_REROUTE,
+        RerouteContext context = RerouteContext.create(
+                RerouteSceneType.RUN_BLOCK_REROUTE,
                 buildBasDevp(1),
                 mock(StationThread.class),
                 buildStationProtocol(186, 10388, 186),
@@ -634,11 +745,11 @@
 
         MessageQueue.init(SlaveType.Devp, 1);
         try {
-            StationOperateProcessUtils.RerouteExecutionResult result = utils.executeReroutePlan(
+            RerouteExecutionResult result = utils.executeReroutePlan(
                     context,
-                    StationOperateProcessUtils.RerouteCommandPlan.dispatch(
+                    RerouteCommandPlan.dispatch(
                             command,
-                            StationOperateProcessUtils.RerouteDecision.proceed(124),
+                            RerouteDecision.proceed(124),
                             "checkStationRunBlock_reroute"
                     )
             );
@@ -652,22 +763,28 @@
 
     @Test
     void stationInExecute_recordsDispatchSessionAfterIssuingMoveCommand() {
-        StationOperateProcessUtils utils = new StationOperateProcessUtils();
+        StationOperateProcessUtils utils = newUtils();
         BasDevpService basDevpService = mock(BasDevpService.class);
         WrkMastService wrkMastService = mock(WrkMastService.class);
         CommonService commonService = mock(CommonService.class);
         RedisUtil redisUtil = mock(RedisUtil.class);
         WrkAnalysisService wrkAnalysisService = mock(WrkAnalysisService.class);
         StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class);
+        StationCommandDispatcher dispatcher = mock(StationCommandDispatcher.class);
+        StationDispatchLoadSupport loadSupport = mock(StationDispatchLoadSupport.class);
         StationThread stationThread = mock(StationThread.class);
         stubTaskDispatchLock(coordinator);
 
-        ReflectionTestUtils.setField(utils, "basDevpService", basDevpService);
-        ReflectionTestUtils.setField(utils, "wrkMastService", wrkMastService);
-        ReflectionTestUtils.setField(utils, "commonService", commonService);
-        ReflectionTestUtils.setField(utils, "redisUtil", redisUtil);
-        ReflectionTestUtils.setField(utils, "wrkAnalysisService", wrkAnalysisService);
-        ReflectionTestUtils.setField(utils, "stationMoveCoordinator", coordinator);
+        StationRegularDispatchProcessor processor = new StationRegularDispatchProcessor();
+        ReflectionTestUtils.setField(processor, "basDevpService", basDevpService);
+        ReflectionTestUtils.setField(processor, "wrkMastService", wrkMastService);
+        ReflectionTestUtils.setField(processor, "commonService", commonService);
+        ReflectionTestUtils.setField(processor, "redisUtil", redisUtil);
+        ReflectionTestUtils.setField(processor, "wrkAnalysisService", wrkAnalysisService);
+        ReflectionTestUtils.setField(processor, "stationMoveCoordinator", coordinator);
+        ReflectionTestUtils.setField(processor, "stationCommandDispatcher", dispatcher);
+        ReflectionTestUtils.setField(processor, "stationDispatchLoadSupport", loadSupport);
+        ReflectionTestUtils.setField(utils, "stationRegularDispatchProcessor", processor);
 
         BasDevp basDevp = buildBasDevp(1);
         basDevp.setBarcodeStationList("[{\"deviceNo\":1,\"stationId\":101}]");
@@ -700,6 +817,18 @@
         when(stationThread.getCommand(com.zy.core.enums.StationCommandType.MOVE, 500670, 101, 102, 0))
                 .thenReturn(command);
         when(redisUtil.get(anyString())).thenReturn(null);
+        when(dispatcher.dispatch(1, command, "station-operate-process", "stationInExecute"))
+                .thenReturn(StationCommandDispatchResult.accepted("accepted", 1, "station-operate-process", "stationInExecute"));
+
+        DispatchLimitConfig baseConfig = new DispatchLimitConfig();
+        LoadGuardState loadGuardState = new LoadGuardState();
+        LoopHitResult noHit = LoopHitResult.noHit();
+        when(loadSupport.getDispatchLimitConfig(null, null)).thenReturn(baseConfig);
+        when(loadSupport.countCurrentStationTask()).thenReturn(0);
+        when(loadSupport.buildLoadGuardState(baseConfig)).thenReturn(loadGuardState);
+        when(loadSupport.getDispatchLimitConfig(101, 102)).thenReturn(baseConfig);
+        when(loadSupport.findPathLoopHit(baseConfig, 101, 102, loadGuardState)).thenReturn(noHit);
+        when(loadSupport.isDispatchBlocked(baseConfig, 0, loadGuardState, false)).thenReturn(false);
 
         MessageQueue.init(SlaveType.Devp, 1);
         SlaveConnection.put(SlaveType.Devp, 1, stationThread);
@@ -714,62 +843,19 @@
     }
 
     @Test
-    void dualCrnStationOutExecute_recordsDispatchSessionAfterIssuingMoveCommand() {
-        StationOperateProcessUtils utils = new StationOperateProcessUtils();
-        WrkMastService wrkMastService = mock(WrkMastService.class);
-        RedisUtil redisUtil = mock(RedisUtil.class);
-        NotifyUtils notifyUtils = mock(NotifyUtils.class);
-        StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class);
-        StationThread stationThread = mock(StationThread.class);
-        stubTaskDispatchLock(coordinator);
+    void dualCrnStationOutExecute_delegatesToOutboundProcessor() {
+        StationOperateProcessUtils utils = newUtils();
+        StationOutboundDispatchProcessor processor = mock(StationOutboundDispatchProcessor.class);
+        ReflectionTestUtils.setField(utils, "stationOutboundDispatchProcessor", processor);
 
-        ReflectionTestUtils.setField(utils, "wrkMastService", wrkMastService);
-        ReflectionTestUtils.setField(utils, "redisUtil", redisUtil);
-        ReflectionTestUtils.setField(utils, "notifyUtils", notifyUtils);
-        ReflectionTestUtils.setField(utils, "stationMoveCoordinator", coordinator);
+        utils.dualCrnStationOutExecute();
 
-        WrkMast wrkMast = buildWrkMast(10335, 145);
-        wrkMast.setDualCrnNo(1);
-        wrkMast.setWrkSts(WrkStsType.OUTBOUND_RUN_COMPLETE.sts);
-        when(wrkMastService.list(any(com.baomidou.mybatisplus.core.conditions.Wrapper.class)))
-                .thenReturn(Collections.singletonList(wrkMast));
-        when(wrkMastService.updateById(wrkMast)).thenReturn(true);
-
-        when(redisUtil.get(anyString())).thenAnswer(invocation -> {
-            String key = invocation.getArgument(0);
-            if (Objects.equals(key, RedisKeyType.DUAL_CRN_OUT_TASK_STATION_INFO.key + 10335)) {
-                return "{\"deviceNo\":1,\"stationId\":198}";
-            }
-            return null;
-        });
-
-        StationProtocol stationProtocol = buildStationProtocol(198, 0, 198);
-        stationProtocol.setAutoing(true);
-        stationProtocol.setLoading(true);
-        when(stationThread.getStatusMap()).thenReturn(Map.of(198, stationProtocol));
-
-        StationCommand command = new StationCommand();
-        command.setTaskNo(10335);
-        command.setStationId(198);
-        command.setTargetStaNo(145);
-        when(stationThread.getCommand(eq(com.zy.core.enums.StationCommandType.MOVE), eq(10335), eq(198), eq(145), eq(0), eq(0.0d)))
-                .thenReturn(command);
-
-        MessageQueue.init(SlaveType.Devp, 1);
-        SlaveConnection.put(SlaveType.Devp, 1, stationThread);
-        try {
-            utils.dualCrnStationOutExecute();
-
-            verify(coordinator, times(1)).recordDispatch(eq(10335), eq(198), eq("dualCrnStationOutExecute"), same(command), eq(false));
-        } finally {
-            MessageQueue.clear(SlaveType.Devp, 1);
-            SlaveConnection.remove(SlaveType.Devp, 1);
-        }
+        verify(processor, times(1)).dualCrnStationOutExecute();
     }
 
     @Test
     void stationOutExecuteFinish_attemptsClearPathBeforeCompletingTask() {
-        StationOperateProcessUtils utils = new StationOperateProcessUtils();
+        StationOperateProcessUtils utils = newUtils();
         WrkMastService wrkMastService = mock(WrkMastService.class);
         BasStationService basStationService = mock(BasStationService.class);
         WrkAnalysisService wrkAnalysisService = mock(WrkAnalysisService.class);
@@ -778,12 +864,14 @@
         StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class);
         StationThread stationThread = mock(StationThread.class);
 
-        ReflectionTestUtils.setField(utils, "wrkMastService", wrkMastService);
-        ReflectionTestUtils.setField(utils, "basStationService", basStationService);
-        ReflectionTestUtils.setField(utils, "wrkAnalysisService", wrkAnalysisService);
-        ReflectionTestUtils.setField(utils, "notifyUtils", notifyUtils);
-        ReflectionTestUtils.setField(utils, "redisUtil", redisUtil);
-        ReflectionTestUtils.setField(utils, "stationMoveCoordinator", coordinator);
+        StationRegularDispatchProcessor processor = new StationRegularDispatchProcessor();
+        ReflectionTestUtils.setField(processor, "wrkMastService", wrkMastService);
+        ReflectionTestUtils.setField(processor, "basStationService", basStationService);
+        ReflectionTestUtils.setField(processor, "wrkAnalysisService", wrkAnalysisService);
+        ReflectionTestUtils.setField(processor, "notifyUtils", notifyUtils);
+        ReflectionTestUtils.setField(processor, "redisUtil", redisUtil);
+        ReflectionTestUtils.setField(processor, "stationMoveCoordinator", coordinator);
+        ReflectionTestUtils.setField(utils, "stationRegularDispatchProcessor", processor);
 
         WrkMast wrkMast = buildWrkMast(10335, 145);
         wrkMast.setWrkSts(WrkStsType.STATION_RUN.sts);
@@ -816,7 +904,7 @@
 
     @Test
     void watchCircleStation_usesSessionArrivalStateWhenLegacyCommandMissing() {
-        StationOperateProcessUtils utils = new StationOperateProcessUtils();
+        StationOperateProcessUtils utils = newUtils();
         BasDevpService basDevpService = mock(BasDevpService.class);
         WrkMastService wrkMastService = mock(WrkMastService.class);
         StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class);
@@ -824,10 +912,12 @@
         StationThread stationThread = mock(StationThread.class);
         stubTaskDispatchLock(coordinator);
 
-        ReflectionTestUtils.setField(utils, "basDevpService", basDevpService);
+        stash(utils, "basDevpService", basDevpService);
         ReflectionTestUtils.setField(utils, "wrkMastService", wrkMastService);
-        ReflectionTestUtils.setField(utils, "stationMoveCoordinator", coordinator);
-        ReflectionTestUtils.setField(utils, "redisUtil", redisUtil);
+        stash(utils, "stationMoveCoordinator", coordinator);
+        stash(utils, "redisUtil", redisUtil);
+        wireOutboundSupport(utils);
+        wireRerouteProcessor(utils);
 
         BasDevp basDevp = buildBasDevp(1);
         when(basDevpService.list(any(com.baomidou.mybatisplus.core.conditions.Wrapper.class)))
diff --git a/src/test/java/com/zy/core/utils/StationRerouteProcessorTest.java b/src/test/java/com/zy/core/utils/StationRerouteProcessorTest.java
new file mode 100644
index 0000000..744fcca
--- /dev/null
+++ b/src/test/java/com/zy/core/utils/StationRerouteProcessorTest.java
@@ -0,0 +1,133 @@
+package com.zy.core.utils;
+
+import com.zy.asrs.entity.BasDevp;
+import com.zy.asrs.entity.WrkMast;
+import com.zy.core.model.command.StationCommand;
+import com.zy.core.model.protocol.StationProtocol;
+import com.zy.core.model.protocol.StationTaskBufferItem;
+import com.zy.core.thread.StationThread;
+import com.zy.core.utils.station.StationRerouteProcessor;
+import com.zy.core.utils.station.model.RerouteCommandPlan;
+import com.zy.core.utils.station.model.RerouteContext;
+import com.zy.core.utils.station.model.RerouteDecision;
+import com.zy.core.utils.station.model.RerouteExecutionResult;
+import com.zy.core.utils.station.model.RerouteSceneType;
+import org.junit.jupiter.api.Test;
+
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+class StationRerouteProcessorTest {
+
+    @Test
+    void rerouteProcessor_noLongerKeepsDispatcherFallbackHelper() {
+        assertThrows(NoSuchMethodException.class,
+                () -> StationRerouteProcessor.class.getDeclaredMethod("getStationCommandDispatcher"));
+    }
+
+    @Test
+    void buildCommandPlan_usesRunBlockCommandBuilderForRunBlockScene() {
+        StationRerouteProcessor processor = new StationRerouteProcessor();
+        StationThread stationThread = mock(StationThread.class);
+        StationCommand command = new StationCommand();
+        command.setTaskNo(100);
+        command.setStationId(10);
+        command.setTargetStaNo(20);
+        when(stationThread.getRunBlockRerouteCommand(100, 10, 20, 0, 0.25d)).thenReturn(command);
+
+        RerouteContext context = RerouteContext.create(
+                RerouteSceneType.RUN_BLOCK_REROUTE,
+                buildBasDevp(1),
+                stationThread,
+                buildStationProtocol(10, 100, 10),
+                buildWrkMast(100, 99),
+                Collections.emptyList(),
+                0.25d,
+                "checkStationRunBlock_reroute"
+        ).withRunBlockCommand()
+                .withCancelSessionBeforeDispatch()
+                .withResetSegmentCommandsBeforeDispatch();
+
+        RerouteCommandPlan plan = processor.buildRerouteCommandPlan(
+                context,
+                RerouteDecision.proceed(20)
+        );
+
+        verify(stationThread).getRunBlockRerouteCommand(100, 10, 20, 0, 0.25d);
+        assertSame(command, plan.command());
+    }
+
+    @Test
+    void executePlan_skipsWhenCurrentTaskStillExistsInBuffer() {
+        StationRerouteProcessor processor = new StationRerouteProcessor();
+        StationCommand command = new StationCommand();
+        command.setTaskNo(100);
+        command.setStationId(10);
+        command.setTargetStaNo(20);
+
+        StationTaskBufferItem bufferItem = new StationTaskBufferItem();
+        bufferItem.setTaskNo(100);
+
+        RerouteContext context = RerouteContext.create(
+                RerouteSceneType.OUT_ORDER,
+                buildBasDevp(1),
+                mock(StationThread.class),
+                buildStationProtocol(10, 100, 10, Collections.singletonList(bufferItem)),
+                buildWrkMast(100, 20),
+                List.of(10, 20),
+                0.0d,
+                "checkStationOutOrder"
+        );
+
+        RerouteExecutionResult result = processor.executeReroutePlan(
+                context,
+                RerouteCommandPlan.dispatch(
+                        command,
+                        RerouteDecision.proceed(20),
+                        "checkStationOutOrder"
+                )
+        );
+
+        assertTrue(result.skipped());
+        assertEquals("buffer-has-current-task", result.skipReason());
+    }
+
+    private static BasDevp buildBasDevp(int devpNo) {
+        BasDevp basDevp = new BasDevp();
+        basDevp.setDevpNo(devpNo);
+        return basDevp;
+    }
+
+    private static WrkMast buildWrkMast(int wrkNo, int targetStationId) {
+        WrkMast wrkMast = new WrkMast();
+        wrkMast.setWrkNo(wrkNo);
+        wrkMast.setStaNo(targetStationId);
+        return wrkMast;
+    }
+
+    private static StationProtocol buildStationProtocol(int stationId,
+                                                        int taskNo,
+                                                        int targetStationId) {
+        return buildStationProtocol(stationId, taskNo, targetStationId, Collections.emptyList());
+    }
+
+    private static StationProtocol buildStationProtocol(int stationId,
+                                                        int taskNo,
+                                                        int targetStationId,
+                                                        List<StationTaskBufferItem> taskBufferItems) {
+        StationProtocol stationProtocol = new StationProtocol();
+        stationProtocol.setStationId(stationId);
+        stationProtocol.setTaskNo(taskNo);
+        stationProtocol.setTargetStaNo(targetStationId);
+        stationProtocol.setTaskBufferItems(taskBufferItems);
+        return stationProtocol;
+    }
+}
diff --git a/src/test/java/com/zy/core/utils/station/StationDispatchRuntimeStateSupportTest.java b/src/test/java/com/zy/core/utils/station/StationDispatchRuntimeStateSupportTest.java
new file mode 100644
index 0000000..53d34ca
--- /dev/null
+++ b/src/test/java/com/zy/core/utils/station/StationDispatchRuntimeStateSupportTest.java
@@ -0,0 +1,139 @@
+package com.zy.core.utils.station;
+
+import com.alibaba.fastjson.JSON;
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.zy.asrs.entity.BasStationOpt;
+import com.zy.asrs.service.BasStationOptService;
+import com.zy.common.utils.RedisUtil;
+import com.zy.core.enums.RedisKeyType;
+import com.zy.core.model.command.StationCommand;
+import org.junit.jupiter.api.Test;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.util.Collections;
+import java.util.Date;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+class StationDispatchRuntimeStateSupportTest {
+
+    @Test
+    void idleTrack_isTimeoutUsesFirstSeenTime() {
+        StationTaskIdleTrack track = new StationTaskIdleTrack(100, 145, System.currentTimeMillis() - 11_000L);
+
+        assertTrue(track.isTimeout(10));
+    }
+
+    @Test
+    void touchIdleTrack_replacesTrackWhenStationChanges() {
+        StationDispatchRuntimeStateSupport support = new StationDispatchRuntimeStateSupport();
+        RedisUtil redisUtil = mock(RedisUtil.class);
+        ReflectionTestUtils.setField(support, "redisUtil", redisUtil);
+        when(redisUtil.get(RedisKeyType.STATION_TASK_IDLE_TRACK_.key + 100))
+                .thenReturn(JSON.toJSONString(new StationTaskIdleTrack(100, 145, System.currentTimeMillis() - 3_000L)));
+
+        StationTaskIdleTrack track = support.touchIdleTrack(100, 146);
+
+        assertEquals(146, track.getStationId());
+        verify(redisUtil).set(eq(RedisKeyType.STATION_TASK_IDLE_TRACK_.key + 100), org.mockito.ArgumentMatchers.any(String.class), eq(3600L));
+    }
+
+    @Test
+    void hasRecentIssuedMoveCommand_returnsTrueForRecentMoveLog() {
+        StationDispatchRuntimeStateSupport support = new StationDispatchRuntimeStateSupport();
+        BasStationOptService basStationOptService = mock(BasStationOptService.class);
+        ReflectionTestUtils.setField(support, "basStationOptService", basStationOptService);
+        when(basStationOptService.list(org.mockito.ArgumentMatchers.<Wrapper<BasStationOpt>>any()))
+                .thenReturn(Collections.singletonList(new BasStationOpt()));
+
+        boolean result = support.hasRecentIssuedMoveCommand(100, 145, 10_000L);
+
+        assertTrue(result);
+    }
+
+    @Test
+    void clearIssuedMoveCommandsDuringIdleStay_marksSendZeroAndAppendsMemo() {
+        StationDispatchRuntimeStateSupport support = new StationDispatchRuntimeStateSupport();
+        BasStationOptService basStationOptService = mock(BasStationOptService.class);
+        ReflectionTestUtils.setField(support, "basStationOptService", basStationOptService);
+        BasStationOpt opt = new BasStationOpt();
+        opt.setId(1L);
+        opt.setSend(1);
+        opt.setMemo("existing");
+        opt.setSendTime(new Date());
+        when(basStationOptService.list(org.mockito.ArgumentMatchers.<Wrapper<BasStationOpt>>any()))
+                .thenReturn(Collections.singletonList(opt));
+
+        int clearedCount = support.clearIssuedMoveCommandsDuringIdleStay(
+                new StationTaskIdleTrack(100, 145, System.currentTimeMillis() - 10_000L),
+                100,
+                145
+        );
+
+        assertEquals(1, clearedCount);
+        assertEquals(0, opt.getSend());
+        assertTrue(opt.getMemo().contains("existing"));
+        assertTrue(opt.getMemo().contains("idleRecoverRerouteCleared(stationId=145)"));
+        assertFalse(opt.getUpdateTime() == null);
+        verify(basStationOptService).updateBatchById(Collections.singletonList(opt));
+    }
+
+    @Test
+    void tryAcquireOutOrderDispatchLock_returnsFalseWhenKeyAlreadyExists() {
+        StationDispatchRuntimeStateSupport support = new StationDispatchRuntimeStateSupport();
+        RedisUtil redisUtil = mock(RedisUtil.class);
+        ReflectionTestUtils.setField(support, "redisUtil", redisUtil);
+        when(redisUtil.get(RedisKeyType.STATION_OUT_ORDER_DISPATCH_LIMIT_.key + "100_145")).thenReturn("lock");
+
+        boolean acquired = support.tryAcquireOutOrderDispatchLock(100, 145, 2);
+
+        assertFalse(acquired);
+    }
+
+    @Test
+    void signalSegmentReset_setsAndClearsResetKey() {
+        StationDispatchRuntimeStateSupport support = new StationDispatchRuntimeStateSupport();
+        RedisUtil redisUtil = mock(RedisUtil.class);
+        ReflectionTestUtils.setField(support, "redisUtil", redisUtil);
+
+        support.signalSegmentReset(100, 0L);
+
+        verify(redisUtil).set(RedisKeyType.DEVICE_STATION_MOVE_RESET.key + 100, "cancel", 3);
+        verify(redisUtil).del(RedisKeyType.DEVICE_STATION_MOVE_RESET.key + 100);
+    }
+
+    @Test
+    void watchCircleCommand_roundTripsThroughRedis() {
+        StationDispatchRuntimeStateSupport support = new StationDispatchRuntimeStateSupport();
+        RedisUtil redisUtil = mock(RedisUtil.class);
+        ReflectionTestUtils.setField(support, "redisUtil", redisUtil);
+        AtomicReference<Object> storedValue = new AtomicReference<>();
+        when(redisUtil.get(RedisKeyType.WATCH_CIRCLE_STATION_.key + 100))
+                .thenAnswer(invocation -> storedValue.get());
+        when(redisUtil.set(eq(RedisKeyType.WATCH_CIRCLE_STATION_.key + 100), org.mockito.ArgumentMatchers.any(String.class), eq(60L * 60 * 24)))
+                .thenAnswer(invocation -> {
+                    storedValue.set(invocation.getArgument(1));
+                    return true;
+                });
+
+        StationCommand command = new StationCommand();
+        command.setTaskNo(100);
+        command.setTargetStaNo(145);
+
+        support.saveWatchCircleCommand(100, command);
+        StationCommand loaded = support.loadWatchCircleCommand(100);
+
+        assertNotNull(loaded);
+        assertEquals(145, loaded.getTargetStaNo());
+        support.clearWatchCircleCommand(100);
+        verify(redisUtil).del(RedisKeyType.WATCH_CIRCLE_STATION_.key + 100);
+    }
+}
diff --git a/src/test/java/com/zy/core/utils/station/StationOutboundDispatchProcessorTest.java b/src/test/java/com/zy/core/utils/station/StationOutboundDispatchProcessorTest.java
new file mode 100644
index 0000000..b678efb
--- /dev/null
+++ b/src/test/java/com/zy/core/utils/station/StationOutboundDispatchProcessorTest.java
@@ -0,0 +1,202 @@
+package com.zy.core.utils.station;
+
+import com.zy.asrs.domain.enums.NotifyMsgType;
+import com.zy.asrs.entity.WrkMast;
+import com.zy.asrs.service.WrkAnalysisService;
+import com.zy.asrs.service.WrkMastService;
+import com.zy.asrs.utils.NotifyUtils;
+import com.zy.common.utils.RedisUtil;
+import com.zy.core.cache.SlaveConnection;
+import com.zy.core.dispatch.StationCommandDispatchResult;
+import com.zy.core.dispatch.StationCommandDispatcher;
+import com.zy.core.enums.RedisKeyType;
+import com.zy.core.enums.SlaveType;
+import com.zy.core.enums.WrkStsType;
+import com.zy.core.model.StationObjModel;
+import com.zy.core.model.command.StationCommand;
+import com.zy.core.model.protocol.StationProtocol;
+import com.zy.core.move.StationMoveCoordinator;
+import com.zy.core.thread.StationThread;
+import com.zy.core.utils.station.model.DispatchLimitConfig;
+import com.zy.core.utils.station.model.LoadGuardState;
+import com.zy.core.utils.station.model.LoopHitResult;
+import com.zy.core.utils.station.model.OutOrderDispatchDecision;
+import org.junit.jupiter.api.Test;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.same;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+class StationOutboundDispatchProcessorTest {
+
+    @Test
+    void crnStationOutExecute_recordsDispatchSessionAndClearsStationCacheAfterIssuingMoveCommand() {
+        StationOutboundDispatchProcessor processor = new StationOutboundDispatchProcessor();
+        WrkMastService wrkMastService = mock(WrkMastService.class);
+        WrkAnalysisService wrkAnalysisService = mock(WrkAnalysisService.class);
+        RedisUtil redisUtil = mock(RedisUtil.class);
+        StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class);
+        StationCommandDispatcher dispatcher = mock(StationCommandDispatcher.class);
+        StationDispatchLoadSupport loadSupport = mock(StationDispatchLoadSupport.class);
+        StationOutboundDecisionSupport decisionSupport = mock(StationOutboundDecisionSupport.class);
+        StationThread stationThread = mock(StationThread.class);
+
+        ReflectionTestUtils.setField(processor, "wrkMastService", wrkMastService);
+        ReflectionTestUtils.setField(processor, "wrkAnalysisService", wrkAnalysisService);
+        ReflectionTestUtils.setField(processor, "redisUtil", redisUtil);
+        ReflectionTestUtils.setField(processor, "stationMoveCoordinator", coordinator);
+        ReflectionTestUtils.setField(processor, "stationCommandDispatcher", dispatcher);
+        ReflectionTestUtils.setField(processor, "stationDispatchLoadSupport", loadSupport);
+        ReflectionTestUtils.setField(processor, "stationOutboundDecisionSupport", decisionSupport);
+
+        WrkMast wrkMast = buildWrkMast(10001, 145);
+        wrkMast.setWrkSts(WrkStsType.OUTBOUND_RUN_COMPLETE.sts);
+        wrkMast.setCrnNo(4);
+        when(wrkMastService.list(any(com.baomidou.mybatisplus.core.conditions.Wrapper.class)))
+                .thenReturn(Collections.singletonList(wrkMast));
+        when(wrkMastService.updateById(wrkMast)).thenReturn(true);
+
+        StationObjModel stationObjModel = new StationObjModel();
+        stationObjModel.setDeviceNo(1);
+        stationObjModel.setStationId(101);
+        when(redisUtil.get(RedisKeyType.CRN_OUT_TASK_COMPLETE_STATION_INFO.key + 10001))
+                .thenReturn(com.alibaba.fastjson.JSON.toJSONString(stationObjModel));
+        when(redisUtil.get(RedisKeyType.STATION_OUT_EXECUTE_LIMIT.key + 101)).thenReturn(null);
+
+        StationProtocol stationProtocol = buildStationProtocol(101, 0, 101);
+        stationProtocol.setAutoing(true);
+        stationProtocol.setLoading(true);
+        when(stationThread.getStatusMap()).thenReturn(Map.of(101, stationProtocol));
+
+        DispatchLimitConfig limitConfig = new DispatchLimitConfig();
+        LoadGuardState loadGuardState = new LoadGuardState();
+        LoopHitResult noHit = LoopHitResult.noHit();
+        OutOrderDispatchDecision decision = OutOrderDispatchDecision.direct(145);
+        StationCommand command = buildCommand(10001, 101, 145);
+
+        when(loadSupport.getDispatchLimitConfig(null, null)).thenReturn(limitConfig);
+        when(loadSupport.countCurrentStationTask()).thenReturn(0);
+        when(loadSupport.buildLoadGuardState(limitConfig)).thenReturn(loadGuardState);
+        when(decisionSupport.getAllOutOrderList()).thenReturn(List.of(101, 121, 145));
+        when(decisionSupport.resolveOutboundPathLenFactor(wrkMast)).thenReturn(0.25d);
+        when(decisionSupport.resolveOutboundDispatchDecision(101, wrkMast, List.of(101, 121, 145), 0.25d)).thenReturn(decision);
+        when(loadSupport.getDispatchLimitConfig(101, 145)).thenReturn(limitConfig);
+        when(loadSupport.findPathLoopHit(limitConfig, 101, 145, loadGuardState, wrkMast, 0.25d)).thenReturn(noHit);
+        when(loadSupport.isDispatchBlocked(limitConfig, 0, loadGuardState, false)).thenReturn(false);
+        when(decisionSupport.buildOutboundMoveCommand(stationThread, wrkMast, 101, 145, 0.25d)).thenReturn(command);
+        when(dispatcher.dispatch(1, command, "station-operate-process", "crnStationOutExecute"))
+                .thenReturn(StationCommandDispatchResult.accepted("accepted", 1, "station-operate-process", "crnStationOutExecute"));
+
+        SlaveConnection.put(SlaveType.Devp, 1, stationThread);
+        try {
+            processor.crnStationOutExecute();
+
+            verify(wrkAnalysisService, times(1)).markOutboundStationStart(eq(wrkMast), any(Date.class));
+            verify(coordinator, times(1)).recordDispatch(eq(10001), eq(101), eq("crnStationOutExecute"), same(command), eq(false));
+            verify(redisUtil, times(1)).set(eq(RedisKeyType.STATION_OUT_EXECUTE_LIMIT.key + 101), eq("lock"), eq(5L));
+            verify(redisUtil, times(1)).del(RedisKeyType.CRN_OUT_TASK_COMPLETE_STATION_INFO.key + 10001);
+            verify(loadSupport, times(1)).saveLoopLoadReserve(10001, noHit);
+            assertEquals(WrkStsType.STATION_RUN.sts, wrkMast.getWrkSts());
+        } finally {
+            SlaveConnection.remove(SlaveType.Devp, 1);
+        }
+    }
+
+    @Test
+    void dualCrnStationOutExecute_recordsDispatchSessionAndClearsDualCacheAfterIssuingMoveCommand() {
+        StationOutboundDispatchProcessor processor = new StationOutboundDispatchProcessor();
+        WrkMastService wrkMastService = mock(WrkMastService.class);
+        RedisUtil redisUtil = mock(RedisUtil.class);
+        StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class);
+        StationCommandDispatcher dispatcher = mock(StationCommandDispatcher.class);
+        NotifyUtils notifyUtils = mock(NotifyUtils.class);
+        StationOutboundDecisionSupport decisionSupport = mock(StationOutboundDecisionSupport.class);
+        StationThread stationThread = mock(StationThread.class);
+
+        ReflectionTestUtils.setField(processor, "wrkMastService", wrkMastService);
+        ReflectionTestUtils.setField(processor, "redisUtil", redisUtil);
+        ReflectionTestUtils.setField(processor, "stationMoveCoordinator", coordinator);
+        ReflectionTestUtils.setField(processor, "stationCommandDispatcher", dispatcher);
+        ReflectionTestUtils.setField(processor, "notifyUtils", notifyUtils);
+        ReflectionTestUtils.setField(processor, "stationOutboundDecisionSupport", decisionSupport);
+
+        WrkMast wrkMast = buildWrkMast(10002, 188);
+        wrkMast.setWrkSts(WrkStsType.OUTBOUND_RUN_COMPLETE.sts);
+        wrkMast.setDualCrnNo(2);
+        when(wrkMastService.list(any(com.baomidou.mybatisplus.core.conditions.Wrapper.class)))
+                .thenReturn(Collections.singletonList(wrkMast));
+        when(wrkMastService.updateById(wrkMast)).thenReturn(true);
+
+        StationObjModel stationObjModel = new StationObjModel();
+        stationObjModel.setDeviceNo(1);
+        stationObjModel.setStationId(121);
+        when(redisUtil.get(RedisKeyType.DUAL_CRN_OUT_TASK_STATION_INFO.key + 10002))
+                .thenReturn(com.alibaba.fastjson.JSON.toJSONString(stationObjModel));
+        when(redisUtil.get(RedisKeyType.STATION_OUT_EXECUTE_LIMIT.key + 121)).thenReturn(null);
+
+        StationProtocol stationProtocol = buildStationProtocol(121, 0, 121);
+        stationProtocol.setAutoing(true);
+        stationProtocol.setLoading(true);
+        when(stationThread.getStatusMap()).thenReturn(Map.of(121, stationProtocol));
+
+        StationCommand command = buildCommand(10002, 121, 188);
+        when(decisionSupport.resolveOutboundPathLenFactor(wrkMast)).thenReturn(0.5d);
+        when(decisionSupport.buildOutboundMoveCommand(stationThread, wrkMast, 121, 188, 0.5d)).thenReturn(command);
+        when(dispatcher.dispatch(1, command, "station-operate-process", "dualCrnStationOutExecute"))
+                .thenReturn(StationCommandDispatchResult.accepted("accepted", 1, "station-operate-process", "dualCrnStationOutExecute"));
+
+        SlaveConnection.put(SlaveType.Devp, 1, stationThread);
+        try {
+            processor.dualCrnStationOutExecute();
+
+            verify(coordinator, times(1)).recordDispatch(eq(10002), eq(121), eq("dualCrnStationOutExecute"), same(command), eq(false));
+            verify(notifyUtils, times(1)).notify(String.valueOf(SlaveType.Devp), 1, "10002", wrkMast.getWmsWrkNo(), NotifyMsgType.STATION_OUT_TASK_RUN, null);
+            verify(redisUtil, times(1)).set(eq(RedisKeyType.STATION_OUT_EXECUTE_LIMIT.key + 121), eq("lock"), eq(5L));
+            verify(redisUtil, times(1)).del(RedisKeyType.DUAL_CRN_OUT_TASK_STATION_INFO.key + 10002);
+            assertEquals(WrkStsType.STATION_RUN.sts, wrkMast.getWrkSts());
+        } finally {
+            SlaveConnection.remove(SlaveType.Devp, 1);
+        }
+    }
+
+    private static WrkMast buildWrkMast(int wrkNo, int targetStationId) {
+        WrkMast wrkMast = new WrkMast();
+        wrkMast.setWrkNo(wrkNo);
+        wrkMast.setStaNo(targetStationId);
+        wrkMast.setWmsWrkNo("WMS-" + wrkNo);
+        wrkMast.setIoTime(new Date());
+        return wrkMast;
+    }
+
+    private static StationProtocol buildStationProtocol(int stationId,
+                                                        int taskNo,
+                                                        int targetStationId) {
+        StationProtocol stationProtocol = new StationProtocol();
+        stationProtocol.setStationId(stationId);
+        stationProtocol.setTaskNo(taskNo);
+        stationProtocol.setTargetStaNo(targetStationId);
+        return stationProtocol;
+    }
+
+    private static StationCommand buildCommand(int wrkNo,
+                                               int stationId,
+                                               int targetStationId) {
+        StationCommand command = new StationCommand();
+        command.setTaskNo(wrkNo);
+        command.setStationId(stationId);
+        command.setTargetStaNo(targetStationId);
+        return command;
+    }
+}
diff --git a/src/test/java/com/zy/core/utils/station/StationRegularDispatchProcessorTest.java b/src/test/java/com/zy/core/utils/station/StationRegularDispatchProcessorTest.java
new file mode 100644
index 0000000..a862ffd
--- /dev/null
+++ b/src/test/java/com/zy/core/utils/station/StationRegularDispatchProcessorTest.java
@@ -0,0 +1,240 @@
+package com.zy.core.utils.station;
+
+import com.zy.asrs.domain.enums.NotifyMsgType;
+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.WrkAnalysisService;
+import com.zy.asrs.service.WrkMastService;
+import com.zy.asrs.utils.NotifyUtils;
+import com.zy.common.entity.FindCrnNoResult;
+import com.zy.common.service.CommonService;
+import com.zy.common.utils.RedisUtil;
+import com.zy.core.cache.SlaveConnection;
+import com.zy.core.dispatch.StationCommandDispatchResult;
+import com.zy.core.dispatch.StationCommandDispatcher;
+import com.zy.core.enums.RedisKeyType;
+import com.zy.core.enums.SlaveType;
+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.thread.StationThread;
+import com.zy.core.utils.station.model.DispatchLimitConfig;
+import com.zy.core.utils.station.model.LoadGuardState;
+import com.zy.core.utils.station.model.LoopHitResult;
+import org.junit.jupiter.api.Test;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.same;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+class StationRegularDispatchProcessorTest {
+
+    @Test
+    void stationInExecute_recordsDispatchSessionAfterIssuingMoveCommand() {
+        StationRegularDispatchProcessor processor = new StationRegularDispatchProcessor();
+        BasDevpService basDevpService = mock(BasDevpService.class);
+        WrkMastService wrkMastService = mock(WrkMastService.class);
+        CommonService commonService = mock(CommonService.class);
+        RedisUtil redisUtil = mock(RedisUtil.class);
+        WrkAnalysisService wrkAnalysisService = mock(WrkAnalysisService.class);
+        StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class);
+        StationCommandDispatcher dispatcher = mock(StationCommandDispatcher.class);
+        StationDispatchLoadSupport loadSupport = mock(StationDispatchLoadSupport.class);
+        StationThread stationThread = mock(StationThread.class);
+
+        ReflectionTestUtils.setField(processor, "basDevpService", basDevpService);
+        ReflectionTestUtils.setField(processor, "wrkMastService", wrkMastService);
+        ReflectionTestUtils.setField(processor, "commonService", commonService);
+        ReflectionTestUtils.setField(processor, "redisUtil", redisUtil);
+        ReflectionTestUtils.setField(processor, "wrkAnalysisService", wrkAnalysisService);
+        ReflectionTestUtils.setField(processor, "stationMoveCoordinator", coordinator);
+        ReflectionTestUtils.setField(processor, "stationCommandDispatcher", dispatcher);
+        ReflectionTestUtils.setField(processor, "stationDispatchLoadSupport", loadSupport);
+
+        BasDevp basDevp = buildBasDevp(1);
+        basDevp.setBarcodeStationList("[{\"deviceNo\":1,\"stationId\":101}]");
+        when(basDevpService.list(any(com.baomidou.mybatisplus.core.conditions.Wrapper.class)))
+                .thenReturn(Collections.singletonList(basDevp));
+
+        StationProtocol stationProtocol = buildStationProtocol(101, 500670, 101);
+        stationProtocol.setAutoing(true);
+        stationProtocol.setLoading(true);
+        stationProtocol.setBarcode("GSL110005");
+        when(stationThread.getStatusMap()).thenReturn(Map.of(101, stationProtocol));
+
+        WrkMast wrkMast = buildWrkMast(500670, 102);
+        wrkMast.setWrkSts(WrkStsType.NEW_INBOUND.sts);
+        wrkMast.setLocNo("8-4-1");
+        when(wrkMastService.getOne(any(com.baomidou.mybatisplus.core.conditions.Wrapper.class))).thenReturn(wrkMast);
+        when(wrkMastService.updateById(wrkMast)).thenReturn(true);
+
+        FindCrnNoResult findCrnNoResult = new FindCrnNoResult();
+        findCrnNoResult.setCrnNo(4);
+        findCrnNoResult.setCrnType(SlaveType.Crn);
+        when(commonService.findCrnNoByLocNo("8-4-1")).thenReturn(findCrnNoResult);
+        when(commonService.findInStationId(findCrnNoResult, 101)).thenReturn(102);
+
+        StationCommand command = new StationCommand();
+        command.setTaskNo(500670);
+        command.setStationId(101);
+        command.setTargetStaNo(102);
+        when(stationThread.getCommand(com.zy.core.enums.StationCommandType.MOVE, 500670, 101, 102, 0))
+                .thenReturn(command);
+        when(redisUtil.get(anyString())).thenReturn(null);
+        when(dispatcher.dispatch(1, command, "station-operate-process", "stationInExecute"))
+                .thenReturn(StationCommandDispatchResult.accepted("accepted", 1, "station-operate-process", "stationInExecute"));
+
+        DispatchLimitConfig baseConfig = new DispatchLimitConfig();
+        LoadGuardState loadGuardState = new LoadGuardState();
+        LoopHitResult noHit = LoopHitResult.noHit();
+        when(loadSupport.getDispatchLimitConfig(null, null)).thenReturn(baseConfig);
+        when(loadSupport.countCurrentStationTask()).thenReturn(0);
+        when(loadSupport.buildLoadGuardState(baseConfig)).thenReturn(loadGuardState);
+        when(loadSupport.getDispatchLimitConfig(101, 102)).thenReturn(baseConfig);
+        when(loadSupport.findPathLoopHit(baseConfig, 101, 102, loadGuardState)).thenReturn(noHit);
+        when(loadSupport.isDispatchBlocked(baseConfig, 0, loadGuardState, false)).thenReturn(false);
+
+        SlaveConnection.put(SlaveType.Devp, 1, stationThread);
+        try {
+            processor.stationInExecute();
+
+            verify(coordinator, times(1)).recordDispatch(eq(500670), eq(101), eq("stationInExecute"), same(command), eq(false));
+            verify(redisUtil, times(1)).set(eq(RedisKeyType.STATION_IN_EXECUTE_LIMIT.key + 101), eq("lock"), eq(5L));
+            verify(loadSupport, times(1)).saveLoopLoadReserve(500670, noHit);
+        } finally {
+            SlaveConnection.remove(SlaveType.Devp, 1);
+        }
+    }
+
+    @Test
+    void stationOutExecuteFinish_attemptsClearPathBeforeCompletingTask() {
+        StationRegularDispatchProcessor processor = new StationRegularDispatchProcessor();
+        WrkMastService wrkMastService = mock(WrkMastService.class);
+        BasStationService basStationService = mock(BasStationService.class);
+        WrkAnalysisService wrkAnalysisService = mock(WrkAnalysisService.class);
+        NotifyUtils notifyUtils = mock(NotifyUtils.class);
+        RedisUtil redisUtil = mock(RedisUtil.class);
+        StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class);
+        StationThread stationThread = mock(StationThread.class);
+
+        ReflectionTestUtils.setField(processor, "wrkMastService", wrkMastService);
+        ReflectionTestUtils.setField(processor, "basStationService", basStationService);
+        ReflectionTestUtils.setField(processor, "wrkAnalysisService", wrkAnalysisService);
+        ReflectionTestUtils.setField(processor, "notifyUtils", notifyUtils);
+        ReflectionTestUtils.setField(processor, "redisUtil", redisUtil);
+        ReflectionTestUtils.setField(processor, "stationMoveCoordinator", coordinator);
+
+        WrkMast wrkMast = buildWrkMast(10335, 145);
+        wrkMast.setWrkSts(WrkStsType.STATION_RUN.sts);
+        when(wrkMastService.list(any(com.baomidou.mybatisplus.core.conditions.Wrapper.class)))
+                .thenReturn(Collections.singletonList(wrkMast));
+        when(wrkMastService.updateById(wrkMast)).thenReturn(true);
+
+        BasStation basStation = new BasStation();
+        basStation.setStationId(145);
+        basStation.setDeviceNo(1);
+        when(basStationService.getOne(any(com.baomidou.mybatisplus.core.conditions.Wrapper.class)))
+                .thenReturn(basStation);
+
+        StationProtocol stationProtocol = buildStationProtocol(145, 10335, 145);
+        when(stationThread.getStatusMap()).thenReturn(Map.of(145, stationProtocol));
+        when(stationThread.clearPath(10335)).thenReturn(true);
+
+        SlaveConnection.put(SlaveType.Devp, 1, stationThread);
+        try {
+            processor.stationOutExecuteFinish();
+
+            verify(stationThread, times(1)).clearPath(10335);
+            verify(coordinator, times(1)).finishSession(10335);
+            verify(wrkMastService, times(1)).updateById(wrkMast);
+            verify(notifyUtils, times(1)).notify(String.valueOf(SlaveType.Devp), 1, "10335", wrkMast.getWmsWrkNo(), NotifyMsgType.STATION_OUT_TASK_RUN_COMPLETE, null);
+            assertEquals(WrkStsType.STATION_RUN_COMPLETE.sts, wrkMast.getWrkSts());
+        } finally {
+            SlaveConnection.remove(SlaveType.Devp, 1);
+        }
+    }
+
+    @Test
+    void checkTaskToComplete_marksTaskCompleteAfterLeavingTargetStation() {
+        StationRegularDispatchProcessor processor = new StationRegularDispatchProcessor();
+        WrkMastService wrkMastService = mock(WrkMastService.class);
+        BasStationService basStationService = mock(BasStationService.class);
+        StationMoveCoordinator coordinator = mock(StationMoveCoordinator.class);
+        RedisUtil redisUtil = mock(RedisUtil.class);
+        StationThread stationThread = mock(StationThread.class);
+
+        ReflectionTestUtils.setField(processor, "wrkMastService", wrkMastService);
+        ReflectionTestUtils.setField(processor, "basStationService", basStationService);
+        ReflectionTestUtils.setField(processor, "stationMoveCoordinator", coordinator);
+        ReflectionTestUtils.setField(processor, "redisUtil", redisUtil);
+
+        WrkMast wrkMast = buildWrkMast(10335, 145);
+        wrkMast.setWrkSts(WrkStsType.STATION_RUN_COMPLETE.sts);
+        when(wrkMastService.list(any(com.baomidou.mybatisplus.core.conditions.Wrapper.class)))
+                .thenReturn(Collections.singletonList(wrkMast));
+
+        BasStation basStation = new BasStation();
+        basStation.setStationId(145);
+        basStation.setDeviceNo(1);
+        when(basStationService.getOne(any(com.baomidou.mybatisplus.core.conditions.Wrapper.class)))
+                .thenReturn(basStation);
+
+        StationProtocol stationProtocol = buildStationProtocol(145, 0, 145);
+        when(stationThread.getStatusMap()).thenReturn(Map.of(145, stationProtocol));
+        when(redisUtil.get(anyString())).thenReturn(null);
+
+        SlaveConnection.put(SlaveType.Devp, 1, stationThread);
+        try {
+            processor.checkTaskToComplete();
+
+            verify(coordinator, times(1)).finishSession(10335);
+            verify(wrkMastService, times(1)).updateById(wrkMast);
+            assertEquals(WrkStsType.COMPLETE_OUTBOUND.sts, wrkMast.getWrkSts());
+        } finally {
+            SlaveConnection.remove(SlaveType.Devp, 1);
+        }
+    }
+
+    private static BasDevp buildBasDevp(int devpNo) {
+        BasDevp basDevp = new BasDevp();
+        basDevp.setDevpNo(devpNo);
+        return basDevp;
+    }
+
+    private static WrkMast buildWrkMast(int wrkNo, int targetStationId) {
+        WrkMast wrkMast = new WrkMast();
+        wrkMast.setWrkNo(wrkNo);
+        wrkMast.setStaNo(targetStationId);
+        wrkMast.setWmsWrkNo("WMS-" + wrkNo);
+        wrkMast.setIoTime(new Date());
+        return wrkMast;
+    }
+
+    private static StationProtocol buildStationProtocol(int stationId,
+                                                        int taskNo,
+                                                        int targetStationId) {
+        StationProtocol stationProtocol = new StationProtocol();
+        stationProtocol.setStationId(stationId);
+        stationProtocol.setTaskNo(taskNo);
+        stationProtocol.setTargetStaNo(targetStationId);
+        return stationProtocol;
+    }
+}
diff --git a/src/test/java/com/zy/core/utils/station/model/StationModelTypePlacementTest.java b/src/test/java/com/zy/core/utils/station/model/StationModelTypePlacementTest.java
new file mode 100644
index 0000000..5d472e8
--- /dev/null
+++ b/src/test/java/com/zy/core/utils/station/model/StationModelTypePlacementTest.java
@@ -0,0 +1,49 @@
+package com.zy.core.utils.station.model;
+
+import com.zy.core.utils.StationOperateProcessUtils;
+import com.zy.core.utils.station.StationDispatchLoadSupport;
+import com.zy.core.utils.station.StationOutboundDecisionSupport;
+import com.zy.core.utils.station.StationRerouteProcessor;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+class StationModelTypePlacementTest {
+
+    @Test
+    void rerouteTypes_areNoLongerNestedInsideStationOperateProcessUtils() {
+        assertFalse(hasDeclaredClass(StationOperateProcessUtils.class, "RerouteSceneType"));
+        assertFalse(hasDeclaredClass(StationOperateProcessUtils.class, "RerouteDecision"));
+        assertFalse(hasDeclaredClass(StationOperateProcessUtils.class, "RerouteContext"));
+        assertFalse(hasDeclaredClass(StationOperateProcessUtils.class, "RerouteCommandPlan"));
+        assertFalse(hasDeclaredClass(StationOperateProcessUtils.class, "RerouteExecutionResult"));
+    }
+
+    @Test
+    void dispatchLoadTypes_areNoLongerNestedInsideStationDispatchLoadSupport() {
+        assertFalse(hasDeclaredClass(StationDispatchLoadSupport.class, "DispatchLimitConfig"));
+        assertFalse(hasDeclaredClass(StationDispatchLoadSupport.class, "LoadGuardState"));
+        assertFalse(hasDeclaredClass(StationDispatchLoadSupport.class, "LoopHitResult"));
+    }
+
+    @Test
+    void outboundDecisionTypes_areNoLongerNestedInsideStationOutboundDecisionSupport() {
+        assertFalse(hasDeclaredClass(StationOutboundDecisionSupport.class, "OutOrderDispatchDecision"));
+        assertFalse(hasDeclaredClass(StationOutboundDecisionSupport.class, "CircleTargetCandidate"));
+    }
+
+    @Test
+    void rerouteProcessor_isLocatedUnderStationPackage() {
+        assertEquals("com.zy.core.utils.station", StationRerouteProcessor.class.getPackageName());
+    }
+
+    private static boolean hasDeclaredClass(Class<?> ownerType, String simpleName) {
+        for (Class<?> declaredClass : ownerType.getDeclaredClasses()) {
+            if (simpleName.equals(declaredClass.getSimpleName())) {
+                return true;
+            }
+        }
+        return false;
+    }
+}

--
Gitblit v1.9.1