namespace Admin.NET.Plugin.AiDOP.DataPlatform; /// /// 岗位原文 → 中立岗位码。精确值优先于关键字。 /// S3/S5/S6/S7 人效分母按 PLANNER/WAREHOUSE/PRODUCTION 逐个过滤,故 OTHER 与 UNKNOWN 均不进分母; /// 两者的差别只在运维可见性:UNKNOWN 会记 POSITION_UNMAPPED 并进待映射清单,OTHER 不会。 /// public static class EmployeePositionRules { public const string Warehouse = "WAREHOUSE"; public const string Production = "PRODUCTION"; public const string Planner = "PLANNER"; public const string Other = "OTHER"; public const string Unknown = "UNKNOWN"; public static readonly string[] WarehouseKeywords = ["仓", "库管", "保管", "物料员", "WMS", "warehouse", "storekeeper"]; public static readonly string[] ProductionKeywords = ["生产", "操作", "装配", "车间", "班组", "产线", "工段", "production", "assembly", "operator", "workshop"]; public static readonly string[] PlannerKeywords = ["计划", "PMC", "调度", "planner", "planning", "scheduler"]; /// 已确认不属于仓管/生产/计划的岗位,归 OTHER 后不再记 POSITION_UNMAPPED 告警。 public static readonly string[] OtherKeywords = ["quality", "质量", "QC", "QA", "数据员", "研发"]; /// T8 gw 原值精确匹配,优先于关键字。 public static readonly IReadOnlyDictionary Exact = new Dictionary(StringComparer.Ordinal) { ["计划"] = Planner, ["仓管"] = Warehouse, ["生产"] = Production }; public static string Classify(string? raw) { if (string.IsNullOrWhiteSpace(raw)) return Unknown; var text = raw.Trim(); if (Exact.TryGetValue(text, out var exact)) return exact; if (Matches(text, WarehouseKeywords)) return Warehouse; if (Matches(text, ProductionKeywords)) return Production; if (Matches(text, PlannerKeywords)) return Planner; // OTHER 放在三类业务岗之后:岗位原文同时含「质量」与「仓」时仍归 WAREHOUSE。 if (Matches(text, OtherKeywords)) return Other; return Unknown; } // 岗位原文的大小写不由平台控制(165 的 JobTitle 是小写英文,T8 的 gw 是中文),统一忽略大小写比对。 private static bool Matches(string text, string[] keywords) => keywords.Any(keyword => text.Contains(keyword, StringComparison.OrdinalIgnoreCase)); }