Update callbackmanager.py
Browse filescyberpunk_theme = gr.themes.Monochrome(
primary_hue="cyan",
secondary_hue="pink",
neutral_hue="slate",
font=["Source Code Pro", "monospace"], # Retro monospace font
font_mono=["Source Code Pro", "monospace"]
)
- callbackmanager.py +416 -228
callbackmanager.py
CHANGED
@@ -6,8 +6,8 @@ import tempfile
|
|
6 |
from datetime import datetime
|
7 |
import traceback
|
8 |
import logging
|
9 |
-
from huggingface_hub import InferenceClient
|
10 |
-
from urllib.parse import urlparse, parse_qs
|
11 |
|
12 |
# Set up logging
|
13 |
logging.basicConfig(level=logging.INFO)
|
@@ -19,11 +19,11 @@ from pdfutils import PDFGenerator, generate_discharge_summary
|
|
19 |
# Import necessary libraries for new file types and AI analysis functions
|
20 |
import pydicom # For DICOM
|
21 |
import hl7 # For HL7
|
22 |
-
from xml.etree import ElementTree
|
23 |
-
from pypdf import PdfReader
|
24 |
-
import csv
|
25 |
-
import io
|
26 |
-
from PIL import Image
|
27 |
|
28 |
system_instructions = """
|
29 |
**Discharge Guard - Medical Data Analysis Assistant**
|
@@ -57,18 +57,25 @@ system_instructions = """
|
|
57 |
"""
|
58 |
|
59 |
# Initialize Inference Client - Ensure YOUR_HF_TOKEN is set in environment variables or replace with your actual token
|
60 |
-
HF_TOKEN = os.getenv("HF_TOKEN")
|
61 |
if not HF_TOKEN:
|
62 |
-
raise ValueError(
|
|
|
|
|
63 |
client = InferenceClient(api_key=HF_TOKEN)
|
64 |
-
model_name = "meta-llama/Llama-3.3-70B-Instruct"
|
|
|
65 |
|
66 |
-
def analyze_dicom_file_with_ai(dicom_file_path):
|
67 |
"""Analyzes DICOM file metadata using Discharge Guard AI."""
|
68 |
try:
|
69 |
-
dicom_file = pydicom.dcmread(
|
|
|
|
|
70 |
dicom_metadata_json = dicom_file.to_json_dict()
|
71 |
-
prediction_response, trace_data_dicom_ai = analyze_dicom_content_ai(
|
|
|
|
|
72 |
if prediction_response:
|
73 |
report = f"Discharge Guard AI Analysis of DICOM Metadata:\n\nDICOM Metadata Analysis Report:\n{prediction_response}\n\nDisclaimer: The Discharge Guard -generated analysis is for conceptual informational purposes only and may or **NOT medical advice.** Analysis is based on DICOM *metadata* and not image interpretation."
|
74 |
return report
|
@@ -81,7 +88,8 @@ def analyze_dicom_file_with_ai(dicom_file_path): # Modified to accept file path
|
|
81 |
except Exception as e:
|
82 |
return f"Error during DICOM file processing in analyze_dicom_file_with_ai: {e}"
|
83 |
|
84 |
-
|
|
|
85 |
"""Analyzes DICOM metadata JSON content using Discharge Guard AI."""
|
86 |
prompt_text = f"""{system_instructions} \n\n Perform a **deep and comprehensive analysis** of the following DICOM metadata in JSON format to provide a **structured summary and identify potential clinically relevant information with deep insights**. Focus not just on summarizing fields, but on **interpreting their clinical significance, identifying subtle patterns, and drawing inferences about the study's implications**. Think like an experienced radiologist reviewing this metadata for crucial diagnostic clues. Remember this is metadata, not the image itself, so focus on what can be gleaned from the data itself. Provide a "**Deep DICOM Metadata Analysis Report**". Important: Use the API Directories fhir endpoints FROM THIS LINK: https://app.meldrx.com/api/directories/fhir/endpoints.
|
87 |
**DICOM Metadata (JSON):**
|
@@ -109,7 +117,7 @@ def analyze_dicom_content_ai(dicom_metadata_json): # Copied from your code
|
|
109 |
model=model_name,
|
110 |
messages=[{"role": "user", "content": prompt_text}],
|
111 |
temperature=0.4,
|
112 |
-
max_tokens=1024,
|
113 |
top_p=0.9,
|
114 |
)
|
115 |
the_response = response.choices[0].message.content
|
@@ -120,13 +128,16 @@ def analyze_dicom_content_ai(dicom_metadata_json): # Copied from your code
|
|
120 |
trace_data_detail_dicom_analysis["error"] = f"AI Analysis Error: {e}"
|
121 |
return error_message, trace_data_detail_dicom_analysis
|
122 |
|
|
|
123 |
# ... (Paste other AI analysis functions: analyze_hl7_file_with_ai, analyze_cda_xml_file_with_ai, analyze_pdf_file_with_ai, analyze_csv_file_with_ai here - ensure to adapt file reading for Gradio file paths if necessary) ...
|
124 |
def analyze_hl7_file_with_ai(hl7_file_path):
|
125 |
"""Analyzes HL7 file content using Discharge Guard AI."""
|
126 |
try:
|
127 |
-
with open(hl7_file_path.name,
|
128 |
hl7_message_raw = f.read()
|
129 |
-
prediction_response, trace_data_hl7_ai = analyze_hl7_content_ai(
|
|
|
|
|
130 |
|
131 |
if prediction_response:
|
132 |
report = f"Discharge Guard AI Analysis of HL7 Message:\n\nHL7 Message Analysis Report:\n{prediction_response}\n\n**Disclaimer:** The Discharge Guard AGI-generated analysis is for conceptual informational purposes only and may or **NOT medical advice.** Analysis is based on HL7 message content."
|
@@ -140,7 +151,8 @@ def analyze_hl7_file_with_ai(hl7_file_path):
|
|
140 |
except Exception as e:
|
141 |
return f"Error during HL7 file processing in analyze_hl7_file_with_ai: {e}"
|
142 |
|
143 |
-
|
|
|
144 |
"""Analyzes HL7 message content using Discharge Guard AI."""
|
145 |
prompt_text = f"""{system_instructions} \n\n Conduct a **deep and thorough analysis** of the following HL7 message content to provide a **structured summary and identify key clinical information with deep understanding**. Go beyond basic parsing; aim to **interpret the clinical narrative** embedded within the HL7 message. **Engage in deep search to contextualize medical codes and terminology**. Provide a "**Comprehensive HL7 Message Analysis Report**".
|
146 |
**HL7 Message Content:**
|
@@ -168,7 +180,7 @@ def analyze_hl7_content_ai(hl7_message_string): # Copied from your code
|
|
168 |
model=model_name,
|
169 |
messages=[{"role": "user", "content": prompt_text}],
|
170 |
temperature=0.4,
|
171 |
-
max_tokens=1024,
|
172 |
top_p=0.9,
|
173 |
)
|
174 |
the_response = response.choices[0].message.content
|
@@ -180,10 +192,12 @@ def analyze_hl7_content_ai(hl7_message_string): # Copied from your code
|
|
180 |
return error_message, trace_data_detail_hl7_analysis
|
181 |
|
182 |
|
183 |
-
def analyze_cda_xml_file_with_ai(cda_xml_file_path):
|
184 |
"""Analyzes generic CDA or XML file content using Discharge Guard AI (more generalized version) Important: Use the API Directories fhir endpoints FROM THIS LINK: https://app.meldrx.com/api/directories/fhir/endpoints."""
|
185 |
try:
|
186 |
-
with open(
|
|
|
|
|
187 |
cda_xml_content = f.read()
|
188 |
prediction_response, trace_data_cda_xml_ai = analyze_cda_xml_content_ai(
|
189 |
cda_xml_content
|
@@ -200,7 +214,8 @@ def analyze_cda_xml_file_with_ai(cda_xml_file_path): # Modified to accept file p
|
|
200 |
except Exception as e:
|
201 |
return f"Error during XML/CDA file processing in analyze_cda_xml_file_with_ai: {e}"
|
202 |
|
203 |
-
|
|
|
204 |
"""Analyzes generic CDA or XML content using Discharge Guard AI (more generalized version)."""
|
205 |
|
206 |
prompt_text = f"""{system_instructions} \n\n Analyze the following medical XML/CDA content to provide a **structured and comprehensive patient data analysis**, similar to how a medical professional would review a patient's chart or a clinical document. You need to parse the XML structure yourself to extract the relevant information. Use bullet points, tables, or numbered steps for complex tasks. Provide a "Medical Document Analysis" report.
|
@@ -230,7 +245,7 @@ def analyze_cda_xml_content_ai(cda_xml_content): # Copied from your code
|
|
230 |
model=model_name,
|
231 |
messages=[{"role": "user", "content": prompt_text}],
|
232 |
temperature=0.4,
|
233 |
-
max_tokens=1024,
|
234 |
top_p=0.9,
|
235 |
)
|
236 |
the_response = response.choices[0].message.content
|
@@ -242,17 +257,21 @@ def analyze_cda_xml_content_ai(cda_xml_content): # Copied from your code
|
|
242 |
return error_message, trace_data_detail_cda_xml_analysis
|
243 |
|
244 |
|
245 |
-
def analyze_pdf_file_with_ai(pdf_file_path):
|
246 |
"""Analyzes PDF file content using Discharge Guard AI."""
|
247 |
try:
|
248 |
-
with open(
|
249 |
-
|
|
|
|
|
250 |
pdf_reader = PdfReader(pdf_file)
|
251 |
text_content = ""
|
252 |
for page in pdf_reader.pages:
|
253 |
text_content += page.extract_text()
|
254 |
|
255 |
-
prediction_response, trace_data_pdf_ai = analyze_pdf_content_ai(
|
|
|
|
|
256 |
|
257 |
if prediction_response:
|
258 |
report = f"Discharge Guard AI Analysis of PDF Content:\n\nMedical Report Analysis Report:\n{prediction_response}\n\n**Disclaimer:** The Discharge Guard AGI-generated analysis is for conceptual informational purposes only and may or **NOT medical advice.** Analysis is based on PDF text content."
|
@@ -266,7 +285,8 @@ def analyze_pdf_file_with_ai(pdf_file_path): # Modified to accept file path
|
|
266 |
except Exception as e:
|
267 |
return f"Error during PDF file processing in analyze_pdf_file_with_ai: {e}"
|
268 |
|
269 |
-
|
|
|
270 |
"""Analyzes PDF text content using Discharge Guard AI."""
|
271 |
prompt_text = f"""{system_instructions} \n\n Analyze the following medical PDF text content to provide a **structured summary and identify key clinical information**. Focus on patient demographics, medical history, findings, diagnoses, medications, recommendations, and any important clinical details conveyed in the document. Provide a "Medical Report Analysis" report.
|
272 |
**Medical PDF Text Content:**
|
@@ -294,7 +314,7 @@ def analyze_pdf_content_ai(pdf_text_content): # Copied from your code
|
|
294 |
model=model_name,
|
295 |
messages=[{"role": "user", "content": prompt_text}],
|
296 |
temperature=0.4,
|
297 |
-
max_tokens=1024,
|
298 |
top_p=0.9,
|
299 |
)
|
300 |
the_response = response.choices[0].message.content
|
@@ -306,10 +326,12 @@ def analyze_pdf_content_ai(pdf_text_content): # Copied from your code
|
|
306 |
return error_message, trace_data_detail_pdf_analysis
|
307 |
|
308 |
|
309 |
-
def analyze_csv_file_with_ai(csv_file_path):
|
310 |
"""Analyzes CSV file content using Discharge Guard AI."""
|
311 |
try:
|
312 |
-
csv_content = csv_file_path.read().decode(
|
|
|
|
|
313 |
prediction_response, trace_data_csv_ai = analyze_csv_content_ai(csv_content)
|
314 |
|
315 |
if prediction_response:
|
@@ -324,7 +346,8 @@ def analyze_csv_file_with_ai(csv_file_path): # Modified to accept file path
|
|
324 |
except Exception as e:
|
325 |
return f"Error during CSV file processing in analyze_csv_file_with_ai: {e}"
|
326 |
|
327 |
-
|
|
|
328 |
"""Analyzes CSV content (string) using Discharge Guard AI."""
|
329 |
prompt_text = f"""{system_instructions} \n\n Analyze the following medical CSV data to provide a **structured summary and identify potential clinical insights**. Assume the CSV represents patient-related medical data. Focus on understanding the columns, summarizing key data points, identifying trends or patterns, and noting any potential clinical significance of the data. Provide a "Data Analysis" report.
|
330 |
**Medical CSV Data:**
|
@@ -352,7 +375,7 @@ def analyze_csv_content_ai(csv_content_string): # Copied from your code
|
|
352 |
model=model_name,
|
353 |
messages=[{"role": "user", "content": prompt_text}],
|
354 |
temperature=0.4,
|
355 |
-
max_tokens=1024,
|
356 |
top_p=0.9,
|
357 |
)
|
358 |
the_response = response.choices[0].message.content
|
@@ -384,19 +407,21 @@ class CallbackManager:
|
|
384 |
self.auth_code = code
|
385 |
if self.api.authenticate_with_code(code):
|
386 |
self.access_token = self.api.access_token
|
387 |
-
return
|
388 |
-
|
|
|
|
|
389 |
|
390 |
def get_patient_data(self) -> str:
|
391 |
"""Fetch patient data from MeldRx"""
|
392 |
try:
|
393 |
if not self.access_token:
|
394 |
logger.warning("Not authenticated when getting patient data")
|
395 |
-
return "Not authenticated. Please provide a valid authorization code first
|
396 |
|
397 |
# For demo purposes, if there's no actual API connected, return mock data
|
398 |
# Remove this in production and use the real API call
|
399 |
-
if not hasattr(self.api,
|
400 |
logger.info("Using mock patient data (no API connection)")
|
401 |
# Return mock FHIR bundle with patient data
|
402 |
mock_data = {
|
@@ -413,18 +438,14 @@ class CallbackManager:
|
|
413 |
{
|
414 |
"use": "official",
|
415 |
"family": "Smith",
|
416 |
-
"given": ["John"]
|
417 |
}
|
418 |
],
|
419 |
"gender": "male",
|
420 |
"birthDate": "1970-01-01",
|
421 |
"address": [
|
422 |
-
{
|
423 |
-
|
424 |
-
"state": "MA",
|
425 |
-
"postalCode": "02108"
|
426 |
-
}
|
427 |
-
]
|
428 |
}
|
429 |
},
|
430 |
{
|
@@ -435,7 +456,7 @@ class CallbackManager:
|
|
435 |
{
|
436 |
"use": "official",
|
437 |
"family": "Johnson",
|
438 |
-
"given": ["Jane"]
|
439 |
}
|
440 |
],
|
441 |
"gender": "female",
|
@@ -444,12 +465,12 @@ class CallbackManager:
|
|
444 |
{
|
445 |
"city": "Cambridge",
|
446 |
"state": "MA",
|
447 |
-
"postalCode": "02139"
|
448 |
}
|
449 |
-
]
|
450 |
}
|
451 |
-
}
|
452 |
-
]
|
453 |
}
|
454 |
return json.dumps(mock_data, indent=2)
|
455 |
|
@@ -457,17 +478,22 @@ class CallbackManager:
|
|
457 |
logger.info("Calling Meldrx API to get patients")
|
458 |
patients = self.api.get_patients()
|
459 |
if patients is not None:
|
460 |
-
return
|
461 |
-
|
|
|
|
|
|
|
|
|
462 |
except Exception as e:
|
463 |
error_msg = f"Error in get_patient_data: {str(e)}"
|
464 |
logger.error(error_msg)
|
465 |
-
return f"Error retrieving patient data: {str(e)}"
|
|
|
466 |
|
467 |
def get_patient_documents(self, patient_id: str = None):
|
468 |
"""Fetch patient documents from MeldRx"""
|
469 |
if not self.access_token:
|
470 |
-
return "Not authenticated. Please provide a valid authorization code first
|
471 |
|
472 |
try:
|
473 |
# This would call the actual MeldRx API to get documents for a specific patient
|
@@ -486,42 +512,94 @@ class CallbackManager:
|
|
486 |
"date": "2023-01-17",
|
487 |
"author": "Lab System",
|
488 |
"content": "CBC results: WBC 7.5, RBC 4.2, Hgb 14.1...",
|
489 |
-
}
|
490 |
]
|
491 |
except Exception as e:
|
492 |
-
return f"Error retrieving patient documents: {str(e)}"
|
|
|
493 |
|
494 |
def display_form(
|
495 |
-
first_name,
|
496 |
-
|
497 |
-
|
498 |
-
|
499 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
500 |
):
|
501 |
form = f"""
|
502 |
-
|
503 |
-
|
504 |
-
-
|
505 |
-
-
|
506 |
-
-
|
507 |
-
-
|
508 |
-
-
|
509 |
-
-
|
510 |
-
-
|
511 |
-
- Date
|
512 |
-
-
|
513 |
-
-
|
514 |
-
-
|
|
|
515 |
- Prepared By: {preparer_name}, {preparer_job_title}
|
|
|
516 |
"""
|
517 |
return form
|
518 |
|
|
|
519 |
def generate_pdf_from_form(
|
520 |
-
first_name,
|
521 |
-
|
522 |
-
|
523 |
-
|
524 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
525 |
):
|
526 |
"""Generate a PDF discharge form using the provided data"""
|
527 |
|
@@ -539,7 +617,7 @@ def generate_pdf_from_form(
|
|
539 |
"address": address,
|
540 |
"city": city,
|
541 |
"state": state,
|
542 |
-
"zip": zip_code
|
543 |
}
|
544 |
|
545 |
discharge_info = {
|
@@ -547,25 +625,27 @@ def generate_pdf_from_form(
|
|
547 |
"date_of_discharge": discharge_date,
|
548 |
"source_of_admission": referral_source,
|
549 |
"mode_of_admission": admission_method,
|
550 |
-
"discharge_against_advice": "Yes"
|
|
|
|
|
551 |
}
|
552 |
|
553 |
diagnosis_info = {
|
554 |
"diagnosis": diagnosis,
|
555 |
"operation_procedure": procedures,
|
556 |
"treatment": "", # Not collected in the form
|
557 |
-
"follow_up": ""
|
558 |
}
|
559 |
|
560 |
medication_info = {
|
561 |
"medications": [medications] if medications else [],
|
562 |
-
"instructions": "" # Not collected in the form
|
563 |
}
|
564 |
|
565 |
prepared_by = {
|
566 |
"name": preparer_name,
|
567 |
"title": preparer_job_title,
|
568 |
-
"signature": "" # Not collected in the form
|
569 |
}
|
570 |
|
571 |
# Generate PDF
|
@@ -574,17 +654,18 @@ def generate_pdf_from_form(
|
|
574 |
discharge_info,
|
575 |
diagnosis_info,
|
576 |
medication_info,
|
577 |
-
prepared_by
|
578 |
)
|
579 |
|
580 |
# Create temporary file to save the PDF
|
581 |
-
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=
|
582 |
temp_file.write(pdf_buffer.read())
|
583 |
temp_file_path = temp_file.name
|
584 |
temp_file.close()
|
585 |
|
586 |
return temp_file_path
|
587 |
|
|
|
588 |
def generate_pdf_from_meldrx(patient_data):
|
589 |
"""Generate a PDF using patient data from MeldRx"""
|
590 |
if isinstance(patient_data, str):
|
@@ -607,10 +688,10 @@ def generate_pdf_from_meldrx(patient_data):
|
|
607 |
# Extract patient info
|
608 |
patient_info = {
|
609 |
"name": f"{patient.get('name', {}).get('given', [''])[0]} {patient.get('name', {}).get('family', '')}",
|
610 |
-
"dob": patient.get(
|
611 |
-
"patient_id": patient.get(
|
612 |
"admission_date": datetime.now().strftime("%Y-%m-%d"), # Mock data
|
613 |
-
"physician": "Dr. Provider" # Mock data
|
614 |
}
|
615 |
|
616 |
# Mock LLM-generated content - This part needs to be replaced with actual AI generation if desired for MeldRx PDF
|
@@ -619,70 +700,91 @@ def generate_pdf_from_meldrx(patient_data):
|
|
619 |
"treatment": "Treatment summary would be generated by AI based on patient data from MeldRx.",
|
620 |
"medications": "Medication list would be generated by AI based on patient data from MeldRx.",
|
621 |
"follow_up": "Follow-up instructions would be generated by AI based on patient data from MeldRx.",
|
622 |
-
"special_instructions": "Special instructions would be generated by AI based on patient data from MeldRx."
|
623 |
}
|
624 |
|
625 |
# Create discharge summary - Using No-AI PDF generation for now, replace with AI-content generation later
|
626 |
output_dir = tempfile.mkdtemp()
|
627 |
-
pdf_path = generate_discharge_summary(
|
|
|
|
|
628 |
|
629 |
-
return pdf_path, "PDF generated successfully (No AI Content in PDF yet)"
|
630 |
|
631 |
except Exception as e:
|
632 |
return None, f"Error generating PDF: {str(e)}"
|
633 |
|
|
|
634 |
def generate_discharge_paper_one_click():
|
635 |
"""One-click function to fetch patient data and generate discharge paper with AI Content."""
|
636 |
patient_data_str = CALLBACK_MANAGER.get_patient_data()
|
637 |
-
if
|
638 |
-
|
|
|
|
|
|
|
|
|
639 |
|
640 |
try:
|
641 |
patient_data = json.loads(patient_data_str)
|
642 |
|
643 |
# --- AI Content Generation for Discharge Summary ---
|
644 |
# This is a placeholder - Replace with actual AI call using InferenceClient and patient_data to generate content
|
645 |
-
ai_generated_content = generate_ai_discharge_content(
|
|
|
|
|
646 |
|
647 |
if not ai_generated_content:
|
648 |
return None, "Error: AI content generation failed."
|
649 |
|
650 |
# --- PDF Generation with AI Content ---
|
651 |
-
pdf_path, status_message = generate_pdf_from_meldrx_with_ai_content(
|
|
|
|
|
652 |
|
653 |
if pdf_path:
|
654 |
return pdf_path, status_message
|
655 |
else:
|
656 |
-
return None, status_message
|
657 |
|
658 |
except json.JSONDecodeError:
|
659 |
return None, "Error: Patient data is not in valid JSON format."
|
660 |
except Exception as e:
|
661 |
return None, f"Error during discharge paper generation: {str(e)}"
|
662 |
|
|
|
663 |
def generate_ai_discharge_content(patient_data):
|
664 |
"""Placeholder function to generate AI content for discharge summary.
|
665 |
-
|
666 |
try:
|
667 |
-
patient_name =
|
|
|
|
|
|
|
|
|
668 |
prompt_text = f"""{system_instructions}\n\nGenerate a discharge summary content (diagnosis, treatment, medications, follow-up instructions, special instructions) for patient: {patient_name}. Base the content on available patient data (if any provided, currently not provided in detail in this mock-up). Focus on creating clinically relevant and informative summary. Remember this is for informational purposes and NOT medical advice."""
|
669 |
|
670 |
response = client.chat.completions.create(
|
671 |
model=model_name,
|
672 |
messages=[{"role": "user", "content": prompt_text}],
|
673 |
-
temperature=0.6,
|
674 |
-
max_tokens=1024,
|
675 |
top_p=0.9,
|
676 |
)
|
677 |
ai_content = response.choices[0].message.content
|
678 |
|
679 |
# Basic parsing of AI content - improve this based on desired output structure from LLM
|
680 |
llm_content = {
|
681 |
-
"diagnosis": "AI Generated Diagnosis (Placeholder):\n"
|
682 |
-
|
683 |
-
"
|
684 |
-
|
685 |
-
"
|
|
|
|
|
|
|
|
|
|
|
686 |
}
|
687 |
return llm_content
|
688 |
|
@@ -690,11 +792,12 @@ def generate_ai_discharge_content(patient_data):
|
|
690 |
logger.error(f"Error generating AI discharge content: {e}")
|
691 |
return None
|
692 |
|
|
|
693 |
def extract_section(ai_content, section_title):
|
694 |
"""Simple placeholder function to extract section from AI content.
|
695 |
-
|
696 |
start_marker = f"**{section_title}:**"
|
697 |
-
end_marker = "\n\n"
|
698 |
start_index = ai_content.find(start_marker)
|
699 |
if start_index != -1:
|
700 |
start_index += len(start_marker)
|
@@ -723,60 +826,72 @@ def generate_pdf_from_meldrx_with_ai_content(patient_data, llm_content):
|
|
723 |
|
724 |
patient_info = {
|
725 |
"name": f"{patient.get('name', {}).get('given', [''])[0]} {patient.get('name', {}).get('family', '')}",
|
726 |
-
"dob": patient.get(
|
727 |
-
"patient_id": patient.get(
|
728 |
"admission_date": datetime.now().strftime("%Y-%m-%d"), # Mock data
|
729 |
-
"physician": "Dr. AI Provider" # Mock data - Indicate AI generated
|
730 |
}
|
731 |
|
732 |
-
|
733 |
output_dir = tempfile.mkdtemp()
|
734 |
-
pdf_path = generate_discharge_summary(
|
|
|
|
|
735 |
|
736 |
-
return pdf_path, "PDF generated successfully with AI Content"
|
737 |
|
738 |
except Exception as e:
|
739 |
return None, f"Error generating PDF with AI content: {str(e)}"
|
740 |
|
|
|
741 |
def extract_auth_code_from_url(redirected_url):
|
742 |
"""Extracts the authorization code from the redirected URL."""
|
743 |
try:
|
744 |
parsed_url = urlparse(redirected_url)
|
745 |
query_params = parse_qs(parsed_url.query)
|
746 |
if "code" in query_params:
|
747 |
-
return query_params["code"][0], None
|
748 |
else:
|
749 |
-
return None, "Authorization code not found in URL."
|
750 |
except Exception as e:
|
751 |
-
return None, f"Error parsing URL: {e}"
|
|
|
752 |
|
753 |
# Create a simplified interface to avoid complex component interactions
|
754 |
CALLBACK_MANAGER = CallbackManager(
|
755 |
redirect_uri="https://multitransformer-discharge-guard.hf.space/callback",
|
756 |
-
client_secret=None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
757 |
)
|
758 |
|
759 |
-
# Create the UI
|
760 |
-
with gr.Blocks() as demo:
|
761 |
-
gr.Markdown("
|
762 |
|
763 |
-
with gr.Tab("Authenticate with MeldRx"):
|
764 |
-
gr.Markdown("
|
765 |
auth_url_output = gr.Textbox(label="Authorization URL", value=CALLBACK_MANAGER.get_auth_url(), interactive=False)
|
766 |
-
gr.Markdown("Copy the URL above, open it in a browser, log in, and paste the
|
767 |
redirected_url_input = gr.Textbox(label="Redirected URL") # New textbox for redirected URL
|
768 |
-
extract_code_button = gr.Button("Extract Authorization Code") #
|
769 |
extracted_code_output = gr.Textbox(label="Extracted Authorization Code", interactive=False) # Textbox to show extracted code
|
770 |
|
771 |
auth_code_input = gr.Textbox(label="Authorization Code (from above, or paste manually if extraction fails)", interactive=True) # Updated label to be clearer
|
772 |
-
auth_submit = gr.Button("Submit Code for Authentication")
|
773 |
-
auth_result = gr.
|
774 |
|
775 |
-
patient_data_button = gr.Button("Fetch Patient Data")
|
776 |
patient_data_output = gr.Textbox(label="Patient Data", lines=10)
|
777 |
|
778 |
# Add button to generate PDF from MeldRx data (No AI)
|
779 |
-
meldrx_pdf_button = gr.Button("Generate PDF from MeldRx Data (No AI)") # Renamed button
|
780 |
meldrx_pdf_status = gr.Textbox(label="PDF Generation Status (No AI)") # Renamed status
|
781 |
meldrx_pdf_download = gr.File(label="Download Generated PDF (No AI)") # Renamed download
|
782 |
|
@@ -784,37 +899,39 @@ with gr.Blocks() as demo:
|
|
784 |
"""Processes the redirected URL to extract and display the authorization code."""
|
785 |
auth_code, error_message = extract_auth_code_from_url(redirected_url)
|
786 |
if auth_code:
|
787 |
-
return auth_code, "Authorization code extracted
|
788 |
else:
|
789 |
-
return "",
|
790 |
|
791 |
|
792 |
extract_code_button.click(
|
793 |
fn=process_redirected_url,
|
794 |
inputs=redirected_url_input,
|
795 |
-
outputs=[extracted_code_output, auth_result]
|
796 |
)
|
797 |
|
798 |
-
|
799 |
auth_submit.click(
|
800 |
fn=CALLBACK_MANAGER.set_auth_code,
|
801 |
-
inputs=extracted_code_output,
|
802 |
-
outputs=auth_result
|
803 |
)
|
804 |
|
|
|
|
|
|
|
805 |
|
806 |
-
|
807 |
-
gr.Markdown("## Patient Data")
|
808 |
-
dashboard_output = gr.HTML("<p>Fetch patient data from the Authentication tab first.</p>")
|
809 |
-
|
810 |
-
refresh_btn = gr.Button("Refresh Data")
|
811 |
|
812 |
# Simple function to update dashboard based on fetched data
|
813 |
def update_dashboard():
|
814 |
try:
|
815 |
data = CALLBACK_MANAGER.get_patient_data()
|
816 |
-
if
|
817 |
-
|
|
|
|
|
|
|
|
|
818 |
|
819 |
try:
|
820 |
# Parse the data
|
@@ -828,7 +945,7 @@ with gr.Blocks() as demo:
|
|
828 |
patients.append(resource)
|
829 |
|
830 |
# Generate HTML card
|
831 |
-
html = "<h3>Patients</h3>"
|
832 |
for patient in patients:
|
833 |
# Extract name
|
834 |
name = patient.get("name", [{}])[0]
|
@@ -839,24 +956,25 @@ with gr.Blocks() as demo:
|
|
839 |
gender = patient.get("gender", "unknown").capitalize()
|
840 |
birth_date = patient.get("birthDate", "Unknown")
|
841 |
|
842 |
-
# Generate HTML card
|
843 |
html += f"""
|
844 |
-
<div style="border: 1px solid #
|
845 |
-
<h4>{given} {family}</h4>
|
846 |
-
<p><strong>Gender:</strong> {gender}</p>
|
847 |
-
<p><strong>Birth Date:</strong> {birth_date}</p>
|
848 |
-
<p><strong>ID:</strong> {patient.get("id", "Unknown")}</p>
|
849 |
</div>
|
850 |
"""
|
851 |
|
852 |
return html
|
853 |
except Exception as e:
|
854 |
-
return f"<p>Error parsing patient data: {str(e)}</p>"
|
855 |
except Exception as e:
|
856 |
-
return f"<p>Error fetching patient data: {str(e)}</p>"
|
857 |
|
858 |
-
|
859 |
-
|
|
|
860 |
with gr.Row():
|
861 |
first_name = gr.Textbox(label="First Name")
|
862 |
last_name = gr.Textbox(label="Last Name")
|
@@ -870,174 +988,244 @@ with gr.Blocks() as demo:
|
|
870 |
city = gr.Textbox(label="City")
|
871 |
state = gr.Textbox(label="State")
|
872 |
zip_code = gr.Textbox(label="Zip Code")
|
873 |
-
gr.Markdown("
|
874 |
with gr.Row():
|
875 |
doctor_first_name = gr.Textbox(label="Doctor's First Name")
|
876 |
doctor_last_name = gr.Textbox(label="Doctor's Last Name")
|
877 |
-
doctor_middle_initial = gr.Textbox(label="Middle Initial")
|
878 |
hospital_name = gr.Textbox(label="Hospital/Clinic Name")
|
879 |
doctor_address = gr.Textbox(label="Address")
|
880 |
with gr.Row():
|
881 |
doctor_city = gr.Textbox(label="City")
|
882 |
doctor_state = gr.Textbox(label="State")
|
883 |
doctor_zip = gr.Textbox(label="Zip Code")
|
884 |
-
gr.Markdown("
|
885 |
with gr.Row():
|
886 |
admission_date = gr.Textbox(label="Date of Admission")
|
887 |
referral_source = gr.Textbox(label="Source of Referral")
|
888 |
admission_method = gr.Textbox(label="Method of Admission")
|
889 |
with gr.Row():
|
890 |
discharge_date = gr.Textbox(label="Date of Discharge")
|
891 |
-
discharge_reason = gr.Radio(
|
|
|
|
|
|
|
892 |
date_of_death = gr.Textbox(label="Date of Death (if applicable)")
|
893 |
-
gr.Markdown("
|
894 |
diagnosis = gr.Textbox(label="Diagnosis")
|
895 |
procedures = gr.Textbox(label="Operation & Procedures")
|
896 |
-
gr.Markdown("
|
897 |
medications = gr.Textbox(label="Medication on Discharge")
|
898 |
-
gr.Markdown("
|
899 |
with gr.Row():
|
900 |
preparer_name = gr.Textbox(label="Name")
|
901 |
preparer_job_title = gr.Textbox(label="Job Title")
|
902 |
|
903 |
# Add buttons for both display form and generate PDF
|
904 |
with gr.Row():
|
905 |
-
submit_display = gr.Button("Display Form")
|
906 |
-
submit_pdf = gr.Button("Generate PDF (No AI)") # Renamed button to clarify no AI
|
907 |
|
908 |
# Output areas
|
909 |
-
form_output = gr.
|
910 |
-
pdf_output = gr.File(label="Download PDF (No AI)")
|
911 |
|
912 |
# Connect the display form button
|
913 |
submit_display.click(
|
914 |
display_form,
|
915 |
inputs=[
|
916 |
-
first_name,
|
917 |
-
|
918 |
-
|
919 |
-
|
920 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
921 |
],
|
922 |
-
outputs=form_output
|
923 |
)
|
924 |
|
925 |
# Connect the generate PDF button (No AI version)
|
926 |
submit_pdf.click(
|
927 |
generate_pdf_from_form,
|
928 |
inputs=[
|
929 |
-
first_name,
|
930 |
-
|
931 |
-
|
932 |
-
|
933 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
934 |
],
|
935 |
-
outputs=pdf_output
|
936 |
)
|
937 |
|
938 |
-
with gr.Tab("Medical File Analysis"):
|
939 |
-
gr.Markdown("
|
940 |
with gr.Column():
|
941 |
-
dicom_file = gr.File(
|
|
|
|
|
942 |
dicom_ai_output = gr.Textbox(label="DICOM Analysis Report", lines=5)
|
943 |
-
analyze_dicom_button = gr.Button("Analyze DICOM with AI")
|
944 |
|
945 |
-
hl7_file = gr.File(
|
|
|
|
|
946 |
hl7_ai_output = gr.Textbox(label="HL7 Analysis Report", lines=5)
|
947 |
-
analyze_hl7_button = gr.Button("Analyze HL7 with AI")
|
948 |
|
949 |
-
xml_file = gr.File(
|
|
|
|
|
950 |
xml_ai_output = gr.Textbox(label="XML Analysis Report", lines=5)
|
951 |
-
analyze_xml_button = gr.Button("Analyze XML with AI")
|
952 |
|
953 |
-
ccda_file = gr.File(
|
|
|
|
|
954 |
ccda_ai_output = gr.Textbox(label="CCDA Analysis Report", lines=5)
|
955 |
-
analyze_ccda_button = gr.Button("Analyze CCDA with AI")
|
956 |
-
|
957 |
-
ccd_file = gr.File(
|
958 |
-
|
959 |
-
|
960 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
961 |
pdf_ai_output = gr.Textbox(label="PDF Analysis Report", lines=5)
|
962 |
-
analyze_pdf_button = gr.Button("Analyze PDF with AI")
|
963 |
|
964 |
-
csv_file = gr.File(
|
|
|
|
|
965 |
csv_ai_output = gr.Textbox(label="CSV Analysis Report", lines=5)
|
966 |
-
analyze_csv_button = gr.Button("Analyze CSV with AI")
|
967 |
-
|
968 |
|
969 |
# Connect AI Analysis Buttons - using REAL AI functions now
|
970 |
analyze_dicom_button.click(
|
971 |
-
analyze_dicom_file_with_ai,
|
972 |
-
inputs=dicom_file,
|
|
|
973 |
)
|
974 |
analyze_hl7_button.click(
|
975 |
-
analyze_hl7_file_with_ai,
|
976 |
-
inputs=hl7_file,
|
|
|
977 |
)
|
978 |
analyze_xml_button.click(
|
979 |
-
analyze_cda_xml_file_with_ai,
|
980 |
-
inputs=xml_file,
|
|
|
981 |
)
|
982 |
analyze_ccda_button.click(
|
983 |
-
analyze_cda_xml_file_with_ai,
|
984 |
-
inputs=ccda_file,
|
|
|
985 |
)
|
986 |
-
analyze_ccd_button.click(
|
987 |
-
analyze_cda_xml_file_with_ai,
|
988 |
-
inputs=ccd_file,
|
|
|
989 |
)
|
990 |
analyze_pdf_button.click(
|
991 |
-
analyze_pdf_file_with_ai,
|
992 |
-
inputs=pdf_file,
|
993 |
-
outputs=pdf_ai_output
|
994 |
)
|
995 |
analyze_csv_button.click(
|
996 |
-
analyze_csv_file_with_ai,
|
997 |
-
inputs=csv_file,
|
998 |
-
outputs=csv_ai_output
|
999 |
)
|
1000 |
|
1001 |
-
|
1002 |
-
|
1003 |
-
|
1004 |
-
|
1005 |
-
|
1006 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1007 |
|
1008 |
one_click_ai_pdf_button.click(
|
1009 |
-
generate_discharge_paper_one_click,
|
1010 |
inputs=[],
|
1011 |
-
outputs=[one_click_ai_pdf_download, one_click_ai_pdf_status]
|
1012 |
)
|
1013 |
|
1014 |
-
|
1015 |
# Connect the patient data buttons
|
1016 |
patient_data_button.click(
|
1017 |
fn=CALLBACK_MANAGER.get_patient_data,
|
1018 |
inputs=None,
|
1019 |
-
outputs=patient_data_output
|
1020 |
)
|
1021 |
|
1022 |
# Connect refresh button to update dashboard
|
1023 |
refresh_btn.click(
|
1024 |
-
fn=update_dashboard,
|
1025 |
-
inputs=None,
|
1026 |
-
outputs=dashboard_output
|
1027 |
)
|
1028 |
|
1029 |
# Corrected the button click function name here to `generate_pdf_from_meldrx` (No AI PDF)
|
1030 |
meldrx_pdf_button.click(
|
1031 |
fn=generate_pdf_from_meldrx,
|
1032 |
inputs=patient_data_output,
|
1033 |
-
outputs=[meldrx_pdf_download, meldrx_pdf_status]
|
1034 |
)
|
1035 |
|
1036 |
# Connect patient data updates to dashboard
|
1037 |
patient_data_button.click(
|
1038 |
-
fn=update_dashboard,
|
1039 |
-
inputs=None,
|
1040 |
-
outputs=dashboard_output
|
1041 |
)
|
1042 |
|
1043 |
# Launch with sharing enabled for public access
|
|
|
6 |
from datetime import datetime
|
7 |
import traceback
|
8 |
import logging
|
9 |
+
from huggingface_hub import InferenceClient # Import InferenceClient
|
10 |
+
from urllib.parse import urlparse, parse_qs # Import URL parsing utilities
|
11 |
|
12 |
# Set up logging
|
13 |
logging.basicConfig(level=logging.INFO)
|
|
|
19 |
# Import necessary libraries for new file types and AI analysis functions
|
20 |
import pydicom # For DICOM
|
21 |
import hl7 # For HL7
|
22 |
+
from xml.etree import ElementTree # For XML and CCDA
|
23 |
+
from pypdf import PdfReader # For PDF
|
24 |
+
import csv # For CSV
|
25 |
+
import io # For IO operations
|
26 |
+
from PIL import Image # For image handling
|
27 |
|
28 |
system_instructions = """
|
29 |
**Discharge Guard - Medical Data Analysis Assistant**
|
|
|
57 |
"""
|
58 |
|
59 |
# Initialize Inference Client - Ensure YOUR_HF_TOKEN is set in environment variables or replace with your actual token
|
60 |
+
HF_TOKEN = os.getenv("HF_TOKEN") # Or replace with your actual token string
|
61 |
if not HF_TOKEN:
|
62 |
+
raise ValueError(
|
63 |
+
"HF_TOKEN environment variable not set. Please set your Hugging Face API token."
|
64 |
+
)
|
65 |
client = InferenceClient(api_key=HF_TOKEN)
|
66 |
+
model_name = "meta-llama/Llama-3.3-70B-Instruct" # Specify the model to use
|
67 |
+
|
68 |
|
69 |
+
def analyze_dicom_file_with_ai(dicom_file_path): # Modified to accept file path
|
70 |
"""Analyzes DICOM file metadata using Discharge Guard AI."""
|
71 |
try:
|
72 |
+
dicom_file = pydicom.dcmread(
|
73 |
+
dicom_file_path.name
|
74 |
+
) # Read dicom using path, access file through .name for Gradio UploadedFile
|
75 |
dicom_metadata_json = dicom_file.to_json_dict()
|
76 |
+
prediction_response, trace_data_dicom_ai = analyze_dicom_content_ai(
|
77 |
+
dicom_metadata_json
|
78 |
+
) # Call content analysis
|
79 |
if prediction_response:
|
80 |
report = f"Discharge Guard AI Analysis of DICOM Metadata:\n\nDICOM Metadata Analysis Report:\n{prediction_response}\n\nDisclaimer: The Discharge Guard -generated analysis is for conceptual informational purposes only and may or **NOT medical advice.** Analysis is based on DICOM *metadata* and not image interpretation."
|
81 |
return report
|
|
|
88 |
except Exception as e:
|
89 |
return f"Error during DICOM file processing in analyze_dicom_file_with_ai: {e}"
|
90 |
|
91 |
+
|
92 |
+
def analyze_dicom_content_ai(dicom_metadata_json): # Copied from your code
|
93 |
"""Analyzes DICOM metadata JSON content using Discharge Guard AI."""
|
94 |
prompt_text = f"""{system_instructions} \n\n Perform a **deep and comprehensive analysis** of the following DICOM metadata in JSON format to provide a **structured summary and identify potential clinically relevant information with deep insights**. Focus not just on summarizing fields, but on **interpreting their clinical significance, identifying subtle patterns, and drawing inferences about the study's implications**. Think like an experienced radiologist reviewing this metadata for crucial diagnostic clues. Remember this is metadata, not the image itself, so focus on what can be gleaned from the data itself. Provide a "**Deep DICOM Metadata Analysis Report**". Important: Use the API Directories fhir endpoints FROM THIS LINK: https://app.meldrx.com/api/directories/fhir/endpoints.
|
95 |
**DICOM Metadata (JSON):**
|
|
|
117 |
model=model_name,
|
118 |
messages=[{"role": "user", "content": prompt_text}],
|
119 |
temperature=0.4,
|
120 |
+
max_tokens=1024, # Adjust as needed
|
121 |
top_p=0.9,
|
122 |
)
|
123 |
the_response = response.choices[0].message.content
|
|
|
128 |
trace_data_detail_dicom_analysis["error"] = f"AI Analysis Error: {e}"
|
129 |
return error_message, trace_data_detail_dicom_analysis
|
130 |
|
131 |
+
|
132 |
# ... (Paste other AI analysis functions: analyze_hl7_file_with_ai, analyze_cda_xml_file_with_ai, analyze_pdf_file_with_ai, analyze_csv_file_with_ai here - ensure to adapt file reading for Gradio file paths if necessary) ...
|
133 |
def analyze_hl7_file_with_ai(hl7_file_path):
|
134 |
"""Analyzes HL7 file content using Discharge Guard AI."""
|
135 |
try:
|
136 |
+
with open(hl7_file_path.name, "r") as f: # Open file using path, access file through .name for Gradio UploadedFile
|
137 |
hl7_message_raw = f.read()
|
138 |
+
prediction_response, trace_data_hl7_ai = analyze_hl7_content_ai(
|
139 |
+
hl7_message_raw
|
140 |
+
)
|
141 |
|
142 |
if prediction_response:
|
143 |
report = f"Discharge Guard AI Analysis of HL7 Message:\n\nHL7 Message Analysis Report:\n{prediction_response}\n\n**Disclaimer:** The Discharge Guard AGI-generated analysis is for conceptual informational purposes only and may or **NOT medical advice.** Analysis is based on HL7 message content."
|
|
|
151 |
except Exception as e:
|
152 |
return f"Error during HL7 file processing in analyze_hl7_file_with_ai: {e}"
|
153 |
|
154 |
+
|
155 |
+
def analyze_hl7_content_ai(hl7_message_string): # Copied from your code
|
156 |
"""Analyzes HL7 message content using Discharge Guard AI."""
|
157 |
prompt_text = f"""{system_instructions} \n\n Conduct a **deep and thorough analysis** of the following HL7 message content to provide a **structured summary and identify key clinical information with deep understanding**. Go beyond basic parsing; aim to **interpret the clinical narrative** embedded within the HL7 message. **Engage in deep search to contextualize medical codes and terminology**. Provide a "**Comprehensive HL7 Message Analysis Report**".
|
158 |
**HL7 Message Content:**
|
|
|
180 |
model=model_name,
|
181 |
messages=[{"role": "user", "content": prompt_text}],
|
182 |
temperature=0.4,
|
183 |
+
max_tokens=1024, # Adjust as needed
|
184 |
top_p=0.9,
|
185 |
)
|
186 |
the_response = response.choices[0].message.content
|
|
|
192 |
return error_message, trace_data_detail_hl7_analysis
|
193 |
|
194 |
|
195 |
+
def analyze_cda_xml_file_with_ai(cda_xml_file_path): # Modified to accept file path
|
196 |
"""Analyzes generic CDA or XML file content using Discharge Guard AI (more generalized version) Important: Use the API Directories fhir endpoints FROM THIS LINK: https://app.meldrx.com/api/directories/fhir/endpoints."""
|
197 |
try:
|
198 |
+
with open(
|
199 |
+
cda_xml_file_path.name, "r"
|
200 |
+
) as f: # Open file using path, access file through .name for Gradio UploadedFile
|
201 |
cda_xml_content = f.read()
|
202 |
prediction_response, trace_data_cda_xml_ai = analyze_cda_xml_content_ai(
|
203 |
cda_xml_content
|
|
|
214 |
except Exception as e:
|
215 |
return f"Error during XML/CDA file processing in analyze_cda_xml_file_with_ai: {e}"
|
216 |
|
217 |
+
|
218 |
+
def analyze_cda_xml_content_ai(cda_xml_content): # Copied from your code
|
219 |
"""Analyzes generic CDA or XML content using Discharge Guard AI (more generalized version)."""
|
220 |
|
221 |
prompt_text = f"""{system_instructions} \n\n Analyze the following medical XML/CDA content to provide a **structured and comprehensive patient data analysis**, similar to how a medical professional would review a patient's chart or a clinical document. You need to parse the XML structure yourself to extract the relevant information. Use bullet points, tables, or numbered steps for complex tasks. Provide a "Medical Document Analysis" report.
|
|
|
245 |
model=model_name,
|
246 |
messages=[{"role": "user", "content": prompt_text}],
|
247 |
temperature=0.4,
|
248 |
+
max_tokens=1024, # Adjust as needed
|
249 |
top_p=0.9,
|
250 |
)
|
251 |
the_response = response.choices[0].message.content
|
|
|
257 |
return error_message, trace_data_detail_cda_xml_analysis
|
258 |
|
259 |
|
260 |
+
def analyze_pdf_file_with_ai(pdf_file_path): # Modified to accept file path
|
261 |
"""Analyzes PDF file content using Discharge Guard AI."""
|
262 |
try:
|
263 |
+
with open(
|
264 |
+
pdf_file_path.name, "rb"
|
265 |
+
) as f: # Open file in binary mode for PdfReader, access file through .name for Gradio UploadedFile
|
266 |
+
pdf_file = f # Pass file object to PdfReader
|
267 |
pdf_reader = PdfReader(pdf_file)
|
268 |
text_content = ""
|
269 |
for page in pdf_reader.pages:
|
270 |
text_content += page.extract_text()
|
271 |
|
272 |
+
prediction_response, trace_data_pdf_ai = analyze_pdf_content_ai(
|
273 |
+
text_content
|
274 |
+
)
|
275 |
|
276 |
if prediction_response:
|
277 |
report = f"Discharge Guard AI Analysis of PDF Content:\n\nMedical Report Analysis Report:\n{prediction_response}\n\n**Disclaimer:** The Discharge Guard AGI-generated analysis is for conceptual informational purposes only and may or **NOT medical advice.** Analysis is based on PDF text content."
|
|
|
285 |
except Exception as e:
|
286 |
return f"Error during PDF file processing in analyze_pdf_file_with_ai: {e}"
|
287 |
|
288 |
+
|
289 |
+
def analyze_pdf_content_ai(pdf_text_content): # Copied from your code
|
290 |
"""Analyzes PDF text content using Discharge Guard AI."""
|
291 |
prompt_text = f"""{system_instructions} \n\n Analyze the following medical PDF text content to provide a **structured summary and identify key clinical information**. Focus on patient demographics, medical history, findings, diagnoses, medications, recommendations, and any important clinical details conveyed in the document. Provide a "Medical Report Analysis" report.
|
292 |
**Medical PDF Text Content:**
|
|
|
314 |
model=model_name,
|
315 |
messages=[{"role": "user", "content": prompt_text}],
|
316 |
temperature=0.4,
|
317 |
+
max_tokens=1024, # Adjust as needed
|
318 |
top_p=0.9,
|
319 |
)
|
320 |
the_response = response.choices[0].message.content
|
|
|
326 |
return error_message, trace_data_detail_pdf_analysis
|
327 |
|
328 |
|
329 |
+
def analyze_csv_file_with_ai(csv_file_path): # Modified to accept file path
|
330 |
"""Analyzes CSV file content using Discharge Guard AI."""
|
331 |
try:
|
332 |
+
csv_content = csv_file_path.read().decode(
|
333 |
+
"utf-8"
|
334 |
+
) # Read content directly from UploadedFile
|
335 |
prediction_response, trace_data_csv_ai = analyze_csv_content_ai(csv_content)
|
336 |
|
337 |
if prediction_response:
|
|
|
346 |
except Exception as e:
|
347 |
return f"Error during CSV file processing in analyze_csv_file_with_ai: {e}"
|
348 |
|
349 |
+
|
350 |
+
def analyze_csv_content_ai(csv_content_string): # Copied from your code
|
351 |
"""Analyzes CSV content (string) using Discharge Guard AI."""
|
352 |
prompt_text = f"""{system_instructions} \n\n Analyze the following medical CSV data to provide a **structured summary and identify potential clinical insights**. Assume the CSV represents patient-related medical data. Focus on understanding the columns, summarizing key data points, identifying trends or patterns, and noting any potential clinical significance of the data. Provide a "Data Analysis" report.
|
353 |
**Medical CSV Data:**
|
|
|
375 |
model=model_name,
|
376 |
messages=[{"role": "user", "content": prompt_text}],
|
377 |
temperature=0.4,
|
378 |
+
max_tokens=1024, # Adjust as needed
|
379 |
top_p=0.9,
|
380 |
)
|
381 |
the_response = response.choices[0].message.content
|
|
|
407 |
self.auth_code = code
|
408 |
if self.api.authenticate_with_code(code):
|
409 |
self.access_token = self.api.access_token
|
410 |
+
return (
|
411 |
+
f"<span style='color:#00FF7F;'>Authentication successful!</span> Access Token: {self.access_token[:10]}... (truncated)" # Neon Green Success
|
412 |
+
)
|
413 |
+
return "<span style='color:#FF4500;'>Authentication failed. Please check the code.</span>" # Neon Orange Error
|
414 |
|
415 |
def get_patient_data(self) -> str:
|
416 |
"""Fetch patient data from MeldRx"""
|
417 |
try:
|
418 |
if not self.access_token:
|
419 |
logger.warning("Not authenticated when getting patient data")
|
420 |
+
return "<span style='color:#FF8C00;'>Not authenticated. Please provide a valid authorization code first.</span>" # Neon Dark Orange
|
421 |
|
422 |
# For demo purposes, if there's no actual API connected, return mock data
|
423 |
# Remove this in production and use the real API call
|
424 |
+
if not hasattr(self.api, "get_patients") or self.api.get_patients is None:
|
425 |
logger.info("Using mock patient data (no API connection)")
|
426 |
# Return mock FHIR bundle with patient data
|
427 |
mock_data = {
|
|
|
438 |
{
|
439 |
"use": "official",
|
440 |
"family": "Smith",
|
441 |
+
"given": ["John"],
|
442 |
}
|
443 |
],
|
444 |
"gender": "male",
|
445 |
"birthDate": "1970-01-01",
|
446 |
"address": [
|
447 |
+
{"city": "Boston", "state": "MA", "postalCode": "02108"}
|
448 |
+
],
|
|
|
|
|
|
|
|
|
449 |
}
|
450 |
},
|
451 |
{
|
|
|
456 |
{
|
457 |
"use": "official",
|
458 |
"family": "Johnson",
|
459 |
+
"given": ["Jane"],
|
460 |
}
|
461 |
],
|
462 |
"gender": "female",
|
|
|
465 |
{
|
466 |
"city": "Cambridge",
|
467 |
"state": "MA",
|
468 |
+
"postalCode": "02139",
|
469 |
}
|
470 |
+
],
|
471 |
}
|
472 |
+
},
|
473 |
+
],
|
474 |
}
|
475 |
return json.dumps(mock_data, indent=2)
|
476 |
|
|
|
478 |
logger.info("Calling Meldrx API to get patients")
|
479 |
patients = self.api.get_patients()
|
480 |
if patients is not None:
|
481 |
+
return (
|
482 |
+
json.dumps(patients, indent=2)
|
483 |
+
if patients
|
484 |
+
else "<span style='color:#FFFF00;'>No patient data returned.</span>" # Neon Yellow
|
485 |
+
)
|
486 |
+
return "<span style='color:#DC143C;'>Failed to retrieve patient data.</span>" # Crimson Error
|
487 |
except Exception as e:
|
488 |
error_msg = f"Error in get_patient_data: {str(e)}"
|
489 |
logger.error(error_msg)
|
490 |
+
return f"<span style='color:#FF6347;'>Error retrieving patient data: {str(e)}</span> {str(e)}" # Tomato Error
|
491 |
+
|
492 |
|
493 |
def get_patient_documents(self, patient_id: str = None):
|
494 |
"""Fetch patient documents from MeldRx"""
|
495 |
if not self.access_token:
|
496 |
+
return "<span style='color:#FF8C00;'>Not authenticated. Please provide a valid authorization code first.</span>" # Neon Dark Orange
|
497 |
|
498 |
try:
|
499 |
# This would call the actual MeldRx API to get documents for a specific patient
|
|
|
512 |
"date": "2023-01-17",
|
513 |
"author": "Lab System",
|
514 |
"content": "CBC results: WBC 7.5, RBC 4.2, Hgb 14.1...",
|
515 |
+
},
|
516 |
]
|
517 |
except Exception as e:
|
518 |
+
return f"<span style='color:#FF6347;'>Error retrieving patient documents: {str(e)}</span>: {str(e)}" # Tomato Error
|
519 |
+
|
520 |
|
521 |
def display_form(
|
522 |
+
first_name,
|
523 |
+
last_name,
|
524 |
+
middle_initial,
|
525 |
+
dob,
|
526 |
+
age,
|
527 |
+
sex,
|
528 |
+
address,
|
529 |
+
city,
|
530 |
+
state,
|
531 |
+
zip_code,
|
532 |
+
doctor_first_name,
|
533 |
+
doctor_last_name,
|
534 |
+
doctor_middle_initial,
|
535 |
+
hospital_name,
|
536 |
+
doctor_address,
|
537 |
+
doctor_city,
|
538 |
+
doctor_state,
|
539 |
+
doctor_zip,
|
540 |
+
admission_date,
|
541 |
+
referral_source,
|
542 |
+
admission_method,
|
543 |
+
discharge_date,
|
544 |
+
discharge_reason,
|
545 |
+
date_of_death,
|
546 |
+
diagnosis,
|
547 |
+
procedures,
|
548 |
+
medications,
|
549 |
+
preparer_name,
|
550 |
+
preparer_job_title,
|
551 |
):
|
552 |
form = f"""
|
553 |
+
<div style='color:#00FFFF; font-family: monospace;'>
|
554 |
+
**Patient Discharge Form** <br>
|
555 |
+
- Name: {first_name} {middle_initial} {last_name} <br>
|
556 |
+
- Date of Birth: {dob}, Age: {age}, Sex: {sex} <br>
|
557 |
+
- Address: {address}, {city}, {state}, {zip_code} <br>
|
558 |
+
- Doctor: {doctor_first_name} {doctor_middle_initial} {doctor_last_name} <br>
|
559 |
+
- Hospital/Clinic: {hospital_name} <br>
|
560 |
+
- Doctor Address: {doctor_address}, {doctor_city}, {doctor_state}, {doctor_zip} <br>
|
561 |
+
- Admission Date: {admission_date}, Source: {referral_source}, Method: {admission_method} <br>
|
562 |
+
- Discharge Date: {discharge_date}, Reason: {discharge_reason} <br>
|
563 |
+
- Date of Death: {date_of_death} <br>
|
564 |
+
- Diagnosis: {diagnosis} <br>
|
565 |
+
- Procedures: {procedures} <br>
|
566 |
+
- Medications: {medications} <br>
|
567 |
- Prepared By: {preparer_name}, {preparer_job_title}
|
568 |
+
</div>
|
569 |
"""
|
570 |
return form
|
571 |
|
572 |
+
|
573 |
def generate_pdf_from_form(
|
574 |
+
first_name,
|
575 |
+
last_name,
|
576 |
+
middle_initial,
|
577 |
+
dob,
|
578 |
+
age,
|
579 |
+
sex,
|
580 |
+
address,
|
581 |
+
city,
|
582 |
+
state,
|
583 |
+
zip_code,
|
584 |
+
doctor_first_name,
|
585 |
+
doctor_last_name,
|
586 |
+
doctor_middle_initial,
|
587 |
+
hospital_name,
|
588 |
+
doctor_address,
|
589 |
+
doctor_city,
|
590 |
+
doctor_state,
|
591 |
+
doctor_zip,
|
592 |
+
admission_date,
|
593 |
+
referral_source,
|
594 |
+
admission_method,
|
595 |
+
discharge_date,
|
596 |
+
discharge_reason,
|
597 |
+
date_of_death,
|
598 |
+
diagnosis,
|
599 |
+
procedures,
|
600 |
+
medications,
|
601 |
+
preparer_name,
|
602 |
+
preparer_job_title,
|
603 |
):
|
604 |
"""Generate a PDF discharge form using the provided data"""
|
605 |
|
|
|
617 |
"address": address,
|
618 |
"city": city,
|
619 |
"state": state,
|
620 |
+
"zip": zip_code,
|
621 |
}
|
622 |
|
623 |
discharge_info = {
|
|
|
625 |
"date_of_discharge": discharge_date,
|
626 |
"source_of_admission": referral_source,
|
627 |
"mode_of_admission": admission_method,
|
628 |
+
"discharge_against_advice": "Yes"
|
629 |
+
if discharge_reason == "Discharge Against Advice"
|
630 |
+
else "No",
|
631 |
}
|
632 |
|
633 |
diagnosis_info = {
|
634 |
"diagnosis": diagnosis,
|
635 |
"operation_procedure": procedures,
|
636 |
"treatment": "", # Not collected in the form
|
637 |
+
"follow_up": "", # Not collected in the form
|
638 |
}
|
639 |
|
640 |
medication_info = {
|
641 |
"medications": [medications] if medications else [],
|
642 |
+
"instructions": "", # Not collected in the form
|
643 |
}
|
644 |
|
645 |
prepared_by = {
|
646 |
"name": preparer_name,
|
647 |
"title": preparer_job_title,
|
648 |
+
"signature": "", # Not collected in the form
|
649 |
}
|
650 |
|
651 |
# Generate PDF
|
|
|
654 |
discharge_info,
|
655 |
diagnosis_info,
|
656 |
medication_info,
|
657 |
+
prepared_by,
|
658 |
)
|
659 |
|
660 |
# Create temporary file to save the PDF
|
661 |
+
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".pdf")
|
662 |
temp_file.write(pdf_buffer.read())
|
663 |
temp_file_path = temp_file.name
|
664 |
temp_file.close()
|
665 |
|
666 |
return temp_file_path
|
667 |
|
668 |
+
|
669 |
def generate_pdf_from_meldrx(patient_data):
|
670 |
"""Generate a PDF using patient data from MeldRx"""
|
671 |
if isinstance(patient_data, str):
|
|
|
688 |
# Extract patient info
|
689 |
patient_info = {
|
690 |
"name": f"{patient.get('name', {}).get('given', [''])[0]} {patient.get('name', {}).get('family', '')}",
|
691 |
+
"dob": patient.get("birthDate", "Unknown"),
|
692 |
+
"patient_id": patient.get("id", "Unknown"),
|
693 |
"admission_date": datetime.now().strftime("%Y-%m-%d"), # Mock data
|
694 |
+
"physician": "Dr. Provider", # Mock data
|
695 |
}
|
696 |
|
697 |
# Mock LLM-generated content - This part needs to be replaced with actual AI generation if desired for MeldRx PDF
|
|
|
700 |
"treatment": "Treatment summary would be generated by AI based on patient data from MeldRx.",
|
701 |
"medications": "Medication list would be generated by AI based on patient data from MeldRx.",
|
702 |
"follow_up": "Follow-up instructions would be generated by AI based on patient data from MeldRx.",
|
703 |
+
"special_instructions": "Special instructions would be generated by AI based on patient data from MeldRx.",
|
704 |
}
|
705 |
|
706 |
# Create discharge summary - Using No-AI PDF generation for now, replace with AI-content generation later
|
707 |
output_dir = tempfile.mkdtemp()
|
708 |
+
pdf_path = generate_discharge_summary(
|
709 |
+
patient_info, llm_content, output_dir
|
710 |
+
) # Still using No-AI template
|
711 |
|
712 |
+
return pdf_path, "PDF generated successfully (No AI Content in PDF yet)" # Indicate No-AI content
|
713 |
|
714 |
except Exception as e:
|
715 |
return None, f"Error generating PDF: {str(e)}"
|
716 |
|
717 |
+
|
718 |
def generate_discharge_paper_one_click():
|
719 |
"""One-click function to fetch patient data and generate discharge paper with AI Content."""
|
720 |
patient_data_str = CALLBACK_MANAGER.get_patient_data()
|
721 |
+
if (
|
722 |
+
patient_data_str.startswith("Not authenticated")
|
723 |
+
or patient_data_str.startswith("Failed")
|
724 |
+
or patient_data_str.startswith("Error")
|
725 |
+
):
|
726 |
+
return None, patient_data_str # Return error message if authentication or data fetch fails
|
727 |
|
728 |
try:
|
729 |
patient_data = json.loads(patient_data_str)
|
730 |
|
731 |
# --- AI Content Generation for Discharge Summary ---
|
732 |
# This is a placeholder - Replace with actual AI call using InferenceClient and patient_data to generate content
|
733 |
+
ai_generated_content = generate_ai_discharge_content(
|
734 |
+
patient_data
|
735 |
+
) # Placeholder AI function
|
736 |
|
737 |
if not ai_generated_content:
|
738 |
return None, "Error: AI content generation failed."
|
739 |
|
740 |
# --- PDF Generation with AI Content ---
|
741 |
+
pdf_path, status_message = generate_pdf_from_meldrx_with_ai_content(
|
742 |
+
patient_data, ai_generated_content
|
743 |
+
) # Function to generate PDF with AI content
|
744 |
|
745 |
if pdf_path:
|
746 |
return pdf_path, status_message
|
747 |
else:
|
748 |
+
return None, status_message # Return status message if PDF generation fails
|
749 |
|
750 |
except json.JSONDecodeError:
|
751 |
return None, "Error: Patient data is not in valid JSON format."
|
752 |
except Exception as e:
|
753 |
return None, f"Error during discharge paper generation: {str(e)}"
|
754 |
|
755 |
+
|
756 |
def generate_ai_discharge_content(patient_data):
|
757 |
"""Placeholder function to generate AI content for discharge summary.
|
758 |
+
Replace this with actual AI call using InferenceClient and patient_data."""
|
759 |
try:
|
760 |
+
patient_name = (
|
761 |
+
f"{patient_data['entry'][0]['resource']['name'][0]['given'][0]} {patient_data['entry'][0]['resource']['name'][0]['family']}"
|
762 |
+
if patient_data.get("entry")
|
763 |
+
else "Unknown Patient"
|
764 |
+
)
|
765 |
prompt_text = f"""{system_instructions}\n\nGenerate a discharge summary content (diagnosis, treatment, medications, follow-up instructions, special instructions) for patient: {patient_name}. Base the content on available patient data (if any provided, currently not provided in detail in this mock-up). Focus on creating clinically relevant and informative summary. Remember this is for informational purposes and NOT medical advice."""
|
766 |
|
767 |
response = client.chat.completions.create(
|
768 |
model=model_name,
|
769 |
messages=[{"role": "user", "content": prompt_text}],
|
770 |
+
temperature=0.6, # Adjust temperature as needed for content generation
|
771 |
+
max_tokens=1024, # Adjust max_tokens as needed
|
772 |
top_p=0.9,
|
773 |
)
|
774 |
ai_content = response.choices[0].message.content
|
775 |
|
776 |
# Basic parsing of AI content - improve this based on desired output structure from LLM
|
777 |
llm_content = {
|
778 |
+
"diagnosis": "AI Generated Diagnosis (Placeholder):\n"
|
779 |
+
+ extract_section(ai_content, "Diagnosis"), # Example extraction - refine based on LLM output
|
780 |
+
"treatment": "AI Generated Treatment (Placeholder):\n"
|
781 |
+
+ extract_section(ai_content, "Treatment"),
|
782 |
+
"medications": "AI Generated Medications (Placeholder):\n"
|
783 |
+
+ extract_section(ai_content, "Medications"),
|
784 |
+
"follow_up": "AI Generated Follow-up (Placeholder):\n"
|
785 |
+
+ extract_section(ai_content, "Follow-up Instructions"),
|
786 |
+
"special_instructions": "AI Generated Special Instructions (Placeholder):\n"
|
787 |
+
+ extract_section(ai_content, "Special Instructions"),
|
788 |
}
|
789 |
return llm_content
|
790 |
|
|
|
792 |
logger.error(f"Error generating AI discharge content: {e}")
|
793 |
return None
|
794 |
|
795 |
+
|
796 |
def extract_section(ai_content, section_title):
|
797 |
"""Simple placeholder function to extract section from AI content.
|
798 |
+
Improve this with more robust parsing based on LLM output format."""
|
799 |
start_marker = f"**{section_title}:**"
|
800 |
+
end_marker = "\n\n" # Adjust based on typical LLM output structure
|
801 |
start_index = ai_content.find(start_marker)
|
802 |
if start_index != -1:
|
803 |
start_index += len(start_marker)
|
|
|
826 |
|
827 |
patient_info = {
|
828 |
"name": f"{patient.get('name', {}).get('given', [''])[0]} {patient.get('name', {}).get('family', '')}",
|
829 |
+
"dob": patient.get("birthDate", "Unknown"),
|
830 |
+
"patient_id": patient.get("id", "Unknown"),
|
831 |
"admission_date": datetime.now().strftime("%Y-%m-%d"), # Mock data
|
832 |
+
"physician": "Dr. AI Provider", # Mock data - Indicate AI generated
|
833 |
}
|
834 |
|
|
|
835 |
output_dir = tempfile.mkdtemp()
|
836 |
+
pdf_path = generate_discharge_summary(
|
837 |
+
patient_info, llm_content, output_dir
|
838 |
+
) # Using AI content now
|
839 |
|
840 |
+
return pdf_path, "PDF generated successfully with AI Content" # Indicate AI content
|
841 |
|
842 |
except Exception as e:
|
843 |
return None, f"Error generating PDF with AI content: {str(e)}"
|
844 |
|
845 |
+
|
846 |
def extract_auth_code_from_url(redirected_url):
|
847 |
"""Extracts the authorization code from the redirected URL."""
|
848 |
try:
|
849 |
parsed_url = urlparse(redirected_url)
|
850 |
query_params = parse_qs(parsed_url.query)
|
851 |
if "code" in query_params:
|
852 |
+
return query_params["code"][0], None # Return code and no error
|
853 |
else:
|
854 |
+
return None, "Authorization code not found in URL." # Return None and error message
|
855 |
except Exception as e:
|
856 |
+
return None, f"Error parsing URL: {e}" # Return None and error message
|
857 |
+
|
858 |
|
859 |
# Create a simplified interface to avoid complex component interactions
|
860 |
CALLBACK_MANAGER = CallbackManager(
|
861 |
redirect_uri="https://multitransformer-discharge-guard.hf.space/callback",
|
862 |
+
client_secret=None,
|
863 |
+
)
|
864 |
+
|
865 |
+
# Define the cyberpunk theme - using a dark base and neon accents
|
866 |
+
cyberpunk_theme = gr.themes.Monochrome(
|
867 |
+
primary_hue="cyan",
|
868 |
+
secondary_hue="pink",
|
869 |
+
neutral_hue="slate",
|
870 |
+
font=["Source Code Pro", "monospace"], # Retro monospace font
|
871 |
+
font_mono=["Source Code Pro", "monospace"]
|
872 |
)
|
873 |
|
874 |
+
# Create the UI with the cyberpunk theme
|
875 |
+
with gr.Blocks(theme=cyberpunk_theme) as demo: # Apply the theme here
|
876 |
+
gr.Markdown("<h1 style='color:#00FFFF; text-shadow: 0 0 5px #00FFFF;'>Discharge Guard <span style='color:#FF00FF; text-shadow: 0 0 5px #FF00FF;'>Cyber</span></h1>") # Cyberpunk Title
|
877 |
|
878 |
+
with gr.Tab("Authenticate with MeldRx", elem_classes="cyberpunk-tab"): # Optional: Class for tab styling
|
879 |
+
gr.Markdown("<h2 style='color:#00FFFF; text-shadow: 0 0 3px #00FFFF;'>SMART on FHIR Authentication</h2>") # Neon Tab Header
|
880 |
auth_url_output = gr.Textbox(label="Authorization URL", value=CALLBACK_MANAGER.get_auth_url(), interactive=False)
|
881 |
+
gr.Markdown("<p style='color:#A9A9A9;'>Copy the URL above, open it in a browser, log in, and paste the <span style='color:#00FFFF;'>entire redirected URL</span> from your browser's address bar below.</p>") # Subdued instructions with neon highlight
|
882 |
redirected_url_input = gr.Textbox(label="Redirected URL") # New textbox for redirected URL
|
883 |
+
extract_code_button = gr.Button("Extract Authorization Code", elem_classes="cyberpunk-button") # Cyberpunk button style
|
884 |
extracted_code_output = gr.Textbox(label="Extracted Authorization Code", interactive=False) # Textbox to show extracted code
|
885 |
|
886 |
auth_code_input = gr.Textbox(label="Authorization Code (from above, or paste manually if extraction fails)", interactive=True) # Updated label to be clearer
|
887 |
+
auth_submit = gr.Button("Submit Code for Authentication", elem_classes="cyberpunk-button") # Cyberpunk button style
|
888 |
+
auth_result = gr.HTML(label="Authentication Result") # Use HTML for styled result
|
889 |
|
890 |
+
patient_data_button = gr.Button("Fetch Patient Data", elem_classes="cyberpunk-button") # Cyberpunk button style
|
891 |
patient_data_output = gr.Textbox(label="Patient Data", lines=10)
|
892 |
|
893 |
# Add button to generate PDF from MeldRx data (No AI)
|
894 |
+
meldrx_pdf_button = gr.Button("Generate PDF from MeldRx Data (No AI)", elem_classes="cyberpunk-button") # Renamed button
|
895 |
meldrx_pdf_status = gr.Textbox(label="PDF Generation Status (No AI)") # Renamed status
|
896 |
meldrx_pdf_download = gr.File(label="Download Generated PDF (No AI)") # Renamed download
|
897 |
|
|
|
899 |
"""Processes the redirected URL to extract and display the authorization code."""
|
900 |
auth_code, error_message = extract_auth_code_from_url(redirected_url)
|
901 |
if auth_code:
|
902 |
+
return auth_code, "<span style='color:#00FF7F;'>Authorization code extracted!</span>" # Neon Green Success
|
903 |
else:
|
904 |
+
return "", f"<span style='color:#FF4500;'>Could not extract authorization code.</span> {error_message or ''}" # Neon Orange Error
|
905 |
|
906 |
|
907 |
extract_code_button.click(
|
908 |
fn=process_redirected_url,
|
909 |
inputs=redirected_url_input,
|
910 |
+
outputs=[extracted_code_output, auth_result] # Reusing auth_result for extraction status
|
911 |
)
|
912 |
|
|
|
913 |
auth_submit.click(
|
914 |
fn=CALLBACK_MANAGER.set_auth_code,
|
915 |
+
inputs=extracted_code_output, # Using extracted code as input for authentication
|
916 |
+
outputs=auth_result,
|
917 |
)
|
918 |
|
919 |
+
with gr.Tab("Patient Dashboard", elem_classes="cyberpunk-tab"): # Optional: Class for tab styling
|
920 |
+
gr.Markdown("<h2 style='color:#00FFFF; text-shadow: 0 0 3px #00FFFF;'>Patient Data</h2>") # Neon Tab Header
|
921 |
+
dashboard_output = gr.HTML("<p style='color:#A9A9A9;'>Fetch patient data from the Authentication tab first.</p>") # Subdued placeholder text
|
922 |
|
923 |
+
refresh_btn = gr.Button("Refresh Data", elem_classes="cyberpunk-button") # Cyberpunk button style
|
|
|
|
|
|
|
|
|
924 |
|
925 |
# Simple function to update dashboard based on fetched data
|
926 |
def update_dashboard():
|
927 |
try:
|
928 |
data = CALLBACK_MANAGER.get_patient_data()
|
929 |
+
if (
|
930 |
+
data.startswith("<span style='color:#FF8C00;'>Not authenticated")
|
931 |
+
or data.startswith("<span style='color:#DC143C;'>Failed")
|
932 |
+
or data.startswith("<span style='color:#FF6347;'>Error")
|
933 |
+
):
|
934 |
+
return f"<p style='color:#FF8C00;'>{data}</p>" # Show auth errors in orange
|
935 |
|
936 |
try:
|
937 |
# Parse the data
|
|
|
945 |
patients.append(resource)
|
946 |
|
947 |
# Generate HTML card
|
948 |
+
html = "<h3 style='color:#00FFFF; text-shadow: 0 0 2px #00FFFF;'>Patients</h3>" # Neon Sub-header
|
949 |
for patient in patients:
|
950 |
# Extract name
|
951 |
name = patient.get("name", [{}])[0]
|
|
|
956 |
gender = patient.get("gender", "unknown").capitalize()
|
957 |
birth_date = patient.get("birthDate", "Unknown")
|
958 |
|
959 |
+
# Generate HTML card with cyberpunk styling
|
960 |
html += f"""
|
961 |
+
<div style="border: 1px solid #00FFFF; padding: 10px; margin: 10px 0; border-radius: 5px; background-color: #222; box-shadow: 0 0 5px #00FFFF;">
|
962 |
+
<h4 style='color:#00FFFF;'>{given} {family}</h4>
|
963 |
+
<p style='color:#A9A9A9;'><strong>Gender:</strong> <span style='color:#00FFFF;'>{gender}</span></p>
|
964 |
+
<p style='color:#A9A9A9;'><strong>Birth Date:</strong> <span style='color:#00FFFF;'>{birth_date}</span></p>
|
965 |
+
<p style='color:#A9A9A9;'><strong>ID:</strong> <span style='color:#00FFFF;'>{patient.get("id", "Unknown")}</span></p>
|
966 |
</div>
|
967 |
"""
|
968 |
|
969 |
return html
|
970 |
except Exception as e:
|
971 |
+
return f"<p style='color:#FF6347;'>Error parsing patient data: {str(e)}</p>" # Tomato Error
|
972 |
except Exception as e:
|
973 |
+
return f"<p style='color:#FF6347;'>Error fetching patient data: {str(e)}</p>" # Tomato Error
|
974 |
|
975 |
+
|
976 |
+
with gr.Tab("Discharge Form", elem_classes="cyberpunk-tab"): # Optional: Class for tab styling
|
977 |
+
gr.Markdown("<h2 style='color:#00FFFF; text-shadow: 0 0 3px #00FFFF;'>Patient Details</h2>") # Neon Tab Header
|
978 |
with gr.Row():
|
979 |
first_name = gr.Textbox(label="First Name")
|
980 |
last_name = gr.Textbox(label="Last Name")
|
|
|
988 |
city = gr.Textbox(label="City")
|
989 |
state = gr.Textbox(label="State")
|
990 |
zip_code = gr.Textbox(label="Zip Code")
|
991 |
+
gr.Markdown("<h2 style='color:#00FFFF; text-shadow: 0 0 3px #00FFFF;'>Primary Healthcare Professional Details</h2>") # Neon Sub-header
|
992 |
with gr.Row():
|
993 |
doctor_first_name = gr.Textbox(label="Doctor's First Name")
|
994 |
doctor_last_name = gr.Textbox(label="Doctor's Last Name")
|
995 |
+
doctor_middle_initial = gr.Textbox(label="Doctor's Middle Initial")
|
996 |
hospital_name = gr.Textbox(label="Hospital/Clinic Name")
|
997 |
doctor_address = gr.Textbox(label="Address")
|
998 |
with gr.Row():
|
999 |
doctor_city = gr.Textbox(label="City")
|
1000 |
doctor_state = gr.Textbox(label="State")
|
1001 |
doctor_zip = gr.Textbox(label="Zip Code")
|
1002 |
+
gr.Markdown("<h2 style='color:#00FFFF; text-shadow: 0 0 3px #00FFFF;'>Admission and Discharge Details</h2>") # Neon Sub-header
|
1003 |
with gr.Row():
|
1004 |
admission_date = gr.Textbox(label="Date of Admission")
|
1005 |
referral_source = gr.Textbox(label="Source of Referral")
|
1006 |
admission_method = gr.Textbox(label="Method of Admission")
|
1007 |
with gr.Row():
|
1008 |
discharge_date = gr.Textbox(label="Date of Discharge")
|
1009 |
+
discharge_reason = gr.Radio(
|
1010 |
+
["Treated", "Transferred", "Discharge Against Advice", "Patient Died"],
|
1011 |
+
label="Discharge Reason",
|
1012 |
+
)
|
1013 |
date_of_death = gr.Textbox(label="Date of Death (if applicable)")
|
1014 |
+
gr.Markdown("<h2 style='color:#00FFFF; text-shadow: 0 0 3px #00FFFF;'>Diagnosis & Procedures</h2>") # Neon Sub-header
|
1015 |
diagnosis = gr.Textbox(label="Diagnosis")
|
1016 |
procedures = gr.Textbox(label="Operation & Procedures")
|
1017 |
+
gr.Markdown("<h2 style='color:#00FFFF; text-shadow: 0 0 3px #00FFFF;'>Medication Details</h2>") # Neon Sub-header
|
1018 |
medications = gr.Textbox(label="Medication on Discharge")
|
1019 |
+
gr.Markdown("<h2 style='color:#00FFFF; text-shadow: 0 0 3px #00FFFF;'>Prepared By</h2>") # Neon Sub-header
|
1020 |
with gr.Row():
|
1021 |
preparer_name = gr.Textbox(label="Name")
|
1022 |
preparer_job_title = gr.Textbox(label="Job Title")
|
1023 |
|
1024 |
# Add buttons for both display form and generate PDF
|
1025 |
with gr.Row():
|
1026 |
+
submit_display = gr.Button("Display Form", elem_classes="cyberpunk-button") # Cyberpunk button style
|
1027 |
+
submit_pdf = gr.Button("Generate PDF (No AI)", elem_classes="cyberpunk-button") # Renamed button to clarify no AI and styled
|
1028 |
|
1029 |
# Output areas
|
1030 |
+
form_output = gr.HTML() # Use HTML to render styled form
|
1031 |
+
pdf_output = gr.File(label="Download PDF (No AI)") # Renamed output to clarify no AI
|
1032 |
|
1033 |
# Connect the display form button
|
1034 |
submit_display.click(
|
1035 |
display_form,
|
1036 |
inputs=[
|
1037 |
+
first_name,
|
1038 |
+
last_name,
|
1039 |
+
middle_initial,
|
1040 |
+
dob,
|
1041 |
+
age,
|
1042 |
+
sex,
|
1043 |
+
address,
|
1044 |
+
city,
|
1045 |
+
state,
|
1046 |
+
zip_code,
|
1047 |
+
doctor_first_name,
|
1048 |
+
doctor_last_name,
|
1049 |
+
doctor_middle_initial,
|
1050 |
+
hospital_name,
|
1051 |
+
doctor_address,
|
1052 |
+
doctor_city,
|
1053 |
+
doctor_state,
|
1054 |
+
doctor_zip,
|
1055 |
+
admission_date,
|
1056 |
+
referral_source,
|
1057 |
+
admission_method,
|
1058 |
+
discharge_date,
|
1059 |
+
discharge_reason,
|
1060 |
+
date_of_death,
|
1061 |
+
diagnosis,
|
1062 |
+
procedures,
|
1063 |
+
medications,
|
1064 |
+
preparer_name,
|
1065 |
+
preparer_job_title,
|
1066 |
],
|
1067 |
+
outputs=form_output,
|
1068 |
)
|
1069 |
|
1070 |
# Connect the generate PDF button (No AI version)
|
1071 |
submit_pdf.click(
|
1072 |
generate_pdf_from_form,
|
1073 |
inputs=[
|
1074 |
+
first_name,
|
1075 |
+
last_name,
|
1076 |
+
middle_initial,
|
1077 |
+
dob,
|
1078 |
+
age,
|
1079 |
+
sex,
|
1080 |
+
address,
|
1081 |
+
city,
|
1082 |
+
state,
|
1083 |
+
zip_code,
|
1084 |
+
doctor_first_name,
|
1085 |
+
doctor_last_name,
|
1086 |
+
doctor_middle_initial,
|
1087 |
+
hospital_name,
|
1088 |
+
doctor_address,
|
1089 |
+
doctor_city,
|
1090 |
+
doctor_state,
|
1091 |
+
doctor_zip,
|
1092 |
+
admission_date,
|
1093 |
+
referral_source,
|
1094 |
+
admission_method,
|
1095 |
+
discharge_date,
|
1096 |
+
discharge_reason,
|
1097 |
+
date_of_death,
|
1098 |
+
diagnosis,
|
1099 |
+
procedures,
|
1100 |
+
medications,
|
1101 |
+
preparer_name,
|
1102 |
+
preparer_job_title,
|
1103 |
],
|
1104 |
+
outputs=pdf_output,
|
1105 |
)
|
1106 |
|
1107 |
+
with gr.Tab("Medical File Analysis", elem_classes="cyberpunk-tab"): # Optional: Class for tab styling
|
1108 |
+
gr.Markdown("<h2 style='color:#00FFFF; text-shadow: 0 0 3px #00FFFF;'>Analyze Medical Files with Discharge Guard AI</h2>") # Neon Tab Header
|
1109 |
with gr.Column():
|
1110 |
+
dicom_file = gr.File(
|
1111 |
+
file_types=[".dcm"], label="Upload DICOM File (.dcm)"
|
1112 |
+
)
|
1113 |
dicom_ai_output = gr.Textbox(label="DICOM Analysis Report", lines=5)
|
1114 |
+
analyze_dicom_button = gr.Button("Analyze DICOM with AI", elem_classes="cyberpunk-button") # Cyberpunk button style
|
1115 |
|
1116 |
+
hl7_file = gr.File(
|
1117 |
+
file_types=[".hl7"], label="Upload HL7 File (.hl7)"
|
1118 |
+
)
|
1119 |
hl7_ai_output = gr.Textbox(label="HL7 Analysis Report", lines=5)
|
1120 |
+
analyze_hl7_button = gr.Button("Analyze HL7 with AI", elem_classes="cyberpunk-button") # Cyberpunk button style
|
1121 |
|
1122 |
+
xml_file = gr.File(
|
1123 |
+
file_types=[".xml"], label="Upload XML File (.xml)"
|
1124 |
+
)
|
1125 |
xml_ai_output = gr.Textbox(label="XML Analysis Report", lines=5)
|
1126 |
+
analyze_xml_button = gr.Button("Analyze XML with AI", elem_classes="cyberpunk-button") # Cyberpunk button style
|
1127 |
|
1128 |
+
ccda_file = gr.File(
|
1129 |
+
file_types=[".xml", ".cda", ".ccd"], label="Upload CCDA File (.xml, .cda, .ccd)"
|
1130 |
+
)
|
1131 |
ccda_ai_output = gr.Textbox(label="CCDA Analysis Report", lines=5)
|
1132 |
+
analyze_ccda_button = gr.Button("Analyze CCDA with AI", elem_classes="cyberpunk-button") # Cyberpunk button style
|
1133 |
+
|
1134 |
+
ccd_file = gr.File(
|
1135 |
+
file_types=[".ccd"],
|
1136 |
+
label="Upload CCD File (.ccd)",
|
1137 |
+
) # Redundant, as CCDA also handles .ccd, but kept for clarity
|
1138 |
+
ccd_ai_output = gr.Textbox(
|
1139 |
+
label="CCD Analysis Report", lines=5
|
1140 |
+
) # Redundant
|
1141 |
+
analyze_ccd_button = gr.Button("Analyze CCD with AI", elem_classes="cyberpunk-button") # Cyberpunk button style # Redundant
|
1142 |
+
pdf_file = gr.File(
|
1143 |
+
file_types=[".pdf"], label="Upload PDF File (.pdf)"
|
1144 |
+
)
|
1145 |
pdf_ai_output = gr.Textbox(label="PDF Analysis Report", lines=5)
|
1146 |
+
analyze_pdf_button = gr.Button("Analyze PDF with AI", elem_classes="cyberpunk-button") # Cyberpunk button style
|
1147 |
|
1148 |
+
csv_file = gr.File(
|
1149 |
+
file_types=[".csv"], label="Upload CSV File (.csv)"
|
1150 |
+
)
|
1151 |
csv_ai_output = gr.Textbox(label="CSV Analysis Report", lines=5)
|
1152 |
+
analyze_csv_button = gr.Button("Analyze CSV with AI", elem_classes="cyberpunk-button") # Cyberpunk button style
|
|
|
1153 |
|
1154 |
# Connect AI Analysis Buttons - using REAL AI functions now
|
1155 |
analyze_dicom_button.click(
|
1156 |
+
analyze_dicom_file_with_ai, # Call REAL AI function
|
1157 |
+
inputs=dicom_file,
|
1158 |
+
outputs=dicom_ai_output,
|
1159 |
)
|
1160 |
analyze_hl7_button.click(
|
1161 |
+
analyze_hl7_file_with_ai, # Call REAL AI function
|
1162 |
+
inputs=hl7_file,
|
1163 |
+
outputs=hl7_ai_output,
|
1164 |
)
|
1165 |
analyze_xml_button.click(
|
1166 |
+
analyze_cda_xml_file_with_ai, # Call REAL AI function
|
1167 |
+
inputs=xml_file,
|
1168 |
+
outputs=xml_ai_output,
|
1169 |
)
|
1170 |
analyze_ccda_button.click(
|
1171 |
+
analyze_cda_xml_file_with_ai, # Call REAL AI function
|
1172 |
+
inputs=ccda_file,
|
1173 |
+
outputs=ccda_ai_output,
|
1174 |
)
|
1175 |
+
analyze_ccd_button.click( # Redundant button, but kept for UI if needed
|
1176 |
+
analyze_cda_xml_file_with_ai, # Call REAL AI function
|
1177 |
+
inputs=ccd_file,
|
1178 |
+
outputs=ccd_ai_output,
|
1179 |
)
|
1180 |
analyze_pdf_button.click(
|
1181 |
+
analyze_pdf_file_with_ai, inputs=pdf_file, outputs=pdf_ai_output
|
|
|
|
|
1182 |
)
|
1183 |
analyze_csv_button.click(
|
1184 |
+
analyze_csv_file_with_ai, inputs=csv_file, outputs=csv_ai_output
|
|
|
|
|
1185 |
)
|
1186 |
|
1187 |
+
with gr.Tab(
|
1188 |
+
"One-Click Discharge Paper (AI)", elem_classes="cyberpunk-tab"
|
1189 |
+
): # New Tab for One-Click Discharge Paper with AI, styled
|
1190 |
+
gr.Markdown("<h2 style='color:#00FFFF; text-shadow: 0 0 3px #00FFFF;'>One-Click Medical Discharge Paper Generation with AI Content</h2>") # Neon Tab Header
|
1191 |
+
one_click_ai_pdf_button = gr.Button(
|
1192 |
+
"Generate Discharge Paper with AI (One-Click)", elem_classes="cyberpunk-button"
|
1193 |
+
) # Updated button label and styled
|
1194 |
+
one_click_ai_pdf_status = gr.Textbox(
|
1195 |
+
label="Discharge Paper Generation Status (AI)"
|
1196 |
+
) # Updated status label
|
1197 |
+
one_click_ai_pdf_download = gr.File(
|
1198 |
+
label="Download Discharge Paper (AI)"
|
1199 |
+
) # Updated download label
|
1200 |
|
1201 |
one_click_ai_pdf_button.click(
|
1202 |
+
generate_discharge_paper_one_click, # Use the one-click function that now calls AI
|
1203 |
inputs=[],
|
1204 |
+
outputs=[one_click_ai_pdf_download, one_click_ai_pdf_status],
|
1205 |
)
|
1206 |
|
|
|
1207 |
# Connect the patient data buttons
|
1208 |
patient_data_button.click(
|
1209 |
fn=CALLBACK_MANAGER.get_patient_data,
|
1210 |
inputs=None,
|
1211 |
+
outputs=patient_data_output,
|
1212 |
)
|
1213 |
|
1214 |
# Connect refresh button to update dashboard
|
1215 |
refresh_btn.click(
|
1216 |
+
fn=update_dashboard, inputs=None, outputs=dashboard_output
|
|
|
|
|
1217 |
)
|
1218 |
|
1219 |
# Corrected the button click function name here to `generate_pdf_from_meldrx` (No AI PDF)
|
1220 |
meldrx_pdf_button.click(
|
1221 |
fn=generate_pdf_from_meldrx,
|
1222 |
inputs=patient_data_output,
|
1223 |
+
outputs=[meldrx_pdf_download, meldrx_pdf_status],
|
1224 |
)
|
1225 |
|
1226 |
# Connect patient data updates to dashboard
|
1227 |
patient_data_button.click(
|
1228 |
+
fn=update_dashboard, inputs=None, outputs=dashboard_output
|
|
|
|
|
1229 |
)
|
1230 |
|
1231 |
# Launch with sharing enabled for public access
|