Tonic commited on
Commit
219df52
·
unverified ·
1 Parent(s): 3f697ea

dockerize and add flask app

Browse files
.dockerignore ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ __pycache__
2
+ *.pyc
3
+ *.pyo
4
+ *.pyd
5
+ .Python
6
+ env/
7
+ venv/
8
+ .env
9
+ *.log
10
+ .git
11
+ .gitignore
app.py CHANGED
@@ -1,405 +1,138 @@
1
- import gradio as gr
2
- from utils.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
  from utils.callbackmanager import CallbackManager
 
 
 
 
12
  from utils.meldrx import MeldRxAPI
13
- from utils.prompts import system_instructions
14
- from old.extractcode import extract_code_from_url
15
- from utils.generators import generate_pdf_from_meldrx, generate_ai_discharge_content, generate_pdf_from_meldrx_with_ai_content, extract_section, 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
16
  # Set up logging
17
  logging.basicConfig(level=logging.INFO)
18
  logger = logging.getLogger(__name__)
19
 
20
- # Import PDF utilities
21
- from utils.pdfutils import PDFGenerator, generate_discharge_summary
22
- from utils.oneclick import generate_discharge_paper_one_click
23
- # Import necessary libraries for new file types and AI analysis functions
24
- import pydicom # For DICOM
25
- import hl7 # For HL7
26
- from xml.etree import ElementTree # For XML and CCDA
27
- from pypdf import PdfReader # For PDF
28
- import csv # For CSV
29
- import io # For IO operations
30
- from PIL import Image # For image handling
31
- from utils.callbackmanager import extract_auth_code_from_url, extract_code_from_url
32
- from utils.generators import generate_pdf_from_meldrx, generate_ai_discharge_content, generate_pdf_from_meldrx_with_ai_content, extract_section, 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_ai_discharge_content , extract_section , generate_pdf_from_meldrx_with_ai_content
33
-
34
-
35
- # Initialize Inference Client - Ensure YOUR_HF_TOKEN is set in environment variables or replace with your actual token
36
- HF_TOKEN = os.getenv("HF_TOKEN") # Or replace with your actual token string
37
- if not HF_TOKEN:
38
- raise ValueError(
39
- "HF_TOKEN environment variable not set. Please set your Hugging Face API token."
40
- )
41
- client = InferenceClient(api_key=HF_TOKEN)
42
- model_name = "meta-llama/Llama-3.3-70B-Instruct" # Specify the model to use
43
 
 
 
 
 
 
44
 
45
- 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,):
46
- form = f"""
 
 
 
 
 
47
  <div style='color:#00FFFF; font-family: monospace;'>
48
- **Patient Discharge Form** <br>
49
- - Name: {first_name} {middle_initial} {last_name} <br>
50
- - Date of Birth: {dob}, Age: {age}, Sex: {sex} <br>
51
- - Address: {address}, {city}, {state}, {zip_code} <br>
52
- - Doctor: {doctor_first_name} {doctor_middle_initial} {doctor_last_name} <br>
53
- - Hospital/Clinic: {hospital_name} <br>
54
- - Doctor Address: {doctor_address}, {doctor_city}, {doctor_state}, {doctor_zip} <br>
55
- - Admission Date: {admission_date}, Source: {referral_source}, Method: {admission_method} <br>
56
- - Discharge Date: {discharge_date}, Reason: {discharge_reason} <br>
57
- - Date of Death: {date_of_death} <br>
58
- - Diagnosis: {diagnosis} <br>
59
- - Procedures: {procedures} <br>
60
- - Medications: {medications} <br>
61
  - Prepared By: {preparer_name}, {preparer_job_title}
62
  </div>
63
  """
64
- return form
65
-
66
-
67
-
68
- CALLBACK_MANAGER = CallbackManager(
69
- redirect_uri="https://multitransformer-discharge-guard.hf.space",
70
- client_secret=None,
71
- )
72
 
73
-
74
- # Define the cyberpunk theme - using a dark base and neon accents
75
- cyberpunk_theme = gr.themes.Monochrome(
76
- primary_hue="cyan",
77
- secondary_hue="pink",
78
- neutral_hue="slate",
79
- font=["Source Code Pro", "monospace"], # Retro monospace font
80
- font_mono=["Source Code Pro", "monospace"]
81
- )
82
-
83
- # Create the UI with the cyberpunk theme
84
- with gr.Blocks(theme=cyberpunk_theme) as demo: # Apply the theme here
85
- 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
86
-
87
- with gr.Tab("Authenticate with MeldRx", elem_classes="cyberpunk-tab"): # Optional: Class for tab styling
88
- gr.Markdown("<h2 style='color:#00FFFF; text-shadow: 0 0 3px #00FFFF;'>SMART on FHIR Authentication</h2>") # Neon Tab Header
89
- auth_url_output = gr.Textbox(label="Authorization URL", value=CALLBACK_MANAGER.get_auth_url(), interactive=False)
90
- 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
91
- redirected_url_input = gr.Textbox(label="Redirected URL") # New textbox for redirected URL
92
- extract_code_button = gr.Button("Extract Authorization Code", elem_classes="cyberpunk-button") # Cyberpunk button style
93
- extracted_code_output = gr.Textbox(label="Extracted Authorization Code", interactive=False) # Textbox to show extracted code
94
-
95
- auth_code_input = gr.Textbox(label="Authorization Code (from above, or paste manually if extraction fails)", interactive=True) # Updated label to be clearer
96
- auth_submit = gr.Button("Submit Code for Authentication", elem_classes="cyberpunk-button") # Cyberpunk button style
97
- auth_result = gr.HTML(label="Authentication Result") # Use HTML for styled result
98
-
99
- patient_data_button = gr.Button("Fetch Patient Data", elem_classes="cyberpunk-button") # Cyberpunk button style
100
- patient_data_output = gr.Textbox(label="Patient Data", lines=10)
101
-
102
- # Add button to generate PDF from MeldRx data (No AI)
103
- meldrx_pdf_button = gr.Button("Generate PDF from MeldRx Data (No AI)", elem_classes="cyberpunk-button") # Renamed button
104
- meldrx_pdf_status = gr.Textbox(label="PDF Generation Status (No AI)") # Renamed status
105
- meldrx_pdf_download = gr.File(label="Download Generated PDF (No AI)") # Renamed download
106
-
107
- def process_redirected_url(redirected_url):
108
- """Processes the redirected URL to extract and display the authorization code."""
109
- auth_code, error_message = extract_auth_code_from_url(redirected_url)
110
- if auth_code:
111
- return auth_code, "<span style='color:#00FF7F;'>Authorization code extracted!</span>" # Neon Green Success
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  else:
113
- return "", f"<span style='color:#FF4500;'>Could not extract authorization code.</span> {error_message or ''}" # Neon Orange Error
114
-
115
-
116
- extract_code_button.click(
117
- fn=process_redirected_url,
118
- inputs=redirected_url_input,
119
- outputs=[extracted_code_output, auth_result],# Reusing auth_result for extraction status
120
- )
121
-
122
- auth_submit.click(
123
- fn=CALLBACK_MANAGER.set_auth_code,
124
- inputs=extracted_code_output, # Using extracted code as input for authentication
125
- outputs=auth_result,
126
- )
127
-
128
- with gr.Tab("Patient Dashboard", elem_classes="cyberpunk-tab"): # Optional: Class for tab styling
129
- gr.Markdown("<h2 style='color:#00FFFF; text-shadow: 0 0 3px #00FFFF;'>Patient Data</h2>") # Neon Tab Header
130
- dashboard_output = gr.HTML("<p style='color:#A9A9A9;'>Fetch patient data from the Authentication tab first.</p>") # Subdued placeholder text
131
-
132
- refresh_btn = gr.Button("Refresh Data", elem_classes="cyberpunk-button") # Cyberpunk button style
133
-
134
- # Simple function to update dashboard based on fetched data
135
- def update_dashboard():
136
- try:
137
- data = CALLBACK_MANAGER.get_patient_data()
138
- if (
139
- data.startswith("<span style='color:#FF8C00;'>Not authenticated")
140
- or data.startswith("<span style='color:#DC143C;'>Failed")
141
- or data.startswith("<span style='color:#FF6347;'>Error")
142
- ):
143
- return f"<p style='color:#FF8C00;'>{data}</p>" # Show auth errors in orange
144
-
145
- try:
146
- # Parse the data
147
- patients_data = json.loads(data)
148
- patients = []
149
-
150
- # Extract patients from bundle
151
- for entry in patients_data.get("entry", []):
152
- resource = entry.get("resource", {})
153
- if resource.get("resourceType") == "Patient":
154
- patients.append(resource)
155
-
156
- # Generate HTML card
157
- html = "<h3 style='color:#00FFFF; text-shadow: 0 0 2px #00FFFF;'>Patients</h3>" # Neon Sub-header
158
- for patient in patients:
159
- # Extract name
160
- name = patient.get("name", [{}])[0]
161
- given = " ".join(name.get("given", ["Unknown"]))
162
- family = name.get("family", "Unknown")
163
-
164
- # Extract other details
165
- gender = patient.get("gender", "unknown").capitalize()
166
- birth_date = patient.get("birthDate", "Unknown")
167
-
168
- # Generate HTML card with cyberpunk styling
169
- html += f"""
170
- <div style="border: 1px solid #00FFFF; padding: 10px; margin: 10px 0; border-radius: 5px; background-color: #222; box-shadow: 0 0 5px #00FFFF;">
171
- <h4 style='color:#00FFFF;'>{given} {family}</h4>
172
- <p style='color:#A9A9A9;'><strong>Gender:</strong> <span style='color:#00FFFF;'>{gender}</span></p>
173
- <p style='color:#A9A9A9;'><strong>Birth Date:</strong> <span style='color:#00FFFF;'>{birth_date}</span></p>
174
- <p style='color:#A9A9A9;'><strong>ID:</strong> <span style='color:#00FFFF;'>{patient.get("id", "Unknown")}</span></p>
175
- </div>
176
- """
177
-
178
- return html
179
- except Exception as e:
180
- return f"<p style='color:#FF6347;'>Error parsing patient data: {str(e)}</p>" # Tomato Error
181
- except Exception as e:
182
- return f"<p style='color:#FF6347;'>Error fetching patient data: {str(e)}</p>" # Tomato Error
183
-
184
-
185
- with gr.Tab("Discharge Form", elem_classes="cyberpunk-tab"): # Optional: Class for tab styling
186
- gr.Markdown("<h2 style='color:#00FFFF; text-shadow: 0 0 3px #00FFFF;'>Patient Details</h2>") # Neon Tab Header
187
- with gr.Row():
188
- first_name = gr.Textbox(label="First Name")
189
- last_name = gr.Textbox(label="Last Name")
190
- middle_initial = gr.Textbox(label="Middle Initial")
191
- with gr.Row():
192
- dob = gr.Textbox(label="Date of Birth")
193
- age = gr.Textbox(label="Age")
194
- sex = gr.Textbox(label="Sex")
195
- address = gr.Textbox(label="Address")
196
- with gr.Row():
197
- city = gr.Textbox(label="City")
198
- state = gr.Textbox(label="State")
199
- zip_code = gr.Textbox(label="Zip Code")
200
- gr.Markdown("<h2 style='color:#00FFFF; text-shadow: 0 0 3px #00FFFF;'>Primary Healthcare Professional Details</h2>") # Neon Sub-header
201
- with gr.Row():
202
- doctor_first_name = gr.Textbox(label="Doctor's First Name")
203
- doctor_last_name = gr.Textbox(label="Doctor's Last Name")
204
- doctor_middle_initial = gr.Textbox(label="Doctor's Middle Initial")
205
- hospital_name = gr.Textbox(label="Hospital/Clinic Name")
206
- doctor_address = gr.Textbox(label="Address")
207
- with gr.Row():
208
- doctor_city = gr.Textbox(label="City")
209
- doctor_state = gr.Textbox(label="State")
210
- doctor_zip = gr.Textbox(label="Zip Code")
211
- gr.Markdown("<h2 style='color:#00FFFF; text-shadow: 0 0 3px #00FFFF;'>Admission and Discharge Details</h2>") # Neon Sub-header
212
- with gr.Row():
213
- admission_date = gr.Textbox(label="Date of Admission")
214
- referral_source = gr.Textbox(label="Source of Referral")
215
- admission_method = gr.Textbox(label="Method of Admission")
216
- with gr.Row():
217
- discharge_date = gr.Textbox(label="Date of Discharge")
218
- discharge_reason = gr.Radio(
219
- ["Treated", "Transferred", "Discharge Against Advice", "Patient Died"],
220
- label="Discharge Reason",
221
- )
222
- date_of_death = gr.Textbox(label="Date of Death (if applicable)")
223
- gr.Markdown("<h2 style='color:#00FFFF; text-shadow: 0 0 3px #00FFFF;'>Diagnosis & Procedures</h2>") # Neon Sub-header
224
- diagnosis = gr.Textbox(label="Diagnosis")
225
- procedures = gr.Textbox(label="Operation & Procedures")
226
- gr.Markdown("<h2 style='color:#00FFFF; text-shadow: 0 0 3px #00FFFF;'>Medication Details</h2>") # Neon Sub-header
227
- medications = gr.Textbox(label="Medication on Discharge")
228
- gr.Markdown("<h2 style='color:#00FFFF; text-shadow: 0 0 3px #00FFFF;'>Prepared By</h2>") # Neon Sub-header
229
- with gr.Row():
230
- preparer_name = gr.Textbox(label="Name")
231
- preparer_job_title = gr.Textbox(label="Job Title")
232
-
233
- # Add buttons for both display form and generate PDF
234
- with gr.Row():
235
- submit_display = gr.Button("Display Form", elem_classes="cyberpunk-button") # Cyberpunk button style
236
- submit_pdf = gr.Button("Generate PDF (No AI)", elem_classes="cyberpunk-button") # Renamed button to clarify no AI and styled
237
-
238
- # Output areas
239
- form_output = gr.HTML() # Use HTML to render styled form
240
- pdf_output = gr.File(label="Download PDF (No AI)") # Renamed output to clarify no AI
241
-
242
- # Connect the display form button
243
- submit_display.click(
244
- display_form,
245
- 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,],
246
- outputs=form_output
247
- )
248
-
249
- # Connect the generate PDF button (No AI version)
250
- submit_pdf.click(
251
- generate_pdf_from_form,
252
- 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,],
253
- outputs=pdf_output
254
- )
255
-
256
- with gr.Tab("Medical File Analysis", elem_classes="cyberpunk-tab"): # Optional: Class for tab styling
257
- gr.Markdown("<h2 style='color:#00FFFF; text-shadow: 0 0 3px #00FFFF;'>Analyze Medical Files with Discharge Guard AI</h2>") # Neon Tab Header
258
- with gr.Column():
259
- dicom_file = gr.File(
260
- file_types=[".dcm"], label="Upload DICOM File (.dcm)"
261
- )
262
- dicom_ai_output = gr.Textbox(label="DICOM Analysis Report", lines=5)
263
- analyze_dicom_button = gr.Button("Analyze DICOM with AI", elem_classes="cyberpunk-button") # Cyberpunk button style
264
-
265
- hl7_file = gr.File(
266
- file_types=[".hl7"], label="Upload HL7 File (.hl7)"
267
- )
268
- hl7_ai_output = gr.Textbox(label="HL7 Analysis Report", lines=5)
269
- analyze_hl7_button = gr.Button("Analyze HL7 with AI", elem_classes="cyberpunk-button") # Cyberpunk button style
270
-
271
- xml_file = gr.File(
272
- file_types=[".xml"], label="Upload XML File (.xml)"
273
- )
274
- xml_ai_output = gr.Textbox(label="XML Analysis Report", lines=5)
275
- analyze_xml_button = gr.Button("Analyze XML with AI", elem_classes="cyberpunk-button") # Cyberpunk button style
276
-
277
- ccda_file = gr.File(
278
- file_types=[".xml", ".cda", ".ccd"], label="Upload CCDA File (.xml, .cda, .ccd)"
279
- )
280
- ccda_ai_output = gr.Textbox(label="CCDA Analysis Report", lines=5)
281
- analyze_ccda_button = gr.Button("Analyze CCDA with AI", elem_classes="cyberpunk-button") # Cyberpunk button style
282
-
283
- ccd_file = gr.File(
284
- file_types=[".ccd"],
285
- label="Upload CCD File (.ccd)",
286
- ) # Redundant, as CCDA also handles .ccd, but kept for clarity
287
- ccd_ai_output = gr.Textbox(
288
- label="CCD Analysis Report", lines=5
289
- ) # Redundant
290
- analyze_ccd_button = gr.Button("Analyze CCD with AI", elem_classes="cyberpunk-button") # Cyberpunk button style # Redundant
291
- pdf_file = gr.File(
292
- file_types=[".pdf"], label="Upload PDF File (.pdf)"
293
- )
294
- pdf_ai_output = gr.Textbox(label="PDF Analysis Report", lines=5)
295
- analyze_pdf_button = gr.Button("Analyze PDF with AI", elem_classes="cyberpunk-button") # Cyberpunk button style
296
-
297
- csv_file = gr.File(
298
- file_types=[".csv"], label="Upload CSV File (.csv)"
299
- )
300
- csv_ai_output = gr.Textbox(label="CSV Analysis Report", lines=5)
301
- analyze_csv_button = gr.Button("Analyze CSV with AI", elem_classes="cyberpunk-button") # Cyberpunk button style
302
-
303
- # Connect AI Analysis Buttons - using REAL AI functions now
304
- analyze_dicom_button.click(
305
- analyze_dicom_file_with_ai, # Call REAL AI function
306
- inputs=dicom_file,
307
- outputs=dicom_ai_output
308
- )
309
- analyze_hl7_button.click(
310
- analyze_hl7_file_with_ai, # Call REAL AI function
311
- inputs=hl7_file,
312
- outputs=hl7_ai_output
313
- )
314
- analyze_xml_button.click(
315
- analyze_cda_xml_file_with_ai, # Call REAL AI function
316
- inputs=xml_file,
317
- outputs=xml_ai_output
318
- )
319
- analyze_ccda_button.click(
320
- analyze_cda_xml_file_with_ai, # Call REAL AI function
321
- inputs=ccda_file,
322
- outputs=ccda_ai_output
323
- )
324
- analyze_ccd_button.click( # Redundant button, but kept for UI if needed
325
- analyze_cda_xml_file_with_ai, # Call REAL AI function
326
- inputs=ccd_file,
327
- outputs=ccd_ai_output
328
- )
329
- analyze_pdf_button.click(
330
- analyze_pdf_file_with_ai, inputs=pdf_file, outputs=pdf_ai_output
331
- )
332
- analyze_csv_button.click(
333
- analyze_csv_file_with_ai, inputs=csv_file, outputs=csv_ai_output
334
- )
335
-
336
- with gr.Tab("One-Click Discharge Paper (AI)", elem_classes="cyberpunk-tab"):
337
- gr.Markdown("<h2 style='color:#00FFFF; text-shadow: 0 0 3px #00FFFF;'>One-Click Medical Discharge Summary with AI Content</h2>")
338
- with gr.Row():
339
- patient_id_input = gr.Textbox(label="Patient ID (Optional)", placeholder="Enter Patient ID")
340
- first_name_input = gr.Textbox(label="First Name (Optional)", placeholder="Enter First Name")
341
- last_name_input = gr.Textbox(label="Last Name (Optional)", placeholder="Enter Last Name")
342
- one_click_ai_button = gr.Button("Generate Discharge Summary with AI (One-Click)", elem_classes="cyberpunk-button")
343
- one_click_ai_output = gr.Textbox(label="AI-Generated Discharge Summary", lines=20, placeholder="Discharge summary will appear here...")
344
-
345
- # Initialize MeldRxAPI (ensure client_id, client_secret, workspace_id are set in environment variables)
346
- client_id = os.getenv("APPID")
347
- client_secret = os.getenv("CLIENT_SECRET")
348
- workspace_id = os.getenv("WORKSPACE_URL")
349
- redirect_uri = "https://multitransformer-discharge-guard.hf.space"
350
- meldrx_api = MeldRxAPI(client_id, client_secret, workspace_id, redirect_uri)
351
-
352
- one_click_ai_button.click(
353
- fn=lambda pid, fname, lname: generate_discharge_paper_one_click(meldrx_api, pid, fname, lname),
354
- inputs=[patient_id_input, first_name_input, last_name_input],
355
- outputs=one_click_ai_output,
356
- )
357
- # Use the global CALLBACK_MANAGER instead of creating a new MeldRxAPI instance
358
- def one_click_handler(patient_id, first_name, last_name):
359
- try:
360
- # Check if CALLBACK_MANAGER is authenticated
361
- if not CALLBACK_MANAGER.access_token:
362
- return None, "Error: Not authenticated. Please authenticate in the 'Authenticate with MeldRx' tab first."
363
-
364
- # Call the one-click function with the existing authenticated CALLBACK_MANAGER.api
365
- pdf_path, status = generate_discharge_paper_one_click(
366
- CALLBACK_MANAGER.api, patient_id, first_name, last_name
367
- )
368
- return pdf_path, status
369
- except Exception as e:
370
- logger.error(f"One-click handler error: {str(e)}")
371
- return None, f"Error: {str(e)}"
372
-
373
- # one_click_ai_button.click(
374
- # fn=lambda pid, fname, lname: generate_discharge_paper_one_click(meldrx_api, pid, fname, lname),
375
- # inputs=[patient_id_input, first_name_input, last_name_input],
376
- # outputs=one_click_ai_output,
377
- # )
378
-
379
-
380
- # Connect the patient data buttons
381
- patient_data_button.click(
382
- fn=CALLBACK_MANAGER.get_patient_data,
383
- inputs=None,
384
- outputs=patient_data_output
385
- )
386
-
387
- # Connect refresh button to update dashboard
388
- refresh_btn.click(
389
- fn=update_dashboard, inputs=None, outputs=dashboard_output
390
- )
391
-
392
- # Corrected the button click function name here to `generate_pdf_from_meldrx` (No AI PDF)
393
- meldrx_pdf_button.click(
394
- fn=generate_pdf_from_meldrx,
395
- inputs=patient_data_output,
396
- outputs=[meldrx_pdf_download, meldrx_pdf_status]
397
- )
398
-
399
- # Connect patient data updates to dashboard
400
- patient_data_button.click(
401
- fn=update_dashboard, inputs=None, outputs=dashboard_output
402
- )
403
-
404
- # Launch with sharing enabled for public access
405
- demo.launch(ssr_mode=False)
 
1
+ from flask import Flask, render_template, request, send_file, jsonify, Response
 
 
2
  import os
3
+ import json
 
 
4
  import logging
 
 
5
  from utils.callbackmanager import CallbackManager
6
+ from utils.generators import (generate_pdf_from_form, generate_pdf_from_meldrx,
7
+ analyze_dicom_file_with_ai, analyze_hl7_file_with_ai,
8
+ analyze_cda_xml_file_with_ai, analyze_pdf_file_with_ai,
9
+ analyze_csv_file_with_ai, generate_discharge_paper_one_click)
10
  from utils.meldrx import MeldRxAPI
11
+
 
 
12
  # Set up logging
13
  logging.basicConfig(level=logging.INFO)
14
  logger = logging.getLogger(__name__)
15
 
16
+ app = Flask(__name__)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
 
18
+ redirect_uri = os.getenv("REDIRECT_URI", "http://localhost:5000/auth/callback")
19
+ CALLBACK_MANAGER = CallbackManager(
20
+ redirect_uri=redirect_uri,
21
+ client_secret=None,
22
+ )
23
 
24
+ # Mock display_form function for HTML rendering
25
+ def display_form(first_name, last_name, middle_initial, dob, age, sex, address, city, state, zip_code,
26
+ doctor_first_name, doctor_last_name, doctor_middle_initial, hospital_name, doctor_address,
27
+ doctor_city, doctor_state, doctor_zip, admission_date, referral_source, admission_method,
28
+ discharge_date, discharge_reason, date_of_death, diagnosis, procedures, medications,
29
+ preparer_name, preparer_job_title):
30
+ return f"""
31
  <div style='color:#00FFFF; font-family: monospace;'>
32
+ <strong>Patient Discharge Form</strong><br>
33
+ - Name: {first_name} {middle_initial} {last_name}<br>
34
+ - Date of Birth: {dob}, Age: {age}, Sex: {sex}<br>
35
+ - Address: {address}, {city}, {state}, {zip_code}<br>
36
+ - Doctor: {doctor_first_name} {doctor_middle_initial} {doctor_last_name}<br>
37
+ - Hospital/Clinic: {hospital_name}<br>
38
+ - Doctor Address: {doctor_address}, {doctor_city}, {doctor_state}, {doctor_zip}<br>
39
+ - Admission Date: {admission_date}, Source: {referral_source}, Method: {admission_method}<br>
40
+ - Discharge Date: {discharge_date}, Reason: {discharge_reason}<br>
41
+ - Date of Death: {date_of_death}<br>
42
+ - Diagnosis: {diagnosis}<br>
43
+ - Procedures: {procedures}<br>
44
+ - Medications: {medications}<br>
45
  - Prepared By: {preparer_name}, {preparer_job_title}
46
  </div>
47
  """
 
 
 
 
 
 
 
 
48
 
49
+ @app.route('/')
50
+ def index():
51
+ return render_template('index.html')
52
+
53
+ @app.route('/auth', methods=['GET', 'POST'])
54
+ def auth():
55
+ auth_url = CALLBACK_MANAGER.get_auth_url()
56
+ if request.method == 'POST':
57
+ redirected_url = request.form.get('redirected_url')
58
+ if redirected_url:
59
+ auth_code, error = CALLBACK_MANAGER.handle_callback(redirected_url)
60
+ result = CALLBACK_MANAGER.set_auth_code(auth_code) if auth_code else f"<span style='color:#FF4500;'>{error}</span>"
61
+ return render_template('auth.html', auth_url=auth_url, auth_result=result)
62
+ auth_code = request.form.get('auth_code')
63
+ if auth_code:
64
+ result = CALLBACK_MANAGER.set_auth_code(auth_code)
65
+ return render_template('auth.html', auth_url=auth_url, auth_result=result)
66
+ return render_template('auth.html', auth_url=auth_url)
67
+
68
+ @app.route('/auth/patient-data', methods=['GET'])
69
+ def patient_data():
70
+ data = CALLBACK_MANAGER.get_patient_data()
71
+ return jsonify(json.loads(data) if data and not data.startswith('<span') else {"error": data})
72
+
73
+ @app.route('/auth/pdf', methods=['GET'])
74
+ def generate_meldrx_pdf():
75
+ patient_data = CALLBACK_MANAGER.get_patient_data()
76
+ pdf_path = generate_pdf_from_meldrx(patient_data)
77
+ return send_file(pdf_path, as_attachment=True, download_name="meldrx_patient_data.pdf")
78
+
79
+ @app.route('/dashboard', methods=['GET'])
80
+ def dashboard():
81
+ data = CALLBACK_MANAGER.get_patient_data()
82
+ if data.startswith('<span'):
83
+ return render_template('dashboard.html', error=data)
84
+ patients_data = json.loads(data)
85
+ patients = [entry['resource'] for entry in patients_data.get('entry', []) if entry['resource'].get('resourceType') == 'Patient']
86
+ return render_template('dashboard.html', patients=patients)
87
+
88
+ @app.route('/form', methods=['GET', 'POST'])
89
+ def discharge_form():
90
+ if request.method == 'POST':
91
+ form_data = request.form.to_dict()
92
+ if 'display' in request.form:
93
+ html_form = display_form(**form_data)
94
+ return render_template('form.html', form_output=html_form)
95
+ elif 'generate_pdf' in request.form:
96
+ pdf_path = generate_pdf_from_form(**form_data)
97
+ return send_file(pdf_path, as_attachment=True, download_name="discharge_form.pdf")
98
+ return render_template('form.html')
99
+
100
+ @app.route('/analysis', methods=['GET', 'POST'])
101
+ def file_analysis():
102
+ if request.method == 'POST':
103
+ file = request.files.get('file')
104
+ file_type = request.form.get('file_type')
105
+ if file:
106
+ file_path = os.path.join('/tmp', file.filename)
107
+ file.save(file_path)
108
+ if file_type == 'dicom':
109
+ result = analyze_dicom_file_with_ai(file_path)
110
+ elif file_type == 'hl7':
111
+ result = analyze_hl7_file_with_ai(file_path)
112
+ elif file_type == 'xml' or file_type == 'ccda' or file_type == 'ccd':
113
+ result = analyze_cda_xml_file_with_ai(file_path)
114
+ elif file_type == 'pdf':
115
+ result = analyze_pdf_file_with_ai(file_path)
116
+ elif file_type == 'csv':
117
+ result = analyze_csv_file_with_ai(file_path)
118
  else:
119
+ result = "Unsupported file type"
120
+ os.remove(file_path)
121
+ return render_template('analysis.html', result=result, file_type=file_type)
122
+ return render_template('analysis.html')
123
+
124
+ @app.route('/oneclick', methods=['GET', 'POST'])
125
+ def one_click():
126
+ if request.method == 'POST':
127
+ patient_id = request.form.get('patient_id', '')
128
+ first_name = request.form.get('first_name', '')
129
+ last_name = request.form.get('last_name', '')
130
+ pdf_path, status = generate_discharge_paper_one_click(CALLBACK_MANAGER.api, patient_id, first_name, last_name)
131
+ if pdf_path:
132
+ return send_file(pdf_path, as_attachment=True, download_name="discharge_summary.pdf")
133
+ return render_template('oneclick.html', status=status)
134
+ return render_template('oneclick.html')
135
+
136
+ if __name__ == '__main__':
137
+ port = int(os.getenv("PORT", 5000)) # Default to 5000, override with PORT env var
138
+ app.run(debug=False, host='0.0.0.0', port=port)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dockerfile ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use an official Python runtime as a parent image
2
+ FROM python:3.11-slim
3
+
4
+ # Set working directory in the container
5
+ WORKDIR /app
6
+
7
+ # Copy requirements file
8
+ COPY requirements.txt .
9
+
10
+ # Install system dependencies and Python packages
11
+ RUN apt-get update && apt-get install -y \
12
+ gcc \
13
+ && rm -rf /var/lib/apt/lists/* \
14
+ && pip install --no-cache-dir -r requirements.txt
15
+
16
+ # Copy the entire application code
17
+ COPY . .
18
+
19
+ # Expose the port Flask will run on
20
+ EXPOSE 5000
21
+
22
+ EXPOSE 7860
23
+
24
+ # Set environment variables (optional, can be overridden in Hugging Face Spaces)
25
+ ENV FLASK_ENV=production
26
+ ENV PYTHONUNBUFFERED=1
27
+
28
+ # Command to run the Flask app
29
+ CMD ["python", "app.py"]
requirements.txt CHANGED
@@ -7,4 +7,7 @@ scikit-learn
7
  python-dotenv
8
  pydicom
9
  hl7
10
- pypdf
 
 
 
 
7
  python-dotenv
8
  pydicom
9
  hl7
10
+ pypdf
11
+ flask
12
+ gradio
13
+ huggingface_hub
static/style.css ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ body {
2
+ background-color: #1a1a1a;
3
+ color: #A9A9A9;
4
+ font-family: 'Source Code Pro', monospace;
5
+ margin: 0;
6
+ padding: 0;
7
+ }
8
+
9
+ header {
10
+ background-color: #222;
11
+ padding: 10px 20px;
12
+ box-shadow: 0 0 10px #00FFFF;
13
+ }
14
+
15
+ h1 {
16
+ color: #00FFFF;
17
+ text-shadow: 0 0 5px #00FFFF;
18
+ margin: 0;
19
+ display: inline-block;
20
+ }
21
+
22
+ h1 span {
23
+ color: #FF00FF;
24
+ text-shadow: 0 0 5px #FF00FF;
25
+ }
26
+
27
+ nav {
28
+ float: right;
29
+ }
30
+
31
+ nav a {
32
+ color: #00FFFF;
33
+ text-decoration: none;
34
+ margin-left: 15px;
35
+ text-shadow: 0 0 3px #00FFFF;
36
+ }
37
+
38
+ nav a:hover {
39
+ color: #FF00FF;
40
+ text-shadow: 0 0 5px #FF00FF;
41
+ }
42
+
43
+ main {
44
+ padding: 20px;
45
+ }
46
+
47
+ h2, h3 {
48
+ color: #00FFFF;
49
+ text-shadow: 0 0 3px #00FFFF;
50
+ }
51
+
52
+ form {
53
+ margin: 20px 0;
54
+ }
55
+
56
+ input, textarea, select {
57
+ background-color: #333;
58
+ border: 1px solid #00FFFF;
59
+ color: #00FFFF;
60
+ padding: 5px;
61
+ margin: 5px 0;
62
+ font-family: 'Source Code Pro', monospace;
63
+ box-shadow: 0 0 3px #00FFFF;
64
+ }
65
+
66
+ input[type="submit"], .cyberpunk-button {
67
+ background-color: #00FFFF;
68
+ color: #1a1a1a;
69
+ border: none;
70
+ padding: 10px 20px;
71
+ cursor: pointer;
72
+ text-shadow: 0 0 3px #1a1a1a;
73
+ box-shadow: 0 0 5px #00FFFF;
74
+ }
75
+
76
+ input[type="submit"]:hover, .cyberpunk-button:hover {
77
+ background-color: #FF00FF;
78
+ color: #1a1a1a;
79
+ box-shadow: 0 0 5px #FF00FF;
80
+ }
81
+
82
+ .patient-card {
83
+ border: 1px solid #00FFFF;
84
+ padding: 10px;
85
+ margin: 10px 0;
86
+ border-radius: 5px;
87
+ background-color: #222;
88
+ box-shadow: 0 0 5px #00FFFF;
89
+ }
90
+
91
+ .patient-card h4 {
92
+ color: #00FFFF;
93
+ margin: 0 0 5px 0;
94
+ }
95
+
96
+ a.cyberpunk-button {
97
+ display: inline-block;
98
+ text-decoration: none;
99
+ margin: 10px 0;
100
+ }
templates/analysis.html ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+ {% block content %}
3
+ <h2>Analyze Medical Files</h2>
4
+ <form method="POST" enctype="multipart/form-data">
5
+ <input type="file" name="file" required><br>
6
+ <select name="file_type" required>
7
+ <option value="dicom">DICOM (.dcm)</option>
8
+ <option value="hl7">HL7 (.hl7)</option>
9
+ <option value="xml">XML (.xml)</option>
10
+ <option value="ccda">CCDA (.xml, .cda, .ccd)</option>
11
+ <option value="ccd">CCD (.ccd)</option>
12
+ <option value="pdf">PDF (.pdf)</option>
13
+ <option value="csv">CSV (.csv)</option>
14
+ </select><br>
15
+ <input type="submit" value="Analyze with AI" class="cyberpunk-button">
16
+ </form>
17
+ {% if result %}
18
+ <h3>Analysis Result ({{ file_type | capitalize }})</h3>
19
+ <div>{{ result }}</div>
20
+ {% endif %}
21
+ {% endblock %}
templates/auth.html ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+ {% block content %}
3
+ <h2>SMART on FHIR Authentication</h2>
4
+ <p>Authorization URL: <a href="{{ auth_url }}">{{ auth_url }}</a></p>
5
+ <form method="POST">
6
+ <label>Redirected URL:</label><br>
7
+ <input type="text" name="redirected_url"><br>
8
+ <input type="submit" value="Extract Code" class="cyberpunk-button">
9
+ </form>
10
+ <form method="POST">
11
+ <label>Authorization Code:</label><br>
12
+ <input type="text" name="auth_code"><br>
13
+ <input type="submit" value="Submit Code" class="cyberpunk-button">
14
+ </form>
15
+ {% if auth_result %}
16
+ <div>{{ auth_result | safe }}</div>
17
+ {% endif %}
18
+ <a href="/auth/patient-data" class="cyberpunk-button">Fetch Patient Data</a>
19
+ <a href="/auth/pdf" class="cyberpunk-button">Generate PDF (No AI)</a>
20
+ {% endblock %}
templates/base.html ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Discharge Guard Cyber</title>
7
+ <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
8
+ </head>
9
+ <body>
10
+ <header>
11
+ <h1>Discharge Guard <span>Cyber</span></h1>
12
+ <nav>
13
+ <a href="/">Home</a>
14
+ <a href="/auth">Authenticate</a>
15
+ <a href="/dashboard">Dashboard</a>
16
+ <a href="/form">Form</a>
17
+ <a href="/analysis">Analysis</a>
18
+ <a href="/oneclick">One-Click</a>
19
+ </nav>
20
+ </header>
21
+ <main>
22
+ {% block content %}{% endblock %}
23
+ </main>
24
+ </body>
25
+ </html>
templates/dashboard.html ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+ {% block content %}
3
+ <h2>Patient Data</h2>
4
+ {% if error %}
5
+ <div>{{ error | safe }}</div>
6
+ {% else %}
7
+ {% for patient in patients %}
8
+ <div class="patient-card">
9
+ <h4>{{ patient.name[0].given|join(' ') }} {{ patient.name[0].family }}</h4>
10
+ <p><strong>Gender:</strong> {{ patient.gender|capitalize }}</p>
11
+ <p><strong>Birth Date:</strong> {{ patient.birthDate }}</p>
12
+ <p><strong>ID:</strong> {{ patient.id }}</p>
13
+ </div>
14
+ {% endfor %}
15
+ {% endif %}
16
+ {% endblock %}
templates/form.html ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+ {% block content %}
3
+ <h2>Patient Discharge Form</h2>
4
+ <form method="POST">
5
+ <!-- Patient Details -->
6
+ <h3>Patient Details</h3>
7
+ <input type="text" name="first_name" placeholder="First Name" required>
8
+ <input type="text" name="last_name" placeholder="Last Name" required>
9
+ <input type="text" name="middle_initial" placeholder="Middle Initial"><br>
10
+ <input type="text" name="dob" placeholder="Date of Birth" required>
11
+ <input type="text" name="age" placeholder="Age" required>
12
+ <input type="text" name="sex" placeholder="Sex" required><br>
13
+ <input type="text" name="address" placeholder="Address" required><br>
14
+ <input type="text" name="city" placeholder="City" required>
15
+ <input type="text" name="state" placeholder="State" required>
16
+ <input type="text" name="zip_code" placeholder="Zip Code" required><br>
17
+ <!-- Doctor Details -->
18
+ <h3>Primary Healthcare Professional Details</h3>
19
+ <input type="text" name="doctor_first_name" placeholder="Doctor's First Name" required>
20
+ <input type="text" name="doctor_last_name" placeholder="Doctor's Last Name" required>
21
+ <input type="text" name="doctor_middle_initial" placeholder="Doctor's Middle Initial"><br>
22
+ <input type="text" name="hospital_name" placeholder="Hospital/Clinic Name" required><br>
23
+ <input type="text" name="doctor_address" placeholder="Doctor Address" required><br>
24
+ <input type="text" name="doctor_city" placeholder="Doctor City" required>
25
+ <input type="text" name="doctor_state" placeholder="Doctor State" required>
26
+ <input type="text" name="doctor_zip" placeholder="Doctor Zip Code" required><br>
27
+ <!-- Admission/Discharge Details -->
28
+ <h3>Admission and Discharge Details</h3>
29
+ <input type="text" name="admission_date" placeholder="Admission Date" required>
30
+ <input type="text" name="referral_source" placeholder="Referral Source" required><br>
31
+ <input type="text" name="admission_method" placeholder="Admission Method" required><br>
32
+ <input type="text" name="discharge_date" placeholder="Discharge Date" required>
33
+ <select name="discharge_reason" required>
34
+ <option value="Treated">Treated</option>
35
+ <option value="Transferred">Transferred</option>
36
+ <option value="Discharge Against Advice">Discharge Against Advice</option>
37
+ <option value="Patient Died">Patient Died</option>
38
+ </select><br>
39
+ <input type="text" name="date_of_death" placeholder="Date of Death (if applicable)"><br>
40
+ <!-- Diagnosis & Procedures -->
41
+ <h3>Diagnosis & Procedures</h3>
42
+ <textarea name="diagnosis" placeholder="Diagnosis" required></textarea><br>
43
+ <textarea name="procedures" placeholder="Procedures" required></textarea><br>
44
+ <!-- Medications -->
45
+ <h3>Medication Details</h3>
46
+ <textarea name="medications" placeholder="Medications on Discharge" required></textarea><br>
47
+ <!-- Preparer -->
48
+ <h3>Prepared By</h3>
49
+ <input type="text" name="preparer_name" placeholder="Preparer Name" required>
50
+ <input type="text" name="preparer_job_title" placeholder="Job Title" required><br>
51
+ <input type="submit" name="display" value="Display Form" class="cyberpunk-button">
52
+ <input type="submit" name="generate_pdf" value="Generate PDF (No AI)" class="cyberpunk-button">
53
+ </form>
54
+ {% if form_output %}
55
+ <div>{{ form_output | safe }}</div>
56
+ {% endif %}
57
+ {% endblock %}
templates/index.html ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+ {% block content %}
3
+ <h2>Welcome to Discharge Guard Cyber</h2>
4
+ <p>Use the navigation above to access features.</p>
5
+ {% endblock %}
templates/oneclick.html ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends "base.html" %}
2
+ {% block content %}
3
+ <h2>One-Click Discharge Summary</h2>
4
+ <form method="POST">
5
+ <input type="text" name="patient_id" placeholder="Patient ID (Optional)">
6
+ <input type="text" name="first_name" placeholder="First Name (Optional)">
7
+ <input type="text" name="last_name" placeholder="Last Name (Optional)"><br>
8
+ <input type="submit" value="Generate with AI" class="cyberpunk-button">
9
+ </form>
10
+ {% if status %}
11
+ <div>{{ status }}</div>
12
+ {% endif %}
13
+ {% endblock %}