AI Agent for Medical Billing Inquiries: Explaining Bills, Processing Payments, and Setting Up Plans
Build an AI agent that explains medical and dental bills in plain language, processes secure payments, sets up payment plans for patients, and handles billing dispute workflows with full Python implementation.
Why Billing Is the Top Source of Patient Frustration
Billing inquiries are the number one reason patients call medical and dental practices. Patients receive statements filled with CDT or CPT codes, insurance adjustments, and confusing line items. Most just want to know: what do I owe, and why? An AI billing agent answers these questions instantly, processes payments securely, and sets up payment plans — all without tying up staff.
Bill Data Model and Retrieval
The billing agent needs access to the full billing record: charges, insurance payments, adjustments, and patient responsibility.
from dataclasses import dataclass, field
from datetime import date, datetime
from typing import Optional
from enum import Enum
from decimal import Decimal
class LineItemStatus(Enum):
BILLED = "billed"
INSURANCE_PAID = "insurance_paid"
ADJUSTED = "adjusted"
PATIENT_DUE = "patient_due"
PAID = "paid"
COLLECTIONS = "collections"
@dataclass
class BillLineItem:
procedure_code: str
procedure_description: str
service_date: date
tooth_number: Optional[int]
fee: Decimal
insurance_paid: Decimal
adjustment: Decimal
patient_responsibility: Decimal
status: LineItemStatus
@dataclass
class PatientBill:
bill_id: str
patient_id: str
patient_name: str
statement_date: date
line_items: list[BillLineItem]
total_charges: Decimal
total_insurance: Decimal
total_adjustments: Decimal
total_patient_due: Decimal
total_paid_by_patient: Decimal
balance_due: Decimal
payment_plan_active: bool = False
class BillingRetriever:
def __init__(self, db):
self.db = db
async def get_patient_bill(
self, patient_id: str,
) -> Optional[PatientBill]:
header = await self.db.fetchrow("""
SELECT b.id, b.patient_id,
p.first_name || ' ' || p.last_name AS name,
b.statement_date,
COALESCE(pp.id IS NOT NULL, false)
AS has_plan
FROM bills b
JOIN patients p ON p.id = b.patient_id
LEFT JOIN payment_plans pp
ON pp.bill_id = b.id AND pp.status = 'active'
WHERE b.patient_id = $1
ORDER BY b.statement_date DESC LIMIT 1
""", patient_id)
if not header:
return None
items = await self.db.fetch("""
SELECT procedure_code, procedure_description,
service_date, tooth_number,
fee, insurance_paid, adjustment,
patient_responsibility, status
FROM bill_line_items
WHERE bill_id = $1
ORDER BY service_date
""", header["id"])
line_items = [
BillLineItem(
procedure_code=r["procedure_code"],
procedure_description=r["procedure_description"],
service_date=r["service_date"],
tooth_number=r["tooth_number"],
fee=Decimal(str(r["fee"])),
insurance_paid=Decimal(str(r["insurance_paid"])),
adjustment=Decimal(str(r["adjustment"])),
patient_responsibility=Decimal(
str(r["patient_responsibility"])
),
status=LineItemStatus(r["status"]),
)
for r in items
]
total_charges = sum(i.fee for i in line_items)
total_insurance = sum(
i.insurance_paid for i in line_items
)
total_adj = sum(i.adjustment for i in line_items)
total_patient = sum(
i.patient_responsibility for i in line_items
)
payments = await self.db.fetchrow("""
SELECT COALESCE(SUM(amount), 0) AS paid
FROM payments WHERE bill_id = $1
""", header["id"])
balance = total_patient - Decimal(str(payments["paid"]))
return PatientBill(
bill_id=header["id"],
patient_id=patient_id,
patient_name=header["name"],
statement_date=header["statement_date"],
line_items=line_items,
total_charges=total_charges,
total_insurance=total_insurance,
total_adjustments=total_adj,
total_patient_due=total_patient,
total_paid_by_patient=Decimal(
str(payments["paid"])
),
balance_due=balance,
payment_plan_active=header["has_plan"],
)
Plain Language Bill Explanation
The agent translates each line item into language the patient can understand, explaining why they owe what they owe.
class BillExplainer:
PROCEDURE_NAMES = {
"D0120": "Regular checkup exam",
"D0274": "X-rays (4 bitewing images)",
"D1110": "Teeth cleaning (adult)",
"D2391": "Tooth-colored filling (1 surface)",
"D2740": "Porcelain crown",
"D3310": "Root canal (front tooth)",
"D7210": "Tooth extraction (surgical)",
}
def explain(self, bill: PatientBill) -> str:
lines = []
lines.append(
f"Bill Summary for {bill.patient_name}\n"
f"Statement Date: {bill.statement_date}\n"
)
for item in bill.line_items:
friendly_name = self.PROCEDURE_NAMES.get(
item.procedure_code,
item.procedure_description,
)
tooth_str = (
f" (Tooth #{item.tooth_number})"
if item.tooth_number else ""
)
lines.append(
f" {friendly_name}{tooth_str}\n"
f" Charge: ${item.fee}\n"
f" Insurance paid: ${item.insurance_paid}\n"
f" Adjustment: -${item.adjustment}\n"
f" Your cost: ${item.patient_responsibility}\n"
)
lines.append(
f"Total charges: ${bill.total_charges}\n"
f"Insurance covered: ${bill.total_insurance}\n"
f"Adjustments: -${bill.total_adjustments}\n"
f"Your total: ${bill.total_patient_due}\n"
f"Already paid: ${bill.total_paid_by_patient}\n"
f"BALANCE DUE: ${bill.balance_due}\n"
)
return "\n".join(lines)
def explain_insurance_adjustment(
self, item: BillLineItem,
) -> str:
if item.adjustment > 0:
return (
f"The ${item.adjustment} adjustment is a "
f"contractual discount. Your dentist agreed "
f"to accept a lower fee as part of the "
f"agreement with your insurance network. "
f"This reduces your out-of-pocket cost."
)
return ""
Secure Payment Processing
The agent processes payments through a PCI-compliant payment gateway. It never handles raw card numbers — instead, it directs patients to a secure tokenization form and processes the resulting token.
See AI Voice Agents Handle Real Calls
Book a free demo or calculate how much you can save with AI voice automation.
class PaymentProcessor:
def __init__(self, gateway_client, db):
self.gateway = gateway_client
self.db = db
async def generate_payment_link(
self, bill_id: str, amount: Decimal,
) -> str:
session = await self.gateway.create_session(
amount=float(amount),
reference=bill_id,
success_url=(
f"https://portal.example.com/payment/success"
f"?bill={bill_id}"
),
cancel_url=(
f"https://portal.example.com/payment/cancel"
),
)
return session["checkout_url"]
async def process_token_payment(
self, bill_id: str, token: str, amount: Decimal,
) -> dict:
result = await self.gateway.charge(
token=token,
amount=float(amount),
description=f"Payment for bill {bill_id}",
)
if result["status"] == "succeeded":
await self.db.execute("""
INSERT INTO payments
(bill_id, amount, method,
transaction_id, paid_at)
VALUES ($1, $2, 'card', $3, $4)
""", bill_id, float(amount),
result["transaction_id"],
datetime.utcnow())
return {
"success": result["status"] == "succeeded",
"transaction_id": result.get("transaction_id"),
"message": result.get("message", ""),
}
Payment Plan Setup
For larger balances, the agent creates structured payment plans with automatic recurring charges.
@dataclass
class PaymentPlan:
id: str
bill_id: str
total_amount: Decimal
monthly_payment: Decimal
term_months: int
start_date: date
next_payment_date: date
payments_made: int = 0
status: str = "active"
class PaymentPlanManager:
def __init__(self, db, gateway):
self.db = db
self.gateway = gateway
async def create_plan(
self, bill_id: str, total: Decimal,
term_months: int, payment_token: str,
) -> PaymentPlan:
monthly = (total / term_months).quantize(
Decimal("0.01")
)
plan_id = str(__import__("uuid").uuid4())
start = date.today()
subscription = await self.gateway.create_subscription(
token=payment_token,
amount=float(monthly),
interval="monthly",
reference=plan_id,
)
plan = PaymentPlan(
id=plan_id,
bill_id=bill_id,
total_amount=total,
monthly_payment=monthly,
term_months=term_months,
start_date=start,
next_payment_date=start,
)
await self.db.execute("""
INSERT INTO payment_plans
(id, bill_id, total_amount, monthly_payment,
term_months, start_date, next_payment_date,
subscription_id, status)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, 'active')
""", plan.id, bill_id, float(total), float(monthly),
term_months, start, start,
subscription["subscription_id"])
return plan
async def get_plan_status(
self, patient_id: str,
) -> Optional[dict]:
plan = await self.db.fetchrow("""
SELECT pp.*, b.patient_id
FROM payment_plans pp
JOIN bills b ON b.id = pp.bill_id
WHERE b.patient_id = $1
AND pp.status = 'active'
""", patient_id)
if not plan:
return None
remaining = (
Decimal(str(plan["total_amount"]))
- Decimal(str(plan["monthly_payment"]))
* plan["payments_made"]
)
return {
"monthly_payment": plan["monthly_payment"],
"payments_made": plan["payments_made"],
"payments_remaining": (
plan["term_months"] - plan["payments_made"]
),
"balance_remaining": float(remaining),
"next_payment": plan["next_payment_date"],
}
FAQ
How does the agent handle billing disputes?
When a patient disputes a charge, the agent creates a formal dispute record with the patient's stated reason, flags the line item for review by the billing team, and pauses collection activity on the disputed amount. The agent can resolve simple disputes — such as duplicate charges — automatically by cross-referencing the appointment record. Complex disputes are escalated to a human billing coordinator with full context attached.
Is it safe to process payments through an AI agent?
The agent never sees or stores raw credit card numbers. It uses tokenization — the patient enters card details on a PCI-compliant form hosted by the payment gateway, which returns a one-time-use token. The agent uses this token to initiate the charge. All payment data flows through the gateway's encrypted infrastructure, and the practice's system only stores the transaction ID and confirmation.
What happens if a patient's automatic payment plan payment fails?
The agent retries the charge once after three days. If the retry also fails, it notifies the patient via their preferred contact method and offers alternatives: update payment information, make a manual payment, or contact the office to adjust the plan. After two consecutive failures, the plan is paused and the billing team receives an alert to follow up personally.
#MedicalBilling #PaymentProcessing #HealthcareAI #PatientFinance #Python #AgenticAI #LearnAI #AIEngineering
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.