| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131 |
- namespace Admin.NET.Plugin.AiDOP.SmartOps;
- public static class SmartOpsImprovementEffectiveness
- {
- public const int WindowSize = 7;
- public const int MinSamples = 3;
- public const string AutoEffective = "AUTO_EFFECTIVE";
- public const string AutoIneffective = "AUTO_INEFFECTIVE";
- public const string AutoInconclusive = "AUTO_INCONCLUSIVE";
- public const string InsufficientData = "INSUFFICIENT_DATA";
- public static bool IsLowerBetter(string? direction) =>
- string.Equals(direction?.Trim(), "lower_is_better", StringComparison.OrdinalIgnoreCase);
- public static IReadOnlyList<decimal> TakeLastValid(IEnumerable<decimal?> values, int take = WindowSize)
- {
- return values.Where(x => x.HasValue).Select(x => x!.Value).TakeLast(take).ToList();
- }
- public static bool MeetsTarget(decimal value, decimal? target, string? direction)
- {
- if (target is null) return false;
- return IsLowerBetter(direction) ? value <= target.Value : value >= target.Value;
- }
- public static int TrailingOnTargetDays(IReadOnlyList<decimal> after, decimal? target, string? direction)
- {
- if (target is null || after.Count == 0) return 0;
- var count = 0;
- for (var i = after.Count - 1; i >= 0; i--)
- {
- if (!MeetsTarget(after[i], target, direction)) break;
- count++;
- }
- return count;
- }
- public static SmartOpsEffectivenessResult Evaluate(
- IReadOnlyList<decimal> before,
- IReadOnlyList<decimal> after,
- decimal? target,
- string? direction,
- int exceptionCountBefore = 0,
- int exceptionCountAfter = 0)
- {
- if (before.Count < MinSamples || after.Count < MinSamples)
- {
- return new SmartOpsEffectivenessResult(
- InsufficientData,
- Mean(before),
- Mean(after),
- null,
- before.Count,
- after.Count,
- TrailingOnTargetDays(after, target, direction),
- AchievementRate(after, target, direction),
- exceptionCountBefore,
- exceptionCountAfter,
- "有效样本不足 3 个,无法自动评价");
- }
- var beforeMean = Mean(before)!.Value;
- var afterMean = Mean(after)!.Value;
- var lower = IsLowerBetter(direction);
- var meanImproved = lower ? afterMean < beforeMean : afterMean > beforeMean;
- var meanWorse = lower ? afterMean > beforeMean : afterMean < beforeMean;
- var delta = afterMean - beforeMean;
- var reached = target is not null && MeetsTarget(afterMean, target, direction);
- var closer = target is not null
- && Math.Abs(afterMean - target.Value) < Math.Abs(beforeMean - target.Value);
- string conclusion;
- string reason;
- if (meanImproved && (reached || closer))
- {
- conclusion = AutoEffective;
- reason = reached ? "后窗口均值改善且已达目标" : "后窗口均值改善且更接近目标";
- }
- else if (meanWorse)
- {
- conclusion = AutoIneffective;
- reason = "后窗口均值劣于改善前,未判定为有效";
- }
- else
- {
- conclusion = AutoInconclusive;
- reason = target is null
- ? "缺少目标值,不能只按差值正负判定"
- : meanImproved
- ? "均值改善但未更接近或达到目标"
- : "前后窗口均值无显著方向变化";
- }
- return new SmartOpsEffectivenessResult(
- conclusion,
- beforeMean,
- afterMean,
- delta,
- before.Count,
- after.Count,
- TrailingOnTargetDays(after, target, direction),
- AchievementRate(after, target, direction),
- exceptionCountBefore,
- exceptionCountAfter,
- reason);
- }
- private static decimal? Mean(IReadOnlyList<decimal> values) =>
- values.Count == 0 ? null : decimal.Round(values.Average(), 4);
- private static decimal AchievementRate(IReadOnlyList<decimal> after, decimal? target, string? direction)
- {
- if (target is null || after.Count == 0) return 0;
- var hit = after.Count(v => MeetsTarget(v, target, direction));
- return decimal.Round(hit * 100m / after.Count, 2);
- }
- }
- public sealed record SmartOpsEffectivenessResult(
- string Conclusion,
- decimal? BeforeMean,
- decimal? AfterMean,
- decimal? Delta,
- int BeforeSamples,
- int AfterSamples,
- int ConsecutiveOnTargetDays,
- decimal AchievementRate,
- int ExceptionCountBefore,
- int ExceptionCountAfter,
- string Reason);
|