Skip to content

The interface engine

The integration plane is the plumbing that turns activity into a chart. It is also the plane most systems get wrong by skipping it — pointing an HL7 feed straight at the EMR. Real hospitals put an interface engine in the middle, and so does the lab.

OpenEMR’s strength is being the clinician-facing record; its native HL7v2 inbound is weak. An interface engine’s whole job is to receive, acknowledge, transform, and route messages. We use OpenIntegrationEngine (OIE), the FOSS fork that continues Mirth Connect after it went proprietary at version 4.5. Each piece then stays good at one job:

Simulated Hospital ──MLLP (HL7v2)──▶ OpenIntegrationEngine ──JDBC──▶ OpenEMR
(activity) (integration) (application)

OIE runs a single channel, ADT to OpenEMR, generated by scripts/gen-channel.py:

  • Source — a TCP/MLLP listener on port 6661. HL7v2 in, auto-generated ACK out.
  • Destination — a JavaScript Writer that parses each message and writes to the OpenEMR MariaDB over JDBC, computing ids from OpenEMR’s own sequences counter.

Every write is idempotent (keyed on MRN, visit number, or a per-patient title), so replays and overlapping messages don’t duplicate data.

HL7 segment / eventOpenEMR destination
PIDpatient_data (incl. SSN from the SS-typed PID-3 repetition, address from PID-11, phone from PID-13)
PV1form_encounter + forms (visit, keyed on visit number)
OBR / OBX (Vital Signs)form_vitals + forms — temp (°C→°F), pulse, BP, SpO₂ into columns; the rest into the note
OBR / OBX (clinical note, encapsulated ^^txt^^ text)pnotes — the note body, titled by document type (Referral Letter, Prescription…)
OBR / OBX (other panels)procedure_orderprocedure_reportprocedure_result (labs)
AL1lists type allergy
DG1lists type medical_problem
PR1lists type surgery
IN1insurance_data (+ lookup-or-create insurance_companies by name)
GT1the insurance_data subscriber (a guarantor who isn’t the patient — e.g. a minor’s parent — becomes the policyholder)
ADT^A02transfer — updates the encounter location
ADT^A03discharge — sets date_end + disposition
ADT^A11 / A13cancel admit / cancel discharge

Authoring channel XML by hand is finicky. These cost real time and are written down so they don’t again:

  • Use PUT /api/channels/{id}, not POST /api/channels. POST silently strips most of the channel body (connector properties, transformer datatypes); PUT preserves them.
  • The channel id must be a real hex UUID. A non-hex id 500s on PUT.
  • HL7v2 datatype sub-blocks are all-or-nothing per converter. If any one block has a shape XStream doesn’t recognise, it discards the entire HL7v2DataTypeProperties object — deserializationProperties becomes null and deploy throws a NullPointerException. We ship only serializationProperties, deserializationProperties, and responseGenerationProperties.
  • responseGenerationProperties is mandatory for the ACK. Without it, the auto-generated ACK is null, the MLLP sender blocks waiting for a reply, and throughput collapses to a trickle. With it: hundreds of messages/min, zero errors.
  • No query params in the JDBC URL. The & collides with XML escaping inside the <script>, and MariaDB’s native-password auth needs none of them.

Mapping procedures surfaced a bug worth more than the feature: a long point-of-care value overflowed the class_code column (varchar(10)) and silently aborted the entire message — patient created, but encounter, diagnosis, and procedure all lost. Live traffic never hit it because the simulator sends short codes (SURG, ED); only an oddly-shaped test message did. That is the exact shape of a bug that hides until one unusual real message arrives in production. The fix — clamp the value to the column width — costs nothing and closes the whole class.

The activity plane can also emit FHIR R4 directly (a separate path from this channel). It works for everything except Observation, due to a library-version skew in the from-source build — the full story is on the Simulated Hospital page.

This is the middle of the four-plane architecture. To bring it up and deploy the channel, see Run the clinical ecosystem.