Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -13,30 +13,65 @@ logging.basicConfig(level=logging.INFO, filename='app_log.txt',
|
|
| 13 |
format='%(asctime)s - %(levelname)s - %(message)s')
|
| 14 |
|
| 15 |
# Load environment variables (Hugging Face Spaces secrets)
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
|
|
|
| 23 |
|
| 24 |
# Validate environment variables
|
| 25 |
-
|
|
|
|
|
|
|
| 26 |
logging.error("Missing environment variables for API integrations.")
|
| 27 |
raise ValueError("Missing environment variables. Configure API tokens in Hugging Face Spaces secrets.")
|
| 28 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
# Salesforce REST API Headers
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
SALESFORCE_HEADERS = {
|
| 31 |
-
"Authorization": f"Bearer {
|
| 32 |
"Content-Type": "application/json"
|
| 33 |
}
|
| 34 |
|
| 35 |
-
# Hugging Face Client
|
| 36 |
class HuggingFaceClient:
|
| 37 |
-
def __init__(self
|
| 38 |
self.api_url = "https://api-inference.huggingface.co/models/"
|
| 39 |
-
self.headers = {
|
| 40 |
self.sentiment_model = "distilbert-base-uncased-finetuned-sst-2-english"
|
| 41 |
self.severity_model = "bert-base-uncased" # Placeholder; use custom model
|
| 42 |
self.translation_model = "facebook/m2m100_418M"
|
|
@@ -58,6 +93,8 @@ class HuggingFaceClient:
|
|
| 58 |
response_text = self.translate(response_text, language_code, "en")
|
| 59 |
sentiment_result = self.query(response_text, self.sentiment_model)
|
| 60 |
severity_result = self.query(response_text, self.severity_model)
|
|
|
|
|
|
|
| 61 |
sentiment_score = self.process_sentiment(sentiment_result)
|
| 62 |
severity_score = self.process_severity(severity_result)
|
| 63 |
risk_level = self.determine_risk_level(sentiment_score, severity_score)
|
|
@@ -70,7 +107,7 @@ class HuggingFaceClient:
|
|
| 70 |
def translate(self, text, source_lang, target_lang):
|
| 71 |
payload = {"inputs": text, "parameters": {"src_lang": source_lang, "tgt_lang": target_lang}}
|
| 72 |
result = self.query(payload, self.translation_model)
|
| 73 |
-
return result[0]
|
| 74 |
|
| 75 |
def process_sentiment(self, result):
|
| 76 |
if result and isinstance(result, list) and len(result) > 0:
|
|
@@ -107,6 +144,9 @@ class SalesforceClient:
|
|
| 107 |
record_id = response.json()["id"]
|
| 108 |
logging.info(f"Created {object_name} record: {record_id}")
|
| 109 |
return record_id
|
|
|
|
|
|
|
|
|
|
| 110 |
except requests.exceptions.RequestException as e:
|
| 111 |
logging.error(f"Failed to create {object_name} record: {str(e)}")
|
| 112 |
return None
|
|
@@ -117,6 +157,9 @@ class SalesforceClient:
|
|
| 117 |
response = requests.get(f"{self.base_url}query/?q={encoded_query}", headers=self.headers)
|
| 118 |
response.raise_for_status()
|
| 119 |
return response.json()["records"]
|
|
|
|
|
|
|
|
|
|
| 120 |
except requests.exceptions.RequestException as e:
|
| 121 |
logging.error(f"Failed to query records: {str(e)}")
|
| 122 |
return []
|
|
@@ -130,42 +173,71 @@ class TwilioClient:
|
|
| 130 |
|
| 131 |
def send_message(self, to_number, body):
|
| 132 |
try:
|
|
|
|
|
|
|
| 133 |
data = {"From": self.from_number, "To": to_number, "Body": body}
|
| 134 |
response = requests.post(self.api_url, auth=self.auth, data=data, timeout=10)
|
| 135 |
response.raise_for_status()
|
| 136 |
logging.info(f"Sent message to {to_number}")
|
| 137 |
return True
|
|
|
|
|
|
|
|
|
|
| 138 |
except requests.exceptions.RequestException as e:
|
| 139 |
logging.error(f"Failed to send message: {str(e)}")
|
| 140 |
-
return
|
| 141 |
|
| 142 |
# Calendly Client
|
| 143 |
class CalendlyClient:
|
| 144 |
def __init__(self, api_token):
|
| 145 |
self.api_url = "https://api.calendly.com/scheduled_events"
|
|
|
|
| 146 |
self.headers = {"Authorization": f"Bearer {api_token}"}
|
| 147 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
def create_event(self, patient_email, event_type_uuid):
|
| 149 |
try:
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
response.raise_for_status()
|
| 154 |
logging.info(f"Scheduled Calendly event for {patient_email}")
|
| 155 |
return response.json()["resource"]["start_time"]
|
|
|
|
|
|
|
|
|
|
| 156 |
except requests.exceptions.RequestException as e:
|
| 157 |
logging.error(f"Failed to schedule Calendly event: {str(e)}")
|
| 158 |
return None
|
| 159 |
|
| 160 |
# Gradio Interface Functions
|
| 161 |
-
hf_client = HuggingFaceClient(
|
| 162 |
sf_client = SalesforceClient(SALESFORCE_BASE_URL, SALESFORCE_HEADERS)
|
| 163 |
twilio_client = TwilioClient(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_PHONE)
|
| 164 |
calendly_client = CalendlyClient(CALENDLY_API_TOKEN)
|
| 165 |
|
| 166 |
def register_patient(phone, email, language, consent_status):
|
|
|
|
|
|
|
| 167 |
try:
|
| 168 |
-
name = f"PAT-{str(
|
| 169 |
patient_data = {
|
| 170 |
"Name": name, "Phone__c": phone, "Email__c": email,
|
| 171 |
"Language__c": language, "ConsentGiven__c": consent_status
|
|
@@ -173,20 +245,22 @@ def register_patient(phone, email, language, consent_status):
|
|
| 173 |
patient_id = sf_client.create_record("Patient__c", patient_data)
|
| 174 |
if patient_id:
|
| 175 |
logging.info(f"Registered patient: {name}")
|
| 176 |
-
return f"Patient {name} registered successfully!"
|
| 177 |
-
return "Failed to register patient."
|
| 178 |
except Exception as e:
|
| 179 |
logging.error(f"Error registering patient: {str(e)}")
|
| 180 |
return f"Error: {str(e)}"
|
| 181 |
|
| 182 |
-
def submit_consent(patient_phone,
|
|
|
|
|
|
|
| 183 |
try:
|
| 184 |
-
patients = sf_client.query_records(f"SELECT Id FROM Patient__c WHERE Phone__c = '{patient_phone}'")
|
| 185 |
if patients:
|
| 186 |
-
consent_data = {"Method__c":
|
| 187 |
consent_id = sf_client.create_record("Consent__c", consent_data)
|
| 188 |
if consent_id:
|
| 189 |
-
logging.info("Consent recorded
|
| 190 |
return "Consent recorded successfully!"
|
| 191 |
return "Failed to record consent."
|
| 192 |
return "Patient not found."
|
|
@@ -195,25 +269,33 @@ def submit_consent(patient_phone, method):
|
|
| 195 |
return f"Error: {str(e)}"
|
| 196 |
|
| 197 |
def schedule_followup(patient_phone, message_template, follow_up_date):
|
|
|
|
|
|
|
| 198 |
try:
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
})
|
| 212 |
-
logging.info(f"Follow-up scheduled for patient: {patients[0]['Id']}")
|
| 213 |
-
return "Follow-up scheduled and message sent!"
|
| 214 |
-
return "Failed to send message."
|
| 215 |
return "Failed to schedule follow-up."
|
| 216 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 217 |
except Exception as e:
|
| 218 |
logging.error(f"Error scheduling follow-up: {str(e)}")
|
| 219 |
return f"Error: {str(e)}"
|
|
@@ -228,13 +310,18 @@ def view_risk_dashboard():
|
|
| 228 |
"Sentiment": log["Sentiment__c"]} for log in symptom_logs
|
| 229 |
])
|
| 230 |
high_risk = df[df["Severity"] == "High"]
|
| 231 |
-
|
|
|
|
|
|
|
|
|
|
| 232 |
return pd.DataFrame(), "No symptom logs available."
|
| 233 |
except Exception as e:
|
| 234 |
logging.error(f"Error loading risk dashboard: {str(e)}")
|
| 235 |
return pd.DataFrame(), f"Error: {str(e)}"
|
| 236 |
|
| 237 |
def escalate_case(patient_id, response_text):
|
|
|
|
|
|
|
| 238 |
try:
|
| 239 |
case_data = {
|
| 240 |
"RelatedPatient__c": patient_id, "Priority__c": "High",
|
|
@@ -249,8 +336,13 @@ def escalate_case(patient_id, response_text):
|
|
| 249 |
logging.error(f"Error escalating case: {str(e)}")
|
| 250 |
return f"Error: {str(e)}"
|
| 251 |
|
| 252 |
-
def schedule_appointment(patient_email,
|
|
|
|
|
|
|
| 253 |
try:
|
|
|
|
|
|
|
|
|
|
| 254 |
event_time = calendly_client.create_event(patient_email, event_type_uuid)
|
| 255 |
if event_time:
|
| 256 |
appointment_data = {"DateTime__c": event_time, "Status__c": "Scheduled"}
|
|
@@ -265,31 +357,33 @@ def schedule_appointment(patient_email, event_type_uuid):
|
|
| 265 |
return f"Error: {str(e)}"
|
| 266 |
|
| 267 |
def submit_survey(patient_phone, question, answer):
|
|
|
|
|
|
|
| 268 |
try:
|
| 269 |
-
patients = sf_client.query_records(f"SELECT Id, Language__c FROM Patient__c WHERE Phone__c = '{patient_phone}'")
|
| 270 |
-
if patients:
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
if analysis:
|
| 276 |
-
symptom_data = {
|
| 277 |
-
"Patient__c": patients[0]["Id"], "ResponseText__c": answer,
|
| 278 |
-
"RiskScore__c": analysis["risk_level"], "Severity__c": analysis["severity_score"],
|
| 279 |
-
"Sentiment__c": analysis["sentiment_score"]
|
| 280 |
-
}
|
| 281 |
-
symptom_id = sf_client.create_record("SymptomLog__c", symptom_data)
|
| 282 |
-
if symptom_id and analysis["risk_level"] == "High":
|
| 283 |
-
case_data = {
|
| 284 |
-
"RelatedPatient__c": patients[0]["Id"], "Priority__c": "High",
|
| 285 |
-
"Description__c": f"High-risk survey response: {answer}"
|
| 286 |
-
}
|
| 287 |
-
sf_client.create_record("Case__c", case_data)
|
| 288 |
-
logging.info(f"Survey submitted for patient: {patients[0]['Id']}")
|
| 289 |
-
return "Survey submitted and analyzed!"
|
| 290 |
-
return "Failed to analyze survey response."
|
| 291 |
return "Failed to submit survey."
|
| 292 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 293 |
except Exception as e:
|
| 294 |
logging.error(f"Error submitting survey: {str(e)}")
|
| 295 |
return f"Error: {str(e)}"
|
|
@@ -346,22 +440,22 @@ with gr.Blocks(theme=gr.themes.Soft(), title="AI-Powered Patient Follow-up Agent
|
|
| 346 |
with gr.Row():
|
| 347 |
escalate_id_input = gr.Textbox(label="Patient ID (for escalation)")
|
| 348 |
escalate_response_input = gr.Textbox(label="Response Text")
|
| 349 |
-
escalate_button = gr.Button("
|
| 350 |
escalate_output = gr.Textbox(label="Result")
|
| 351 |
escalate_button.click(
|
| 352 |
-
fn=
|
| 353 |
inputs=[escalate_id_input, escalate_response_input],
|
| 354 |
outputs=escalate_output
|
| 355 |
)
|
| 356 |
|
| 357 |
with gr.Tab("Appointment Scheduling"):
|
| 358 |
appt_email_input = gr.Textbox(label="Patient Email", placeholder="patient@example.com")
|
| 359 |
-
|
| 360 |
appt_button = gr.Button("Schedule Appointment")
|
| 361 |
appt_output = gr.Textbox(label="Result")
|
| 362 |
appt_button.click(
|
| 363 |
fn=schedule_appointment,
|
| 364 |
-
inputs=[appt_email_input,
|
| 365 |
outputs=appt_output
|
| 366 |
)
|
| 367 |
|
|
|
|
| 13 |
format='%(asctime)s - %(levelname)s - %(message)s')
|
| 14 |
|
| 15 |
# Load environment variables (Hugging Face Spaces secrets)
|
| 16 |
+
SALESFORCE_USERNAME = os.getenv("SF_USERNAME", "app41@clinic.com")
|
| 17 |
+
SALESFORCE_PASSWORD = os.getenv("SF_PASSWORD", "text@12345")
|
| 18 |
+
SALESFORCE_SECURITY_TOKEN = os.getenv("SF_SECURITY_TOKEN", "6JgPkW3K5MPPGXDgc2JNxCUdB")
|
| 19 |
+
SALESFORCE_INSTANCE_URL = os.getenv("SF_INSTANCE_URL", "https://clinic3-dev-ed.develop.lightning.force.com/")
|
| 20 |
+
TWILIO_ACCOUNT_SID = os.getenv("TWILIO_ACCOUNT_SID", "AC80ba89ceecaa0f3058326f5c86d63f18")
|
| 21 |
+
TWILIO_AUTH_TOKEN = os.getenv("TWILIO_AUTH_TOKEN", "13d8416e924902b19f8181b77c968060")
|
| 22 |
+
TWILIO_PHONE = os.getenv("TWILIO_PHONE", "+13304226436")
|
| 23 |
+
CALENDLY_API_TOKEN = os.getenv("CALENDLY_API_TOKEN", "eyJraWQiOiIxY2UxZTEzNjE3ZGNmNzY2YjNjZWJjY2Y4ZGM1YmFmYThhNjVlNjg0MDIzZjdjMzJiZTgzNDliMjM4MDEzNWI0IiwidHlwIjoiUEFUIiwiYWxnIjoiRVMyNTYifQ.eyJpc3MiOiJodHRwczovL2F1dGguY2FsZW5kbHkuY29tIiwiaWF0IjoxNzUwODQ5MDc3LCJqdGkiOiI2Njk2N2NjOC1lODk3LTRjZWQtOGM1NS03MGZlNzk5MGRhNTAiLCJ1c2VyX3V1aWQiOiIxYWNjMTMwZS1kN2MzLTQyOTAtYTBjNy1mMzZkYTYxMjMwMDQifQ.BexmuW5XR8wk4_FsROEv5eH7deUSSetNwjCaNiNeBLBYnbJDiG4Hfzbg2qNbUNR0Q8Nb-6-3uU3UuwXex-CgHQ")
|
| 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, CALENDLY_API_TOKEN]
|
| 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
|
| 33 |
+
def get_salesforce_access_token():
|
| 34 |
+
try:
|
| 35 |
+
login_url = "https://login.salesforce.com/services/Soap/u/60.0"
|
| 36 |
+
payload = f"""<?xml version="1.0" encoding="utf-8" ?>
|
| 37 |
+
<env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
| 38 |
+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
| 39 |
+
xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
|
| 40 |
+
<env:Body>
|
| 41 |
+
<n1:login xmlns:n1="urn:partner.soap.sforce.com">
|
| 42 |
+
<n1:username>{SALESFORCE_USERNAME}</n1:username>
|
| 43 |
+
<n1:password>{SALESFORCE_PASSWORD}{SALESFORCE_SECURITY_TOKEN}</n1:password>
|
| 44 |
+
</n1:login>
|
| 45 |
+
</env:Body>
|
| 46 |
+
</env:Envelope>"""
|
| 47 |
+
headers = {"Content-Type": "text/xml", "SOAPAction": "login"}
|
| 48 |
+
response = requests.post(login_url, data=payload, headers=headers, timeout=10)
|
| 49 |
+
response.raise_for_status()
|
| 50 |
+
response_text = response.text
|
| 51 |
+
session_id = response_text.split("<sessionId>")[1].split("</sessionId>")[0]
|
| 52 |
+
server_url = response_text.split("<serverUrl>")[1].split("</serverUrl>")[0]
|
| 53 |
+
instance_url = server_url.split("/services")[0]
|
| 54 |
+
return {"access_token": session_id, "instance_url": instance_url}
|
| 55 |
+
except Exception as e:
|
| 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.")
|
| 63 |
+
raise ValueError("Salesforce authentication failed.")
|
| 64 |
+
SALESFORCE_BASE_URL = sf_auth["instance_url"] + "/services/data/v60.0/"
|
| 65 |
SALESFORCE_HEADERS = {
|
| 66 |
+
"Authorization": f"Bearer {sf_auth['access_token']}",
|
| 67 |
"Content-Type": "application/json"
|
| 68 |
}
|
| 69 |
|
| 70 |
+
# Hugging Face Client (No token needed in Hugging Face Spaces)
|
| 71 |
class HuggingFaceClient:
|
| 72 |
+
def __init__(self):
|
| 73 |
self.api_url = "https://api-inference.huggingface.co/models/"
|
| 74 |
+
self.headers = {} # Token is implicit in Hugging Face Spaces
|
| 75 |
self.sentiment_model = "distilbert-base-uncased-finetuned-sst-2-english"
|
| 76 |
self.severity_model = "bert-base-uncased" # Placeholder; use custom model
|
| 77 |
self.translation_model = "facebook/m2m100_418M"
|
|
|
|
| 93 |
response_text = self.translate(response_text, language_code, "en")
|
| 94 |
sentiment_result = self.query(response_text, self.sentiment_model)
|
| 95 |
severity_result = self.query(response_text, self.severity_model)
|
| 96 |
+
if not sentiment_result or not severity_result:
|
| 97 |
+
return None
|
| 98 |
sentiment_score = self.process_sentiment(sentiment_result)
|
| 99 |
severity_score = self.process_severity(severity_result)
|
| 100 |
risk_level = self.determine_risk_level(sentiment_score, severity_score)
|
|
|
|
| 107 |
def translate(self, text, source_lang, target_lang):
|
| 108 |
payload = {"inputs": text, "parameters": {"src_lang": source_lang, "tgt_lang": target_lang}}
|
| 109 |
result = self.query(payload, self.translation_model)
|
| 110 |
+
return result[0].get("translation_text", text) if result and isinstance(result, list) else text
|
| 111 |
|
| 112 |
def process_sentiment(self, result):
|
| 113 |
if result and isinstance(result, list) and len(result) > 0:
|
|
|
|
| 144 |
record_id = response.json()["id"]
|
| 145 |
logging.info(f"Created {object_name} record: {record_id}")
|
| 146 |
return record_id
|
| 147 |
+
except requests.exceptions.HTTPError as e:
|
| 148 |
+
logging.error(f"HTTP error creating {object_name} record: {e.response.status_code} - {e.response.text}")
|
| 149 |
+
return None
|
| 150 |
except requests.exceptions.RequestException as e:
|
| 151 |
logging.error(f"Failed to create {object_name} record: {str(e)}")
|
| 152 |
return None
|
|
|
|
| 157 |
response = requests.get(f"{self.base_url}query/?q={encoded_query}", headers=self.headers)
|
| 158 |
response.raise_for_status()
|
| 159 |
return response.json()["records"]
|
| 160 |
+
except requests.exceptions.HTTPError as e:
|
| 161 |
+
logging.error(f"HTTP error querying records: {e.response.status_code} - {e.response.text}")
|
| 162 |
+
return []
|
| 163 |
except requests.exceptions.RequestException as e:
|
| 164 |
logging.error(f"Failed to query records: {str(e)}")
|
| 165 |
return []
|
|
|
|
| 173 |
|
| 174 |
def send_message(self, to_number, body):
|
| 175 |
try:
|
| 176 |
+
if not to_number.startswith("+"):
|
| 177 |
+
return "Invalid phone number format. Must start with +country_code."
|
| 178 |
data = {"From": self.from_number, "To": to_number, "Body": body}
|
| 179 |
response = requests.post(self.api_url, auth=self.auth, data=data, timeout=10)
|
| 180 |
response.raise_for_status()
|
| 181 |
logging.info(f"Sent message to {to_number}")
|
| 182 |
return True
|
| 183 |
+
except requests.exceptions.HTTPError as e:
|
| 184 |
+
logging.error(f"Twilio HTTP error: {e.response.status_code} - {e.response.text}")
|
| 185 |
+
return f"Twilio error: {e.response.text}"
|
| 186 |
except requests.exceptions.RequestException as e:
|
| 187 |
logging.error(f"Failed to send message: {str(e)}")
|
| 188 |
+
return str(e)
|
| 189 |
|
| 190 |
# Calendly Client
|
| 191 |
class CalendlyClient:
|
| 192 |
def __init__(self, api_token):
|
| 193 |
self.api_url = "https://api.calendly.com/scheduled_events"
|
| 194 |
+
self.event_types_url = "https://api.calendly.com/event_types"
|
| 195 |
self.headers = {"Authorization": f"Bearer {api_token}"}
|
| 196 |
|
| 197 |
+
def get_event_type_uuid(self, event_slug="30min"):
|
| 198 |
+
try:
|
| 199 |
+
response = requests.get(self.event_types_url, headers=self.headers, timeout=5)
|
| 200 |
+
response.raise_for_status()
|
| 201 |
+
event_types = response.json()["collection"]
|
| 202 |
+
for event in event_types:
|
| 203 |
+
if event["slug"].endswith(event_slug):
|
| 204 |
+
return event["uuid"]
|
| 205 |
+
logging.error(f"Event type {event_slug} not found.")
|
| 206 |
+
return None
|
| 207 |
+
except requests.exceptions.RequestException as e:
|
| 208 |
+
logging.error(f"Failed to fetch event types: {str(e)}")
|
| 209 |
+
return None
|
| 210 |
+
|
| 211 |
def create_event(self, patient_email, event_type_uuid):
|
| 212 |
try:
|
| 213 |
+
if not event_type_uuid:
|
| 214 |
+
return None
|
| 215 |
+
event_data = {
|
| 216 |
+
"event_type": f"https://api.calendly.com/event_types/{event_type_uuid}",
|
| 217 |
+
"invitee_email": patient_email
|
| 218 |
+
}
|
| 219 |
+
response = requests.post(self.api_url, headers=self.headers, json=event_data, timeout=10)
|
| 220 |
response.raise_for_status()
|
| 221 |
logging.info(f"Scheduled Calendly event for {patient_email}")
|
| 222 |
return response.json()["resource"]["start_time"]
|
| 223 |
+
except requests.exceptions.HTTPError as e:
|
| 224 |
+
logging.error(f"Calendly HTTP error: {e.response.status_code} - {e.response.text}")
|
| 225 |
+
return None
|
| 226 |
except requests.exceptions.RequestException as e:
|
| 227 |
logging.error(f"Failed to schedule Calendly event: {str(e)}")
|
| 228 |
return None
|
| 229 |
|
| 230 |
# Gradio Interface Functions
|
| 231 |
+
hf_client = HuggingFaceClient()
|
| 232 |
sf_client = SalesforceClient(SALESFORCE_BASE_URL, SALESFORCE_HEADERS)
|
| 233 |
twilio_client = TwilioClient(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_PHONE)
|
| 234 |
calendly_client = CalendlyClient(CALENDLY_API_TOKEN)
|
| 235 |
|
| 236 |
def register_patient(phone, email, language, consent_status):
|
| 237 |
+
if not phone or not email or not language or not consent_status:
|
| 238 |
+
return "All fields are required."
|
| 239 |
try:
|
| 240 |
+
name = f"PAT-{str(uuid4())[:7]}" # Unique patient ID
|
| 241 |
patient_data = {
|
| 242 |
"Name": name, "Phone__c": phone, "Email__c": email,
|
| 243 |
"Language__c": language, "ConsentGiven__c": consent_status
|
|
|
|
| 245 |
patient_id = sf_client.create_record("Patient__c", patient_data)
|
| 246 |
if patient_id:
|
| 247 |
logging.info(f"Registered patient: {name}")
|
| 248 |
+
return f"Patient {name} registered successfully! ID: {patient_id}"
|
| 249 |
+
return "Failed to register patient. Check Salesforce logs."
|
| 250 |
except Exception as e:
|
| 251 |
logging.error(f"Error registering patient: {str(e)}")
|
| 252 |
return f"Error: {str(e)}"
|
| 253 |
|
| 254 |
+
def submit_consent(patient_phone, consent_method):
|
| 255 |
+
if not patient_phone or not consent_method:
|
| 256 |
+
return "All fields are required."
|
| 257 |
try:
|
| 258 |
+
patients = sf_client.query_records(f"SELECT Id, Name FROM Patient__c WHERE Phone__c = '{patient_phone}'")
|
| 259 |
if patients:
|
| 260 |
+
consent_data = {"Method__c": consent_method, "GivenOn__c": date.today().isoformat()}
|
| 261 |
consent_id = sf_client.create_record("Consent__c", consent_data)
|
| 262 |
if consent_id:
|
| 263 |
+
logging.info(f"Consent recorded for patient: {patients[0]['Name']}")
|
| 264 |
return "Consent recorded successfully!"
|
| 265 |
return "Failed to record consent."
|
| 266 |
return "Patient not found."
|
|
|
|
| 269 |
return f"Error: {str(e)}"
|
| 270 |
|
| 271 |
def schedule_followup(patient_phone, message_template, follow_up_date):
|
| 272 |
+
if not patient_phone or not message_template or not follow_up_date:
|
| 273 |
+
return "All fields are required."
|
| 274 |
try:
|
| 275 |
+
follow_up_date = datetime.strptime(follow_up_date, "%Y-%m-%d").date()
|
| 276 |
+
patients = sf_client.query_records(f"SELECT Id, ConsentGiven__c, Language__c, Name FROM Patient__c WHERE Phone__c = '{patient_phone}'")
|
| 277 |
+
if not patients:
|
| 278 |
+
return "Patient not found."
|
| 279 |
+
if patients[0]["ConsentGiven__c"] != "Approved":
|
| 280 |
+
return "Consent not approved for messaging."
|
| 281 |
+
followup_data = {
|
| 282 |
+
"Patient__c": patients[0]["Id"], "FollowUpDate__c": follow_up_date.isoformat(),
|
| 283 |
+
"MessageTemplate__c": message_template, "Status__c": "Scheduled"
|
| 284 |
+
}
|
| 285 |
+
followup_id = sf_client.create_record("FollowUpPlan__c", followup_data)
|
| 286 |
+
if not followup_id:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 287 |
return "Failed to schedule follow-up."
|
| 288 |
+
send_result = twilio_client.send_message(patient_phone, message_template)
|
| 289 |
+
if send_result is True:
|
| 290 |
+
sf_client.create_record("MessageLog__c", {
|
| 291 |
+
"Patient__c": patients[0]["Id"], "MessageText__c": message_template,
|
| 292 |
+
"Direction__c": "Outbound", "Timestamp__c": datetime.utcnow().isoformat()
|
| 293 |
+
})
|
| 294 |
+
logging.info(f"Follow-up scheduled and message sent for patient: {patients[0]['Name']}")
|
| 295 |
+
return "Follow-up scheduled and message sent!"
|
| 296 |
+
return f"Failed to send message: {send_result}"
|
| 297 |
+
except ValueError:
|
| 298 |
+
return "Invalid date format. Use YYYY-MM-DD."
|
| 299 |
except Exception as e:
|
| 300 |
logging.error(f"Error scheduling follow-up: {str(e)}")
|
| 301 |
return f"Error: {str(e)}"
|
|
|
|
| 310 |
"Sentiment": log["Sentiment__c"]} for log in symptom_logs
|
| 311 |
])
|
| 312 |
high_risk = df[df["Severity"] == "High"]
|
| 313 |
+
high_risk_info = high_risk.to_dict('records') if not high_risk.empty else "No high-risk cases."
|
| 314 |
+
logging.info("Risk dashboard loaded successfully.")
|
| 315 |
+
return df, high_risk_info
|
| 316 |
+
logging.info("No symptom logs found.")
|
| 317 |
return pd.DataFrame(), "No symptom logs available."
|
| 318 |
except Exception as e:
|
| 319 |
logging.error(f"Error loading risk dashboard: {str(e)}")
|
| 320 |
return pd.DataFrame(), f"Error: {str(e)}"
|
| 321 |
|
| 322 |
def escalate_case(patient_id, response_text):
|
| 323 |
+
if not patient_id or not response_text:
|
| 324 |
+
return "All fields are required."
|
| 325 |
try:
|
| 326 |
case_data = {
|
| 327 |
"RelatedPatient__c": patient_id, "Priority__c": "High",
|
|
|
|
| 336 |
logging.error(f"Error escalating case: {str(e)}")
|
| 337 |
return f"Error: {str(e)}"
|
| 338 |
|
| 339 |
+
def schedule_appointment(patient_email, event_type_slug="30min"):
|
| 340 |
+
if not patient_email or not event_type_slug:
|
| 341 |
+
return "All fields are required."
|
| 342 |
try:
|
| 343 |
+
event_type_uuid = calendly_client.get_event_type_uuid(event_type_slug)
|
| 344 |
+
if not event_type_uuid:
|
| 345 |
+
return f"Event type '{event_type_slug}' not found."
|
| 346 |
event_time = calendly_client.create_event(patient_email, event_type_uuid)
|
| 347 |
if event_time:
|
| 348 |
appointment_data = {"DateTime__c": event_time, "Status__c": "Scheduled"}
|
|
|
|
| 357 |
return f"Error: {str(e)}"
|
| 358 |
|
| 359 |
def submit_survey(patient_phone, question, answer):
|
| 360 |
+
if not patient_phone or not question or not answer:
|
| 361 |
+
return "All fields are required."
|
| 362 |
try:
|
| 363 |
+
patients = sf_client.query_records(f"SELECT Id, Language__c, Name FROM Patient__c WHERE Phone__c = '{patient_phone}'")
|
| 364 |
+
if not patients:
|
| 365 |
+
return "Patient not found."
|
| 366 |
+
survey_data = {"Question__c": question, "Answer__c": answer}
|
| 367 |
+
survey_id = sf_client.create_record("Survey__c", survey_data)
|
| 368 |
+
if not survey_id:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 369 |
return "Failed to submit survey."
|
| 370 |
+
analysis = hf_client.analyze_response(answer, patients[0]["Language__c"])
|
| 371 |
+
if analysis:
|
| 372 |
+
symptom_data = {
|
| 373 |
+
"Patient__c": patients[0]["Id"], "ResponseText__c": answer,
|
| 374 |
+
"RiskScore__c": analysis["risk_level"], "Severity__c": analysis["severity_score"],
|
| 375 |
+
"Sentiment__c": analysis["sentiment_score"]
|
| 376 |
+
}
|
| 377 |
+
symptom_id = sf_client.create_record("SymptomLog__c", symptom_data)
|
| 378 |
+
if symptom_id and analysis["risk_level"] == "High":
|
| 379 |
+
case_data = {
|
| 380 |
+
"RelatedPatient__c": patients[0]["Id"], "Priority__c": "High",
|
| 381 |
+
"Description__c": f"High-risk survey response: {answer}"
|
| 382 |
+
}
|
| 383 |
+
sf_client.create_record("Case__c", case_data)
|
| 384 |
+
logging.info(f"Survey submitted for patient: {patients[0]['Name']}")
|
| 385 |
+
return f"Survey submitted and analyzed! Risk Level: {analysis['risk_level']}"
|
| 386 |
+
return "Failed to analyze survey response."
|
| 387 |
except Exception as e:
|
| 388 |
logging.error(f"Error submitting survey: {str(e)}")
|
| 389 |
return f"Error: {str(e)}"
|
|
|
|
| 440 |
with gr.Row():
|
| 441 |
escalate_id_input = gr.Textbox(label="Patient ID (for escalation)")
|
| 442 |
escalate_response_input = gr.Textbox(label="Response Text")
|
| 443 |
+
escalate_button = gr.Button("Escalate Case")
|
| 444 |
escalate_output = gr.Textbox(label="Result")
|
| 445 |
escalate_button.click(
|
| 446 |
+
fn=escalate_case,
|
| 447 |
inputs=[escalate_id_input, escalate_response_input],
|
| 448 |
outputs=escalate_output
|
| 449 |
)
|
| 450 |
|
| 451 |
with gr.Tab("Appointment Scheduling"):
|
| 452 |
appt_email_input = gr.Textbox(label="Patient Email", placeholder="patient@example.com")
|
| 453 |
+
appt_event_input = gr.Textbox(label="Event Type Slug", value="30min", placeholder="e.g., 30min")
|
| 454 |
appt_button = gr.Button("Schedule Appointment")
|
| 455 |
appt_output = gr.Textbox(label="Result")
|
| 456 |
appt_button.click(
|
| 457 |
fn=schedule_appointment,
|
| 458 |
+
inputs=[appt_email_input, appt_event_input],
|
| 459 |
outputs=appt_output
|
| 460 |
)
|
| 461 |
|