@cosyte/astm 0.0.3 → 0.0.5
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 +503 -67
- package/README.md +53 -21
- package/dist/index.cjs +301 -68
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +883 -434
- package/dist/index.d.ts +883 -434
- package/dist/index.mjs +296 -69
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -2
package/dist/index.d.ts
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* record layer.
|
|
4
4
|
*
|
|
5
5
|
* **PHI discipline (the whole point).** A position identifies *where* a
|
|
6
|
-
* deviation occurred
|
|
7
|
-
* 1-based field / component indices
|
|
6
|
+
* deviation occurred (the record's ordinal index, its type letter, and the
|
|
7
|
+
* 1-based field / component indices) and **never** carries a field value. A
|
|
8
8
|
* warning or error may be logged verbatim without leaking a patient name, an
|
|
9
9
|
* identifier, or a result value.
|
|
10
10
|
*/
|
|
@@ -51,21 +51,21 @@ interface AstmPosition {
|
|
|
51
51
|
* ```
|
|
52
52
|
*/
|
|
53
53
|
declare const WARNING_CODES: {
|
|
54
|
-
/** A record's type letter is not one of the modeled types
|
|
54
|
+
/** A record's type letter is not one of the modeled types: surfaced as an unsupported record. */
|
|
55
55
|
readonly ASTM_RECORD_UNKNOWN_TYPE: "ASTM_RECORD_UNKNOWN_TYPE";
|
|
56
|
-
/** The header declared delimiters other than the canonical `H
|
|
56
|
+
/** The header declared delimiters other than the canonical `H|\^&`: tolerated, noted. */
|
|
57
57
|
readonly ASTM_NONSTANDARD_DELIMITERS: "ASTM_NONSTANDARD_DELIMITERS";
|
|
58
|
-
/** An escape sequence body was not one of `&F&`/`&S&`/`&R&`/`&E
|
|
58
|
+
/** An escape sequence body was not one of `&F&`/`&S&`/`&R&`/`&E&`: preserved verbatim. */
|
|
59
59
|
readonly ASTM_UNKNOWN_ESCAPE_SEQUENCE: "ASTM_UNKNOWN_ESCAPE_SEQUENCE";
|
|
60
60
|
/**
|
|
61
61
|
* A result value field carried an *unescaped* component delimiter, so it split into more than one
|
|
62
|
-
* component. Both the full raw value and the split are surfaced and this warning fires
|
|
62
|
+
* component. Both the full raw value and the split are surfaced and this warning fires: the
|
|
63
63
|
* ambiguity is never resolved silently into a truncated value.
|
|
64
64
|
*/
|
|
65
65
|
readonly ASTM_RECORD_AMBIGUOUS_VALUE_SPLIT: "ASTM_RECORD_AMBIGUOUS_VALUE_SPLIT";
|
|
66
66
|
/**
|
|
67
67
|
* A result's abnormal-flag field (`R` field 7) carried a letter outside HL7 Table 0078. The flag is
|
|
68
|
-
* surfaced as `undefined
|
|
68
|
+
* surfaced as `undefined`: never dropped, and **never coerced to `normal`** (a clinical error).
|
|
69
69
|
*/
|
|
70
70
|
readonly ASTM_RECORD_UNDEFINED_ABNORMAL_FLAG: "ASTM_RECORD_UNDEFINED_ABNORMAL_FLAG";
|
|
71
71
|
/**
|
|
@@ -75,7 +75,7 @@ declare const WARNING_CODES: {
|
|
|
75
75
|
readonly ASTM_RECORD_UNDEFINED_RESULT_STATUS: "ASTM_RECORD_UNDEFINED_RESULT_STATUS";
|
|
76
76
|
/**
|
|
77
77
|
* A result's reference-range field (`R` field 6) did not match a recognized form (`low-high`,
|
|
78
|
-
* `<high`, `>low`). The text is surfaced verbatim as `unparsed
|
|
78
|
+
* `<high`, `>low`). The text is surfaced verbatim as `unparsed`: a bound is **never fabricated**.
|
|
79
79
|
*/
|
|
80
80
|
readonly ASTM_RECORD_UNPARSEABLE_REFERENCE_RANGE: "ASTM_RECORD_UNPARSEABLE_REFERENCE_RANGE";
|
|
81
81
|
/**
|
|
@@ -84,35 +84,57 @@ declare const WARNING_CODES: {
|
|
|
84
84
|
*/
|
|
85
85
|
readonly ASTM_RECORD_UNITS_ABSENT: "ASTM_RECORD_UNITS_ABSENT";
|
|
86
86
|
/**
|
|
87
|
-
* A `C` (comment) record had no valid preceding `H`/`P`/`O`/`R` parent
|
|
87
|
+
* A `C` (comment) record had no valid preceding `H`/`P`/`O`/`R` parent: an **orphan**. The comment
|
|
88
88
|
* is attached to the message root (`attachedToRoot: true`) and surfaced, **never dropped**.
|
|
89
89
|
*/
|
|
90
90
|
readonly ASTM_RECORD_ORPHAN_COMMENT: "ASTM_RECORD_ORPHAN_COMMENT";
|
|
91
91
|
/**
|
|
92
92
|
* A `YYYYMMDDHHMMSS` timestamp had an odd digit run that truncates a two-digit component in half
|
|
93
93
|
* (e.g. a partial day/hour). The raw run is preserved and the structured value stops at the last
|
|
94
|
-
* **complete** component
|
|
94
|
+
* **complete** component: the dangling digit is **never zero-filled into a fabricated time**.
|
|
95
95
|
*/
|
|
96
96
|
readonly ASTM_RECORD_PARTIAL_TIMESTAMP: "ASTM_RECORD_PARTIAL_TIMESTAMP";
|
|
97
97
|
/**
|
|
98
98
|
* A `Q` (request-information) record carried a request-information status code (field 13). The code
|
|
99
99
|
* *set* is `[OSS-derived / paywalled]` with no publicly-groundable enumeration, so the parser
|
|
100
100
|
* interprets **none** of them: the status is surfaced verbatim and this value-free warning flags that
|
|
101
|
-
* it was passed through **uninterpreted
|
|
101
|
+
* it was passed through **uninterpreted**, never mapped to a guessed meaning.
|
|
102
102
|
*/
|
|
103
103
|
readonly ASTM_RECORD_UNINTERPRETED_QUERY_STATUS: "ASTM_RECORD_UNINTERPRETED_QUERY_STATUS";
|
|
104
104
|
/**
|
|
105
|
-
* A message carried **both** a `Q` (request) and an `R` (result) record
|
|
105
|
+
* A message carried **both** a `Q` (request) and an `R` (result) record: a contradictory shape. The
|
|
106
106
|
* message is classified `host-query` (the `Q` **dominates**, so it is never read as a result set) and
|
|
107
107
|
* this warning flags the anomaly. Positional context only; no field value.
|
|
108
108
|
*/
|
|
109
109
|
readonly ASTM_RECORD_AMBIGUOUS_MESSAGE_KIND: "ASTM_RECORD_AMBIGUOUS_MESSAGE_KIND";
|
|
110
|
+
/**
|
|
111
|
+
* A later `H` (header) record declared a **different** delimiter set from the one in force, and the
|
|
112
|
+
* parser **followed it**: that header and every record after it are read with the newly-declared
|
|
113
|
+
* delimiters, until the next `H`. Records already read keep the set that was in force when they were
|
|
114
|
+
* read: a redeclaration never reinterprets bytes that have already been consumed.
|
|
115
|
+
*
|
|
116
|
+
* A stream carrying several messages back to back is ordinary, and a header that simply **repeats**
|
|
117
|
+
* the delimiters already in force is a no-op that warns nothing. This code fires only when the set
|
|
118
|
+
* actually changes, because that is the point at which a reader still using the old set would begin
|
|
119
|
+
* merging fields together.
|
|
120
|
+
*/
|
|
121
|
+
readonly ASTM_RECORD_DELIMITERS_REDECLARED: "ASTM_RECORD_DELIMITERS_REDECLARED";
|
|
122
|
+
/**
|
|
123
|
+
* A later `H` (header) record could not declare a usable delimiter set: it was too short, or the
|
|
124
|
+
* field separator it named also appeared among the other three, leaving the four roles
|
|
125
|
+
* indistinguishable. The delimiters **already in force are kept** and every record is still surfaced;
|
|
126
|
+
* a set is never guessed and no record is dropped.
|
|
127
|
+
*
|
|
128
|
+
* The same condition on the *first* header is unrecoverable and remains the
|
|
129
|
+
* `ASTM_RECORD_UNDECLARED_DELIMITERS` fatal: there is no earlier set to fall back to.
|
|
130
|
+
*/
|
|
131
|
+
readonly ASTM_RECORD_UNREADABLE_REDECLARATION: "ASTM_RECORD_UNREADABLE_REDECLARATION";
|
|
110
132
|
/**
|
|
111
133
|
* The downgraded form an active vendor {@link AstmProfile} produces from a deviation it *expects*
|
|
112
134
|
* (see `src/profiles/`). The original warning is **never dropped**: its code moves to
|
|
113
135
|
* {@link AstmRecordWarning.toleratedCode}, the warning is re-badged `PROFILE_QUIRK_APPLIED` with
|
|
114
136
|
* `expected: true` and the tolerating profile named, so a consumer can filter known, grounded noise
|
|
115
|
-
* while the fact of the deviation
|
|
137
|
+
* while the fact of the deviation, and where it was, survives. A profile can only ever reach this
|
|
116
138
|
* path for a **non-safety-critical** code (enforced at profile-definition time); a safety-critical
|
|
117
139
|
* deviation (a result value, flag, status, patient identifier, code system, or a frame-integrity
|
|
118
140
|
* warning) can **never** be tolerated, so it can never be re-badged here.
|
|
@@ -148,14 +170,14 @@ interface AstmRecordWarning {
|
|
|
148
170
|
* `true` when an active vendor {@link AstmProfile} *expected* this deviation and re-badged it as a
|
|
149
171
|
* {@link WARNING_CODES.PROFILE_QUIRK_APPLIED}. An `expected` warning does **not** escalate to a
|
|
150
172
|
* thrown `AstmStrictError` in strict mode (the whole point of the profile is that this deviation is
|
|
151
|
-
* known and benign)
|
|
173
|
+
* known and benign): it is still recorded, so nothing is hidden. Absent on an untolerated warning.
|
|
152
174
|
*/
|
|
153
175
|
readonly expected?: boolean;
|
|
154
176
|
/** The name of the {@link AstmProfile} that tolerated this warning, when `expected`. */
|
|
155
177
|
readonly profile?: string;
|
|
156
178
|
/**
|
|
157
179
|
* When `code` is {@link WARNING_CODES.PROFILE_QUIRK_APPLIED}, the original warning code the profile
|
|
158
|
-
* tolerated
|
|
180
|
+
* tolerated, so a consumer can still see *which* deviation was re-badged as expected.
|
|
159
181
|
*/
|
|
160
182
|
readonly toleratedCode?: WarningCode;
|
|
161
183
|
}
|
|
@@ -181,6 +203,31 @@ declare function unknownRecordType(position: AstmPosition): AstmRecordWarning;
|
|
|
181
203
|
* ```
|
|
182
204
|
*/
|
|
183
205
|
declare function nonStandardDelimiters(position: AstmPosition): AstmRecordWarning;
|
|
206
|
+
/**
|
|
207
|
+
* Build an `ASTM_RECORD_DELIMITERS_REDECLARED` warning. Emitted when a later `H`
|
|
208
|
+
* record declares a delimiter set different from the one in force; the new set is
|
|
209
|
+
* honored from that header onward. Positional context only: never the delimiters
|
|
210
|
+
* themselves.
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* ```ts
|
|
214
|
+
* import { delimitersRedeclared } from "@cosyte/astm";
|
|
215
|
+
* delimitersRedeclared({ recordIndex: 5, recordType: "H" });
|
|
216
|
+
* ```
|
|
217
|
+
*/
|
|
218
|
+
declare function delimitersRedeclared(position: AstmPosition): AstmRecordWarning;
|
|
219
|
+
/**
|
|
220
|
+
* Build an `ASTM_RECORD_UNREADABLE_REDECLARATION` warning. Emitted when a later
|
|
221
|
+
* `H` record cannot declare a usable delimiter set; the set already in force is
|
|
222
|
+
* kept and every record is still surfaced.
|
|
223
|
+
*
|
|
224
|
+
* @example
|
|
225
|
+
* ```ts
|
|
226
|
+
* import { unreadableRedeclaration } from "@cosyte/astm";
|
|
227
|
+
* unreadableRedeclaration({ recordIndex: 2, recordType: "H" });
|
|
228
|
+
* ```
|
|
229
|
+
*/
|
|
230
|
+
declare function unreadableRedeclaration(position: AstmPosition): AstmRecordWarning;
|
|
184
231
|
/**
|
|
185
232
|
* Build an `ASTM_UNKNOWN_ESCAPE_SEQUENCE` warning. The sequence is preserved
|
|
186
233
|
* verbatim in the decoded value; the warning body carries neither the sequence
|
|
@@ -195,7 +242,7 @@ declare function nonStandardDelimiters(position: AstmPosition): AstmRecordWarnin
|
|
|
195
242
|
declare function unknownEscapeSequence(position: AstmPosition): AstmRecordWarning;
|
|
196
243
|
/**
|
|
197
244
|
* Build an `ASTM_RECORD_AMBIGUOUS_VALUE_SPLIT` warning. Emitted when a result
|
|
198
|
-
* value field split on an unescaped component delimiter
|
|
245
|
+
* value field split on an unescaped component delimiter: the full raw value and
|
|
199
246
|
* the split are both surfaced, never a silent truncation.
|
|
200
247
|
*
|
|
201
248
|
* @example
|
|
@@ -299,7 +346,7 @@ declare function uninterpretedQueryStatus(position: AstmPosition): AstmRecordWar
|
|
|
299
346
|
*/
|
|
300
347
|
declare function ambiguousMessageKind(position: AstmPosition): AstmRecordWarning;
|
|
301
348
|
/**
|
|
302
|
-
* Build a `PROFILE_QUIRK_APPLIED` warning
|
|
349
|
+
* Build a `PROFILE_QUIRK_APPLIED` warning: the downgraded form an active vendor profile produces from
|
|
303
350
|
* a deviation it *expects*. The original warning is **not dropped**: its `code` moves to
|
|
304
351
|
* `toleratedCode`, the warning is re-badged `PROFILE_QUIRK_APPLIED`, `expected` is set, and the
|
|
305
352
|
* tolerating profile is named. The original `position` and `message` are preserved (both PHI-free by
|
|
@@ -326,7 +373,7 @@ declare function profileQuirkApplied(original: AstmRecordWarning, profileName: s
|
|
|
326
373
|
*
|
|
327
374
|
* ASTM/CLSI-LIS02 messages are **self-describing**: every `H` (header) record
|
|
328
375
|
* declares the four delimiters it uses, in the bytes immediately after the `H`.
|
|
329
|
-
* A parser MUST read them from the record and MUST NOT hardcode them
|
|
376
|
+
* A parser MUST read them from the record and MUST NOT hardcode them: a vendor
|
|
330
377
|
* is free to declare non-standard delimiters and a conformant reader follows.
|
|
331
378
|
*
|
|
332
379
|
* The canonical declaration is `H|\^&`:
|
|
@@ -358,18 +405,18 @@ declare function profileQuirkApplied(original: AstmRecordWarning, profileName: s
|
|
|
358
405
|
* ```
|
|
359
406
|
*/
|
|
360
407
|
interface Delimiters {
|
|
361
|
-
/** Field separator
|
|
408
|
+
/** Field separator: the char immediately after `H` in the header. */
|
|
362
409
|
readonly field: string;
|
|
363
|
-
/** Repeat (repetition) separator
|
|
410
|
+
/** Repeat (repetition) separator: ASTM `\` by default. */
|
|
364
411
|
readonly repeat: string;
|
|
365
|
-
/** Component separator
|
|
412
|
+
/** Component separator: ASTM `^` by default. */
|
|
366
413
|
readonly component: string;
|
|
367
|
-
/** Escape character
|
|
414
|
+
/** Escape character: ASTM `&` by default (introduces `&F&`/`&S&`/`&R&`/`&E&`). */
|
|
368
415
|
readonly escape: string;
|
|
369
416
|
}
|
|
370
417
|
/**
|
|
371
418
|
* The canonical ASTM delimiter set (`H|\^&`). Used only as documentation and as
|
|
372
|
-
* a comparison baseline for the non-standard-delimiter warning
|
|
419
|
+
* a comparison baseline for the non-standard-delimiter warning: never as a
|
|
373
420
|
* parse-time default (delimiters are always read from the header).
|
|
374
421
|
*
|
|
375
422
|
* @example
|
|
@@ -383,10 +430,18 @@ declare const CANONICAL_DELIMITERS: Delimiters;
|
|
|
383
430
|
* Read the four delimiters from a header record's raw text (a single `H`
|
|
384
431
|
* record, its terminator already stripped).
|
|
385
432
|
*
|
|
386
|
-
* Returns `
|
|
387
|
-
*
|
|
388
|
-
*
|
|
389
|
-
*
|
|
433
|
+
* Returns `undefined` when the record cannot declare all four delimiters: it is
|
|
434
|
+
* shorter than `H` + a field separator + a 3-char delimiter definition, or the
|
|
435
|
+
* field separator it names also appears among the other three.
|
|
436
|
+
*
|
|
437
|
+
* What the caller does with that depends on **which** header it is. On the first
|
|
438
|
+
* header it is the `ASTM_RECORD_UNDECLARED_DELIMITERS` fatal, because there is no
|
|
439
|
+
* earlier set to fall back to. On any later header (a stream may carry several
|
|
440
|
+
* messages, each declaring its own set) the delimiters already in force are kept
|
|
441
|
+
* and an `ASTM_RECORD_UNREADABLE_REDECLARATION` warning is raised instead; a set is
|
|
442
|
+
* never guessed and no record is dropped.
|
|
443
|
+
*
|
|
444
|
+
* This function does not throw: delimiter resolution and the escalation decision are
|
|
390
445
|
* kept separate so the reader stays pure and testable.
|
|
391
446
|
*
|
|
392
447
|
* @param headerRecord - The raw `H` record text (no trailing CR/LF).
|
|
@@ -413,12 +468,12 @@ declare function readDelimiters(headerRecord: string): Delimiters | undefined;
|
|
|
413
468
|
declare function isNonStandard(d: Delimiters): boolean;
|
|
414
469
|
|
|
415
470
|
/**
|
|
416
|
-
* The ASTM date/time value
|
|
471
|
+
* The ASTM date/time value: `YYYYMMDDHHMMSS`, precision-preserving, no timezone.
|
|
417
472
|
*
|
|
418
473
|
* ASTM timestamps are a run of digits, most-significant first, truncated at
|
|
419
474
|
* whatever precision the instrument reports. There is **no timezone field**:
|
|
420
475
|
* the value is local to the instrument and this parser never assumes UTC. A
|
|
421
|
-
* date-only value (`YYYYMMDD`) is a normal, complete value at day precision
|
|
476
|
+
* date-only value (`YYYYMMDD`) is a normal, complete value at day precision:
|
|
422
477
|
* **not** an error and never zero-filled into a fake time.
|
|
423
478
|
*/
|
|
424
479
|
/** The precision to which an {@link AstmDate} is populated. */
|
|
@@ -427,7 +482,7 @@ type AstmDatePrecision = "year" | "month" | "day" | "hour" | "minute" | "second"
|
|
|
427
482
|
* A parsed ASTM date/time. Immutable plain data; the populated fields extend
|
|
428
483
|
* exactly as far as {@link AstmDate.precision}. Absent components are left
|
|
429
484
|
* `undefined` rather than defaulted, so a consumer can tell "midnight" from
|
|
430
|
-
* "no time given". No timezone is modeled
|
|
485
|
+
* "no time given". No timezone is modeled: the value is instrument-local.
|
|
431
486
|
*
|
|
432
487
|
* @example
|
|
433
488
|
* ```ts
|
|
@@ -449,10 +504,10 @@ interface AstmDate {
|
|
|
449
504
|
/** How far the components are populated. */
|
|
450
505
|
readonly precision: AstmDatePrecision;
|
|
451
506
|
/**
|
|
452
|
-
* `true` when the digit run does **not** align to a whole-component boundary
|
|
507
|
+
* `true` when the digit run does **not** align to a whole-component boundary: an odd number of
|
|
453
508
|
* digits that cuts a two-digit component (month/day/hour/minute/second) in half (lengths 5, 7, 9,
|
|
454
509
|
* 11, 13). The full run is preserved in {@link AstmDate.raw} and the structured value is truncated
|
|
455
|
-
* to the last **complete** component
|
|
510
|
+
* to the last **complete** component: the dangling digit is **never zero-filled into a fabricated
|
|
456
511
|
* time**. Absent (never `false`) for a clean value. A caller surfaces this as a value-free
|
|
457
512
|
* `ASTM_RECORD_PARTIAL_TIMESTAMP` warning.
|
|
458
513
|
*/
|
|
@@ -461,7 +516,7 @@ interface AstmDate {
|
|
|
461
516
|
/**
|
|
462
517
|
* Parse an ASTM `YYYYMMDDHHMMSS` value, preserving whatever precision is
|
|
463
518
|
* present. Returns `undefined` for a value that is not a usable timestamp (fewer
|
|
464
|
-
* than four leading digits for the year, or non-digit content)
|
|
519
|
+
* than four leading digits for the year, or non-digit content): a caller keeps
|
|
465
520
|
* the raw field text either way, so nothing is lost. A partial value is parsed,
|
|
466
521
|
* never rejected.
|
|
467
522
|
*
|
|
@@ -480,7 +535,7 @@ interface AstmDate {
|
|
|
480
535
|
declare function parseAstmDate(raw: string): AstmDate | undefined;
|
|
481
536
|
/**
|
|
482
537
|
* Render an {@link AstmDate} as an ISO-8601-*like* string truncated to its
|
|
483
|
-
* precision, with **no** `Z` and **no** offset
|
|
538
|
+
* precision, with **no** `Z` and **no** offset: because ASTM carries no
|
|
484
539
|
* timezone and appending one would fabricate information. A consumer that knows
|
|
485
540
|
* the instrument's zone can attach it; this function never assumes UTC.
|
|
486
541
|
*
|
|
@@ -501,9 +556,9 @@ declare function astmDateToLocalISO(d: AstmDate): string;
|
|
|
501
556
|
* spec's component order is LOINC-slot / test-name / coding-scheme / local-code.
|
|
502
557
|
* In the field the way real analyzers emit it:
|
|
503
558
|
*
|
|
504
|
-
* - **component 1** is a LOINC slot
|
|
559
|
+
* - **component 1** is a LOINC slot: *almost always empty*; a few vendors place
|
|
505
560
|
* an inline LOINC when one exists;
|
|
506
|
-
* - **component 4** is the vendor/local code
|
|
561
|
+
* - **component 4** is the vendor/local code: the identifier that is actually
|
|
507
562
|
* present, and therefore the **primary** identifier here.
|
|
508
563
|
*
|
|
509
564
|
* Following the sibling parsers' conservative posture, this module is a
|
|
@@ -513,11 +568,11 @@ declare function astmDateToLocalISO(d: AstmDate): string;
|
|
|
513
568
|
*/
|
|
514
569
|
/** Where a Universal Test ID's usable identifier came from. */
|
|
515
570
|
type UniversalTestIdProvenance =
|
|
516
|
-
/** Component 1 (the LOINC slot) is populated
|
|
571
|
+
/** Component 1 (the LOINC slot) is populated: a candidate LOINC, recognized not validated. */
|
|
517
572
|
"inline-loinc-candidate"
|
|
518
573
|
/** Component 4 (the vendor/local code) carries the identifier. */
|
|
519
574
|
| "local-code"
|
|
520
|
-
/** Only the test name (component 2) is present
|
|
575
|
+
/** Only the test name (component 2) is present: no code. */
|
|
521
576
|
| "name-only"
|
|
522
577
|
/** Nothing usable in the field. */
|
|
523
578
|
| "empty";
|
|
@@ -536,13 +591,13 @@ type UniversalTestIdProvenance =
|
|
|
536
591
|
interface UniversalTestId {
|
|
537
592
|
/** The field's components, verbatim and in order. */
|
|
538
593
|
readonly components: readonly string[];
|
|
539
|
-
/** Component 1 when populated
|
|
594
|
+
/** Component 1 when populated: a *candidate* LOINC (provenance only, never validated). */
|
|
540
595
|
readonly loincCandidate?: string;
|
|
541
|
-
/** Component 2
|
|
596
|
+
/** Component 2: the test / battery name, when present. */
|
|
542
597
|
readonly testName?: string;
|
|
543
|
-
/** Component 3
|
|
598
|
+
/** Component 3: the coding-scheme selector, when present. */
|
|
544
599
|
readonly codingScheme?: string;
|
|
545
|
-
/** Component 4
|
|
600
|
+
/** Component 4, the vendor/local code: the primary identifier when no inline LOINC is given. */
|
|
546
601
|
readonly localCode?: string;
|
|
547
602
|
/** Where the primary identifier came from. */
|
|
548
603
|
readonly provenance: UniversalTestIdProvenance;
|
|
@@ -562,7 +617,7 @@ declare function recognizeUniversalTestId(components: readonly string[]): Univer
|
|
|
562
617
|
/**
|
|
563
618
|
* The primary code to key a result on: the inline LOINC candidate when a vendor
|
|
564
619
|
* supplied one, otherwise the local (vendor) code. Returns `undefined` when the
|
|
565
|
-
* field carries no code at all (name-only or empty)
|
|
620
|
+
* field carries no code at all (name-only or empty): never a guess.
|
|
566
621
|
*
|
|
567
622
|
* @param u - A recognized Universal Test ID.
|
|
568
623
|
* @returns The primary code, or `undefined`.
|
|
@@ -578,7 +633,7 @@ declare function primaryCode(u: UniversalTestId): string | undefined;
|
|
|
578
633
|
* Positional context for frame-codec warnings and errors.
|
|
579
634
|
*
|
|
580
635
|
* **PHI discipline (the whole point).** A frame deviation is located by **frame
|
|
581
|
-
* number + byte offset** and nothing else
|
|
636
|
+
* number + byte offset** and nothing else: never the record text a frame
|
|
582
637
|
* carries. A frame warning or error may be logged verbatim without ever leaking a
|
|
583
638
|
* result value, a patient identifier, or any other field the reassembled record
|
|
584
639
|
* bytes contain.
|
|
@@ -604,12 +659,12 @@ interface AstmFramePosition {
|
|
|
604
659
|
}
|
|
605
660
|
|
|
606
661
|
/**
|
|
607
|
-
* The **frame** warning registry (`ASTM_FRAME_*`) for the ASTM/CLSI-LIS01 codec
|
|
662
|
+
* The **frame** warning registry (`ASTM_FRAME_*`) for the ASTM/CLSI-LIS01 codec:
|
|
608
663
|
* the second of the package's two registries, alongside the record layer's
|
|
609
664
|
* `ASTM_RECORD_*` (both share only the `EMPTY_INPUT` fatal).
|
|
610
665
|
*
|
|
611
666
|
* A frame warning is the lenient codec's record of a tolerated byte-level
|
|
612
|
-
* deviation: it never crashes, never invents a value, and
|
|
667
|
+
* deviation: it never crashes, never invents a value, and, critically, never
|
|
613
668
|
* lets an untrusted frame be silently merged into a record. Every warning carries
|
|
614
669
|
* a stable code plus an {@link AstmFramePosition} (frame number + byte offset) and
|
|
615
670
|
* **never** the record bytes a frame carries (PHI discipline). Consumers compare
|
|
@@ -634,12 +689,12 @@ declare const FRAME_WARNING_CODES: {
|
|
|
634
689
|
* A frame's two-hex-char checksum did not match the modulo-256 sum recomputed over its bytes. The
|
|
635
690
|
* frame is surfaced with `trusted: false` and its text is **never merged** into a reassembled
|
|
636
691
|
* record (default **warn** in lenient mode, escalated to a thrown error in `strict`). Corruption is
|
|
637
|
-
* never silently trusted
|
|
692
|
+
* never silently trusted: the "checksums are routinely not validated" claim was refuted; we
|
|
638
693
|
* validate.
|
|
639
694
|
*/
|
|
640
695
|
readonly ASTM_FRAME_BAD_CHECKSUM: "ASTM_FRAME_BAD_CHECKSUM";
|
|
641
696
|
/**
|
|
642
|
-
* A frame's sequence number was not the expected next value (`1 → … → 7 → 0 → …`)
|
|
697
|
+
* A frame's sequence number was not the expected next value (`1 → … → 7 → 0 → …`): a frame was
|
|
643
698
|
* possibly dropped. The stream is **never silently concatenated across the gap** as if contiguous;
|
|
644
699
|
* the in-progress record is tainted and not emitted as a clean reassembly.
|
|
645
700
|
*/
|
|
@@ -653,7 +708,7 @@ declare const FRAME_WARNING_CODES: {
|
|
|
653
708
|
readonly ASTM_FRAME_UNTERMINATED: "ASTM_FRAME_UNTERMINATED";
|
|
654
709
|
/**
|
|
655
710
|
* A frame's record text exceeded the 240-byte limit without a split. The frame is still surfaced
|
|
656
|
-
* (and, if its checksum validates, reassembled)
|
|
711
|
+
* (and, if its checksum validates, reassembled): the deviation is flagged, not silently dropped
|
|
657
712
|
* (warn in lenient, thrown in `strict`).
|
|
658
713
|
*/
|
|
659
714
|
readonly ASTM_FRAME_OVERSIZE: "ASTM_FRAME_OVERSIZE";
|
|
@@ -730,14 +785,14 @@ declare function frameOversize(position: AstmFramePosition): AstmFrameWarning;
|
|
|
730
785
|
|
|
731
786
|
/**
|
|
732
787
|
* The **LTP** warning registry (`ASTM_LTP_*`) for the ASTM/CLSI-LIS01 protocol
|
|
733
|
-
* layer
|
|
788
|
+
* layer: the third of the package's registries, alongside the record layer's
|
|
734
789
|
* `ASTM_RECORD_*` and the frame codec's `ASTM_FRAME_*`.
|
|
735
790
|
*
|
|
736
791
|
* An LTP warning is the protocol reducer's (or the transport detector's) record
|
|
737
792
|
* of a tolerated session-level deviation: an ambiguous framed/raw stream, a
|
|
738
793
|
* control event that arrived in a state that did not expect it, or a frame the
|
|
739
794
|
* receiver had to reject (`NAK`) rather than accept. Every warning carries a
|
|
740
|
-
* stable code plus, at most, a **frame number
|
|
795
|
+
* stable code plus, at most, a **frame number**: never the record bytes a frame
|
|
741
796
|
* carries (PHI discipline). Consumers compare `warning.code ===
|
|
742
797
|
* LTP_WARNING_CODES.<CODE>`; renaming a code is a **breaking change**.
|
|
743
798
|
*/
|
|
@@ -754,21 +809,21 @@ declare function frameOversize(position: AstmFramePosition): AstmFrameWarning;
|
|
|
754
809
|
declare const LTP_WARNING_CODES: {
|
|
755
810
|
/**
|
|
756
811
|
* The transport detector could not tell a framed stream from a raw (unframed) one from its leading
|
|
757
|
-
* byte
|
|
812
|
+
* byte: it was neither `STX`/`ENQ` nor a bare record letter. It **defaults to framed** and warns,
|
|
758
813
|
* never guessing silently into data loss; a profile override forces the mode. (cobas b121 drops
|
|
759
|
-
* framing over TCP; cobas 4800 / Iguana retain it
|
|
814
|
+
* framing over TCP; cobas 4800 / Iguana retain it, both realities exist, so an unrecognizable lead
|
|
760
815
|
* is defaulted, not assumed.)
|
|
761
816
|
*/
|
|
762
817
|
readonly ASTM_LTP_AMBIGUOUS_TRANSPORT: "ASTM_LTP_AMBIGUOUS_TRANSPORT";
|
|
763
818
|
/**
|
|
764
|
-
* A control event arrived in a protocol state that did not expect it
|
|
819
|
+
* A control event arrived in a protocol state that did not expect it: e.g. an inbound `ACK`/`NAK`
|
|
765
820
|
* at a receiver (which sends, never receives, those), or an `ENQ` mid-transfer. The event is
|
|
766
821
|
* surfaced and handled defensively (an unexpected `ACK`/`NAK` is **never** read as acceptance of
|
|
767
822
|
* data); it never advances the transfer as if valid.
|
|
768
823
|
*/
|
|
769
824
|
readonly ASTM_LTP_UNEXPECTED_EVENT: "ASTM_LTP_UNEXPECTED_EVENT";
|
|
770
825
|
/**
|
|
771
|
-
* The receiver rejected a received frame with a `NAK` instead of accepting it
|
|
826
|
+
* The receiver rejected a received frame with a `NAK` instead of accepting it: because its
|
|
772
827
|
* checksum failed, it was unterminated, or its frame number was out of sequence. The frame's text
|
|
773
828
|
* is **never** appended to the record and the transfer does **not** advance; the receiver awaits the
|
|
774
829
|
* sender's retransmit. This is the protocol-level face of the frame codec's fail-safe: a bad frame
|
|
@@ -783,7 +838,7 @@ declare const LTP_WARNING_CODES: {
|
|
|
783
838
|
type LtpWarningCode = (typeof LTP_WARNING_CODES)[keyof typeof LTP_WARNING_CODES];
|
|
784
839
|
/**
|
|
785
840
|
* A single LTP protocol / transport warning: a stable code, a value-free
|
|
786
|
-
* human-readable message, and
|
|
841
|
+
* human-readable message, and, for a frame-scoped deviation, the frame number
|
|
787
842
|
* only. Never carries a frame's record bytes.
|
|
788
843
|
*
|
|
789
844
|
* @example
|
|
@@ -791,7 +846,7 @@ type LtpWarningCode = (typeof LTP_WARNING_CODES)[keyof typeof LTP_WARNING_CODES]
|
|
|
791
846
|
* import type { AstmLtpWarning } from "@cosyte/astm";
|
|
792
847
|
* const w: AstmLtpWarning = {
|
|
793
848
|
* code: "ASTM_LTP_FRAME_REJECTED",
|
|
794
|
-
* message: "Frame rejected
|
|
849
|
+
* message: "Frame rejected, NAK sent, retransmit expected.",
|
|
795
850
|
* frameNumber: 2,
|
|
796
851
|
* };
|
|
797
852
|
* ```
|
|
@@ -846,22 +901,22 @@ declare function ltpFrameRejected(frameNumber?: number): AstmLtpWarning;
|
|
|
846
901
|
* (RS-232) always uses full E1381 framing. Over **TCP it varies within a single
|
|
847
902
|
* vendor**: the Roche cobas 4800 and iNTERFACEWARE Iguana retain the full
|
|
848
903
|
* `ENQ`/`ACK` + `STX`/checksum framing over TCP (**framed-TCP**), while the cobas
|
|
849
|
-
* b121 drops all low-level framing
|
|
904
|
+
* b121 drops all low-level framing, "TCP itself ensures correctness", and streams
|
|
850
905
|
* de-framed record bytes directly (**raw-TCP**). A reader must handle both and must
|
|
851
906
|
* not assume the handshake is present.
|
|
852
907
|
*
|
|
853
908
|
* {@link detectFraming} decides which it is from the stream's leading byte: `STX`
|
|
854
909
|
* or `ENQ` ⇒ framed (feed it to the frame codec + {@link ltpReduce}); a bare record
|
|
855
910
|
* letter ⇒ raw (feed it straight to `parseAstmRecords`). Anything else is
|
|
856
|
-
* **ambiguous**, and the fail-safe is to **default to framed** and warn
|
|
857
|
-
* guess silently into data loss
|
|
911
|
+
* **ambiguous**, and the fail-safe is to **default to framed** and warn, never to
|
|
912
|
+
* guess silently into data loss, with an explicit override for a profile that
|
|
858
913
|
* knows better.
|
|
859
914
|
*/
|
|
860
915
|
|
|
861
916
|
/**
|
|
862
917
|
* The transport framing of an ASTM byte stream: `"framed"` (E1381 `STX`/checksum
|
|
863
918
|
* frames, the serial and framed-TCP realities) or `"raw"` (framing dropped,
|
|
864
|
-
* de-framed record bytes streamed directly
|
|
919
|
+
* de-framed record bytes streamed directly, the raw-TCP reality).
|
|
865
920
|
*/
|
|
866
921
|
type AstmFraming = "framed" | "raw";
|
|
867
922
|
/**
|
|
@@ -870,8 +925,8 @@ type AstmFraming = "framed" | "raw";
|
|
|
870
925
|
interface DetectFramingOptions {
|
|
871
926
|
/**
|
|
872
927
|
* A profile-supplied override. When set, detection is bypassed entirely and this
|
|
873
|
-
* value is returned with no warning
|
|
874
|
-
*
|
|
928
|
+
* value is returned with no warning: the way a vendor profile forces raw for
|
|
929
|
+
* a cobas b121 or framed for a cobas 4800 regardless of the leading byte.
|
|
875
930
|
*/
|
|
876
931
|
readonly override?: AstmFraming;
|
|
877
932
|
}
|
|
@@ -920,7 +975,7 @@ declare function detectFraming(bytes: Uint8Array, options?: DetectFramingOptions
|
|
|
920
975
|
* plus an optional **transport override** (the raw-vs-framed-TCP knob). It mirrors
|
|
921
976
|
* the sibling `@cosyte/hl7` `defineProfile` and `@cosyte/ccda` `defineCcdaProfile`
|
|
922
977
|
* shape (`name` / `lineage` / `describe()` / `extends`-merge) but models ASTM's
|
|
923
|
-
* quirks
|
|
978
|
+
* quirks (a warning code the stream is expected to trip, and its framing reality)
|
|
924
979
|
* rather than HL7 v2 Z-segments or C-CDA template deviations.
|
|
925
980
|
*
|
|
926
981
|
* A profile **never changes an extracted value**. It operates purely at the
|
|
@@ -929,29 +984,29 @@ declare function detectFraming(bytes: Uint8Array, options?: DetectFramingOptions
|
|
|
929
984
|
* layer (forcing framed/raw before a single byte is parsed). The two load-bearing
|
|
930
985
|
* safety rules, enforced by `defineAstmProfile`:
|
|
931
986
|
*
|
|
932
|
-
* 1. **A profile can never tolerate a safety-critical warning code
|
|
987
|
+
* 1. **A profile can never tolerate a safety-critical warning code**: a result
|
|
933
988
|
* value, abnormal flag, result status, reference range, units, patient/comment
|
|
934
989
|
* context, message-kind ambiguity, code system, or any frame/LTP integrity
|
|
935
990
|
* warning (see `src/profiles/safety.ts`). Naming one is a *definition-time
|
|
936
|
-
* throw*, not a silent relaxation
|
|
991
|
+
* throw*, not a silent relaxation, so a profile can never make a bad checksum
|
|
937
992
|
* "ok" or a cancelled result read "final."
|
|
938
993
|
* 2. **A tolerated deviation is downgraded, never dropped.** The parser still
|
|
939
994
|
* records it (re-coded {@link WARNING_CODES.PROFILE_QUIRK_APPLIED}, flagged
|
|
940
995
|
* `expected: true`, carrying the original `toleratedCode`) so nothing is
|
|
941
|
-
* silently hidden
|
|
996
|
+
* silently hidden, Postel's Law with a receipt.
|
|
942
997
|
*/
|
|
943
998
|
|
|
944
999
|
/**
|
|
945
|
-
* Any warning code from any of the three registries
|
|
1000
|
+
* Any warning code from any of the three registries: the record layer
|
|
946
1001
|
* (`ASTM_RECORD_*` / `ASTM_NONSTANDARD_DELIMITERS` / `ASTM_UNKNOWN_ESCAPE_SEQUENCE`),
|
|
947
1002
|
* the frame codec (`ASTM_FRAME_*`), or the LTP protocol layer (`ASTM_LTP_*`). A
|
|
948
1003
|
* {@link QuirkTolerance} may *name* any of them, but the safety gate refuses all
|
|
949
|
-
* but the small, benign, record-layer subset
|
|
1004
|
+
* but the small, benign, record-layer subset, so the union exists mainly to let
|
|
950
1005
|
* the gate reject a frame/LTP code with a precise, typed message.
|
|
951
1006
|
*/
|
|
952
1007
|
type AnyAstmWarningCode = WarningCode | FrameWarningCode | LtpWarningCode;
|
|
953
1008
|
/**
|
|
954
|
-
* Provenance for an {@link AstmProfile}
|
|
1009
|
+
* Provenance for an {@link AstmProfile}: the **real, cited public artifact** a
|
|
955
1010
|
* profile's quirks are grounded in. A quirk is encoded only when a real
|
|
956
1011
|
* (de-identified) document or a redistributable reference corpus grounds it; this
|
|
957
1012
|
* record is where that grounding is stated, so a reviewer can trace every
|
|
@@ -970,7 +1025,7 @@ type AnyAstmWarningCode = WarningCode | FrameWarningCode | LtpWarningCode;
|
|
|
970
1025
|
interface AstmProfileProvenance {
|
|
971
1026
|
/** Short human-readable name of the grounding source (corpus, transcript, or spec). */
|
|
972
1027
|
readonly source: string;
|
|
973
|
-
/** A citation the grounding can be traced to
|
|
1028
|
+
/** A citation the grounding can be traced to: a URL, DOI, or repo+path. */
|
|
974
1029
|
readonly reference: string;
|
|
975
1030
|
/** When the grounding was last verified (ISO date) or the pinned commit SHA. */
|
|
976
1031
|
readonly retrieved?: string;
|
|
@@ -980,7 +1035,7 @@ interface AstmProfileProvenance {
|
|
|
980
1035
|
/**
|
|
981
1036
|
* Optional structural narrowing for a {@link QuirkTolerance}. When present, the
|
|
982
1037
|
* tolerance applies only to warnings whose PHI-free {@link AstmPosition} matches
|
|
983
|
-
* every provided field
|
|
1038
|
+
* every provided field, so a profile can expect a deviation on one record type
|
|
984
1039
|
* (e.g. an unknown escape only inside `R` result values) without blanket-tolerating
|
|
985
1040
|
* it everywhere. Matching is on **structural identifiers only** (record-type
|
|
986
1041
|
* letter, 1-based field index); there is no matching on any field *value*, by
|
|
@@ -1017,7 +1072,7 @@ interface AstmQuirkMatch {
|
|
|
1017
1072
|
interface AstmQuirkTolerance {
|
|
1018
1073
|
/** The existing, non-safety-critical warning code this profile expects. */
|
|
1019
1074
|
readonly code: AnyAstmWarningCode;
|
|
1020
|
-
/** Why the profile expects this deviation
|
|
1075
|
+
/** Why the profile expects this deviation: grounded in its provenance. */
|
|
1021
1076
|
readonly rationale: string;
|
|
1022
1077
|
/** Optional structural narrowing (record type / field index). */
|
|
1023
1078
|
readonly match?: AstmQuirkMatch;
|
|
@@ -1027,7 +1082,7 @@ interface AstmQuirkTolerance {
|
|
|
1027
1082
|
* {@link defineAstmProfile}; consumers pass it to `parseAstmRecords(raw, { profile })`
|
|
1028
1083
|
* (or register it as the process default), and feed its {@link AstmProfile.transport}
|
|
1029
1084
|
* to `detectFraming(bytes, { override })`. Hand-authoring the object literal is
|
|
1030
|
-
* supported but discouraged
|
|
1085
|
+
* supported but discouraged: the factory validates the safety rules and attaches
|
|
1031
1086
|
* `describe()`.
|
|
1032
1087
|
*
|
|
1033
1088
|
* @example
|
|
@@ -1042,13 +1097,13 @@ interface AstmProfile {
|
|
|
1042
1097
|
readonly name: string;
|
|
1043
1098
|
/** Optional human-readable description. */
|
|
1044
1099
|
readonly description?: string;
|
|
1045
|
-
/** Resolved lineage
|
|
1100
|
+
/** Resolved lineage: `[...parents, name]`, first-occurrence deduped. */
|
|
1046
1101
|
readonly lineage: readonly string[];
|
|
1047
1102
|
/** The expected, non-safety-critical deviations this profile tolerates. */
|
|
1048
1103
|
readonly tolerate: readonly AstmQuirkTolerance[];
|
|
1049
1104
|
/**
|
|
1050
1105
|
* The raw-vs-framed-TCP override. When set, a consumer forces this framing via
|
|
1051
|
-
* `detectFraming(bytes, { override: profile.transport })
|
|
1106
|
+
* `detectFraming(bytes, { override: profile.transport })`, the way a profile
|
|
1052
1107
|
* that knows a vendor's transport reality (e.g. framing dropped over raw TCP)
|
|
1053
1108
|
* bypasses leading-byte auto-detection. Absent means "let detection decide."
|
|
1054
1109
|
*/
|
|
@@ -1084,9 +1139,9 @@ interface DefineAstmProfileOptions {
|
|
|
1084
1139
|
}
|
|
1085
1140
|
|
|
1086
1141
|
/**
|
|
1087
|
-
* Safety-critical result semantics for the ASTM `R` (result) record
|
|
1142
|
+
* Safety-critical result semantics for the ASTM `R` (result) record.
|
|
1088
1143
|
*
|
|
1089
|
-
*
|
|
1144
|
+
* The parser surfaces the `R` record's flag, status, units, and reference-range
|
|
1090
1145
|
* fields **raw**. This module turns those raw letters into modeled, **fail-safe**
|
|
1091
1146
|
* semantics, under one rule: **never a confident wrong value.**
|
|
1092
1147
|
*
|
|
@@ -1095,33 +1150,33 @@ interface DefineAstmProfileOptions {
|
|
|
1095
1150
|
* coerced to `normal`**.
|
|
1096
1151
|
* - A **result status** (field 9) is modeled so a **correction (`C`)** or a
|
|
1097
1152
|
* **cancellation (`X`)** can never read as an active-final result, and an
|
|
1098
|
-
* **absent** status is a typed `unspecified
|
|
1153
|
+
* **absent** status is a typed `unspecified`: **never assumed `F` (final)**.
|
|
1099
1154
|
* - A **reference range** (field 6) is parsed into a low/high (or open-ended)
|
|
1100
|
-
* pair; an unparseable range is surfaced **verbatim
|
|
1155
|
+
* pair; an unparseable range is surfaced **verbatim**, a bound is **never
|
|
1101
1156
|
* fabricated**.
|
|
1102
1157
|
*
|
|
1103
1158
|
* Every bound and code is surfaced verbatim; nothing is collapsed, reconciled,
|
|
1104
1159
|
* or converted. Units are handled at the record layer (a numeric value without
|
|
1105
|
-
* units warns; units are never defaulted, guessed, or converted
|
|
1160
|
+
* units warns; units are never defaulted, guessed, or converted, see
|
|
1106
1161
|
* `./parse.ts`).
|
|
1107
1162
|
*/
|
|
1108
1163
|
/**
|
|
1109
1164
|
* The recognized abnormal-flag letters, from **HL7 v2.2 Table 0078** (the flag
|
|
1110
|
-
* *value* set
|
|
1165
|
+
* *value* set, a published fact set, not CLSI prose). Read in the context of an
|
|
1111
1166
|
* `R` record's field 7: here `S`/`R`/`I` are the microbiology susceptibility
|
|
1112
1167
|
* codes and `R` is **not** the repeat delimiter.
|
|
1113
1168
|
*/
|
|
1114
1169
|
type AbnormalFlagCode = "L" | "H" | "LL" | "HH" | "<" | ">" | "N" | "A" | "AA" | "U" | "D" | "B" | "W" | "S" | "R" | "I";
|
|
1115
1170
|
/**
|
|
1116
1171
|
* The modeled meaning of a recognized {@link AbnormalFlagCode}, plus the two
|
|
1117
|
-
* fail-safe sentinels: `undefined` (a flag was present but is not in Table 0078)
|
|
1118
|
-
*
|
|
1172
|
+
* fail-safe sentinels: `undefined` (a flag was present but is not in Table 0078),
|
|
1173
|
+
* which is **never** collapsed to `normal`.
|
|
1119
1174
|
*/
|
|
1120
1175
|
type AbnormalFlagMeaning = "below-normal" | "above-normal" | "critically-below-normal" | "critically-above-normal" | "below-scale" | "above-scale" | "normal" | "abnormal" | "very-abnormal" | "significant-change-up" | "significant-change-down" | "better" | "worse" | "susceptible" | "resistant" | "intermediate" | "undefined";
|
|
1121
1176
|
/**
|
|
1122
1177
|
* A recognized (or explicitly *un*recognized) abnormal flag. The raw field text
|
|
1123
1178
|
* is always preserved; `recognized` is `false` and `meaning` is `"undefined"`
|
|
1124
|
-
* for any letter outside Table 0078
|
|
1179
|
+
* for any letter outside Table 0078, the flag is surfaced, never dropped, and
|
|
1125
1180
|
* **never coerced to `normal`**.
|
|
1126
1181
|
*
|
|
1127
1182
|
* @example
|
|
@@ -1147,7 +1202,7 @@ interface AbnormalFlag {
|
|
|
1147
1202
|
*
|
|
1148
1203
|
* Leading/trailing whitespace is ignored for the lookup but the `raw` text is
|
|
1149
1204
|
* preserved verbatim. An unrecognized flag yields `{ recognized: false, meaning:
|
|
1150
|
-
* "undefined" }
|
|
1205
|
+
* "undefined" }`, surfaced, never dropped, and **never `"normal"`**.
|
|
1151
1206
|
*
|
|
1152
1207
|
* @param raw - The verbatim field-7 text.
|
|
1153
1208
|
* @returns The interpreted flag.
|
|
@@ -1161,13 +1216,13 @@ interface AbnormalFlag {
|
|
|
1161
1216
|
declare function interpretAbnormalFlag(raw: string): AbnormalFlag;
|
|
1162
1217
|
/**
|
|
1163
1218
|
* The recognized result-status letters (`R`-record field 9). The clinically
|
|
1164
|
-
* load-bearing members are **`C` (correction
|
|
1219
|
+
* load-bearing members are **`C` (correction, supersedes a previously
|
|
1165
1220
|
* transmitted value)** and **`X` (cannot be done / cancelled)**.
|
|
1166
1221
|
*/
|
|
1167
1222
|
type ResultStatusCode = "F" | "C" | "P" | "R" | "S" | "I" | "X";
|
|
1168
1223
|
/**
|
|
1169
1224
|
* The modeled meaning of a result status, plus two fail-safe sentinels:
|
|
1170
|
-
* `unspecified` (the field was absent
|
|
1225
|
+
* `unspecified` (the field was absent, **never assumed `final`**) and
|
|
1171
1226
|
* `undefined` (a letter was present but is not a recognized status).
|
|
1172
1227
|
*/
|
|
1173
1228
|
type ResultStatusMeaning = "final" | "correction" | "preliminary" | "previously-transmitted" | "partial" | "pending" | "cancelled" | "unspecified" | "undefined";
|
|
@@ -1177,11 +1232,11 @@ type ResultStatusMeaning = "final" | "correction" | "preliminary" | "previously-
|
|
|
1177
1232
|
*
|
|
1178
1233
|
* - **`isActiveFinal`** is `true` **only** for a plain `F` (final). It is `false`
|
|
1179
1234
|
* for a correction (`C`), a cancellation (`X`), a preliminary/partial/pending
|
|
1180
|
-
* result, an absent status, and an unrecognized one
|
|
1235
|
+
* result, an absent status, and an unrecognized one, so a superseded or
|
|
1181
1236
|
* cancelled result can **never** read as current/final.
|
|
1182
|
-
* - **`supersedes`** is `true` for `C
|
|
1237
|
+
* - **`supersedes`** is `true` for `C`: this value replaces a previously
|
|
1183
1238
|
* transmitted one.
|
|
1184
|
-
* - **`cancelled`** is `true` for `X
|
|
1239
|
+
* - **`cancelled`** is `true` for `X`: the result cannot be done / was cancelled.
|
|
1185
1240
|
*
|
|
1186
1241
|
* @example
|
|
1187
1242
|
* ```ts
|
|
@@ -1200,17 +1255,17 @@ interface ResultStatus {
|
|
|
1200
1255
|
readonly meaning: ResultStatusMeaning;
|
|
1201
1256
|
/** Whether the raw text matched a recognized status letter. */
|
|
1202
1257
|
readonly recognized: boolean;
|
|
1203
|
-
/** `true` **only** for a plain `F` (final)
|
|
1258
|
+
/** `true` **only** for a plain `F` (final): never for `C`, `X`, absent, or unrecognized. */
|
|
1204
1259
|
readonly isActiveFinal: boolean;
|
|
1205
|
-
/** `true` for `C
|
|
1260
|
+
/** `true` for `C`: this value supersedes a previously transmitted result. */
|
|
1206
1261
|
readonly supersedes: boolean;
|
|
1207
|
-
/** `true` for `X
|
|
1262
|
+
/** `true` for `X`: the result cannot be done / was cancelled. */
|
|
1208
1263
|
readonly cancelled: boolean;
|
|
1209
1264
|
}
|
|
1210
1265
|
/**
|
|
1211
1266
|
* Interpret an `R`-record result-status field (field 9).
|
|
1212
1267
|
*
|
|
1213
|
-
* An **absent** status (`undefined` or empty) is typed `unspecified
|
|
1268
|
+
* An **absent** status (`undefined` or empty) is typed `unspecified`: **never
|
|
1214
1269
|
* assumed `final`**. A **present but unrecognized** letter is `undefined`
|
|
1215
1270
|
* (recognized `false`). In every non-`F` case `isActiveFinal` is `false`, so a
|
|
1216
1271
|
* correction, a cancellation, or an unknown status can never read as current.
|
|
@@ -1229,10 +1284,10 @@ declare function interpretResultStatus(raw: string | undefined): ResultStatus;
|
|
|
1229
1284
|
/**
|
|
1230
1285
|
* The shape of a parsed reference range.
|
|
1231
1286
|
*
|
|
1232
|
-
* - `closed
|
|
1233
|
-
* - `open-low
|
|
1234
|
-
* - `open-high
|
|
1235
|
-
* - `unparsed
|
|
1287
|
+
* - `closed`: both a low and a high bound (`3.5-5.0`).
|
|
1288
|
+
* - `open-low`: an upper bound only (`<5`), so everything at or below `high`.
|
|
1289
|
+
* - `open-high`: a lower bound only (`>10`), so everything at or above `low`.
|
|
1290
|
+
* - `unparsed`: the text did not match a recognized form; both bounds are
|
|
1236
1291
|
* absent and the raw text is surfaced. **A bound is never fabricated.**
|
|
1237
1292
|
*/
|
|
1238
1293
|
type ReferenceRangeKind = "closed" | "open-low" | "open-high" | "unparsed";
|
|
@@ -1241,10 +1296,10 @@ type ReferenceRangeKind = "closed" | "open-low" | "open-high" | "unparsed";
|
|
|
1241
1296
|
* (never coerced to a float, never rounded, never converted) so nothing is lost
|
|
1242
1297
|
* or fabricated.
|
|
1243
1298
|
*
|
|
1244
|
-
* **`[OSS-derived]
|
|
1299
|
+
* **`[OSS-derived]`: the exact lower/upper delimiter (`-`) and the open-ended
|
|
1245
1300
|
* `<x`/`>x` forms are taken from the permissively-licensed OSS reference parsers
|
|
1246
1301
|
* and cross-verified vendor transcripts; they are not confirmed against the
|
|
1247
|
-
* purchased CLSI LIS02-A2
|
|
1302
|
+
* purchased CLSI LIS02-A2. Anything that does not match these
|
|
1248
1303
|
* forms is surfaced verbatim as `unparsed`, never guessed into a bound.**
|
|
1249
1304
|
*
|
|
1250
1305
|
* @example
|
|
@@ -1271,8 +1326,8 @@ interface ReferenceRange {
|
|
|
1271
1326
|
* open-ended) pair.
|
|
1272
1327
|
*
|
|
1273
1328
|
* Recognized forms are `low-high` (closed), `<high` (open-low), and `>low`
|
|
1274
|
-
* (open-high). Anything else
|
|
1275
|
-
* bare non-numeric token
|
|
1329
|
+
* (open-high). Anything else, including an ambiguous multi-dash string or a
|
|
1330
|
+
* bare non-numeric token, is returned as `kind: "unparsed"` with the raw text
|
|
1276
1331
|
* preserved and **no bound invented**. Bounds are surfaced as verbatim text, not
|
|
1277
1332
|
* coerced to numbers.
|
|
1278
1333
|
*
|
|
@@ -1294,13 +1349,12 @@ declare function parseReferenceRange(raw: string): ReferenceRange;
|
|
|
1294
1349
|
* A message is a header (which carries the resolved delimiters as provenance), an
|
|
1295
1350
|
* ordered list of typed records, and the Tier-2 warnings the lenient parser
|
|
1296
1351
|
* accumulated. Every record keeps its raw {@link AstmField}s **and** the typed
|
|
1297
|
-
* accessors relevant to its type
|
|
1352
|
+
* accessors relevant to its type: the raw tree is always present so nothing the
|
|
1298
1353
|
* parser did not model is lost.
|
|
1299
1354
|
*
|
|
1300
|
-
*
|
|
1301
|
-
* {@link UnsupportedRecord} (surfaced, never dropped).
|
|
1302
|
-
*
|
|
1303
|
-
* phases — this layer surfaces the safety-critical values **raw**.
|
|
1355
|
+
* `H`/`P`/`O`/`R`/`C`/`Q`/`M`/`S`/`L` are modeled; every other type letter is an
|
|
1356
|
+
* {@link UnsupportedRecord} (surfaced, never dropped). Alongside the modeled
|
|
1357
|
+
* views, this layer always surfaces the safety-critical values **raw**.
|
|
1304
1358
|
*/
|
|
1305
1359
|
|
|
1306
1360
|
/**
|
|
@@ -1333,14 +1387,25 @@ interface RecordBase {
|
|
|
1333
1387
|
*/
|
|
1334
1388
|
interface HeaderRecord extends RecordBase {
|
|
1335
1389
|
readonly type: "H";
|
|
1336
|
-
/**
|
|
1390
|
+
/**
|
|
1391
|
+
* The four delimiters **this** header put into force: the set its own fields, and every record
|
|
1392
|
+
* after it, were read with.
|
|
1393
|
+
*
|
|
1394
|
+
* A stream may carry several messages (`H` … `L`) and each header declares its own set, so on a
|
|
1395
|
+
* multi-header stream these can differ between headers; {@link AstmMessage.delimiters} is the
|
|
1396
|
+
* first header's. When a later header cannot declare a usable set, the set already in force is
|
|
1397
|
+
* kept and reported here (with an `ASTM_RECORD_UNREADABLE_REDECLARATION` warning) rather than a
|
|
1398
|
+
* guessed one.
|
|
1399
|
+
*/
|
|
1337
1400
|
readonly delimiters: Delimiters;
|
|
1338
1401
|
/**
|
|
1339
|
-
* The header's exact wire text (terminator excluded),
|
|
1340
|
-
*
|
|
1341
|
-
*
|
|
1342
|
-
*
|
|
1343
|
-
*
|
|
1402
|
+
* The header's exact wire text (terminator excluded), kept as **provenance**: the
|
|
1403
|
+
* delimiters are read from this raw text, never from {@link RecordBase.fields}.
|
|
1404
|
+
*
|
|
1405
|
+
* It is **not** what the serializer emits from. To change a header field, edit
|
|
1406
|
+
* {@link RecordBase.fields}: `fields[0]` is the type letter, `fields[1]` is the
|
|
1407
|
+
* delimiter declaration, and the header's ASTM data fields follow from `fields[2]`.
|
|
1408
|
+
* Editing this string instead has no effect on emit.
|
|
1344
1409
|
*/
|
|
1345
1410
|
readonly rawLine: string;
|
|
1346
1411
|
}
|
|
@@ -1357,158 +1422,158 @@ interface PatientName {
|
|
|
1357
1422
|
* The `P` (patient) record.
|
|
1358
1423
|
*
|
|
1359
1424
|
* **Safety:** the practice-assigned ID (field 3) and the laboratory-assigned ID
|
|
1360
|
-
* (field 4) are modeled as **distinct** fields and never collapsed
|
|
1425
|
+
* (field 4) are modeled as **distinct** fields and never collapsed, conflating
|
|
1361
1426
|
* them is the primary result-misfiling path.
|
|
1362
1427
|
*/
|
|
1363
1428
|
interface PatientRecord extends RecordBase {
|
|
1364
1429
|
readonly type: "P";
|
|
1365
|
-
/** Field 2
|
|
1430
|
+
/** Field 2: sequence number. */
|
|
1366
1431
|
readonly seq?: string;
|
|
1367
|
-
/** Field 3
|
|
1432
|
+
/** Field 3: practice-assigned patient ID. Distinct from {@link PatientRecord.laboratoryAssignedId}. */
|
|
1368
1433
|
readonly practiceAssignedId?: string;
|
|
1369
|
-
/** Field 4
|
|
1434
|
+
/** Field 4: laboratory-assigned patient ID. Distinct from {@link PatientRecord.practiceAssignedId}. */
|
|
1370
1435
|
readonly laboratoryAssignedId?: string;
|
|
1371
1436
|
/**
|
|
1372
|
-
* Field 5
|
|
1373
|
-
* separate from the practice- and laboratory-assigned IDs
|
|
1437
|
+
* Field 5: a third patient identifier (e.g. a national/alternate ID), surfaced verbatim. Kept
|
|
1438
|
+
* separate from the practice- and laboratory-assigned IDs: the three never collapse into one.
|
|
1374
1439
|
*/
|
|
1375
1440
|
readonly patientIdThree?: string;
|
|
1376
|
-
/** Field 6
|
|
1441
|
+
/** Field 6: patient name (`Last^First^Middle`). */
|
|
1377
1442
|
readonly name?: PatientName;
|
|
1378
|
-
/** Field 7
|
|
1443
|
+
/** Field 7: mother's maiden name, surfaced verbatim (a surname component; PHI). */
|
|
1379
1444
|
readonly mothersMaidenName?: string;
|
|
1380
|
-
/** Field 8
|
|
1445
|
+
/** Field 8: birthdate (`YYYYMMDDHHMMSS`, precision-preserving; a truncated run sets `truncated`). */
|
|
1381
1446
|
readonly birthDate?: AstmDate;
|
|
1382
|
-
/** Field 9
|
|
1447
|
+
/** Field 9: sex, surfaced raw (`M`/`F`/`U`/vendor value). */
|
|
1383
1448
|
readonly sex?: string;
|
|
1384
1449
|
}
|
|
1385
1450
|
/**
|
|
1386
|
-
* The `O` (order) record
|
|
1451
|
+
* The `O` (order) record: binds a result to a specimen.
|
|
1387
1452
|
*/
|
|
1388
1453
|
interface OrderRecord extends RecordBase {
|
|
1389
1454
|
readonly type: "O";
|
|
1390
|
-
/** Field 2
|
|
1455
|
+
/** Field 2: sequence number. */
|
|
1391
1456
|
readonly seq?: string;
|
|
1392
|
-
/** Field 3
|
|
1457
|
+
/** Field 3: specimen / accession ID. */
|
|
1393
1458
|
readonly specimenId?: string;
|
|
1394
|
-
/** Field 4
|
|
1459
|
+
/** Field 4: instrument specimen ID. */
|
|
1395
1460
|
readonly instrumentSpecimenId?: string;
|
|
1396
|
-
/** Field 5
|
|
1461
|
+
/** Field 5: Universal Test ID (same caret structure as a result's). */
|
|
1397
1462
|
readonly universalTestId?: UniversalTestId;
|
|
1398
1463
|
/**
|
|
1399
|
-
* Field 6
|
|
1464
|
+
* Field 6: priority, surfaced raw (e.g. `S` STAT, `R` routine, `A` ASAP). Vendor letters vary;
|
|
1400
1465
|
* the code set is `[OSS-derived]` (the exact enumeration is in the paywalled CLSI LIS02-A2), so the
|
|
1401
1466
|
* value is surfaced verbatim and **never mapped to a guessed meaning**.
|
|
1402
1467
|
*/
|
|
1403
1468
|
readonly priority?: string;
|
|
1404
1469
|
/**
|
|
1405
|
-
* Field 12
|
|
1406
|
-
* (`~12`) and the code set are `[OSS-derived]` (paywalled)
|
|
1470
|
+
* Field 12: action code, surfaced raw (e.g. `C` cancel, `A` add, `N` new). The exact field index
|
|
1471
|
+
* (`~12`) and the code set are `[OSS-derived]` (paywalled): surfaced verbatim, never interpreted.
|
|
1407
1472
|
*/
|
|
1408
1473
|
readonly actionCode?: string;
|
|
1409
1474
|
/**
|
|
1410
|
-
* Field 26
|
|
1411
|
-
* field index (`~26`) and the code set are `[OSS-derived]` (paywalled)
|
|
1475
|
+
* Field 26: report type, surfaced raw (e.g. `F` final, `P` preliminary, `X` cancel). The exact
|
|
1476
|
+
* field index (`~26`) and the code set are `[OSS-derived]` (paywalled): surfaced verbatim.
|
|
1412
1477
|
*/
|
|
1413
1478
|
readonly reportType?: string;
|
|
1414
1479
|
}
|
|
1415
1480
|
/**
|
|
1416
|
-
* The `R` (result) record
|
|
1481
|
+
* The `R` (result) record: the value itself.
|
|
1417
1482
|
*
|
|
1418
1483
|
* The raw safety-critical fields (value, `units`, `referenceRange`,
|
|
1419
1484
|
* `abnormalFlags`, `resultStatus`) are always surfaced exactly as received.
|
|
1420
|
-
*
|
|
1485
|
+
* The **modeled, fail-safe** semantics sit alongside them: `flag`
|
|
1421
1486
|
* (Table 0078, `undefined` never coerced to normal), `status` (a `C`/`X` never
|
|
1422
1487
|
* reads as active-final; an absent status is `unspecified`, never `final`), and
|
|
1423
1488
|
* `range` (open/closed bounds surfaced verbatim, never fabricated). The raw
|
|
1424
|
-
* strings and the modeled views coexist
|
|
1489
|
+
* strings and the modeled views coexist: nothing is collapsed or reconciled.
|
|
1425
1490
|
*/
|
|
1426
1491
|
interface ResultRecord extends RecordBase {
|
|
1427
1492
|
readonly type: "R";
|
|
1428
|
-
/** Field 2
|
|
1493
|
+
/** Field 2: sequence number. */
|
|
1429
1494
|
readonly seq?: string;
|
|
1430
|
-
/** Field 3
|
|
1495
|
+
/** Field 3: Universal Test ID (local code in component 4 is the primary identifier). */
|
|
1431
1496
|
readonly universalTestId?: UniversalTestId;
|
|
1432
1497
|
/**
|
|
1433
|
-
* Field 4
|
|
1498
|
+
* Field 4: the measured value. In the ordinary single-component case this is the decoded scalar.
|
|
1434
1499
|
* When the value field carried an *unescaped* component delimiter (an ambiguity), this is the
|
|
1435
|
-
* **full raw field text
|
|
1500
|
+
* **full raw field text**, never a truncated component, and {@link ResultRecord.valueComponents}
|
|
1436
1501
|
* plus an `ASTM_RECORD_AMBIGUOUS_VALUE_SPLIT` warning are also present.
|
|
1437
1502
|
*/
|
|
1438
1503
|
readonly value?: string;
|
|
1439
1504
|
/**
|
|
1440
1505
|
* Field 4 split into components, present only when the value field carried an *unescaped* component
|
|
1441
|
-
* delimiter
|
|
1506
|
+
* delimiter: i.e. it read as more than one component. Surfaced alongside the full raw value (and a
|
|
1442
1507
|
* warning) so an ambiguous split is visible, never resolved silently.
|
|
1443
1508
|
*/
|
|
1444
1509
|
readonly valueComponents?: readonly string[];
|
|
1445
|
-
/** Field 5
|
|
1510
|
+
/** Field 5: units (vendor free text; a missing unit is not defaulted). */
|
|
1446
1511
|
readonly units?: string;
|
|
1447
|
-
/** Field 6
|
|
1512
|
+
/** Field 6: reference range, surfaced raw. */
|
|
1448
1513
|
readonly referenceRange?: string;
|
|
1449
1514
|
/**
|
|
1450
|
-
* Field 6
|
|
1515
|
+
* Field 6: the reference range parsed into low/high (or open-ended) bounds, present only when the
|
|
1451
1516
|
* field carried a value. An unparseable range is `kind: "unparsed"` with the raw text preserved and
|
|
1452
1517
|
* **no bound fabricated**. Bounds are verbatim numeric text, never coerced to floats.
|
|
1453
1518
|
*/
|
|
1454
1519
|
readonly range?: ReferenceRange;
|
|
1455
|
-
/** Field 7
|
|
1520
|
+
/** Field 7: abnormal flags, surfaced raw (HL7 Table 0078 values). */
|
|
1456
1521
|
readonly abnormalFlags?: string;
|
|
1457
1522
|
/**
|
|
1458
|
-
* Field 7
|
|
1459
|
-
* carried a value. An unrecognized flag is `{ recognized: false, meaning: "undefined" }
|
|
1523
|
+
* Field 7: the abnormal flag interpreted against HL7 Table 0078, present only when the field
|
|
1524
|
+
* carried a value. An unrecognized flag is `{ recognized: false, meaning: "undefined" }`, surfaced,
|
|
1460
1525
|
* never dropped, and **never coerced to `normal`**.
|
|
1461
1526
|
*/
|
|
1462
1527
|
readonly flag?: AbnormalFlag;
|
|
1463
|
-
/** Field 9
|
|
1528
|
+
/** Field 9: result status, surfaced raw (`F`/`C`/`X`/…). */
|
|
1464
1529
|
readonly resultStatus?: string;
|
|
1465
1530
|
/**
|
|
1466
|
-
* Field 9
|
|
1531
|
+
* Field 9: the modeled result status. **Always present** (an absent field yields a typed
|
|
1467
1532
|
* `unspecified`, never assumed `final`), so `status.isActiveFinal` is always a reliable boolean: it
|
|
1468
1533
|
* is `true` only for a plain `F`, and `false` for a correction (`C`), a cancellation (`X`), a
|
|
1469
1534
|
* partial/preliminary/pending, an absent, or an unrecognized status.
|
|
1470
1535
|
*/
|
|
1471
1536
|
readonly status: ResultStatus;
|
|
1472
|
-
/** Field 11
|
|
1537
|
+
/** Field 11: operator. */
|
|
1473
1538
|
readonly operator?: string;
|
|
1474
|
-
/** Field 12
|
|
1539
|
+
/** Field 12: test started timestamp. */
|
|
1475
1540
|
readonly startedAt?: AstmDate;
|
|
1476
|
-
/** Field 13
|
|
1541
|
+
/** Field 13: test completed timestamp. */
|
|
1477
1542
|
readonly completedAt?: AstmDate;
|
|
1478
|
-
/** Field 14
|
|
1543
|
+
/** Field 14: instrument identifier. */
|
|
1479
1544
|
readonly instrument?: string;
|
|
1480
1545
|
}
|
|
1481
1546
|
/**
|
|
1482
|
-
* The `C` (comment) record
|
|
1547
|
+
* The `C` (comment) record: free-text context attached to a parent record.
|
|
1483
1548
|
*
|
|
1484
1549
|
* A comment is **attached by position** to the immediately-preceding `H`/`P`/`O`/`R` record
|
|
1485
1550
|
* ({@link CommentRecord.parentIndex}); consecutive comments share that parent. **Fail-safe:** a
|
|
1486
|
-
* comment with no valid preceding parent is an **orphan
|
|
1551
|
+
* comment with no valid preceding parent is an **orphan**: it is attached to the message root
|
|
1487
1552
|
* ({@link CommentRecord.attachedToRoot} `true`) and a value-free `ASTM_RECORD_ORPHAN_COMMENT` warning
|
|
1488
|
-
* fires
|
|
1553
|
+
* fires, **never silently dropped**, so a comment carrying (e.g.) "QC / non-compliant" context can
|
|
1489
1554
|
* never float away unnoticed.
|
|
1490
1555
|
*/
|
|
1491
1556
|
interface CommentRecord extends RecordBase {
|
|
1492
1557
|
readonly type: "C";
|
|
1493
|
-
/** Field 2
|
|
1558
|
+
/** Field 2: sequence number. */
|
|
1494
1559
|
readonly seq?: string;
|
|
1495
|
-
/** Field 3
|
|
1560
|
+
/** Field 3: comment source (who/what produced it), surfaced verbatim. */
|
|
1496
1561
|
readonly source?: string;
|
|
1497
1562
|
/**
|
|
1498
|
-
* Field 4
|
|
1563
|
+
* Field 4: the comment text, surfaced as the **full** field text (all components), never
|
|
1499
1564
|
* truncated to the first component. The component structure is in {@link CommentRecord.textComponents}.
|
|
1500
1565
|
*/
|
|
1501
1566
|
readonly text?: string;
|
|
1502
1567
|
/**
|
|
1503
|
-
* Field 4
|
|
1568
|
+
* Field 4: the comment text split into its decoded components (comment text is component-capable;
|
|
1504
1569
|
* multiple components are a normal structured comment, **not** an ambiguity). Present only when the
|
|
1505
1570
|
* field carried more than one component.
|
|
1506
1571
|
*/
|
|
1507
1572
|
readonly textComponents?: readonly string[];
|
|
1508
1573
|
/**
|
|
1509
|
-
* Field 5
|
|
1574
|
+
* Field 5: comment type code, surfaced verbatim. **`[OSS-derived]`:** `I` (instrument) is the only
|
|
1510
1575
|
* value seen in the permissively-licensed real transcripts; other values (e.g. `G`/`T`/`P`) are
|
|
1511
|
-
* defined only in the paywalled CLSI LIS02-A2 and are **not** interpreted here
|
|
1576
|
+
* defined only in the paywalled CLSI LIS02-A2 and are **not** interpreted here: the raw code is
|
|
1512
1577
|
* surfaced, never mapped to a guessed meaning.
|
|
1513
1578
|
*/
|
|
1514
1579
|
readonly commentType?: string;
|
|
@@ -1517,57 +1582,57 @@ interface CommentRecord extends RecordBase {
|
|
|
1517
1582
|
* the comment is an orphan attached to the message root (see {@link CommentRecord.attachedToRoot}).
|
|
1518
1583
|
*/
|
|
1519
1584
|
readonly parentIndex?: number;
|
|
1520
|
-
/** `true` when no valid parent preceded
|
|
1585
|
+
/** `true` when no valid parent preceded: the comment is attached to the message root (and warned). */
|
|
1521
1586
|
readonly attachedToRoot: boolean;
|
|
1522
1587
|
}
|
|
1523
1588
|
/**
|
|
1524
|
-
* The `Q` (Request Information) record
|
|
1589
|
+
* The `Q` (Request Information) record: the host-query request.
|
|
1525
1590
|
*
|
|
1526
1591
|
* A `Q` record asks the LIS for information (e.g. outstanding orders for a
|
|
1527
1592
|
* specimen); its **presence classifies the whole message as a request, never a
|
|
1528
1593
|
* result set** (see {@link AstmMessage.classification}). Its safety-relevant
|
|
1529
|
-
* fields
|
|
1594
|
+
* fields, the starting/ending range ID and the request-information status, are
|
|
1530
1595
|
* surfaced **verbatim**; the field *positions* are the public ASTM E1394 layout,
|
|
1531
1596
|
* but their internal structure and code meanings are **`[OSS-derived / paywalled]`**
|
|
1532
|
-
*
|
|
1597
|
+
* and are therefore **never interpreted or guessed**.
|
|
1533
1598
|
*/
|
|
1534
1599
|
interface QueryRecord extends RecordBase {
|
|
1535
|
-
/** Field 2
|
|
1600
|
+
/** Field 2: sequence number. */
|
|
1536
1601
|
readonly seq?: string;
|
|
1537
1602
|
readonly type: "Q";
|
|
1538
1603
|
/**
|
|
1539
|
-
* Field 3
|
|
1604
|
+
* Field 3: starting range ID number, surfaced as the **full** verbatim field text (never truncated
|
|
1540
1605
|
* to a component). Its caret component structure (e.g. patient ID ^ specimen ID) is
|
|
1541
|
-
* **`[OSS-derived / paywalled]
|
|
1606
|
+
* **`[OSS-derived / paywalled]`**: the raw split is available via {@link RecordBase.fields}, but the
|
|
1542
1607
|
* *meaning* of each component is **never** assigned here.
|
|
1543
1608
|
*/
|
|
1544
1609
|
readonly startingRangeId?: string;
|
|
1545
|
-
/** Field 4
|
|
1610
|
+
/** Field 4: ending range ID number, surfaced verbatim (same `[OSS-derived]` caveat as field 3). */
|
|
1546
1611
|
readonly endingRangeId?: string;
|
|
1547
1612
|
/**
|
|
1548
|
-
* Field 5
|
|
1613
|
+
* Field 5: Universal Test ID (the same caret structure as an `O`/`R` record's), recognized by
|
|
1549
1614
|
* provenance only. When the field is the literal universal-query keyword, {@link QueryRecord.queriesAllTests}
|
|
1550
|
-
* is set instead
|
|
1615
|
+
* is set instead: see its caveat.
|
|
1551
1616
|
*/
|
|
1552
1617
|
readonly universalTestId?: UniversalTestId;
|
|
1553
1618
|
/**
|
|
1554
1619
|
* `true` when field 5 is the literal `ALL` universal-query keyword (case-insensitive). **`[OSS-derived
|
|
1555
1620
|
* / paywalled]`:** the token is surfaced because it appears in the OSS references, but its exact
|
|
1556
1621
|
* host-query *behavior* (which tests a bare `ALL` selects, and whether the vendor answers with a full
|
|
1557
|
-
* `H/P/O/L` or a `P/O`-only response) is paywalled and vendor-specific
|
|
1622
|
+
* `H/P/O/L` or a `P/O`-only response) is paywalled and vendor-specific, **not decided here**.
|
|
1558
1623
|
*/
|
|
1559
1624
|
readonly queriesAllTests: boolean;
|
|
1560
1625
|
/**
|
|
1561
|
-
* Field 13
|
|
1562
|
-
* **`[OSS-derived / paywalled]
|
|
1563
|
-
* parser recognizes **none** of them and interprets nothing
|
|
1626
|
+
* Field 13: request-information status code(s), surfaced **verbatim**. The status code *set* is
|
|
1627
|
+
* **`[OSS-derived / paywalled]`**: with no publicly-groundable enumeration, this
|
|
1628
|
+
* parser recognizes **none** of them and interprets nothing, every present status is surfaced raw
|
|
1564
1629
|
* and flagged with a value-free `ASTM_RECORD_UNINTERPRETED_QUERY_STATUS` warning. Never mapped to a
|
|
1565
1630
|
* guessed meaning.
|
|
1566
1631
|
*/
|
|
1567
1632
|
readonly requestInformationStatus?: string;
|
|
1568
1633
|
}
|
|
1569
1634
|
/**
|
|
1570
|
-
* The `M` (manufacturer) record
|
|
1635
|
+
* The `M` (manufacturer) record: vendor-defined free-form data
|
|
1571
1636
|
* (QC / calibration / maintenance), surfaced **VERBATIM** and **never interpreted
|
|
1572
1637
|
* into typed clinical fields**.
|
|
1573
1638
|
*
|
|
@@ -1578,30 +1643,48 @@ interface QueryRecord extends RecordBase {
|
|
|
1578
1643
|
*/
|
|
1579
1644
|
interface ManufacturerRecord extends RecordBase {
|
|
1580
1645
|
readonly type: "M";
|
|
1581
|
-
/**
|
|
1646
|
+
/**
|
|
1647
|
+
* The record's exact wire text (terminator excluded), preserved byte-for-byte.
|
|
1648
|
+
*
|
|
1649
|
+
* Emit reproduces these bytes exactly whenever a reader using the delimiters being
|
|
1650
|
+
* emitted against would recover the fields this record models: always the case when
|
|
1651
|
+
* the record is already in those delimiters, and also when it carries no delimiter
|
|
1652
|
+
* either set would split on. Otherwise the record is re-encoded from
|
|
1653
|
+
* {@link RecordBase.fields}, so the row can never go out in delimiters the header does
|
|
1654
|
+
* not declare. To change a value, edit `fields`; editing this string has no effect on emit.
|
|
1655
|
+
*/
|
|
1582
1656
|
readonly rawLine: string;
|
|
1583
1657
|
}
|
|
1584
1658
|
/**
|
|
1585
|
-
* The `S` (scientific) record
|
|
1659
|
+
* The `S` (scientific) record: vendor-defined free-form data, surfaced
|
|
1586
1660
|
* **VERBATIM** and **never interpreted into typed clinical fields** (same posture
|
|
1587
1661
|
* as {@link ManufacturerRecord}).
|
|
1588
1662
|
*/
|
|
1589
1663
|
interface ScientificRecord extends RecordBase {
|
|
1590
1664
|
readonly type: "S";
|
|
1591
|
-
/**
|
|
1665
|
+
/**
|
|
1666
|
+
* The record's exact wire text (terminator excluded), preserved byte-for-byte.
|
|
1667
|
+
*
|
|
1668
|
+
* Emit reproduces these bytes exactly whenever a reader using the delimiters being
|
|
1669
|
+
* emitted against would recover the fields this record models: always the case when
|
|
1670
|
+
* the record is already in those delimiters, and also when it carries no delimiter
|
|
1671
|
+
* either set would split on. Otherwise the record is re-encoded from
|
|
1672
|
+
* {@link RecordBase.fields}, so the row can never go out in delimiters the header does
|
|
1673
|
+
* not declare. To change a value, edit `fields`; editing this string has no effect on emit.
|
|
1674
|
+
*/
|
|
1592
1675
|
readonly rawLine: string;
|
|
1593
1676
|
}
|
|
1594
1677
|
/**
|
|
1595
|
-
* The `L` (terminator) record
|
|
1678
|
+
* The `L` (terminator) record: closes a message.
|
|
1596
1679
|
*/
|
|
1597
1680
|
interface TerminatorRecord extends RecordBase {
|
|
1598
1681
|
readonly type: "L";
|
|
1599
1682
|
}
|
|
1600
1683
|
/**
|
|
1601
1684
|
* Any record whose type letter is not modeled (a genuinely unknown letter).
|
|
1602
|
-
* `H`/`P`/`O`/`R`/`C`/`
|
|
1603
|
-
*
|
|
1604
|
-
* `ASTM_RECORD_UNKNOWN_TYPE` warning
|
|
1685
|
+
* `H`/`P`/`O`/`R`/`C`/`Q`/`M`/`S`/`L` are all modeled; anything else is surfaced
|
|
1686
|
+
* with its raw fields intact and flagged with an
|
|
1687
|
+
* `ASTM_RECORD_UNKNOWN_TYPE` warning, never dropped.
|
|
1605
1688
|
*/
|
|
1606
1689
|
interface UnsupportedRecord extends RecordBase {
|
|
1607
1690
|
readonly type: "unsupported";
|
|
@@ -1613,30 +1696,30 @@ type AstmRecord = HeaderRecord | PatientRecord | OrderRecord | ResultRecord | Co
|
|
|
1613
1696
|
/**
|
|
1614
1697
|
* How a message is classified by the host-query flow.
|
|
1615
1698
|
*
|
|
1616
|
-
* - `host-query`
|
|
1699
|
+
* - `host-query` (the message carries at least one `Q` record): it is a **request
|
|
1617
1700
|
* for information**, and **must never be read as a result set** (the load-bearing
|
|
1618
1701
|
* safety distinction of this layer). `Q` **dominates**: a `Q` present classifies
|
|
1619
1702
|
* the message as a request even if a result record is also present (an anomaly,
|
|
1620
|
-
* separately warned)
|
|
1703
|
+
* separately warned), so a `Q`-bearing message is never silently treated as a
|
|
1621
1704
|
* result upload.
|
|
1622
|
-
* - `results`
|
|
1623
|
-
* - `orders`
|
|
1705
|
+
* - `results` (no `Q`, at least one `R` result record): a result upload / response.
|
|
1706
|
+
* - `orders` (no `Q`, no `R`, at least one `O` order record): an order download,
|
|
1624
1707
|
* or a query response before results are attached.
|
|
1625
|
-
* - `indeterminate
|
|
1708
|
+
* - `indeterminate`: none of the above (e.g. header + terminator only). Not
|
|
1626
1709
|
* guessed into one of the other kinds.
|
|
1627
1710
|
*/
|
|
1628
1711
|
type AstmMessageKind = "host-query" | "results" | "orders" | "indeterminate";
|
|
1629
1712
|
/**
|
|
1630
1713
|
* The message-level classification from the host-query flow. `isHostQueryRequest`
|
|
1631
1714
|
* is the single boolean a consumer should gate on before treating records as
|
|
1632
|
-
* results
|
|
1715
|
+
* results: it is `true` **iff** a `Q` record is present.
|
|
1633
1716
|
*
|
|
1634
1717
|
* @example
|
|
1635
1718
|
* ```ts
|
|
1636
1719
|
* import { parseAstmRecords } from "@cosyte/astm";
|
|
1637
1720
|
* const req = parseAstmRecords("H|\\^&\rP|1\rQ|1|^SPEC-7||ALL\rL|1\r");
|
|
1638
1721
|
* req.classification.kind; // "host-query"
|
|
1639
|
-
* req.classification.isHostQueryRequest; // true
|
|
1722
|
+
* req.classification.isHostQueryRequest; // true, never read its records as results
|
|
1640
1723
|
* ```
|
|
1641
1724
|
*/
|
|
1642
1725
|
interface AstmMessageClassification {
|
|
@@ -1649,7 +1732,7 @@ interface AstmMessageClassification {
|
|
|
1649
1732
|
/** At least one `O` (order) record is present. */
|
|
1650
1733
|
readonly hasOrders: boolean;
|
|
1651
1734
|
/**
|
|
1652
|
-
* `true` **iff** `kind === "host-query"` (a `Q` record is present)
|
|
1735
|
+
* `true` **iff** `kind === "host-query"` (a `Q` record is present), the safety surface: gate on this
|
|
1653
1736
|
* before treating records as results, so a query is never misread as a result upload.
|
|
1654
1737
|
*/
|
|
1655
1738
|
readonly isHostQueryRequest: boolean;
|
|
@@ -1667,11 +1750,20 @@ interface AstmMessageClassification {
|
|
|
1667
1750
|
* ```
|
|
1668
1751
|
*/
|
|
1669
1752
|
interface AstmMessage {
|
|
1753
|
+
/** The **first** header in the stream. A multi-header stream's later headers are in {@link AstmMessage.records}. */
|
|
1670
1754
|
readonly header: HeaderRecord;
|
|
1671
1755
|
readonly records: readonly AstmRecord[];
|
|
1756
|
+
/**
|
|
1757
|
+
* The delimiters the **first** header declared.
|
|
1758
|
+
*
|
|
1759
|
+
* A stream may carry several messages (`H` … `L`), and a later header may declare a different set,
|
|
1760
|
+
* which is honored from that header onward. So this is not necessarily the set every record was
|
|
1761
|
+
* read with: each header reports its own on {@link HeaderRecord.delimiters}, and a change is
|
|
1762
|
+
* flagged `ASTM_RECORD_DELIMITERS_REDECLARED`.
|
|
1763
|
+
*/
|
|
1672
1764
|
readonly delimiters: Delimiters;
|
|
1673
1765
|
/**
|
|
1674
|
-
* The host-query classification of this message
|
|
1766
|
+
* The host-query classification of this message: whether it is a request (`Q`
|
|
1675
1767
|
* present), a result upload, an order download, or indeterminate. Gate on
|
|
1676
1768
|
* {@link AstmMessageClassification.isHostQueryRequest} before reading records as
|
|
1677
1769
|
* results.
|
|
@@ -1679,7 +1771,7 @@ interface AstmMessage {
|
|
|
1679
1771
|
readonly classification: AstmMessageClassification;
|
|
1680
1772
|
readonly warnings: readonly AstmRecordWarning[];
|
|
1681
1773
|
/**
|
|
1682
|
-
* The vendor profile that was active for this parse, when one applied
|
|
1774
|
+
* The vendor profile that was active for this parse, when one applied: its
|
|
1683
1775
|
* `name` and resolved `lineage` (attribution only; the profile object itself is
|
|
1684
1776
|
* not embedded). Absent when no profile applied. A profile that tolerated a
|
|
1685
1777
|
* deviation shows up as a `PROFILE_QUIRK_APPLIED` entry in {@link warnings}, not
|
|
@@ -1697,7 +1789,7 @@ interface AstmParseOptions {
|
|
|
1697
1789
|
/**
|
|
1698
1790
|
* When `true`, escalate any Tier-2 deviation to a thrown {@link AstmStrictError}
|
|
1699
1791
|
* instead of accumulating a warning. Off by default. A deviation a profile
|
|
1700
|
-
* *expected* (a `PROFILE_QUIRK_APPLIED`) does **not** escalate
|
|
1792
|
+
* *expected* (a `PROFILE_QUIRK_APPLIED`) does **not** escalate: it is known,
|
|
1701
1793
|
* benign, and still recorded.
|
|
1702
1794
|
*/
|
|
1703
1795
|
readonly strict?: boolean;
|
|
@@ -1714,11 +1806,11 @@ interface AstmParseOptions {
|
|
|
1714
1806
|
/**
|
|
1715
1807
|
* The record-layer entry point: {@link parseAstmRecords}.
|
|
1716
1808
|
*
|
|
1717
|
-
*
|
|
1718
|
-
* layer
|
|
1719
|
-
* every record escape-aware, and builds the immutable
|
|
1720
|
-
* by default: vendor quirks become typed warnings, and
|
|
1721
|
-
* structural conditions are fatal.
|
|
1809
|
+
* It assumes **already-de-framed** record bytes; {@link parseFramedAstm} composes
|
|
1810
|
+
* the E1381/LIS01 framing layer with this one. It reads the four delimiters from each
|
|
1811
|
+
* `H` record, tokenizes every record escape-aware, and builds the immutable
|
|
1812
|
+
* {@link AstmMessage}, lenient by default: vendor quirks become typed warnings, and
|
|
1813
|
+
* only three unrecoverable structural conditions are fatal.
|
|
1722
1814
|
*/
|
|
1723
1815
|
|
|
1724
1816
|
/**
|
|
@@ -1746,8 +1838,18 @@ declare class AstmStrictError extends Error {
|
|
|
1746
1838
|
*
|
|
1747
1839
|
* The stream is a sequence of records separated by `CR` (with `LF`/`CRLF`
|
|
1748
1840
|
* tolerated); the first record must be an `H` header, which declares the
|
|
1749
|
-
* delimiters
|
|
1750
|
-
*
|
|
1841
|
+
* delimiters. Lenient by default: set `strict` to reject any tolerated deviation.
|
|
1842
|
+
*
|
|
1843
|
+
* **Several messages in one stream.** A message runs `H` … `L`, so a stream may carry
|
|
1844
|
+
* more than one, and each `H` declares the delimiters for the records that follow it.
|
|
1845
|
+
* A later header that declares a **different** set is honored from that header onward
|
|
1846
|
+
* and flagged `ASTM_RECORD_DELIMITERS_REDECLARED`; records already read keep the set
|
|
1847
|
+
* they were read with, so a redeclaration never reinterprets earlier bytes. A header
|
|
1848
|
+
* that merely restates the set in force is a no-op and warns nothing. A later header
|
|
1849
|
+
* that cannot declare a usable set keeps the delimiters already in force and warns
|
|
1850
|
+
* `ASTM_RECORD_UNREADABLE_REDECLARATION`: a set is never guessed, and no record is
|
|
1851
|
+
* dropped. Per-header sets are on each {@link HeaderRecord.delimiters};
|
|
1852
|
+
* {@link AstmMessage.delimiters} is the first header's.
|
|
1751
1853
|
*
|
|
1752
1854
|
* @param raw - The de-framed record bytes, as a string or `Uint8Array` (decoded
|
|
1753
1855
|
* latin1 so byte values survive 1:1).
|
|
@@ -1765,9 +1867,9 @@ declare class AstmStrictError extends Error {
|
|
|
1765
1867
|
*/
|
|
1766
1868
|
declare function parseAstmRecords(raw: string | Uint8Array, options?: AstmParseOptions): AstmMessage;
|
|
1767
1869
|
/**
|
|
1768
|
-
* Attach every `C` (comment) record to its parent by position
|
|
1870
|
+
* Attach every `C` (comment) record to its parent by position: the immediately-preceding
|
|
1769
1871
|
* `H`/`P`/`O`/`R` record; consecutive comments share that parent. **Fail-safe:** a comment with no
|
|
1770
|
-
* valid preceding parent is an **orphan
|
|
1872
|
+
* valid preceding parent is an **orphan**, it is attached to the message root (`attachedToRoot:
|
|
1771
1873
|
* true`) with an `ASTM_RECORD_ORPHAN_COMMENT` warning, **never dropped**. Returns a new array; the
|
|
1772
1874
|
* non-comment records pass through unchanged.
|
|
1773
1875
|
*
|
|
@@ -1789,33 +1891,63 @@ declare function parseAstmRecords(raw: string | Uint8Array, options?: AstmParseO
|
|
|
1789
1891
|
declare function attachComments(records: readonly AstmRecord[], warnings: AstmRecordWarning[]): AstmRecord[];
|
|
1790
1892
|
|
|
1791
1893
|
/**
|
|
1792
|
-
* The record-layer **emit** side: {@link serializeAstmRecords}
|
|
1894
|
+
* The record-layer **emit** side: {@link serializeAstmRecords}.
|
|
1793
1895
|
*
|
|
1794
1896
|
* The inverse of `parseAstmRecords`. Where the parser is liberal (vendor quirks
|
|
1795
1897
|
* become warnings), the serializer is **conservative**: it always emits a
|
|
1796
1898
|
* spec-clean stream with the **canonical** delimiter set (`H|\^&`), re-escapes
|
|
1797
1899
|
* every embedded delimiter so an embedded `|`/`^`/`\`/`&` in a value can never
|
|
1798
|
-
* break framing (the exact inverse of the
|
|
1900
|
+
* break framing (the exact inverse of the escape codec), and terminates
|
|
1799
1901
|
* each record with a `CR`.
|
|
1800
1902
|
*
|
|
1801
|
-
* **Round-trip
|
|
1802
|
-
*
|
|
1803
|
-
*
|
|
1804
|
-
*
|
|
1805
|
-
*
|
|
1806
|
-
*
|
|
1807
|
-
*
|
|
1903
|
+
* **Round-trip.** `parseAstmRecords(serializeAstmRecords(msg))` reproduces the modeled
|
|
1904
|
+
* records: the same components, the same typed accessors, and the canonical delimiter
|
|
1905
|
+
* set (a non-canonical source is normalized to `H|\^&` by default). Normalizing sets the
|
|
1906
|
+
* four delimiter **roles**; it does not delete a declaration's surplus characters, so a
|
|
1907
|
+
* canonically-declared header that arrived carrying surplus keeps it on the default path
|
|
1908
|
+
* too.
|
|
1909
|
+
*
|
|
1910
|
+
* **One delimiter set for the whole stream.** Every record (including `H`, `M` and `S`)
|
|
1911
|
+
* is emitted against the delimiters being emitted with, and the header declares that same
|
|
1912
|
+
* set. `M` and `S` carry vendor free-form data and are reproduced **byte-for-byte** from
|
|
1913
|
+
* their preserved `rawLine` whenever a reader using those delimiters would recover exactly
|
|
1914
|
+
* the fields they model, which is the case for any stream already in the emit delimiter
|
|
1915
|
+
* set. When it is not, the record arrived under one delimiter set and is being emitted
|
|
1916
|
+
* under another, the record is re-encoded from its decoded tree instead, so its fields
|
|
1917
|
+
* survive as fields rather than collapsing into one on the next read.
|
|
1918
|
+
*
|
|
1919
|
+
* **Emit follows the model.** Every record type, `H` included, is emitted from its decoded
|
|
1920
|
+
* `AstmField.repeats` tree, so an edit to a record's `fields` is reflected on emit. The one
|
|
1921
|
+
* part of a record that is never taken from the model is the header's delimiter
|
|
1922
|
+
* declaration, and only in the three positions that carry a role: those always state the
|
|
1923
|
+
* repeat, component and escape characters actually in use, whatever the model says. Any
|
|
1924
|
+
* further characters the declaration carried, a reader assigns them no role, come from
|
|
1925
|
+
* the model like everything else, so a header that arrived as `H|\^&#` goes back out as
|
|
1926
|
+
* `H|\^&#` rather than losing the `#`. That holds on the **default** canonical path as
|
|
1927
|
+
* well as when a set is passed explicitly: the surplus of a canonically-declared header
|
|
1928
|
+
* survives normalization, because normalization is about the four roles and the surplus
|
|
1929
|
+
* has none.
|
|
1808
1930
|
*
|
|
1809
1931
|
* **Never break framing.** A component leaf that contains a record terminator
|
|
1810
1932
|
* (`CR`/`LF`) cannot be escaped by the ASTM escape codec (only the four declared
|
|
1811
1933
|
* delimiters have mnemonics), so emitting it would silently corrupt the wire.
|
|
1812
1934
|
* The serializer refuses: an embedded `CR`/`LF` is a typed {@link AstmSerializeError},
|
|
1813
1935
|
* never emitted raw.
|
|
1936
|
+
*
|
|
1937
|
+
* **Check the delimiter set before writing.** Three conditions are required for the
|
|
1938
|
+
* emitted bytes to read back as the records that produced them (each separator exactly
|
|
1939
|
+
* one character, no separator a `CR`/`LF`, no two separators the same character) and a
|
|
1940
|
+
* set failing any of them is a typed {@link AstmSerializeError}
|
|
1941
|
+
* (`ASTM_EMIT_INVALID_DELIMITERS`) rather than output this library's own parser would
|
|
1942
|
+
* reject or silently re-read with a different field tree. These are **necessary**
|
|
1943
|
+
* conditions, not a guarantee of readback: a set can pass all three and still produce a
|
|
1944
|
+
* stream that reads back wrong, a separator that collides with a record's type letter
|
|
1945
|
+
* is the known case, and it corrupts identically with or without this check.
|
|
1814
1946
|
*/
|
|
1815
1947
|
|
|
1816
1948
|
/**
|
|
1817
1949
|
* Thrown by the record/frame emit side when a value cannot be serialized into a
|
|
1818
|
-
* spec-clean stream
|
|
1950
|
+
* spec-clean stream: specifically when a component contains a record terminator
|
|
1819
1951
|
* (`CR`/`LF`), which the ASTM escape codec cannot encode and which would break
|
|
1820
1952
|
* framing if emitted raw. Carries a stable code + positional context, never the
|
|
1821
1953
|
* offending value (PHI discipline).
|
|
@@ -1832,27 +1964,53 @@ declare function attachComments(records: readonly AstmRecord[], warnings: AstmRe
|
|
|
1832
1964
|
* ```
|
|
1833
1965
|
*/
|
|
1834
1966
|
declare class AstmSerializeError extends Error {
|
|
1835
|
-
/**
|
|
1836
|
-
|
|
1967
|
+
/**
|
|
1968
|
+
* Stable discriminant. `ASTM_EMIT_UNENCODABLE_VALUE` for a `CR`/`LF` in a value;
|
|
1969
|
+
* `ASTM_EMIT_INVALID_DELIMITERS` for a delimiter set that cannot be emitted
|
|
1970
|
+
* reversibly.
|
|
1971
|
+
*/
|
|
1972
|
+
readonly code: AstmSerializeErrorCode;
|
|
1837
1973
|
/** 0-based ordinal of the record within the message, when known. */
|
|
1838
1974
|
readonly recordIndex?: number;
|
|
1839
1975
|
/** @internal */
|
|
1840
|
-
constructor(message: string, recordIndex?: number);
|
|
1976
|
+
constructor(message: string, recordIndex?: number, code?: AstmSerializeErrorCode);
|
|
1841
1977
|
}
|
|
1842
1978
|
/**
|
|
1843
|
-
*
|
|
1979
|
+
* The reasons emit refuses rather than writing a stream that cannot be read back.
|
|
1980
|
+
*
|
|
1981
|
+
* - `ASTM_EMIT_UNENCODABLE_VALUE`: a component holds a record terminator
|
|
1982
|
+
* (`CR`/`LF`), which the escape codec has no mnemonic for.
|
|
1983
|
+
* - `ASTM_EMIT_INVALID_DELIMITERS`: the delimiter set to emit against failed one
|
|
1984
|
+
* of the three conditions readback requires (see {@link serializeAstmRecords}).
|
|
1985
|
+
* Those conditions are necessary rather than sufficient, so this code means a
|
|
1986
|
+
* set was rejected, not that every unreversible set is.
|
|
1987
|
+
*
|
|
1988
|
+
* @example
|
|
1989
|
+
* ```ts
|
|
1990
|
+
* import type { AstmSerializeErrorCode } from "@cosyte/astm";
|
|
1991
|
+
* const code: AstmSerializeErrorCode = "ASTM_EMIT_INVALID_DELIMITERS";
|
|
1992
|
+
* ```
|
|
1993
|
+
*/
|
|
1994
|
+
type AstmSerializeErrorCode = "ASTM_EMIT_UNENCODABLE_VALUE" | "ASTM_EMIT_INVALID_DELIMITERS";
|
|
1995
|
+
/**
|
|
1996
|
+
* Escape-encode one component leaf for spec-clean emit: the inverse of
|
|
1844
1997
|
* `decodeEscapes`. The **escape character itself is encoded first** (`&` → `&E&`)
|
|
1845
1998
|
* so a later delimiter substitution can never double-encode the `&` it just
|
|
1846
1999
|
* introduced; then the field / component / repeat delimiters map to their
|
|
1847
2000
|
* mnemonics.
|
|
1848
2001
|
*
|
|
1849
2002
|
* A `CR`/`LF` in the leaf has no escape mnemonic and would break framing, so it
|
|
1850
|
-
* is rejected with an {@link AstmSerializeError} rather than emitted raw
|
|
2003
|
+
* is rejected with an {@link AstmSerializeError} rather than emitted raw, as is a
|
|
2004
|
+
* delimiter set the result could not be read back with.
|
|
1851
2005
|
*
|
|
1852
2006
|
* @param leaf - One already-decoded component string.
|
|
1853
2007
|
* @param d - The delimiters to emit against (canonical for spec-clean output).
|
|
1854
2008
|
* @param recordIndex - The enclosing record's index, for error context.
|
|
1855
2009
|
* @returns The escaped component text.
|
|
2010
|
+
* @throws {@link AstmSerializeError} for a `CR`/`LF` in the leaf
|
|
2011
|
+
* (`ASTM_EMIT_UNENCODABLE_VALUE`), or for a delimiter set failing one of the
|
|
2012
|
+
* three conditions readback requires (`ASTM_EMIT_INVALID_DELIMITERS`), which
|
|
2013
|
+
* are necessary, not sufficient, so a set can pass them and still not reverse.
|
|
1856
2014
|
* @example
|
|
1857
2015
|
* ```ts
|
|
1858
2016
|
* import { encodeComponent, CANONICAL_DELIMITERS } from "@cosyte/astm";
|
|
@@ -1865,15 +2023,20 @@ declare function encodeComponent(leaf: string, d: Delimiters, recordIndex?: numb
|
|
|
1865
2023
|
* terminator). Emits with the given delimiters, defaulting to the canonical set.
|
|
1866
2024
|
*
|
|
1867
2025
|
* The header (`H`) is special-cased: its delimiter-definition field is emitted as
|
|
1868
|
-
* the **literal**
|
|
1869
|
-
*
|
|
1870
|
-
*
|
|
1871
|
-
* `
|
|
2026
|
+
* the **literal** declaration of `d`, never escaped, escaping it would corrupt the
|
|
2027
|
+
* very declaration a reader depends on, followed by any characters the modeled
|
|
2028
|
+
* declaration carried beyond the three a reader takes its roles from. Manufacturer
|
|
2029
|
+
* (`M`) and scientific (`S`) records are reproduced **byte-identically** from their
|
|
2030
|
+
* preserved `rawLine` when they are already in `d`, and re-encoded from their fields
|
|
2031
|
+
* when they are not, so their fields never collapse into one on the next read.
|
|
1872
2032
|
*
|
|
1873
2033
|
* @param record - The record to serialize.
|
|
1874
2034
|
* @param d - The delimiters to emit against; defaults to `H|\^&`.
|
|
1875
2035
|
* @returns The record's wire text, terminator excluded.
|
|
1876
|
-
* @throws {@link AstmSerializeError} when a component contains an unencodable `CR`/`LF
|
|
2036
|
+
* @throws {@link AstmSerializeError} when a component contains an unencodable `CR`/`LF`
|
|
2037
|
+
* (`ASTM_EMIT_UNENCODABLE_VALUE`), or when `d` fails one of the three conditions
|
|
2038
|
+
* readback requires (`ASTM_EMIT_INVALID_DELIMITERS`): necessary conditions, not
|
|
2039
|
+
* sufficient ones, so a set can pass them and still not reverse.
|
|
1877
2040
|
* @example
|
|
1878
2041
|
* ```ts
|
|
1879
2042
|
* import { serializeAstmRecord, parseAstmRecords } from "@cosyte/astm";
|
|
@@ -1884,16 +2047,36 @@ declare function encodeComponent(leaf: string, d: Delimiters, recordIndex?: numb
|
|
|
1884
2047
|
declare function serializeAstmRecord(record: AstmRecord, d?: Delimiters): string;
|
|
1885
2048
|
/**
|
|
1886
2049
|
* Serialize a whole ASTM message (or a bare record list) to a spec-clean,
|
|
1887
|
-
* `CR`-terminated record stream
|
|
2050
|
+
* `CR`-terminated record stream: the inverse of `parseAstmRecords`.
|
|
1888
2051
|
*
|
|
1889
2052
|
* Emit is **conservative**: the canonical `H|\^&` delimiters, every embedded
|
|
1890
2053
|
* delimiter re-escaped, each record closed with a `CR`. A message parsed with
|
|
1891
|
-
* non-canonical delimiters is **normalized** to the canonical set on emit
|
|
1892
|
-
*
|
|
2054
|
+
* non-canonical delimiters is **normalized** to the canonical set on emit: every
|
|
2055
|
+
* record, `M` and `S` included, so the emitted stream is in one delimiter set and
|
|
2056
|
+
* re-parsing it recovers every field. Passing `d` explicitly emits against that set
|
|
2057
|
+
* instead, and the header declares it. Normalization replaces the four delimiter
|
|
2058
|
+
* **roles**; a header declaration carrying characters beyond the three that hold a
|
|
2059
|
+
* role keeps them, on this path as on any other, rather than being truncated.
|
|
2060
|
+
*
|
|
2061
|
+
* **`d` is checked before anything is written.** Each of the four separators must be
|
|
2062
|
+
* exactly one character, none may be a record terminator, and no two may share a
|
|
2063
|
+
* character: otherwise the emitted bytes cannot be read back as the records that
|
|
2064
|
+
* produced them, and emit has no warning channel with which to say so. A set that
|
|
2065
|
+
* fails is an {@link AstmSerializeError} with code `ASTM_EMIT_INVALID_DELIMITERS`.
|
|
2066
|
+
* This is stricter than the parser, which reads some sets it cannot reverse, so
|
|
2067
|
+
* `serializeAstmRecords(msg, msg.delimiters)` can refuse a message that parsed:
|
|
2068
|
+
* in exactly the cases where it used to emit a stream that read back wrong. The
|
|
2069
|
+
* three conditions are necessary rather than sufficient: a set that passes them can
|
|
2070
|
+
* still read back wrong if a separator collides with a record's type letter, which
|
|
2071
|
+
* this check does not claim to catch.
|
|
1893
2072
|
*
|
|
1894
2073
|
* @param input - A parsed {@link AstmMessage} or a list of {@link AstmRecord}s.
|
|
2074
|
+
* @param d - The delimiters to emit against; defaults to the canonical `H|\^&` set.
|
|
1895
2075
|
* @returns The serialized record stream (`CR` after every record).
|
|
1896
|
-
* @throws {@link AstmSerializeError} when a component contains an unencodable `CR`/`LF
|
|
2076
|
+
* @throws {@link AstmSerializeError} when a component contains an unencodable `CR`/`LF`
|
|
2077
|
+
* (`ASTM_EMIT_UNENCODABLE_VALUE`), or when `d` fails one of the three conditions
|
|
2078
|
+
* readback requires (`ASTM_EMIT_INVALID_DELIMITERS`): necessary conditions, not
|
|
2079
|
+
* sufficient ones, so a set can pass them and still not reverse.
|
|
1897
2080
|
* @example
|
|
1898
2081
|
* ```ts
|
|
1899
2082
|
* import { parseAstmRecords, serializeAstmRecords } from "@cosyte/astm";
|
|
@@ -1910,6 +2093,10 @@ declare function serializeAstmRecords(input: AstmMessage | readonly AstmRecord[]
|
|
|
1910
2093
|
* @param field - The field to serialize.
|
|
1911
2094
|
* @param d - The delimiters to emit against; defaults to `H|\^&`.
|
|
1912
2095
|
* @returns The escaped field text.
|
|
2096
|
+
* @throws {@link AstmSerializeError} when a component contains an unencodable `CR`/`LF`
|
|
2097
|
+
* (`ASTM_EMIT_UNENCODABLE_VALUE`), or when `d` fails one of the three conditions
|
|
2098
|
+
* readback requires (`ASTM_EMIT_INVALID_DELIMITERS`): necessary conditions, not
|
|
2099
|
+
* sufficient ones, so a set can pass them and still not reverse.
|
|
1913
2100
|
* @example
|
|
1914
2101
|
* ```ts
|
|
1915
2102
|
* import { serializeField, tokenizeRecord, CANONICAL_DELIMITERS } from "@cosyte/astm";
|
|
@@ -1920,20 +2107,20 @@ declare function serializeAstmRecords(input: AstmMessage | readonly AstmRecord[]
|
|
|
1920
2107
|
declare function serializeField(field: AstmField, d?: Delimiters): string;
|
|
1921
2108
|
|
|
1922
2109
|
/**
|
|
1923
|
-
* The record-layer **builder**: {@link buildAstmMessage}
|
|
2110
|
+
* The record-layer **builder**: {@link buildAstmMessage}.
|
|
1924
2111
|
*
|
|
1925
2112
|
* A builder produces spec-clean E1394 records **by construction** from typed
|
|
1926
2113
|
* caller input, under two disciplines:
|
|
1927
2114
|
*
|
|
1928
2115
|
* - **Never fabricate.** A builder emits **only** the values the caller supplied.
|
|
1929
2116
|
* An unsupplied field is left **empty**, never filled with a default clinical
|
|
1930
|
-
* value
|
|
2117
|
+
* value: no defaulted result status, abnormal flag, units, value, or patient
|
|
1931
2118
|
* identifier. (Leaving a field empty is not a guess; inventing an `F` status or
|
|
1932
2119
|
* an `N` flag would be.) A consumer that parses a built result whose status the
|
|
1933
2120
|
* caller omitted reads `status.meaning === "unspecified"`, never `"final"`.
|
|
1934
2121
|
* - **Compute, don't guess, the structure.** The record type letters, the
|
|
1935
2122
|
* delimiter declaration (canonical `H|\^&`), the message terminator (`L`), and
|
|
1936
|
-
* the per-record sequence counters are **structural
|
|
2123
|
+
* the per-record sequence counters are **structural**: computed by the builder,
|
|
1937
2124
|
* not clinical values. The caller may override any sequence number.
|
|
1938
2125
|
*
|
|
1939
2126
|
* Every value is escape-encoded on emit (via the serializer), so an embedded
|
|
@@ -1946,98 +2133,98 @@ interface PatientNameInput {
|
|
|
1946
2133
|
readonly first?: string;
|
|
1947
2134
|
readonly middle?: string;
|
|
1948
2135
|
}
|
|
1949
|
-
/** Input for a `P` (patient) record. The three IDs stay distinct
|
|
2136
|
+
/** Input for a `P` (patient) record. The three IDs stay distinct: none defaults from another. */
|
|
1950
2137
|
interface PatientInput {
|
|
1951
2138
|
readonly type: "P";
|
|
1952
2139
|
/** Structural sequence number; auto-computed when omitted. */
|
|
1953
2140
|
readonly seq?: string;
|
|
1954
|
-
/** Field 3
|
|
2141
|
+
/** Field 3: practice-assigned patient ID. */
|
|
1955
2142
|
readonly practiceAssignedId?: string;
|
|
1956
|
-
/** Field 4
|
|
2143
|
+
/** Field 4: laboratory-assigned patient ID. */
|
|
1957
2144
|
readonly laboratoryAssignedId?: string;
|
|
1958
|
-
/** Field 5
|
|
2145
|
+
/** Field 5: a third patient identifier. */
|
|
1959
2146
|
readonly patientIdThree?: string;
|
|
1960
|
-
/** Field 6
|
|
2147
|
+
/** Field 6: patient name (`Last^First^Middle`). */
|
|
1961
2148
|
readonly name?: PatientNameInput;
|
|
1962
|
-
/** Field 7
|
|
2149
|
+
/** Field 7: mother's maiden name. */
|
|
1963
2150
|
readonly mothersMaidenName?: string;
|
|
1964
|
-
/** Field 8
|
|
2151
|
+
/** Field 8: birthdate (`YYYYMMDDHHMMSS`), emitted verbatim, never reformatted. */
|
|
1965
2152
|
readonly birthDate?: string;
|
|
1966
|
-
/** Field 9
|
|
2153
|
+
/** Field 9: sex, emitted verbatim (never defaulted). */
|
|
1967
2154
|
readonly sex?: string;
|
|
1968
2155
|
}
|
|
1969
2156
|
/** Input for an `O` (order) record. */
|
|
1970
2157
|
interface OrderInput {
|
|
1971
2158
|
readonly type: "O";
|
|
1972
2159
|
readonly seq?: string;
|
|
1973
|
-
/** Field 3
|
|
2160
|
+
/** Field 3: specimen / accession ID. */
|
|
1974
2161
|
readonly specimenId?: string;
|
|
1975
|
-
/** Field 4
|
|
2162
|
+
/** Field 4: instrument specimen ID. */
|
|
1976
2163
|
readonly instrumentSpecimenId?: string;
|
|
1977
|
-
/** Field 5
|
|
2164
|
+
/** Field 5: Universal Test ID components (verbatim, in order; e.g. `["", "", "", "687"]`). */
|
|
1978
2165
|
readonly universalTestId?: readonly string[];
|
|
1979
|
-
/** Field 6
|
|
2166
|
+
/** Field 6: priority, emitted verbatim. */
|
|
1980
2167
|
readonly priority?: string;
|
|
1981
|
-
/** Field 12
|
|
2168
|
+
/** Field 12: action code, emitted verbatim. */
|
|
1982
2169
|
readonly actionCode?: string;
|
|
1983
|
-
/** Field 26
|
|
2170
|
+
/** Field 26: report type, emitted verbatim. */
|
|
1984
2171
|
readonly reportType?: string;
|
|
1985
2172
|
}
|
|
1986
2173
|
/** Input for an `R` (result) record. No clinical field is defaulted; unsupplied ⇒ empty. */
|
|
1987
2174
|
interface ResultInput {
|
|
1988
2175
|
readonly type: "R";
|
|
1989
2176
|
readonly seq?: string;
|
|
1990
|
-
/** Field 3
|
|
2177
|
+
/** Field 3: Universal Test ID components (verbatim). */
|
|
1991
2178
|
readonly universalTestId?: readonly string[];
|
|
1992
|
-
/** Field 4
|
|
2179
|
+
/** Field 4: the measured value, emitted verbatim (never defaulted). */
|
|
1993
2180
|
readonly value?: string;
|
|
1994
|
-
/** Field 5
|
|
2181
|
+
/** Field 5: units (vendor free text; never defaulted, guessed, or converted). */
|
|
1995
2182
|
readonly units?: string;
|
|
1996
|
-
/** Field 6
|
|
2183
|
+
/** Field 6: reference range, emitted verbatim. */
|
|
1997
2184
|
readonly referenceRange?: string;
|
|
1998
|
-
/** Field 7
|
|
2185
|
+
/** Field 7: abnormal flags, emitted verbatim (never defaulted to `N`). */
|
|
1999
2186
|
readonly abnormalFlags?: string;
|
|
2000
|
-
/** Field 9
|
|
2187
|
+
/** Field 9: result status, emitted verbatim (never defaulted to `F`). */
|
|
2001
2188
|
readonly resultStatus?: string;
|
|
2002
|
-
/** Field 11
|
|
2189
|
+
/** Field 11: operator. */
|
|
2003
2190
|
readonly operator?: string;
|
|
2004
|
-
/** Field 12
|
|
2191
|
+
/** Field 12: test-started timestamp (`YYYYMMDDHHMMSS`), verbatim. */
|
|
2005
2192
|
readonly startedAt?: string;
|
|
2006
|
-
/** Field 13
|
|
2193
|
+
/** Field 13: test-completed timestamp, verbatim. */
|
|
2007
2194
|
readonly completedAt?: string;
|
|
2008
|
-
/** Field 14
|
|
2195
|
+
/** Field 14: instrument identifier. */
|
|
2009
2196
|
readonly instrument?: string;
|
|
2010
2197
|
}
|
|
2011
2198
|
/** Input for a `C` (comment) record. */
|
|
2012
2199
|
interface CommentInput {
|
|
2013
2200
|
readonly type: "C";
|
|
2014
2201
|
readonly seq?: string;
|
|
2015
|
-
/** Field 3
|
|
2202
|
+
/** Field 3: comment source. */
|
|
2016
2203
|
readonly source?: string;
|
|
2017
|
-
/** Field 4
|
|
2204
|
+
/** Field 4: comment text (a single component). Use {@link CommentInput.textComponents} for a structured comment. */
|
|
2018
2205
|
readonly text?: string;
|
|
2019
|
-
/** Field 4
|
|
2206
|
+
/** Field 4: comment text as explicit components (takes precedence over `text` when set). */
|
|
2020
2207
|
readonly textComponents?: readonly string[];
|
|
2021
|
-
/** Field 5
|
|
2208
|
+
/** Field 5: comment type code, emitted verbatim. */
|
|
2022
2209
|
readonly commentType?: string;
|
|
2023
2210
|
}
|
|
2024
2211
|
/** Input for a `Q` (request-information) record. */
|
|
2025
2212
|
interface QueryInput {
|
|
2026
2213
|
readonly type: "Q";
|
|
2027
2214
|
readonly seq?: string;
|
|
2028
|
-
/** Field 3
|
|
2215
|
+
/** Field 3: starting range ID, emitted verbatim. */
|
|
2029
2216
|
readonly startingRangeId?: string;
|
|
2030
|
-
/** Field 4
|
|
2217
|
+
/** Field 4: ending range ID, emitted verbatim. */
|
|
2031
2218
|
readonly endingRangeId?: string;
|
|
2032
|
-
/** Field 5
|
|
2219
|
+
/** Field 5: Universal Test ID components; ignored when {@link QueryInput.queriesAllTests} is set. */
|
|
2033
2220
|
readonly universalTestId?: readonly string[];
|
|
2034
|
-
/** Field 5
|
|
2221
|
+
/** Field 5: emit the literal `ALL` universal-query keyword instead of a Universal Test ID. */
|
|
2035
2222
|
readonly queriesAllTests?: boolean;
|
|
2036
|
-
/** Field 13
|
|
2223
|
+
/** Field 13: request-information status, emitted verbatim. */
|
|
2037
2224
|
readonly requestInformationStatus?: string;
|
|
2038
2225
|
}
|
|
2039
2226
|
/**
|
|
2040
|
-
* Input for an `M` (manufacturer) or `S` (scientific) record
|
|
2227
|
+
* Input for an `M` (manufacturer) or `S` (scientific) record: vendor-defined
|
|
2041
2228
|
* free-form data. Emitted verbatim from the caller's fields; never interpreted.
|
|
2042
2229
|
*/
|
|
2043
2230
|
interface VerbatimInput {
|
|
@@ -2059,7 +2246,7 @@ interface MessageInput {
|
|
|
2059
2246
|
/** The body records, in order. `H` and `L` are supplied by the builder. */
|
|
2060
2247
|
readonly records: readonly AstmRecordInput[];
|
|
2061
2248
|
/**
|
|
2062
|
-
* Field 3 of the auto-appended `L` (terminator) record
|
|
2249
|
+
* Field 3 of the auto-appended `L` (terminator) record: the termination code
|
|
2063
2250
|
* (e.g. `N` normal). Omitted by default (not defaulted to a value the caller
|
|
2064
2251
|
* did not choose); the `L` seq is always emitted.
|
|
2065
2252
|
*/
|
|
@@ -2071,7 +2258,7 @@ interface MessageInput {
|
|
|
2071
2258
|
* The builder emits the canonical `H|\^&` header (plus any header fields), then
|
|
2072
2259
|
* every body record with each supplied field at its correct 1-based position and
|
|
2073
2260
|
* every value escape-encoded, then an `L` terminator. **Nothing clinical is
|
|
2074
|
-
* defaulted**
|
|
2261
|
+
* defaulted** (an unsupplied field is empty, never a guessed value) and the
|
|
2075
2262
|
* structural pieces (delimiters, record types, sequence counters, terminator) are
|
|
2076
2263
|
* computed. The result parses back to an equal message: build → parse fidelity by
|
|
2077
2264
|
* construction.
|
|
@@ -2091,20 +2278,32 @@ interface MessageInput {
|
|
|
2091
2278
|
declare function buildAstmMessage(input: MessageInput): string;
|
|
2092
2279
|
|
|
2093
2280
|
/**
|
|
2094
|
-
* Typed extractors over an {@link AstmMessage}
|
|
2281
|
+
* Typed extractors over an {@link AstmMessage}: the one-line "pull the value out"
|
|
2095
2282
|
* surface that is the package's north star.
|
|
2096
2283
|
*
|
|
2097
|
-
* They are thin
|
|
2098
|
-
* never
|
|
2099
|
-
*
|
|
2100
|
-
*
|
|
2284
|
+
* They are thin and immutable: they read the already-parsed model and never re-parse,
|
|
2285
|
+
* never mutate, and never fabricate.
|
|
2286
|
+
*
|
|
2287
|
+
* **They are scoped to a single message, and they enforce it.** An `AstmMessage` models a
|
|
2288
|
+
* whole record **stream**, which may carry several `H` … `L` messages back to back. These
|
|
2289
|
+
* extractors read across the whole stream, so on a multi-message stream `patient()` used to
|
|
2290
|
+
* answer with the first `P` in the stream while `results()` answered with every `R` in it:
|
|
2291
|
+
* pairing the two attributed one patient's results to another, silently and with no warning.
|
|
2292
|
+
* They now **refuse** that stream: an {@link AstmAmbiguousStreamError} instead of a confident
|
|
2293
|
+
* wrong answer, which is the same rule the rest of this parser follows. {@link messages} is
|
|
2294
|
+
* the path that works on any stream, and `commentsFor()` keeps working on every stream
|
|
2295
|
+
* because its parent record already names the message.
|
|
2296
|
+
*
|
|
2297
|
+
* @see {@link messages}
|
|
2101
2298
|
*/
|
|
2102
2299
|
|
|
2103
2300
|
/**
|
|
2104
2301
|
* Every result (`R`) record in the message, in wire order.
|
|
2105
2302
|
*
|
|
2106
|
-
* @param msg - A parsed message.
|
|
2303
|
+
* @param msg - A parsed **single-message** stream.
|
|
2107
2304
|
* @returns The result records (possibly empty).
|
|
2305
|
+
* @throws {@link AstmAmbiguousStreamError} (`ASTM_AMBIGUOUS_MULTI_MESSAGE`) when the stream
|
|
2306
|
+
* carries more than one message: see {@link messages}.
|
|
2108
2307
|
* @example
|
|
2109
2308
|
* ```ts
|
|
2110
2309
|
* import { parseAstmRecords, results } from "@cosyte/astm";
|
|
@@ -2114,13 +2313,18 @@ declare function buildAstmMessage(input: MessageInput): string;
|
|
|
2114
2313
|
*/
|
|
2115
2314
|
declare function results(msg: AstmMessage): readonly ResultRecord[];
|
|
2116
2315
|
/**
|
|
2117
|
-
* The
|
|
2316
|
+
* The message's patient (`P`) record, or `undefined` when the message carries none.
|
|
2118
2317
|
*
|
|
2119
|
-
*
|
|
2120
|
-
*
|
|
2318
|
+
* This is the identity a result files against, so it is answered only when the stream
|
|
2319
|
+
* determines exactly one. It **refuses** the two shapes where "the patient" is a guess: a
|
|
2320
|
+
* stream carrying several messages, and a single message carrying several `P` records. In
|
|
2321
|
+
* both, the old behaviour, the first `P` in the stream, is the wrong-patient path itself.
|
|
2121
2322
|
*
|
|
2122
|
-
* @param msg - A parsed message.
|
|
2123
|
-
* @returns The
|
|
2323
|
+
* @param msg - A parsed single-message stream carrying at most one patient.
|
|
2324
|
+
* @returns The patient record, or `undefined` when the message carries none.
|
|
2325
|
+
* @throws {@link AstmAmbiguousStreamError} (`ASTM_AMBIGUOUS_MULTI_MESSAGE`) when the stream
|
|
2326
|
+
* carries more than one message, or (`ASTM_AMBIGUOUS_MULTI_PATIENT`) when its one message
|
|
2327
|
+
* carries more than one `P`. Use {@link messages} and read `patients` in both cases.
|
|
2124
2328
|
* @example
|
|
2125
2329
|
* ```ts
|
|
2126
2330
|
* import { parseAstmRecords, patient } from "@cosyte/astm";
|
|
@@ -2132,8 +2336,10 @@ declare function patient(msg: AstmMessage): PatientRecord | undefined;
|
|
|
2132
2336
|
/**
|
|
2133
2337
|
* Every order (`O`) record in the message, in wire order.
|
|
2134
2338
|
*
|
|
2135
|
-
* @param msg - A parsed message.
|
|
2339
|
+
* @param msg - A parsed **single-message** stream.
|
|
2136
2340
|
* @returns The order records (possibly empty).
|
|
2341
|
+
* @throws {@link AstmAmbiguousStreamError} (`ASTM_AMBIGUOUS_MULTI_MESSAGE`) when the stream
|
|
2342
|
+
* carries more than one message: see {@link messages}.
|
|
2137
2343
|
* @example
|
|
2138
2344
|
* ```ts
|
|
2139
2345
|
* import { parseAstmRecords, orders } from "@cosyte/astm";
|
|
@@ -2145,10 +2351,12 @@ declare function orders(msg: AstmMessage): readonly OrderRecord[];
|
|
|
2145
2351
|
/**
|
|
2146
2352
|
* Every comment (`C`) record in the message, in wire order. Each carries the
|
|
2147
2353
|
* `parentIndex` of the `H`/`P`/`O`/`R` it attaches to (or `attachedToRoot` when
|
|
2148
|
-
* it is an orphan)
|
|
2354
|
+
* it is an orphan): use {@link commentsFor} to get the comments of one record.
|
|
2149
2355
|
*
|
|
2150
|
-
* @param msg - A parsed message.
|
|
2356
|
+
* @param msg - A parsed **single-message** stream.
|
|
2151
2357
|
* @returns The comment records (possibly empty).
|
|
2358
|
+
* @throws {@link AstmAmbiguousStreamError} (`ASTM_AMBIGUOUS_MULTI_MESSAGE`) when the stream
|
|
2359
|
+
* carries more than one message: see {@link messages}.
|
|
2152
2360
|
* @example
|
|
2153
2361
|
* ```ts
|
|
2154
2362
|
* import { parseAstmRecords, comments } from "@cosyte/astm";
|
|
@@ -2159,8 +2367,11 @@ declare function orders(msg: AstmMessage): readonly OrderRecord[];
|
|
|
2159
2367
|
declare function comments(msg: AstmMessage): readonly CommentRecord[];
|
|
2160
2368
|
/**
|
|
2161
2369
|
* The comment (`C`) records attached to a given parent record, in wire order.
|
|
2370
|
+
*
|
|
2371
|
+
* Unlike the other extractors this works on **any** stream, single- or multi-message: the
|
|
2372
|
+
* parent record it is handed already names the message, so there is nothing to disambiguate.
|
|
2162
2373
|
* Returns the comments whose `parentIndex` is that record's `recordIndex`, so a
|
|
2163
|
-
* comment carrying (e.g.) QC context is read against the record it modifies
|
|
2374
|
+
* comment carrying (e.g.) QC context is read against the record it modifies:
|
|
2164
2375
|
* never floated to the wrong one.
|
|
2165
2376
|
*
|
|
2166
2377
|
* @param msg - A parsed message.
|
|
@@ -2176,11 +2387,13 @@ declare function comments(msg: AstmMessage): readonly CommentRecord[];
|
|
|
2176
2387
|
declare function commentsFor(msg: AstmMessage, parent: AstmRecord): readonly CommentRecord[];
|
|
2177
2388
|
/**
|
|
2178
2389
|
* Every request-information (`Q`) record in the message, in wire order. A non-empty
|
|
2179
|
-
* result means the message is a **host-query request**, not a result set
|
|
2390
|
+
* result means the message is a **host-query request**, not a result set: see
|
|
2180
2391
|
* {@link AstmMessage.classification} (`isHostQueryRequest`).
|
|
2181
2392
|
*
|
|
2182
|
-
* @param msg - A parsed message.
|
|
2393
|
+
* @param msg - A parsed **single-message** stream.
|
|
2183
2394
|
* @returns The query records (possibly empty).
|
|
2395
|
+
* @throws {@link AstmAmbiguousStreamError} (`ASTM_AMBIGUOUS_MULTI_MESSAGE`) when the stream
|
|
2396
|
+
* carries more than one message: see {@link messages}.
|
|
2184
2397
|
* @example
|
|
2185
2398
|
* ```ts
|
|
2186
2399
|
* import { parseAstmRecords, query } from "@cosyte/astm";
|
|
@@ -2190,9 +2403,195 @@ declare function commentsFor(msg: AstmMessage, parent: AstmRecord): readonly Com
|
|
|
2190
2403
|
*/
|
|
2191
2404
|
declare function query(msg: AstmMessage): readonly QueryRecord[];
|
|
2192
2405
|
|
|
2406
|
+
/**
|
|
2407
|
+
* Message grouping: the safe replacement for the stream-scoped extractors.
|
|
2408
|
+
*
|
|
2409
|
+
* A parsed {@link AstmMessage} models a whole **record stream**, and a stream may carry
|
|
2410
|
+
* several messages back to back: a message runs from an `H` header to its `L` terminator.
|
|
2411
|
+
* The flat extractors in `./extractors.js` read the whole stream, so on a multi-message
|
|
2412
|
+
* stream `patient(msg)` answered with the first `P` in the stream while `results(msg)`
|
|
2413
|
+
* answered with every `R` in it: pairing the two, which is exactly what the package's
|
|
2414
|
+
* one-line north star does, attributed one patient's results to another. It needed no
|
|
2415
|
+
* redeclaration and no unusual delimiters, and it warned about nothing.
|
|
2416
|
+
*
|
|
2417
|
+
* {@link messages} is the fix: it splits the stream into the messages it actually
|
|
2418
|
+
* contains, and each one carries only its own records, so a patient and a result are only
|
|
2419
|
+
* ever paired inside the message that carried both.
|
|
2420
|
+
*
|
|
2421
|
+
* **Where a message begins.** A new message opens at every `H` record and runs to the
|
|
2422
|
+
* record before the next `H` (or the end of the stream). That is deliberately the *same*
|
|
2423
|
+
* boundary the parser already uses to scope delimiters: the active delimiter set is
|
|
2424
|
+
* re-read at each `H` and governs that header and the records that follow it, so message
|
|
2425
|
+
* boundaries and delimiter scope cannot disagree. The `L` terminator **closes** a message
|
|
2426
|
+
* but does not open a scope, so anything between an `L` and the next `H` stays with the
|
|
2427
|
+
* message its header opened rather than being dropped. Grouping is therefore **total**:
|
|
2428
|
+
* every record of the stream lands in exactly one message, none duplicated, none lost.
|
|
2429
|
+
*
|
|
2430
|
+
* **What the standard grounds, and what it does not.** CLSI LIS02-A2 §2 is definitional
|
|
2431
|
+
* about the unit itself: a message is bounded by the `H` record at one end and the `L`
|
|
2432
|
+
* record at the other. That clause is in CLSI's free sample and is the whole of what is
|
|
2433
|
+
* claimed from the standard here. It does **not** settle which record *opens* a new scope
|
|
2434
|
+
* partway through a stream, so treating a second `H` as the start of the next message
|
|
2435
|
+
* (rather than, say, requiring an intervening `L`) is a reasoned choice, not a citation. It
|
|
2436
|
+
* is made this way because it is the boundary this parser already enforces for delimiters,
|
|
2437
|
+
* and because it never drops a record.
|
|
2438
|
+
*
|
|
2439
|
+
* **What is deliberately not modeled.** ASTM's within-message record hierarchy, which `P`
|
|
2440
|
+
* a given `R` files against when one message carries several patients, is not modeled
|
|
2441
|
+
* here. The clauses that would ground it (the message-level structure diagram, and the `P`
|
|
2442
|
+
* sequence-number rule) are withheld from the free sample and are paywalled, so rather than
|
|
2443
|
+
* guess a scoping rule this layer refuses to answer: a message carrying more than one `P`
|
|
2444
|
+
* leaves {@link AstmStreamMessage.patient} `undefined` and surfaces all of them on
|
|
2445
|
+
* {@link AstmStreamMessage.patients}. Multi-patient messages are real, at least one
|
|
2446
|
+
* openly-published vendor interface grammar makes the patient group repeatable on the
|
|
2447
|
+
* download direction, so this is a deferral with a known shape, not a claim that the case
|
|
2448
|
+
* does not arise, and not a claim that the standard is silent about it.
|
|
2449
|
+
*
|
|
2450
|
+
* **The OSS corpus was checked and offers no prior art.** It is informative here, unlike
|
|
2451
|
+
* for delimiter questions, but only negatively: the two commonly-cited reference
|
|
2452
|
+
* implementations both dissolve the message unit. One dispatches records to per-type
|
|
2453
|
+
* callbacks one at a time with no carried state, leaving the grouping to the integrator;
|
|
2454
|
+
* the other merges every message of a transport session into one envelope of flat
|
|
2455
|
+
* per-record-type buckets, from which the association cannot be recovered at all. A flat
|
|
2456
|
+
* model is the incumbent shape, and the misattribution above is what it costs.
|
|
2457
|
+
*/
|
|
2458
|
+
|
|
2459
|
+
/**
|
|
2460
|
+
* Stable codes for the two ways a flat, stream-scoped accessor can fail to name a single
|
|
2461
|
+
* answer. Renaming a code is a **breaking change**.
|
|
2462
|
+
*
|
|
2463
|
+
* @example
|
|
2464
|
+
* ```ts
|
|
2465
|
+
* import { parseAstmRecords, patient, AMBIGUOUS_CODES, AstmAmbiguousStreamError } from "@cosyte/astm";
|
|
2466
|
+
* try {
|
|
2467
|
+
* patient(parseAstmRecords(raw));
|
|
2468
|
+
* } catch (err) {
|
|
2469
|
+
* if (err instanceof AstmAmbiguousStreamError) {
|
|
2470
|
+
* err.code === AMBIGUOUS_CODES.ASTM_AMBIGUOUS_MULTI_MESSAGE;
|
|
2471
|
+
* }
|
|
2472
|
+
* }
|
|
2473
|
+
* ```
|
|
2474
|
+
*/
|
|
2475
|
+
declare const AMBIGUOUS_CODES: {
|
|
2476
|
+
/** The stream carries more than one `H` … `L` message, so a stream-wide answer spans patients. */
|
|
2477
|
+
readonly ASTM_AMBIGUOUS_MULTI_MESSAGE: "ASTM_AMBIGUOUS_MULTI_MESSAGE";
|
|
2478
|
+
/** The one message in the stream carries more than one `P`, so "the patient" is not determined. */
|
|
2479
|
+
readonly ASTM_AMBIGUOUS_MULTI_PATIENT: "ASTM_AMBIGUOUS_MULTI_PATIENT";
|
|
2480
|
+
};
|
|
2481
|
+
/** A value from {@link AMBIGUOUS_CODES}: the discriminant on {@link AstmAmbiguousStreamError}. */
|
|
2482
|
+
type AmbiguousCode = (typeof AMBIGUOUS_CODES)[keyof typeof AMBIGUOUS_CODES];
|
|
2483
|
+
/**
|
|
2484
|
+
* Thrown by a flat extractor when the stream it was handed does not determine a single
|
|
2485
|
+
* answer: either because it carries several messages, or because its one message carries
|
|
2486
|
+
* several patients.
|
|
2487
|
+
*
|
|
2488
|
+
* The throw **is** the fix. A caller who gets this error is strictly better off than one
|
|
2489
|
+
* who silently received another patient's results, and the remedy is mechanical: walk
|
|
2490
|
+
* {@link messages} and read each message's own records.
|
|
2491
|
+
*
|
|
2492
|
+
* **PHI:** carries a stable code, a position, and two counts. Never a field value, never a
|
|
2493
|
+
* patient identifier.
|
|
2494
|
+
*
|
|
2495
|
+
* @example
|
|
2496
|
+
* ```ts
|
|
2497
|
+
* import { parseAstmRecords, messages, results, AstmAmbiguousStreamError } from "@cosyte/astm";
|
|
2498
|
+
* const msg = parseAstmRecords(raw);
|
|
2499
|
+
* try {
|
|
2500
|
+
* results(msg);
|
|
2501
|
+
* } catch (err) {
|
|
2502
|
+
* if (err instanceof AstmAmbiguousStreamError) {
|
|
2503
|
+
* for (const m of messages(msg)) m.results.length;
|
|
2504
|
+
* }
|
|
2505
|
+
* }
|
|
2506
|
+
* ```
|
|
2507
|
+
*/
|
|
2508
|
+
declare class AstmAmbiguousStreamError extends Error {
|
|
2509
|
+
/** The stable discriminant: see {@link AMBIGUOUS_CODES}. */
|
|
2510
|
+
readonly code: AmbiguousCode;
|
|
2511
|
+
/** Where the ambiguity became visible: the second header, or the second `P`. Value-free. */
|
|
2512
|
+
readonly position: AstmPosition;
|
|
2513
|
+
/** How many `H` … `L` messages the stream carries. */
|
|
2514
|
+
readonly messageCount: number;
|
|
2515
|
+
/** How many `P` records the message in question carries. */
|
|
2516
|
+
readonly patientCount: number;
|
|
2517
|
+
/** @internal */
|
|
2518
|
+
constructor(code: AmbiguousCode, message: string, position: AstmPosition, counts: {
|
|
2519
|
+
readonly messageCount: number;
|
|
2520
|
+
readonly patientCount: number;
|
|
2521
|
+
});
|
|
2522
|
+
}
|
|
2523
|
+
/**
|
|
2524
|
+
* One `H` … `L` message inside a parsed stream, with its own records and nothing else.
|
|
2525
|
+
*
|
|
2526
|
+
* This is the unit the flat extractors were always assumed to be reading. `AstmMessage`
|
|
2527
|
+
* models the whole stream; this models a message within it.
|
|
2528
|
+
*/
|
|
2529
|
+
interface AstmStreamMessage {
|
|
2530
|
+
/** 0-based ordinal of this message within the stream. */
|
|
2531
|
+
readonly index: number;
|
|
2532
|
+
/** This message's header record. */
|
|
2533
|
+
readonly header: HeaderRecord;
|
|
2534
|
+
/**
|
|
2535
|
+
* The delimiter set in force for this message: the header's own resolved set, which is
|
|
2536
|
+
* the set its records were actually read with. When a later header's declaration was
|
|
2537
|
+
* unusable, this is the set that stayed in force, never a guessed one.
|
|
2538
|
+
*/
|
|
2539
|
+
readonly delimiters: Delimiters;
|
|
2540
|
+
/** Every record of this message in wire order, the header first. */
|
|
2541
|
+
readonly records: readonly AstmRecord[];
|
|
2542
|
+
/**
|
|
2543
|
+
* This message's patient, but **only when the message determines one**: the single `P`
|
|
2544
|
+
* when it carries exactly one, and `undefined` when it carries none **or** several.
|
|
2545
|
+
* `patients.length` distinguishes those two cases; there is no third meaning.
|
|
2546
|
+
*
|
|
2547
|
+
* A message carrying several patients is not answered with the first one, because
|
|
2548
|
+
* "the first `P`" is precisely the guess that files a result against the wrong person.
|
|
2549
|
+
*/
|
|
2550
|
+
readonly patient: PatientRecord | undefined;
|
|
2551
|
+
/** Every `P` record in this message, in wire order (usually zero or one). */
|
|
2552
|
+
readonly patients: readonly PatientRecord[];
|
|
2553
|
+
/** Every `R` (result) record in this message, in wire order. */
|
|
2554
|
+
readonly results: readonly ResultRecord[];
|
|
2555
|
+
/** Every `O` (order) record in this message, in wire order. */
|
|
2556
|
+
readonly orders: readonly OrderRecord[];
|
|
2557
|
+
/** Every `C` (comment) record in this message, in wire order. */
|
|
2558
|
+
readonly comments: readonly CommentRecord[];
|
|
2559
|
+
/**
|
|
2560
|
+
* Every `Q` (request-information) record in this message, in wire order.
|
|
2561
|
+
*
|
|
2562
|
+
* For this message's host-query classification, call `classifyMessage(m.records)`. The
|
|
2563
|
+
* `classification` on the parsed model is folded over the **whole stream**, so it is not
|
|
2564
|
+
* per-message and is not mirrored here; deriving it from a message's own records is.
|
|
2565
|
+
*/
|
|
2566
|
+
readonly queries: readonly QueryRecord[];
|
|
2567
|
+
}
|
|
2568
|
+
/**
|
|
2569
|
+
* Split a parsed stream into the `H` … `L` messages it carries.
|
|
2570
|
+
*
|
|
2571
|
+
* This is the safe path and it never throws: a single-message stream yields exactly one
|
|
2572
|
+
* entry, and pairing a patient with a result inside one entry is correct by construction.
|
|
2573
|
+
*
|
|
2574
|
+
* Returns an **array**, not an iterator: the stream is already fully parsed and frozen, so
|
|
2575
|
+
* there is no work to defer, and every other extractor in this package returns a
|
|
2576
|
+
* `readonly` array.
|
|
2577
|
+
*
|
|
2578
|
+
* @param msg - A parsed stream.
|
|
2579
|
+
* @returns The messages, in wire order; never empty for a parsed stream.
|
|
2580
|
+
* @example
|
|
2581
|
+
* ```ts
|
|
2582
|
+
* import { parseAstmRecords, messages } from "@cosyte/astm";
|
|
2583
|
+
* const stream = parseAstmRecords("H|\\^&\rP|1|PRAC-1\rR|1|^^^687|1.0|U/L||N||F\rL|1\r");
|
|
2584
|
+
* for (const m of messages(stream)) {
|
|
2585
|
+
* m.patient?.practiceAssignedId; // "PRAC-1"
|
|
2586
|
+
* m.results[0]?.value; // "1.0"
|
|
2587
|
+
* }
|
|
2588
|
+
* ```
|
|
2589
|
+
*/
|
|
2590
|
+
declare function messages(msg: AstmMessage): readonly AstmStreamMessage[];
|
|
2591
|
+
|
|
2193
2592
|
/**
|
|
2194
2593
|
* The **host-query flow**: classify a message as a request, a result upload, an
|
|
2195
|
-
* order download, or indeterminate
|
|
2594
|
+
* order download, or indeterminate.
|
|
2196
2595
|
*
|
|
2197
2596
|
* On real analyzers the host-query mode is first-class and sometimes mandatory
|
|
2198
2597
|
* (on the Roche cobas 4800 there is *no* results-sending-only option): the
|
|
@@ -2201,14 +2600,14 @@ declare function query(msg: AstmMessage): readonly QueryRecord[];
|
|
|
2201
2600
|
* order flow, so this module makes the distinction **explicit**:
|
|
2202
2601
|
*
|
|
2203
2602
|
* **The `Q` record dominates.** Any message carrying a `Q` (request-information)
|
|
2204
|
-
* record is classified `host-query
|
|
2603
|
+
* record is classified `host-query`, a request, and is **never** treated as a
|
|
2205
2604
|
* result set, even if a result record is also present (an anomaly, which the
|
|
2206
2605
|
* parser flags separately with `ASTM_RECORD_AMBIGUOUS_MESSAGE_KIND`). This is the
|
|
2207
2606
|
* whole point: a `Q`-bearing message can never silently read as a result upload.
|
|
2208
2607
|
*/
|
|
2209
2608
|
|
|
2210
2609
|
/**
|
|
2211
|
-
* Classify a record stream by the host-query flow. Pure and total
|
|
2610
|
+
* Classify a record stream by the host-query flow. Pure and total: it only reads
|
|
2212
2611
|
* the record type letters, never a field value.
|
|
2213
2612
|
*
|
|
2214
2613
|
* `Q` dominates: a message with any `Q` record is a `host-query` request even when
|
|
@@ -2254,10 +2653,40 @@ declare function classifyMessage(records: readonly AstmRecord[]): AstmMessageCla
|
|
|
2254
2653
|
* ```
|
|
2255
2654
|
*/
|
|
2256
2655
|
declare function tokenizeRecord(record: string, d: Delimiters, onUnknownEscape?: (fieldIndex: number) => void): AstmField[];
|
|
2656
|
+
/**
|
|
2657
|
+
* Tokenize an `H` (header) record into its fields.
|
|
2658
|
+
*
|
|
2659
|
+
* A header cannot go through {@link tokenizeRecord}: its second field is the
|
|
2660
|
+
* **delimiter declaration**, which carries the escape character *literally*
|
|
2661
|
+
* rather than as an escape sequence. The generic escape-aware split treats that
|
|
2662
|
+
* lone escape character as the opening of an unterminated escape and swallows
|
|
2663
|
+
* the whole remainder of the record into one field, so every data field after
|
|
2664
|
+
* the declaration is lost. This tokenizer instead takes the declaration verbatim
|
|
2665
|
+
* as one opaque field and applies the ordinary escape-aware tokenizer to the
|
|
2666
|
+
* data portion that follows it.
|
|
2667
|
+
*
|
|
2668
|
+
* `fields[0]` is the type-letter field and `fields[1]` is the delimiter
|
|
2669
|
+
* declaration (verbatim, never escape-decoded); the header's ASTM data fields
|
|
2670
|
+
* follow from `fields[2]`.
|
|
2671
|
+
*
|
|
2672
|
+
* @param record - The raw `H` record text (terminator already stripped).
|
|
2673
|
+
* @param d - The delimiters declared by this header.
|
|
2674
|
+
* @param onUnknownEscape - Called with the 0-based whole-record field index for
|
|
2675
|
+
* each unrecognized escape sequence in the data portion.
|
|
2676
|
+
* @returns The header's fields.
|
|
2677
|
+
* @example
|
|
2678
|
+
* ```ts
|
|
2679
|
+
* import { tokenizeHeader, CANONICAL_DELIMITERS } from "@cosyte/astm";
|
|
2680
|
+
* const fields = tokenizeHeader("H|\\^&|||sender", CANONICAL_DELIMITERS);
|
|
2681
|
+
* fields[1].raw; // "\\^&"
|
|
2682
|
+
* fields[4].raw; // "sender"
|
|
2683
|
+
* ```
|
|
2684
|
+
*/
|
|
2685
|
+
declare function tokenizeHeader(record: string, d: Delimiters, onUnknownEscape?: (fieldIndex: number) => void): AstmField[];
|
|
2257
2686
|
/**
|
|
2258
2687
|
* The primary scalar of a field: its first repeat's first component, decoded.
|
|
2259
2688
|
* Returns `undefined` for a truly empty field so callers can distinguish
|
|
2260
|
-
* "absent" from a value
|
|
2689
|
+
* "absent" from a value: never defaulting a missing value.
|
|
2261
2690
|
*
|
|
2262
2691
|
* @example
|
|
2263
2692
|
* ```ts
|
|
@@ -2274,9 +2703,13 @@ declare function fieldScalar(field: AstmField | undefined): string | undefined;
|
|
|
2274
2703
|
* Tier-3 **fatal** codes mark input the parser cannot recover into a structured
|
|
2275
2704
|
* `AstmMessage`; anything less severe is a Tier-2 warning (see `./warnings.ts`).
|
|
2276
2705
|
* `AstmParseError` is thrown directly and consumers narrow via the `code`
|
|
2277
|
-
* discriminant.
|
|
2278
|
-
*
|
|
2279
|
-
*
|
|
2706
|
+
* discriminant. {@link FatalCode} is **closed**: these three are all of it. The frame
|
|
2707
|
+
* codec reuses `EMPTY_INPUT` (an empty stream is fatal in both its lenient and strict
|
|
2708
|
+
* modes) but does not widen the union, its own thrown errors are separate types with
|
|
2709
|
+
* their own discriminants: {@link AstmFrameEncodeError} carries
|
|
2710
|
+
* `ASTM_FRAME_EMPTY_RECORD`, and {@link AstmFrameStrictError} carries the rejected
|
|
2711
|
+
* warnings rather than a `code`. So narrowing an `AstmParseError` on `code` will only
|
|
2712
|
+
* ever see a value from this set.
|
|
2280
2713
|
*/
|
|
2281
2714
|
|
|
2282
2715
|
/**
|
|
@@ -2297,15 +2730,15 @@ declare function fieldScalar(field: AstmField | undefined): string | undefined;
|
|
|
2297
2730
|
* ```
|
|
2298
2731
|
*/
|
|
2299
2732
|
declare const FATAL_CODES: {
|
|
2300
|
-
/** Input was empty or whitespace-only
|
|
2733
|
+
/** Input was empty or whitespace-only: there is nothing to parse. Shared across layers. */
|
|
2301
2734
|
readonly EMPTY_INPUT: "EMPTY_INPUT";
|
|
2302
|
-
/** The first record is not an `H` (header) record
|
|
2735
|
+
/** The first record is not an `H` (header) record: an ASTM message must lead with `H`. */
|
|
2303
2736
|
readonly ASTM_RECORD_NO_HEADER: "ASTM_RECORD_NO_HEADER";
|
|
2304
2737
|
/** The `H` record is too short to declare the four delimiters (field/repeat/component/escape). */
|
|
2305
2738
|
readonly ASTM_RECORD_UNDECLARED_DELIMITERS: "ASTM_RECORD_UNDECLARED_DELIMITERS";
|
|
2306
2739
|
};
|
|
2307
2740
|
/**
|
|
2308
|
-
* A value from {@link FATAL_CODES}
|
|
2741
|
+
* A value from {@link FATAL_CODES}: the type carried by a thrown {@link AstmParseError}.
|
|
2309
2742
|
*/
|
|
2310
2743
|
type FatalCode = (typeof FATAL_CODES)[keyof typeof FATAL_CODES];
|
|
2311
2744
|
/**
|
|
@@ -2341,11 +2774,11 @@ declare class AstmParseError extends Error {
|
|
|
2341
2774
|
}
|
|
2342
2775
|
|
|
2343
2776
|
/**
|
|
2344
|
-
* The ASTM escape codec
|
|
2777
|
+
* The ASTM escape codec: the fix for a documented silent-misread class.
|
|
2345
2778
|
*
|
|
2346
2779
|
* ASTM escapes an embedded delimiter with the escape character on both sides of
|
|
2347
|
-
* a single mnemonic letter (the InterSystems mnemonics, cross-verified
|
|
2348
|
-
*
|
|
2780
|
+
* a single mnemonic letter (the InterSystems mnemonics, cross-verified against
|
|
2781
|
+
* the OSS reference implementations):
|
|
2349
2782
|
*
|
|
2350
2783
|
* ```
|
|
2351
2784
|
* &F& → field delimiter (default "|")
|
|
@@ -2355,18 +2788,18 @@ declare class AstmParseError extends Error {
|
|
|
2355
2788
|
* ```
|
|
2356
2789
|
*
|
|
2357
2790
|
* **Why this is a requirement, not a nicety.** A result value can legitimately
|
|
2358
|
-
* contain a component delimiter
|
|
2791
|
+
* contain a component delimiter: e.g. a titre written `1^40`. On the wire that
|
|
2359
2792
|
* is escaped as `1&S&40`. A parser that splits a field into components on the
|
|
2360
2793
|
* raw component delimiter and *never decodes the escape* is safe here (the
|
|
2361
2794
|
* escape body `&S&` contains no literal `^`), but a parser that **decodes first
|
|
2362
2795
|
* and splits second** turns `1&S&40` into `1^40` and then mis-splits it into two
|
|
2363
|
-
* components
|
|
2796
|
+
* components: the silent misread `python-astm` / `Chistousov` exhibit. The
|
|
2364
2797
|
* correct order, implemented here, is **escape-aware split, then decode each
|
|
2365
2798
|
* leaf**: the escape sequence survives the split intact and only then decodes to
|
|
2366
2799
|
* a single literal, so `1&S&40` reads as exactly one component.
|
|
2367
2800
|
*
|
|
2368
|
-
* Re-escaping (the inverse, for spec-clean emit)
|
|
2369
|
-
*
|
|
2801
|
+
* Re-escaping (the inverse, for spec-clean emit) lives in the serializer and is
|
|
2802
|
+
* deliberately not implemented here.
|
|
2370
2803
|
*/
|
|
2371
2804
|
|
|
2372
2805
|
/**
|
|
@@ -2399,7 +2832,7 @@ declare function decodeEscapes(leaf: string, d: Delimiters, onUnknown?: UnknownE
|
|
|
2399
2832
|
/**
|
|
2400
2833
|
* Split `text` on `delimiter`, treating any escape sequence (`escape …
|
|
2401
2834
|
* escape`) as an opaque atom so a delimiter that appears *inside* an escape body
|
|
2402
|
-
* never causes a split. Returns the raw (still-encoded) segments
|
|
2835
|
+
* never causes a split. Returns the raw (still-encoded) segments: decoding is
|
|
2403
2836
|
* the caller's next step, per the escape-aware-split-then-decode contract.
|
|
2404
2837
|
*
|
|
2405
2838
|
* For the four canonical mnemonics this is belt-and-suspenders (their bodies are
|
|
@@ -2444,12 +2877,12 @@ declare function splitEscapeAware(text: string, delimiter: string, escape: strin
|
|
|
2444
2877
|
declare function deepFreeze<T>(value: T): T;
|
|
2445
2878
|
|
|
2446
2879
|
/**
|
|
2447
|
-
* `defineAstmProfile()
|
|
2880
|
+
* `defineAstmProfile()`: the public factory for building immutable
|
|
2448
2881
|
* {@link AstmProfile} objects with the safety rules enforced and `describe()`
|
|
2449
2882
|
* attached. Mirrors the sibling `@cosyte/hl7` `defineProfile()` / `@cosyte/ccda`
|
|
2450
2883
|
* `defineCcdaProfile()` shape (name / lineage / `extends`-merge / `describe`) while
|
|
2451
|
-
* modelling ASTM quirks
|
|
2452
|
-
* optional raw-vs-framed transport override
|
|
2884
|
+
* modelling ASTM quirks (a tolerated warning code with provenance, plus the
|
|
2885
|
+
* optional raw-vs-framed transport override) rather than HL7 v2 Z-segments or
|
|
2453
2886
|
* C-CDA template deviations.
|
|
2454
2887
|
*
|
|
2455
2888
|
* Zero runtime deps. No `any`; immutability at the return boundary via
|
|
@@ -2459,7 +2892,7 @@ declare function deepFreeze<T>(value: T): T;
|
|
|
2459
2892
|
/**
|
|
2460
2893
|
* Build a frozen {@link AstmProfile} from a validated options object. Throws
|
|
2461
2894
|
* {@link AstmProfileDefinitionError} on a bad name, an unknown option key, an
|
|
2462
|
-
* invalid `transport`, or an invalid `tolerate` entry
|
|
2895
|
+
* invalid `transport`, or an invalid `tolerate` entry, including the **safety
|
|
2463
2896
|
* rule**: a profile may never tolerate a safety-critical warning code (default-deny
|
|
2464
2897
|
* across all three registries).
|
|
2465
2898
|
*
|
|
@@ -2492,18 +2925,18 @@ declare function defineAstmProfile(opts: DefineAstmProfileOptions): AstmProfile;
|
|
|
2492
2925
|
/**
|
|
2493
2926
|
* The definition-time error for the `@cosyte/astm` vendor-profile subsystem.
|
|
2494
2927
|
*
|
|
2495
|
-
* A profile is validated when it is *defined*, not when it is applied
|
|
2928
|
+
* A profile is validated when it is *defined*, not when it is applied, so a
|
|
2496
2929
|
* misconfigured profile (a bad name, an unknown option key, a tolerated code that
|
|
2497
2930
|
* is unknown or **safety-critical**) fails loudly at construction, long before it
|
|
2498
2931
|
* could quiet a real deviation. This is the class that failure throws. It is a
|
|
2499
2932
|
* programming error (a bad profile literal), distinct from `AstmParseError` (bad
|
|
2500
|
-
* input)
|
|
2933
|
+
* input), so consumers narrow on it separately.
|
|
2501
2934
|
*/
|
|
2502
2935
|
/**
|
|
2503
2936
|
* Thrown by `defineAstmProfile` when a profile definition is invalid: a missing or
|
|
2504
2937
|
* empty `name`, an unknown option key, an invalid `transport` value, or a
|
|
2505
|
-
* `tolerate` entry whose code is unknown, whose `rationale` is empty, or
|
|
2506
|
-
* load-bearing safety rule
|
|
2938
|
+
* `tolerate` entry whose code is unknown, whose `rationale` is empty, or, the
|
|
2939
|
+
* load-bearing safety rule, whose code is **safety-critical** (a result value,
|
|
2507
2940
|
* abnormal flag, result status, reference range, units, patient/comment context,
|
|
2508
2941
|
* message-kind, code system, or any frame/LTP integrity warning). Carries the
|
|
2509
2942
|
* offending profile's `name` when it is known, so a multi-profile build names the
|
|
@@ -2543,14 +2976,14 @@ declare class AstmProfileDefinitionError extends Error {
|
|
|
2543
2976
|
* option always wins, and `profile: null` opts out of the default for a single call.
|
|
2544
2977
|
*
|
|
2545
2978
|
* The single mutable module-scoped `let` is the only such state in the profile
|
|
2546
|
-
* subsystem
|
|
2979
|
+
* subsystem: an intentional, documented trade-off (identical to the siblings).
|
|
2547
2980
|
* Tests that set a default MUST clear it in teardown (`setDefaultAstmProfile(null)`)
|
|
2548
2981
|
* to avoid cross-test bleed.
|
|
2549
2982
|
*/
|
|
2550
2983
|
|
|
2551
2984
|
/**
|
|
2552
2985
|
* Look up a built-in profile by name. Returns `undefined` when no built-in has that
|
|
2553
|
-
* name (a user-defined profile is not in this registry
|
|
2986
|
+
* name (a user-defined profile is not in this registry, pass it directly).
|
|
2554
2987
|
*
|
|
2555
2988
|
* @param name - The built-in profile name.
|
|
2556
2989
|
* @returns The profile, or `undefined`.
|
|
@@ -2578,7 +3011,7 @@ declare function listAstmProfiles(): readonly string[];
|
|
|
2578
3011
|
* clear. An explicit `parseAstmRecords(raw, { profile })` always wins;
|
|
2579
3012
|
* `{ profile: null }` opts out of the default for a single call.
|
|
2580
3013
|
*
|
|
2581
|
-
* **Test hygiene:** the only mutable module-scoped state here
|
|
3014
|
+
* **Test hygiene:** the only mutable module-scoped state here, tests that call this
|
|
2582
3015
|
* MUST clear it in teardown or default-profile bleed infects later tests.
|
|
2583
3016
|
*
|
|
2584
3017
|
* @param profile - The profile to register as default, or `null` to clear.
|
|
@@ -2611,7 +3044,7 @@ declare function getDefaultAstmProfile(): AstmProfile | undefined;
|
|
|
2611
3044
|
* plus the transport-override accessor the framing detector consumes.
|
|
2612
3045
|
*
|
|
2613
3046
|
* A profile **never touches an extracted value**. The warning transform operates
|
|
2614
|
-
* purely at the warning layer
|
|
3047
|
+
* purely at the warning layer: downgrading a deviation it *expects* into a
|
|
2615
3048
|
* `PROFILE_QUIRK_APPLIED` warning (flagged `expected`, carrying the original
|
|
2616
3049
|
* `toleratedCode`) while leaving every un-expected warning untouched. Because the
|
|
2617
3050
|
* transform only ever rewrites a warning object (never a record, field, or value),
|
|
@@ -2643,7 +3076,7 @@ declare function applyAstmProfile(profile: AstmProfile, warning: AstmRecordWarni
|
|
|
2643
3076
|
* Apply a profile across a whole warning list, returning a NEW array (the input is
|
|
2644
3077
|
* never mutated). Returned unchanged (same reference contents, a shallow copy) when
|
|
2645
3078
|
* `profile` is `undefined`, so the no-profile path pays only a copy. Every tolerated
|
|
2646
|
-
* warning is re-badged; every other warning passes through by identity
|
|
3079
|
+
* warning is re-badged; every other warning passes through by identity, nothing is
|
|
2647
3080
|
* dropped, reordered, or reallocated beyond the re-badge.
|
|
2648
3081
|
*
|
|
2649
3082
|
* @param warnings - The accumulated record warnings.
|
|
@@ -2657,7 +3090,7 @@ declare function applyAstmProfile(profile: AstmProfile, warning: AstmRecordWarni
|
|
|
2657
3090
|
*/
|
|
2658
3091
|
declare function applyAstmProfileToWarnings(warnings: readonly AstmRecordWarning[], profile: AstmProfile | undefined): AstmRecordWarning[];
|
|
2659
3092
|
/**
|
|
2660
|
-
* The profile's transport override, if any
|
|
3093
|
+
* The profile's transport override, if any: the value a consumer feeds to
|
|
2661
3094
|
* `detectFraming(bytes, { override })` to force framed/raw and bypass leading-byte
|
|
2662
3095
|
* auto-detection. `undefined` means "let detection decide."
|
|
2663
3096
|
*
|
|
@@ -2675,47 +3108,47 @@ declare function resolveProfileTransport(profile: AstmProfile | undefined): Astm
|
|
|
2675
3108
|
/**
|
|
2676
3109
|
* The safety spine of the profile subsystem: the set of warning codes a profile is
|
|
2677
3110
|
* **forbidden** to tolerate. A vendor profile exists to quiet known, benign
|
|
2678
|
-
* structural noise
|
|
3111
|
+
* structural noise: never to hide a deviation that could change a clinical
|
|
2679
3112
|
* reading or corrupt the wire.
|
|
2680
3113
|
*
|
|
2681
3114
|
* **Design: default-deny.** Rather than enumerate the *forbidden* codes (a list you
|
|
2682
|
-
* can forget to extend when a new warning ships
|
|
3115
|
+
* can forget to extend when a new warning ships, the exact incompleteness a
|
|
2683
3116
|
* safety gate must not have), this module enumerates the tiny set of **explicitly
|
|
2684
3117
|
* tolerable** codes and treats *every other* code, across all three registries
|
|
2685
3118
|
* (record `ASTM_RECORD_*`, frame `ASTM_FRAME_*`, protocol `ASTM_LTP_*`), as
|
|
2686
|
-
* safety-critical. A warning code added
|
|
3119
|
+
* safety-critical. A warning code added later is therefore
|
|
2687
3120
|
* safety-critical **by default** until someone deliberately, reviewably adds it to
|
|
2688
3121
|
* the allow-list below. Widening the allow-list can only ever be an explicit,
|
|
2689
3122
|
* argued act; forgetting to touch this file can never *weaken* the gate.
|
|
2690
3123
|
*
|
|
2691
3124
|
* The four tolerable codes are the only deviations that (a) are structural/
|
|
2692
3125
|
* syntactic vendor noise and (b) **cannot alter, drop, or fabricate an extracted
|
|
2693
|
-
* value
|
|
3126
|
+
* value**: because a profile only ever re-badges a *warning*, never re-parses. In
|
|
2694
3127
|
* every one, the underlying datum is already surfaced verbatim regardless of the
|
|
2695
3128
|
* profile:
|
|
2696
3129
|
*
|
|
2697
|
-
* - `ASTM_RECORD_UNKNOWN_TYPE
|
|
3130
|
+
* - `ASTM_RECORD_UNKNOWN_TYPE`: an unmodeled record type, surfaced verbatim as an
|
|
2698
3131
|
* `unsupported` record carrying no interpreted clinical field.
|
|
2699
|
-
* - `ASTM_NONSTANDARD_DELIMITERS
|
|
3132
|
+
* - `ASTM_NONSTANDARD_DELIMITERS`: the header's own declared delimiters are read
|
|
2700
3133
|
* and honored either way; this only notes they differ from the canonical set.
|
|
2701
|
-
* - `ASTM_UNKNOWN_ESCAPE_SEQUENCE
|
|
3134
|
+
* - `ASTM_UNKNOWN_ESCAPE_SEQUENCE`: an unrecognized `&…&` body is **preserved
|
|
2702
3135
|
* byte-for-byte** in the decoded value (the escape codec never guesses); the
|
|
2703
3136
|
* warning is a purely syntactic advisory, the value is identical with or without
|
|
2704
3137
|
* the profile.
|
|
2705
|
-
* - `ASTM_RECORD_UNINTERPRETED_QUERY_STATUS
|
|
3138
|
+
* - `ASTM_RECORD_UNINTERPRETED_QUERY_STATUS`: a `Q`-record request-information
|
|
2706
3139
|
* status surfaced verbatim on a *request* record; the code set is paywalled and
|
|
2707
3140
|
* is never interpreted, profile or not.
|
|
2708
3141
|
*
|
|
2709
|
-
* Everything a wrong value could hide from
|
|
3142
|
+
* Everything a wrong value could hide from: a result value split ambiguity, an
|
|
2710
3143
|
* undefined abnormal flag or result status, an unparseable reference range, absent
|
|
2711
3144
|
* units, a mis-attached comment, a partial timestamp, a query-vs-result ambiguity,
|
|
2712
3145
|
* a bad frame checksum / sequence gap / unterminated / oversize frame, an
|
|
2713
|
-
* ambiguous transport, an unexpected protocol event, or a rejected frame
|
|
3146
|
+
* ambiguous transport, an unexpected protocol event, or a rejected frame: is
|
|
2714
3147
|
* forbidden.
|
|
2715
3148
|
*/
|
|
2716
3149
|
|
|
2717
3150
|
/**
|
|
2718
|
-
* The **only** warning codes a profile may list in its `tolerate` set
|
|
3151
|
+
* The **only** warning codes a profile may list in its `tolerate` set: benign,
|
|
2719
3152
|
* structural/syntactic vendor noise that cannot alter, drop, or fabricate an
|
|
2720
3153
|
* extracted value. Frozen so it cannot be mutated at runtime to smuggle a code in.
|
|
2721
3154
|
* Adding to this set is a deliberate, reviewable act (and each addition must be an
|
|
@@ -2741,7 +3174,7 @@ declare const TOLERABLE_CODES: ReadonlySet<AnyAstmWarningCode>;
|
|
|
2741
3174
|
*/
|
|
2742
3175
|
declare const ALL_ASTM_WARNING_CODES: ReadonlySet<AnyAstmWarningCode>;
|
|
2743
3176
|
/**
|
|
2744
|
-
* The forbidden set
|
|
3177
|
+
* The forbidden set: computed as **every known code minus the tolerable
|
|
2745
3178
|
* allow-list**, so it is complete by construction. Frozen. A code appears here iff
|
|
2746
3179
|
* it is a real warning code that is not in {@link TOLERABLE_CODES}.
|
|
2747
3180
|
*
|
|
@@ -2754,7 +3187,7 @@ declare const ALL_ASTM_WARNING_CODES: ReadonlySet<AnyAstmWarningCode>;
|
|
|
2754
3187
|
*/
|
|
2755
3188
|
declare const SAFETY_CRITICAL_CODES: ReadonlySet<AnyAstmWarningCode>;
|
|
2756
3189
|
/**
|
|
2757
|
-
* True when `code` is a known warning code that is **not** tolerable
|
|
3190
|
+
* True when `code` is a known warning code that is **not** tolerable: i.e. a
|
|
2758
3191
|
* profile may never list it. A code that is not a real warning code at all returns
|
|
2759
3192
|
* `false` here (the validator reports "unknown code" separately, a distinct
|
|
2760
3193
|
* failure with a distinct message).
|
|
@@ -2772,14 +3205,14 @@ declare function isSafetyCriticalCode(code: string): boolean;
|
|
|
2772
3205
|
|
|
2773
3206
|
/**
|
|
2774
3207
|
* The `default` conservative baseline profile. Tolerates **nothing** and forces no
|
|
2775
|
-
* transport
|
|
3208
|
+
* transport: every deviation surfaces as its own warning, unmodified, and framing
|
|
2776
3209
|
* is auto-detected. It exists so "no profile" and "the default profile" are the
|
|
2777
3210
|
* same, explicit behaviour, and so a consumer can name the baseline when composing
|
|
2778
3211
|
* (`extends: astmProfiles.default`) without importing a special sentinel. Absence of
|
|
2779
|
-
* a profile means this conservative default applied
|
|
3212
|
+
* a profile means this conservative default applied: not that a stream was fully
|
|
2780
3213
|
* understood.
|
|
2781
3214
|
*
|
|
2782
|
-
* Authored through the public `defineAstmProfile()` API
|
|
3215
|
+
* Authored through the public `defineAstmProfile()` API: zero privileged coupling;
|
|
2783
3216
|
* it is exactly what a user would write.
|
|
2784
3217
|
*/
|
|
2785
3218
|
|
|
@@ -2796,14 +3229,13 @@ declare function isSafetyCriticalCode(code: string): boolean;
|
|
|
2796
3229
|
declare const defaultProfile: AstmProfile;
|
|
2797
3230
|
|
|
2798
3231
|
/**
|
|
2799
|
-
* The `referenceCorpus` profile
|
|
3232
|
+
* The `referenceCorpus` profile: a **non-vendor**, evidence-backed tolerance
|
|
2800
3233
|
* grounded firsthand in the **redistributable OSS reference corpus**, not an
|
|
2801
3234
|
* invented per-vendor deviation matrix.
|
|
2802
3235
|
*
|
|
2803
|
-
* Grounding (firsthand-verified 2026-07-21; the corpus is permissively licensed
|
|
2804
|
-
* in scope for grounding, per the roadmap):
|
|
3236
|
+
* Grounding (firsthand-verified 2026-07-21; the corpus is permissively licensed):
|
|
2805
3237
|
*
|
|
2806
|
-
* - **`kxepal/python-astm` `codec.py`** (BSD)
|
|
3238
|
+
* - **`kxepal/python-astm` `codec.py`** (BSD), its `decode_record` /
|
|
2807
3239
|
* `decode_component` / `decode_repeated_component` split fields, components, and
|
|
2808
3240
|
* repeats on the raw delimiters and **never un-escape** ASTM escape sequences: the
|
|
2809
3241
|
* source contains zero handling of `&F&`/`&S&`/`&R&`/`&E&`, and the escape
|
|
@@ -2814,27 +3246,26 @@ declare const defaultProfile: AstmProfile;
|
|
|
2814
3246
|
* or non-standard `&…&` body inside a free-text field (units, comments, test
|
|
2815
3247
|
* names) that our **escape-aware** tokenizer flags as
|
|
2816
3248
|
* `ASTM_UNKNOWN_ESCAPE_SEQUENCE`. We are deliberately stricter than the OSS
|
|
2817
|
-
* references here
|
|
2818
|
-
* noise when talking to that ecosystem
|
|
3249
|
+
* references here, so the warning is *expected*
|
|
3250
|
+
* noise when talking to that ecosystem: recognizable and non-clinical.
|
|
2819
3251
|
*
|
|
2820
3252
|
* **Why this is safe.** The escape codec **preserves an unrecognized `&…&` body
|
|
2821
3253
|
* byte-for-byte** in the decoded value (`decodeEscapes` never guesses); the value a
|
|
2822
3254
|
* consumer reads is identical whether or not this profile is active. The profile
|
|
2823
|
-
* only re-badges the *warning
|
|
3255
|
+
* only re-badges the *warning*: no value, flag, status, or identifier is altered
|
|
2824
3256
|
* or dropped. And by the safety gate in `defineAstmProfile`, it **cannot** tolerate
|
|
2825
3257
|
* a result value / flag / status / range / units, a patient or comment context, a
|
|
2826
|
-
* message-kind ambiguity, or any frame / LTP integrity warning
|
|
3258
|
+
* message-kind ambiguity, or any frame / LTP integrity warning: those always
|
|
2827
3259
|
* surface, profile or not.
|
|
2828
3260
|
*
|
|
2829
3261
|
* **Honesty.** This is a *reference-corpus* profile, not a claim about any named
|
|
2830
3262
|
* analyzer vendor. No per-vendor ("cobas does X") behaviour is asserted; a named
|
|
2831
|
-
* vendor profile awaits a firsthand vendor-attributed quirk document
|
|
2832
|
-
* deferred (`REAL-CORPUS`).
|
|
3263
|
+
* vendor profile awaits a firsthand vendor-attributed quirk document.
|
|
2833
3264
|
*/
|
|
2834
3265
|
|
|
2835
3266
|
/**
|
|
2836
3267
|
* Tolerates the non-standard-escape noise that streams from the escape-agnostic OSS
|
|
2837
|
-
* reference stack (`python-astm` / `senaite`) carry
|
|
3268
|
+
* reference stack (`python-astm` / `senaite`) carry: the value is preserved
|
|
2838
3269
|
* verbatim; only the syntactic warning is expected.
|
|
2839
3270
|
*
|
|
2840
3271
|
* @example
|
|
@@ -2854,7 +3285,7 @@ declare const referenceCorpus: AstmProfile;
|
|
|
2854
3285
|
* set, the definition-time error, and the profile types.
|
|
2855
3286
|
*
|
|
2856
3287
|
* The individual built-ins are reached via `astmProfiles.referenceCorpus` etc., not
|
|
2857
|
-
* as top-level named exports
|
|
3288
|
+
* as top-level named exports: mirrors the sibling `profiles` namespace convention
|
|
2858
3289
|
* ("default" is too generic for a top-level export).
|
|
2859
3290
|
*/
|
|
2860
3291
|
|
|
@@ -2864,7 +3295,7 @@ declare const referenceCorpus: AstmProfile;
|
|
|
2864
3295
|
* redistributable OSS reference corpus), each authored via the public
|
|
2865
3296
|
* `defineAstmProfile()` API and carrying its cited provenance. Named per-vendor
|
|
2866
3297
|
* profiles (cobas / Sysmex / …) are **deferred** pending a firsthand
|
|
2867
|
-
* vendor-attributed quirk document
|
|
3298
|
+
* vendor-attributed quirk document: the engine fully supports them; we do not ship
|
|
2868
3299
|
* ungrounded ones.
|
|
2869
3300
|
*
|
|
2870
3301
|
* @example
|
|
@@ -2883,18 +3314,18 @@ declare const astmProfiles: {
|
|
|
2883
3314
|
* The **consumer-supplied** LIVD catalog: a vendor-test-code → LOINC index built
|
|
2884
3315
|
* from an IICC LIVD ("LOINC to Vendor IVD") mapping the consumer provides.
|
|
2885
3316
|
*
|
|
2886
|
-
* **Bring-your-own, by design
|
|
2887
|
-
* **no** LOINC, SNOMED, or LIVD data
|
|
3317
|
+
* **Bring-your-own, by design.** `@cosyte/astm` bundles
|
|
3318
|
+
* **no** LOINC, SNOMED, or LIVD data: LOINC is © Regenstrief (redistributable
|
|
2888
3319
|
* only with its attribution notice) and the public CDC LIVD publication is a
|
|
2889
3320
|
* SARS-CoV-2-specific file that also carries SNOMED CT (separately licensed), not
|
|
2890
3321
|
* a general-analyte, public-domain catalog. So the package stays a **structural
|
|
2891
3322
|
* recognizer, not a dictionary**: it recognizes the Universal Test ID's LOINC slot
|
|
2892
|
-
* and surfaces vendor codes verbatim
|
|
2893
|
-
* *supply* their own LIVD file to map those codes
|
|
3323
|
+
* and surfaces vendor codes verbatim, and this module lets a consumer
|
|
3324
|
+
* *supply* their own LIVD file to map those codes, the terminology data, and its
|
|
2894
3325
|
* license obligations, are the consumer's.
|
|
2895
3326
|
*
|
|
2896
3327
|
* **Grounded firsthand on the IICC LIVD digital format / HL7 LIVD IG.** The
|
|
2897
|
-
* mapping key is the **Vendor Analyte Code
|
|
3328
|
+
* mapping key is the **Vendor Analyte Code**: the vendor *transmission code* the
|
|
2898
3329
|
* instrument sends for an automated test, which is exactly the local code an ASTM
|
|
2899
3330
|
* analyzer puts in the Universal Test ID (`R`/`O` field, component 4). The mapping
|
|
2900
3331
|
* target is the **LOINC Code** (with the LOINC Long Common Name as an optional
|
|
@@ -2911,7 +3342,7 @@ declare const astmProfiles: {
|
|
|
2911
3342
|
* a catalog-hygiene concern the consumer owns.
|
|
2912
3343
|
*/
|
|
2913
3344
|
/**
|
|
2914
|
-
* One LIVD mapping row a consumer supplies
|
|
3345
|
+
* One LIVD mapping row a consumer supplies: a vendor test code and the LOINC it
|
|
2915
3346
|
* maps to, plus optional human-readable / provenance fields. Modeled on the IICC
|
|
2916
3347
|
* LIVD digital format's data elements.
|
|
2917
3348
|
*
|
|
@@ -2923,20 +3354,20 @@ declare const astmProfiles: {
|
|
|
2923
3354
|
*/
|
|
2924
3355
|
interface LivdEntry {
|
|
2925
3356
|
/**
|
|
2926
|
-
* The **Vendor Analyte Code
|
|
3357
|
+
* The **Vendor Analyte Code**: the vendor transmission code the instrument sends (the local code
|
|
2927
3358
|
* in an ASTM Universal Test ID, component 4). The mapping key; compared **verbatim** (exact,
|
|
2928
|
-
* case-sensitive) against the reported code
|
|
3359
|
+
* case-sensitive) against the reported code, never normalized or fuzzy-matched.
|
|
2929
3360
|
*/
|
|
2930
3361
|
readonly vendorCode: string;
|
|
2931
3362
|
/**
|
|
2932
3363
|
* The **LOINC Code** this vendor code maps to (e.g. `"1920-8"`). Taken from the consumer's catalog
|
|
2933
|
-
* as-is and **never validated, altered, or invented
|
|
3364
|
+
* as-is and **never validated, altered, or invented**: the parser does not ship a LOINC table and
|
|
2934
3365
|
* cannot check it; it only carries what the catalog says.
|
|
2935
3366
|
*/
|
|
2936
3367
|
readonly loinc: string;
|
|
2937
|
-
/** The **LOINC Long Common Name**, when the catalog supplies it
|
|
3368
|
+
/** The **LOINC Long Common Name**, when the catalog supplies it: an optional human-readable label. */
|
|
2938
3369
|
readonly loincLongName?: string;
|
|
2939
|
-
/** The **Vendor Analyte Name
|
|
3370
|
+
/** The **Vendor Analyte Name**: the vendor's human-readable analyte label, when supplied. */
|
|
2940
3371
|
readonly vendorAnalyteName?: string;
|
|
2941
3372
|
/** The device **Manufacturer**, when the catalog scopes the mapping to a device (optional provenance). */
|
|
2942
3373
|
readonly manufacturer?: string;
|
|
@@ -2945,7 +3376,7 @@ interface LivdEntry {
|
|
|
2945
3376
|
}
|
|
2946
3377
|
/**
|
|
2947
3378
|
* The outcome of looking a vendor code up in a {@link LivdCatalog}. A distinct
|
|
2948
|
-
* value per safe disposition
|
|
3379
|
+
* value per safe disposition: a hit is `mapped`; a miss is `unmapped`; a code that
|
|
2949
3380
|
* matched more than one **distinct** LOINC is `ambiguous` with the candidates
|
|
2950
3381
|
* surfaced but **none chosen**. There is deliberately no "guessed" case.
|
|
2951
3382
|
*/
|
|
@@ -2956,11 +3387,11 @@ type LivdLookup =
|
|
|
2956
3387
|
readonly loinc: string;
|
|
2957
3388
|
readonly loincLongName?: string;
|
|
2958
3389
|
}
|
|
2959
|
-
/** No entry for this code
|
|
3390
|
+
/** No entry for this code: a miss. The code stays verbatim; no LOINC is invented. */
|
|
2960
3391
|
| {
|
|
2961
3392
|
readonly status: "unmapped";
|
|
2962
3393
|
}
|
|
2963
|
-
/** More than one distinct LOINC
|
|
3394
|
+
/** More than one distinct LOINC: surfaced for inspection, never resolved to one. */
|
|
2964
3395
|
| {
|
|
2965
3396
|
readonly status: "ambiguous";
|
|
2966
3397
|
readonly candidates: readonly string[];
|
|
@@ -2976,7 +3407,7 @@ interface LivdCatalog {
|
|
|
2976
3407
|
/**
|
|
2977
3408
|
* Look a vendor code up, verbatim (exact, case-sensitive). Returns `mapped` on a
|
|
2978
3409
|
* single-LOINC hit, `unmapped` on a miss, and `ambiguous` when the code carries
|
|
2979
|
-
* more than one distinct LOINC
|
|
3410
|
+
* more than one distinct LOINC: never a guess.
|
|
2980
3411
|
*
|
|
2981
3412
|
* @param vendorCode - The reported vendor/local test code.
|
|
2982
3413
|
* @returns The lookup outcome.
|
|
@@ -3011,14 +3442,14 @@ interface LivdCatalog {
|
|
|
3011
3442
|
declare function defineLivdCatalog(entries: readonly LivdEntry[]): LivdCatalog;
|
|
3012
3443
|
|
|
3013
3444
|
/**
|
|
3014
|
-
* The **terminology** warning registry (`ASTM_LIVD_*`) for the
|
|
3015
|
-
*
|
|
3445
|
+
* The **terminology** warning registry (`ASTM_LIVD_*`) for the LIVD-aware LOINC
|
|
3446
|
+
* recognition layer: the fourth of the package's registries,
|
|
3016
3447
|
* alongside the record layer's `ASTM_RECORD_*`, the frame codec's `ASTM_FRAME_*`,
|
|
3017
3448
|
* and the protocol reducer's `ASTM_LTP_*`.
|
|
3018
3449
|
*
|
|
3019
3450
|
* A terminology warning is the record of a **safe non-mapping**: a reported
|
|
3020
3451
|
* vendor test code that a consumer-supplied LIVD catalog did not map to a single
|
|
3021
|
-
* LOINC. It never carries a LOINC the catalog did not vouch for
|
|
3452
|
+
* LOINC. It never carries a LOINC the catalog did not vouch for: the whole point
|
|
3022
3453
|
* of the layer is that an unmapped or ambiguous code is surfaced as *unmapped*,
|
|
3023
3454
|
* **never a guessed LOINC** (mis-identifying a test is safety-critical). Every
|
|
3024
3455
|
* warning carries a stable code plus positional context ({@link AstmPosition}) and
|
|
@@ -3029,7 +3460,7 @@ declare function defineLivdCatalog(entries: readonly LivdEntry[]): LivdCatalog;
|
|
|
3029
3460
|
* profile safety gate's universe (`ALL_ASTM_WARNING_CODES`): a LIVD non-mapping is
|
|
3030
3461
|
* a post-parse advisory produced by the opt-in {@link applyLivd} helper, not a
|
|
3031
3462
|
* parse-time deviation a vendor profile could ever "tolerate". A profile referencing
|
|
3032
|
-
* one is rejected as an unknown code
|
|
3463
|
+
* one is rejected as an unknown code: correctly, since there is nothing here to
|
|
3033
3464
|
* quiet.
|
|
3034
3465
|
*/
|
|
3035
3466
|
|
|
@@ -3046,14 +3477,14 @@ declare function defineLivdCatalog(entries: readonly LivdEntry[]): LivdCatalog;
|
|
|
3046
3477
|
declare const LIVD_WARNING_CODES: {
|
|
3047
3478
|
/**
|
|
3048
3479
|
* A record carried a vendor/local test code, but the consumer-supplied LIVD catalog held **no**
|
|
3049
|
-
* entry for it. The code stays surfaced verbatim and the mapping is `unmapped
|
|
3480
|
+
* entry for it. The code stays surfaced verbatim and the mapping is `unmapped`: a LOINC is
|
|
3050
3481
|
* **never** guessed. Purely advisory: the raw code and value are untouched.
|
|
3051
3482
|
*/
|
|
3052
3483
|
readonly ASTM_LIVD_UNMAPPED_CODE: "ASTM_LIVD_UNMAPPED_CODE";
|
|
3053
3484
|
/**
|
|
3054
3485
|
* A vendor/local test code matched **more than one distinct LOINC** in the catalog (e.g. the same
|
|
3055
3486
|
* transmission code used by two devices for different analytes). The mapping is `ambiguous` and the
|
|
3056
|
-
* candidate LOINCs are surfaced for inspection, but **none is chosen
|
|
3487
|
+
* candidate LOINCs are surfaced for inspection, but **none is chosen**: refusing to pick is the
|
|
3057
3488
|
* fail-safe, since guessing wrong mis-identifies the test.
|
|
3058
3489
|
*/
|
|
3059
3490
|
readonly ASTM_LIVD_AMBIGUOUS_MAPPING: "ASTM_LIVD_AMBIGUOUS_MAPPING";
|
|
@@ -3072,7 +3503,7 @@ type LivdWarningCode = (typeof LIVD_WARNING_CODES)[keyof typeof LIVD_WARNING_COD
|
|
|
3072
3503
|
* import type { AstmLivdWarning } from "@cosyte/astm";
|
|
3073
3504
|
* const w: AstmLivdWarning = {
|
|
3074
3505
|
* code: "ASTM_LIVD_UNMAPPED_CODE",
|
|
3075
|
-
* message: "Reported test code had no LIVD mapping
|
|
3506
|
+
* message: "Reported test code had no LIVD mapping, surfaced unmapped, never a guessed LOINC.",
|
|
3076
3507
|
* position: { recordIndex: 3, recordType: "R" },
|
|
3077
3508
|
* };
|
|
3078
3509
|
* ```
|
|
@@ -3111,15 +3542,15 @@ declare function livdUnmappedCode(position: AstmPosition): AstmLivdWarning;
|
|
|
3111
3542
|
declare function livdAmbiguousMapping(position: AstmPosition): AstmLivdWarning;
|
|
3112
3543
|
|
|
3113
3544
|
/**
|
|
3114
|
-
* The **additive, advisory** LIVD annotation layer
|
|
3545
|
+
* The **additive, advisory** LIVD annotation layer. {@link applyLivd}
|
|
3115
3546
|
* reads a parsed {@link AstmMessage} and a consumer-supplied {@link LivdCatalog}
|
|
3116
|
-
* and returns a **separate** layer of per-record LOINC annotations
|
|
3547
|
+
* and returns a **separate** layer of per-record LOINC annotations: it **never
|
|
3117
3548
|
* mutates, alters, or drops** the raw reported code or value. Recognition is an
|
|
3118
3549
|
* annotation, not a rewrite: the wire stays exactly as parsed.
|
|
3119
3550
|
*
|
|
3120
3551
|
* **The never-fabricate rule (the safety-critical point).** A vendor code with no
|
|
3121
3552
|
* single LIVD mapping surfaces as a typed `unmapped`/`ambiguous`/`no-code`
|
|
3122
|
-
* annotation
|
|
3553
|
+
* annotation: **never** a guessed LOINC. A wrong LOINC mis-identifies a test, so
|
|
3123
3554
|
* the layer only ever reports a LOINC the wire itself carried (an inline LOINC
|
|
3124
3555
|
* candidate) or one the consumer's catalog vouched for (labeled `derived`), and
|
|
3125
3556
|
* otherwise reports nothing but the miss.
|
|
@@ -3129,14 +3560,14 @@ declare function livdAmbiguousMapping(position: AstmPosition): AstmLivdWarning;
|
|
|
3129
3560
|
* The outcome of annotating one record's Universal Test ID against a LIVD catalog.
|
|
3130
3561
|
* A distinct case per disposition; there is no case in which a LOINC is guessed.
|
|
3131
3562
|
*
|
|
3132
|
-
* - `mapped
|
|
3563
|
+
* - `mapped`, the vendor/local code resolved to a single LOINC **via the catalog**
|
|
3133
3564
|
* (labeled `derived: true`, `source: "livd"`).
|
|
3134
|
-
* - `inline-loinc
|
|
3565
|
+
* - `inline-loinc`, the wire itself carried a LOINC in the Universal Test ID's slot
|
|
3135
3566
|
* (component 1); surfaced `source: "wire"`, **not** derived and **not** validated.
|
|
3136
|
-
* - `unmapped
|
|
3137
|
-
* - `ambiguous
|
|
3567
|
+
* - `unmapped`: a vendor/local code with no catalog entry.
|
|
3568
|
+
* - `ambiguous`: a vendor/local code matching more than one distinct LOINC; the
|
|
3138
3569
|
* candidates are surfaced but **none is chosen**.
|
|
3139
|
-
* - `no-code
|
|
3570
|
+
* - `no-code`: the record carried no usable test code at all (name-only/empty), so
|
|
3140
3571
|
* there was nothing to map.
|
|
3141
3572
|
*/
|
|
3142
3573
|
type LivdMapping = {
|
|
@@ -3158,7 +3589,7 @@ type LivdMapping = {
|
|
|
3158
3589
|
readonly status: "no-code";
|
|
3159
3590
|
};
|
|
3160
3591
|
/**
|
|
3161
|
-
* One record's LIVD annotation
|
|
3592
|
+
* One record's LIVD annotation, which record, the code that was looked up
|
|
3162
3593
|
* (verbatim), how it was recognized, and the mapping outcome. Additive: it points
|
|
3163
3594
|
* at the record by index and never replaces it.
|
|
3164
3595
|
*
|
|
@@ -3183,7 +3614,7 @@ interface LivdAnnotation {
|
|
|
3183
3614
|
readonly reportedCode?: string;
|
|
3184
3615
|
/** How the reported code was recognized in the Universal Test ID (provenance only, never a lookup). */
|
|
3185
3616
|
readonly provenance: UniversalTestIdProvenance;
|
|
3186
|
-
/** The mapping outcome
|
|
3617
|
+
/** The mapping outcome: never a guessed LOINC. */
|
|
3187
3618
|
readonly mapping: LivdMapping;
|
|
3188
3619
|
}
|
|
3189
3620
|
/**
|
|
@@ -3194,7 +3625,7 @@ interface LivdAnnotation {
|
|
|
3194
3625
|
interface LivdResult {
|
|
3195
3626
|
/** One annotation per `R`/`O` record, in wire order. */
|
|
3196
3627
|
readonly annotations: readonly LivdAnnotation[];
|
|
3197
|
-
/** A value-free warning per `unmapped`/`ambiguous` code
|
|
3628
|
+
/** A value-free warning per `unmapped`/`ambiguous` code: never per `mapped`/`inline-loinc`/`no-code`. */
|
|
3198
3629
|
readonly warnings: readonly AstmLivdWarning[];
|
|
3199
3630
|
}
|
|
3200
3631
|
/**
|
|
@@ -3270,7 +3701,7 @@ interface FrameChecksum {
|
|
|
3270
3701
|
}
|
|
3271
3702
|
/**
|
|
3272
3703
|
* One decoded ASTM frame. `text` is the frame's record-byte payload (the bytes
|
|
3273
|
-
* between the frame number and the terminator, escapes untouched
|
|
3704
|
+
* between the frame number and the terminator, escapes untouched, this is the
|
|
3274
3705
|
* framing layer, not the record layer). `trusted` is the single flag a consumer
|
|
3275
3706
|
* gates on: it is `true` only for a fully-terminated frame whose checksum
|
|
3276
3707
|
* validated, and such frames are the only ones reassembled into `records`.
|
|
@@ -3308,7 +3739,7 @@ interface AstmFrame {
|
|
|
3308
3739
|
*/
|
|
3309
3740
|
interface DecodeAstmFramesResult {
|
|
3310
3741
|
/**
|
|
3311
|
-
* The reassembled record byte-strings
|
|
3742
|
+
* The reassembled record byte-strings: one entry per complete record (a run of
|
|
3312
3743
|
* frames closed by an `ETX`), in wire order. **Only** clean reassemblies appear:
|
|
3313
3744
|
* a record whose frames included a bad checksum, a sequence gap, or an
|
|
3314
3745
|
* unterminated frame is **omitted** (its frames are still in {@link DecodeAstmFramesResult.frames},
|
|
@@ -3336,16 +3767,16 @@ interface FrameOptions {
|
|
|
3336
3767
|
* The value model for the pure LTP protocol reducer ({@link ltpReduce}).
|
|
3337
3768
|
*
|
|
3338
3769
|
* The reducer models the **receiver** side of an ASTM/CLSI-LIS01 (LIS01-A2)
|
|
3339
|
-
* session
|
|
3770
|
+
* session, the host/LIS receiving an upload, because that is where the one
|
|
3340
3771
|
* safety decision lives: whether to `ACK` (accept) or `NAK` (reject) a frame that
|
|
3341
3772
|
* just arrived. It is a **pure function** of the current {@link LtpState} and one
|
|
3342
3773
|
* inbound {@link LtpEvent}, returning an {@link LtpTransition} (the next state plus
|
|
3343
3774
|
* the actions the consumer should take and any warnings). No sockets, no timers,
|
|
3344
|
-
* no I/O
|
|
3775
|
+
* no I/O: the consumer owns the wire and the clock; the reducer owns the logic.
|
|
3345
3776
|
*/
|
|
3346
3777
|
|
|
3347
3778
|
/**
|
|
3348
|
-
* The protocol phase. The line is either **neutral** (idle
|
|
3779
|
+
* The protocol phase. The line is either **neutral** (idle, awaiting an `ENQ` to
|
|
3349
3780
|
* establish, or reset there after an `EOT`) or in **transfer** (establishment
|
|
3350
3781
|
* accepted; frames and their per-frame `ACK`/`NAK` flow until the sender's `EOT`).
|
|
3351
3782
|
* The classic three-phase LIS01 model (establishment → transfer → termination)
|
|
@@ -3383,7 +3814,7 @@ interface LtpState {
|
|
|
3383
3814
|
/** `true` when a record is mid-reassembly (an `ETB` frame was accepted, awaiting its `ETX`). */
|
|
3384
3815
|
readonly recordOpen: boolean;
|
|
3385
3816
|
/**
|
|
3386
|
-
* The bytes accumulated so far for the in-progress record
|
|
3817
|
+
* The bytes accumulated so far for the in-progress record: the concatenation of
|
|
3387
3818
|
* the `ETB` frames accepted since the last `ETX`. Empty when no record is open.
|
|
3388
3819
|
* These bytes are **never** delivered on their own: only an `ETX` completes a
|
|
3389
3820
|
* record, at which point they (plus the final frame's text) become one entry in
|
|
@@ -3395,7 +3826,7 @@ interface LtpState {
|
|
|
3395
3826
|
* An inbound protocol event the consumer feeds the reducer: one of the four LTP
|
|
3396
3827
|
* control signals (`ENQ`/`ACK`/`NAK`/`EOT`) read off the wire, or a `frame` the
|
|
3397
3828
|
* consumer already decoded with `decodeAstmFrames` (the reducer reuses the codec's
|
|
3398
|
-
* `trusted`/`checksum` verdict
|
|
3829
|
+
* `trusted`/`checksum` verdict, it never re-derives it).
|
|
3399
3830
|
*/
|
|
3400
3831
|
type LtpEvent = {
|
|
3401
3832
|
readonly type: "enq";
|
|
@@ -3442,14 +3873,14 @@ interface LtpTransition {
|
|
|
3442
3873
|
* The pure LTP protocol reducer: {@link ltpReduce} and {@link ltpInitialState}.
|
|
3443
3874
|
*
|
|
3444
3875
|
* A deterministic, socket-free receiver-side state machine for ASTM/CLSI-LIS01
|
|
3445
|
-
* (LIS01-A2). The consumer owns the wire
|
|
3446
|
-
* `decodeAstmFrames`, and drives this reducer one {@link LtpEvent} at a time
|
|
3876
|
+
* (LIS01-A2). The consumer owns the wire (it reads bytes, decodes frames with
|
|
3877
|
+
* `decodeAstmFrames`, and drives this reducer one {@link LtpEvent} at a time) and
|
|
3447
3878
|
* the reducer decides, purely, what happens next: whether to `ACK` or `NAK`, when a
|
|
3448
3879
|
* record is complete, and when the session returns to neutral.
|
|
3449
3880
|
*
|
|
3450
3881
|
* **The one inviolable rule (borrowed from `mllp`'s ACK-failsafe discipline):** a
|
|
3451
|
-
* frame the codec did not vouch for
|
|
3452
|
-
* out of sequence
|
|
3882
|
+
* frame the codec did not vouch for (a bad checksum, an unterminated frame, or one
|
|
3883
|
+
* out of sequence) is answered with `NAK`, **never** a fabricated positive `ACK`,
|
|
3453
3884
|
* and its bytes are **never** appended to a record or delivered. A `NAK` drives
|
|
3454
3885
|
* retransmit, not acceptance. This is the protocol-level twin of the frame codec's
|
|
3455
3886
|
* "a bad-checksum frame is never merged into a record."
|
|
@@ -3472,14 +3903,14 @@ interface LtpTransition {
|
|
|
3472
3903
|
*/
|
|
3473
3904
|
declare function ltpInitialState(): LtpState;
|
|
3474
3905
|
/**
|
|
3475
|
-
* Advance the LTP session by one inbound event
|
|
3906
|
+
* Advance the LTP session by one inbound event: a pure function, no I/O.
|
|
3476
3907
|
*
|
|
3477
3908
|
* Behaviour by phase:
|
|
3478
|
-
* - **neutral
|
|
3909
|
+
* - **neutral**: `enq` accepts establishment (`sendAck`, enter transfer); `eot` is
|
|
3479
3910
|
* a benign line reset (no-op); `ack`/`nak` are unexpected at a receiver (surfaced,
|
|
3480
3911
|
* never read as acceptance); a `frame` before establishment is tolerated
|
|
3481
|
-
* (Postel's Law)
|
|
3482
|
-
* - **transfer
|
|
3912
|
+
* (Postel's Law), the session auto-establishes and processes it, with a warning.
|
|
3913
|
+
* - **transfer**: a `frame` is accepted (`sendAck`, appended, sequence advanced) only
|
|
3483
3914
|
* when it is trusted *and* carries the expected number; a duplicate of the last
|
|
3484
3915
|
* accepted frame is idempotently re-`ACK`ed without re-appending; a bad or
|
|
3485
3916
|
* out-of-sequence frame is `NAK`ed and dropped. `eot` terminates the transfer and
|
|
@@ -3505,18 +3936,18 @@ declare function ltpReduce(state: LtpState, event: LtpEvent): LtpTransition;
|
|
|
3505
3936
|
* (was E1381), plus the bare-record-letter set the transport detector keys on.
|
|
3506
3937
|
*
|
|
3507
3938
|
* The frame codec (`../frames`) owns the *inside* of a frame (`STX FN text
|
|
3508
|
-
* ETB/ETX CS CRLF`); this module owns the *handshake around* frames
|
|
3939
|
+
* ETB/ETX CS CRLF`); this module owns the *handshake around* frames, the
|
|
3509
3940
|
* establishment/transfer/termination signalling that the {@link ltpReduce}
|
|
3510
3941
|
* reducer consumes. These are the raw byte values; the reducer and the transport
|
|
3511
3942
|
* detector are the only consumers.
|
|
3512
3943
|
*/
|
|
3513
|
-
/** Enquiry (`0x05`)
|
|
3944
|
+
/** Enquiry (`0x05`): the sender's request to establish a transfer. */
|
|
3514
3945
|
declare const ENQ = 5;
|
|
3515
|
-
/** Acknowledge (`0x06`)
|
|
3946
|
+
/** Acknowledge (`0x06`): the receiver accepted the last establishment or frame. */
|
|
3516
3947
|
declare const ACK = 6;
|
|
3517
|
-
/** Negative acknowledge (`0x15`)
|
|
3948
|
+
/** Negative acknowledge (`0x15`): the receiver rejected the last frame; retransmit, do not accept. */
|
|
3518
3949
|
declare const NAK = 21;
|
|
3519
|
-
/** End of transmission (`0x04`)
|
|
3950
|
+
/** End of transmission (`0x04`): the sender terminated the transfer; the line returns to neutral. */
|
|
3520
3951
|
declare const EOT = 4;
|
|
3521
3952
|
|
|
3522
3953
|
/**
|
|
@@ -3530,7 +3961,7 @@ declare const EOT = 4;
|
|
|
3530
3961
|
* **both** modes: a bad-checksum frame is surfaced flagged untrusted and **never**
|
|
3531
3962
|
* merged into a record, and a sequence gap is **never** silently bridged.
|
|
3532
3963
|
*
|
|
3533
|
-
* This is the framing layer only
|
|
3964
|
+
* This is the framing layer only: it does not parse record grammar. Hand a
|
|
3534
3965
|
* reassembled record to `parseAstmRecords`, or use `parseFramedAstm` to compose
|
|
3535
3966
|
* both layers at the edge.
|
|
3536
3967
|
*/
|
|
@@ -3540,8 +3971,8 @@ declare const EOT = 4;
|
|
|
3540
3971
|
*
|
|
3541
3972
|
* Bytes outside a frame (before the first `STX`, or between a frame's trailing
|
|
3542
3973
|
* `LF` and the next `STX`) are skipped: in ASTM they are low-level transfer
|
|
3543
|
-
* control (`ENQ`/`ACK`/`NAK`/`EOT`
|
|
3544
|
-
*
|
|
3974
|
+
* control (`ENQ`/`ACK`/`NAK`/`EOT`), never record content, so skipping them is
|
|
3975
|
+
* not data loss.
|
|
3545
3976
|
*
|
|
3546
3977
|
* @param bytes - The framed byte stream.
|
|
3547
3978
|
* @param options - Decode options; lenient unless `strict` is set.
|
|
@@ -3551,7 +3982,7 @@ declare const EOT = 4;
|
|
|
3551
3982
|
* @example
|
|
3552
3983
|
* ```ts
|
|
3553
3984
|
* import { decodeAstmFrames } from "@cosyte/astm";
|
|
3554
|
-
* // One final frame carrying the record text "R|1|"
|
|
3985
|
+
* // One final frame carrying the record text "R|1|": checksum "AF" over FN..ETX (mod 256).
|
|
3555
3986
|
* const bytes = new Uint8Array([0x02, 0x31, 0x52, 0x7c, 0x31, 0x7c, 0x03, 0x41, 0x46, 0x0d, 0x0a]);
|
|
3556
3987
|
* const { records, frames } = decodeAstmFrames(bytes);
|
|
3557
3988
|
* frames[0]?.frameNumber; // 1
|
|
@@ -3561,10 +3992,10 @@ declare const EOT = 4;
|
|
|
3561
3992
|
declare function decodeAstmFrames(bytes: Uint8Array, options?: FrameOptions): DecodeAstmFramesResult;
|
|
3562
3993
|
|
|
3563
3994
|
/**
|
|
3564
|
-
* The frame-layer **emit** side: {@link composeAstmFrames}
|
|
3995
|
+
* The frame-layer **emit** side: {@link composeAstmFrames}.
|
|
3565
3996
|
*
|
|
3566
3997
|
* The exact inverse of {@link decodeAstmFrames}. Given the reassembled record
|
|
3567
|
-
* byte-strings (one entry per complete record
|
|
3998
|
+
* byte-strings (one entry per complete record, the same shape `decodeAstmFrames`
|
|
3568
3999
|
* *produces*), it wraps each into one or more `<STX> FN text <ETB|ETX> CS <CR><LF>`
|
|
3569
4000
|
* frames, so `decodeAstmFrames(composeAstmFrames(records)).records` reproduces the
|
|
3570
4001
|
* input records exactly.
|
|
@@ -3576,7 +4007,7 @@ declare function decodeAstmFrames(bytes: Uint8Array, options?: FrameOptions): De
|
|
|
3576
4007
|
* - the **frame number** `0`–`7`, starting at `1` and rolling over `7 → 0 → 1`,
|
|
3577
4008
|
* continuous across every frame in the stream (as the decoder's sequence check
|
|
3578
4009
|
* expects);
|
|
3579
|
-
* - the **240-byte text split
|
|
4010
|
+
* - the **240-byte text split**: a record longer than 240 bytes is split across
|
|
3580
4011
|
* frames closed by `ETB` (intermediate) with a final `ETX`; the seven framing
|
|
3581
4012
|
* control bytes are never counted toward the 240.
|
|
3582
4013
|
*
|
|
@@ -3585,7 +4016,7 @@ declare function decodeAstmFrames(bytes: Uint8Array, options?: FrameOptions): De
|
|
|
3585
4016
|
*/
|
|
3586
4017
|
/**
|
|
3587
4018
|
* Thrown by {@link composeAstmFrames} when the input cannot be framed into a
|
|
3588
|
-
* spec-clean stream
|
|
4019
|
+
* spec-clean stream: an empty record list, or a record with no bytes (a frame
|
|
3589
4020
|
* must carry a record; an empty one is a structural error, never an empty frame).
|
|
3590
4021
|
* Carries a stable code + the offending record index, never record bytes.
|
|
3591
4022
|
*
|
|
@@ -3611,12 +4042,12 @@ declare class AstmFrameEncodeError extends Error {
|
|
|
3611
4042
|
interface ComposeFramesOptions {
|
|
3612
4043
|
/**
|
|
3613
4044
|
* The frame number to start the sequence at (`0`–`7`). Defaults to
|
|
3614
|
-
* {@link FIRST_FRAME_NUMBER} (`1`)
|
|
4045
|
+
* {@link FIRST_FRAME_NUMBER} (`1`): the ASTM convention.
|
|
3615
4046
|
*/
|
|
3616
4047
|
readonly startFrameNumber?: number;
|
|
3617
4048
|
}
|
|
3618
4049
|
/**
|
|
3619
|
-
* Frame reassembled record bytes into a spec-clean ASTM/CLSI-LIS01 byte stream
|
|
4050
|
+
* Frame reassembled record bytes into a spec-clean ASTM/CLSI-LIS01 byte stream:
|
|
3620
4051
|
* the inverse of {@link decodeAstmFrames}.
|
|
3621
4052
|
*
|
|
3622
4053
|
* Each record is split at 240 text bytes into `ETB`-closed intermediate frames
|
|
@@ -3643,11 +4074,11 @@ declare function composeAstmFrames(records: readonly (Uint8Array | string)[], op
|
|
|
3643
4074
|
* Composing the two layers at the edge: {@link parseFramedAstm}.
|
|
3644
4075
|
*
|
|
3645
4076
|
* The framing layer ({@link decodeAstmFrames}) and the record layer
|
|
3646
|
-
* (`parseAstmRecords`) share nothing but the payload boundary
|
|
4077
|
+
* (`parseAstmRecords`) share nothing but the payload boundary: a frame carries
|
|
3647
4078
|
* record bytes; a record knows nothing about frames. This helper joins them at the
|
|
3648
4079
|
* one point they meet: decode a framed stream into trusted, reassembled record
|
|
3649
4080
|
* bytes, then parse those bytes into an {@link AstmMessage}. It is deliberately
|
|
3650
|
-
* thin
|
|
4081
|
+
* thin: a consumer that already holds de-framed record bytes calls
|
|
3651
4082
|
* `parseAstmRecords` directly, and a consumer that only wants frames calls
|
|
3652
4083
|
* `decodeAstmFrames` directly.
|
|
3653
4084
|
*/
|
|
@@ -3684,7 +4115,7 @@ interface FramedAstmResult {
|
|
|
3684
4115
|
* @example
|
|
3685
4116
|
* ```ts
|
|
3686
4117
|
* import { parseFramedAstm } from "@cosyte/astm";
|
|
3687
|
-
* // A single final frame carrying "H|\^&\r"
|
|
4118
|
+
* // A single final frame carrying "H|\^&\r": checksum "E5" over FN..ETX (mod 256).
|
|
3688
4119
|
* const bytes = new Uint8Array([
|
|
3689
4120
|
* 0x02, 0x31, 0x48, 0x7c, 0x5c, 0x5e, 0x26, 0x0d, 0x03, 0x45, 0x35, 0x0d, 0x0a,
|
|
3690
4121
|
* ]);
|
|
@@ -3696,7 +4127,7 @@ interface FramedAstmResult {
|
|
|
3696
4127
|
declare function parseFramedAstm(bytes: Uint8Array, options?: FrameOptions & AstmParseOptions): FramedAstmResult;
|
|
3697
4128
|
/**
|
|
3698
4129
|
* Serialize an ASTM message (or a bare record list) and frame it into a spec-clean
|
|
3699
|
-
* byte stream in one call
|
|
4130
|
+
* byte stream in one call: the inverse of {@link parseFramedAstm}, composing the
|
|
3700
4131
|
* two emit layers at the edge.
|
|
3701
4132
|
*
|
|
3702
4133
|
* Each record is serialized to spec-clean, `CR`-terminated wire text (canonical
|
|
@@ -3724,15 +4155,15 @@ declare function serializeFramedAstm(input: AstmMessage | readonly AstmRecord[],
|
|
|
3724
4155
|
* The ASTM/CLSI-LIS01 modulo-256 frame checksum.
|
|
3725
4156
|
*
|
|
3726
4157
|
* The checksum is the sum, modulo 256, of every byte **after** the `STX` up to
|
|
3727
|
-
* and **including** the `ETB`/`ETX` terminator
|
|
4158
|
+
* and **including** the `ETB`/`ETX` terminator: i.e. the frame number, the record
|
|
3728
4159
|
* text, and the terminator byte. It is carried on the wire as **two hexadecimal
|
|
3729
4160
|
* characters**. This module computes it (`computeChecksum`), formats it for emit
|
|
3730
|
-
* (`toChecksumHex
|
|
3731
|
-
* (`parseChecksumHex
|
|
4161
|
+
* (`toChecksumHex`, always **uppercase**), and reads it back leniently
|
|
4162
|
+
* (`parseChecksumHex`, **accepts lowercase**), matching the real-vendor quirk that
|
|
3732
4163
|
* some analyzers emit a lowercase checksum.
|
|
3733
4164
|
*/
|
|
3734
4165
|
/**
|
|
3735
|
-
* Sum bytes `[start, endInclusive]` of `bytes` modulo 256
|
|
4166
|
+
* Sum bytes `[start, endInclusive]` of `bytes` modulo 256: the ASTM frame
|
|
3736
4167
|
* checksum span (frame number through the terminator, inclusive).
|
|
3737
4168
|
*
|
|
3738
4169
|
* @param bytes - The full decoded byte stream.
|
|
@@ -3763,7 +4194,7 @@ declare function toChecksumHex(checksum: number): string;
|
|
|
3763
4194
|
/**
|
|
3764
4195
|
* Read the two-hex-char checksum at `bytes[i0]`/`bytes[i1]`, **case-insensitively**
|
|
3765
4196
|
* (a lowercase checksum is a tolerated real-vendor quirk). Returns `undefined` when
|
|
3766
|
-
* either position is out of range or not a hex digit
|
|
4197
|
+
* either position is out of range or not a hex digit: the caller then treats the
|
|
3767
4198
|
* frame as having no readable declared checksum, never as a match.
|
|
3768
4199
|
*
|
|
3769
4200
|
* @param bytes - The full decoded byte stream.
|
|
@@ -3780,7 +4211,7 @@ declare function toChecksumHex(checksum: number): string;
|
|
|
3780
4211
|
declare function parseChecksumHex(bytes: Uint8Array, i0: number, i1: number): number | undefined;
|
|
3781
4212
|
|
|
3782
4213
|
/**
|
|
3783
|
-
* The frame codec's thrown error
|
|
4214
|
+
* The frame codec's thrown error: the `strict`-mode counterpart to its lenient
|
|
3784
4215
|
* warnings.
|
|
3785
4216
|
*
|
|
3786
4217
|
* The frame layer is lenient by default (byte-level deviations become
|
|
@@ -3815,37 +4246,55 @@ declare class AstmFrameStrictError extends Error {
|
|
|
3815
4246
|
}
|
|
3816
4247
|
|
|
3817
4248
|
/**
|
|
3818
|
-
* Public entry point for the `@cosyte/astm` package
|
|
4249
|
+
* Public entry point for the `@cosyte/astm` package: the ASTM/CLSI-LIS02
|
|
3819
4250
|
* **record** layer.
|
|
3820
4251
|
*
|
|
3821
4252
|
* The north star: hand a de-framed ASTM record stream to {@link parseAstmRecords}
|
|
3822
|
-
* and pull a result's value, units, and flag out in one line
|
|
3823
|
-
* (vendor quirks become typed warnings, never silent loss)
|
|
3824
|
-
*
|
|
4253
|
+
* and pull a result's value, units, and flag out in one line. Parsing is lenient
|
|
4254
|
+
* (vendor quirks become typed warnings, never silent loss) and, the whole point,
|
|
4255
|
+
* **never returns a confident wrong value**. Delimiters are read from each header,
|
|
3825
4256
|
* embedded escapes are decoded before a value is split, the practice- and
|
|
3826
4257
|
* laboratory-assigned patient IDs stay distinct, and every deviation is a stable,
|
|
3827
|
-
* value-free warning. Result flag/status semantics
|
|
3828
|
-
* depth + the `C` comment record + partial-timestamp hardening
|
|
4258
|
+
* value-free warning. Result flag/status semantics, patient/order identity
|
|
4259
|
+
* depth + the `C` comment record + partial-timestamp hardening, and the
|
|
3829
4260
|
* request-information (`Q`) record + host-query classification + verbatim `M`/`S`
|
|
3830
|
-
* records
|
|
4261
|
+
* records are all modeled: the **record-content layer is now feature-complete**.
|
|
4262
|
+
*
|
|
4263
|
+
* A parsed model is a whole record **stream**, and a stream may carry several `H` … `L`
|
|
4264
|
+
* messages. {@link messages} splits it into them, so a patient and a result are only ever
|
|
4265
|
+
* paired inside the message that carried both; the flat extractors ({@link patient},
|
|
4266
|
+
* {@link results}, …) read the whole stream and therefore **refuse** one they cannot answer
|
|
4267
|
+
* for, with an {@link AstmAmbiguousStreamError}, rather than answering across patients.
|
|
3831
4268
|
*
|
|
3832
|
-
* The E1381/CLSI-LIS01 **framing** layer
|
|
4269
|
+
* The E1381/CLSI-LIS01 **framing** layer lives alongside the record layer and
|
|
3833
4270
|
* shares nothing but the payload boundary: {@link decodeAstmFrames} decodes a framed
|
|
3834
4271
|
* byte stream (`<STX> FN text <ETB|ETX> CS <CR><LF>`) into frames + reassembled
|
|
3835
|
-
* record bytes
|
|
4272
|
+
* record bytes: verifying the modulo-256 checksum (a bad frame is surfaced
|
|
3836
4273
|
* untrusted, never merged), tracking frame-number sequencing (a gap is never
|
|
3837
4274
|
* silently bridged), and reassembling the 240-byte-limited multi-frame records.
|
|
3838
4275
|
* {@link parseFramedAstm} composes the two layers at the edge.
|
|
3839
4276
|
*
|
|
3840
|
-
* The LTP **protocol** layer
|
|
3841
|
-
* auto-detects the transport reality
|
|
3842
|
-
* (cobas
|
|
4277
|
+
* The LTP **protocol** layer sits above the frame codec: {@link detectFraming}
|
|
4278
|
+
* auto-detects the transport reality from the stream's leading byte, framed
|
|
4279
|
+
* (serial / cobas 4800 / Iguana) vs raw (cobas b121, framing dropped).
|
|
3843
4280
|
* {@link ltpReduce} is a **pure, socket-free** receiver-side state machine over
|
|
3844
4281
|
* `ENQ`/`ACK`/`NAK`/`EOT` + frame-received events. The consumer owns the wire; the
|
|
3845
4282
|
* reducer decides. Its inviolable rule mirrors `mllp`'s ACK-failsafe: a frame the
|
|
3846
4283
|
* codec did not vouch for is answered with `NAK`, **never** a fabricated positive
|
|
3847
|
-
* `ACK`, and never merged into a record
|
|
3848
|
-
*
|
|
4284
|
+
* `ACK`, and never merged into a record, a `NAK` drives retransmit, not acceptance.
|
|
4285
|
+
*
|
|
4286
|
+
* The **emit** half is the conservative inverse of the record and framing layers.
|
|
4287
|
+
* {@link buildAstmMessage} constructs records from typed caller input, emitting only
|
|
4288
|
+
* the values the caller supplied; {@link serializeAstmRecords} writes them out with the
|
|
4289
|
+
* canonical `H|\^&` delimiters by default, re-escaping every embedded delimiter in a
|
|
4290
|
+
* decoded component leaf (every record type is emitted from its decoded `fields`, except that
|
|
4291
|
+
* `M`/`S` bytes pass through untouched when they already read correctly under the emit set, so
|
|
4292
|
+
* an edit to the model is reflected on emit and the whole stream goes out in the one
|
|
4293
|
+
* delimiter set the header declares); {@link composeAstmFrames} wraps them into frames whose
|
|
4294
|
+
* modulo-256 checksum is
|
|
4295
|
+
* computed rather than accepted; and {@link serializeFramedAstm} composes the last two.
|
|
4296
|
+
* The protocol layer has no emit inverse by design: {@link ltpReduce} returns the actions
|
|
4297
|
+
* to take, and the consumer owns the wire.
|
|
3849
4298
|
*/
|
|
3850
4299
|
/**
|
|
3851
4300
|
* Library version string, kept in lockstep with `package.json#version`.
|
|
@@ -3863,4 +4312,4 @@ declare class AstmFrameStrictError extends Error {
|
|
|
3863
4312
|
*/
|
|
3864
4313
|
declare const VERSION: string;
|
|
3865
4314
|
|
|
3866
|
-
export { ALL_ASTM_WARNING_CODES, ACK as ASTM_ACK, ENQ as ASTM_ENQ, EOT as ASTM_EOT, NAK as ASTM_NAK, type AbnormalFlag, type AbnormalFlagCode, type AbnormalFlagMeaning, type AnyAstmWarningCode, type AstmDate, type AstmDatePrecision, type AstmField, type AstmFrame, AstmFrameEncodeError, type AstmFramePosition, AstmFrameStrictError, type AstmFrameWarning, type AstmFraming, type AstmLivdWarning, type AstmLtpWarning, type AstmMessage, type AstmMessageClassification, type AstmMessageKind, AstmParseError, type AstmParseOptions, type AstmPosition, type AstmProfile, AstmProfileDefinitionError, type AstmProfileProvenance, type AstmQuirkMatch, type AstmQuirkTolerance, type AstmRecord, type AstmRecordInput, type AstmRecordWarning, AstmSerializeError, AstmStrictError, CANONICAL_DELIMITERS, type CommentInput, type CommentRecord, type ComposeFramesOptions, type DecodeAstmFramesResult, type DefineAstmProfileOptions, type Delimiters, type DetectFramingOptions, type DetectFramingResult, FATAL_CODES, FRAME_WARNING_CODES, type FatalCode, type FrameChecksum, type FrameOptions, type FrameTerminator, type FrameWarningCode, type FramedAstmResult, type HeaderInput, type HeaderRecord, LIVD_WARNING_CODES, LTP_WARNING_CODES, type LivdAnnotation, type LivdCatalog, type LivdEntry, type LivdLookup, type LivdMapping, type LivdResult, type LivdWarningCode, type LtpAction, type LtpEvent, type LtpPhase, type LtpState, type LtpTransition, type LtpWarningCode, type ManufacturerRecord, type MessageInput, type OrderInput, type OrderRecord, type PatientInput, type PatientName, type PatientNameInput, type PatientRecord, type QueryInput, type QueryRecord, type ReferenceRange, type ReferenceRangeKind, type ResultInput, type ResultRecord, type ResultStatus, type ResultStatusCode, type ResultStatusMeaning, SAFETY_CRITICAL_CODES, type ScientificRecord, TOLERABLE_CODES, type TerminatorRecord, type UniversalTestId, type UniversalTestIdProvenance, type UnknownEscapeSink, type UnsupportedRecord, VERSION, type VerbatimInput, WARNING_CODES, type WarningCode, ambiguousMessageKind, ambiguousValueSplit, applyAstmProfile, applyAstmProfileToWarnings, applyLivd, astmDateToLocalISO, astmProfiles, attachComments, buildAstmMessage, classifyMessage, comments, commentsFor, composeAstmFrames, computeChecksum, decodeAstmFrames, decodeEscapes, deepFreeze, defineAstmProfile, defineLivdCatalog, detectFraming, encodeComponent, fieldScalar, frameBadChecksum, frameOversize, frameSequenceGap, frameUnterminated, getAstmProfile, getDefaultAstmProfile, interpretAbnormalFlag, interpretResultStatus, isNonStandard, isSafetyCriticalCode, listAstmProfiles, livdAmbiguousMapping, livdUnmappedCode, lookupLivdForRecord, ltpAmbiguousTransport, ltpFrameRejected, ltpInitialState, ltpReduce, ltpUnexpectedEvent, nonStandardDelimiters, orders, orphanComment, parseAstmDate, parseAstmRecords, parseChecksumHex, parseFramedAstm, parseReferenceRange, partialTimestamp, patient, primaryCode, profileQuirkApplied, query, readDelimiters, recognizeUniversalTestId, resolveProfileTransport, results, serializeAstmRecord, serializeAstmRecords, serializeField, serializeFramedAstm, setDefaultAstmProfile, splitEscapeAware, toChecksumHex, tokenizeRecord, undefinedAbnormalFlag, undefinedResultStatus, uninterpretedQueryStatus, unitsAbsent, unknownEscapeSequence, unknownRecordType, unparseableReferenceRange };
|
|
4315
|
+
export { ALL_ASTM_WARNING_CODES, AMBIGUOUS_CODES, ACK as ASTM_ACK, ENQ as ASTM_ENQ, EOT as ASTM_EOT, NAK as ASTM_NAK, type AbnormalFlag, type AbnormalFlagCode, type AbnormalFlagMeaning, type AmbiguousCode, type AnyAstmWarningCode, AstmAmbiguousStreamError, type AstmDate, type AstmDatePrecision, type AstmField, type AstmFrame, AstmFrameEncodeError, type AstmFramePosition, AstmFrameStrictError, type AstmFrameWarning, type AstmFraming, type AstmLivdWarning, type AstmLtpWarning, type AstmMessage, type AstmMessageClassification, type AstmMessageKind, AstmParseError, type AstmParseOptions, type AstmPosition, type AstmProfile, AstmProfileDefinitionError, type AstmProfileProvenance, type AstmQuirkMatch, type AstmQuirkTolerance, type AstmRecord, type AstmRecordInput, type AstmRecordWarning, AstmSerializeError, type AstmSerializeErrorCode, type AstmStreamMessage, AstmStrictError, CANONICAL_DELIMITERS, type CommentInput, type CommentRecord, type ComposeFramesOptions, type DecodeAstmFramesResult, type DefineAstmProfileOptions, type Delimiters, type DetectFramingOptions, type DetectFramingResult, FATAL_CODES, FRAME_WARNING_CODES, type FatalCode, type FrameChecksum, type FrameOptions, type FrameTerminator, type FrameWarningCode, type FramedAstmResult, type HeaderInput, type HeaderRecord, LIVD_WARNING_CODES, LTP_WARNING_CODES, type LivdAnnotation, type LivdCatalog, type LivdEntry, type LivdLookup, type LivdMapping, type LivdResult, type LivdWarningCode, type LtpAction, type LtpEvent, type LtpPhase, type LtpState, type LtpTransition, type LtpWarningCode, type ManufacturerRecord, type MessageInput, type OrderInput, type OrderRecord, type PatientInput, type PatientName, type PatientNameInput, type PatientRecord, type QueryInput, type QueryRecord, type ReferenceRange, type ReferenceRangeKind, type ResultInput, type ResultRecord, type ResultStatus, type ResultStatusCode, type ResultStatusMeaning, SAFETY_CRITICAL_CODES, type ScientificRecord, TOLERABLE_CODES, type TerminatorRecord, type UniversalTestId, type UniversalTestIdProvenance, type UnknownEscapeSink, type UnsupportedRecord, VERSION, type VerbatimInput, WARNING_CODES, type WarningCode, ambiguousMessageKind, ambiguousValueSplit, applyAstmProfile, applyAstmProfileToWarnings, applyLivd, astmDateToLocalISO, astmProfiles, attachComments, buildAstmMessage, classifyMessage, comments, commentsFor, composeAstmFrames, computeChecksum, decodeAstmFrames, decodeEscapes, deepFreeze, defineAstmProfile, defineLivdCatalog, delimitersRedeclared, detectFraming, encodeComponent, fieldScalar, frameBadChecksum, frameOversize, frameSequenceGap, frameUnterminated, getAstmProfile, getDefaultAstmProfile, interpretAbnormalFlag, interpretResultStatus, isNonStandard, isSafetyCriticalCode, listAstmProfiles, livdAmbiguousMapping, livdUnmappedCode, lookupLivdForRecord, ltpAmbiguousTransport, ltpFrameRejected, ltpInitialState, ltpReduce, ltpUnexpectedEvent, messages, nonStandardDelimiters, orders, orphanComment, parseAstmDate, parseAstmRecords, parseChecksumHex, parseFramedAstm, parseReferenceRange, partialTimestamp, patient, primaryCode, profileQuirkApplied, query, readDelimiters, recognizeUniversalTestId, resolveProfileTransport, results, serializeAstmRecord, serializeAstmRecords, serializeField, serializeFramedAstm, setDefaultAstmProfile, splitEscapeAware, toChecksumHex, tokenizeHeader, tokenizeRecord, undefinedAbnormalFlag, undefinedResultStatus, uninterpretedQueryStatus, unitsAbsent, unknownEscapeSequence, unknownRecordType, unparseableReferenceRange, unreadableRedeclaration };
|