From 63b01db83d9aad8a15276b4236a9a22e4aeef065 Mon Sep 17 00:00:00 2001
From: Junjie <fallin.jie@qq.com>
Date: 星期二, 05 五月 2026 12:30:59 +0800
Subject: [PATCH] # Agent数据分析V3.0.1.7

---
 src/main/java/com/zy/ai/service/impl/AutoTuneApplyServiceImpl.java |  321 +++++++++++++++++++++++++++++++++++++++++++----------
 1 files changed, 260 insertions(+), 61 deletions(-)

diff --git a/src/main/java/com/zy/ai/service/impl/AutoTuneApplyServiceImpl.java b/src/main/java/com/zy/ai/service/impl/AutoTuneApplyServiceImpl.java
index 3c42210..ab23dd9 100644
--- a/src/main/java/com/zy/ai/service/impl/AutoTuneApplyServiceImpl.java
+++ b/src/main/java/com/zy/ai/service/impl/AutoTuneApplyServiceImpl.java
@@ -5,6 +5,7 @@
 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.AutoTuneControlModeSnapshot;
 import com.zy.ai.domain.autotune.AutoTuneJobStatus;
 import com.zy.ai.domain.autotune.AutoTuneRuleDefinition;
 import com.zy.ai.domain.autotune.AutoTuneTargetType;
@@ -14,16 +15,18 @@
 import com.zy.ai.service.AiAutoTuneChangeService;
 import com.zy.ai.service.AiAutoTuneJobService;
 import com.zy.ai.service.AutoTuneApplyService;
+import com.zy.ai.service.AutoTuneControlModeService;
 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;
+import com.zy.core.enums.WrkStsType;
 import com.zy.system.entity.Config;
 import com.zy.system.service.ConfigService;
 import org.slf4j.Logger;
@@ -34,6 +37,7 @@
 import org.springframework.transaction.support.TransactionTemplate;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Date;
 import java.util.HashSet;
 import java.util.List;
@@ -46,9 +50,17 @@
 
     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 String ANALYSIS_ONLY_REJECT_REASON = "浠呭垎鏋愭ā寮忕姝㈠疄闄呭簲鐢�/鍥炴粴锛屾湭淇敼杩愯鍙傛暟";
+    private static final List<Long> FINAL_WRK_STS_LIST = Arrays.asList(
+            WrkStsType.COMPLETE_INBOUND.sts,
+            WrkStsType.SETTLE_INBOUND.sts,
+            WrkStsType.COMPLETE_OUTBOUND.sts,
+            WrkStsType.SETTLE_OUTBOUND.sts,
+            WrkStsType.COMPLETE_LOC_MOVE.sts,
+            WrkStsType.COMPLETE_CRN_MOVE.sts
+    );
 
     @Autowired
     private AiAutoTuneJobService aiAutoTuneJobService;
@@ -57,13 +69,15 @@
     @Autowired
     private ConfigService configService;
     @Autowired
+    private AutoTuneControlModeService autoTuneControlModeService;
+    @Autowired
     private BasStationService basStationService;
     @Autowired
     private BasCrnpService basCrnpService;
     @Autowired
     private BasDualCrnpService basDualCrnpService;
     @Autowired
-    private StationFlowCapacityService stationFlowCapacityService;
+    private WrkMastService wrkMastService;
     @Autowired
     private PlatformTransactionManager transactionManager;
     @Autowired
@@ -72,17 +86,24 @@
     @Override
     public AutoTuneApplyResult apply(AutoTuneApplyRequest request) {
         AutoTuneApplyRequest safeRequest = request == null ? new AutoTuneApplyRequest() : request;
+        AutoTuneControlModeSnapshot controlMode = currentControlModeSnapshot();
         boolean dryRun = Boolean.TRUE.equals(safeRequest.getDryRun());
         Date now = new Date();
         AiAutoTuneJob job = createJob(safeRequest, dryRun, now);
 
         if (dryRun) {
-            return applyDryRun(safeRequest, job, now);
+            return applyDryRun(safeRequest, job, now, controlMode);
         }
-        return applyRealWithLock(safeRequest, job, now);
+        if (isAnalysisOnly(controlMode)) {
+            return rejectRealApplyForAnalysisOnly(safeRequest, job, now, controlMode);
+        }
+        return applyRealWithLock(safeRequest, job, now, controlMode);
     }
 
-    private AutoTuneApplyResult applyDryRun(AutoTuneApplyRequest request, AiAutoTuneJob job, Date now) {
+    private AutoTuneApplyResult applyDryRun(AutoTuneApplyRequest request,
+                                            AiAutoTuneJob job,
+                                            Date now,
+                                            AutoTuneControlModeSnapshot controlMode) {
         List<ValidatedChange> validatedChanges = validateChanges(request, true, now);
         ApplyPersistenceResult persistenceResult = persistApplyResultInTransaction(
                 job,
@@ -92,10 +113,13 @@
                 now,
                 false
         );
-        return buildResult(job, persistenceResult.getAuditChanges(), true);
+        return buildResult(job, persistenceResult.getAuditChanges(), true, controlMode);
     }
 
-    private AutoTuneApplyResult applyRealWithLock(AutoTuneApplyRequest request, AiAutoTuneJob job, Date now) {
+    private AutoTuneApplyResult applyRealWithLock(AutoTuneApplyRequest request,
+                                                  AiAutoTuneJob job,
+                                                  Date now,
+                                                  AutoTuneControlModeSnapshot controlMode) {
         if (request.getChanges() == null || request.getChanges().isEmpty()) {
             ApplyPersistenceResult persistenceResult = persistApplyResultInTransaction(
                     job,
@@ -105,13 +129,13 @@
                     now,
                     false
             );
-            return buildResult(job, persistenceResult.getAuditChanges(), false);
+            return buildResult(job, persistenceResult.getAuditChanges(), false, controlMode);
         }
 
         String lockKey = RedisKeyType.AI_AUTO_TUNE_APPLY_LOCK.key;
         String lockToken = UUID.randomUUID().toString();
         if (!redisUtil.trySetStringIfAbsent(lockKey, lockToken, APPLY_LOCK_SECONDS)) {
-            return rejectRealApplyForUnavailableLock(request, job, now, lockKey);
+            return rejectRealApplyForUnavailableLock(request, job, now, lockKey, controlMode);
         }
 
         try {
@@ -129,7 +153,7 @@
                         now,
                         false
                 );
-                return buildResult(job, persistenceResult.getAuditChanges(), false);
+                return buildResult(job, persistenceResult.getAuditChanges(), false, controlMode);
             }
 
             try {
@@ -141,8 +165,8 @@
                         now,
                         true
                 );
-                refreshSystemConfigCacheIfNeeded(persistenceResult);
-                return buildResult(job, persistenceResult.getAuditChanges(), false);
+                refreshSystemConfigCacheSafely(persistenceResult);
+                return buildResult(job, persistenceResult.getAuditChanges(), false, controlMode);
             } catch (RuntimeException exception) {
                 markWriteFailure(validatedChanges, exception);
                 Date failureNow = new Date();
@@ -155,7 +179,7 @@
                         failureNow,
                         false
                 );
-                return buildResult(failureJob, persistenceResult.getAuditChanges(), false);
+                return buildResult(failureJob, persistenceResult.getAuditChanges(), false, controlMode);
             }
         } finally {
             redisUtil.compareAndDelete(lockKey, lockToken);
@@ -165,7 +189,8 @@
     private AutoTuneApplyResult rejectRealApplyForUnavailableLock(AutoTuneApplyRequest request,
                                                                   AiAutoTuneJob job,
                                                                   Date now,
-                                                                  String lockKey) {
+                                                                  String lockKey,
+                                                                  AutoTuneControlModeSnapshot controlMode) {
         boolean lockKeyExists = redisUtil.hasKey(lockKey);
         LOGGER.warn("鐢宠AI鑷姩璋冨弬 apply 閿佸け璐ワ紝lockKey={}, lockKeyExists={}", lockKey, lockKeyExists);
         List<ValidatedChange> validatedChanges = buildLockBusyChanges(request);
@@ -177,7 +202,18 @@
                 now,
                 false
         );
-        return buildResult(job, persistenceResult.getAuditChanges(), false);
+        return buildResult(job, persistenceResult.getAuditChanges(), false, controlMode);
+    }
+
+    private AutoTuneApplyResult rejectRealApplyForAnalysisOnly(AutoTuneApplyRequest request,
+                                                               AiAutoTuneJob job,
+                                                               Date now,
+                                                               AutoTuneControlModeSnapshot controlMode) {
+        ApplyPersistenceResult persistenceResult = persistAnalysisOnlyApplyRejectionInTransaction(job, request, now);
+        AutoTuneApplyResult result = buildResult(job, persistenceResult.getAuditChanges(), false, controlMode);
+        result.setAnalysisOnly(true);
+        result.setNoApply(true);
+        return result;
     }
 
     private List<ValidatedChange> buildLockBusyChanges(AutoTuneApplyRequest request) {
@@ -195,30 +231,33 @@
 
     @Override
     public AutoTuneApplyResult rollbackLastSuccessfulJob(String reason) {
+        AutoTuneControlModeSnapshot controlMode = currentControlModeSnapshot();
         Date now = new Date();
         AiAutoTuneJob rollbackJob = createRollbackJob(reason, now);
-
-        List<AiAutoTuneChange> sourceChanges = findLatestSuccessfulChanges();
-        if (sourceChanges.isEmpty()) {
-            persistNoRollbackSourceJobInTransaction(rollbackJob, now);
-            return buildResult(rollbackJob, new ArrayList<>(), false);
+        if (isAnalysisOnly(controlMode)) {
+            return rejectRollbackForAnalysisOnly(rollbackJob, now, controlMode);
         }
 
         String lockKey = RedisKeyType.AI_AUTO_TUNE_APPLY_LOCK.key;
         String lockToken = UUID.randomUUID().toString();
         if (!redisUtil.trySetStringIfAbsent(lockKey, lockToken, APPLY_LOCK_SECONDS)) {
-            return rejectRollbackForUnavailableLock(reason, sourceChanges, now, lockKey);
+            return rejectRollbackForUnavailableLock(reason, now, lockKey, controlMode);
         }
 
         try {
+            List<AiAutoTuneChange> sourceChanges = findLatestSuccessfulChanges();
+            if (sourceChanges.isEmpty()) {
+                persistNoRollbackSourceJobInTransaction(rollbackJob, now);
+                return buildResult(rollbackJob, new ArrayList<>(), false, controlMode);
+            }
             try {
                 RollbackPersistenceResult persistenceResult = persistRollbackResultInTransaction(
                         rollbackJob,
                         sourceChanges,
                         now
                 );
-                refreshRollbackConfigCacheIfNeeded(persistenceResult);
-                return buildResult(rollbackJob, persistenceResult.getRollbackChanges(), false);
+                refreshRollbackConfigCacheSafely(persistenceResult);
+                return buildResult(rollbackJob, persistenceResult.getRollbackChanges(), false, controlMode);
             } catch (RuntimeException exception) {
                 Date failureNow = new Date();
                 AiAutoTuneJob failureJob = createRollbackJob(reason, failureNow);
@@ -228,7 +267,7 @@
                         exception,
                         failureNow
                 );
-                return buildResult(failureJob, persistenceResult.getRollbackChanges(), false);
+                return buildResult(failureJob, persistenceResult.getRollbackChanges(), false, controlMode);
             }
         } finally {
             redisUtil.compareAndDelete(lockKey, lockToken);
@@ -236,19 +275,30 @@
     }
 
     private AutoTuneApplyResult rejectRollbackForUnavailableLock(String reason,
-                                                                 List<AiAutoTuneChange> sourceChanges,
                                                                  Date now,
-                                                                 String lockKey) {
+                                                                 String lockKey,
+                                                                 AutoTuneControlModeSnapshot controlMode) {
         boolean lockKeyExists = redisUtil.hasKey(lockKey);
         LOGGER.warn("鐢宠AI鑷姩璋冨弬 rollback 閿佸け璐ワ紝lockKey={}, lockKeyExists={}", lockKey, lockKeyExists);
         AiAutoTuneJob rollbackJob = createRollbackJob(reason, now);
-        RollbackPersistenceResult persistenceResult = persistFailedRollbackResultInTransaction(
+        RollbackPersistenceResult persistenceResult = persistRollbackLockFailureInTransaction(
                 rollbackJob,
-                sourceChanges,
-                new IllegalStateException(APPLY_LOCK_BUSY_REASON),
                 now
         );
-        return buildResult(rollbackJob, persistenceResult.getRollbackChanges(), false);
+        return buildResult(rollbackJob, persistenceResult.getRollbackChanges(), false, controlMode);
+    }
+
+    private AutoTuneApplyResult rejectRollbackForAnalysisOnly(AiAutoTuneJob rollbackJob,
+                                                              Date now,
+                                                              AutoTuneControlModeSnapshot controlMode) {
+        RollbackPersistenceResult persistenceResult = persistAnalysisOnlyRollbackRejectionInTransaction(
+                rollbackJob,
+                now
+        );
+        AutoTuneApplyResult result = buildResult(rollbackJob, persistenceResult.getRollbackChanges(), false, controlMode);
+        result.setAnalysisOnly(true);
+        result.setNoApply(true);
+        return result;
     }
 
     private List<ValidatedChange> validateChanges(AutoTuneApplyRequest request, boolean dryRun, Date now) {
@@ -288,11 +338,14 @@
         validatedChange.setOldValue(currentValue.getOldValue());
 
         Integer maxValue = resolveMaxValue(validatedChange, rule, requestedValue);
-        if (maxValue == null) {
-            return validatedChange.reject("绔欑偣 " + validatedChange.getTargetId()
-                    + " 缂哄皯 OUT 鏂瑰悜 bufferCapacity锛屾棤娉曡瘉鏄� outTaskLimit 涓婇檺");
+        if (requestedValue < rule.getMinValue()) {
+            if (maxValue != null) {
+                return validatedChange.reject(validatedChange.getTargetKey() + " 蹇呴』鍦� "
+                        + rule.getMinValue() + "~" + maxValue + " 鑼冨洿鍐�");
+            }
+            return validatedChange.reject(validatedChange.getTargetKey() + " 蹇呴』涓嶅皬浜� " + rule.getMinValue());
         }
-        if (requestedValue < rule.getMinValue() || requestedValue > maxValue) {
+        if (maxValue != null && requestedValue > maxValue) {
             return validatedChange.reject(validatedChange.getTargetKey() + " 蹇呴』鍦� "
                     + rule.getMinValue() + "~" + maxValue + " 鑼冨洿鍐�");
         }
@@ -327,14 +380,14 @@
             if (config == null) {
                 return CurrentValue.rejected("杩愯鍙傛暟涓嶅瓨鍦�: " + validatedChange.getTargetKey());
             }
-            return numericCurrentValue(config.getValue(), false, validatedChange.getTargetKey());
+            return numericCurrentValue(config.getValue(), validatedChange.getTargetKey());
         }
         if (AutoTuneTargetType.STATION.equals(targetType)) {
             BasStation station = basStationService.getById(targetId);
             if (station == null) {
                 return CurrentValue.rejected("绔欑偣涓嶅瓨鍦�: " + validatedChange.getTargetId());
             }
-            return numericCurrentValue(toText(station.getOutTaskLimit()), true, validatedChange.getTargetKey());
+            return stationOutTaskLimitCurrentValue(station.getOutTaskLimit(), validatedChange.getTargetKey());
         }
         if (AutoTuneTargetType.CRN.equals(targetType)) {
             BasCrnp crnp = basCrnpService.getById(targetId);
@@ -342,28 +395,29 @@
                 return CurrentValue.rejected("鍫嗗灈鏈轰笉瀛樺湪: " + validatedChange.getTargetId());
             }
             Integer value = "maxOutTask".equals(validatedChange.getTargetKey()) ? crnp.getMaxOutTask() : crnp.getMaxInTask();
-            return numericCurrentValue(toText(value), false, validatedChange.getTargetKey());
+            return numericCurrentValue(toText(value), validatedChange.getTargetKey());
         }
         BasDualCrnp dualCrnp = basDualCrnpService.getById(targetId);
         if (dualCrnp == null) {
             return CurrentValue.rejected("鍙屽伐浣嶅爢鍨涙満涓嶅瓨鍦�: " + validatedChange.getTargetId());
         }
         Integer value = "maxOutTask".equals(validatedChange.getTargetKey()) ? dualCrnp.getMaxOutTask() : dualCrnp.getMaxInTask();
-        return numericCurrentValue(toText(value), false, validatedChange.getTargetKey());
+        return numericCurrentValue(toText(value), validatedChange.getTargetKey());
     }
 
-    private CurrentValue numericCurrentValue(String oldValue, boolean nullOrNegativeAsZero, String targetKey) {
+    private CurrentValue stationOutTaskLimitCurrentValue(Integer outTaskLimit, String targetKey) {
+        if (outTaskLimit == null || outTaskLimit < 0) {
+            return CurrentValue.rejected(targetKey + " 褰撳墠涓轰笉闄愬埗锛岄渶瑕佷汉宸ュ厛鍒濆鍖栦负鏈夐檺鍊煎悗鎵嶈兘鑷姩璋冨弬");
+        }
+        return numericCurrentValue(toText(outTaskLimit), targetKey);
+    }
+
+    private CurrentValue numericCurrentValue(String oldValue, 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 + " 褰撳墠鍊间笉鏄暣鏁帮紝鏃犳硶璁$畻姝ラ暱");
@@ -377,16 +431,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) {
@@ -488,9 +537,9 @@
         }
     }
 
-    private void refreshSystemConfigCacheIfNeeded(ApplyPersistenceResult persistenceResult) {
+    private void refreshSystemConfigCacheSafely(ApplyPersistenceResult persistenceResult) {
         if (persistenceResult != null && persistenceResult.isRefreshConfigCache()) {
-            configService.refreshSystemConfigCache();
+            refreshSystemConfigCacheSafely("apply");
         }
     }
 
@@ -542,6 +591,45 @@
         });
     }
 
+    private RollbackPersistenceResult persistRollbackLockFailureInTransaction(AiAutoTuneJob rollbackJob, Date now) {
+        TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
+        return transactionTemplate.execute(status -> {
+            saveJob(rollbackJob);
+            List<AiAutoTuneChange> rollbackChanges = buildRollbackLockFailureChanges(rollbackJob.getId(), now);
+            saveAuditChanges(rollbackChanges);
+            finishRollbackJob(rollbackJob, rollbackChanges, now);
+            updateJob(rollbackJob);
+            return new RollbackPersistenceResult(rollbackChanges, false);
+        });
+    }
+
+    private ApplyPersistenceResult persistAnalysisOnlyApplyRejectionInTransaction(AiAutoTuneJob job,
+                                                                                  AutoTuneApplyRequest request,
+                                                                                  Date now) {
+        TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
+        return transactionTemplate.execute(status -> {
+            saveJob(job);
+            List<AiAutoTuneChange> auditChanges = buildAnalysisOnlyApplyChanges(job.getId(), request, now);
+            saveAuditChanges(auditChanges);
+            finishAnalysisOnlyRejectedJob(job, auditChanges, now);
+            updateJob(job);
+            return new ApplyPersistenceResult(auditChanges, false);
+        });
+    }
+
+    private RollbackPersistenceResult persistAnalysisOnlyRollbackRejectionInTransaction(AiAutoTuneJob rollbackJob,
+                                                                                       Date now) {
+        TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
+        return transactionTemplate.execute(status -> {
+            saveJob(rollbackJob);
+            List<AiAutoTuneChange> rollbackChanges = buildAnalysisOnlyRollbackChanges(rollbackJob.getId(), now);
+            saveAuditChanges(rollbackChanges);
+            finishAnalysisOnlyRejectedJob(rollbackJob, rollbackChanges, now);
+            updateJob(rollbackJob);
+            return new RollbackPersistenceResult(rollbackChanges, false);
+        });
+    }
+
     private RollbackPersistenceResult rollbackChanges(Long rollbackJobId, List<AiAutoTuneChange> sourceChanges, Date now) {
         List<AiAutoTuneChange> rollbackChanges = new ArrayList<>();
         boolean refreshConfigCache = false;
@@ -564,9 +652,81 @@
         return new RollbackPersistenceResult(rollbackChanges, refreshConfigCache);
     }
 
-    private void refreshRollbackConfigCacheIfNeeded(RollbackPersistenceResult persistenceResult) {
+    private List<AiAutoTuneChange> buildRollbackLockFailureChanges(Long rollbackJobId, Date now) {
+        AiAutoTuneChange rollbackChange = new AiAutoTuneChange();
+        rollbackChange.setJobId(rollbackJobId);
+        rollbackChange.setTargetType(AutoTuneTriggerType.ROLLBACK.getCode());
+        rollbackChange.setTargetId("");
+        rollbackChange.setTargetKey("latest_successful_job");
+        rollbackChange.setResultStatus(ChangeStatus.FAILED.getCode());
+        rollbackChange.setRejectReason(APPLY_LOCK_BUSY_REASON);
+        rollbackChange.setCreateTime(now);
+        return List.of(rollbackChange);
+    }
+
+    private List<AiAutoTuneChange> buildAnalysisOnlyApplyChanges(Long jobId,
+                                                                 AutoTuneApplyRequest request,
+                                                                 Date now) {
+        List<AiAutoTuneChange> changes = new ArrayList<>();
+        List<AutoTuneChangeCommand> commands = request == null ? null : request.getChanges();
+        if (commands == null || commands.isEmpty()) {
+            changes.add(buildAnalysisOnlyChange(jobId, "analysis_only", "", "apply", null, now));
+            return changes;
+        }
+        for (AutoTuneChangeCommand command : commands) {
+            changes.add(buildAnalysisOnlyChange(
+                    jobId,
+                    normalizeText(command == null ? null : command.getTargetType()),
+                    normalizeText(command == null ? null : command.getTargetId()),
+                    normalizeText(command == null ? null : command.getTargetKey()),
+                    command == null ? null : command.getNewValue(),
+                    now
+            ));
+        }
+        return changes;
+    }
+
+    private List<AiAutoTuneChange> buildAnalysisOnlyRollbackChanges(Long jobId, Date now) {
+        AiAutoTuneChange rollbackChange = buildAnalysisOnlyChange(
+                jobId,
+                "analysis_only",
+                "",
+                "rollback",
+                null,
+                now
+        );
+        return List.of(rollbackChange);
+    }
+
+    private AiAutoTuneChange buildAnalysisOnlyChange(Long jobId,
+                                                     String targetType,
+                                                     String targetId,
+                                                     String targetKey,
+                                                     String requestedValue,
+                                                     Date now) {
+        AiAutoTuneChange change = new AiAutoTuneChange();
+        change.setJobId(jobId);
+        change.setTargetType(targetType);
+        change.setTargetId(targetId);
+        change.setTargetKey(targetKey);
+        change.setRequestedValue(requestedValue);
+        change.setResultStatus(ChangeStatus.REJECTED.getCode());
+        change.setRejectReason(ANALYSIS_ONLY_REJECT_REASON);
+        change.setCreateTime(now);
+        return change;
+    }
+
+    private void refreshRollbackConfigCacheSafely(RollbackPersistenceResult persistenceResult) {
         if (persistenceResult != null && persistenceResult.isRefreshConfigCache()) {
+            refreshSystemConfigCacheSafely("rollback");
+        }
+    }
+
+    private void refreshSystemConfigCacheSafely(String scene) {
+        try {
             configService.refreshSystemConfigCache();
+        } catch (RuntimeException exception) {
+            LOGGER.warn("AI鑷姩璋冨弬{}宸叉彁浜わ紝浣嗗埛鏂扮郴缁熼厤缃紦瀛樺け璐�", scene, exception);
         }
     }
 
@@ -650,7 +810,7 @@
         job.setTriggerType(AutoTuneTriggerType.normalize(request.getTriggerType()));
         job.setStatus(AutoTuneJobStatus.RUNNING.getCode());
         job.setStartTime(now);
-        job.setHasActiveTasks(0);
+        job.setHasActiveTasks(resolveHasActiveTasksForAudit());
         job.setPromptSceneCode(PROMPT_SCENE_CODE);
         job.setSummary(dryRun ? "AI鑷姩璋冨弬 dry-run: " + safeReason(request.getReason()) : safeReason(request.getReason()));
         job.setIntervalBefore(readIntervalMinutes());
@@ -669,7 +829,7 @@
         job.setTriggerType(AutoTuneTriggerType.ROLLBACK.getCode());
         job.setStatus(AutoTuneJobStatus.RUNNING.getCode());
         job.setStartTime(now);
-        job.setHasActiveTasks(0);
+        job.setHasActiveTasks(resolveHasActiveTasksForAudit());
         job.setPromptSceneCode(PROMPT_SCENE_CODE);
         job.setSummary(safeReason(reason));
         job.setIntervalBefore(readIntervalMinutes());
@@ -681,6 +841,21 @@
         job.setTotalTokens(0);
         job.setCreateTime(now);
         return job;
+    }
+
+    private int resolveHasActiveTasksForAudit() {
+        if (wrkMastService == null) {
+            LOGGER.warn("AI鑷姩璋冨弬瀹¤鏃犳硶鑾峰彇 WrkMastService锛宧asActiveTasks 鎸� 0 璁板綍");
+            return 0;
+        }
+        try {
+            QueryWrapper<WrkMast> queryWrapper = new QueryWrapper<>();
+            queryWrapper.and(wrapper -> wrapper.notIn("wrk_sts", FINAL_WRK_STS_LIST).or().isNull("wrk_sts"));
+            return wrkMastService.count(queryWrapper) > 0 ? 1 : 0;
+        } catch (RuntimeException exception) {
+            LOGGER.warn("AI鑷姩璋冨弬瀹¤鏌ヨ鏈畬鎴愪换鍔″け璐ワ紝hasActiveTasks 鎸� 0 璁板綍", exception);
+            return 0;
+        }
     }
 
     private void finishJob(AiAutoTuneJob job,
@@ -699,6 +874,17 @@
         if (rejectCount > 0) {
             job.setErrorMessage(firstRejectReason(auditChanges));
         }
+    }
+
+    private void finishAnalysisOnlyRejectedJob(AiAutoTuneJob job, List<AiAutoTuneChange> changes, Date now) {
+        int rejectCount = Math.max(1, countRejected(changes));
+        job.setFinishTime(now);
+        job.setSuccessCount(0);
+        job.setRejectCount(rejectCount);
+        job.setIntervalAfter(readIntervalMinutes());
+        job.setStatus(AutoTuneJobStatus.REJECTED.getCode());
+        job.setSummary(ANALYSIS_ONLY_REJECT_REASON);
+        job.setErrorMessage(ANALYSIS_ONLY_REJECT_REASON);
     }
 
     private void finishRollbackJob(AiAutoTuneJob job, List<AiAutoTuneChange> changes, Date now) {
@@ -828,11 +1014,16 @@
         return !AutoTuneTriggerType.ROLLBACK.getCode().equals(job.getTriggerType());
     }
 
-    private AutoTuneApplyResult buildResult(AiAutoTuneJob job, List<AiAutoTuneChange> changes, boolean dryRun) {
+    private AutoTuneApplyResult buildResult(AiAutoTuneJob job,
+                                            List<AiAutoTuneChange> changes,
+                                            boolean dryRun,
+                                            AutoTuneControlModeSnapshot controlMode) {
         AutoTuneApplyResult result = new AutoTuneApplyResult();
         result.setDryRun(dryRun);
         result.setSuccess(AutoTuneJobStatus.SUCCESS.getCode().equals(job.getStatus())
                 || AutoTuneJobStatus.NO_CHANGE.getCode().equals(job.getStatus()));
+        result.setAnalysisOnly(isAnalysisOnly(controlMode));
+        result.setNoApply(false);
         result.setJobId(job.getId());
         result.setSummary(job.getSummary());
         result.setSuccessCount(job.getSuccessCount());
@@ -936,6 +1127,14 @@
         }
     }
 
+    private AutoTuneControlModeSnapshot currentControlModeSnapshot() {
+        return autoTuneControlModeService.currentMode();
+    }
+
+    private boolean isAnalysisOnly(AutoTuneControlModeSnapshot controlMode) {
+        return controlMode == null || Boolean.TRUE.equals(controlMode.getAnalysisOnly());
+    }
+
     private String toText(Integer value) {
         return value == null ? null : String.valueOf(value);
     }

--
Gitblit v1.9.1