varshakolanu commited on
Commit
b15a11a
·
verified ·
1 Parent(s): 25255a7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +140 -172
app.py CHANGED
@@ -9,11 +9,11 @@ import os
9
  from urllib.parse import quote, unquote
10
  import time
11
 
12
- # Configure logging for audit purposes (FR4, NFR: Security)
13
  logging.basicConfig(level=logging.INFO, filename='app_log.txt',
14
  format='%(asctime)s - %(levelname)s - %(message)s')
15
 
16
- # Load environment variables (Hugging Face Spaces secrets)
17
  SALESFORCE_USERNAME = os.getenv("SF_USERNAME", "app41@clinic.com")
18
  SALESFORCE_PASSWORD = os.getenv("SF_PASSWORD", "text@12345")
19
  SALESFORCE_SECURITY_TOKEN = os.getenv("SF_SECURITY_TOKEN", "6JgPkW3K5MPPGXDgc2JNxCUdB")
@@ -22,11 +22,10 @@ TWILIO_ACCOUNT_SID = os.getenv("TWILIO_ACCOUNT_SID", "AC80ba89ceecaa0f3058326f5c
22
  TWILIO_AUTH_TOKEN = os.getenv("TWILIO_AUTH_TOKEN", "13d8416e924902b19f8181b77c968060")
23
  TWILIO_PHONE = os.getenv("TWILIO_PHONE", "+13304226436")
24
 
25
- # Validate environment variables
26
  required_vars = [SALESFORCE_USERNAME, SALESFORCE_PASSWORD, SALESFORCE_SECURITY_TOKEN, SALESFORCE_INSTANCE_URL,
27
  TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_PHONE]
28
  if not all(required_vars):
29
- logging.error("Missing environment variables for API integrations.")
30
  raise ValueError("Missing environment variables. Configure API tokens in Hugging Face Spaces secrets.")
31
 
32
  # Salesforce Authentication
@@ -56,7 +55,6 @@ def get_salesforce_access_token():
56
  logging.error(f"Salesforce authentication failed: {str(e)}")
57
  return None
58
 
59
- # Salesforce REST API Headers
60
  sf_auth = get_salesforce_access_token()
61
  if not sf_auth:
62
  logging.error("Failed to authenticate with Salesforce.")
@@ -67,7 +65,7 @@ SALESFORCE_HEADERS = {
67
  "Content-Type": "application/json"
68
  }
69
 
70
- # Hugging Face Client for Risk Analysis
71
  class HuggingFaceClient:
72
  def __init__(self):
73
  self.api_url = "https://api-inference.huggingface.co/models/"
@@ -82,7 +80,7 @@ class HuggingFaceClient:
82
  response.raise_for_status()
83
  return response.json()
84
  except requests.exceptions.RequestException as e:
85
- logging.error(f"API request failed for model {model}: {str(e)}")
86
  return None
87
 
88
  def analyze_response(self, response_text):
@@ -127,29 +125,18 @@ class SalesforceClient:
127
  try:
128
  response = requests.post(f"{self.base_url}sobjects/{object_name}/", headers=self.headers, json=data)
129
  response.raise_for_status()
130
- record_id = response.json()["id"]
131
- logging.info(f"Created {object_name} record: {record_id}")
132
- return record_id
133
- except requests.exceptions.HTTPError as e:
134
- error_message = f"HTTP error creating {object_name} record: {e.response.status_code} - {e.response.text}"
135
- logging.error(error_message)
136
- return error_message
137
- except requests.exceptions.RequestException as e:
138
- error_message = f"Failed to create {object_name} record: {str(e)}"
139
- logging.error(error_message)
140
- return error_message
141
 
142
  def update_record(self, object_name, record_id, data):
143
  try:
144
  response = requests.patch(f"{self.base_url}sobjects/{object_name}/{record_id}", headers=self.headers, json=data)
145
  response.raise_for_status()
146
- logging.info(f"Updated {object_name} record: {record_id}")
147
  return True
148
- except requests.exceptions.HTTPError as e:
149
- logging.error(f"HTTP error updating {object_name} record: {e.response.status_code} - {e.response.text}")
150
- return False
151
- except requests.exceptions.RequestException as e:
152
- logging.error(f"Failed to update {object_name} record: {str(e)}")
153
  return False
154
 
155
  def query_records(self, query):
@@ -158,11 +145,8 @@ class SalesforceClient:
158
  response = requests.get(f"{self.base_url}query/?q={encoded_query}", headers=self.headers)
159
  response.raise_for_status()
160
  return response.json()["records"]
161
- except requests.exceptions.HTTPError as e:
162
- logging.error(f"HTTP error querying records: {e.response.status_code} - {e.response.text}")
163
- return []
164
- except requests.exceptions.RequestException as e:
165
- logging.error(f"Failed to query records: {str(e)}")
166
  return []
167
 
168
  # Twilio Client
@@ -175,99 +159,87 @@ class TwilioClient:
175
  def send_message(self, to_number, body, method="sms"):
176
  try:
177
  if not to_number.startswith("+"):
178
- return "Invalid phone number format. Must start with +country_code."
179
  to_number = f"whatsapp:{to_number}" if method == "whatsapp" else to_number
180
  from_number = f"whatsapp:{self.from_number}" if method == "whatsapp" else self.from_number
181
  data = {"From": from_number, "To": to_number, "Body": body}
182
  response = requests.post(self.api_url, auth=self.auth, data=data, timeout=10)
183
  response.raise_for_status()
184
- logging.info(f"Sent {method.upper()} message to {to_number}")
185
  return True
186
- except requests.exceptions.HTTPError as e:
187
- logging.error(f"Twilio HTTP error: {e.response.status_code} - {e.response.text}")
188
- return f"Twilio error: {e.response.text}"
189
- except requests.exceptions.RequestException as e:
190
- logging.error(f"Failed to send message: {str(e)}")
191
- return str(e)
192
 
193
  # Initialize Clients
194
  hf_client = HuggingFaceClient()
195
  sf_client = SalesforceClient(SALESFORCE_BASE_URL, SALESFORCE_HEADERS)
196
  twilio_client = TwilioClient(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_PHONE)
197
 
198
- # Get patient info from URL parameters
199
  def get_patient_info():
200
  patient_id = unquote(os.getenv("patientId", ""))
201
  patient_name = unquote(os.getenv("patientName", ""))
202
  if patient_id and patient_name:
203
- logging.info(f"Loaded patient: ID={patient_id}, Name={patient_name}")
204
  return patient_id, patient_name
205
- logging.warning("No patient data from URL. Defaulting to manual input.")
206
  return None, None
207
 
208
  patient_id, patient_name = get_patient_info()
209
  patient_id_state = gr.State(value=patient_id)
210
  patient_name_state = gr.State(value=patient_name)
211
 
212
- # Follow-Up Scheduler (Simulated Cron Job)
213
  def follow_up_scheduler():
214
  while True:
215
  current_date = date.today().isoformat()
216
- plans = sf_client.query_records(f"SELECT Patient__c, MessageContent__c FROM FollowUpPlan__c WHERE ScheduledDate__c = {current_date} AND Status__c = 'Scheduled'")
217
  for plan in plans:
218
- patient = sf_client.query_records(f"SELECT Phone__c, ConsentGiven__c FROM Patient__c WHERE Id = '{plan['Patient__c']}'")[0]
219
- if patient["ConsentGiven__c"]:
220
- message = plan["MessageContent__c"] or f"Hi {patient_name_state.value}, this is your daily follow-up check-in."
221
- twilio_client.send_message(patient["Phone__c"], message)
222
- sf_client.create_record("MessageLog__c", {
223
- "Patient__c": plan["Patient__c"], "MessageText__c": message,
224
- "Direction__c": "Outbound", "Timestamp__c": datetime.utcnow().isoformat()
225
- })
226
- sf_client.update_record("FollowUpPlan__c", plan["Id"], {"Status__c": "Sent"})
227
  time.sleep(86400) # Run daily
228
 
229
- # Automated Follow-up Function
230
  def send_automated_followup(patient_id, action_type):
231
  if not patient_id:
232
- return "Patient ID not found. Please log in via Salesforce."
233
  patients = sf_client.query_records(f"SELECT Phone__c, Name, ConsentGiven__c FROM Patient__c WHERE Id = '{patient_id}'")
234
- if not patients:
235
- return "Patient not found."
236
  patient = patients[0]
237
- if not patient["ConsentGiven__c"]:
238
- return "Consent not given. Please update your consent."
239
  consent = sf_client.query_records(f"SELECT Method__c FROM Consent__c WHERE Patient__c = '{patient_id}' ORDER BY CreatedDate DESC LIMIT 1")
240
  consent_method = consent[0]["Method__c"] if consent else "SMS"
241
  message_templates = {
242
- "consent": f"Hello {patient['Name']}, thank you for registering with our clinic!",
243
- "daily": f"Hi {patient['Name']}, this is your daily follow-up. How are you feeling today?",
244
- "appointment": f"Dear {patient['Name']}, your appointment is confirmed. Details will follow."
245
  }
246
- message = message_templates.get(action_type, f"Hi {patient['Name']}, an action ({action_type}) was performed.")
247
- send_result = twilio_client.send_message(patient["Phone__c"], message, method=consent_method.lower())
248
- if send_result is True:
249
  sf_client.create_record("MessageLog__c", {
250
  "Patient__c": patient_id, "MessageText__c": message,
251
  "Direction__c": "Outbound", "Timestamp__c": datetime.utcnow().isoformat()
252
  })
253
- sf_client.update_record("Patient__c", patient_id, {"FollowUpMessage__c": message})
254
- logging.info(f"Automated follow-up sent for {action_type}: {patient_id}")
255
- return f"Follow-up message sent for {action_type}!"
256
- return f"Failed to send follow-up: {send_result}"
257
 
258
  # Update Patient Profile
259
  def update_patient_profile(patient_id, height, weight, emergency_name, emergency_number, emergency_relationship):
260
  if not patient_id:
261
- return "Patient ID not found."
262
  update_data = {}
263
- if height: update_data["Height__c"] = float(height)
264
- if weight: update_data["Weight__c"] = float(weight)
265
  if emergency_name: update_data["EmergencyContactName__c"] = emergency_name
266
  if emergency_number: update_data["EmergencyContactNumber__c"] = emergency_number
267
  if emergency_relationship: update_data["EmergencyContactRelationship__c"] = emergency_relationship
268
  if update_data:
269
- result = sf_client.update_record("Patient__c", patient_id, update_data)
270
- if result:
271
  return "Profile updated successfully!"
272
  return "Failed to update profile."
273
  return "No changes made."
@@ -276,114 +248,106 @@ def update_patient_profile(patient_id, height, weight, emergency_name, emergency
276
  def schedule_appointment(patient_id, doctor_name, appointment_date, time_slot, reason, special_requests):
277
  if not all([patient_id, doctor_name, appointment_date, time_slot]):
278
  return "All fields are required."
279
- doctors = sf_client.query_records("SELECT Name FROM Doctor__c")
280
- if doctor_name not in [doc["Name"] for doc in doctors]:
281
  return "Invalid doctor selected."
282
  appointment_data = {
283
  "Patient__c": patient_id,
284
  "Name": f"Appointment with {doctor_name}",
285
  "DateTime__c": f"{appointment_date} {time_slot}:00",
286
  "Status__c": "Scheduled",
287
- "Email__c": sf_client.query_records(f"SELECT Email__c FROM Patient__c WHERE Id = '{patient_id}'")[0]["Email__c"],
288
  }
289
  if reason: appointment_data["Reason__c"] = reason
290
  if special_requests: appointment_data["SpecialRequests__c"] = special_requests
291
  result = sf_client.create_record("Appointment__c", appointment_data)
292
- if isinstance(result, str):
293
- return result
294
- send_automated_followup(patient_id, "appointment")
295
- return f"Appointment scheduled with {doctor_name} on {appointment_date} at {time_slot}:00!"
296
 
297
  # View Risk Dashboard
298
  def view_risk_dashboard(patient_id):
299
- query = f"SELECT ResponseText__c, RiskScore__c, Severity__c, Sentiment__c FROM SymptomLog__c WHERE Patient__c = '{patient_id}'"
300
- symptom_logs = sf_client.query_records(query)
 
301
  if symptom_logs:
302
  df = pd.DataFrame([
303
  {"Response": log["ResponseText__c"], "Risk Score": log["RiskScore__c"],
304
  "Severity": log["Severity__c"], "Sentiment": log["Sentiment__c"]}
305
  for log in symptom_logs
306
  ])
307
- high_risk = df[df["Severity"] == "High"].to_dict('records')
308
  return df, high_risk if high_risk else "No high-risk cases."
309
  return pd.DataFrame(), "No symptom logs available."
310
 
311
  # Escalate Case
312
  def escalate_case(patient_id, response_text):
313
  if not patient_id or not response_text:
314
- return "Please provide a response text."
315
  analysis = hf_client.analyze_response(response_text)
316
  if analysis and analysis["risk_level"] == "High":
317
  case_data = {
318
  "RelatedPatient__c": patient_id, "Priority__c": "High",
319
  "Description__c": f"High-risk response: {response_text}"
320
  }
321
- result = sf_client.create_record("Case__c", case_data)
322
- if isinstance(result, str):
323
- return result
324
- patient = sf_client.query_records(f"SELECT Phone__c FROM Patient__c WHERE Id = '{patient_id}'")[0]
325
- twilio_client.send_message(patient["Phone__c"], "Your case has been escalated. A doctor will contact you soon.")
326
- return "Case escalated successfully!"
327
- return "No high-risk case detected."
328
 
329
  # Gradio Interface
330
- with gr.Blocks(theme=gr.themes.Soft(), title="HealthPortal - Patient Care System") as demo:
331
- gr.Markdown(f"# HealthPortal - Patient Care System")
 
 
 
 
 
 
 
 
 
332
  with gr.Tabs():
333
- with gr.Tab("Dashboard"):
334
- gr.Markdown(f"## Welcome back, {patient_name_state.value or 'Guest'}!")
335
- gr.Markdown("Here's an overview of your healthcare journey")
336
- with gr.Row():
337
- with gr.Column():
338
- next_appt = sf_client.query_records(f"SELECT DateTime__c, Name FROM Appointment__c WHERE Patient__c = '{patient_id_state.value}' AND Status__c = 'Scheduled' ORDER BY DateTime__c ASC LIMIT 1")
339
- gr.Markdown(f"**Next Appointment**\n{next_appt[0]['DateTime__c'].split(' ')[0]} - {next_appt[0]['Name'].split('with ')[1]}") if next_appt else gr.Markdown("**Next Appointment**\nNo upcoming appointments")
340
- with gr.Column():
341
- pending_fields = [f for f in ["Height__c", "Weight__c", "EmergencyContactName__c"] if not sf_client.query_records(f"SELECT {f} FROM Patient__c WHERE Id = '{patient_id_state.value}'")[0].get(f)]
342
- gr.Markdown(f"**Pending Forms**\n{len(pending_fields)} - {', '.join(pending_fields)}") if pending_fields else gr.Markdown("**Pending Forms**\nNone")
343
- with gr.Column():
344
- health_score = sf_client.query_records(f"SELECT AVG(RiskScore__c) FROM SymptomLog__c WHERE Patient__c = '{patient_id_state.value}'")[0]["expr0"] or 0
345
- gr.Markdown(f"**Health Score**\n{int(health_score)}/100\n{'Excellent progress' if health_score >= 80 else 'Monitor progress'}")
346
- with gr.Column():
347
- notifications = sf_client.query_records(f"SELECT MessageText__c FROM MessageLog__c WHERE Patient__c = '{patient_id_state.value}' ORDER BY Timestamp__c DESC LIMIT 3")
348
- gr.Markdown(f"**Notifications**\n{len(notifications)} - {'Lab results available' if notifications else 'No new notifications'}")
349
- with gr.Row():
350
- with gr.Column():
351
- gr.Markdown("**Tasks & Reminders**")
352
- tasks = [
353
- gr.Markdown("Complete Pre-visit Form\nFill out questionnaire before your appointment\nDue: Dec 14") if pending_fields else gr.Markdown("Complete Pre-visit Form\nAll fields completed"),
354
- gr.Markdown("Review Lab Results\nCheck your latest blood work results\nAvailable now"),
355
- gr.Markdown("Schedule Follow-up\nRecommended: Jan 2025")
356
- ]
357
- with gr.Column():
358
- gr.Markdown("**Recent Activity**")
359
- activities = [
360
- gr.Markdown("Lab Results Available\nBlood work results from Dec 10 are now available"),
361
- gr.Markdown("Appointment Confirmed\nYour Dec 15 appointment with Dr. Smith is confirmed\n1 day ago"),
362
- gr.Markdown("Follow-up Reminder\nTime for your weekly blood pressure check\n5 days ago")
363
- ]
364
 
365
  with gr.Tab("Appointments"):
366
- gr.Markdown("## Appointments\nManage your healthcare appointments and schedule new visits")
367
- with gr.Row():
368
- with gr.Column():
369
- upcoming_appts = sf_client.query_records(f"SELECT DateTime__c, Name FROM Appointment__c WHERE Patient__c = '{patient_id_state.value}' AND Status__c = 'Scheduled'")
370
- for appt in upcoming_appts[:2]:
371
- gr.Markdown(f"{appt['Name']}\n{appt['DateTime__c']}\nReschedule | Cancel | Call Office")
372
- with gr.Column():
373
- gr.Markdown("**Quick Actions**")
374
- gr.Button("View Available Times")
375
- gr.Button("Call to Schedule")
376
- gr.Button("Request Urgent Care")
377
- with gr.Row():
378
- with gr.Column():
379
  gr.Markdown("**Schedule New Appointment**")
380
- doctors = [doc["Name"] for doc in sf_client.query_records("SELECT Name FROM Doctor__c")]
381
- doctor_input = gr.Dropdown(doctors or ["None"], label="Select Provider", value=doctors[0] if doctors else "None")
382
  date_input = gr.Textbox(label="Preferred Date", value=date.today().isoformat(), interactive=True)
383
- time_slots = ["09:00", "10:00", "11:00", "14:00", "15:00", "16:00"] # Mock, to be dynamic
384
  time_input = gr.Dropdown(time_slots, label="Available Time Slots", value=time_slots[0])
385
- reason_input = gr.Textbox(label="Reason for Visit", value="Please describe the reason for your appointment...")
386
- special_input = gr.Textbox(label="Special Instructions or Requests", value="Any special accommodations or preferences...")
387
  appt_button = gr.Button("Schedule Appointment")
388
  appt_output = gr.Textbox(label="Result")
389
  appt_button.click(
@@ -393,37 +357,41 @@ with gr.Blocks(theme=gr.themes.Soft(), title="HealthPortal - Patient Care System
393
  )
394
 
395
  with gr.Tab("Health Records"):
396
- gr.Markdown("## Health Records")
397
- patient_data = sf_client.query_records(f"SELECT Name, Height__c, Weight__c, EmergencyContactName__c, EmergencyContactNumber__c, EmergencyContactRelationship__c FROM Patient__c WHERE Id = '{patient_id_state.value}'")[0]
398
- with gr.Row():
399
- gr.Markdown(f"**Patient Info: {patient_data['Name']}**")
400
- height_input = gr.Number(label="Height", value=patient_data["Height__c"] or 0, interactive=True)
401
- weight_input = gr.Number(label="Weight", value=patient_data["Weight__c"] or 0, interactive=True)
402
- emergency_name_input = gr.Textbox(label="Emergency Contact Name", value=patient_data["EmergencyContactName__c"] or "")
403
- emergency_number_input = gr.Textbox(label="Emergency Contact Number", value=patient_data["EmergencyContactNumber__c"] or "")
404
- emergency_rel_input = gr.Textbox(label="Emergency Contact Relationship", value=patient_data["EmergencyContactRelationship__c"] or "")
405
- update_button = gr.Button("Update Profile")
406
- update_output = gr.Textbox(label="Result")
407
- update_button.click(
408
- fn=update_patient_profile,
409
- inputs=[patient_id_state, height_input, weight_input, emergency_name_input, emergency_number_input, emergency_rel_input],
410
- outputs=update_output
411
- )
 
 
 
412
 
413
  with gr.Tab("Follow-up"):
414
- gr.Markdown("## Follow-up")
415
- gr.Markdown("Submit your feedback or escalate a case")
416
- survey_questions = ["How would you rate your visit?", "Are you experiencing discomfort?", "How satisfied are you with staff?"]
417
- survey_question_input = gr.Dropdown(survey_questions, label="Select Question", value=survey_questions[0])
418
- survey_answer_input = gr.Textbox(label="Your Answer", lines=4)
419
- survey_button = gr.Button("Submit Survey")
420
- survey_output = gr.Textbox(label="Result")
421
- survey_button.click(
422
- fn=lambda pid, q, a: submit_survey(pid, q, a) or send_automated_followup(pid, "daily"),
423
- inputs=[patient_id_state, survey_question_input, survey_answer_input],
424
- outputs=survey_output
425
- )
426
- with gr.Row():
 
427
  escalate_response_input = gr.Textbox(label="Response for Escalation", lines=4)
428
  escalate_button = gr.Button("Escalate Case")
429
  escalate_output = gr.Textbox(label="Result")
@@ -431,9 +399,9 @@ with gr.Blocks(theme=gr.themes.Soft(), title="HealthPortal - Patient Care System
431
  fn=escalate_case,
432
  inputs=[patient_id_state, escalate_response_input],
433
  outputs=escalate_output
434
- )
435
 
436
- # Launch Gradio app with scheduler
437
  if __name__ == "__main__":
438
  import threading
439
  scheduler_thread = threading.Thread(target=follow_up_scheduler, daemon=True)
 
9
  from urllib.parse import quote, unquote
10
  import time
11
 
12
+ # Configure logging for audit purposes
13
  logging.basicConfig(level=logging.INFO, filename='app_log.txt',
14
  format='%(asctime)s - %(levelname)s - %(message)s')
15
 
16
+ # Load environment variables
17
  SALESFORCE_USERNAME = os.getenv("SF_USERNAME", "app41@clinic.com")
18
  SALESFORCE_PASSWORD = os.getenv("SF_PASSWORD", "text@12345")
19
  SALESFORCE_SECURITY_TOKEN = os.getenv("SF_SECURITY_TOKEN", "6JgPkW3K5MPPGXDgc2JNxCUdB")
 
22
  TWILIO_AUTH_TOKEN = os.getenv("TWILIO_AUTH_TOKEN", "13d8416e924902b19f8181b77c968060")
23
  TWILIO_PHONE = os.getenv("TWILIO_PHONE", "+13304226436")
24
 
 
25
  required_vars = [SALESFORCE_USERNAME, SALESFORCE_PASSWORD, SALESFORCE_SECURITY_TOKEN, SALESFORCE_INSTANCE_URL,
26
  TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_PHONE]
27
  if not all(required_vars):
28
+ logging.error("Missing environment variables.")
29
  raise ValueError("Missing environment variables. Configure API tokens in Hugging Face Spaces secrets.")
30
 
31
  # Salesforce Authentication
 
55
  logging.error(f"Salesforce authentication failed: {str(e)}")
56
  return None
57
 
 
58
  sf_auth = get_salesforce_access_token()
59
  if not sf_auth:
60
  logging.error("Failed to authenticate with Salesforce.")
 
65
  "Content-Type": "application/json"
66
  }
67
 
68
+ # Hugging Face Client
69
  class HuggingFaceClient:
70
  def __init__(self):
71
  self.api_url = "https://api-inference.huggingface.co/models/"
 
80
  response.raise_for_status()
81
  return response.json()
82
  except requests.exceptions.RequestException as e:
83
+ logging.error(f"API request failed: {str(e)}")
84
  return None
85
 
86
  def analyze_response(self, response_text):
 
125
  try:
126
  response = requests.post(f"{self.base_url}sobjects/{object_name}/", headers=self.headers, json=data)
127
  response.raise_for_status()
128
+ return response.json()["id"]
129
+ except Exception as e:
130
+ logging.error(f"Create record failed: {str(e)}")
131
+ return None
 
 
 
 
 
 
 
132
 
133
  def update_record(self, object_name, record_id, data):
134
  try:
135
  response = requests.patch(f"{self.base_url}sobjects/{object_name}/{record_id}", headers=self.headers, json=data)
136
  response.raise_for_status()
 
137
  return True
138
+ except Exception as e:
139
+ logging.error(f"Update record failed: {str(e)}")
 
 
 
140
  return False
141
 
142
  def query_records(self, query):
 
145
  response = requests.get(f"{self.base_url}query/?q={encoded_query}", headers=self.headers)
146
  response.raise_for_status()
147
  return response.json()["records"]
148
+ except Exception as e:
149
+ logging.error(f"Query records failed: {str(e)}")
 
 
 
150
  return []
151
 
152
  # Twilio Client
 
159
  def send_message(self, to_number, body, method="sms"):
160
  try:
161
  if not to_number.startswith("+"):
162
+ return False
163
  to_number = f"whatsapp:{to_number}" if method == "whatsapp" else to_number
164
  from_number = f"whatsapp:{self.from_number}" if method == "whatsapp" else self.from_number
165
  data = {"From": from_number, "To": to_number, "Body": body}
166
  response = requests.post(self.api_url, auth=self.auth, data=data, timeout=10)
167
  response.raise_for_status()
 
168
  return True
169
+ except Exception as e:
170
+ logging.error(f"Send message failed: {str(e)}")
171
+ return False
 
 
 
172
 
173
  # Initialize Clients
174
  hf_client = HuggingFaceClient()
175
  sf_client = SalesforceClient(SALESFORCE_BASE_URL, SALESFORCE_HEADERS)
176
  twilio_client = TwilioClient(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_PHONE)
177
 
178
+ # Patient Info and State Management
179
  def get_patient_info():
180
  patient_id = unquote(os.getenv("patientId", ""))
181
  patient_name = unquote(os.getenv("patientName", ""))
182
  if patient_id and patient_name:
 
183
  return patient_id, patient_name
 
184
  return None, None
185
 
186
  patient_id, patient_name = get_patient_info()
187
  patient_id_state = gr.State(value=patient_id)
188
  patient_name_state = gr.State(value=patient_name)
189
 
190
+ # Follow-Up Scheduler
191
  def follow_up_scheduler():
192
  while True:
193
  current_date = date.today().isoformat()
194
+ plans = sf_client.query_records(f"SELECT Patient__c, MessageContent__c FROM FollowUpPlan__c WHERE ScheduledDate__c = '{current_date}' AND Status__c = 'Scheduled'")
195
  for plan in plans:
196
+ patient = sf_client.query_records(f"SELECT Phone__c, ConsentGiven__c FROM Patient__c WHERE Id = '{plan['Patient__c']}'")
197
+ if patient and patient[0]["ConsentGiven__c"]:
198
+ message = plan.get("MessageContent__c", f"Hi, this is your daily follow-up.")
199
+ if twilio_client.send_message(patient[0]["Phone__c"], message):
200
+ sf_client.create_record("MessageLog__c", {
201
+ "Patient__c": plan["Patient__c"], "MessageText__c": message,
202
+ "Direction__c": "Outbound", "Timestamp__c": datetime.utcnow().isoformat()
203
+ })
204
+ sf_client.update_record("FollowUpPlan__c", plan["Id"], {"Status__c": "Sent"})
205
  time.sleep(86400) # Run daily
206
 
207
+ # Automated Follow-up
208
  def send_automated_followup(patient_id, action_type):
209
  if not patient_id:
210
+ return "Please log in to receive follow-ups."
211
  patients = sf_client.query_records(f"SELECT Phone__c, Name, ConsentGiven__c FROM Patient__c WHERE Id = '{patient_id}'")
212
+ if not patients or not patients[0]["ConsentGiven__c"]:
213
+ return "No patient found or consent not given."
214
  patient = patients[0]
 
 
215
  consent = sf_client.query_records(f"SELECT Method__c FROM Consent__c WHERE Patient__c = '{patient_id}' ORDER BY CreatedDate DESC LIMIT 1")
216
  consent_method = consent[0]["Method__c"] if consent else "SMS"
217
  message_templates = {
218
+ "consent": f"Hello {patient['Name']}, thank you for registering!",
219
+ "daily": f"Hi {patient['Name']}, your daily follow-up check-in.",
220
+ "appointment": f"Dear {patient['Name']}, your appointment is confirmed."
221
  }
222
+ message = message_templates.get(action_type, f"Hi {patient['Name']}, an action was performed.")
223
+ if twilio_client.send_message(patient["Phone__c"], message, method=consent_method.lower()):
 
224
  sf_client.create_record("MessageLog__c", {
225
  "Patient__c": patient_id, "MessageText__c": message,
226
  "Direction__c": "Outbound", "Timestamp__c": datetime.utcnow().isoformat()
227
  })
228
+ return f"Follow-up sent for {action_type}!"
229
+ return "Failed to send follow-up."
 
 
230
 
231
  # Update Patient Profile
232
  def update_patient_profile(patient_id, height, weight, emergency_name, emergency_number, emergency_relationship):
233
  if not patient_id:
234
+ return "Please log in to update your profile."
235
  update_data = {}
236
+ if height is not None and height != "": update_data["Height__c"] = float(height)
237
+ if weight is not None and weight != "": update_data["Weight__c"] = float(weight)
238
  if emergency_name: update_data["EmergencyContactName__c"] = emergency_name
239
  if emergency_number: update_data["EmergencyContactNumber__c"] = emergency_number
240
  if emergency_relationship: update_data["EmergencyContactRelationship__c"] = emergency_relationship
241
  if update_data:
242
+ if sf_client.update_record("Patient__c", patient_id, update_data):
 
243
  return "Profile updated successfully!"
244
  return "Failed to update profile."
245
  return "No changes made."
 
248
  def schedule_appointment(patient_id, doctor_name, appointment_date, time_slot, reason, special_requests):
249
  if not all([patient_id, doctor_name, appointment_date, time_slot]):
250
  return "All fields are required."
251
+ doctors = [doc["Name"] for doc in sf_client.query_records("SELECT Name FROM Doctor__c")]
252
+ if doctor_name not in doctors and doctor_name != "None":
253
  return "Invalid doctor selected."
254
  appointment_data = {
255
  "Patient__c": patient_id,
256
  "Name": f"Appointment with {doctor_name}",
257
  "DateTime__c": f"{appointment_date} {time_slot}:00",
258
  "Status__c": "Scheduled",
259
+ "Email__c": sf_client.query_records(f"SELECT Email__c FROM Patient__c WHERE Id = '{patient_id}'")[0]["Email__c"] if sf_client.query_records(f"SELECT Email__c FROM Patient__c WHERE Id = '{patient_id}'") else "",
260
  }
261
  if reason: appointment_data["Reason__c"] = reason
262
  if special_requests: appointment_data["SpecialRequests__c"] = special_requests
263
  result = sf_client.create_record("Appointment__c", appointment_data)
264
+ if result:
265
+ send_automated_followup(patient_id, "appointment")
266
+ return f"Appointment scheduled with {doctor_name} on {appointment_date} at {time_slot}:00!"
267
+ return "Failed to schedule appointment."
268
 
269
  # View Risk Dashboard
270
  def view_risk_dashboard(patient_id):
271
+ if not patient_id:
272
+ return pd.DataFrame(), "Please log in to view your risk dashboard."
273
+ symptom_logs = sf_client.query_records(f"SELECT ResponseText__c, RiskScore__c, Severity__c, Sentiment__c FROM SymptomLog__c WHERE Patient__c = '{patient_id}'")
274
  if symptom_logs:
275
  df = pd.DataFrame([
276
  {"Response": log["ResponseText__c"], "Risk Score": log["RiskScore__c"],
277
  "Severity": log["Severity__c"], "Sentiment": log["Sentiment__c"]}
278
  for log in symptom_logs
279
  ])
280
+ high_risk = df[df["Severity"] == "High"].to_dict('records') if not df.empty and "Severity" in df else []
281
  return df, high_risk if high_risk else "No high-risk cases."
282
  return pd.DataFrame(), "No symptom logs available."
283
 
284
  # Escalate Case
285
  def escalate_case(patient_id, response_text):
286
  if not patient_id or not response_text:
287
+ return "Please log in and provide a response text."
288
  analysis = hf_client.analyze_response(response_text)
289
  if analysis and analysis["risk_level"] == "High":
290
  case_data = {
291
  "RelatedPatient__c": patient_id, "Priority__c": "High",
292
  "Description__c": f"High-risk response: {response_text}"
293
  }
294
+ if sf_client.create_record("Case__c", case_data):
295
+ patient = sf_client.query_records(f"SELECT Phone__c FROM Patient__c WHERE Id = '{patient_id}'")
296
+ if patient:
297
+ twilio_client.send_message(patient[0]["Phone__c"], "Your case is escalated. A doctor will contact you.")
298
+ return "Case escalated successfully!"
299
+ return "No high-risk case detected or escalation failed."
 
300
 
301
  # Gradio Interface
302
+ with gr.Blocks(theme=gr.themes.Soft(), title="HealthPortal - Patient Care") as demo:
303
+ gr.Markdown("# HealthPortal - Patient Care System")
304
+ patient_id_state = gr.State(value=patient_id)
305
+ patient_name_state = gr.State(value=patient_name)
306
+
307
+ def get_welcome_message(pid):
308
+ if pid:
309
+ patient = sf_client.query_records(f"SELECT Name FROM Patient__c WHERE Id = '{pid}'")
310
+ return f"## Welcome back, {patient[0]['Name'] if patient else 'Guest'}!" if patient else "## Welcome, Guest!"
311
+ return "## Welcome, Guest!"
312
+
313
  with gr.Tabs():
314
+ with gr.Tab("Home"):
315
+ gr.Markdown(get_welcome_message(patient_id_state.value))
316
+ if not patient_id_state.value:
317
+ gr.Markdown("**You are not logged in.** Please [login](/apex/PatientLoginForm) or [register](/apex/PatientRegistrationFormImproved) to access your personalized dashboard.")
318
+ gr.Button("Login", link="/apex/PatientLoginForm")
319
+ gr.Button("Register", link="/apex/PatientRegistrationFormImproved")
320
+ else:
321
+ with gr.Row():
322
+ with gr.Column():
323
+ next_appt = sf_client.query_records(f"SELECT DateTime__c, Name FROM Appointment__c WHERE Patient__c = '{patient_id_state.value}' AND Status__c = 'Scheduled' ORDER BY DateTime__c ASC LIMIT 1")
324
+ gr.Markdown(f"**Next Appointment**\n{next_appt[0]['DateTime__c'].split(' ')[0]} - {next_appt[0]['Name'].split('with ')[1]}") if next_appt else gr.Markdown("**Next Appointment**\nNo upcoming appointments")
325
+ with gr.Column():
326
+ patient_data = sf_client.query_records(f"SELECT Height__c, Weight__c, EmergencyContactName__c FROM Patient__c WHERE Id = '{patient_id_state.value}'")
327
+ if patient_data:
328
+ pending_fields = [f for f in ["Height__c", "Weight__c", "EmergencyContactName__c"] if not patient_data[0].get(f)]
329
+ gr.Markdown(f"**Pending Forms**\n{len(pending_fields)} - {', '.join(pending_fields)}") if pending_fields else gr.Markdown("**Pending Forms**\nNone")
330
+ with gr.Column():
331
+ health_score = sf_client.query_records(f"SELECT AVG(RiskScore__c) FROM SymptomLog__c WHERE Patient__c = '{patient_id_state.value}'")[0].get("expr0", 0)
332
+ gr.Markdown(f"**Health Score**\n{int(health_score)}/100\n{'Excellent' if health_score >= 80 else 'Monitor'}")
 
 
 
 
 
 
 
 
 
 
 
 
333
 
334
  with gr.Tab("Appointments"):
335
+ if not patient_id_state.value:
336
+ gr.Markdown("Please log in to manage appointments.")
337
+ else:
338
+ gr.Markdown("## Appointments")
339
+ upcoming_appts = sf_client.query_records(f"SELECT DateTime__c, Name FROM Appointment__c WHERE Patient__c = '{patient_id_state.value}' AND Status__c = 'Scheduled'")
340
+ for appt in upcoming_appts[:2]:
341
+ gr.Markdown(f"{appt['Name']}\n{appt['DateTime__c']}\nReschedule | Cancel | Call Office")
342
+ with gr.Row():
 
 
 
 
 
343
  gr.Markdown("**Schedule New Appointment**")
344
+ doctors = [doc["Name"] for doc in sf_client.query_records("SELECT Name FROM Doctor__c")] or ["None"]
345
+ doctor_input = gr.Dropdown(doctors, label="Select Provider", value=doctors[0] if doctors[0] != "None" else "")
346
  date_input = gr.Textbox(label="Preferred Date", value=date.today().isoformat(), interactive=True)
347
+ time_slots = ["09:00", "10:00", "11:00", "14:00", "15:00", "16:00"] # Mock
348
  time_input = gr.Dropdown(time_slots, label="Available Time Slots", value=time_slots[0])
349
+ reason_input = gr.Textbox(label="Reason for Visit")
350
+ special_input = gr.Textbox(label="Special Instructions")
351
  appt_button = gr.Button("Schedule Appointment")
352
  appt_output = gr.Textbox(label="Result")
353
  appt_button.click(
 
357
  )
358
 
359
  with gr.Tab("Health Records"):
360
+ if not patient_id_state.value:
361
+ gr.Markdown("Please log in to view or update your health records.")
362
+ else:
363
+ gr.Markdown("## Health Records")
364
+ patient_data = sf_client.query_records(f"SELECT Name, Height__c, Weight__c, EmergencyContactName__c, EmergencyContactNumber__c, EmergencyContactRelationship__c FROM Patient__c WHERE Id = '{patient_id_state.value}'")
365
+ if patient_data:
366
+ patient = patient_data[0]
367
+ height_input = gr.Number(label="Height", value=patient.get("Height__c", 0), interactive=True)
368
+ weight_input = gr.Number(label="Weight", value=patient.get("Weight__c", 0), interactive=True)
369
+ emergency_name_input = gr.Textbox(label="Emergency Contact Name", value=patient.get("EmergencyContactName__c", ""))
370
+ emergency_number_input = gr.Textbox(label="Emergency Contact Number", value=patient.get("EmergencyContactNumber__c", ""))
371
+ emergency_rel_input = gr.Textbox(label="Emergency Contact Relationship", value=patient.get("EmergencyContactRelationship__c", ""))
372
+ update_button = gr.Button("Update Profile")
373
+ update_output = gr.Textbox(label="Result")
374
+ update_button.click(
375
+ fn=update_patient_profile,
376
+ inputs=[patient_id_state, height_input, weight_input, emergency_name_input, emergency_number_input, emergency_rel_input],
377
+ outputs=update_output
378
+ )
379
 
380
  with gr.Tab("Follow-up"):
381
+ if not patient_id_state.value:
382
+ gr.Markdown("Please log in to submit feedback or escalate cases.")
383
+ else:
384
+ gr.Markdown("## Follow-up")
385
+ survey_questions = ["How was your visit?", "Any discomfort?", "Staff satisfaction?"]
386
+ survey_question_input = gr.Dropdown(survey_questions, label="Select Question", value=survey_questions[0])
387
+ survey_answer_input = gr.Textbox(label="Your Answer", lines=4)
388
+ survey_button = gr.Button("Submit Survey")
389
+ survey_output = gr.Textbox(label="Result")
390
+ survey_button.click(
391
+ fn=lambda pid, q, a: submit_survey(pid, q, a) or send_automated_followup(pid, "daily"),
392
+ inputs=[patient_id_state, survey_question_input, survey_answer_input],
393
+ outputs=survey_output
394
+ )
395
  escalate_response_input = gr.Textbox(label="Response for Escalation", lines=4)
396
  escalate_button = gr.Button("Escalate Case")
397
  escalate_output = gr.Textbox(label="Result")
 
399
  fn=escalate_case,
400
  inputs=[patient_id_state, escalate_response_input],
401
  outputs=escalate_output
402
+ )
403
 
404
+ # Launch with Scheduler
405
  if __name__ == "__main__":
406
  import threading
407
  scheduler_thread = threading.Thread(target=follow_up_scheduler, daemon=True)