“FHIR or openEHR?” is asked in most health data platform discussions, usually as if one had to win. The question hides a category error. HL7 FHIR was designed to move clinical data between systems. openEHR was designed to keep a clinical record for the lifetime of a patient. A platform has to do both, and the two standards are closer to a transport and a store than to competitors.
The overlap is still real. FHIR servers persist data. openEHR has a REST API. Both model a blood pressure. An architect has to decide which model is authoritative, where the other one is derived, and what is lost at the boundary.
This article compares the two on the dimensions that affect architecture, shows the same blood pressure in each, and gives a coexistence design with a working mapping in Python and C#. The mapping is small on purpose. Its interesting output is not the FHIR resource but the list of what the FHIR resource could not carry.
Two standards, two questions

FHIR defines a fixed set of resource types such as Patient, Observation and MedicationRequest, each deliberately generic. An Observation can hold almost any measurement. Specific meaning is added by profiles: an implementation guide says that a blood pressure is an Observation with a particular LOINC code and two components. FHIR follows the 80 per cent rule, so an element belongs in the base resource only if most systems use it, and everything else is an extension.
openEHR separates two levels. A small, stable reference model defines generic structures: compositions, observations, evaluations, instructions, actions, and data types. Clinical content is defined above it in archetypes, each of which aims to describe everything that could sensibly be recorded about one concept. The blood pressure archetype covers the readings, and also the patient’s position, the cuff size, the measurement site, the device and more. Templates then select and constrain archetypes for a real form or message. The software only has to implement the reference model, so new clinical content does not require new tables or a release.
That difference in philosophy produces the practical one. A FHIR profile says what must be present for an exchange to be valid. An archetype says what may be recorded so that nothing clinically relevant is lost. One is a minimum, the other a maximum.
The same blood pressure, twice
An openEHR composition in canonical JSON, trimmed to the blood pressure entry. The node identifiers such as at0004 come from the archetype and are the same in every system that uses it.
{
"_type": "OBSERVATION",
"archetype_node_id": "openEHR-EHR-OBSERVATION.blood_pressure.v2",
"data": {
"_type": "HISTORY", "archetype_node_id": "at0001",
"events": [{
"_type": "POINT_EVENT", "archetype_node_id": "at0006",
"time": {"_type": "DV_DATE_TIME", "value": "2026-11-03T09:42:00+00:00"},
"data": {"_type": "ITEM_TREE", "archetype_node_id": "at0003", "items": [
{"_type": "ELEMENT", "archetype_node_id": "at0004", "name": {"value": "Systolic"},
"value": {"_type": "DV_QUANTITY", "magnitude": 142, "units": "mm[Hg]"}},
{"_type": "ELEMENT", "archetype_node_id": "at0005", "name": {"value": "Diastolic"},
"value": {"_type": "DV_QUANTITY", "magnitude": 88, "units": "mm[Hg]"}}
]},
"state": {"_type": "ITEM_TREE", "archetype_node_id": "at0007", "items": [
{"_type": "ELEMENT", "archetype_node_id": "at0008", "name": {"value": "Position"},
"value": {"_type": "DV_CODED_TEXT", "value": "Sitting",
"defining_code": {"terminology_id": {"value": "local"}, "code_string": "at1001"}}}
]}
}]
},
"protocol": {"_type": "ITEM_TREE", "archetype_node_id": "at0011", "items": [
{"_type": "ELEMENT", "archetype_node_id": "at0013", "name": {"value": "Cuff size"},
"value": {"_type": "DV_CODED_TEXT", "value": "Large Adult",
"defining_code": {"terminology_id": {"value": "local"}, "code_string": "at0016"}}},
{"_type": "ELEMENT", "archetype_node_id": "at0014", "name": {"value": "Location of measurement"},
"value": {"_type": "DV_CODED_TEXT", "value": "Right arm",
"defining_code": {"terminology_id": {"value": "local"}, "code_string": "at0025"}}}
]}
}
The same measurement as a FHIR R4 Observation, following the standard blood pressure profile: a panel code with a systolic and a diastolic component.
{
"resourceType": "Observation",
"status": "final",
"identifier": [{"system": "urn:openehr:composition-uid",
"value": "8f2c1a9e-3b7d-4c55-9a10-6d2e4f7b1c33::synthetic.example::1"}],
"category": [{"coding": [{"system": "http://terminology.hl7.org/CodeSystem/observation-category",
"code": "vital-signs"}]}],
"code": {"coding": [{"system": "http://loinc.org", "code": "85354-9", "display": "Blood pressure panel"}]},
"subject": {"reference": "Patient/pat-001"},
"effectiveDateTime": "2026-11-03T09:42:00+00:00",
"component": [
{"code": {"coding": [{"system": "http://loinc.org", "code": "8480-6", "display": "Systolic blood pressure"}]},
"valueQuantity": {"value": 142, "unit": "mmHg", "system": "http://unitsofmeasure.org", "code": "mm[Hg]"}},
{"code": {"coding": [{"system": "http://loinc.org", "code": "8462-4", "display": "Diastolic blood pressure"}]},
"valueQuantity": {"value": 88, "unit": "mmHg", "system": "http://unitsofmeasure.org", "code": "mm[Hg]"}}
],
"bodySite": {"coding": [{"system": "http://snomed.info/sct", "code": "368209003",
"display": "Right upper arm structure"}]}
}
The readings, the time and the measurement site made the crossing. The patient’s position and the cuff size did not. FHIR could carry them with extensions or additional components, but the standard profile does not ask for them, so a receiving system will not look for them. For a hypertension clinic that distinction matters: a seated reading and a standing reading are different clinical facts.
Three other differences are visible. The openEHR record is identified by meaning, through archetype paths, where FHIR identifies meaning by terminology codes. The openEHR record has no patient reference inside it, because a composition lives inside one patient’s EHR, while every FHIR resource names its subject. And the openEHR version identifier in uid ends in a version number, because every change creates a new version by design.
Querying each
The models lead to different query styles. FHIR search is a REST call with parameters defined per resource type:
GET [base]/Observation?patient=pat-001&code=http://loinc.org|85354-9&date=ge2026-01-01&_sort=-date
openEHR uses the Archetype Query Language (AQL), which looks like SQL but navigates archetype paths, so one query works on any conformant repository regardless of its physical schema:
SELECT
c/uid/value AS composition,
o/data[at0001]/events[at0006]/time/value AS measured_at,
o/data[at0001]/events[at0006]/data[at0003]/items[at0004]/value/magnitude AS systolic,
o/data[at0001]/events[at0006]/data[at0003]/items[at0005]/value/magnitude AS diastolic,
o/data[at0001]/events[at0006]/state[at0007]/items[at0008]/value/value AS position
FROM EHR e
CONTAINS COMPOSITION c
CONTAINS OBSERVATION o[openEHR-EHR-OBSERVATION.blood_pressure.v2]
WHERE e/ehr_id/value = $ehr_id
AND o/data[at0001]/events[at0006]/data[at0003]/items[at0004]/value/magnitude >= 140
ORDER BY o/data[at0001]/events[at0006]/time/value DESC
In release 1.1.0 of the openEHR REST API the query is sent as the q field of a POST to /v1/query/aql, with query_parameters alongside. Note what the AQL can ask that the FHIR search cannot without custom search parameters: the patient’s position, and a population query across every EHR by dropping the WHERE on ehr_id. Note also what FHIR gives that AQL does not: a query any FHIR client already knows how to issue, with no knowledge of archetypes.
I had no openEHR repository available while writing. The paths use node identifiers checked against the published blood pressure archetype, and the syntax follows the AQL specification, but the query has not been run. Test it against your own repository and template before relying on it.
When to use which
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#FFF3E0','primaryTextColor':'#232F3E','primaryBorderColor':'#FF9900','lineColor':'#232F3E','clusterBkg':'#F8FAFC','clusterBorder':'#CBD5E1','edgeLabelBackground':'#FFFFFF','nodeTextColor':'#232F3E'}}}%%
flowchart TD
Q1{What is the system's main job?}
Q1 -- "Exchange data with other
organisations and apps" --> F[FHIR as the contract]
Q1 -- "Be the long-term
clinical record" --> Q2{How rich and varied
is the clinical content?}
Q2 -- "Standard summaries: problems,
medication, results" --> FS[FHIR-native store
is sufficient]
Q2 -- "Detailed, evolving, specialty
content owned by clinicians" --> O[openEHR repository]
O --> B[Expose it through FHIR
for exchange]
FS --> B2[Add openEHR later if
content outgrows profiles]
class Q1,Q2 edge
class F,FS mesh
class O compute
class B,B2 store
classDef compute fill:#FFF3E0,stroke:#FF9900,stroke-width:2px,color:#232F3E
classDef edge fill:#EBF8FF,stroke:#0073BB,stroke-width:2px,color:#004C74
classDef store fill:#F0FFF4,stroke:#1D8102,stroke-width:2px,color:#145202
classDef alert fill:#FEF2F2,stroke:#D13212,stroke-width:2px,color:#991B1B
classDef mesh fill:#F3E8FF,stroke:#7C3AED,stroke-width:2px,color:#4C1D95- Choose FHIR as the contract for anything that crosses an organisational boundary: patient access, referrals, national summaries, app integration. The ecosystem of servers, client libraries, app-launch standards and regulation is built around it.
- Choose a FHIR-native store when the record you keep is broadly what you exchange. A patient-facing app, a referral hub or an integration layer rarely needs more than well-profiled resources.
- Choose openEHR for persistence when clinicians need to define and change detailed content, when several applications from different vendors must share one record, and when the data has to outlive every one of those applications.
- Do not choose openEHR to avoid FHIR. You will still need a FHIR interface the day a partner, a regulator or an app asks for one.
How they coexist

The common pattern keeps one authoritative clinical data repository and derives the exchange view from it. Applications write compositions. A mapping layer translates archetype paths into FHIR elements and local codes into standard terminologies. A FHIR API serves the result to everyone outside.
%%{init: {'theme':'base','themeVariables':{'actorBkg':'#FFF3E0','actorBorder':'#FF9900','actorTextColor':'#232F3E','signalColor':'#0073BB','signalTextColor':'#0F172A','noteBkgColor':'#FFF8E7','noteBorderColor':'#FF9900','noteTextColor':'#232F3E','labelBoxBkgColor':'#FFF3E0','labelBoxBorderColor':'#FF9900','labelTextColor':'#232F3E','loopTextColor':'#232F3E','activationBkgColor':'#EBF8FF','activationBorderColor':'#0073BB','lineColor':'#232F3E','textColor':'#232F3E'}}}%%
sequenceDiagram
participant P as Partner system
participant F as FHIR API
participant M as Mapping layer
participant O as openEHR repository
P->>F: GET Observation?patient=..
F->>M: Resolve patient to EHR id
M->>O: AQL for blood pressure entries
O-->>M: Result set by archetype path
M-->>F: FHIR Observations + loss report
F-->>P: Bundle conforming to the profileThere are two ways to run that mapping, and the choice is architectural.
| On request (facade) | On write (materialised) | |
|---|---|---|
| How it works | Each FHIR call is translated to AQL and the result mapped back | Every committed composition is mapped once and stored in a FHIR server |
| Freshness | Always current | Lags by the event pipeline |
| FHIR search | Limited to what you translate to AQL | Full search on the FHIR server |
| Cost of a mapping change | Immediate | Re-map and reload affected data |
| Failure mode | A slow or unsupported query | A stale or missing resource |
| Fits | A small set of well-defined reads | Broad API access, analytics, national exchange |
On Azure the pieces map as follows.
| Role | Azure building block | Notes |
|---|---|---|
| openEHR repository | An open-source or commercial openEHR server on Container Apps or AKS, with Azure Database for PostgreSQL where the server uses it | I know of no first-party managed openEHR service on Azure. You run one or buy one |
| Mapping layer | Container Apps or Functions running .NET or Python | Stateless. Scale it separately from both stores |
| FHIR API and store | FHIR service in Azure Health Data Services | For the materialised pattern. For a facade, your own API in front |
| Change feed | Event Grid, from the repository’s commit events | Carries composition identifiers, not clinical payloads |
| Identity | Microsoft Entra ID, with SMART on FHIR scopes at the FHIR API | One identity model for both sides |
| Patient index | A small service or table mapping EHR id to FHIR Patient id | The mapping layer must never guess this |
A mapping that reports what it loses
The sample maps the blood pressure composition shown earlier. It is hand-written for one archetype, which is not how a programme with hundreds of archetypes should work, but it makes the mechanics visible. The return value has two parts: the FHIR resources, and a report of what was mapped and what was not carried.
"""bp_mapping.py - openEHR blood pressure OBSERVATION (canonical JSON) to FHIR R4 Observation."""
from dataclasses import dataclass, field
BP_ARCHETYPE = "openEHR-EHR-OBSERVATION.blood_pressure.v2"
LOINC, UCUM, SNOMED = "http://loinc.org", "http://unitsofmeasure.org", "http://snomed.info/sct"
COMPOSITION_ID_SYSTEM = "urn:openehr:composition-uid"
# archetype node -> (LOINC code, display)
COMPONENTS = {"at0004": ("8480-6", "Systolic blood pressure"), "at0005": ("8462-4", "Diastolic blood pressure")}
# archetype-local code for Location of measurement -> SNOMED CT body site. Deliberately incomplete.
BODY_SITES = {"at0025": ("368209003", "Right upper arm structure")}
@dataclass
class MappingReport:
mapped: list[str] = field(default_factory=list)
not_carried: list[str] = field(default_factory=list) # present in openEHR, absent from the FHIR output
def _items(tree: dict | None) -> dict[str, dict]:
return {item["archetype_node_id"]: item for item in (tree or {}).get("items", [])}
def blood_pressure_to_fhir(composition: dict, patient_id: str) -> tuple[list[dict], MappingReport]:
"""One FHIR Observation per openEHR event. patient_id comes from your EHR-to-Patient index, not from here."""
report, out = MappingReport(), []
uid = composition["uid"]["value"]
for entry in composition.get("content", []):
if entry.get("archetype_node_id") != BP_ARCHETYPE:
report.not_carried.append(f"entry {entry.get('archetype_node_id')}: no mapping defined")
continue
protocol = _items(entry.get("protocol"))
for event in entry["data"]["events"]:
values, components = _items(event.get("data")), []
for node, (code, display) in COMPONENTS.items():
if node not in values:
continue
quantity = values[node]["value"]
if quantity["units"] != "mm[Hg]":
raise ValueError(f"{node}: expected mm[Hg], got {quantity['units']}")
components.append({
"code": {"coding": [{"system": LOINC, "code": code, "display": display}]},
"valueQuantity": {"value": quantity["magnitude"], "unit": "mmHg", "system": UCUM, "code": "mm[Hg]"},
})
report.mapped.append(f"{values[node]['name']['value']} -> component {code}")
if not components:
report.not_carried.append("event without systolic or diastolic: skipped")
continue
observation = {
"resourceType": "Observation",
"status": "final",
"identifier": [{"system": COMPOSITION_ID_SYSTEM, "value": uid}], # the way back to the source
"category": [{"coding": [{"system": "http://terminology.hl7.org/CodeSystem/observation-category",
"code": "vital-signs"}]}],
"code": {"coding": [{"system": LOINC, "code": "85354-9", "display": "Blood pressure panel"}]},
"subject": {"reference": f"Patient/{patient_id}"},
"effectiveDateTime": event["time"]["value"],
"component": components,
}
site = protocol.get("at0014")
if site:
local = site["value"]["defining_code"]["code_string"]
if local in BODY_SITES:
code, display = BODY_SITES[local]
observation["bodySite"] = {"coding": [{"system": SNOMED, "code": code, "display": display}]}
report.mapped.append(f"Location of measurement -> bodySite {code}")
else:
report.not_carried.append(f"Location of measurement '{site['value']['value']}': no SNOMED mapping")
for node, item in values.items():
if node not in COMPONENTS:
report.not_carried.append(f"data/{item['name']['value']}")
for item in _items(event.get("state")).values():
report.not_carried.append(f"state/{item['name']['value']} = {item['value'].get('value')}")
for node, item in protocol.items():
if node != "at0014":
report.not_carried.append(f"protocol/{item['name']['value']} = {item['value'].get('value')}")
out.append(observation)
return out, report
The C# version has the same structure and uses only System.Text.Json, so it needs no FHIR or openEHR SDK. The core of it:
using System.Text.Json.Nodes;
namespace Interop;
/// <summary>What the mapping produced and, as important, what it could not carry.</summary>
public sealed record MappingResult(
IReadOnlyList<JsonObject> Observations, IReadOnlyList<string> Mapped, IReadOnlyList<string> NotCarried);
public static class BloodPressureMapper
{
public const string Archetype = "openEHR-EHR-OBSERVATION.blood_pressure.v2";
private const string Loinc = "http://loinc.org";
private const string Ucum = "http://unitsofmeasure.org";
private static readonly (string Node, string Code, string Display)[] Components =
[
("at0004", "8480-6", "Systolic blood pressure"),
("at0005", "8462-4", "Diastolic blood pressure"),
];
/// <param name="patientId">Comes from your EHR-to-Patient index, never from the composition.</param>
public static MappingResult ToFhir(JsonNode composition, string patientId)
{
ArgumentException.ThrowIfNullOrWhiteSpace(patientId);
List<JsonObject> observations = [];
List<string> mapped = [], notCarried = [];
string uid = Text(composition["uid"]?["value"], "composition uid");
foreach (JsonNode? entry in composition["content"]?.AsArray() ?? [])
{
string? archetype = (string?)entry?["archetype_node_id"];
if (archetype != Archetype)
{
notCarried.Add($"entry {archetype}: no mapping defined");
continue;
}
Dictionary<string, JsonNode> protocol = Items(entry!["protocol"]);
foreach (JsonNode? ev in entry["data"]?["events"]?.AsArray() ?? [])
{
Dictionary<string, JsonNode> values = Items(ev?["data"]);
JsonArray components = [];
foreach ((string node, string code, string display) in Components)
{
if (!values.TryGetValue(node, out JsonNode? element))
{
continue;
}
string units = Text(element["value"]?["units"], $"{node} units");
if (units != "mm[Hg]")
{
// A unit the mapping does not expect stops the pipeline. It never passes through.
throw new InvalidOperationException($"{node}: expected mm[Hg], got {units}");
}
components.Add(new JsonObject
{
["code"] = Coding(Loinc, code, display),
["valueQuantity"] = new JsonObject
{
["value"] = element["value"]!["magnitude"]!.GetValue<decimal>(),
["unit"] = "mmHg",
["system"] = Ucum,
["code"] = "mm[Hg]",
},
});
mapped.Add($"{Name(element)} -> component {code}");
}
if (components.Count == 0)
{
notCarried.Add("event without systolic or diastolic: skipped");
continue;
}
JsonObject observation = new()
{
["resourceType"] = "Observation",
["status"] = "final",
["identifier"] = new JsonArray(new JsonObject
{ ["system"] = "urn:openehr:composition-uid", ["value"] = uid }),
["code"] = Coding(Loinc, "85354-9", "Blood pressure panel"),
["subject"] = new JsonObject { ["reference"] = $"Patient/{patientId}" },
["effectiveDateTime"] = Text(ev!["time"]?["value"], "event time"),
["component"] = components,
};
// Body site, category and the remaining loss reporting are in the repository version.
notCarried.AddRange(Items(ev["state"]).Values
.Select(i => $"state/{Name(i)} = {(string?)i["value"]?["value"]}"));
notCarried.AddRange(protocol.Where(p => p.Key != "at0014")
.Select(p => $"protocol/{Name(p.Value)} = {(string?)p.Value["value"]?["value"]}"));
observations.Add(observation);
}
}
return new MappingResult(observations, mapped, notCarried);
}
private static Dictionary<string, JsonNode> Items(JsonNode? tree) =>
(tree?["items"]?.AsArray() ?? [])
.Where(item => item is not null)
.ToDictionary(item => Text(item!["archetype_node_id"], "archetype_node_id"), item => item!);
private static string Name(JsonNode element) => (string?)element["name"]?["value"] ?? "unnamed";
private static string Text(JsonNode? node, string what) =>
(string?)node ?? throw new InvalidOperationException($"Missing {what}");
private static JsonObject Coding(string system, string code, string display) =>
new() { ["coding"] = new JsonArray(new JsonObject
{ ["system"] = system, ["code"] = code, ["display"] = display }) };
}
Both implementations print the same report for the sample composition:
mapped -> Systolic -> component 8480-6; Diastolic -> component 8462-4; Location of measurement -> bodySite 368209003
not carried -> state/Position = Sitting; protocol/Cuff size = Large Adult
unit check -> at0004: expected mm[Hg], got kPa
The Python sample also maps back from FHIR to openEHR. The round trip restores the two readings and the time, and nothing else: no state, no protocol. That is the practical argument for deciding which side is authoritative. Whichever model is derived should never be written back over the one it came from.
The Python mapping and its tests ran on Python 3.13. The full C# version in the repository compiled with warnings treated as errors and ran on the .NET 10 SDK (10.0.115), with the same assertions. The listing above is an excerpt of that file with the body-site branch removed for length. The excerpt was also compiled on its own. All data is synthetic, and neither implementation was run against a live openEHR or FHIR server.
Mapping at scale
Nobody should hand-write a few hundred of these. Two developments make that unnecessary. FHIRconnect is a declarative mapping language for openEHR and FHIR, with an open-source engine, openFHIR, that executes the mappings in both directions. Its authors reported in November 2025 that they had mapped 24 international archetypes to 15 FHIR profiles, with about 65 per cent of the mapping content reused across projects. Separately, HL7 has been publishing openEHR archetypes as FHIR logical models, which gives the two communities a shared artefact to map against.
The tooling does not remove the governance. A mapping is a clinical statement that two things mean the same. It needs an owner, a version, a review by someone who understands both models, and a test set. Treat the mapping library as you treat the archetypes and profiles themselves.
Running it in production
- Name the source of truth. One store is authoritative for each kind of data. Derived views are read-only. Write it down, because the question returns every time a new application wants to write.
- Keep the loss report. Log what each mapping could not carry, per archetype and per profile. A rising count is the earliest sign that the clinical content has moved ahead of the exchange profile.
- Own the patient index. openEHR identifies an EHR, FHIR identifies a Patient. The link between them is master data with its own lifecycle, including merges.
- Translate terminology explicitly. Archetype-local codes such as
at0025mean nothing outside the archetype. Map them to SNOMED CT or LOINC through a terminology service, and report unmapped values instead of guessing. - Fail on unexpected units. The sample stops on anything other than
mm[Hg]. Silent conversion, or silent pass-through, is how a kilopascal reading becomes a clinical incident. - Version mappings with templates. A new template version can move or add nodes. Pin each mapping to the template versions it was tested against.
- Carry provenance both ways. The FHIR resource holds the composition identifier. Keep the reverse link too, so that a correction in the repository finds every resource derived from it.
The European Health Data Space Regulation, (EU) 2025/327, sets obligations for how electronic health data is made available and exchanged, in a European exchange format defined through implementing acts. It does not prescribe the internal data model of a record system. That leaves room for either persistence choice, provided the exchange interface conforms. Under GDPR, the repository, the derived FHIR store, the mapping logs and any event payloads all hold special-category data, so each needs the same access control, residency and retention decisions. Erasure and rectification must reach the derived copy as well as the source.
A mapping that drops context, swaps systolic and diastolic, or carries the wrong unit produces a record that looks valid and is clinically wrong. Mappings belong in the clinical safety case, with a hazard log entry, clinical review and test evidence for each one before it goes live.
Key takeaways
- FHIR is designed for exchange and openEHR for persistence. Compare them on the job in front of you, not in the abstract.
- A FHIR profile states the minimum for a valid exchange. An archetype states the maximum that may be recorded. Mapping from the second to the first always loses something.
- Decide which store is authoritative and keep every derived view read-only.
- Choose between mapping on request and mapping on write deliberately. They fail in different ways.
- Make every mapping report what it did not carry, and treat mappings as governed clinical artefacts.
If you run both standards side by side, I would like to hear which direction you chose as authoritative and what the loss report taught you. Leave a comment below.
Glossary
- HL7 FHIR
- Fast Healthcare Interoperability Resources. A standard for exchanging health data as resources over a REST API.
- openEHR
- An open specification for clinical data repositories, built on a stable reference model and clinician-authored archetypes.
- Reference model
- The small set of generic structures and data types that openEHR software implements.
- Archetype
- A reusable, formal definition of one clinical concept, such as blood pressure, with all the data that may be recorded about it.
- Template
- A selection and constraint of archetypes for a specific form, document or message.
- Composition
- The openEHR unit of committed, versioned clinical content, comparable to a document.
- AQL
- Archetype Query Language. Queries data by archetype path, independent of the physical database schema.
- Profile
- A set of constraints on a FHIR resource for a particular use, published in an implementation guide.
- Resource
- One unit of FHIR data, such as an Observation or a Patient.
- CKM
- Clinical Knowledge Manager. The shared repository where openEHR archetypes are developed and reviewed.
- Canonical JSON
- The openEHR serialisation that mirrors the reference model exactly, with type markers on each node.
- Facade
- An API that translates requests to another model at request time without storing its own copy.
- EHDS
- European Health Data Space. EU regulation governing access to and exchange of electronic health data.
- UCUM
- Unified Code for Units of Measure. The unit system used by both FHIR and openEHR quantities.
References & Further Reading
- → openEHR specifications — Reference model, archetype formalism, AQL and the REST API
- → openEHR REST API: Query — Endpoints and request fields for executing AQL
- → Blood pressure archetype in CKM — The archetype whose node identifiers are used in this article
- → HL7 FHIR R4 specification — Resources, profiles and search
- → FHIR R4: Observation Blood Pressure profile — The panel and component structure used for the output
- → FHIRconnect: Towards a seamless integration of openEHR and FHIR — The mapping language, engine and evaluation figures cited above
- → openFHIR on GitHub — The open-source engine that executes FHIRconnect mappings
- → EHRbase on GitHub — An open-source openEHR clinical data repository
- → Regulation (EU) 2025/327, European Health Data Space — The legal text
- → RAG over FHIR Data on AWS HealthLake and Azure Health Data Services — The earlier article on this blog on retrieval over FHIR records
Discover more from C4: Container, Code, Cloud & Context
Subscribe to get the latest posts sent to your email.