assembling-fhir-bundles
GitHub将多个FHIR R4资源组装为有效的事务Bundle,用于POST到EHR。支持确定性URN、自动引用重写及请求块生成,适用于需打包Condition等资源的场景。
Trigger Scenarios
Install
npx skills add maziyarpanahi/openmed --skill assembling-fhir-bundles -g -y
SKILL.md
Frontmatter
{
"name": "assembling-fhir-bundles",
"license": "Apache-2.0",
"metadata": {
"pairs": "after",
"project": "OpenMed",
"version": "1.0",
"category": "fhir-interop"
},
"description": "Package multiple FHIR R4 resources produced from OpenMed output into a single valid transaction Bundle ready to POST to an EHR, using OpenMed's verified bundle assembler openmed.clinical.exporters.fhir.to_bundle. Covers deterministic urn:uuid fullUrls, automatic in-Bundle reference rewriting, request blocks (method\/url) for transaction vs batch, and conditional create. Use after exporting-to-fhir when the user has several Condition\/Observation\/MedicationStatement resources and wants one transaction Bundle, mentions Bundle, transaction, references, or posting to a FHIR server. Builds on exporting-to-fhir; pairs after."
}
Assembling FHIR Bundles
A FHIR server ingests one transaction Bundle, not loose resources, and the
resources inside it must cross-reference each other (Condition.subject →
Patient, Observation.encounter → Encounter, DiagnosticReport.result →
Observation). OpenMed ships a deterministic, mechanical Bundle assembler —
openmed.clinical.exporters.fhir.to_bundle — that wraps the resources you built
in exporting-to-fhir into a valid R4 Bundle and wires up the references.
When to use
Use after you have a list of standalone resources from exporting-to-fhir and
the destination is a FHIR server. Reach for it when the user says "build a
Bundle", "transaction", "POST these resources", or needs internal references
resolved. To check the Bundle against US Core, hand off to validating-us-core.
What OpenMed gives you (verified API)
from openmed.clinical.exporters.fhir import to_bundle, deterministic_fullurl
bundle = to_bundle(
resources, # Sequence[Mapping] each with a resourceType
doc_id="note-2024-03-02-001", # seeds stable urn:uuid fullUrls
bundle_type="transaction", # "transaction" | "batch" | "collection" | ...
)
to_bundle does exactly three things, and never synthesises or validates:
- Deterministic
fullUrl. Each resource gets aurn:uuidseeded bydoc_id+ its index, so the same input always produces byte-identical output (golden-test friendly). You can pre-compute the same urn withdeterministic_fullurl(doc_id, index). - Reference rewriting. Any
{"reference": "ResourceType/id"}whose target is present in the Bundle is repointed at that resource'sfullUrl. References to resources absent from the Bundle (e.g. a Patient removed by de-identification) are left untouched — no dangling internal refs. - Request blocks. For
transaction/batchbundles each entry gets arequestblock ({"method": "POST", "url": "<ResourceType>"}) so the server knows to create it.
It raises ValueError if a resource lacks resourceType, or if two
resources share the same ResourceType/id (duplicate ids would silently corrupt
the reference map).
Quick start
from openmed.clinical.exporters.fhir import to_bundle
from openmed.clinical.exporters.codeable_concept_simple import coding, codeable_concept
condition = {
"resourceType": "Condition", "id": "cond-1",
"clinicalStatus": {"coding": [{
"system": "http://terminology.hl7.org/CodeSystem/condition-clinical",
"code": "active"}]},
"code": codeable_concept(
[coding("snomed", "44054006", "Diabetes mellitus type 2")],
text="type 2 diabetes"),
"subject": {"reference": "Patient/patient-1"}, # internal ref, rewritten
}
medication = {
"resourceType": "MedicationStatement", "id": "med-1", "status": "active",
"medicationCodeableConcept": codeable_concept(
[coding("rxnorm", "860975", "metformin 500 MG Oral Tablet")],
text="metformin 500 mg"),
"subject": {"reference": "Patient/patient-1"},
}
patient = {
"resourceType": "Patient", "id": "patient-1",
"gender": "unknown", # de-identified, synthetic
}
bundle = to_bundle([patient, condition, medication],
doc_id="demo-note", bundle_type="transaction")
Worked: the transaction Bundle
{
"resourceType": "Bundle",
"type": "transaction",
"entry": [
{
"fullUrl": "urn:uuid:6f1c...e2",
"resource": { "resourceType": "Patient", "id": "patient-1", "gender": "unknown" },
"request": { "method": "POST", "url": "Patient" }
},
{
"fullUrl": "urn:uuid:9a3b...77",
"resource": {
"resourceType": "Condition", "id": "cond-1",
"subject": { "reference": "urn:uuid:6f1c...e2" }
},
"request": { "method": "POST", "url": "Condition" }
},
{
"fullUrl": "urn:uuid:c0d4...19",
"resource": {
"resourceType": "MedicationStatement", "id": "med-1",
"subject": { "reference": "urn:uuid:6f1c...e2" }
},
"request": { "method": "POST", "url": "MedicationStatement" }
}
]
}
Note Condition.subject and MedicationStatement.subject were rewritten from
"Patient/patient-1" to the Patient entry's fullUrl — that is what makes the
transaction resolvable in a single POST.
Workflow
- Build resources with
exporting-to-fhir; give each a uniqueid. - Put any resource you reference inside the same Bundle — including the
Patient — so the reference resolves. References to resources you intend to be
already on the server (e.g. an existing Patient) are left as literal
"Patient/<id>"; resolve those with conditional create (below). - Call
to_bundle(resources, doc_id=<stable>, bundle_type="transaction"). - POST the whole Bundle to the server base:
POST [base] {Bundle}. - Validate first against US Core (
validating-us-core).
Conditional create (don't duplicate an existing Patient)
to_bundle writes POST <ResourceType> request blocks. To make a transaction
idempotent, post-process the entry's request to add an ifNoneExist query
so the server reuses an existing match instead of creating a duplicate:
for entry in bundle["entry"]:
if entry["resource"]["resourceType"] == "Patient":
entry["request"]["ifNoneExist"] = "identifier=http://hospital.example|MRN-REDACTED"
The server creates the Patient only if no match exists; otherwise it links the
references to the existing one. PUT with a known id is the alternative for
true upserts.
Hand-off to / from OpenMed
- From OpenMed: the resource list comes from
exporting-to-fhir, which in turn comes fromopenmed.analyze_text. Keepdoc_idstable per source document so re-running the pipeline yields the same Bundle. - De-identify a built Bundle:
openmed.interop.fhir_operations.de_identify_bundle(bundle)walks every entry's free text + XHTML narrative and de-identifies it while preserving Bundletype, entry order,fullUrls,requestblocks, and references — codes, systems, and temporal values are never altered. Use it as a final safety pass before transmission if any narrative might carry PHI. - OperationOutcome: a transaction either fully succeeds or fully fails; the
server returns a Bundle of responses (or an
OperationOutcomeon error). Surface those to the user; for your own pre-flight findings useto_operation_outcome(...)from the same package.
Edge cases & gotchas
- Resources without an
idare valid but unreferenceable — nothing can point at them and they will not be reference-rewrite targets. - Duplicate
ResourceType/idraises. This is intentional: a duplicate id would silently overwrite an entry in the reference map and corrupt cross-references. Make ids unique. - External references are left alone. Only references whose target is in
the Bundle are rewritten; a
"Patient/existing-123"you mean to resolve on the server stays literal — pair it withifNoneExistor aPUT. transactionvsbatch.transactionis atomic (all-or-nothing, server resolvesurn:uuidreferences);batchis independent per-entry and does not guarantee reference resolution. Usetransactionwhen entries reference each other.collection/documentbundles get norequestblocks (onlytransaction/batchdo) — correct, since they are not meant to be POSTed for creation.- The assembler does not validate profiles. Run
validating-us-corebefore submission.
Standards & references
- FHIR R4 Bundle: https://hl7.org/fhir/R4/bundle.html
- Transactions & batches: https://hl7.org/fhir/R4/http.html#transaction
- Bundle references /
fullUrlresolution: https://hl7.org/fhir/R4/bundle.html#references - Conditional create (
ifNoneExist): https://hl7.org/fhir/R4/http.html#cond-update
Version History
- f213557 Current 2026-07-23 00:43


