@cosyte/hl7 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +436 -147
- package/README.md +154 -91
- package/TRADEMARKS.md +37 -0
- package/dist/index.cjs +1504 -111
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2060 -477
- package/dist/index.d.ts +2060 -477
- package/dist/index.mjs +1479 -112
- package/dist/index.mjs.map +1 -1
- package/package.json +14 -6
package/dist/index.cjs
CHANGED
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
var crypto = require('crypto');
|
|
4
4
|
var buffer = require('buffer');
|
|
5
5
|
|
|
6
|
+
var __defProp = Object.defineProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
|
|
6
12
|
// src/parser/errors.ts
|
|
7
13
|
var FATAL_CODES = {
|
|
8
14
|
NO_MSH_SEGMENT: "NO_MSH_SEGMENT",
|
|
@@ -68,7 +74,7 @@ function readDelimiters(firstSegment) {
|
|
|
68
74
|
if (firstSegment.length < 3 || firstSegment.slice(0, 3) !== "MSH") {
|
|
69
75
|
throw new Hl7ParseError(
|
|
70
76
|
FATAL_CODES.NO_MSH_SEGMENT,
|
|
71
|
-
"First segment is not MSH
|
|
77
|
+
"First segment is not MSH: HL7 v2 messages must begin with an MSH segment.",
|
|
72
78
|
fatalPosition,
|
|
73
79
|
snip
|
|
74
80
|
);
|
|
@@ -76,7 +82,7 @@ function readDelimiters(firstSegment) {
|
|
|
76
82
|
if (firstSegment.length < 8) {
|
|
77
83
|
throw new Hl7ParseError(
|
|
78
84
|
FATAL_CODES.MSH_TOO_SHORT,
|
|
79
|
-
"MSH segment is truncated
|
|
85
|
+
"MSH segment is truncated: cannot read encoding characters.",
|
|
80
86
|
fatalPosition,
|
|
81
87
|
snip
|
|
82
88
|
);
|
|
@@ -85,7 +91,7 @@ function readDelimiters(firstSegment) {
|
|
|
85
91
|
if (/\s/u.test(field)) {
|
|
86
92
|
throw new Hl7ParseError(
|
|
87
93
|
FATAL_CODES.INVALID_ENCODING_CHARACTERS,
|
|
88
|
-
"MSH-1 field separator is whitespace
|
|
94
|
+
"MSH-1 field separator is whitespace: refusing to parse.",
|
|
89
95
|
fatalPosition,
|
|
90
96
|
snip
|
|
91
97
|
);
|
|
@@ -269,7 +275,8 @@ var WARNING_CODES = {
|
|
|
269
275
|
ACK_NO_CORRELATION_ID: "ACK_NO_CORRELATION_ID",
|
|
270
276
|
MERGE_MISSING_PRIOR_OR_SURVIVOR: "MERGE_MISSING_PRIOR_OR_SURVIVOR",
|
|
271
277
|
BATCH_COUNT_MISMATCH: "BATCH_COUNT_MISMATCH",
|
|
272
|
-
BATCH_MISSING_TRAILER: "BATCH_MISSING_TRAILER"
|
|
278
|
+
BATCH_MISSING_TRAILER: "BATCH_MISSING_TRAILER",
|
|
279
|
+
UNTERMINATED_STREAM_MESSAGE: "UNTERMINATED_STREAM_MESSAGE"
|
|
273
280
|
};
|
|
274
281
|
function mllpFramingStripped(position) {
|
|
275
282
|
return {
|
|
@@ -347,7 +354,7 @@ function extraFields(position, segmentName2, extraCount) {
|
|
|
347
354
|
function unknownSegment(position, segmentName2) {
|
|
348
355
|
return {
|
|
349
356
|
code: WARNING_CODES.UNKNOWN_SEGMENT,
|
|
350
|
-
message: `Unknown segment "${segmentName2}"
|
|
357
|
+
message: `Unknown segment "${segmentName2}": not in HL7 spec and no profile claim.`,
|
|
351
358
|
position
|
|
352
359
|
};
|
|
353
360
|
}
|
|
@@ -432,7 +439,14 @@ function batchCountMismatch(position, unit, declared, actual) {
|
|
|
432
439
|
function batchMissingTrailer(position, header, expectedTrailer) {
|
|
433
440
|
return {
|
|
434
441
|
code: WARNING_CODES.BATCH_MISSING_TRAILER,
|
|
435
|
-
message: `Batch envelope header "${header}" has no matching "${expectedTrailer}" trailer; splitBatch surfaces this but does not reject
|
|
442
|
+
message: `Batch envelope header "${header}" has no matching "${expectedTrailer}" trailer; splitBatch surfaces this but does not reject: a profile that mandates the full envelope may.`,
|
|
443
|
+
position
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
function unterminatedStreamMessage(position) {
|
|
447
|
+
return {
|
|
448
|
+
code: WARNING_CODES.UNTERMINATED_STREAM_MESSAGE,
|
|
449
|
+
message: "The final message in the stream ended without a segment terminator; it is still yielded in full. If the source terminates every segment, this may indicate a truncated feed.",
|
|
436
450
|
position
|
|
437
451
|
};
|
|
438
452
|
}
|
|
@@ -588,7 +602,7 @@ var RECOGNIZED_PRESERVED_SET = /* @__PURE__ */ new Set([
|
|
|
588
602
|
"H",
|
|
589
603
|
// begin highlighting (§2.7.1)
|
|
590
604
|
"N",
|
|
591
|
-
// normal text
|
|
605
|
+
// normal text: end highlighting (§2.7.1)
|
|
592
606
|
".sp",
|
|
593
607
|
// vertical skip (§2.7.6)
|
|
594
608
|
".in",
|
|
@@ -775,7 +789,7 @@ function tokenizeComponent(raw, enc, emit, position) {
|
|
|
775
789
|
var MESSAGE_STRUCTURE_DEFINITIONS = Object.freeze([
|
|
776
790
|
// ── ADT (Ch. 3): patient identification (PID) + visit (PV1) are Required
|
|
777
791
|
// for these admit/transfer/discharge/register/update/cancel events.
|
|
778
|
-
// EVN is intentionally excluded
|
|
792
|
+
// EVN is intentionally excluded: real senders omit it freely, making it
|
|
779
793
|
// a weak (false-positive-prone) signal.
|
|
780
794
|
Object.freeze({
|
|
781
795
|
messageCode: "ADT",
|
|
@@ -787,7 +801,7 @@ var MESSAGE_STRUCTURE_DEFINITIONS = Object.freeze([
|
|
|
787
801
|
}),
|
|
788
802
|
// ── ORU^R01 (Ch. 7): the OBR/OBX result group is the Required payload.
|
|
789
803
|
// PID is excluded (the patient-result group's PID is R within the group,
|
|
790
|
-
// but a result-only relay can legitimately omit it at the top
|
|
804
|
+
// but a result-only relay can legitimately omit it at the top: keep the
|
|
791
805
|
// anchor on the result segments themselves, the true truncation signal).
|
|
792
806
|
Object.freeze({
|
|
793
807
|
messageCode: "ORU",
|
|
@@ -797,7 +811,7 @@ var MESSAGE_STRUCTURE_DEFINITIONS = Object.freeze([
|
|
|
797
811
|
])
|
|
798
812
|
}),
|
|
799
813
|
// ── Orders (Ch. 4): ORC is the Required common-order segment across the
|
|
800
|
-
// order message family. OBR is excluded
|
|
814
|
+
// order message family. OBR is excluded: it is Optional in several of
|
|
801
815
|
// these (OML/OMG/OMI carry it inside an optional observation-request
|
|
802
816
|
// group), so anchoring only on ORC avoids a false positive.
|
|
803
817
|
Object.freeze({
|
|
@@ -860,7 +874,7 @@ var MESSAGE_STRUCTURE_DEFINITIONS = Object.freeze([
|
|
|
860
874
|
Object.freeze({ name: "schedule", anchorSegments: Object.freeze(["SCH"]) })
|
|
861
875
|
])
|
|
862
876
|
}),
|
|
863
|
-
// ── MDM (Ch. 9): document management
|
|
877
|
+
// ── MDM (Ch. 9): document management: PID + the TXA document header are
|
|
864
878
|
// Required for T02 (original document) and T06 (document addendum).
|
|
865
879
|
Object.freeze({
|
|
866
880
|
messageCode: "MDM",
|
|
@@ -870,7 +884,7 @@ var MESSAGE_STRUCTURE_DEFINITIONS = Object.freeze([
|
|
|
870
884
|
Object.freeze({ name: "document", anchorSegments: Object.freeze(["TXA"]) })
|
|
871
885
|
])
|
|
872
886
|
}),
|
|
873
|
-
// ── DFT^P03 (Ch. 6): financial transaction
|
|
887
|
+
// ── DFT^P03 (Ch. 6): financial transaction: PID + at least one FT1
|
|
874
888
|
// financial-transaction segment are Required.
|
|
875
889
|
Object.freeze({
|
|
876
890
|
messageCode: "DFT",
|
|
@@ -880,7 +894,7 @@ var MESSAGE_STRUCTURE_DEFINITIONS = Object.freeze([
|
|
|
880
894
|
Object.freeze({ name: "financial", anchorSegments: Object.freeze(["FT1"]) })
|
|
881
895
|
])
|
|
882
896
|
}),
|
|
883
|
-
// ── VXU^V04 (CDC IG): immunization update
|
|
897
|
+
// ── VXU^V04 (CDC IG): immunization update: PID is Required. RXA is
|
|
884
898
|
// excluded: it lives in the Optional order group of the CDC IG, so a
|
|
885
899
|
// query-shaped VXU with no administered vaccine must not warn.
|
|
886
900
|
Object.freeze({
|
|
@@ -890,7 +904,7 @@ var MESSAGE_STRUCTURE_DEFINITIONS = Object.freeze([
|
|
|
890
904
|
Object.freeze({ name: "patient", anchorSegments: Object.freeze(["PID"]) })
|
|
891
905
|
])
|
|
892
906
|
}),
|
|
893
|
-
// ── ACK (Ch. 2): a general acknowledgment
|
|
907
|
+
// ── ACK (Ch. 2): a general acknowledgment: MSA (message acknowledgment) is
|
|
894
908
|
// Required. Matched on message code alone (no trigger event in MSH-9.2).
|
|
895
909
|
Object.freeze({
|
|
896
910
|
messageCode: "ACK",
|
|
@@ -1020,7 +1034,7 @@ function parsePath(path) {
|
|
|
1020
1034
|
}
|
|
1021
1035
|
if (i !== path.length) {
|
|
1022
1036
|
throw new TypeError(
|
|
1023
|
-
`Invalid HL7 dot-path: "${path}" (trailing content at position ${String(i)}
|
|
1037
|
+
`Invalid HL7 dot-path: "${path}" (trailing content at position ${String(i)}: too deep or malformed).`
|
|
1024
1038
|
);
|
|
1025
1039
|
}
|
|
1026
1040
|
return freezeDotPath(out);
|
|
@@ -1146,6 +1160,127 @@ function emitSegment(seg, enc) {
|
|
|
1146
1160
|
return parts.join(enc.field);
|
|
1147
1161
|
}
|
|
1148
1162
|
|
|
1163
|
+
// src/text/render.ts
|
|
1164
|
+
var FORMATTING_RE = /^\.(br|sp|in|ti|fi|nf|ce|sk)[ +]?([+-]?\d+)?$/u;
|
|
1165
|
+
var DEFAULT_TRUNCATION_CHAR2 = "#";
|
|
1166
|
+
var MAX_FORMAT_REPEAT = 100;
|
|
1167
|
+
function normalizeBreaks(s, newline) {
|
|
1168
|
+
return s.replace(/\r\n|\r|\n/gu, newline);
|
|
1169
|
+
}
|
|
1170
|
+
function clampCount(arg) {
|
|
1171
|
+
if (arg === void 0) return 1;
|
|
1172
|
+
const n = Math.trunc(Number(arg));
|
|
1173
|
+
if (!Number.isFinite(n) || n < 1) return 1;
|
|
1174
|
+
return Math.min(n, MAX_FORMAT_REPEAT);
|
|
1175
|
+
}
|
|
1176
|
+
function renderText(input, enc = DEFAULT_ENCODING_CHARACTERS, opts) {
|
|
1177
|
+
const esc = enc.escape;
|
|
1178
|
+
const newline = opts?.newline ?? "\n";
|
|
1179
|
+
const runs = [];
|
|
1180
|
+
const unrendered = [];
|
|
1181
|
+
let highlighted = false;
|
|
1182
|
+
let current = "";
|
|
1183
|
+
const flush = () => {
|
|
1184
|
+
if (current !== "") {
|
|
1185
|
+
runs.push({ text: current, highlighted });
|
|
1186
|
+
current = "";
|
|
1187
|
+
}
|
|
1188
|
+
};
|
|
1189
|
+
let i = 0;
|
|
1190
|
+
const len = input.length;
|
|
1191
|
+
while (i < len) {
|
|
1192
|
+
const ch = input.charAt(i);
|
|
1193
|
+
if (ch === "\r" || ch === "\n") {
|
|
1194
|
+
current += newline;
|
|
1195
|
+
i += ch === "\r" && input.charAt(i + 1) === "\n" ? 2 : 1;
|
|
1196
|
+
continue;
|
|
1197
|
+
}
|
|
1198
|
+
if (ch !== esc) {
|
|
1199
|
+
current += ch;
|
|
1200
|
+
i++;
|
|
1201
|
+
continue;
|
|
1202
|
+
}
|
|
1203
|
+
const close = input.indexOf(esc, i + 1);
|
|
1204
|
+
if (close === -1) {
|
|
1205
|
+
const tail = input.slice(i);
|
|
1206
|
+
current += tail;
|
|
1207
|
+
unrendered.push(tail);
|
|
1208
|
+
break;
|
|
1209
|
+
}
|
|
1210
|
+
const body = input.slice(i + 1, close);
|
|
1211
|
+
const literal = esc + body + esc;
|
|
1212
|
+
if (body === "H") {
|
|
1213
|
+
flush();
|
|
1214
|
+
highlighted = true;
|
|
1215
|
+
} else if (body === "N") {
|
|
1216
|
+
flush();
|
|
1217
|
+
highlighted = false;
|
|
1218
|
+
} else {
|
|
1219
|
+
const decoded = decodeBody(body, enc, newline);
|
|
1220
|
+
if (decoded !== null) {
|
|
1221
|
+
current += decoded;
|
|
1222
|
+
} else {
|
|
1223
|
+
current += literal;
|
|
1224
|
+
unrendered.push(literal);
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
i = close + 1;
|
|
1228
|
+
}
|
|
1229
|
+
flush();
|
|
1230
|
+
const text = runs.reduce((acc, r) => acc + r.text, "");
|
|
1231
|
+
return { text, runs, unrenderedSequences: unrendered };
|
|
1232
|
+
}
|
|
1233
|
+
function decodeBody(body, enc, newline) {
|
|
1234
|
+
switch (body) {
|
|
1235
|
+
case "F":
|
|
1236
|
+
return enc.field;
|
|
1237
|
+
case "S":
|
|
1238
|
+
return enc.component;
|
|
1239
|
+
case "T":
|
|
1240
|
+
return enc.subcomponent;
|
|
1241
|
+
case "R":
|
|
1242
|
+
return enc.repetition;
|
|
1243
|
+
case "E":
|
|
1244
|
+
return enc.escape;
|
|
1245
|
+
case "P":
|
|
1246
|
+
return enc.truncation ?? DEFAULT_TRUNCATION_CHAR2;
|
|
1247
|
+
}
|
|
1248
|
+
if (body.charAt(0) === ".") {
|
|
1249
|
+
const m = FORMATTING_RE.exec(body);
|
|
1250
|
+
if (m !== null) return renderFormatting(m[1] ?? "", m[2], newline);
|
|
1251
|
+
return null;
|
|
1252
|
+
}
|
|
1253
|
+
if (body.charAt(0) === "X") {
|
|
1254
|
+
const hex = body.slice(1);
|
|
1255
|
+
if (hex.length === 0 || hex.length % 2 !== 0 || !/^[0-9A-Fa-f]+$/u.test(hex)) {
|
|
1256
|
+
return null;
|
|
1257
|
+
}
|
|
1258
|
+
let out = "";
|
|
1259
|
+
for (let j = 0; j < hex.length; j += 2) {
|
|
1260
|
+
out += String.fromCharCode(parseInt(hex.slice(j, j + 2), 16));
|
|
1261
|
+
}
|
|
1262
|
+
return normalizeBreaks(out, newline);
|
|
1263
|
+
}
|
|
1264
|
+
return null;
|
|
1265
|
+
}
|
|
1266
|
+
function renderFormatting(cmd, arg, newline) {
|
|
1267
|
+
switch (cmd) {
|
|
1268
|
+
case "br":
|
|
1269
|
+
case "ce":
|
|
1270
|
+
return newline;
|
|
1271
|
+
case "sp":
|
|
1272
|
+
return newline.repeat(clampCount(arg));
|
|
1273
|
+
case "sk":
|
|
1274
|
+
return " ".repeat(clampCount(arg));
|
|
1275
|
+
case "in":
|
|
1276
|
+
case "ti":
|
|
1277
|
+
case "fi":
|
|
1278
|
+
case "nf":
|
|
1279
|
+
default:
|
|
1280
|
+
return "";
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1149
1284
|
// src/model/types/_shared.ts
|
|
1150
1285
|
function readSubcomponent(component, index, _enc) {
|
|
1151
1286
|
if (component === void 0) return void 0;
|
|
@@ -1724,13 +1859,13 @@ function parseXtn(rep, enc) {
|
|
|
1724
1859
|
// src/model/field.ts
|
|
1725
1860
|
var DEFAULT_POSITION = { segmentIndex: 0 };
|
|
1726
1861
|
var Field = class {
|
|
1727
|
-
/** HL7 null indicator
|
|
1862
|
+
/** HL7 null indicator: `true` iff the underlying field was the two-char literal `""`. */
|
|
1728
1863
|
isNull;
|
|
1729
1864
|
/** Reference to the underlying `RawField.repetitions` (no defensive copy). */
|
|
1730
1865
|
repetitions;
|
|
1731
1866
|
/** The 5 encoding characters for this message. Exposed for composite parsers (Plan 04). @internal */
|
|
1732
1867
|
enc;
|
|
1733
|
-
/** Position of this field in the parent message
|
|
1868
|
+
/** Position of this field in the parent message: used for position-aware error messages. @internal */
|
|
1734
1869
|
position;
|
|
1735
1870
|
/** The full `RawField` this wrapper wraps. Exposed for composite parsers (Plan 02/03/04). @internal */
|
|
1736
1871
|
raw;
|
|
@@ -1748,7 +1883,7 @@ var Field = class {
|
|
|
1748
1883
|
}
|
|
1749
1884
|
/**
|
|
1750
1885
|
* First-repetition, first-component, first-subcomponent value as a decoded
|
|
1751
|
-
* string
|
|
1886
|
+
* string: unescaped ONCE by the tokenizer on parse, returned verbatim here
|
|
1752
1887
|
* (never re-unescaped, HL7-VALUE-REDECODE). Returns `""` when the field is
|
|
1753
1888
|
* empty or HL7 null. Equivalent to `msg.get('SEG.N')` for a top-level access.
|
|
1754
1889
|
*
|
|
@@ -1769,13 +1904,13 @@ var Field = class {
|
|
|
1769
1904
|
return sub;
|
|
1770
1905
|
}
|
|
1771
1906
|
/**
|
|
1772
|
-
* The field's **canonical wire text
|
|
1907
|
+
* The field's **canonical wire text**: the full field re-serialized with
|
|
1773
1908
|
* the active delimiters and re-escaped content (repetitions, components,
|
|
1774
1909
|
* and subcomponents included). Contrast with {@link value}, which
|
|
1775
1910
|
* returns only the first subcomponent of the first component of the first
|
|
1776
|
-
* repetition (decoded once at parse
|
|
1911
|
+
* repetition (decoded once at parse: never re-unescaped).
|
|
1777
1912
|
*
|
|
1778
|
-
* Use this when a field must be compared or echoed as a whole
|
|
1913
|
+
* Use this when a field must be compared or echoed as a whole: e.g.
|
|
1779
1914
|
* correlating an ACK's MSA-2 against the inbound MSH-10, where a
|
|
1780
1915
|
* vendor-quirk control id containing an unescaped delimiter (`ID^X`) must
|
|
1781
1916
|
* not be truncated to its first component.
|
|
@@ -1794,7 +1929,7 @@ var Field = class {
|
|
|
1794
1929
|
*
|
|
1795
1930
|
* **MSH-1/MSH-2 caveat.** Like {@link value}, calling this on MSH-1 or
|
|
1796
1931
|
* MSH-2 (the delimiter-definition fields) re-escapes the encoding
|
|
1797
|
-
* characters themselves and produces garbage
|
|
1932
|
+
* characters themselves and produces garbage: those two fields are only
|
|
1798
1933
|
* meaningful through `Hl7Message.encodingCharacters`.
|
|
1799
1934
|
*
|
|
1800
1935
|
* @example
|
|
@@ -1809,12 +1944,33 @@ var Field = class {
|
|
|
1809
1944
|
return emitField(this.raw, this.enc);
|
|
1810
1945
|
}
|
|
1811
1946
|
/**
|
|
1812
|
-
*
|
|
1947
|
+
* Render this field's formatted text (HL7 v2 §2.7 highlight + formatting
|
|
1948
|
+
* escapes) into a normalized {@link RenderedText} display model: plain text
|
|
1949
|
+
* plus highlight-aware runs. A **read projection** over the field's
|
|
1950
|
+
* byte-verbatim wire {@link text}: it never mutates the raw value, and it
|
|
1951
|
+
* never fabricates (an unrenderable escape is preserved + flagged). Use this
|
|
1952
|
+
* to surface a clinical narrative (an NTE / OBX-5 note) to a human without
|
|
1953
|
+
* the raw `\.br\` / `\H\` sentinels.
|
|
1954
|
+
*
|
|
1955
|
+
* @param opts - see {@link RenderTextOptions} (e.g. a custom line-break string).
|
|
1956
|
+
* @returns the normalized display model.
|
|
1957
|
+
*
|
|
1958
|
+
* @example
|
|
1959
|
+
* ```ts
|
|
1960
|
+
* const note = msg.segments("OBX")[0]?.field(5);
|
|
1961
|
+
* note?.render().text; // "Specimen received.\nGross exam normal."
|
|
1962
|
+
* ```
|
|
1963
|
+
*/
|
|
1964
|
+
render(opts) {
|
|
1965
|
+
return renderText(this.text, this.enc, opts);
|
|
1966
|
+
}
|
|
1967
|
+
/**
|
|
1968
|
+
* Return a synthetic empty `Field` sentinel: used by `Segment.field(n)` to
|
|
1813
1969
|
* honor MODEL-05's "never throws on missing" contract. The returned Field
|
|
1814
1970
|
* has `isNull === false`, `repetitions === []`, and `value === ""`.
|
|
1815
1971
|
* Referentially stable across calls (same instance returned each time).
|
|
1816
1972
|
*
|
|
1817
|
-
* The `enc` argument is accepted for API symmetry but ignored
|
|
1973
|
+
* The `enc` argument is accepted for API symmetry but ignored: the
|
|
1818
1974
|
* synthetic field carries no content, so unescape would be a no-op
|
|
1819
1975
|
* regardless of the active encoding characters.
|
|
1820
1976
|
*
|
|
@@ -1831,7 +1987,7 @@ var Field = class {
|
|
|
1831
1987
|
/**
|
|
1832
1988
|
* Coerce this field's first repetition to a typed `XPN` (Extended Person
|
|
1833
1989
|
* Name). Absent components are OMITTED from the result
|
|
1834
|
-
* (`exactOptionalPropertyTypes`). Not memoized in v1
|
|
1990
|
+
* (`exactOptionalPropertyTypes`). Not memoized in v1: each call re-parses
|
|
1835
1991
|
* (D-09).
|
|
1836
1992
|
*
|
|
1837
1993
|
* @example
|
|
@@ -1921,7 +2077,7 @@ var Field = class {
|
|
|
1921
2077
|
return parsePl(this.repetitions[0] ?? EMPTY_REP, this.enc);
|
|
1922
2078
|
}
|
|
1923
2079
|
/**
|
|
1924
|
-
* Coerce this field's first repetition to a typed `TS` (Time Stamp)
|
|
2080
|
+
* Coerce this field's first repetition to a typed `TS` (Time Stamp): the
|
|
1925
2081
|
* fidelity `DtmParts` (raw + parts + precision + timezone). `valid` is
|
|
1926
2082
|
* `false` on unparseable input (no throw). Build an absolute instant only on
|
|
1927
2083
|
* explicit request via `dtmToDate(ts)`.
|
|
@@ -1937,7 +2093,7 @@ var Field = class {
|
|
|
1937
2093
|
}
|
|
1938
2094
|
/**
|
|
1939
2095
|
* Coerce this field's first repetition to a typed `NM` (Numeric).
|
|
1940
|
-
* `{ raw, value }
|
|
2096
|
+
* `{ raw, value }`: `value` is `undefined` on non-numeric input.
|
|
1941
2097
|
*
|
|
1942
2098
|
* @example
|
|
1943
2099
|
* ```ts
|
|
@@ -2007,7 +2163,7 @@ var EMPTY_REP = Object.freeze({
|
|
|
2007
2163
|
var Segment = class {
|
|
2008
2164
|
/** Segment name (3 chars, e.g. `"PID"`, `"OBX"`, `"ZPI"`). */
|
|
2009
2165
|
type;
|
|
2010
|
-
/** Reference to the underlying `RawSegment.fields
|
|
2166
|
+
/** Reference to the underlying `RawSegment.fields`: 1-indexed per HL7 convention. */
|
|
2011
2167
|
fields;
|
|
2012
2168
|
/** The 5 encoding characters for this message. Exposed for composite parsers. @internal */
|
|
2013
2169
|
enc;
|
|
@@ -2023,7 +2179,7 @@ var Segment = class {
|
|
|
2023
2179
|
* @internal
|
|
2024
2180
|
*/
|
|
2025
2181
|
customFields;
|
|
2026
|
-
/** Lazy cache of Field wrappers
|
|
2182
|
+
/** Lazy cache of Field wrappers: one per fields[] position. @internal */
|
|
2027
2183
|
_fieldWrappers;
|
|
2028
2184
|
/**
|
|
2029
2185
|
* Construct a new `Segment`. Called internally by `Hl7Message`; user code
|
|
@@ -2046,16 +2202,16 @@ var Segment = class {
|
|
|
2046
2202
|
}
|
|
2047
2203
|
/**
|
|
2048
2204
|
* Return the `Field` wrapper at HL7 position `n`. Indexing follows the HL7
|
|
2049
|
-
* 1-indexed convention
|
|
2205
|
+
* 1-indexed convention: `seg.field(5)` on a PID segment maps to PID-5.
|
|
2050
2206
|
* MSH segments use the same user-facing convention: `msh.field(1)` returns
|
|
2051
2207
|
* the field-separator (MSH-1), `msh.field(2)` returns encoding chars
|
|
2052
|
-
* (MSH-2), `msh.field(3)` returns MSH-3, and so on
|
|
2208
|
+
* (MSH-2), `msh.field(3)` returns MSH-3, and so on: the internal
|
|
2053
2209
|
* `fields[N-1]` offset for MSH segments is applied here (mirrors the
|
|
2054
2210
|
* dot-path resolver in `dot-path.ts`, keeping `msg.segments('MSH')[0].field(3)`
|
|
2055
2211
|
* and `msg.get('MSH.3')` in agreement).
|
|
2056
2212
|
*
|
|
2057
2213
|
* Returns a synthetic empty `Field` (`.isNull === false`, `.value === ""`)
|
|
2058
|
-
* when `n` is out of range
|
|
2214
|
+
* when `n` is out of range: never throws (MODEL-05). Successive calls with
|
|
2059
2215
|
* the same `n` return the same `Field` instance (D-12).
|
|
2060
2216
|
*
|
|
2061
2217
|
* @example
|
|
@@ -2082,13 +2238,13 @@ var Segment = class {
|
|
|
2082
2238
|
/**
|
|
2083
2239
|
* Return the `Field` at the profile-declared position for `name`, or
|
|
2084
2240
|
* `undefined` when no custom mapping exists (PROF-07). Unlike `field(n)`,
|
|
2085
|
-
* missing names return `undefined
|
|
2241
|
+
* missing names return `undefined`, NOT a synthetic empty Field, so
|
|
2086
2242
|
* typos surface instead of silently resolving to an empty string (D-14).
|
|
2087
2243
|
*
|
|
2088
2244
|
* For segments without a profile-declared customSegments slice (most
|
|
2089
2245
|
* non-Z segments, and any Z-segment whose host message had no profile
|
|
2090
2246
|
* applied), this method always returns `undefined` (D-15 defense-in-depth
|
|
2091
|
-
|
|
2247
|
+
*: D-05 already rejects standard-segment overlays at `defineProfile()`
|
|
2092
2248
|
* time).
|
|
2093
2249
|
*
|
|
2094
2250
|
* When the declared position is out of range for the underlying
|
|
@@ -3136,8 +3292,8 @@ function buildMeta(msg) {
|
|
|
3136
3292
|
const out = {};
|
|
3137
3293
|
const msh = msg.segments("MSH")[0];
|
|
3138
3294
|
if (msh !== void 0) {
|
|
3139
|
-
const
|
|
3140
|
-
const firstRep =
|
|
3295
|
+
const typeField2 = msh.field(9);
|
|
3296
|
+
const firstRep = typeField2.repetitions[0];
|
|
3141
3297
|
if (firstRep !== void 0) {
|
|
3142
3298
|
const parts = [];
|
|
3143
3299
|
for (let i = 0; i < firstRep.components.length; i++) {
|
|
@@ -3456,7 +3612,7 @@ function emitJson(msg) {
|
|
|
3456
3612
|
name: seg.name,
|
|
3457
3613
|
fields: seg.fields.map((field) => ({
|
|
3458
3614
|
// D-17 + Claude's-Discretion: `repetitions` is ALWAYS `[]` when the
|
|
3459
|
-
// field has zero repetitions
|
|
3615
|
+
// field has zero repetitions: shape-stable, no conditional omission.
|
|
3460
3616
|
repetitions: field.repetitions.map((rep) => ({
|
|
3461
3617
|
components: rep.components.map((comp) => ({
|
|
3462
3618
|
subcomponents: comp.subcomponents.slice()
|
|
@@ -3475,7 +3631,7 @@ function emitJson(msg) {
|
|
|
3475
3631
|
},
|
|
3476
3632
|
segments,
|
|
3477
3633
|
// D-19: ALWAYS present, even when empty. `msg.warnings` is already frozen
|
|
3478
|
-
// at Hl7Message construction (Phase 2)
|
|
3634
|
+
// at Hl7Message construction (Phase 2): pass-through is safe.
|
|
3479
3635
|
warnings: msg.warnings
|
|
3480
3636
|
};
|
|
3481
3637
|
if (msg.profile !== void 0) {
|
|
@@ -3516,6 +3672,205 @@ function emitMshSegment(seg, enc) {
|
|
|
3516
3672
|
return "MSH" + enc.field + msh2 + enc.field + tail;
|
|
3517
3673
|
}
|
|
3518
3674
|
|
|
3675
|
+
// src/builder/encode-composite.ts
|
|
3676
|
+
function sc(value) {
|
|
3677
|
+
return { subcomponents: [value ?? ""] };
|
|
3678
|
+
}
|
|
3679
|
+
function hdComponent(hd) {
|
|
3680
|
+
if (hd === void 0) return { subcomponents: [] };
|
|
3681
|
+
const subs = [hd.namespaceId ?? "", hd.universalId ?? "", hd.universalIdType ?? ""];
|
|
3682
|
+
while (subs.length > 0 && subs[subs.length - 1] === "") subs.pop();
|
|
3683
|
+
return { subcomponents: subs };
|
|
3684
|
+
}
|
|
3685
|
+
function isEmptyComponent(c) {
|
|
3686
|
+
return c.subcomponents.every((s) => s === "");
|
|
3687
|
+
}
|
|
3688
|
+
function toField(components) {
|
|
3689
|
+
const trimmed = components.slice();
|
|
3690
|
+
while (trimmed.length > 0) {
|
|
3691
|
+
const last = trimmed[trimmed.length - 1];
|
|
3692
|
+
if (last !== void 0 && isEmptyComponent(last)) trimmed.pop();
|
|
3693
|
+
else break;
|
|
3694
|
+
}
|
|
3695
|
+
if (trimmed.length === 0) return { repetitions: [], isNull: false };
|
|
3696
|
+
return { repetitions: [{ components: trimmed }], isNull: false };
|
|
3697
|
+
}
|
|
3698
|
+
function encodeXpn(v) {
|
|
3699
|
+
return toField([
|
|
3700
|
+
sc(v.familyName),
|
|
3701
|
+
sc(v.givenName),
|
|
3702
|
+
sc(v.secondName),
|
|
3703
|
+
sc(v.suffix),
|
|
3704
|
+
sc(v.prefix),
|
|
3705
|
+
sc(v.degree),
|
|
3706
|
+
sc(v.nameTypeCode),
|
|
3707
|
+
sc(v.nameRepresentationCode),
|
|
3708
|
+
sc(v.nameContext),
|
|
3709
|
+
sc(v.nameValidityRange),
|
|
3710
|
+
sc(v.nameAssemblyOrder),
|
|
3711
|
+
sc(v.effectiveDate),
|
|
3712
|
+
sc(v.expirationDate),
|
|
3713
|
+
sc(v.professionalSuffix)
|
|
3714
|
+
]);
|
|
3715
|
+
}
|
|
3716
|
+
function encodeXad(v) {
|
|
3717
|
+
return toField([
|
|
3718
|
+
sc(v.street),
|
|
3719
|
+
sc(v.otherDesignation),
|
|
3720
|
+
sc(v.city),
|
|
3721
|
+
sc(v.stateOrProvince),
|
|
3722
|
+
sc(v.zipOrPostalCode),
|
|
3723
|
+
sc(v.country),
|
|
3724
|
+
sc(v.addressType),
|
|
3725
|
+
sc(v.otherGeographicDesignation),
|
|
3726
|
+
sc(v.countyParishCode),
|
|
3727
|
+
sc(v.censusTract),
|
|
3728
|
+
sc(v.addressRepresentationCode),
|
|
3729
|
+
sc(v.addressValidityRange)
|
|
3730
|
+
]);
|
|
3731
|
+
}
|
|
3732
|
+
function encodeCx(v) {
|
|
3733
|
+
return toField([
|
|
3734
|
+
sc(v.idNumber),
|
|
3735
|
+
sc(v.checkDigit),
|
|
3736
|
+
sc(v.checkDigitScheme),
|
|
3737
|
+
hdComponent(v.assigningAuthority),
|
|
3738
|
+
sc(v.identifierTypeCode),
|
|
3739
|
+
sc(v.assigningFacility),
|
|
3740
|
+
sc(v.effectiveDate),
|
|
3741
|
+
sc(v.expirationDate),
|
|
3742
|
+
sc(v.assigningJurisdiction),
|
|
3743
|
+
sc(v.assigningAgencyOrDepartment)
|
|
3744
|
+
]);
|
|
3745
|
+
}
|
|
3746
|
+
function encodeCwe(v) {
|
|
3747
|
+
const components = [
|
|
3748
|
+
sc(v.identifier),
|
|
3749
|
+
sc(v.text),
|
|
3750
|
+
sc(v.nameOfCodingSystem),
|
|
3751
|
+
sc(v.alternateIdentifier),
|
|
3752
|
+
sc(v.alternateText),
|
|
3753
|
+
sc(v.nameOfAlternateCodingSystem),
|
|
3754
|
+
sc(v.codingSystemVersionId),
|
|
3755
|
+
sc(v.alternateCodingSystemVersionId),
|
|
3756
|
+
sc(v.originalText)
|
|
3757
|
+
];
|
|
3758
|
+
for (const extra of v.extraComponents ?? []) components.push(sc(extra));
|
|
3759
|
+
return toField(components);
|
|
3760
|
+
}
|
|
3761
|
+
function encodeCe(v) {
|
|
3762
|
+
const components = [
|
|
3763
|
+
sc(v.identifier),
|
|
3764
|
+
sc(v.text),
|
|
3765
|
+
sc(v.nameOfCodingSystem),
|
|
3766
|
+
sc(v.alternateIdentifier),
|
|
3767
|
+
sc(v.alternateText),
|
|
3768
|
+
sc(v.nameOfAlternateCodingSystem)
|
|
3769
|
+
];
|
|
3770
|
+
for (const extra of v.extraComponents ?? []) components.push(sc(extra));
|
|
3771
|
+
return toField(components);
|
|
3772
|
+
}
|
|
3773
|
+
function encodeXtn(v) {
|
|
3774
|
+
return toField([
|
|
3775
|
+
sc(v.telephoneNumber),
|
|
3776
|
+
sc(v.telecommunicationUseCode),
|
|
3777
|
+
sc(v.telecommunicationEquipmentType),
|
|
3778
|
+
sc(v.emailAddress),
|
|
3779
|
+
sc(v.countryCode),
|
|
3780
|
+
sc(v.areaCityCode),
|
|
3781
|
+
sc(v.localNumber),
|
|
3782
|
+
sc(v.extension),
|
|
3783
|
+
sc(v.anyText),
|
|
3784
|
+
sc(v.extensionPrefix),
|
|
3785
|
+
sc(v.speedDialCode),
|
|
3786
|
+
sc(v.unformattedTelephoneNumber)
|
|
3787
|
+
]);
|
|
3788
|
+
}
|
|
3789
|
+
function encodePl(v) {
|
|
3790
|
+
return toField([
|
|
3791
|
+
sc(v.pointOfCare),
|
|
3792
|
+
sc(v.room),
|
|
3793
|
+
sc(v.bed),
|
|
3794
|
+
hdComponent(v.facility),
|
|
3795
|
+
sc(v.locationStatus),
|
|
3796
|
+
sc(v.personLocationType),
|
|
3797
|
+
sc(v.building),
|
|
3798
|
+
sc(v.floor),
|
|
3799
|
+
sc(v.locationDescription),
|
|
3800
|
+
sc(v.comprehensiveLocationId),
|
|
3801
|
+
sc(v.assigningAuthorityForLocation)
|
|
3802
|
+
]);
|
|
3803
|
+
}
|
|
3804
|
+
function encodeHd(v) {
|
|
3805
|
+
return toField([sc(v.namespaceId), sc(v.universalId), sc(v.universalIdType)]);
|
|
3806
|
+
}
|
|
3807
|
+
function encodeTs(v) {
|
|
3808
|
+
const raw = typeof v === "string" ? v : v.raw;
|
|
3809
|
+
return toField([sc(raw)]);
|
|
3810
|
+
}
|
|
3811
|
+
function encodeNm(v) {
|
|
3812
|
+
let raw;
|
|
3813
|
+
if (typeof v === "number") raw = String(v);
|
|
3814
|
+
else if (typeof v === "string") raw = v;
|
|
3815
|
+
else raw = v.raw;
|
|
3816
|
+
return toField([sc(raw)]);
|
|
3817
|
+
}
|
|
3818
|
+
function encodeXcn(v) {
|
|
3819
|
+
return toField([
|
|
3820
|
+
sc(v.idNumber),
|
|
3821
|
+
sc(v.familyName),
|
|
3822
|
+
sc(v.givenName),
|
|
3823
|
+
sc(v.secondName),
|
|
3824
|
+
sc(v.suffix),
|
|
3825
|
+
sc(v.prefix),
|
|
3826
|
+
sc(v.degree),
|
|
3827
|
+
sc(v.sourceTable),
|
|
3828
|
+
hdComponent(v.assigningAuthority),
|
|
3829
|
+
sc(v.nameTypeCode),
|
|
3830
|
+
sc(v.identifierCheckDigit),
|
|
3831
|
+
sc(v.checkDigitScheme),
|
|
3832
|
+
sc(v.identifierTypeCode)
|
|
3833
|
+
]);
|
|
3834
|
+
}
|
|
3835
|
+
function encodeComposite(kind, value) {
|
|
3836
|
+
switch (kind) {
|
|
3837
|
+
case "XPN":
|
|
3838
|
+
return encodeXpn(value);
|
|
3839
|
+
case "XAD":
|
|
3840
|
+
return encodeXad(value);
|
|
3841
|
+
case "CX":
|
|
3842
|
+
return encodeCx(value);
|
|
3843
|
+
case "CWE":
|
|
3844
|
+
return encodeCwe(value);
|
|
3845
|
+
case "CE":
|
|
3846
|
+
return encodeCe(value);
|
|
3847
|
+
case "XTN":
|
|
3848
|
+
return encodeXtn(value);
|
|
3849
|
+
case "PL":
|
|
3850
|
+
return encodePl(value);
|
|
3851
|
+
case "TS":
|
|
3852
|
+
return encodeTs(value);
|
|
3853
|
+
case "NM":
|
|
3854
|
+
return encodeNm(value);
|
|
3855
|
+
case "HD":
|
|
3856
|
+
return encodeHd(value);
|
|
3857
|
+
case "XCN":
|
|
3858
|
+
return encodeXcn(value);
|
|
3859
|
+
default: {
|
|
3860
|
+
const never = kind;
|
|
3861
|
+
throw new TypeError(`encodeComposite: unknown composite kind ${JSON.stringify(never)}.`);
|
|
3862
|
+
}
|
|
3863
|
+
}
|
|
3864
|
+
}
|
|
3865
|
+
function encodeCompositeReps(kind, values) {
|
|
3866
|
+
const repetitions = [];
|
|
3867
|
+
for (const value of values) {
|
|
3868
|
+
const encoded = encodeComposite(kind, value);
|
|
3869
|
+
for (const rep of encoded.repetitions) repetitions.push(rep);
|
|
3870
|
+
}
|
|
3871
|
+
return { repetitions, isNull: false };
|
|
3872
|
+
}
|
|
3873
|
+
|
|
3519
3874
|
// src/model/message.ts
|
|
3520
3875
|
var SEGMENT_NAME_RE2 = /^[A-Z][A-Z0-9]{2}$/u;
|
|
3521
3876
|
function toMutableArray(arr) {
|
|
@@ -3525,7 +3880,7 @@ var Hl7Message = class {
|
|
|
3525
3880
|
/**
|
|
3526
3881
|
* Raw positional tree produced by the parser. 1-indexed per HL7 convention
|
|
3527
3882
|
* (`fields[0]` is the segment-name / MSH separator placeholder slot). Use
|
|
3528
|
-
* `segments(type)` / `allSegments()` for typed wrapper access
|
|
3883
|
+
* `segments(type)` / `allSegments()` for typed wrapper access: this field
|
|
3529
3884
|
* is exposed for advanced callers that need the raw tree directly (e.g.
|
|
3530
3885
|
* future serialization phase 5).
|
|
3531
3886
|
*/
|
|
@@ -3535,7 +3890,7 @@ var Hl7Message = class {
|
|
|
3535
3890
|
warnings;
|
|
3536
3891
|
profile;
|
|
3537
3892
|
/**
|
|
3538
|
-
* Merged `dateFormats` list
|
|
3893
|
+
* Merged `dateFormats` list: `options.dateFormats ++ profile.dateFormats`
|
|
3539
3894
|
* deduped first-occurrence per D-21. Empty array when neither source
|
|
3540
3895
|
* supplied any formats. Exposed publicly so helpers (`msg.meta.timestamp`)
|
|
3541
3896
|
* and advanced callers can introspect the active cascade.
|
|
@@ -3565,14 +3920,14 @@ var Hl7Message = class {
|
|
|
3565
3920
|
_allSegments;
|
|
3566
3921
|
/**
|
|
3567
3922
|
* Lazily built `Meta` view (Phase 4 D-02 memoization). Dropped wholesale
|
|
3568
|
-
* by `invalidateCaches`. `Meta` is always defined (D-03
|
|
3923
|
+
* by `invalidateCaches`. `Meta` is always defined (D-03: MSH absence
|
|
3569
3924
|
* throws `NO_MSH_SEGMENT` at parse time) so no null-sentinel is needed.
|
|
3570
3925
|
* @internal
|
|
3571
3926
|
*/
|
|
3572
3927
|
_meta;
|
|
3573
3928
|
/**
|
|
3574
3929
|
* Lazily built `MessageStructure` view (Phase G). Dropped wholesale by
|
|
3575
|
-
* `invalidateCaches`. Always defined
|
|
3930
|
+
* `invalidateCaches`. Always defined: `analyzeMessageStructure` returns an
|
|
3576
3931
|
* `recognized: false` summary for unmodelled types rather than absence.
|
|
3577
3932
|
* @internal
|
|
3578
3933
|
*/
|
|
@@ -3609,9 +3964,9 @@ var Hl7Message = class {
|
|
|
3609
3964
|
}
|
|
3610
3965
|
/**
|
|
3611
3966
|
* Resolve a dot-path (e.g. `PID.5.1`, `OBX[2].5`, `PID.3[0].1`) to its
|
|
3612
|
-
* decoded leaf string (unescaped once at parse
|
|
3967
|
+
* decoded leaf string (unescaped once at parse: never re-unescaped on read,
|
|
3613
3968
|
* HL7-VALUE-REDECODE). Returns `undefined` when the path doesn't
|
|
3614
|
-
* resolve
|
|
3969
|
+
* resolve: never throws on missing path (MODEL-05). Throws `TypeError`
|
|
3615
3970
|
* on malformed path syntax (e.g. `"pid.5"`, empty string).
|
|
3616
3971
|
*
|
|
3617
3972
|
* @example
|
|
@@ -3620,7 +3975,7 @@ var Hl7Message = class {
|
|
|
3620
3975
|
* msg.get("PID.5.1"); // "Smith"
|
|
3621
3976
|
* msg.get("OBX[2].5"); // third OBX's 5th field
|
|
3622
3977
|
* msg.get("NOT.9.9"); // undefined
|
|
3623
|
-
* msg.get("MSH.12"); // "2.5"
|
|
3978
|
+
* msg.get("MSH.12"); // "2.5": HL7 version string
|
|
3624
3979
|
* ```
|
|
3625
3980
|
*/
|
|
3626
3981
|
get(path) {
|
|
@@ -3629,7 +3984,7 @@ var Hl7Message = class {
|
|
|
3629
3984
|
/**
|
|
3630
3985
|
* Return every `Segment` of `segmentType` in document order. Returns `[]`
|
|
3631
3986
|
* (empty array, NEVER `undefined`) when no segment of that type exists
|
|
3632
|
-
* (MODEL-02). Alias for `segments(segmentType)
|
|
3987
|
+
* (MODEL-02). Alias for `segments(segmentType)`: shares the same cache.
|
|
3633
3988
|
*
|
|
3634
3989
|
* @example
|
|
3635
3990
|
* ```ts
|
|
@@ -3695,7 +4050,7 @@ var Hl7Message = class {
|
|
|
3695
4050
|
}
|
|
3696
4051
|
/**
|
|
3697
4052
|
* MSH-derived message metadata (type, controlId, timestamp, version, etc.).
|
|
3698
|
-
* D-01: plain object. D-02: memoized
|
|
4053
|
+
* D-01: plain object. D-02: memoized: `msg.meta === msg.meta` across
|
|
3699
4054
|
* reads until mutation invalidates. D-03: always defined (MSH absence
|
|
3700
4055
|
* throws `NO_MSH_SEGMENT` at parse time).
|
|
3701
4056
|
*
|
|
@@ -3711,10 +4066,10 @@ var Hl7Message = class {
|
|
|
3711
4066
|
}
|
|
3712
4067
|
/**
|
|
3713
4068
|
* Structural-conformance summary for the common message types (roadmap
|
|
3714
|
-
* Phase G)
|
|
4069
|
+
* Phase G): a misroute/truncation safety net, NOT a conformance validator.
|
|
3715
4070
|
* Reports, per the message's recognized (MSH-9.1, MSH-9.2) type, which
|
|
3716
4071
|
* Required segment groups are present and which are entirely absent
|
|
3717
|
-
* (`missingGroups
|
|
4072
|
+
* (`missingGroups`: the same set the parser flags as
|
|
3718
4073
|
* `MISSING_EXPECTED_GROUP` warnings). For an unmodelled type, `recognized`
|
|
3719
4074
|
* is `false` and `missingGroups` is empty. D-02: memoized.
|
|
3720
4075
|
*
|
|
@@ -3729,14 +4084,14 @@ var Hl7Message = class {
|
|
|
3729
4084
|
}
|
|
3730
4085
|
/**
|
|
3731
4086
|
* PID-derived patient view, or `undefined` when no PID segment exists
|
|
3732
|
-
* (D-04). D-02: memoized. HELPERS-07: never throws
|
|
4087
|
+
* (D-04). D-02: memoized. HELPERS-07: never throws: absent fields
|
|
3733
4088
|
* surface as `undefined` on the returned `Patient` object.
|
|
3734
4089
|
*
|
|
3735
4090
|
* @example
|
|
3736
4091
|
* ```ts
|
|
3737
4092
|
* console.log(msg.patient?.mrn);
|
|
3738
4093
|
* console.log(msg.patient?.fullName);
|
|
3739
|
-
* console.log(msg.patient?.dateOfBirth?.raw); // fidelity TS
|
|
4094
|
+
* console.log(msg.patient?.dateOfBirth?.raw); // fidelity TS: e.g. "19800115"
|
|
3740
4095
|
* ```
|
|
3741
4096
|
*/
|
|
3742
4097
|
get patient() {
|
|
@@ -3764,7 +4119,7 @@ var Hl7Message = class {
|
|
|
3764
4119
|
}
|
|
3765
4120
|
/**
|
|
3766
4121
|
* Every OBX segment as a typed Observation in document order. D-05:
|
|
3767
|
-
* returns `[]` when no OBX present. D-06: NOT memoized
|
|
4122
|
+
* returns `[]` when no OBX present. D-06: NOT memoized: each call
|
|
3768
4123
|
* re-walks `rawSegments`. Value type is discriminated per D-13.
|
|
3769
4124
|
*
|
|
3770
4125
|
* @example
|
|
@@ -3779,7 +4134,7 @@ var Hl7Message = class {
|
|
|
3779
4134
|
}
|
|
3780
4135
|
/**
|
|
3781
4136
|
* Every OBR as an Order with its OBX children grouped positionally (D-12) and
|
|
3782
|
-
* its TQ1 / legacy embedded-TQ (ORC-7) `timings` (Phase M
|
|
4137
|
+
* its TQ1 / legacy embedded-TQ (ORC-7) `timings` (Phase M: the repeat pattern
|
|
3783
4138
|
* surfaced verbatim, never resolved to a schedule). D-05: returns `[]` when no
|
|
3784
4139
|
* OBR present. D-06: not memoized.
|
|
3785
4140
|
*
|
|
@@ -3787,7 +4142,7 @@ var Hl7Message = class {
|
|
|
3787
4142
|
* ```ts
|
|
3788
4143
|
* for (const order of msg.orders()) {
|
|
3789
4144
|
* console.log(order.placerOrderNumber, order.observations.length);
|
|
3790
|
-
* for (const t of order.timings) console.log(t.repeatPattern?.code); // e.g. "Q6H"
|
|
4145
|
+
* for (const t of order.timings) console.log(t.repeatPattern?.code); // e.g. "Q6H": verbatim
|
|
3791
4146
|
* }
|
|
3792
4147
|
* ```
|
|
3793
4148
|
*/
|
|
@@ -3795,11 +4150,11 @@ var Hl7Message = class {
|
|
|
3795
4150
|
return orders(this);
|
|
3796
4151
|
}
|
|
3797
4152
|
/**
|
|
3798
|
-
* Message-level NTE notes (Phase P)
|
|
4153
|
+
* Message-level NTE notes (Phase P): every NTE segment with no recognized
|
|
3799
4154
|
* preceding parent (not immediately following a `PID`, `ORC`, `OBR`, or
|
|
3800
4155
|
* `OBX`), surfaced verbatim in document order so nothing is dropped. Notes
|
|
3801
4156
|
* that DO attach to a specific patient / order / result are exposed on those
|
|
3802
|
-
* helper outputs (`msg.patient?.notes`, `order.notes`, `observation.notes`)
|
|
4157
|
+
* helper outputs (`msg.patient?.notes`, `order.notes`, `observation.notes`),
|
|
3803
4158
|
* not here. D-05: returns `[]` when there are none. D-06: NOT memoized.
|
|
3804
4159
|
*
|
|
3805
4160
|
* @example
|
|
@@ -3812,7 +4167,7 @@ var Hl7Message = class {
|
|
|
3812
4167
|
}
|
|
3813
4168
|
/**
|
|
3814
4169
|
* Every recognized ADT patient-identity event (merge / move / link /
|
|
3815
|
-
* unlink / person add/update
|
|
4170
|
+
* unlink / person add/update: roadmap Phase K), with the MRG-sourced
|
|
3816
4171
|
* `prior` and PID/PV1-sourced `surviving` parties labelled by role and the
|
|
3817
4172
|
* spec-constant `direction: "MRG_TO_PID"` on merge/move events. Returns
|
|
3818
4173
|
* `[]` when the trigger event is not in the identity family. D-06: not
|
|
@@ -3839,7 +4194,7 @@ var Hl7Message = class {
|
|
|
3839
4194
|
* and never reconciled (Phase D §4).
|
|
3840
4195
|
*
|
|
3841
4196
|
* Each medication also carries its TQ1 / legacy embedded-TQ (RXE-1) `timings`
|
|
3842
|
-
* (Phase M
|
|
4197
|
+
* (Phase M: repeat pattern verbatim, never resolved to a schedule).
|
|
3843
4198
|
*
|
|
3844
4199
|
* @example
|
|
3845
4200
|
* ```ts
|
|
@@ -3860,7 +4215,7 @@ var Hl7Message = class {
|
|
|
3860
4215
|
* returns `[]` when no RXA present. D-06: not memoized. The vaccine code
|
|
3861
4216
|
* carries its own provenance; the action code (RXA-21) is surfaced verbatim
|
|
3862
4217
|
* and `recordOrigin` (administered vs historical) is derived only from the
|
|
3863
|
-
* well-known NIP001 RXA-9.1 codes
|
|
4218
|
+
* well-known NIP001 RXA-9.1 codes: never guessed.
|
|
3864
4219
|
*
|
|
3865
4220
|
* @example
|
|
3866
4221
|
* ```ts
|
|
@@ -3930,7 +4285,7 @@ var Hl7Message = class {
|
|
|
3930
4285
|
* status (Table 0278), SCH-11 start/end timing, and the resource groups
|
|
3931
4286
|
* (service / general / location / personnel). D-05: returns `[]` when no SCH
|
|
3932
4287
|
* is present. D-06: not memoized. Never throws (HELPERS-07). Not a
|
|
3933
|
-
* scheduling-workflow state machine
|
|
4288
|
+
* scheduling-workflow state machine: see the package known-limitations.
|
|
3934
4289
|
*
|
|
3935
4290
|
* @example
|
|
3936
4291
|
* ```ts
|
|
@@ -3947,7 +4302,7 @@ var Hl7Message = class {
|
|
|
3947
4302
|
* Every TXA of an MDM message as a typed `ClinicalDocument` (roadmap Phase Q),
|
|
3948
4303
|
* with the OBX narrative body grouped positionally under that TXA. The
|
|
3949
4304
|
* completion status (TXA-17) and availability status (TXA-19) are surfaced as
|
|
3950
|
-
* **distinct** fields and never conflated
|
|
4305
|
+
* **distinct** fields and never conflated: a document can be available before
|
|
3951
4306
|
* it is authenticated, and reading a preliminary document as final is the
|
|
3952
4307
|
* harm. D-05: returns `[]` when no TXA is present. D-06: not memoized. Never
|
|
3953
4308
|
* throws (HELPERS-07).
|
|
@@ -3967,7 +4322,7 @@ var Hl7Message = class {
|
|
|
3967
4322
|
* Every FT1 of a DFT message as a typed `Charge` (roadmap Phase Q), one per
|
|
3968
4323
|
* FT1 in document order. Surfaces billing-critical fields (FT1-6 transaction
|
|
3969
4324
|
* type, FT1-7 code, FT1-11/12 extended/unit amount, FT1-19 diagnosis linkage)
|
|
3970
|
-
* with **no billing logic and no money-as-float
|
|
4325
|
+
* with **no billing logic and no money-as-float**: amounts are the verbatim
|
|
3971
4326
|
* CP wire text. D-05: returns `[]` when no FT1 is present. D-06: not memoized.
|
|
3972
4327
|
* Never throws (HELPERS-07).
|
|
3973
4328
|
*
|
|
@@ -3988,7 +4343,7 @@ var Hl7Message = class {
|
|
|
3988
4343
|
* D-05; MSH-1 and MSH-2 are inlined verbatim from
|
|
3989
4344
|
* `this.encodingCharacters` per D-06; every field string passes through
|
|
3990
4345
|
* `reescape` per D-04. `RawField.isNull === true` is preserved as the
|
|
3991
|
-
* HL7 literal `""` (D-02). Pure
|
|
4346
|
+
* HL7 literal `""` (D-02). Pure: never warns, never throws (D-07).
|
|
3992
4347
|
*
|
|
3993
4348
|
* @example
|
|
3994
4349
|
* ```ts
|
|
@@ -4006,7 +4361,7 @@ var Hl7Message = class {
|
|
|
4006
4361
|
* Re-walks `rawSegments` on every call (D-30 no caching). Mirrors the
|
|
4007
4362
|
* raw tree one-for-one, preserves `isNull`, always includes
|
|
4008
4363
|
* `warnings: []`, and includes `profile: { name, lineage }` only when
|
|
4009
|
-
* `this.profile` is truthy (D-19/D-20). Pure
|
|
4364
|
+
* `this.profile` is truthy (D-19/D-20). Pure: never warns, never throws.
|
|
4010
4365
|
*
|
|
4011
4366
|
* @example
|
|
4012
4367
|
* ```ts
|
|
@@ -4025,16 +4380,16 @@ var Hl7Message = class {
|
|
|
4025
4380
|
* debugging (SER-04). Single opinionated format (D-22 no options):
|
|
4026
4381
|
* header line with type / controlId / timestamp / segment count, then
|
|
4027
4382
|
* one line per segment with labeled `[N]=value` fields (D-23). Composite
|
|
4028
|
-
* values render as their raw HL7 string
|
|
4029
|
-
* (D-24). Pure
|
|
4383
|
+
* values render as their raw HL7 string: depth stops at field level
|
|
4384
|
+
* (D-24). Pure: never warns, never throws (D-26).
|
|
4030
4385
|
*
|
|
4031
4386
|
* **Field values render as their raw HL7 string representation.**
|
|
4032
|
-
* Embedded delimiters in user data appear as escape sequences
|
|
4387
|
+
* Embedded delimiters in user data appear as escape sequences: e.g.
|
|
4033
4388
|
* a patient family name containing `|` renders as `Smith\F\Jones`
|
|
4034
4389
|
* (NOT `Smith|Jones`). This preserves round-trip fidelity: copy-pasting
|
|
4035
4390
|
* prettyPrint output into `parseHL7` yields a structurally equivalent
|
|
4036
4391
|
* message. For un-escaped human display, parse the composite first via
|
|
4037
|
-
* typed accessors (e.g. `msg.patient?.familyName`)
|
|
4392
|
+
* typed accessors (e.g. `msg.patient?.familyName`): those return
|
|
4038
4393
|
* already-decoded strings.
|
|
4039
4394
|
*
|
|
4040
4395
|
* @example
|
|
@@ -4054,13 +4409,13 @@ var Hl7Message = class {
|
|
|
4054
4409
|
* Set the string value at a dot-path. Mutates the underlying tree and
|
|
4055
4410
|
* returns `this` for chaining (D-15). Auto-creates missing repetitions,
|
|
4056
4411
|
* components, and subcomponents WITHIN an existing field, but does NOT
|
|
4057
|
-
* auto-create segments
|
|
4412
|
+
* auto-create segments: callers must `addSegment` first (throws
|
|
4058
4413
|
* `TypeError` with an actionable message otherwise).
|
|
4059
4414
|
*
|
|
4060
4415
|
* The value is accepted verbatim: unescaped delimiter characters are NOT
|
|
4061
4416
|
* rejected on input (D-18). Re-escaping on serialize is Phase 5's concern.
|
|
4062
4417
|
*
|
|
4063
|
-
* MSH-1 / MSH-2 follow the user-facing HL7 convention
|
|
4418
|
+
* MSH-1 / MSH-2 follow the user-facing HL7 convention: `setField("MSH.3", ...)`
|
|
4064
4419
|
* targets MSH-3 (sending application), matching `msg.get("MSH.3")`.
|
|
4065
4420
|
*
|
|
4066
4421
|
* Segment/Field wrapper caches are invalidated wholesale on success (D-17).
|
|
@@ -4097,7 +4452,7 @@ var Hl7Message = class {
|
|
|
4097
4452
|
const mutSegments = toMutableArray(this.rawSegments);
|
|
4098
4453
|
const seg = mutSegments[segIdx];
|
|
4099
4454
|
if (seg === void 0) {
|
|
4100
|
-
throw new Error("setField: internal invariant
|
|
4455
|
+
throw new Error("setField: internal invariant, segment disappeared after lookup.");
|
|
4101
4456
|
}
|
|
4102
4457
|
const mutFields = toMutableArray(seg.fields);
|
|
4103
4458
|
while (mutFields.length <= rawFieldIndex) {
|
|
@@ -4105,7 +4460,7 @@ var Hl7Message = class {
|
|
|
4105
4460
|
}
|
|
4106
4461
|
const field = mutFields[rawFieldIndex];
|
|
4107
4462
|
if (field === void 0) {
|
|
4108
|
-
throw new Error("setField: internal invariant
|
|
4463
|
+
throw new Error("setField: internal invariant, field missing after growth.");
|
|
4109
4464
|
}
|
|
4110
4465
|
const mutReps = toMutableArray(field.repetitions);
|
|
4111
4466
|
const repIdx = parsed.repetitionIndex ?? 0;
|
|
@@ -4114,7 +4469,7 @@ var Hl7Message = class {
|
|
|
4114
4469
|
}
|
|
4115
4470
|
const rep = mutReps[repIdx];
|
|
4116
4471
|
if (rep === void 0) {
|
|
4117
|
-
throw new Error("setField: internal invariant
|
|
4472
|
+
throw new Error("setField: internal invariant, repetition missing after growth.");
|
|
4118
4473
|
}
|
|
4119
4474
|
const mutComps = toMutableArray(rep.components);
|
|
4120
4475
|
const compIdx = (parsed.componentIndex ?? 1) - 1;
|
|
@@ -4123,7 +4478,7 @@ var Hl7Message = class {
|
|
|
4123
4478
|
}
|
|
4124
4479
|
const comp = mutComps[compIdx];
|
|
4125
4480
|
if (comp === void 0) {
|
|
4126
|
-
throw new Error("setField: internal invariant
|
|
4481
|
+
throw new Error("setField: internal invariant, component missing after growth.");
|
|
4127
4482
|
}
|
|
4128
4483
|
const mutSubs = toMutableArray(comp.subcomponents);
|
|
4129
4484
|
const subIdx = (parsed.subcomponentIndex ?? 1) - 1;
|
|
@@ -4143,9 +4498,95 @@ var Hl7Message = class {
|
|
|
4143
4498
|
this.invalidateCaches();
|
|
4144
4499
|
return this;
|
|
4145
4500
|
}
|
|
4501
|
+
/**
|
|
4502
|
+
* Set a **typed composite** at a field (or field-repetition) dot-path,
|
|
4503
|
+
* roadmap Phase T, the conservative-emit mirror of the typed read accessors
|
|
4504
|
+
* (`asXpn`/`asCx`/…). The caller passes a structured value (an XPN name, a CX
|
|
4505
|
+
* identifier, a TS timestamp, …) by its {@link CompositeKind}, and the setter
|
|
4506
|
+
* encodes it into a spec-clean field using the HL7-R encode-safe path: any
|
|
4507
|
+
* delimiter embedded in a component value is **escaped, never injected**, so
|
|
4508
|
+
* a `familyName` of `"Smith^Jr"` re-parses to exactly that string rather than
|
|
4509
|
+
* forging a component boundary. No hand-assembly of `^`/`&`/`~`.
|
|
4510
|
+
*
|
|
4511
|
+
* The path must resolve to a **field** (`"PID.5"`) or a specific
|
|
4512
|
+
* **repetition** of a field (`"PID.11[1]"`). A component/subcomponent-level
|
|
4513
|
+
* path (`"PID.5.1"`) is rejected with `TypeError`: a composite occupies a
|
|
4514
|
+
* whole field, not a single component. Like {@link setField}, the target
|
|
4515
|
+
* segment must already exist (`addSegment` first); the repetition defaults to
|
|
4516
|
+
* index 0 and other repetitions of the field are preserved.
|
|
4517
|
+
*
|
|
4518
|
+
* Never fabricates: an omitted optional composite field encodes to an
|
|
4519
|
+
* empty/absent component, never a defaulted value; an all-empty composite
|
|
4520
|
+
* clears the field. Segment/helper caches are invalidated on success; the
|
|
4521
|
+
* frozen `warnings` array is untouched.
|
|
4522
|
+
*
|
|
4523
|
+
* @example
|
|
4524
|
+
* ```ts
|
|
4525
|
+
* const msg = buildMessage({ type: "ADT^A01" }).addSegment("PID", [""]);
|
|
4526
|
+
* msg.setComposite("PID.5", "XPN", { familyName: "Smith", givenName: "Ann" });
|
|
4527
|
+
* msg.setComposite("PID.3", "CX", { idNumber: "MRN001", identifierTypeCode: "MR" });
|
|
4528
|
+
* msg.setComposite("PID.7", "TS", "19880705");
|
|
4529
|
+
* msg.get("PID.5.1"); // "Smith"
|
|
4530
|
+
* ```
|
|
4531
|
+
*/
|
|
4532
|
+
setComposite(path, kind, value) {
|
|
4533
|
+
const parsed = parsePath(path);
|
|
4534
|
+
if (parsed.componentIndex !== void 0 || parsed.subcomponentIndex !== void 0) {
|
|
4535
|
+
throw new TypeError(
|
|
4536
|
+
`setComposite: path "${path}" targets a component/subcomponent, but a typed composite occupies a whole field. Use a field path (e.g. "PID.5") or a field-repetition path (e.g. "PID.11[1]").`
|
|
4537
|
+
);
|
|
4538
|
+
}
|
|
4539
|
+
let seen = 0;
|
|
4540
|
+
let segIdx = -1;
|
|
4541
|
+
for (let i = 0; i < this.rawSegments.length; i++) {
|
|
4542
|
+
const s = this.rawSegments[i];
|
|
4543
|
+
if (s === void 0) continue;
|
|
4544
|
+
if (s.name === parsed.segmentType) {
|
|
4545
|
+
if (seen === parsed.segmentIndex) {
|
|
4546
|
+
segIdx = i;
|
|
4547
|
+
break;
|
|
4548
|
+
}
|
|
4549
|
+
seen++;
|
|
4550
|
+
}
|
|
4551
|
+
}
|
|
4552
|
+
if (segIdx === -1) {
|
|
4553
|
+
throw new TypeError(
|
|
4554
|
+
`setComposite: segment "${parsed.segmentType}" (occurrence ${String(parsed.segmentIndex)}) not found. Add it first with addSegment("${parsed.segmentType}", [...]).`
|
|
4555
|
+
);
|
|
4556
|
+
}
|
|
4557
|
+
const encoded = encodeComposite(kind, value);
|
|
4558
|
+
const encodedRep = encoded.repetitions[0] ?? { components: [] };
|
|
4559
|
+
const rawFieldIndex = parsed.segmentType === "MSH" ? parsed.fieldIndex - 1 : parsed.fieldIndex;
|
|
4560
|
+
const mutSegments = toMutableArray(this.rawSegments);
|
|
4561
|
+
const seg = mutSegments[segIdx];
|
|
4562
|
+
if (seg === void 0) {
|
|
4563
|
+
throw new Error("setComposite: internal invariant, segment disappeared after lookup.");
|
|
4564
|
+
}
|
|
4565
|
+
const mutFields = toMutableArray(seg.fields);
|
|
4566
|
+
while (mutFields.length <= rawFieldIndex) {
|
|
4567
|
+
mutFields.push({ repetitions: [], isNull: false });
|
|
4568
|
+
}
|
|
4569
|
+
const field = mutFields[rawFieldIndex];
|
|
4570
|
+
if (field === void 0) {
|
|
4571
|
+
throw new Error("setComposite: internal invariant, field missing after growth.");
|
|
4572
|
+
}
|
|
4573
|
+
const mutReps = toMutableArray(field.repetitions);
|
|
4574
|
+
const repIdx = parsed.repetitionIndex ?? 0;
|
|
4575
|
+
while (mutReps.length <= repIdx) {
|
|
4576
|
+
mutReps.push({ components: [] });
|
|
4577
|
+
}
|
|
4578
|
+
mutReps[repIdx] = encodedRep;
|
|
4579
|
+
const newField = { repetitions: mutReps, isNull: false };
|
|
4580
|
+
mutFields[rawFieldIndex] = newField;
|
|
4581
|
+
const newSeg = { name: seg.name, fields: mutFields };
|
|
4582
|
+
mutSegments[segIdx] = newSeg;
|
|
4583
|
+
this.rawSegments = mutSegments;
|
|
4584
|
+
this.invalidateCaches();
|
|
4585
|
+
return this;
|
|
4586
|
+
}
|
|
4146
4587
|
/**
|
|
4147
4588
|
* Append a new segment to the end of the message. `name` must match
|
|
4148
|
-
* `/^(?:[A-Z]{3}|Z[A-Z0-9]{2})$/u
|
|
4589
|
+
* `/^(?:[A-Z]{3}|Z[A-Z0-9]{2})$/u`: throws `TypeError` otherwise (D-19).
|
|
4149
4590
|
*
|
|
4150
4591
|
* `fields` is interpreted in HL7 1-indexed terms: `addSegment("NTE", [a, b, c])`
|
|
4151
4592
|
* produces a segment whose `NTE-1 = a`, `NTE-2 = b`, `NTE-3 = c`. The
|
|
@@ -4195,14 +4636,14 @@ var Hl7Message = class {
|
|
|
4195
4636
|
/**
|
|
4196
4637
|
* Remove segments by type + occurrence or by type + all. Call shapes:
|
|
4197
4638
|
*
|
|
4198
|
-
* - `removeSegment("NTE")
|
|
4199
|
-
* - `removeSegment("OBX", 1)
|
|
4200
|
-
* - `removeSegment("OBX", { all: true })
|
|
4639
|
+
* - `removeSegment("NTE")`: remove the FIRST NTE (occurrence 0).
|
|
4640
|
+
* - `removeSegment("OBX", 1)`: remove the SECOND OBX (0-indexed per D-01).
|
|
4641
|
+
* - `removeSegment("OBX", { all: true })`: remove ALL OBX segments.
|
|
4201
4642
|
*
|
|
4202
|
-
* `MSH` is protected
|
|
4643
|
+
* `MSH` is protected: `removeSegment("MSH")` throws `TypeError` (every
|
|
4203
4644
|
* HL7 message must retain its MSH segment). Unknown segment types are a
|
|
4204
4645
|
* no-op (idempotent; no throw). Segment name must match the D-19 shape
|
|
4205
|
-
* regex
|
|
4646
|
+
* regex: invalid shapes throw `TypeError` for symmetry with `addSegment`.
|
|
4206
4647
|
*
|
|
4207
4648
|
* Invalidates caches on return; warnings untouched (D-16).
|
|
4208
4649
|
*
|
|
@@ -4380,9 +4821,9 @@ function extractVersion(msh) {
|
|
|
4380
4821
|
}
|
|
4381
4822
|
function extractMessageType(msh) {
|
|
4382
4823
|
if (msh === void 0 || msh.name !== "MSH") return { messageCode: "", triggerEvent: "" };
|
|
4383
|
-
const
|
|
4384
|
-
if (
|
|
4385
|
-
const firstRep =
|
|
4824
|
+
const typeField2 = msh.fields[8];
|
|
4825
|
+
if (typeField2 === void 0) return { messageCode: "", triggerEvent: "" };
|
|
4826
|
+
const firstRep = typeField2.repetitions[0];
|
|
4386
4827
|
if (firstRep === void 0) return { messageCode: "", triggerEvent: "" };
|
|
4387
4828
|
const messageCode = firstRep.components[0]?.subcomponents[0] ?? "";
|
|
4388
4829
|
const triggerEvent = firstRep.components[1]?.subcomponents[0] ?? "";
|
|
@@ -4514,6 +4955,24 @@ function parseHL7(raw, optionsOrProfile) {
|
|
|
4514
4955
|
return new Hl7Message(init);
|
|
4515
4956
|
}
|
|
4516
4957
|
|
|
4958
|
+
// src/text/codec.ts
|
|
4959
|
+
function ignore() {
|
|
4960
|
+
}
|
|
4961
|
+
function decodeText(input, enc = DEFAULT_ENCODING_CHARACTERS) {
|
|
4962
|
+
return unescape(input, enc, ignore, { segmentIndex: 0 });
|
|
4963
|
+
}
|
|
4964
|
+
function encodeText(input, enc = DEFAULT_ENCODING_CHARACTERS) {
|
|
4965
|
+
return reescape(input, enc);
|
|
4966
|
+
}
|
|
4967
|
+
|
|
4968
|
+
// src/text/index.ts
|
|
4969
|
+
var text_exports = {};
|
|
4970
|
+
__export(text_exports, {
|
|
4971
|
+
decode: () => decodeText,
|
|
4972
|
+
encode: () => encodeText,
|
|
4973
|
+
render: () => renderText
|
|
4974
|
+
});
|
|
4975
|
+
|
|
4517
4976
|
// src/model/types/namespace.ts
|
|
4518
4977
|
var namespace_exports = {};
|
|
4519
4978
|
var ALNUM_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
@@ -4629,6 +5088,247 @@ function compositeField(typeString) {
|
|
|
4629
5088
|
};
|
|
4630
5089
|
}
|
|
4631
5090
|
|
|
5091
|
+
// src/builder/segment-fields.ts
|
|
5092
|
+
function scalarField2(value) {
|
|
5093
|
+
if (value === void 0 || value === "") return { repetitions: [], isNull: false };
|
|
5094
|
+
return { repetitions: [{ components: [{ subcomponents: [value] }] }], isNull: false };
|
|
5095
|
+
}
|
|
5096
|
+
function absentField2() {
|
|
5097
|
+
return { repetitions: [], isNull: false };
|
|
5098
|
+
}
|
|
5099
|
+
function typeField(typeString) {
|
|
5100
|
+
const components = typeString.split("^").map((p) => ({ subcomponents: [p] }));
|
|
5101
|
+
return { repetitions: [{ components }], isNull: false };
|
|
5102
|
+
}
|
|
5103
|
+
function resolveTimestamp2(ts) {
|
|
5104
|
+
if (ts === void 0) return formatHl7Timestamp(/* @__PURE__ */ new Date());
|
|
5105
|
+
if (typeof ts === "string") return ts;
|
|
5106
|
+
return formatHl7Timestamp(ts);
|
|
5107
|
+
}
|
|
5108
|
+
function buildMshSegment(type, envelope) {
|
|
5109
|
+
const enc = DEFAULT_ENCODING_CHARACTERS;
|
|
5110
|
+
const fields = [
|
|
5111
|
+
scalarField2(enc.field),
|
|
5112
|
+
// fields[0] MSH-1 field separator placeholder
|
|
5113
|
+
scalarField2(
|
|
5114
|
+
enc.component + enc.repetition + enc.escape + enc.subcomponent + (enc.truncation ?? "")
|
|
5115
|
+
),
|
|
5116
|
+
// MSH-2 encoding characters
|
|
5117
|
+
scalarField2(envelope.sendingApp),
|
|
5118
|
+
// MSH-3
|
|
5119
|
+
scalarField2(envelope.sendingFacility),
|
|
5120
|
+
// MSH-4
|
|
5121
|
+
scalarField2(envelope.receivingApp),
|
|
5122
|
+
// MSH-5
|
|
5123
|
+
scalarField2(envelope.receivingFacility),
|
|
5124
|
+
// MSH-6
|
|
5125
|
+
scalarField2(resolveTimestamp2(envelope.timestamp)),
|
|
5126
|
+
// MSH-7
|
|
5127
|
+
absentField2(),
|
|
5128
|
+
// MSH-8
|
|
5129
|
+
typeField(type),
|
|
5130
|
+
// MSH-9
|
|
5131
|
+
scalarField2(envelope.controlId ?? generateControlId()),
|
|
5132
|
+
// MSH-10
|
|
5133
|
+
scalarField2(envelope.processingId ?? "P"),
|
|
5134
|
+
// MSH-11
|
|
5135
|
+
scalarField2(envelope.version ?? "2.5")
|
|
5136
|
+
// MSH-12
|
|
5137
|
+
];
|
|
5138
|
+
return { name: "MSH", fields };
|
|
5139
|
+
}
|
|
5140
|
+
function assembleSegment(name, positions) {
|
|
5141
|
+
let max = 0;
|
|
5142
|
+
for (const [pos, field] of positions) {
|
|
5143
|
+
if (field !== void 0 && pos > max) max = pos;
|
|
5144
|
+
}
|
|
5145
|
+
const fields = [absentField2()];
|
|
5146
|
+
for (let i = 1; i <= max; i++) {
|
|
5147
|
+
fields.push(positions.get(i) ?? absentField2());
|
|
5148
|
+
}
|
|
5149
|
+
return { name, fields };
|
|
5150
|
+
}
|
|
5151
|
+
|
|
5152
|
+
// src/builder/build-adt.ts
|
|
5153
|
+
function repField(kind, value) {
|
|
5154
|
+
if (value === void 0) return { repetitions: [], isNull: false };
|
|
5155
|
+
if (Array.isArray(value)) {
|
|
5156
|
+
return encodeCompositeReps(kind, value);
|
|
5157
|
+
}
|
|
5158
|
+
return encodeComposite(kind, value);
|
|
5159
|
+
}
|
|
5160
|
+
function buildAdt(event, init) {
|
|
5161
|
+
if (typeof event !== "string" || event.trim().length === 0) {
|
|
5162
|
+
throw new TypeError(
|
|
5163
|
+
`buildAdt: \`event\` (the ADT trigger, e.g. "A01") is required and must be a non-empty string. Received: ${JSON.stringify(event)}.`
|
|
5164
|
+
);
|
|
5165
|
+
}
|
|
5166
|
+
if (init === null || init === void 0 || init.patient === void 0) {
|
|
5167
|
+
throw new TypeError(
|
|
5168
|
+
"buildAdt: `patient` is required. An ADT patient event is never emitted with a fabricated patient. Supply at least one identifier or a name."
|
|
5169
|
+
);
|
|
5170
|
+
}
|
|
5171
|
+
const enc = DEFAULT_ENCODING_CHARACTERS;
|
|
5172
|
+
const version = init.version ?? "2.5";
|
|
5173
|
+
const segments = [buildMshSegment(`ADT^${event}`, init)];
|
|
5174
|
+
const evn = init.event;
|
|
5175
|
+
segments.push(
|
|
5176
|
+
assembleSegment(
|
|
5177
|
+
"EVN",
|
|
5178
|
+
/* @__PURE__ */ new Map([
|
|
5179
|
+
[1, scalarField2(event)],
|
|
5180
|
+
[
|
|
5181
|
+
2,
|
|
5182
|
+
evn?.recordedDateTime !== void 0 ? encodeComposite("TS", evn.recordedDateTime) : void 0
|
|
5183
|
+
],
|
|
5184
|
+
[
|
|
5185
|
+
6,
|
|
5186
|
+
evn?.eventOccurred !== void 0 ? encodeComposite("TS", evn.eventOccurred) : void 0
|
|
5187
|
+
]
|
|
5188
|
+
])
|
|
5189
|
+
)
|
|
5190
|
+
);
|
|
5191
|
+
const p = init.patient;
|
|
5192
|
+
segments.push(
|
|
5193
|
+
assembleSegment(
|
|
5194
|
+
"PID",
|
|
5195
|
+
/* @__PURE__ */ new Map([
|
|
5196
|
+
[1, p.setId !== void 0 ? scalarField2(p.setId) : void 0],
|
|
5197
|
+
[3, p.identifiers !== void 0 ? repField("CX", p.identifiers) : void 0],
|
|
5198
|
+
[5, p.name !== void 0 ? repField("XPN", p.name) : void 0],
|
|
5199
|
+
[
|
|
5200
|
+
6,
|
|
5201
|
+
p.mothersMaidenName !== void 0 ? encodeComposite("XPN", p.mothersMaidenName) : void 0
|
|
5202
|
+
],
|
|
5203
|
+
[7, p.birthDateTime !== void 0 ? encodeComposite("TS", p.birthDateTime) : void 0],
|
|
5204
|
+
[8, p.administrativeSex !== void 0 ? scalarField2(p.administrativeSex) : void 0],
|
|
5205
|
+
[11, p.address !== void 0 ? repField("XAD", p.address) : void 0],
|
|
5206
|
+
[13, p.phoneHome !== void 0 ? repField("XTN", p.phoneHome) : void 0],
|
|
5207
|
+
[18, p.accountNumber !== void 0 ? encodeComposite("CX", p.accountNumber) : void 0]
|
|
5208
|
+
])
|
|
5209
|
+
)
|
|
5210
|
+
);
|
|
5211
|
+
const v = init.visit;
|
|
5212
|
+
segments.push(
|
|
5213
|
+
assembleSegment(
|
|
5214
|
+
"PV1",
|
|
5215
|
+
/* @__PURE__ */ new Map([
|
|
5216
|
+
[1, v?.setId !== void 0 ? scalarField2(v.setId) : void 0],
|
|
5217
|
+
[2, v?.patientClass !== void 0 ? scalarField2(v.patientClass) : void 0],
|
|
5218
|
+
[
|
|
5219
|
+
3,
|
|
5220
|
+
v?.assignedLocation !== void 0 ? encodeComposite("PL", v.assignedLocation) : void 0
|
|
5221
|
+
],
|
|
5222
|
+
[7, v?.attendingDoctor !== void 0 ? repField("XCN", v.attendingDoctor) : void 0],
|
|
5223
|
+
[8, v?.referringDoctor !== void 0 ? repField("XCN", v.referringDoctor) : void 0],
|
|
5224
|
+
[19, v?.visitNumber !== void 0 ? encodeComposite("CX", v.visitNumber) : void 0],
|
|
5225
|
+
[44, v?.admitDateTime !== void 0 ? encodeComposite("TS", v.admitDateTime) : void 0]
|
|
5226
|
+
])
|
|
5227
|
+
)
|
|
5228
|
+
);
|
|
5229
|
+
return new Hl7Message({
|
|
5230
|
+
segments,
|
|
5231
|
+
encodingCharacters: enc,
|
|
5232
|
+
version,
|
|
5233
|
+
warnings: []
|
|
5234
|
+
});
|
|
5235
|
+
}
|
|
5236
|
+
|
|
5237
|
+
// src/builder/build-oru.ts
|
|
5238
|
+
function repField2(kind, value) {
|
|
5239
|
+
if (value === void 0) return { repetitions: [], isNull: false };
|
|
5240
|
+
if (Array.isArray(value)) {
|
|
5241
|
+
return encodeCompositeReps(kind, value);
|
|
5242
|
+
}
|
|
5243
|
+
return encodeComposite(kind, value);
|
|
5244
|
+
}
|
|
5245
|
+
function buildOru(init) {
|
|
5246
|
+
if (init === null || init === void 0 || init.patient === void 0) {
|
|
5247
|
+
throw new TypeError(
|
|
5248
|
+
"buildOru: `patient` is required. An ORU is never emitted with a fabricated subject."
|
|
5249
|
+
);
|
|
5250
|
+
}
|
|
5251
|
+
if (init.observations === void 0 || init.observations.length === 0) {
|
|
5252
|
+
throw new TypeError(
|
|
5253
|
+
"buildOru: at least one `observations` entry (OBX) is required. An ORU with no result is a typed error, never a fabricated observation."
|
|
5254
|
+
);
|
|
5255
|
+
}
|
|
5256
|
+
const enc = DEFAULT_ENCODING_CHARACTERS;
|
|
5257
|
+
const version = init.version ?? "2.5";
|
|
5258
|
+
const segments = [buildMshSegment("ORU^R01", init)];
|
|
5259
|
+
const p = init.patient;
|
|
5260
|
+
segments.push(
|
|
5261
|
+
assembleSegment(
|
|
5262
|
+
"PID",
|
|
5263
|
+
/* @__PURE__ */ new Map([
|
|
5264
|
+
[1, p.setId !== void 0 ? scalarField2(p.setId) : void 0],
|
|
5265
|
+
[3, p.identifiers !== void 0 ? repField2("CX", p.identifiers) : void 0],
|
|
5266
|
+
[5, p.name !== void 0 ? repField2("XPN", p.name) : void 0],
|
|
5267
|
+
[
|
|
5268
|
+
6,
|
|
5269
|
+
p.mothersMaidenName !== void 0 ? encodeComposite("XPN", p.mothersMaidenName) : void 0
|
|
5270
|
+
],
|
|
5271
|
+
[7, p.birthDateTime !== void 0 ? encodeComposite("TS", p.birthDateTime) : void 0],
|
|
5272
|
+
[8, p.administrativeSex !== void 0 ? scalarField2(p.administrativeSex) : void 0],
|
|
5273
|
+
[11, p.address !== void 0 ? repField2("XAD", p.address) : void 0],
|
|
5274
|
+
[13, p.phoneHome !== void 0 ? repField2("XTN", p.phoneHome) : void 0],
|
|
5275
|
+
[18, p.accountNumber !== void 0 ? encodeComposite("CX", p.accountNumber) : void 0]
|
|
5276
|
+
])
|
|
5277
|
+
)
|
|
5278
|
+
);
|
|
5279
|
+
const o = init.order;
|
|
5280
|
+
segments.push(
|
|
5281
|
+
assembleSegment(
|
|
5282
|
+
"OBR",
|
|
5283
|
+
/* @__PURE__ */ new Map([
|
|
5284
|
+
[1, o?.setId !== void 0 ? scalarField2(o.setId) : void 0],
|
|
5285
|
+
[2, o?.placerOrderNumber !== void 0 ? scalarField2(o.placerOrderNumber) : void 0],
|
|
5286
|
+
[3, o?.fillerOrderNumber !== void 0 ? scalarField2(o.fillerOrderNumber) : void 0],
|
|
5287
|
+
[
|
|
5288
|
+
4,
|
|
5289
|
+
o?.universalServiceId !== void 0 ? encodeComposite("CWE", o.universalServiceId) : void 0
|
|
5290
|
+
],
|
|
5291
|
+
[
|
|
5292
|
+
7,
|
|
5293
|
+
o?.observationDateTime !== void 0 ? encodeComposite("TS", o.observationDateTime) : void 0
|
|
5294
|
+
],
|
|
5295
|
+
[16, o?.orderingProvider !== void 0 ? repField2("XCN", o.orderingProvider) : void 0],
|
|
5296
|
+
[25, o?.resultStatus !== void 0 ? scalarField2(o.resultStatus) : void 0]
|
|
5297
|
+
])
|
|
5298
|
+
)
|
|
5299
|
+
);
|
|
5300
|
+
for (const obs of init.observations) {
|
|
5301
|
+
segments.push(
|
|
5302
|
+
assembleSegment(
|
|
5303
|
+
"OBX",
|
|
5304
|
+
/* @__PURE__ */ new Map([
|
|
5305
|
+
[1, obs.setId !== void 0 ? scalarField2(obs.setId) : void 0],
|
|
5306
|
+
[2, obs.valueType !== void 0 ? scalarField2(obs.valueType) : void 0],
|
|
5307
|
+
[3, obs.identifier !== void 0 ? encodeComposite("CWE", obs.identifier) : void 0],
|
|
5308
|
+
[5, obs.value !== void 0 ? scalarField2(obs.value) : void 0],
|
|
5309
|
+
[6, obs.units !== void 0 ? encodeComposite("CWE", obs.units) : void 0],
|
|
5310
|
+
[7, obs.referenceRange !== void 0 ? scalarField2(obs.referenceRange) : void 0],
|
|
5311
|
+
[8, obs.abnormalFlags !== void 0 ? scalarField2(obs.abnormalFlags) : void 0],
|
|
5312
|
+
[
|
|
5313
|
+
11,
|
|
5314
|
+
obs.observationResultStatus !== void 0 ? scalarField2(obs.observationResultStatus) : void 0
|
|
5315
|
+
],
|
|
5316
|
+
[
|
|
5317
|
+
14,
|
|
5318
|
+
obs.observationDateTime !== void 0 ? encodeComposite("TS", obs.observationDateTime) : void 0
|
|
5319
|
+
]
|
|
5320
|
+
])
|
|
5321
|
+
)
|
|
5322
|
+
);
|
|
5323
|
+
}
|
|
5324
|
+
return new Hl7Message({
|
|
5325
|
+
segments,
|
|
5326
|
+
encodingCharacters: enc,
|
|
5327
|
+
version,
|
|
5328
|
+
warnings: []
|
|
5329
|
+
});
|
|
5330
|
+
}
|
|
5331
|
+
|
|
4632
5332
|
// src/builder/ack-tables.ts
|
|
4633
5333
|
var ACK_CODES = {
|
|
4634
5334
|
AA: "AA",
|
|
@@ -4711,7 +5411,7 @@ function buildAck(inbound, options) {
|
|
|
4711
5411
|
const enc = DEFAULT_ENCODING_CHARACTERS;
|
|
4712
5412
|
const meta = inbound.meta;
|
|
4713
5413
|
const msh = inbound.segments("MSH")[0];
|
|
4714
|
-
const inboundMsh = (n) => msh?.field(n).raw ??
|
|
5414
|
+
const inboundMsh = (n) => msh?.field(n).raw ?? absentField3();
|
|
4715
5415
|
const sameEnc = encodingsEqual(inbound.encodingCharacters, enc);
|
|
4716
5416
|
const rawControlId = sameEnc ? inboundMsh(10) : stripEscapeOverlay(inboundMsh(10));
|
|
4717
5417
|
const hasCorrelation = rawFieldHasContent(rawControlId);
|
|
@@ -4720,8 +5420,8 @@ function buildAck(inbound, options) {
|
|
|
4720
5420
|
const version = meta.version ?? "2.5";
|
|
4721
5421
|
const processingId = meta.processingId ?? "P";
|
|
4722
5422
|
const mshFields = [
|
|
4723
|
-
|
|
4724
|
-
|
|
5423
|
+
scalarField3(enc.field),
|
|
5424
|
+
scalarField3(
|
|
4725
5425
|
enc.component + enc.repetition + enc.escape + enc.subcomponent + (enc.truncation ?? "")
|
|
4726
5426
|
),
|
|
4727
5427
|
inboundMsh(5),
|
|
@@ -4732,26 +5432,26 @@ function buildAck(inbound, options) {
|
|
|
4732
5432
|
// MSH-5 receivingApp = inbound MSH-3 (sendingApp)
|
|
4733
5433
|
inboundMsh(4),
|
|
4734
5434
|
// MSH-6 receivingFacility = inbound MSH-4 (sendingFacility)
|
|
4735
|
-
|
|
5435
|
+
scalarField3(formatHl7Timestamp(/* @__PURE__ */ new Date())),
|
|
4736
5436
|
// MSH-7
|
|
4737
|
-
|
|
5437
|
+
absentField3(),
|
|
4738
5438
|
// MSH-8
|
|
4739
5439
|
compositeField2(ackType),
|
|
4740
5440
|
// MSH-9
|
|
4741
|
-
|
|
5441
|
+
scalarField3(generateControlId()),
|
|
4742
5442
|
// MSH-10
|
|
4743
|
-
|
|
5443
|
+
scalarField3(processingId),
|
|
4744
5444
|
// MSH-11
|
|
4745
|
-
|
|
5445
|
+
scalarField3(version)
|
|
4746
5446
|
// MSH-12
|
|
4747
5447
|
];
|
|
4748
5448
|
const segments = [{ name: "MSH", fields: mshFields }];
|
|
4749
5449
|
segments.push({
|
|
4750
5450
|
name: "MSA",
|
|
4751
5451
|
fields: [
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
hasCorrelation ? rawControlId :
|
|
5452
|
+
absentField3(),
|
|
5453
|
+
scalarField3(emittedCode),
|
|
5454
|
+
hasCorrelation ? rawControlId : absentField3()
|
|
4755
5455
|
]
|
|
4756
5456
|
});
|
|
4757
5457
|
for (const detail of normalizeErrors(options.error)) {
|
|
@@ -4791,24 +5491,24 @@ function buildErrSegment(detail) {
|
|
|
4791
5491
|
return {
|
|
4792
5492
|
name: "ERR",
|
|
4793
5493
|
fields: [
|
|
4794
|
-
|
|
4795
|
-
// fields[0]
|
|
4796
|
-
|
|
5494
|
+
absentField3(),
|
|
5495
|
+
// fields[0]: segment-name/separator placeholder slot
|
|
5496
|
+
absentField3(),
|
|
4797
5497
|
// ERR-1 (deprecated v2.4 / withdrawn v2.7)
|
|
4798
|
-
detail.location !== void 0 ? compositeField2(detail.location) :
|
|
5498
|
+
detail.location !== void 0 ? compositeField2(detail.location) : absentField3(),
|
|
4799
5499
|
// ERR-2 (ERL)
|
|
4800
5500
|
err3,
|
|
4801
5501
|
// ERR-3
|
|
4802
|
-
|
|
5502
|
+
scalarField3(severity)
|
|
4803
5503
|
// ERR-4
|
|
4804
5504
|
]
|
|
4805
5505
|
};
|
|
4806
5506
|
}
|
|
4807
|
-
function
|
|
5507
|
+
function scalarField3(value) {
|
|
4808
5508
|
if (value === "") return { repetitions: [], isNull: false };
|
|
4809
5509
|
return { repetitions: [{ components: [{ subcomponents: [value] }] }], isNull: false };
|
|
4810
5510
|
}
|
|
4811
|
-
function
|
|
5511
|
+
function absentField3() {
|
|
4812
5512
|
return { repetitions: [], isNull: false };
|
|
4813
5513
|
}
|
|
4814
5514
|
function encodingsEqual(a, b) {
|
|
@@ -5068,7 +5768,7 @@ function validateCustomSegments(map, profileName) {
|
|
|
5068
5768
|
for (const key of Object.keys(map)) {
|
|
5069
5769
|
if (!Z_SEGMENT_RE.test(key)) {
|
|
5070
5770
|
throw new ProfileDefinitionError(
|
|
5071
|
-
`Profile '${profileName}' declares customSegments for '${key}'
|
|
5771
|
+
`Profile '${profileName}' declares customSegments for '${key}': only Z-segments (Z[A-Z0-9]{2}) are allowed in v1. Typed overlays are a v2 feature.`,
|
|
5072
5772
|
profileName
|
|
5073
5773
|
);
|
|
5074
5774
|
}
|
|
@@ -5095,7 +5795,7 @@ function validateDateFormats(formats, profileName) {
|
|
|
5095
5795
|
const f = formats[i] ?? "";
|
|
5096
5796
|
if (!TOKEN_MATCH_RE.test(f)) {
|
|
5097
5797
|
throw new ProfileDefinitionError(
|
|
5098
|
-
`Profile '${profileName}' dateFormats[${String(i)}] = ${JSON.stringify(f)} is malformed
|
|
5798
|
+
`Profile '${profileName}' dateFormats[${String(i)}] = ${JSON.stringify(f)} is malformed: must contain at least one of ${SUPPORTED_DATE_TOKENS.join("/")}.`,
|
|
5099
5799
|
profileName
|
|
5100
5800
|
);
|
|
5101
5801
|
}
|
|
@@ -5153,7 +5853,7 @@ function defineProfile(opts) {
|
|
|
5153
5853
|
// src/profiles/athena.ts
|
|
5154
5854
|
var athena = defineProfile({
|
|
5155
5855
|
name: "athena",
|
|
5156
|
-
description: "athenahealth Interop
|
|
5856
|
+
description: "athenahealth Interop: US-short-date format and care-team Z-segment",
|
|
5157
5857
|
dateFormats: ["MM/DD/YYYY"],
|
|
5158
5858
|
customSegments: {
|
|
5159
5859
|
ZCA: { fields: { careTeamRole: 3, providerId: 5, providerName: 6 } }
|
|
@@ -5163,7 +5863,7 @@ var athena = defineProfile({
|
|
|
5163
5863
|
// src/profiles/cerner.ts
|
|
5164
5864
|
var cerner = defineProfile({
|
|
5165
5865
|
name: "cerner",
|
|
5166
|
-
description: "Cerner Millennium
|
|
5866
|
+
description: "Cerner Millennium: ISO-style timestamps and common Z-segments",
|
|
5167
5867
|
dateFormats: ["YYYY-MM-DDTHH:mm:ss", "YYYY-MM-DD"],
|
|
5168
5868
|
customSegments: {
|
|
5169
5869
|
ZDS: { fields: { summaryText: 3 } },
|
|
@@ -5174,7 +5874,7 @@ var cerner = defineProfile({
|
|
|
5174
5874
|
// src/profiles/epic.ts
|
|
5175
5875
|
var epic = defineProfile({
|
|
5176
5876
|
name: "epic",
|
|
5177
|
-
description: "Epic Bridges Interconnect
|
|
5877
|
+
description: "Epic Bridges Interconnect: ADT-style date formats and common Z-segments",
|
|
5178
5878
|
dateFormats: ["MM/DD/YYYY HH:mm:ss", "MM/DD/YYYY"],
|
|
5179
5879
|
customSegments: {
|
|
5180
5880
|
ZDP: { fields: { departmentCode: 3, departmentName: 4 } },
|
|
@@ -5185,7 +5885,7 @@ var epic = defineProfile({
|
|
|
5185
5885
|
// src/profiles/genericLab.ts
|
|
5186
5886
|
var genericLab = defineProfile({
|
|
5187
5887
|
name: "genericLab",
|
|
5188
|
-
description: "Generic reference-laboratory
|
|
5888
|
+
description: "Generic reference-laboratory: ASTM-era + ISO-date formats + lab Z-segments",
|
|
5189
5889
|
dateFormats: ["YYYYMMDD HHmm", "YYYY-MM-DD"],
|
|
5190
5890
|
customSegments: {
|
|
5191
5891
|
ZLB: { fields: { specimenOverride: 3, methodOverride: 5 } },
|
|
@@ -5196,10 +5896,122 @@ var genericLab = defineProfile({
|
|
|
5196
5896
|
// src/profiles/meditech.ts
|
|
5197
5897
|
var meditech = defineProfile({
|
|
5198
5898
|
name: "meditech",
|
|
5199
|
-
description: "
|
|
5899
|
+
description: "MEDITECH MAGIC/6.x/Expanse: minute-precision timestamps and DFT charge Z-segments (ZF1/ZF2)",
|
|
5200
5900
|
dateFormats: ["YYYYMMDDHHmm"],
|
|
5201
5901
|
customSegments: {
|
|
5202
|
-
|
|
5902
|
+
ZF1: {
|
|
5903
|
+
fields: {
|
|
5904
|
+
providerEncounter: 1,
|
|
5905
|
+
misServiceGroup: 2,
|
|
5906
|
+
serviceGroupCopay: 3,
|
|
5907
|
+
visitCopay: 4,
|
|
5908
|
+
copayMinimum: 5,
|
|
5909
|
+
copayMaximum: 6
|
|
5910
|
+
}
|
|
5911
|
+
},
|
|
5912
|
+
ZF2: {
|
|
5913
|
+
fields: {
|
|
5914
|
+
setId: 1,
|
|
5915
|
+
providerEncounter: 2,
|
|
5916
|
+
encounterDate: 3,
|
|
5917
|
+
encounterProcedure: 4,
|
|
5918
|
+
encounterProcedureQuantity: 5,
|
|
5919
|
+
encounterProcedureCharge: 6,
|
|
5920
|
+
prvProcedureAmountPaid: 7,
|
|
5921
|
+
prvProcedureAmountDue: 8
|
|
5922
|
+
}
|
|
5923
|
+
}
|
|
5924
|
+
}
|
|
5925
|
+
});
|
|
5926
|
+
|
|
5927
|
+
// src/profiles/philips.ts
|
|
5928
|
+
var philips = defineProfile({
|
|
5929
|
+
name: "philips",
|
|
5930
|
+
description: "Philips Vue PACS (IS Link) imaging: ZDS/ZLK/ZAO/ZEB/ZAP/ZAV order/patient/visit-filler Z-segments",
|
|
5931
|
+
customSegments: {
|
|
5932
|
+
// §5.11 ZDS: Order Filler: DICOM Study Instance UID (RP, comp 1).
|
|
5933
|
+
ZDS: { fields: { studyInstanceUid: 1 } },
|
|
5934
|
+
// §5.12 ZLK: Order Filler: study/order linking ids.
|
|
5935
|
+
ZLK: { fields: { externalWorkitemId: 1, orderLinkId: 2 } },
|
|
5936
|
+
// §5.13 ZAO: Order Filler: order additional details (note: no field 7).
|
|
5937
|
+
ZAO: {
|
|
5938
|
+
fields: {
|
|
5939
|
+
orderAdditionalDetails: 1,
|
|
5940
|
+
orderWithNoImages: 2,
|
|
5941
|
+
modality: 3,
|
|
5942
|
+
bodyPart: 4,
|
|
5943
|
+
resultTransferStatus: 5,
|
|
5944
|
+
departmentId: 6,
|
|
5945
|
+
device: 8,
|
|
5946
|
+
section: 9,
|
|
5947
|
+
orderCustomString1: 10,
|
|
5948
|
+
orderCustomString2: 11,
|
|
5949
|
+
orderCustomString3: 12,
|
|
5950
|
+
orderCustomString4: 13,
|
|
5951
|
+
orderCustomString5: 14,
|
|
5952
|
+
orderCustomString6: 15,
|
|
5953
|
+
orderCustomString7: 16,
|
|
5954
|
+
orderCustomNumber1: 17,
|
|
5955
|
+
orderCustomNumber2: 18,
|
|
5956
|
+
orderCustomDate1: 19,
|
|
5957
|
+
orderCustomDate2: 20,
|
|
5958
|
+
orderCustomDate3: 21,
|
|
5959
|
+
technicianFamilyName: 22,
|
|
5960
|
+
technicianGivenName: 23,
|
|
5961
|
+
technicianMiddleName: 24,
|
|
5962
|
+
technicianId: 25,
|
|
5963
|
+
radiologistFamilyName: 26,
|
|
5964
|
+
radiologistGivenName: 27,
|
|
5965
|
+
radiologistMiddleName: 28,
|
|
5966
|
+
radiologistId: 29,
|
|
5967
|
+
orderCreatedBy: 30,
|
|
5968
|
+
orderUpdatedBy: 31,
|
|
5969
|
+
acquisitionStatus: 32
|
|
5970
|
+
}
|
|
5971
|
+
},
|
|
5972
|
+
// §5.14 ZEB: Patient Filler: encrypted patient-info blob (PHI-bearing).
|
|
5973
|
+
ZEB: { fields: { encryptedPatientInfo: 1 } },
|
|
5974
|
+
// §5.15 ZAP: Patient Filler: patient additional details (note: no field 2).
|
|
5975
|
+
ZAP: {
|
|
5976
|
+
fields: {
|
|
5977
|
+
patientAdditionalDetails: 1,
|
|
5978
|
+
patientCustomString1: 3,
|
|
5979
|
+
patientCustomString2: 4,
|
|
5980
|
+
patientCustomString3: 5,
|
|
5981
|
+
patientCustomString4: 6,
|
|
5982
|
+
patientCustomString5: 7,
|
|
5983
|
+
patientCustomString6: 8,
|
|
5984
|
+
patientCustomString7: 9,
|
|
5985
|
+
patientCustomNumber1: 10,
|
|
5986
|
+
patientCustomNumber2: 11,
|
|
5987
|
+
patientCustomDate1: 12,
|
|
5988
|
+
patientCustomDate2: 13,
|
|
5989
|
+
patientCustomDate3: 14
|
|
5990
|
+
}
|
|
5991
|
+
},
|
|
5992
|
+
// §5.16 ZAV: Visit Filler: visit additional details.
|
|
5993
|
+
ZAV: { fields: { visitAdditionalDetails: 1 } }
|
|
5994
|
+
}
|
|
5995
|
+
});
|
|
5996
|
+
|
|
5997
|
+
// src/profiles/va.ts
|
|
5998
|
+
var va = defineProfile({
|
|
5999
|
+
name: "va",
|
|
6000
|
+
description: "VA VistA Radiology/Nuclear Medicine imaging: ZDS DICOM Study Instance UID segment (ORM + ORU, IHE RAD)",
|
|
6001
|
+
customSegments: {
|
|
6002
|
+
// ZDS-1 = Study Instance UID (RP), UID in the first component (1.1 Pointer).
|
|
6003
|
+
// VA Radiology/Nuclear Medicine 5.0 HL7 IS v3.6 (June 2024): "ZDS Segment
|
|
6004
|
+
// Fields in ORU and ORM".
|
|
6005
|
+
ZDS: { fields: { studyInstanceUid: 1 } }
|
|
6006
|
+
}
|
|
6007
|
+
});
|
|
6008
|
+
|
|
6009
|
+
// src/profiles/visage.ts
|
|
6010
|
+
var visage = defineProfile({
|
|
6011
|
+
name: "visage",
|
|
6012
|
+
description: "Visage 7 imaging/PACS: ZDS DICOM Study Instance UID segment (IHE RAD)",
|
|
6013
|
+
customSegments: {
|
|
6014
|
+
ZDS: { fields: { studyInstanceUid: 1 } }
|
|
5203
6015
|
}
|
|
5204
6016
|
});
|
|
5205
6017
|
|
|
@@ -5209,7 +6021,10 @@ var profiles = Object.freeze({
|
|
|
5209
6021
|
cerner,
|
|
5210
6022
|
epic,
|
|
5211
6023
|
genericLab,
|
|
5212
|
-
meditech
|
|
6024
|
+
meditech,
|
|
6025
|
+
philips,
|
|
6026
|
+
va,
|
|
6027
|
+
visage
|
|
5213
6028
|
});
|
|
5214
6029
|
|
|
5215
6030
|
// src/model/coding-system.ts
|
|
@@ -5441,8 +6256,560 @@ function splitBatch(raw, optionsOrProfile) {
|
|
|
5441
6256
|
return Object.freeze(result);
|
|
5442
6257
|
}
|
|
5443
6258
|
|
|
6259
|
+
// src/conformance/types.ts
|
|
6260
|
+
var USAGE_CODES = Object.freeze(["R", "RE", "C", "CE", "O", "X"]);
|
|
6261
|
+
var FINDING_CODES = {
|
|
6262
|
+
/** A Required (`R`) segment or field is absent (or present-but-empty). */
|
|
6263
|
+
PROFILE_REQUIRED_ABSENT: "PROFILE_REQUIRED_ABSENT",
|
|
6264
|
+
/** A Not-permitted (`X`) segment or field is present. */
|
|
6265
|
+
PROFILE_NOT_PERMITTED: "PROFILE_NOT_PERMITTED",
|
|
6266
|
+
/** A segment-occurrence or field-repetition count is outside its cardinality. */
|
|
6267
|
+
PROFILE_CARDINALITY: "PROFILE_CARDINALITY",
|
|
6268
|
+
/** A checked component value exceeds the declared maximum length. */
|
|
6269
|
+
PROFILE_LENGTH: "PROFILE_LENGTH",
|
|
6270
|
+
/** A checked component value is not a member of the consumer-supplied value set. */
|
|
6271
|
+
PROFILE_VALUE_NOT_IN_SET: "PROFILE_VALUE_NOT_IN_SET",
|
|
6272
|
+
/** The profile ITSELF is structurally malformed (a diagnostic, not a message finding). */
|
|
6273
|
+
PROFILE_MALFORMED: "PROFILE_MALFORMED"
|
|
6274
|
+
};
|
|
6275
|
+
|
|
6276
|
+
// src/conformance/findings.ts
|
|
6277
|
+
function describeLocus(locus) {
|
|
6278
|
+
let s = locus.segment;
|
|
6279
|
+
if (locus.occurrence !== void 0 && locus.occurrence > 0) {
|
|
6280
|
+
s += `[occurrence ${String(locus.occurrence)}]`;
|
|
6281
|
+
}
|
|
6282
|
+
if (locus.field !== void 0) {
|
|
6283
|
+
s += `-${String(locus.field)}`;
|
|
6284
|
+
}
|
|
6285
|
+
if (locus.repetition !== void 0) {
|
|
6286
|
+
s += ` rep ${String(locus.repetition)}`;
|
|
6287
|
+
}
|
|
6288
|
+
if (locus.component !== void 0) {
|
|
6289
|
+
s += ` component ${String(locus.component)}`;
|
|
6290
|
+
}
|
|
6291
|
+
return s;
|
|
6292
|
+
}
|
|
6293
|
+
function requiredAbsent(locus, severity) {
|
|
6294
|
+
const kind = locus.field === void 0 ? "segment" : "field";
|
|
6295
|
+
return {
|
|
6296
|
+
code: FINDING_CODES.PROFILE_REQUIRED_ABSENT,
|
|
6297
|
+
severity,
|
|
6298
|
+
locus,
|
|
6299
|
+
message: `Required ${kind} ${describeLocus(locus)} is absent or empty (usage R).`
|
|
6300
|
+
};
|
|
6301
|
+
}
|
|
6302
|
+
function notPermitted(locus, severity) {
|
|
6303
|
+
const kind = locus.field === void 0 ? "segment" : "field";
|
|
6304
|
+
return {
|
|
6305
|
+
code: FINDING_CODES.PROFILE_NOT_PERMITTED,
|
|
6306
|
+
severity,
|
|
6307
|
+
locus,
|
|
6308
|
+
message: `${kind === "segment" ? "Segment" : "Field"} ${describeLocus(
|
|
6309
|
+
locus
|
|
6310
|
+
)} is present but the profile marks it not-permitted (usage X).`
|
|
6311
|
+
};
|
|
6312
|
+
}
|
|
6313
|
+
function cardinality(locus, actual, bound, unit, severity) {
|
|
6314
|
+
const parts = [];
|
|
6315
|
+
if (bound.min !== void 0) parts.push(`min ${String(bound.min)}`);
|
|
6316
|
+
if (bound.max !== void 0) parts.push(`max ${String(bound.max)}`);
|
|
6317
|
+
return {
|
|
6318
|
+
code: FINDING_CODES.PROFILE_CARDINALITY,
|
|
6319
|
+
severity,
|
|
6320
|
+
locus,
|
|
6321
|
+
message: `${describeLocus(locus)} has ${String(actual)} ${unit}(s), outside the declared cardinality (${parts.join(
|
|
6322
|
+
", "
|
|
6323
|
+
)}).`
|
|
6324
|
+
};
|
|
6325
|
+
}
|
|
6326
|
+
function length(locus, actualLength, maxLength, severity) {
|
|
6327
|
+
return {
|
|
6328
|
+
code: FINDING_CODES.PROFILE_LENGTH,
|
|
6329
|
+
severity,
|
|
6330
|
+
locus,
|
|
6331
|
+
message: `${describeLocus(locus)} value length ${String(
|
|
6332
|
+
actualLength
|
|
6333
|
+
)} exceeds the declared maximum of ${String(maxLength)}.`
|
|
6334
|
+
};
|
|
6335
|
+
}
|
|
6336
|
+
function valueNotInSet(locus, valueSetSize, severity) {
|
|
6337
|
+
return {
|
|
6338
|
+
code: FINDING_CODES.PROFILE_VALUE_NOT_IN_SET,
|
|
6339
|
+
severity,
|
|
6340
|
+
locus,
|
|
6341
|
+
message: `${describeLocus(
|
|
6342
|
+
locus
|
|
6343
|
+
)} value is not in the profile value set (${String(valueSetSize)} permitted code(s)).`
|
|
6344
|
+
};
|
|
6345
|
+
}
|
|
6346
|
+
function malformed(locus, detail) {
|
|
6347
|
+
return {
|
|
6348
|
+
code: FINDING_CODES.PROFILE_MALFORMED,
|
|
6349
|
+
severity: "error",
|
|
6350
|
+
locus,
|
|
6351
|
+
message: `Malformed conformance profile: ${detail}`
|
|
6352
|
+
};
|
|
6353
|
+
}
|
|
6354
|
+
|
|
6355
|
+
// src/conformance/profile-shape.ts
|
|
6356
|
+
var SEGMENT_NAME_RE3 = /^[A-Z][A-Z0-9]{2}$/u;
|
|
6357
|
+
var SEVERITIES = ["error", "warning", "info"];
|
|
6358
|
+
function isObject(v) {
|
|
6359
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
6360
|
+
}
|
|
6361
|
+
function checkCardinality(card, locus, out) {
|
|
6362
|
+
if (card === void 0) return;
|
|
6363
|
+
if (!isObject(card)) {
|
|
6364
|
+
out.push({ locus, detail: `${describe(locus)} cardinality must be an object.` });
|
|
6365
|
+
return;
|
|
6366
|
+
}
|
|
6367
|
+
const min = card["min"];
|
|
6368
|
+
const max = card["max"];
|
|
6369
|
+
if (min !== void 0 && (typeof min !== "number" || !Number.isInteger(min) || min < 0)) {
|
|
6370
|
+
out.push({
|
|
6371
|
+
locus,
|
|
6372
|
+
detail: `${describe(locus)} cardinality.min must be a non-negative integer.`
|
|
6373
|
+
});
|
|
6374
|
+
}
|
|
6375
|
+
if (max !== void 0 && max !== "*" && (typeof max !== "number" || !Number.isInteger(max) || max < 0)) {
|
|
6376
|
+
out.push({
|
|
6377
|
+
locus,
|
|
6378
|
+
detail: `${describe(locus)} cardinality.max must be a non-negative integer or "*".`
|
|
6379
|
+
});
|
|
6380
|
+
}
|
|
6381
|
+
if (typeof min === "number" && typeof max === "number" && Number.isInteger(min) && Number.isInteger(max) && max < min) {
|
|
6382
|
+
out.push({
|
|
6383
|
+
locus,
|
|
6384
|
+
detail: `${describe(locus)} cardinality.max (${String(max)}) is less than min (${String(min)}).`
|
|
6385
|
+
});
|
|
6386
|
+
}
|
|
6387
|
+
}
|
|
6388
|
+
function describe(locus) {
|
|
6389
|
+
if (locus.segment === "(profile)") return "profile";
|
|
6390
|
+
let s = `segment "${locus.segment}"`;
|
|
6391
|
+
if (locus.field !== void 0) s += ` field ${String(locus.field)}`;
|
|
6392
|
+
return s;
|
|
6393
|
+
}
|
|
6394
|
+
function collectProfileDefects(profile) {
|
|
6395
|
+
const out = [];
|
|
6396
|
+
const root = { segment: "(profile)" };
|
|
6397
|
+
if (!isObject(profile)) {
|
|
6398
|
+
out.push({ locus: root, detail: "profile must be an object with `name` and `segments`." });
|
|
6399
|
+
return out;
|
|
6400
|
+
}
|
|
6401
|
+
if (typeof profile["name"] !== "string" || profile["name"].trim().length === 0) {
|
|
6402
|
+
out.push({ locus: root, detail: "profile.name must be a non-empty string." });
|
|
6403
|
+
}
|
|
6404
|
+
const segments = profile["segments"];
|
|
6405
|
+
if (!Array.isArray(segments)) {
|
|
6406
|
+
out.push({ locus: root, detail: "profile.segments must be an array of segment rules." });
|
|
6407
|
+
return out;
|
|
6408
|
+
}
|
|
6409
|
+
for (let i = 0; i < segments.length; i++) {
|
|
6410
|
+
const seg = segments[i];
|
|
6411
|
+
if (!isObject(seg)) {
|
|
6412
|
+
out.push({ locus: root, detail: `segments[${String(i)}] must be an object.` });
|
|
6413
|
+
continue;
|
|
6414
|
+
}
|
|
6415
|
+
const name = seg["segment"];
|
|
6416
|
+
if (typeof name !== "string" || !SEGMENT_NAME_RE3.test(name)) {
|
|
6417
|
+
out.push({
|
|
6418
|
+
locus: root,
|
|
6419
|
+
detail: `segments[${String(i)}].segment must be a valid segment name ([A-Z][A-Z0-9]{2}).`
|
|
6420
|
+
});
|
|
6421
|
+
continue;
|
|
6422
|
+
}
|
|
6423
|
+
const segLocus = { segment: name };
|
|
6424
|
+
checkUsage(seg["usage"], segLocus, out);
|
|
6425
|
+
checkSeverity(seg["severity"], segLocus, out);
|
|
6426
|
+
checkCardinality(seg["cardinality"], segLocus, out);
|
|
6427
|
+
const fields = seg["fields"];
|
|
6428
|
+
if (fields !== void 0) {
|
|
6429
|
+
if (!Array.isArray(fields)) {
|
|
6430
|
+
out.push({ locus: segLocus, detail: `segment "${name}" fields must be an array.` });
|
|
6431
|
+
continue;
|
|
6432
|
+
}
|
|
6433
|
+
for (let j = 0; j < fields.length; j++) {
|
|
6434
|
+
checkFieldRule(fields[j], name, j, out);
|
|
6435
|
+
}
|
|
6436
|
+
}
|
|
6437
|
+
}
|
|
6438
|
+
return out;
|
|
6439
|
+
}
|
|
6440
|
+
function checkUsage(usage, locus, out) {
|
|
6441
|
+
if (usage === void 0) return;
|
|
6442
|
+
if (typeof usage !== "string" || !USAGE_CODES.includes(usage)) {
|
|
6443
|
+
out.push({
|
|
6444
|
+
locus,
|
|
6445
|
+
detail: `${describe(locus)} usage must be one of ${USAGE_CODES.join("/")}.`
|
|
6446
|
+
});
|
|
6447
|
+
}
|
|
6448
|
+
}
|
|
6449
|
+
function checkSeverity(sev, locus, out) {
|
|
6450
|
+
if (sev === void 0) return;
|
|
6451
|
+
if (typeof sev !== "string" || !SEVERITIES.includes(sev)) {
|
|
6452
|
+
out.push({
|
|
6453
|
+
locus,
|
|
6454
|
+
detail: `${describe(locus)} severity must be one of ${SEVERITIES.join("/")}.`
|
|
6455
|
+
});
|
|
6456
|
+
}
|
|
6457
|
+
}
|
|
6458
|
+
function checkFieldRule(rule, segment, index, out) {
|
|
6459
|
+
const locus = { segment };
|
|
6460
|
+
if (!isObject(rule)) {
|
|
6461
|
+
out.push({ locus, detail: `segment "${segment}" fields[${String(index)}] must be an object.` });
|
|
6462
|
+
return;
|
|
6463
|
+
}
|
|
6464
|
+
const field = rule["field"];
|
|
6465
|
+
if (typeof field !== "number" || !Number.isInteger(field) || field < 1) {
|
|
6466
|
+
out.push({
|
|
6467
|
+
locus,
|
|
6468
|
+
detail: `segment "${segment}" fields[${String(index)}].field must be a positive (1-indexed) integer.`
|
|
6469
|
+
});
|
|
6470
|
+
return;
|
|
6471
|
+
}
|
|
6472
|
+
const fieldLocus = { segment, field };
|
|
6473
|
+
checkUsage(rule["usage"], fieldLocus, out);
|
|
6474
|
+
checkSeverity(rule["severity"], fieldLocus, out);
|
|
6475
|
+
checkCardinality(rule["cardinality"], fieldLocus, out);
|
|
6476
|
+
const len = rule["length"];
|
|
6477
|
+
if (len !== void 0 && (typeof len !== "number" || !Number.isInteger(len) || len < 0)) {
|
|
6478
|
+
out.push({
|
|
6479
|
+
locus: fieldLocus,
|
|
6480
|
+
detail: `${describe(fieldLocus)} length must be a non-negative integer.`
|
|
6481
|
+
});
|
|
6482
|
+
}
|
|
6483
|
+
const comp = rule["component"];
|
|
6484
|
+
if (comp !== void 0 && (typeof comp !== "number" || !Number.isInteger(comp) || comp < 1)) {
|
|
6485
|
+
out.push({
|
|
6486
|
+
locus: fieldLocus,
|
|
6487
|
+
detail: `${describe(fieldLocus)} component must be a positive (1-indexed) integer.`
|
|
6488
|
+
});
|
|
6489
|
+
}
|
|
6490
|
+
const vs = rule["valueSet"];
|
|
6491
|
+
if (vs !== void 0) {
|
|
6492
|
+
if (!Array.isArray(vs)) {
|
|
6493
|
+
out.push({
|
|
6494
|
+
locus: fieldLocus,
|
|
6495
|
+
detail: `${describe(fieldLocus)} valueSet must be an array of strings.`
|
|
6496
|
+
});
|
|
6497
|
+
} else if (!vs.every((c) => typeof c === "string")) {
|
|
6498
|
+
out.push({
|
|
6499
|
+
locus: fieldLocus,
|
|
6500
|
+
detail: `${describe(fieldLocus)} valueSet entries must all be strings.`
|
|
6501
|
+
});
|
|
6502
|
+
}
|
|
6503
|
+
}
|
|
6504
|
+
}
|
|
6505
|
+
function defineConformanceProfile(profile) {
|
|
6506
|
+
const defects = collectProfileDefects(profile);
|
|
6507
|
+
if (defects.length > 0) {
|
|
6508
|
+
const name = isObject(profile) && typeof profile["name"] === "string" ? profile["name"] : void 0;
|
|
6509
|
+
throw new ProfileDefinitionError(
|
|
6510
|
+
`Conformance profile has ${String(defects.length)} defect(s): ` + defects.map((d) => d.detail).join("; "),
|
|
6511
|
+
name
|
|
6512
|
+
);
|
|
6513
|
+
}
|
|
6514
|
+
return profile;
|
|
6515
|
+
}
|
|
6516
|
+
|
|
6517
|
+
// src/conformance/validate-against-profile.ts
|
|
6518
|
+
function safeName(profile) {
|
|
6519
|
+
if (typeof profile === "object" && profile !== null && !Array.isArray(profile)) {
|
|
6520
|
+
const n = profile["name"];
|
|
6521
|
+
if (typeof n === "string" && n.trim().length > 0) return n;
|
|
6522
|
+
}
|
|
6523
|
+
return "(unnamed profile)";
|
|
6524
|
+
}
|
|
6525
|
+
function componentValue(rep, component) {
|
|
6526
|
+
return rep.components[component - 1]?.subcomponents[0] ?? "";
|
|
6527
|
+
}
|
|
6528
|
+
function fieldHasValue(field) {
|
|
6529
|
+
return field.repetitions.some(
|
|
6530
|
+
(rep) => rep.components.some((comp) => comp.subcomponents.some((sub) => sub !== ""))
|
|
6531
|
+
);
|
|
6532
|
+
}
|
|
6533
|
+
function presenceVerdict(usage, present) {
|
|
6534
|
+
if (usage === "R" && !present) return "required-absent";
|
|
6535
|
+
if (usage === "X" && present) return "not-permitted";
|
|
6536
|
+
return null;
|
|
6537
|
+
}
|
|
6538
|
+
function checkCardinality2(count, card, locus, unit, severity, out) {
|
|
6539
|
+
if (card === void 0) return;
|
|
6540
|
+
const { min, max } = card;
|
|
6541
|
+
if (typeof max === "number" && count > max) {
|
|
6542
|
+
out.push(cardinality(locus, count, card, unit, severity));
|
|
6543
|
+
return;
|
|
6544
|
+
}
|
|
6545
|
+
if (typeof min === "number" && count > 0 && count < min) {
|
|
6546
|
+
out.push(cardinality(locus, count, card, unit, severity));
|
|
6547
|
+
}
|
|
6548
|
+
}
|
|
6549
|
+
function checkFields(seg, occurrence, fields, out) {
|
|
6550
|
+
for (const rule of fields) {
|
|
6551
|
+
const severity = rule.severity ?? "error";
|
|
6552
|
+
const field = seg.field(rule.field);
|
|
6553
|
+
const present = fieldHasValue(field);
|
|
6554
|
+
const baseLocus = { segment: seg.type, field: rule.field, occurrence };
|
|
6555
|
+
const verdict = presenceVerdict(rule.usage, present);
|
|
6556
|
+
if (verdict === "required-absent") {
|
|
6557
|
+
out.push(requiredAbsent(baseLocus, severity));
|
|
6558
|
+
continue;
|
|
6559
|
+
}
|
|
6560
|
+
if (verdict === "not-permitted") {
|
|
6561
|
+
out.push(notPermitted(baseLocus, severity));
|
|
6562
|
+
continue;
|
|
6563
|
+
}
|
|
6564
|
+
if (!present) continue;
|
|
6565
|
+
const reps = field.repetitions;
|
|
6566
|
+
checkCardinality2(reps.length, rule.cardinality, baseLocus, "repetition", severity, out);
|
|
6567
|
+
if (rule.length === void 0 && rule.valueSet === void 0) continue;
|
|
6568
|
+
const component = rule.component ?? 1;
|
|
6569
|
+
for (let r = 0; r < reps.length; r++) {
|
|
6570
|
+
const rep = reps[r];
|
|
6571
|
+
if (rep === void 0) continue;
|
|
6572
|
+
const value = componentValue(rep, component);
|
|
6573
|
+
const locus = {
|
|
6574
|
+
segment: seg.type,
|
|
6575
|
+
field: rule.field,
|
|
6576
|
+
component,
|
|
6577
|
+
repetition: r,
|
|
6578
|
+
occurrence
|
|
6579
|
+
};
|
|
6580
|
+
if (rule.length !== void 0 && value.length > rule.length) {
|
|
6581
|
+
out.push(length(locus, value.length, rule.length, severity));
|
|
6582
|
+
}
|
|
6583
|
+
if (rule.valueSet !== void 0 && value !== "" && !rule.valueSet.includes(value)) {
|
|
6584
|
+
out.push(valueNotInSet(locus, rule.valueSet.length, severity));
|
|
6585
|
+
}
|
|
6586
|
+
}
|
|
6587
|
+
}
|
|
6588
|
+
}
|
|
6589
|
+
function checkSegment(message, rule, out) {
|
|
6590
|
+
const severity = rule.severity ?? "error";
|
|
6591
|
+
const occurrences = message.segments(rule.segment);
|
|
6592
|
+
const count = occurrences.length;
|
|
6593
|
+
const present = count > 0;
|
|
6594
|
+
const segLocus = { segment: rule.segment };
|
|
6595
|
+
const verdict = presenceVerdict(rule.usage, present);
|
|
6596
|
+
if (verdict === "required-absent") {
|
|
6597
|
+
out.push(requiredAbsent(segLocus, severity));
|
|
6598
|
+
return;
|
|
6599
|
+
}
|
|
6600
|
+
if (verdict === "not-permitted") {
|
|
6601
|
+
out.push(notPermitted(segLocus, severity));
|
|
6602
|
+
return;
|
|
6603
|
+
}
|
|
6604
|
+
checkCardinality2(count, rule.cardinality, segLocus, "occurrence", severity, out);
|
|
6605
|
+
if (!present || rule.fields === void 0) return;
|
|
6606
|
+
for (let occ = 0; occ < occurrences.length; occ++) {
|
|
6607
|
+
const seg = occurrences[occ];
|
|
6608
|
+
if (seg === void 0) continue;
|
|
6609
|
+
checkFields(seg, occ, rule.fields, out);
|
|
6610
|
+
}
|
|
6611
|
+
}
|
|
6612
|
+
function validateAgainstProfile(message, profile) {
|
|
6613
|
+
const defects = collectProfileDefects(profile);
|
|
6614
|
+
if (defects.length > 0) {
|
|
6615
|
+
const findings = defects.map((d) => malformed(d.locus, d.detail));
|
|
6616
|
+
return Object.freeze({ profileName: safeName(profile), findings: Object.freeze(findings) });
|
|
6617
|
+
}
|
|
6618
|
+
const out = [];
|
|
6619
|
+
for (const rule of profile.segments) {
|
|
6620
|
+
checkSegment(message, rule, out);
|
|
6621
|
+
}
|
|
6622
|
+
return Object.freeze({ profileName: profile.name, findings: Object.freeze(out) });
|
|
6623
|
+
}
|
|
6624
|
+
|
|
6625
|
+
// src/conformance/index.ts
|
|
6626
|
+
var conformance_exports = {};
|
|
6627
|
+
__export(conformance_exports, {
|
|
6628
|
+
FINDING_CODES: () => FINDING_CODES,
|
|
6629
|
+
USAGE_CODES: () => USAGE_CODES,
|
|
6630
|
+
collectProfileDefects: () => collectProfileDefects,
|
|
6631
|
+
defineConformanceProfile: () => defineConformanceProfile,
|
|
6632
|
+
validateAgainstProfile: () => validateAgainstProfile
|
|
6633
|
+
});
|
|
6634
|
+
function isEnvelopeName2(name) {
|
|
6635
|
+
return name === "FHS" || name === "BHS" || name === "BTS" || name === "FTS";
|
|
6636
|
+
}
|
|
6637
|
+
function decodeChunk(chunk) {
|
|
6638
|
+
if (typeof chunk === "string") return { text: chunk, wasBuffer: false };
|
|
6639
|
+
if (chunk instanceof Uint8Array) {
|
|
6640
|
+
return { text: buffer.Buffer.from(chunk).toString("latin1"), wasBuffer: true };
|
|
6641
|
+
}
|
|
6642
|
+
throw new TypeError(
|
|
6643
|
+
`parseStream: each chunk must be a string, Buffer, or Uint8Array; received ${chunk === null ? "null" : typeof chunk}.`
|
|
6644
|
+
);
|
|
6645
|
+
}
|
|
6646
|
+
async function* iterateChunks(source) {
|
|
6647
|
+
if (source === null || source === void 0) {
|
|
6648
|
+
throw new TypeError("parseStream: source must be an async-iterable or iterable of chunks.");
|
|
6649
|
+
}
|
|
6650
|
+
const asAsync = source[Symbol.asyncIterator];
|
|
6651
|
+
if (typeof asAsync === "function") {
|
|
6652
|
+
for await (const chunk of source) yield chunk;
|
|
6653
|
+
return;
|
|
6654
|
+
}
|
|
6655
|
+
const asSync = source[Symbol.iterator];
|
|
6656
|
+
if (typeof asSync === "function") {
|
|
6657
|
+
for (const chunk of source) yield chunk;
|
|
6658
|
+
return;
|
|
6659
|
+
}
|
|
6660
|
+
throw new TypeError(
|
|
6661
|
+
"parseStream: source must be an async-iterable or iterable of chunks (e.g. a Node Readable)."
|
|
6662
|
+
);
|
|
6663
|
+
}
|
|
6664
|
+
function nextSegment(buf, cursor, final) {
|
|
6665
|
+
if (cursor >= buf.length) return null;
|
|
6666
|
+
const r = buf.indexOf("\r", cursor);
|
|
6667
|
+
const n = buf.indexOf("\n", cursor);
|
|
6668
|
+
let idx;
|
|
6669
|
+
let isCr;
|
|
6670
|
+
if (r === -1 && n === -1) return null;
|
|
6671
|
+
else if (r === -1) {
|
|
6672
|
+
idx = n;
|
|
6673
|
+
isCr = false;
|
|
6674
|
+
} else if (n === -1) {
|
|
6675
|
+
idx = r;
|
|
6676
|
+
isCr = true;
|
|
6677
|
+
} else if (r < n) {
|
|
6678
|
+
idx = r;
|
|
6679
|
+
isCr = true;
|
|
6680
|
+
} else {
|
|
6681
|
+
idx = n;
|
|
6682
|
+
isCr = false;
|
|
6683
|
+
}
|
|
6684
|
+
if (isCr && idx === buf.length - 1 && !final) return null;
|
|
6685
|
+
let termLen = 1;
|
|
6686
|
+
if (isCr && idx + 1 < buf.length && buf.charCodeAt(idx + 1) === 10) termLen = 2;
|
|
6687
|
+
return { seg: buf.slice(cursor, idx), next: idx + termLen };
|
|
6688
|
+
}
|
|
6689
|
+
function parseStreamEntry(rawMessage, position, wasBuffer, optionsOrProfile, streamWarnings) {
|
|
6690
|
+
const input = wasBuffer ? buffer.Buffer.from(rawMessage, "latin1") : rawMessage;
|
|
6691
|
+
try {
|
|
6692
|
+
const message = optionsOrProfile === void 0 ? parseHL7(input) : parseHL7(input, optionsOrProfile);
|
|
6693
|
+
return Object.freeze({ ok: true, message, raw: rawMessage, position, streamWarnings });
|
|
6694
|
+
} catch (error) {
|
|
6695
|
+
if (error instanceof Hl7ParseError) {
|
|
6696
|
+
return Object.freeze({ ok: false, error, raw: rawMessage, position, streamWarnings });
|
|
6697
|
+
}
|
|
6698
|
+
throw error;
|
|
6699
|
+
}
|
|
6700
|
+
}
|
|
6701
|
+
async function* parseStream(source, optionsOrProfile) {
|
|
6702
|
+
if (typeof source === "string" || source instanceof Uint8Array) {
|
|
6703
|
+
throw new TypeError(
|
|
6704
|
+
"parseStream expects a stream of chunks (a Node Readable, an async-iterable, or an iterable of string/Buffer chunks), not a single string or Buffer. For one message use parseHL7; to stream a whole buffer, wrap it: parseStream([buffer])."
|
|
6705
|
+
);
|
|
6706
|
+
}
|
|
6707
|
+
let pending = "";
|
|
6708
|
+
let currentLines = [];
|
|
6709
|
+
let messageStartIndex = -1;
|
|
6710
|
+
let globalSegmentIndex = 0;
|
|
6711
|
+
let wasBuffer = false;
|
|
6712
|
+
let firstChunkSeen = false;
|
|
6713
|
+
let bomStripped = false;
|
|
6714
|
+
const flush = (finalUnterminated2) => {
|
|
6715
|
+
while (currentLines.length > 0 && currentLines[currentLines.length - 1] === "") {
|
|
6716
|
+
currentLines.pop();
|
|
6717
|
+
}
|
|
6718
|
+
if (currentLines.length === 0) {
|
|
6719
|
+
messageStartIndex = -1;
|
|
6720
|
+
return null;
|
|
6721
|
+
}
|
|
6722
|
+
const position = Object.freeze({ segmentIndex: messageStartIndex });
|
|
6723
|
+
const streamWarnings = finalUnterminated2 ? Object.freeze([unterminatedStreamMessage(position)]) : Object.freeze([]);
|
|
6724
|
+
const entry = parseStreamEntry(
|
|
6725
|
+
currentLines.join("\r"),
|
|
6726
|
+
position,
|
|
6727
|
+
wasBuffer,
|
|
6728
|
+
optionsOrProfile,
|
|
6729
|
+
streamWarnings
|
|
6730
|
+
);
|
|
6731
|
+
currentLines = [];
|
|
6732
|
+
messageStartIndex = -1;
|
|
6733
|
+
return entry;
|
|
6734
|
+
};
|
|
6735
|
+
const processSegment = (seg) => {
|
|
6736
|
+
const thisIndex = globalSegmentIndex;
|
|
6737
|
+
globalSegmentIndex += 1;
|
|
6738
|
+
const name = seg.slice(0, 3);
|
|
6739
|
+
if (isEnvelopeName2(name)) {
|
|
6740
|
+
return flush(false);
|
|
6741
|
+
}
|
|
6742
|
+
if (name === "MSH") {
|
|
6743
|
+
const flushed = flush(false);
|
|
6744
|
+
currentLines = [seg];
|
|
6745
|
+
messageStartIndex = thisIndex;
|
|
6746
|
+
return flushed;
|
|
6747
|
+
}
|
|
6748
|
+
if (currentLines.length === 0) {
|
|
6749
|
+
if (seg.trim() === "") return null;
|
|
6750
|
+
messageStartIndex = thisIndex;
|
|
6751
|
+
}
|
|
6752
|
+
currentLines.push(seg);
|
|
6753
|
+
return null;
|
|
6754
|
+
};
|
|
6755
|
+
const tryStripBom = (final) => {
|
|
6756
|
+
if (bomStripped || pending.length === 0) return;
|
|
6757
|
+
if (pending.charCodeAt(0) === 65279) {
|
|
6758
|
+
pending = pending.slice(1);
|
|
6759
|
+
bomStripped = true;
|
|
6760
|
+
} else if (pending.startsWith("\xEF\xBB\xBF")) {
|
|
6761
|
+
pending = pending.slice(3);
|
|
6762
|
+
bomStripped = true;
|
|
6763
|
+
} else if (final || !"\xEF\xBB\xBF".startsWith(pending)) {
|
|
6764
|
+
bomStripped = true;
|
|
6765
|
+
}
|
|
6766
|
+
};
|
|
6767
|
+
for await (const rawChunk of iterateChunks(source)) {
|
|
6768
|
+
const { text, wasBuffer: chunkWasBuffer } = decodeChunk(rawChunk);
|
|
6769
|
+
if (!firstChunkSeen) {
|
|
6770
|
+
wasBuffer = chunkWasBuffer;
|
|
6771
|
+
firstChunkSeen = true;
|
|
6772
|
+
}
|
|
6773
|
+
if (text.length === 0) continue;
|
|
6774
|
+
pending += text;
|
|
6775
|
+
tryStripBom(false);
|
|
6776
|
+
if (!bomStripped) continue;
|
|
6777
|
+
let cursor = 0;
|
|
6778
|
+
for (; ; ) {
|
|
6779
|
+
const res = nextSegment(pending, cursor, false);
|
|
6780
|
+
if (res === null) break;
|
|
6781
|
+
cursor = res.next;
|
|
6782
|
+
const entry = processSegment(res.seg);
|
|
6783
|
+
if (entry !== null) yield entry;
|
|
6784
|
+
}
|
|
6785
|
+
pending = pending.slice(cursor);
|
|
6786
|
+
}
|
|
6787
|
+
tryStripBom(true);
|
|
6788
|
+
{
|
|
6789
|
+
let cursor = 0;
|
|
6790
|
+
for (; ; ) {
|
|
6791
|
+
const res = nextSegment(pending, cursor, true);
|
|
6792
|
+
if (res === null) break;
|
|
6793
|
+
cursor = res.next;
|
|
6794
|
+
const entry = processSegment(res.seg);
|
|
6795
|
+
if (entry !== null) yield entry;
|
|
6796
|
+
}
|
|
6797
|
+
pending = pending.slice(cursor);
|
|
6798
|
+
}
|
|
6799
|
+
const tail = pending;
|
|
6800
|
+
pending = "";
|
|
6801
|
+
let finalUnterminated = false;
|
|
6802
|
+
if (tail.length > 0) {
|
|
6803
|
+
const flushed = processSegment(tail);
|
|
6804
|
+
if (flushed !== null) yield flushed;
|
|
6805
|
+
finalUnterminated = currentLines.length > 0 && tail.trim() !== "";
|
|
6806
|
+
}
|
|
6807
|
+
const finalEntry = flush(finalUnterminated);
|
|
6808
|
+
if (finalEntry !== null) yield finalEntry;
|
|
6809
|
+
}
|
|
6810
|
+
|
|
5444
6811
|
// src/index.ts
|
|
5445
|
-
var VERSION = "0.0.
|
|
6812
|
+
var VERSION = "0.0.2";
|
|
5446
6813
|
|
|
5447
6814
|
exports.ACK_CODES = ACK_CODES;
|
|
5448
6815
|
exports.ACK_CONDITIONS = ACK_CONDITIONS;
|
|
@@ -5452,6 +6819,7 @@ exports.ERR_CONDITION_CODES = ERR_CONDITION_CODES;
|
|
|
5452
6819
|
exports.ERR_CONDITION_CODE_SYSTEM = ERR_CONDITION_CODE_SYSTEM;
|
|
5453
6820
|
exports.ERR_SEVERITIES = ERR_SEVERITIES;
|
|
5454
6821
|
exports.FATAL_CODES = FATAL_CODES;
|
|
6822
|
+
exports.FINDING_CODES = FINDING_CODES;
|
|
5455
6823
|
exports.Field = Field;
|
|
5456
6824
|
exports.HL7 = namespace_exports;
|
|
5457
6825
|
exports.Hl7Message = Hl7Message;
|
|
@@ -5462,6 +6830,7 @@ exports.MESSAGE_STRUCTURE_DEFINITIONS = MESSAGE_STRUCTURE_DEFINITIONS;
|
|
|
5462
6830
|
exports.ProfileDefinitionError = ProfileDefinitionError;
|
|
5463
6831
|
exports.SUPPORTED_DATE_TOKENS = SUPPORTED_DATE_TOKENS;
|
|
5464
6832
|
exports.Segment = Segment;
|
|
6833
|
+
exports.USAGE_CODES = USAGE_CODES;
|
|
5465
6834
|
exports.VERSION = VERSION;
|
|
5466
6835
|
exports.WARNING_CODES = WARNING_CODES;
|
|
5467
6836
|
exports.ackNoCorrelationId = ackNoCorrelationId;
|
|
@@ -5470,15 +6839,34 @@ exports.analyzeMessageStructure = analyzeMessageStructure;
|
|
|
5470
6839
|
exports.batchCountMismatch = batchCountMismatch;
|
|
5471
6840
|
exports.batchMissingTrailer = batchMissingTrailer;
|
|
5472
6841
|
exports.buildAck = buildAck;
|
|
6842
|
+
exports.buildAdt = buildAdt;
|
|
5473
6843
|
exports.buildMessage = buildMessage;
|
|
6844
|
+
exports.buildOru = buildOru;
|
|
5474
6845
|
exports.canonicalCharset = canonicalCharset;
|
|
5475
6846
|
exports.codingSystem = codingSystem;
|
|
5476
6847
|
exports.codingSystemOf = codingSystemOf;
|
|
6848
|
+
exports.conformance = conformance_exports;
|
|
6849
|
+
exports.decodeText = decodeText;
|
|
6850
|
+
exports.defineConformanceProfile = defineConformanceProfile;
|
|
5477
6851
|
exports.defineProfile = defineProfile;
|
|
5478
6852
|
exports.detectAckMode = detectAckMode;
|
|
5479
6853
|
exports.downgradePositiveAck = downgradePositiveAck;
|
|
5480
6854
|
exports.dtmToDate = dtmToDate;
|
|
5481
6855
|
exports.duplicateRequiredSegment = duplicateRequiredSegment;
|
|
6856
|
+
exports.encodeCe = encodeCe;
|
|
6857
|
+
exports.encodeComposite = encodeComposite;
|
|
6858
|
+
exports.encodeCompositeReps = encodeCompositeReps;
|
|
6859
|
+
exports.encodeCwe = encodeCwe;
|
|
6860
|
+
exports.encodeCx = encodeCx;
|
|
6861
|
+
exports.encodeHd = encodeHd;
|
|
6862
|
+
exports.encodeNm = encodeNm;
|
|
6863
|
+
exports.encodePl = encodePl;
|
|
6864
|
+
exports.encodeText = encodeText;
|
|
6865
|
+
exports.encodeTs = encodeTs;
|
|
6866
|
+
exports.encodeXad = encodeXad;
|
|
6867
|
+
exports.encodeXcn = encodeXcn;
|
|
6868
|
+
exports.encodeXpn = encodeXpn;
|
|
6869
|
+
exports.encodeXtn = encodeXtn;
|
|
5482
6870
|
exports.encodingMismatch = encodingMismatch;
|
|
5483
6871
|
exports.extraFields = extraFields;
|
|
5484
6872
|
exports.fieldWhitespaceTrimmed = fieldWhitespaceTrimmed;
|
|
@@ -5501,6 +6889,7 @@ exports.parseNm = parseNm;
|
|
|
5501
6889
|
exports.parsePath = parsePath;
|
|
5502
6890
|
exports.parsePl = parsePl;
|
|
5503
6891
|
exports.parseSn = parseSn;
|
|
6892
|
+
exports.parseStream = parseStream;
|
|
5504
6893
|
exports.parseTs = parseTs;
|
|
5505
6894
|
exports.parseXad = parseXad;
|
|
5506
6895
|
exports.parseXcn = parseXcn;
|
|
@@ -5509,11 +6898,13 @@ exports.parseXtn = parseXtn;
|
|
|
5509
6898
|
exports.pickMrn = pickMrn;
|
|
5510
6899
|
exports.profiles = profiles;
|
|
5511
6900
|
exports.reescape = reescape;
|
|
6901
|
+
exports.renderText = renderText;
|
|
5512
6902
|
exports.resolveCharset = resolveCharset;
|
|
5513
6903
|
exports.resolvePath = resolvePath;
|
|
5514
6904
|
exports.segmentCase = segmentCase;
|
|
5515
6905
|
exports.setDefaultProfile = setDefaultProfile;
|
|
5516
6906
|
exports.splitBatch = splitBatch;
|
|
6907
|
+
exports.text = text_exports;
|
|
5517
6908
|
exports.timestampFallbackFormat = timestampFallbackFormat;
|
|
5518
6909
|
exports.unescape = unescape;
|
|
5519
6910
|
exports.unknownCharset = unknownCharset;
|
|
@@ -5521,6 +6912,8 @@ exports.unknownEscapeSequence = unknownEscapeSequence;
|
|
|
5521
6912
|
exports.unknownSegment = unknownSegment;
|
|
5522
6913
|
exports.unsupportedCharset = unsupportedCharset;
|
|
5523
6914
|
exports.unterminatedEscapeSequence = unterminatedEscapeSequence;
|
|
6915
|
+
exports.unterminatedStreamMessage = unterminatedStreamMessage;
|
|
6916
|
+
exports.validateAgainstProfile = validateAgainstProfile;
|
|
5524
6917
|
exports.versionMismatch = versionMismatch;
|
|
5525
6918
|
//# sourceMappingURL=index.cjs.map
|
|
5526
6919
|
//# sourceMappingURL=index.cjs.map
|