ES
SOP-IT-006 · v2.0
Updated Jul 5, 2026

BCBOT — Eligibility Verification SOP

For offices with Dentrix · Complete playbook to replicate at any client
Product: Autonomous robot that every afternoon downloads next-day appointments from Dentrix (which has NO API), verifies each patient's insurance eligibility via Stedi (BC ClearingHouse), generates the coverage PDF and sends 2 emails to the team. 100% no intervention.
This SOP is the playbook to replicate BCBOT at any client with Dentrix. Follow it top-to-bottom for a new client.

1. How it works (daily flow summary)

At 6:00pm Task Scheduler launches the robot, which does everything itself:

1Opens/logs in to Dentrix

Appointment Book + Office Manager. If already open, uses it.

2Daily Huddle Report with date = next day

Preview → sends route slips to Batch Processor.

3Waits for batch loading

Waits for the bar to finish loading all route slips (≥90 s).

4Opens the last route slip

Ctrl+End + Enter → waits for render.

5Prints to PDF

Microsoft Print to PDF → C:\BCBOT\ROUTESLIPS.pdf (waits for the write to finish).

6Launches Python processing

Parses route slips, crosses with roster (member ID) and crosswalk (Stedi payer ID), queries eligibility, generates coverage PDFs (without Stedi logo).

7Sends 2 emails to hello@brandacare.com

  • [ESCALATION] — those that could NOT be verified (missing data, inactive, unsupported payer, retry).
  • [PDF TO UPLOAD] — verified OK, with PDFs attached to upload to Dentrix.

8Closes leftover windows

To leave things clean for the next day.

Key rule: only processes appointments WITHOUT the "-BC" note. Those with "-BC" (any variant: - BC, /BC, INS ACTIVE BC) are skipped = already verified. When the team resolves an escalation, they add "-BC" so it doesn't get reprocessed.

2. Per-client requirements

3. Installation (once per client)

3.1 Software

1Install Python 3.x

Check "Add to PATH". Note the python.exe path.

2Install libraries

pip install pdfplumber reportlab pypdf

Add --break-system-packages if needed.

3Install AutoHotkey v2

autohotkey.com

4Explorer → View → File name extensions = ON

To see real .txt/.ahk.

3.2 Folder and files

  1. Create C:\BCBOT.
  2. Copy all system files (see §10) to C:\BCBOT.
  3. In run_robot.bat and run_daily.bat, set the python.exe path for this client.

3.3 Credentials (text files, NEVER in code)

Create in C:\BCBOT (Notepad → Save As, type "All Files"):

FileContent
dentrix_user.txtDentrix username
dentrix_pass.txtDentrix password
stedi_key.txtStedi API key
smtp_user.txtGmail email (e.g. hello@brandacare.com)
smtp_pass.txtGmail App Password (16 chars, no spaces)
Security note: the robot reads credentials by pattern (dentrix_user*), so it tolerates small name variations. Still, verify they exist and are not empty (if dentrix_pass.txt is missing, login aborts instantly). Accepted risk: Dentrix password is stored in plain text on the machine.

3.4 Provider config

In fase0_build_270.py, edit the PROVIDER block:

PROVIDER = {
    "organizationName": "LEGAL NAME OF THE OFFICE",
    "npi": "PROVIDER_NPI",
    "taxId": "TIN_WITHOUT_DASH",
}

And CLINIC in fase0_run.py (short name that appears in emails).

4. Per-client data (roster + payer crosswalk)

This is what makes the robot verify MOST patients, not just some.

4.1 Roster (member IDs) — "Insurance Carrier List" report

In Dentrix → Office Manager → Reports → Reference → Insurance Carrier List:

Parse to cache:

python fase0_parse_roster.py roster_full.pdf roster_cache.json

Generates roster_cache.json (Chart# → member ID + payer ID + carrier). Refresh every 1–3 months or when many new patients come in (new ones don't appear until regenerating the roster).

Critical note: the full "Insurance Carrier List" (all carriers) is the difference between verifying few or many. A filtered roster leaves out entire carriers (e.g. Delta Dental, Cigna) and those patients come out as "missing member_id".

4.2 Payer crosswalk (carrier name → Stedi payer ID)

The payer ID from Dentrix's roster is garbage ("SOURCE") in ~2/3 of cases, so the real payer ID is resolved by carrier name:

Payer resolution priority (implemented in fase0_build_270.py):

  1. Patient's REAL roster payer (if valid, not "SOURCE") — wins.
  2. Reviewed crosswalk by name (exact or spaceless: "Met life" = "MetLife").
  3. Match against master payer_map.csv (token subset, safe).
  4. Local manual crosswalk.

4.3 Payers NOT supported by Stedi

In carrier_map.py, maintain the list of carriers/payers Stedi doesn't support (e.g. Solstice / 76578). Those escalate directly without querying (verified in the payer's own portal). Add ones the client uses that Stedi rejects.

4.4 Self-pay / Medicaid

Detected by carrier name and listed separately (not escalated as "missing data") — no insurance to verify.

5. The "-BC" note convention

The team marks already-verified appointments with "BC" standalone in the note (any variant: -BC, - BC, /BC, INS ACTIVE BC). The robot skips those and only processes the ones WITHOUT mark. Training the front-desk on this convention is part of onboarding.

Robust detection: the robot detects the mark both in the appointment note (NOTES:) and in the patient notes (Patient Notes:), and even if it's on a second line. It does NOT confuse BCBS or IDs like BC123 — only counts "BC" as a standalone word.

6. Automation

6.1 Windows auto-login (recommended, for reboots)

If the machine reboots by itself, Windows must stay logged in so the robot can handle windows. Configure auto-login (netplwiz → uncheck "Users must enter a user name and password", or AutoAdminLogon in registry).

Tradeoff: the machine starts without asking for Windows password.

6.2 Scheduled task at 6pm

In cmd (as admin):

schtasks /Create /TN "BCBOT Diario" /TR "C:\BCBOT\run_daily.bat" /SC DAILY /ST 18:00 /F

run_daily.bat launches the robot in AUTO mode (no dialogs) that runs everything and closes. The task is permanent: survives reboots and runs daily by itself.

Requirement: Windows session must be started (not on login screen) at 6pm. With auto-login + machine on, covered. If the machine is OFF at 6pm, that day is skipped — optionally, in Task Scheduler → the task → Settings → check "Run task as soon as possible after a scheduled start is missed" to recover on boot.

7. The 2 emails (daily output)

[ESCALATION] {CLINIC} — {date}

Patients to resolve manually, grouped by reason (couldn't verify / inactive / unsupported payer / retry). The team verifies manually and marks them with "-BC".

[PDF TO UPLOAD {CLINIC} {date}]

Verified OK, with coverage PDFs attached (Stedi logo covered) to upload to the patient's file in Dentrix.

Client-facing: in client text we never mention "Stedi": we use "BC ClearingHouse".

If SMTP fails, emails are saved to C:\BCBOT\out\ (email_1_escalacion.txt, email_2_pdfs_para_subir.txt) and PDFs in out/ to send manually.

8. Alerts

9. Maintenance and troubleshooting

Routine:
  • Refresh roster every 1–3 months (§4.1)
  • Add to crosswalk any new carriers escalated for "missing payer_id"
  • Add to unsupported list any payers Stedi rejects consistently
SymptomCause / Fix
Login fails instantlyMissing dentrix_pass.txt (or empty). Recreate it.
Doesn't open DentrixLogin window has different name ("Appointments, Open" vs "Office Manager, Open"): the robot accepts both. Verify credentials and Office.exe path.
ROUTESLIPS.pdf at 0 KBPrinted before rendering, or file was open in Edge (locked). The robot waits for PDF write to finish. Close Edge.
Saves old route slipBatch was still loading. The robot waits ≥90 s after Preview.
Emojis break the logFixed (ASCII prints + PYTHONIOENCODING=utf-8).
Payer changes between runsFixed: roster payer wins over name-based guess.
"missing member_id"Patient not in roster (new) → refresh roster, or load insurance in Dentrix. The route slip does NOT print the member ID.
"UNKNOWN"Payer responded but didn't determine coverage (sometimes payer ID from another state, e.g. BCBS). Review manually.
"unsupported payer"Stedi doesn't support that payer (e.g. Solstice). Verify in payer's portal.
Stedi HTTP 401API key needs Key prefix (already handled). Regenerate key if persists.
SMTP 535Regenerate Gmail App Password (16 chars, no spaces).

10. File inventory (C:\BCBOT)

Robot / automation

FileFunction
robot.ahkAutoHotkey robot (login + download + orchestration). TEST_DATE="" = next day.
run_daily.batLaunches the robot in AUTO mode (triggered by Task Scheduler).
run_robot.batRuns only the Python processing on ROUTESLIPS.pdf.
alert_login.py / alert_login.batAlert email if it can't open Dentrix.

Python pipeline

FileFunction
fase0_run.pyOrchestrator (parses, verifies, builds emails). CLINIC here.
fase0_parse_route_slip.py / fase0_parse_schedule.pyRoute slip parser.
fase0_parse_roster.pyParser of "Insurance Carrier List" → roster_cache.json.
fase0_build_270.pyJoin route slip + roster + crosswalk → 270 request (PROVIDER config here).
carrier_map.pyCarrier → payer ID crosswalk (roster + master), self-pay, unsupported.
stedi_client.pyStedi calls (eligibility + coverage PDF).
pdf_postprocess.pyCovers Stedi logo.
fase0_pdf.pyBackup PDF if Stedi's fails.
fase0_email.pyBuilds and sends the 2 emails.

Data

FileFunction
roster_cache.jsonParsed roster (Chart# → member ID + payer ID).
payer_map.csvMaster carrier → Stedi payer ID list.
carrier_to_payer_REVIEW.csvReviewed client crosswalk.

Credentials (§3.3) — 5 .txt files.
OutputC:\BCBOT\out\ (logs, emails as .txt, coverage PDFs).

11. New client onboarding checklist

#Step
1Remote access to the Dentrix machine
2Python + AutoHotkey v2 installed; python.exe path in the .bat
3C:\BCBOT with all files (§10)
45 credential files created
5PROVIDER (NPI/TIN/name) and CLINIC configured
6Roster generated (Insurance Carrier List, ALL) → roster_cache.json
7Payer crosswalk reviewed (carrier_to_payer_REVIEW.csv)
8Client's unsupported payer list
9Front-desk trained on the "-BC" convention
10Windows auto-login configured
116pm task scheduled (schtasks)
12Test run OK (both emails arrive, PDFs with real weight)