The goal
BrainStim Centers was losing ~40 admin hours/week scoring PDF questionnaires, copy-pasting Rise4 bookings into Monday.com + CharmHealth, and reconciling eClinical referrals by hand. Automate every repeatable step and keep it running 24/7.
The solution
A three-module production suite on a managed VPS: (1) a SharePoint/Teams PDF pipeline parsing 60+ questionnaire templates with auto-scoring; (2) an Outlook mail monitor that syncs Rise4 bookings to Monday.com (update-or-create against the current month's group) and creates CharmHealth patients via Playwright; (3) an eClinical reconciler implementing the clinic's branching rules (Hold On CNI, Sonja Review, Case Details Needed, Scheduled). All running as systemd services with structured logs.
Highlights
- ~40 admin hours/week saved across three workflows
- 24/7 systemd-managed uptime on a VPS
- 60+ medical questionnaires parsed via one template-driven pipeline
- Outlook OTP + Playwright JWT capture for always-on Rise4 auth
The challenge
Dozens of medical questionnaire templates in English and Spanish, each with a unique layout — a per-template parser wouldn't scale.
One pdf_pipeline.py with a templates.json registry declaring each template's fingerprint + field coordinates + scoring rules. Adding a template is a config edit, not a code change.
Rise4's API requires a JWT token-id that lives only in browser localStorage and rotates frequently.
Playwright login flow that fetches the OTP from Outlook via Microsoft Graph, navigates to the contacts smart-list, intercepts the contacts/search/2 request to capture the token into .env, and a watchdog that F5s every 30 min and re-logs-in on demand.
Every booking has to land cleanly in Monday (update or create) and in CharmHealth (create if absent) without duplicates, even when emails arrive out of order.
build_monday_existing_lookup() indexes the current month's group by email+phone; find_existing_monday_item() decides update-vs-create; CharmHealth checks for the patient before the Playwright create flow.
The owner's spec is dense branching rules (Hold On CNI / Sonja Review / Case Details Needed / Scheduled). Mis-interpreting one branch would silently corrupt leads.
Each branch implemented as a named scenario function with a [sync] -> <scenario> log line and a log-then-act pattern so every state transition is reviewable; the spec notes live in the repo as ground truth.
The clinic's admin staff cannot SSH or run Python directly.
run_teams_process.bat is a double-click launcher for the PDF module; the Rise4/Monday module runs as a systemd service on the VPS; all runs are idempotent via processed_state.json.
What was delivered
- PDF Parser module: pdf_pipeline.py, templates.json, teams_process.py, run_teams_process.bat, staff README
- Rise4 → Monday sync (rise4_mail_monitor, sync_rise4_to_monday, rise_monday_sync/runner.py)
- Rise4 auth (rise_login_playwright.py) with Outlook Graph OTP
- CharmHealth integration (charm_add_patient_from_json.py, charmhealth_questionnaire.py)
- Extract Questionnaires Data module (fetch_patients.py)
- eClinical reconciler with named scenario branches
- VPS systemd setup, .env templates, AUTOMATION_SETUP.md, AGENTS.md
- Business-rule spec notes files as repo ground truth
Results
~40h/wk
Admin time saved
24/7
Uptime
60+
Questionnaire templates
Monday + CharmHealth + eClinical
Sync targets
What it taught me
- Template-driven parsing (config + scoring) is the only scalable way to tackle a library of questionnaires
- Playwright login + Outlook Graph OTP + network-response capture beats calling SPA auth APIs directly
- Turning each business branch into a named scenario function with [sync] -> <name> logs makes post-mortem trivial
