-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathextract_entities.py
More file actions
157 lines (139 loc) · 6.95 KB
/
Copy pathextract_entities.py
File metadata and controls
157 lines (139 loc) · 6.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
#!/usr/bin/env python3
"""Entity extraction over a JSON batch of claims.
Inputs and outputs are CLI args so the script is portable across machines.
Codex P2 fix (mm-e431 follow-up): no hardcoded G:/_OneDrive/... paths.
"""
import argparse
import json
import re
from pathlib import Path
# Entity patterns
PATTERNS = {
"person_name": [
r"\b[A-Z][a-z]+\s+[A-Z][a-z]+\b", # First Last
r"\b[A-Z](?:ndrej|Karpathy|Lovelace)\b", # Known names
],
"spanish_surname": [
r"\b(?:García|Messi|López|Rodríguez|Martínez|Hernández|González|Pérez|Fernández|Díaz)\b",
],
"time_expression": [
r"(?:next|last|this)\s+(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)",
r"(?:el|la)\s+(?:lunes|martes|miércoles|jueves|viernes|sábado|domingo)(?:\s+(?:pasado|próximo|que viene))?",
r"Q[1-4]\s+20\d{2}",
r"in\s+\d+\s+(?:days|weeks|months|hours|minutes)",
r"(?:next|last)\s+(?:week|month|quarter|year)",
],
"model_name": [
r"(?:gpt-4o(?:-mini)?|claude-(?:opus|sonnet|haiku)(?:-\d+)?(?:-\d+)?|gemini-\d+\.?\d*-(?:flash|pro)|llama-\d+|mistral)",
r"\b(?:GPT-4|GPT-3\.5|Claude|Gemini|Llama|Mistral)\b",
],
"library_name": [
r"\b(?:FastAPI|React|Django|Flask|SQLAlchemy|PyTorch|TensorFlow|pandas|numpy|pytest|Webpack|Vite|Next\.js|Vue|Angular|Express|Node\.js|pyafipws|Qdrant|PostgreSQL|SQLite|Redis|Celery|Gunicorn|pytest|Docker|Kubernetes|Terraform|Ansible|Prometheus|Grafana|ELK|Spring|Hibernate|Maven|Gradle|Kotlin|Rust|Go|Ruby|PHP|Java|C\#|C\+\+|JavaScript|TypeScript)\b",
],
"concept": [
r"(?:writer-lock|RRF\s+fusion|AFIP\s+electronic\s+invoicing|byzantine\s+consensus|consensus\s+algorithm|state\s+machine|replication|vector\s+search|embedding|retrieval|ranking|BM25|semantic\s+similarity|recall|precision|F1|MAP|NDCG|cross-encoder|knowledge\s+graph|ontology|schema|validation|schema\s+evolution|backward\s+compatibility)",
],
}
def extract_entities(text):
"""Extract entities from claim text."""
entities = []
seen = set()
# person_name: look for capital letters in pairs
person_matches = re.finditer(r'\b[A-Z][a-z]+(?:\s+[A-Z][a-z]+)+\b', text)
for m in person_matches:
surface = m.group()
if surface not in seen and len(entities) < 8:
entities.append({"kind": "person_name", "surface_form": surface})
seen.add(surface)
# time_expression: structured date/time patterns
time_patterns = [
(r'\b(?:next|last|this)\s+(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday|week|month|quarter|year)\b', 'time_expression'),
(r'\b(?:el|la)\s+(?:lunes|martes|miércoles|jueves|viernes|sábado|domingo)(?:\s+(?:pasado|próximo|que\s+viene))?\b', 'time_expression'),
(r'\bQ[1-4]\s+20\d{2}\b', 'time_expression'),
(r'\bin\s+\d+\s+(?:days?|weeks?|months?|hours?|minutes?)\b', 'time_expression'),
]
for pattern, kind in time_patterns:
for m in re.finditer(pattern, text, re.IGNORECASE):
surface = m.group()
if surface not in seen and len(entities) < 8:
entities.append({"kind": kind, "surface_form": surface})
seen.add(surface)
# model_name: LLM model identifiers
model_patterns = [
r'\bgpt-4o(?:-mini)?\b',
r'\bGPT-[34](?:\.[50])?\b',
r'\bclaude-(?:opus|sonnet|haiku)(?:-\d+)*\b',
r'\bgemini-\d+(?:\.\d)?-(?:flash|pro)\b',
r'\bClaude\b',
r'\bGPT\b',
r'\bGemini\b',
r'\b(?:Llama|Mistral|Falcon)\b',
]
for pattern in model_patterns:
for m in re.finditer(pattern, text, re.IGNORECASE):
surface = m.group()
if surface not in seen and len(entities) < 8:
entities.append({"kind": "model_name", "surface_form": surface})
seen.add(surface)
# library_name: software frameworks/libraries
lib_patterns = [
r'\b(?:FastAPI|SQLAlchemy|PyTorch|TensorFlow|pytest|Webpack|Next\.js|Express|SQLite|PostgreSQL|Docker|Kubernetes|Terraform|Redis|Celery|Qdrant|pyafipws|FastMCP)\b',
r'\b(?:React|Django|Flask|pandas|numpy|Vue|Angular|Spring|Hibernate)\b',
]
for pattern in lib_patterns:
for m in re.finditer(pattern, text, re.IGNORECASE):
surface = m.group()
if surface not in seen and len(entities) < 8:
entities.append({"kind": "library_name", "surface_form": surface})
seen.add(surface)
# concept: domain-specific concepts
concept_patterns = [
r'\b(?:writer-lock|RRF\s+fusion|AFIP\s+electronic\s+invoicing|byzantine\s+consensus)\b',
r'\b(?:BM25|semantic\s+similarity|cross-encoder|knowledge\s+graph|state\s+machine)\b',
r'\b(?:vector\s+search|embedding|retrieval|ranking|consensus)\b',
]
for pattern in concept_patterns:
for m in re.finditer(pattern, text, re.IGNORECASE):
surface = m.group()
if surface not in seen and len(entities) < 8:
entities.append({"kind": "concept", "surface_form": surface})
seen.add(surface)
# spanish_surname: solo apellido patterns
spanish_surnames = ['García', 'Messi', 'López', 'Rodríguez', 'Martínez', 'Hernández', 'González', 'Pérez', 'Fernández', 'Díaz']
for surname in spanish_surnames:
for m in re.finditer(r'\b' + surname + r'\b', text):
surface = m.group()
if surface not in seen and len(entities) < 8:
entities.append({"kind": "spanish_surname", "surface_form": surface})
seen.add(surface)
break # Only once per surname
return entities
def main():
repo_root = Path(__file__).resolve().parent
default_input = repo_root / "artifacts" / "l2-haiku-batches" / "in-batch01.json"
default_output = repo_root / "artifacts" / "l2-haiku-batches" / "out-batch01.json"
parser = argparse.ArgumentParser(description="Extract L2 entities from a JSON batch of claims.")
parser.add_argument("--input", "-i", type=Path, default=default_input,
help=f"Input JSON file with claims [default: {default_input}]")
parser.add_argument("--output", "-o", type=Path, default=default_output,
help=f"Output JSON file [default: {default_output}]")
args = parser.parse_args()
input_path = args.input
output_path = args.output
output_path.parent.mkdir(parents=True, exist_ok=True)
with open(input_path, 'r', encoding='utf-8') as f:
claims = json.load(f)
results = []
total_entities = 0
for claim in claims:
entities = extract_entities(claim['text'])
total_entities += len(entities)
results.append({
"id": claim['id'],
"entities": entities
})
with open(output_path, 'w', encoding='utf-8') as f:
json.dump(results, f, ensure_ascii=False, indent=2)
print(f"done: {len(results)} claims processed, {total_entities} entities extracted")
if __name__ == '__main__':
main()