Skip to content
Learn Agentic AI11 min read0 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:

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:

See AI Voice Agents Handle Real Calls

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

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:

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 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.