Skip to content
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.

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 this article
C

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.