SmartDiagnosisTree.cs 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. using Admin.NET.Plugin.AiDOP.Entity;
  2. using Admin.NET.Plugin.AiDOP.Infrastructure;
  3. namespace Admin.NET.Plugin.AiDOP.Controllers;
  4. /// <summary>
  5. /// 智慧诊断选根与指标树:只沿 ParentId 下钻,无下级时自然截止,不跨树补位。
  6. /// </summary>
  7. internal static class SmartDiagnosisTree
  8. {
  9. public const string SelectionSpecified = "specified";
  10. public const string SelectionGlobalWorst = "global_worst";
  11. internal readonly record struct ValueSnap(decimal? MetricValue, decimal? TargetValue);
  12. internal readonly record struct RootResolveResult(
  13. AdoSmartOpsKpiMaster? Root,
  14. string SelectionMode,
  15. string? Error);
  16. public static RootResolveResult ResolveRoot(
  17. List<AdoSmartOpsKpiMaster> l1Metrics,
  18. IReadOnlyDictionary<string, ValueSnap> values,
  19. string? metricCode)
  20. {
  21. if (!string.IsNullOrWhiteSpace(metricCode))
  22. {
  23. var specified = l1Metrics.FirstOrDefault(x =>
  24. string.Equals(x.MetricCode, metricCode.Trim(), StringComparison.OrdinalIgnoreCase));
  25. if (specified == null)
  26. return new RootResolveResult(null, SelectionSpecified, "metricCode 不是当前模块已启用的 L1 指标。");
  27. return new RootResolveResult(specified, SelectionSpecified, null);
  28. }
  29. var worst = l1Metrics
  30. .OrderByDescending(x => DiagnosisSeverity(ComputeStatus(x, values)))
  31. .ThenByDescending(x => DiagnosisDeviation(x, values))
  32. .ThenBy(x => x.SortNo)
  33. .FirstOrDefault();
  34. return new RootResolveResult(worst, SelectionGlobalWorst, null);
  35. }
  36. public static List<AdoSmartOpsKpiMaster> DirectChildren(
  37. IEnumerable<AdoSmartOpsKpiMaster> kpis,
  38. long parentId,
  39. int level) =>
  40. kpis
  41. .Where(x => x.MetricLevel == level && x.ParentId == parentId)
  42. .OrderBy(x => x.SortNo)
  43. .ToList();
  44. public static string ComputeStatus(AdoSmartOpsKpiMaster kpi, IReadOnlyDictionary<string, ValueSnap> values)
  45. {
  46. values.TryGetValue(kpi.MetricCode, out var value);
  47. return AidopS4KpiMerge.AchievementLevel(
  48. value.MetricValue,
  49. value.TargetValue,
  50. kpi.Direction ?? "higher_is_better",
  51. kpi.YellowThreshold,
  52. kpi.RedThreshold);
  53. }
  54. public static decimal DiagnosisDeviation(AdoSmartOpsKpiMaster kpi, IReadOnlyDictionary<string, ValueSnap> values)
  55. {
  56. if (!values.TryGetValue(kpi.MetricCode, out var value) || value.MetricValue == null || value.TargetValue == null || value.TargetValue == 0)
  57. return 0m;
  58. var current = value.MetricValue.Value;
  59. var target = value.TargetValue.Value;
  60. return string.Equals(kpi.Direction, "lower_is_better", StringComparison.OrdinalIgnoreCase)
  61. ? Math.Max(0m, (current - target) / Math.Abs(target))
  62. : Math.Max(0m, (target - current) / Math.Abs(target));
  63. }
  64. public static int DiagnosisSeverity(string? status) => (status ?? "").ToLowerInvariant() switch
  65. {
  66. "red" => 3,
  67. "yellow" => 2,
  68. "green" => 1,
  69. _ => 0
  70. };
  71. public static string BuildDiagnosisRate(decimal? current, decimal? target, string? direction)
  72. {
  73. if (current == null || target == null || target == 0) return "-";
  74. var ratio = string.Equals(direction, "lower_is_better", StringComparison.OrdinalIgnoreCase)
  75. ? target.Value / current.Value * 100m
  76. : current.Value / target.Value * 100m;
  77. return $"{decimal.Round(ratio, 0):0}%";
  78. }
  79. }