EmployeePositionRules.cs 3.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
  1. namespace Admin.NET.Plugin.AiDOP.DataPlatform;
  2. /// <summary>
  3. /// 岗位原文 → 中立岗位码。精确值优先于关键字。
  4. /// S3/S5/S6/S7 人效分母按 PLANNER/WAREHOUSE/PRODUCTION 逐个过滤,故 OTHER 与 UNKNOWN 均不进分母;
  5. /// 两者的差别只在运维可见性:UNKNOWN 会记 POSITION_UNMAPPED 并进待映射清单,OTHER 不会。
  6. /// </summary>
  7. public static class EmployeePositionRules
  8. {
  9. public const string Warehouse = "WAREHOUSE";
  10. public const string Production = "PRODUCTION";
  11. public const string Planner = "PLANNER";
  12. public const string Other = "OTHER";
  13. public const string Unknown = "UNKNOWN";
  14. /// <summary>人工映射允许写入的岗位码全集。前端下拉选项必须与本数组一致,由守卫测试钉住。</summary>
  15. public static readonly string[] AllowedCodes = [Warehouse, Production, Planner, Other, Unknown];
  16. public static readonly string[] WarehouseKeywords = ["仓", "库管", "保管", "物料员", "WMS", "warehouse", "storekeeper"];
  17. public static readonly string[] ProductionKeywords = ["生产", "操作", "装配", "车间", "班组", "产线", "工段", "production", "assembly", "operator", "workshop"];
  18. public static readonly string[] PlannerKeywords = ["计划", "PMC", "调度", "planner", "planning", "scheduler"];
  19. /// <summary>已确认不属于仓管/生产/计划的岗位,归 OTHER 后不再记 POSITION_UNMAPPED 告警。</summary>
  20. public static readonly string[] OtherKeywords = ["quality", "质量", "QC", "QA", "数据员", "研发"];
  21. /// <summary>T8 gw 原值精确匹配,优先于关键字。</summary>
  22. public static readonly IReadOnlyDictionary<string, string> Exact = new Dictionary<string, string>(StringComparer.Ordinal)
  23. {
  24. ["计划"] = Planner,
  25. ["仓管"] = Warehouse,
  26. ["生产"] = Production
  27. };
  28. public static string Classify(string? raw)
  29. {
  30. if (string.IsNullOrWhiteSpace(raw))
  31. return Unknown;
  32. var text = raw.Trim();
  33. if (Exact.TryGetValue(text, out var exact))
  34. return exact;
  35. if (Matches(text, WarehouseKeywords))
  36. return Warehouse;
  37. if (Matches(text, ProductionKeywords))
  38. return Production;
  39. if (Matches(text, PlannerKeywords))
  40. return Planner;
  41. // OTHER 放在三类业务岗之后:岗位原文同时含「质量」与「仓」时仍归 WAREHOUSE。
  42. if (Matches(text, OtherKeywords))
  43. return Other;
  44. return Unknown;
  45. }
  46. /// <summary>
  47. /// 规范化人工传入的岗位码:命中返回标准大写值,非法返回 null。
  48. /// MySQL 默认排序规则不区分大小写,直接存小写会绕过校验却与 KPI 的等值过滤"看起来"仍匹配,故一律归一化再入库。
  49. /// </summary>
  50. public static string? Normalize(string? code)
  51. {
  52. if (string.IsNullOrWhiteSpace(code))
  53. return null;
  54. var text = code.Trim();
  55. return AllowedCodes.FirstOrDefault(allowed => string.Equals(allowed, text, StringComparison.OrdinalIgnoreCase));
  56. }
  57. // 岗位原文的大小写不由平台控制(165 的 JobTitle 是小写英文,T8 的 gw 是中文),统一忽略大小写比对。
  58. private static bool Matches(string text, string[] keywords)
  59. => keywords.Any(keyword => text.Contains(keyword, StringComparison.OrdinalIgnoreCase));
  60. }