Case study
Turn the old system off with a config flag
We built a coexistence layer that conforms a legacy HL7 v2 feed into US Core FHIR without touching the source system. Act one: the gate quarantines a screening every generic validator passes. Act two: one config flag retires the legacy intake, and nothing downstream notices.
- Standard
- HL7 v2 to FHIR R4, US Core 7.0.0
- Gate
- Structure, terminology, profile, instrument sum
- Cutover
- One config flag, intake.mode
- Work type
- Standfast internal research
The problem
A decades-old EHR that every site evolved on its own. Hundreds of bespoke point-to-point interfaces per site, most of them undocumented. And a new EHR standing up alongside it, on a timeline measured in years, while all of those interfaces keep running.
The instinct is to treat this as a re-integration project: rebuild every wire against the new system. That is a project that never ends, and it doubles the number of things that can break for the entire transition. The useful question is different. Can a layer sit between the legacy system and its consumers, conform what flows through it, and become the one place the new system plugs in, so that retiring the old one is a configuration change rather than a rebuild? We built that layer and ran it end to end.
Act one: what a generic validator misses
The pipeline reads a PHQ-9 depression screening off the legacy HL7 v2 feed, adapts it into a US Core FHIR Observation, and runs it through a four-check gate: structure, terminology, the US Core 7.0.0 profile, and one more that no profile defines. A PHQ-9 total is the sum of its nine item scores. The gate checks that it is.
The showpiece message reports a total of 14. Its nine items add up to 21. Structure passes, terminology passes, US Core 7.0.0 passes: a generic FHIR validator accepts this message and posts a wrong score to a patient's mental health record. The instrument-aware check catches it, and the pipeline quarantines it with a precise reason, SEMANTIC_SUM_MISMATCH. Quarantined, not silently dropped, not blindly published. That is the difference between moving data and modernizing a system.

Act two: retire the source system with a config flag
Today, every consumer of the legacy system's data has its own bespoke wire, and the clinical app that captures the screening is welded to that system. Coexistence changes the shape without changing the source of record: the legacy feed drops into a FHIR hub, the hub conforms, gates, and publishes one event, and both EHRs receive the result on their own return legs. The legacy system does not know anything happened.
Then the cutover. One configuration value, intake.mode, flips from legacy to direct. The feed poller goes dark, the legacy system is retired as the intake middleman, and the same clinical app sends the same ORU^R01 straight to the hub. Same parser, same gate, same event, same consumers. Nothing downstream changes, because nothing downstream was ever wired to the legacy system in the first place.

The proof is one patient, two routes, one platform. The screening that traveled through the legacy system and the one that bypassed it land as the same US Core Observation, on the same trend, behind the same read API, with the same care flags and the same deliveries. And during coexistence, the legacy system keeps receiving results on the return leg until it is fully retired. Turning it off becomes configuration, not a project.

Where the line is
This is a clean-room prototype on open standards. The screenings are synthetic. Both EHRs are in-process stand-ins: a mock MLLP listener on the legacy side and a mock FHIR endpoint on the target side. The flip is a live toggle for the sake of the walkthrough; in production it is a configuration change deployed like any other.
It is also deliberately one slice. This is the conformance and interoperability core, not the enterprise interface catalog, the governance model, or the cost model that a full modernization needs around it. What transfers is the pattern: conform at the publish boundary, strangle one interface at a time behind a canonical model, and make retirement a configuration change you can prove instead of a rebuild you have to hope for.