Ali2206 commited on
Commit
d184610
·
verified ·
1 Parent(s): 4a93687

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +214 -223
app.py CHANGED
@@ -1,23 +1,16 @@
1
  import sys
2
  import os
3
- import polars as pl
4
  import json
5
  import gradio as gr
6
- from typing import List, Tuple
7
  import hashlib
8
  import shutil
9
  import re
10
  from datetime import datetime
11
  import time
12
- import asyncio
13
- import aiofiles
14
- import cachetools
15
- import logging
16
  import markdown
17
-
18
- # Set up logging
19
- logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
20
- logger = logging.getLogger(__name__)
21
 
22
  # Configuration and setup
23
  persistent_dir = "/data/hf_cache"
@@ -40,226 +33,185 @@ sys.path.insert(0, src_path)
40
 
41
  from txagent.txagent import TxAgent
42
 
43
- # Cache for processed data
44
- cache = cachetools.LRUCache(maxsize=100)
45
-
46
  def file_hash(path: str) -> str:
47
- """Generate MD5 hash of a file."""
48
  with open(path, "rb") as f:
49
  return hashlib.md5(f.read()).hexdigest()
50
 
51
  def clean_response(text: str) -> str:
52
- """Clean text by removing unwanted characters and normalizing."""
53
  try:
54
  text = text.encode('utf-8', 'surrogatepass').decode('utf-8')
55
  except UnicodeError:
56
  text = text.encode('utf-8', 'replace').decode('utf-8')
57
 
 
58
  text = re.sub(r"\[.*?\]|\bNone\b", "", text, flags=re.DOTALL)
59
  text = re.sub(r"\n{3,}", "\n\n", text)
60
  text = re.sub(r"[^\n#\-\*\w\s\.,:\(\)]+", "", text)
61
  return text.strip()
62
 
63
- async def load_and_clean_data(file_path: str) -> pl.DataFrame:
64
- """Load and clean Excel data using polars."""
65
- try:
66
- logger.info(f"Loading Excel file: {file_path}")
67
- df = pl.read_excel(file_path).with_columns([
68
- pl.col(col).str.strip_chars().fill_null("").alias(col) for col in [
69
- "Booking Number", "Form Name", "Form Item", "Item Response",
70
- "Interviewer", "Interview Date", "Description"
71
- ]
72
- ]).filter(pl.col("Booking Number").str.starts_with("BKG"))
73
- logger.info(f"Loaded {len(df)} records")
74
- return df
75
- except Exception as e:
76
- logger.error(f"Error loading data: {str(e)}")
77
- raise
78
-
79
- def generate_summary(df: pl.DataFrame) -> tuple[str, dict]:
80
- """Generate summary statistics and interesting fact."""
81
- symptom_counts = {}
82
- for desc in df["Description"]:
83
- desc = desc.lower()
84
- if "chest discomfort" in desc:
85
- symptom_counts["Chest Discomfort"] = symptom_counts.get("Chest Discomfort", 0) + 1
86
- if "headaches" in desc:
87
- symptom_counts["Headaches"] = symptom_counts.get("Headaches", 0) + 1
88
- if "weight loss" in desc:
89
- symptom_counts["Weight Loss"] = symptom_counts.get("Weight Loss", 0) + 1
90
- if "back pain" in desc:
91
- symptom_counts["Chronic Back Pain"] = symptom_counts.get("Chronic Back Pain", 0) + 1
92
- if "cough" in desc:
93
- symptom_counts["Persistent Cough"] = symptom_counts.get("Persistent Cough", 0) + 1
94
 
95
- total_records = len(df)
96
- unique_bookings = df["Booking Number"].n_unique()
97
- interesting_fact = (
98
- f"Chest discomfort was reported in {symptom_counts.get('Chest Discomfort', 0)} records, "
99
- "frequently leading to ECG/lab referrals. Inconsistent follow-up documentation raises "
100
- "concerns about potential missed cardiovascular diagnoses."
101
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
 
103
- summary = (
104
- f"## Summary\n\n"
105
- f"Analyzed {total_records:,} patient records from {unique_bookings:,} unique bookings in 2023. "
106
- f"Key findings include a high prevalence of chest discomfort ({symptom_counts.get('Chest Discomfort', 0)} instances), "
107
- f"suggesting possible underdiagnosis of cardiovascular issues.\n\n"
108
- f"### Interesting Fact\n{interesting_fact}\n"
109
  )
110
- return summary, symptom_counts
111
-
112
- def prepare_aggregate_prompt(df: pl.DataFrame) -> str:
113
- """Prepare a single prompt for all patient data."""
114
- groups = df.group_by("Booking Number").agg([
115
- pl.col("Form Name"), pl.col("Form Item"),
116
- pl.col("Item Response"), pl.col("Interviewer"),
117
- pl.col("Interview Date"), pl.col("Description")
118
- ])
119
 
120
- records = []
121
- for booking in groups.iter_rows(named=True):
122
- booking_id = booking["Booking Number"]
123
- for i in range(len(booking["Form Name"])):
124
- record = (
125
- f"- {booking['Form Name'][i]}: {booking['Form Item'][i]} = {booking['Item Response'][i]} "
126
- f"({booking['Interview Date'][i]} by {booking['Interviewer'][i]})\n{booking['Description'][i]}"
127
- )
128
- records.append(clean_response(record))
129
 
130
- record_text = "\n".join(records)
131
  prompt = f"""
132
- Patient Medical History Analysis
 
 
 
133
 
134
- Instructions:
135
- Analyze the following aggregated patient data from all bookings to identify potential missed diagnoses, medication conflicts, incomplete assessments, and urgent follow-up needs across the entire dataset. Provide a comprehensive summary under the specified markdown headings. Focus on patterns and recurring issues across multiple patients.
136
 
137
- Data:
138
- {record_text}
 
 
 
 
 
139
 
 
140
  ### Missed Diagnoses
141
- - ...
142
 
143
- ### Medication Conflicts
144
- - ...
145
 
146
- ### Incomplete Assessments
147
- - ...
148
 
149
  ### Urgent Follow-up
150
- - ...
 
 
 
151
  """
152
  return prompt
153
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
  def init_agent():
155
- """Initialize TxAgent with tool configuration."""
156
  default_tool_path = os.path.abspath("data/new_tool.json")
157
  target_tool_path = os.path.join(tool_cache_dir, "new_tool.json")
158
 
159
  if not os.path.exists(target_tool_path):
160
  shutil.copy(default_tool_path, target_tool_path)
161
 
162
- try:
163
- agent = TxAgent(
164
- model_name="mims-harvard/TxAgent-T1-Llama-3.1-8B",
165
- rag_model_name="mims-harvard/ToolRAG-T1-GTE-Qwen2-1.5B",
166
- tool_files_dict={"new_tool": target_tool_path},
167
- force_finish=True,
168
- enable_checker=True,
169
- step_rag_num=4,
170
- seed=100,
171
- additional_default_tools=[],
172
- )
173
- agent.init_model()
174
- return agent
175
- except Exception as e:
176
- logger.error(f"Failed to initialize TxAgent: {str(e)}")
177
- raise
178
 
179
- async def generate_report(agent, df: pl.DataFrame, file_hash_value: str) -> tuple[str, str]:
180
- """Generate a comprehensive markdown report."""
181
- logger.info("Generating comprehensive report...")
182
- report_path = os.path.join(report_dir, f"{file_hash_value}_report.md")
183
-
184
- # Generate summary
185
- summary, symptom_counts = generate_summary(df)
186
-
187
- # Prepare and run aggregated analysis
188
- prompt = prepare_aggregate_prompt(df)
189
- full_output = ""
190
-
191
- try:
192
- chunk_output = ""
193
- for result in agent.run_gradio_chat(
194
- message=prompt,
195
- history=[],
196
- temperature=0.2,
197
- max_new_tokens=2048,
198
- max_token=8192,
199
- call_agent=False,
200
- conversation=[],
201
- ):
202
- if isinstance(result, list):
203
- for r in result:
204
- if hasattr(r, 'content') and r.content:
205
- cleaned = clean_response(r.content)
206
- chunk_output += cleaned + "\n"
207
- elif isinstance(result, str):
208
- cleaned = clean_response(result)
209
- chunk_output += cleaned + "\n"
210
- full_output = chunk_output.strip()
211
- yield full_output, None # Stream partial results
212
-
213
- # Filter out empty sections
214
- sections = ["Missed Diagnoses", "Medication Conflicts", "Incomplete Assessments", "Urgent Follow-up"]
215
- filtered_output = []
216
- current_section = None
217
- for line in full_output.split("\n"):
218
- if any(line.startswith(f"### {section}") for section in sections):
219
- current_section = line
220
- filtered_output.append(line)
221
- elif current_section and line.strip().startswith("-") and line.strip() != "- ...":
222
- filtered_output.append(line)
223
-
224
- # Compile final report
225
- final_output = summary + "## Clinical Findings\n\n"
226
- if filtered_output:
227
- final_output += "\n".join(filtered_output) + "\n\n"
228
- else:
229
- final_output += "No significant clinical findings identified.\n\n"
230
-
231
- final_output += (
232
- "## Conclusion\n\n"
233
- "The analysis reveals significant gaps in patient care, including potential missed cardiovascular diagnoses "
234
- "due to inconsistent follow-up on chest discomfort and elevated vitals. Low medication adherence is a recurring "
235
- "issue, likely impacting treatment efficacy. Incomplete assessments, particularly missing vital signs, hinder "
236
- "comprehensive care. Urgent follow-up is recommended for patients with chest discomfort and elevated vitals to "
237
- "prevent adverse outcomes."
238
- )
239
-
240
- # Save report
241
- async with aiofiles.open(report_path, "w") as f:
242
- await f.write(final_output)
243
-
244
- logger.info(f"Report saved to {report_path}")
245
- yield final_output, report_path
246
-
247
- except Exception as e:
248
- logger.error(f"Error generating report: {str(e)}")
249
- yield f"Error: {str(e)}", None
250
 
251
  def create_ui(agent):
252
- """Create Gradio interface for clinical oversight analysis."""
253
- with gr.Blocks(
254
- theme=gr.themes.Soft(),
255
- title="Clinical Oversight Assistant",
256
- css="""
257
- .gradio-container {max-width: 1000px; margin: auto; font-family: Arial, sans-serif;}
258
- #chatbot {border: 1px solid #e5e7eb; border-radius: 8px; padding: 10px; background: #f9fafb;}
259
- .markdown {white-space: pre-wrap;}
260
- """
261
- ) as demo:
262
- gr.Markdown("# 🏥 Clinical Oversight Assistant (Excel Optimized)")
263
 
264
  with gr.Tabs():
265
  with gr.TabItem("Analysis"):
@@ -268,7 +220,7 @@ def create_ui(agent):
268
  with gr.Column(scale=1):
269
  file_upload = gr.File(
270
  label="Upload Excel File",
271
- file_types=[".xlsx"],
272
  file_count="single",
273
  interactive=True
274
  )
@@ -288,7 +240,7 @@ def create_ui(agent):
288
  height=600,
289
  bubble_full_width=False,
290
  show_copy_button=True,
291
- elem_id="chatbot"
292
  )
293
  download_output = gr.File(
294
  label="Download Full Report",
@@ -301,65 +253,107 @@ def create_ui(agent):
301
 
302
  1. **Upload Excel File**: Select your patient records Excel file
303
  2. **Add Instructions** (Optional): Provide any specific analysis requests
304
- 3. **Click Analyze**: The system will process all patient records and generate a comprehensive report
305
  4. **Review Results**: Analysis appears in the chat window
306
- 5. **Download Report**: Get a full markdown report of all findings
307
 
308
  ### Excel File Requirements
309
  Your Excel file must contain these columns:
310
- - Booking Number
311
- - Form Name
312
- - Form Item
313
- - Item Response
314
- - Interview Date
315
- - Interviewer
316
- - Description
317
 
318
  ### Analysis Includes
319
- - Missed diagnoses
320
- - Medication conflicts
321
- - Incomplete assessments
322
- - Urgent follow-up needs
 
323
  """)
324
 
325
  def format_message(role: str, content: str) -> Tuple[str, str]:
326
- """Format messages for the chatbot in (user, bot) format."""
327
  if role == "user":
328
  return (content, None)
329
  else:
330
  return (None, content)
331
 
332
- async def analyze(message: str, chat_history: List[Tuple[str, str]], file) -> Tuple[List[Tuple[str, str]], str]:
333
- """Analyze uploaded file and generate comprehensive report."""
334
  if not file:
335
  raise gr.Error("Please upload an Excel file first")
336
 
337
  try:
338
- # Initialize chat history
339
  new_history = chat_history + [format_message("user", message)]
340
  new_history.append(format_message("assistant", "⏳ Processing Excel data..."))
341
  yield new_history, None
342
 
343
- # Load and clean data
344
- df = await load_and_clean_data(file.name)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
345
  file_hash_value = file_hash(file.name)
 
 
 
 
 
 
 
 
346
 
347
- # Generate report
348
- async for output, report_path in generate_report(agent, df, file_hash_value):
349
- if output:
350
- new_history[-1] = format_message("assistant", output)
351
- yield new_history, report_path
352
- else:
353
- yield new_history, report_path
354
 
355
  except Exception as e:
356
- logger.error(f"Analysis failed: {str(e)}")
357
  new_history.append(format_message("assistant", f"❌ Error: {str(e)}"))
358
  yield new_history, None
359
  raise gr.Error(f"Analysis failed: {str(e)}")
360
 
361
  def clear_chat():
362
- """Clear chat history and download output."""
363
  return [], None
364
 
365
  # Event handlers
@@ -367,15 +361,13 @@ def create_ui(agent):
367
  analyze,
368
  inputs=[msg_input, chatbot, file_upload],
369
  outputs=[chatbot, download_output],
370
- api_name="analyze",
371
- queue=True
372
  )
373
 
374
  msg_input.submit(
375
  analyze,
376
  inputs=[msg_input, chatbot, file_upload],
377
- outputs=[chatbot, download_output],
378
- queue=True
379
  )
380
 
381
  clear_btn.click(
@@ -402,6 +394,5 @@ if __name__ == "__main__":
402
  share=False
403
  )
404
  except Exception as e:
405
- logger.error(f"Failed to launch application: {str(e)}")
406
  print(f"Failed to launch application: {str(e)}")
407
  sys.exit(1)
 
1
  import sys
2
  import os
3
+ import pandas as pd
4
  import json
5
  import gradio as gr
6
+ from typing import List, Tuple, Dict, Any
7
  import hashlib
8
  import shutil
9
  import re
10
  from datetime import datetime
11
  import time
 
 
 
 
12
  import markdown
13
+ from collections import defaultdict
 
 
 
14
 
15
  # Configuration and setup
16
  persistent_dir = "/data/hf_cache"
 
33
 
34
  from txagent.txagent import TxAgent
35
 
 
 
 
36
  def file_hash(path: str) -> str:
37
+ """Generate MD5 hash of file contents"""
38
  with open(path, "rb") as f:
39
  return hashlib.md5(f.read()).hexdigest()
40
 
41
  def clean_response(text: str) -> str:
42
+ """Clean and normalize text output"""
43
  try:
44
  text = text.encode('utf-8', 'surrogatepass').decode('utf-8')
45
  except UnicodeError:
46
  text = text.encode('utf-8', 'replace').decode('utf-8')
47
 
48
+ # Remove unwanted patterns and normalize whitespace
49
  text = re.sub(r"\[.*?\]|\bNone\b", "", text, flags=re.DOTALL)
50
  text = re.sub(r"\n{3,}", "\n\n", text)
51
  text = re.sub(r"[^\n#\-\*\w\s\.,:\(\)]+", "", text)
52
  return text.strip()
53
 
54
+ def extract_medical_data(df: pd.DataFrame) -> Dict[str, Any]:
55
+ """Extract and organize medical data from DataFrame"""
56
+ medical_data = defaultdict(list)
57
+
58
+ for _, row in df.iterrows():
59
+ record = {
60
+ 'form_name': row.get('Form Name', ''),
61
+ 'form_item': row.get('Form Item', ''),
62
+ 'response': row.get('Item Response', ''),
63
+ 'date': row.get('Interview Date', ''),
64
+ 'interviewer': row.get('Interviewer', ''),
65
+ 'description': row.get('Description', '')
66
+ }
67
+ medical_data[row['Booking Number']].append(record)
68
+
69
+ return medical_data
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
 
71
+ def identify_red_flags(records: List[Dict[str, Any]]) -> Dict[str, List[str]]:
72
+ """Identify potential red flags in medical records"""
73
+ red_flags = {
74
+ 'symptoms': defaultdict(list),
75
+ 'medications': defaultdict(list),
76
+ 'diagnoses': defaultdict(list),
77
+ 'vitals': defaultdict(list),
78
+ 'labs': defaultdict(list)
79
+ }
80
+
81
+ for record in records:
82
+ form_name = record['form_name'].lower()
83
+ item = record['form_item'].lower()
84
+ response = record['response'].lower()
85
+
86
+ # Symptom patterns
87
+ if 'pain' in item or 'symptom' in form_name:
88
+ if 'severe' in response or 'chronic' in response:
89
+ red_flags['symptoms'][item].append(response)
90
+
91
+ # Medication checks
92
+ elif 'medication' in form_name or 'drug' in form_name:
93
+ if 'interaction' in response or 'allergy' in response:
94
+ red_flags['medications'][item].append(response)
95
+
96
+ # Diagnosis inconsistencies
97
+ elif 'diagnosis' in form_name:
98
+ if 'rule out' in response or 'possible' in response:
99
+ red_flags['diagnoses'][item].append(response)
100
+
101
+ # Abnormal vitals
102
+ elif 'vital' in form_name:
103
+ try:
104
+ value = float(re.search(r'\d+\.?\d*', response).group())
105
+ if ('blood pressure' in item and value > 140) or \
106
+ ('heart rate' in item and (value < 50 or value > 100)) or \
107
+ ('temperature' in item and value > 38):
108
+ red_flags['vitals'][item].append(response)
109
+ except:
110
+ pass
111
+
112
+ # Abnormal labs
113
+ elif 'lab' in form_name or 'test' in form_name:
114
+ if 'abnormal' in response or 'high' in response or 'low' in response:
115
+ red_flags['labs'][item].append(response)
116
+
117
+ return red_flags
118
 
119
+ def generate_analysis_prompt(booking: str, records: List[Dict[str, Any]], red_flags: Dict[str, Any]]) -> str:
120
+ """Generate structured prompt for analysis"""
121
+ records_text = "\n".join(
122
+ f"- {r['form_name']}: {r['form_item']} = {r['response']} ({r['date']} by {r['interviewer']})\n {r['description']}"
123
+ for r in records
 
124
  )
 
 
 
 
 
 
 
 
 
125
 
126
+ red_flags_text = "\n".join(
127
+ f"### {category.capitalize()} Red Flags\n" + "\n".join(
128
+ f"- {item}: {', '.join(responses)}"
129
+ for item, responses in items.items()
130
+ )
131
+ for category, items in red_flags.items() if items
132
+ )
 
 
133
 
 
134
  prompt = f"""
135
+ **Patient Booking Number**: {booking}
136
+
137
+ **Medical Records Summary**:
138
+ {records_text}
139
 
140
+ **Identified Red Flags**:
141
+ {red_flags_text if red_flags_text else "No obvious red flags detected"}
142
 
143
+ **Comprehensive Analysis Instructions**:
144
+ 1. Review all medical data and red flags above
145
+ 2. Identify any potential missed diagnoses based on symptoms, labs, and clinical findings
146
+ 3. Check for medication conflicts or inappropriate prescriptions
147
+ 4. Note any incomplete assessments or missing diagnostic workups
148
+ 5. Flag any urgent follow-up needs or critical findings
149
+ 6. Provide recommendations in clear, actionable terms
150
 
151
+ **Required Output Format**:
152
  ### Missed Diagnoses
153
+ - [List any conditions that may have been overlooked based on the data]
154
 
155
+ ### Medication Issues
156
+ - [List any medication conflicts, inappropriate prescriptions, or missing medications]
157
 
158
+ ### Assessment Gaps
159
+ - [List any incomplete assessments or missing diagnostic tests]
160
 
161
  ### Urgent Follow-up
162
+ - [List any findings requiring immediate attention]
163
+
164
+ ### Clinical Recommendations
165
+ - [Provide specific recommendations for next steps]
166
  """
167
  return prompt
168
 
169
+ def parse_excel_to_prompts(file_path: str) -> List[Tuple[str, str]]:
170
+ """Parse Excel file into analysis prompts with red flag detection"""
171
+ try:
172
+ xl = pd.ExcelFile(file_path)
173
+ df = xl.parse(xl.sheet_names[0], header=0).fillna("")
174
+ medical_data = extract_medical_data(df)
175
+
176
+ prompts = []
177
+ for booking, records in medical_data.items():
178
+ red_flags = identify_red_flags(records)
179
+ prompt = generate_analysis_prompt(booking, records, red_flags)
180
+ prompts.append((booking, prompt))
181
+
182
+ return prompts
183
+ except Exception as e:
184
+ raise ValueError(f"Error parsing Excel file: {str(e)}")
185
+
186
  def init_agent():
187
+ """Initialize the TxAgent with appropriate settings"""
188
  default_tool_path = os.path.abspath("data/new_tool.json")
189
  target_tool_path = os.path.join(tool_cache_dir, "new_tool.json")
190
 
191
  if not os.path.exists(target_tool_path):
192
  shutil.copy(default_tool_path, target_tool_path)
193
 
194
+ agent = TxAgent(
195
+ model_name="mims-harvard/TxAgent-T1-Llama-3.1-8B",
196
+ rag_model_name="mims-harvard/ToolRAG-T1-GTE-Qwen2-1.5B",
197
+ tool_files_dict={"new_tool": target_tool_path},
198
+ force_finish=True,
199
+ enable_checker=True,
200
+ step_rag_num=4,
201
+ seed=100,
202
+ additional_default_tools=[],
203
+ )
204
+ agent.init_model()
205
+ return agent
 
 
 
 
206
 
207
+ def format_markdown(text: str) -> str:
208
+ """Convert markdown text to HTML for better display"""
209
+ return markdown.markdown(text, extensions=['fenced_code', 'tables'])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
 
211
  def create_ui(agent):
212
+ """Create Gradio UI interface"""
213
+ with gr.Blocks(theme=gr.themes.Soft(), title="Clinical Oversight Assistant") as demo:
214
+ gr.Markdown("# 🏥 Clinical Oversight Assistant (Missed Diagnosis Detection)")
 
 
 
 
 
 
 
 
215
 
216
  with gr.Tabs():
217
  with gr.TabItem("Analysis"):
 
220
  with gr.Column(scale=1):
221
  file_upload = gr.File(
222
  label="Upload Excel File",
223
+ file_types=[".xlsx"],
224
  file_count="single",
225
  interactive=True
226
  )
 
240
  height=600,
241
  bubble_full_width=False,
242
  show_copy_button=True,
243
+ render_markdown=True
244
  )
245
  download_output = gr.File(
246
  label="Download Full Report",
 
253
 
254
  1. **Upload Excel File**: Select your patient records Excel file
255
  2. **Add Instructions** (Optional): Provide any specific analysis requests
256
+ 3. **Click Analyze**: The system will process each patient record
257
  4. **Review Results**: Analysis appears in the chat window
258
+ 5. **Download Report**: Get a full text report of all findings
259
 
260
  ### Excel File Requirements
261
  Your Excel file must contain these columns:
262
+ - Booking Number (patient identifier)
263
+ - Form Name (type of medical form)
264
+ - Form Item (specific field name)
265
+ - Item Response (patient response or value)
266
+ - Interview Date (date of recording)
267
+ - Interviewer (who recorded the data)
268
+ - Description (additional notes)
269
 
270
  ### Analysis Includes
271
+ - **Missed diagnoses**: Potential conditions not identified
272
+ - **Medication issues**: Conflicts, side effects, inappropriate prescriptions
273
+ - **Assessment gaps**: Missing tests or incomplete evaluations
274
+ - **Urgent follow-up**: Critical findings needing immediate attention
275
+ - **Clinical recommendations**: Actionable next steps
276
  """)
277
 
278
  def format_message(role: str, content: str) -> Tuple[str, str]:
279
+ """Format messages for the chatbot in (user, bot) format"""
280
  if role == "user":
281
  return (content, None)
282
  else:
283
  return (None, content)
284
 
285
+ def analyze(message: str, chat_history: List[Tuple[str, str]], file) -> Tuple[List[Tuple[str, str]], str]:
286
+ """Main analysis function"""
287
  if not file:
288
  raise gr.Error("Please upload an Excel file first")
289
 
290
  try:
291
+ # Initialize chat history with user message
292
  new_history = chat_history + [format_message("user", message)]
293
  new_history.append(format_message("assistant", "⏳ Processing Excel data..."))
294
  yield new_history, None
295
 
296
+ prompts = parse_excel_to_prompts(file.name)
297
+ full_output = ""
298
+
299
+ for idx, (booking, prompt) in enumerate(prompts, 1):
300
+ chunk_output = ""
301
+ try:
302
+ for result in agent.run_gradio_chat(
303
+ message=prompt,
304
+ history=[],
305
+ temperature=0.2,
306
+ max_new_tokens=1024,
307
+ max_token=4096,
308
+ call_agent=False,
309
+ conversation=[],
310
+ ):
311
+ if isinstance(result, list):
312
+ for r in result:
313
+ if hasattr(r, 'content') and r.content:
314
+ cleaned = clean_response(r.content)
315
+ chunk_output += cleaned + "\n"
316
+ elif isinstance(result, str):
317
+ cleaned = clean_response(result)
318
+ chunk_output += cleaned + "\n"
319
+
320
+ if chunk_output:
321
+ output = f"## Patient Booking: {booking}\n{chunk_output.strip()}\n"
322
+ new_history[-1] = format_message("assistant", output)
323
+ yield new_history, None
324
+
325
+ except Exception as e:
326
+ error_msg = f"⚠️ Error processing booking {booking}: {str(e)}"
327
+ new_history.append(format_message("assistant", error_msg))
328
+ yield new_history, None
329
+ continue
330
+
331
+ if chunk_output:
332
+ output = f"## Patient Booking: {booking}\n{chunk_output.strip()}\n"
333
+ new_history.append(format_message("assistant", output))
334
+ full_output += output + "\n"
335
+ yield new_history, None
336
+
337
+ # Save report
338
  file_hash_value = file_hash(file.name)
339
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
340
+ report_path = os.path.join(report_dir, f"{file_hash_value}_{timestamp}_report.md")
341
+
342
+ with open(report_path, "w", encoding="utf-8") as f:
343
+ f.write("# Clinical Oversight Analysis Report\n\n")
344
+ f.write(f"**Generated on**: {timestamp}\n\n")
345
+ f.write(f"**Source file**: {file.name}\n\n")
346
+ f.write(full_output)
347
 
348
+ yield new_history, report_path if os.path.exists(report_path) else None
 
 
 
 
 
 
349
 
350
  except Exception as e:
 
351
  new_history.append(format_message("assistant", f"❌ Error: {str(e)}"))
352
  yield new_history, None
353
  raise gr.Error(f"Analysis failed: {str(e)}")
354
 
355
  def clear_chat():
356
+ """Clear chat history and outputs"""
357
  return [], None
358
 
359
  # Event handlers
 
361
  analyze,
362
  inputs=[msg_input, chatbot, file_upload],
363
  outputs=[chatbot, download_output],
364
+ api_name="analyze"
 
365
  )
366
 
367
  msg_input.submit(
368
  analyze,
369
  inputs=[msg_input, chatbot, file_upload],
370
+ outputs=[chatbot, download_output]
 
371
  )
372
 
373
  clear_btn.click(
 
394
  share=False
395
  )
396
  except Exception as e:
 
397
  print(f"Failed to launch application: {str(e)}")
398
  sys.exit(1)