analyze_shift_mapping.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. """分析排班表的组织代码 → 项目 映射(用考勤表交叉比对推断)。"""
  2. from __future__ import annotations
  3. import re
  4. import sys
  5. from pathlib import Path
  6. import pandas as pd
  7. sys.stdout.reconfigure(encoding="utf-8")
  8. HR_ROOT = Path(r"E:\CODE\knowledge_agent\reference\20260529_申勤提供数据\人事部")
  9. def _norm_project(name: str) -> str:
  10. return name.removeprefix("青浦-").strip()
  11. def _header_info(df: pd.DataFrame) -> tuple[str, int]:
  12. project, year = "", 2026
  13. for r in range(min(3, len(df))):
  14. for c in range(min(12, df.shape[1])):
  15. v = df.iat[r, c]
  16. if pd.isna(v):
  17. continue
  18. s = str(v)
  19. m = re.search(r"项目[::]\s*(.+)", s)
  20. if m and not project:
  21. project = m.group(1).strip()
  22. ym = re.search(r"20(\d{2})\s*年", s)
  23. if ym:
  24. year = 2000 + int(ym.group(1))
  25. return project, year
  26. def _month(sh: str) -> int | None:
  27. m = re.search(r"(\d{1,2})\s*月", sh)
  28. return int(m.group(1)) if m else None
  29. def main() -> None:
  30. # 1) 考勤:工号 -> {(项目, 月)}
  31. att: dict[str, set[tuple[str, int]]] = {}
  32. for p in sorted(HR_ROOT.rglob("*.xls")):
  33. try:
  34. xls = pd.ExcelFile(p)
  35. except Exception: # noqa: BLE001
  36. continue
  37. for sh in xls.sheet_names:
  38. month = _month(sh)
  39. if month is None:
  40. continue
  41. try:
  42. df = pd.read_excel(xls, sheet_name=sh, header=None, dtype=str)
  43. except Exception: # noqa: BLE001
  44. continue
  45. project, _ = _header_info(df)
  46. project = _norm_project(project)
  47. sub = df.iloc[4:].copy()
  48. sub.columns = range(sub.shape[1])
  49. for emp in sub[1].dropna():
  50. e = str(emp).strip()
  51. if e:
  52. att.setdefault(e, set()).add((project, month))
  53. # 2) 排班:组织代码 -> {工号集合},按文件分组
  54. org_by_file: dict[str, dict[str, set[str]]] = {}
  55. for p in sorted(HR_ROOT.rglob("排班*.xlsx")):
  56. try:
  57. xls = pd.ExcelFile(p)
  58. except Exception: # noqa: BLE001
  59. continue
  60. orgs: dict[str, set[str]] = {}
  61. for sh in xls.sheet_names:
  62. if sh == "班次列表":
  63. continue
  64. try:
  65. df = pd.read_excel(xls, sheet_name=sh, header=None, dtype=str)
  66. except Exception: # noqa: BLE001
  67. continue
  68. if df.shape[1] < 3:
  69. continue
  70. sub = df.iloc[2:].copy()
  71. sub.columns = range(sub.shape[1])
  72. for _, row in sub.iterrows():
  73. oc = str(row[0]).strip() if pd.notna(row[0]) else ""
  74. emp = str(row[2]).strip() if pd.notna(row[2]) else ""
  75. if oc and emp:
  76. orgs.setdefault(oc, set()).add(emp)
  77. org_by_file[p.name] = orgs
  78. # 3) 交叉比对:组织代码的员工在考勤里属于哪些项目
  79. print("== 组织代码 → 项目 推断(考勤交叉比对)==")
  80. total_orgs = 0
  81. resolved = 0
  82. for fname, orgs in org_by_file.items():
  83. print(f"\n### {fname[:30]}… 组织代码数: {len(orgs)}")
  84. for oc, emps in sorted(orgs.items()):
  85. total_orgs += 1
  86. projs = set()
  87. for e in emps:
  88. for (pj, _m) in att.get(e, set()):
  89. projs.add(pj)
  90. if len(projs) == 1:
  91. resolved += 1
  92. status = "✓ 唯一"
  93. elif len(projs) > 1:
  94. status = f"? 多项目 {sorted(projs)}"
  95. else:
  96. status = "✗ 无法推断(考勤中无此人)"
  97. print(f" {oc}: {status} 员工数: {len(emps)}")
  98. print(f"\n组织代码总数: {total_orgs},唯一推断成功: {resolved}")
  99. if __name__ == "__main__":
  100. main()