SmartDiagnosisTreeTests.cs 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  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 MixedL3_OnlyConfiguredChildrenReturned()
  47. {
  48. var kpis = SampleTree();
  49. var l2WithL3 = kpis.First(x => x.MetricCode == "S1_L2_001");
  50. var l2WithoutL3 = kpis.First(x => x.MetricCode == "S1_L2_004");
  51. var withL3 = SmartDiagnosisTree.DirectChildren(kpis, l2WithL3.Id, 3);
  52. var withoutL3 = SmartDiagnosisTree.DirectChildren(kpis, l2WithoutL3.Id, 3);
  53. Assert.Single(withL3);
  54. Assert.Equal("S1_L3_001", withL3[0].MetricCode);
  55. Assert.Empty(withoutL3);
  56. }
  57. [Fact]
  58. public void L3WithoutL4_ReturnsEmptyLeaf()
  59. {
  60. var kpis = SampleTree();
  61. var l3 = kpis.First(x => x.MetricCode == "S1_L3_001");
  62. Assert.Empty(SmartDiagnosisTree.DirectChildren(kpis, l3.Id, 4));
  63. }
  64. [Fact]
  65. public void InvalidSpecifiedMetric_DoesNotFallBackToGlobal()
  66. {
  67. var kpis = SampleTree();
  68. var values = new Dictionary<string, SmartDiagnosisTree.ValueSnap>(StringComparer.OrdinalIgnoreCase)
  69. {
  70. ["S1_L1_004"] = new(101.94m, 15m),
  71. };
  72. var resolved = SmartDiagnosisTree.ResolveRoot(L1(kpis), values, "NOT_AN_L1");
  73. Assert.Null(resolved.Root);
  74. Assert.Equal(SmartDiagnosisTree.SelectionSpecified, resolved.SelectionMode);
  75. Assert.Equal("metricCode 不是当前模块已启用的 L1 指标。", resolved.Error);
  76. }
  77. [Fact]
  78. public void BuildDiagnosisRate_UsesFormula_NotHardcoded58()
  79. {
  80. Assert.Equal("84%", SmartDiagnosisTree.BuildDiagnosisRate(1.79m, 1.5m, "lower_is_better"));
  81. Assert.Equal("50%", SmartDiagnosisTree.BuildDiagnosisRate(50m, 100m, "higher_is_better"));
  82. Assert.Equal("-", SmartDiagnosisTree.BuildDiagnosisRate(null, 1.5m, "lower_is_better"));
  83. Assert.Equal("-", SmartDiagnosisTree.BuildDiagnosisRate(1.79m, 0m, "lower_is_better"));
  84. }
  85. private static List<AdoSmartOpsKpiMaster> L1(List<AdoSmartOpsKpiMaster> kpis) =>
  86. kpis.Where(x => x.MetricLevel == 1).OrderBy(x => x.SortNo).ToList();
  87. private static Dictionary<string, SmartDiagnosisTree.ValueSnap> EmptyValues() =>
  88. new(StringComparer.OrdinalIgnoreCase);
  89. private static List<AdoSmartOpsKpiMaster> SampleTree()
  90. {
  91. var l1Cycle = Kpi(337, "S1_L1_001", "订单评审周期(天)", 1, null, 1, "lower_is_better");
  92. var l1Rate = Kpi(338, "S1_L1_002", "订单评审满足率(%)", 1, null, 2, "higher_is_better");
  93. var l1Turn = Kpi(340, "S1_L1_004", "成品库存周转(天)", 1, null, 4, "lower_is_better");
  94. var l2Contract = Kpi(369, "S1_L2_001", "合同评审周期(天)", 2, l1Cycle.Id, 1, "lower_is_better");
  95. var l2Rate = Kpi(370, "S1_L2_002", "合同评审满足率(%)", 2, l1Rate.Id, 2, "higher_is_better");
  96. var l2Bom = Kpi(372, "S1_L2_004", "BOM评审周期(天)", 2, l1Cycle.Id, 4, "lower_is_better");
  97. var l3 = Kpi(401, "S1_L3_001", "意见评审", 3, l2Contract.Id, 1, "lower_is_better");
  98. return [l1Cycle, l1Rate, l1Turn, l2Contract, l2Rate, l2Bom, l3];
  99. }
  100. private static AdoSmartOpsKpiMaster Kpi(
  101. long id, string code, string name, int level, long? parentId, int sortNo, string direction) =>
  102. new()
  103. {
  104. Id = id,
  105. MetricCode = code,
  106. MetricName = name,
  107. MetricLevel = level,
  108. ParentId = parentId,
  109. SortNo = sortNo,
  110. Direction = direction,
  111. ModuleCode = "S1",
  112. IsEnabled = true,
  113. };
  114. }