Tonic commited on
Commit
3f9dd86
·
unverified ·
1 Parent(s): 66a769c

add callback manager , model app

Browse files
Files changed (2) hide show
  1. callbackmanager.py +8 -47
  2. meldrx.py +80 -20
callbackmanager.py CHANGED
@@ -3,35 +3,22 @@ from meldrx import MeldRxAPI
3
  import json
4
  import os
5
 
6
- # CallbackManager class to handle OAuth callbacks
7
  class CallbackManager:
8
  def __init__(self, redirect_uri: str, client_secret: str = None):
9
- """
10
- Initialize the CallbackManager with MeldRx API credentials.
11
- The client_id and workspace_id are retrieved from environment variables APPID and WORKSPACE_URL.
12
-
13
- Args:
14
- redirect_uri (str): The redirect URI for the Gradio app (e.g., Space URL + /callback).
15
- client_secret (str, optional): The client secret (for confidential clients).
16
- """
17
- client_id = os.getenv("APPID") # Retrieve client_id from APPID secret
18
  if not client_id:
19
  raise ValueError("APPID environment variable not set. Please configure it in your Space secrets.")
20
-
21
- workspace_id = os.getenv("WORKSPACE_URL") # Retrieve workspace_id from WORKSPACE_URL secret
22
  if not workspace_id:
23
  raise ValueError("WORKSPACE_URL environment variable not set. Please configure it in your Space secrets.")
24
-
25
  self.api = MeldRxAPI(client_id, client_secret, workspace_id, redirect_uri)
26
  self.auth_code = None
27
  self.access_token = None
28
 
29
  def get_auth_url(self) -> str:
30
- """Generate and return the SMART on FHIR authorization URL."""
31
  return self.api.get_authorization_url()
32
 
33
  def set_auth_code(self, code: str) -> str:
34
- """Set the authorization code and attempt to get an access token."""
35
  self.auth_code = code
36
  if self.api.authenticate_with_code(code):
37
  self.access_token = self.api.access_token
@@ -39,7 +26,6 @@ class CallbackManager:
39
  return "Authentication failed. Please check the code."
40
 
41
  def get_patient_data(self) -> str:
42
- """Retrieve patient data using the access token."""
43
  if not self.access_token:
44
  return "Not authenticated. Please provide a valid authorization code first."
45
  patients = self.api.get_patients()
@@ -47,7 +33,6 @@ class CallbackManager:
47
  return json.dumps(patients, indent=2) if patients else "No patient data returned."
48
  return "Failed to retrieve patient data."
49
 
50
- # Discharge form display function (unchanged)
51
  def display_form(
52
  first_name, last_name, middle_initial, dob, age, sex, address, city, state, zip_code,
53
  doctor_first_name, doctor_last_name, doctor_middle_initial, hospital_name, doctor_address,
@@ -88,17 +73,16 @@ def display_form(
88
  """
89
  return form
90
 
91
- # Initialize CallbackManager, fetching client_id and workspace_id from secrets
92
  CALLBACK_MANAGER = CallbackManager(
93
  redirect_uri="https://multitransformer-discharge-guard.hf.space/callback",
94
- client_secret=None # Replace with actual secret if using a confidential client
95
  )
96
 
97
- # Gradio interface (unchanged)
 
98
  with gr.Blocks() as demo:
99
  gr.Markdown("# Patient Discharge Form with MeldRx Integration")
100
-
101
- # Authentication Section
102
  with gr.Tab("Authenticate with MeldRx"):
103
  gr.Markdown("## SMART on FHIR Authentication")
104
  auth_url_output = gr.Textbox(label="Authorization URL", value=CALLBACK_MANAGER.get_auth_url(), interactive=False)
@@ -108,77 +92,54 @@ with gr.Blocks() as demo:
108
  auth_result = gr.Textbox(label="Authentication Result")
109
  patient_data_button = gr.Button("Fetch Patient Data")
110
  patient_data_output = gr.Textbox(label="Patient Data")
111
-
112
- auth_submit.click(
113
- fn=CALLBACK_MANAGER.set_auth_code,
114
- inputs=auth_code_input,
115
- outputs=auth_result
116
- )
117
- patient_data_button.click(
118
- fn=CALLBACK_MANAGER.get_patient_data,
119
- inputs=None,
120
- outputs=patient_data_output
121
- )
122
-
123
- # Discharge Form Section
124
  with gr.Tab("Discharge Form"):
125
  gr.Markdown("## Patient Details")
126
  with gr.Row():
127
  first_name = gr.Textbox(label="First Name")
128
  last_name = gr.Textbox(label="Last Name")
129
  middle_initial = gr.Textbox(label="Middle Initial")
130
-
131
  with gr.Row():
132
  dob = gr.Textbox(label="Date of Birth")
133
  age = gr.Textbox(label="Age")
134
  sex = gr.Textbox(label="Sex")
135
-
136
  address = gr.Textbox(label="Address")
137
  with gr.Row():
138
  city = gr.Textbox(label="City")
139
  state = gr.Textbox(label="State")
140
  zip_code = gr.Textbox(label="Zip Code")
141
-
142
  gr.Markdown("## Primary Healthcare Professional Details")
143
  with gr.Row():
144
  doctor_first_name = gr.Textbox(label="Doctor's First Name")
145
  doctor_last_name = gr.Textbox(label="Doctor's Last Name")
146
  doctor_middle_initial = gr.Textbox(label="Middle Initial")
147
-
148
  hospital_name = gr.Textbox(label="Hospital/Clinic Name")
149
  doctor_address = gr.Textbox(label="Address")
150
  with gr.Row():
151
  doctor_city = gr.Textbox(label="City")
152
  doctor_state = gr.Textbox(label="State")
153
  doctor_zip = gr.Textbox(label="Zip Code")
154
-
155
  gr.Markdown("## Admission and Discharge Details")
156
  with gr.Row():
157
  admission_date = gr.Textbox(label="Date of Admission")
158
  referral_source = gr.Textbox(label="Source of Referral")
159
-
160
  admission_method = gr.Textbox(label="Method of Admission")
161
  with gr.Row():
162
  discharge_date = gr.Textbox(label="Date of Discharge")
163
  discharge_reason = gr.Radio(["Treated", "Transferred", "Discharge Against Advice", "Patient Died"], label="Discharge Reason")
164
-
165
  date_of_death = gr.Textbox(label="Date of Death (if applicable)")
166
-
167
  gr.Markdown("## Diagnosis & Procedures")
168
  diagnosis = gr.Textbox(label="Diagnosis")
169
  procedures = gr.Textbox(label="Operation & Procedures")
170
-
171
  gr.Markdown("## Medication Details")
172
  medications = gr.Textbox(label="Medication on Discharge")
173
-
174
  gr.Markdown("## Prepared By")
175
  with gr.Row():
176
  preparer_name = gr.Textbox(label="Name")
177
  preparer_job_title = gr.Textbox(label="Job Title")
178
-
179
  submit = gr.Button("Generate Form")
180
  output = gr.Markdown()
181
-
182
  submit.click(
183
  display_form,
184
  inputs=[
 
3
  import json
4
  import os
5
 
 
6
  class CallbackManager:
7
  def __init__(self, redirect_uri: str, client_secret: str = None):
8
+ client_id = os.getenv("APPID")
 
 
 
 
 
 
 
 
9
  if not client_id:
10
  raise ValueError("APPID environment variable not set. Please configure it in your Space secrets.")
11
+ workspace_id = os.getenv("WORKSPACE_URL")
 
12
  if not workspace_id:
13
  raise ValueError("WORKSPACE_URL environment variable not set. Please configure it in your Space secrets.")
 
14
  self.api = MeldRxAPI(client_id, client_secret, workspace_id, redirect_uri)
15
  self.auth_code = None
16
  self.access_token = None
17
 
18
  def get_auth_url(self) -> str:
 
19
  return self.api.get_authorization_url()
20
 
21
  def set_auth_code(self, code: str) -> str:
 
22
  self.auth_code = code
23
  if self.api.authenticate_with_code(code):
24
  self.access_token = self.api.access_token
 
26
  return "Authentication failed. Please check the code."
27
 
28
  def get_patient_data(self) -> str:
 
29
  if not self.access_token:
30
  return "Not authenticated. Please provide a valid authorization code first."
31
  patients = self.api.get_patients()
 
33
  return json.dumps(patients, indent=2) if patients else "No patient data returned."
34
  return "Failed to retrieve patient data."
35
 
 
36
  def display_form(
37
  first_name, last_name, middle_initial, dob, age, sex, address, city, state, zip_code,
38
  doctor_first_name, doctor_last_name, doctor_middle_initial, hospital_name, doctor_address,
 
73
  """
74
  return form
75
 
76
+ # Initialize CallbackManager
77
  CALLBACK_MANAGER = CallbackManager(
78
  redirect_uri="https://multitransformer-discharge-guard.hf.space/callback",
79
+ client_secret=None # Replace with actual secret if needed
80
  )
81
 
82
+
83
+
84
  with gr.Blocks() as demo:
85
  gr.Markdown("# Patient Discharge Form with MeldRx Integration")
 
 
86
  with gr.Tab("Authenticate with MeldRx"):
87
  gr.Markdown("## SMART on FHIR Authentication")
88
  auth_url_output = gr.Textbox(label="Authorization URL", value=CALLBACK_MANAGER.get_auth_url(), interactive=False)
 
92
  auth_result = gr.Textbox(label="Authentication Result")
93
  patient_data_button = gr.Button("Fetch Patient Data")
94
  patient_data_output = gr.Textbox(label="Patient Data")
95
+ auth_submit.click(fn=CALLBACK_MANAGER.set_auth_code, inputs=auth_code_input, outputs=auth_result)
96
+ patient_data_button.click(fn=CALLBACK_MANAGER.get_patient_data, inputs=None, outputs=patient_data_output)
 
 
 
 
 
 
 
 
 
 
 
97
  with gr.Tab("Discharge Form"):
98
  gr.Markdown("## Patient Details")
99
  with gr.Row():
100
  first_name = gr.Textbox(label="First Name")
101
  last_name = gr.Textbox(label="Last Name")
102
  middle_initial = gr.Textbox(label="Middle Initial")
 
103
  with gr.Row():
104
  dob = gr.Textbox(label="Date of Birth")
105
  age = gr.Textbox(label="Age")
106
  sex = gr.Textbox(label="Sex")
 
107
  address = gr.Textbox(label="Address")
108
  with gr.Row():
109
  city = gr.Textbox(label="City")
110
  state = gr.Textbox(label="State")
111
  zip_code = gr.Textbox(label="Zip Code")
 
112
  gr.Markdown("## Primary Healthcare Professional Details")
113
  with gr.Row():
114
  doctor_first_name = gr.Textbox(label="Doctor's First Name")
115
  doctor_last_name = gr.Textbox(label="Doctor's Last Name")
116
  doctor_middle_initial = gr.Textbox(label="Middle Initial")
 
117
  hospital_name = gr.Textbox(label="Hospital/Clinic Name")
118
  doctor_address = gr.Textbox(label="Address")
119
  with gr.Row():
120
  doctor_city = gr.Textbox(label="City")
121
  doctor_state = gr.Textbox(label="State")
122
  doctor_zip = gr.Textbox(label="Zip Code")
 
123
  gr.Markdown("## Admission and Discharge Details")
124
  with gr.Row():
125
  admission_date = gr.Textbox(label="Date of Admission")
126
  referral_source = gr.Textbox(label="Source of Referral")
 
127
  admission_method = gr.Textbox(label="Method of Admission")
128
  with gr.Row():
129
  discharge_date = gr.Textbox(label="Date of Discharge")
130
  discharge_reason = gr.Radio(["Treated", "Transferred", "Discharge Against Advice", "Patient Died"], label="Discharge Reason")
 
131
  date_of_death = gr.Textbox(label="Date of Death (if applicable)")
 
132
  gr.Markdown("## Diagnosis & Procedures")
133
  diagnosis = gr.Textbox(label="Diagnosis")
134
  procedures = gr.Textbox(label="Operation & Procedures")
 
135
  gr.Markdown("## Medication Details")
136
  medications = gr.Textbox(label="Medication on Discharge")
 
137
  gr.Markdown("## Prepared By")
138
  with gr.Row():
139
  preparer_name = gr.Textbox(label="Name")
140
  preparer_job_title = gr.Textbox(label="Job Title")
 
141
  submit = gr.Button("Generate Form")
142
  output = gr.Markdown()
 
143
  submit.click(
144
  display_form,
145
  inputs=[
meldrx.py CHANGED
@@ -1,23 +1,12 @@
1
  import requests
2
  import json
 
 
 
3
  from typing import Optional, Dict, Any
4
 
5
  class MeldRxAPI:
6
- """
7
- A Python class to interact with the MeldRx API, including the MIPS API.
8
- Provides methods to authenticate, retrieve/update patients, create virtual workspaces,
9
- and interact with encounter data.
10
- """
11
-
12
  def __init__(self, client_id: str, client_secret: str, workspace_id: str, redirect_uri: str):
13
- """
14
- Initialize the MeldRxAPI class with client credentials and workspace ID.
15
-
16
- Args:
17
- client_id (str): The client ID (App ID) obtained from MeldRx Developer Portal.
18
- client_secret (str): The client secret for authentication.
19
- workspace_id (str): The workspace slug/ID to interact with.
20
- """
21
  self.base_url = "https://app.meldrx.com"
22
  self.api_base_url = f"{self.base_url}/api"
23
  self.fhir_base_url = f"{self.api_base_url}/fhir/{workspace_id}"
@@ -29,22 +18,93 @@ class MeldRxAPI:
29
  self.workspace_id = workspace_id
30
  self.redirect_uri = redirect_uri
31
  self.access_token = None
 
32
  self.session = requests.Session()
33
 
34
- def authenticate(self) -> bool:
35
- """
36
- Authenticate with the MeldRx API to obtain an access token.
 
37
 
38
- Returns:
39
- bool: True if authentication is successful, False otherwise.
40
- """
 
 
 
 
41
  payload = {
42
  "grant_type": "client_credentials",
43
  "client_id": self.client_id,
44
  "client_secret": self.client_secret
45
  }
46
  headers = {"Content-Type": "application/x-www-form-urlencoded"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  try:
49
  response = self.session.post(self.token_url, data=payload, headers=headers)
50
  response.raise_for_status()
 
1
  import requests
2
  import json
3
+ import base64
4
+ import hashlib
5
+ import secrets
6
  from typing import Optional, Dict, Any
7
 
8
  class MeldRxAPI:
 
 
 
 
 
 
9
  def __init__(self, client_id: str, client_secret: str, workspace_id: str, redirect_uri: str):
 
 
 
 
 
 
 
 
10
  self.base_url = "https://app.meldrx.com"
11
  self.api_base_url = f"{self.base_url}/api"
12
  self.fhir_base_url = f"{self.api_base_url}/fhir/{workspace_id}"
 
18
  self.workspace_id = workspace_id
19
  self.redirect_uri = redirect_uri
20
  self.access_token = None
21
+ self.code_verifier = None # Store the code_verifier for later use
22
  self.session = requests.Session()
23
 
24
+ def _generate_code_verifier(self) -> str:
25
+ """Generate a random code_verifier string (43-128 characters)."""
26
+ self.code_verifier = secrets.token_urlsafe(32) # Generates a 43-character safe string
27
+ return self.code_verifier
28
 
29
+ def _generate_code_challenge(self, code_verifier: str) -> str:
30
+ """Generate a code_challenge from the code_verifier using SHA-256."""
31
+ sha256_hash = hashlib.sha256(code_verifier.encode('utf-8')).digest()
32
+ code_challenge = base64.urlsafe_b64encode(sha256_hash).decode('utf-8').rstrip('=')
33
+ return code_challenge
34
+
35
+ def authenticate(self) -> bool:
36
  payload = {
37
  "grant_type": "client_credentials",
38
  "client_id": self.client_id,
39
  "client_secret": self.client_secret
40
  }
41
  headers = {"Content-Type": "application/x-www-form-urlencoded"}
42
+ try:
43
+ response = self.session.post(self.token_url, data=payload, headers=headers)
44
+ response.raise_for_status()
45
+ token_data = response.json()
46
+ self.access_token = token_data.get("access_token")
47
+ if not self.access_token:
48
+ raise ValueError("No access token received from the server.")
49
+ return True
50
+ except requests.RequestException as e:
51
+ print(f"Authentication failed: {e}")
52
+ return False
53
+ except ValueError as e:
54
+ print(f"Authentication error: {e}")
55
+ return False
56
 
57
+ def _get_headers(self) -> Dict[str, str]:
58
+ headers = {"Content-Type": "application/json"}
59
+ if self.access_token:
60
+ headers["Authorization"] = f"Bearer {self.access_token}"
61
+ return headers
62
+
63
+ def get_patients(self) -> Optional[Dict[str, Any]]:
64
+ url = f"{self.fhir_base_url}/Patient"
65
+ if not self.access_token and not self.authenticate():
66
+ print("Cannot proceed without authentication.")
67
+ return None
68
+ try:
69
+ response = self.session.get(url, headers=self._get_headers())
70
+ response.raise_for_status()
71
+ return response.json() if response.text else {}
72
+ except requests.RequestException as e:
73
+ print(f"Failed to retrieve patients: {e}")
74
+ return None
75
+
76
+ def get_authorization_url(self, scope: str = "patient/*.read openid profile", state: str = "random_state") -> str:
77
+ """Generate the SMART on FHIR authorization URL with PKCE."""
78
+ code_verifier = self._generate_code_verifier()
79
+ code_challenge = self._generate_code_challenge(code_verifier)
80
+ params = {
81
+ "response_type": "code",
82
+ "client_id": self.client_id,
83
+ "redirect_uri": self.redirect_uri,
84
+ "scope": scope,
85
+ "state": state,
86
+ "aud": self.fhir_base_url,
87
+ "code_challenge": code_challenge,
88
+ "code_challenge_method": "S256" # MeldRx likely expects SHA-256
89
+ }
90
+ query_string = "&".join(f"{k}={v}" for k, v in params.items())
91
+ return f"{self.authorize_url}?{query_string}"
92
+
93
+ def authenticate_with_code(self, auth_code: str) -> bool:
94
+ """Exchange an authorization code for an access token, including the code_verifier."""
95
+ if not self.code_verifier:
96
+ print("Code verifier not set. Generate an authorization URL first.")
97
+ return False
98
+ payload = {
99
+ "grant_type": "authorization_code",
100
+ "code": auth_code,
101
+ "redirect_uri": self.redirect_uri,
102
+ "client_id": self.client_id,
103
+ "code_verifier": self.code_verifier # Include the code_verifier
104
+ }
105
+ if self.client_secret:
106
+ payload["client_secret"] = self.client_secret
107
+ headers = {"Content-Type": "application/x-www-form-urlencoded"}
108
  try:
109
  response = self.session.post(self.token_url, data=payload, headers=headers)
110
  response.raise_for_status()