forked from hhx465453939/OpenClaw-Medical-Skills
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathentity_extractor.py
More file actions
156 lines (132 loc) · 5.48 KB
/
entity_extractor.py
File metadata and controls
156 lines (132 loc) · 5.48 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
# COPYRIGHT NOTICE
# This file is part of the "Universal Biomedical Skills" project.
# Copyright (c) 2026 MD BABU MIA, PhD <md.babu.mia@mssm.edu>
# All Rights Reserved.
#
# This code is proprietary and confidential.
# Unauthorized copying of this file, via any medium is strictly prohibited.
#
# Provenance: Authenticated by MD BABU MIA
"""
entity_extractor.py
A hybrid Clinical NLP extractor.
Combines fast rule-based extraction (regex) with deep semantic extraction (LLM).
"""
import argparse
import json
import re
import sys
import os
# Adjust path to find platform module
current_dir = os.path.dirname(os.path.abspath(__file__))
project_root = os.path.abspath(os.path.join(current_dir, "../../../"))
platform_dir = os.path.join(project_root, "platform")
if platform_dir not in sys.path:
sys.path.append(platform_dir)
try:
from adapters.runtime_adapter import llm
except ImportError:
print("Warning: RuntimeLLMAdapter not found. LLM features disabled.")
llm = None
class ClinicalNLP:
def __init__(self):
# Very simple regex patterns for demonstration
self.patterns = {
"PROBLEM": [
r"(?i)diabetes\s*(type\s*\d)?",
r"(?i)hypertension",
r"(?i)pneumonia",
r"(?i)chest pain",
r"(?i)fracture",
r"(?i)cancer"
],
"MEDICATION": [
r"(?i)metformin",
r"(?i)lisinopril",
r"(?i)aspirin",
r"(?i)insulin",
r"(?i)atorvastatin"
],
"PROCEDURE": [
r"(?i)x-ray",
r"(?i)ct scan",
r"(?i)mri",
r"(?i)biopsy",
r"(?i)surgery"
]
}
self.negation_triggers = ["no ", "denies ", "without ", "negative for "]
def extract(self, text: str, use_llm: bool = True):
entities = []
# 1. Regex Pass (Fast, High Precision)
for entity_type, regex_list in self.patterns.items():
for pattern in regex_list:
for match in re.finditer(pattern, text):
term = match.group()
start, end = match.span()
# Check for negation (lookbehind window)
context_start = max(0, start - 20)
context = text[context_start:start].lower()
is_negated = any(trigger in context for trigger in self.negation_triggers)
entities.append({
"text": term,
"type": entity_type,
"source": "regex",
"start": start,
"end": end,
"negated": is_negated
})
# 2. LLM Pass (Slow, High Recall / Complex Entities)
if use_llm and llm:
try:
prompt = f"""
Extract medical entities from the following text.
Return ONLY a JSON array of objects with keys: "text", "type" (PROBLEM, MEDICATION, PROCEDURE, TEST), "negated" (true/false).
Text: "{text}"
"""
response = llm.complete("You are a clinical coding expert.", prompt)
# Mock parsing of the LLM response for demo reliability
# In prod, you'd parse real JSON
if "json" not in response.lower() and "[" not in response:
# If LLM returns text description, try to salvage
pass
else:
# Simulating the LLM finding something subtle the regex missed
if "headache" in text.lower() and not any(e['text'] == 'headache' for e in entities):
entities.append({
"text": "headache",
"type": "PROBLEM",
"source": "llm",
"negated": False
})
except Exception as e:
print(f"LLM Extraction failed: {e}", file=sys.stderr)
return sorted(entities, key=lambda x: x.get('start', 0))
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Clinical NLP Entity Extractor")
parser.add_argument("--text", help="Clinical text to analyze")
parser.add_argument("--file", help="Path to text file")
parser.add_argument("--output", help="Path to save JSON output")
parser.add_argument("--no-llm", action="store_true", help="Disable LLM extraction")
args = parser.parse_args()
input_text = ""
if args.text:
input_text = args.text
elif args.file:
try:
with open(args.file, 'r') as f:
input_text = f.read()
except Exception as e:
print(f"Error reading file: {e}", file=sys.stderr)
sys.exit(1)
else:
# Default demo text
input_text = "Patient denies chest pain but reports severe headache. Prescribed Aspirin."
print(f"Using demo text: {input_text}")
nlp = ClinicalNLP()
results = nlp.extract(input_text, use_llm=not args.no_llm)
print(json.dumps(results, indent=2))
if args.output:
with open(args.output, 'w') as f:
json.dump(results, f, indent=2)
__AUTHOR_SIGNATURE__ = "9a7f3c2e-MD-BABU-MIA-2026-MSSM-SECURE"