Working with HL7

Normalize parsed HL7 before mapping and implement customer-specific workflows around the generated FHIR Bundle.

FireSonic keeps deterministic, generally applicable HL7 → FHIR mapping in the Java platform core. Customer-specific behavior belongs in Groovy: normalize source HL7 in preConvert(), then apply organization-specific workflow logic or FHIR enrichment in postConvert().

Keep the boundary clear

Use the built-in mapper for generic interoperability. Use Groovy helpers and workflow classes for behavior that depends on the customer's local HL7 conventions, FHIR server state, location model, merge policy, terminology, or operational rules.

HL7Helper

HL7Helper provides defensive, null-tolerant helpers for common HL7 operations so customer scripts do not need to work directly with low-level HAPI APIs for every field.

OperationExamples
Read / write known pathsget(), set(), copy(), isEmpty()
Read real Segment objectssafeGet(), setFieldEncoded(), setFieldComponent()
Clear contentclear(), clearRepeatedSegments(), removeSegments()
Message metadatagetMessageCode(), getTriggerEvent(), getMessageControlId()
Date/time conversionreadTS(), readDT(), resolveEventDateTime()
def msg = envelope.hl7Message
String patientId = HL7Helper.get(msg, "/PID-3-1")
String trigger = HL7Helper.getTriggerEvent(msg)
HL7Helper.set(msg, "/PID-8", "F")

Known paths vs repeated or nested segments

Use Terser-style paths through HL7Helper when the path is known and stable. Repeating groups are different: messages such as ORU can contain OBR and OBX segments inside nested HAPI groups, so guessing group paths is fragile.

Known path
String visit = HL7Helper.get(msg, "/PV1-19-1")
HL7Helper.set(msg, "/OBR-3-1", "3510540058")
Repeated / nested segments
HL7SegmentScanner.byName(msg, "OBX").each { ref ->
    String code = HL7Helper.safeGet(ref.segment, 3, 1)
}

HL7SegmentScanner and HL7SegmentRef

HL7SegmentScanner recursively walks the parsed HAPI group tree and returns a flat snapshot of real segment occurrences in traversal order. Each HL7SegmentRef exposes the segment index, name, actual HAPI segment, owning group, and zero-based repetition.

for (HL7SegmentRef ref : HL7SegmentScanner.scan(msg)) {
    if (ref.name == "OBX") {
        String value = HL7Helper.safeGet(ref.segment, 5)
    }
}

Clearing fields and segments

HL7Helper.clear(msg, "/PV1-20")
HL7Helper.clearRepeatedSegments(msg, "IN1")

HL7Helper.removeSegments(msg, "DG1") { dg1 ->
    HL7Helper.safeGet(dg1, 3, 1) == "B34.2"
}
Clearing is not structural removal

Segment helpers clear matching segment content; they do not promise physical removal from HAPI's internal group metadata. FireSonic mappers skip empty segments.

Timezone normalization

HL7TimezoneNormalizer adds a configured default timezone only when an HL7 TS/DTM value contains time precision and no timezone. Date-only values and values that already carry an offset remain unchanged.

HL7TimezoneNormalizer.applyTimezone(
    msg,
    "+0200",
    ["/MSH-7", "/EVN-2-1", "/PV1-44-1"]
)

HL7TimezoneNormalizer.applyTimezoneRepeated(
    msg,
    "OBR",
    [6, 7, 8, 14, 22],
    "+0200"
)

MessageEnvelope mutability

Do not mutate rawHL7

Treat rawHL7 as the immutable audit copy. Modify or replace the parsed envelope.hl7Message in preConvert().

Complex customer workflows

Not every HL7 event can be handled correctly by a universal mapper. FireSonic keeps customer-dependent behavior in Groovy workflow classes that operate around the mapped Bundle and can use managed SDK services.

ADT^A02 Patient Transfer

Patient transfer behavior depends heavily on how each organization models locations and encounter history. The reference workflow demonstrates a practical default that can be adapted to the customer's own rules.

  • Find the existing Encounter by identifier.
  • Merge the existing and newly mapped location history.
  • Preserve locations that did not change.
  • Close locations that are no longer present.
  • Activate newly mapped locations.
  • Replace the default contained-location behavior when the customer uses a different Location model.

ADT^A40 Patient Merge

Patient merge is an identity workflow rather than a simple field mapping. The reference workflow coordinates the merge with the target FHIR server while leaving customer-specific survivorship policy customizable.

  • Resolve the survivor and deprecated Patients.
  • Apply merge semantics and Patient links.
  • Post the merge transaction.
  • Relink dependent resources in batches.
  • Retry transient failures through SDK support utilities.
  • Keep demographic survivorship rules customer-specific.

Main script as orchestrator

The injected services live in the main IntegrationScript. Helper workflows receive the services they need explicitly.

if ("A02" == envelope.triggerEvent) {
    return new PatientTransferWorkflow(fhir)
        .applyTransferMerge(envelope, bundle)
}

if ("A40" == envelope.triggerEvent) {
    return new PatientMergeWorkflow(fhir, support)
        .applyPatientMerge(envelope, bundle)
}

Explore the SDK API in your IDE

The SDK exposes public API types such as IntegrationScript, MessageEnvelope, FhirClient, ScriptSupport, cache/HTTP/metrics APIs, and dependency-injection annotations. In IntelliJ IDEA or Eclipse, use autocomplete, Open Declaration, and Javadoc to inspect the available methods and contracts directly while writing Groovy scripts.