analyze_renewal_folders.py 3.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. """扫描项目档案目录,识别合同期文件夹并生成续签链清单(CSV)。"""
  2. from __future__ import annotations
  3. import csv
  4. import re
  5. import sys
  6. from datetime import date, timedelta
  7. from pathlib import Path
  8. sys.stdout.reconfigure(encoding="utf-8")
  9. ROOT = Path(r"E:\申勤物业项目资料")
  10. OUT = Path(r"E:\CODE\knowledge_agent\output\续签链-档案目录.csv")
  11. PAT = re.compile(r"^(\d{4})\.(\d{1,2})\.(\d{1,2})-(\d{4})\.(\d{1,2})\.(\d{1,2})(.*)$")
  12. def _d(y: str, m: str, d: str) -> date:
  13. return date(int(y), int(m), int(d))
  14. def _norm_subject(name: str) -> str:
  15. """归一化合同主体:去掉扩展名、点招/补充标记与空白。"""
  16. s = name
  17. for suf in (".pdf", ".doc", ".docx", ".xls", ".xlsx"):
  18. s = s.replace(suf, "")
  19. s = re.sub(r"[((]?(点招|补充)[))]?", "", s)
  20. s = re.sub(r"[—-].*(点招|补充)$", "", s)
  21. return re.sub(r"\s+", "", s)
  22. def main() -> None:
  23. rows = []
  24. chains = 0
  25. period_total = 0
  26. for region in sorted(p for p in ROOT.iterdir() if p.is_dir() and not p.name.startswith(("6-", "7-"))):
  27. for proj in sorted(p for p in region.iterdir() if p.is_dir()):
  28. periods = []
  29. # 项目目录下可能直接是合同期文件夹,也可能再套一层项目名
  30. for cand in sorted(p for p in proj.iterdir() if p.is_dir()):
  31. m = PAT.match(cand.name)
  32. if m:
  33. periods.append((cand.name, m))
  34. else:
  35. for sub in sorted(p for p in cand.iterdir() if p.is_dir()):
  36. m2 = PAT.match(sub.name)
  37. if m2:
  38. periods.append((sub.name, m2))
  39. if not periods:
  40. continue
  41. periods.sort(key=lambda x: (_d(x[1].group(1), x[1].group(2), x[1].group(3)), x[0]))
  42. period_total += len(periods)
  43. num = proj.name.split("-")[0] if "-" in proj.name else ""
  44. prev: tuple[date, str, str] | None = None
  45. for name, m in periods:
  46. start = _d(m.group(1), m.group(2), m.group(3))
  47. end = _d(m.group(4), m.group(5), m.group(6))
  48. subject = _norm_subject(m.group(7))
  49. renewal = ""
  50. if prev is not None:
  51. contig = start <= prev[0] + timedelta(days=3)
  52. same_subj = prev[2] == subject or not prev[2] or not subject
  53. if contig and same_subj:
  54. renewal = f"是(上一期: {prev[1]})"
  55. chains += 1
  56. rows.append([
  57. region.name, proj.name, num, name,
  58. start.isoformat(), end.isoformat(), subject, renewal,
  59. ])
  60. prev = (end, name, subject)
  61. with OUT.open("w", newline="", encoding="utf-8-sig") as f:
  62. w = csv.writer(f)
  63. w.writerow(["区域", "项目目录", "目录编号", "合同期文件夹", "开始日期", "结束日期", "合同主体(归一)", "续签自上一期"])
  64. w.writerows(rows)
  65. print(f"扫描根目录: {ROOT}")
  66. print(f"合同期文件夹总数: {period_total}")
  67. print(f"识别到续签衔接: {chains} 条")
  68. print(f"输出: {OUT}")
  69. # 示例
  70. print("\n示例(青浦区图书馆):")
  71. for r in rows:
  72. if "102-青浦区图书馆" in r[1]:
  73. print(" ", " | ".join(r[2:]))
  74. if __name__ == "__main__":
  75. main()