Junjie
2026-04-27 0c336d5c5c0596691c9b33c08643c03486d47d5f
refactor: move station buffer capacity to bas station
2个文件已添加
13个文件已修改
5个文件已删除
477 ■■■■■ 已修改文件
src/main/java/com/zy/ai/domain/autotune/AutoTuneParameterSnapshot.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/service/impl/AutoTuneApplyServiceImpl.java 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/service/impl/AutoTuneSnapshotServiceImpl.java 23 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/service/impl/FlowTopologySnapshotServiceImpl.java 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/utils/AiPromptUtils.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/entity/BasStation.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/entity/StationFlowCapacity.java 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/mapper/StationFlowCapacityMapper.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/service/StationFlowCapacityService.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/service/impl/StationFlowCapacityServiceImpl.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/BasStationMapper.xml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/sql/20260427_add_out_buffer_capacity_to_asr_bas_station.sql 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/sql/20260427_create_station_flow_capacity.sql 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/sql/20260427_update_auto_tune_prompt_out_buffer_capacity.sql 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/static/js/basStation/basStation.js 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/views/ai/auto_tune.html 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/views/basStation/basStation.html 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/test/java/com/zy/ai/service/AutoTuneApplyServiceImplTest.java 43 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/test/java/com/zy/ai/service/FlowTopologySnapshotServiceImplTest.java 59 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/test/java/com/zy/ai/service/impl/AutoTuneSnapshotServiceImplTest.java 30 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/ai/domain/autotune/AutoTuneParameterSnapshot.java
@@ -17,6 +17,8 @@
    private Map<String, Integer> stationOutTaskLimits;
    private Map<String, Integer> stationOutBufferCapacities;
    private Map<String, Integer> crnMaxOutTask;
    private Map<String, Integer> crnMaxInTask;
src/main/java/com/zy/ai/service/impl/AutoTuneApplyServiceImpl.java
@@ -17,12 +17,10 @@
import com.zy.asrs.entity.BasCrnp;
import com.zy.asrs.entity.BasDualCrnp;
import com.zy.asrs.entity.BasStation;
import com.zy.asrs.entity.StationFlowCapacity;
import com.zy.asrs.entity.WrkMast;
import com.zy.asrs.service.BasCrnpService;
import com.zy.asrs.service.BasDualCrnpService;
import com.zy.asrs.service.BasStationService;
import com.zy.asrs.service.StationFlowCapacityService;
import com.zy.asrs.service.WrkMastService;
import com.zy.common.utils.RedisUtil;
import com.zy.core.enums.RedisKeyType;
@@ -50,7 +48,6 @@
    private static final Logger LOGGER = LoggerFactory.getLogger(AutoTuneApplyServiceImpl.class);
    private static final String PROMPT_SCENE_CODE = "auto_tune_apply";
    private static final String DIRECTION_OUT = "OUT";
    private static final long APPLY_LOCK_SECONDS = 120L;
    private static final String APPLY_LOCK_BUSY_REASON = "申请调参锁失败,锁不可用,可能已有任务或 Redis 异常";
    private static final List<Long> FINAL_WRK_STS_LIST = Arrays.asList(
@@ -74,8 +71,6 @@
    private BasCrnpService basCrnpService;
    @Autowired
    private BasDualCrnpService basDualCrnpService;
    @Autowired
    private StationFlowCapacityService stationFlowCapacityService;
    @Autowired
    private WrkMastService wrkMastService;
    @Autowired
@@ -300,7 +295,7 @@
        Integer maxValue = resolveMaxValue(validatedChange, rule, requestedValue);
        if (maxValue == null) {
            return validatedChange.reject("站点 " + validatedChange.getTargetId()
                    + " 缺少 OUT 方向 bufferCapacity,无法证明 outTaskLimit 上限");
                    + " 缺少 outBufferCapacity,无法证明 outTaskLimit 上限");
        }
        if (requestedValue < rule.getMinValue() || requestedValue > maxValue) {
            return validatedChange.reject(validatedChange.getTargetKey() + " 必须在 "
@@ -388,16 +383,11 @@
            return rule.getMaxValue();
        }
        Integer targetId = parseTargetId(validatedChange.getTargetId(), rule.getTargetType());
        StationFlowCapacity capacity = stationFlowCapacityService.getOne(
                new QueryWrapper<StationFlowCapacity>()
                        .eq("station_id", targetId)
                        .eq("direction_code", DIRECTION_OUT)
                        .last("limit 1")
        );
        if (capacity == null || capacity.getBufferCapacity() == null) {
        BasStation station = basStationService.getById(targetId);
        if (station == null || station.getOutBufferCapacity() == null) {
            return requestedValue == 0 ? 0 : null;
        }
        return Math.max(0, capacity.getBufferCapacity());
        return Math.max(0, station.getOutBufferCapacity());
    }
    private Date findCooldownExpireTime(ValidatedChange validatedChange, Date now) {
src/main/java/com/zy/ai/service/impl/AutoTuneSnapshotServiceImpl.java
@@ -224,7 +224,9 @@
        ));
        List<BasCrnp> crnList = loadCrnList();
        List<BasDualCrnp> dualCrnList = loadDualCrnList();
        snapshot.setStationOutTaskLimits(loadStationOutTaskLimits());
        List<BasStation> outStationList = loadOutStationList();
        snapshot.setStationOutTaskLimits(buildStationOutTaskLimitMap(outStationList));
        snapshot.setStationOutBufferCapacities(buildStationOutBufferCapacityMap(outStationList));
        snapshot.setCrnMaxOutTask(buildCrnMaxOutTask(crnList));
        snapshot.setCrnMaxInTask(buildCrnMaxInTask(crnList));
        snapshot.setDualCrnMaxOutTask(buildDualCrnMaxOutTask(dualCrnList));
@@ -247,19 +249,18 @@
        }
    }
    private Map<String, Integer> loadStationOutTaskLimits() {
        Map<String, Integer> result = new LinkedHashMap<>();
    private List<BasStation> loadOutStationList() {
        if (basStationService == null) {
            return result;
            return Collections.emptyList();
        }
        Set<Integer> outStationIds = loadOutStationIds();
        if (outStationIds.isEmpty()) {
            return result;
            return Collections.emptyList();
        }
        QueryWrapper<BasStation> wrapper = new QueryWrapper<>();
        wrapper.in("station_id", outStationIds);
        wrapper.orderByAsc("station_id");
        return buildStationOutTaskLimitMap(basStationService.list(wrapper));
        return safeList(basStationService.list(wrapper));
    }
    private Set<Integer> loadOutStationIds() {
@@ -295,6 +296,16 @@
        return result;
    }
    Map<String, Integer> buildStationOutBufferCapacityMap(List<BasStation> stationList) {
        Map<String, Integer> result = new LinkedHashMap<>();
        for (BasStation station : safeList(stationList)) {
            if (station != null && station.getStationId() != null) {
                result.put(String.valueOf(station.getStationId()), station.getOutBufferCapacity());
            }
        }
        return result;
    }
    private Map<String, Integer> buildCrnMaxOutTask(List<BasCrnp> crnList) {
        Map<String, Integer> result = new LinkedHashMap<>();
        for (BasCrnp crn : safeList(crnList)) {
src/main/java/com/zy/ai/service/impl/FlowTopologySnapshotServiceImpl.java
@@ -4,9 +4,12 @@
import com.zy.ai.domain.autotune.AutoTuneFlowTopologyItem;
import com.zy.ai.domain.autotune.AutoTuneStationRuntimeItem;
import com.zy.ai.service.FlowTopologySnapshotService;
import com.zy.asrs.entity.StationFlowCapacity;
import com.zy.asrs.service.StationFlowCapacityService;
import com.zy.asrs.entity.BasDevp;
import com.zy.asrs.entity.BasStation;
import com.zy.asrs.service.BasDevpService;
import com.zy.asrs.service.BasStationService;
import com.zy.common.utils.NavigateUtils;
import com.zy.core.model.StationObjModel;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -24,31 +27,36 @@
@Service("flowTopologySnapshotService")
public class FlowTopologySnapshotServiceImpl implements FlowTopologySnapshotService {
    private static final String DIRECTION_OUT = "OUT";
    @Autowired
    private StationFlowCapacityService stationFlowCapacityService;
    private BasDevpService basDevpService;
    @Autowired
    private BasStationService basStationService;
    @Autowired
    private NavigateUtils navigateUtils;
    @Override
    public List<AutoTuneFlowTopologyItem> buildSnapshot(List<AutoTuneStationRuntimeItem> stationRuntimeSnapshot) {
        List<StationFlowCapacity> capacities = loadCapacityList();
        List<BasStation> outStations = loadOutStationList();
        Map<Integer, List<Integer>> adjacencyMap = loadSortedAdjacencyMap();
        List<AutoTuneFlowTopologyItem> itemList = new ArrayList<>();
        for (StationFlowCapacity capacity : capacities) {
            if (capacity == null || capacity.getStationId() == null) {
        for (BasStation station : outStations) {
            if (station == null || station.getStationId() == null) {
                continue;
            }
            itemList.add(buildTopologyItem(capacity, adjacencyMap, stationRuntimeSnapshot));
            itemList.add(buildTopologyItem(station, adjacencyMap, stationRuntimeSnapshot));
        }
        return itemList;
    }
    public AutoTuneFlowTopologyItem buildTopologyItem(StationFlowCapacity capacity,
    public AutoTuneFlowTopologyItem buildTopologyItem(BasStation station,
                                                     Map<Integer, List<Integer>> adjacencyMap,
                                                     List<AutoTuneStationRuntimeItem> stationRuntimeSnapshot) {
        Integer targetStationId = capacity.getStationId();
        Integer bufferCapacity = defaultInt(capacity.getBufferCapacity());
        Integer targetStationId = station.getStationId();
        Integer bufferCapacity = station.getOutBufferCapacity();
        List<Integer> adjacentStationIds = sortedList(adjacencyMap == null ? null : adjacencyMap.get(targetStationId));
        List<Integer> flowStationIds = buildFlowStationIds(targetStationId, adjacentStationIds);
        List<AutoTuneStationRuntimeItem> flowRuntimeItems = filterRuntimeByStationIds(stationRuntimeSnapshot, flowStationIds);
@@ -56,7 +64,7 @@
        AutoTuneFlowTopologyItem item = new AutoTuneFlowTopologyItem();
        item.setTargetStationId(targetStationId);
        item.setDirection(capacity.getDirectionCode());
        item.setDirection(DIRECTION_OUT);
        item.setAdjacentStationIds(adjacentStationIds);
        // 当前只存在无向站点邻接图,不能把邻接事实冒充为上下游方向事实。
        item.setUpstreamStationIds(Collections.emptyList());
@@ -64,7 +72,7 @@
        item.setFlowStationIds(flowStationIds);
        item.setBufferCapacity(bufferCapacity);
        item.setOccupiedCount(runtimeCount.getOccupiedCount());
        item.setFreeCount(Math.max(0, bufferCapacity - runtimeCount.getOccupiedCount()));
        item.setFreeCount(resolveFreeCount(bufferCapacity, runtimeCount.getOccupiedCount()));
        item.setNonAutoingCount(runtimeCount.getNonAutoingCount());
        item.setLoadingCount(runtimeCount.getLoadingCount());
        item.setTaskHoldingCount(runtimeCount.getTaskHoldingCount());
@@ -99,32 +107,39 @@
        return count;
    }
    public StationFlowCapacity findCapacity(List<StationFlowCapacity> capacities,
                                            Integer stationId,
                                            String directionCode) {
        if (capacities == null || stationId == null || directionCode == null) {
            return null;
        }
        for (StationFlowCapacity capacity : capacities) {
            if (capacity == null) {
                continue;
            }
            if (Objects.equals(capacity.getStationId(), stationId)
                    && directionCode.equals(capacity.getDirectionCode())) {
                return capacity;
            }
        }
        return null;
    }
    private List<StationFlowCapacity> loadCapacityList() {
        if (stationFlowCapacityService == null) {
    private List<BasStation> loadOutStationList() {
        if (basDevpService == null || basStationService == null) {
            return Collections.emptyList();
        }
        QueryWrapper<StationFlowCapacity> wrapper = new QueryWrapper<>();
        wrapper.orderByAsc("station_id", "direction_code");
        List<StationFlowCapacity> capacityList = stationFlowCapacityService.list(wrapper);
        return capacityList == null ? Collections.emptyList() : capacityList;
        Set<Integer> stationIds = loadOutStationIds();
        if (stationIds.isEmpty()) {
            return Collections.emptyList();
        }
        QueryWrapper<BasStation> wrapper = new QueryWrapper<>();
        wrapper.in("station_id", stationIds);
        wrapper.orderByAsc("station_id");
        List<BasStation> stationList = basStationService.list(wrapper);
        return stationList == null ? Collections.emptyList() : stationList;
    }
    private Set<Integer> loadOutStationIds() {
        LinkedHashSet<Integer> stationIds = new LinkedHashSet<>();
        if (basDevpService == null) {
            return stationIds;
        }
        QueryWrapper<BasDevp> wrapper = new QueryWrapper<>();
        wrapper.eq("status", 1);
        wrapper.orderByAsc("devp_no");
        List<BasDevp> basDevpList = basDevpService.list(wrapper);
        for (BasDevp basDevp : safeList(basDevpList)) {
            List<StationObjModel> outStationList = safeList(basDevp == null ? null : basDevp.getOutStationList$());
            for (StationObjModel stationObjModel : outStationList) {
                if (stationObjModel != null && stationObjModel.getStationId() != null) {
                    stationIds.add(stationObjModel.getStationId());
                }
            }
        }
        return stationIds;
    }
    private Map<Integer, List<Integer>> loadSortedAdjacencyMap() {
@@ -178,8 +193,15 @@
        return result;
    }
    private int defaultInt(Integer value) {
        return value == null ? 0 : value;
    private Integer resolveFreeCount(Integer bufferCapacity, int occupiedCount) {
        if (bufferCapacity == null) {
            return null;
        }
        return Math.max(0, Math.max(0, bufferCapacity) - occupiedCount);
    }
    private <T> List<T> safeList(List<T> value) {
        return value == null ? Collections.emptyList() : value;
    }
    @Data
src/main/java/com/zy/ai/utils/AiPromptUtils.java
@@ -157,6 +157,7 @@
                            "- outTaskLimit:对应 asr_bas_station.out_task_limit\n" +
                            "- maxOutTask:对应 asr_bas_crnp.max_out_task / asr_bas_dual_crnp.max_out_task\n" +
                            "- maxInTask:对应 asr_bas_crnp.max_in_task / asr_bas_dual_crnp.max_in_task\n\n" +
                            "注意:asr_bas_station.out_buffer_capacity 是人工维护的出库缓存容量,只用于证明 outTaskLimit 可上调上限,Agent 不允许修改该字段;增大 outTaskLimit 时建议值不得超过对应站点 outBufferCapacity。\n\n" +
                            "Step 4 提交变更\n" +
                            "- 先通过 wcs_local_dispatch_apply_auto_tune_changes 执行 dry-run。\n" +
                            "- dry-run 通过后才允许通过同一工具实际应用。\n" +
src/main/java/com/zy/asrs/entity/BasStation.java
@@ -116,6 +116,13 @@
    @TableField("out_task_limit")
    private Integer outTaskLimit;
    /**
     * 出库缓存容量
     */
    @ApiModelProperty(value= "出库缓存容量")
    @TableField("out_buffer_capacity")
    private Integer outBufferCapacity;
    public BasStation() {}
    public BasStation(Integer status,Integer wrkNo,String inEnable,String outEnable,Long createBy,Date createTime,Long updateBy,Date updateTime,String memo,Integer stationLev) {
src/main/java/com/zy/asrs/entity/StationFlowCapacity.java
File was deleted
src/main/java/com/zy/asrs/mapper/StationFlowCapacityMapper.java
File was deleted
src/main/java/com/zy/asrs/service/StationFlowCapacityService.java
File was deleted
src/main/java/com/zy/asrs/service/impl/StationFlowCapacityServiceImpl.java
File was deleted
src/main/resources/mapper/BasStationMapper.xml
@@ -18,6 +18,7 @@
        <result column="device_no" property="deviceNo" />
        <result column="station_alias" property="stationAlias" />
        <result column="out_task_limit" property="outTaskLimit" />
        <result column="out_buffer_capacity" property="outBufferCapacity" />
    </resultMap>
src/main/resources/sql/20260427_add_out_buffer_capacity_to_asr_bas_station.sql
New file
@@ -0,0 +1,55 @@
-- asr_bas_station 增加出库缓存容量配置,替代 asr_station_flow_capacity
-- 用途:证明出库站点 out_task_limit 的可上调上限
-- 适用数据库:MySQL
SET @current_db := DATABASE();
SET @out_buffer_capacity_exists := (
  SELECT COUNT(1)
  FROM information_schema.COLUMNS
  WHERE TABLE_SCHEMA = @current_db
    AND TABLE_NAME = 'asr_bas_station'
    AND COLUMN_NAME = 'out_buffer_capacity'
);
SET @add_out_buffer_capacity_sql := IF(
  @out_buffer_capacity_exists = 0,
  'ALTER TABLE asr_bas_station ADD COLUMN out_buffer_capacity INT NULL COMMENT ''出库缓存容量,用于证明 out_task_limit 上限'' AFTER out_task_limit',
  'SELECT ''column out_buffer_capacity already exists'' '
);
PREPARE stmt_out_buffer_capacity FROM @add_out_buffer_capacity_sql;
EXECUTE stmt_out_buffer_capacity;
DEALLOCATE PREPARE stmt_out_buffer_capacity;
SET @station_flow_capacity_exists := (
  SELECT COUNT(1)
  FROM information_schema.TABLES
  WHERE TABLE_SCHEMA = @current_db
    AND TABLE_NAME = 'asr_station_flow_capacity'
);
SET @migrate_station_flow_capacity_sql := IF(
  @station_flow_capacity_exists > 0,
  'UPDATE asr_bas_station station
     JOIN asr_station_flow_capacity capacity
       ON capacity.station_id = station.station_id
      AND capacity.direction_code = ''OUT''
      AND capacity.buffer_capacity IS NOT NULL
      SET station.out_buffer_capacity = capacity.buffer_capacity
    WHERE station.out_buffer_capacity IS NULL',
  'SELECT ''table asr_station_flow_capacity not exists'' '
);
PREPARE stmt_migrate_station_flow_capacity FROM @migrate_station_flow_capacity_sql;
EXECUTE stmt_migrate_station_flow_capacity;
DEALLOCATE PREPARE stmt_migrate_station_flow_capacity;
SET @drop_station_flow_capacity_sql := IF(
  @station_flow_capacity_exists > 0,
  'DROP TABLE asr_station_flow_capacity',
  'SELECT ''table asr_station_flow_capacity already removed'' '
);
PREPARE stmt_drop_station_flow_capacity FROM @drop_station_flow_capacity_sql;
EXECUTE stmt_drop_station_flow_capacity;
DEALLOCATE PREPARE stmt_drop_station_flow_capacity;
SHOW COLUMNS FROM asr_bas_station LIKE 'out_buffer_capacity';
src/main/resources/sql/20260427_create_station_flow_capacity.sql
File was deleted
src/main/resources/sql/20260427_update_auto_tune_prompt_out_buffer_capacity.sql
New file
@@ -0,0 +1,9 @@
UPDATE sys_ai_prompt_block block
JOIN sys_ai_prompt_template template ON template.id = block.template_id
SET block.content = CONCAT(
    block.content,
    '\n注意:asr_bas_station.out_buffer_capacity 是人工维护的出库缓存容量,只用于证明 outTaskLimit 可上调上限,Agent 不允许修改该字段;增大 outTaskLimit 时建议值不得超过对应站点 outBufferCapacity。'
)
WHERE template.scene_code = 'wcs_auto_tune_dispatch'
  AND block.block_type = 'scene_playbook'
  AND block.content NOT LIKE '%out_buffer_capacity 是人工维护的出库缓存容量%';
src/main/webapp/static/js/basStation/basStation.js
@@ -256,6 +256,24 @@
        checkboxInactiveRaw: '0'
    },
    {
        field: 'outBufferCapacity',
        columnName: 'out_buffer_capacity',
        label: '出库缓存容量',
        tableProp: 'outBufferCapacity',
        exportField: 'outBufferCapacity',
        kind: 'text',
        valueType: 'number',
        required: false,
        primaryKey: false,
        sortable: false,
        textarea: false,
        minWidth: 130,
        enumOptions: [],
        foreignQuery: '',
        checkboxActiveRaw: '1',
        checkboxInactiveRaw: '0'
    },
    {
        field: 'stationId',
        columnName: 'station_id',
        label: '编  号',
@@ -506,6 +524,24 @@
        foreignQuery: '',
        checkboxActiveRaw: '1',
        checkboxInactiveRaw: '0'
    },
    {
        field: 'outBufferCapacity',
        columnName: 'out_buffer_capacity',
        label: '出库缓存容量',
        tableProp: 'outBufferCapacity',
        exportField: 'outBufferCapacity',
        kind: 'text',
        valueType: 'number',
        required: false,
        primaryKey: false,
        sortable: false,
        textarea: false,
        minWidth: 130,
        enumOptions: [],
        foreignQuery: '',
        checkboxActiveRaw: '1',
        checkboxInactiveRaw: '0'
    }
    ]);
src/main/webapp/views/ai/auto_tune.html
@@ -343,6 +343,13 @@
              </div>
            </div>
            <div class="map-box">
              <div class="map-title">出库站点 outBufferCapacity</div>
              <div class="pill-row">
                <span class="kv-pill" v-for="item in mapEntries(parameterSnapshot.stationOutBufferCapacities)" :key="'sb_' + item.key">{{ item.key }}: {{ valueOrDash(item.value) }}</span>
                <span class="small-muted" v-if="mapEntries(parameterSnapshot.stationOutBufferCapacities).length === 0">暂无数据</span>
              </div>
            </div>
            <div class="map-box">
              <div class="map-title">单工位堆垛机 maxOut/maxIn</div>
              <div class="pill-row">
                <span class="kv-pill" v-for="item in combineTaskLimitEntries(parameterSnapshot.crnMaxOutTask, parameterSnapshot.crnMaxInTask)" :key="'c_' + item.key">{{ item.key }}: 出{{ item.out }} / 入{{ item.in }}</span>
src/main/webapp/views/basStation/basStation.html
@@ -665,6 +665,6 @@
<script type="text/javascript" src="../../static/js/common.js" charset="utf-8"></script>
<script type="text/javascript" src="../../static/vue/js/vue.min.js"></script>
<script type="text/javascript" src="../../static/vue/element/element.js"></script>
<script type="text/javascript" src="../../static/js/basStation/basStation.js?v=20260310" charset="utf-8"></script>
<script type="text/javascript" src="../../static/js/basStation/basStation.js?v=20260427" charset="utf-8"></script>
</body>
</html>
src/test/java/com/zy/ai/service/AutoTuneApplyServiceImplTest.java
@@ -10,12 +10,10 @@
import com.zy.asrs.entity.BasCrnp;
import com.zy.asrs.entity.BasDualCrnp;
import com.zy.asrs.entity.BasStation;
import com.zy.asrs.entity.StationFlowCapacity;
import com.zy.asrs.entity.WrkMast;
import com.zy.asrs.service.BasCrnpService;
import com.zy.asrs.service.BasDualCrnpService;
import com.zy.asrs.service.BasStationService;
import com.zy.asrs.service.StationFlowCapacityService;
import com.zy.asrs.service.WrkMastService;
import com.zy.common.utils.RedisUtil;
import com.zy.core.enums.RedisKeyType;
@@ -79,8 +77,6 @@
    @Mock
    private BasDualCrnpService basDualCrnpService;
    @Mock
    private StationFlowCapacityService stationFlowCapacityService;
    @Mock
    private WrkMastService wrkMastService;
    @Mock
    private RedisUtil redisUtil;
@@ -96,7 +92,6 @@
        ReflectionTestUtils.setField(service, "basStationService", basStationService);
        ReflectionTestUtils.setField(service, "basCrnpService", basCrnpService);
        ReflectionTestUtils.setField(service, "basDualCrnpService", basDualCrnpService);
        ReflectionTestUtils.setField(service, "stationFlowCapacityService", stationFlowCapacityService);
        ReflectionTestUtils.setField(service, "wrkMastService", wrkMastService);
        ReflectionTestUtils.setField(service, "transactionManager", transactionManager);
        ReflectionTestUtils.setField(service, "redisUtil", redisUtil);
@@ -201,8 +196,7 @@
    @Test
    void rejectStationOutTaskLimitAboveDirectionalBufferCapacity() {
        when(basStationService.getById(101)).thenReturn(station(101, 1));
        when(stationFlowCapacityService.getOne(any(Wrapper.class))).thenReturn(capacity(101, "OUT", 2));
        when(basStationService.getById(101)).thenReturn(station(101, 1, 2));
        service.apply(request(true, command("station", "101", "outTaskLimit", "3")));
@@ -220,7 +214,6 @@
        List<AiAutoTuneChange> changes = savedChanges();
        assertEquals("rejected", changes.get(0).getResultStatus());
        assertTrue(changes.get(0).getRejectReason().contains("需要人工先初始化为有限值"));
        verify(stationFlowCapacityService, never()).getOne(any(Wrapper.class));
    }
    @Test
@@ -232,13 +225,11 @@
        List<AiAutoTuneChange> changes = savedChanges();
        assertEquals("rejected", changes.get(0).getResultStatus());
        assertTrue(changes.get(0).getRejectReason().contains("需要人工先初始化为有限值"));
        verify(stationFlowCapacityService, never()).getOne(any(Wrapper.class));
    }
    @Test
    void allowStationOutTaskLimitZeroToOneAsFiniteStep() {
        when(basStationService.getById(101)).thenReturn(station(101, 0));
        when(stationFlowCapacityService.getOne(any(Wrapper.class))).thenReturn(capacity(101, "OUT", 1));
        when(basStationService.getById(101)).thenReturn(station(101, 0, 1));
        AutoTuneApplyResult result = service.apply(request(true, command("station", "101", "outTaskLimit", "1")));
@@ -247,6 +238,17 @@
        assertEquals("dry_run", changes.get(0).getResultStatus());
        assertEquals("0", changes.get(0).getOldValue());
        assertEquals("1", changes.get(0).getRequestedValue());
    }
    @Test
    void rejectStationOutTaskLimitWithoutOutBufferCapacity() {
        when(basStationService.getById(101)).thenReturn(station(101, 0));
        service.apply(request(true, command("station", "101", "outTaskLimit", "1")));
        List<AiAutoTuneChange> changes = savedChanges();
        assertEquals("rejected", changes.get(0).getResultStatus());
        assertTrue(changes.get(0).getRejectReason().contains("缺少 outBufferCapacity"));
    }
    @Test
@@ -267,8 +269,7 @@
    @Test
    void realMixedValidAndInvalidBatchRejectsAndDoesNotWriteTargets() {
        when(configService.getOne(any(Wrapper.class))).thenReturn(config("conveyorStationTaskLimit", "10"));
        when(basStationService.getById(101)).thenReturn(station(101, 1));
        when(stationFlowCapacityService.getOne(any(Wrapper.class))).thenReturn(capacity(101, "OUT", 2));
        when(basStationService.getById(101)).thenReturn(station(101, 1, 2));
        AutoTuneApplyResult result = service.apply(request(false,
                command("sys_config", null, "conveyorStationTaskLimit", "15"),
@@ -376,8 +377,7 @@
    @Test
    void applyMixedBatchSuccessfully() {
        when(configService.getOne(any(Wrapper.class))).thenReturn(config("conveyorStationTaskLimit", "10"));
        when(basStationService.getById(101)).thenReturn(station(101, 1));
        when(stationFlowCapacityService.getOne(any(Wrapper.class))).thenReturn(capacity(101, "OUT", 2));
        when(basStationService.getById(101)).thenReturn(station(101, 1, 2));
        when(basCrnpService.getById(1)).thenReturn(crn(1, 1, 1));
        when(basDualCrnpService.getById(2)).thenReturn(dualCrn(2, 1, 1));
@@ -789,9 +789,14 @@
    }
    private BasStation station(Integer stationId, Integer outTaskLimit) {
        return station(stationId, outTaskLimit, null);
    }
    private BasStation station(Integer stationId, Integer outTaskLimit, Integer outBufferCapacity) {
        BasStation station = new BasStation();
        station.setStationId(stationId);
        station.setOutTaskLimit(outTaskLimit);
        station.setOutBufferCapacity(outBufferCapacity);
        return station;
    }
@@ -809,14 +814,6 @@
        dualCrnp.setMaxOutTask(maxOutTask);
        dualCrnp.setMaxInTask(maxInTask);
        return dualCrnp;
    }
    private StationFlowCapacity capacity(Integer stationId, String directionCode, Integer bufferCapacity) {
        StationFlowCapacity capacity = new StationFlowCapacity();
        capacity.setStationId(stationId);
        capacity.setDirectionCode(directionCode);
        capacity.setBufferCapacity(bufferCapacity);
        return capacity;
    }
    private AiAutoTuneChange successChange(Long jobId,
src/test/java/com/zy/ai/service/FlowTopologySnapshotServiceImplTest.java
@@ -3,7 +3,7 @@
import com.zy.ai.domain.autotune.AutoTuneFlowTopologyItem;
import com.zy.ai.domain.autotune.AutoTuneStationRuntimeItem;
import com.zy.ai.service.impl.FlowTopologySnapshotServiceImpl;
import com.zy.asrs.entity.StationFlowCapacity;
import com.zy.asrs.entity.BasStation;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
@@ -12,19 +12,17 @@
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
class FlowTopologySnapshotServiceImplTest {
    @Test
    void buildTopologyItemUsesExplicitDirectionAndTargetStation() {
        FlowTopologySnapshotServiceImpl service = new FlowTopologySnapshotServiceImpl();
        StationFlowCapacity capacity = capacity(101, "OUT", 3);
        BasStation station = station(101, 3);
        Map<Integer, List<Integer>> adjacency = new LinkedHashMap<>();
        adjacency.put(101, Arrays.asList(103, 102));
        AutoTuneFlowTopologyItem item = service.buildTopologyItem(
                capacity,
                station,
                adjacency,
                Arrays.asList(runtime(101, 1, 0, 0), runtime(102, 1, 1, 0))
        );
@@ -40,12 +38,12 @@
    @Test
    void undirectedGraphOnlyPopulatesAdjacentAndFlowStations() {
        FlowTopologySnapshotServiceImpl service = new FlowTopologySnapshotServiceImpl();
        StationFlowCapacity capacity = capacity(201, "IN", 4);
        BasStation station = station(201, 4);
        Map<Integer, List<Integer>> adjacency = new LinkedHashMap<>();
        adjacency.put(201, Arrays.asList(203, 202));
        AutoTuneFlowTopologyItem item = service.buildTopologyItem(
                capacity,
                station,
                adjacency,
                Arrays.asList(runtime(202, 1, 1, 0), runtime(203, 1, 0, 8001))
        );
@@ -76,30 +74,14 @@
    }
    @Test
    void findCapacityUsesStationIdAndDirectionCode() {
        FlowTopologySnapshotServiceImpl service = new FlowTopologySnapshotServiceImpl();
        List<StationFlowCapacity> capacities = Arrays.asList(
                capacity(101, "IN", 2),
                capacity(101, "OUT", 4),
                capacity(102, "OUT", 8)
        );
        StationFlowCapacity result = service.findCapacity(capacities, 101, "OUT");
        assertNotNull(result);
        assertEquals(4, result.getBufferCapacity());
        assertEquals("OUT", result.getDirectionCode());
    }
    @Test
    void flowStationIdsAndCountsAreNotTruncatedByBufferCapacity() {
        FlowTopologySnapshotServiceImpl service = new FlowTopologySnapshotServiceImpl();
        StationFlowCapacity capacity = capacity(101, "OUT", 1);
        BasStation station = station(101, 1);
        Map<Integer, List<Integer>> adjacency = new LinkedHashMap<>();
        adjacency.put(101, Arrays.asList(104, 102, 103));
        AutoTuneFlowTopologyItem item = service.buildTopologyItem(
                capacity,
                station,
                adjacency,
                Arrays.asList(
                        runtime(101, 1, 0, 0),
@@ -117,12 +99,27 @@
        assertEquals(0, item.getFreeCount());
    }
    private StationFlowCapacity capacity(Integer stationId, String directionCode, Integer bufferCapacity) {
        StationFlowCapacity capacity = new StationFlowCapacity();
        capacity.setStationId(stationId);
        capacity.setDirectionCode(directionCode);
        capacity.setBufferCapacity(bufferCapacity);
        return capacity;
    @Test
    void missingOutBufferCapacityKeepsFreeCountUnknown() {
        FlowTopologySnapshotServiceImpl service = new FlowTopologySnapshotServiceImpl();
        BasStation station = station(101, null);
        AutoTuneFlowTopologyItem item = service.buildTopologyItem(
                station,
                new LinkedHashMap<>(),
                Arrays.asList(runtime(101, 1, 1, 0))
        );
        assertEquals(null, item.getBufferCapacity());
        assertEquals(null, item.getFreeCount());
        assertEquals(1, item.getOccupiedCount());
    }
    private BasStation station(Integer stationId, Integer outBufferCapacity) {
        BasStation station = new BasStation();
        station.setStationId(stationId);
        station.setOutBufferCapacity(outBufferCapacity);
        return station;
    }
    private AutoTuneStationRuntimeItem runtime(Integer stationId, Integer autoing, Integer loading, Integer taskNo) {
src/test/java/com/zy/ai/service/impl/AutoTuneSnapshotServiceImplTest.java
@@ -52,6 +52,19 @@
    }
    @Test
    void buildStationOutBufferCapacityMapPreservesConfiguredCapacity() {
        Map<String, Integer> result = service.buildStationOutBufferCapacityMap(Arrays.asList(
                station(101, 1, null),
                station(102, 1, 0),
                station(103, 1, 3)
        ));
        assertNull(result.get("101"));
        assertEquals(0, result.get("102"));
        assertEquals(3, result.get("103"));
    }
    @Test
    void loadActiveTasksIncludesNullWrkStatusInGroupedCondition() {
        WrkMastService wrkMastService = mock(WrkMastService.class);
        when(wrkMastService.list(any(Wrapper.class))).thenReturn(Collections.emptyList());
@@ -68,7 +81,7 @@
    }
    @Test
    void loadStationOutTaskLimitsOnlyQueriesBasDevpOutStations() {
    void loadOutStationListOnlyQueriesBasDevpOutStations() {
        BasStationService basStationService = mock(BasStationService.class);
        BasDevpService basDevpService = mock(BasDevpService.class);
        ReflectionTestUtils.setField(service, "basStationService", basStationService);
@@ -82,11 +95,11 @@
                station(102, 3)
        ));
        Map<String, Integer> result = ReflectionTestUtils.invokeMethod(service, "loadStationOutTaskLimits");
        java.util.List<BasStation> result = ReflectionTestUtils.invokeMethod(service, "loadOutStationList");
        assertEquals(2, result.size());
        assertEquals(2, result.get("101"));
        assertEquals(3, result.get("102"));
        assertEquals(101, result.get(0).getStationId());
        assertEquals(102, result.get(1).getStationId());
        ArgumentCaptor<Wrapper<BasDevp>> basDevpWrapperCaptor = ArgumentCaptor.forClass(Wrapper.class);
        verify(basDevpService).list(basDevpWrapperCaptor.capture());
@@ -98,23 +111,28 @@
    }
    @Test
    void loadStationOutTaskLimitsDoesNotQueryStationsWhenBasDevpOutStationsAreEmpty() {
    void loadOutStationListDoesNotQueryStationsWhenBasDevpOutStationsAreEmpty() {
        BasStationService basStationService = mock(BasStationService.class);
        BasDevpService basDevpService = mock(BasDevpService.class);
        ReflectionTestUtils.setField(service, "basStationService", basStationService);
        ReflectionTestUtils.setField(service, "basDevpService", basDevpService);
        when(basDevpService.list(any(Wrapper.class))).thenReturn(Collections.singletonList(basDevp(1)));
        Map<String, Integer> result = ReflectionTestUtils.invokeMethod(service, "loadStationOutTaskLimits");
        java.util.List<BasStation> result = ReflectionTestUtils.invokeMethod(service, "loadOutStationList");
        assertTrue(result.isEmpty());
        verify(basStationService, never()).list(any(Wrapper.class));
    }
    private BasStation station(Integer stationId, Integer outTaskLimit) {
        return station(stationId, outTaskLimit, null);
    }
    private BasStation station(Integer stationId, Integer outTaskLimit, Integer outBufferCapacity) {
        BasStation station = new BasStation();
        station.setStationId(stationId);
        station.setOutTaskLimit(outTaskLimit);
        station.setOutBufferCapacity(outBufferCapacity);
        return station;
    }