import gradio as gr
from utils.meldrx import MeldRxAPI
import json
import os
import tempfile
from datetime import datetime
import traceback
import logging
from huggingface_hub import InferenceClient # Import InferenceClient
from urllib.parse import urlparse, parse_qs # Import URL parsing utilities
from utils.callbackmanager import CallbackManager
from prompts import system_instructions
# Set up logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Import PDF utilities
from pdfutils import PDFGenerator, generate_discharge_summary
# Import necessary libraries for new file types and AI analysis functions
import pydicom # For DICOM
import hl7 # For HL7
from xml.etree import ElementTree # For XML and CCDA
from pypdf import PdfReader # For PDF
import csv # For CSV
import io # For IO operations
from PIL import Image # For image handling
from utils.generators import extract_auth_code_from_url, generate_pdf_from_meldrx, generate_ai_discharge_content, generate_pdf_from_meldrx_with_ai_content, extract_section, generate_discharge_paper_one_click, generate_pdf_from_form, generate_discharge_summary, generate_ai_discharge_content, analyze_dicom_file_with_ai, analyze_hl7_file_with_ai, analyze_cda_xml_file_with_ai, analyze_pdf_file_with_ai, analyze_csv_file_with_ai, generate_pdf_from_form , generate_discharge_paper_one_click , generate_ai_discharge_content , extract_section , generate_pdf_from_meldrx_with_ai_content
# Initialize Inference Client - Ensure YOUR_HF_TOKEN is set in environment variables or replace with your actual token
HF_TOKEN = os.getenv("HF_TOKEN") # Or replace with your actual token string
if not HF_TOKEN:
raise ValueError(
"HF_TOKEN environment variable not set. Please set your Hugging Face API token."
)
client = InferenceClient(api_key=HF_TOKEN)
model_name = "meta-llama/Llama-3.3-70B-Instruct" # Specify the model to use
def display_form(first_name, last_name, middle_initial, dob, age, sex, address, city, state, zip_code, doctor_first_name, doctor_last_name, doctor_middle_initial, hospital_name, doctor_address, doctor_city, doctor_state, doctor_zip, admission_date, referral_source, admission_method, discharge_date, discharge_reason, date_of_death, diagnosis, procedures, medications, preparer_name, preparer_job_title,):
form = f"""
**Patient Discharge Form**
- Name: {first_name} {middle_initial} {last_name}
- Date of Birth: {dob}, Age: {age}, Sex: {sex}
- Address: {address}, {city}, {state}, {zip_code}
- Doctor: {doctor_first_name} {doctor_middle_initial} {doctor_last_name}
- Hospital/Clinic: {hospital_name}
- Doctor Address: {doctor_address}, {doctor_city}, {doctor_state}, {doctor_zip}
- Admission Date: {admission_date}, Source: {referral_source}, Method: {admission_method}
- Discharge Date: {discharge_date}, Reason: {discharge_reason}
- Date of Death: {date_of_death}
- Diagnosis: {diagnosis}
- Procedures: {procedures}
- Medications: {medications}
- Prepared By: {preparer_name}, {preparer_job_title}
"""
return form
# Create a simplified interface to avoid complex component interactions
CALLBACK_MANAGER = CallbackManager(
redirect_uri="https://multitransformer-discharge-guard.hf.space/callback",
client_secret=None,
)
def generate_discharge_paper_one_click():
"""One-click function to fetch patient data and generate discharge paper with AI Content."""
patient_data_str = CALLBACK_MANAGER.get_patient_data()
if (
patient_data_str.startswith("Not authenticated")
or patient_data_str.startswith("Failed")
or patient_data_str.startswith("Error")
):
return None, patient_data_str # Return error message if authentication or data fetch fails
try:
patient_data = json.loads(patient_data_str)
# --- AI Content Generation for Discharge Summary ---
# This is a placeholder - Replace with actual AI call using InferenceClient and patient_data to generate content
ai_generated_content = generate_ai_discharge_content(
patient_data
) # Placeholder AI function
if not ai_generated_content:
return None, "Error: AI content generation failed."
# --- PDF Generation with AI Content ---
pdf_path, status_message = generate_pdf_from_meldrx_with_ai_content(
patient_data, ai_generated_content
) # Function to generate PDF with AI content
if pdf_path:
return pdf_path, status_message
else:
return None, status_message # Return status message if PDF generation fails
except json.JSONDecodeError:
return None, "Error: Patient data is not in valid JSON format."
except Exception as e:
return None, f"Error during discharge paper generation: {str(e)}"
# Define the cyberpunk theme - using a dark base and neon accents
cyberpunk_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"]
)
# Create the UI with the cyberpunk theme
with gr.Blocks(theme=cyberpunk_theme) as demo: # Apply the theme here
gr.Markdown("Discharge Guard Cyber
") # Cyberpunk Title
with gr.Tab("Authenticate with MeldRx", elem_classes="cyberpunk-tab"): # Optional: Class for tab styling
gr.Markdown("SMART on FHIR Authentication
") # Neon Tab Header
auth_url_output = gr.Textbox(label="Authorization URL", value=CALLBACK_MANAGER.get_auth_url(), interactive=False)
gr.Markdown("Copy the URL above, open it in a browser, log in, and paste the entire redirected URL from your browser's address bar below.
") # Subdued instructions with neon highlight
redirected_url_input = gr.Textbox(label="Redirected URL") # New textbox for redirected URL
extract_code_button = gr.Button("Extract Authorization Code", elem_classes="cyberpunk-button") # Cyberpunk button style
extracted_code_output = gr.Textbox(label="Extracted Authorization Code", interactive=False) # Textbox to show extracted code
auth_code_input = gr.Textbox(label="Authorization Code (from above, or paste manually if extraction fails)", interactive=True) # Updated label to be clearer
auth_submit = gr.Button("Submit Code for Authentication", elem_classes="cyberpunk-button") # Cyberpunk button style
auth_result = gr.HTML(label="Authentication Result") # Use HTML for styled result
patient_data_button = gr.Button("Fetch Patient Data", elem_classes="cyberpunk-button") # Cyberpunk button style
patient_data_output = gr.Textbox(label="Patient Data", lines=10)
# Add button to generate PDF from MeldRx data (No AI)
meldrx_pdf_button = gr.Button("Generate PDF from MeldRx Data (No AI)", elem_classes="cyberpunk-button") # Renamed button
meldrx_pdf_status = gr.Textbox(label="PDF Generation Status (No AI)") # Renamed status
meldrx_pdf_download = gr.File(label="Download Generated PDF (No AI)") # Renamed download
def process_redirected_url(redirected_url):
"""Processes the redirected URL to extract and display the authorization code."""
auth_code, error_message = extract_auth_code_from_url(redirected_url)
if auth_code:
return auth_code, "Authorization code extracted!" # Neon Green Success
else:
return "", f"Could not extract authorization code. {error_message or ''}" # Neon Orange Error
extract_code_button.click(
fn=process_redirected_url,
inputs=redirected_url_input,
outputs=[extracted_code_output, auth_result],# Reusing auth_result for extraction status
)
auth_submit.click(
fn=CALLBACK_MANAGER.set_auth_code,
inputs=extracted_code_output, # Using extracted code as input for authentication
outputs=auth_result,
)
with gr.Tab("Patient Dashboard", elem_classes="cyberpunk-tab"): # Optional: Class for tab styling
gr.Markdown("Patient Data
") # Neon Tab Header
dashboard_output = gr.HTML("Fetch patient data from the Authentication tab first.
") # Subdued placeholder text
refresh_btn = gr.Button("Refresh Data", elem_classes="cyberpunk-button") # Cyberpunk button style
# Simple function to update dashboard based on fetched data
def update_dashboard():
try:
data = CALLBACK_MANAGER.get_patient_data()
if (
data.startswith("Not authenticated")
or data.startswith("Failed")
or data.startswith("Error")
):
return f"{data}
" # Show auth errors in orange
try:
# Parse the data
patients_data = json.loads(data)
patients = []
# Extract patients from bundle
for entry in patients_data.get("entry", []):
resource = entry.get("resource", {})
if resource.get("resourceType") == "Patient":
patients.append(resource)
# Generate HTML card
html = "Patients
" # Neon Sub-header
for patient in patients:
# Extract name
name = patient.get("name", [{}])[0]
given = " ".join(name.get("given", ["Unknown"]))
family = name.get("family", "Unknown")
# Extract other details
gender = patient.get("gender", "unknown").capitalize()
birth_date = patient.get("birthDate", "Unknown")
# Generate HTML card with cyberpunk styling
html += f"""
{given} {family}
Gender: {gender}
Birth Date: {birth_date}
ID: {patient.get("id", "Unknown")}
"""
return html
except Exception as e:
return f"Error parsing patient data: {str(e)}
" # Tomato Error
except Exception as e:
return f"Error fetching patient data: {str(e)}
" # Tomato Error
with gr.Tab("Discharge Form", elem_classes="cyberpunk-tab"): # Optional: Class for tab styling
gr.Markdown("Patient Details
") # Neon Tab Header
with gr.Row():
first_name = gr.Textbox(label="First Name")
last_name = gr.Textbox(label="Last Name")
middle_initial = gr.Textbox(label="Middle Initial")
with gr.Row():
dob = gr.Textbox(label="Date of Birth")
age = gr.Textbox(label="Age")
sex = gr.Textbox(label="Sex")
address = gr.Textbox(label="Address")
with gr.Row():
city = gr.Textbox(label="City")
state = gr.Textbox(label="State")
zip_code = gr.Textbox(label="Zip Code")
gr.Markdown("Primary Healthcare Professional Details
") # Neon Sub-header
with gr.Row():
doctor_first_name = gr.Textbox(label="Doctor's First Name")
doctor_last_name = gr.Textbox(label="Doctor's Last Name")
doctor_middle_initial = gr.Textbox(label="Doctor's Middle Initial")
hospital_name = gr.Textbox(label="Hospital/Clinic Name")
doctor_address = gr.Textbox(label="Address")
with gr.Row():
doctor_city = gr.Textbox(label="City")
doctor_state = gr.Textbox(label="State")
doctor_zip = gr.Textbox(label="Zip Code")
gr.Markdown("Admission and Discharge Details
") # Neon Sub-header
with gr.Row():
admission_date = gr.Textbox(label="Date of Admission")
referral_source = gr.Textbox(label="Source of Referral")
admission_method = gr.Textbox(label="Method of Admission")
with gr.Row():
discharge_date = gr.Textbox(label="Date of Discharge")
discharge_reason = gr.Radio(
["Treated", "Transferred", "Discharge Against Advice", "Patient Died"],
label="Discharge Reason",
)
date_of_death = gr.Textbox(label="Date of Death (if applicable)")
gr.Markdown("Diagnosis & Procedures
") # Neon Sub-header
diagnosis = gr.Textbox(label="Diagnosis")
procedures = gr.Textbox(label="Operation & Procedures")
gr.Markdown("Medication Details
") # Neon Sub-header
medications = gr.Textbox(label="Medication on Discharge")
gr.Markdown("Prepared By
") # Neon Sub-header
with gr.Row():
preparer_name = gr.Textbox(label="Name")
preparer_job_title = gr.Textbox(label="Job Title")
# Add buttons for both display form and generate PDF
with gr.Row():
submit_display = gr.Button("Display Form", elem_classes="cyberpunk-button") # Cyberpunk button style
submit_pdf = gr.Button("Generate PDF (No AI)", elem_classes="cyberpunk-button") # Renamed button to clarify no AI and styled
# Output areas
form_output = gr.HTML() # Use HTML to render styled form
pdf_output = gr.File(label="Download PDF (No AI)") # Renamed output to clarify no AI
# Connect the display form button
submit_display.click(
display_form,
inputs=[ first_name, last_name, middle_initial, dob, age, sex, address, city, state, zip_code, doctor_first_name, doctor_last_name, doctor_middle_initial, hospital_name, doctor_address, doctor_city, doctor_state, doctor_zip, admission_date, referral_source, admission_method, discharge_date, discharge_reason, date_of_death, diagnosis, procedures, medications, preparer_name, preparer_job_title,],
outputs=form_output
)
# Connect the generate PDF button (No AI version)
submit_pdf.click(
generate_pdf_from_form,
inputs=[ first_name, last_name, middle_initial, dob, age, sex, address, city, state, zip_code, doctor_first_name, doctor_last_name, doctor_middle_initial, hospital_name, doctor_address, doctor_city, doctor_state, doctor_zip, admission_date, referral_source, admission_method, discharge_date, discharge_reason, date_of_death, diagnosis, procedures, medications, preparer_name, preparer_job_title,],
outputs=pdf_output
)
with gr.Tab("Medical File Analysis", elem_classes="cyberpunk-tab"): # Optional: Class for tab styling
gr.Markdown("Analyze Medical Files with Discharge Guard AI
") # Neon Tab Header
with gr.Column():
dicom_file = gr.File(
file_types=[".dcm"], label="Upload DICOM File (.dcm)"
)
dicom_ai_output = gr.Textbox(label="DICOM Analysis Report", lines=5)
analyze_dicom_button = gr.Button("Analyze DICOM with AI", elem_classes="cyberpunk-button") # Cyberpunk button style
hl7_file = gr.File(
file_types=[".hl7"], label="Upload HL7 File (.hl7)"
)
hl7_ai_output = gr.Textbox(label="HL7 Analysis Report", lines=5)
analyze_hl7_button = gr.Button("Analyze HL7 with AI", elem_classes="cyberpunk-button") # Cyberpunk button style
xml_file = gr.File(
file_types=[".xml"], label="Upload XML File (.xml)"
)
xml_ai_output = gr.Textbox(label="XML Analysis Report", lines=5)
analyze_xml_button = gr.Button("Analyze XML with AI", elem_classes="cyberpunk-button") # Cyberpunk button style
ccda_file = gr.File(
file_types=[".xml", ".cda", ".ccd"], label="Upload CCDA File (.xml, .cda, .ccd)"
)
ccda_ai_output = gr.Textbox(label="CCDA Analysis Report", lines=5)
analyze_ccda_button = gr.Button("Analyze CCDA with AI", elem_classes="cyberpunk-button") # Cyberpunk button style
ccd_file = gr.File(
file_types=[".ccd"],
label="Upload CCD File (.ccd)",
) # Redundant, as CCDA also handles .ccd, but kept for clarity
ccd_ai_output = gr.Textbox(
label="CCD Analysis Report", lines=5
) # Redundant
analyze_ccd_button = gr.Button("Analyze CCD with AI", elem_classes="cyberpunk-button") # Cyberpunk button style # Redundant
pdf_file = gr.File(
file_types=[".pdf"], label="Upload PDF File (.pdf)"
)
pdf_ai_output = gr.Textbox(label="PDF Analysis Report", lines=5)
analyze_pdf_button = gr.Button("Analyze PDF with AI", elem_classes="cyberpunk-button") # Cyberpunk button style
csv_file = gr.File(
file_types=[".csv"], label="Upload CSV File (.csv)"
)
csv_ai_output = gr.Textbox(label="CSV Analysis Report", lines=5)
analyze_csv_button = gr.Button("Analyze CSV with AI", elem_classes="cyberpunk-button") # Cyberpunk button style
# Connect AI Analysis Buttons - using REAL AI functions now
analyze_dicom_button.click(
analyze_dicom_file_with_ai, # Call REAL AI function
inputs=dicom_file,
outputs=dicom_ai_output
)
analyze_hl7_button.click(
analyze_hl7_file_with_ai, # Call REAL AI function
inputs=hl7_file,
outputs=hl7_ai_output
)
analyze_xml_button.click(
analyze_cda_xml_file_with_ai, # Call REAL AI function
inputs=xml_file,
outputs=xml_ai_output
)
analyze_ccda_button.click(
analyze_cda_xml_file_with_ai, # Call REAL AI function
inputs=ccda_file,
outputs=ccda_ai_output
)
analyze_ccd_button.click( # Redundant button, but kept for UI if needed
analyze_cda_xml_file_with_ai, # Call REAL AI function
inputs=ccd_file,
outputs=ccd_ai_output
)
analyze_pdf_button.click(
analyze_pdf_file_with_ai, inputs=pdf_file, outputs=pdf_ai_output
)
analyze_csv_button.click(
analyze_csv_file_with_ai, inputs=csv_file, outputs=csv_ai_output
)
with gr.Tab(
"One-Click Discharge Paper (AI)", elem_classes="cyberpunk-tab"
): # New Tab for One-Click Discharge Paper with AI, styled
gr.Markdown("One-Click Medical Discharge Paper Generation with AI Content
") # Neon Tab Header
one_click_ai_pdf_button = gr.Button(
"Generate Discharge Paper with AI (One-Click)", elem_classes="cyberpunk-button"
) # Updated button label and styled
one_click_ai_pdf_status = gr.Textbox(
label="Discharge Paper Generation Status (AI)"
) # Updated status label
one_click_ai_pdf_download = gr.File(
label="Download Discharge Paper (AI)"
) # Updated download label
one_click_ai_pdf_button.click(
generate_discharge_paper_one_click, # Use the one-click function that now calls AI
inputs=[],
outputs=[one_click_ai_pdf_download, one_click_ai_pdf_status],
)
# Connect the patient data buttons
patient_data_button.click(
fn=CALLBACK_MANAGER.get_patient_data,
inputs=None,
outputs=patient_data_output
)
# Connect refresh button to update dashboard
refresh_btn.click(
fn=update_dashboard, inputs=None, outputs=dashboard_output
)
# Corrected the button click function name here to `generate_pdf_from_meldrx` (No AI PDF)
meldrx_pdf_button.click(
fn=generate_pdf_from_meldrx,
inputs=patient_data_output,
outputs=[meldrx_pdf_download, meldrx_pdf_status]
)
# Connect patient data updates to dashboard
patient_data_button.click(
fn=update_dashboard, inputs=None, outputs=dashboard_output
)
# Launch with sharing enabled for public access
demo.launch(ssr_mode=False)