Skip to content
Learn Agentic AI
Learn Agentic AI14 min read0 views

Building a Referral Coordination Agent: Specialist Matching and Appointment Facilitation

Build an AI agent that manages the end-to-end referral workflow — matching patients to specialists based on clinical needs and insurance, checking availability, transferring records, and tracking referral completion.

The Referral Coordination Problem

When a general dentist refers a patient to a specialist — an endodontist for a root canal, an oral surgeon for an extraction, or a periodontist for gum treatment — a complex coordination chain begins. The referring office must find an appropriate specialist, verify the specialist accepts the patient's insurance, transfer clinical records, and schedule the appointment. Each step involves phone calls, faxes, and manual tracking. Studies show that 25 to 50 percent of referrals are never completed, meaning patients fall through the cracks.

A referral coordination agent automates this entire workflow, ensuring every referral reaches its destination.

Referral Data Model

from dataclasses import dataclass, field
from datetime import date, datetime
from typing import Optional
from enum import Enum
import uuid


class ReferralStatus(Enum):
    CREATED = "created"
    SPECIALIST_MATCHED = "specialist_matched"
    APPOINTMENT_SCHEDULED = "appointment_scheduled"
    RECORDS_SENT = "records_sent"
    COMPLETED = "completed"
    PATIENT_DECLINED = "patient_declined"
    EXPIRED = "expired"


class Specialty(Enum):
    ENDODONTICS = "endodontics"
    ORAL_SURGERY = "oral_surgery"
    PERIODONTICS = "periodontics"
    ORTHODONTICS = "orthodontics"
    PROSTHODONTICS = "prosthodontics"
    PEDIATRIC = "pediatric_dentistry"
    PATHOLOGY = "oral_pathology"


@dataclass
class Referral:
    id: str = field(
        default_factory=lambda: str(uuid.uuid4())
    )
    patient_id: str = ""
    referring_provider_id: str = ""
    specialty_needed: Specialty = Specialty.ENDODONTICS
    reason: str = ""
    urgency: str = "routine"  # routine, urgent, emergency
    tooth_numbers: list[int] = field(default_factory=list)
    clinical_notes: str = ""
    matched_specialist_id: Optional[str] = None
    appointment_date: Optional[datetime] = None
    status: ReferralStatus = ReferralStatus.CREATED
    created_at: datetime = field(
        default_factory=datetime.utcnow
    )
    insurance_payer_id: Optional[str] = None


@dataclass
class Specialist:
    id: str
    name: str
    specialty: Specialty
    practice_name: str
    phone: str
    fax: str
    email: str
    address: str
    accepted_insurances: list[str]
    npi: str
    average_wait_days: int
    distance_miles: float = 0.0
    rating: float = 0.0
    accepts_emergency: bool = False

Specialist Matching Engine

The matching engine finds the best specialist based on multiple criteria: specialty, insurance acceptance, distance, availability, and patient preferences.

flowchart TD
    START["Building a Referral Coordination Agent: Specialis…"] --> A
    A["The Referral Coordination Problem"]
    A --> B
    B["Referral Data Model"]
    B --> C
    C["Specialist Matching Engine"]
    C --> D
    D["Availability Checking and Appointment S…"]
    D --> E
    E["Clinical Document Transfer"]
    E --> F
    F["Referral Completion Tracking"]
    F --> G
    G["FAQ"]
    G --> DONE["Key Takeaways"]
    style START fill:#4f46e5,stroke:#4338ca,color:#fff
    style DONE fill:#059669,stroke:#047857,color:#fff
from typing import Optional


class SpecialistMatcher:
    def __init__(self, db):
        self.db = db

    async def find_matches(
        self, referral: Referral,
        patient_lat: float, patient_lng: float,
        max_distance_miles: float = 25.0,
        limit: int = 5,
    ) -> list[Specialist]:
        rows = await self.db.fetch("""
            SELECT s.*,
                earth_distance(
                    ll_to_earth(s.latitude, s.longitude),
                    ll_to_earth($3, $4)
                ) / 1609.34 AS distance_miles
            FROM specialists s
            JOIN specialist_insurances si
                ON si.specialist_id = s.id
            WHERE s.specialty = $1
              AND si.payer_id = $2
              AND s.accepting_new_patients = true
              AND earth_distance(
                  ll_to_earth(s.latitude, s.longitude),
                  ll_to_earth($3, $4)
              ) / 1609.34 <= $5
            ORDER BY
                CASE WHEN $6 = 'emergency'
                     AND s.accepts_emergency
                     THEN 0 ELSE 1 END,
                s.average_wait_days ASC,
                distance_miles ASC
            LIMIT $7
        """,
            referral.specialty_needed.value,
            referral.insurance_payer_id,
            patient_lat, patient_lng,
            max_distance_miles,
            referral.urgency,
            limit,
        )

        return [
            Specialist(
                id=r["id"],
                name=r["name"],
                specialty=Specialty(r["specialty"]),
                practice_name=r["practice_name"],
                phone=r["phone"],
                fax=r["fax"],
                email=r["email"],
                address=r["address"],
                accepted_insurances=[],
                npi=r["npi"],
                average_wait_days=r["average_wait_days"],
                distance_miles=round(r["distance_miles"], 1),
                rating=r.get("rating", 0),
                accepts_emergency=r["accepts_emergency"],
            )
            for r in rows
        ]

    def rank_matches(
        self, specialists: list[Specialist],
        urgency: str,
    ) -> list[Specialist]:
        def score(s: Specialist) -> float:
            distance_score = max(0, 25 - s.distance_miles) / 25
            wait_score = max(0, 30 - s.average_wait_days) / 30
            rating_score = s.rating / 5.0

            if urgency == "emergency":
                return wait_score * 0.6 + distance_score * 0.3 + rating_score * 0.1
            elif urgency == "urgent":
                return wait_score * 0.4 + distance_score * 0.3 + rating_score * 0.3
            else:
                return distance_score * 0.3 + wait_score * 0.3 + rating_score * 0.4

        return sorted(specialists, key=score, reverse=True)

Availability Checking and Appointment Scheduling

Once a specialist is selected, the agent checks their availability and books the appointment through the specialist's scheduling system.

class ReferralScheduler:
    def __init__(self, db):
        self.db = db

    async def check_specialist_availability(
        self, specialist_id: str,
        preferred_date: date,
        search_days: int = 14,
    ) -> list[dict]:
        rows = await self.db.fetch("""
            SELECT schedule_date, start_time, end_time,
                   slot_duration_minutes
            FROM specialist_availability
            WHERE specialist_id = $1
              AND schedule_date BETWEEN $2
                  AND ($2 + $3 * INTERVAL '1 day')
              AND slots_remaining > 0
            ORDER BY schedule_date, start_time
        """, specialist_id, preferred_date, search_days)

        return [
            {
                "date": r["schedule_date"],
                "start": r["start_time"],
                "end": r["end_time"],
            }
            for r in rows
        ]

    async def book_referral_appointment(
        self, referral: Referral,
        specialist: Specialist,
        appointment_datetime: datetime,
    ) -> dict:
        await self.db.execute("""
            UPDATE referrals
            SET matched_specialist_id = $2,
                appointment_date = $3,
                status = 'appointment_scheduled'
            WHERE id = $1
        """, referral.id, specialist.id,
             appointment_datetime)

        await self.db.execute("""
            INSERT INTO referral_appointments
                (referral_id, specialist_id, patient_id,
                 appointment_time, status)
            VALUES ($1, $2, $3, $4, 'scheduled')
        """, referral.id, specialist.id,
             referral.patient_id, appointment_datetime)

        return {
            "specialist": specialist.name,
            "practice": specialist.practice_name,
            "address": specialist.address,
            "phone": specialist.phone,
            "appointment": appointment_datetime.isoformat(),
        }

Clinical Document Transfer

The agent packages and sends relevant clinical documents — x-rays, treatment notes, medical history — to the specialist's office.

See AI Voice Agents Handle Real Calls

Book a free demo or calculate how much you can save with AI voice automation.

class DocumentTransfer:
    def __init__(self, db, fax_client, secure_email):
        self.db = db
        self.fax = fax_client
        self.secure_email = secure_email

    async def prepare_referral_packet(
        self, referral: Referral,
    ) -> dict:
        documents = await self.db.fetch("""
            SELECT d.id, d.doc_type, d.file_path,
                   d.created_at
            FROM patient_documents d
            WHERE d.patient_id = $1
              AND (
                  d.doc_type IN (
                      'xray', 'periapical', 'panoramic',
                      'cbct'
                  )
                  OR d.created_at > CURRENT_DATE
                      - INTERVAL '90 days'
              )
            ORDER BY d.created_at DESC
        """, referral.patient_id)

        medical_history = await self.db.fetchrow("""
            SELECT allergies, medications, conditions,
                   blood_pressure, medical_alerts
            FROM patient_medical_history
            WHERE patient_id = $1
        """, referral.patient_id)

        return {
            "referral_id": referral.id,
            "clinical_notes": referral.clinical_notes,
            "reason": referral.reason,
            "tooth_numbers": referral.tooth_numbers,
            "documents": [
                {
                    "type": d["doc_type"],
                    "path": d["file_path"],
                }
                for d in documents
            ],
            "medical_history": dict(medical_history)
                if medical_history else {},
        }

    async def send_to_specialist(
        self, specialist: Specialist,
        packet: dict,
        method: str = "secure_email",
    ) -> bool:
        if method == "fax":
            pdf = await self._generate_referral_pdf(packet)
            result = await self.fax.send(
                specialist.fax, pdf
            )
        else:
            result = await self.secure_email.send(
                to=specialist.email,
                subject=(
                    f"Referral: Patient "
                    f"{packet['referral_id']}"
                ),
                attachments=packet["documents"],
                body=self._format_referral_letter(packet),
            )

        await self.db.execute("""
            UPDATE referrals
            SET status = 'records_sent'
            WHERE id = $1
        """, packet["referral_id"])

        return result.get("success", False)

Referral Completion Tracking

The agent monitors whether referred patients actually complete their specialist visit, closing the loop for the referring provider.

class ReferralTracker:
    def __init__(self, db, notification_service):
        self.db = db
        self.notify = notification_service

    async def check_completion_status(self):
        pending = await self.db.fetch("""
            SELECT r.*, p.first_name, p.phone,
                   s.name AS specialist_name
            FROM referrals r
            JOIN patients p ON p.id = r.patient_id
            JOIN specialists s
                ON s.id = r.matched_specialist_id
            WHERE r.status IN (
                'appointment_scheduled', 'records_sent'
            )
            AND r.appointment_date < CURRENT_TIMESTAMP
        """)

        for ref in pending:
            days_past = (
                datetime.utcnow() - ref["appointment_date"]
            ).days

            if days_past > 7:
                await self.notify.send_to_provider(
                    provider_id=ref["referring_provider_id"],
                    message=(
                        f"Referral for {ref['first_name']} "
                        f"to {ref['specialist_name']} may be "
                        f"incomplete. Appointment was "
                        f"{days_past} days ago."
                    ),
                )

FAQ

The agent presents the ranked specialist options as suggestions, not requirements. If the patient names a specific specialist, the agent looks them up in the database, verifies they accept the patient's insurance, and proceeds with that choice. If the specialist is not in the system, the agent adds their information and still handles record transfer and appointment coordination.

What happens when no specialist within range accepts the patient's insurance?

The agent expands the search radius in increments and also checks for specialists who offer sliding-scale fees or payment plans for out-of-network patients. It presents the options transparently — showing both in-network options farther away and closer out-of-network options with estimated costs — so the patient and referring provider can make an informed decision.

How does the agent get the specialist's availability if they use a different scheduling system?

The agent supports multiple integration methods. For specialists on the same practice management software, it queries availability directly. For external practices, it uses standardized APIs where available or falls back to faxing a referral request with preferred dates. The specialist's office confirms the appointment, and the agent updates the referral status automatically.


#ReferralManagement #SpecialistMatching #HealthcareAI #CareCoordination #Python #AgenticAI #LearnAI #AIEngineering

Share
C

Written by

CallSphere Team

Expert insights on AI voice agents and customer communication automation.

Try CallSphere AI Voice Agents

See how AI voice agents work for your industry. Live demo available -- no signup required.

Related Articles You May Like

Healthcare

Reducing ER Boarding with AI Voice Triage: Nurse Line Automation That Diverts Non-Emergent Calls

How AI nurse triage agents route non-emergent callers away from the ER toward urgent care, telehealth, and self-care — measurably reducing door-to-provider time.

AI Interview Prep

7 AI Coding Interview Questions From Anthropic, Meta & OpenAI (2026 Edition)

Real AI coding interview questions from Anthropic, Meta, and OpenAI in 2026. Includes implementing attention from scratch, Anthropic's progressive coding screens, Meta's AI-assisted round, and vector search — with solution approaches.

Learn Agentic AI

Building a Multi-Agent Data Pipeline: Ingestion, Transformation, and Analysis Agents

Build a three-agent data pipeline with ingestion, transformation, and analysis agents that process data from APIs, CSVs, and databases using Python.

Learn Agentic AI

Building a Research Agent with Web Search and Report Generation: Complete Tutorial

Build a research agent that searches the web, extracts and synthesizes data, and generates formatted reports using OpenAI Agents SDK and web search tools.

Learn Agentic AI

AI Agents for Healthcare: Appointment Scheduling, Insurance Verification, and Patient Triage

How healthcare AI agents handle real workflows: appointment booking with provider matching, insurance eligibility checks, symptom triage, HIPAA compliance, and EHR integration patterns.

Learn Agentic AI

OpenAI Agents SDK in 2026: Building Multi-Agent Systems with Handoffs and Guardrails

Complete tutorial on the OpenAI Agents SDK covering agent creation, tool definitions, handoff patterns between specialist agents, and input/output guardrails for safe AI systems.