using System.Text.RegularExpressions; using Admin.NET.Plugin.AiDOP.Entity; using SqlSugar; namespace Admin.NET.Plugin.AiDOP.Infrastructure.FormulaExpr; /// /// 把结构化公式替换为人读预览: /// $F_S1_001 → 统计期订单总数 /// @S1_L1_001 → 订单评审周期(天) /// / → ÷, * → × /// public static class FormulaPreviewBuilder { private static readonly Regex RefRe = new(@"([@$])([A-Za-z][A-Za-z0-9_]*)", RegexOptions.Compiled); public static async Task BuildAsync( ISqlSugarClient db, long tenantId, string? expr, FormulaParser.ParseResult? parsed = null) { if (string.IsNullOrWhiteSpace(expr)) return string.Empty; parsed ??= FormulaParser.Parse(expr); var metricMap = parsed.MetricRefs.Count == 0 ? new Dictionary() : (await db.Queryable() .Where(x => x.TenantId == tenantId && parsed.MetricRefs.Contains(x.MetricCode)) .Select(x => new { x.MetricCode, x.MetricName }) .ToListAsync()) .ToDictionary(x => x.MetricCode, x => x.MetricName, StringComparer.OrdinalIgnoreCase); var factMap = parsed.FactRefs.Count == 0 ? new Dictionary() : (await db.Queryable() .Where(x => x.TenantId == tenantId && parsed.FactRefs.Contains(x.FactCode)) .Select(x => new { x.FactCode, x.FactName }) .ToListAsync()) .ToDictionary(x => x.FactCode, x => x.FactName, StringComparer.OrdinalIgnoreCase); var preview = RefRe.Replace(expr, m => { var sym = m.Groups[1].Value; var code = m.Groups[2].Value; if (sym == "@" && metricMap.TryGetValue(code, out var mn)) return mn ?? $"@{code}"; if (sym == "$" && factMap.TryGetValue(code, out var fn)) return fn ?? $"${code}"; return m.Value; }); preview = preview.Replace('/', '÷').Replace('*', '×'); // 连续空白压缩 preview = Regex.Replace(preview, @"\s+", " ").Trim(); return preview; } }