From f9e8786921acb65398204b8021e972234776c745 Mon Sep 17 00:00:00 2001 From: Deep Koluguri Date: Tue, 26 May 2026 21:51:50 -0400 Subject: [PATCH] Revamp booking flow --- src/curaflow_agent/activities.py | 65 +++++++++++++++++++++----------- src/curaflow_agent/db.py | 2 +- src/curaflow_agent/workflows.py | 6 +-- 3 files changed, 47 insertions(+), 26 deletions(-) diff --git a/src/curaflow_agent/activities.py b/src/curaflow_agent/activities.py index 80556ed..360f99f 100644 --- a/src/curaflow_agent/activities.py +++ b/src/curaflow_agent/activities.py @@ -163,31 +163,53 @@ async def save_clinical_record_activity(payload: dict) -> str: class AppointmentIntentSchema(BaseModel): is_booking_request: bool = Field(description="True if the user is asking to book, schedule, or modify an appointment.") - department: str = Field(description="The department they want to visit (e.g. Cardiology, General, Dental). Leave empty if not specified.") - appointment_date: str = Field(description="The date they want to visit (e.g. 'Monday', 'Tomorrow', '10/12/2026'). Leave empty if not specified.") - appointment_time: str = Field(description="The time they want to visit (e.g. 'Morning', '3 PM'). Leave empty if not specified.") - missing_info: list[str] = Field(description="List of fields that are still missing and need to be asked. E.g. ['department', 'appointment_date']") - reply_message: str = Field(description="The message to send to the user. IMPORTANT: If asking for department, you MUST list the 4 available departments: Cardiology, General Medicine, Pediatrics, Orthopedics.") + doctor_name: str = Field(description="The specific doctor they want to visit. Leave empty if not specified.") + appointment_date: str = Field(description="The date they want to visit. Leave empty if not specified.") + appointment_time: str = Field(description="The time they want to visit. Leave empty if not specified.") + missing_info: list[str] = Field(description="List of fields that are still missing and need to be asked.") + reply_message: str = Field(description="The exact text to send to the user. Ask for missing details using numbered lists.") @activity.defn -async def parse_booking_intent_activity(chat_history: list[dict]) -> dict: - """ - Parses the chat history using the LLM to extract booking intent and details. - chat_history is a list of {"role": "user"|"assistant", "content": "..."} - """ +async def parse_booking_intent_activity(payload: dict) -> dict: + chat_history = payload.get("chat_history", []) + to_number = payload.get("to_number", "") + + # 1. Fetch available doctors for the given tenant + import psycopg2 + doctors = [] + try: + conn = psycopg2.connect("postgres://postgres:curio_secret@curio-db:5432/curio") + cur = conn.cursor() + # Find tenant by whatsapp_number + cur.execute("SELECT id FROM tenants WHERE whatsapp_number = %s OR whatsapp_number = %s", (to_number, to_number.replace('whatsapp:', ''))) + row = cur.fetchone() + if row: + tenant_id = row[0] + cur.execute("SELECT name FROM users WHERE tenant_id = %s AND role = 'DOCTOR' AND status = 'active'", (tenant_id,)) + doctors = [doc[0] for doc in cur.fetchall()] + cur.close() + conn.close() + except Exception as e: + print(f"DB Error fetching doctors: {e}") + + doctors_str = ", ".join(doctors) if doctors else "Any Available Doctor" + from langchain_core.prompts import PromptTemplate prompt = PromptTemplate.from_template( "You are a helpful hospital receptionist bot talking to a patient on WhatsApp.\n" "Here is the conversation history:\n" "{history}\n\n" - "Analyze the conversation. Extract any appointment details (department, date, time).\n" - "If they want to book an appointment but haven't specified department, date, and time, " - "add those to missing_info and formulate a reply_message asking for them.\n" - "CRITICAL INSTRUCTIONS FOR ASKING:\n" - "- If the user hasn't specified a department, you MUST list the 4 available departments (Cardiology, General Medicine, Pediatrics, Orthopedics) so they know what to choose. Do not make them guess!\n" - "- If they haven't specified a time, suggest common intervals (e.g. 10:00 AM, 11:00 AM, 6:00 PM).\n" - "If all details are present, set missing_info to empty and formulate a confirmation reply_message.\n" + f"Available Doctors: {doctors_str}\n\n" + "Analyze the conversation. Extract any appointment details (doctor_name, appointment_date, appointment_time).\n" + "If they want to book an appointment, gather missing details sequentially:\n" + "1. If doctor_name is missing, check the Available Doctors. If there is more than 1 doctor, ask the user to choose using a numbered list (1. Dr. X, 2. Dr. Y). If there is only 1 doctor, auto-select them and move to the next step.\n" + "2. If appointment_date is missing, ask for a date and suggest 2 or 3 specific dates for the current week as a numbered list (1. Monday, 2. Tuesday).\n" + "3. If appointment_time is missing, ask for a time and suggest two 30-minute time slots as a numbered list (1. 10:00 AM, 2. 10:30 AM).\n" + "CRITICAL INSTRUCTIONS:\n" + "- Only ask for ONE missing piece of information at a time.\n" + "- Format your choices as strict numbered lists so the user can just type the number.\n" + "- If all details are present, set missing_info to empty and formulate a confirmation reply_message.\n" "Format output strictly as JSON matching the schema." ) @@ -204,14 +226,13 @@ async def parse_booking_intent_activity(chat_history: list[dict]) -> dict: return result.dict() except Exception as e: print(f"Failed to parse booking intent: {e}") - # Fallback due to Pydantic strictness failures return { "is_booking_request": True, - "department": "", + "doctor_name": "", "appointment_date": "", "appointment_time": "", - "missing_info": ["department", "appointment_date", "appointment_time"], - "reply_message": "I'm sorry, I'm having trouble understanding. Could you please specify the department, date, and time you'd like to book?" + "missing_info": ["doctor_name", "appointment_date", "appointment_time"], + "reply_message": "I'm sorry, I encountered an error. Could you please let me know which doctor you want to book?" } @activity.defn @@ -252,7 +273,7 @@ async def save_appointment_activity(payload: dict) -> str: try: record = Appointment( phone_number=payload.get("phone_number", ""), - department=payload.get("department", ""), + doctor_name=payload.get("doctor_name", ""), appointment_date=payload.get("appointment_date", ""), appointment_time=payload.get("appointment_time", ""), status="CONFIRMED" diff --git a/src/curaflow_agent/db.py b/src/curaflow_agent/db.py index cfb1ecb..2a3eec1 100644 --- a/src/curaflow_agent/db.py +++ b/src/curaflow_agent/db.py @@ -33,7 +33,7 @@ class Appointment(Base): id = Column(Integer, primary_key=True, index=True) phone_number = Column(String(50), index=True, nullable=False) - department = Column(String(100), nullable=True) + doctor_name = Column(String(100), nullable=True) appointment_date = Column(String(50), nullable=True) appointment_time = Column(String(50), nullable=True) status = Column(String(20), default="PENDING") diff --git a/src/curaflow_agent/workflows.py b/src/curaflow_agent/workflows.py index 99a82d5..8fd3d97 100644 --- a/src/curaflow_agent/workflows.py +++ b/src/curaflow_agent/workflows.py @@ -130,14 +130,14 @@ class AppointmentBookingWorkflow: self._new_message_event.set() @workflow.run - async def run(self, initial_message: str, phone_number: str) -> dict: + async def run(self, initial_message: str, phone_number: str, to_number: str = "") -> dict: self._chat_history.append({"role": "user", "content": initial_message}) while True: # 1. Parse intent and get next action intent_data = await workflow.execute_activity( "parse_booking_intent_activity", - self._chat_history, + {"chat_history": self._chat_history, "to_number": to_number}, retry_policy=RetryPolicy(maximum_attempts=3), schedule_to_close_timeout=timedelta(minutes=5) ) @@ -159,7 +159,7 @@ class AppointmentBookingWorkflow: "save_appointment_activity", { "phone_number": phone_number, - "department": intent_data.get("department"), + "doctor_name": intent_data.get("doctor_name"), "appointment_date": intent_data.get("appointment_date"), "appointment_time": intent_data.get("appointment_time") },