| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172 |
- using System.Data;
- using System.Globalization;
- using System.Text.RegularExpressions;
- namespace Admin.NET.Plugin.AiDOP.Infrastructure.FormulaExpr;
- /// <summary>
- /// 结构化公式运行时求值器 v1。
- /// 当前用于打通“可执行计算”的工程落点:调用方负责提供 @指标 与 $事实 的数值上下文。
- /// </summary>
- public static class FormulaRuntimeEvaluator
- {
- private static readonly Regex MetricRefRe = new(@"@([A-Za-z][A-Za-z0-9_]*)", RegexOptions.Compiled);
- private static readonly Regex FactRefRe = new(@"\$([A-Za-z][A-Za-z0-9_]*)", RegexOptions.Compiled);
- public sealed class RuntimeResult
- {
- public decimal? Value { get; init; }
- public List<string> Errors { get; } = new();
- public bool Success => Errors.Count == 0 && Value.HasValue;
- }
- public static RuntimeResult TryEvaluate(
- string? formulaExpr,
- IReadOnlyDictionary<string, decimal?> metricValues,
- IReadOnlyDictionary<string, decimal?> factValues)
- {
- var parsed = FormulaParser.Parse(formulaExpr);
- var result = new RuntimeResult();
- if (parsed.IsEmpty)
- {
- result.Errors.Add("公式为空");
- return result;
- }
- foreach (var error in parsed.Errors) result.Errors.Add(error);
- if (result.Errors.Count > 0) return result;
- var expr = formulaExpr!.Trim().Replace("%", "/100", StringComparison.Ordinal);
- expr = MetricRefRe.Replace(expr, m =>
- {
- var code = m.Groups[1].Value;
- if (!metricValues.TryGetValue(code, out var value) || value == null)
- {
- result.Errors.Add($"缺少指标值 @{code}");
- return "0";
- }
- return value.Value.ToString(CultureInfo.InvariantCulture);
- });
- expr = FactRefRe.Replace(expr, m =>
- {
- var code = m.Groups[1].Value;
- if (!factValues.TryGetValue(code, out var value) || value == null)
- {
- result.Errors.Add($"缺少事实值 ${code}");
- return "0";
- }
- return value.Value.ToString(CultureInfo.InvariantCulture);
- });
- if (result.Errors.Count > 0) return result;
- try
- {
- var computed = new DataTable().Compute(expr, "");
- return new RuntimeResult { Value = Convert.ToDecimal(computed, CultureInfo.InvariantCulture) };
- }
- catch (Exception ex)
- {
- result.Errors.Add("公式计算失败:" + ex.Message);
- return result;
- }
- }
- }
|