package com.zy.ai.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.zy.ai.domain.autotune.AutoTuneApplyRequest; import com.zy.ai.domain.autotune.AutoTuneApplyResult; import com.zy.ai.domain.autotune.AutoTuneChangeCommand; import com.zy.ai.domain.autotune.AutoTuneJobStatus; import com.zy.ai.domain.autotune.AutoTuneRuleDefinition; import com.zy.ai.domain.autotune.AutoTuneTargetType; import com.zy.ai.domain.autotune.AutoTuneTriggerType; import com.zy.ai.entity.AiAutoTuneChange; import com.zy.ai.entity.AiAutoTuneJob; import com.zy.ai.service.AiAutoTuneChangeService; import com.zy.ai.service.AiAutoTuneJobService; import com.zy.ai.service.AutoTuneApplyService; 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.service.BasCrnpService; import com.zy.asrs.service.BasDualCrnpService; import com.zy.asrs.service.BasStationService; import com.zy.asrs.service.StationFlowCapacityService; import com.zy.system.entity.Config; import com.zy.system.service.ConfigService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Objects; @Service("autoTuneApplyService") public class AutoTuneApplyServiceImpl implements AutoTuneApplyService { private static final String PROMPT_SCENE_CODE = "auto_tune_apply"; private static final String DIRECTION_OUT = "OUT"; @Autowired private AiAutoTuneJobService aiAutoTuneJobService; @Autowired private AiAutoTuneChangeService aiAutoTuneChangeService; @Autowired private ConfigService configService; @Autowired private BasStationService basStationService; @Autowired private BasCrnpService basCrnpService; @Autowired private BasDualCrnpService basDualCrnpService; @Autowired private StationFlowCapacityService stationFlowCapacityService; @Override @Transactional public AutoTuneApplyResult apply(AutoTuneApplyRequest request) { AutoTuneApplyRequest safeRequest = request == null ? new AutoTuneApplyRequest() : request; boolean dryRun = Boolean.TRUE.equals(safeRequest.getDryRun()); Date now = new Date(); AiAutoTuneJob job = createJob(safeRequest, dryRun, now); aiAutoTuneJobService.save(job); List validatedChanges = validateChanges(safeRequest, dryRun, now); boolean hasRejectedChange = hasRejectedChange(validatedChanges); if (!dryRun && hasRejectedChange) { markAcceptedChangesAsBatchRejected(validatedChanges); } if (!dryRun && !hasRejectedChange) { applyValidatedChanges(validatedChanges); } List auditChanges = buildAuditChanges(job.getId(), validatedChanges, now); if (!auditChanges.isEmpty()) { aiAutoTuneChangeService.saveBatch(auditChanges); } finishJob(job, safeRequest, auditChanges, dryRun, now); aiAutoTuneJobService.updateById(job); return buildResult(job, auditChanges, dryRun); } @Override @Transactional public AutoTuneApplyResult rollbackLastSuccessfulJob(String reason) { Date now = new Date(); AiAutoTuneJob rollbackJob = createRollbackJob(reason, now); aiAutoTuneJobService.save(rollbackJob); List sourceChanges = findLatestSuccessfulChanges(); if (sourceChanges.isEmpty()) { rollbackJob.setStatus(AutoTuneJobStatus.REJECTED.getCode()); rollbackJob.setFinishTime(now); rollbackJob.setRejectCount(0); rollbackJob.setSuccessCount(0); rollbackJob.setSummary("未找到可回滚的成功调参记录"); rollbackJob.setErrorMessage("未找到可回滚的成功调参记录"); aiAutoTuneJobService.updateById(rollbackJob); return buildResult(rollbackJob, new ArrayList<>(), false); } List rollbackChanges = new ArrayList<>(); boolean refreshedConfigCache = false; for (AiAutoTuneChange sourceChange : sourceChanges) { AiAutoTuneChange rollbackChange = buildRollbackChange(rollbackJob.getId(), sourceChange, now); try { String currentValue = readCurrentValue( sourceChange.getTargetType(), sourceChange.getTargetId(), sourceChange.getTargetKey() ); rollbackChange.setOldValue(currentValue); writeValue(sourceChange.getTargetType(), sourceChange.getTargetId(), sourceChange.getTargetKey(), sourceChange.getOldValue()); if (AutoTuneTargetType.SYS_CONFIG.getCode().equals(sourceChange.getTargetType())) { refreshedConfigCache = true; } rollbackChange.setAppliedValue(sourceChange.getOldValue()); rollbackChange.setResultStatus(ChangeStatus.SUCCESS.getCode()); } catch (Exception exception) { rollbackChange.setResultStatus(ChangeStatus.FAILED.getCode()); rollbackChange.setRejectReason(exception.getMessage()); } rollbackChanges.add(rollbackChange); } if (refreshedConfigCache) { configService.refreshSystemConfigCache(); } aiAutoTuneChangeService.saveBatch(rollbackChanges); finishRollbackJob(rollbackJob, rollbackChanges, now); aiAutoTuneJobService.updateById(rollbackJob); return buildResult(rollbackJob, rollbackChanges, false); } private List validateChanges(AutoTuneApplyRequest request, boolean dryRun, Date now) { List result = new ArrayList<>(); if (request.getChanges() == null || request.getChanges().isEmpty()) { return result; } for (AutoTuneChangeCommand command : request.getChanges()) { result.add(validateChange(command, dryRun, now)); } return result; } private ValidatedChange validateChange(AutoTuneChangeCommand command, boolean dryRun, Date now) { ValidatedChange validatedChange = new ValidatedChange(command); AutoTuneRuleDefinition.Rule rule = AutoTuneRuleDefinition.findRule(command.getTargetType(), command.getTargetKey()); if (rule == null) { return validatedChange.reject("不支持的调参目标: " + command.getTargetType() + "/" + command.getTargetKey()); } validatedChange.setRule(rule); Integer requestedValue = parseRequestedInt(command.getNewValue()); if (requestedValue == null) { return validatedChange.reject(command.getTargetKey() + " 必须为整数"); } validatedChange.setRequestedIntValue(requestedValue); validatedChange.setRequestedValue(String.valueOf(requestedValue)); CurrentValue currentValue = readCurrentValueForValidation(command, rule); if (currentValue.getRejectReason() != null) { return validatedChange.reject(currentValue.getRejectReason()); } validatedChange.setOldValue(currentValue.getOldValue()); Integer maxValue = resolveMaxValue(command, rule, requestedValue); if (maxValue == null) { return validatedChange.reject("站点 " + command.getTargetId() + " 缺少 OUT 方向 bufferCapacity,无法证明 outTaskLimit 上限"); } if (requestedValue < rule.getMinValue() || requestedValue > maxValue) { return validatedChange.reject(command.getTargetKey() + " 必须在 " + rule.getMinValue() + "~" + maxValue + " 范围内"); } if (Objects.equals(currentValue.getNumericValue(), requestedValue)) { return validatedChange.accept(ChangeStatus.NO_CHANGE, null); } int step = Math.abs(requestedValue - currentValue.getNumericValue()); if (step > rule.getMaxStep()) { return validatedChange.reject(command.getTargetKey() + " 单次调整步长不能超过 " + rule.getMaxStep()); } Date cooldownExpireTime = findCooldownExpireTime(command, now); if (cooldownExpireTime != null) { validatedChange.setCooldownExpireTime(cooldownExpireTime); return validatedChange.reject("目标仍在冷却期,冷却截止时间: " + cooldownExpireTime); } Date nextCooldownExpireTime = new Date(now.getTime() + rule.getCooldownMinutes() * 60_000L); validatedChange.setCooldownExpireTime(nextCooldownExpireTime); return validatedChange.accept(dryRun ? ChangeStatus.DRY_RUN : ChangeStatus.PENDING, null); } private CurrentValue readCurrentValueForValidation(AutoTuneChangeCommand command, AutoTuneRuleDefinition.Rule rule) { AutoTuneTargetType targetType = rule.getTargetType(); Integer targetId = parseTargetId(command.getTargetId(), targetType); if (!AutoTuneTargetType.SYS_CONFIG.equals(targetType) && targetId == null) { return CurrentValue.rejected("targetId 必须为整数"); } if (AutoTuneTargetType.SYS_CONFIG.equals(targetType)) { Config config = configService.getOne(new QueryWrapper().eq("code", command.getTargetKey()).last("limit 1")); if (config == null) { return CurrentValue.rejected("运行参数不存在: " + command.getTargetKey()); } return numericCurrentValue(config.getValue(), false, command.getTargetKey()); } if (AutoTuneTargetType.STATION.equals(targetType)) { BasStation station = basStationService.getById(targetId); if (station == null) { return CurrentValue.rejected("站点不存在: " + command.getTargetId()); } return numericCurrentValue(toText(station.getOutTaskLimit()), true, command.getTargetKey()); } if (AutoTuneTargetType.CRN.equals(targetType)) { BasCrnp crnp = basCrnpService.getById(targetId); if (crnp == null) { return CurrentValue.rejected("堆垛机不存在: " + command.getTargetId()); } Integer value = "maxOutTask".equals(command.getTargetKey()) ? crnp.getMaxOutTask() : crnp.getMaxInTask(); return numericCurrentValue(toText(value), false, command.getTargetKey()); } BasDualCrnp dualCrnp = basDualCrnpService.getById(targetId); if (dualCrnp == null) { return CurrentValue.rejected("双工位堆垛机不存在: " + command.getTargetId()); } Integer value = "maxOutTask".equals(command.getTargetKey()) ? dualCrnp.getMaxOutTask() : dualCrnp.getMaxInTask(); return numericCurrentValue(toText(value), false, command.getTargetKey()); } private CurrentValue numericCurrentValue(String oldValue, boolean nullOrNegativeAsZero, String targetKey) { if (oldValue == null || oldValue.trim().isEmpty()) { if (nullOrNegativeAsZero) { return CurrentValue.accepted(null, 0); } return CurrentValue.rejected(targetKey + " 当前值为空,无法计算步长"); } try { Integer parsedValue = Integer.valueOf(oldValue.trim()); if (nullOrNegativeAsZero && parsedValue < 0) { return CurrentValue.accepted(oldValue, 0); } return CurrentValue.accepted(oldValue, parsedValue); } catch (Exception exception) { return CurrentValue.rejected(targetKey + " 当前值不是整数,无法计算步长"); } } private Integer resolveMaxValue(AutoTuneChangeCommand command, AutoTuneRuleDefinition.Rule rule, Integer requestedValue) { if (!rule.isDynamicMaxValue()) { return rule.getMaxValue(); } Integer targetId = parseTargetId(command.getTargetId(), rule.getTargetType()); StationFlowCapacity capacity = stationFlowCapacityService.getOne( new QueryWrapper() .eq("station_id", targetId) .eq("direction_code", DIRECTION_OUT) .last("limit 1") ); if (capacity == null || capacity.getBufferCapacity() == null) { return requestedValue == 0 ? 0 : null; } return Math.max(0, capacity.getBufferCapacity()); } private Date findCooldownExpireTime(AutoTuneChangeCommand command, Date now) { String targetId = normalizeTargetId(command.getTargetType(), command.getTargetId()); List recentChanges = aiAutoTuneChangeService.list( new QueryWrapper() .eq("target_type", command.getTargetType()) .eq("target_id", targetId) .eq("target_key", command.getTargetKey()) .eq("result_status", ChangeStatus.SUCCESS.getCode()) .gt("cooldown_expire_time", now) .orderByDesc("create_time") .last("limit 1") ); if (recentChanges == null || recentChanges.isEmpty()) { return null; } AiAutoTuneChange latestChange = recentChanges.get(0); Date cooldownExpireTime = latestChange.getCooldownExpireTime(); if (cooldownExpireTime == null || !cooldownExpireTime.after(now)) { return null; } return cooldownExpireTime; } private void applyValidatedChanges(List validatedChanges) { boolean refreshConfigCache = false; for (ValidatedChange validatedChange : validatedChanges) { if (ChangeStatus.NO_CHANGE.equals(validatedChange.getStatus())) { continue; } AutoTuneChangeCommand command = validatedChange.getCommand(); writeValue(command.getTargetType(), command.getTargetId(), command.getTargetKey(), validatedChange.getRequestedValue()); validatedChange.accept(ChangeStatus.SUCCESS, null); validatedChange.setAppliedValue(validatedChange.getRequestedValue()); if (AutoTuneTargetType.SYS_CONFIG.getCode().equals(command.getTargetType())) { refreshConfigCache = true; } } if (refreshConfigCache) { configService.refreshSystemConfigCache(); } } private void writeValue(String targetType, String targetId, String targetKey, String value) { AutoTuneTargetType parsedTargetType = AutoTuneTargetType.fromCode(targetType); if (AutoTuneTargetType.SYS_CONFIG.equals(parsedTargetType)) { if (!configService.saveConfigValue(targetKey, value)) { throw new IllegalStateException("保存运行参数失败: " + targetKey); } return; } Integer parsedTargetId = parseTargetId(targetId, parsedTargetType); Integer intValue = value == null ? null : Integer.valueOf(value); if (AutoTuneTargetType.STATION.equals(parsedTargetType)) { boolean updated = basStationService.update(new UpdateWrapper() .eq("station_id", parsedTargetId) .set("out_task_limit", intValue)); if (!updated) { throw new IllegalStateException("保存站点参数失败: " + targetId + "/" + targetKey); } return; } if (AutoTuneTargetType.CRN.equals(parsedTargetType)) { String column = "maxOutTask".equals(targetKey) ? "max_out_task" : "max_in_task"; boolean updated = basCrnpService.update(new UpdateWrapper() .eq("crn_no", parsedTargetId) .set(column, intValue)); if (!updated) { throw new IllegalStateException("保存堆垛机参数失败: " + targetId + "/" + targetKey); } return; } String column = "maxOutTask".equals(targetKey) ? "max_out_task" : "max_in_task"; boolean updated = basDualCrnpService.update(new UpdateWrapper() .eq("crn_no", parsedTargetId) .set(column, intValue)); if (!updated) { throw new IllegalStateException("保存双工位堆垛机参数失败: " + targetId + "/" + targetKey); } } private String readCurrentValue(String targetType, String targetId, String targetKey) { AutoTuneTargetType parsedTargetType = AutoTuneTargetType.fromCode(targetType); Integer parsedTargetId = parseTargetId(targetId, parsedTargetType); if (AutoTuneTargetType.SYS_CONFIG.equals(parsedTargetType)) { Config config = configService.getOne(new QueryWrapper().eq("code", targetKey).last("limit 1")); return config == null ? null : config.getValue(); } if (AutoTuneTargetType.STATION.equals(parsedTargetType)) { BasStation station = basStationService.getById(parsedTargetId); return station == null ? null : toText(station.getOutTaskLimit()); } if (AutoTuneTargetType.CRN.equals(parsedTargetType)) { BasCrnp crnp = basCrnpService.getById(parsedTargetId); if (crnp == null) { return null; } return toText("maxOutTask".equals(targetKey) ? crnp.getMaxOutTask() : crnp.getMaxInTask()); } BasDualCrnp dualCrnp = basDualCrnpService.getById(parsedTargetId); if (dualCrnp == null) { return null; } return toText("maxOutTask".equals(targetKey) ? dualCrnp.getMaxOutTask() : dualCrnp.getMaxInTask()); } private AiAutoTuneJob createJob(AutoTuneApplyRequest request, boolean dryRun, Date now) { AiAutoTuneJob job = new AiAutoTuneJob(); job.setTriggerType(AutoTuneTriggerType.normalize(request.getTriggerType())); job.setStatus(AutoTuneJobStatus.RUNNING.getCode()); job.setStartTime(now); job.setHasActiveTasks(0); job.setPromptSceneCode(PROMPT_SCENE_CODE); job.setSummary(dryRun ? "AI自动调参 dry-run: " + safeReason(request.getReason()) : safeReason(request.getReason())); job.setIntervalBefore(readIntervalMinutes()); job.setSuccessCount(0); job.setRejectCount(0); job.setLlmCallCount(0); job.setPromptTokens(0); job.setCompletionTokens(0); job.setTotalTokens(0); job.setCreateTime(now); return job; } private AiAutoTuneJob createRollbackJob(String reason, Date now) { AiAutoTuneJob job = new AiAutoTuneJob(); job.setTriggerType(AutoTuneTriggerType.ROLLBACK.getCode()); job.setStatus(AutoTuneJobStatus.RUNNING.getCode()); job.setStartTime(now); job.setHasActiveTasks(0); job.setPromptSceneCode(PROMPT_SCENE_CODE); job.setSummary(safeReason(reason)); job.setIntervalBefore(readIntervalMinutes()); job.setSuccessCount(0); job.setRejectCount(0); job.setLlmCallCount(0); job.setPromptTokens(0); job.setCompletionTokens(0); job.setTotalTokens(0); job.setCreateTime(now); return job; } private void finishJob(AiAutoTuneJob job, AutoTuneApplyRequest request, List auditChanges, boolean dryRun, Date now) { int successCount = countAccepted(auditChanges); int rejectCount = countRejected(auditChanges); job.setFinishTime(now); job.setSuccessCount(successCount); job.setRejectCount(rejectCount); job.setIntervalAfter(readIntervalMinutes()); job.setStatus(resolveJobStatus(auditChanges, dryRun)); job.setSummary(buildSummary(request.getReason(), successCount, rejectCount, dryRun)); if (rejectCount > 0) { job.setErrorMessage(firstRejectReason(auditChanges)); } } private void finishRollbackJob(AiAutoTuneJob job, List changes, Date now) { int successCount = countAccepted(changes); int rejectCount = countRejected(changes); job.setFinishTime(now); job.setSuccessCount(successCount); job.setRejectCount(rejectCount); job.setIntervalAfter(readIntervalMinutes()); job.setStatus(rejectCount == 0 ? AutoTuneJobStatus.SUCCESS.getCode() : AutoTuneJobStatus.PARTIAL_SUCCESS.getCode()); job.setSummary("回滚最近一次成功调参,成功 " + successCount + " 项,失败 " + rejectCount + " 项"); if (rejectCount > 0) { job.setErrorMessage(firstRejectReason(changes)); } } private String resolveJobStatus(List auditChanges, boolean dryRun) { if (auditChanges.isEmpty()) { return AutoTuneJobStatus.NO_CHANGE.getCode(); } int rejectedCount = countRejected(auditChanges); int acceptedCount = countAccepted(auditChanges); if (rejectedCount == auditChanges.size()) { return AutoTuneJobStatus.REJECTED.getCode(); } if (rejectedCount > 0) { return dryRun ? AutoTuneJobStatus.PARTIAL_SUCCESS.getCode() : AutoTuneJobStatus.REJECTED.getCode(); } if (acceptedCount == 0) { return AutoTuneJobStatus.NO_CHANGE.getCode(); } boolean allNoChange = true; for (AiAutoTuneChange change : auditChanges) { if (!ChangeStatus.NO_CHANGE.getCode().equals(change.getResultStatus())) { allNoChange = false; break; } } return allNoChange ? AutoTuneJobStatus.NO_CHANGE.getCode() : AutoTuneJobStatus.SUCCESS.getCode(); } private List buildAuditChanges(Long jobId, List validatedChanges, Date now) { List auditChanges = new ArrayList<>(); for (ValidatedChange validatedChange : validatedChanges) { AiAutoTuneChange change = new AiAutoTuneChange(); AutoTuneChangeCommand command = validatedChange.getCommand(); change.setJobId(jobId); change.setTargetType(command.getTargetType()); change.setTargetId(normalizeTargetId(command.getTargetType(), command.getTargetId())); change.setTargetKey(command.getTargetKey()); change.setOldValue(validatedChange.getOldValue()); change.setRequestedValue(validatedChange.getRequestedValue() == null ? command.getNewValue() : validatedChange.getRequestedValue()); change.setAppliedValue(validatedChange.getAppliedValue()); change.setResultStatus(validatedChange.getStatus().getCode()); change.setRejectReason(validatedChange.getRejectReason()); change.setCooldownExpireTime(validatedChange.getCooldownExpireTime()); change.setCreateTime(now); auditChanges.add(change); } return auditChanges; } private AiAutoTuneChange buildRollbackChange(Long jobId, AiAutoTuneChange sourceChange, Date now) { AiAutoTuneChange rollbackChange = new AiAutoTuneChange(); rollbackChange.setJobId(jobId); rollbackChange.setTargetType(sourceChange.getTargetType()); rollbackChange.setTargetId(sourceChange.getTargetId()); rollbackChange.setTargetKey(sourceChange.getTargetKey()); rollbackChange.setRequestedValue(sourceChange.getOldValue()); rollbackChange.setCreateTime(now); return rollbackChange; } private List findLatestSuccessfulChanges() { List jobs = aiAutoTuneJobService.list( new QueryWrapper() .eq("status", AutoTuneJobStatus.SUCCESS.getCode()) .ne("trigger_type", AutoTuneTriggerType.ROLLBACK.getCode()) .orderByDesc("finish_time") .last("limit 20") ); if (jobs == null || jobs.isEmpty()) { return new ArrayList<>(); } for (AiAutoTuneJob job : jobs) { List changes = aiAutoTuneChangeService.list( new QueryWrapper() .eq("job_id", job.getId()) .eq("result_status", ChangeStatus.SUCCESS.getCode()) .orderByAsc("id") ); if (changes != null && !changes.isEmpty()) { return changes; } } return new ArrayList<>(); } private AutoTuneApplyResult buildResult(AiAutoTuneJob job, List changes, boolean dryRun) { AutoTuneApplyResult result = new AutoTuneApplyResult(); result.setDryRun(dryRun); result.setSuccess(AutoTuneJobStatus.SUCCESS.getCode().equals(job.getStatus()) || AutoTuneJobStatus.NO_CHANGE.getCode().equals(job.getStatus())); result.setJobId(job.getId()); result.setSummary(job.getSummary()); result.setSuccessCount(job.getSuccessCount()); result.setRejectCount(job.getRejectCount()); result.setChanges(changes); return result; } private void markAcceptedChangesAsBatchRejected(List validatedChanges) { for (ValidatedChange validatedChange : validatedChanges) { if (ChangeStatus.REJECTED.equals(validatedChange.getStatus())) { continue; } if (ChangeStatus.NO_CHANGE.equals(validatedChange.getStatus())) { continue; } validatedChange.reject("同批次存在被拒绝变更,未执行写入"); } } private boolean hasRejectedChange(List validatedChanges) { for (ValidatedChange validatedChange : validatedChanges) { if (ChangeStatus.REJECTED.equals(validatedChange.getStatus())) { return true; } } return false; } private Integer parseRequestedInt(String value) { if (value == null || value.trim().isEmpty()) { return null; } try { return Integer.valueOf(value.trim()); } catch (Exception exception) { return null; } } private Integer parseTargetId(String targetId, AutoTuneTargetType targetType) { if (AutoTuneTargetType.SYS_CONFIG.equals(targetType)) { return null; } if (targetId == null || targetId.trim().isEmpty()) { return null; } try { return Integer.valueOf(targetId.trim()); } catch (Exception exception) { return null; } } private String normalizeTargetId(String targetType, String targetId) { AutoTuneTargetType parsedTargetType = AutoTuneTargetType.fromCode(targetType); if (AutoTuneTargetType.SYS_CONFIG.equals(parsedTargetType)) { return ""; } return targetId == null ? "" : targetId.trim(); } private int readIntervalMinutes() { String value = configService.getConfigValue("aiAutoTuneIntervalMinutes", null); if (value == null || value.trim().isEmpty()) { return 0; } try { return Integer.parseInt(value.trim()); } catch (Exception exception) { return 0; } } private String toText(Integer value) { return value == null ? null : String.valueOf(value); } private String safeReason(String reason) { return reason == null ? "" : reason.trim(); } private String buildSummary(String reason, int successCount, int rejectCount, boolean dryRun) { String prefix = dryRun ? "AI自动调参 dry-run" : "AI自动调参"; return prefix + ",成功 " + successCount + " 项,拒绝 " + rejectCount + " 项。原因: " + safeReason(reason); } private int countAccepted(List changes) { int count = 0; for (AiAutoTuneChange change : changes) { if (ChangeStatus.SUCCESS.getCode().equals(change.getResultStatus()) || ChangeStatus.DRY_RUN.getCode().equals(change.getResultStatus()) || ChangeStatus.NO_CHANGE.getCode().equals(change.getResultStatus())) { count++; } } return count; } private int countRejected(List changes) { int count = 0; for (AiAutoTuneChange change : changes) { if (ChangeStatus.REJECTED.getCode().equals(change.getResultStatus()) || ChangeStatus.FAILED.getCode().equals(change.getResultStatus())) { count++; } } return count; } private String firstRejectReason(List changes) { for (AiAutoTuneChange change : changes) { if (change.getRejectReason() != null && !change.getRejectReason().trim().isEmpty()) { return change.getRejectReason(); } } return null; } private static class ValidatedChange { private final AutoTuneChangeCommand command; private AutoTuneRuleDefinition.Rule rule; private String oldValue; private String requestedValue; private String appliedValue; private Integer requestedIntValue; private ChangeStatus status = ChangeStatus.REJECTED; private String rejectReason; private Date cooldownExpireTime; private ValidatedChange(AutoTuneChangeCommand command) { this.command = command == null ? new AutoTuneChangeCommand() : command; } private ValidatedChange reject(String reason) { this.status = ChangeStatus.REJECTED; this.rejectReason = reason; this.appliedValue = null; return this; } private ValidatedChange accept(ChangeStatus status, String reason) { this.status = status; this.rejectReason = reason; if (ChangeStatus.NO_CHANGE.equals(status)) { this.appliedValue = this.oldValue; } return this; } public AutoTuneChangeCommand getCommand() { return command; } public AutoTuneRuleDefinition.Rule getRule() { return rule; } public void setRule(AutoTuneRuleDefinition.Rule rule) { this.rule = rule; } public String getOldValue() { return oldValue; } public void setOldValue(String oldValue) { this.oldValue = oldValue; } public String getRequestedValue() { return requestedValue; } public void setRequestedValue(String requestedValue) { this.requestedValue = requestedValue; } public String getAppliedValue() { return appliedValue; } public void setAppliedValue(String appliedValue) { this.appliedValue = appliedValue; } public Integer getRequestedIntValue() { return requestedIntValue; } public void setRequestedIntValue(Integer requestedIntValue) { this.requestedIntValue = requestedIntValue; } public ChangeStatus getStatus() { return status; } public String getRejectReason() { return rejectReason; } public Date getCooldownExpireTime() { return cooldownExpireTime; } public void setCooldownExpireTime(Date cooldownExpireTime) { this.cooldownExpireTime = cooldownExpireTime; } } private static class CurrentValue { private final String oldValue; private final Integer numericValue; private final String rejectReason; private CurrentValue(String oldValue, Integer numericValue, String rejectReason) { this.oldValue = oldValue; this.numericValue = numericValue; this.rejectReason = rejectReason; } private static CurrentValue accepted(String oldValue, Integer numericValue) { return new CurrentValue(oldValue, numericValue, null); } private static CurrentValue rejected(String rejectReason) { return new CurrentValue(null, null, rejectReason); } public String getOldValue() { return oldValue; } public Integer getNumericValue() { return numericValue; } public String getRejectReason() { return rejectReason; } } private enum ChangeStatus { PENDING("pending"), SUCCESS("success"), REJECTED("rejected"), FAILED("failed"), DRY_RUN("dry_run"), NO_CHANGE("no_change"); private final String code; ChangeStatus(String code) { this.code = code; } public String getCode() { return code; } } }