bio_schema.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. """
  2. BIO tagging schema for labor arbitration case element extraction.
  3. 15 entity types, 31 labels total (15 x B/I + O).
  4. """
  5. from __future__ import annotations
  6. from typing import Any
  7. # Entity types with their BIO prefixes
  8. ENTITY_TYPES = [
  9. "APPLICANT_NAME", # 申请人姓名
  10. "RESPONDENT_NAME", # 被申请人名称
  11. "ENTRY_DATE", # 入职日期
  12. "LEAVE_DATE", # 离职日期
  13. "FILING_DATE", # 立案日期
  14. "MONTH_SALARY", # 月工资标准
  15. "CLAIM_AMOUNT", # 主张金额
  16. "WORKER_POSITION", # 劳动者岗位
  17. "ARBITRATION_ORG", # 仲裁机构
  18. "LAW_REF", # 法律条款引用
  19. "CASE_NUMBER", # 案号
  20. "EVIDENCE", # 证据材料
  21. "TERMINATION_REASON", # 解除原因
  22. "OVERTIME_DESC", # 加班描述
  23. "WORK_DURATION", # 工作年限
  24. ]
  25. # Build label list: O + B-ENTITY + I-ENTITY for each type
  26. LABELS = ["O"]
  27. LABEL2ID = {"O": 0}
  28. for entity in ENTITY_TYPES:
  29. b_label = f"B-{entity}"
  30. i_label = f"I-{entity}"
  31. LABELS.append(b_label)
  32. LABELS.append(i_label)
  33. LABEL2ID[b_label] = len(LABEL2ID)
  34. LABEL2ID[i_label] = len(LABEL2ID)
  35. ID2LABEL = {v: k for k, v in LABEL2ID.items()}
  36. NUM_LABELS = len(LABELS)
  37. def get_entity_spans_from_bio(
  38. tokens: list[str],
  39. labels: list[int],
  40. ) -> list[dict[str, Any]]:
  41. """Convert BIO tag sequence back to entity spans."""
  42. spans = []
  43. current_entity = None
  44. current_tokens = []
  45. current_start = -1
  46. for i, (token, label_id) in enumerate(zip(tokens, labels)):
  47. label = ID2LABEL.get(label_id, "O")
  48. if label.startswith("B-"):
  49. if current_entity is not None:
  50. spans.append({
  51. "entity": current_entity,
  52. "text": "".join(current_tokens),
  53. "start": current_start,
  54. "end": i,
  55. })
  56. current_entity = label[2:]
  57. current_tokens = [token]
  58. current_start = i
  59. elif label.startswith("I-") and current_entity == label[2:]:
  60. current_tokens.append(token)
  61. else:
  62. if current_entity is not None:
  63. spans.append({
  64. "entity": current_entity,
  65. "text": "".join(current_tokens),
  66. "start": current_start,
  67. "end": i,
  68. })
  69. current_entity = None
  70. current_tokens = []
  71. current_start = -1
  72. if current_entity is not None:
  73. spans.append({
  74. "entity": current_entity,
  75. "text": "".join(current_tokens),
  76. "start": current_start,
  77. "end": len(tokens),
  78. })
  79. return spans
  80. def spans_to_bio(
  81. tokens: list[str],
  82. spans: list[dict[str, Any]],
  83. ) -> list[int]:
  84. """Convert entity spans to BIO tag sequence."""
  85. labels = ["O"] * len(tokens)
  86. for span in spans:
  87. entity = span["entity"]
  88. start = span["start"]
  89. end = span["end"]
  90. b_key = f"B-{entity}"
  91. i_key = f"I-{entity}"
  92. if b_key in LABEL2ID:
  93. labels[start] = b_key
  94. for i in range(start + 1, min(end, len(tokens))):
  95. labels[i] = i_key
  96. return [LABEL2ID[l] for l in labels]
  97. # Mapping from entity types to flat schema field keys (for converting
  98. # extracted spans into the element dictionary expected by the backend)
  99. ENTITY_TO_FIELD = {
  100. "APPLICANT_NAME": "applicant_name",
  101. "RESPONDENT_NAME": "respondent_name",
  102. "ENTRY_DATE": "entry_date",
  103. "LEAVE_DATE": "leave_date",
  104. "FILING_DATE": "filing_date",
  105. "MONTH_SALARY": "month_salary",
  106. "CLAIM_AMOUNT": "claims", # merges into claims.amount_total
  107. "WORKER_POSITION": "worker_position",
  108. "ARBITRATION_ORG": "arbitration_org",
  109. "LAW_REF": "law_refs", # list field
  110. "CASE_NUMBER": "case_number",
  111. "EVIDENCE": "evidence_materials", # list field
  112. "TERMINATION_REASON": "termination_reason",
  113. "OVERTIME_DESC": "overtime_desc",
  114. "WORK_DURATION": "work_duration_text",
  115. }