Launch a world-class patient registry in months, not years — powered by a complete Personal Health Record

For Patient Advocacy Organizations

Your Registry
at Mission Speed

Patient advocacy organizations face an impossible tradeoff. Traditional registries take years to build, require millions in funding, and rely on manual data entry that patients quickly abandon. HealthKey lets your organization launch a full-featured, scalable patient registry in months — powered by a complete Personal Health Record that patients actually use.

For Doctors & Clinical Leaders → For Developers & Builders →
RESEARCH ANALYST Survival Analysis 100 0 Treatment A Control Treatment Pathways Dx 1st Line Outcome Cohort Explorer 2,847 Breast Lung Myeloma Lymphoma AML 2,847 patients · 5 cancer types · 142 sites HEALTHKEY PATIENT My Health Record Imported Verified 142 results Review Synced 80% Trial Matches 3 96% 89% ? 72% Standard of Care Current Next Alt
Built on
OMOP CDM HL7 FHIR & PHR-SFM HIPAA Compliant SOC 2 & GDPR-Ready

Powered by a Complete Personal Health Record

Not just a registry — a living record that patients actually use

Traditional registries capture a snapshot. HealthKey's registry is built on something no traditional registry has: a complete, longitudinal Personal Health Record for every patient — automatically assembled from every provider they've ever seen.

🏥

Central Health Record

Every record from every provider — hospitals, labs, specialists, and wearables — automatically assembled into one complete, reconciled health record. No manual data entry. No missing history.

🔬

Lab Tracking

Lab results, genomic assays, and biomarker profiles flow in automatically and are tracked longitudinally — giving patients and their care teams a complete picture of how their disease is evolving.

Clinical Trial Matching

Every patient's record is continuously screened against open clinical trials. Eligibility is pre-computed, per-criterion, with transparent verdicts — no manual forms, no missed opportunities.

📋

Treatment Options

Standard-of-care guidance mapped to each patient's specific diagnosis, stage, and biomarker profile — so patients and clinicians can see the full landscape of options, not just what one provider recommends.

🤝

Clinician Sharing

Patients share their complete record with any provider, instantly. A secure, permissioned link with full audit trail — no fax machines, no CD-ROMs, no re-requesting records.

🔒

Patient-Owned by Design

Patient data is never sold. Each member decides who sees what, for how long, and can revoke access at any time. The patient owns their data — that's the architectural rule everything is built on.

How HealthKey Compares

Not all registries are created equal

Traditional registries and survey-based tools served their purpose. HealthKey is a fundamentally different approach — built on the patient's own complete health record.

Traditional Registries REDCap / Survey-Based HealthKey
Setup Time 2-5 years Months (limited scope) 3-6 months
Setup Cost $2M-10M+ Low (but manual) Fraction of traditional
Data Source Manual abstraction Patient surveys Direct from EHRs + patient
Data Completeness Partial — one site at a time Minimal — self-reported Complete longitudinal record
Research-Ready Requires ETL Requires ETL OMOP CDM from day one
Patients Own Their Data By design
Trial Matching Automatic, per-criterion
Patient Support Tools Record, labs, treatment options

Our Mission

"Patients deserve to own the truth of their full health picture — not as a PDF export, but as a living, working record that acts on their behalf."

— HealthKey founding principle

Built for Patients, Ready for Research

Standardized data from day one

Every record in a HealthKey registry is automatically normalized to OMOP CDM — the same standard used by OHDSI's global research network. Your data is research-ready the moment a patient enrolls, not after months of manual ETL.

Built-in analytics let researchers explore cohorts, visualize outcomes, and test hypotheses without exporting data or standing up a separate warehouse.

Implementation Options

Deployed your way, on your timeline

Open Source Self-Host

Deploy PHRAME on your own infrastructure. Full control, full customization, no vendor lock-in.

HealthKey Hosted

We deploy and manage the registry for you — HIPAA-compliant cloud, implementation support, and ongoing operations.

Implementation Services

Configuration, data migration, training, and go-live support. 3-6 months from kickoff to launch.

Built in the Open

The infrastructure stack is open source

PHR infrastructure shouldn't be a black box. The patient data schema, the trial matching engine, and the data pipeline are all published under permissive licenses on GitHub — so your team can audit, extend, and trust what you're building on.

Your clinical team can audit every matching rule. Your members can verify exactly how their record is structured and how every trial verdict was reached. Your technical team can inspect, extend, and customize every layer.

Explore our projects →
  • promop

    Patient Record OMOP

    A superset of OMOP CDM v5.4 with oncology extensions and a comprehensive PatientRecord — the structured patient model behind every HealthKey record.

  • exact

    Precision Trial Matching

    A stateless matching engine that returns per-criterion verdicts — passed, failed, or indeterminate — with the exact field value and threshold behind each decision.

  • prism

    Analytics

    The analytics layer over PRomop — exposing aggregate outcomes, cohort comparisons, and population-level insight to researchers and health systems, consented and privacy-preserving.

For Patient Advocacy Organizations

A Registry That's Ready When You Are

Comprehensive. Flexible. Scalable. Stand one up in months, not years — with data quality traditional registries can't match. HealthKey gives your foundation a production-ready patient registry powered by complete Personal Health Records, so your research starts sooner and your members get more.

Talk to Our Team See the Product →

The Impossible Tradeoff

Why most registries never get off the ground

Patient advocacy organizations have been stuck choosing between bad options. HealthKey changes the equation.

🕑

Traditional registries take years

Custom development, site-by-site integration, IRB approvals at every institution. Most foundations never finish — and the ones that do spend $2M–$10M+ before a single patient enrolls.

📋

Survey tools capture fragments

REDCap and survey-based tools are fast to stand up but rely on patients manually entering data. Engagement drops off quickly, and the data you collect is self-reported, incomplete, and not research-ready.

🔒

EHR data stays locked away

The richest clinical data sits in EHR systems your organization can't access. But your members can — patients have the legal right to aggregate their own records from every provider. HealthKey turns that right into a working product.

A Different Kind of Registry

Built on a complete Personal Health Record

HealthKey's registry isn't powered by surveys or manual chart abstraction. Every patient gets a full Personal Health Record that automatically pulls together data from every source — giving your registry depth and completeness that traditional approaches can't match.

The PHR is the engine. It aggregates, normalizes, and structures the data. The registry is what your organization and your researchers see — OMOP-standardized, analytics-ready, continuously updated.

PHR Patient Record
🏥 EHR Data
🧬 Genetics
📈 Wearables
📄 PDFs & Records
🏠 SDOH
📝 PROs

What Your Members Get

A health record that works for them — not just for research

Patients join your registry because it gives them something valuable — a complete health record they can actually use. That's why they stay engaged, and why your data is better.

🏥

Complete Health Record

Every record from every provider — hospitals, labs, specialists — automatically assembled into one longitudinal record. Members never have to gather their own records again.

🔬

Lab & Biomarker Tracking

Lab results, genomic assays, and biomarker profiles tracked longitudinally. Patients and their care teams can see how their disease is evolving over time.

Clinical Trial Matching

Every patient's record is continuously screened against open trials. Eligibility is pre-computed, per-criterion, with transparent verdicts. No manual forms, no missed opportunities.

📋

Treatment Options

Standard-of-care guidance mapped to each patient's specific diagnosis, stage, and biomarker profile — so patients see the full landscape of options, not just what one provider recommends.

What Your Research Team Gets

Research-ready data from the moment a patient enrolls

Every record is automatically normalized to OMOP CDM — the same standard used by the OHDSI global research network. No manual ETL. No waiting months for clean data.

Built-in analytics let your research team explore cohorts, visualize outcomes, and test hypotheses directly — without exporting data or standing up a separate warehouse.

✓ OMOP CDM Standardized

Every patient record mapped to OMOP — conditions, procedures, drugs, measurements, genomics.

✓ Longitudinal by Default

Records grow over time as patients continue receiving care. Your registry never goes stale.

✓ Built-in Analytics

Cohort exploration, outcome visualization, and hypothesis testing — ready from day one.

✓ Consented & Privacy-Preserving

Every data point is patient-consented. Researchers access aggregate analytics, never raw PHI.

Why Foundations Choose HealthKey

Differentiate your foundation with capabilities no one else can offer

🚀

Launch in Months, Not Years

Go from kickoff to live registry in 3–6 months. No multi-year development cycles, no custom integrations at every hospital site.

🎯

Your Brand, Your Mission

The registry is deployed under your organization's brand, reflecting your mission. Members see your foundation, not a technology vendor.

💪

Drive Member Engagement

Members don't just contribute data — they get a health record, trial matching, and treatment guidance. That's why they sign up and stay active.

🔍

Accelerate Research

Research-ready OMOP data from the moment a patient enrolls. Enable studies, attract grants, and produce publications that advance your disease area.

👥

Connect Patients to Trials

Automatic, continuous trial matching means your members are the first to know about relevant studies. No more missed opportunities — for patients or for sponsors.

🔓

Open Source, No Lock-In

PHRAME is published under permissive licenses. Your team can audit, extend, and customize every layer. Self-host or let us manage it — the choice is yours.

Ready to launch your registry?

Let's talk about what your foundation needs and how quickly we can get you there.

Talk to Our Team

PHRAME Platform

Comprehensive Clinical Record Platform for Your Members

Every provider visit, lab result, imaging study, genomic assay, and wearable reading — unified into one longitudinal record your members own and your clinical team can act on. Built on OMOP and HL7 FHIR, the world's most rigorous clinical data standards.

OMOP CDM HL7 FHIR Genomics Ready Trial Matching Integrate Wearables Clinician Sharing

Core capabilities

Everything a PHR platform
needs, built in

🔗

Universal Data Import

Records arrive from any EHR, hospital portal, or lab system — automatically translated into a clean, standardized format using the OMOP Common Data Model. Multiple providers, one coherent longitudinal record. No manual data entry, no paper faxes, no missing history.

🩺

Conflict Resolution

When two providers disagree on a diagnosis, medication, or measurement, our PResolution engine flags the conflict and presents it clearly. You decide what's correct — with supporting evidence from each source. No more silent data errors.

🧬

Oncology & Genomics

We go beyond standard OMOP with extensions for lines of therapy, TNM staging, genomic assay results, biomarker profiles (EGFR, BRCA1/2, PD-L1, HER2 and more), and ICD-O-3 histology. The most complete oncology record available.

Real-Time Trial Matching

Every member's record is continuously evaluated against open clinical trials. When eligibility changes — a new trial opens, a new lab result arrives — the match runs automatically. Your clinical team sees who qualifies and why, without reviewing a single chart.

📈

Outcome Tracking

Track your treatment responses with clinical-grade metrics: Overall Response Rate, Complete & Partial Response, Progression-Free Survival, Overall Survival, and adverse event monitoring — all derived directly from your record.

🤝

Clinician & Researcher Access

Share your full record — or a curated subset — with any clinician or research team. Set expiry dates, revoke access instantly, and see exactly who viewed what. Your permissions, your terms.

Positioning

Open-Source, Decision-Ready Oncology Data

Where longitudinal oncology data platforms sit on openness vs. decision-readiness

PRomop positioning chart showing longitudinal oncology data platforms plotted on openness vs. decision-readiness axes, with PRomop in the top-right quadrant

Who it's for

Built for the organizations that build PHRs

PHRAME serves every organization in the PHR ecosystem — from the builders and deployers, to the pharma and research partners who use the data downstream.

Foundations

Run member-owned PHRs at disease-community scale.

  • Complete longitudinal member records
  • Automatic conflict detection & resolution
  • Trial match & standard of care notifications
  • Real-world data collection for research partnerships
  • OMOP export for consortium analytics
  • Revenue-share ready via HealthKey data co-op

HIEs & Health Systems

Add structured longitudinal records to your exchange.

  • FHIR R4 ingestion from any connected EHR
  • OMOP normalization across all data sources
  • Standard of care recommendations at point of care
  • Patient-mediated consent and data sharing
  • Federated architecture — your data stays with you
  • Trial matching and clinical decision support built in

Clinics & Cancer Centers

Deploy best-in-class oncology records at the point of care.

  • Patient history reconciled across all providers
  • Trial eligibility pre-computed for every patient
  • Genomics and biomarker profiles integrated
  • Real-time outcome tracking
  • Standard of care recommendations
  • Secure, audited access log

Pharma & Research

Access consented, research-grade real-world data.

  • Pre-consented, OMOP-standardized cohorts
  • OHDSI Achilles analytics out of the box
  • Longitudinal RWE with patient-reported outcomes
  • Cohort discovery and phenotyping tools
  • Accelerated trial recruitment via matched patients
  • Trust-governed access — never a data sale

Under the hood

The PRomop patient record database

PRomop is the structured patient record at the center of PHRAME — an open-source OMOP CDM v5.4 extension designed from the ground up to be matchable, longitudinal, and interoperable.

What PRomop adds to OMOP

A complete patient record, built on OMOP — ready for clinical decision support

Standard OMOP tables store clinical facts — conditions, measurements, drug exposures — coded against SNOMED, LOINC, and RxNorm. But trial eligibility criteria are written in clinical language: "ECOG ≤ 2," "no more than two prior lines of therapy," "ANC ≥ 1.5 × 10⁹/L." PRomop bridges that gap.

The PatientRecord derives clinical judgments from the full OMOP picture — therapy line count, refractory status, measurable disease per IMWG, TP53 disruption, lymphocyte doubling time — computed once, in one place, with fully auditable rules. The derivation logic is open; your clinical informatics team can inspect and verify every field.

Records arrive from any connected EHR or lab system; PRomop normalizes them into OMOP and refreshes the PatientRecord automatically. Any dataset already in OMOP can be loaded without additional transformation — so existing institutional data is immediately usable.

✦ OMOP CDM v5.4 foundation
✦ Oncology & genomics extensions
✦ Comprehensive PatientRecord projection
✦ Auditable clinical derivations
✦ Accepts records from any connected EHR
✦ Compatible with existing OMOP datasets
WHITEPAPER PRomop: Building a Comprehensive Longitudinal Decision-Ready Patient Health Record Adam Blum · HealthKey, Inc. · 2026 Read the paper →

Under the hood

EXACT: precision trial matching

EXACT is the stateless matching engine that sits on top of PRomop — consuming a patient's PatientRecord and returning a per-trial, per-criterion verdict for every open trial in your catalog.

What makes EXACT different

Verdicts with explanations, not scores

Most matchers return a ranked list. EXACT returns a trace: for every trial, every eligibility criterion is shown as passed, failed, or indeterminate — with the exact patient field value and trial threshold behind each verdict. A patient navigator can see precisely which criterion knocked a trial out and whether it's a data quality issue, a stale lab, or a genuine exclusion.

The third verdict state — indeterminate — is the one that matters most operationally. It means "this patient would qualify if you also had a current ECOG score on file." That turns the matcher into a targeted data-collection prompt rather than a binary gate, and it's what distinguishes a tool clinicians actually use from one that gets demoed twice.

EXACT is stateless: nothing about the patient is persisted inside the matcher. It receives a PRomop PatientRecord, evaluates it against the trials catalog, and returns verdicts. It ships as a Django REST API for interactive use or a batch shell script for population-level runs — both backed by the same matching core.

✦ Per-criterion pass / fail / indeterminate verdict
✦ Plain-language explanation for every verdict
✦ Indeterminate flag surfaces missing data gaps
✦ Runs automatically as the record updates
✦ Patient data never stored inside the matcher
✦ Full population or individual patient screening
PROCEEDINGS Structuring Eligibility on Both Sides: The EXACT System for Precision Clinical Trial Matching Adam Blum · HealthKey, Inc. · Harvard DCI 2026 Download paper ↓

Under the hood

PRism: aggregate outcomes for research

Patient Record insights, Statistics & Measurement. PRism exposes the aggregate power of the patient population — consented, standardized, and privacy-preserving — to pharma, researchers, and health systems.

How PRism works

OMOP-native, federated, patient-consented

Because every PHRAME patient record is stored in OMOP CDM, the full OHDSI analytics ecosystem works out of the box — Achilles for data characterization, Atlas for cohort definition and phenotyping, and any existing OHDSI study package. Researchers who already work in OMOP have nothing new to learn.

The architecture is federated: each deploying organization keeps its own data enclave. Compute goes to the data; only aggregate results return. No patient record ever leaves the organization that holds it. This is not a policy promise — it is a structural guarantee, and it is what allows patient consent to be meaningful rather than nominal.

For pharma and research partners, the result is access to pre-consented, OMOP-standardized real-world cohorts with longitudinal treatment histories, patient-reported outcomes, and biomarker profiles — the kind of data that typically takes years and millions of dollars to assemble through traditional means.

PRISM
✦ Full OHDSI toolchain (Achilles, Atlas)
✦ Federated — data never leaves the enclave
✦ Patient-consented cohorts
✦ Longitudinal RWE with PROs
✦ Cohort discovery & phenotyping
✦ Trust-governed — never a data sale

Under the hood

How records arrive clean and complete

Three components handle the journey from raw incoming data to a clean, clinically complete record — each with a single, auditable responsibility.

PRofile

Profiles incoming FHIR bundles before they touch the record — identifying structure, gaps, and anomalies so problems surface at the boundary, not after import.

PRogram

Translates incoming data from any source into standardized OMOP format — so records from Memorial Hospital and City Lab are directly comparable. Mapping logic is fully auditable.

PResolution

Detects conflicting or erroneous values post-import and surfaces them for patient-led reconciliation — no silent data errors make it into the record.

Standards layer

Built on OMOP CDM — extended for oncology

The Observational Medical Outcomes Partnership Common Data Model is the global standard for clinical observational research. PHRAME stores every patient record in OMOP, making it compatible with the entire OHDSI ecosystem and analyzable with tools used by researchers worldwide.

We extend the standard schema with oncology and genomics fields — lines of therapy, TNM staging, biomarker profiles, genomic assay results — validated against OMOP conventions so records are both standard-compliant and clinically complete.

✦ FHIR R4 ingestion
✦ OMOP CDM v5.4 storage
✦ OHDSI vocabulary mapping
✦ Data Quality Dashboard
✦ Achilles analytics layer

Under the hood

SoC: standard of care recommendations

SoC is PHRAME's clinical decision support service — evaluating each patient's current treatment against authoritative guidelines and surfacing actionable recommendations at the point of care.

How SoC works

Guideline-driven, patient-specific, explainable

SoC compares a patient's PRomop record against structured representations of published treatment guidelines — NCCN and FDA-approved indications in the United States, and the appropriate national and regional equivalents in other geographies. For each patient, it determines which therapies are guideline-concordant given their diagnosis, stage, biomarker profile, and treatment history.

Like EXACT, SoC returns an explanation alongside every recommendation: this therapy is first-line per NCCN for ER+/HER2− metastatic breast cancer after prior CDK4/6 inhibitor exposure; this option is off-guideline because the patient has already received it; this pathway requires a test result not yet on file. Recommendations without reasons aren't useful in a clinical setting.

Because SoC reads directly from the PRomop PatientRecord, it stays current as the record is updated — a new lab result or a completed treatment line can immediately change which recommendations are active, without any manual re-entry.

✦ NCCN & FDA indications (US)
✦ International guideline equivalents
✦ Biomarker- and stage-aware matching
✦ Per-recommendation explanations
✦ Updates automatically with the record
✦ Surfaces gaps requiring additional tests

Hosted Services

We run it. You focus on your mission.

HealthKey offers a fully managed deployment of the PHRAME stack — so your team doesn't need to operate infrastructure. We handle provisioning, security hardening, upgrades, and uptime while your organization gets all the benefits of the open source platform without the operational overhead.

⚙️

Base fee per component

Each PHRAME component you deploy — PRomop, EXACT, SoC, PRism — carries an annual base fee covering hosting, maintenance, and support. Deploy only what you need.

👤

Per-patient annual fee

A per-patient fee scales with your membership. You pay for the patients you have, not a capacity ceiling you might never reach. Pricing is designed to work at community scale, not just enterprise.

📅

Annual billing

All fees are invoiced annually. No surprise overages, no usage metering mid-year. Predictable cost that makes budget planning straightforward for foundations, HIEs, and health systems alike.

Security & compliance

Built to meet the standards your partners require

🏥
HIPAA
Full compliance with US health data privacy and security requirements
🇪🇺
GDPR Ready
EU-compliant data handling with full subject access rights
🔐
SOC 2 Type II
Independently audited security, availability, and confidentiality controls
🔒 AES-256 encryption at rest 🔒 TLS 1.3 in transit 🏢 Data residency (US · EU · UK) 📋 Full immutable audit trail 🚫 Zero data selling, ever

Full security documentation and SOC 2 report available under NDA. Contact us →

Partner with us

Ready to build with PHRAME?

We work with foundations, HIEs, and clinics to deploy and customize the PHRAME stack. Get in touch to discuss your use case.

Talk to Our Team Explore Open Source

Our Story

We built HealthKey because the system failed someone we love

Health data is fragmented, inaccessible, and routinely fails the patients who need it most. Clinical trial matches go unfound. Conflicts go unresolved. Histories get lost between providers.

HealthKey exists to fix that — starting with giving patients the complete, accurate record they've always deserved.

Our Mission

Patients own the truth of their full health picture

We believe a health record should reconcile conflicting information, import from every provider, serve as a foundation for clinical decision-making, and — above all — belong to the patient. Not the hospital. Not the insurer. Not us.

Join us in fixing health data

We're hiring across engineering, clinical, and product. We're also always looking for research and clinical partners.

Get in Touch View Open Roles

The Team

Clinical expertise meets engineering depth

We're a team of entrepreneurs, engineers, clinicians, and data scientists who've spent careers working at the intersection of health and technology.

Paul Ahlstrom

Paul Ahlstrom

CEO

Innovator, entrepreneur, author, and venture capitalist with more than 30 years operating on both sides of the table. Paul has co-founded investment funds across the Americas — including Alta Ventures Mexico, Alta Growth Capital, and vSpring Capital — raising over $1.4 billion and backing 150+ startups, among them Angel Studios, Ancestry.com, and HealthTree. After his wife Jenny was diagnosed with multiple myeloma, they co-founded HealthTree Foundation in 2012, applying entrepreneurial discipline to the problem of fragmented cancer data. He is the co-author of Nail It Then Scale It and creator of the Big Idea Canvas, both widely used in startup programs worldwide.

Adam Blum

Adam Blum

CTO

AI tech entrepreneur and author of Neural Networks in C++ (Wiley, 1992). Adam built multiple successful startups — including Rhomobile (acquired by Motorola), OpenEd (acquired by ACT), and Skillmore (acquired by Apollo Education Group) — and taught at UC Berkeley and Carnegie Mellon. His personal diagnosis of follicular lymphoma led him to found CancerBot and architect the open source PHRAME stack behind HealthKey.AI.

Steven Labkoff

Steven Labkoff, MD

Advisor

Over three decades of experience in life sciences and healthcare innovation. Currently VP of Development and Medical Analytics at Bristol Myers Squibb and collaborating scientist at Beth Israel Deaconess Medical Center. Previously Chief Data Officer at the Multiple Myeloma Research Foundation, where he built the largest multi-data registry in oncology. Prior roles at Pfizer, AstraZeneca, and Deloitte. Fellow of ACMI, ACP, and AMIA.

Jude Fitzgibbon

Jude Fitzgibbon, PhD

Advisor

Professor of Personalised Cancer Medicine at Barts Cancer Institute, Queen Mary University of London. Previously VP of Heme Discovery at AstraZeneca and Director at Barts. His research centres on hematological malignancies with particular focus on follicular lymphoma. Degrees from Trinity College Dublin and UCL; h-index of 58.

Partner with us

Ready to build with PHRAME?

We work with foundations, HIEs, and clinics to deploy and customize the PHRAME stack. Get in touch to discuss your use case.

Talk to Our Team Explore PHRAME →

Open Source

A patient record should not be a black box.

The schema behind every HealthKey record, and the engine that matches it to clinical trials, are published openly on GitHub. Read the code. Audit the rules. Contribute back.

View on GitHub Talk to the team

Architecture

How PRomop, EXACT, and the PHR fit together

The patient record is an OMOP database extended by PRomop. The PatientRecord layer exposes eligibility-ready fields to EXACT, which evaluates every trial criterion and returns a fully transparent per-criterion trace — no black-box scores, no opaque rankings. The PHR sits on top, giving patients and care teams a legible view of the whole picture.

HealthKey PHR architecture diagram showing PRomop, PatientRecord, EXACT, and the PHR layer

Our Projects

Two repositories, one mission

PRomop defines what a precision-medicine patient record looks like. EXACT shows how it should be matched. Together, they form the open foundation beneath the HealthKey PHR.

healthkey-ai / promop

PRomop

A clinical-trial-ready superset of OMOP CDM v5.4.

PRomop extends the standard OMOP Common Data Model with oncology and genomics tables, a richer episode model for lines of therapy, and a comprehensive PatientRecord projection of eligibility-ready fields. Every computed field — therapy lines count, refractory status, measurable disease per IMWG, TP53 disruption, lymphocyte doubling time — is derived once, in one place, with auditable rules.

OMOP CDM v5.4 Oncology + Genomics PostgreSQL 266 fields
View on GitHub
healthkey-ai / exact

EXACT (EXtracting Attributes from Clinical Trials)

Explainable, precision clinical trial matching.

EXACT is a stateless matching engine that consumes a PRomop-aligned patient profile and a trials catalog, then returns a per-trial trace: every eligibility criterion shown as passed, failed, or indeterminate, with the exact patient field value and trial threshold behind each verdict. No opaque scores. Patients and navigators act on reasons.

Explainable AI Stateless Python Per-criterion trace
View on GitHub
EXACT trial matcher at AMIA 2026
Conference presentation · slides & session details

Why Open Source

Three reasons we publish the code

i.

Clinicians can audit it

The logic that decides whether a patient is eligible for a trial — or refractory to a therapy — should be inspectable by a human oncologist, not buried inside a vendor's binary. Every derivation rule in PRomop is in the open.

ii.

Researchers can extend it

PRomop starts with multiple myeloma and is designed to grow. Adding a new disease — follicular lymphoma, CLL, the next blood cancer — is a matter of contributing computed fields, not waiting on a vendor roadmap.

iii.

Patients can verify it

If your record told you that you don't qualify for a trial, you should be able to see exactly which field and which threshold produced that answer. EXACT makes the verdict legible. The code makes it accountable.

How They Fit Together

The architecture, in one paragraph

A HealthKey patient record is an OMOP database with the PRomop extensions and a comprehensive PatientRecord layer sitting alongside it. When a patient or their navigator asks "which trials am I eligible for?", the PatientRecord is handed to EXACT along with a structured trials catalog. EXACT evaluates each trial's criteria, returns a per-criterion verdict, and explains every answer in terms of the patient's actual data. Nothing is persisted inside the matcher. Nothing is hidden inside a score. The patient model is open. The matching engine is open. The PHR built on top is what we sell.

See the full PHR architecture →

Get Involved

Star us, fork us, file an issue

Whether you're a clinician with a rule to suggest, a researcher with a new disease model in mind, or an engineer who wants to contribute — we'd love your help.

PRomop on GitHub EXACT on GitHub

HealthKey Blog

Thoughts from the team

Architecture decisions, open source deep dives, and lessons from building PHR infrastructure.

Open Source

Building a Comprehensive PHR: Why OMOP, Why a Flat Projection, and What Open Source Leaves You

Three pillars define a useful PHR: it has to be a real record system, it should be built on OMOP rather than FHIR, and it needs a flat person-grain projection for CDS, trial matching, and predictive models. Six open source families, measured against all three.

19 min read
Architecture

Decision-Ready Wearables: One Clinical Layer Across Every Device

How PROMOP normalizes consumer wearable data into OMOP CDM v5.4 and LOINC — and why that normalization is what turns a step count into something a clinician, a trial coordinator, or a model can actually act on.

14 min read
Interoperability

How Athena Turns FHIR Codes into a Decision-Ready Patient Record

A concrete walkthrough of LOINC, CPT, and SNOMED CT from an incoming FHIR record through PRomop's OMOP tables and into PatientRecord.

8 min read
Standards

How We Built an Oncology Patient PHR That Conforms to the HL7 PHR Standard

HealthKey.ai's PRomop achieves conformance on all 25 Essential functions of its HL7 PHR-S FM R2 Functional Profile for oncology patient health records.

8 min read
Research

PRomop: Building a Comprehensive Longitudinal Decision-Ready Patient Health Record

A production-validated architecture that separates a standards-based OMOP transactional record from a flattened PatientRecord projection — eliminating 27–39 joins per eligibility query and enabling analytics, trial matching, and care evaluation from one shared substrate.

12 min read
Engineering

Getting Started with PHRAME

A data scientist's guide to assembling PRomop, PRism, EXACT, SoC, and fhir_importers into working patient-centered health infrastructure — step by step.

8 min read
Patient Voice

What Patients Taught Us About Trial Matching — and Where EXACT Goes Next

At a Harvard roundtable, patients reshaped how we think about trial matching — and clarified the next chapter of EXACT.

7 min read
Vision

A Vision for Centralized Patient Data Repositories

Empowering patients, enabling clinicians, and unlocking the full potential of healthcare data — what a patient-controlled, centralized health record could look like and why we need one.

7 min read
Data Model

Towards a Common Patient Information Schema

What a unified, patient-centric data model could look like for breast cancer trial matching — and how it maps to OMOP and FHIR while remaining extensible to other cancers and broader patient-data use cases.

4 min read
Architecture

PRomop: Extending OMOP to Be a Comprehensive Transactional and Longitudinal Patient Record

We discuss a vision for centralized patient database repositories and the architecture of PRomop — how it builds on OMOP's solid foundation while adding what's needed for true precision clinical trial matching and a comprehensive longitudinal record.

8 min read
Open Source

EXACT: An Open-Source Precision Clinical Trial Matcher Built on OMOP

EXACT treats eligibility matching as a structured-data problem against a patient model designed to be matchable — returning per-trial traces that explain exactly why a patient qualifies or doesn't, not just a ranked list.

11 min read
Open Source

Building a Comprehensive PHR: Why OMOP, Why a Flat Projection, and What Open Source Leaves You

Three requirements define a useful personal health record. Nothing in open source satisfies all three, and the reasons why turn out to be a map of the whole landscape.

You are building a PHR. Not a viewer — a comprehensive longitudinal record for a person, that other things can be built on top of. So you go looking for prior art, because surely this is solved.

What you find is a genuinely strong landscape: real standards, peer-reviewed validation, mature ecosystems, projects with more institutional weight behind them than most commercial products. You wire two or three of them together and the pile of work in front of you has not shrunk much.

The reason is that the requirement has three parts, and every project in this space satisfies one or two of them:

  1. It has to be a PHR. A complete longitudinal record for a person, with everything a PHR system is actually obliged to do — consent, audit, provenance, terminology lifecycle, patient-facing record semantics.
  2. It should be built on OMOP, not FHIR. FHIR is an exchange format and an excellent one. As the basis for a comprehensive record you intend to analyze — which is what foundations and HIEs want — it is the wrong substrate.
  3. It needs a flat projection for decision-readiness. Clinical decision support, trial matching, and predictive models all need to know what is true about one person now, cheaply and repeatedly.

That is the whole thesis, and the rest of this post is the three pillars argued properly, then the landscape measured against them.

Pillar 1 — It has to actually be a PHR

"Comprehensive longitudinal record" is the easy half. The other half is the set of obligations that come with being a record about a person rather than a dataset about a population, and they are more demanding than most builders expect.

The HL7 Personal Health Record System Functional Model, Release 2 (PHR-S FM R2) is the most complete statement of them: 748 mandatory criteria spanning personal health functions, security, audit, terminology management, and data interchange. Nobody conforms to all of it, and the standard says so — conformance is profile-based by design. You select a coherent subset and implement it fully.

Read what it asks for and notice how little of it is "features":

  • Authentication hardening — account lockout, password reuse prevention, admin-triggered force-change enforced on every request.
  • Audit that survives scrutiny — records in FHIR AuditEvent format, per-row tamper-evidence, delete-restriction, break-glass access with an elevated trail, and auditing of audit-log reads themselves.
  • Terminology lifecycle — not just using SNOMED and LOINC, but version tracking, deprecation workflows, and concept replacement in the coded store.
  • Interchange integrity — content digest verification on ingest, non-repudiation signatures on export.
  • Patient-facing record semantics — consent-driven demographic redaction, advance directive in-effect status, entered-in-error status, field-level revision history.

The trap is that several of these are not a layer you add on top. Consent-driven redaction, entered-in-error, revision history, terminology versioning, and advance-directive state all reach into how records are stored and read. Build your record first and discover the functional model second, and you are retrofitting redaction and provenance into a schema designed without them — a rewrite, not a sprint.

This is also worth reading early because it finds real holes. When we ran our own PHR-S FM audit we expected paperwork. It found that our audit trail had no tamper-evidence, no delete-restriction, and no chain integrity, and that our authentication had no lockout, no reuse prevention, and no force-change. We had believed both were fine.

Keep this pillar in mind as you read the rest: most of the projects in this landscape are not PHR systems and do not claim to be. They are analytics and ML substrates. Holding them to a functional model for a PHR would be unfair — but if you assemble a PHR out of them, none of this arrives with the data.

Pillar 2 — It should be built on OMOP, not FHIR

This is the pillar we think is genuinely unusual, and the one most likely to be argued with, so here is the argument in full.

FHIR is superb at what it was designed for: moving a patient's record between two systems, and giving applications a consistent API to read it. If you are acquiring data from providers, exchanging it, or building an app against it, FHIR is the answer and there is no serious competitor. We ingest FHIR R4 and we will keep doing so.

But acquisition format and storage substrate are different decisions, and defaulting the second to the first is the most consequential mistake in this space. As the basis for a comprehensive record you intend to analyze, FHIR has four structural problems:

Vocabularies are permitted, not enforced. A FHIR CodeableConcept can carry any coding system, and outside of a few constrained bindings nothing obliges a source to use one you recognize. Two feeds can describe the same drug in two vocabularies and both are valid FHIR. OMOP's load process requires source codes to be mapped to standard concepts — SNOMED, RxNorm, LOINC — so comparability is established once, at the door, rather than negotiated by every downstream query.

There is no concept hierarchy to query through. This one is underrated and it is where analytics actually lives. "Any anthracycline," "any platinum agent," "any beta blocker" are single predicates in OMOP because concept_ancestor encodes the hierarchy. In FHIR you have a code, and the traversal is a terminology service call you write, maintain, and hope everyone else wrote the same way.

The grain is a resource, and resources nest and unroll. Population questions require flattening a nested document model into tables, which is precisely why an international working group spent eighteen months producing SQL-on-FHIR. That work is excellent — it just tells you the substrate was not built for this.

It is versioned for exchange, not for a decade of stored history. R4 to R5 is a migration for data you have already persisted. OMOP's model is deliberately stable and its vocabulary releases are versioned content rather than schema churn.

Now the positive case, which matters more. Choosing OMOP means an entire analytic ecosystem applies to your record with no adapter: ATLAS for cohort definition, Achilles for characterization, the Data Quality Dashboard for checks, PatientLevelPrediction for models, and the OHDSI network's accumulated study packages. None of that is something you build. It is something you inherit by having earned the format.

For foundations and HIEs this is the whole ballgame. Their job is aggregating across many sources and asking questions of the aggregate. Comparability across sources is the product. OMOP was designed for exactly that problem — multi-site observational research across heterogeneous source systems — and FHIR was designed to move one record between two systems. A foundation that stores FHIR and hopes to analyze it later has deferred the hardest work to the point where it is most expensive and least likely to be done consistently.

So the position is not "FHIR bad." It is FHIR at the edges, OMOP in the middle — and we think the second half of that is what almost nobody else is doing for a PHR.

Pillar 3 — It needs a flat projection for decision-readiness

OMOP solves comparability and it does not solve immediacy. It is normalized and event-oriented, which is right for populations and expensive for individuals. Asking "what is true about this person now" means joins, concept lookups, and most-recent-value subqueries, every time, in every application.

Three use cases drove us to do something about it:

  • Clinical decision support and standard-of-care evaluation — guideline rules that need current line of therapy, stage, and latest labs, evaluated per patient, continuously.
  • Trial matching — eligibility criteria evaluated across a whole population, repeatedly, against thousands of trials.
  • Predictive models and cohort analytics — feature assembly that otherwise re-derives the same clinical state from raw events every run.

All three need the same thing, and all three would otherwise build it separately. Be specific about what that work is, because it is not schema translation:

  • Line of therapy. Not a field on any resource — temporal reasoning across drug exposures, regimen definitions, gaps, and intent. In production we needed structural rules, OHDSI's ARTEMIS regimen detection, our own logic, and the ability to read physicians' notes to get this right on real-world data.
  • Current disease status. Staging, progression, response — assembled from observations, notes, and episodes recorded at different times by different people.
  • Most-recent-value resolution. Trivially stated, annoyingly expensive: every lab criterion needs a correlated subquery, and there are usually ten of them.
  • Biomarker normalization. The same marker arrives as text, as a code, as a percentage, as a ratio, under three different names.
  • Source reconciliation. Two feeds disagree. Something has to decide, once, and record why.

That is clinical reasoning, not transformation, and it gets re-implemented slightly differently by a different developer in your CDS engine, your matcher, and your analytics layer — until the three quietly disagree about the same patient.

A lot of projects flatten health data. The useful question is whether they flatten along the axis you need, and four sub-questions settle it: grain (what is one row — a resource, an event, an encounter, a patient at a study index date, or a person as they are now?), derivation (are clinical composites computed, or just raw values reshaped?), currency (rebuilt nightly, per study run, or on write?), and consumer (a model's tensor, a population report, or a row a coordinator can read and a rule can cite?).

The landscape, measured against the three pillars

Flat projections over FHIR

SQL-on-FHIR v2, Pathling, Aidbox, FHIR Data Pipes

SQL on FHIR v2 is the strongest work in this family and you should know it well. An international working group produced a standard, implementation-agnostic way to define tabular views over FHIR resources using FHIRPath, published in npj Digital Medicine in 2025 and validated by replicating a clinical study over 580 million FHIR resources on two independent implementations with identical results. Implementations include Pathling (CSIRO), Aidbox (Health Samurai), and Google's FHIR Data Pipes.

Pillar 1 — no. It is a view definition standard, not a record system. Consent, audit, and provenance are elsewhere.
Pillar 2 — no, by definition. It is the best available answer to "I chose FHIR as my substrate and now I need tables," which is a different problem from not choosing FHIR as your substrate.
Pillar 3 — partly. A ViewDefinition is resource-grain, bound to a single resource type, and it unrolls rather than collapsing to one row per patient. FHIRPath cannot express line of therapy. You can assemble a person-grain layer by joining a dozen ViewDefinitions — but then the clinical logic lives in your SQL, unversioned, re-implemented per consumer.

Use it when you need a tabular extract another organization can execute unchanged. Nothing else comes close.

Analytics-ready models with precomputed clinical concepts

Tuva, i2b2/ACT, PCORnet, Sentinel

The Tuva Project is an open-source healthcare data model and analytics framework built as a dbt package, taking raw claims, clinical, eligibility, and pharmacy data through an input layer, preprocessing, a core data model, and data marts — with data quality tests, terminology sets, and source connectors, running inside your own warehouse. Crucially it ships real derived content: chronic conditions, HCC risk, readmissions, quality measures. Its chronic conditions mart implements two groupers, one from CMS and one Tuva wrote after finding gaps in the CMS logic.

Worth saying plainly: Tuva already makes the derive-once argument and has been shipping it for years. So have i2b2, PCORnet, and Sentinel, for well over a decade. Nobody should present flattening as an invention.

Pillar 1 — no. An analytics model, not a record system.
Pillar 2 — a peer, not a match. It is a genuine analytic substrate with enforced terminology, which is the right instinct; it is simply a different one, tuned to claims rather than to clinical observation, and outside the OHDSI ecosystem.
Pillar 3 — partly. Its natural grain is the member-month, encounter, or episode, and its derivations are population- and financial-grade. It answers "what is the risk-adjusted burden of this population" extremely well, and is not built to answer "what is true about this person, right now."

Use it if your data is claims and your questions are about populations. Adopt it rather than re-deriving it. Its ceiling for oncology is that staging, biomarkers, and response assessment are not in the claim.

ML-ready event streams

MEDS, MEDS-Tab, FEMR

MEDS — the Medical Event Data Standard — is a deliberately minimal, event-centric schema for machine learning over EHR data, described in NEJM AI in 2026. As of March 2026 it was in use across 21 institutions, in at least 27 papers and preprints, supporting 17 datasets. Alongside it sit MEDS-Tab, which tabularizes event streams into model features, and FEMR from Stanford's Shah lab, which ingests OMOP into patient timelines for foundation-model training.

Pillar 1 — no, and explicitly not trying.
Pillar 2 — complementary. MEDS positions itself as complementary to OMOP rather than a replacement, and is deliberately semantics-light; FEMR reads OMOP as an input. Neither wants to be your record.
Pillar 3 — different axis. The output is a feature matrix, which is right for a model and wrong for a decision: a tensor is not auditable by a coordinator, cannot be shown to a patient, and does not have to explain itself.

Use it if you are training models on patient timelines — and note that a projection collapsing history into current state is the wrong shape for a sequence model. You want both, over the same source of truth.

OHDSI-native derivation

FeatureExtraction, PatientLevelPrediction

OHDSI's FeatureExtraction generates covariates for a cohort directly from CDM data — a large predefined set spanning conditions, drugs, procedures, age, and comorbidity indices, plus custom covariate builders — feeding PatientLevelPrediction and CohortMethod.

Pillar 1 — no. Study-execution tooling.
Pillar 2 — yes. This is the OMOP-native family, and we are inside it rather than adjacent to it.
Pillar 3 — close, and instructive about the difference. It is index-date scoped — the premise is a cohort and a defined t = 0, not "now." It emits a sparse long format of (covariateId, personId, value) triples for a model to consume, not named columns a person reads. It is R and batch, not event-driven serving. And its covariates are generic rather than clinical composites: you could write a custom builder that infers lines of therapy, but it would be per-study code rather than a shared, versioned derivation.

Use it for observational studies and prediction models on OMOP. We depend on parts of this ecosystem ourselves.

Person-grain summary documents

C-CDA CCD, FHIR International Patient Summary, $everything

This family rarely comes up and it should. The C-CDA Continuity of Care Document, the FHIR International Patient Summary, and the Patient/$everything and $summary operations are all explicitly person-grain: one artifact, one patient, curated. It is useful proof that person-grain summarization is a recognized need with decades of standards work behind it.

Pillar 1 — partly. Person-grain and standards-backed, but a document rather than a system.
Pillar 2 — no. FHIR- and CDA-based, with the vocabulary looseness that implies.
Pillar 3 — no. Document-grain rather than column-grain: built for exchange and human reading, not for a WHERE clause. There is no derived state in an IPS — no current line of therapy, no "is this patient on treatment." And $everything returns the full transactional history, which is the layer you want underneath a projection rather than the one you serve decisions from.

PHR platforms and FHIR application backends

Fasten Health, Medplum, HAPI FHIR, OpenMRS, OpenEMR

Fasten Health is an open-source, self-hosted personal and family health record connecting to a very large number of provider endpoints via SMART-on-FHIR. Medplum is an Apache-2.0, FHIR-native developer platform: FHIR R4 datastore, REST and GraphQL APIs, TypeScript SDK, React components, SMART-on-FHIR auth, access policies, subscriptions, serverless bots, with US Core and Bulk Data conformance.

Pillar 1 — the closest anyone gets. These are real record systems, and Medplum in particular is strong on the trust-infrastructure half — declarative access policies, audit, SOC 2 and ONC certification. What a platform gives you is the access half; the half that touches the data model is still yours.
Pillar 2 — no. They are FHIR datastores by design, which is exactly the substrate choice Pillar 2 argues against for an analytic record.
Pillar 3 — no. They store and serve; they do not derive. A FHIR datastore's answer to "what line of therapy is this patient on" is "write some code."

Use them for the hardest and least glamorous parts of a PHR: endpoint connectivity, OAuth and token refresh, storage, access control, audit, compliance posture. This is permanently-maintained work and you should not write it yourself.

The scorecard

FamilyRepresentativeA PHR system?Analytic substratePerson-grain decision projection?Best at
Flat FHIR viewsSQL-on-FHIR v2, Pathling, AidboxnoFHIRno — resource-grain, unrollsportable tabular extracts
Analytics-ready modelsTuva, i2b2/ACT, PCORnetnoown model, claims-anchoredno — member-month / encounter grainpopulation analytics on claims
ML event streamsMEDS, MEDS-Tab, FEMRnoevent stream, semantics-lightno — feature matrix, not a readable rowmodel training on timelines
OHDSI derivationFeatureExtraction, PLPnoOMOPno — index-date scoped, sparse long formatobservational studies
Summary documentsC-CDA CCD, FHIR IPS, $everythingpartly — document, not systemFHIR / CDAno — curated, not derivedexchange & human reading
PHR / FHIR platformsFasten, Medplum, HAPIyesFHIRno — storage and accessconnectivity, storage, apps
PRomopPatientRecord over OMOP 5.4yesOMOPyes — 304 columns, refreshed on writeper-patient decisions

The three pillars are the middle three columns. Every family clears one or two. The combination — a real record system, on an analytic substrate, with a decision-ready person-grain projection over it — is the empty slot.

What PRomop hands you

Taken in pillar order.

It is a PHR. PRomop is audited against a published HL7 PHR-S FM R2 functional profile for oncology patient health records — 26 functions across personal health, security and audit, and terminology and interoperability, verified criterion by criterion against the source code. Of the profile's 78 mandatory SHALL criteria, 75 are met and none are unmet; every Essential function is fully conformant. Concretely: hash-chained HMAC-signed audit records in FHIR AuditEvent format with delete-restriction, account lockout and password-reuse prevention, terminology version tracking with deprecation and concept replacement, content digests on ingest and signed export bundles, consent-driven demographic redaction on the read path. The full conformance write-up is here.

It is built on OMOP. OMOP CDM 5.4 with oncology extensions is the source of truth — everything that ever happened, with source codes mapped to standard concepts at load. FHIR R4 comes in at the edge via ingestion, and C-CDA is converted upstream. Because the store is genuinely conformant, ATLAS, Achilles, and the Data Quality Dashboard apply to your data unchanged, and multi-site work stays possible. This is the pillar that makes a foundation's or an HIE's analytics ambitions realistic rather than aspirational.

It offers a flat projection. PatientRecord collapses a patient's entire longitudinal history into a single 304-column row representing what is true now. Line of therapy, current disease status, normalized biomarkers, latest values, reconciled sources — computed once, at projection time, and materialized. Refresh is event-driven: a change to the underlying record regenerates that patient's row, so the view stays current without a nightly job. It can be invoked on demand and disabled during bulk backfills with a single rebuild at the end.

What that means for the three use cases that drove it:

What you're buildingWhat it reads
CDS and standard-of-care evaluationGuideline rules become predicates over named columns — current line, stage, most recent labs — instead of a state-reconstruction pipeline per rule.
Trial matchingA 20-criterion eligibility search that takes 27–39 joins over raw OMOP becomes zero joins against one row per patient — an estimated ~30×–200× on a representative workload.
Predictive models and analyticsThe flattened feature row already exists with clinical composites in it, so feature engineering starts from derived state rather than raw events, and cohort selection runs over one table.

The structural property that ties Pillars 2 and 3 together: the projection is a projection, not a fork. The OMOP tables remain the source of truth and stay conformant. Delete every PatientRecord row and the whole thing rebuilds from the CDM. You are not trading the ecosystem for the convenience.

It runs in production across the HealthTree Foundation (~14,000 blood-cancer patients) and CancerBot (~3,500), matching against roughly 6,000 actively recruiting trials in five cancer types — offered as evidence the pattern survives real-world data, which is the only test that counts.

These compose. Use all of them.

None of this is either/or. A reasonable stack:

  • Fasten or Medplum at the front door — patient connectivity, auth, storage, the app surface. Do not write this yourself.
  • OMOP CDM 5.4 as the source of truth — FHIR at the edges, OMOP in the middle.
  • The PatientRecord projection over it — clinical state computed once, refreshed on write, read by every application you build.
  • SQL-on-FHIR when you need to hand tables to someone else, MEDS or FEMR when you are training on the transactional layer, FeatureExtraction when you are running a study.

Nothing about a projection layer requires that we also be your FHIR server, your connector network, or your ML pipeline — and we are not trying to be.

What building on it actually costs you

  • The projection is a living artifact. Because it pre-computes what consumers need, it stays coupled to evolving clinical demand. A new trial criterion or decision rule sometimes means a new column. Plan for that as an ongoing obligation, not a one-time build.
  • The valuable derivations need clinical reasoning, not just ETL. Lines of therapy resisted purely structural normalization on real-world data. Expect the highest-value fields to require rules, and sometimes unstructured text.
  • Mapping to OMOP is real work. The comparability Pillar 2 buys is paid for at load time, in vocabulary mapping. That cost is why people default to storing FHIR — and deferring it does not remove it, it just moves it somewhere more expensive.
  • Validation so far is in oncology. The OMOP foundation is disease-agnostic and the projection pattern is general, but our extensions and derived fields have been proven in blood cancers and breast cancer.

Where to start

The three pillars are also the sequence.

Decide you are building a PHR, and read the PHR-S FM functional model before you finalize how records are stored and read — consent, provenance, and redaction reach into the schema, and they are cheap early and expensive late.

Choose OMOP as the substrate and FHIR as the edge. If anyone downstream will want analytics — and for a foundation or an HIE that is the entire reason the data was gathered — this is the decision that determines whether those questions are answerable at all. Storing FHIR and planning to normalize later is deferring the hardest work to the point of maximum cost.

Then decide who owns clinical-state derivation. Every application, separately and inconsistently — or once, in a projection everything reads. CDS, trial matching, and predictive models all need the same derived state, and they are the reason the projection exists.

Everything else in this landscape is excellent at the job it was built for. The job that keeps getting left over is all three at once: a real record system, on a substrate that supports analysis, with a person-grain projection that makes it decision-ready.

PRomop and the rest of PHRAME are open source at github.com/healthkey-ai, with a live analytics demo on synthetic data at prism.healthkey.ai. Precision trial matching — what EXACT does that ranked-list matchers don't — is its own subject, and its own post.

← Back to Blog
Architecture

Decision-Ready Wearables: One Clinical Layer Across Every Device

How PROMOP normalizes consumer wearable data into OMOP CDM v5.4 and LOINC, and why that normalization is what turns a step count into something a clinician, a trial coordinator, or a model can actually act on.

On scope. The architecture described here is device-agnostic by construction. Apple Health and Garmin are the two adapters implemented in the codebase today, so they are what this article uses for concrete examples — real HealthKit identifiers, real FIT message names, real concept ids. Fitbit, Whoop, and Oura are planned and fit the same three-layer model without changing anything below the parser boundary. The section Beyond Apple and Garmin sets out precisely what a new device adds and what it doesn't.

The problem is not the data. It's that every vendor has its own of everything.

An Apple Watch, a Garmin Fenix, a Fitbit Charge, a Whoop strap, and an Oura ring all measure resting heart rate. They all do it reasonably well. What they do not do is agree on how to say so.

Apple exports an XML record typed HKQuantityTypeIdentifierRestingHeartRate. Garmin writes a binary FIT file containing a monitoring_hr_data message with a resting_heart_rate field — and on devices that don't emit that message at all, nothing, just an all-day stream of monitoring.heart_rate samples from which resting HR has to be inferred. Fitbit, Whoop, and Oura each serve their own JSON from their own OAuth-gated REST API, with their own field names, their own units, and their own opinion about what "daily" means.

None of these identifiers is a clinical vocabulary term. They are product API surfaces, versioned on vendor timelines, describing vendor features. HKQuantityTypeIdentifierAppleExerciseTime is not a concept in any terminology a research warehouse, a trial protocol, or a decision-support rule has ever heard of.

The tempting shortcut is to store the vendor payload as-is and sort it out at query time. That choice does not remove the problem; it moves it, and multiplies it. Every consumer downstream — the eligibility screen, the deterioration alert, the cohort extract — now has to know every vendor's vocabulary, unit conventions, aggregation rules, and edge cases. Add a device and every one of those consumers changes. Five vendors is not five integrations; it's five integrations times every query you will ever write. The vendors' schemas become your schema, and you inherit all of their churn.

PROMOP takes the opposite approach: normalize once, at the edge, into a real clinical vocabulary. Nothing downstream ever learns what a HKQuantityType is.

Three layers, one contract

Apple Healthexport.zip
Garmin.fit
Fitbit / Whoop / OuraOAuth REST JSON — planned
↓   device adapters
WearableSample(metric_key, date, value) — 17 canonical metrics
↓   concept resolution — 13 LOINC + 4 HK-Wearable
OMOP measurement13 metrics
OMOP observation4 metrics
↓   projection
PatientRecord21 flat 30-day columns
Standard of care
Trial matching
Analytics & models

Layer 1 — device adapters. One adapter per vendor, one output type for all of them. WearableSample is a three-field tuple: (metric_key, date, value). That is the entire contract between the device world and everything else. Vendor vocabulary does not cross this boundary.

The seventeen canonical metric keys are the vocabulary the rest of the system speaks: steps, resting_hr, hrv_sdnn, spo2, respiratory_rate, sleep_duration, vo2_max, and so on. When Apple emits HKQuantityTypeIdentifierRestingHeartRate and Garmin emits monitoring_hr_data.resting_heart_rate, both become resting_hr — and from that point they are indistinguishable. A Fitbit adapter reading restingHeartRate from an activity-heart response, or an Oura adapter reading a nightly lowest_heart_rate, joins the same key and becomes equally indistinguishable. The same metric is stored identically no matter which device produced it: same concept, same units, same table. A query for resting heart rate never has to know what the patient is wearing.

Every parser also collapses to one value per metric per calendar day before returning, using the same sum-vs-mean rule. Cumulative quantities (steps, active minutes, sleep, distance, flights, energy) are summed; rates and percentages (HR, SpO2, HRV, respiratory rate, walking speed, VO2 max, body mass) are averaged. The rule is applied identically in every adapter, which is what makes a Garmin patient's step count and an Apple patient's step count the same kind of number — and what a Fitbit or Oura adapter has to honor to make theirs the same kind too.

Layer 2 — OMOP measurement and observation, keyed by LOINC. Each canonical metric maps to exactly one controlled-vocabulary concept. This is the join point between the two adapters, and the point at which the data acquires clinical meaning rather than just clinical-adjacent plausibility.

Layer 3 — the PatientRecord projection. Twenty-one flat, pre-aggregated 30-day summary columns derived from the OMOP rows. This is the surface that decisions actually read.

Layer 2: the mapping that carries the meaning

Seventeen metrics, thirteen of which have a genuine LOINC code:

MetricLOINCConceptOMOP tableUnitDaily agg.Apple HealthKit typeGarmin FIT message.field
steps55423-8Number of steps in unspecified time Pedometerobservation/dsumHKQuantityTypeIdentifierStepCountmonitoring.steps / .cycles (max — cumulative counter); fallback session.total_steps
active_minutes55411-3Exercise durationobservationminsumHKQuantityTypeIdentifierAppleExerciseTimemonitoring.active_time ÷ 60; fallback session.total_timer_time ÷ 60
sleep_duration93832-4Sleep durationobservationhsumHKCategoryTypeIdentifierSleepAnalysis (asleep spans only)sleep_level timestamp spans; fallback sleep_data.total_timer_time ÷ 3600
flights_climbed100304-5Flights climbed [#] Reporting Periodobservation{flights}sumHKQuantityTypeIdentifierFlightsClimbed— Apple-only today
resting_hr40443-4Heart rate --restingmeasurement/minmeanHKQuantityTypeIdentifierRestingHeartRate, else derivedmonitoring_hr_data.resting_heart_rate, else derived from monitoring.heart_rate
hrv_sdnn80404-7R-R interval SD (heart rate variability)measurementmsmeanHKQuantityTypeIdentifierHeartRateVariabilitySDNNhrv_status_summary.weekly_average; hrv.sdnn — but see the HRV note below
spo259408-5Oxygen saturation in arterial blood by pulse oximetrymeasurement%meanHKQuantityTypeIdentifierOxygenSaturationspo2_data.reading_spo2; fallback session.saturated_hemoglobin_percent
respiratory_rate9279-1Respiratory ratemeasurement/minmeanHKQuantityTypeIdentifierRespiratoryRaterespiration_rate.respiration_rate; fallback session.avg_respiration_rate
vo2_max94122-9Oxygen consumption (VO2)/body weightmeasurementmL/kg/minmeanHKQuantityTypeIdentifierVO2Maxsession.enhanced_max_oxygen_consumptionvo2_max
distance41953-1Walking distance 24 hour CalculatedmeasurementkmsumHKQuantityTypeIdentifierDistanceWalkingRunningmonitoring.distance ÷ 1000; session.total_distance ÷ 1000
walking_speed41957-2Walking speed 24 hour mean Calculatedmeasurementkm/hrmeanHKQuantityTypeIdentifierWalkingSpeed— Apple-only today
active_energy93819-1Calories burned in unspecified time --during activitymeasurementkcalsumHKQuantityTypeIdentifierActiveEnergyBurnedmonitoring.active_calories; session.total_calories
body_mass29463-7Body weightmeasurementkgmeanHKQuantityTypeIdentifierBodyMass— Apple-only today

The last two columns are the vendor surfaces each adapter reads; the ÷ conversions are unit normalization applied in the parser. "Else derived" on resting_hr is the shared fallback both adapters use when a day has no dedicated resting-HR record: the 10th percentile of that day's all-day heart-rate samples — the minimum is too noisy, the mean is inflated by activity. A dash means Garmin has no adapter for that metric yet, so a Garmin-only patient has a permanent null there. The four locally minted HK-Wearable metrics are covered in the next section; the same table with concept ids, artifact bounds, and open gaps lives in the mapping document in the PROMOP repository.

The four metrics LOINC doesn't have

Consumer wearables measure some things clinical terminology has never needed a code for. LOINC has no concept for walking step length, walking double-support percentage, heart rate during walking, or basal energy expenditure in kcal/day.

The wrong answer is to find the nearest-looking code and use it. The right answer is to mint locally — under strict quarantine, so a local concept can never be mistaken for a standard one:

  • a dedicated HK-Wearable vocabulary, never LOINC
  • source='HealthKey' on every row, so consumers mirroring the vocabulary tables can filter local content with a single predicate
  • HK-*-shaped concept codes
  • concept_id >= 2,000,000,000 — the range OHDSI reserves for locally-authored concepts, where Athena never allocates

The four mints are allocated contiguously from 2,029,606,350, continuing the project's existing HK-Labs block. They are visibly, structurally local. Nothing about them can pass for a vocabulary release row.

Why "we mapped it to LOINC" is not the same as "we mapped it correctly"

A code that looks like LOINC is trivial to produce. Six-digit-dash-digit is a shape, not a guarantee: a plausible-looking string may not exist in any release, may be deprecated, or may resolve to a real concept that means something entirely different from the metric filed under it.

That last case is the dangerous one, and it is worse than dropping the data. A row with a wrong concept id looks completely valid. It has a real concept, a real unit, a real date. Any query trusting measurement_concept_id — which is to say, any correct OMOP query — will read it confidently and report a plausible number for the wrong thing. Nothing downstream can detect it, because there is nothing malformed to detect. A nearest-looking code for an untranslatable metric fails the same way, just more quietly.

So the mapping is governed by a rule rather than by care: every code is verified against Athena's concept_name before it enters the map, and a metric with no faithful code is minted locally rather than approximated. Concretely, that means:

  • the map is checked against a full Athena vocabulary load, not against documentation or recall — a code that does not resolve is not a code
  • the resolved concept_name has to actually describe the metric, not merely be adjacent to it; "close enough" is a rejection, not a pass
  • a metric with no faithful concept goes to a quarantined HK-Wearable mint, never to an approximate LOINC code
  • the same check is a precondition for every new metric and every new device adapter, not a one-time cleanup

The consistency claim in this article rests entirely on that discipline. Two devices agreeing on a code that means the wrong thing is not interoperability.

Routing follows the vocabulary, not our opinion

Four metrics — steps, active minutes, sleep duration, flights climbed — resolve to Observation-domain concepts and are written to observation. The other thirteen are Measurement-domain and go to measurement.

The write path does not hard-code that list. It reads concept.domain_id at runtime, so routing stays correct automatically if a code ever changes. This matters more than it sounds: OMOP's rule is that a concept's domain determines its table, and violating it is exactly the kind of local shortcut that passes every internal test and then fails Achilles/DQD domain checks the first time the data reaches a research warehouse.

The read path is built to be indifferent to the split — it merges measurement and observation rows into a single index keyed by concept code, so a metric reads the same way regardless of which table its domain routed it to. Consumers never encode the split either.

What else happens at write time

Three things that make the OMOP layer trustworthy rather than merely populated:

Artifact filtering. Readings outside physiologic bounds are discarded before the row is created — SpO2 outside 70–100%, resting HR outside 20–300 bpm, HRV outside 1–300 ms. Rejected readings are never persisted, so the OMOP tables hold only plausible values. The same bounds are applied again on read, so rows written before a bound was tightened cannot leak into a summary.

Idempotent ingestion. Dedup is on (metric_key, date, value), checked against whichever table the concept's domain routes to. Re-uploading an overlapping export is safe — a patient syncing monthly does not accumulate duplicate days.

Loud failure on unmapped metrics. If a concept cannot be resolved, the upload logs a warning naming the affected metrics and returns unmapped_samples and unmapped_metrics in the HTTP response. The alternative — skipping silently — is the failure mode this is built to prevent: a deployment whose vocabulary tables are missing a handful of wearable concepts would return HTTP 200 with a success count while discarding most of the upload, indistinguishable from "the device exported no data."

Beyond Apple and Garmin: what a new device actually costs

Apple and Garmin are the two adapters that exist today. Fitbit, Whoop, and Oura are planned, and the reason they are a small piece of work rather than a large one is that the boundary was drawn in the right place.

Transport differs. The contract doesn't.

Apple and Garmin arrive as file uploads — an export.zip the patient generates from the Health app, a .fit file pulled from Garmin Connect or a USB-mounted watch. Fitbit, Whoop, and Oura are OAuth-gated cloud APIs: the patient authorizes once, and the server pulls JSON on a schedule.

That is a genuine architectural difference, and it is worth being precise about where it lands. It changes how bytes arrive — token storage, refresh handling, scheduled pulls, rate limits, revocation — and it changes nothing at all about what an adapter emits. WearableSample is (metric_key, date, value). It has no opinion about whether those values came out of a zip archive or an HTTP response.

So the cloud-API work is real work, but it sits above the parser boundary and is shared across all three vendors rather than duplicated per vendor. Below the boundary — concepts, tables, artifact bounds, aggregation, projection, every downstream query — nothing changes.

The metrics line up

The canonical metric set was derived from what wearables actually measure, not from what Apple and Garmin happen to call things, so the overlap with the other three vendors is high:

Canonical metricFitbitWhoopOura
stepslimited — not a historical Whoop capability
active_minutes✓ (active-zone minutes)✓ (strain/activity durations)
resting_hr✓ (nightly lowest HR)
hrv_sdnnbut RMSSD — see belowbut RMSSDbut RMSSD
spo2
respiratory_rate
sleep_duration
active_energy / basal_energy
distance✓ (equivalent walking distance)
flights_climbed
vo2_max✓ (cardio fitness score)
body_mass✓ (Aria scale)
walking_speed, walking_step_length, walking_double_support_pct, walking_hr_avg

This table is a planning sketch of vendor capability, not a verified field map. Exact endpoint and field names must be confirmed against each vendor's current API documentation when its adapter is written — which is the same rule already applied to every LOINC code in this system.

Two things fall out of it. First, the four Apple-only gait metrics stay Apple-only: a ring and a strap have no way to measure step length or double-support percentage, and no other vendor should be mapped onto those concepts to make a column look populated. Second, several vendors will leave metrics null — which is not a defect but the exact situation wearable_coverage_ratio_30d exists to make legible. A Whoop-only patient with no step data is a patient the eligibility screen can correctly decline to evaluate on step count, rather than one it silently scores as sedentary.

HRV is where a careless adapter would get it wrong

This is the sharpest example of why the verification rule matters, and it is worth dwelling on because it is so easy to get wrong.

"HRV in milliseconds" is not one measurement. SDNN is the standard deviation of the full R-R interval series; RMSSD is the root mean square of successive differences. They are different statistics over the same signal, they respond to different physiology, and they produce different numbers from identical data. LOINC 80404-7 — the code this system uses for hrv_sdnn — is specifically the standard-deviation form.

Apple's identifier is unambiguous: HKQuantityTypeIdentifierHeartRateVariabilitySDNN. Fitbit, Whoop, and Oura all report RMSSD-derived values. Filing an RMSSD number under the SDNN code because both are "HRV in ms" is exactly the failure described above: a row that looks entirely valid, that any correct OMOP query will read confidently, and that means something other than what it says. The code resolves, the unit is right, and the number is wrong.

The correct handling is a second canonical metric with its own concept — resolved against Athena at implementation time, not aliased onto 80404-7 — and a PatientRecord column that does not average the two together. That is more work than one adapter line. It is also the difference between an interoperability layer and a pile of numbers that share a unit.

This applies to code already shipped. Garmin's HRV Status is documented by Garmin as RMSSD-based, and the existing adapter files hrv_status_summary.weekly_average under hrv_sdnn. That mapping needs verification before any further HRV work — it may already be the same conflation. It is tracked as an open gap in the mapping document.

What a new adapter actually changes

Concretely, adding Fitbit touches:

  • a new parser module emitting WearableSample — the substantive work
  • the device_type allow-list in upload_wearable, currently the literal tuple ('garmin', 'apple'), plus the per-device file-extension validation and parser dispatch beside it — for a cloud vendor, a sync entry point alongside the upload one
  • the frontend's detectDeviceType and its upload-history label, which today is a two-way ternary that would render any third device as "Apple"
  • new canonical metrics, only if the vendor measures something the seventeen don't cover (readiness scores, skin temperature) — each needing a verified concept or a quarantined HK-Wearable mint

And it changes none of: the concept map for existing metrics, the domain routing, the artifact bounds, the dedup rule, the aggregation logic, the twenty-one PatientRecord columns, the trial eligibility query, the care-alert thresholds, or any model feature. That asymmetry is the whole return on drawing the boundary at three fields.

Layer 3: from OMOP rows to a decision surface

A trial screening query against raw OMOP has to touch, per patient: seventeen metrics × up to thirty days × two tables, joined to concept, filtered by code, aggregated, and windowed. Per patient. For every criterion, in every screen.

So PROMOP materializes the answer. _get_wearable_data derives twenty-one flat columns on PatientRecord, refreshed automatically whenever the underlying OMOP rows change:

ColumnDerivation
median_daily_steps_30dmedian of daily step totals
active_minutes_per_day_30dmean of daily active-minute totals
activity_trend_30dfirst vs. second half of window: improving / stable / declining / insufficient_data
resting_heart_rate_avg_30dmean of daily means
hrv_sdnn_avg_30dmean of daily means
oxygen_saturation_min_30dminimum valid reading in window
oxygen_saturation_avg_30dmean of daily means
respiratory_rate_avg_30dmean of daily means
sleep_duration_hours_avg_30dmean nightly total
vo2_max_avg_30d, walking_speed_avg_30d, distance_km_per_day_30d, …12 further metric summaries
wearable_last_sync_atanchor date of the window
wearable_coverage_ratio_30dvalid days ÷ 30

Three design decisions in that table are worth pulling out.

The window anchors on data, not on today. The 30-day window ends at the most recent day with at least seven valid days behind it — not at the current date. A patient who stopped syncing three weeks ago yields a summary describing the period they actually wore the device, timestamped honestly in wearable_last_sync_at, rather than a window that is 70% empty.

SpO2 is summarized by minimum, not mean. One reading of 87% is clinically significant in a way that a 30-day average of 96% will never reveal. The aggregation function is a clinical judgment, not a default.

Insufficient data is a value, not a null. activity_trend_30d requires seven valid days in each half of the window and reports insufficient_data when it can't. A consumer can distinguish "this patient is stable" from "we don't know," which a null cannot express.

What the projection buys

Against the documented benchmark procedure — a 20-criterion trial eligibility pull, and a full PatientRecord derivation across all 19 sections:

PathReads fromRelative speedup
20-criterion trial eligibility rowPatientRecord vs. raw OMOP subqueries~6.9×
Full patient record derivationPatientRecord vs. live OMOP derivation~46.8×

Absolute latencies are hardware- and cache-dependent; the reproducible result is the ratio. The full-derivation gap is larger because nineteen sections × multiple queries each compounds the OMOP overhead, while the PatientRecord read cost is essentially flat regardless of how many fields are requested.

The projection is a cache, and it is treated as one: derived columns are read-only over the API, written only by the refresh service, so a client cannot PATCH a summary into disagreement with the OMOP rows it claims to summarize. (Eleven of the twenty-one columns added most recently still need to be added to that read-only list — one of the tracked gaps.) The OMOP tables remain the system of record. Delete every PatientRecord row and the entire projection rebuilds.

Why this is decision-ready

"Decision-ready" is a specific claim: that a consumer can read a value, know what it means, know how much to trust it, and act — without knowing anything about the device that produced it.

Standard of care

Functional decline is one of the strongest signals in oncology, and one of the worst-captured. ECOG performance status is assessed at clinic visits, by different clinicians, on a five-point scale, weeks apart. Between visits there is nothing.

median_daily_steps_30d and activity_trend_30d are a continuous, objective proxy measured every day the patient wears the device. activity_trend_30d = 'declining' on a patient two cycles into treatment is a supportive-care conversation that would otherwise have waited for the next visit.

The other summaries map to specific surveillance questions: rising resting_heart_rate_avg_30d with falling hrv_sdnn_avg_30d is a recognized deconditioning and cardiotoxicity pattern relevant to anthracycline and HER2-directed therapy. oxygen_saturation_min_30d < 90 is a pulmonary flag. respiratory_rate_avg_30d supports infection and pneumonitis escalation.

None of these are diagnoses, and the architecture doesn't pretend otherwise — but they are inputs, available continuously, in units a protocol can be written against.

Trial matching

Eligibility criteria are written in clinical language: performance status, cardiopulmonary reserve, activity limitation. Screening against raw wearable time series is expensive enough that in practice it doesn't happen, so wearable data is simply excluded from pre-screening.

A flat, LOINC-anchored, vendor-neutral projection makes the criterion a column read. And because the normalization happened at ingestion, one screening query covers an entire mixed cohort — Apple, Garmin, and every vendor added later, screened by identical logic, with no per-vendor branch and no vendor covariate. A screening query written today does not change when Fitbit, Whoop, and Oura patients start appearing in the cohort.

wearable_coverage_ratio_30d is what makes this defensible rather than merely fast. A coordinator screening on median_daily_steps_30d > 4000 can require wearable_coverage_ratio_30d >= 0.5 alongside it, and know the difference between a patient who walks 4,000 steps a day and a patient who wore the watch twice.

Analytics and predictive models

Feature engineering over wearable data is normally a per-vendor ETL problem. Here it isn't: the features are already canonical, already unit-normalized, already artifact-filtered, and already windowed. A cohort assembled from PatientRecord mixes device populations without a vendor indicator variable — because device identity was resolved away three layers earlier.

Coverage ratio doubles as a missingness feature, which matters more than it looks: wearable adherence is itself correlated with function and with outcome, so a model that treats "no data" as "missing at random" is making an assumption the coverage column lets it stop making.

And because the OMOP layer underneath is CDM v5.4-conformant with real LOINC concepts, the same cohort exports to an OHDSI research warehouse without a translation step. That is the deeper payoff of doing Layer 2 properly: the analytics story isn't PROMOP-specific.

Knowing what you don't have

An honest data layer is measured by what it refuses to assert. This one:

  • discards implausible readings before they're persisted, and again on read
  • requires seven valid days before emitting any 30-day summary column
  • publishes its own coverage as a first-class column rather than making consumers infer it
  • anchors windows to real data, and timestamps the anchor
  • distinguishes "insufficient data" from "stable" with a sentinel value rather than a null
  • reports unmapped metrics in the upload response instead of returning a success count

The known limitations are documented rather than papered over. unit_concept_id is not yet populated (only unit_source_value), which standard OMOP consumers read. The measurement type concept currently in use resolves to "Survey," which a wearable reading is not. Six metrics are Apple-only because the Garmin adapter has no source for them yet — a Garmin patient has permanent nulls in six columns. Storage is at daily grain with no measurement_datetime, so nocturnal SpO2 desaturation and circadian HR analyses are out of reach today.

All six are tracked as numbered gaps with proposed fixes in the mapping document. A gap you've written down is a roadmap item. A gap you haven't is a bug someone else will find in your data.

What this gives a medical informatics developer that a wearable aggregator doesn't

There is an established category of product that solves the first half of this problem well. Spike API offers a hosted unified API across 500+ wearables, IoT devices, and health sources. Open Wearables, from Momentum, is a self-hosted open-source platform doing something similar across 200+ devices — Garmin, Oura, Whoop, Fitbit, Polar, Suunto, Strava, plus Apple HealthKit, Health Connect, and Samsung Health.

Both are genuinely useful, and both are better than PROMOP at the thing they do. Two adapters is not 500 devices. If your problem is acquisition — OAuth dances, token refresh, rate limits, five vendors' JSON, background sync on two mobile platforms — that is real, unglamorous, permanently-maintained work, and buying or forking it is a reasonable decision.

But acquisition is Layer 1. A unified API normalizes the schema; it does not assign clinical meaning. A field called resting_heart_rate in a vendor-neutral JSON payload is still a product schema — a good one, versioned on a vendor's roadmap, defined by its own documentation. Neither platform publicly documents mapping to LOINC concept ids, OMOP domain routing, or a persistence model. That isn't a criticism; it's a scope boundary. It just means the clinical half of the job is still yours.

The three steps that come after "we have the data"

Concretely, here is what a medical informatics developer still owns after wiring up an aggregator, and what PROMOP's three layers hand over instead:

Question you eventually have to answerAggregator APIPROMOP
What is this number, in a vocabulary someone outside my company recognizes?a field name in their schemaa LOINC concept id verified against a full Athena load — or a visibly quarantined HK-Wearable mint
Where does it get stored?your problemmeasurement or observation, routed by concept.domain_id at write time
Is 187% SpO2 in my database?your problemrejected at write, and again at read, against physiologic bounds
What happens when the patient re-syncs an overlapping export?your problemdedup on (metric_key, date, value) — idempotent
Is this 30-day average based on 29 days or 2?your problemwearable_coverage_ratio_30d, published as a column
How much does a 20-criterion eligibility screen cost per patient?time-series query per criteriona flat column read — ~6.9× faster in the documented benchmark
How do I hand this cohort to a research warehouse?write an ETL to OMOPit already is OMOP CDM v5.4

That last row is the one that compounds. Because the data lands in CDM v5.4 with real concept ids and correct domains, the entire OHDSI ecosystem applies to it with no adapter: Achilles and Data Quality Dashboard will characterize and check it, ATLAS cohort definitions written against LOINC will find it, and a multi-site study can include your patients without anyone writing a bespoke translation for your wearable table. That is not a feature PROMOP has to build; it's a consequence of having earned the format. A proprietary unified schema — however clean — buys none of it, and an OMOP ETL written after the fact is exactly where the mapping mistakes described earlier get made, by whoever is least equipped to catch them.

The projection layer compounds the same way in the other direction. Trial criteria, care alerts, and model features are written against median_daily_steps_30d and oxygen_saturation_min_30d — twenty-one stable, documented, pre-aggregated columns — not against a time series plus an aggregation opinion re-implemented in each consumer. Two teams computing "average resting HR over 30 days" from raw samples will disagree about missing days, artifact bounds, and window anchoring, and both will be sure they're right.

These are complementary, not competing

The honest framing is that an aggregator and this architecture sit on opposite sides of the same boundary, and the boundary is three fields wide.

Open Wearables or Spike is a perfectly good Layer 1. An adapter that reads their normalized payload and emits WearableSample(metric_key, date, value) is a small module — the same shape as the Apple and Garmin adapters, minus the file parsing — and it would bring hundreds of devices behind a boundary that already knows what a resting heart rate means. Everything from concept resolution down would be untouched, because nothing below the boundary has ever known where the bytes came from.

What does not come free with either of them, and does not come free with a hand-rolled integration either, is deciding what a vendor's number actually means before assigning it a concept. That work is per-metric, irreducible, and the reason this article spends as much space on HRV as it does on architecture.

The shape of the argument

Consumer wearables produce genuinely useful clinical signal in a format no clinical system can consume. The instinct is to build vendor integrations. The better move is to build one normalization boundary and put every vendor behind it.

PROMOP's boundary is three fields wide — (metric_key, date, value) — and everything past it is OMOP CDM v5.4 with LOINC concepts, artifact bounds, domain-correct routing, and a flat projection with published coverage. Apple and Garmin are behind it today; Fitbit, Whoop, and Oura are the next three, and adding each one means writing one adapter. Nothing else in the system changes: not the concepts, not the tables, not the projection, not the trial query, not the model features.

The one thing that does not get easier with each vendor is the part that never was easy — deciding what a vendor's number actually means before assigning it a concept. That work is per-metric and irreducible, and HRV is the standing proof of it.

That is the whole point. Interoperability isn't achieved by supporting many formats. It's achieved by having one, and being rigorous about what it means.

← Back to Blog
Vision

A Vision for Centralized Patient Data Repositories

Empowering patients, enabling clinicians, and unlocking the full potential of healthcare data

When Your Health Story Lives in Four Systems

When I was diagnosed with follicular lymphoma, my medical information proceeded to be scattered across four healthcare systems and three countries.

My initial labs were done in France, follow-up appointments with my private GP in Scotland, diagnostic work and treatment through NHS Scotland, and then a consultation request with MD Anderson Cancer Center in Houston.

Each institution held part of my story — but none had the complete picture.

When MD Anderson asked for my records, the NHS Subject Access Request could only produce DICOM images of CT and PET-CT scans. There was no structured electronic data for labs, diagnostics, or biomarkers. Despite everything being "digital," the information couldn't actually move.

Even if each provider had given me a data file, I still would have had to collate and interpret dozens of test results, medications, and reports to create a consistent record. Which version of my labs should be considered the truth? How could I ensure continuity of care across systems that don't talk to each other?

That experience crystallized something for me: we need a patient-controlled, centralized repository for health data — one that aggregates structured information from all sources, so both patients and clinicians can make informed, confident decisions.

The Missing Link: A Home for Patient Data

The healthcare industry has spent years working on data interoperability — and progress has been real. The FHIR standard (Fast Healthcare Interoperability Resources) has made it easier for systems to talk to each other.

But FHIR doesn't define where data should live or how it should be assembled into a single, coherent patient record. It standardizes the pipes — not the container.

That's why, despite decades of investment, patients still find themselves chasing PDFs, CDs, or portals that only hold fragments of their health history.

Regional Health Information Exchanges (HIEs) attempt to solve part of this problem, but they remain fragmented by geography and policy. There's an opportunity now — especially with modern privacy controls and cloud architecture — to design something strategic, global, and patient-centric from the ground up.

At HealthKey, we're deeply interested in consuming this kind of unified data repository (with patient authorization) for clinical trial matching. Having a trustworthy, structured record would make it vastly easier to match patients to precision medicine studies quickly and safely.

The Harvard-Radcliffe Initiative

Last week, I joined a Harvard Radcliffe Institute working seminar on building a long-term patient data repository — an effort led by Yuri Quintana, who assembled an extraordinary group of experts in clinical informatics, patient advocacy, and data standards.

Several patients shared their personal experiences. I hadn't expected to tell mine, but Yuri invited me to speak about the challenges of assembling my fragmented records.

The most powerful story came from Betsy Lowe, a mother who maintains Excel spreadsheets to curate the medical histories of several of her children living with chronic illness. Betsy's experience drove home how far we still have to go — and how urgently we need tools that make this easier.

Among the participants were:

  • Alexa McCray, creator of clinicaltrials.gov, the first clinical trials registry, decades ahead of its time
  • Dave deBronkart ("e-Patient Dave"), a pioneer in patient data rights
  • Cait Desroches, who for years has steered the OpenNotes initiative to the success it is today

Their presence underscored the goal: this must be a patient-first repository — something that gives individuals agency over their complete health record and enables clinicians to provide better, safer care.

What a Patient-Centered Repository Could Enable

Our workgroups identified two key principles:

1. Patient Control of Data Creation

  • Patients can grant write access to their providers.
  • They can collate and reconcile records from multiple sources.
  • They decide which version of overlapping data to treat as authoritative.
  • They can enrich their record with lifestyle, environment, and wearable data (diet, exercise, Apple Watch, Fitbit, etc.).

2. Patient Control of Data Usage

  • Patients choose who can see what — providers, caregivers, peers, or navigators.
  • They can delegate access rights to a trusted clinician or family member.

Why It Matters

Imagine a world where your entire medical record simply exists — securely updated by each provider through FHIR feeds, automatically organized, and available when you need it.

For Patients

  • No more managing binders or chasing records.
  • A complete view of your history, medications, and results.
  • Control over what's shared and with whom.
  • The ability to opt in to share anonymized data for research — even earning royalties when your data contributes to discoveries.

For Clinicians

  • More complete data at the point of care.
  • Better diagnostic accuracy and continuity.
  • Less administrative overhead.
  • Stronger patient relationships built on transparency.

For Researchers and AI Developers

  • High-quality, structured data that accelerates discovery.
  • Ability to train models responsibly using consented, representative datasets.
  • When data is complete, AI assistants and navigators can meaningfully support patients and clinicians alike — from interpreting lab results to identifying trial matches or care pathways.

Barriers We Must Overcome

The hardest problems are not technical — they're institutional and cultural. We identified several major barriers:

  1. Provider trust: Clinicians hesitate to use data from other institutions (or patients) for liability reasons. Potential fix: regulations that recognize verified, patient-controlled repositories as trusted sources.
  2. Data ownership: Some institutions resist sharing data for fear of losing patients. Patient-centered systems can strengthen continuity, not weaken it.
  3. Billing and incentives: Providers may prefer repeating diagnostics they can bill for. Solution: policy incentives for data reuse and interoperability compliance.
  4. Error correction: Institutions are reluctant to correct legacy data. Solution: feedback mechanisms that log corrections transparently and return them to source systems.

Our collective answer to skepticism that "patients aren't competent to manage their own records" was simple: if not the patient — who?

Defining Success: SMART Goals

We converted our vision into specific, measurable goals. Among them:

  • Patient signups: 13 million within five years of launch (about 5% of U.S. adults — a proven tipping point for adoption).
  • Engagement: Each user logs at least one session per year within two years.
  • Referrals: 5% of users refer peers within two years.
  • Satisfaction: Two-thirds of users rate the service as valuable.
  • Institutional participation: 20% of FHIR-adopting providers push data within five years.
  • Carer enablement: 5% of users have linked caregiver accounts.

Speaking to Stakeholders

Different audiences have different motivations, and we defined simple, focused messages for each:

  • Motivated Patient: "Optimize your health with the full picture of your data. Choose what's shared, to whom, and for what."
  • Caregiver: "Helping someone manage their health is hard enough. We simplify the information part."
  • Clinician: "Caring for the whole patient just got easier."
  • Hospital: "More complete data, better outcomes."
  • Funder (Tech/Data): "Accelerate an ecosystem built on high-quality, consented patient data."
  • Funder (Social Good): "Empower patients and families to take charge of their health."

Milestones Toward Delivery

We proposed a phased roadmap — each milestone is immediately useful and adds tangible value over the previous one:

  1. FHIR-Importing Mobile App: A smartphone app that aggregates data from providers via FHIR push feeds. Data can live only on the device if patients prefer.
  2. Web-Based Portal: Adds a hosted version for patients comfortable with cloud access.
  3. Patient-Provided Data: Support for device data, PDFs, and manual uploads.
  4. AI Assistant Integration: An LLM-powered assistant helps patients structure, reconcile, and correct data.
  5. Bidirectional Data Exchange: Patients can send updates or corrections back to providers (optional for acceptance).
  6. Anonymized Data Sharing: Patients can opt to share de-identified data for research — and receive royalties when used by for-profit entities, funding sustainability for the non-profit repository.

Building It: Open and Collaborative

Such a repository should be built and governed by a trusted non-profit, ideally within the Harvard DCI Network.

At HealthKey, we've already begun developing a foundation for this through our open-source project PRomop — available on GitHub.

PRomop extends the OMOP schema to include richer genomic data (such as assay information) and provides a flattened table structure optimized for clinical trial matching and predictive modeling. It's fully open source and designed for community collaboration through the DCI Network.

While our initial use case is trial matching, the schema supports broader applications: building predictive models for standard-of-care outcomes, powering AI research, and supporting long-term population health studies.

The Road Ahead

A unified, patient-controlled data repository isn't just a technical challenge — it's a moral and practical imperative. Every day, patients lose time, clarity, and even treatment opportunities because their data lives in silos.

We now have the standards, the technology, and the collective will to fix that. What we need next is collaboration — across patients, clinicians, technologists, and policymakers — to make it real.

The future of healthcare data belongs to those who share it responsibly. Let's build the infrastructure that makes that possible.

← Back to Blog
Data Model

Towards a Common Patient Information Schema

What a unified, patient-centric data model could look like for breast cancer trial matching

We talked in a recent post about centralized patient data repositories. Let's dig deeper into what such a common repository might actually look like. CancerBot, as a clinical trial matching product, is deeply focused on the structured data required for accurate clinical trial eligibility. That scope gives us a concrete testbed for assessing whether a given data schema is sufficient for real-world matching.

Of course, a broader ecosystem will eventually need far more: patient-generated health data, imaging, genomics beyond trial eligibility, quality of life metrics, wearable feeds, and more. But for now, the breast cancer trial-matching use case gives us a clean, bounded first iteration. Future versions will extend naturally to follicular lymphoma, multiple myeloma, and other blood cancers supported by CancerBot.

With that in mind, here is what a patient information schema could look like for breast cancer clinical trial matching.

1. Core Demographics

The foundation of any patient schema begins with the universal identifiers and sociogeographic context that trials commonly use in eligibility logic:

  • Age, gender, ethnicity
  • Country, region, postal code
  • Latitude/longitude (for distance-to-trial-site calculations)

These may seem mundane, but they have real implications. Many trials restrict enrollment to specific countries or regions. Others score travel burden or require reasonable proximity to a clinical site. Even ethnicity can sometimes influence eligibility when germline risk or pharmacogenomic variants are relevant.

2. Physiologic Measurements

Real, quantifiable values that describe the patient's physiology — almost always required for baseline safety assessments:

  • Height, weight, BMI
  • Blood pressure, heart rate
  • Ejection fraction (cardiac function)
  • QTc interval (cardiac conduction risk)
  • Pulmonary function test summaries

These values ensure a patient can safely receive the investigational therapy. QTc is crucial for certain targeted therapies; ejection fraction is mandatory for HER2-directed therapy trials.

3. Clinical Status

This category describes the cancer itself and the patient's functional ability to undergo treatment:

  • Disease (breast cancer subtype or diagnosis code)
  • Stage (AJCC staging)
  • ECOG and Karnofsky performance status

This is where most trial logic starts. Nearly every breast cancer trial is written around stage (e.g., metastatic vs. early) and functional status (e.g., ECOG ≤ 1).

4. Medical History

Trial protocols depend heavily on past and existing health conditions, because comorbidities directly affect safety:

  • Other active malignancies
  • Pre-existing conditions (cardiac, autoimmune, neurologic)
  • Neuropathy grade
  • HIV, hepatitis B/C status
  • Prior interstitial lung disease (ILD), prior pneumonitis, and ILD grade
  • Geographic and infectious exposure risks

Breast cancer trials increasingly exclude patients with prior ILD or pneumonitis due to the pulmonary toxicity risks of antibody–drug conjugates (ADCs). Viral infection status remains essential for immunotherapy safety.

5. Hematology, Renal, and Hepatic Diagnostics

The backbone of systemic therapy eligibility — these values appear in nearly every inclusion/exclusion section. The schema must store not only the values but also the units, since matching logic commonly breaks when unit conversions are missing:

  • Hematology (CBC): ANC, platelets, WBC, RBC, hemoglobin
  • Renal function: creatinine clearance, serum creatinine, eGFR
  • Hepatic function: AST, ALT, ALP, total and direct bilirubin, albumin
  • Electrolytes & minerals: serum calcium

6. Treatment History

Breast cancer therapy is highly line-dependent; trial matching must know exactly what a patient has received and whether they responded:

  • Treatment lines: first-line, second-line, later-line therapies with dates and outcomes (PR, PD, intolerant)
  • Supportive care: bisphosphonates, steroids, G-CSF — important but not counted as systemic lines
  • Disease course: relapse count, remission duration
  • Refractory status: endocrine-refractory, CDK4/6-refractory, ADC-refractory
  • Safety & washout: washout duration, persisting toxicity grade (per CTCAE)
  • Prior ADC exposure — a major eligibility gate for modern trials

Without a structured, accurate treatment history, matching is guesswork.

7. Behavioral & Reproductive Safety Factors

Often overlooked, but central to trial safety and compliance:

  • Consent & cognitive status: consent capability, mental health considerations, caregiver availability
  • Reproductive safety: pregnancy or lactation, pregnancy test results, contraception use
  • Substance use: tobacco, alcohol or other substances
  • Exposure risk: geographic exposure (TB, fungal diseases), occupational exposure (healthcare, lab, industrial toxins)

Putting It All Together

What emerges is a coherent, structured, interoperable patient information schema that supports breast cancer trial matching at a level of precision that older approaches simply cannot achieve. A schema like this:

  • Captures the full clinical picture needed for safe and accurate matching
  • Maps cleanly to OMOP and FHIR
  • Is extensible to additional cancers
  • Can support broader patient-data repositories beyond trial matching
  • Enables true eligibility filtering — not guesswork or vignette-based ranking
← Back to Blog
Architecture

PRomop: Extending OMOP to Be a Comprehensive Transactional and Longitudinal Patient Record

We have discussed earlier a vision for centralized patient database repositories. One of the many use cases for a truly comprehensive common patient record is doing the precision trial matching that CancerBot performs. So we built PRomop, which builds on the solid foundation of OHDSI OMOP's relational database schema but adds things necessary for true precision clinical trial matching, and starts to add many of the other things needed by other use cases such as evaluating standard of care options and capturing a truly global picture of the patient. The "PR" in PRomop stands for "Patient Record". This post presents a summary of the architecture of PRomop and describes how it can be used as a solid underlying patient data store for other use cases you may have in mind.

Why Start With OMOP?

The Observational Medical Outcomes Partnership (OMOP) Common Data Model, stewarded by OHDSI, is the de facto standard for normalizing observational health data. It gives us a battle-tested relational schema, a rich set of standardized vocabularies (LOINC, SNOMED CT, RxNorm, ICD-O-3), and an enormous ecosystem of analytical tooling. Starting from OMOP means we inherit interoperability with that entire ecosystem for free — any cohort analysis, phenotyping, or population-level study that runs on OMOP runs on PRomop.

But OMOP was designed primarily for retrospective, population-scale research. It does a beautiful job of answering questions like "how do patients on drug X compare to patients on drug Y over time?" What it doesn't do quite so gracefully is answer the question CancerBot has to answer every day: "Given everything we know about this specific patient, right now, which clinical trials could they qualify for, and which would be best for them?"

That question is transactional (it has to be answered in seconds, not hours), longitudinal (it depends on the complete arc of the patient's disease and treatment), and precision-oriented (it turns on dozens of specific biomarker, staging, and behavioral attributes that OMOP doesn't surface directly). PRomop is our attempt to make OMOP answer that question without breaking its compatibility with the broader OMOP world.

The Architectural Idea

PRomop is organized around a simple principle: all clinical data lives in standard OMOP tables, using standard vocabularies. We don't create a parallel universe of custom tables for biomarkers, treatment lines, or social factors. Instead:

  • Biomarkers (ER, PR, HER2, PD-L1, Ki-67, genetic mutations) go in the standard Measurement table, keyed by LOINC concepts.
  • Lab values (hemoglobin, creatinine, calcium, AST/ALT, and the rest) go in Measurement, also keyed by LOINC.
  • Vital signs and anthropometrics go in Measurement.
  • Social determinants and health behaviors go in Observation, keyed by SNOMED CT.
  • Cancer staging — T, N, M, stage group, grade, primary site, histology — goes in Observation with ICD-O-3 and SNOMED concepts, using the CDM v5.4 observation_event_id to link back to the underlying ConditionOccurrence.
  • Treatment response goes in Observation with SNOMED concepts.
  • Treatments go in DrugExposure, and treatment lines are derived from drug exposure patterns rather than stored as a separate denormalized entity.
  • Cancer episodes use the official OMOP Oncology Extension: Episode, EpisodeEvent, CancerModifier, Histology, StemTable.

The net effect is that every clinical fact in PRomop is readable by any off-the-shelf OMOP tool, and any dataset already in OMOP can be loaded into PRomop without transformation. The project has been explicitly refactored to remove earlier non-standard extension tables in favor of standard tables with the right vocabulary concepts.

The PatientRecord Projection

This is the workhorse of PRomop for transactional use cases. PatientRecord is a wide, denormalized, single-row-per-patient view — over 100 fields — that aggregates the information a trial-matching engine actually needs to reason about a patient, pulled from the standard OMOP tables underneath. It is explicitly not a new place to store clinical data. Nothing lives only in PatientRecord. It is a materialized, research-friendly projection populated by a management command (populate_patient_info) that walks Person, Measurement, Observation, DrugExposure, ConditionOccurrence, and the oncology extension tables.

Why bother? Because joining across six or seven OMOP tables and resolving LOINC/SNOMED/ICD-O-3 concepts every time CancerBot evaluates a patient against a trial's eligibility criteria is slow and query-heavy. When a patient loads their dashboard and we score them against thousands of candidate trials in real time, we need single-row, indexed access to their clinical profile. PatientRecord gives us that, without sacrificing the source-of-truth status of the OMOP tables.

What's Actually In There

The PatientRecord shape is expressed as a set of tabs in the front-end admin portal:

  • General — demographics, geography, language, disease, stage, performance status (Karnofsky and ECOG), comorbidity flags.
  • Disease-specific tabs (e.g., Multiple Myeloma, Follicular Lymphoma) — cytogenetic markers, disease-specific staging, CRAB/SLiM criteria, progression markers.
  • Treatment — prior therapy, first/second/later-line therapies with dates and outcomes, stem cell transplant history, refractory status, relapse count.
  • Blood / Liver / Labs — the full complement of lab values with proper units, computed derived values like eGFR, and disease-relevant composites like serum free light chains.
  • Behavior — consent capability, caregiver availability, contraceptive use, pregnancy/lactation status, language skills, tobacco and recreational drug use, occupational and environmental exposure risk.

Every one of those fields is, underneath, an aggregation over standard OMOP rows with standard concept codes. That's the whole architectural trick.

Getting Data In: FHIR

OMOP is great for analysis but it's not the format in which patient data typically arrives. Modern EHRs and health information exchanges speak FHIR R4. PRomop ships with a FHIR Bundle upload endpoint: you POST a FHIR R4 Bundle, the loader walks the resources (Patient, Observation, Condition, MedicationStatement, MedicationAdministration, DiagnosticReport, etc.), maps each to the appropriate OMOP table, resolves or creates the right concept IDs, and then triggers a refresh of the patient's PatientRecord row. There's also a synthetic FHIR patient generator for testing, which produces realistic oncology bundles including biomarker panels and treatment histories.

How PRomop Supports Precision Trial Matching

The payoff is that eligibility evaluation becomes tractable. A typical oncology trial eligibility rule — "ECOG ≤ 1, hemoglobin ≥ 9 g/dL, adequate renal function (creatinine clearance ≥ 60 mL/min), HER2-positive, no active Hep B, at least one prior line of therapy, able to consent in English or Spanish" — translates to a query over a handful of indexed fields on PatientRecord. We can score thousands of trials against a patient in real time, present the results, and let the patient and their care team explore why they matched or didn't match each one. When we need the audit trail — "where did this HER2 status come from, exactly?" — we drop back to the OMOP tables underneath and pull the source Measurement row with its date, provider, and concept.

Beyond Trial Matching

Trial matching is the forcing function, but the architecture generalizes. A few use cases PRomop serves well out of the box:

  • Standard-of-care evaluation. Given a complete, coded treatment history and disease state, you can check a patient against evidence-based guideline trees (NCCN, ESMO) in the same way you check them against trial criteria. The required data is the same; only the rule base differs.
  • Longitudinal patient journey visualization. Because episodes, drug exposures, measurements, and observations all carry dates, you can reconstruct the full arc of a patient's disease and treatment for display, summarization, or LLM input.
  • Cohort discovery and phenotyping. This is OMOP's native strength, and PRomop inherits it — any existing OHDSI phenotype library or cohort tool works against the standard tables.
  • Clinical decision support. The coded, denormalized PatientRecord is a natural feature vector for ML models and a natural input for retrieval-augmented LLM workflows.
  • Patient-facing summaries. Because the data is coded rather than just transcribed, you can generate grounded, non-hallucinated patient summaries where every claim traces back to a structured source row.

A Few Practical Notes

PRomop is a Django 5 + Django REST Framework backend with a React/TypeScript front-end, backed by PostgreSQL, with Docker and Render deployment configurations included. The codebase is organized into three Django apps:

  • omop_core — standard OMOP CDM core tables plus the PatientRecord integration model.
  • omop_oncology — the standard OMOP Oncology Extension models.
  • omop_genomics — a placeholder for future genomic-extension work; genomic data currently lives in Measurement and Observation with LOINC concepts.

Migrations use the SeparateDatabaseAndState pattern with idempotent IF NOT EXISTS SQL, which has proven necessary when managing a production database that drifts from Django's migration state — a realistic concern for any long-lived health data system. The project is open source at github.com/healthkey-ai/promop.

Closing Thought

A lot of what goes wrong in health data tooling is the temptation to throw away the standard and build something bespoke because the standard doesn't fit your specific use case perfectly. PRomop is our argument for the opposite move: keep the standard, use its vocabularies religiously, add the minimum extension surface your use case genuinely requires, and solve the performance problem with a well-defined denormalized projection rather than a parallel schema.

The result is a patient record that is simultaneously a first-class OMOP citizen and a fast, transactional substrate for precision applications like CancerBot — and, we hope, for whatever you want to build on top of it next.

PRomop is open source

The full codebase is available at github.com/healthkey-ai/promop. Issues, PRs, and extension proposals welcome.

← Back to Blog
Open Source

EXACT: An Open-Source Precision Clinical Trial Matcher Built on OMOP

Matching a cancer patient to a clinical trial sounds like it ought to be a solved problem. In reality, most matching systems are either (a) proprietary black boxes buried inside a sponsor's CTMS, or (b) brittle keyword searches over ClinicalTrials.gov that miss the clinical nuance that determines whether a patient is actually eligible. EXACT — the open-source matcher maintained at github.com/healthkey-ai/exact — takes a different approach: it treats eligibility matching as a structured-data problem against a patient model that was designed, from the ground up, to be matchable.

This article walks through what EXACT does, the data substrate it sits on, and why the combination produces results that a keyword matcher can't.

The core idea

EXACT is an eligibility-matching engine. You give it a patient (a structured clinical record) and a set of trials (structured eligibility criteria), and it returns which trials the patient qualifies for, which they don't, and — critically — why.

The "why" is the part most matchers skip. A useful match result isn't a ranked list; it's a per-trial trace: this criterion passed because the patient's ANC is 1.8 × 10⁹/L and the threshold is ≥1.5; that criterion failed because the patient has had three prior lines of therapy and the trial caps at two; this other one is indeterminate because we don't have a current ECOG score on file. Patients and navigators act on reasons, not scores.

To produce that kind of output, the matcher has to reason over the same vocabulary that the eligibility criteria use. That is where the rest of the stack comes in.

The patient model: PRomop

EXACT does not parse patient records. It is a stateless matching engine: it receives a structured patient profile — the same field set that the PRomop project defines — and the trial catalog it matches against, then returns verdicts. Nothing about the patient is persisted inside EXACT itself; the only local state it keeps is authentication. PRomop (Patient Record OMOP) is the patient database that produces those structured profiles. It's built on the OMOP Common Data Model v6.0, extended with oncology-specific tables and a denormalized projection called PatientRecord that flattens the clinical picture into the 266 fields an eligibility engine actually needs.

The split matters. OMOP is excellent for storage, interoperability, and longitudinal analytics — conditions, drug exposures, measurements, observations, all coded against SNOMED / LOINC / RxNorm. But eligibility criteria aren't written in OMOP. They're written in statements like "ECOG performance status ≤ 2," "no more than two prior lines of therapy," "triple-negative breast cancer," "absolute neutrophil count ≥ 1.5 × 10⁹/L," or "measurable disease per RECIST 1.1." To evaluate those, you need both the raw coded events and derived clinical judgments that roll up from them.

PRomop does both. The underlying OMOP tables hold the ground truth: condition_occurrence, measurement, observation, drug_exposure, plus oncology extensions (omop_oncology.Episode, EpisodeEvent, AILineOfTherapySummary). The PatientRecord model exposes a flattened, eligibility-ready view of that ground truth, with a number of fields computed automatically on save:

  • therapy_lines_count — count of non-empty first / second / later therapy fields.
  • prior_therapy — categorical, in the exact vocabulary EXACT matches against: "None," "One line," "Two lines," or "More than two lines of therapy."
  • treatment_refractory_status — derived from the sequence of per-line outcomes: zero negative outcomes means "Not Refractory"; one means "Primary"; two means "Secondary"; three or more means "Multi-Refractory."
  • relapse_count — counts successful outcomes (CR / sCR / VGPR) followed by a new line of therapy, unless manually overridden.
  • measurable_disease_imwg — applies IMWG criteria on M-protein and free light chains to decide whether the patient has measurable disease by the myeloma standard.
  • tp53_disruption — true iff any entry in the patient's genetic_mutations list has gene = TP53 and interpretation = pathogenic.
  • lymphocyte_doubling_time — log-linear fit on serial absolute lymphocyte counts.
  • bmi, patient_age — unit-aware derivations from weight/height and date of birth respectively.

These aren't cosmetic conveniences. Every one of them corresponds to a phrasing that appears in real oncology eligibility criteria. By computing them once, in the patient database, with explicit rules that clinicians can audit, EXACT gets to do simple field comparisons instead of re-deriving clinical judgments inside the matcher. The derivation logic lives in one place; the matcher stays boring (in the best possible way).

The eligibility substrate

On the other side of the match is a structured trials database — one row per trial, with columns for every eligibility attribute the matcher knows how to evaluate. Each column corresponds to a PatientRecord field or a derived one, so matching becomes a per-criterion comparison rather than a per-trial free-text read.

Concretely, the trials table carries columns for:

  • Cancer type and subtype — histology (e.g., IDC vs. ILC for breast), disease-specific constraints.
  • Receptor and biomarker status — ER, PR, HER2, HR, TNBC, PD-L1 (tumor cells, IC%, CPS), HRD. Most of these are tri-valued (positive / negative / unknown), because eligibility routinely requires "unknown" to be resolved before enrollment.
  • Staging — T, N, M, overall stage, and disease burden flags like measurable_disease_by_recist_status, bone_only_metastasis_status, metastatic_status.
  • Prior therapy — allowed / required / excluded lines, specific agent exposures (prior_exposure_flags), and categorical line counts in the EXACT vocabulary above.
  • Refractory / relapse state — required or excluded values of treatment_refractory_status and relapse_count.
  • Lab thresholds — hematology (ANC, platelets, hemoglobin), renal (creatinine, eGFR), hepatic (AST, ALT, bilirubin, albumin), electrolytes, coagulation, LDH, inflammation markers, cardiac markers. Each threshold is stored with its unit so UCUM conversions happen explicitly.
  • Genomic requirements — specific mutations or mutation classes (BRCA1/2, PIK3CA, ESR1, TP53 disruption, etc.), using the same structured mutation schema the patient model uses.
  • Performance and eligibility status — ECOG, Karnofsky, consent capability, cognitive status, reproductive safety.
  • Exclusions — concurrent malignancies, active infections (HBV / HCV / HIV status), washout requirements.
  • Administrative constraints — age range, geography (trial sites within a reachable radius), language.

The trials table is the schema-side mirror of PatientRecord. That symmetry is what makes the matcher tractable.

What the matcher actually does

Given that substrate, EXACT's job decomposes into four things:

1. Attribute-level evaluation

For each (trial, criterion) pair, EXACT compares the trial's stored requirement against the patient's field. Numeric thresholds use unit-aware comparison (a threshold expressed in ×10⁹/L matches a patient value stored as cells/µL). Categorical fields use the trial's allowed-set semantics. Boolean flags get straightforward logic. Missing patient data produces an indeterminate result, never a silent pass.

2. Criterion-level composition

Real eligibility is rarely a single field. "ANC ≥ 1.5 AND platelets ≥ 100 AND no prior anti-PD-L1 exposure" is three attribute comparisons ANDed together; "measurable disease by RECIST OR bone-only metastatic disease with evaluable marker" is a disjunction over two derived flags. EXACT composes attribute results with explicit boolean logic, and propagates indeterminacy correctly — an AND with any fail is a fail, but an AND with a pass and an indeterminate is still indeterminate.

3. Trial-level verdict

EXACT assigns each (patient, trial) pair one of three states: Eligible if all inclusion criteria pass and no exclusion criterion triggers; Ineligible if any inclusion fails or any exclusion fires; Potential if the only thing blocking the verdict is missing data on a small number of criteria. The Potential state is the one that matters most in practice — it's the matcher's way of saying "you would qualify for this trial if you also had a recent ECOG score on file," and it's what turns a match engine into a useful data-collection prompt rather than a binary gate.

4. Explanation

Every verdict comes with the criterion-by-criterion trace that produced it. This is what makes EXACT auditable: a patient navigator can see exactly which criterion knocked the trial out, point at the underlying PatientRecord field, and decide whether it's wrong (data quality issue), stale (need a fresh lab), or correct (genuinely ineligible).

The vocabulary contract

One detail deserves its own mention. The categorical fields — prior_therapy, treatment_refractory_status, relapse states, receptor statuses — all use controlled vocabularies that are shared between the patient model (PRomop) and the trials model. The schema documentation explicitly calls these "EXACT & CB matcher vocabulary" values.

This is the boring infrastructure work that makes the whole thing go. Without it, you end up with patient records saying "2 prior lines" and trial records saying "≤2 prior lines of therapy" and a matcher desperately trying to bridge them with regex. With it, both sides store the same enum values, comparison is a dictionary lookup, and the code stays short.

FHIR as a boundary, not a pivot

EXACT matches against PRomop, not FHIR. But PRomop itself ingests from FHIR — the documented ETL pipeline validates, deduplicates, and lands FHIR resources into the OMOP tables that back PatientRecord. The schema spreadsheet carries the FHIR mapping for every field, which means a partner system can stream FHIR bundles in at the boundary, and EXACT can match against the resulting structured record without ever itself parsing a Bundle or traversing a Patient.telecom[system=email] path.

The separation of concerns is the point. FHIR is the interchange format. OMOP is the storage and analytics substrate. PatientRecord is the eligibility projection. EXACT is the matcher. Each layer does one job, so each layer stays debuggable.

Running EXACT

EXACT ships as a Django application that can run in two modes: as a REST API server, or as a one-shot batch matcher driven by a shell script. Both modes share the same matching core; they differ only in how the patient profile gets in and how the results come out.

Server mode

Clone the repo, install dependencies, migrate the local auth database, and point it at your trials catalog:

pip install -r requirements.txt
python manage.py migrate
export TRIALS_DATABASE_URL=postgresql://readonly:secret@trials-db.example.com:5432/trials
python manage.py runserver

In this mode, patient profiles are passed inline with each API request — nothing patient-related is persisted by EXACT. Authentication tokens are the only local state. The full REST reference lives in docs/api.md.

If no external trials database is configured, EXACT falls back to a single local database for everything, which is handy for development. Seed reference data with python manage.py seed_reference_data before running.

Batch mode

To match a batch of patients directly from the patient database against the trial catalog — no web server, no HTTP — use the trials4patients.sh script:

export TRIALS_DATABASE_URL=postgresql://...
export PATIENT_DATABASE_URL=postgresql://...
bash scripts/trials4patients.sh

The useful environment variables:

  • TRIALS_DATABASE_URL (required) — remote trials PostgreSQL database.
  • PATIENT_DATABASE_URL (required) — patient database (PRomop).
  • PERSON_IDS (default: all) — comma-separated PRomop person IDs to process.
  • PATIENT_LIMIT (default: all) — cap on number of patients.
  • SEARCH_LIMIT (default: 20) — top N trials returned per patient.
  • RESULTS_CSV — if set, writes an evaluator-ready CSV to this path.

Evaluating results

EXACT ships with an evaluator that scores a run's output against a ground-truth CSV. Both files use the same four-column schema:

PRomop Patient ID,Trial,Eligible/Potential,Suitability Score
20291,NCT03452774,potential,81
20291,NCT07038785,eligible,79

Trials that EXACT considers ineligible are simply absent; the CSV records only the positive-verdict trials, ranked. The typical evaluation run has three steps:

  1. Extract the patient cohort from the ground truth:
    PERSON_IDS=$(tail -n +2 scripts/evaluator/ground_truth.csv \
      | cut -d',' -f1 | sort -u | tr '\n' ',' | sed 's/,$//')
  2. Run the matcher and write a results CSV:
    PERSON_IDS="$PERSON_IDS" \
    SEARCH_LIMIT=5 \
    RESULTS_CSV=results.csv \
    bash scripts/trials4patients.sh
  3. Score the results:
    bash scripts/evaluator/evaluate.sh \
      scripts/evaluator/ground_truth.csv \
      results.csv

The evaluator computes recall, precision, F1, type_match_rate, score_match_rate, score_MAE, score_bias, MRR, and avg_rank — micro-averaged across the cohort. Add --output /tmp/comparison.json to get the full per-trial breakdown.

One nuance worth noting: SEARCH_LIMIT is not just a performance knob — it changes the shape of the penalty metric. A lower top_n makes the penalty for missing trials harsher, because a trial ranked just outside the returned window is treated the same as one EXACT doesn't return at all.

When recall drops, the cause is one of two things. If the expected trial exists in the catalog but ranked below top_n, it's a ranking problem — raise SEARCH_LIMIT. If the expected trial isn't in the catalog at all, it's a coverage gap. The quick check:

python manage.py shell -c \
  "from trials.models import Trial; print(Trial.objects.filter(code='NCT03452774').exists())"

Why this design holds up

Trial eligibility is unforgiving in a very specific way: a false positive means a patient is told they qualify for a trial they don't, which wastes everyone's time and erodes trust; a false negative means a patient never hears about a trial that could have helped. Both failure modes trace back to the same root cause: mushy data. Free-text eligibility, scraped keywords, inferred labs, uncontrolled vocabularies — any one of them is enough to make the output unreliable.

EXACT's answer is to push the mushiness out of the matcher. The matcher only does comparisons. The clinical derivations happen in PRomop, with explicit rules. The vocabulary is controlled on both sides. The thresholds are unit-aware. The explanation is first-class. The whole system is open source, which means the derivations are inspectable by the clinicians whose judgments they encode.

For an oncology population where eligibility criteria run thirty lines deep and a single missing lab can flip the verdict, that discipline is the difference between a trial matcher that clinicians actually use and one that gets demoed twice and quietly retired.

EXACT and PRomop are maintained at github.com/healthkey-ai under open-source licenses. The patient schema described in this article — including FHIR and OMOP mappings for all 266 PatientRecord fields — is published alongside PRomop as the interoperability reference.

← Back to Blog
Patient Voice

What Patients Taught Us About Trial Matching — and Where EXACT Goes Next

At a recent pre-conference roundtable on clinical trial matching, hosted as part of Harvard's Patient-Powered Digital Health Conference, we did something we try to do as often as possible at HealthKey: we stopped talking and listened. The room was full of patients and advocates who had lived the experience of searching for a trial — and what they told us reshaped how we think about the problem.

It also clarified something about the matcher we build in the open, EXACT. EXACT already does the hard part well: it reads a trial's eligibility criteria, evaluates them against a patient's longitudinal record, and returns a per-criterion verdict — eligible, potential, or ineligible — with the reasoning attached. That transparency is deliberate, and it's a real advance over the opaque, ranked-list approach most matchers take. But the roundtable made one thing unmistakable: matching a patient to an eligible trial, done perfectly, still leaves a large part of the patient's actual problem unsolved.

This post is about that gap, and about why we think the next chapter of trial matching — the one we're building toward — looks very different from what the category contemplates today.

The real problem isn't discovery. It's the decision.

The sharpest insight from the room was that finding eligible trials is necessary but nowhere near sufficient. One participant described enrolling in a trial without ever comparing it to standard of care or comprehensively searching for alternatives — not out of carelessness, but because nothing in the system is built to help a patient make that comparison. The information needed to decide well is scattered across portals, PDFs, and conversations, and no tool brings it into one place where a patient and their clinician can weigh it together.

This is the part of the problem most trial matchers don't even attempt. They answer "which trials could I join?" The patients in that room were asking a harder question: "given everything I value, which of my options — trials and standard of care — is right for me?"

For EXACT, this points to a clear direction. Eligibility is the foundation, not the finish line. The same patient record that EXACT uses to evaluate trial criteria can evaluate standard-of-care options on identical footing — which means the natural next step is presenting trials and standard of care side by side, as comparable choices rather than separate searches. We're fortunate that the architecture underneath EXACT already evaluates both against one structured record, so this is an extension of what we've built, not a departure from it.

Values are not a footnote. They're the rubric — and the community should write it.

Everyone agreed that the right choice depends on the patient's own priorities — and then they pushed further, in a way that genuinely sharpened our thinking. The usual framing of "risk, benefit, and burden" is too coarse. Burden, one participant pointed out, isn't even a fixed quantity: what counts as burdensome varies enormously from one patient to the next. The true cost of travel is routinely underestimated by the trials industry. Preferences seem to carry less weight for older patients. And benefits get undersold too — the value of traveling to join a community of patients facing the same disease is real, and almost never communicated.

The most important thing anyone said all afternoon was this: the patient voice has to help define the rubric itself, not merely answer it.

EXACT already takes patient values seriously. It performs patient-centered multi-criteria decision analysis — weighing risk, benefit, and burden — to turn a set of eligible options into something a patient can actually reason about. That's a meaningful step beyond a yes/no eligibility list. But the roundtable convinced us those rubrics can be much better, and that the people who can make them better aren't us.

The dimensions that matter — what burden really means, how travel weighs against benefit, when community and cohort value tip a decision, how preferences should be honored regardless of a patient's age — are not best decided in a vacuum by engineers. They're best decided by patient support groups and advocates who live the disease, optimized per disease, with all the factors that came up in that room, and with the care and attention only those communities can bring. So the direction we're committed to is this: rubrics co-developed with patient foundations and advocacy groups, tuned disease by disease, and published openly so that anyone can use them — in a digital tool or with a pen and a conversation. A good rubric shouldn't be locked inside one piece of software.

Where we'll keep leading is integration. We'll take these community-optimized rubrics and build them directly into EXACT's recommendations as suitability scores, so the matcher doesn't just tell a patient what they're eligible for — it reflects what their own community has decided matters most. And because the primary users of EXACT are exactly those foundations and patient support groups, we're positioned to do something no general-purpose matcher can: foster fast feedback and rapid iteration with the very groups authoring the rubrics, refining the scoring continuously as they learn. The rubric improves, the scores improve, the matches improve — in a loop that runs at community speed.

No trial matcher we know of is building toward this. We are.

Ask for the right thing, at the right time

A practical insight reshaped how we think about sequencing. A long preferences instrument is a burden if you hand it to someone before they know whether any trial is even open to them. The better order is the obvious one once you hear it: determine eligibility first — cheap, structured, fast — and then do the deeper, more personal work of weighing options only across the trials that are genuinely viable.

This fits EXACT naturally. Eligibility filtering is exactly the kind of fast structured pass EXACT already performs; the preference work belongs downstream, on the smaller surviving set. It keeps the heavier instrument off the critical path and respects the patient's time.

There's a deeper principle underneath this, which one participant captured better than we could have: utility drives trust, trust drives more information, and more information drives better matches. Patients share data when they can see it improving their care. This is why EXACT's "potential" verdict matters — when a match hinges on one missing piece of information, telling the patient exactly what's missing and why it matters turns a dead-end into a reason to engage. Branching, purpose-aware data collection that visibly unlocks better answers is the mechanism that earns the trust the whole system runs on.

Transparency about data is a feature, not fine print

One of the most actionable observations was about data itself. A participant described being asked for far more data than a trial could plausibly need, with no explanation of what was actually required — and a consent document that didn't even state her data would be de-identified. The opportunity here is real and largely unexploited: scoped, transparent data-usage agreements that say plainly what a study needs, limit use to the agent or procedure in question, and are explicit about de-identification — while still leaving room for the legitimate science of novel sub-cohort analysis.

This sits squarely inside HealthKey's founding conviction that patients should own and control their health data. It's an area we're actively thinking through as the patient-facing side of trial matching matures. We'd rather treat data scope as something a patient understands and controls than as something buried in a form.

Communities, not just individuals

Finally, the room kept returning to the power of patient disease communities. The idea that resonated most was patient groups banding together on a per-disease basis to define what good looks like — what the priorities are, what burden means, what benefit looks like for their disease. This maps directly onto how we already work: our deepest deployments live inside disease communities, and we think the right operating model is one where HealthKey builds the framework and the communities themselves populate the priorities that matter to them. The rubric, in other words, gets written by the people it serves.

There's also an honest boundary worth naming. Today the system is largely not set up for patients to drive their own trial search — enrollment usually still routes through a physician, and the friction is significantly worse for patients outside the United States. We don't have a complete answer to that yet. But building an open, patient-facing matcher is a deliberate step toward a world where patients have far more agency than they do now.

A note on rare disease

One caution shaped everything above. In rare disease, there often isn't an accepted standard of care to compare against — which means the whole "trial versus standard of care" framing can break down. Any comparison we build has to degrade gracefully when there's no comparator, never assuming standard of care exists. It's a reminder that the patients with the fewest options are often the ones the tooling serves worst, and they're exactly the ones we most want to serve well.

Where this leaves us

None of this is a critique of where EXACT stands today. Transparent, per-criterion, open-source trial matching with patient-centered MCDA across multiple cancers is already further than most of the field has gone, and we're proud of it. What the roundtable gave us is a map of the territory beyond it — the trial-versus-standard-of-care decision, the right-time data ask, scoped and transparent consent, and above all community-authored, per-disease rubrics that anyone can use and that we integrate into our suitability scores through a fast feedback loop with the foundations we serve.

That territory is where trial matching is going, and very little of the category is even looking in that direction. We are. We're building it in the open, and we're building it with patients — because, as that afternoon made clear, they understand the problem better than anyone.

EXACT is open source and available at github.com/healthkey-ai/EXACT. If you're a patient advocate, clinician, or researcher who wants to help shape the work described here, we'd like to hear from you.

← Back to Blog

Get in Touch

Let's connect

Whether you're a patient, a clinician, a researcher, or a potential partner — we'd love to hear from you.

Or email us directly at support@healthkey.ai

Request Early Access or a Demo

Fill in your details and we’ll be in touch soon

Engineering

Getting Started with PHRAME

A data scientist's guide to patient-centered health infrastructure.

If you work with patient data — as an informaticist, a data scientist at a research foundation, or an engineer building health tooling — you already know the shape of the problem. The records are fragmented across providers. The standards are real but the mappings are manual. And the gap between "we have data" and "we can act on it for an individual patient" is wide and mostly hand-built.

PHRAME is an open-source suite from HealthKey designed to close that gap. It is infrastructure, not an end-user product: a set of components you assemble into a longitudinal, standards-based patient record and then build on. This post walks through how to go from a pile of patient data to working trial matching, outcomes analytics, and standard-of-care suggestions — using five projects that fit together.

Everything is available at github.com/healthkey-ai. The projects are designed to be adopted incrementally: you can start with the database alone and add the rest as you need them.

The Shape of the Suite

Before the steps, the mental model. PHRAME is organized as a pipeline with a storage layer in the middle and a serving layer on top:

  • PRomop — the patient database. A standards-based longitudinal store built on the OMOP Common Data Model (CDM 5.4), with oncology extensions. This is where every patient record lives.
  • PRism — the analytics platform that sits over PRomop, for population and cohort-level insight.
  • EXACT — the clinical trial matcher, which evaluates patients against trial eligibility criteria.
  • SoC — the standard-of-care service, which evaluates guideline-based care options for a patient.
  • fhir_importers — the ingestion path for pulling additional records in from EHRs via FHIR.

The key architectural idea worth internalizing up front: everything serves off the same patient record. Trial matching, outcomes analytics, and standard-of-care evaluation all read from PRomop rather than each maintaining its own copy of the truth. That is what makes the suite coherent rather than five disconnected tools.

Step 1 — Load Patients with PRomop

Start with the database. PRomop gives you a standard OMOP CDM 5.4 schema — the familiar tables like person, condition_occurrence, drug_exposure, measurement, observation, procedure_occurrence — plus oncology-specific extensions for episodes and lines of therapy that the base CDM doesn't cover well.

The first task is getting your source data mapped into that schema. If you come from the OHDSI world this will feel native: PRomop is designed to work with the standard tooling (WhiteRabbit and Rabbit-in-a-Hat for profiling and source-to-OMOP mapping, Usagi and Athena for vocabulary mapping to LOINC, SNOMED CT, RxNorm, and ICD-O-3). You profile your source, author the field mappings, and load.

What makes PRomop more than "just an OMOP database" is PatientRecord — a flattened, denormalized projection of each patient into a single wide row of ~266 fields. The transactional CDM tables are the source of truth and capture everything that ever happened; PatientRecord is the decision-ready view of what is true now: demographics, staging, treatment lines, biomarkers, labs, derived fields like prior-therapy and current status. You populate it with a management command after loading.

This projection is the load-bearing piece for everything downstream — it is what lets analytics, trial matching, and care evaluation all run on the same substrate without re-deriving patient state each time. By the end of this step you have a queryable, standards-conformant longitudinal record for your patient population.

Step 2 — Analyze Aggregate Outcomes with PRism

With patients loaded, PRism gives you the population view. It is the analytics layer over PRomop, built for the questions a research foundation or informatics team actually asks: what are the outcomes across this cohort, how do treatment patterns shift over time, what does survival look like for a given subgroup.

Out of the box you get standard oncology analytics — Kaplan-Meier curves for overall survival, progression-free survival, and event-free survival; treatment-sequence visualizations (a sunburst of how therapy changes line to line); cohort definition and saving; and the data-characterization and quality metrics you'd expect from an OHDSI-style stack.

Because PRism reads the same PatientRecord projection, defining a cohort and computing an outcome on it is fast — you are querying a flat structure, not reassembling longitudinal state on every run.

A live demo running on synthetic data is at prism.healthkey.ai — you can create an account and explore the chart types against synthetic multiple myeloma and breast cancer patients to see the shape of what you'd get on your own cohort. This is the step where most teams get their first "we couldn't see this before" moment: aggregate outcomes across a fragmented population, computed on a standardized record.

Step 3 — Find Trials for Patients with EXACT

EXACT is the open-source clinical trial matcher. Where most matchers return an opaque ranked list, EXACT evaluates each patient against trial eligibility criteria one criterion at a time and returns a tri-valued verdict per trial:

  • Eligible — all inclusion criteria pass and no exclusions fire.
  • Potential — the only thing standing between the patient and a verdict is missing data; EXACT tells you exactly what is missing.
  • Ineligible — an inclusion failed or an exclusion triggered.

That middle state is the one that matters operationally. In real cohorts, "potential — need one more data point" is often the largest group, and surfacing precisely what's missing turns a dead-end into an action: go collect that value, or import it (see Step 5).

EXACT runs against a structured trials database — for example a continuously updated feed of oncology trials from ClinicalTrials.gov, EU-CTR, and ISRCTN — using the same PatientRecord fields you populated in Step 1, so no patient re-modeling is required to start matching.

Because the verdicts are per-criterion and explained, EXACT's output is also auditable: you can show a clinician or a patient navigator why a trial matched or didn't, rather than asking them to trust a score.

Step 4 — Suggest Standard-of-Care Options with SoC

Trial matching answers "what research could this patient join." The SoC service answers the equally important question patients and clinicians face: "what are the established care options right now."

SoC evaluates guideline-based standard-of-care pathways against the patient's record, with cancer-type-specific logic, and produces the care options applicable to that patient's current state.

Running trial matching and standard-of-care evaluation on the same substrate is deliberate. A patient deciding what to do next needs both in view — a trial is rarely the right frame in isolation, and the most useful systems present research options and established care side by side. Because EXACT and SoC both read PatientRecord, you get that side-by-side view without integration glue.

One caution worth carrying: in rare disease there may be no accepted standard of care to compare against, so don't assume SoC always returns a comparator.

Step 5 — Import Additional EHR Records via FHIR

Steps 1–4 work entirely on the data you started with. But patient records are never complete, and the fhir_importers project is how you enrich them. It ingests FHIR R4 resources — Patient, Condition, Observation, MedicationRequest, and the rest — and routes them into PRomop, where they land in the same CDM tables and flow back into the PatientRecord projection.

This is the step that turns a static cohort into a living one. As you connect EHR sources (including via networks like TEFCA/QHIN), new labs, diagnoses, and treatments arrive, the projection updates, and every downstream capability sees the richer picture automatically: outcomes analytics get more complete, trial matches that were "potential — missing data" can resolve to "eligible," and standard-of-care evaluation reflects the patient's current state.

The importers are FHIR-native, so FHIR Bundles route in directly; non-FHIR formats like C-CDA go through conversion first.

A practical note on sequencing: many teams deliberately start without EHR import — a manual or research-loaded cohort is enough to stand up the whole pipeline and prove value — and add fhir_importers once the data-sharing permissions are in place. The architecture supports either order.

Putting It Together

The whole loop, in one breath: load your population into PRomop and populate PatientRecord; see aggregate outcomes with PRism; match individuals to trials with EXACT; evaluate established care with SoC; and keep the records growing with fhir_importers — each new record flowing back through the same projection so every capability improves at once.

The reason it hangs together is the single shared record. You model patient state once, in a standards-based way, and four different kinds of value — population analytics, trial matching, care evaluation, ongoing ingestion — all read from it. That is the whole thesis of PHRAME as infrastructure: do the hard part (a clean longitudinal record) once, and make everything else a query against it.

Where to Start

Clone the projects from github.com/healthkey-ai and stand up PRomop first against a small cohort — even synthetic or research-loaded data is enough to see the pipeline work end to end. Explore the analytics live at prism.healthkey.ai before you load your own. From there, add EXACT, SoC, and fhir_importers in whatever order matches your priorities.

It's open source because patient-centered infrastructure should be something the whole field can build on. If you're an informaticist or data scientist working on this problem, we'd like to see what you build with it.

Specific setup instructions, dependencies, and supported versions are in each project's repository README.

← Back to Blog
Research

PRomop: Building a Comprehensive Longitudinal Decision-Ready Patient Health Record

Abstract

Health systems and biopharma face a persistent gap between holding patient data and acting on it. Records remain fragmented across providers, standards-conformant but manually mapped, and structured for storage rather than decision-making. Every downstream application — analytics, trial matching, clinical decision support — re-derives patient clinical state from scratch, multiplying effort and inconsistency.

We present PRomop, an open-source longitudinal patient health record built on the OMOP Common Data Model (CDM 5.4) with oncology extensions. Its keystone is PatientRecord, a flattened, denormalized projection that collapses each patient's complete longitudinal history into a single decision-ready row of 304 columns. While the transactional CDM tables preserve everything that ever happened, PatientRecord represents what is true now, computing patient-state derivations once rather than repeatedly per consumer. This lets population analytics, clinical trial matching, and standard-of-care evaluation operate on one shared substrate rather than maintaining divergent copies of the truth.

PRomop is deployed in production across the HealthTree Foundation (~14,000 blood-cancer patients) and CancerBot (~3,500 patients), supporting trial matching against ~6,000 actively recruiting trials across five cancer types. A 20-criterion eligibility search that requires 27–39 joins over raw OMOP reduces to zero joins against the projection — an estimated ~30×–200× speedup.

Keywords: longitudinal patient record, OMOP CDM, OHDSI, clinical trial matching, real-world data, oncology informatics, decision support, open-source health infrastructure.

Introduction

Across health systems and biopharma, the hardest problem in applied health AI is rarely the model — it is the data beneath it. Patient records are scattered across providers, electronic health records, laboratories, and registries. Even where data is captured in standards-conformant form, the mappings are manual, the structure is optimized for storage rather than decision-making, and the distance between holding patient data and acting on it for an individual remains wide and largely hand-built.

A consequence of this gap is repeated, redundant effort. Each downstream application — a cohort analytics pipeline, a clinical trial matcher, a clinical decision support rule engine — must independently reconstruct the patient's clinical state from the underlying transactional record: resolving lines of therapy, determining current disease status, normalizing biomarkers, reconciling conflicting source values. This re-derivation is expensive, error-prone, and a frequent source of inconsistency between applications that should agree.

This paper presents PRomop, an open-source longitudinal patient health record designed to close that gap. PRomop makes two commitments simultaneously: it is standards-based, building on the OMOP Common Data Model so that it interoperates with the broader OHDSI ecosystem; and it is decision-ready, exposing a flattened projection of each patient that downstream applications can consume directly without re-deriving clinical state.

The central architectural idea is a deliberate separation between a transactional record and a decision-ready projection. The transactional layer — standard OMOP tables with oncology extensions — preserves everything that ever happened to a patient. A computed projection we call PatientRecord collapses that history into a single wide row representing what is true now. Patient-state derivation is performed once, during projection, and materialized; every consuming application reads the same projection rather than reconstructing state.

Our contributions are:

  • An architecture that separates a standards-based transactional record from a flattened, decision-ready projection (PatientRecord), enabling multiple application classes to serve from one substrate.
  • Oncology extensions to OMOP CDM 5.4 (episodes, lines of therapy) developed as a proposed upstream contribution to OHDSI rather than a proprietary fork.
  • Operational evidence from production deployment across two independent oncology organizations totaling ~17,500 patients, including an analysis showing a 20-criterion eligibility search reduced from 27–39 joins over raw OMOP to zero joins against the projection (an estimated ~30×–200× speedup), and trial matching across ~6,000 actively recruiting trials in five cancer types.
  • Candid deployment lessons, particularly the inadequacy of purely structural approaches for inferring lines of therapy and the ongoing maintenance burden of a demand-coupled projection.

Background and Related Work

The OMOP Common Data Model and OHDSI

The OMOP CDM is a widely adopted standard for representing observational health data in a common relational schema, maintained by the OHDSI community. Its strength is interoperability: standardized vocabularies (SNOMED CT, RxNorm, LOINC) and a shared schema allow analyses written once to run across institutions. OMOP is, however, a model optimized for storage and population-level observational analysis. It is normalized and event-oriented; answering a question about an individual patient's current clinical state typically requires substantial joins and derivation. PRomop adopts OMOP as its foundation precisely to inherit this interoperability, and adds a projection layer to address decision-readiness for the individual patient.

Oncology Extensions

Base OMOP represents oncology concepts such as cancer episodes and lines of therapy only partially. The OHDSI Oncology Working Group has developed extensions to the CDM, including the Episode and Episode_Event tables that model disease episodes and treatment regimens. PRomop builds on this direction and contributes additional oncology columns, which we intend to submit upstream.

Flattened and Feature-Oriented Patient Representations

The tension between normalized clinical models and flattened, analysis- or ML-ready representations is well known; feature stores and denormalized "patient-level" tables are common in practice. PRomop's contribution is not the idea of flattening per se, but the discipline of computing a single canonical decision-ready projection once, materializing the derivations, and serving all downstream workloads — analytics, matching, and clinical decision support — from it.

Clinical Trial Matching

Automated trial matching has seen substantial recent work, much of it using large language models — TrialGPT, TrialMatchAI, and OncoLLM. Much of this work returns ranked lists of candidate trials. PRomop's companion matcher, EXACT, instead evaluates eligibility criterion-by-criterion against the PatientRecord projection and returns a tri-valued verdict (eligible / potential / ineligible) per trial.

Method and Architecture

PRomop's design rests on a two-layer separation between a transactional record and a decision-ready projection, implemented on open standards and validated through production deployment.

PRomop architecture diagram showing three layers: INGEST (FHIR-native and other sources), STORE (transactional OMOP CDM tables feeding the PatientRecord flattened projection of 304 columns), and SERVE (PRism, EXACT, and SoC all reading from the shared projection)
Figure 1. PRomop architecture. An ingest layer (FHIR-native and other sources) loads data into the store layer: transactional OMOP CDM 5.4 tables with oncology extensions serve as the source of truth, from which the PatientRecord projection (304 columns) is derived once. All serve-layer applications — PRism, EXACT, and SoC — read from the same projection rather than reconstructing patient state.

Storage Layer

We adopt OMOP CDM 5.4 as the foundation, populating its standard clinical tables (person, condition_occurrence, drug_exposure, measurement, observation, procedure_occurrence) and extending it with oncology-specific structures for episodes and lines of therapy that the base model represents poorly. Source data is ingested via OHDSI-standard practice: profiling source schemas, authoring explicit field mappings, and mapping vocabularies to LOINC, SNOMED CT, RxNorm, and ICD-O-3.

Projection Layer: PatientRecord

The architectural core is PatientRecord: a flattened, denormalized projection that collapses each patient's full longitudinal history into a single wide row of 304 columns. Where the transactional tables preserve everything that ever happened, PatientRecord is computed to represent what is true now: demographics, staging, treatment lines, biomarkers, laboratory values, and derived clinical state such as prior therapy and current disease status.

Derivation logic that would otherwise be re-implemented in every downstream consumer — computing lines of therapy, resolving current status, normalizing biomarkers — is performed once, at projection time, and materialized. Projection refresh is event-driven: changes to a patient's underlying record trigger regeneration of that patient's projection, keeping the decision-ready view current. Refresh can also be invoked on demand, and event-driven refresh can be disabled during bulk operations (e.g., large backfills), with a single rebuild at batch end.

Serving Layer

Because every consuming workload reads the same projection, three distinct application classes operate on one substrate rather than maintaining divergent copies: (i) population and cohort analytics; (ii) per-criterion clinical trial matching; and (iii) guideline-based standard-of-care evaluation. This shared-substrate design is the method's central efficiency claim: the join surface and state-derivation cost that normally scale with the number of applications are collapsed into a single shared step.

Ingestion and Currency

Records are enriched over time through FHIR-native ingestion: FHIR R4 resources are routed into the CDM tables, while non-FHIR formats (C-CDA) are converted upstream. As new clinical events arrive, the projection updates and downstream capabilities reflect the richer record automatically, including the resolution of previously incomplete states.

Results and Outcomes

Existence Proof at Scale

PRomop's primary result is an existence proof at scale: a standards-based longitudinal record with a decision-ready projection, operating in production rather than in prototype. The architecture is deployed across two independent oncology organizations — the HealthTree Foundation (~14,000 blood-cancer patients) and CancerBot (~3,500 patients) — totaling roughly 17,500 real patients drawn from fragmented, heterogeneous, real-world sources.

Decision-Readiness in Practice

The PatientRecord projection collapses each patient's longitudinal history into a single 304-column decision-ready row across both deployments. Consider a realistic 20-criterion eligibility search over raw OMOP — roughly 8–10 laboratory criteria, 5 condition criteria, 3 prior-therapy criteria, and 2 procedure criteria. This requires on the order of 27–39 joins, spanning person, condition_occurrence, measurement, drug_exposure, episode, and procedure_occurrence, with a concept lookup for nearly every criterion.

Laboratory criteria are the dominant cost: because measurement stores one row per test per date, each lab criterion needs not only a table join and a concept join but a correlated subquery (effectively GROUP BY with MAX(date)) to recover the most-recent value. The query cost grows roughly as O(ncriteria × nmeasurement × log nconcept).

Table 1. Approximate table cardinality. The measurement table dominates: a 20-criterion query with ~10 laboratory criteria must scan it repeatedly, filter on concept_id, and aggregate to the latest value before joining back to person.

Table Rows per 10k patients
measurement1M – 10M
drug_exposure100k – 1M
condition_occurrence50k – 500k
concept (CDM-wide)2M+
PatientRecord10k (one row per patient)

Against PatientRecord, the same 20-criterion search requires zero joins: every criterion resolves to a predicate on a single 10,000-row table, because the latest values, concept resolutions, and derived states have already been computed at ingest. Adding a criterion is linear in patient count rather than in criteria count.

Table 2. Join count and estimated query-time comparison for a 20-criterion eligibility search at 10,000 patients. The gap widens with each added criterion.

Approach Joins Rows touched Est. time (10k pts)
Raw OMOP, 20 criteria27–395M–15M across tables15–120 s
PatientRecord, 20 criteria010k, one table50–500 ms
Effective speedup~30×–200×

One Substrate, Multiple Workloads

The shared-substrate claim held: population analytics, clinical trial matching, and standard-of-care evaluation all run against the same projection without maintaining separate copies of patient state. Trial matching operates against approximately 6,000 actively recruiting trials spanning follicular lymphoma, multiple myeloma, breast cancer, chronic lymphocytic leukemia, and mantle cell lymphoma, evaluated for the full ~17,500-patient population. Adding a new application became a matter of querying an existing record rather than rebuilding patient state, lowering the marginal cost of each new capability.

Operational Lessons

Inferring lines of therapy. This proved a particular challenge. Purely structural normalization was insufficient for real-world oncology data; we supplemented the OHDSI ARTEMIS approach with our own rules and the ability to read from physicians' notes to derive therapy lines reliably from incomplete and inconsistent sources. This suggests a broader principle: the decision-ready projection is where clinical reasoning, not merely data transformation, must live.

The projection is a living artifact. Decision-readiness is not a stable end state. As new trial eligibility criteria and clinical decision support rules emerged, additional fields had to be added to the projection, requiring ongoing vigilance to keep the decision-ready view aligned with downstream demand. Teams adopting this pattern should plan for projection maintenance as a continuous obligation.

Discussion

The central lesson of PRomop is that the hardest part of applied health AI is not the model or even standardization — it is making a standardized record decision-ready, and doing so once rather than repeatedly. Eliminating the 27–39 joins of a raw-OMOP eligibility query down to zero is striking, but its real significance is economic rather than technical: it changes the marginal cost of every new application. When patient state is derived once at projection time, adding a trial matcher, an analytics view, or a clinical decision support rule becomes a query against an existing record rather than a fresh state-reconstruction effort. The architecture's value compounds as applications accumulate.

The lines-of-therapy experience complicates any claim that decision-readiness is purely a structural problem. The fields hardest to derive are precisely those most valuable downstream, and they resist tidy extract-transform-load; they require embedded clinical reasoning, including reading unstructured notes. A second lesson tempers the architecture's appeal: the same property that makes PatientRecord powerful — pre-computing what consumers need — makes it a living artifact coupled to evolving clinical demand, with a continuous maintenance cost.

These results carry limitations worth stating plainly. Our evidence is operational and analytical rather than benchmarked: we demonstrate that the architecture works at scale and quantify its join savings from OMOP cardinality and query structure, but we do not yet report controlled end-to-end timing against alternatives. Both deployments are in oncology; the OMOP foundation is disease-agnostic, but the oncology extensions and derived fields are not yet validated outside this domain. The ~30×–200× speedup is an estimate over a representative eligibility workload and should be read as illustrative of the pattern's effect rather than a universal benchmark.

Situated against the field, PRomop's contribution is deliberately not novelty in the data model — it builds on OMOP precisely to avoid fragmenting the standards ecosystem and returns its oncology extensions upstream. The novelty is the projection layer and the discipline of computing decision-readiness once.

Conclusion

PRomop demonstrates that a flattened, decision-ready projection over a standards-based longitudinal record is a viable, deployed pattern for turning fragmented patient data into infrastructure that AI applications can act on. By computing patient state once and serving analytics, trial matching, and standard-of-care evaluation from a single shared record, PRomop collapses the redundant derivation that burdens conventional pipelines — reducing a 20-criterion eligibility search from 27–39 joins over raw OMOP to zero against the projection, an estimated ~30×–200× speedup — while remaining conformant with OMOP and contributing its oncology extensions back to OHDSI.

Deployed across ~17,500 real oncology patients, it offers a concrete, open-source pattern for moving from proof to practice. Future work includes validation beyond oncology, formal end-to-end benchmarking, and completion of the upstream OHDSI oncology contribution.

Availability

The PRomop project and related PHRAME components are open source and available at github.com/healthkey-ai. A live analytics demonstration on synthetic data is available at prism.healthkey.ai.

Acknowledgments

Thank you to HealthTree for funding and feedback. Thank you to advisors Steve Labkoff and Yuri Quintana for guidance and insight.

References

  1. Observational Health Data Sciences and Informatics. The Book of OHDSI. 2021. ohdsi.github.io/TheBookOfOhdsi
  2. OHDSI Oncology Working Group. OMOP CDM Oncology Extension. ohdsi.github.io/CommonDataModel/oncology.html
  3. Zong N, et al. Deep learning-based feature extraction for clinical phenotyping using EHRs. Int J Mol Sci. 2022;23(19):11834.
  4. Jin Q, et al. Matching patients to clinical trials with large language models (TrialGPT). Nature Communications. 2024;15:6342.
  5. Smith A, et al. TrialMatchAI: AI-driven patient-to-trial matching platform. Nature Communications. 2026;17:1024.
  6. CI4CC. Large Language Models for Clinical Trials (OncoLLM). CI4CC Workshop Proceedings, 2024.
  7. Golozar A, et al. Introducing ARTEMIS: Advanced Regimen Detection. OHDSI Symposium, 2023.
← Back to Blog
Standards

How We Built an Oncology Patient PHR That Conforms to the HL7 PHR Standard

HealthKey.ai's PRomop achieves conformance on all 25 Essential functions — of its HL7 PHR-S FM R2 Functional Profile for oncology patient health records.

HL7 PHR-S FM R2 Conformance Journey — before and after WS0 sprint, 24 of 26 functions conformant including all 25 Essential

The HL7 Personal Health Record System Functional Model Release 2 (PHR-S FM R2) is the most comprehensive standard for what a PHR system should do. It covers everything from patient demographics to audit trail management to clinical decision support — 748 mandatory criteria across hundreds of functions.

Nobody conforms to all of it. That's by design.

The standard itself says that conformance is profile-based: a system conforms to one or more Functional Profiles, never to the entire model. The question isn't "did you implement all 748 criteria?" but "did you pick a coherent, defensible subset and fully implement it?"

This post describes how we chose our profile, how we closed the gaps, and what we learned along the way.

Why bother with PHR-S FM at all?

We're building PRomop, an oncology patient health record that stores clinical data in the OMOP Common Data Model, accepts FHIR R4 bundles, and gives patients a portal to view and manage their health information. Our users are cancer patients — people who need to trust that their system handles data correctly, securely, and transparently.

We could have just built features and moved on. But we wanted a structured way to answer the question: is our security, audit, and data handling actually good enough? Not "we think so" — but "we checked every criterion against the source code and can show our work."

PHR-S FM R2 gave us that framework. It's not a certification program. It's a functional model — a comprehensive checklist of what a PHR system should do, organized into functions with specific, testable conformance criteria. Each criterion is tagged SHALL (mandatory), SHOULD (recommended), or MAY (optional). A function is conformant only when all its SHALL criteria are met.

Choosing the profile: the 80/20 split

The full PHR-S FM R2 model has a problem for a focused product like ours: about 68% of its mandatory criteria live in a single mega-cluster — the RI.1.1 record-lifecycle events (52 functions, 278 SHALL) and the granular per-trigger audit functions (roughly 230 SHALL). These functions define detailed evidence management for every lifecycle state change — originate, amend, attest, translate, transmit, and so on.

That's important work for a comprehensive EHR. For an oncology patient PHR that ingests FHIR bundles and lets patients view their data, it's not where the meaningful risk lives. The meaningful risk lives in authentication, authorization, audit trails, terminology management, and data interchange — the Trust Infrastructure functions that protect every interaction.

So we drew a line. Our HealthKey Oncology Patient PHR Functional Profile selects 26 functions across three areas:

Personal Health (10 functions): The account-holder-facing functions — managing demographics, clinical data, advance directives, consents, FHIR export, and provider communications. These are the features patients actually use.

Trust Infrastructure — Security & Audit (8 functions): Authentication (including lockout, password reuse prevention, and force-change), authorization, secure data routing, audit triggers, audit log management, audit log indelibility, and audit notification. These are the controls that make the system trustworthy.

Trust Infrastructure — Terminology & Interoperability (8 functions): Standard terminology, terminology versioning, terminology mapping, interchange standards, application integration, system integration, and information import/export. These ensure the system speaks FHIR and OMOP correctly and can prove it.

We classified each function as Essential (required for the profile) or Optional (declared out of scope). 25 functions are Essential; 2 are Optional (multi-version interchange and interchange agreement enforcement — both reasonable to defer for a single-standard FHIR R4 system).

Everything else — provider information management, financial functions, registries, clinical decision support, health education, the full record-lifecycle event model — is explicitly out of scope with documented rationale. We're not hiding gaps; we're being precise about what we claim.

The initial audit: 49 of 78 criteria met

The profile defines 78 mandatory (SHALL) criteria across the 26 functions. Our first criterion-level code audit — four independent passes over the source code, each criterion graded MET, PARTIAL, or NOT MET — found that 49 were MET, 18 were PARTIAL, and 11 were NOT MET. Only 10 of 26 functions were fully conformant.

That sounds bad. It wasn't. Most of the gaps fell into a few categories:

Authentication hardening (TI.1.1): We had Django's authentication system, OAuth2, and session management, but we were missing account lockout after failed attempts, password reuse prevention, and an admin-triggered force-change mechanism. These are specific, implementable requirements.

Audit trail rigor (TI.2.x): We had request-level audit logging, but not in FHIR AuditEvent format, not with tamper-evidence signatures, not with delete-restriction, and not with break-glass access patterns. The audit trail existed but didn't meet the standard's security requirements.

Terminology lifecycle (TI.4.2): We used OMOP vocabularies and SNOMED/LOINC, but we didn't have version tracking, deprecation workflows, or concept replacement — the operational terminology management the standard requires.

Data interchange integrity (S.3.6, PH.2.3): We accepted FHIR bundles and exported data, but we didn't have content digest verification on ingest or non-repudiation signatures on export.

Closing the gaps: WS0

We filed GitHub issues for every gap and worked through them systematically over a series of PRs. We called this sprint WS0 — "Work Stream Zero," the foundation before feature work resumes.

Here's what we built:

Password security: Django password validators on signup and invite flows. Account lockout after configurable failed attempts with automatic unlock. Password reuse prevention (configurable history depth). Admin-triggered force-password-change flag that blocks all API access until the password is changed. Admin password reset via email link. The force-change flag is wired end to end: an admin action sets it, middleware enforces it (403 on every API request except the change-password endpoint), and the SPA shows a blocking change-password screen.

Audit overhaul: FHIR AuditEvent-format audit records. Audit-log-access auditing (reading audit logs generates its own audit entry). Admin and background task audit triggers. Per-row HMAC-SHA256 tamper-evidence signatures. Delete-restriction (admin deletion disabled; retention-only deletion through the audited retention job). Break-glass emergency access with elevated audit trail.

Audit hash chain: A chain_hash field linking each audit row to its predecessor, sealed under an advisory lock. This means that deleting or inserting rows between survivors is now detectable by verify_audit_integrity. The disclosed limitation: tail-truncation of the newest rows is undetectable without an external anchor (publishing the latest chain_hash periodically closes this — planned).

Terminology management: Version history tracking for vocabulary changes. Deprecation workflow with replacement concept pointers. Concept substitution in the coded data store.

Exchange integrity: Content digest verification on FHIR bundle ingest (opt-in via request header, transport auth carries the SHALL). HMAC-signed export bundles for non-repudiation. Interchange agreement registry with admin audit trail.

Patient record management: Entered-in-error status for patient records. Consent-driven demographic redaction on the primary serializer read path. Advance directive "in-effect" status tracking. Field-level revision history.

Communications security: Proxy-authorization for the provider communication render API. Message-level confidentiality controls.

Force-change wiring: The must_change_password flag, previously defined but inert, wired through the full request lifecycle — admin set, middleware enforcement, SPA blocking screen, and clear on successful password change.

Each PR included targeted tests. By the end of WS0, our backend test suite had grown to 1,010 passing tests.

The re-verification: 75 of 78 criteria met

After WS0 merged, we re-ran the full criterion-level code audit — four independent passes over the current dev branch, each of the 78 SHALL criteria individually graded.

Result: 75 MET, 3 PARTIAL, 0 NOT MET. 24 of 26 functions fully conformant.

The 3 remaining PARTIAL criteria are in the 2 Optional functions:

TI.5.2 (interchange-standard versioning): We declare FHIR R4 and cleanly reject non-R4 with HTTP 406, but we don't do cross-version transformation. For a single-standard system, this is reasonable.

TI.5.4 (interchange agreements): We have InterchangeAgreement records and OAuth/OrgTrust-based access control, but access provisioning isn't directly driven by the agreement records. OAuth scoping does the enforcement; the agreements are descriptive.

Most importantly: every Essential function in the profile is fully conformant. The last Essential gap — TI.2.2.1 audit log indelibility — was closed by the hash chain implementation.

What we learned

Profile-based conformance is the right approach. Trying to conform to the full PHR-S FM R2 model would have been a multi-year distraction. Choosing a focused profile let us make a meaningful claim in weeks, not years, and the process of selecting the profile forced us to think carefully about what matters for our users.

The standard found real gaps. We thought our audit trail was fine. It wasn't — it lacked tamper-evidence, delete-restriction, and chain integrity. We thought our authentication was solid. It was missing lockout, reuse prevention, and force-change. These aren't theoretical concerns; they're the kind of security controls that matter when you're handling cancer patients' health data.

Criterion-level auditing is worth the effort. Grading each criterion individually against the source code — not just "does this function exist?" but "does the code satisfy this specific requirement?" — caught gaps that a higher-level assessment would have missed. The difference between "we have an audit trail" and "our audit trail has per-row HMAC signatures and a hash chain with delete-restriction" is the difference between a vague claim and a defensible one.

Self-attestation is honest when you show your work. We're not claiming HL7 validation. We're saying: here's our profile, here's every criterion, here's our determination for each one, here's what's PARTIAL and why, here's the disclosed limitations. The conformance claim document runs to 175 lines with appendices. That's not a checkbox exercise; it's a transparent accounting.

The 80/20 rule applies to standards. The 26 functions we selected cover the security, audit, terminology, and data handling controls that actually protect our users. The 68% of criteria we excluded — the record-lifecycle event mega-cluster — would add documentation and evidence management for state transitions our system doesn't perform. That's honest scoping, not corner-cutting.

What's next

The conformance claim is ready for signature. Two follow-up items remain on the roadmap:

  • External chain_hash anchoring for the audit hash chain — periodically publishing the latest hash to close the tail-truncation detection gap (TI.2.2.1 disclosed limitation).
  • TI.5.2 and TI.5.4 remain Optional and can be promoted to Essential if a multi-version FHIR partner or agreement-driven provisioning requirement emerges.

The full profile, conformance claim, and traceability matrix are maintained in our repository. We update them with every PR that touches a conformance-relevant function.

If you're building a PHR and wondering whether standards conformance is worth it: it is. Not because a certification body requires it, but because the process of rigorous criterion-level verification makes your system better. The gaps it finds are real. The security controls it demands are the right ones. And the documentation it produces is exactly what your users — and their data — deserve.

Adam Blum is co-founder and CTO of HealthKey.ai. PRomop is an open-source oncology patient health record built on OMOP CDM and FHIR R4.

← Back to Blog

Case Studies

HealthKey in the real world.

See how PRomop and EXACT are powering production applications that connect cancer patients with the care they need.

CancerBot

AI-powered clinical trial matching for cancer patients — built entirely on PRomop and EXACT.

Read case study ↓

HealthTree

How a patient-built nonprofit assembled a complete, patient-owned health record for 14,000+ blood cancer patients.

Read case study ↓

Case Study

CancerBot

The first AI-powered clinical trial matching service built on an open, oncology-grade patient record — connecting cancer patients with potentially life-saving trials in minutes, not months.

PRomop EXACT Multiple Myeloma Follicular Lymphoma Breast Cancer

The Problem

2% enrollment. 98% left behind.

Historically, only 2% of cancer patients ever enroll in a clinical trial — not because trials don't exist, but because finding them is confusing, time-consuming, and opaque. Eligibility criteria are written in clinical language, spread across dozens of registries, and require detailed lab values that most patients don't know how to gather.

The result: patients who could benefit from cutting-edge treatments never hear about them, and trial sponsors struggle to hit enrollment targets.

2%
of cancer patients
enroll in clinical trials
98%
never find out
what they qualify for

The Solution

A complete patient record meets an explainable matching engine.

CancerBot is built entirely on HealthKey's two open-source properties — PRomop for patient data and EXACT for trial matching. Together they give patients a personalised, jargon-free answer to the question: which trials do I qualify for, and why?

🗂️

PRomop Patient Record

Every patient's clinical history — labs, diagnoses, therapy lines, genomics, ECOG status, prior treatments — is stored in a PRomop-aligned OMOP record. 266 eligibility-ready fields, derived once, in one place, with auditable rules.

🎯

EXACT Trial Matching

EXACT consumes each patient's PRomop profile and evaluates every eligibility criterion for every trial — returning a per-criterion trace showing exactly which criteria passed, failed, or couldn't be determined, with the specific patient value behind each verdict.

💬

No Black Boxes

Results are presented in plain language. Patients see which trials match and precisely why — not an opaque score. A navigator reviews the matches and confirms understanding before any outreach.

How It Works

From sign-up to matched trial in minutes.

01

Complete your patient record

Sign up for free at app.cancerbot.org and answer structured questions about your diagnosis, labs, prior treatments, and current status. Your data is stored in PRomop format.

02

EXACT evaluates eligibility

Your PRomop profile is run through EXACT's stateless matching engine against an up-to-date trials catalog. Every criterion is evaluated with a pass, fail, or indeterminate verdict.

03

Review personalised matches

You see your matched trials ranked and explained — each with the exact reason you qualify. No jargon, no guesswork.

04

Navigator support

A patient navigator reviews your matches with you, answers questions, and provides ongoing support through the enrollment process.

Supported Cancer Types

Multiple Myeloma
Including LOT inference, ISS staging, M-protein, FISH
Follicular Lymphoma
FLIPI score, GELF criteria, grade, transformation status
Breast Cancer
HR/HER2 status, BRCA, TNM staging, therapy history

Built on HealthKey

Why PRomop + EXACT?

CancerBot's founder Adam Blum, diagnosed with follicular lymphoma, experienced firsthand how broken the trial-finding process was. He built CancerBot on HealthKey's open infrastructure so the patient data model and matching engine could be audited, extended, and trusted.

📐

One data standard

PRomop's OMOP-aligned schema means every lab value, therapy line, and biomarker is stored consistently — no ad-hoc mappings, no one-off fields.

🔍

Explainable by design

EXACT was built to show its work. Every eligibility verdict references the exact patient value and trial threshold — so patients and navigators can act on reasons, not scores.

🔓

Open source foundation

Both PRomop and EXACT are published on GitHub. CancerBot's matching logic can be audited by patients, providers, and researchers — building the trust that clinical trial enrollment requires.

Visit CancerBot.org

Case Study · Together We Care, Together We Cure

HealthTree Foundation

Turning a diagnosis into infrastructure — how HealthTree Foundation built a complete, patient-owned personal health record, and how HealthKey helped make cancer data work for the patient and for researchers.

A nonprofit innovation story spanning 2010–2026 — from a single myeloma patient to 14,000+ patients and 65,000+ research participants.

Founded 2012 Personal Health Record Multiple Myeloma Cure Hub Nonprofit · 501(c)(3)
14,000+
patients in Cure Hub
7,900+
hospitals connected
65,000+
research participants

Executive Summary

It began with one patient.

HealthTree Foundation is a global nonprofit that exists to help people with blood cancer live longer and better — and to accelerate the search for a cure. It began in 2010 with Jenny Ahlstrom, a 43-year-old mother of six, diagnosed with multiple myeloma and given a prognosis measured in a few short years.

Rather than accept that the data needed to fight her disease was scattered, locked away, and inaccessible, Jenny and her husband Paul decided to treat the diagnosis like a startup problem. The result, formally founded in 2012, is HealthTree Foundation and its flagship platform, HealthTree Cure Hub — the only tool that invites patients to contribute their complete, real-world health data to academic research while giving them tools to navigate their own care in return.

"I wish this had existed when I was first diagnosed, that I didn't have to build it."

— Jenny Ahlstrom, Founder, HealthTree Foundation

The Problem

A patient's data belonged to everyone except the patient.

In 2010, while the Ahlstrom family was living in Mexico, Jenny was diagnosed with multiple myeloma — a blood cancer that typically strikes patients in their seventies. She was 43. The five-year median survival was roughly 50%, and her high-risk genetic features put her individual prognosis closer to two years. Tandem stem cell transplants, thousands of miles travelled, months away from her family — and along the way, a systemic failure that had nothing to do with any single hospital or doctor.

A loss they had seen before

The Ahlstroms had already watched this failure play out once before. Paul's brother David had been diagnosed with acute myeloid leukemia six years earlier and lived only a year. During his care, David tried an off-label drug that bought him six additional months — but that information was effectively lost to every other patient in the world. The same drug was formally approved for his indication fourteen years later. That is how slowly knowledge moves when it is trapped inside individual cases.

🧩

Fragmentation

Every hospital holds only "little bits and pieces" of a patient's history. No one institution — and critically, not the patient — holds the complete picture.

⏸️

Inertia

A patient's health data sits idle inside a hospital's online chart, helping neither the patient, their doctor, nor the research community find a cure.

⚠️

Trust

Tech companies asked patients to hand over their health data with a vague promise to "do something cool with it later." For patients, that is a non-starter.

"Having cancer is like playing chess with your life, and you just don't want to make a wrong move."

— Jenny Ahlstrom

The Insight

Treat the diagnosis like a startup.

Paul Ahlstrom is a serial entrepreneur who had spent his career building companies and helping create a venture capital industry in Mexico. Jenny had her own technology background from years at IBM. Somewhere in the chaos of treatment, they made a decision that would define the next decade and a half: approach the cancer diagnosis the way they would approach a startup.

That reframing produced a testable hypothesis. If the powerful, real-world experience of every myeloma patient could be aggregated into one place, that collective knowledge — not any single trial — could become the fastest engine for research. The missing ingredient was not data. It was a trusted way to assemble it.

Earning trust by being patients

A 50-city tour. 860+ patients. One kitchen table at a time.

In 2018, HealthTree took the hypothesis on the road. The Ahlstroms and their six children sat beside elderly patients in living rooms across 50 cities, helping them enter their own data and watching, in real time, where the barriers were.

The patients' questions were consistent: Who are you? Why are you doing this? What will you do with my data? HealthTree could answer all three credibly for one reason — the people building it were myeloma patients themselves, doing it to accelerate a cure. That authenticity is the foundation the entire data model rests on.

Principle 1

The patient owns the data.

Information contributed to HealthTree remains anonymised, secure, and entirely under the patient's control.

Principle 2

The data is never sold.

HealthTree does not sell patient data or information. Doing so would destroy the very trust the model depends on.

Building the Personal Health Record

From fragments to a single, structured record.

The tool that emerged from the 50-city tour became HealthTree Cure Hub. At its heart is the Personal Health Record — powered by HealthKey.ai's PHRAME — a complete, longitudinal, patient-owned record of a person's cancer journey, assembled from scattered records across 7,900+ hospitals including Huntsman Cancer Institute, Dana-Farber, Mayo Clinic, MD Anderson, and Memorial Sloan Kettering.

A pile of PDFs is not a usable record. The breakthrough is that the data is structured — organised consistently enough that software can reason over it. Under the hood, each patient's history is normalised into PRomop, HealthKey's open-source superset of OMOP CDM with oncology and genomics extensions and a 266-field eligibility-ready projection. Assemble the record once, correctly, and every downstream service becomes possible. One record; many tools.

01

Treatment Option Matching

Personalised treatment options ranked in the order myeloma experts would consider them — using decision logic built with the help of myeloma specialists. A patient explores options from home and brings them to their doctor.

02

Clinical Trial Matching

The PRomop PatientRecord is compared against trial criteria using HealthKey's EXACT engine, and HealthTree's team helps the patient actually enroll.

03

The Twin Machine

Connects a patient with other patients whose cancer history closely resembles their own. See what worked for your "twins," add them as friends, and chat anonymously — exactly the knowledge that was lost when Paul's brother tried his off-label drug alone.

04

Side Effect Solutions

Crowdsourced, patient-reported outcomes. See what other patients actually experienced — how many tried a particular solution and how often it helped — and contribute what worked for you in turn.

05

Powering Clinical Research

The same PHR — anonymised and contributed with the patient's consent — is what makes HealthTree's research model possible. A built-in researcher portal lets academic investigators post surveys and studies and access rich real-world data. In one early COVID-19 pilot, HealthTree recruited 1,100 patients in four weeks.

The HealthKey Partnership

Two layers, one mission.

Assembling a clean, structured, interoperable health record from thousands of incompatible hospital systems is a serious engineering problem. HealthTree partnered with HealthKey to provide the backend technology that powers Cure Hub — a deliberate division of labor between the trust layer and the infrastructure layer.

🌲

HealthTree Foundation

The trust & community layer

A 501(c)(3) nonprofit that holds the relationship with patients, earns and keeps their trust, educates and supports them, and connects them to research. The layer patients see and believe in.

🔧

HealthKey

The infrastructure layer

The backend technology that ingests fragmented records, processes and standardises them against healthcare data standards, and produces the clean, structured PHR. At its core is PRomop — an open-source, comprehensive longitudinal patient record built as a superset of OMOP CDM v5.4, with oncology and genomics extensions and a 266-field eligibility-ready projection. The engine that makes the patient-facing promise deliverable at scale.

With HealthKey providing the technology backbone — and PRomop published in the open so clinicians, researchers, and patients can audit exactly how the record is structured — work that once took clinicians hours can now take minutes.

"They've been able to do some things that many academic centers only dream of, which is they're able to amass data and make sense of the data."

— Dr. Douglas Sborov, Huntsman Cancer Institute

Impact

From one patient to a global community.

The structured-data model doesn't just make research possible — it makes it fast. HealthTree reports average recruitment of just four to six weeks for real-world-data studies, with full projects completing in nine to eighteen months. For patients racing the clock, compressing research timelines from years to months is a direct contribution to survival.

9,200+

searched treatments & trials

1,300+

side-effect solutions

76+

surveys & studies completed

78

investigators included

1,100

patients recruited in 4 weeks (COVID-19 pilot)

4–6 wks

average study recruitment time

Fifteen years later

"I could never have imagined what we have built. Just the creation that came out of this terrible situation — it's become a blessing instead of a curse in my life."

— Jenny Ahlstrom, Founder of HealthTree Foundation

Fifteen years after a diagnosis that gave her roughly two years, Jenny has no evidence of active cancer — in remission for more than four years following CAR-T cell therapy, an immunotherapy that re-engineers a patient's own immune cells to attack the cancer. Precisely the kind of advanced treatment that a complete, well-navigated health record helps patients discover.

Key Takeaways

What HealthTree got right.

Start with trust, not data.

HealthTree succeeded where others failed because patients — building for patients — earned the right to aggregate data before asking for it.

The PHR is the foundation.

Assembling one complete, structured, patient-owned record from thousands of fragmented sources is the hard problem; solving it once makes every downstream service possible.

Separate trust from technology.

HealthTree holds the patient relationship; HealthKey provides the backend infrastructure — built on the open-source PRomop patient record so the data model itself is auditable. The division lets each do what it does best.

Structured data turns a record into a platform.

Because the PHR is structured, it can power treatment matching, trial matching, the Twin Machine, side-effect solutions, and research — all from one record.

Patient ownership is permanent.

Data stays anonymised, secure, under the patient's control, and is never sold. That commitment is what keeps the entire model viable.

Faster research saves lives.

Recruitment in weeks instead of years means patients racing terminal diseases benefit from discoveries sooner.

Where We Are Going

Disease-agnostic by design.

HealthTree is not stopping at multiple myeloma. The same architecture — a trusted nonprofit relationship layer on top of a HealthKey-powered data and infrastructure layer — is being extended to additional blood cancers, to solid tumors, and toward other terminal diseases.

The long-term vision: a future of more effective and personalised cures, in which every patient owns a complete, trusted, interoperable health record — and in which that record works as hard for the patient, and for research, as it possibly can.

Interoperability

How Athena Turns FHIR Codes into a Decision-Ready Patient Record

FHIR tells us how to exchange a clinical fact. Athena helps us make sure that fact means the same thing after it has arrived.

One patient may contribute a laboratory result from a hospital, a diagnosis from an ambulatory EHR, a medication from a pharmacy feed, and a procedure from a claims system. FHIR can carry all of them, but their codes may come from different terminologies: LOINC, SNOMED CT, RxNorm, CPT, ICD, local codes, and more. A decision engine should not have to rediscover those differences every time it asks a clinical question.

That is why the OHDSI/OMOP vocabulary is central to PRomop. Athena is the place where the OHDSI community publishes, browses, and downloads that vocabulary and its mappings. In PRomop, the downloaded vocabulary is represented by the OMOP Concept and relationship tables. The importer uses it to resolve incoming FHIR coding to a standard concept, then stores the clinical event in the OMOP table determined by that concept's domain.

A small but important distinction: Athena does not “clean” a patient record by itself. It supplies versioned concepts and mappings; PRomop's ingestion rules apply them, preserve the original code and source value, and keep the resulting OMOP event auditable.

The common language behind five kinds of clinical facts

OMOP's standard concepts give each fact a durable, machine-readable meaning. A source code can map directly to a standard concept, map through a source-to-standard relationship, or require a reviewed local mapping if the vocabulary does not yet cover it. The target concept's domain directs the row to the right clinical table.

Clinical factTypical incoming codePRomop's OMOP destination
Drug or administrationRxNorm, NDCDrugExposure
Procedure or diagnostic serviceCPT, HCPCS, SNOMED CTProcedureOccurrence
Diagnosis or findingSNOMED CT, ICDConditionOccurrence
Quantitative or categorical test resultLOINC, SNOMED CTMeasurement
Context, assessment, or fact not represented by the above domainsSNOMED CT, local codeObservation

This is more than a naming convention. Standard *_concept_id fields are the basis for OMOP analytics, while the corresponding source fields retain what the sending system actually supplied. The result is both interoperable and traceable.

Example 1: a LOINC heart-rate result

Consider a FHIR Observation from a patient's record. Its code is LOINC 8867-4 (heart rate); its value is 60; and its unit is /min. FHIR has made the payload legible. Athena makes its clinical identity reusable across systems.

1. FHIR

Observation.code
LOINC 8867-4
value: 60 /min

2. Concept

PRomop resolves the code to the versioned Concept row: LOINC, Measurement domain, standard concept.

3. OMOP event

A Measurement row stores the person, date/time, standard measurement concept, numeric value, and standardized unit.

4. PatientRecord

The projection selects the current relevant value and exposes it as a decision-ready vital-sign attribute.

Because LOINC is itself a standard OMOP vocabulary for this kind of test, this case is primarily a resolution rather than a translation into a different clinical term. The importer finds the Concept row for 8867-4, confirms that its domain is Measurement, and writes a Measurement event. Its measurement_concept_id points to the standard concept; measurement_source_value retains 8867-4; and the result and unit remain attached to the event.

What the stored row looks like

For this example, Athena resolves LOINC 8867-4 to the PRomop Concept record concept_id = 3027018 (Heart rate). The following is a representative row in PRomop's underlying Measurement table after ingest; the patient and event identifiers are illustrative, while the vocabulary identifiers show the concrete mapping.

Measurement columnStored valueWhy it is there
measurement_id915027A unique identifier for this clinical event.
person_id842The PRomop person receiving the measurement.
measurement_concept_id3027018The standard OMOP concept: Heart rate.
measurement_datetime2026-06-15 10:42:00When the FHIR observation was effective.
value_as_number60The patient's measured heart rate.
unit_concept_id8541The standard OMOP unit concept for “per minute.”
measurement_source_value8867-4The original code received in the FHIR resource.
measurement_source_concept_id3027018LOINC is already standard here, so source and standard concept coincide.
unit_source_value/minThe unit as supplied by the source.

Then PRomop refreshes PatientRecord. The raw OMOP layer still holds every heart-rate observation, including date, source, and value. The projection applies explicit recency and relevance rules to provide the current value a trial-matching or decision-support rule needs. A rule such as “review a patient with resting heart rate above a threshold” reads one decision-ready patient row, while an auditor can always navigate back to the originating Measurement row and FHIR resource.

Example 2: a diagnostic that is not LOINC

Not every diagnostic is a laboratory-style observation. Suppose an EHR sends a chest CT as a FHIR Procedure, coded CPT 71250 (CT thorax without contrast). That code describes a diagnostic service, not a result with a numeric value. It should not be forced into Measurement merely because it is “diagnostic.”

1. FHIR

Procedure.code
CPT 71250
performed date

2. Athena mapping

The CPT source concept follows its valid Maps to relationship to the appropriate standard procedure concept.

3. OMOP event

The target's Procedure domain directs PRomop to ProcedureOccurrence, with the source CPT retained.

4. PatientRecord

Projection logic can expose a recent imaging event or derived cancer-status evidence where the application needs it.

The imaging report's clinical conclusion is handled separately and honestly. A conclusion such as “pulmonary nodule present” can become a coded condition or observation when a suitable source code and mapping exist; narrative text is retained as provenance rather than pretending it is already a structured diagnosis. If the EHR supplies only a local imaging code, PRomop preserves it and routes it through a governed local mapping workflow. A local source concept can be mapped to a standard concept; when no appropriate standard concept exists, the event remains identifiable as a local concept rather than being silently guessed.

Example 3: a SNOMED CT diagnosis from an EHR

Now consider a FHIR Condition coded SNOMED CT 44054006, type 2 diabetes mellitus. In OMOP, SNOMED CT is the standard vocabulary for most conditions and findings. The importer resolves the code against the PRomop Concept table, verifies its standard, Condition-domain concept, and creates a ConditionOccurrence record linked to the patient and recorded date.

Here, the original SNOMED CT code is already the canonical clinical representation. PRomop still preserves the incoming coding in the source fields, records the provenance type as appropriate for the EHR source, and uses the standard concept identifier for analysis. The PatientRecord refresh can turn that longitudinal fact into a current comorbidity flag used consistently by eligibility checks, population analytics, and patient-facing summaries.

Why this matters: standardize once, decide many times

Athena is the shared reference point that lets PRomop reconcile code systems without erasing their origin. The transactional OMOP record stores what happened and how it was originally expressed. PatientRecord computes what is true and useful now. That separation means a newly arrived FHIR record can improve trial matching, care evaluation, analytics, and patient summaries without each product implementing its own private translation layer.

It also keeps the system humble. Mappings are versioned, source values are retained, and ambiguous or unmapped local codes are surfaced for review. In clinical data, interoperability is not achieved by hiding uncertainty. It is achieved by making meaning explicit enough to reuse—and provenance intact enough to trust.

Further reading

  1. OMOP CDM conventions and standardized vocabulary fields
  2. OHDSI FAQ: Athena vocabulary mappings and the Concept Relationship table
  3. THEMIS guidance on LOINC measurements, values, and units
← Back to Blog