| | |
| | | package com.zy.acs.manager.core.service; |
| | | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.zy.acs.common.enums.AgvStatusType; |
| | | import com.zy.acs.manager.manager.entity.Agv; |
| | | import com.zy.acs.manager.manager.entity.AgvDetail; |
| | | import com.zy.acs.manager.manager.entity.Guarantee; |
| | | import com.zy.acs.manager.manager.entity.Task; |
| | | import com.zy.acs.manager.manager.enums.StatusType; |
| | | import com.zy.acs.manager.manager.enums.TaskStsType; |
| | | import com.zy.acs.manager.manager.enums.TaskTypeType; |
| | | import com.zy.acs.manager.manager.service.AgvDetailService; |
| | | import com.zy.acs.manager.manager.service.AgvService; |
| | | import com.zy.acs.manager.manager.service.TaskService; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Data; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import java.time.LocalDateTime; |
| | | import java.util.ArrayList; |
| | | import java.util.Comparator; |
| | | import java.util.List; |
| | | import java.util.Objects; |
| | | import java.util.Optional; |
| | | |
| | | /** |
| | | * Runtime guarantee orchestration entry. |
| | | */ |
| | | public interface GuaranteeRuntimeService { |
| | | @Slf4j |
| | | @Service |
| | | public class GuaranteeRuntimeService { |
| | | |
| | | /** |
| | | * Lead time entrance: make sure enough vehicles can reach the target window. |
| | | */ |
| | | void prepare(Guarantee plan, LocalDateTime targetTime); |
| | | private final AgvService agvService; |
| | | private final AgvDetailService agvDetailService; |
| | | private final TaskService taskService; |
| | | private final MainLockWrapService mainLockWrapService; |
| | | |
| | | /** |
| | | * Lock stage: restrict non-essential dispatching for reserve vehicles. |
| | | */ |
| | | void lock(Guarantee plan, LocalDateTime targetTime); |
| | | public GuaranteeRuntimeService(AgvService agvService, |
| | | AgvDetailService agvDetailService, |
| | | TaskService taskService, |
| | | MainLockWrapService mainLockWrapService) { |
| | | this.agvService = agvService; |
| | | this.agvDetailService = agvDetailService; |
| | | this.taskService = taskService; |
| | | this.mainLockWrapService = mainLockWrapService; |
| | | } |
| | | |
| | | /** |
| | | * Executed repeatedly during the guarantee window to keep SLA. |
| | | */ |
| | | void checkWindow(Guarantee plan, LocalDateTime targetTime); |
| | | public void prepare(Guarantee plan, LocalDateTime targetTime) { |
| | | CapacitySnapshot snapshot = evaluate(plan); |
| | | if (snapshot.availableCount >= plan.getRequiredCount()) { |
| | | log.debug("Guarantee[{}] already has {} available vehicles for {}", |
| | | plan.getName(), snapshot.availableCount, targetTime); |
| | | return; |
| | | } |
| | | int shortage = plan.getRequiredCount() - snapshot.availableCount; |
| | | log.info("Guarantee[{}] shortage {} vehicles for {} (minSoc={}%), scheduling charge.", |
| | | plan.getName(), shortage, targetTime, plan.getMinSoc()); |
| | | dispatchChargeTasks(snapshot, shortage); |
| | | } |
| | | |
| | | /** |
| | | * Called once after the window ends to release state/records. |
| | | */ |
| | | void finish(Guarantee plan, LocalDateTime targetTime); |
| | | public void lock(Guarantee plan, LocalDateTime targetTime) { |
| | | log.info("Guarantee[{}] entering lock stage for {}", plan.getName(), targetTime); |
| | | // TODO persist lock state / inform dispatcher once integration is ready |
| | | } |
| | | |
| | | public void checkWindow(Guarantee plan, LocalDateTime targetTime) { |
| | | CapacitySnapshot snapshot = evaluate(plan); |
| | | if (snapshot.availableCount < plan.getRequiredCount()) { |
| | | int shortage = plan.getRequiredCount() - snapshot.availableCount; |
| | | log.warn("Guarantee[{}] window shortage {} vehicles for {}. Trigger quick recharge.", |
| | | plan.getName(), shortage, targetTime); |
| | | dispatchChargeTasks(snapshot, shortage); |
| | | } |
| | | } |
| | | |
| | | public void finish(Guarantee plan, LocalDateTime targetTime) { |
| | | log.info("Guarantee[{}] finished window for {}", plan.getName(), targetTime); |
| | | // TODO release any lock/flag once detail implementation is defined |
| | | } |
| | | |
| | | private CapacitySnapshot evaluate(Guarantee plan) { |
| | | int minSoc = Optional.ofNullable(plan.getMinSoc()).orElse(50); |
| | | List<Agv> scopedAgvs = findScopedAgvs(plan); |
| | | int available = 0; |
| | | List<ChargeCandidate> candidates = new ArrayList<>(); |
| | | for (Agv agv : scopedAgvs) { |
| | | AgvDetail detail = agvDetailService.selectByAgvId(agv.getId()); |
| | | if (detail == null || detail.getSoc() == null) { |
| | | continue; |
| | | } |
| | | if (detail.getSoc() >= minSoc && isAvailable(agv, detail)) { |
| | | available++; |
| | | } else { |
| | | candidates.add(new ChargeCandidate(agv, detail)); |
| | | } |
| | | } |
| | | candidates.sort(Comparator.comparingInt(o -> Optional.ofNullable(o.detail.getSoc()).orElse(0))); |
| | | return new CapacitySnapshot(available, candidates); |
| | | } |
| | | |
| | | private boolean isAvailable(Agv agv, AgvDetail detail) { |
| | | if (!Objects.equals(agv.getStatus(), StatusType.ENABLE.val)) { |
| | | return false; |
| | | } |
| | | if (detail.getAgvStatus() != null && detail.getAgvStatus().equals(AgvStatusType.CHARGE)) { |
| | | return false; |
| | | } |
| | | boolean busy = taskService.count(new LambdaQueryWrapper<Task>() |
| | | .eq(Task::getAgvId, agv.getId()) |
| | | .in(Task::getTaskSts, |
| | | TaskStsType.WAITING.val(), |
| | | TaskStsType.ASSIGN.val(), |
| | | TaskStsType.PROGRESS.val())) > 0; |
| | | return !busy; |
| | | } |
| | | |
| | | private void dispatchChargeTasks(CapacitySnapshot snapshot, int shortage) { |
| | | if (shortage <= 0) { |
| | | return; |
| | | } |
| | | int scheduled = 0; |
| | | for (ChargeCandidate candidate : snapshot.candidates) { |
| | | if (scheduled >= shortage) { |
| | | break; |
| | | } |
| | | log.info("Scheduling AGV [{}] for charging to support guarantee", candidate.agv.getName()); |
| | | mainLockWrapService.buildMinorTask(candidate.agv.getId(), TaskTypeType.TO_CHARGE, null, null); |
| | | scheduled++; |
| | | } |
| | | } |
| | | |
| | | private List<Agv> findScopedAgvs(Guarantee plan) { |
| | | LambdaQueryWrapper<Agv> wrapper = new LambdaQueryWrapper<Agv>() |
| | | .eq(Agv::getStatus, StatusType.ENABLE.val); |
| | | if ("MODEL".equalsIgnoreCase(plan.getScopeType()) && plan.getScopeValue() != null) { |
| | | try { |
| | | wrapper.eq(Agv::getAgvModel, Long.valueOf(plan.getScopeValue())); |
| | | } catch (NumberFormatException ignore) { |
| | | log.warn("Guarantee[{}] invalid scopeValue {}", plan.getName(), plan.getScopeValue()); |
| | | } |
| | | } |
| | | return agvService.list(wrapper); |
| | | } |
| | | |
| | | @Data |
| | | private static class CapacitySnapshot { |
| | | private final int availableCount; |
| | | private final List<ChargeCandidate> candidates; |
| | | } |
| | | |
| | | @Data |
| | | @AllArgsConstructor |
| | | private static class ChargeCandidate { |
| | | private Agv agv; |
| | | private AgvDetail detail; |
| | | } |
| | | } |
| | | |