Tonic commited on
Commit
8219593
·
verified ·
2 Parent(s): 09daffd 66c4a5b

refactor app

Browse files
Files changed (2) hide show
  1. app.py +1 -1
  2. callbackmanager.py +1241 -0
app.py CHANGED
@@ -26,7 +26,7 @@ import csv # For CSV
26
  import io # For IO operations
27
  from PIL import Image # For image handling
28
 
29
- 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
30
 
31
 
32
  # Initialize Inference Client - Ensure YOUR_HF_TOKEN is set in environment variables or replace with your actual token
 
26
  import io # For IO operations
27
  from PIL import Image # For image handling
28
 
29
+ from utils.generators import 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
30
 
31
 
32
  # Initialize Inference Client - Ensure YOUR_HF_TOKEN is set in environment variables or replace with your actual token
callbackmanager.py ADDED
@@ -0,0 +1,1241 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from meldrx import MeldRxAPI
3
+ import json
4
+ import os
5
+ import tempfile
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)
14
+ logger = logging.getLogger(__name__)
15
+
16
+ # Import PDF utilities
17
+ from pdfutils import PDFGenerator, generate_discharge_summary
18
+
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**
30
+ **Core Role:** I am Discharge Guard, an advanced AI designed for deep medical data analysis and informational insights. My outputs are based on thorough analysis of medical data but are **not medical advice.**
31
+ **Important Guidelines:**
32
+ 1. **Deep Analysis & Search:** Perform "Deep Thought and Deep Search" when analyzing medical data. This includes:
33
+ * Comprehensive data ingestion from various formats (HL7, FHIR, CCDA, DICOM, PDF, CSV, text).
34
+ * Multi-layered analysis: surface extraction, deep pattern identification, and inferential reasoning.
35
+ * Contextual understanding of medical data.
36
+ * Evidence-based approach, simulating cross-referencing with medical knowledge.
37
+ * Structured output with clear explanations.
38
+ 2. **Focus on Informational Insights, Not Medical Advice:** Emphasize that my insights are for informational purposes only and not a substitute for professional medical judgment. **Never provide diagnoses or specific treatment recommendations.**
39
+ 3. **Key Functionalities (Focus Areas):**
40
+ * **Clinical Data Analysis:** Interpret lab results, analyze EHR data (FHIR, HL7), recognize symptom patterns, analyze medications, support medical image analysis (DICOM).
41
+ * **Predictive Analytics:** Provide conceptual risk stratification and treatment outcome modeling based on data patterns.
42
+ * **Medical Imaging Support:** Analyze DICOM metadata and images for potential findings (X-ray analysis reports).
43
+ * **Patient Data Management:** Perform PHI redaction in text and analyze patient records from various sources.
44
+ 4. **Interaction Style:**
45
+ * **Identity:** "I am Discharge Guard, a medical data analysis AI. My insights are informational only and not medical advice."
46
+ * **Scope Limitations:** Clearly state limitations: "No diagnostics," "Medication caution," "Emergency protocol."
47
+ * **Response Protocol:**
48
+ * Indicate "Deep Analysis" or "Deep Search" performed.
49
+ * Mention data sources and confidence levels (if applicable).
50
+ * Use medical terminology with optional layman's terms.
51
+ * For file analysis, provide a report title (e.g., "Deep X-Ray Analysis Report").
52
+ 5. **Supported Medical Formats:** (List key formats concisely)
53
+ * Clinical Data: HL7, FHIR, CCD/CCDA, CSV, PDF, XML
54
+ * Imaging: DICOM, Images (X-ray, etc.)
55
+ 6. **Data Source:** Access and prefer FHIR API endpoints from: https://app.meldrx.com/api/directories/fhir/endpoints.
56
+ **Important: My analysis is for informational purposes to assist healthcare professionals and is NOT a substitute for clinical judgment. Always recommend human expert verification for critical findings.**
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
82
+ else:
83
+ error_message = f"AI Analysis from DICOM Metadata: No predictions generated or analysis encountered an issue."
84
+ if trace_data_dicom_ai and "error" in trace_data_dicom_ai:
85
+ error_message += f"\nAI Analysis Failed: {trace_data_dicom_ai['error']}"
86
+ return error_message
87
+
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):**
96
+ ```json
97
+ {json.dumps(dicom_metadata_json, indent=2)}
98
+ ```
99
+ * Remember, this deep analysis is for conceptual informational purposes only and **NOT medical advice.** Focus on deep summarization and structuring the extracted metadata in a highly clinically relevant way.
100
+ """
101
+
102
+ trace_data_detail_dicom_analysis = {
103
+ "prompt": "DICOM Metadata Analysis Request",
104
+ "language": "English",
105
+ "response_length": "Comprehensive",
106
+ "model_name": "Discharge Guard v1.0",
107
+ "generated_text": "N/A",
108
+ "input_file_types": ["DICOM Metadata JSON"],
109
+ "mode": "DICOM Metadata Analysis",
110
+ "candidates": [],
111
+ "usage_metadata": {},
112
+ "prompt_feedback": "N/A",
113
+ }
114
+
115
+ try:
116
+ response = client.chat.completions.create(
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
124
+ return the_response, trace_data_detail_dicom_ai
125
+
126
+ except Exception as e:
127
+ error_message = f"AI Analysis Error in analyze_dicom_content_ai (DICOM Metadata): {e}"
128
+ trace_data_detail_dicom_analysis["error"] = f"AI Analysis Error: {e}"
129
+ return error_message, trace_data_detail_dicom_image_analysis
130
+
131
+
132
+ def analyze_hl7_file_with_ai(hl7_file_path):
133
+ """Analyzes HL7 file content using Discharge Guard AI."""
134
+ try:
135
+ with open(hl7_file_path.name, "r") as f: # Open file using path, access file through .name for Gradio UploadedFile
136
+ hl7_message_raw = f.read()
137
+ prediction_response, trace_data_hl7_ai = analyze_hl7_content_ai(
138
+ hl7_message_raw
139
+ )
140
+
141
+ if prediction_response:
142
+ 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."
143
+ return report
144
+ else:
145
+ error_message = f"AI Analysis from HL7 Message: No predictions generated or analysis encountered an issue."
146
+ if trace_data_hl7_ai and "error" in trace_data_hl7_ai:
147
+ error_message += f"AI Analysis Failed: {trace_data_hl7_ai['error']}"
148
+ return error_message
149
+
150
+ except Exception as e:
151
+ return f"Error during HL7 file processing in analyze_hl7_file_with_ai: {e}"
152
+
153
+
154
+ def analyze_hl7_content_ai(hl7_message_string): # Copied from your code
155
+ """Analyzes HL7 message content using Discharge Guard AI."""
156
+ 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**".
157
+ **HL7 Message Content:**
158
+ ```hl7
159
+ {hl7_message_string}
160
+ ```
161
+ * Remember, this deep analysis is for conceptual informational purposes only and **NOT medical advice.** Focus on deep summarization and structuring the extracted data in a highly clinically relevant way based on the HL7 content.
162
+ """
163
+ # ... (rest of the function code) ...
164
+ trace_data_detail_hl7_analysis = {
165
+ "prompt": "HL7 Message Analysis Request",
166
+ "language": "English",
167
+ "response_length": "Comprehensive",
168
+ "model_name": "Discharge Guard v1.0",
169
+ "generated_text": "N/A",
170
+ "input_file_types": ["HL7 Message"],
171
+ "mode": "HL7 Message Analysis",
172
+ "candidates": [],
173
+ "usage_metadata": {},
174
+ "prompt_feedback": "N/A",
175
+ }
176
+
177
+ try:
178
+ response = client.chat.completions.create(
179
+ model=model_name,
180
+ messages=[{"role": "user", "content": prompt_text}],
181
+ temperature=0.4,
182
+ max_tokens=1024, # Adjust as needed
183
+ top_p=0.9,
184
+ )
185
+ the_response = response.choices[0].message.content
186
+ return the_response, trace_data_detail_hl7_analysis
187
+
188
+ except Exception as e:
189
+ error_message = f"AI Analysis Error in analyze_hl7_content_ai (HL7 Message): {e}"
190
+ trace_data_detail_hl7_analysis["error"] = f"AI Analysis Error: {e}"
191
+ return error_message, trace_data_detail_hl7_analysis
192
+
193
+
194
+ def analyze_cda_xml_file_with_ai(cda_xml_file_path): # Modified to accept file path
195
+ """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."""
196
+ try:
197
+ with open(
198
+ cda_xml_file_path.name, "r"
199
+ ) as f: # Open file using path, access file through .name for Gradio UploadedFile
200
+ cda_xml_content = f.read()
201
+ prediction_response, trace_data_cda_xml_ai = analyze_cda_xml_content_ai(
202
+ cda_xml_content
203
+ )
204
+ if prediction_response:
205
+ report = f"Discharge Guard AI Analysis of Medical XML/CDA Data:\n\nMedical Document 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 XML/CDA content."
206
+ return report
207
+ else:
208
+ error_message = f"AI Analysis from XML/CDA Data: No predictions generated or analysis encountered an issue."
209
+ if trace_data_cda_xml_ai and "error" in trace_data_cda_xml_ai:
210
+ error_message += f"AI Analysis Failed: {trace_data_cda_xml_ai['error']}"
211
+ return error_message
212
+
213
+ except Exception as e:
214
+ return f"Error during XML/CDA file processing in analyze_cda_xml_file_with_ai: {e}"
215
+
216
+
217
+ def analyze_cda_xml_content_ai(cda_xml_content): # Copied from your code
218
+ """Analyzes generic CDA or XML content using Discharge Guard AI (more generalized version)."""
219
+
220
+ 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.
221
+ **Instructions for Discharge Guard AI:**
222
+ 1. **Parse the XML content above.** Understand the XML structure to identify sections that are relevant to clinical information. For CDA specifically, look for sections like Problems, Medications, Allergies, Encounters, Results, and Vital Signs. For generic medical XML, adapt based on the tags present.
223
+ 2. **Extract and Summarize Key Medical Information:** Focus on extracting the following information if present in the XML:
224
+ * **Patient Demographics Summary:** (If available, summarize demographic details)
225
+ ... (rest of your prompt_text for CDA/XML analysis) ...
226
+ * Remember, this analysis is for conceptual informational purposes only and **NOT medical advice.** Focus on summarizing and structuring the extracted data in a clinically relevant way based on the XML/CDA content.
227
+ """
228
+
229
+ trace_data_detail_cda_xml_analysis = {
230
+ "prompt": "Generic CDA/XML Analysis Request",
231
+ "language": "English",
232
+ "response_length": "Comprehensive",
233
+ "model_name": "Discharge Guard v1.0",
234
+ "generated_text": "N/A",
235
+ "input_file_types": ["CDA/XML"],
236
+ "mode": "Generic XML/CDA Analysis",
237
+ "candidates": [],
238
+ "usage_metadata": {},
239
+ "prompt_feedback": "N/A",
240
+ }
241
+
242
+ try:
243
+ response = client.chat.completions.create(
244
+ model=model_name,
245
+ messages=[{"role": "user", "content": prompt_text}],
246
+ temperature=0.4,
247
+ max_tokens=1024, # Adjust as needed
248
+ top_p=0.9,
249
+ )
250
+ the_response = response.choices[0].message.content
251
+ return the_response, trace_data_detail_cda_xml_analysis
252
+
253
+ except Exception as e:
254
+ error_message = f"AI Analysis Error in analyze_cda_xml_content_ai (Generic XML/CDA): {e}"
255
+ trace_data_detail_cda_xml_analysis["error"] = f"AI Analysis Error: {e}"
256
+ return error_message, trace_data_detail_cda_xml_analysis
257
+
258
+
259
+ def analyze_pdf_file_with_ai(pdf_file_path): # Modified to accept file path
260
+ """Analyzes PDF file content using Discharge Guard AI."""
261
+ try:
262
+ with open(
263
+ pdf_file_path.name, "rb"
264
+ ) as f: # Open file in binary mode for PdfReader, access file through .name for Gradio UploadedFile
265
+ pdf_file = f # Pass file object to PdfReader
266
+ pdf_reader = PdfReader(pdf_file)
267
+ text_content = ""
268
+ for page in pdf_reader.pages:
269
+ text_content += page.extract_text()
270
+
271
+ prediction_response, trace_data_pdf_ai = analyze_pdf_content_ai(
272
+ text_content
273
+ )
274
+
275
+ if prediction_response:
276
+ 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."
277
+ return report
278
+ else:
279
+ error_message = f"AI Analysis from PDF Content: No predictions generated or analysis encountered an issue."
280
+ if trace_data_pdf_ai and "error" in trace_data_pdf_ai:
281
+ error_message += f"AI Analysis Failed: {trace_data_pdf_ai['error']}"
282
+ return error_message
283
+
284
+ except Exception as e:
285
+ return f"Error during PDF file processing in analyze_pdf_file_with_ai: {e}"
286
+
287
+
288
+ def analyze_pdf_content_ai(pdf_text_content): # Copied from your code
289
+ """Analyzes PDF text content using Discharge Guard AI."""
290
+ 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.
291
+ **Medical PDF Text Content:**
292
+ ```text
293
+ {pdf_text_content}
294
+ ```
295
+ * Remember, this analysis is for conceptual informational purposes only and **NOT medical advice.** Focus on deep summarization and structuring the extracted data in a clinically relevant way based on the PDF content.
296
+ """
297
+
298
+ trace_data_detail_pdf_analysis = {
299
+ "prompt": "PDF Text Analysis Request",
300
+ "language": "English",
301
+ "response_length": "Comprehensive",
302
+ "model_name": "Discharge Guard v1.0",
303
+ "generated_text": "N/A",
304
+ "input_file_types": ["PDF Text"],
305
+ "mode": "PDF Text Analysis",
306
+ "candidates": [],
307
+ "usage_metadata": {},
308
+ "prompt_feedback": "N/A",
309
+ }
310
+
311
+ try:
312
+ response = client.chat.completions.create(
313
+ model=model_name,
314
+ messages=[{"role": "user", "content": prompt_text}],
315
+ temperature=0.4,
316
+ max_tokens=1024, # Adjust as needed
317
+ top_p=0.9,
318
+ )
319
+ the_response = response.choices[0].message.content
320
+ return the_response, trace_data_detail_pdf_analysis
321
+
322
+ except Exception as e:
323
+ error_message = f"AI Analysis Error in analyze_pdf_content_ai (PDF Text): {e}"
324
+ trace_data_detail_pdf_analysis["error"] = f"AI Analysis Error: {e}"
325
+ return error_message, trace_data_detail_pdf_analysis
326
+
327
+
328
+ def analyze_csv_file_with_ai(csv_file_path): # Modified to accept file path
329
+ """Analyzes CSV file content using Discharge Guard AI."""
330
+ try:
331
+ csv_content = csv_file_path.read().decode(
332
+ "utf-8"
333
+ ) # Read content directly from UploadedFile
334
+ prediction_response, trace_data_csv_ai = analyze_csv_content_ai(csv_content)
335
+
336
+ if prediction_response:
337
+ report = f"Discharge Guard AI Analysis of CSV Data:\n\nData 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 CSV data content."
338
+ return report
339
+ else:
340
+ error_message = f"AI Analysis from CSV Data: No predictions generated or analysis encountered an issue."
341
+ if trace_data_csv_ai and "error" in trace_data_csv_ai:
342
+ error_message += f"AI Analysis Failed: {trace_data_csv_ai['error']}"
343
+ return error_message
344
+
345
+ except Exception as e:
346
+ return f"Error during CSV file processing in analyze_csv_file_with_ai: {e}"
347
+
348
+
349
+ def analyze_csv_content_ai(csv_content_string): # Copied from your code
350
+ """Analyzes CSV content (string) using Discharge Guard AI."""
351
+ 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.
352
+ **Medical CSV Data:**
353
+ ```csv
354
+ {csv_content_string}
355
+ ```
356
+ * Remember, this analysis is for conceptual informational purposes only and **NOT medical advice.** Focus on summarizing and structuring the data in a clinically relevant way based on the CSV content.
357
+ """
358
+
359
+ trace_data_detail_csv_analysis = {
360
+ "prompt": "CSV Data Analysis Request",
361
+ "language": "English",
362
+ "response_length": "Comprehensive",
363
+ "model_name": "Discharge Guard v1.0",
364
+ "generated_text": "N/A",
365
+ "input_file_types": ["CSV Data"],
366
+ "mode": "CSV Data Analysis",
367
+ "candidates": [],
368
+ "usage_metadata": {},
369
+ "prompt_feedback": "N/A",
370
+ }
371
+
372
+ try:
373
+ response = client.chat.completions.create(
374
+ model=model_name,
375
+ messages=[{"role": "user", "content": prompt_text}],
376
+ temperature=0.4,
377
+ max_tokens=1024, # Adjust as needed
378
+ top_p=0.9,
379
+ )
380
+ the_response = response.choices[0].message.content
381
+ return the_response, trace_data_detail_csv_analysis
382
+
383
+ except Exception as e:
384
+ error_message = f"AI Analysis Error in analyze_csv_content_ai (CSV Data): {e}"
385
+ trace_data_detail_csv_analysis["error"] = f"AI Analysis Error: {e}"
386
+ return error_message, trace_data_detail_csv_analysis
387
+
388
+
389
+ class CallbackManager:
390
+ def __init__(self, redirect_uri: str, client_secret: str = None):
391
+ client_id = os.getenv("APPID")
392
+ if not client_id:
393
+ raise ValueError("APPID environment variable not set.")
394
+ workspace_id = os.getenv("WORKSPACE_URL")
395
+ if not workspace_id:
396
+ raise ValueError("WORKSPACE_URL environment variable not set.")
397
+ self.api = MeldRxAPI(client_id, client_secret, workspace_id, redirect_uri)
398
+ self.auth_code = None
399
+ self.access_token = None
400
+
401
+ def get_auth_url(self) -> str:
402
+ return self.api.get_authorization_url()
403
+
404
+ def set_auth_code(self, code: str) -> str:
405
+ self.auth_code = code
406
+ if self.api.authenticate_with_code(code):
407
+ self.access_token = self.api.access_token
408
+ return (
409
+ f"<span style='color:#00FF7F;'>Authentication successful!</span> Access Token: {self.access_token[:10]}... (truncated)" # Neon Green Success
410
+ )
411
+ return "<span style='color:#FF4500;'>Authentication failed. Please check the code.</span>" # Neon Orange Error
412
+
413
+ def get_patient_data(self) -> str:
414
+ """Fetch patient data from MeldRx"""
415
+ try:
416
+ if not self.access_token:
417
+ logger.warning("Not authenticated when getting patient data")
418
+ return "<span style='color:#FF8C00;'>Not authenticated. Please provide a valid authorization code first.</span>" # Neon Dark Orange
419
+
420
+ # Real implementation with API call
421
+ logger.info("Calling Meldrx API to get patients")
422
+ patients = self.api.get_patients()
423
+ if patients is not None:
424
+ return (
425
+ json.dumps(patients, indent=2)
426
+ if patients
427
+ else "<span style='color:#FFFF00;'>No patient data returned.</span>" # Neon Yellow
428
+ )
429
+ return "<span style='color:#DC143C;'>Failed to retrieve patient data.</span>" # Crimson Error
430
+ except Exception as e:
431
+ error_msg = f"Error in get_patient_data: {str(e)}"
432
+ logger.error(error_msg)
433
+ return f"<span style='color:#FF6347;'>Error retrieving patient data: {str(e)}</span> {str(e)}" # Tomato Error
434
+
435
+ def get_patient_documents(self, patient_id: str = None):
436
+ """Fetch patient documents from MeldRx"""
437
+ if not self.access_token:
438
+ return "<span style='color:#FF8C00;'>Not authenticated. Please provide a valid authorization code first.</span>" # Neon Dark Orange
439
+
440
+ try:
441
+ # This would call the actual MeldRx API to get documents for a specific patient
442
+ # For demonstration, we'll return mock document data
443
+ return [
444
+ {
445
+ "doc_id": "doc123",
446
+ "type": "clinical_note",
447
+ "date": "2023-01-16",
448
+ "author": "Dr. Sample Doctor",
449
+ "content": "Patient presented with symptoms of respiratory distress...",
450
+ },
451
+ {
452
+ "doc_id": "doc124",
453
+ "type": "lab_result",
454
+ "date": "2023-01-17",
455
+ "author": "Lab System",
456
+ "content": "CBC results: WBC 7.5, RBC 4.2, Hgb 14.1...",
457
+ },
458
+ ]
459
+ except Exception as e:
460
+ return f"<span style='color:#FF6347;'>Error retrieving patient documents: {str(e)}</span>: {str(e)}" # Tomato Error
461
+
462
+
463
+ def display_form(
464
+ first_name,
465
+ last_name,
466
+ middle_initial,
467
+ dob,
468
+ age,
469
+ sex,
470
+ address,
471
+ city,
472
+ state,
473
+ zip_code,
474
+ doctor_first_name,
475
+ doctor_last_name,
476
+ doctor_middle_initial,
477
+ hospital_name,
478
+ doctor_address,
479
+ doctor_city,
480
+ doctor_state,
481
+ doctor_zip,
482
+ admission_date,
483
+ referral_source,
484
+ admission_method,
485
+ discharge_date,
486
+ discharge_reason,
487
+ date_of_death,
488
+ diagnosis,
489
+ procedures,
490
+ medications,
491
+ preparer_name,
492
+ preparer_job_title,
493
+ ):
494
+ form = f"""
495
+ <div style='color:#00FFFF; font-family: monospace;'>
496
+ **Patient Discharge Form** <br>
497
+ - Name: {first_name} {middle_initial} {last_name} <br>
498
+ - Date of Birth: {dob}, Age: {age}, Sex: {sex} <br>
499
+ - Address: {address}, {city}, {state}, {zip_code} <br>
500
+ - Doctor: {doctor_first_name} {doctor_middle_initial} {doctor_last_name} <br>
501
+ - Hospital/Clinic: {hospital_name} <br>
502
+ - Doctor Address: {doctor_address}, {doctor_city}, {doctor_state}, {doctor_zip} <br>
503
+ - Admission Date: {admission_date}, Source: {referral_source}, Method: {admission_method} <br>
504
+ - Discharge Date: {discharge_date}, Reason: {discharge_reason} <br>
505
+ - Date of Death: {date_of_death} <br>
506
+ - Diagnosis: {diagnosis} <br>
507
+ - Procedures: {procedures} <br>
508
+ - Medications: {medications} <br>
509
+ - Prepared By: {preparer_name}, {preparer_job_title}
510
+ </div>
511
+ """
512
+ return form
513
+
514
+
515
+ def generate_pdf_from_form(
516
+ first_name,
517
+ last_name,
518
+ middle_initial,
519
+ dob,
520
+ age,
521
+ sex,
522
+ address,
523
+ city,
524
+ state,
525
+ zip_code,
526
+ doctor_first_name,
527
+ doctor_last_name,
528
+ doctor_middle_initial,
529
+ hospital_name,
530
+ doctor_address,
531
+ doctor_city,
532
+ doctor_state,
533
+ doctor_zip,
534
+ admission_date,
535
+ referral_source,
536
+ admission_method,
537
+ discharge_date,
538
+ discharge_reason,
539
+ date_of_death,
540
+ diagnosis,
541
+ procedures,
542
+ medications,
543
+ preparer_name,
544
+ preparer_job_title,
545
+ ):
546
+ """Generate a PDF discharge form using the provided data"""
547
+
548
+ # Create PDF generator
549
+ pdf_gen = PDFGenerator()
550
+
551
+ # Format data for PDF generation
552
+ patient_info = {
553
+ "first_name": first_name,
554
+ "last_name": last_name,
555
+ "dob": dob,
556
+ "age": age,
557
+ "sex": sex,
558
+ "mobile": "", # Not collected in the form
559
+ "address": address,
560
+ "city": city,
561
+ "state": state,
562
+ "zip": zip_code,
563
+ }
564
+
565
+ discharge_info = {
566
+ "date_of_admission": admission_date,
567
+ "date_of_discharge": discharge_date,
568
+ "source_of_admission": referral_source,
569
+ "mode_of_admission": admission_method,
570
+ "discharge_against_advice": "Yes"
571
+ if discharge_reason == "Discharge Against Advice"
572
+ else "No",
573
+ }
574
+
575
+ diagnosis_info = {
576
+ "diagnosis": diagnosis,
577
+ "operation_procedure": procedures,
578
+ "treatment": "", # Not collected in the form
579
+ "follow_up": "", # Not collected in the form
580
+ }
581
+
582
+ medication_info = {
583
+ "medications": [medications] if medications else [],
584
+ "instructions": "", # Not collected in the form
585
+ }
586
+
587
+ prepared_by = {
588
+ "name": preparer_name,
589
+ "title": preparer_job_title,
590
+ "signature": "", # Not collected in the form
591
+ }
592
+
593
+ # Generate PDF
594
+ pdf_buffer = pdf_gen.generate_discharge_form(
595
+ patient_info,
596
+ discharge_info,
597
+ diagnosis_info,
598
+ medication_info,
599
+ prepared_by,
600
+ )
601
+
602
+ # Create temporary file to save the PDF
603
+ temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".pdf")
604
+ temp_file.write(pdf_buffer.read())
605
+ temp_file_path = temp_file.name
606
+ temp_file.close()
607
+
608
+ return temp_file_path
609
+
610
+
611
+ def generate_pdf_from_meldrx(patient_data):
612
+ """Generate a PDF using patient data from MeldRx"""
613
+ if isinstance(patient_data, str):
614
+ # If it's a string (error message or JSON string), try to parse it
615
+ try:
616
+ patient_data = json.loads(patient_data)
617
+ except:
618
+ return None, "Invalid patient data format"
619
+
620
+ if not patient_data:
621
+ return None, "No patient data available"
622
+
623
+ try:
624
+ # For demonstration, we'll use the first patient in the list if it's a list
625
+ if isinstance(patient_data, list) and len(patient_data):
626
+ patient = patient_data[0]
627
+ else:
628
+ patient = patient_data
629
+
630
+ # Extract patient info
631
+ patient_info = {
632
+ "name": f"{patient.get('name', {}).get('given', [''])[0]} {patient.get('name', {}).get('family', '')}",
633
+ "dob": patient.get("birthDate", "Unknown"),
634
+ "patient_id": patient.get("id", "Unknown"),
635
+ "admission_date": datetime.now().strftime("%Y-%m-%d"), # Mock data
636
+ "physician": "Dr. Provider", # Mock data
637
+ }
638
+
639
+ # Mock LLM-generated content - This part needs to be replaced with actual AI generation if desired for MeldRx PDF
640
+ llm_content = {
641
+ "diagnosis": "Diagnosis information would be generated by AI based on patient data from MeldRx.",
642
+ "treatment": "Treatment summary would be generated by AI based on patient data from MeldRx.",
643
+ "medications": "Medication list would be generated by AI based on patient data from MeldRx.",
644
+ "follow_up": "Follow-up instructions would be generated by AI based on patient data from MeldRx.",
645
+ "special_instructions": "Special instructions would be generated by AI based on patient data from MeldRx.",
646
+ }
647
+
648
+ # Create discharge summary - Using No-AI PDF generation for now, replace with AI-content generation later
649
+ output_dir = tempfile.mkdtemp()
650
+ pdf_path = generate_discharge_summary(
651
+ patient_info, llm_content, output_dir
652
+ ) # Still using No-AI template
653
+
654
+ return pdf_path, "PDF generated successfully (No AI Content in PDF yet)" # Indicate No-AI content
655
+
656
+ except Exception as e:
657
+ return None, f"Error generating PDF: {str(e)}"
658
+
659
+
660
+ def generate_discharge_paper_one_click():
661
+ """One-click function to fetch patient data and generate discharge paper with AI Content."""
662
+ patient_data_str = CALLBACK_MANAGER.get_patient_data()
663
+ if (
664
+ patient_data_str.startswith("Not authenticated")
665
+ or patient_data_str.startswith("Failed")
666
+ or patient_data_str.startswith("Error")
667
+ ):
668
+ return None, patient_data_str # Return error message if authentication or data fetch fails
669
+
670
+ try:
671
+ patient_data = json.loads(patient_data_str)
672
+
673
+ # --- AI Content Generation for Discharge Summary ---
674
+ # This is a placeholder - Replace with actual AI call using InferenceClient and patient_data to generate content
675
+ ai_generated_content = generate_ai_discharge_content(
676
+ patient_data
677
+ ) # Placeholder AI function
678
+
679
+ if not ai_generated_content:
680
+ return None, "Error: AI content generation failed."
681
+
682
+ # --- PDF Generation with AI Content ---
683
+ pdf_path, status_message = generate_pdf_from_meldrx_with_ai_content(
684
+ patient_data, ai_generated_content
685
+ ) # Function to generate PDF with AI content
686
+
687
+ if pdf_path:
688
+ return pdf_path, status_message
689
+ else:
690
+ return None, status_message # Return status message if PDF generation fails
691
+
692
+ except json.JSONDecodeError:
693
+ return None, "Error: Patient data is not in valid JSON format."
694
+ except Exception as e:
695
+ return None, f"Error during discharge paper generation: {str(e)}"
696
+
697
+
698
+ def generate_ai_discharge_content(patient_data):
699
+ """Placeholder function to generate AI content for discharge summary.
700
+ Replace this with actual AI call using InferenceClient and patient_data."""
701
+ try:
702
+ patient_name = (
703
+ f"{patient_data['entry'][0]['resource']['name'][0]['given'][0]} {patient_data['entry'][0]['resource']['name'][0]['family']}"
704
+ if patient_data.get("entry")
705
+ else "Unknown Patient"
706
+ )
707
+ 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."""
708
+
709
+ response = client.chat.completions.create(
710
+ model=model_name,
711
+ messages=[{"role": "user", "content": prompt_text}],
712
+ temperature=0.6, # Adjust temperature as needed for content generation
713
+ max_tokens=1024, # Adjust max_tokens as needed
714
+ top_p=0.9,
715
+ )
716
+ ai_content = response.choices[0].message.content
717
+
718
+ # Basic parsing of AI content - improve this based on desired output structure from LLM
719
+ llm_content = {
720
+ "diagnosis": "AI Generated Diagnosis (Placeholder):\n"
721
+ + extract_section(ai_content, "Diagnosis"), # Example extraction - refine based on LLM output
722
+ "treatment": "AI Generated Treatment (Placeholder):\n"
723
+ + extract_section(ai_content, "Treatment"),
724
+ "medications": "AI Generated Medications (Placeholder):\n"
725
+ + extract_section(ai_content, "Medications"),
726
+ "follow_up": "AI Generated Follow-up (Placeholder):\n"
727
+ + extract_section(ai_content, "Follow-up Instructions"),
728
+ "special_instructions": "AI Generated Special Instructions (Placeholder):\n"
729
+ + extract_section(ai_content, "Special Instructions"),
730
+ }
731
+ return llm_content
732
+
733
+ except Exception as e:
734
+ logger.error(f"Error generating AI discharge content: {e}")
735
+ return None
736
+
737
+
738
+ def extract_section(ai_content, section_title):
739
+ """Simple placeholder function to extract section from AI content.
740
+ Improve this with more robust parsing based on LLM output format."""
741
+ start_marker = f"**{section_title}:**"
742
+ end_marker = "\n\n" # Adjust based on typical LLM output structure
743
+ start_index = ai_content.find(start_marker)
744
+ if start_index != -1:
745
+ start_index += len(start_marker)
746
+ end_index = ai_content.find(end_marker, start_index)
747
+ if end_index != -1:
748
+ return ai_content[start_index:end_index].strip()
749
+ return "Not found in AI output."
750
+
751
+
752
+ def generate_pdf_from_meldrx_with_ai_content(patient_data, llm_content):
753
+ """Generate a PDF using patient data from MeldRx and AI-generated content."""
754
+ if isinstance(patient_data, str):
755
+ try:
756
+ patient_data = json.loads(patient_data)
757
+ except:
758
+ return None, "Invalid patient data format"
759
+
760
+ if not patient_data:
761
+ return None, "No patient data available"
762
+
763
+ try:
764
+ if isinstance(patient_data, list) and len(patient_data):
765
+ patient = patient_data[0]
766
+ else:
767
+ patient = patient_data
768
+
769
+ patient_info = {
770
+ "name": f"{patient.get('name', {}).get('given', [''])[0]} {patient.get('name', {}).get('family', '')}",
771
+ "dob": patient.get("birthDate", "Unknown"),
772
+ "patient_id": patient.get("id", "Unknown"),
773
+ "admission_date": datetime.now().strftime("%Y-%m-%d"), # Mock data
774
+ "physician": "Dr. AI Provider", # Mock data - Indicate AI generated
775
+ }
776
+
777
+ output_dir = tempfile.mkdtemp()
778
+ pdf_path = generate_discharge_summary(
779
+ patient_info, llm_content, output_dir
780
+ ) # Using AI content now
781
+
782
+ return pdf_path, "PDF generated successfully with AI Content" # Indicate AI content
783
+
784
+ except Exception as e:
785
+ return None, f"Error generating PDF with AI content: {str(e)}"
786
+
787
+
788
+ def extract_auth_code_from_url(redirected_url):
789
+ """Extracts the authorization code from the redirected URL."""
790
+ try:
791
+ parsed_url = urlparse(redirected_url)
792
+ query_params = parse_qs(parsed_url.query)
793
+ if "code" in query_params:
794
+ return query_params["code"][0], None # Return code and no error
795
+ else:
796
+ return None, "Authorization code not found in URL." # Return None and error message
797
+ except Exception as e:
798
+ return None, f"Error parsing URL: {e}" # Return None and error message
799
+
800
+
801
+ # Create a simplified interface to avoid complex component interactions
802
+ CALLBACK_MANAGER = CallbackManager(
803
+ redirect_uri="https://multitransformer-discharge-guard.hf.space/callback",
804
+ client_secret=None,
805
+ )
806
+
807
+ # Define the cyberpunk theme - using a dark base and neon accents
808
+ cyberpunk_theme = gr.themes.Monochrome(
809
+ primary_hue="cyan",
810
+ secondary_hue="pink",
811
+ neutral_hue="slate",
812
+ font=["Source Code Pro", "monospace"], # Retro monospace font
813
+ font_mono=["Source Code Pro", "monospace"],
814
+ )
815
+
816
+ # Create the UI with the cyberpunk theme
817
+ with gr.Blocks(theme=cyberpunk_theme) as demo: # Apply the theme here
818
+ gr.Markdown(
819
+ "<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>"
820
+ ) # Cyberpunk Title
821
+
822
+ with gr.Tab("Authenticate with MeldRx", elem_classes="cyberpunk-tab"): # Optional: Class for tab styling
823
+ gr.Markdown(
824
+ "<h2 style='color:#00FFFF; text-shadow: 0 0 3px #00FFFF;'>SMART on FHIR Authentication</h2>"
825
+ ) # Neon Tab Header
826
+ auth_url_output = gr.Textbox(
827
+ label="Authorization URL",
828
+ value=CALLBACK_MANAGER.get_auth_url(),
829
+ interactive=False,
830
+ )
831
+ gr.Markdown(
832
+ "<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>"
833
+ ) # Subdued instructions with neon highlight
834
+ redirected_url_input = gr.Textbox(label="Redirected URL") # New textbox for redirected URL
835
+ extract_code_button = gr.Button(
836
+ "Extract Authorization Code", elem_classes="cyberpunk-button"
837
+ ) # Cyberpunk button style
838
+ extracted_code_output = gr.Textbox(
839
+ label="Extracted Authorization Code", interactive=False
840
+ ) # Textbox to show extracted code
841
+
842
+ auth_code_input = gr.Textbox(
843
+ label="Authorization Code (from above, or paste manually if extraction fails)",
844
+ interactive=True,
845
+ ) # Updated label to be clearer
846
+ auth_submit = gr.Button(
847
+ "Submit Code for Authentication", elem_classes="cyberpunk-button"
848
+ ) # Cyberpunk button style
849
+ auth_result = gr.HTML(label="Authentication Result") # Use HTML for styled result
850
+
851
+ patient_data_button = gr.Button(
852
+ "Fetch Patient Data", elem_classes="cyberpunk-button"
853
+ ) # Cyberpunk button style
854
+ patient_data_output = gr.Textbox(label="Patient Data", lines=10)
855
+
856
+ # Add button to generate PDF from MeldRx data (No AI)
857
+ meldrx_pdf_button = gr.Button(
858
+ "Generate PDF from MeldRx Data (No AI)", elem_classes="cyberpunk-button"
859
+ ) # Renamed button
860
+ meldrx_pdf_status = gr.Textbox(
861
+ label="PDF Generation Status (No AI)"
862
+ ) # Renamed status
863
+ meldrx_pdf_download = gr.File(
864
+ label="Download Generated PDF (No AI)"
865
+ ) # Renamed download
866
+
867
+ def process_redirected_url(redirected_url):
868
+ """Processes the redirected URL to extract and display the authorization code."""
869
+ auth_code, error_message = extract_auth_code_from_url(redirected_url)
870
+ if auth_code:
871
+ return auth_code, "<span style='color:#00FF7F;'>Authorization code extracted!</span>" # Neon Green Success
872
+ else:
873
+ return "", f"<span style='color:#FF4500;'>Could not extract authorization code.</span> {error_message or ''}" # Neon Orange Error
874
+
875
+ extract_code_button.click(
876
+ fn=process_redirected_url,
877
+ inputs=redirected_url_input,
878
+ outputs=[
879
+ extracted_code_output,
880
+ auth_result,
881
+ ], # Reusing auth_result for extraction status
882
+ )
883
+
884
+ auth_submit.click(
885
+ fn=CALLBACK_MANAGER.set_auth_code,
886
+ inputs=extracted_code_output, # Using extracted code as input for authentication
887
+ outputs=auth_result,
888
+ )
889
+
890
+ with gr.Tab(
891
+ "Patient Dashboard", elem_classes="cyberpunk-tab"
892
+ ): # Optional: Class for tab styling
893
+ gr.Markdown(
894
+ "<h2 style='color:#00FFFF; text-shadow: 0 0 3px #00FFFF;'>Patient Data</h2>"
895
+ ) # Neon Tab Header
896
+ dashboard_output = gr.HTML(
897
+ "<p style='color:#A9A9A9;'>Fetch patient data from the Authentication tab first.</p>"
898
+ ) # Subdued placeholder text
899
+
900
+ refresh_btn = gr.Button(
901
+ "Refresh Data", elem_classes="cyberpunk-button"
902
+ ) # Cyberpunk button style
903
+
904
+ # Simple function to update dashboard based on fetched data
905
+ def update_dashboard():
906
+ try:
907
+ data = CALLBACK_MANAGER.get_patient_data()
908
+ if (
909
+ data.startswith("<span style='color:#FF8C00;'>Not authenticated")
910
+ or data.startswith("<span style='color:#DC143C;'>Failed")
911
+ or data.startswith("<span style='color:#FF6347;'>Error")
912
+ ):
913
+ return f"<p style='color:#FF8C00;'>{data}</p>" # Show auth errors in orange
914
+
915
+ try:
916
+ # Parse the data
917
+ patients_data = json.loads(data)
918
+ patients = []
919
+
920
+ # Extract patients from bundle
921
+ for entry in patients_data.get("entry", []):
922
+ resource = entry.get("resource", {})
923
+ if resource.get("resourceType") == "Patient":
924
+ patients.append(resource)
925
+
926
+ # Generate HTML card
927
+ html = "<h3 style='color:#00FFFF; text-shadow: 0 0 2px #00FFFF;'>Patients</h3>" # Neon Sub-header
928
+ for patient in patients:
929
+ # Extract name
930
+ name = patient.get("name", [{}])[0]
931
+ given = " ".join(name.get("given", ["Unknown"]))
932
+ family = name.get("family", "Unknown")
933
+
934
+ # Extract other details
935
+ gender = patient.get("gender", "unknown").capitalize()
936
+ birth_date = patient.get("birthDate", "Unknown")
937
+
938
+ # Generate HTML card with cyberpunk styling
939
+ html += f"""
940
+ <div style="border: 1px solid #00FFFF; padding: 10px; margin: 10px 0; border-radius: 5px; background-color: #222; box-shadow: 0 0 5px #00FFFF;">
941
+ <h4 style='color:#00FFFF;'>{given} {family}</h4>
942
+ <p style='color:#A9A9A9;'><strong>Gender:</strong> <span style='color:#00FFFF;'>{gender}</span></p>
943
+ <p style='color:#A9A9A9;'><strong>Birth Date:</strong> <span style='color:#00FFFF;'>{birth_date}</span></p>
944
+ <p style='color:#A9A9A9;'><strong>ID:</strong> <span style='color:#00FFFF;'>{patient.get("id", "Unknown")}</span></p>
945
+ </div>
946
+ """
947
+
948
+ return html
949
+ except Exception as e:
950
+ return f"<p style='color:#FF6347;'>Error parsing patient data: {str(e)}</p>" # Tomato Error
951
+ except Exception as e:
952
+ return f"<p style='color:#FF6347;'>Error fetching patient data: {str(e)}</p>" # Tomato Error
953
+
954
+ refresh_btn.click(fn=update_dashboard, inputs=None, outputs=dashboard_output)
955
+
956
+ with gr.Tab("Discharge Form", elem_classes="cyberpunk-tab"): # Optional: Class for tab styling
957
+ gr.Markdown(
958
+ "<h2 style='color:#00FFFF; text-shadow: 0 0 3px #00FFFF;'>Patient Details</h2>"
959
+ ) # Neon Tab Header
960
+ with gr.Row():
961
+ first_name = gr.Textbox(label="First Name")
962
+ last_name = gr.Textbox(label="Last Name")
963
+ middle_initial = gr.Textbox(label="Middle Initial")
964
+ with gr.Row():
965
+ dob = gr.Textbox(label="Date of Birth")
966
+ age = gr.Textbox(label="Age")
967
+ sex = gr.Textbox(label="Sex")
968
+ address = gr.Textbox(label="Address")
969
+ with gr.Row():
970
+ city = gr.Textbox(label="City")
971
+ state = gr.Textbox(label="State")
972
+ zip_code = gr.Textbox(label="Zip Code")
973
+ gr.Markdown(
974
+ "<h2 style='color:#00FFFF; text-shadow: 0 0 3px #00FFFF;'>Primary Healthcare Professional Details</h2>"
975
+ ) # Neon Sub-header
976
+ with gr.Row():
977
+ doctor_first_name = gr.Textbox(label="Doctor's First Name")
978
+ doctor_last_name = gr.Textbox(label="Doctor's Last Name")
979
+ doctor_middle_initial = gr.Textbox(label="Doctor's Middle Initial")
980
+ hospital_name = gr.Textbox(label="Hospital/Clinic Name")
981
+ doctor_address = gr.Textbox(label="Address")
982
+ with gr.Row():
983
+ doctor_city = gr.Textbox(label="City")
984
+ doctor_state = gr.Textbox(label="State")
985
+ doctor_zip = gr.Textbox(label="Zip Code")
986
+ gr.Markdown(
987
+ "<h2 style='color:#00FFFF; text-shadow: 0 0 3px #00FFFF;'>Admission and Discharge Details</h2>"
988
+ ) # Neon Sub-header
989
+ with gr.Row():
990
+ admission_date = gr.Textbox(label="Date of Admission")
991
+ referral_source = gr.Textbox(label="Source of Referral")
992
+ admission_method = gr.Textbox(label="Method of Admission")
993
+ with gr.Row():
994
+ discharge_date = gr.Textbox(label="Date of Discharge")
995
+ discharge_reason = gr.Radio(
996
+ ["Treated", "Transferred", "Discharge Against Advice", "Patient Died"],
997
+ label="Discharge Reason",
998
+ )
999
+ date_of_death = gr.Textbox(label="Date of Death (if applicable)")
1000
+ gr.Markdown(
1001
+ "<h2 style='color:#00FFFF; text-shadow: 0 0 3px #00FFFF;'>Diagnosis & Procedures</h2>"
1002
+ ) # Neon Sub-header
1003
+ diagnosis = gr.Textbox(label="Diagnosis")
1004
+ procedures = gr.Textbox(label="Operation & Procedures")
1005
+ gr.Markdown(
1006
+ "<h2 style='color:#00FFFF; text-shadow: 0 0 3px #00FFFF;'>Medication Details</h2>"
1007
+ ) # Neon Sub-header
1008
+ medications = gr.Textbox(label="Medication on Discharge")
1009
+ gr.Markdown(
1010
+ "<h2 style='color:#00FFFF; text-shadow: 0 0 3px #00FFFF;'>Prepared By</h2>"
1011
+ ) # Neon Sub-header
1012
+ with gr.Row():
1013
+ preparer_name = gr.Textbox(label="Name")
1014
+ preparer_job_title = gr.Textbox(label="Job Title")
1015
+
1016
+ # Add buttons for both display form and generate PDF
1017
+ with gr.Row():
1018
+ submit_display = gr.Button(
1019
+ "Display Form", elem_classes="cyberpunk-button"
1020
+ ) # Cyberpunk button style
1021
+ submit_pdf = gr.Button(
1022
+ "Generate PDF (No AI)", elem_classes="cyberpunk-button"
1023
+ ) # Renamed button to clarify no AI and styled
1024
+
1025
+ # Output areas
1026
+ form_output = gr.HTML() # Use HTML to render styled form
1027
+ pdf_output = gr.File(
1028
+ label="Download PDF (No AI)"
1029
+ ) # Renamed output to clarify no AI
1030
+
1031
+ # Connect the display form button
1032
+ submit_display.click(
1033
+ display_form,
1034
+ inputs=[
1035
+ first_name,
1036
+ last_name,
1037
+ middle_initial,
1038
+ dob,
1039
+ age,
1040
+ sex,
1041
+ address,
1042
+ city,
1043
+ state,
1044
+ zip_code,
1045
+ doctor_first_name,
1046
+ doctor_last_name,
1047
+ doctor_middle_initial,
1048
+ hospital_name,
1049
+ doctor_address,
1050
+ doctor_city,
1051
+ doctor_state,
1052
+ doctor_zip,
1053
+ admission_date,
1054
+ referral_source,
1055
+ admission_method,
1056
+ discharge_date,
1057
+ discharge_reason,
1058
+ date_of_death,
1059
+ diagnosis,
1060
+ procedures,
1061
+ medications,
1062
+ preparer_name,
1063
+ preparer_job_title,
1064
+ ],
1065
+ outputs=form_output,
1066
+ )
1067
+
1068
+ # Connect the generate PDF button (No AI version)
1069
+ submit_pdf.click(
1070
+ generate_pdf_from_form,
1071
+ inputs=[
1072
+ first_name,
1073
+ last_name,
1074
+ middle_initial,
1075
+ dob,
1076
+ age,
1077
+ sex,
1078
+ address,
1079
+ city,
1080
+ state,
1081
+ zip_code,
1082
+ doctor_first_name,
1083
+ doctor_last_name,
1084
+ doctor_middle_initial,
1085
+ hospital_name,
1086
+ doctor_address,
1087
+ doctor_city,
1088
+ doctor_state,
1089
+ doctor_zip,
1090
+ admission_date,
1091
+ referral_source,
1092
+ admission_method,
1093
+ discharge_date,
1094
+ discharge_reason,
1095
+ date_of_death,
1096
+ diagnosis,
1097
+ procedures,
1098
+ medications,
1099
+ preparer_name,
1100
+ preparer_job_title,
1101
+ ],
1102
+ outputs=pdf_output,
1103
+ )
1104
+
1105
+ with gr.Tab(
1106
+ "Medical File Analysis", elem_classes="cyberpunk-tab"
1107
+ ): # Optional: Class for tab styling
1108
+ gr.Markdown(
1109
+ "<h2 style='color:#00FFFF; text-shadow: 0 0 3px #00FFFF;'>Analyze Medical Files with Discharge Guard AI</h2>"
1110
+ ) # Neon Tab Header
1111
+ with gr.Column():
1112
+ dicom_file = gr.File(
1113
+ file_types=[".dcm"], label="Upload DICOM File (.dcm)"
1114
+ )
1115
+ dicom_ai_output = gr.Textbox(label="DICOM Analysis Report", lines=5)
1116
+ analyze_dicom_button = gr.Button(
1117
+ "Analyze DICOM with AI", elem_classes="cyberpunk-button"
1118
+ ) # Cyberpunk button style
1119
+
1120
+ hl7_file = gr.File(file_types=[".hl7"], label="Upload HL7 File (.hl7)")
1121
+ hl7_ai_output = gr.Textbox(label="HL7 Analysis Report", lines=5)
1122
+ analyze_hl7_button = gr.Button(
1123
+ "Analyze HL7 with AI", elem_classes="cyberpunk-button"
1124
+ ) # Cyberpunk button style
1125
+
1126
+ xml_file = gr.File(file_types=[".xml"], label="Upload XML File (.xml)")
1127
+ xml_ai_output = gr.Textbox(label="XML Analysis Report", lines=5)
1128
+ analyze_xml_button = gr.Button(
1129
+ "Analyze XML with AI", elem_classes="cyberpunk-button"
1130
+ ) # Cyberpunk button style
1131
+
1132
+ ccda_file = gr.File(
1133
+ file_types=[".xml", ".cda", ".ccd"],
1134
+ label="Upload CCDA File (.xml, .cda, .ccd)",
1135
+ )
1136
+ ccda_ai_output = gr.Textbox(label="CCDA Analysis Report", lines=5)
1137
+ analyze_ccda_button = gr.Button(
1138
+ "Analyze CCDA with AI", elem_classes="cyberpunk-button"
1139
+ ) # Cyberpunk button style
1140
+
1141
+ ccd_file = gr.File(
1142
+ file_types=[".ccd"],
1143
+ label="Upload CCD File (.ccd)",
1144
+ ) # Redundant, as CCDA also handles .ccd, but kept for clarity
1145
+ ccd_ai_output = gr.Textbox(
1146
+ label="CCD Analysis Report", lines=5
1147
+ ) # Redundant
1148
+ analyze_ccd_button = gr.Button(
1149
+ "Analyze CCD with AI", elem_classes="cyberpunk-button"
1150
+ ) # Cyberpunk button style # Redundant
1151
+ pdf_file = gr.File(file_types=[".pdf"], label="Upload PDF File (.pdf)")
1152
+ pdf_ai_output = gr.Textbox(label="PDF Analysis Report", lines=5)
1153
+ analyze_pdf_button = gr.Button(
1154
+ "Analyze PDF with AI", elem_classes="cyberpunk-button"
1155
+ ) # Cyberpunk button style
1156
+
1157
+ csv_file = gr.File(file_types=[".csv"], label="Upload CSV File (.csv)")
1158
+ csv_ai_output = gr.Textbox(label="CSV Analysis Report", lines=5)
1159
+ analyze_csv_button = gr.Button(
1160
+ "Analyze CSV with AI", elem_classes="cyberpunk-button"
1161
+ ) # Cyberpunk button style
1162
+
1163
+ # Connect AI Analysis Buttons - using REAL AI functions now
1164
+ analyze_dicom_button.click(
1165
+ analyze_dicom_file_with_ai, # Call REAL AI function
1166
+ inputs=dicom_file,
1167
+ outputs=dicom_ai_output,
1168
+ )
1169
+ analyze_hl7_button.click(
1170
+ analyze_hl7_file_with_ai, # Call REAL AI function
1171
+ inputs=hl7_file,
1172
+ outputs=hl7_ai_output,
1173
+ )
1174
+ analyze_xml_button.click(
1175
+ analyze_cda_xml_file_with_ai, # Call REAL AI function
1176
+ inputs=xml_file,
1177
+ outputs=xml_ai_output,
1178
+ )
1179
+ analyze_ccda_button.click(
1180
+ analyze_cda_xml_file_with_ai, # Call REAL AI function
1181
+ inputs=ccda_file,
1182
+ outputs=ccda_ai_output,
1183
+ )
1184
+ analyze_ccd_button.click( # Redundant button, but kept for UI if needed
1185
+ analyze_cda_xml_file_with_ai, # Call REAL AI function
1186
+ inputs=ccd_file,
1187
+ outputs=ccd_ai_output,
1188
+ )
1189
+ analyze_pdf_button.click(
1190
+ analyze_pdf_file_with_ai, inputs=pdf_file, outputs=pdf_ai_output
1191
+ )
1192
+ analyze_csv_button.click(
1193
+ analyze_csv_file_with_ai, inputs=csv_file, outputs=csv_ai_output
1194
+ )
1195
+
1196
+ with gr.Tab(
1197
+ "One-Click Discharge Paper (AI)", elem_classes="cyberpunk-tab"
1198
+ ): # New Tab for One-Click Discharge Paper with AI, styled
1199
+ gr.Markdown(
1200
+ "<h2 style='color:#00FFFF; text-shadow: 0 0 3px #00FFFF;'>One-Click Medical Discharge Paper Generation with AI Content</h2>"
1201
+ ) # Neon Tab Header
1202
+ one_click_ai_pdf_button = gr.Button(
1203
+ "Generate Discharge Paper with AI (One-Click)",
1204
+ elem_classes="cyberpunk-button",
1205
+ ) # Updated button label and styled
1206
+ one_click_ai_pdf_status = gr.Textbox(
1207
+ label="Discharge Paper Generation Status (AI)"
1208
+ ) # Updated status label
1209
+ one_click_ai_pdf_download = gr.File(
1210
+ label="Download Discharge Paper (AI)"
1211
+ ) # Updated download label
1212
+
1213
+ one_click_ai_pdf_button.click(
1214
+ generate_discharge_paper_one_click, # Use the one-click function that now calls AI
1215
+ inputs=[],
1216
+ outputs=[one_click_ai_pdf_download, one_click_ai_pdf_status],
1217
+ )
1218
+
1219
+ # Connect the patient data buttons
1220
+ patient_data_button.click(
1221
+ fn=CALLBACK_MANAGER.get_patient_data, inputs=None, outputs=patient_data_output
1222
+ )
1223
+
1224
+ # Connect refresh button to update dashboard
1225
+ refresh_btn.click(fn=update_dashboard, inputs=None, outputs=dashboard_output)
1226
+
1227
+ # Corrected the button click function name here to `generate_pdf_from_meldrx` (No AI PDF)
1228
+ meldrx_pdf_button.click(
1229
+ fn=generate_pdf_from_meldrx,
1230
+ inputs=patient_data_output,
1231
+ outputs=[meldrx_pdf_download, meldrx_pdf_status],
1232
+ )
1233
+
1234
+ # Connect patient data updates to dashboard
1235
+ patient_data_button.click(
1236
+ fn=update_dashboard, inputs=None, outputs=dashboard_output
1237
+ )
1238
+
1239
+
1240
+ # Launch with sharing enabled for public access
1241
+ demo.launch(ssr_mode=False)