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.
- 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
- Windows machine at the office with Dentrix installed and running (ideally the front-desk one). Remote access (RustDesk / AnyDesk / RDP) to administer it.
- "Microsoft Print to PDF" available (comes with Windows 10/11).
- Stedi account (BC ClearingHouse) with eligibility API key.
- Gmail with 16-char App Password (to send emails).
- Provider data: NPI, TIN, legal name.
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
4Explorer → View → File name extensions = ON
To see real .txt/.ahk.
3.2 Folder and files
- Create
C:\BCBOT. - Copy all system files (see §10) to
C:\BCBOT. - In
run_robot.batandrun_daily.bat, set thepython.exepath for this client.
3.3 Credentials (text files, NEVER in code)
Create in C:\BCBOT (Notepad → Save As, type "All Files"):
| File | Content |
|---|---|
dentrix_user.txt | Dentrix username |
dentrix_pass.txt | Dentrix password |
stedi_key.txt | Stedi API key |
smtp_user.txt | Gmail email (e.g. hello@brandacare.com) |
smtp_pass.txt | Gmail App Password (16 chars, no spaces) |
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:
- Report Type: Dental
- Select Insurance Carrier: From
<ALL>To<ALL>← critical, to include ALL carriers - List Types: Standard List + Include Subscribers
- Leave unchecked: Include All Insured Patients, Provider IDs, Mailing Labels
- OK → goes to Batch Processor → open that item → Print to PDF → save as
roster_full.pdf
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).
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_map.csv— MASTER list (carrier → Stedi payer ID). Primary source; keep updated.carrier_to_payer_REVIEW.csv— crosswalk of the client's roster carriers, auto-mapped + manually reviewed (FINAL_PAYER_IDcolumn).
Payer resolution priority (implemented in fase0_build_270.py):
- Patient's REAL roster payer (if valid, not "SOURCE") — wins.
- Reviewed crosswalk by name (exact or spaceless: "Met life" = "MetLife").
- Match against master
payer_map.csv(token subset, safe). - 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.
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).
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.
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.
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
- If the robot can't open/log in to Dentrix →
[BCBOT ALERTA]email tohello@brandacare.com(check the machine remotely). - If the PDF comes out empty (0 KB) → alert email (day with no patients, or something got stuck).
9. Maintenance and troubleshooting
- 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
| Symptom | Cause / Fix |
|---|---|
| Login fails instantly | Missing dentrix_pass.txt (or empty). Recreate it. |
| Doesn't open Dentrix | Login window has different name ("Appointments, Open" vs "Office Manager, Open"): the robot accepts both. Verify credentials and Office.exe path. |
| ROUTESLIPS.pdf at 0 KB | Printed before rendering, or file was open in Edge (locked). The robot waits for PDF write to finish. Close Edge. |
| Saves old route slip | Batch was still loading. The robot waits ≥90 s after Preview. |
| Emojis break the log | Fixed (ASCII prints + PYTHONIOENCODING=utf-8). |
| Payer changes between runs | Fixed: 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 401 | API key needs Key prefix (already handled). Regenerate key if persists. |
| SMTP 535 | Regenerate Gmail App Password (16 chars, no spaces). |
10. File inventory (C:\BCBOT)
Robot / automation
| File | Function |
|---|---|
robot.ahk | AutoHotkey robot (login + download + orchestration). TEST_DATE="" = next day. |
run_daily.bat | Launches the robot in AUTO mode (triggered by Task Scheduler). |
run_robot.bat | Runs only the Python processing on ROUTESLIPS.pdf. |
alert_login.py / alert_login.bat | Alert email if it can't open Dentrix. |
Python pipeline
| File | Function |
|---|---|
fase0_run.py | Orchestrator (parses, verifies, builds emails). CLINIC here. |
fase0_parse_route_slip.py / fase0_parse_schedule.py | Route slip parser. |
fase0_parse_roster.py | Parser of "Insurance Carrier List" → roster_cache.json. |
fase0_build_270.py | Join route slip + roster + crosswalk → 270 request (PROVIDER config here). |
carrier_map.py | Carrier → payer ID crosswalk (roster + master), self-pay, unsupported. |
stedi_client.py | Stedi calls (eligibility + coverage PDF). |
pdf_postprocess.py | Covers Stedi logo. |
fase0_pdf.py | Backup PDF if Stedi's fails. |
fase0_email.py | Builds and sends the 2 emails. |
Data
| File | Function |
|---|---|
roster_cache.json | Parsed roster (Chart# → member ID + payer ID). |
payer_map.csv | Master carrier → Stedi payer ID list. |
carrier_to_payer_REVIEW.csv | Reviewed client crosswalk. |
Credentials (§3.3) — 5 .txt files.
Output — C:\BCBOT\out\ (logs, emails as .txt, coverage PDFs).
11. New client onboarding checklist
| # | Step |
|---|---|
| 1 | Remote access to the Dentrix machine |
| 2 | Python + AutoHotkey v2 installed; python.exe path in the .bat |
| 3 | C:\BCBOT with all files (§10) |
| 4 | 5 credential files created |
| 5 | PROVIDER (NPI/TIN/name) and CLINIC configured |
| 6 | Roster generated (Insurance Carrier List, ALL) → roster_cache.json |
| 7 | Payer crosswalk reviewed (carrier_to_payer_REVIEW.csv) |
| 8 | Client's unsupported payer list |
| 9 | Front-desk trained on the "-BC" convention |
| 10 | Windows auto-login configured |
| 11 | 6pm task scheduled (schtasks) |
| 12 | Test run OK (both emails arrive, PDFs with real weight) |
