# Project export: Cura

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: Cal Hacks 12.0
- Tagline: AI Medical Assistant: Intelligent Symptom Checker and Care Connector
- Devpost: https://devpost.com/software/cura-kthowx
- GitHub: https://github.com/hongdnn/healthcare_ai_backend
- Demo: https://production.creao.ai/share?app=PNCQOyOh&utm_source=share&utm_medium=link
- Video: https://www.youtube.com/embed/DM4mWK8XNcI?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Result: winner (Regeneron: Honorable Mention)
- Team: 2 GitHub contributor(s) — hongdnn (5 commits), secretlycharles (3 commits)

## Devpost submission (written by the team)

### Overview

🩺 Project Story: Cura About the Project Cura is an AI-powered medical voice assistant designed to make healthcare more accessible and responsive. It listens to patient symptoms, provides personalized guidance, connects users to doctors, and schedules appointments — all through natural, human-like conversation. We were inspired by how difficult it can be to access immediate medical advice, especially when clinics are overwhelmed or unavailable. Our goal was to create an intelligent assistant that supports both patients and healthcare professionals by automating symptom triage, appointment scheduling, and follow-up care. Through building Cura, we learned how to integrate real-time voice AI, generative language models, and healthcare data systems into one cohesive workflow. We explored challenges in conversational design, natural language understanding, and securely managing patient interactions. Cura’s architecture combines: 🎙️ LiveKit Agents — for real-time voice calls and natural dialogue 🎙️ LiveKit Agents — for real-time voice calls and natural dialogue ⚙️ FastAPI and MongoDB — for backend data storage and healthcare logic ⚙️ FastAPI and MongoDB — for backend data storage and healthcare logic 🧠 Chroma — as a vector database for symptom similarity search and matching, which diagnose patient's issue and give advice based on healthcare service's internal data 🧠 Chroma — as a vector database for symptom similarity search and matching, which diagnose patient's issue and give advice based on healthcare service's internal data 📅 CreaoAI — for intelligent scheduling and calendar integration 📅 CreaoAI — for intelligent scheduling and calendar integration 💻 Creao (React web) and Swift (mobile) — for patient and provider dashboards 💻 Creao (React web) and Swift (mobile) — for patient and provider dashboards Gmail API for appointment booking confirmation * Gmail API for appointment booking confirmation * Challenges Faced Building Cura presented several key challenges: Designing natural and empathetic voice conversations that adapt to user tone and intent Designing natural and empathetic voice conversations that adapt to user tone and intent Managing speech latency for real-time responses Managing speech latency for real-time responses Algorithm to match the most similar symptoms from user's input. We separate each symptom from healthcare data and embed each of them to count the number of matching symptoms and get average cosine similarity that receives best result * Algorithm to match the most similar symptoms from user's input. We separate each symptom from healthcare data and embed each of them to count the number of matching symptoms and get average cosine similarity that receives best result * Structuring a scalable database that connects patients, clinicians, and appointment data Structuring a scalable database that connects patients, clinicians, and appointment data Integrating multiple APIs (LiveKit, Chroma, Gmail) into a seamless workflow Integrating multiple APIs (LiveKit, Chroma, Gmail) into a seamless workflow Ensuring that AI-generated responses remain medically cautious and privacy-aware Ensuring that AI-generated responses remain medically cautious and privacy-aware Despite these challenges, we successfully created a working prototype that listens to patients, identifies symptoms, connects them with doctors, and schedules appointments — all through an intelligent voice interface.

## README (from the GitHub repository)

## Prerequisites

- Python 3.13 or newer.

## Installing uv and running the agent

To check if `uv` is installed, run:

```bash
uv --version
```

## Environment variables

```
# .env.local example
LIVEKIT_API_KEY=your_livekit_key
LIVEKIT_API_SECRET=your_livekit_secret
LIVEKIT_URL=your_livekit_url
NEXT_PUBLIC_LIVEKIT_URL=your_livekit_url
# any other vars your environment needs
```

Adjust names and values according to your runtime configuration.

## How to run the agent

Install library:
```bash
uv sync
```

Run the agent script:

```bash
uv run src/agent.py dev
```


## Detected evidence (automated analysis)

Indexed codebase: 16 recognized source files, 83 KB.
- Python (language) — detected in the code
- FastAPI (technology) — claimed on Devpost, not found in the code
- MongoDB (technology) — claimed on Devpost, not found in the code
- Swift (language) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (20 of 20)

```
.gitignore
.idea/healthcare_ai_backend.iml
.idea/inspectionProfiles/profiles_settings.xml
.idea/inspectionProfiles/Project_Default.xml
.idea/misc.xml
.idea/vcs.xml
.idea/workspace.xml
pyproject.toml
README.md
src/agent.py
src/chroma/chroma_service.py
src/chroma/generate_excel.py
src/db/generate_data.py
src/db/mongo_service.py
src/feedback_agent.py
src/main.py
src/models/user.py
src/trigger_feedback_call.py
src/utils/time_utils.py
uv.lock
```

### Dependencies

- pyproject.toml: chromadb@>=1.2.1, dateparser@>=1.2.2, faker@>=37.12.0, fastapi[standard]@>=0.120.0, livekit-agents[silero,turn-detector]@~=1.2, livekit-plugins-noise-cancellation@~=0.2, openpyxl@>=3.1.5, pandas@>=2.3.3, pymongo@>=4.15.3, python-dotenv@>=1.1.1, resend@>=2.17.0, uvicorn[standard]@>=0.38.0

### Recent commits (newest first)

- conversations/user endpoint added
- fix
- add outbound call
- Merge branch 'main' of https://github.com/hongdnn/healthcare_ai_backend
- send booking email
- new health issues
- added conversations endpoint
- Initial

## Key source files (fetched from GitHub, selected and truncated for size)

### pyproject.toml

```
[project]
name = "healthcare_ai_backend"
version = "0.1.0"
requires-python = ">=3.13"
dependencies = [
    "chromadb>=1.2.1",
    "dateparser>=1.2.2",
    "faker>=37.12.0",
    "fastapi[standard]>=0.120.0",
    "livekit-agents[silero,turn-detector]~=1.2",
    "livekit-plugins-noise-cancellation~=0.2",
    "openpyxl>=3.1.5",
    "pandas>=2.3.3",
    "pymongo>=4.15.3",
    "python-dotenv>=1.1.1",
    "resend>=2.17.0",
    "uvicorn[standard]>=0.38.0",
]

```

### src/main.py

```python
from fastapi import FastAPI
from fastapi.responses import JSONResponse
from pydantic import BaseModel
from pymongo import AsyncMongoClient
from dotenv import load_dotenv
from contextlib import asynccontextmanager
from bson import ObjectId
import os
import smtplib
from email.message import EmailMessage

load_dotenv(".env.local")

@asynccontextmanager
async def lifespan(api: FastAPI):
    # Startup: Connect to MongoDB
    print("🚀 Connecting to MongoDB...")
    app.mongodb_client = AsyncMongoClient(os.environ["MONGODB_URL"])
    app.db = app.mongodb_client.healthcare_db
    print("✅ MongoDB connected")
    
    yield  # App runs here
    
    # Shutdown: Close MongoDB connection
    print("🔌 Closing MongoDB connection...")
    await app.mongodb_client.close()
    print("👋 MongoDB disconnected")

# --- Create FastAPI app --- #
app = FastAPI(
    title="Healthcare AI API",
    lifespan=lifespan
)

from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=[
        "*",  # (optional) to allow all
    ],
    allow_credentials=True,
    allow_methods=["*"],      # MUST include OPTIONS
    allow_headers=["*"],
)


@app.get("/")
async def index():
    return JSONResponse({"status": "ok", "message": "Healthcare assistant API running"})

@app.get("/health")
async def health():
    return JSONResponse({"status": "healthy"})

class LoginModel(BaseModel):
    """
    Container for login payload
    """
    email: str

@app.post("/login")
async def login(data: LoginModel):
    print("user: ", data.email)
    user = await app.db.users.find_one({"email": data.email})

    if user is not None:
        return JSONResponse(
            status_code=200,
            content={"status": "ok", "id": str(user["_id"]), "type": user["type"]}
        )

    return JSONResponse(
        status_code=401,
        content={"status": "failed"}
    )

@app.get("/calendar/user")
async def user_calendar(id: str):
    calendars = await app.db.calendars.find({"user_id": id}).to_list(length=None)

    return JSONResponse(
        status_code=200,
        content={
            "appointments": [
                {
                    "issue": calendar["issue"],
                    "start_datetime": str(calendar["start_datetime"]),
                    "end_datetime": str(calendar["end_datetime"]),
                    "confirmation": calendar["confirmation"]
                } for calendar in calendars
            ]
        } if len (calendars) > 0 else []
    )

@app.get("/calendar/doctor")
async def doctor_calendar(id: str):
    calendars = await app.db.calendars.find({"doctor_id": id}).to_list(length=None)

    if len(calendars) > 0:
        # Format data
        appointments = []
        for calendar in calendars:
            user = await app.db.users.find_one({"_id": ObjectId(calendar["user_id"])})
            print(user)
            
            appointments.append({
                "user": {
                    "id": str(user["_id"]),
                    "name": user["name"],
                    "phone": user["phone"],
                    "email": user["email"],
                },
                "details": {
                    "id": str(calendar["_id"]),
                    "issue": calendar["issue"],
                    "start_datetime": str(calendar["start_datetime"]),
                    "end_datetime": str(calendar["end_datetime"]),
                    "confirmation": calendar["confirmation"],
                },
            })

        # Return data
        return JSONResponse(
            status_code=200,
            content={
                "appointments": appointments
            }
        )

    # Return empty appointments
    return JSONResponse(
        status_code=200,
        content={
            "appointments": []
        }
    )

class EmailModel(BaseModel):
    """
    Container for email payload
    """
    email: str
    subject: str
    content: str

@app.post("/email")
async def email(data: EmailModel):
    try:
        msg = EmailMessage()
        msg.set_content(data.content)
        msg["Subject"] = data.subject
        msg["From"] = os.environ["EMAIL"]
        msg["To"] = data.email

        server = smtplib.SMTP_SSL("smtp.gmail.com", 465)
        server.login(os.environ["EMAIL"], os.environ["EMAIL_PASSWORD"])
        server.send_message(msg)
        server.quit()

        # Return success
        return JSONResponse(
            status_code=200,
            content={
                "status": "ok"
            }
        )
    except Exception as e:
        print("error: ", e)
        return JSONResponse(
            status_code=500,
            content={
                "status": "failed"
            }
        )
    
@app.get("/conversations/user")
async def conversation_user(id: str):
    conversations = await app.db.conversations.find({"user_id": id}).to_list(length=None)

    if conversations is not None:
        data = []
        for conversation in conversations:
            calendar = await app.db.calendars.find_one({"_id": ObjectId(conversation["appointment_id"])})
            data.append({
                "conversation": {
                    "detail": {
                        "appointment": {
                            "doctor_id": calendar["doctor_id"],
                            "issue": calendar["issue"],
                            "start_datetime": str(calendar["start_datetime"]),
                            "end_datetime": str(calendar["end_datetime"]),
                            "confirmation": calendar["confirmation"],
                            "created_at": str(calendar["created_at"])
                        },
                        "ai_summary": {
                            "issue": conversation["issue"],
                            "symptoms": conversation["symptoms"],
                            "recommendations": conversation["recommendations"]
                        }
                    }
                }
     
[truncated — 1652 more characters]
```

### .idea/vcs.xml

```xml
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="VcsDirectoryMappings">
    <mapping directory="$PROJECT_DIR$" vcs="Git" />
  </component>
</project>
```

### .idea/misc.xml

```xml
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="Black">
    <option name="sdkName" value="Python 3.13 (healthcare_ai_backend)" />
  </component>
  <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.13 (healthcare_ai_backend)" project-jdk-type="Python SDK" />
</project>
```

### src/trigger_feedback_call.py

```python
import asyncio
import json
import random
from livekit import api
from dotenv import load_dotenv

load_dotenv(".env.local")

async def trigger_feedback_call(phone_number: str, appointment_id: str):
    print(f"Triggering feedback call to {phone_number} for appointment {appointment_id}...")
    room_name = f"outbound-{''.join(str(random.randint(0,9)) for _ in range(10))}"
    metadata = json.dumps({"phone_number": phone_number, "appointment_id": appointment_id})

    async with api.LiveKitAPI() as lk:
        await lk.agent_dispatch.create_dispatch(
            api.CreateAgentDispatchRequest(
                agent_name="my-telephony-agent",
                room=room_name,
                metadata=metadata
            )
        )
    print("✅ Dispatch created successfully.")

if __name__ == "__main__":
    asyncio.run(trigger_feedback_call("+12096841862", "68fe1148af65e089f32fe5f5"))
```

### src/feedback_agent.py

```python
import asyncio
import json
from datetime import datetime
import random
from dotenv import load_dotenv
from livekit import agents, api
from livekit.agents import AgentSession, Agent, RoomInputOptions
from livekit.plugins import noise_cancellation, silero
from livekit.plugins.turn_detector.multilingual import MultilingualModel
from livekit.rtc import ConnectionState
from db.mongo_service import MongoService
from utils.time_utils import format_datetime_natural

load_dotenv(".env.local")

mongo_service = MongoService()

# --- Feedback Agent --- #
class FeedbackAgent(Agent):
    def __init__(self, user, appointment):
        self.user = user
        self.appointment = appointment
        self.feedback = None
        self.improvement = None
        self.additional_notes = None

        @agents.function_tool
        async def record_feedback(ctx: agents.RunContext, feedback: str, improved: bool, notes: str = "") -> dict:
            """Store patient's feedback after follow-up call"""
            try:
                mongo_service.save_feedback(
                    appointment_id=self.appointment["_id"],
                    user_id=str(self.user["_id"]),
                    feedback=feedback,
                    improved=improved,
                    notes=notes,
                    timestamp=datetime.now()
                )
                return {"result": "Feedback recorded successfully."}
            except Exception as e:
                print(f"Error saving feedback: {e}")
                return {"result": "There was an error saving feedback."}

        super().__init__(
            instructions=(
                "You are Cura’s healthcare follow-up assistant. "
                "You are calling the patient one week after their appointment to check how they’re doing. "
                "Start the call by greeting the patient warmly and confirming their name and recent appointment. "
                "Then ask them if they feel any improvement in their condition since the visit. "
                "If they say yes, record the improvement and thank them. "
                "If they say no, ask politely what symptoms persist or if they need help scheduling a follow-up appointment. "
                "At the end, summarize their feedback and call the `record_feedback` tool to store it in the database. "
                "Be empathetic and use natural human tone."
            ),
            tools=[record_feedback],
        )


# --- Entrypoint --- #
async def entrypoint(ctx: agents.JobContext):
    await ctx.connect()
    room = ctx.room

    # --- Step 1: Parse metadata for outbound call ---
    phone_number = None
    appointment = None
    try:
        if ctx.job.metadata:
            data = json.loads(ctx.job.metadata)
            print(f"Parsed metadata: {data}")
            phone_number = data.get("phone_number")
            appointment = mongo_service.fetch_appointment_by_id(data.get("appointment_id"))
    except Exception as e:
        print(f"Error parsing metadata: {e}")

    print(f"Using phone number: {phone_number}, appointment: {appointment}")
    if not phone_number or not appointment:
        print("⚠️ Missing phone number or appointment info.")
        await ctx.shutdown()
        return

    # --- Step 2: Place outbound call ---
    try:
        await ctx.api.sip.create_sip_participant(api.CreateSIPParticipantRequest(
            room_name=ctx.room.name,
            sip_trunk_id="ST_6xaaSRD7hsnH", 
            sip_call_to=phone_number,
            participant_identity=phone_number,
            wait_until_answered=True,
        ))
        print(f"📞 Outbound call to {phone_number} picked up successfully.")
    except api.TwirpError as e:
        print(f"❌ Error creating SIP participant: {e.message}")
        await ctx.shutdown()
        return

    # --- Step 3: Wait for participant ---
    participant = None
    for _ in range(10):
        if room.remote_participants:
            participant = next(iter(room.remote_participants.values()))
            break
        await asyncio.sleep(1)

    if not participant:
        print("⚠️ No participant joined the follow-up call.")
        return

    print(f"✅ Patient {participant.identity} joined the feedback call.")

    user = mongo_service.fetch_user_by_phone(phone_number)
    print(f"Fetched user: {user.name} ({user.email})")

    # --- Step 4: Start AgentSession ---
    session = AgentSession(
        stt="assemblyai/universal-streaming:en",
        llm="openai/gpt-4.1-mini",
        tts="deepgram/nova-3-general",
        vad=silero.VAD.load(),
        turn_detection=MultilingualModel()
    )

    agent = FeedbackAgent(user=user, appointment=appointment)

    await session.start(
        room=ctx.room,
        agent=agent,
        room_input_options=RoomInputOptions(
            noise_cancellation=noise_cancellation.BVC()
        )
    )

    # Outbound call: let the agent start conversation
    await session.generate_reply(
        instructions=f"Call the patient to check on their recovery after their visit on {format_datetime_natural(appointment['datetime'])}."
    )

    # --- Step 5: Cleanup on disconnect ---
    async def cleanup():
        try:
            if hasattr(session, "_tasks"):
                for t in list(session._tasks):
                    if not t.done():
                        t.cancel()
            if room and room.connection_state == ConnectionState.CONN_CONNECTED:
                await room.disconnect()
            print("🧹 Feedback session cleaned up successfully")
        except Exception as e:
            print(f"⚠️ Cleanup error: {e}")

    room.on("participant_disconnected", lambda p: asyncio.create_task(cleanup()))

if __name__ == "__main__":
    agents.cli.run_app(agents.WorkerOptions(entrypoint_fnc=entrypoint, agent_name="my-telephony-agent"))
```

### .idea/workspace.xml

```xml
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="AutoImportSettings">
    <option name="autoReloadType" value="SELECTIVE" />
  </component>
  <component name="ChangeListManager">
    <list default="true" id="6d7e9222-6512-4971-9d6c-964ac00fd2ba" name="Changes" comment="">
      <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
      <change beforePath="$PROJECT_DIR$/src/chroma/chroma_service.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/chroma/chroma_service.py" afterDir="false" />
      <change beforePath="$PROJECT_DIR$/src/main.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/main.py" afterDir="false" />
    </list>
    <option name="SHOW_DIALOG" value="false" />
    <option name="HIGHLIGHT_CONFLICTS" value="true" />
    <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
    <option name="LAST_RESOLUTION" value="IGNORE" />
  </component>
  <component name="FileTemplateManagerImpl">
    <option name="RECENT_TEMPLATES">
      <list>
        <option value="Python Script" />
      </list>
    </option>
  </component>
  <component name="Git.Settings">
    <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
  </component>
  <component name="ProjectColorInfo">{
  &quot;associatedIndex&quot;: 6
}</component>
  <component name="ProjectId" id="34WS65Ne16rulQmNmhFZWs9gN3P" />
  <component name="ProjectViewState">
    <option name="hideEmptyMiddlePackages" value="true" />
    <option name="showLibraryContents" value="true" />
  </component>
  <component name="PropertiesComponent"><![CDATA[{
  "keyToString": {
    "ModuleVcsDetector.initialDetectionPerformed": "true",
    "Python.backend_service.executor": "Run",
    "Python.chroma_service (1).executor": "Run",
    "Python.chroma_service.executor": "Run",
    "Python.database.executor": "Run",
    "Python.generate_excel (1).executor": "Run",
    "Python.generate_excel.executor": "Run",
    "Python.main.executor": "Run",
    "RunOnceActivity.ShowReadmeOnStart": "true",
    "RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
    "RunOnceActivity.git.unshallow": "true",
    "git-widget-placeholder": "backend",
    "last_opened_file_path": "/Users/admin/Desktop/Calhacks/healthcare_ai_backend",
    "node.js.detected.package.eslint": "true",
    "node.js.detected.package.tslint": "true",
    "node.js.selected.package.eslint": "(autodetect)",
    "node.js.selected.package.tslint": "(autodetect)",
    "nodejs_package_manager_path": "npm",
    "vue.rearranger.settings.migration": "true"
  }
}]]></component>
  <component name="RecentsManager">
    <key name="MoveFile.RECENT_KEYS">
      <recent name="$PROJECT_DIR$/src" />
      <recent name="$PROJECT_DIR$/src/backend/chroma" />
      <recent name="$PROJECT_DIR$/src/backend" />
    </key>
  </component>
  <component name="RunManager" selected="Python.chroma_service (1)">
    <configuration name="backend_service" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
      <module name="healthcare_ai_backend" />
      <option name="ENV_FILES" value="" />
      <option name="INTERPRETER_OPTIONS" value="" />
      <option name="PARENT_ENVS" value="true" />
      <envs>
        <env name="PYTHONUNBUFFERED" value="1" />
      </envs>
      <option name="SDK_HOME" value="" />
      <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/src/backend" />
      <option name="IS_MODULE_SDK" value="true" />
      <option name="ADD_CONTENT_ROOTS" value="true" />
      <option name="ADD_SOURCE_ROOTS" value="true" />
      <EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
      <option name="SCRIPT_NAME" value="$PROJECT_DIR$/src/backend/backend_service.py" />
      <option name="PARAMETERS" value="" />
      <option name="SHOW_COMMAND_LINE" value="false" />
      <option name="EMULATE_TERMINAL" value="false" />
      <option name="MODULE_MODE" value="false" />
      <option name="REDIRECT_INPUT" value="false" />
      <option name="INPUT_FILE" value="" />
      <method v="2" />
    </configuration>
    <configuration name="chroma_service (1)" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
      <module name="healthcare_ai_backend" />
      <option name="ENV_FILES" value="" />
      <option name="INTERPRETER_OPTIONS" value="" />
      <option name="PARENT_ENVS" value="true" />
      <envs>
        <env name="PYTHONUNBUFFERED" value="1" />
      </envs>
      <option name="SDK_HOME" value="" />
      <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/src/chroma" />
      <option name="IS_MODULE_SDK" value="true" />
      <option name="ADD_CONTENT_ROOTS" value="true" />
      <option name="ADD_SOURCE_ROOTS" value="true" />
      <EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
      <option name="SCRIPT_NAME" value="$PROJECT_DIR$/src/chroma/chroma_service.py" />
      <option name="PARAMETERS" value="" />
      <option name="SHOW_COMMAND_LINE" value="false" />
      <option name="EMULATE_TERMINAL" value="false" />
      <option name="MODULE_MODE" value="false" />
      <option name="REDIRECT_INPUT" value="false" />
      <option name="INPUT_FILE" value="" />
      <method v="2" />
    </configuration>
    <configuration name="chroma_service" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
      <module name="healthcare_ai_backend" />
      <option name="ENV_FILES" value="" />
      <option name="INTERPRETER_OPTIONS" value="" />
      <option name="PARENT_ENVS" value="true" />
      <envs>
        <env name="PYTHONUNBUFFERED" value="1" />
      </envs>
      <option name="SDK_HOME" value="" />
      <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/src/backend" />
      <option name="IS_MODULE_SDK" value="true" />
      <option name="ADD_CONTENT_ROOTS" va
[truncated — 6483 more characters]
```

### src/agent.py

```python
import asyncio
from dotenv import load_dotenv
from livekit import agents
from livekit.agents import AgentSession, Agent, RoomInputOptions
from livekit.plugins import noise_cancellation, silero
from livekit.plugins.turn_detector.multilingual import MultilingualModel
from livekit.rtc import ConnectionState
import dateparser
from chroma.chroma_service import ChromaService
import re
from db.mongo_service import MongoService
from models.user import User
from utils.time_utils import format_datetime_natural

load_dotenv(".env.local")

chroma_service = ChromaService()
mongo_service = MongoService()

# --- Helper: simple text → list of symptoms --- #
def extract_symptoms(text: str) -> list[str]:
    """
    Convert free text like 'headache and a bit cold' → ['headache', 'cold']
    """
    tokens = re.split(r"[,\s]*(?:and|but|with|also|plus|,|\s)+[,\s]*", text, flags=re.IGNORECASE)
    return [t.strip().lower() for t in tokens if t.strip()]

        
# --- Tool: Parse datetime --- #
@agents.function_tool
async def parse_datetime(ctx: agents.RunContext, text: str) -> dict:
    """
    Converts natural language datetime into an ISO8601 timestamp.
    Example: "tomorrow 3pm" -> {"datetime": "2025-10-23T15:00:00-07:00"}
    """
    parsed = dateparser.parse(text)
    if not parsed:
        return {"error": "Could not parse date/time."}
    return {"datetime": parsed.isoformat()}

# --- Define main Assistant agent --- #
class MainAssistant(Agent):
    def __init__(self, user: User | None) -> None:
        self.user = user
        self.issue = ""
        self.symptoms = []
        self.recommendations = []
        self.appointment_id = None
        
        @agents.function_tool
        async def symptom_check_api(ctx: agents.RunContext, symptoms: str, n_result: int = 3) -> dict:
            """
            Uses Chroma to find the most similar known health issue based on symptoms.
            If multiple results are returned, suggest additional symptoms to the user to refine the query.
            """
            try:
                user_symptoms = extract_symptoms(symptoms)
                print(f"Extracted symptoms: {user_symptoms}")
                results = chroma_service.query(user_symptoms, n_results=n_result)
                if not results:
                    raise ValueError("No results from Chroma")

                # If only one result, return immediately
                if len(results) == 1:
                    self.issue = results[0].get("health_issue", "unknown")
                    self.symptoms = user_symptoms.copy()
                    self.recommendations = results[0].get("advice", "Please consult a healthcare professional.").split(',')
                    return {
                        "issue": results[0].get("health_issue", "unknown"),
                        "recommendation": results[0].get("advice", "Please consult a healthcare professional.")
                    }

                # Multiple results: extract unique symptoms not already mentioned
                additional_symptoms = set()
                for r in results:
                    symptom_str = r.get("symptoms", "")
                    # split by comma and strip whitespace
                    for s in symptom_str.split(","):
                        s_clean = s.strip().lower()
                        if s_clean and s_clean not in user_symptoms:
                            additional_symptoms.add(s_clean)

                # Pick up to 3 additional symptoms to suggest
                suggested_symptoms = list(additional_symptoms)[:3]

                return {
                    "issue": "I found several possible conditions.",
                    "recommendation": "",
                    "suggested_symptoms": suggested_symptoms  
                }

            except Exception as e:
                print(f"Error in symptom_check_api: {e}")
                return {
                    "issue": "unknown",
                    "recommendation": "Please consult a healthcare professional."
                }
        
        @agents.function_tool
        async def book_appointment(ctx: agents.RunContext, issue: str, preferred_time: str) -> dict:
            user_id = str(self.user._id) if self.user else "anonymous"
            rebooking = False

            # --- Handle rebooking ---
            if self.appointment_id:
                print(f"🔄 Deleting previous appointment {self.appointment_id} for rebooking...")
                mongo_service.delete_appointment(self.appointment_id)
                rebooking = True

            print(f"📅 Booking appointment for {user_id} regarding {issue} at {preferred_time}")
            result = mongo_service.create_appointment(
                user_id=user_id,
                issue=f"Appointment regarding {issue}",
                datetime_iso=preferred_time,
                confirmation="confirmed"
            )

            if not result:
                return {"confirmation": "There was a scheduling conflict. Please choose a different time."}

            self.appointment_id = result

            # --- Send confirmation email asynchronously ---
            if self.user and self.user.email:
                print(f"Preparing to send {'update' if rebooking else 'confirmation'} email to {self.user.email}...")
                formatted_time = format_datetime_natural(preferred_time)

                async def send_email():
                    subject = (
                        f"Cura Appointment Update Confirmation at {formatted_time}"
                        if rebooking
                        else f"Cura Appointment Confirmation at {formatted_time}"
                    )
                    content = (
                        f"Hello {self.user.name},\n\n"
                        f"Your appointment with Cura regarding '{issue}' has been successfully "
                        f"{'updated' if rebooking else 'booked'}.\n"
                        f"📅 Date & Time: {formatted_time}\n"
     
[truncated — 7335 more characters]
```

### .idea/inspectionProfiles/profiles_settings.xml

```xml
<component name="InspectionProjectProfileManager">
  <settings>
    <option name="USE_PROJECT_PROFILE" value="false" />
    <version value="1.0" />
  </settings>
</component>
```

### src/models/user.py

```python
class User:
    def __init__(self, _id: str, name: str, phone: str, email: str, user_type: str):
        self._id = _id
        self.name = name
        self.phone = phone
        self.email = email
        self.type = user_type

    def __repr__(self):
        return f"User(name={self.name!r}, email={self.email!r}, type={self.type!r})"
```

[6 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]