Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -6,7 +6,7 @@ from uuid import uuid4
|
|
| 6 |
import logging
|
| 7 |
import pandas as pd
|
| 8 |
import os
|
| 9 |
-
from urllib.parse import quote
|
| 10 |
|
| 11 |
# Configure logging for audit purposes (FR4, NFR: Security)
|
| 12 |
logging.basicConfig(level=logging.INFO, filename='app_log.txt',
|
|
@@ -67,13 +67,13 @@ SALESFORCE_HEADERS = {
|
|
| 67 |
"Content-Type": "application/json"
|
| 68 |
}
|
| 69 |
|
| 70 |
-
# Hugging Face Client
|
| 71 |
class HuggingFaceClient:
|
| 72 |
def __init__(self):
|
| 73 |
self.api_url = "https://api-inference.huggingface.co/models/"
|
| 74 |
-
self.headers = {}
|
| 75 |
self.sentiment_model = "distilbert-base-uncased-finetuned-sst-2-english"
|
| 76 |
-
self.severity_model = "bert-base-uncased"
|
| 77 |
self.translation_model = "facebook/m2m100_418M"
|
| 78 |
|
| 79 |
def query(self, text, model):
|
|
@@ -231,121 +231,100 @@ class CalendlyClient:
|
|
| 231 |
logging.error(f"Failed to schedule Calendly event: {str(e)}")
|
| 232 |
return None
|
| 233 |
|
| 234 |
-
#
|
| 235 |
hf_client = HuggingFaceClient()
|
| 236 |
sf_client = SalesforceClient(SALESFORCE_BASE_URL, SALESFORCE_HEADERS)
|
| 237 |
twilio_client = TwilioClient(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_PHONE)
|
| 238 |
calendly_client = CalendlyClient(CALENDLY_API_TOKEN)
|
| 239 |
|
| 240 |
-
# State to store patient
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 249 |
(med_yes_no == "Yes" and not med_list):
|
| 250 |
-
return "All fields are required.",
|
| 251 |
try:
|
| 252 |
patient_data = {
|
| 253 |
"First_Name__c": name_first,
|
| 254 |
"Last_Name__c": name_last,
|
| 255 |
-
"
|
| 256 |
-
"Gender__c":
|
| 257 |
-
"Height__c": height,
|
| 258 |
-
"Weight__c": weight,
|
| 259 |
-
"
|
| 260 |
"Phone__c": phone,
|
| 261 |
"Email__c": email,
|
| 262 |
"City__c": address_city,
|
| 263 |
"State__c": address_state,
|
| 264 |
-
"
|
| 265 |
-
"
|
| 266 |
-
"
|
| 267 |
-
"
|
| 268 |
-
"
|
| 269 |
-
"Emergency_Number__c": emergency_number or "",
|
| 270 |
"Language__c": language,
|
| 271 |
-
"ConsentGiven__c": consent_status
|
| 272 |
}
|
| 273 |
result = sf_client.create_record("Patient__c", patient_data)
|
| 274 |
-
if isinstance(result, str):
|
| 275 |
-
return result,
|
| 276 |
if result:
|
| 277 |
logging.info(f"Registered patient: {result}")
|
| 278 |
message = f"Welcome! You are registered. Your patient ID is {result}."
|
| 279 |
-
if consent_status
|
| 280 |
-
twilio_client.send_message(phone, message, method=
|
| 281 |
-
|
| 282 |
-
|
|
|
|
| 283 |
except Exception as e:
|
| 284 |
logging.error(f"Error registering patient: {str(e)}")
|
| 285 |
-
return f"Error registering patient: {str(e)}. Please try again or contact support.",
|
| 286 |
-
|
| 287 |
-
def submit_consent(patient_phone, consent_method):
|
| 288 |
-
if not patient_phone or not consent_method:
|
| 289 |
-
return "All fields are required."
|
| 290 |
-
try:
|
| 291 |
-
patients = sf_client.query_records(f"SELECT Id, Name FROM Patient__c WHERE Phone__c = '{patient_phone}'")
|
| 292 |
-
if patients:
|
| 293 |
-
consent_data = {"Method__c": consent_method, "GivenOn__c": date.today().isoformat()}
|
| 294 |
-
result = sf_client.create_record("Consent__c", consent_data)
|
| 295 |
-
if isinstance(result, str): # Error message returned
|
| 296 |
-
return result
|
| 297 |
-
if result:
|
| 298 |
-
message = f"Your consent method has been updated to {consent_method}."
|
| 299 |
-
twilio_client.send_message(patient_phone, message, method=consent_method.lower())
|
| 300 |
-
logging.info(f"Consent recorded for patient: {patients[0]['Name']}")
|
| 301 |
-
return "Consent recorded successfully! Your consent is now updated."
|
| 302 |
-
return "Failed to record consent. Please try again or contact support."
|
| 303 |
-
return "Patient not found. Please register the patient first."
|
| 304 |
-
except Exception as e:
|
| 305 |
-
logging.error(f"Error submitting consent: {str(e)}")
|
| 306 |
-
return f"Error submitting consent: {str(e)}. Please try again or contact support."
|
| 307 |
-
|
| 308 |
-
def schedule_followup(patient_phone, message_template, follow_up_date):
|
| 309 |
-
if not patient_phone or not message_template or not follow_up_date:
|
| 310 |
-
return "All fields are required."
|
| 311 |
-
try:
|
| 312 |
-
follow_up_date = datetime.strptime(follow_up_date, "%Y-%m-%d").date()
|
| 313 |
-
patients = sf_client.query_records(f"SELECT Id, ConsentGiven__c, Language__c, Name FROM Patient__c WHERE Phone__c = '{patient_phone}'")
|
| 314 |
-
if not patients:
|
| 315 |
-
return "Patient not found. Please register the patient first."
|
| 316 |
-
if patients[0]["ConsentGiven__c"] != "Approved":
|
| 317 |
-
return "Consent not approved for messaging. Please update consent status."
|
| 318 |
-
consent = sf_client.query_records(f"SELECT Method__c FROM Consent__c WHERE Patient__c = '{patients[0]['Id']}' ORDER BY CreatedDate DESC LIMIT 1")
|
| 319 |
-
consent_method = consent[0]["Method__c"] if consent else "SMS"
|
| 320 |
-
followup_data = {
|
| 321 |
-
"Patient__c": patients[0]["Id"], "FollowUpDate__c": follow_up_date.isoformat(),
|
| 322 |
-
"MessageTemplate__c": message_template, "Status__c": "Scheduled"
|
| 323 |
-
}
|
| 324 |
-
result = sf_client.create_record("FollowUpPlan__c", followup_data)
|
| 325 |
-
if isinstance(result, str): # Error message returned
|
| 326 |
-
return result
|
| 327 |
-
if not result:
|
| 328 |
-
return "Failed to schedule follow-up. Please try again or contact support."
|
| 329 |
-
send_result = twilio_client.send_message(patient_phone, message_template, method=consent_method.lower())
|
| 330 |
-
if send_result is True:
|
| 331 |
-
sf_client.create_record("MessageLog__c", {
|
| 332 |
-
"Patient__c": patients[0]["Id"], "MessageText__c": message_template,
|
| 333 |
-
"Direction__c": "Outbound", "Timestamp__c": datetime.utcnow().isoformat()
|
| 334 |
-
})
|
| 335 |
-
logging.info(f"Follow-up scheduled and message sent for patient: {patients[0]['Name']}")
|
| 336 |
-
return "Follow-up scheduled successfully, and a message has been sent!"
|
| 337 |
-
return f"Failed to send message: {send_result}. Please try again or contact support."
|
| 338 |
-
except ValueError:
|
| 339 |
-
return "Invalid date format. Please use YYYY-MM-DD."
|
| 340 |
-
except Exception as e:
|
| 341 |
-
logging.error(f"Error scheduling follow-up: {str(e)}")
|
| 342 |
-
return f"Error scheduling follow-up: {str(e)}. Please try again or contact support."
|
| 343 |
|
|
|
|
| 344 |
def schedule_appointment(patient_email, event_type_slug="30min"):
|
| 345 |
-
if not patient_email
|
| 346 |
-
return "
|
| 347 |
try:
|
| 348 |
-
patients = sf_client.query_records(f"SELECT Id, Phone__c, ConsentGiven__c,
|
| 349 |
if not patients:
|
| 350 |
return "Patient not found. Please register the patient first."
|
| 351 |
event_type_uuid = calendly_client.get_event_type_uuid(event_type_slug)
|
|
@@ -355,14 +334,16 @@ def schedule_appointment(patient_email, event_type_slug="30min"):
|
|
| 355 |
if event_time:
|
| 356 |
appointment_data = {"DateTime__c": event_time, "Status__c": "Scheduled", "Patient__c": patients[0]["Id"]}
|
| 357 |
result = sf_client.create_record("Appointment__c", appointment_data)
|
| 358 |
-
if isinstance(result, str):
|
| 359 |
return result
|
| 360 |
if result:
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
|
|
|
|
|
|
|
| 364 |
twilio_client.send_message(patients[0]["Phone__c"], message, method=consent_method.lower())
|
| 365 |
-
|
| 366 |
return f"Appointment scheduled successfully for {event_time}!"
|
| 367 |
return "Failed to create appointment record. Please try again or contact support."
|
| 368 |
return "Failed to schedule appointment. Please try again or contact support."
|
|
@@ -370,64 +351,66 @@ def schedule_appointment(patient_email, event_type_slug="30min"):
|
|
| 370 |
logging.error(f"Error scheduling appointment: {str(e)}")
|
| 371 |
return f"Error scheduling appointment: {str(e)}. Please try again or contact support."
|
| 372 |
|
|
|
|
| 373 |
def submit_survey(patient_phone, answer):
|
| 374 |
if not patient_phone or not answer:
|
| 375 |
return "Please provide an answer to the selected question."
|
| 376 |
-
default_questions = {
|
| 377 |
-
"en": ["How would you rate your visit today?", "Are you experiencing any discomfort?", "How satisfied are you with our staff?"],
|
| 378 |
-
"te": ["నిన్ను ఈ రోజు ఎలా అనిపించింది?", "మీకు ఏ రకమైన అసౌకర్యం ఉందా?", "మా సిబ్బందిపై మీ సంతృప్తి ఎంత?"],
|
| 379 |
-
"hi": ["आज आपकी यात्रा कैसी रही?", "क्या आपको कोई असुविधा हो रही है?", "हमारी टीम से आप कितने संतुष्ट हैं?"]
|
| 380 |
-
}
|
| 381 |
try:
|
| 382 |
patients = sf_client.query_records(f"SELECT Id, Language__c, Name FROM Patient__c WHERE Phone__c = '{patient_phone}'")
|
| 383 |
if not patients:
|
| 384 |
return "Patient not found. Please register the patient first."
|
| 385 |
-
language = patients[0]["Language__c"].lower()
|
| 386 |
-
question =
|
| 387 |
-
survey_data = {"Question__c": question, "Answer__c": answer}
|
| 388 |
result = sf_client.create_record("Survey__c", survey_data)
|
| 389 |
-
if isinstance(result, str):
|
| 390 |
return result
|
| 391 |
-
if
|
| 392 |
-
|
| 393 |
-
|
| 394 |
-
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
"Sentiment__c": analysis["sentiment_score"]
|
| 399 |
-
}
|
| 400 |
-
result = sf_client.create_record("SymptomLog__c", symptom_data)
|
| 401 |
-
if isinstance(result, str): # Error message returned
|
| 402 |
-
return result
|
| 403 |
-
if result and analysis["risk_level"] == "High":
|
| 404 |
-
case_data = {
|
| 405 |
-
"RelatedPatient__c": patients[0]["Id"], "Priority__c": "High",
|
| 406 |
-
"Description__c": f"High-risk survey response: {answer}"
|
| 407 |
}
|
| 408 |
-
sf_client.create_record("
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
|
| 412 |
-
|
| 413 |
-
|
| 414 |
-
|
| 415 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 416 |
except Exception as e:
|
| 417 |
logging.error(f"Error submitting survey: {str(e)}")
|
| 418 |
return f"Error submitting survey: {str(e)}. Please try again or contact support."
|
| 419 |
|
| 420 |
-
|
|
|
|
| 421 |
try:
|
| 422 |
-
|
|
|
|
|
|
|
|
|
|
| 423 |
if symptom_logs:
|
| 424 |
df = pd.DataFrame([
|
| 425 |
-
{"Patient": log["Patient__c"], "Response": log["ResponseText__c"],
|
| 426 |
"Risk Score": log["RiskScore__c"], "Severity": log["Severity__c"],
|
| 427 |
"Sentiment": log["Sentiment__c"]} for log in symptom_logs
|
| 428 |
])
|
| 429 |
high_risk = df[df["Severity"] == "High"]
|
| 430 |
-
high_risk_info = high_risk.to_dict('records') if not high_risk.empty else "No high-risk cases."
|
| 431 |
logging.info("Risk dashboard loaded successfully.")
|
| 432 |
return df, high_risk_info
|
| 433 |
logging.info("No symptom logs found.")
|
|
@@ -436,6 +419,7 @@ def view_risk_dashboard():
|
|
| 436 |
logging.error(f"Error loading risk dashboard: {str(e)}")
|
| 437 |
return pd.DataFrame(), f"Error loading risk dashboard: {str(e)}. Please try again or contact support."
|
| 438 |
|
|
|
|
| 439 |
def escalate_case(patient_id, response_text):
|
| 440 |
if not patient_id or not response_text:
|
| 441 |
return "All fields are required."
|
|
@@ -445,13 +429,15 @@ def escalate_case(patient_id, response_text):
|
|
| 445 |
"Description__c": f"High-risk response: {response_text}"
|
| 446 |
}
|
| 447 |
result = sf_client.create_record("Case__c", case_data)
|
| 448 |
-
if isinstance(result, str):
|
| 449 |
return result
|
| 450 |
if result:
|
| 451 |
patients = sf_client.query_records(f"SELECT Phone__c FROM Patient__c WHERE Id = '{patient_id}'")
|
| 452 |
if patients:
|
| 453 |
-
|
| 454 |
-
|
|
|
|
|
|
|
| 455 |
message = "Your case has been escalated due to a high-risk response. A doctor will contact you soon."
|
| 456 |
twilio_client.send_message(patients[0]["Phone__c"], message, method=consent_method.lower())
|
| 457 |
logging.info(f"Case created for patient: {patient_id}")
|
|
@@ -462,133 +448,110 @@ def escalate_case(patient_id, response_text):
|
|
| 462 |
return f"Error escalating case: {str(e)}. Please try again or contact support."
|
| 463 |
|
| 464 |
# Gradio Interface
|
| 465 |
-
with gr.Blocks(theme=gr.themes.Soft(), title="AI
|
| 466 |
-
gr.Markdown("# AI
|
| 467 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 468 |
with gr.Tabs():
|
| 469 |
-
with gr.Tab("Patient
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 470 |
with gr.Row():
|
| 471 |
with gr.Column():
|
| 472 |
-
name_first = gr.Textbox(label="First Name", placeholder="
|
| 473 |
-
name_last = gr.Textbox(label="Last Name", placeholder="
|
| 474 |
with gr.Column():
|
| 475 |
-
dob_input = gr.Textbox(label="Date of Birth", placeholder="MM-DD-YYYY")
|
| 476 |
-
|
| 477 |
with gr.Row():
|
| 478 |
-
height_input = gr.Textbox(label="Height (inches)", placeholder="e.g., 65")
|
| 479 |
-
weight_input = gr.Textbox(label="Weight (pounds)", placeholder="e.g., 150")
|
| 480 |
-
marital_input = gr.Dropdown(label="Marital Status", choices=["
|
| 481 |
with gr.Row():
|
| 482 |
-
phone_input = gr.Textbox(label="Contact Number", placeholder="
|
| 483 |
-
email_input = gr.Textbox(label="Email", placeholder="
|
| 484 |
with gr.Row():
|
| 485 |
with gr.Column():
|
| 486 |
-
address_city = gr.Textbox(label="City", placeholder="
|
| 487 |
-
address_state = gr.Textbox(label="State
|
| 488 |
-
postal_input = gr.Textbox(label="Postal
|
| 489 |
with gr.Row():
|
| 490 |
-
med_yes_no = gr.Radio(label="Taking
|
| 491 |
-
med_list = gr.Textbox(label="
|
| 492 |
-
with gr.Accordion("
|
| 493 |
with gr.Row():
|
| 494 |
with gr.Column():
|
| 495 |
-
emergency_first = gr.Textbox(label="First Name", placeholder="
|
| 496 |
-
emergency_last = gr.Textbox(label="Last Name", placeholder="
|
| 497 |
with gr.Column():
|
| 498 |
-
emergency_relation = gr.Textbox(label="Relationship", placeholder="
|
| 499 |
-
emergency_number = gr.Textbox(label="Contact Number", placeholder="
|
| 500 |
-
language_input = gr.Dropdown(["English", "Telugu", "Hindi"], label="Language")
|
| 501 |
-
consent_input = gr.Dropdown(["SMS", "WhatsApp", "Awaiting"], label="Consent Method")
|
| 502 |
-
register_button = gr.Button("
|
| 503 |
-
register_output = gr.Textbox(label="Result")
|
| 504 |
-
|
| 505 |
-
# Update visibility of medication list based on radio selection
|
| 506 |
def toggle_med_list(med_choice):
|
| 507 |
return gr.update(visible=med_choice == "Yes")
|
| 508 |
|
| 509 |
-
med_yes_no.change(
|
| 510 |
-
fn=toggle_med_list,
|
| 511 |
-
inputs=med_yes_no,
|
| 512 |
-
outputs=med_list
|
| 513 |
-
)
|
| 514 |
-
|
| 515 |
register_button.click(
|
| 516 |
fn=register_patient,
|
| 517 |
-
inputs=[name_first, name_last, dob_input,
|
| 518 |
-
phone_input, email_input, address_city, address_state,
|
| 519 |
-
|
| 520 |
-
|
| 521 |
-
outputs=[register_output, patient_phone_state]
|
| 522 |
)
|
| 523 |
|
| 524 |
-
with gr.Tab("
|
| 525 |
-
|
| 526 |
-
|
| 527 |
-
|
| 528 |
-
|
| 529 |
-
consent_button.click(
|
| 530 |
-
fn=submit_consent,
|
| 531 |
-
inputs=[consent_phone_input, consent_method_input],
|
| 532 |
-
outputs=consent_output
|
| 533 |
-
)
|
| 534 |
-
|
| 535 |
-
with gr.Tab("Follow-up Messaging"):
|
| 536 |
-
followup_phone_input = gr.Textbox(label="Patient Phone Number", value=lambda x: x, interactive=False) if patient_phone_state.value else gr.Textbox(label="Patient Phone Number", placeholder="+1234567890")
|
| 537 |
-
message_template_input = gr.Textbox(label="Message Template", value="How are you feeling today?")
|
| 538 |
-
followup_date_input = gr.Textbox(label="Follow-up Date (YYYY-MM-DD)", value=date.today().isoformat())
|
| 539 |
-
followup_button = gr.Button("Schedule Follow-up")
|
| 540 |
-
followup_output = gr.Textbox(label="Result")
|
| 541 |
-
followup_button.click(
|
| 542 |
-
fn=schedule_followup,
|
| 543 |
-
inputs=[followup_phone_input, message_template_input, followup_date_input],
|
| 544 |
-
outputs=followup_output
|
| 545 |
-
)
|
| 546 |
-
|
| 547 |
-
with gr.Tab("Appointment Scheduling"):
|
| 548 |
-
appt_email_input = gr.Textbox(label="Patient Email", placeholder="patient@example.com")
|
| 549 |
-
appt_event_input = gr.Textbox(label="Event Type Slug", value="30min", placeholder="e.g., 30min")
|
| 550 |
-
appt_button = gr.Button("Schedule Appointment")
|
| 551 |
-
appt_output = gr.Textbox(label="Result")
|
| 552 |
appt_button.click(
|
| 553 |
fn=schedule_appointment,
|
| 554 |
inputs=[appt_email_input, appt_event_input],
|
| 555 |
outputs=appt_output
|
| 556 |
)
|
| 557 |
|
| 558 |
-
with gr.Tab("Survey"):
|
| 559 |
-
survey_phone_input = gr.Textbox(label="Patient Phone Number",
|
| 560 |
survey_question_input = gr.Dropdown(label="Select Question", choices=[
|
| 561 |
"How would you rate your visit today?",
|
| 562 |
"Are you experiencing any discomfort?",
|
| 563 |
"How satisfied are you with our staff?"
|
| 564 |
-
],
|
| 565 |
-
survey_answer_input = gr.Textbox(label="Your Answer", lines=4)
|
| 566 |
survey_button = gr.Button("Submit Survey")
|
| 567 |
-
survey_output = gr.Textbox(label="Result")
|
| 568 |
survey_button.click(
|
| 569 |
fn=submit_survey,
|
| 570 |
inputs=[survey_phone_input, survey_answer_input],
|
| 571 |
outputs=survey_output
|
| 572 |
)
|
| 573 |
|
| 574 |
-
with gr.Tab("
|
| 575 |
-
|
| 576 |
-
|
| 577 |
-
|
| 578 |
-
|
| 579 |
-
|
| 580 |
-
|
|
|
|
|
|
|
| 581 |
)
|
| 582 |
-
with gr.Row():
|
| 583 |
-
escalate_id_input = gr.Textbox(label="Patient ID (for escalation)")
|
| 584 |
-
escalate_response_input = gr.Textbox(label="Response Text")
|
| 585 |
-
escalate_button = gr.Button("Escalate Case")
|
| 586 |
-
escalate_output = gr.Textbox(label="Result")
|
| 587 |
-
escalate_button.click(
|
| 588 |
-
fn=escalate_case,
|
| 589 |
-
inputs=[escalate_id_input, escalate_response_input],
|
| 590 |
-
outputs=escalate_output
|
| 591 |
-
)
|
| 592 |
|
| 593 |
# Launch Gradio app
|
| 594 |
if __name__ == "__main__":
|
|
|
|
| 6 |
import logging
|
| 7 |
import pandas as pd
|
| 8 |
import os
|
| 9 |
+
from urllib.parse import quote, urlparse, parse_qs
|
| 10 |
|
| 11 |
# Configure logging for audit purposes (FR4, NFR: Security)
|
| 12 |
logging.basicConfig(level=logging.INFO, filename='app_log.txt',
|
|
|
|
| 67 |
"Content-Type": "application/json"
|
| 68 |
}
|
| 69 |
|
| 70 |
+
# Hugging Face Client
|
| 71 |
class HuggingFaceClient:
|
| 72 |
def __init__(self):
|
| 73 |
self.api_url = "https://api-inference.huggingface.co/models/"
|
| 74 |
+
self.headers = {}
|
| 75 |
self.sentiment_model = "distilbert-base-uncased-finetuned-sst-2-english"
|
| 76 |
+
self.severity_model = "bert-base-uncased"
|
| 77 |
self.translation_model = "facebook/m2m100_418M"
|
| 78 |
|
| 79 |
def query(self, text, model):
|
|
|
|
| 231 |
logging.error(f"Failed to schedule Calendly event: {str(e)}")
|
| 232 |
return None
|
| 233 |
|
| 234 |
+
# Initialize Clients
|
| 235 |
hf_client = HuggingFaceClient()
|
| 236 |
sf_client = SalesforceClient(SALESFORCE_BASE_URL, SALESFORCE_HEADERS)
|
| 237 |
twilio_client = TwilioClient(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_PHONE)
|
| 238 |
calendly_client = CalendlyClient(CALENDLY_API_TOKEN)
|
| 239 |
|
| 240 |
+
# State to store patient context from URL
|
| 241 |
+
patient_state = gr.State(value=None)
|
| 242 |
+
|
| 243 |
+
# Get URL parameters
|
| 244 |
+
def get_url_params():
|
| 245 |
+
query = os.getenv("QUERY_STRING", "")
|
| 246 |
+
params = parse_qs(query)
|
| 247 |
+
patient_id = params.get("patientId", [None])[0]
|
| 248 |
+
patient_name = params.get("patientName", [None])[0]
|
| 249 |
+
if patient_id and patient_name:
|
| 250 |
+
return {"patientId": patient_id, "patientName": patient_name}
|
| 251 |
+
return None
|
| 252 |
+
|
| 253 |
+
# Initialize patient state from URL
|
| 254 |
+
initial_patient = get_url_params()
|
| 255 |
+
if initial_patient:
|
| 256 |
+
patient_state.value = initial_patient
|
| 257 |
+
|
| 258 |
+
# Automated Follow-up Message
|
| 259 |
+
def send_automated_followup(patient_id, action, phone=None):
|
| 260 |
+
try:
|
| 261 |
+
patients = sf_client.query_records(f"SELECT Phone__c, ConsentGiven__c, Name FROM Patient__c WHERE Id = '{patient_id}'")
|
| 262 |
+
if not patients or patients[0]["ConsentGiven__c"] != "Approved":
|
| 263 |
+
return
|
| 264 |
+
phone = phone or patients[0]["Phone__c"]
|
| 265 |
+
consent = sf_client.query_records(f"SELECT Method__c FROM Consent__c WHERE Patient__c = '{patient_id}' ORDER BY CreatedDate DESC LIMIT 1")
|
| 266 |
+
consent_method = consent[0]["Method__c"] if consent else "SMS"
|
| 267 |
+
messages = {
|
| 268 |
+
"registration": f"Welcome, {patients[0]['Name']}! Your registration is complete. A follow-up is scheduled.",
|
| 269 |
+
"survey": f"Dear {patients[0]['Name']}, thank you for your survey response. We’ll follow up soon.",
|
| 270 |
+
"appointment": f"Hi {patients[0]['Name']}, your appointment is confirmed. Details will follow."
|
| 271 |
+
}
|
| 272 |
+
message = messages.get(action, "A follow-up action has been triggered.")
|
| 273 |
+
twilio_client.send_message(phone, message, method=consent_method.lower())
|
| 274 |
+
logging.info(f"Automated follow-up sent for {action} to {phone}")
|
| 275 |
+
except Exception as e:
|
| 276 |
+
logging.error(f"Error sending automated follow-up: {str(e)}")
|
| 277 |
+
|
| 278 |
+
# Registration Function
|
| 279 |
+
def register_patient(name_first, name_last, dob, gender, height, weight, marital, phone, email,
|
| 280 |
+
address_city, address_state, postal, med_yes_no, med_list,
|
| 281 |
+
emergency_first, emergency_last, emergency_relation, emergency_number, language, consent_status):
|
| 282 |
+
if not all([name_first, name_last, dob, gender, height, weight, marital, phone, email,
|
| 283 |
+
address_city, address_state, postal, language, consent_status]) or \
|
| 284 |
(med_yes_no == "Yes" and not med_list):
|
| 285 |
+
return "All fields are required.", None
|
| 286 |
try:
|
| 287 |
patient_data = {
|
| 288 |
"First_Name__c": name_first,
|
| 289 |
"Last_Name__c": name_last,
|
| 290 |
+
"DateOfBirth__c": dob,
|
| 291 |
+
"Gender__c": gender,
|
| 292 |
+
"Height__c": float(height),
|
| 293 |
+
"Weight__c": float(weight),
|
| 294 |
+
"MaritalStatus__c": marital,
|
| 295 |
"Phone__c": phone,
|
| 296 |
"Email__c": email,
|
| 297 |
"City__c": address_city,
|
| 298 |
"State__c": address_state,
|
| 299 |
+
"PostalCode__c": postal,
|
| 300 |
+
"Medication__c": med_list if med_yes_no == "Yes" else "None",
|
| 301 |
+
"EmergencyContactName__c": f"{emergency_first} {emergency_last}".strip() or "",
|
| 302 |
+
"EmergencyContactRelationship__c": emergency_relation or "",
|
| 303 |
+
"EmergencyContactNumber__c": emergency_number or "",
|
|
|
|
| 304 |
"Language__c": language,
|
| 305 |
+
"ConsentGiven__c": consent_status == "Approved"
|
| 306 |
}
|
| 307 |
result = sf_client.create_record("Patient__c", patient_data)
|
| 308 |
+
if isinstance(result, str):
|
| 309 |
+
return result, None
|
| 310 |
if result:
|
| 311 |
logging.info(f"Registered patient: {result}")
|
| 312 |
message = f"Welcome! You are registered. Your patient ID is {result}."
|
| 313 |
+
if consent_status == "Approved":
|
| 314 |
+
twilio_client.send_message(phone, message, method="sms")
|
| 315 |
+
send_automated_followup(result, "registration", phone)
|
| 316 |
+
return f"Patient registered successfully! Your patient ID is {result}.", {"patientId": result, "patientName": f"{name_first} {name_last}"}
|
| 317 |
+
return "Failed to register patient. Please try again or contact support.", None
|
| 318 |
except Exception as e:
|
| 319 |
logging.error(f"Error registering patient: {str(e)}")
|
| 320 |
+
return f"Error registering patient: {str(e)}. Please try again or contact support.", None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 321 |
|
| 322 |
+
# Appointment Scheduling
|
| 323 |
def schedule_appointment(patient_email, event_type_slug="30min"):
|
| 324 |
+
if not patient_email:
|
| 325 |
+
return "Patient Email is required."
|
| 326 |
try:
|
| 327 |
+
patients = sf_client.query_records(f"SELECT Id, Phone__c, ConsentGiven__c, Name FROM Patient__c WHERE Email__c = '{patient_email}'")
|
| 328 |
if not patients:
|
| 329 |
return "Patient not found. Please register the patient first."
|
| 330 |
event_type_uuid = calendly_client.get_event_type_uuid(event_type_slug)
|
|
|
|
| 334 |
if event_time:
|
| 335 |
appointment_data = {"DateTime__c": event_time, "Status__c": "Scheduled", "Patient__c": patients[0]["Id"]}
|
| 336 |
result = sf_client.create_record("Appointment__c", appointment_data)
|
| 337 |
+
if isinstance(result, str):
|
| 338 |
return result
|
| 339 |
if result:
|
| 340 |
+
consent_method = "SMS"
|
| 341 |
+
if patients[0]["ConsentGiven__c"]:
|
| 342 |
+
consent = sf_client.query_records(f"SELECT Method__c FROM Consent__c WHERE Patient__c = '{patients[0]['Id']}' ORDER BY CreatedDate DESC LIMIT 1")
|
| 343 |
+
consent_method = consent[0]["Method__c"] if consent else "SMS"
|
| 344 |
+
message = f"Hi {patients[0]['Name']}, your appointment is scheduled for {event_time}. Please confirm."
|
| 345 |
twilio_client.send_message(patients[0]["Phone__c"], message, method=consent_method.lower())
|
| 346 |
+
send_automated_followup(patients[0]["Id"], "appointment", patients[0]["Phone__c"])
|
| 347 |
return f"Appointment scheduled successfully for {event_time}!"
|
| 348 |
return "Failed to create appointment record. Please try again or contact support."
|
| 349 |
return "Failed to schedule appointment. Please try again or contact support."
|
|
|
|
| 351 |
logging.error(f"Error scheduling appointment: {str(e)}")
|
| 352 |
return f"Error scheduling appointment: {str(e)}. Please try again or contact support."
|
| 353 |
|
| 354 |
+
# Survey Submission
|
| 355 |
def submit_survey(patient_phone, answer):
|
| 356 |
if not patient_phone or not answer:
|
| 357 |
return "Please provide an answer to the selected question."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 358 |
try:
|
| 359 |
patients = sf_client.query_records(f"SELECT Id, Language__c, Name FROM Patient__c WHERE Phone__c = '{patient_phone}'")
|
| 360 |
if not patients:
|
| 361 |
return "Patient not found. Please register the patient first."
|
| 362 |
+
language = patients[0]["Language__c"].lower() or "en"
|
| 363 |
+
question = "How would you rate your visit today?" # Default question
|
| 364 |
+
survey_data = {"Question__c": question, "Answer__c": answer, "Patient__c": patients[0]["Id"]}
|
| 365 |
result = sf_client.create_record("Survey__c", survey_data)
|
| 366 |
+
if isinstance(result, str):
|
| 367 |
return result
|
| 368 |
+
if result:
|
| 369 |
+
analysis = hf_client.analyze_response(answer, language)
|
| 370 |
+
if analysis:
|
| 371 |
+
symptom_data = {
|
| 372 |
+
"Patient__c": patients[0]["Id"], "ResponseText__c": answer,
|
| 373 |
+
"RiskScore__c": analysis["risk_level"], "Severity__c": analysis["severity_score"],
|
| 374 |
+
"Sentiment__c": analysis["sentiment_score"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 375 |
}
|
| 376 |
+
symptom_result = sf_client.create_record("SymptomLog__c", symptom_data)
|
| 377 |
+
if isinstance(symptom_result, str):
|
| 378 |
+
return symptom_result
|
| 379 |
+
if analysis["risk_level"] == "High":
|
| 380 |
+
case_data = {
|
| 381 |
+
"RelatedPatient__c": patients[0]["Id"], "Priority__c": "High",
|
| 382 |
+
"Description__c": f"High-risk survey response: {answer}"
|
| 383 |
+
}
|
| 384 |
+
sf_client.create_record("Case__c", case_data)
|
| 385 |
+
consent_method = "SMS"
|
| 386 |
+
if patients[0]["ConsentGiven__c"]:
|
| 387 |
+
consent = sf_client.query_records(f"SELECT Method__c FROM Consent__c WHERE Patient__c = '{patients[0]['Id']}' ORDER BY CreatedDate DESC LIMIT 1")
|
| 388 |
+
consent_method = consent[0]["Method__c"] if consent else "SMS"
|
| 389 |
+
message = f"Thank you, {patients[0]['Name']}, for your response to '{question}'. Your risk level is {analysis['risk_level']}."
|
| 390 |
+
twilio_client.send_message(patient_phone, message, method=consent_method.lower())
|
| 391 |
+
send_automated_followup(patients[0]["Id"], "survey", patient_phone)
|
| 392 |
+
return f"Survey submitted successfully! Risk Level: {analysis['risk_level']}."
|
| 393 |
+
return "Failed to analyze survey response. Please try again or contact support."
|
| 394 |
+
return "Failed to submit survey. Please try again or contact support."
|
| 395 |
except Exception as e:
|
| 396 |
logging.error(f"Error submitting survey: {str(e)}")
|
| 397 |
return f"Error submitting survey: {str(e)}. Please try again or contact support."
|
| 398 |
|
| 399 |
+
# Risk Dashboard
|
| 400 |
+
def view_risk_dashboard(patient_id=None):
|
| 401 |
try:
|
| 402 |
+
query = "SELECT Patient__c, ResponseText__c, RiskScore__c, Severity__c, Sentiment__c FROM SymptomLog__c"
|
| 403 |
+
if patient_id:
|
| 404 |
+
query += f" WHERE Patient__c = '{patient_id}'"
|
| 405 |
+
symptom_logs = sf_client.query_records(query)
|
| 406 |
if symptom_logs:
|
| 407 |
df = pd.DataFrame([
|
| 408 |
+
{"Patient ID": log["Patient__c"], "Response": log["ResponseText__c"],
|
| 409 |
"Risk Score": log["RiskScore__c"], "Severity": log["Severity__c"],
|
| 410 |
"Sentiment": log["Sentiment__c"]} for log in symptom_logs
|
| 411 |
])
|
| 412 |
high_risk = df[df["Severity"] == "High"]
|
| 413 |
+
high_risk_info = high_risk.to_dict('records') if not high_risk.empty else "No high-risk cases for this patient."
|
| 414 |
logging.info("Risk dashboard loaded successfully.")
|
| 415 |
return df, high_risk_info
|
| 416 |
logging.info("No symptom logs found.")
|
|
|
|
| 419 |
logging.error(f"Error loading risk dashboard: {str(e)}")
|
| 420 |
return pd.DataFrame(), f"Error loading risk dashboard: {str(e)}. Please try again or contact support."
|
| 421 |
|
| 422 |
+
# Escalate Case
|
| 423 |
def escalate_case(patient_id, response_text):
|
| 424 |
if not patient_id or not response_text:
|
| 425 |
return "All fields are required."
|
|
|
|
| 429 |
"Description__c": f"High-risk response: {response_text}"
|
| 430 |
}
|
| 431 |
result = sf_client.create_record("Case__c", case_data)
|
| 432 |
+
if isinstance(result, str):
|
| 433 |
return result
|
| 434 |
if result:
|
| 435 |
patients = sf_client.query_records(f"SELECT Phone__c FROM Patient__c WHERE Id = '{patient_id}'")
|
| 436 |
if patients:
|
| 437 |
+
consent_method = "SMS"
|
| 438 |
+
if patients[0]["ConsentGiven__c"]:
|
| 439 |
+
consent = sf_client.query_records(f"SELECT Method__c FROM Consent__c WHERE Patient__c = '{patient_id}' ORDER BY CreatedDate DESC LIMIT 1")
|
| 440 |
+
consent_method = consent[0]["Method__c"] if consent else "SMS"
|
| 441 |
message = "Your case has been escalated due to a high-risk response. A doctor will contact you soon."
|
| 442 |
twilio_client.send_message(patients[0]["Phone__c"], message, method=consent_method.lower())
|
| 443 |
logging.info(f"Case created for patient: {patient_id}")
|
|
|
|
| 448 |
return f"Error escalating case: {str(e)}. Please try again or contact support."
|
| 449 |
|
| 450 |
# Gradio Interface
|
| 451 |
+
with gr.Blocks(theme=gr.themes.Soft(), title="Clinic AI Assistant") as demo:
|
| 452 |
+
gr.Markdown("# Clinic AI Assistant - Patient Management Dashboard")
|
| 453 |
+
gr.Markdown("Welcome to your personalized clinic dashboard. Log in via Salesforce to access your data.")
|
| 454 |
+
|
| 455 |
+
# Get patient context from URL or state
|
| 456 |
+
patient_context = patient_state.value or {}
|
| 457 |
+
patient_id = patient_context.get("patientId")
|
| 458 |
+
patient_name = patient_context.get("patientName")
|
| 459 |
+
|
| 460 |
with gr.Tabs():
|
| 461 |
+
with gr.Tab("Patient Dashboard", id=0):
|
| 462 |
+
gr.Markdown(f"## Welcome, {patient_name or 'Guest'}! (Patient ID: {patient_id or 'Not Logged In'})")
|
| 463 |
+
if patient_id:
|
| 464 |
+
dashboard_table, high_risk_info = view_risk_dashboard(patient_id)
|
| 465 |
+
gr.Dataframe(dashboard_table, label="Your Symptom Logs", interactive=False)
|
| 466 |
+
gr.Textbox(high_risk_info, label="High-Risk Cases", interactive=False)
|
| 467 |
+
else:
|
| 468 |
+
gr.Markdown("Please log in via Salesforce to view your dashboard.")
|
| 469 |
+
|
| 470 |
+
with gr.Tab("Register New Patient", id=1):
|
| 471 |
with gr.Row():
|
| 472 |
with gr.Column():
|
| 473 |
+
name_first = gr.Textbox(label="First Name", placeholder="John", info="Enter your first name")
|
| 474 |
+
name_last = gr.Textbox(label="Last Name", placeholder="Doe", info="Enter your last name")
|
| 475 |
with gr.Column():
|
| 476 |
+
dob_input = gr.Textbox(label="Date of Birth (MM-DD-YYYY)", placeholder="01-15-1990", info="e.g., MM-DD-YYYY")
|
| 477 |
+
gender_input = gr.Dropdown(label="Gender", choices=["Male", "Female", "Other", "Prefer Not to Say"], value="Prefer Not to Say")
|
| 478 |
with gr.Row():
|
| 479 |
+
height_input = gr.Textbox(label="Height (inches)", placeholder="65", info="e.g., 65 inches")
|
| 480 |
+
weight_input = gr.Textbox(label="Weight (pounds)", placeholder="150", info="e.g., 150 lbs")
|
| 481 |
+
marital_input = gr.Dropdown(label="Marital Status", choices=["Single", "Married", "Divorced", "Widowed"], value="Single")
|
| 482 |
with gr.Row():
|
| 483 |
+
phone_input = gr.Textbox(label="Contact Number", placeholder="+1234567890", info="e.g., +12025550123")
|
| 484 |
+
email_input = gr.Textbox(label="Email", placeholder="john.doe@clinic.com", info="e.g., your.email@domain.com")
|
| 485 |
with gr.Row():
|
| 486 |
with gr.Column():
|
| 487 |
+
address_city = gr.Textbox(label="City", placeholder="New York", info="e.g., New York")
|
| 488 |
+
address_state = gr.Textbox(label="State", placeholder="NY", info="e.g., NY")
|
| 489 |
+
postal_input = gr.Textbox(label="Postal Code", placeholder="10001", info="e.g., 10001")
|
| 490 |
with gr.Row():
|
| 491 |
+
med_yes_no = gr.Radio(label="Taking Medications?", choices=["Yes", "No"], value="No")
|
| 492 |
+
med_list = gr.Textbox(label="Medication List", placeholder="e.g., Aspirin, 100mg", lines=2, visible=False)
|
| 493 |
+
with gr.Accordion("Emergency Contact", open=False):
|
| 494 |
with gr.Row():
|
| 495 |
with gr.Column():
|
| 496 |
+
emergency_first = gr.Textbox(label="First Name", placeholder="Jane")
|
| 497 |
+
emergency_last = gr.Textbox(label="Last Name", placeholder="Doe")
|
| 498 |
with gr.Column():
|
| 499 |
+
emergency_relation = gr.Textbox(label="Relationship", placeholder="Spouse")
|
| 500 |
+
emergency_number = gr.Textbox(label="Contact Number", placeholder="+1234567890")
|
| 501 |
+
language_input = gr.Dropdown(["English", "Telugu", "Hindi"], label="Preferred Language", value="English")
|
| 502 |
+
consent_input = gr.Dropdown(["SMS", "WhatsApp", "Awaiting"], label="Consent Method", value="SMS")
|
| 503 |
+
register_button = gr.Button("Register Patient")
|
| 504 |
+
register_output = gr.Textbox(label="Registration Result")
|
| 505 |
+
|
|
|
|
| 506 |
def toggle_med_list(med_choice):
|
| 507 |
return gr.update(visible=med_choice == "Yes")
|
| 508 |
|
| 509 |
+
med_yes_no.change(fn=toggle_med_list, inputs=med_yes_no, outputs=med_list)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 510 |
register_button.click(
|
| 511 |
fn=register_patient,
|
| 512 |
+
inputs=[name_first, name_last, dob_input, gender_input, height_input, weight_input, marital_input,
|
| 513 |
+
phone_input, email_input, address_city, address_state, postal_input, med_yes_no, med_list,
|
| 514 |
+
emergency_first, emergency_last, emergency_relation, emergency_number, language_input, consent_input],
|
| 515 |
+
outputs=[register_output, patient_state]
|
|
|
|
| 516 |
)
|
| 517 |
|
| 518 |
+
with gr.Tab("Schedule Appointment", id=2):
|
| 519 |
+
appt_email_input = gr.Textbox(label="Patient Email", placeholder="john.doe@clinic.com", value=patient_context.get("email", ""))
|
| 520 |
+
appt_event_input = gr.Dropdown(label="Appointment Duration", choices=["15min", "30min", "60min"], value="30min", info="Select appointment length")
|
| 521 |
+
appt_button = gr.Button("Book Appointment")
|
| 522 |
+
appt_output = gr.Textbox(label="Appointment Result")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 523 |
appt_button.click(
|
| 524 |
fn=schedule_appointment,
|
| 525 |
inputs=[appt_email_input, appt_event_input],
|
| 526 |
outputs=appt_output
|
| 527 |
)
|
| 528 |
|
| 529 |
+
with gr.Tab("Submit Survey", id=3):
|
| 530 |
+
survey_phone_input = gr.Textbox(label="Patient Phone Number", placeholder="+1234567890", value=patient_context.get("phone", ""), info="e.g., +12025550123")
|
| 531 |
survey_question_input = gr.Dropdown(label="Select Question", choices=[
|
| 532 |
"How would you rate your visit today?",
|
| 533 |
"Are you experiencing any discomfort?",
|
| 534 |
"How satisfied are you with our staff?"
|
| 535 |
+
], value="How would you rate your visit today?")
|
| 536 |
+
survey_answer_input = gr.Textbox(label="Your Answer", lines=4, placeholder="e.g., Very satisfied")
|
| 537 |
survey_button = gr.Button("Submit Survey")
|
| 538 |
+
survey_output = gr.Textbox(label="Survey Result")
|
| 539 |
survey_button.click(
|
| 540 |
fn=submit_survey,
|
| 541 |
inputs=[survey_phone_input, survey_answer_input],
|
| 542 |
outputs=survey_output
|
| 543 |
)
|
| 544 |
|
| 545 |
+
with gr.Tab("Escalate Case", id=4):
|
| 546 |
+
escalate_id_input = gr.Textbox(label="Patient ID", placeholder="e.g., 001xxxxxxxxxxxx", value=patient_context.get("patientId", ""), info="Enter Patient ID for escalation")
|
| 547 |
+
escalate_response_input = gr.Textbox(label="Response Text", placeholder="e.g., Severe pain reported", info="Describe the issue")
|
| 548 |
+
escalate_button = gr.Button("Escalate Case")
|
| 549 |
+
escalate_output = gr.Textbox(label="Escalation Result")
|
| 550 |
+
escalate_button.click(
|
| 551 |
+
fn=escalate_case,
|
| 552 |
+
inputs=[escalate_id_input, escalate_response_input],
|
| 553 |
+
outputs=escalate_output
|
| 554 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 555 |
|
| 556 |
# Launch Gradio app
|
| 557 |
if __name__ == "__main__":
|