Retrieval-augmented generation (RAG) grew up on documents: split the text, embed the pieces, fetch the nearest ones, hand them to a model. Point that pipeline at a FHIR server and it runs without complaint. It will also, sooner or later, quote a lab value that was entered in error, miss an allergy because it ranked sixth, or return a result that belongs to a different patient with a similar history.
A FHIR record is not a document corpus. It is a graph of coded, dated, status-bearing resources, most of which have an exact answer that a search parameter can find better than an embedding can. This article sets out a retrieval design that respects that, shows it running against synthetic data, and maps it onto AWS HealthLake and the FHIR service in Azure Health Data Services as they stood on 21 July 2026.
Everything here is illustrated with synthetic data and has not been clinically validated. A system that summarises or answers questions about patient records can influence care. It needs a clinical safety case, a hazard log, a named clinical safety officer and local governance before any clinician relies on it.
Why FHIR is not a document corpus
- Most questions have one right answer. “What is the latest HbA1c?” is a query on
Observationby LOINC code, sorted by date. Similarity search can only approximate what FHIR search returns exactly. - Status changes meaning. An observation marked
entered-in-error, a condition whose verification status isrefutedand a medication request that isstoppedare all in the record. None of them is a current fact about the patient. - Raw JSON embeds badly. A serialised resource is mostly field names, system URLs and identifiers. Two observations of different analytes look more alike to an embedding model than an observation and the clinic note that discusses it.
- The patient is a boundary, not a feature. In document search a slightly wrong result is a relevance problem. Here, a result from the wrong patient is a patient-safety incident and a data breach at once.
- Some facts must always be present. Allergies and current medication matter to almost any clinical answer, and they are rarely the nearest neighbours of the question asked.
The design that follows from this has two retrieval paths behind a router, a pinned set of safety facts, and a hard partition by patient.

Render statements, do not embed JSON
The first step turns each resource into one short sentence a clinician would recognise, with the unit, the date and the status in the text, and the resource reference kept alongside as the citation. Records that are not facts are excluded with a reason. Resource types the renderer does not understand are reported, never silently dropped, because an invisible gap in a clinical summary is worse than a visible one.
"""fhir_statements.py - FHIR R4 resources to short clinical statements with provenance."""
import base64
from dataclasses import dataclass, field
# Records that must never reach a prompt as if they were facts about the patient.
EXCLUDED_STATUS = {"entered-in-error", "cancelled", "unknown"}
EXCLUDED_VERIFICATION = {"entered-in-error", "refuted"}
@dataclass(frozen=True)
class Statement:
patient_id: str
resource: str # "Observation/obs-2": the citation
kind: str # resource type
date: str # clinically relevant date, ISO 8601, may be ""
text: str
codes: tuple[str, ...] = ()
pinned: bool = False # always sent to the model, never left to similarity search
@dataclass
class RenderReport:
statements: list[Statement] = field(default_factory=list)
excluded: list[tuple[str, str]] = field(default_factory=list) # (resource, reason)
unsupported: list[str] = field(default_factory=list) # resource types with no renderer
def _codes(concept: dict | None) -> tuple[str, ...]:
return tuple(f"{c.get('system', '')}|{c.get('code', '')}" for c in (concept or {}).get("coding", []))
def _label(concept: dict | None) -> str:
concept = concept or {}
if concept.get("text"):
return concept["text"]
return next((c["display"] for c in concept.get("coding", []) if c.get("display")), "unspecified")
def _first_code(concept: dict | None) -> str:
return next((c.get("code", "") for c in (concept or {}).get("coding", [])), "")
def _patient_id(resource: dict) -> str:
ref = (resource.get("subject") or resource.get("patient") or {}).get("reference", "")
return ref.split("/")[-1]
def _condition(r: dict):
verification = _first_code(r.get("verificationStatus"))
if verification in EXCLUDED_VERIFICATION:
return None, f"verificationStatus={verification}"
clinical = _first_code(r.get("clinicalStatus")) or "status not recorded"
date = r.get("onsetDateTime") or r.get("recordedDate", "")
return (date, f"Condition ({clinical}): {_label(r.get('code'))}. Onset {date or 'not recorded'}.",
_codes(r.get("code")), False), None
def _observation(r: dict):
if r.get("status") in EXCLUDED_STATUS:
return None, f"status={r.get('status')}"
date = r.get("effectiveDateTime", "")
q = r.get("valueQuantity")
if q:
value = f"{q.get('value')} {q.get('unit') or q.get('code') or '(unit not recorded)'}"
else:
value = r.get("valueString") or _label(r.get("valueCodeableConcept"))
flag = _label(r["interpretation"][0]) if r.get("interpretation") else ""
text = f"Observation on {date or 'unknown date'}: {_label(r.get('code'))} = {value}"
return (date, text + (f" ({flag})." if flag else "."), _codes(r.get("code")), False), None
def _medication_request(r: dict):
status = r.get("status", "unknown")
if status in EXCLUDED_STATUS:
return None, f"status={status}"
date = r.get("authoredOn", "")
dose = (r.get("dosageInstruction") or [{}])[0].get("text", "dose not recorded")
text = f"Medication ({status}): {_label(r.get('medicationCodeableConcept'))}, {dose}. Prescribed {date or 'date not recorded'}."
return (date, text, _codes(r.get("medicationCodeableConcept")), status == "active"), None
def _allergy(r: dict):
verification = _first_code(r.get("verificationStatus"))
if verification in EXCLUDED_VERIFICATION:
return None, f"verificationStatus={verification}"
reactions = ", ".join(_label(m) for rx in r.get("reaction", []) for m in rx.get("manifestation", [])) or "not recorded"
text = (f"Allergy or intolerance ({r.get('criticality', 'criticality not recorded')}): "
f"{_label(r.get('code'))}. Reaction: {reactions}.")
return (r.get("recordedDate", ""), text, _codes(r.get("code")), True), None
RENDERERS = {"Condition": _condition, "Observation": _observation,
"MedicationRequest": _medication_request, "AllergyIntolerance": _allergy}
def _document_chunks(r: dict, patient_id: str, max_chars: int = 600) -> list[Statement]:
"""Free-text notes are the one place where semantic search earns its keep."""
if r.get("status") in EXCLUDED_STATUS or r.get("docStatus") == "entered-in-error":
return []
out = []
for content in r.get("content", []):
att = content.get("attachment", {})
if not att.get("contentType", "").startswith("text/plain") or "data" not in att:
continue
note = base64.b64decode(att["data"]).decode("utf-8")
paragraphs, buf = [p.strip() for p in note.split("\n\n") if p.strip()], ""
for para in paragraphs + [""]:
if buf and (not para or len(buf) + len(para) > max_chars):
out.append(Statement(patient_id, f"DocumentReference/{r['id']}", "DocumentReference",
r.get("date", ""), f"Note ({_label(r.get('type'))}, {r.get('date', '')[:10]}): {buf}"))
buf = ""
buf = f"{buf}\n{para}".strip()
return out
def render_bundle(bundle: dict) -> RenderReport:
report = RenderReport()
for entry in bundle.get("entry", []):
r = entry["resource"]
kind, ref = r["resourceType"], f"{r['resourceType']}/{r.get('id', '?')}"
if kind == "Patient":
continue # demographics are not embedded
patient_id = _patient_id(r)
if not patient_id:
report.excluded.append((ref, "no patient reference"))
continue
if kind == "DocumentReference":
chunks = _document_chunks(r, patient_id)
report.statements.extend(chunks) if chunks else report.excluded.append((ref, "no usable text"))
continue
renderer = RENDERERS.get(kind)
if renderer is None:
report.unsupported.append(kind) # reported, never silently dropped
continue
rendered, reason = renderer(r)
if rendered is None:
report.excluded.append((ref, reason))
continue
date, text, codes, pinned = rendered
report.statements.append(Statement(patient_id, ref, kind, date, text, codes, pinned))
return report
Three choices in that file are deliberate. Patient demographics are not rendered at all, which keeps names and addresses out of the vector store. A stopped medication is still rendered, labelled as stopped, because “was this patient ever on gliclazide” is a fair question, but only active medication is pinned. And the exclusion lists are short constants at the top of the file, where a clinical reviewer can read and challenge them.
Two retrieval paths and a pinned set
Retrieval then takes one of two routes. A question that names a coded concept and asks for the latest value goes to coded data and returns exactly one statement. Everything else goes to similarity search over that patient’s statements. On both routes the patient’s allergies and active medication are added from structured data, whatever the question was.
"""fhir_retrieval.py - patient-scoped retrieval: a structured path, a semantic path, pinned safety facts."""
import hashlib
import math
import re
from typing import Protocol
from urllib.parse import urlencode
from fhir_statements import Statement
LOINC = "http://loinc.org"
# Questions with one right answer go to coded data. Extend from your own terminology service.
CODED_CONCEPTS = {
"hba1c": (LOINC, "4548-4"), "glycated haemoglobin": (LOINC, "4548-4"), "a1c": (LOINC, "4548-4"),
"egfr": (LOINC, "62238-1"), "creatinine": (LOINC, "2160-0"), "potassium": (LOINC, "2823-3"),
}
LATEST = re.compile(r"\b(latest|last|most recent|current)\b", re.I)
class Embedder(Protocol):
def embed(self, text: str) -> list[float]: ...
class HashingEmbedder:
"""Deterministic stand-in for tests. Production uses a real embedding model behind the same method."""
def __init__(self, dims: int = 256) -> None:
self.dims = dims
def embed(self, text: str) -> list[float]:
vec = [0.0] * self.dims
for token in re.findall(r"[a-z0-9]+", text.lower()):
vec[int(hashlib.sha256(token.encode()).hexdigest(), 16) % self.dims] += 1.0
norm = math.sqrt(sum(v * v for v in vec)) or 1.0
return [v / norm for v in vec]
class PatientIndex:
"""One partition per patient. The patient id selects the partition; it is never a similarity signal."""
def __init__(self, embedder: Embedder) -> None:
self._embedder = embedder
self._partitions: dict[str, list[tuple[Statement, list[float]]]] = {}
def add(self, statements: list[Statement]) -> None:
for s in statements:
self._partitions.setdefault(s.patient_id, []).append((s, self._embedder.embed(s.text)))
def statements(self, patient_id: str) -> list[Statement]:
return [s for s, _ in self._partitions.get(patient_id, [])]
def search(self, patient_id: str, question: str, k: int = 5) -> list[Statement]:
q = self._embedder.embed(question)
scored = [(sum(a * b for a, b in zip(q, v)), s) for s, v in self._partitions.get(patient_id, [])]
return [s for score, s in sorted(scored, key=lambda t: t[0], reverse=True)[:k] if score > 0]
def purge(self, patient_id: str) -> int:
"""Erasure: remove every derived record when the source record is erased."""
return len(self._partitions.pop(patient_id, []))
def coded_concept(question: str) -> tuple[str, str] | None:
lowered = question.lower()
return next((code for term, code in CODED_CONCEPTS.items() if term in lowered), None)
def fhir_search_url(base_url: str, patient_id: str, system: str, code: str, count: int = 1) -> str:
"""The same question asked of the FHIR server directly. Works on HealthLake and the Azure FHIR service."""
query = urlencode({"patient": patient_id, "code": f"{system}|{code}", "_sort": "-date", "_count": count})
return f"{base_url.rstrip('/')}/Observation?{query}"
def retrieve(index: PatientIndex, patient_id: str, question: str, k: int = 5) -> dict:
"""Returns pinned safety facts, plus either an exact coded answer or semantic matches."""
everything = index.statements(patient_id)
pinned = [s for s in everything if s.pinned]
concept = coded_concept(question)
if concept and LATEST.search(question):
token = f"{concept[0]}|{concept[1]}"
matches = sorted((s for s in everything if token in s.codes), key=lambda s: s.date, reverse=True)
return {"route": "structured", "pinned": pinned, "evidence": matches[:1]}
evidence = [s for s in index.search(patient_id, question, k) if not s.pinned]
return {"route": "semantic", "pinned": pinned, "evidence": evidence}
def build_context(result: dict) -> str:
def lines(items):
return "\n".join(f"- {s.text} [{s.resource}]" for s in items) or "- none recorded"
return ("Answer only from the record extracts below. Cite the bracketed resource for every statement. "
"If the extracts do not contain the answer, say that the record does not show it.\n\n"
f"ALWAYS CONSIDER (allergies and active medication):\n{lines(result['pinned'])}\n\n"
f"EVIDENCE FOR THIS QUESTION:\n{lines(result['evidence'])}")
The router here is a keyword table, which is enough to show the principle and far too little for production. The realistic version gives the model one tool that runs FHIR searches, and fhir_search_url shows the shape of the call: the same question, asked of the FHIR server, for the freshest answer available. The in-memory index stands in for a vector store with a per-patient filter or index. The vector store comparison covers where that boundary sits in each AWS option.
%%{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 U as Clinician app
participant R as Retrieval service
participant F as FHIR server
participant V as Vector store
U->>R: Question + SMART launch context
R->>F: Allergies and active medication for patient
F-->>R: Pinned facts
alt Coded question
R->>F: Observation search by code, newest first
F-->>R: Newest matching result
else Free-text question
R->>V: Similarity search within the patient partition
V-->>R: Note chunks with resource references
end
R-->>U: Extracts with citations, ready for the modelWhat the tests assert
The test builds a synthetic bundle for two patients. The second patient is designed to be a better match for the test questions than the first: a higher HbA1c on a later date, and a note that mentions dizziness three times.
| Property | How it is tested | Result |
|---|---|---|
| Corrected results are excluded | An HbA1c of 71.0 % marked entered-in-error on the latest date | Reported as excluded with its reason. The latest value returned is 7.2 % |
| Refuted diagnoses are excluded | A myocardial infarction with verification status refuted | Reported as excluded. It never appears in a context |
| Gaps are visible | An Immunization resource with no renderer | Listed under unsupported types |
| Coded questions get exact answers | “What is the latest HbA1c?” | Structured route, one statement, Observation/o2 |
| Patients never mix | The same questions, with a better-matching second patient in the index | Only the first patient’s statements are returned on both routes |
| Safety facts are always present | “Has the patient mentioned dizziness?” | The penicillin allergy and active metformin are in the context |
| Stopped drugs are not shown as current | A stopped gliclazide prescription | Absent from the pinned section |
| Everything is citable | Every rendered statement | Each carries a patient id and a resource reference |
| Erasure reaches derived data | Purge of one patient | No statements remain for that patient |
The context built for the dizziness question, exactly as the test printed it:
Answer only from the record extracts below. Cite the bracketed resource for every statement. If the extracts do not contain the answer, say that the record does not show it.
ALWAYS CONSIDER (allergies and active medication):
- Medication (active): Metformin 500 mg tablets, one tablet twice daily. Prescribed 2024-02-01. [MedicationRequest/m1]
- Allergy or intolerance (high): Penicillin. Reaction: anaphylaxis. [AllergyIntolerance/a1]
EVIDENCE FOR THIS QUESTION:
- Note (Clinic note, 2026-06-02): Review of type 2 diabetes. Patient reports good adherence to metformin.
Foot examination normal. Reports occasional dizziness on standing in the mornings.
Plan: continue current treatment, repeat HbA1c in three months. [DocumentReference/d1]
Both modules ran on Python 3.13 with no dependencies, against hand-written synthetic FHIR R4 resources. The embedder in the test is a deterministic hashing function, so the test proves the partitioning, exclusion, routing and pinning logic. It says nothing about the retrieval quality of a real embedding model, and nothing here was run against HealthLake or the Azure FHIR service.

Mapping the design to HealthLake and Azure Health Data Services
Both services are managed FHIR R4 servers with SMART on FHIR authorization, and the design needs nothing that only one of them has.
| Step | AWS HealthLake | Azure Health Data Services FHIR service |
|---|---|---|
| Pinned facts and coded questions | FHIR search on AllergyIntolerance, MedicationRequest, Observation | The same FHIR searches |
| One patient’s whole record | Patient/$everything, available since February 2024 | Patient/$everything, with _type, _since, start and end. No _count, and RelatedPerson is not returned |
| Initial load of the index | Bulk $export to Amazon S3. Filters on meta.security and meta.tag since April 2026 | Bulk $export to Azure storage |
| Keeping the index current | FHIR Subscriptions, since October 2025 | FHIR events delivered through Event Grid |
| PHI in free text | Amazon Comprehend Medical PHI detection | De-identification service: tag, redact or surrogate, covering the 18 HIPAA identifiers |
| Embeddings and generation | Amazon Bedrock | Azure OpenAI |
| Vector store | S3 Vectors, OpenSearch Serverless or Aurora pgvector | Azure AI Search |
| Caller identity | SMART on FHIR, with v2 scopes since January 2025, or IAM | SMART on FHIR, or Microsoft Entra ID role-based access |
| Erasure at source | $erase for one resource and its versions, $purge for a patient compartment, since August 2025 | Hard delete of resources and their history |
| EU data residency | Dublin (Ireland) Region available since June 2025 | Check regional availability for your workspace |
Microsoft lists 30 September 2026 as the end date for Azure API for FHIR, the older standalone service. If your FHIR data still lives there, the migration to the FHIR service in Azure Health Data Services comes before any retrieval project, and every endpoint and identity setting in your design will change with it.
HealthLake’s integrated medical NLP, which extracts entities from notes into FHIR resources, has been off by default since February 2023 and needs a support request to enable. Do not design on the assumption that notes arrive pre-coded.
Keeping the index in step with the record
A vector index of clinical data is a copy, and copies go stale. Three events must reach it, and the third is the one usually forgotten.
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#FFF3E0','primaryTextColor':'#232F3E','primaryBorderColor':'#FF9900','lineColor':'#232F3E','clusterBkg':'#F8FAFC','clusterBorder':'#CBD5E1','edgeLabelBackground':'#FFFFFF','nodeTextColor':'#232F3E'}}}%%
flowchart TD
E[Change event from the FHIR server] --> T{What changed?}
T -- "Created or updated" --> R[Re-render the resource]
R --> S{Still a fact?}
S -- "Yes" --> U[Replace its statements
in the patient partition]
S -- "No: entered-in-error,
refuted, cancelled" --> D[Delete its statements]
T -- "Deleted" --> D
T -- "Patient erased" --> P[Purge the whole partition
and confirm it is empty]
U --> A[Write an audit record]
D --> A
P --> A
class E,T,S edge
class R,U compute
class D,P alert
class A 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- Updates replace, they do not append. Key statements by resource reference so that a corrected value overwrites the old one. Two versions of the same result in an index is how a superseded value gets quoted.
- A status change can be a deletion. When a result is marked
entered-in-errorthe resource still exists and an update event fires. The indexer must treat it as a removal. - Erasure must reach every copy. When a patient compartment is purged at source, the statements, embeddings, caches and any stored prompts or transcripts go too. Verify the partition is empty and record that you did.
- Measure the lag. The time between a write to the FHIR server and its appearance in the index is a clinical safety parameter. Publish it, alarm on it, and send coded questions to the FHIR server directly so that the most safety-critical answers do not depend on it.
Identity, consent and audit
- The patient comes from the launch context. With SMART on FHIR, the patient in scope is established when the clinician opens the record. The retrieval service takes it from the token, never from the wording of the question, where a typed name or a prompt injection could change it.
- The user’s access applies to retrieval. If a clinician may not read a category of data in the record system, the assistant must not surface it. Use security labels on resources, filter on them at indexing and at query time, and remember that a shared vector index flattens whatever access model the source had unless you rebuild it.
- Record what was shown. For each answer, log who asked, for which patient, which resources were retrieved and what the model returned. That log is how a clinician’s later question, “why did it say that”, gets answered. It is also special-category data in its own right.
- De-identify where identity is not needed. For evaluation sets, analytics and model comparison, run notes through PHI detection first. Treat the output as lower risk, not as anonymous: automated de-identification misses things, and both vendors say so.
Running it in production
- Build an evaluation set with clinicians. Real question types, expected source resources, and cases designed to fail: the corrected result, the stopped drug, the similar patient. Run it on every change to prompts, models, renderers or index settings.
- Extend the renderers deliberately. Each new resource type needs a clinical decision about which statuses count as facts. Track the unsupported list from the report and show users what the assistant cannot see.
- Normalise dates and units. The sample sorts ISO 8601 strings. Real records mix precisions and time zones, and the same analyte arrives in different units from different laboratories.
- Keep the model’s instructions narrow. Answer from extracts, cite every statement, say when the record is silent. Then test that it does, because an instruction is a request and not a guarantee.
- Watch cost at the vector store, not only the model. One partition per patient suits stores that bill by index size or support cheap per-key filtering. Size it with your patient count before choosing.
- Keep a human in the decision. Present answers with their citations as a reading aid. Nothing the assistant produces should write back to the record or trigger an action without a clinician’s review.
Health data is special-category data under GDPR. Processing it this way calls for a data protection impact assessment, a lawful basis under Articles 6 and 9, data residency decisions for the FHIR store, the vector store and the model endpoint, and contractual assurance that prompts are not retained or used for training. Embeddings and prompt logs derived from the record carry the same obligations as the record. Depending on its intended purpose, software that informs clinical decisions may also be a medical device, and may be a high-risk system under the EU AI Act. Get a regulatory assessment early. It changes the evidence you need to collect from the first prototype onward.
Key takeaways
- Use FHIR search for questions with an exact answer, and similarity search for free-text notes. Most clinical questions are the first kind.
- Partition by patient and take the patient from the authenticated launch context. Never let the patient be a ranking signal.
- Exclude records that are not facts: entered-in-error, refuted, cancelled. Report what was excluded and what could not be read.
- Pin allergies and active medication into every context from structured data.
- Treat the index as a copy with a measured lag, and make corrections and erasure reach it.
If you have taken a retrieval system over FHIR data through clinical safety review, I would like to hear which hazards your reviewers raised that are missing here. Leave a comment below.
Two days after this article’s date, on 23 July 2026, AWS announced previews of a HealthLake Data Transformation Agent for converting C-CDA and CSV data to FHIR, and of resource matching for duplicate records. Azure API for FHIR reached its 30 September 2026 end date. Neither changes the retrieval design described here. Check the current release notes of both services before starting.
Glossary
- FHIR
- Fast Healthcare Interoperability Resources. The HL7 standard for representing and exchanging health data as resources over a REST API.
- Resource
- One unit of FHIR data, such as an Observation, Condition or MedicationRequest.
- Patient compartment
- The set of resources that belong to one patient. Operations such as
$everythingreturn it. - LOINC
- The coding system most widely used to identify laboratory tests and clinical observations.
- SNOMED CT
- A clinical terminology used to code diagnoses, findings and procedures.
- Entered-in-error
- A FHIR status marking a record that should never have existed. It remains stored for audit.
- Verification status
- On a Condition or AllergyIntolerance, whether it is confirmed, provisional, refuted or entered in error.
- SMART on FHIR
- An OAuth 2.0 profile for launching apps against a FHIR server with a user, scopes and a patient in context.
- Bulk export (
$export) - A FHIR operation that writes large sets of resources to storage asynchronously.
- RAG
- Retrieval-augmented generation. Fetching relevant extracts and giving them to a model so that answers are grounded in them.
- Pinned context
- Facts added to every prompt from structured data, independent of similarity ranking.
- Provenance
- The link from a statement in an answer back to the resource it came from.
- De-identification
- Removing or replacing identifiers in data. Automated methods reduce risk and do not guarantee anonymity.
- Hazard log
- The clinical safety record of identified hazards, their causes, controls and residual risk.
References & Further Reading
- → HL7 FHIR R4 specification — Resource definitions, status value sets and search
- → FHIR R4: Patient compartment — Which resources belong to a patient
- → SMART App Launch — Launch context, scopes and token contents
- → What is AWS HealthLake? — Service overview, conformance and the clinical-use statement
- → AWS HealthLake releases — Dates for $everything, Subscriptions, SMART v2 scopes, $erase and $purge
- → Azure Health Data Services: FHIR service overview — Capabilities, access control and platform options
- → Azure FHIR service: Patient-everything — Parameters, paging phases and limitations
- → Azure Health Data Services: de-identification service — Tag, redact and surrogate operations and their limits
- → Azure API for FHIR release notes — The 30 September 2026 end date
- → Building Interoperable Healthcare Data Systems for AI — The earlier article on this blog covering standards and governance
Discover more from C4: Container, Code, Cloud & Context
Subscribe to get the latest posts sent to your email.