ES
SOP-CL-008 · v1.0
Jun 11, 2026

Patient AR Audit

Claims · Audit patients with balance via Open Dental API

Purpose

Generate a weekly report of patients with outstanding balance in Open Dental, separating what insurance owes vs. what patient owes, so Billing Jr can prioritize follow-up.

Time: ~3-10 min runtime depending on client size  ·  Audience: Billing Junior + Manager

Pre-checks

CheckHow to verify
Client has OD API enabled"has_od_api": true in clients/<id>.json
venv activatedPrompt starts with (.venv)
Access to client Google SheetConfigured in clients/<id>.json under google_sheet

How to run

1Basic mode (all patients with balance)

python patient_ar_audit.py <client-id>

Default: scans procedures from last 365 days, groups by patient, calculates balance, writes to client Google Sheet.

2With min-days outstanding filter

python patient_ar_audit.py <client-id> --min-days 30

Skips claims newer than X days (default 30). Useful to not call insurance for a 5-day-old claim.

3"All balances" mode (no Stedi check)

python patient_ar_audit.py <client-id> --all-with-balance --no-stedi-check

For clients migrated from other PMS (Hallandale type). Lists ALL patients with balance without checking Stedi status.

What the report contains

ColumnMeaning
PatNumPatient Number in OD
Chart #Chart Number (if any)
Patient NameLast, First
Last Procedure DOSDate of last complete procedure
Days OutstandingDays since last procedure
BalTotalTotal patient owes
InsBalanceOf that total, how much insurance owes
PatBalanceOf that total, how much patient owes
Insurance CarrierPatient's primary carrier
Subscriber IDSubscriber ID
Group #Group number
PhonePatient phone for contact
ActionSystem-suggested action

Workflow to work the report

1Open client Google Sheet

Tab with client prefix + "Patient AR Audit" + timestamp.

2Sort by Days Outstanding (descending)

Attack the oldest first (filing limit risk).

3For each patient with InsBalance > 0:

  • Was claim sent? → if not, send it
  • Was claim denied? → see SOP-CL-005 (Common Denials)
  • More than 60 days? → Stedi 276 status check + escalate if needed
  • More than 90 days? → consider appeal

4For each patient with PatBalance > 0:

  • Call patient (script in dashboard)
  • Offer payment plan if balance >$500
  • Note contact in OD

5Mark resolved in sheet

Each worked row → mark status. Next run won't show it again if balance dropped to 0.

Special cases system detects automatically

DetectionWhy it matters
Insurance payment received but claim still marked pendingNeed to post EOB in OD
InsPayAmt > 0 but claim not linkedOrphan data — investigate and fix
BalTotal <= 0Balance already zero — skip
WriteOff/DedApplied > 0 without paymentAdjustment already applied — verify if expected
Non-ADA procedures (DEDUC, ADJUST)Filter out — not real procedures
Historical note: System was refined after "Wendy Jackson Cabrera" case in April 2026 — had $650 Ins Payment that wasn't detected because only ClaimNum was checked. Now detects InsPayAmt, WriteOff, DedApplied and BalTotal.

Troubleshooting

ErrorSolution
Intermittent ReadTimeoutRe-run — has automatic retry with exponential backoff
OD returns 0 patientsEndpoint /patients doesn't expose BalTotal. Script already pivoted to use /procedurelogs
DateTStamp filter rejectedOD doesn't accept that filter. Script paginates all and filters client-side
Hits Sheet row limitScript already calls _ensure_capacity() before each write

Reference commands

# Basic mode (recommended for OD API clients)
python patient_ar_audit.py benitez-dental

# With min threshold
python patient_ar_audit.py casas-family-dentistry --min-days 30

# Legacy mode (migrated clients)
python patient_ar_audit.py hallandale-dental --all-with-balance --no-stedi-check

# Single patient only (debug)
python patient_ar_audit.py benitez-dental --single-patient 12345

Related