namespace Admin.NET.Plugin.AiDOP.SmartOps;
///
/// KPI 原子聚合规则注册表(全局 S1-S9):指标如何从原子层二次聚合,以及支持哪些筛选字段。
///
public static class SmartOpsKpiAggregateRuleRegistry
{
public const string DomainOrderDelivery = "order_delivery";
public const string DomainWorkSchedule = "work_schedule";
public const string DomainSupplyPurchase = "supply_purchase";
public const string DomainInventory = "inventory";
public const string AggSum = "sum";
public const string AggRatio = "ratio";
public const string AggAvg = "avg";
public const string AggMin = "min";
public const string AggMax = "max";
public const string FilterFull = "filterable_full";
public const string FilterPartial = "filterable_partial";
public const string FilterSummaryOnly = "summary_only";
private static readonly string[] OrderDeliveryFilters =
["dateStart", "dateEnd", "customer", "product", "orderNo", "productionLine"];
private static readonly string[] WorkScheduleFilters =
["dateStart", "dateEnd", "product", "orderNo", "productionLine", "workOrder", "equipment"];
private static readonly string[] SupplyPurchaseFilters =
["dateStart", "dateEnd", "supplier", "material", "poNo"];
private static readonly string[] InventoryFilters =
["dateStart", "dateEnd", "warehouse", "material", "workOrder", "outboundNo"];
public static SmartOpsKpiAggregateRule? TryGet(string? metricCode)
{
if (string.IsNullOrWhiteSpace(metricCode)) return null;
return ResolveByPattern(metricCode.Trim());
}
public static bool SupportsFilter(string metricCode, SmartOpsDashboardFilter filter)
{
var rule = TryGet(metricCode);
if (rule == null || rule.FilterMode == FilterSummaryOnly) return false;
if (filter.IsEmpty) return true;
var active = GetActiveFilterKeys(filter);
if (active.Count == 0) return true;
return active.All(k => rule.SupportedFilters.Contains(k, StringComparer.OrdinalIgnoreCase));
}
public static IReadOnlyList GetUnsupportedFilters(string metricCode, SmartOpsDashboardFilter filter)
{
var rule = TryGet(metricCode);
if (rule == null || filter.IsEmpty) return Array.Empty();
return GetActiveFilterKeys(filter)
.Where(k => !rule.SupportedFilters.Contains(k, StringComparer.OrdinalIgnoreCase))
.ToList();
}
public static IEnumerable GetDomainsForModule(string moduleCode)
{
return moduleCode.ToUpperInvariant() switch
{
"S1" or "S7" => new[] { DomainOrderDelivery, DomainInventory },
"S2" or "S6" => new[] { DomainWorkSchedule },
"S3" or "S4" => new[] { DomainSupplyPurchase },
"S5" => new[] { DomainInventory, DomainSupplyPurchase },
"S9" => new[] { DomainOrderDelivery, DomainWorkSchedule, DomainSupplyPurchase, DomainInventory },
_ => Array.Empty()
};
}
public static string ResolveFilterMode(string metricCode, SmartOpsDashboardFilter filter)
{
var rule = TryGet(metricCode);
if (rule == null || rule.FilterMode == FilterSummaryOnly) return FilterSummaryOnly;
if (filter.IsEmpty) return FilterSummaryOnly;
if (SupportsFilter(metricCode, filter)) return FilterFull;
return FilterPartial;
}
private static SmartOpsKpiAggregateRule? ResolveByPattern(string metricCode)
{
var parts = metricCode.Split('_', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
if (parts.Length < 3) return null;
var module = parts[0].ToUpperInvariant();
if (module == "S8") return SummaryRule(metricCode);
var domain = ResolveDomain(module, metricCode);
if (domain == null) return SummaryRule(metricCode);
var suffix = parts[^1];
var aggregation = ResolveAggregation(suffix, metricCode);
var filters = ResolveFilters(domain);
return new SmartOpsKpiAggregateRule(metricCode, domain, aggregation, FilterPartial, filters);
}
private static string? ResolveDomain(string module, string metricCode)
{
if (module == "S9")
{
return metricCode.ToUpperInvariant() switch
{
var c when c.StartsWith("S9_L1_001", StringComparison.OrdinalIgnoreCase) => null,
var c when c.StartsWith("S9_L1_002", StringComparison.OrdinalIgnoreCase) ||
c.StartsWith("S9_L1_003", StringComparison.OrdinalIgnoreCase) => DomainOrderDelivery,
var c when c.StartsWith("S9_L1_004", StringComparison.OrdinalIgnoreCase) => DomainWorkSchedule,
var c when c.StartsWith("S9_L1_005", StringComparison.OrdinalIgnoreCase) => DomainInventory,
_ => ResolveDomainByModule(module)
};
}
return ResolveDomainByModule(module);
}
private static string? ResolveDomainByModule(string module) => module switch
{
"S1" or "S7" => DomainOrderDelivery,
"S2" or "S6" => DomainWorkSchedule,
"S3" or "S4" => DomainSupplyPurchase,
"S5" => DomainInventory,
_ => null
};
private static string ResolveAggregation(string suffix, string metricCode)
{
if (metricCode.Contains("数量", StringComparison.Ordinal) ||
metricCode.EndsWith("_001", StringComparison.Ordinal) && metricCode.StartsWith("S8", StringComparison.OrdinalIgnoreCase))
{
return AggSum;
}
return suffix switch
{
"001" => AggAvg,
"002" => AggRatio,
"003" => AggAvg,
"004" => AggAvg,
"005" => AggAvg,
_ => AggAvg
};
}
private static string[] ResolveFilters(string domain) => domain switch
{
DomainOrderDelivery => OrderDeliveryFilters,
DomainWorkSchedule => WorkScheduleFilters,
DomainSupplyPurchase => SupplyPurchaseFilters,
DomainInventory => InventoryFilters,
_ => Array.Empty()
};
private static SmartOpsKpiAggregateRule SummaryRule(string metricCode) =>
new(metricCode, string.Empty, AggAvg, FilterSummaryOnly, Array.Empty());
private static List GetActiveFilterKeys(SmartOpsDashboardFilter filter)
{
var keys = new List();
if (filter.DateStart.HasValue || filter.DateEnd.HasValue) keys.Add("dateStart");
if (!string.IsNullOrWhiteSpace(filter.Product)) keys.Add("product");
if (!string.IsNullOrWhiteSpace(filter.OrderNo)) keys.Add("orderNo");
if (!string.IsNullOrWhiteSpace(filter.ProductionLine)) keys.Add("productionLine");
if (!string.IsNullOrWhiteSpace(filter.Customer)) keys.Add("customer");
if (!string.IsNullOrWhiteSpace(filter.Supplier)) keys.Add("supplier");
if (!string.IsNullOrWhiteSpace(filter.Material)) keys.Add("material");
if (!string.IsNullOrWhiteSpace(filter.PoNo)) keys.Add("poNo");
if (!string.IsNullOrWhiteSpace(filter.Warehouse)) keys.Add("warehouse");
if (!string.IsNullOrWhiteSpace(filter.WorkOrder)) keys.Add("workOrder");
if (!string.IsNullOrWhiteSpace(filter.Equipment)) keys.Add("equipment");
if (!string.IsNullOrWhiteSpace(filter.OutboundNo)) keys.Add("outboundNo");
return keys;
}
}
public sealed record SmartOpsKpiAggregateRule(
string MetricCode,
string DomainCode,
string AggregationType,
string FilterMode,
string[] SupportedFilters);