Ali2206 commited on
Commit
3deb36c
·
verified ·
1 Parent(s): c399514

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +397 -126
app.py CHANGED
@@ -4,13 +4,27 @@ import pandas as pd
4
  import pdfplumber
5
  import json
6
  import gradio as gr
7
- from typing import List
8
  from concurrent.futures import ThreadPoolExecutor, as_completed
9
  import hashlib
10
  import shutil
11
  import re
12
  import psutil
13
  import subprocess
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
  # Persistent directory
16
  persistent_dir = "/data/hf_cache"
@@ -41,172 +55,429 @@ MEDICAL_KEYWORDS = {'diagnosis', 'assessment', 'plan', 'results', 'medications',
41
  'allergies', 'summary', 'impression', 'findings', 'recommendations'}
42
 
43
  def sanitize_utf8(text: str) -> str:
44
- return text.encode("utf-8", "ignore").decode("utf-8")
 
 
 
 
 
45
 
46
  def file_hash(path: str) -> str:
47
- with open(path, "rb") as f:
48
- return hashlib.md5(f.read()).hexdigest()
 
 
 
 
 
49
 
50
  def extract_priority_pages(file_path: str, max_pages: int = 20) -> str:
 
51
  try:
52
  text_chunks = []
 
 
53
  with pdfplumber.open(file_path) as pdf:
 
54
  for i, page in enumerate(pdf.pages[:3]):
55
- text = page.extract_text() or ""
56
- text_chunks.append(f"=== Page {i+1} ===\n{text.strip()}")
 
 
 
 
 
 
57
  for i, page in enumerate(pdf.pages[3:max_pages], start=4):
58
- page_text = page.extract_text() or ""
59
- if any(re.search(rf'\b{kw}\b', page_text.lower()) for kw in MEDICAL_KEYWORDS):
60
- text_chunks.append(f"=== Page {i} ===\n{page_text.strip()}")
 
 
 
 
61
  return "\n\n".join(text_chunks)
62
  except Exception as e:
 
63
  return f"PDF processing error: {str(e)}"
64
 
65
  def convert_file_to_json(file_path: str, file_type: str) -> str:
 
66
  try:
67
  h = file_hash(file_path)
 
 
 
68
  cache_path = os.path.join(file_cache_dir, f"{h}.json")
 
 
69
  if os.path.exists(cache_path):
70
- with open(cache_path, "r", encoding="utf-8") as f:
71
- return f.read()
72
-
73
- if file_type == "pdf":
74
- text = extract_priority_pages(file_path)
75
- result = json.dumps({"filename": os.path.basename(file_path), "content": text, "status": "initial"})
76
- elif file_type == "csv":
77
- df = pd.read_csv(file_path, encoding_errors="replace", header=None, dtype=str,
78
- skip_blank_lines=False, on_bad_lines="skip")
79
- content = df.fillna("").astype(str).values.tolist()
80
- result = json.dumps({"filename": os.path.basename(file_path), "rows": content})
81
- elif file_type in ["xls", "xlsx"]:
82
  try:
83
- df = pd.read_excel(file_path, engine="openpyxl", header=None, dtype=str)
84
- except Exception:
85
- df = pd.read_excel(file_path, engine="xlrd", header=None, dtype=str)
86
- content = df.fillna("").astype(str).values.tolist()
87
- result = json.dumps({"filename": os.path.basename(file_path), "rows": content})
88
- else:
89
- result = json.dumps({"error": f"Unsupported file type: {file_type}"})
90
- with open(cache_path, "w", encoding="utf-8") as f:
91
- f.write(result)
92
- return result
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
  except Exception as e:
94
- return json.dumps({"error": f"Error processing {os.path.basename(file_path)}: {str(e)}"})
 
95
 
96
  def log_system_usage(tag=""):
 
97
  try:
98
  cpu = psutil.cpu_percent(interval=1)
99
  mem = psutil.virtual_memory()
100
- print(f"[{tag}] CPU: {cpu}% | RAM: {mem.used // (1024**2)}MB / {mem.total // (1024**2)}MB")
101
- result = subprocess.run(
102
- ["nvidia-smi", "--query-gpu=memory.used,memory.total,utilization.gpu", "--format=csv,nounits,noheader"],
103
- capture_output=True, text=True
104
- )
105
- if result.returncode == 0:
106
- used, total, util = result.stdout.strip().split(", ")
107
- print(f"[{tag}] GPU: {used}MB / {total}MB | Utilization: {util}%")
 
 
 
 
108
  except Exception as e:
109
- print(f"[{tag}] GPU/CPU monitor failed: {e}")
110
 
111
  def init_agent():
112
- print("🔁 Initializing model...")
 
113
  log_system_usage("Before Load")
 
114
  default_tool_path = os.path.abspath("data/new_tool.json")
115
  target_tool_path = os.path.join(tool_cache_dir, "new_tool.json")
116
- if not os.path.exists(target_tool_path):
117
- shutil.copy(default_tool_path, target_tool_path)
118
-
119
- agent = TxAgent(
120
- model_name="mims-harvard/TxAgent-T1-Llama-3.1-8B",
121
- rag_model_name="mims-harvard/ToolRAG-T1-GTE-Qwen2-1.5B",
122
- tool_files_dict={"new_tool": target_tool_path},
123
- force_finish=True,
124
- enable_checker=True,
125
- step_rag_num=8,
126
- seed=100,
127
- additional_default_tools=[],
128
- )
129
- agent.init_model()
130
- log_system_usage("After Load")
131
- print("✅ Agent Ready")
132
- return agent
133
 
134
- def create_ui(agent):
135
- with gr.Blocks(theme=gr.themes.Soft()) as demo:
136
- gr.Markdown("<h1 style='text-align: center;'>🩺 Clinical Oversight Assistant</h1>")
137
- chatbot = gr.Chatbot(label="Analysis", height=600, type="messages")
138
- file_upload = gr.File(file_types=[".pdf", ".csv", ".xls", ".xlsx"], file_count="multiple")
139
- msg_input = gr.Textbox(placeholder="Ask about potential oversights...", show_label=False)
140
- send_btn = gr.Button("Analyze", variant="primary")
141
- download_output = gr.File(label="Download Full Report")
142
-
143
- def analyze(message: str, history: list, files: list):
144
- history = history + [{"role": "user", "content": message}]
145
- history.append({"role": "assistant", "content": "⏳ Analyzing records for potential oversights..."})
146
- yield history, None
147
-
148
- extracted = ""
149
- file_hash_value = ""
150
- if files:
151
- with ThreadPoolExecutor(max_workers=4) as executor:
152
- futures = [executor.submit(convert_file_to_json, f.name, f.name.split(".")[-1].lower()) for f in files]
153
- results = [sanitize_utf8(f.result()) for f in as_completed(futures)]
154
- extracted = "\n".join(results)
155
- file_hash_value = file_hash(files[0].name)
156
-
157
- prompt = f"""Review these medical records and identify EXACTLY what might have been missed:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  1. List potential missed diagnoses
159
  2. Flag any medication conflicts
160
  3. Note incomplete assessments
161
  4. Highlight abnormal results needing follow-up
 
162
  Medical Records:
163
  {extracted[:12000]}
 
164
  ### Potential Oversights:
165
  """
166
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
  try:
168
- for chunk in agent.run_gradio_chat(
169
- message=prompt,
170
- history=[],
171
- temperature=0.2,
172
- max_new_tokens=2048,
173
- max_token=4096,
174
- call_agent=False,
175
- conversation=[],
176
- ):
177
- if chunk is None:
178
- continue
179
-
180
- if isinstance(chunk, list) and all(hasattr(m, 'role') and hasattr(m, 'content') for m in chunk):
181
- for m in chunk:
182
- cleaned_content = re.sub(r"\[TOOL_CALLS\].*?(?=(\[|\Z))", "", m.content, flags=re.DOTALL).strip()
183
- history.append({"role": m.role, "content": cleaned_content or "⚠️ No answer returned."})
184
- yield history, None
185
- elif isinstance(chunk, str):
186
- cleaned_chunk = re.sub(r"\[TOOL_CALLS\].*?(?=(\[|\Z))", "", chunk, flags=re.DOTALL).strip()
187
- history[-1]["content"] += cleaned_chunk or "⚠️ No content generated."
188
- yield history, None
189
-
190
- report_path = os.path.join(report_dir, f"{file_hash_value}_report.txt") if file_hash_value else None
191
- yield history, report_path if report_path and os.path.exists(report_path) else None
192
-
193
- except Exception as e:
194
- print("🚨 ERROR:", e)
195
- history.append({"role": "assistant", "content": f"❌ Error occurred: {str(e)}"})
196
- yield history, None
197
-
198
- send_btn.click(analyze, inputs=[msg_input, gr.State([]), file_upload], outputs=[chatbot, download_output])
199
- msg_input.submit(analyze, inputs=[msg_input, gr.State([]), file_upload], outputs=[chatbot, download_output])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
  return demo
201
 
202
  if __name__ == "__main__":
203
- print("🚀 Launching app...")
204
- agent = init_agent()
205
- demo = create_ui(agent)
206
- demo.queue(api_open=False).launch(
207
- server_name="0.0.0.0",
208
- server_port=7860,
209
- show_error=True,
210
- allowed_paths=[report_dir],
211
- share=False
212
- )
 
 
 
 
 
 
 
 
 
4
  import pdfplumber
5
  import json
6
  import gradio as gr
7
+ from typing import List, Dict, Any
8
  from concurrent.futures import ThreadPoolExecutor, as_completed
9
  import hashlib
10
  import shutil
11
  import re
12
  import psutil
13
  import subprocess
14
+ import logging
15
+ import traceback
16
+ from datetime import datetime
17
+
18
+ # Configure logging
19
+ logging.basicConfig(
20
+ level=logging.INFO,
21
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
22
+ handlers=[
23
+ logging.StreamHandler(),
24
+ logging.FileHandler('clinical_oversight.log')
25
+ ]
26
+ )
27
+ logger = logging.getLogger(__name__)
28
 
29
  # Persistent directory
30
  persistent_dir = "/data/hf_cache"
 
55
  'allergies', 'summary', 'impression', 'findings', 'recommendations'}
56
 
57
  def sanitize_utf8(text: str) -> str:
58
+ """Ensure text is UTF-8 encoded and clean."""
59
+ try:
60
+ return text.encode("utf-8", "ignore").decode("utf-8")
61
+ except Exception as e:
62
+ logger.error(f"UTF-8 sanitization failed: {str(e)}")
63
+ return ""
64
 
65
  def file_hash(path: str) -> str:
66
+ """Generate MD5 hash of file content."""
67
+ try:
68
+ with open(path, "rb") as f:
69
+ return hashlib.md5(f.read()).hexdigest()
70
+ except Exception as e:
71
+ logger.error(f"File hash generation failed for {path}: {str(e)}")
72
+ return ""
73
 
74
  def extract_priority_pages(file_path: str, max_pages: int = 20) -> str:
75
+ """Extract pages from PDF with priority given to pages containing medical keywords."""
76
  try:
77
  text_chunks = []
78
+ logger.info(f"Extracting pages from {file_path}")
79
+
80
  with pdfplumber.open(file_path) as pdf:
81
+ # Always extract first 3 pages
82
  for i, page in enumerate(pdf.pages[:3]):
83
+ try:
84
+ text = page.extract_text() or ""
85
+ text_chunks.append(f"=== Page {i+1} ===\n{text.strip()}")
86
+ except Exception as page_error:
87
+ logger.warning(f"Error processing page {i+1}: {str(page_error)}")
88
+ text_chunks.append(f"=== Page {i+1} ===\n[Error extracting content]")
89
+
90
+ # Extract remaining pages that contain medical keywords
91
  for i, page in enumerate(pdf.pages[3:max_pages], start=4):
92
+ try:
93
+ page_text = page.extract_text() or ""
94
+ if any(re.search(rf'\b{kw}\b', page_text.lower()) for kw in MEDICAL_KEYWORDS):
95
+ text_chunks.append(f"=== Page {i} ===\n{page_text.strip()}")
96
+ except Exception as page_error:
97
+ logger.warning(f"Error processing page {i}: {str(page_error)}")
98
+
99
  return "\n\n".join(text_chunks)
100
  except Exception as e:
101
+ logger.error(f"PDF processing error for {file_path}: {str(e)}")
102
  return f"PDF processing error: {str(e)}"
103
 
104
  def convert_file_to_json(file_path: str, file_type: str) -> str:
105
+ """Convert different file types to JSON format with caching."""
106
  try:
107
  h = file_hash(file_path)
108
+ if not h:
109
+ return json.dumps({"error": "Could not generate file hash"})
110
+
111
  cache_path = os.path.join(file_cache_dir, f"{h}.json")
112
+
113
+ # Check cache first
114
  if os.path.exists(cache_path):
 
 
 
 
 
 
 
 
 
 
 
 
115
  try:
116
+ with open(cache_path, "r", encoding="utf-8") as f:
117
+ return f.read()
118
+ except Exception as cache_error:
119
+ logger.error(f"Cache read error for {file_path}: {str(cache_error)}")
120
+
121
+ result = {}
122
+ try:
123
+ if file_type == "pdf":
124
+ text = extract_priority_pages(file_path)
125
+ result = {
126
+ "filename": os.path.basename(file_path),
127
+ "content": text,
128
+ "status": "initial",
129
+ "file_type": "pdf"
130
+ }
131
+ elif file_type == "csv":
132
+ df = pd.read_csv(
133
+ file_path,
134
+ encoding_errors="replace",
135
+ header=None,
136
+ dtype=str,
137
+ skip_blank_lines=False,
138
+ on_bad_lines="skip"
139
+ )
140
+ content = df.fillna("").astype(str).values.tolist()
141
+ result = {
142
+ "filename": os.path.basename(file_path),
143
+ "rows": content,
144
+ "file_type": "csv"
145
+ }
146
+ elif file_type in ["xls", "xlsx"]:
147
+ try:
148
+ df = pd.read_excel(file_path, engine="openpyxl", header=None, dtype=str)
149
+ except Exception:
150
+ try:
151
+ df = pd.read_excel(file_path, engine="xlrd", header=None, dtype=str)
152
+ except Exception as excel_error:
153
+ logger.error(f"Excel read error for {file_path}: {str(excel_error)}")
154
+ raise
155
+ content = df.fillna("").astype(str).values.tolist()
156
+ result = {
157
+ "filename": os.path.basename(file_path),
158
+ "rows": content,
159
+ "file_type": "excel"
160
+ }
161
+ else:
162
+ result = {"error": f"Unsupported file type: {file_type}"}
163
+
164
+ json_result = json.dumps(result)
165
+
166
+ # Save to cache
167
+ try:
168
+ with open(cache_path, "w", encoding="utf-8") as f:
169
+ f.write(json_result)
170
+ except Exception as cache_write_error:
171
+ logger.error(f"Cache write error for {file_path}: {str(cache_write_error)}")
172
+
173
+ return json_result
174
+ except Exception as processing_error:
175
+ logger.error(f"Error processing {file_path}: {str(processing_error)}")
176
+ return json.dumps({"error": f"Error processing {os.path.basename(file_path)}: {str(processing_error)}"})
177
  except Exception as e:
178
+ logger.error(f"Unexpected error in convert_file_to_json: {str(e)}")
179
+ return json.dumps({"error": f"Unexpected error processing file: {str(e)}"})
180
 
181
  def log_system_usage(tag=""):
182
+ """Log system resource usage including CPU, RAM, and GPU."""
183
  try:
184
  cpu = psutil.cpu_percent(interval=1)
185
  mem = psutil.virtual_memory()
186
+ logger.info(f"[{tag}] CPU: {cpu}% | RAM: {mem.used // (1024**2)}MB / {mem.total // (1024**2)}MB")
187
+
188
+ try:
189
+ result = subprocess.run(
190
+ ["nvidia-smi", "--query-gpu=memory.used,memory.total,utilization.gpu", "--format=csv,nounits,noheader"],
191
+ capture_output=True, text=True
192
+ )
193
+ if result.returncode == 0:
194
+ used, total, util = result.stdout.strip().split(", ")
195
+ logger.info(f"[{tag}] GPU: {used}MB / {total}MB | Utilization: {util}%")
196
+ except Exception as gpu_error:
197
+ logger.warning(f"[{tag}] GPU monitor failed: {gpu_error}")
198
  except Exception as e:
199
+ logger.error(f"System usage logging failed: {str(e)}")
200
 
201
  def init_agent():
202
+ """Initialize the TxAgent with proper configuration."""
203
+ logger.info("Initializing model...")
204
  log_system_usage("Before Load")
205
+
206
  default_tool_path = os.path.abspath("data/new_tool.json")
207
  target_tool_path = os.path.join(tool_cache_dir, "new_tool.json")
208
+
209
+ try:
210
+ if not os.path.exists(target_tool_path):
211
+ shutil.copy(default_tool_path, target_tool_path)
212
+ logger.info("Copied default tool configuration")
213
+ except Exception as e:
214
+ logger.error(f"Tool configuration copy failed: {str(e)}")
215
+ raise
 
 
 
 
 
 
 
 
 
216
 
217
+ try:
218
+ agent = TxAgent(
219
+ model_name="mims-harvard/TxAgent-T1-Llama-3.1-8B",
220
+ rag_model_name="mims-harvard/ToolRAG-T1-GTE-Qwen2-1.5B",
221
+ tool_files_dict={"new_tool": target_tool_path},
222
+ force_finish=True,
223
+ enable_checker=True,
224
+ step_rag_num=8,
225
+ seed=100,
226
+ additional_default_tools=[],
227
+ )
228
+ agent.init_model()
229
+ log_system_usage("After Load")
230
+ logger.info("Agent initialization successful")
231
+ return agent
232
+ except Exception as e:
233
+ logger.error(f"Agent initialization failed: {str(e)}")
234
+ raise
235
+
236
+ def save_report(content: str, file_hash_value: str = "") -> str:
237
+ """Save analysis report to file and return path."""
238
+ try:
239
+ if not file_hash_value:
240
+ file_hash_value = hashlib.md5(content.encode()).hexdigest()
241
+
242
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
243
+ report_filename = f"report_{timestamp}_{file_hash_value[:8]}.txt"
244
+ report_path = os.path.join(report_dir, report_filename)
245
+
246
+ with open(report_path, "w", encoding="utf-8") as f:
247
+ f.write(content)
248
+
249
+ logger.info(f"Report saved to {report_path}")
250
+ return report_path
251
+ except Exception as e:
252
+ logger.error(f"Failed to save report: {str(e)}")
253
+ return ""
254
+
255
+ def clean_response(content: str) -> str:
256
+ """Clean up model response by removing tool call artifacts."""
257
+ if not content:
258
+ return "⚠️ No content generated."
259
+
260
+ try:
261
+ # Remove tool call artifacts
262
+ cleaned = re.sub(r"\[TOOL_CALLS\].*?(?=(\[|\Z))", "", content, flags=re.DOTALL).strip()
263
+ # Remove excessive whitespace
264
+ cleaned = re.sub(r"\n{3,}", "\n\n", cleaned)
265
+ return cleaned or "⚠️ Empty response after cleaning."
266
+ except Exception as e:
267
+ logger.error(f"Response cleaning failed: {str(e)}")
268
+ return content
269
+
270
+ def process_model_response(chunk: Any, history: List[Dict[str, str]]) -> List[Dict[str, str]]:
271
+ """Process model response chunk and update chat history."""
272
+ try:
273
+ if chunk is None:
274
+ return history
275
+
276
+ if isinstance(chunk, list) and all(hasattr(m, 'role') and hasattr(m, 'content') for m in chunk):
277
+ for m in chunk:
278
+ cleaned_content = clean_response(m.content)
279
+ history.append({"role": m.role, "content": cleaned_content})
280
+ elif isinstance(chunk, str):
281
+ cleaned_chunk = clean_response(chunk)
282
+ if history and history[-1]["role"] == "assistant":
283
+ history[-1]["content"] += cleaned_chunk
284
+ else:
285
+ history.append({"role": "assistant", "content": cleaned_chunk})
286
+ else:
287
+ logger.warning(f"Unexpected response type: {type(chunk)}")
288
+
289
+ return history
290
+ except Exception as e:
291
+ logger.error(f"Error processing model response: {str(e)}")
292
+ history.append({"role": "assistant", "content": f"⚠️ Error processing response: {str(e)}"})
293
+ return history
294
+
295
+ def analyze(message: str, history: list, files: list):
296
+ """Main analysis function that processes files and generates responses."""
297
+ try:
298
+ # Initial response
299
+ new_history = history.copy()
300
+ new_history.append({"role": "user", "content": message})
301
+ new_history.append({"role": "assistant", "content": "⏳ Analyzing records for potential oversights..."})
302
+ yield new_history, None
303
+
304
+ # Process files
305
+ extracted = ""
306
+ file_hash_value = ""
307
+ if files:
308
+ logger.info(f"Processing {len(files)} files...")
309
+ with ThreadPoolExecutor(max_workers=4) as executor:
310
+ futures = []
311
+ for f in files:
312
+ try:
313
+ file_type = f.name.split(".")[-1].lower()
314
+ futures.append(executor.submit(convert_file_to_json, f.name, file_type))
315
+ except Exception as e:
316
+ logger.error(f"Error submitting file {f.name} for processing: {str(e)}")
317
+ new_history.append({"role": "system", "content": f"⚠️ Error processing {f.name}: {str(e)}"})
318
+
319
+ results = []
320
+ for f in as_completed(futures):
321
+ try:
322
+ results.append(sanitize_utf8(f.result()))
323
+ except Exception as e:
324
+ logger.error(f"Error getting file processing result: {str(e)}")
325
+ results.append(json.dumps({"error": "File processing failed"}))
326
+
327
+ extracted = "\n".join(results)
328
+ try:
329
+ file_hash_value = file_hash(files[0].name) if files else ""
330
+ except Exception as e:
331
+ logger.error(f"Error generating file hash: {str(e)}")
332
+ file_hash_value = ""
333
+
334
+ # Prepare prompt
335
+ prompt = f"""Review these medical records and identify EXACTLY what might have been missed:
336
  1. List potential missed diagnoses
337
  2. Flag any medication conflicts
338
  3. Note incomplete assessments
339
  4. Highlight abnormal results needing follow-up
340
+
341
  Medical Records:
342
  {extracted[:12000]}
343
+
344
  ### Potential Oversights:
345
  """
346
+ logger.info(f"Prompt length: {len(prompt)} characters")
347
+
348
+ # Initialize agent response
349
+ agent = init_agent()
350
+ response_content = ""
351
+ report_path = ""
352
+
353
+ # Process agent response
354
+ for chunk in agent.run_gradio_chat(
355
+ message=prompt,
356
+ history=[],
357
+ temperature=0.2,
358
+ max_new_tokens=2048,
359
+ max_token=4096,
360
+ call_agent=False,
361
+ conversation=[],
362
+ ):
363
  try:
364
+ new_history = process_model_response(chunk, new_history)
365
+ if isinstance(chunk, str):
366
+ response_content += clean_response(chunk)
367
+
368
+ yield new_history, None
369
+ except Exception as chunk_error:
370
+ logger.error(f"Error processing chunk: {str(chunk_error)}")
371
+ new_history.append({"role": "assistant", "content": f"⚠️ Error processing response chunk: {str(chunk_error)}"})
372
+ yield new_history, None
373
+
374
+ # Save final report
375
+ if response_content:
376
+ try:
377
+ report_path = save_report(response_content, file_hash_value)
378
+ except Exception as report_error:
379
+ logger.error(f"Error saving report: {str(report_error)}")
380
+ new_history.append({"role": "system", "content": "⚠️ Failed to save full report"})
381
+
382
+ yield new_history, report_path if report_path and os.path.exists(report_path) else None
383
+
384
+ except Exception as e:
385
+ logger.error(f"Analysis error: {str(e)}\n{traceback.format_exc()}")
386
+ error_history = history.copy()
387
+ error_history.append({"role": "assistant", "content": f"❌ Critical error occurred: {str(e)}"})
388
+ yield error_history, None
389
+
390
+ def create_ui(agent):
391
+ """Create Gradio UI interface."""
392
+ with gr.Blocks(theme=gr.themes.Soft(), title="Clinical Oversight Assistant") as demo:
393
+ gr.Markdown("<h1 style='text-align: center;'>🩺 Clinical Oversight Assistant</h1>")
394
+ gr.Markdown("""
395
+ <div style='text-align: center; margin-bottom: 20px;'>
396
+ Upload medical records and ask about potential oversights or missed diagnoses.
397
+ </div>
398
+ """)
399
+
400
+ with gr.Row():
401
+ with gr.Column(scale=2):
402
+ chatbot = gr.Chatbot(
403
+ label="Analysis Conversation",
404
+ height=600,
405
+ bubble_full_width=False,
406
+ show_copy_button=True
407
+ )
408
+ msg_input = gr.Textbox(
409
+ placeholder="Ask about potential oversights...",
410
+ show_label=False,
411
+ container=False
412
+ )
413
+ with gr.Row():
414
+ send_btn = gr.Button("Analyze", variant="primary")
415
+ clear_btn = gr.Button("Clear")
416
+
417
+ with gr.Column(scale=1):
418
+ file_upload = gr.File(
419
+ file_types=[".pdf", ".csv", ".xls", ".xlsx"],
420
+ file_count="multiple",
421
+ label="Upload Medical Records"
422
+ )
423
+ download_output = gr.File(
424
+ label="Download Full Report",
425
+ interactive=False
426
+ )
427
+ gr.Markdown("""
428
+ <div style='margin-top: 20px; font-size: 0.9em; color: #666;'>
429
+ <b>Note:</b> The system analyzes PDFs, CSVs, and Excel files for potential clinical oversights.
430
+ </div>
431
+ """)
432
+
433
+ # Event handlers
434
+ send_btn.click(
435
+ analyze,
436
+ inputs=[msg_input, gr.State([]), file_upload],
437
+ outputs=[chatbot, download_output]
438
+ )
439
+
440
+ msg_input.submit(
441
+ analyze,
442
+ inputs=[msg_input, gr.State([]), file_upload],
443
+ outputs=[chatbot, download_output]
444
+ )
445
+
446
+ clear_btn.click(
447
+ lambda: ([], None),
448
+ inputs=[],
449
+ outputs=[chatbot, download_output]
450
+ )
451
+
452
+ # Add some examples
453
+ gr.Examples(
454
+ examples=[
455
+ ["What potential diagnoses might have been missed in these records?"],
456
+ ["Are there any medication conflicts I should be aware of?"],
457
+ ["What abnormal results need follow-up in these reports?"]
458
+ ],
459
+ inputs=msg_input,
460
+ label="Example Questions"
461
+ )
462
+
463
  return demo
464
 
465
  if __name__ == "__main__":
466
+ try:
467
+ logger.info("🚀 Launching Clinical Oversight Assistant...")
468
+ agent = init_agent()
469
+ demo = create_ui(agent)
470
+
471
+ demo.queue(
472
+ api_open=False,
473
+ concurrency_count=2
474
+ ).launch(
475
+ server_name="0.0.0.0",
476
+ server_port=7860,
477
+ show_error=True,
478
+ allowed_paths=[report_dir],
479
+ share=False
480
+ )
481
+ except Exception as e:
482
+ logger.error(f"Application failed to start: {str(e)}\n{traceback.format_exc()}")
483
+ raise