AidopS4KpiMerge.cs 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. namespace Admin.NET.Plugin.AiDOP.Infrastructure;
  2. /// <summary>
  3. /// 期量差与绿黄红(见 doc/plan/关键定义说明/期量差说明.md)。
  4. /// 达成比例 = 实际 ÷ 目标 × 100%。
  5. /// higher_is_better 默认黄 95 / 严重红 80;lower_is_better 默认黄 110 / 严重红 120。
  6. /// YellowThreshold 分绿黄红;RedThreshold 仅决定红色是否严重告警(闪烁)。
  7. /// </summary>
  8. public static class AidopS4KpiMerge
  9. {
  10. public const string Green = "green";
  11. public const string Yellow = "yellow";
  12. public const string Red = "red";
  13. public const decimal DefaultHigherYellow = 95m;
  14. public const decimal DefaultHigherRed = 80m;
  15. public const decimal DefaultLowerYellow = 110m;
  16. public const decimal DefaultLowerRed = 120m;
  17. public sealed record KpiAchievementEvaluation(
  18. string Status,
  19. decimal? RatioPercent,
  20. bool IsBeyondRedThreshold,
  21. decimal EffectiveYellowThreshold,
  22. decimal EffectiveRedThreshold);
  23. public static bool IsLowerBetter(string? direction) =>
  24. string.Equals(direction, "lower_is_better", StringComparison.OrdinalIgnoreCase);
  25. /// <summary>
  26. /// 保存校验。两个阈值都为空时通过;只配一个时另一个按方向默认值参与组合校验。
  27. /// 不交换配置值。
  28. /// </summary>
  29. public static bool TryValidateThresholds(
  30. string? direction,
  31. decimal? yellowThreshold,
  32. decimal? redThreshold,
  33. out string? error)
  34. {
  35. error = null;
  36. if (yellowThreshold == null && redThreshold == null)
  37. return true;
  38. var lower = IsLowerBetter(direction);
  39. var yellow = yellowThreshold ?? (lower ? DefaultLowerYellow : DefaultHigherYellow);
  40. var red = redThreshold ?? (lower ? DefaultLowerRed : DefaultHigherRed);
  41. if (lower)
  42. {
  43. if (yellow >= 100m && yellow <= red)
  44. return true;
  45. error = "lower_is_better 阈值不合法,应满足 100 <= YellowThreshold <= RedThreshold。";
  46. return false;
  47. }
  48. if (red > 0m && red <= yellow && yellow <= 100m)
  49. return true;
  50. error = "higher_is_better 阈值不合法,应满足 0 < RedThreshold <= YellowThreshold <= 100。";
  51. return false;
  52. }
  53. public static KpiAchievementEvaluation Evaluate(
  54. decimal? current,
  55. decimal? target,
  56. string? direction,
  57. decimal? yellowThreshold = null,
  58. decimal? redThreshold = null)
  59. {
  60. var (yellow, red) = ResolveEffectiveThresholds(direction, yellowThreshold, redThreshold);
  61. if (current == null || target == null || target.Value == 0)
  62. return new KpiAchievementEvaluation(Yellow, null, false, yellow, red);
  63. var ratio = current.Value / target.Value * 100m;
  64. if (IsLowerBetter(direction))
  65. {
  66. var status = ratio <= 100m ? Green : ratio <= yellow ? Yellow : Red;
  67. var blink = status == Red && ratio > red;
  68. return new KpiAchievementEvaluation(status, ratio, blink, yellow, red);
  69. }
  70. {
  71. var status = ratio >= 100m ? Green : ratio >= yellow ? Yellow : Red;
  72. var blink = status == Red && ratio < red;
  73. return new KpiAchievementEvaluation(status, ratio, blink, yellow, red);
  74. }
  75. }
  76. public static string AchievementLevel(
  77. decimal? current, decimal? target, string direction,
  78. decimal? yellowThreshold = null, decimal? redThreshold = null) =>
  79. Evaluate(current, target, direction, yellowThreshold, redThreshold).Status;
  80. public static bool IsBeyondRedThreshold(
  81. decimal? current,
  82. decimal? target,
  83. string? direction,
  84. decimal? yellowThreshold = null,
  85. decimal? redThreshold = null) =>
  86. Evaluate(current, target, direction, yellowThreshold, redThreshold).IsBeyondRedThreshold;
  87. /// <summary>期量差数值(当前−目标);展示用箭头与符号在调用方处理。</summary>
  88. public static decimal? GapValue(decimal? current, decimal? target)
  89. {
  90. if (current == null || target == null) return null;
  91. return current.Value - target.Value;
  92. }
  93. public static string GapArrow(decimal? gap)
  94. {
  95. if (gap == null) return "flat";
  96. return gap.Value >= 0 ? "up" : "down";
  97. }
  98. private static (decimal Yellow, decimal Red) ResolveEffectiveThresholds(
  99. string? direction,
  100. decimal? yellowThreshold,
  101. decimal? redThreshold)
  102. {
  103. var lower = IsLowerBetter(direction);
  104. var defaults = lower
  105. ? (DefaultLowerYellow, DefaultLowerRed)
  106. : (DefaultHigherYellow, DefaultHigherRed);
  107. if (!TryValidateThresholds(direction, yellowThreshold, redThreshold, out _))
  108. return defaults;
  109. return (
  110. yellowThreshold ?? defaults.Item1,
  111. redThreshold ?? defaults.Item2);
  112. }
  113. }