Skip to content

fix(parsers): honour HL7 v2 MSH delimiters, repetitions and escape sequences - #382

Merged
kevincostner17 merged 1 commit into
mainfrom
fix/hl7v2-delimiters
Sep 15, 2026
Merged

kevincostner17 merged 1 commit into
mainfrom
fix/hl7v2-delimiters

Conversation

@kevincostner17

Copy link
Copy Markdown
Contributor

Summary

The HL7 v2 ER7 parser hard-coded | and ^, so it ignored the delimiters a message declares in MSH-1/MSH-2. It never split ~ repetitions and never decoded escape sequences. PID-3 12345~98765 became the patient id, \S\/\T\ stayed in values, and a #-delimited message parsed to 0 messages.

  • Each MSH sets its message's delimiters: field separator from seg[3], component/repetition/escape/subcomponent from MSH-2 (missing ones default to ^~\&). They apply until the next MSH.
  • Segment ids are read with seg[:3], and a known segment must be followed by the current field separator. Unknown segments are still reported by name.
  • Single-valued columns (ids, names, codes, dates, units, status) take the first repetition.
  • OBX-5 (observation value) is a repeating field, so it keeps every repetition, joined with ~. A custom repetition character is re-emitted as ~.
  • Fields are split before escapes are decoded, so an escaped delimiter such as \R\ never splits a value. \F\ \S\ \T\ \R\ \E\ are decoded using the message's delimiters. Other escapes (\H\, \N\, \Xhh\, \.br\) are kept verbatim.
  • Whole-field values and message_type use the standard ^ / & / ~ separators whatever the message declared.
  • Standard |^~\& messages without escapes, and without repetitions outside OBX-5, give the same frames, metadata and warnings as before.

Most of the hl7v2.py line count is ruff format on a file that was not formatted on main.

Tests

New tests/parsers/test_hl7v2_delimiters.py:

  • the issue repro (first repetition, escapes, # field separator)
  • custom MSH#$*@! delimiters, and a partial MSH-2 that falls back to defaults
  • first repetition for single-valued columns
  • OBX-5 keeping every repetition, with standard delimiters and with a custom repetition character
  • \R\ decoded after the split rather than split on
  • each escape, plus unknown and unterminated escapes, and escapes using the message's own delimiters
  • escaped delimiters not splitting components
  • three messages with alternating delimiters in one input
  • a PIDX prefix treated as unknown
  • a standard multi-message fixture compared against frames from the previous parser

Verification

  • ruff check . passes; changed files are ruff format-clean
  • mypy src/freshdata: no issues in 202 source files
  • pytest -m "not online and not large": py3.12 4483 passed / 6 skipped; py3.9 4479 passed / 10 skipped

Closes #261

…quences

The HL7 v2 parser hard-coded "|" and "^", so it ignored the delimiters a
message declares in MSH-1/MSH-2. It never split "~" repetitions and never
decoded escape sequences. As a result PID-3 "12345~98765" came out as the
patient id, "\S\" / "\T\" stayed in text values, and a message using "#"
as its field separator parsed to zero messages.

Each MSH segment now sets the delimiters for its message: the field
separator comes from seg[3] and the component, repetition, escape and
subcomponent characters from MSH-2. Any that are missing default to
^~\&. Those delimiters stay in force until the next MSH. Segment ids are
detected with seg[:3], and a recognised segment must be followed by the
current field separator.

Single-valued columns (ids, names, codes, dates, units, status) take the
first repetition. OBX-5 is a repeating field, so the observation value
keeps every repetition, joined with "~". Fields are split before escapes
are decoded, so an escaped delimiter such as \R\ never splits a value.
\F\ \S\ \T\ \R\ \E\ are decoded using the message's own delimiters, and
other escapes (\H\, \N\, \Xhh\, \.br\) are kept verbatim. Whole-field
values are re-emitted with the standard ^ / & / ~ separators.

Standard |^~\& messages without escapes, and without repetitions outside
OBX-5, produce the same frames, metadata and warnings as before.

Closes #261
@coderabbitai

coderabbitai Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 2294033a-a40a-4e93-bc0e-c8cfa0fb083f


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown

FreshData benchmark report — performance

  • freshdata: ?
  • python: ?
  • platform: ?
fixture n_rows n_cols p50 s p95 s peak MB repair % false-repair % preserve % trust monotonic export %

Authored-code reduction (Metric 6)

@kevincostner17
kevincostner17 merged commit 4bbceb0 into main Sep 15, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

HL7 v2 parser ignores repetition separator, escape sequences and MSH-1/MSH-2 delimiters

1 participant