← Back to setup

Bridging the data gap between Curant and Healthvana

HV Curalytics extracts pharmacy data from Curant's reporting portal and delivers it to Healthvana, unlocking patient visibility, accurate renewals, and shipment tracking.

Curant Health
Specialty pharmacy
Curalytics
Reporting portal
HV Curalytics
Bookmarklet + panel
CSV
Joined export
Healthvana
Production system

The Problem#

Healthvana patients get their medications through Curant Health, a specialty pharmacy. Curant verifies prescriptions, packages medications, and ships them.

Curant's reporting portal, Curalytics, holds all of this data: who received what, when shipments went out, tracking numbers, package contents. Healthvana needs every bit of it.

There is no API between the two systems. The data sits behind a login screen. Until now, getting it out meant copy-pasting or running fragile console scripts with no way to verify the output.

What This Unlocks#

Running this 2–3 times a day enables four things that were impossible before:

1

Find patients stuck without medication

100+ patients have valid prescriptions but haven't been sent medication. They're waiting and we didn't know.

2

Know when patients actually receive meds

See when patients actually received meds, not when the Rx was written. The gap can be weeks.

3

Send accurate renewal reminders

Time renewals to actual receipt, not Rx date. No more early or late reminders.

4

Share shipment tracking with patients

Patients and CS can see where their medication is: courier, tracking number, delivery date.

See It Run#

Click Download Data to watch the pipeline run against simulated data.

Interactive demo

HV Curalyticsv1.0
Last 7 days

2/4/2026 → 2/11/2026

Healthvana · rx.hvna.dev

How It Works#

Curalytics splits its data across three reports:

  • Dispensed Scripts (shipments, recipients, tracking)
  • Medication Summaries (prescriptions, drug names, providers)
  • Package Contents (what was in each box, with quantities)

The tool pulls all three, deduplicates, joins by prescription number and shipment ID, and produces one CSV.

Dispensed Scripts
~308
rows
Med Summaries
~319
rows
Package Contents
~352
items
↓ ↓ ↓
Deduplicate
Remove ~52 duplicate summary rows
Join
Link by RxNumber + ShipmentId
Validate schema
Compare fields against baseline
CSV Export
~268
joined rows, ready for import

These numbers are from a real run. The whole process takes about 10 seconds.

The Iframe Bridge#

Clicking the bookmarklet on Curalytics loads bootstrap.js from our server. It does two things:

  1. Creates a floating panel (an iframe, a page within a page) hosted at rx.hvna.dev/client.
  2. Sets up a message bridge between the Curalytics page and the panel using postMessage.

The panel handles UI and processing, but can't call Curalytics APIs directly because it's on a different domain. So it asks the parent page (Curalytics, where you're logged in) to fetch on its behalf. The parent makes the authenticated call and sends the result back.

The panel handles logic. The parent handles authentication. Both validate message origins.

Curalytics Page
your logged-in session
HV Panel
iframe from rx.hvna.dev
Panel loaded, requesting handshake
hello
hello-ack
Connection established
"Fetch dispensed scripts for me"
api-request
api-response
Fetched with cookies, returning data
"Now fetch medication summaries"
api-request
api-response
Summary data returned
"Fetch package contents (x5)"
api-request
api-response
All package data returned
← panel sends to page  ·  → page sends to panel

The bookmarklet never changes. All code lives on our server. Deploy to Vercel, and every user gets the update on their next click.

The Data Pipeline#

The panel runs a five-phase pipeline:

Phase 1

Fetch dispensed scripts

POST with date range. Returns shipments: patient info, ship dates, courier, tracking.

Phase 2

Fetch medication summaries

POST for the same range. Returns prescriptions: drug names, quantities, providers.

Phase 3

Fetch package contents

One GET per shipment for box contents. Runs 12 at a time, cached locally. Repeat runs skip known shipments.

Phase 4

Deduplicate & join

Remove duplicate summaries (same RxNumber + CompletedDate), then join: summaries → packages by Rx number, packages → shipments by shipment ID.

Phase 5

Validate & export

Compare API fields against baseline from the previous run. Warn if anything changed. Export as a 26-column CSV.

About 10 seconds for a week of data. Progress is shown in real time.

Schema Validation#

Curalytics' API has no documentation. If Curant renames a field or removes a column, the data just changes.

The tool records field names from each response as a baseline and compares on the next run:

  • !Missing fields: a field we depend on disappeared. CSV will have blank columns.
  • +New fields: the API is returning data we haven't seen. We might want it in the export.

The first person to hit a changed API sees it immediately instead of days later when imports look wrong.

Reliability & Debug Mode#

This feeds into production. The test suite has three layers:

  1. Pipeline integration tests: 14 tests run the full pipeline against mock data and verify CSV output matches hand-calculated values. Catches wrong joins, lost records, broken dedup.
  2. Debug harness at /debug: simulates Curalytics with mock data. Loads the real bootstrap.js, creates the real iframe, intercepts API calls. Six scenarios: Normal, Empty, Error, Expired Session, Schema Change, Partial Failure.
  3. End-to-end browser tests: 13 Playwright tests drive the debug harness from script injection through UI rendering.

71 tests total. Unit tests run in under a second; the full suite in about 10.

Ready to use it?

Setup takes 30 seconds.

Go to setup instructions →