fhir_ex

An Elixir library for working with FHIR R5 resources. Provides typed structs, JSON serialization, and field-level validation for the resources and data types used in lab exam ordering and patient admissions workflows.

Features

Installation

Add fhir_ex to your dependencies:

def deps do
  [
    {:fhir_ex, "~> 0.1.0"}
  ]
end

Quick start

Lab order (ServiceRequest)

alias FhirEx.Resources.ServiceRequest
alias FhirEx.Types.{CodeableConcept, Coding, Identifier, Reference, Annotation}

order = %ServiceRequest{
  id: "order-001",
  identifier: [
    %Identifier{system: "http://hospital.org/orders", value: "ORD-2024-0042"}
  ],
  status: "active",
  intent: "order",
  priority: "routine",
  code: %CodeableConcept{
    coding: [%Coding{system: "http://loinc.org", code: "58410-2", display: "CBC panel"}],
    text: "Complete Blood Count"
  },
  subject: %Reference{reference: "Patient/patient-001"},
  encounter: %Reference{reference: "Encounter/enc-001"},
  requester: %Reference{reference: "Practitioner/pract-001"},
  authored_on: "2024-01-15T08:00:00Z",
  occurrence: {:date_time, "2024-01-15T10:00:00Z"},
  note: [%Annotation{text: "Patient must fast for 8 hours before blood draw."}]
}

json = FhirEx.JSON.encode!(order)
JSON output ```json { "resourceType": "ServiceRequest", "id": "order-001", "status": "active", "intent": "order", "priority": "routine", "authoredOn": "2024-01-15T08:00:00Z", "occurrenceDateTime": "2024-01-15T10:00:00Z", "code": { "coding": [{"system": "http://loinc.org", "code": "58410-2", "display": "CBC panel"}], "text": "Complete Blood Count" }, "subject": {"reference": "Patient/patient-001"}, "encounter": {"reference": "Encounter/enc-001"}, "requester": {"reference": "Practitioner/pract-001"}, "identifier": [{"system": "http://hospital.org/orders", "value": "ORD-2024-0042"}], "note": [{"text": "Patient must fast for 8 hours before blood draw."}] } ```

Lab result (Observation)

alias FhirEx.Resources.Observation
alias FhirEx.Types.{CodeableConcept, Coding, Reference, Quantity}

observation = %Observation{
  id: "obs-hemoglobin",
  status: "final",
  category: [
    %CodeableConcept{
      coding: [%Coding{
        system: "http://terminology.hl7.org/CodeSystem/observation-category",
        code: "laboratory"
      }]
    }
  ],
  code: %CodeableConcept{
    coding: [%Coding{system: "http://loinc.org", code: "718-7", display: "Hemoglobin"}],
    text: "Hemoglobin"
  },
  subject: %Reference{reference: "Patient/patient-001"},
  encounter: %Reference{reference: "Encounter/enc-001"},
  effective: {:date_time, "2024-01-15T09:30:00Z"},
  issued: "2024-01-15T10:00:00Z",
  value: {:quantity, %Quantity{value: 14.5, unit: "g/dL", system: "http://unitsofmeasure.org", code: "g/dL"}},
  reference_range: [
    %{
      low:  %Quantity{value: 12.0, unit: "g/dL", system: "http://unitsofmeasure.org", code: "g/dL"},
      high: %Quantity{value: 17.5, unit: "g/dL", system: "http://unitsofmeasure.org", code: "g/dL"},
      text: "12.0–17.5 g/dL"
    }
  ]
}

Patient admission (Encounter)

alias FhirEx.Resources.Encounter
alias FhirEx.Types.{CodeableConcept, Coding, Reference, Period}

encounter = %Encounter{
  id: "enc-001",
  status: "in-progress",
  class: [
    %CodeableConcept{
      coding: [%Coding{
        system: "http://terminology.hl7.org/CodeSystem/v3-ActCode",
        code: "IMP",
        display: "inpatient encounter"
      }]
    }
  ],
  subject: %Reference{reference: "Patient/patient-001"},
  service_provider: %Reference{reference: "Organization/org-001"},
  actual_period: %Period{start: "2024-01-15T08:00:00Z"},
  admission: %{
    admit_source: %CodeableConcept{
      coding: [%Coding{
        system: "http://terminology.hl7.org/CodeSystem/admit-source",
        code: "emd",
        display: "From accident/emergency department"
      }]
    }
  }
}

JSON

Encoding

json = FhirEx.JSON.encode!(resource)

Decoding

resource = FhirEx.JSON.decode!(json_string, FhirEx.Resources.Patient)

Pass the target module as the second argument. Nested structs are reconstructed automatically.

Round-trip

patient
|> FhirEx.JSON.encode!()
|> FhirEx.JSON.decode!(Patient)

Polymorphic fields

FHIR uses [x] to indicate a field that can hold one of several types (e.g. value[x], occurrence[x]). In this library they are represented as tagged tuples in Elixir and serialised to the correct FHIR JSON key.

Elixir (internal) FHIR JSON key
{:date_time, "2024-01-15T10:00:00Z"}"occurrenceDateTime"
{:period, %Period{...}}"occurrencePeriod"
{:quantity, %Quantity{...}}"valueQuantity"
{:codeable_concept, %CodeableConcept{...}}"valueCodeableConcept"
{:string, "Positive"}"valueString"
{:boolean, true}"valueBoolean"
{:range, %Range{...}}"valueRange"

Resources that use polymorphic fields:

Validation

alias FhirEx.Validation
alias FhirEx.Validation.Error

case Validation.validate(resource) do
  {:ok, resource} ->
    # proceed

  {:validation_error, errors} ->
    IO.puts(Error.format_all(errors))
end

validate!/1 raises ArgumentError instead of returning the tuple:

Validation.validate!(resource)

Error structure

Each error carries a JSON-pointer style path list and a human-readable message:

%Error{path: ["name", "1", "use"], message: "must be one of: usual | official | ..."}

Format helpers:

Error.format(error)       #=> "name.1.use: must be one of: usual | official | ..."
Error.format_all(errors)  #=> newline-separated string of all errors

Nested struct errors are validated recursively and their paths are prefixed with the parent field and list index, so you always know exactly where the problem is.

What is validated

Type / Resource Rules
Codingcode has no whitespace; system is a non-empty URI
CodeableConcept at least one of coding or text present; nested codings valid
Identifieruse is a valid code; system is a non-empty URI
HumanNameuse is a valid code; at least one name part present
Addressuse and type are valid codes
ContactPointsystem/use are valid codes; value required when system is set
Periodstart/end are valid FHIR dateTimes; startend
Quantitycomparator is a valid code; system required when code is set
Range both quantities valid; low.system matches high.system
Reference at least one of reference/identifier/display; reference string format
Annotationtext required; authorReference and authorString are mutually exclusive
Extensionurl required; at most one value[x]; value[x] and nested extensions mutually exclusive
MetaversionId matches FHIR id format; lastUpdated is a valid instant
Narrativestatus/div required; div must be an XHTML <div> element
Patientgender, birthDate format; deceased* and multipleBirth* mutual exclusivity
Encounterstatus required and valid
ServiceRequeststatus, intent, subject required; priority valid if set
Observationstatus, code required; value[x] type validated; component code required
Specimenstatus valid if set; receivedTime is a valid instant
DiagnosticReportstatus, code required

Resources

Module FHIR resource Primary use
FhirEx.Resources.PatientPatient Demographics and identifiers
FhirEx.Resources.PractitionerPractitioner Ordering clinician, result interpreter
FhirEx.Resources.OrganizationOrganization Hospital, laboratory, clinic
FhirEx.Resources.EncounterEncounter Admissions and visits
FhirEx.Resources.ServiceRequestServiceRequest Lab exam orders
FhirEx.Resources.ObservationObservation Lab results and measurements
FhirEx.Resources.SpecimenSpecimen Biological samples
FhirEx.Resources.DiagnosticReportDiagnosticReport Report bundling observations

Data types

Module FHIR type Notes
FhirEx.Types.Primitives@type aliases for all 18 FHIR R5 primitives
FhirEx.Types.CodingCoding system + code + display
FhirEx.Types.CodeableConceptCodeableConcept[Coding] + text
FhirEx.Types.IdentifierIdentifier MRN, NPI, accession number
FhirEx.Types.ReferenceReference Relative, absolute, logical, or contained
FhirEx.Types.HumanNameHumanName
FhirEx.Types.AddressAddress
FhirEx.Types.ContactPointContactPoint Phone, email, etc.
FhirEx.Types.PeriodPeriod Start/end datetime range
FhirEx.Types.QuantityQuantity Measured amount with UCUM unit
FhirEx.Types.RangeRange Low/high Quantity pair
FhirEx.Types.RatioRatio Numerator/denominator (INR, titers)
FhirEx.Types.AnnotationAnnotation Text note with author + time
FhirEx.Types.ExtensionExtension FHIR extensibility mechanism
FhirEx.Types.MetaMeta Version, profile, tags
FhirEx.Types.NarrativeNarrative XHTML human-readable summary

FHIR R5 notes

This library targets FHIR R5. Key differences from R4 that are reflected in the structs:

Development

mix deps.get
mix test
mix docs   # generate HTML documentation

The test suite has 180 tests covering struct construction, JSON round-trips, nil field omission, all polymorphic field variants, validation rules, and nested error path propagation.

License

MIT