Skip to main content
Version: v0.1.11

Validate a message's structure and content

Three checks, from the cheapest to the most specific. Each reads the message and changes nothing, and none of them is an attestation of conformance: an empty result means the checks that ran found nothing, not that the message is conformant.

CheckAsksRuns
msg.structureare the segments this message type requires present?on every parse
validateMessageStructure(msg)do the segments follow the published order and counts?when you call it
validateAgainstProfile(msg, p)does the message meet the interface specification you wrote down?when you call it

Spot a truncated or misrouted message​

msg.structure reports whether the segments HL7's published structures require at least once are present. An ORU^R01 with no OBR is almost always cut short or sent to the wrong feed. The same finding is a MISSING_EXPECTED_GROUP warning, so a channel can route on it alone.

import { parseHL7, WARNING_CODES } from "@cosyte/hl7";

const msg = parseHL7(
"MSH|^~\\&|LAB|MAIN|EHR|REF|20260419143000||ORU^R01|EX00070|P|2.5\r" +
"PID|1||MRN67890^^^HOSP^MR",
);

msg.structure.recognized; // => true
msg.structure.missingSegments; // => ["OBR"]
msg.structure.structureIds; // => ["ORU_R01-A", "ORU_R01-B", "ORU_R01-C", "ORU_R01-D"]
msg.warnings.some((w) => w.code === WARNING_CODES.MISSING_EXPECTED_GROUP); // => true

The expectations are derived offline from a vendored copy of the published structures, with no network call. A segment inside an optional group is not expected, a segment is expected only when every published variant of the structure requires it, and a type it does not recognize gives recognized: false and no warning. It covers the message codes ADT, ORU, ORM, OML, OMG, OMP, OMI, SIU, MDM, DFT, VXU and ACK. Message-type and structure awareness has the full table and the derivation rules.

Check order and counts against the published structure​

validateMessageStructure(msg) is an opt-in check over the same vendored structures, at segment level. It reports a segment out of order (STRUCTURE_SEGMENT_OUT_OF_ORDER), fewer or more of a segment than the structure allows (STRUCTURE_SEGMENT_CARDINALITY), and a segment the structure does not name, a Z-segment included (STRUCTURE_SEGMENT_UNEXPECTED, a warning). Each finding names a segment, its occurrence and the structure, never a field value.

import { parseHL7, validateMessageStructure } from "@cosyte/hl7";

// An ADT^A01 that sends PV1 before PID.
const msg = parseHL7(
"MSH|^~\\&|CLINIC|MAIN|LAB|REF|20260419143000||ADT^A01|EX00071|P|2.5\r" +
"EVN|A01|20260419143000\r" +
"PV1|1|I\r" +
"PID|1||MRN12345^^^HOSP^MR",
);

const result = validateMessageStructure(msg);

result.validated; // => true
result.structureId; // => "ADT_A01-A"
result.findings.map((f) => [f.severity, f.code, f.locus.segment]); // => [["error", "STRUCTURE_SEGMENT_OUT_OF_ORDER", "PID"]]

Read validated before findings. A message type the registry does not model, the one retained transcription (ORM^O01), and a message with no readable type come back validated: false with a reason and no findings: "the published structure cannot answer" is a different answer from "nothing was wrong".

import { parseHL7, validateMessageStructure } from "@cosyte/hl7";

const result = validateMessageStructure(
parseHL7("MSH|^~\\&|CLINIC|MAIN|LAB|REF|20260419143000||ORM^O01|EX00072|P|2.5"),
);

result.validated; // => false
result.reason; // => "RETAINED_TRANSCRIPTION"

Where the publication splits a structure into variants, matching one variant is enough; findings appear only when every variant is broken, and they are one named variant's. Field content, data types, value sets and HL7 tables are not checked.

Run a conformance profile you write​

When you have an interface specification ("our ADT feed requires PID-3, sex must be M, F or U, no Z-segments"), write it as a conformance profile and validateAgainstProfile(msg, profile) returns typed findings. You write the profile and every value set; the package ships none, and makes no network call.

import { type ConformanceProfile, parseHL7, validateAgainstProfile } from "@cosyte/hl7";

const profile: ConformanceProfile = {
name: "our-adt-intake",
segments: [
{
segment: "PID",
usage: "R",
fields: [
{ field: 3, name: "Patient Identifiers", usage: "R", cardinality: { min: 1, max: 1 } },
{ field: 8, name: "Administrative Sex", usage: "RE", valueSet: ["M", "F", "U"] },
],
},
{ segment: "ZZZ", usage: "X" },
],
};

const msg = parseHL7(
"MSH|^~\\&|CLINIC|MAIN|LAB|REF|20260419143000||ADT^A01|EX00073|P|2.5\r" +
"PID|1||MRN12345^^^HOSP^MR||Doe^John||19800101|Q\r" +
"ZZZ|1",
);

const { findings } = validateAgainstProfile(msg, profile);

findings.map((f) => f.code); // => ["PROFILE_VALUE_NOT_IN_SET", "PROFILE_NOT_PERMITTED"]
findings[0]?.message; // => "PID-8 rep 0 component 1 value is not in the profile value set (3 permitted code(s))."

The engine never throws (a malformed profile gives PROFILE_MALFORMED findings), a valid message gives no findings, no finding carries a value from the message, and the message is not changed. defineConformanceProfile(profile) is the strict alternative for authoring: it throws a ProfileDefinitionError listing every defect.

A conditional usage can carry a predicate the engine evaluates against the message. Here PID-8 is required when a birth date was sent and not permitted when it was not:

import { type ConformanceProfile, parseHL7, validateAgainstProfile } from "@cosyte/hl7";

const profile: ConformanceProfile = {
name: "sex-with-birth-date",
segments: [
{
segment: "PID",
usage: "R",
fields: [
{
field: 8,
usage: "C(R/X)",
condition: { location: { segment: "PID", field: 7 }, presence: "is valued" },
},
],
},
],
};

const header = "MSH|^~\\&|CLINIC|MAIN|LAB|REF|20260419143000||ADT^A01|EX00074|P|2.5\r";
const withBirthDate = parseHL7(header + "PID|1||MRN12345^^^HOSP^MR||Doe^John||19800101");
const withoutBirthDate = parseHL7(header + "PID|1||MRN12345^^^HOSP^MR||Doe^John|||F");

validateAgainstProfile(withBirthDate, profile).findings.map((f) => f.code); // => ["PROFILE_REQUIRED_ABSENT"]
validateAgainstProfile(withoutBirthDate, profile).findings.map((f) => f.code); // => ["PROFILE_NOT_PERMITTED"]

A predicate the message cannot decide gives PROFILE_CONDITION_UNEVALUATABLE, and that element's presence is not assessed rather than guessed. Conformance profiles covers the usage codes, the predicate verbs, profile levels, value-set bindings and every finding code. This is a different thing from the parse profiles of Use vendor profiles, which change how a message is read.

Reference​