refactor: move station buffer capacity to bas station
| | |
| | | |
| | | private Map<String, Integer> stationOutTaskLimits; |
| | | |
| | | private Map<String, Integer> stationOutBufferCapacities; |
| | | |
| | | private Map<String, Integer> crnMaxOutTask; |
| | | |
| | | private Map<String, Integer> crnMaxInTask; |
| | |
| | | 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; |
| | |
| | | |
| | | 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( |
| | |
| | | private BasCrnpService basCrnpService; |
| | | @Autowired |
| | | private BasDualCrnpService basDualCrnpService; |
| | | @Autowired |
| | | private StationFlowCapacityService stationFlowCapacityService; |
| | | @Autowired |
| | | private WrkMastService wrkMastService; |
| | | @Autowired |
| | |
| | | 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() + " 必须在 " |
| | |
| | | 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) { |
| | |
| | | )); |
| | | 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)); |
| | |
| | | } |
| | | } |
| | | |
| | | 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() { |
| | |
| | | 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)) { |
| | |
| | | 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; |
| | |
| | | @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); |
| | |
| | | |
| | | AutoTuneFlowTopologyItem item = new AutoTuneFlowTopologyItem(); |
| | | item.setTargetStationId(targetStationId); |
| | | item.setDirection(capacity.getDirectionCode()); |
| | | item.setDirection(DIRECTION_OUT); |
| | | item.setAdjacentStationIds(adjacentStationIds); |
| | | // 当前只存在无向站点邻接图,不能把邻接事实冒充为上下游方向事实。 |
| | | item.setUpstreamStationIds(Collections.emptyList()); |
| | |
| | | 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()); |
| | |
| | | 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() { |
| | |
| | | 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 |
| | |
| | | "- 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" + |
| | |
| | | @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) { |
| | |
| | | <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> |
| | | |
| New file |
| | |
| | | -- 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'; |
| New file |
| | |
| | | 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 是人工维护的出库缓存容量%'; |
| | |
| | | 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: '编 号', |
| | |
| | | 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' |
| | | } |
| | | |
| | | ]); |
| | |
| | | </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> |
| | |
| | | <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> |
| | |
| | | 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; |
| | |
| | | @Mock |
| | | private BasDualCrnpService basDualCrnpService; |
| | | @Mock |
| | | private StationFlowCapacityService stationFlowCapacityService; |
| | | @Mock |
| | | private WrkMastService wrkMastService; |
| | | @Mock |
| | | private RedisUtil redisUtil; |
| | |
| | | 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); |
| | |
| | | |
| | | @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"))); |
| | | |
| | |
| | | List<AiAutoTuneChange> changes = savedChanges(); |
| | | assertEquals("rejected", changes.get(0).getResultStatus()); |
| | | assertTrue(changes.get(0).getRejectReason().contains("需要人工先初始化为有限值")); |
| | | verify(stationFlowCapacityService, never()).getOne(any(Wrapper.class)); |
| | | } |
| | | |
| | | @Test |
| | |
| | | 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"))); |
| | | |
| | |
| | | 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 |
| | |
| | | @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"), |
| | |
| | | @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)); |
| | | |
| | |
| | | } |
| | | |
| | | 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; |
| | | } |
| | | |
| | |
| | | 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, |
| | |
| | | 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; |
| | |
| | | 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)) |
| | | ); |
| | |
| | | @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)) |
| | | ); |
| | |
| | | } |
| | | |
| | | @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), |
| | |
| | | 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) { |
| | |
| | | } |
| | | |
| | | @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()); |
| | |
| | | } |
| | | |
| | | @Test |
| | | void loadStationOutTaskLimitsOnlyQueriesBasDevpOutStations() { |
| | | void loadOutStationListOnlyQueriesBasDevpOutStations() { |
| | | BasStationService basStationService = mock(BasStationService.class); |
| | | BasDevpService basDevpService = mock(BasDevpService.class); |
| | | ReflectionTestUtils.setField(service, "basStationService", basStationService); |
| | |
| | | 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()); |
| | |
| | | } |
| | | |
| | | @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; |
| | | } |
| | | |