Skip to content

This Week in Dr. Ray Automation: Heidi Sync, Session API, and 9 New Services

A massive week of launches: bidirectional Heidi-to-MN sync, a new Session API with audit capabilities, Clarius DynamoDB migration, and 9 new microservices.

Matt Dennis

This week was arguably the most productive of the quarter. We shipped 12 new or significantly updated services across the Dr. Ray automation stack — ranging from a brand-new bidirectional sync between Heidi Health and Maternity Neighborhood, to an entirely new Session API with audit capabilities, to infrastructure migrations that set us up for the next phase of growth.


Heidi → Maternity Neighborhood Sync

The marquee launch this week: mn-heidi-sync, a CLI tool that syncs Heidi Health clinical sessions directly into Maternity Neighborhood (MN) as encounters.


Why this matters

Dr. Ray’s practice uses Heidi Health for clinical note-taking during patient visits. Maternity Neighborhood is the practice management system that handles billing, scheduling, and patient records. Until now, these two systems lived in parallel — staff were manually re-entering session data into MN after each visit.


What mn-heidi-sync does:

  • Pulls sessions from Heidi via their API
  • Deduplicates against existing MN encounters (by date + patient name fuzzy matching)
  • Creates new encounters in MN with the consult notes, timestamps, and metadata
  • Runs as a CLI with --cron mode for automated nightly runs
  • Includes a QA verification tool (qa.js + verify.sh) for data integrity checks

# Run manually
node sync.js

# Cron mode (webhook-only, no file saving)
node sync.js --cron

# Dry run
node sync.js --dry-run

Duplicate guarding

The trickiest part of any sync is avoiding duplicates. We implemented two layers:

  1. Local guard: Check DynamoDB for progressNoteContains + listProgressNotes before creating
  2. MN-side guard: Query MN’s API for existing encounters on the same date before addEncounter

Both checks use fuzzy name matching (Levenshtein distance + tokenization) to handle variations like “Julia Ray” vs “Dr. Julia Ray” vs “Ray, Julia”.


Reverse-engineering the MN API

Maternity Neighborhood doesn’t have a public API. So we built one.


The approach:

We fired up Chrome, opened DevTools, and used the Chrome DevTools MCP to capture every network request the browser made when creating an encounter. This gave us the exact form structure, headers, and endpoints needed to replicate the browser’s behavior programmatically.


What we found:

  • Login flow: Requires XSRF token obtained from /sterling/session/ (returns 401, but sets the cookie). Login POST to /sterling/session/login/ with JSON body + X-XSRF-TOKEN header + Origin/Referer headers.
  • Add encounter: POST to /episodes/{episodeId}/progress_notes/add/ with multipart form data
  • Form structure: A massive form — 40+ form fields covering everything from physical exam (heent, chest, abdomen, extremities, skin) to antenatal data (menstrual history, problems list, genetic screens, conception details)

// Form fields captured from browser
{
  'antenatal-currpreg-problems_values': { ... },
  'antenatal-currpreg-tests_screens_values': { ... },
  'antenatal-currpreg-conception_values': { ... },
  'physical-genitourinary_internal_values': { ... },
  'physical-abdominal_back_values': { ... },
  'csrf': '61724dfa936f5970074f7d041fd500edbfc2d88a',
  'service_date': '02/19/2026',
  // ... 30+ more fields
}

The draft problem:

Early on, we hit a wall: encounters created via the API appeared as drafts in the MN UI, despite getting HTTP 200 responses. We spent significant time reverse-engineering why:

  • Hypothesis 1: Empty autosave.id field — tested, didn’t fix it
  • Hypothesis 2: Missing sec-fetch-* headers — tested, didn’t fix it
  • Hypothesis 3: Field order differences — still investigating

The root cause is still being debugged, but the sync still works — drafts can be finalized manually, and the data makes it into MN.


No JSON API for progress notes:

Here’s the kicker: MN has no JSON endpoint for listing progress notes. We had to scrape the HTML list page (GET .../episodes/{id}/progress_notes/) and search the response body for session IDs to detect duplicates.


// Duplicate guard: fetch HTML, search for session ID
async progressNoteContains(episodeId, sessionId) {
  const html = await this.ehrFetch(`/episodes/${episodeId}/progress_notes/`);
  return html.includes(`Session ID: ${sessionId}`);
}

This is the ugly side of reverse-engineering: sometimes you resort to regex-matching HTML because that’s all the API gives you.


Heidi Session API

We launched a new Express API that wraps the Heidi Sessions data with additional capabilities:


Audit mode

The Session API now exposes an auditSessions endpoint that compares Heidi sessions against Google Calendar to flag suspicious data:

  • Bad Name: Patient name doesn’t match any calendar event (potential typo or test data)
  • No Calendar Match: Session exists but no corresponding calendar entry found

This uses fuzzy matching across both the session name and calendar event name.


Batch name updates

If a patient updates their name (marriage, legal change, etc.), the API supports updatePatientName — a batch update that changes the patient name across all their sessions and triggers re-embedding for semantic search.


Timezone-aware dates

Heidi timestamps come without timezone info. The API now normalizes all dates using America/Los_Angeles (Dr. Ray’s timezone) and stores a localDate field for reliable calendar matching.


Heidi Explorer UI

A new React app built this week for exploring and auditing Heidi sessions:


  • Session Review mode: Compare session discrepancies vs Google Calendar
  • AuditSessionCard: Visual flags for sessions with bad names or no calendar match
  • Inline patient name editing: Update a patient’s name across all sessions directly from the UI
  • Fixed StrictMode double-API-call bug: Reduced initial load from 6 API calls to 3

Clarius Scraper: DynamoDB Migration

The Clarius ultrasound scraper got a significant backend overhaul:


What’s new

  • DynamoDB migration: Moved from exam-ids.json file storage to the ClariusExams table in DynamoDB
  • Catch-up pagination: Scrapes up to 3 pages on each run, stops when hitting an already-processed exam
  • Reserved keyword fix: Used ExpressionAttributeNames for the processed field (DynamoDB reserved word)
  • Bundled upload-fax.sh: The script now lives in the repo so it works on VPS without depending on the parent drray directory

# Resync + PDF generation
./resync.sh

# Dry run
./resync.sh --dry-run

New Microservices

New services launched or initialized this week:


ServiceDescription

| heidi-session-api | Express API for Heidi sessions with audit/calendar matching | | search-calendar | Calendar search service |


Stripe Installment Updates

The pricing calculator and Stripe handler got love too:

  • Custom installment option: Patients can now select custom installment plans
  • Finalize 15 days before due date: Installments now finalize 15 days before the selected payment date (was previously same-day)
  • Test mode badge: Visual indicator when running against Stripe staging
  • Generic go-back navigation: Improved UX flow

Weekly Commit Digest

One more thing — I wrote a digest script that generates a weekly commit summary across all 70 subdirectory repositories in the drray monorepo-coordination repo:


# Daily digest (last 24 hours)
./digest

# Weekly digest (last 7 days)
./digest weekly
./digest w

This week: 54 commits across 23 repositories. The full digest is available in the drray repo.