Prechádzať zdrojové kódy

fix(s8): align out-of-range rule result columns

- Prefer canonical SQL aliases measured_value/source_object_id/related_object_code when evaluating out-of-range hits.
- Fall back only to params_json configured fields when canonical aliases are absent.
- Bounds stay row-level params fields (no canonical contract); logic unchanged.
- SHORTAGE evaluator left unchanged: no shortage rules exist and the wizard never generates SHORTAGE, so no contract-mismatch surface.
- Keep TIMEOUT fix unchanged. Do not alter SQL generation, params_json, dictionaries, watch rules, or DB.
- No rule enabled and no exception generated during smoke (ado_s8_exception 41 -> 41).
YY968XX 1 mesiac pred
rodič
commit
0a21e463ad

+ 28 - 8
server/Plugins/Admin.NET.Plugin.AiDOP/Service/S8/Rules/S8OutOfRangeRuleEvaluator.cs

@@ -25,6 +25,14 @@ public class S8OutOfRangeRuleEvaluator : IS8RuleEvaluator, ITransient
     private const string SqlDataSourceType = "SQL";
     private const string DefaultExceptionTypeCode = "EQUIP_FAULT";
 
+    // S8-WATCH-EXPRESSION-COLUMN-CONTRACT-SHORTAGE-OUTOFRANGE-FIX-1:S8ConfigDraftService.BuildExpression
+    // 把 VALUE_RANGE 结果列统一别名为以下 canonical 名(无论源表真实列名为何)。evaluator 优先按 canonical
+    // 读取,仅当结果集不含 canonical 列时才回退到 params_json 指定的真实列名(兼容历史/手工规则)。
+    // 上下限为行内可选字段、无 canonical 契约,沿用 params 既有逻辑不变。
+    private const string CanonicalMeasuredValueColumn = "measured_value";
+    private const string CanonicalSourceObjectIdColumn = "source_object_id";
+    private const string CanonicalRelatedObjectCodeColumn = "related_object_code";
+
     private readonly SqlSugarRepository<AdoS8DataSource> _dataSourceRep;
     private readonly S8SqlSugarScopeFactory _scopeFactory;
     private readonly ILogger<S8OutOfRangeRuleEvaluator> _logger;
@@ -98,9 +106,14 @@ public class S8OutOfRangeRuleEvaluator : IS8RuleEvaluator, ITransient
             ? rule.WatchObjectType
             : rule.SourceObjectType!;
 
+        // 结果列名一次性解析(结果集列在整张 DataTable 内稳定):canonical 优先,缺失回退 params 真实列名。
+        var measuredValueColumn = ResolveResultColumn(table, CanonicalMeasuredValueColumn, parameters.MeasuredValueField);
+        var objectCodeColumn = ResolveResultColumn(table, CanonicalRelatedObjectCodeColumn, parameters.ObjectCodeField);
+        var objectIdColumn = ResolveResultColumn(table, CanonicalSourceObjectIdColumn, parameters.ObjectIdField);
+
         foreach (DataRow row in table.Rows)
         {
-            var measured = ReadDecimal(row, parameters.MeasuredValueField!);
+            var measured = ReadDecimal(row, measuredValueColumn);
             if (measured == null) continue;
 
             // 行内上下限优先;缺失时回退到固定上下限。
@@ -135,15 +148,10 @@ public class S8OutOfRangeRuleEvaluator : IS8RuleEvaluator, ITransient
             }
             if (direction == null) continue;
 
-            var objectCodeField = string.IsNullOrWhiteSpace(parameters.ObjectCodeField)
-                ? "related_object_code"
-                : parameters.ObjectCodeField!;
-            var relatedObjectCode = ReadString(row, objectCodeField) ?? string.Empty;
+            var relatedObjectCode = ReadString(row, objectCodeColumn) ?? string.Empty;
             if (string.IsNullOrWhiteSpace(relatedObjectCode)) continue;
 
-            var sourceObjectId = string.IsNullOrWhiteSpace(parameters.ObjectIdField)
-                ? relatedObjectCode
-                : ReadString(row, parameters.ObjectIdField!) ?? relatedObjectCode;
+            var sourceObjectId = ReadString(row, objectIdColumn) ?? relatedObjectCode;
 
             var dedupKey = BuildDedupKey(tenantId, factoryId, rule.RuleCode, sourceObjectType, sourceObjectId);
 
@@ -202,6 +210,18 @@ public class S8OutOfRangeRuleEvaluator : IS8RuleEvaluator, ITransient
         return JsonSerializer.Serialize(payload);
     }
 
+    /// <summary>
+    /// 结果列名解析:BuildExpression 已把 VALUE_RANGE 结果列统一别名为 canonical(measured_value/
+    /// source_object_id/related_object_code)。优先返回 canonical 列名;仅当结果集不含 canonical 列时,
+    /// 回退到 params_json 指定的真实列名(兼容历史/手工规则)。仅在 canonical 与 params 字段之间二选一,
+    /// 不新增无依据兜底字段。
+    /// </summary>
+    private static string ResolveResultColumn(DataTable table, string canonicalColumn, string? paramsColumn)
+    {
+        if (table.Columns.Contains(canonicalColumn)) return canonicalColumn;
+        return string.IsNullOrWhiteSpace(paramsColumn) ? canonicalColumn : paramsColumn!;
+    }
+
     private static string? ReadString(DataRow row, string columnName)
     {
         if (string.IsNullOrWhiteSpace(columnName)) return null;