FHIR Transformer Module
Durable reference for the FHIR write/read transformer layer: layer responsibilities, resource coverage, code-system URIs, add-resource checklists.
Durable reference for the FHIR write/read transformer layer in
services/public-api. Update this doc as resources are added. Companion to the (one-shot) handoff atdocs/architecture/fhir-ipd-handoff-2026-05-09.md.
Module location
services/public-api/src/api/publicapi/modules/fhir/
Layer responsibilities
| Layer | Files | What it does | What it must NOT do |
|---|---|---|---|
| Capability | fhir-capability.controller.ts |
Hand-maintained CapabilityStatement at GET /fhir/metadata. Lists every advertised resource + interaction. |
Auto-derive from controllers (it doesn’t, intentionally — keeps profile claims explicit). |
| Resource controllers | resources/*.controller.ts |
One per FHIR resource type. HTTP route handlers. Fetch from Moleculer, call transformers. | Contain mapping logic (delegate to transformers). |
| Forward transformers | transformers/<resource>.transformer.ts |
medOS document → FHIR R4 resource. Pure functions. | Reach into Moleculer / DB. The controller fetches; the transformer maps. |
| Reverse transformers | transformers/fhir-to-medos.transformer.ts (single file) |
FHIR R4 resource → medOS write payload. Pure functions. | Same as above. Single file by convention. |
| Bundle composers | e.g. transformers/surgicalWorkflow.transformer.ts |
Compose multi-resource document Bundles. Imports forward transformers. | Fetch records — caller passes them in. |
Resource coverage matrix
| Resource | GET / search | POST / PUT (write) | Forward mapper | Reverse mapper | Tests |
|---|---|---|---|---|---|
| Patient | ✅ | ✅ | patient.transformer.ts |
fromFhirPatient |
partial |
| Encounter | ✅ | ✅ | encounter.transformer.ts |
fromFhirEncounter |
✅ (14 tests) |
| Condition | ✅ | ✅ | condition.transformer.ts |
fromFhirCondition |
— |
| Observation | ✅ | ✅ | observation.transformer.ts |
fromFhirObservation |
— |
| ServiceRequest | ✅ | ✅ | serviceRequest.transformer.ts |
fromFhirServiceRequest |
— |
| MedicationRequest | ✅ | ✅ | medicationRequest.transformer.ts |
fromFhirMedicationRequest |
✅ (9 tests) |
| AllergyIntolerance | ✅ | ✅ | allergyIntolerance.transformer.ts |
fromFhirAllergyIntolerance |
— |
| DocumentReference | ✅ | ✅ (via surgical bundle) | inline in surgicalWorkflow.transformer.ts |
— | ✅ via surgical |
| Procedure | partial (via surgical) | (via surgical) | surgicalWorkflow.transformer.ts |
— | ✅ (14 tests) |
| QuestionnaireResponse | (via surgical) | — | surgicalWorkflow.transformer.ts |
— | ✅ via surgical |
| Task (Acknowledgement) | ✅ | partial | ack-to-fhir-task.transformer.ts |
fhirTaskToAck |
✅ (pre-existing) |
| SurgicalWorkflow (custom Bundle) | ✅ GET /fhir/SurgicalWorkflow/:orId |
— | surgicalWorkflow.transformer.ts |
n/a | ✅ |
| EpisodeOfCare | ❌ | ❌ | — | — | — |
| Coverage | ❌ | ❌ | — | — | — |
| Account / Invoice | ❌ | ❌ | — | — | — |
Update this table when adding a new resource.
Conventions
Code system URIs
| medOS concept | URI |
|---|---|
| Standard FHIR diagnosis roles (AD/DD/CC/CM/pre-op/post-op/billing) | http://terminology.hl7.org/CodeSystem/diagnosis-role |
medOS-specific diagnosis roles (e.g. working) |
http://medos.health/CodeSystem/diagnosis-role |
| Standard FHIR location physical types | http://terminology.hl7.org/CodeSystem/location-physical-type |
| HL7 v3 ActCode (Encounter.class) | http://terminology.hl7.org/CodeSystem/v3-ActCode |
When a medOS concept has no canonical FHIR equivalent, mint a URI under http://medos.health/CodeSystem/<concept> rather than abusing the canonical URL. See encounter.transformer.ts:STANDARD_ROLE_DISPLAY for the pattern.
Reference shapes
Always emit references as { reference: '/<id>' }. Add display only when the medOS document carries a denormalised name. Don’t ever emit a bare string id.
// ✅ Right
{ reference: 'Patient/pat-7' }
{ reference: 'Location/loc-1', display: 'Ward 4B' }
// ❌ Wrong
{ reference: 'pat-7' }
'Patient/pat-7'
Date / period emission
- For dates already stored as ISO strings: pass through as-is.
- For dates stored as
Dateobjects (Mongoose default): always wrap withnew Date(x).toISOString()to avoid timezone drift between Node hosts. - Periods: always emit
{ start, end }even when one side is undefined — better for client diffing than omitting the wrapper.
_id: false on subdocs
When adding a sub-array to a Mongoose-style schema in packages/platform-api-schema, always set _id: false on the inner schema spec unless you genuinely need a stable id per entry. Example from Encounter.ts:
diagnoses: {
type: [{
conditionRef: { type: String, ref: 'Condition' },
use: String,
rank: Number,
_id: false, // ← required
}],
default: undefined,
}
Without it Mongoose auto-mints _id on every entry, breaking idempotent re-saves and bloating the document.
Test patterns
- One spec file per transformer file. Named
<name>.transformer.spec.ts. Lives in__tests__/(sibling totransformers/). - Imports:
from '../transformers/<name>'. Tests use only standard jest globals — no testing-library, no nest test harness. - Each
describeblock builds abaseFixtureand clones it per test. Don’t share mutable fixtures. - Reverse mappers (
fromFhir*) get their own consolidated spec atfhir-to-medos.transformer.spec.ts. - Bundle composers test the shape (resource types present, partOf links wired) rather than every field of every entry — those are covered by the per-resource specs.
Adding a new resource — checklist
- Forward mapper: create
transformers/<resource>.transformer.ts. ExporttoFhir(doc). - Reverse mapper (if write is supported): add
fromFhir(fhir)totransformers/fhir-to-medos.transformer.ts. - Controller: create
resources/fhir-<resource>.controller.ts. Wire to a Moleculer service. - CapabilityStatement: add an entry to
rest[0].resource[]infhir-capability.controller.ts(interactions, profile, searchParams). - Tests: create
__tests__/<resource>.transformer.spec.tsand add cases tofhir-to-medos.transformer.spec.tsfor the reverse direction. - Update this doc: tick the box in the resource coverage matrix above.
Adding a new field on an existing resource — checklist
- Schema (if needed): add the field to the entity in
packages/platform-api-schema/src/<area>/<entity>/.ts. If it’s a sub-array, set_id: false. - Forward mapper: emit the field in the relevant
tofunction. Skip it (omit) when absent — don’t emitnull/ empty arrays. - Reverse mapper: parse the field in
from. Be defensive about missing intermediate paths. - Tests: add at least three cases — present (asserts emission), absent (asserts omission), edge (e.g. partial / unknown enum).
Sibling docs
docs/architecture/fhir-ipd-handoff-2026-05-09.md— handoff for the IPD-hardening work.docs/architecture/bia-rt-2-fhir-careplan-profile.md— profile reference for the BIA CarePlan resource.docs/architecture/imaging-acknowledgement-shapes.md— acknowledgement / Task shape catalogue (interacts withack-to-fhir-task.transformer.ts).CLAUDE.md— backend deployment caveats forservices/public-api.