~/fhir-interop

a fhir and hl7v2 pipeline.

A learning project. It takes clinical data in two common formats, FHIR R4 bundles and raw HL7v2 messages, turns both into one internal model, validates it, stores it in Postgres, and serves it back over a GraphQL API and a small FHIR REST API. Written in Go.

I built it to keep practicing the stack from my internship on something in my own field. It only ever touches synthetic patient data. Farrah handles QA while I work on the backend.

The pipeline demo running the terminology sample. Convert is skipped because the input is already FHIR.

how it works

Both input formats end up as the same set of Go structs, so everything after the first step is shared.

  1. Parse. FHIR bundles are decoded straight from JSON. HL7v2 messages go through a segment and field parser written by hand, no third-party library.
  2. Normalize. HL7v2 segments (PID, PV1, OBX) are mapped onto the same FHIR structs the JSON path produces.
  3. Validate. Structure, required fields, and values are checked against FHIR value sets and date formats. Problems come back as a list rather than a single error.
  4. Terminology check. Observation codes are checked against LOINC and SNOMED using a public terminology server. The check runs in a bounded worker pool and fails open if the server is slow.
  5. Persist. Each resource is stored in Postgres with a few flattened columns for querying plus the raw JSON. Writes upsert by id.
  6. Serve. A GraphQL API with API-key auth and cursor pagination, and a read-only FHIR REST API for reading and searching Patient records.

the two APIs

GraphQL is the main way to read the data. The FHIR REST API returns the shapes other health systems expect, so far for Patient only.

GraphQL playground. The query is on the left, the live response on the right, with the API key set in the headers tab.
GET /fhir/Patient?family=West&_count=2 returns a FHIR searchset Bundle. Search params: family, given, gender, birthdate, _count.

pipeline demo

There is a page that runs a bundle through the whole pipeline and streams each stage to the browser over Server-Sent Events. The terminology results show each code as valid, invalid, or unverified when the server could not be reached. It's the page in the screenshot at the top.

browsing the data

A small page over the GraphQL API for looking through stored patients and their encounters and observations. It is meant for checking that ingestion worked, not as a real UI.

Patient list on the left, the selected patient's encounters and observations on the right.

stack

language
Go
graphql
gqlgen
database
PostgreSQL, GORM
terminology
tx.fhir.org, LOINC and SNOMED
runtime
Docker, multi-stage build on a distroless base
ci
GitHub Actions: format, vet, build, test on every push
data
synthetic only, generated with Synthea
madarang.dev