Skip to content
Patient Intake AI Agents: Automating Pre-Visit Data Collection
Learn Agentic AI11 min read24 views

Patient Intake AI Agents: Automating Pre-Visit Data Collection

Build an AI agent that automates patient intake by generating dynamic forms, verifying insurance eligibility, collecting medical history, and managing consent documents before the visit.

The Patient Intake Problem

The average patient spends 15 to 20 minutes filling out paperwork before every visit. Much of this data already exists somewhere in the healthcare system — previous visit records, insurance databases, pharmacy histories. An AI intake agent can pre-populate known information, ask only for what is missing, verify insurance in real time, and present everything to the clinical team before the patient walks through the door.

The result is shorter wait times, fewer data entry errors, and more complete records at the point of care.

Dynamic Form Generation

Rather than handing every patient the same 10-page clipboard, the intake agent generates forms based on the visit type, the patient's history, and what data the practice already has on file:

Hear it before you finish reading

Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.

Try Live Demo →
flowchart LR
    CALLER(["Patient or Caregiver"])
    subgraph TEL["Telephony"]
        SIP["Twilio SIP and PSTN"]
    end
    subgraph BRAIN["Healthcare AI Agent"]
        STT["Streaming STT<br/>Deepgram or Whisper"]
        NLU{"Intent and<br/>Entity Extraction"}
        TOOLS["Tool Calls"]
        TTS["Streaming TTS<br/>ElevenLabs or Rime"]
    end
    subgraph DATA["Live Data Plane"]
        CRM[("CRM and Notes")]
        CAL[("Calendar and<br/>Schedule")]
        KB[("Knowledge Base<br/>and Policies")]
    end
    subgraph OUT["Outcomes"]
        O1(["Appointment booked"])
        O2(["Prescription refill request"])
        O3(["Triage to clinician"])
    end
    CALLER --> SIP --> STT --> NLU
    NLU -->|Lookup| TOOLS
    TOOLS <--> CRM
    TOOLS <--> CAL
    TOOLS <--> KB
    NLU --> TTS --> SIP --> CALLER
    NLU -->|Resolved| O1
    NLU -->|Schedule| O2
    NLU -->|Escalate| O3
    style CALLER fill:#f1f5f9,stroke:#64748b,color:#0f172a
    style NLU fill:#4f46e5,stroke:#4338ca,color:#fff
    style O1 fill:#059669,stroke:#047857,color:#fff
    style O2 fill:#0ea5e9,stroke:#0369a1,color:#fff
    style O3 fill:#f59e0b,stroke:#d97706,color:#1f2937
from dataclasses import dataclass, field
from enum import Enum
from typing import Optional

class FieldType(Enum):
    TEXT = "text"
    DATE = "date"
    SELECT = "select"
    MULTI_SELECT = "multi_select"
    BOOLEAN = "boolean"
    FILE_UPLOAD = "file_upload"

@dataclass
class IntakeField:
    name: str
    label: str
    field_type: FieldType
    required: bool = True
    options: list[str] = field(default_factory=list)
    prefilled_value: Optional[str] = None
    help_text: Optional[str] = None

class IntakeFormGenerator:
    def __init__(self, patient_record: dict, visit_type: str):
        self.patient = patient_record
        self.visit_type = visit_type

    def generate_fields(self) -> list[IntakeField]:
        fields = self._base_demographics()
        fields += self._insurance_fields()
        fields += self._visit_specific_fields()
        fields += self._consent_fields()
        # Remove fields we already have valid data for
        return [f for f in fields if not self._is_already_complete(f)]

    def _base_demographics(self) -> list[IntakeField]:
        return [
            IntakeField(
                name="full_name",
                label="Full Legal Name",
                field_type=FieldType.TEXT,
                prefilled_value=self.patient.get("name"),
            ),
            IntakeField(
                name="date_of_birth",
                label="Date of Birth",
                field_type=FieldType.DATE,
                prefilled_value=self.patient.get("dob"),
            ),
            IntakeField(
                name="phone",
                label="Phone Number",
                field_type=FieldType.TEXT,
                prefilled_value=self.patient.get("phone"),
            ),
            IntakeField(
                name="emergency_contact",
                label="Emergency Contact Name and Phone",
                field_type=FieldType.TEXT,
            ),
        ]

    def _is_already_complete(self, field_obj: IntakeField) -> bool:
        if field_obj.prefilled_value and field_obj.name != "emergency_contact":
            return True
        return False

The key insight is _is_already_complete — the agent skips fields where valid data already exists, turning a 40-field form into a 10-field form for returning patients.

Insurance Verification in Real Time

Instead of collecting an insurance card photo and verifying it days later, the agent verifies eligibility at intake time:

import httpx
from dataclasses import dataclass
from typing import Optional

@dataclass
class EligibilityResult:
    is_active: bool
    plan_name: str
    group_number: str
    copay_amount: Optional[float] = None
    deductible_remaining: Optional[float] = None
    requires_referral: bool = False
    error_message: Optional[str] = None

class InsuranceVerifier:
    def __init__(self, clearinghouse_url: str, api_key: str):
        self._client = httpx.AsyncClient(
            base_url=clearinghouse_url,
            headers={"Authorization": f"Bearer {api_key}"},
            timeout=15.0,
        )

    async def verify_eligibility(
        self, payer_id: str, member_id: str, dob: str, service_type: str
    ) -> EligibilityResult:
        payload = {
            "payer_id": payer_id,
            "member_id": member_id,
            "date_of_birth": dob,
            "service_type_code": service_type,
            "date_of_service": "2026-03-17",
        }
        try:
            response = await self._client.post("/eligibility/verify", json=payload)
            response.raise_for_status()
            data = response.json()
            return EligibilityResult(
                is_active=data["active"],
                plan_name=data["plan_name"],
                group_number=data["group_number"],
                copay_amount=data.get("copay"),
                deductible_remaining=data.get("deductible_remaining"),
                requires_referral=data.get("referral_required", False),
            )
        except httpx.HTTPStatusError as e:
            return EligibilityResult(
                is_active=False,
                plan_name="",
                group_number="",
                error_message=f"Verification failed: {e.response.status_code}",
            )

Medical History Collection

The agent collects medical history conversationally, asking follow-up questions based on previous answers:

class MedicalHistoryCollector:
    CONDITION_FOLLOWUPS = {
        "diabetes": ["What type of diabetes?", "Current medications?", "Last A1C level?"],
        "hypertension": ["Current blood pressure medications?", "Last BP reading?"],
        "asthma": ["Current inhalers?", "Frequency of attacks?"],
    }

    def __init__(self):
        self.collected: dict = {}
        self.pending_followups: list[str] = []

    def process_conditions(self, conditions: list[str]) -> list[str]:
        followup_questions = []
        for condition in conditions:
            self.collected[condition] = {"reported": True}
            normalized = condition.lower().strip()
            if normalized in self.CONDITION_FOLLOWUPS:
                followup_questions.extend(self.CONDITION_FOLLOWUPS[normalized])
        self.pending_followups = followup_questions
        return followup_questions

    def get_summary(self) -> dict:
        return {
            "conditions": self.collected,
            "complete": len(self.pending_followups) == 0,
            "pending_questions": self.pending_followups,
        }

Digital consent must be explicit, timestamped, and auditable:

Still reading? Stop comparing — try CallSphere live.

CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.

from datetime import datetime
import hashlib

@dataclass
class ConsentRecord:
    patient_id: str
    consent_type: str
    version: str
    granted: bool
    timestamp: datetime
    ip_address: str
    document_hash: str

class ConsentManager:
    REQUIRED_CONSENTS = [
        ("treatment", "Consent to Treatment", "v2.1"),
        ("privacy", "Notice of Privacy Practices", "v3.0"),
        ("telehealth", "Telehealth Consent", "v1.4"),
    ]

    def get_pending_consents(self, patient_id: str, existing: list[str]) -> list[tuple]:
        return [
            (ctype, label, version)
            for ctype, label, version in self.REQUIRED_CONSENTS
            if ctype not in existing
        ]

    def record_consent(
        self, patient_id: str, consent_type: str, version: str, document_text: str, ip: str
    ) -> ConsentRecord:
        doc_hash = hashlib.sha256(document_text.encode()).hexdigest()
        return ConsentRecord(
            patient_id=patient_id,
            consent_type=consent_type,
            version=version,
            granted=True,
            timestamp=datetime.utcnow(),
            ip_address=ip,
            document_hash=doc_hash,
        )

FAQ

How does the agent handle patients who cannot complete digital intake?

The agent should detect when a patient is struggling (repeated validation errors, long pauses, requests for help) and offer to transfer to a staff member. The partially completed data is saved so the staff member can pick up where the patient left off rather than starting over.

What happens when insurance verification returns conflicting data?

The agent flags discrepancies — such as a member ID that matches but the name does not — and escalates to billing staff. It does not silently accept mismatched data, as this leads to claim denials downstream.

Can the intake agent handle minors and dependents?

Yes, but with additional logic. For patients under 18, the agent must collect the legal guardian's information and obtain consent from the guardian rather than the patient. The form generator checks the patient's date of birth and adjusts the required fields accordingly.


#HealthcareAI #PatientIntake #InsuranceVerification #MedicalHistory #Python #AgenticAI #LearnAI #AIEngineering

Share

Try CallSphere AI Voice Agents

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