prepare_dataset.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. #!/usr/bin/env python3
  2. """
  3. Data cleaning and preparation for labor arbitration case corpus.
  4. Reads uploaded case files, filters non-labor cases, sanitizes privacy markers,
  5. and outputs a cleaned JSON corpus.
  6. """
  7. from __future__ import annotations
  8. import json
  9. import re
  10. from pathlib import Path
  11. UPLOADS_DIR = Path(__file__).resolve().parent.parent / "uploads"
  12. OUTPUT_PATH = Path(__file__).resolve().parent.parent / "data" / "raw_corpus.json"
  13. # Keywords that indicate a labor arbitration case (must have at least one)
  14. LABOR_KEYWORDS = [
  15. "申请人", "被申请人", "劳动关系", "工资", "加班",
  16. "劳动合同", "辞退", "解除劳动", "经济补偿", "工伤",
  17. "仲裁请求", "请求事项", "劳动争议", "社保", "仲裁委员会",
  18. ]
  19. # Keywords that suggest a non-labor case (civil loan, contract dispute, etc.)
  20. NON_LABOR_KEYWORDS = [
  21. "借款", "欠条", "民间借贷", "买卖合同", "租赁合同",
  22. "房屋买卖", "知识产权", "侵犯商标",
  23. ]
  24. def _clean_privacy(text: str) -> str:
  25. """Replace privacy-sensitive patterns with placeholders."""
  26. # Base64-like identity hashes (long alphanumeric/+//= strings)
  27. text = re.sub(r"[A-Za-z0-9+/]{30,}={0,3}", "[ID_HASH]", text)
  28. # Chinese ID card numbers (18 digits, possibly with X at end)
  29. text = re.sub(r"\b[1-9]\d{5}(?:19|20)\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])\d{3}[\dXx]\b", "[ID_NUM]", text)
  30. # Phone numbers
  31. text = re.sub(r"1[3-9]\d{9}", "[PHONE]", text)
  32. # Generic XX placeholder (keep as is - already anonymized)
  33. # text = re.sub(r"X{2,}", "[REDACTED]", text)
  34. return text
  35. def _is_labor_case(text: str) -> bool:
  36. """Check if the text appears to be a labor arbitration case."""
  37. labor_score = sum(1 for kw in LABOR_KEYWORDS if kw in text)
  38. non_labor_score = sum(1 for kw in NON_LABOR_KEYWORDS if kw in text)
  39. return labor_score >= 2 and non_labor_score == 0
  40. def _normalize_text(text: str) -> str:
  41. """Normalize whitespace and encoding."""
  42. text = text.replace("\r\n", "\n").replace("\r", "\n")
  43. # Normalize multiple newlines
  44. text = re.sub(r"\n{3,}", "\n\n", text)
  45. # Strip trailing/leading whitespace
  46. text = text.strip()
  47. return text
  48. def prepare_corpus() -> list[dict]:
  49. """Main data preparation function."""
  50. if not UPLOADS_DIR.exists():
  51. raise FileNotFoundError(f"Uploads directory not found: {UPLOADS_DIR}")
  52. OUTPUT_PATH.parent.mkdir(parents=True, exist_ok=True)
  53. corpus = []
  54. skipped = []
  55. files = sorted(UPLOADS_DIR.glob("*.txt"))
  56. for idx, filepath in enumerate(files):
  57. text = filepath.read_text(encoding="utf-8")
  58. text = _normalize_text(text)
  59. if not text.strip():
  60. skipped.append({"file": filepath.name, "reason": "empty"})
  61. continue
  62. if not _is_labor_case(text):
  63. skipped.append({"file": filepath.name, "reason": "non-labor"})
  64. continue
  65. text = _clean_privacy(text)
  66. # Extract case ID from filename (e.g., "10_xxx.txt" -> 10, or "364-014-2022-0001.txt")
  67. try:
  68. case_id = int(filepath.stem.split("_")[0])
  69. except ValueError:
  70. case_id = idx + 1000 # fallback for numeric filenames without underscore prefix
  71. corpus.append({
  72. "case_id": case_id,
  73. "file": filepath.name,
  74. "text": text,
  75. })
  76. # Save corpus
  77. output = {
  78. "total_files": len(files),
  79. "valid_cases": len(corpus),
  80. "skipped": skipped,
  81. "cases": corpus,
  82. }
  83. OUTPUT_PATH.write_text(json.dumps(output, ensure_ascii=False, indent=2), encoding="utf-8")
  84. print(f"Processed {len(files)} files:")
  85. print(f" Valid labor cases: {len(corpus)}")
  86. print(f" Skipped: {len(skipped)}")
  87. for s in skipped:
  88. print(f" - {s['file']}: {s['reason']}")
  89. print(f"Output saved to: {OUTPUT_PATH}")
  90. return corpus
  91. if __name__ == "__main__":
  92. prepare_corpus()