Hi everyone,
For the past months I’ve been working on a CDR written in Rust, called
FerroEHR. It started as a deep dive into EHRbase to understand how a CDR
works from the inside, and somewhere along the way it turned into a full
implementation of its own. Version 4.0.1 is out now, and I’m at the point
where I’m mostly testing my own assumptions. I’m looking for a few people
willing to poke at it and tell me where it falls apart.
What’s in it right now:
- ITS-REST 1.1.0 with canonical JSON and XML, AQL 1.1, running on
PostgreSQL 18. One static binary, no JVM. - The RM/BASE/AM types and the JSON/XML codecs are generated straight from
the published machine-readable specs (BMM, XSDs, the REST OpenAPI). When a
spec updates I regenerate. Where the spec text was ambiguous I kept a
register of those decisions, and the defects and contradictions I found in
the released specs along the way are being reported back to openEHR. - A
spec_profileswitch selects the specification generation the server
runs on: the released texts (RM 1.1.0 with BASE 1.2.0) or the development
generation (RM 1.2.0 with BASE 1.3.0, the default). Both are generated
from the corresponding published BMMs, so you always know exactly which
model you’re running. - ADL 2.4 source templates (parsing, AOM2 validation, flattening, OPT2) as
well as classic OPT 1.4, WebTemplate, FLAT and STRUCTURED, EHR Extract,
demographics, item tags, and an IHE ATNA audit log with a RESTful ATNA
(ITI-81) read side. - A web admin console that ships as its own container image. It covers EHR
browsing with rendered compositions, template management, stored queries,
an AQL workbench with a point-and-click query builder, contribution
authoring, and the audit browser. It talks to the server strictly over the
same public REST API you would use. - Conformance testing is built in. A runner executes a machine-readable
catalogue of about 1,100 cases against a freshly composed server and
writes the report from the run artifacts. All the numbers in the repo come
from those committed runs; I don’t type any of them by hand. I run the
same catalogue against EHRbase for comparison and publish both directions.
The measured performance classes come from open-loop sustained runs whose
histograms are committed, so anyone can re-check them.
One thing to know if you’re coming from EHRbase: FerroEHR is
deliberately strict. It accepts exactly what the released specs admit, so
templates, compositions and queries that work on a more lenient server may
be refused here. Concretely:
- Templates are validated while they are being constructed at upload. An OPT
that doesn’t parse and validate cleanly never reaches the database. There
is no “store now, fail later” path. - The JSON reader is strict: undeclared keys, duplicate keys, and malformed
identifiers (EHR ids, version ids, archetype ids) get a 400. Nothing is
silently ignored. - RM invariants and terminology bindings are enforced at commit time. A
composition that a lenient validator waves through can come back as a 422
with the exact path and rule that failed. - The AQL engine has a documented feature envelope. A construct it doesn’t
support yet gets an explicit rejection, never an approximate answer. - The simplified formats (FLAT/STRUCTURED) are implemented from the spec
text alone. There is no vendor-quirk compatibility mode, so payloads that
rely on vendor-specific FLAT behaviour may not carry over unchanged.
In every one of these cases the error tells you what was wrong and where.
If you hit a refusal you believe is spec-valid, that’s exactly the kind of
report I’m hoping for.
Fair warning: this is a young project and I’m the only maintainer, so don’t
put patient data on it just yet. And since the test catalogue is something I
built myself from the spec text, a green run proves consistency with my
reading of the specs. Whether that matches your reading is exactly the thing
I can’t verify alone.
Everything is open source (MIT; the vendored openEHR artifacts stay under
their own Apache-2.0 terms). Trying it out needs no clone and no build. The
compose files ship with every release and pull the signed, published images:
- Download
docker-compose.ymlfrom the
latest release
into an empty directory, then:
docker compose up
The API then runs at http://localhost:8080/ferroehr/rest/openehr/v1
(dev login ferroehr / ferroehr, Swagger UI at
http://localhost:8080/ferroehr/rest/swagger-ui). The admin console is one
flag away: docker compose --profile admin-ui up, then open
http://localhost:3000. The same release page carries a Keycloak overlay
for the OIDC variant and an observability overlay with Prometheus and
Grafana. For Kubernetes there is a published Helm chart
(oci://ghcr.io/rubentalstra/charts/ferroehr). Docs live at
https://ferroehr.eu/.
Things I’d especially like to hear about: real-world templates and
compositions that don’t round-trip the way you expect, AQL queries from your
daily work that get rejected or answered differently than in your current
CDR, and anything that made you give up during setup. If you think the whole
approach is misguided, that’s useful too. Better to hear it now.
Best Greetings,
Ruben Talstra
PS: I’ll also be speaking at EHRCON26 in September, on a different topic.
See you there in person as well:
EHRCON26 — Tuesday, September 22, 13:45–14:00
A DICOM RT DVH Caching Layer: Bridging DICOM and openEHR
Ruben Talstra (Hanze University of Applied Sciences)