| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990 |
- using Admin.NET.Plugin.AiDOP.Entity;
- using Admin.NET.Plugin.AiDOP.Infrastructure;
- namespace Admin.NET.Plugin.AiDOP.Controllers;
- /// <summary>
- /// 智慧诊断选根与指标树:只沿 ParentId 下钻,无下级时自然截止,不跨树补位。
- /// </summary>
- internal static class SmartDiagnosisTree
- {
- public const string SelectionSpecified = "specified";
- public const string SelectionGlobalWorst = "global_worst";
- internal readonly record struct ValueSnap(decimal? MetricValue, decimal? TargetValue);
- internal readonly record struct RootResolveResult(
- AdoSmartOpsKpiMaster? Root,
- string SelectionMode,
- string? Error);
- public static RootResolveResult ResolveRoot(
- List<AdoSmartOpsKpiMaster> l1Metrics,
- IReadOnlyDictionary<string, ValueSnap> values,
- string? metricCode)
- {
- if (!string.IsNullOrWhiteSpace(metricCode))
- {
- var specified = l1Metrics.FirstOrDefault(x =>
- string.Equals(x.MetricCode, metricCode.Trim(), StringComparison.OrdinalIgnoreCase));
- if (specified == null)
- return new RootResolveResult(null, SelectionSpecified, "metricCode 不是当前模块已启用的 L1 指标。");
- return new RootResolveResult(specified, SelectionSpecified, null);
- }
- var worst = l1Metrics
- .OrderByDescending(x => DiagnosisSeverity(ComputeStatus(x, values)))
- .ThenByDescending(x => DiagnosisDeviation(x, values))
- .ThenBy(x => x.SortNo)
- .FirstOrDefault();
- return new RootResolveResult(worst, SelectionGlobalWorst, null);
- }
- public static List<AdoSmartOpsKpiMaster> DirectChildren(
- IEnumerable<AdoSmartOpsKpiMaster> kpis,
- long parentId,
- int level) =>
- kpis
- .Where(x => x.MetricLevel == level && x.ParentId == parentId)
- .OrderBy(x => x.SortNo)
- .ToList();
- public static string ComputeStatus(AdoSmartOpsKpiMaster kpi, IReadOnlyDictionary<string, ValueSnap> values)
- {
- values.TryGetValue(kpi.MetricCode, out var value);
- return AidopS4KpiMerge.AchievementLevel(
- value.MetricValue,
- value.TargetValue,
- kpi.Direction ?? "higher_is_better",
- kpi.YellowThreshold,
- kpi.RedThreshold);
- }
- public static decimal DiagnosisDeviation(AdoSmartOpsKpiMaster kpi, IReadOnlyDictionary<string, ValueSnap> values)
- {
- if (!values.TryGetValue(kpi.MetricCode, out var value) || value.MetricValue == null || value.TargetValue == null || value.TargetValue == 0)
- return 0m;
- var current = value.MetricValue.Value;
- var target = value.TargetValue.Value;
- return string.Equals(kpi.Direction, "lower_is_better", StringComparison.OrdinalIgnoreCase)
- ? Math.Max(0m, (current - target) / Math.Abs(target))
- : Math.Max(0m, (target - current) / Math.Abs(target));
- }
- public static int DiagnosisSeverity(string? status) => (status ?? "").ToLowerInvariant() switch
- {
- "red" => 3,
- "yellow" => 2,
- "green" => 1,
- _ => 0
- };
- public static string BuildDiagnosisRate(decimal? current, decimal? target, string? direction)
- {
- if (current == null || target == null || target == 0) return "-";
- var ratio = string.Equals(direction, "lower_is_better", StringComparison.OrdinalIgnoreCase)
- ? target.Value / current.Value * 100m
- : current.Value / target.Value * 100m;
- return $"{decimal.Round(ratio, 0):0}%";
- }
- }
|