Skip to content

May 2026: Build Log — Concierge OB Automation

A month of shipping for a solo concierge OB practice: medical records forms, fax intelligence, control center outreach, and a phantom-records audit.

Matt Dennis

The stack for a solo concierge OB practice is deceptively large. It starts with “we need to track faxes” and ends with a multi-Lambda document pipeline, a Playwright-driven Maternity Neighborhood sync agent, an OpenPhone outreach intelligence layer, and a Heidi clinical AI integration. May was a heavy month. Here’s what shipped.


Medical Records Request — new production app

The old process for releasing medical records: a phone call, a faxed PDF, manual follow-up. Not scalable, not auditable.


We shipped a patient-facing Authorization for Release of Health Information form at medical-records-request (Amplify + Lambda). Five-step mobile wizard: patient info, receiving provider, scope of release, review, signature. Patients can draw or type their signature. On submit, the backend generates a PDF, stores it in S3, writes an audit trail to DynamoDB (MedicalRecordsRequests table — sourceIp, userAgent, apiRequestId, Pacific dateSigned), and returns a 7-day presigned download URL.


The form supports URL pre-fill (?name=&dob=&phone=) for staff-generated links and a ?sample=true QA mode that pre-populates dummy data. Post-launch polish: native date picker for expiration, improved DOB/phone field UX on mobile, a “what to do next” confirmation screen.


The Lambda (generate-medical-records-request, POST https://d5qqxzxo54.execute-api.us-east-1.amazonaws.com/generate) is the only backend — no separate auth layer. The form is intentionally limited: no representative/relationship fields, no in-app fax, no “Start Over” after submit. MVP ships; the rest waits for a real requirement.


Fax pipeline: sender-aware routing

The fax pipeline previously relied entirely on Bedrock Nova to classify incoming faxes. That works for most documents, but it creates unnecessary inference cost and latency for faxes from known senders.


retrieveFax now has a resolveFaxType() function that checks a hardcoded sender map first — Bedrock only fills in Uncategorized. Known routing rules: the shared MFM aggregator number routes to MFM Result, the Sansum Diabetes fax line always maps to Clinical Note regardless of what Bedrock thinks. finalFaxType threads through the DynamoDB record, Google Drive folder path, S3 key rename, and Pumble notification.


docs-service also got MFM classification updates: MFM Report → MFM Result across all prompts and enums, two new MFM providers added to the classification list, and Sansum Diabetes explicitly excluded from MFM detection.


Heidi → MN sync: phantom records audit

mn-heidi-sync pushes Heidi clinical AI session notes into Maternity Neighborhood as encounters. It had been running in production for months, and we’d never done a full audit of its accuracy.


We built qa-audit.js — a read-only CLI that scrapes the current MN encounter list for each active patient and cross-references it against the HeidiSessions DynamoDB table. The audit found 19 “phantoms”: encounters present in MN that had no corresponding Heidi session, or sessions in Heidi that hadn’t synced.


Investigating each one revealed a mix: true gaps (sessions that had failed silently), false positives (encounters created manually before the sync existed), and a date collision bug where two sessions on the same date triggered a silent skip instead of a review flag. That last one got fixed — duplicate sessions now set mnReviewNeeded in DynamoDB instead of disappearing. A new DATE_GUARD_FIRED audit category tracks the pattern going forward.


The sync also got an HTML stripping pass: Heidi occasionally emits consult notes as raw HTML (with <p> and <strong> tags intact), which was writing literal markup into MN encounter bodies. Plain-text normalization now happens at both ingest (heidi-scraper) and write time (mn-heidi-sync).


Control Center: Website Leads + outreach intelligence

The Control Center (control-center, Amplify) is the practice’s internal ops dashboard. This month it got its most substantial feature yet: a Website Leads tab.


The tab pulls recent leads from Airtable and enriches each one with computed outreach status from OpenPhone. The status logic: if a staff member sent a message manually, it’s replied; if the automation fired a template, it’s auto_sent; if the patient responded after that, it’s resolved. The 30-day SMS lookback window is what makes the logic tractable — older threads don’t pollute the status calculation.


OpenPhone contact matching needed work to make this reliable. The heidi-patient-matcher now does exact last-name + fuzzy first-name matching, which handles nickname variants (Jessie/Jessica, Becca/Rebecca) that would otherwise fail a strict equality check.


The full Control Center UI also got a Dr. Ray brand pass — color tokens, typography, spacing — applied consistently across all tabs. Deep-linkable tabs via URL hash. Nav overflow fix (the More dropdown was clipped by overflow: hidden on the parent). These sound small but they matter when someone is using a tool 20 times a day.


Mobile phlebotomy: the PRD

Not shipped yet, but worth documenting: the practice uses a contracted mobile phlebotomist for NIPT, prenatal labs, and glucose screening at patients’ homes. The current process is entirely manual — one-off emails, no confirmation loop, no status tracking.


We wrote a full PRD for a structured request system: Airtable as the system of record, a request creation flow for staff, and a phlebotomist-facing acknowledgment mechanism. The glucose screening case is what makes this urgent — patients pre-drink a glucose beverage exactly one hour before the draw, so a missed or uncoordinated appointment isn’t just inconvenient, it’s a missed screening window.


The MVP intentionally excludes a phlebotomist-facing app and automated email generation. Both are post-MVP. First requirement is visibility: does the practice know the status of every pending draw? Right now, the answer is no.


Infrastructure

openphoneWebhook had a silent bug: OpenPhone sends gzip-compressed webhook payloads, and the Lambda was receiving garbled JSON on some events. One-line fix, but worth noting — the bug had been live since the webhook was first deployed.


mn-create-client-api launched as a new VPS service for programmatically creating Maternity Neighborhood clients from outside the Playwright automation. Backed by the same MN session management as the sync agent.


VPS documentation got a Debian audit pass: ss and journalctl query patterns replaced the old netstat and auth.log references that had become stale after the February server migration.



42 commits across 9 repos in the second half of the month alone. One new patient-facing app in production. The practice is about to have a pretty good answer to “what happened to that lead we got on Tuesday.”