SmartDiagnosisTreeTests.cs 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. using Admin.NET.Plugin.AiDOP.Controllers;
  2. using Admin.NET.Plugin.AiDOP.Entity;
  3. using Xunit;
  4. namespace Admin.NET.Plugin.AiDOP.Tests.SmartOps;
  5. public class SmartDiagnosisTreeTests
  6. {
  7. [Fact]
  8. public void SpecifiedRoot_WithL2_ReturnsOnlyOwnChildren()
  9. {
  10. var kpis = SampleTree();
  11. var values = new Dictionary<string, SmartDiagnosisTree.ValueSnap>(StringComparer.OrdinalIgnoreCase);
  12. var resolved = SmartDiagnosisTree.ResolveRoot(L1(kpis), values, "S1_L1_001");
  13. Assert.Null(resolved.Error);
  14. Assert.Equal(SmartDiagnosisTree.SelectionSpecified, resolved.SelectionMode);
  15. Assert.Equal("S1_L1_001", resolved.Root!.MetricCode);
  16. var children = SmartDiagnosisTree.DirectChildren(kpis, resolved.Root.Id, 2);
  17. Assert.Equal(new[] { "S1_L2_001", "S1_L2_004" }, children.Select(x => x.MetricCode).ToArray());
  18. Assert.DoesNotContain(children, x => x.MetricCode == "S1_L2_002");
  19. }
  20. [Fact]
  21. public void SpecifiedRoot_WithoutL2_ReturnsEmptyChildren()
  22. {
  23. var kpis = SampleTree();
  24. var resolved = SmartDiagnosisTree.ResolveRoot(L1(kpis), EmptyValues(), "S1_L1_004");
  25. Assert.Null(resolved.Error);
  26. Assert.Equal("S1_L1_004", resolved.Root!.MetricCode);
  27. Assert.Empty(SmartDiagnosisTree.DirectChildren(kpis, resolved.Root.Id, 2));
  28. }
  29. [Fact]
  30. public void GlobalWorst_WithoutL2_StillSelectsThatRoot()
  31. {
  32. var kpis = SampleTree();
  33. var values = new Dictionary<string, SmartDiagnosisTree.ValueSnap>(StringComparer.OrdinalIgnoreCase)
  34. {
  35. ["S1_L1_001"] = new(20m, 20m),
  36. ["S1_L1_002"] = new(90m, 95m),
  37. ["S1_L1_004"] = new(101.94m, 15m),
  38. };
  39. var resolved = SmartDiagnosisTree.ResolveRoot(L1(kpis), values, null);
  40. Assert.Null(resolved.Error);
  41. Assert.Equal(SmartDiagnosisTree.SelectionGlobalWorst, resolved.SelectionMode);
  42. Assert.Equal("S1_L1_004", resolved.Root!.MetricCode);
  43. Assert.Empty(SmartDiagnosisTree.DirectChildren(kpis, resolved.Root.Id, 2));
  44. }
  45. [Fact]
  46. public void GlobalWorst_PrefersMetricWithValue_OverMissingMetric()
  47. {
  48. var kpis = SampleTree();
  49. var values = new Dictionary<string, SmartDiagnosisTree.ValueSnap>(StringComparer.OrdinalIgnoreCase)
  50. {
  51. ["S1_L1_001"] = new(null, null),
  52. ["S1_L1_002"] = new(90m, 95m),
  53. };
  54. var resolved = SmartDiagnosisTree.ResolveRoot(L1(kpis), values, null);
  55. Assert.Null(resolved.Error);
  56. Assert.Equal("S1_L1_002", resolved.Root!.MetricCode);
  57. }
  58. [Fact]
  59. public void MixedL3_OnlyConfiguredChildrenReturned()
  60. {
  61. var kpis = SampleTree();
  62. var l2WithL3 = kpis.First(x => x.MetricCode == "S1_L2_001");
  63. var l2WithoutL3 = kpis.First(x => x.MetricCode == "S1_L2_004");
  64. var withL3 = SmartDiagnosisTree.DirectChildren(kpis, l2WithL3.Id, 3);
  65. var withoutL3 = SmartDiagnosisTree.DirectChildren(kpis, l2WithoutL3.Id, 3);
  66. Assert.Single(withL3);
  67. Assert.Equal("S1_L3_001", withL3[0].MetricCode);
  68. Assert.Empty(withoutL3);
  69. }
  70. [Fact]
  71. public void L3WithoutL4_ReturnsEmptyLeaf()
  72. {
  73. var kpis = SampleTree();
  74. var l3 = kpis.First(x => x.MetricCode == "S1_L3_001");
  75. Assert.Empty(SmartDiagnosisTree.DirectChildren(kpis, l3.Id, 4));
  76. }
  77. [Fact]
  78. public void InvalidSpecifiedMetric_DoesNotFallBackToGlobal()
  79. {
  80. var kpis = SampleTree();
  81. var values = new Dictionary<string, SmartDiagnosisTree.ValueSnap>(StringComparer.OrdinalIgnoreCase)
  82. {
  83. ["S1_L1_004"] = new(101.94m, 15m),
  84. };
  85. var resolved = SmartDiagnosisTree.ResolveRoot(L1(kpis), values, "NOT_AN_L1");
  86. Assert.Null(resolved.Root);
  87. Assert.Equal(SmartDiagnosisTree.SelectionSpecified, resolved.SelectionMode);
  88. Assert.Equal("metricCode 不是当前模块已启用的 L1 指标。", resolved.Error);
  89. }
  90. [Fact]
  91. public void BuildDiagnosisRate_UsesFormula_NotHardcoded58()
  92. {
  93. Assert.Equal("84%", SmartDiagnosisTree.BuildDiagnosisRate(1.79m, 1.5m, "lower_is_better"));
  94. Assert.Equal("50%", SmartDiagnosisTree.BuildDiagnosisRate(50m, 100m, "higher_is_better"));
  95. Assert.Equal("-", SmartDiagnosisTree.BuildDiagnosisRate(null, 1.5m, "lower_is_better"));
  96. Assert.Equal("-", SmartDiagnosisTree.BuildDiagnosisRate(1.79m, 0m, "lower_is_better"));
  97. Assert.Equal("-", SmartDiagnosisTree.BuildDiagnosisRate(0m, 1.5m, "lower_is_better"));
  98. }
  99. private static List<AdoSmartOpsKpiMaster> L1(List<AdoSmartOpsKpiMaster> kpis) =>
  100. kpis.Where(x => x.MetricLevel == 1).OrderBy(x => x.SortNo).ToList();
  101. private static Dictionary<string, SmartDiagnosisTree.ValueSnap> EmptyValues() =>
  102. new(StringComparer.OrdinalIgnoreCase);
  103. private static List<AdoSmartOpsKpiMaster> SampleTree()
  104. {
  105. var l1Cycle = Kpi(337, "S1_L1_001", "订单评审周期(天)", 1, null, 1, "lower_is_better");
  106. var l1Rate = Kpi(338, "S1_L1_002", "订单评审满足率(%)", 1, null, 2, "higher_is_better");
  107. var l1Turn = Kpi(340, "S1_L1_004", "成品库存周转(天)", 1, null, 4, "lower_is_better");
  108. var l2Contract = Kpi(369, "S1_L2_001", "合同评审周期(天)", 2, l1Cycle.Id, 1, "lower_is_better");
  109. var l2Rate = Kpi(370, "S1_L2_002", "合同评审满足率(%)", 2, l1Rate.Id, 2, "higher_is_better");
  110. var l2Bom = Kpi(372, "S1_L2_004", "BOM评审周期(天)", 2, l1Cycle.Id, 4, "lower_is_better");
  111. var l3 = Kpi(401, "S1_L3_001", "意见评审", 3, l2Contract.Id, 1, "lower_is_better");
  112. return [l1Cycle, l1Rate, l1Turn, l2Contract, l2Rate, l2Bom, l3];
  113. }
  114. private static AdoSmartOpsKpiMaster Kpi(
  115. long id, string code, string name, int level, long? parentId, int sortNo, string direction) =>
  116. new()
  117. {
  118. Id = id,
  119. MetricCode = code,
  120. MetricName = name,
  121. MetricLevel = level,
  122. ParentId = parentId,
  123. SortNo = sortNo,
  124. Direction = direction,
  125. ModuleCode = "S1",
  126. IsEnabled = true,
  127. };
  128. }