@cosyte/astm 0.0.7 → 0.0.9
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 +215 -0
- package/README.md +84 -5
- package/dist/index.cjs +258 -159
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +404 -57
- package/dist/index.d.ts +404 -57
- package/dist/index.mjs +257 -160
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -51,12 +51,65 @@ interface AstmPosition {
|
|
|
51
51
|
* ```
|
|
52
52
|
*/
|
|
53
53
|
declare const WARNING_CODES: {
|
|
54
|
-
/**
|
|
54
|
+
/**
|
|
55
|
+
* A record's type letter is not one of the modeled types: surfaced as an unsupported record,
|
|
56
|
+
* never dropped. Treat it as a possible lost message boundary, because a header the reader did
|
|
57
|
+
* not recognize as one does not open a new message.
|
|
58
|
+
*/
|
|
55
59
|
readonly ASTM_RECORD_UNKNOWN_TYPE: "ASTM_RECORD_UNKNOWN_TYPE";
|
|
60
|
+
/**
|
|
61
|
+
* The delimiters in force found **no field separator at all** in a record that carries content
|
|
62
|
+
* beyond its type letter, so the whole record read back as a single field and none of its modeled
|
|
63
|
+
* fields could be recovered. The raw line is surfaced intact and nothing is dropped, but on a
|
|
64
|
+
* result record it means the value, the units and the status are all absent from the parsed model.
|
|
65
|
+
*
|
|
66
|
+
* This is one signature of a record being read with a delimiter set that does not belong to it,
|
|
67
|
+
* which is how a delimiter-scoping mistake turns into lost values. It is reported rather than
|
|
68
|
+
* repaired, because recovering the fields would mean guessing which set the sender meant.
|
|
69
|
+
*
|
|
70
|
+
* **Its absence is not evidence that a record was read in its own set.** This tests one of the
|
|
71
|
+
* four delimiter roles, the field separator, and only in its total form, where that separator
|
|
72
|
+
* occurs in the line (unescaped). Two whole classes of the same loss are outside it: a foreign set
|
|
73
|
+
* whose field separator happens to occur somewhere in the line still splits (on the wrong
|
|
74
|
+
* boundaries, silently, and this can happen to one record inside a run of these warnings); and a
|
|
75
|
+
* set differing in the repeat, component or escape role usually splits into fields normally,
|
|
76
|
+
* where a mis-split component can cost a test identity while the value survives, and where an
|
|
77
|
+
* `&X&` sequence whose body is a delimiter is an opaque atom, so that delimiter does not split
|
|
78
|
+
* and every field after it shifts. The escape role's worst case has narrowed and not
|
|
79
|
+
* disappeared: an escape character heading no sequence is now read as a literal and reported
|
|
80
|
+
* under {@link WARNING_CODES.ASTM_UNPAIRED_ESCAPE_CHARACTER} rather than merging the rest of the
|
|
81
|
+
* record, but a delimiter swallowed inside an `&X&` body is reported only by
|
|
82
|
+
* {@link WARNING_CODES.ASTM_UNKNOWN_ESCAPE_SEQUENCE}, which is tolerable. Treat this code as a
|
|
83
|
+
* report that one record definitely lost its fields, never as a sweep that would have fired if
|
|
84
|
+
* any had.
|
|
85
|
+
*/
|
|
86
|
+
readonly ASTM_RECORD_FIELDS_UNSEPARATED: "ASTM_RECORD_FIELDS_UNSEPARATED";
|
|
56
87
|
/** The header declared delimiters other than the canonical `H|\^&`: tolerated, noted. */
|
|
57
88
|
readonly ASTM_NONSTANDARD_DELIMITERS: "ASTM_NONSTANDARD_DELIMITERS";
|
|
58
89
|
/** An escape sequence body was not one of `&F&`/`&S&`/`&R&`/`&E&`: preserved verbatim. */
|
|
59
90
|
readonly ASTM_UNKNOWN_ESCAPE_SEQUENCE: "ASTM_UNKNOWN_ESCAPE_SEQUENCE";
|
|
91
|
+
/**
|
|
92
|
+
* An escape character appeared where no escape sequence starts (an escape sequence is the escape
|
|
93
|
+
* character, one body character, and the escape character again). It is read as the **literal
|
|
94
|
+
* character it is** and kept byte-for-byte in the decoded value. Nothing is dropped and no byte is
|
|
95
|
+
* invented: this flags that the sender did not write the character the spec-clean way, which
|
|
96
|
+
* is `&E&`.
|
|
97
|
+
*
|
|
98
|
+
* **What it replaced is the reason it exists.** The codec used to read such a character as the
|
|
99
|
+
* opening of a sequence that never closed and merge the whole remainder of the record into the
|
|
100
|
+
* field it sat in, reporting nothing at all. One `&` in a result value cost the units, the
|
|
101
|
+
* abnormal flag and the status together and left the status reading `unspecified` rather than
|
|
102
|
+
* `final`; one in a surname cost the patient's birth date and sex. Emit then re-escaped the merged
|
|
103
|
+
* text into a spec-clean-looking line that read back as the same wrong value, so the mis-read
|
|
104
|
+
* survived a round trip without ever surfacing.
|
|
105
|
+
*
|
|
106
|
+
* **This code is not a statement about the rest of the record.** It reports one character. A
|
|
107
|
+
* *different* escape character in the same record may still head a real three-character sequence,
|
|
108
|
+
* and if that sequence's body happens to be a delimiter (`&|&` under the canonical set) the atom
|
|
109
|
+
* rule means that delimiter does not split. That case is reported separately, under
|
|
110
|
+
* {@link WARNING_CODES.ASTM_UNKNOWN_ESCAPE_SEQUENCE}, and it can still cost a field boundary.
|
|
111
|
+
*/
|
|
112
|
+
readonly ASTM_UNPAIRED_ESCAPE_CHARACTER: "ASTM_UNPAIRED_ESCAPE_CHARACTER";
|
|
60
113
|
/**
|
|
61
114
|
* A result value field carried an *unescaped* component delimiter, so it split into more than one
|
|
62
115
|
* component. Both the full raw value and the split are surfaced and this warning fires: the
|
|
@@ -185,6 +238,11 @@ interface AstmRecordWarning {
|
|
|
185
238
|
* Build an `ASTM_RECORD_UNKNOWN_TYPE` warning. The record is still surfaced (as
|
|
186
239
|
* an unsupported record), never dropped.
|
|
187
240
|
*
|
|
241
|
+
* **This one is not cosmetic.** Message grouping decides where a message starts by
|
|
242
|
+
* reading each record's type letter, so an unrecognized letter may be a header the
|
|
243
|
+
* reader failed to see, and two messages then read as one. A profile is therefore
|
|
244
|
+
* not permitted to tolerate this code.
|
|
245
|
+
*
|
|
188
246
|
* @example
|
|
189
247
|
* ```ts
|
|
190
248
|
* import { unknownRecordType } from "@cosyte/astm";
|
|
@@ -192,6 +250,28 @@ interface AstmRecordWarning {
|
|
|
192
250
|
* ```
|
|
193
251
|
*/
|
|
194
252
|
declare function unknownRecordType(position: AstmPosition): AstmRecordWarning;
|
|
253
|
+
/**
|
|
254
|
+
* Build an `ASTM_RECORD_FIELDS_UNSEPARATED` warning. The raw line is surfaced
|
|
255
|
+
* intact; what is lost is every **modeled** field of the record, because the
|
|
256
|
+
* delimiters in force never split it.
|
|
257
|
+
*
|
|
258
|
+
* **This one is not cosmetic either.** A record with content but no field
|
|
259
|
+
* separator is a record being read with a delimiter set that does not belong to
|
|
260
|
+
* it, and on a result record that costs the value, the units and the status in
|
|
261
|
+
* one go. The fields are not reconstructed, because doing so would mean guessing
|
|
262
|
+
* which set the sender meant, so a profile is not permitted to tolerate this code.
|
|
263
|
+
*
|
|
264
|
+
* **It is a report, not a sweep.** See {@link WARNING_CODES} for the two classes
|
|
265
|
+
* of the same loss it does not see: its absence never certifies a record split
|
|
266
|
+
* correctly.
|
|
267
|
+
*
|
|
268
|
+
* @example
|
|
269
|
+
* ```ts
|
|
270
|
+
* import { fieldsUnseparated } from "@cosyte/astm";
|
|
271
|
+
* fieldsUnseparated({ recordIndex: 4, recordType: "R" });
|
|
272
|
+
* ```
|
|
273
|
+
*/
|
|
274
|
+
declare function fieldsUnseparated(position: AstmPosition): AstmRecordWarning;
|
|
195
275
|
/**
|
|
196
276
|
* Build an `ASTM_NONSTANDARD_DELIMITERS` warning. The declared delimiters are
|
|
197
277
|
* used as-is; this only flags that they differ from the canonical set.
|
|
@@ -240,6 +320,28 @@ declare function unreadableRedeclaration(position: AstmPosition): AstmRecordWarn
|
|
|
240
320
|
* ```
|
|
241
321
|
*/
|
|
242
322
|
declare function unknownEscapeSequence(position: AstmPosition): AstmRecordWarning;
|
|
323
|
+
/**
|
|
324
|
+
* Build an `ASTM_UNPAIRED_ESCAPE_CHARACTER` warning. The character is preserved
|
|
325
|
+
* verbatim as a literal in the decoded value and opens no atom, so it does not
|
|
326
|
+
* merge the rest of the record; the warning body carries neither the character's
|
|
327
|
+
* surroundings nor any field value.
|
|
328
|
+
*
|
|
329
|
+
* **It is a statement about that one character, not about the record.** A
|
|
330
|
+
* different escape character in the same record may head a real three-character
|
|
331
|
+
* sequence, and if that sequence's body is a delimiter, that delimiter does not
|
|
332
|
+
* split. See {@link WARNING_CODES.ASTM_UNPAIRED_ESCAPE_CHARACTER}.
|
|
333
|
+
*
|
|
334
|
+
* A profile **may** tolerate this code: the value it reports is byte-identical
|
|
335
|
+
* with the warning and without it, because reading the character as a literal is
|
|
336
|
+
* the parse, not a consequence of the warning.
|
|
337
|
+
*
|
|
338
|
+
* @example
|
|
339
|
+
* ```ts
|
|
340
|
+
* import { unpairedEscapeCharacter } from "@cosyte/astm";
|
|
341
|
+
* unpairedEscapeCharacter({ recordIndex: 4, recordType: "R", fieldIndex: 4 });
|
|
342
|
+
* ```
|
|
343
|
+
*/
|
|
344
|
+
declare function unpairedEscapeCharacter(position: AstmPosition): AstmRecordWarning;
|
|
243
345
|
/**
|
|
244
346
|
* Build an `ASTM_RECORD_AMBIGUOUS_VALUE_SPLIT` warning. Emitted when a result
|
|
245
347
|
* value field split on an unescaped component delimiter: the full raw value and
|
|
@@ -986,10 +1088,11 @@ declare function detectFraming(bytes: Uint8Array, options?: DetectFramingOptions
|
|
|
986
1088
|
*
|
|
987
1089
|
* 1. **A profile can never tolerate a safety-critical warning code**: a result
|
|
988
1090
|
* value, abnormal flag, result status, reference range, units, patient/comment
|
|
989
|
-
* context, message-kind ambiguity, code system,
|
|
990
|
-
* warning (see `src/profiles/safety.ts`). Naming one is
|
|
991
|
-
* throw*, not a silent relaxation, so a profile can never
|
|
992
|
-
* "ok"
|
|
1091
|
+
* context, message-kind ambiguity, code system, an unrecognized record type, or
|
|
1092
|
+
* any frame/LTP integrity warning (see `src/profiles/safety.ts`). Naming one is
|
|
1093
|
+
* a *definition-time throw*, not a silent relaxation, so a profile can never
|
|
1094
|
+
* make a bad checksum "ok", a cancelled result read "final", or quiet the
|
|
1095
|
+
* warning that says two messages were read as one.
|
|
993
1096
|
* 2. **A tolerated deviation is downgraded, never dropped.** The parser still
|
|
994
1097
|
* records it (re-coded {@link WARNING_CODES.PROFILE_QUIRK_APPLIED}, flagged
|
|
995
1098
|
* `expected: true`, carrying the original `toleratedCode`) so nothing is
|
|
@@ -1124,7 +1227,7 @@ interface AstmProfile {
|
|
|
1124
1227
|
* const opts: DefineAstmProfileOptions = {
|
|
1125
1228
|
* name: "my-analyzer",
|
|
1126
1229
|
* transport: "raw",
|
|
1127
|
-
* tolerate: [{ code: "
|
|
1230
|
+
* tolerate: [{ code: "ASTM_NONSTANDARD_DELIMITERS", rationale: "declares its own set" }],
|
|
1128
1231
|
* };
|
|
1129
1232
|
* const p = defineAstmProfile(opts);
|
|
1130
1233
|
* ```
|
|
@@ -1685,6 +1788,21 @@ interface TerminatorRecord extends RecordBase {
|
|
|
1685
1788
|
* `H`/`P`/`O`/`R`/`C`/`Q`/`M`/`S`/`L` are all modeled; anything else is surfaced
|
|
1686
1789
|
* with its raw fields intact and flagged with an
|
|
1687
1790
|
* `ASTM_RECORD_UNKNOWN_TYPE` warning, never dropped.
|
|
1791
|
+
*
|
|
1792
|
+
* **One of these may be a header.** Message grouping decides where a message
|
|
1793
|
+
* starts by reading the type letter, so a header the reader could not recognize
|
|
1794
|
+
* arrives here instead, opens no message, and the messages either side of it are
|
|
1795
|
+
* grouped as one. Check `rawType` before trusting a split.
|
|
1796
|
+
*
|
|
1797
|
+
* **And one of these may be a `Q`.** Message classification counts type letters
|
|
1798
|
+
* too, so a message carrying an unsupported record is classified `indeterminate`
|
|
1799
|
+
* unless a `Q` was read outright: see
|
|
1800
|
+
* {@link AstmMessageClassification.hasUnrecognized}.
|
|
1801
|
+
*
|
|
1802
|
+
* **A header that arrives here also failed to re-scope the delimiters.** The
|
|
1803
|
+
* records after it are read with the set already in force, and where that set is
|
|
1804
|
+
* not theirs they do not split at all, which is reported per record as
|
|
1805
|
+
* `ASTM_RECORD_FIELDS_UNSEPARATED`.
|
|
1688
1806
|
*/
|
|
1689
1807
|
interface UnsupportedRecord extends RecordBase {
|
|
1690
1808
|
readonly type: "unsupported";
|
|
@@ -1723,7 +1841,14 @@ type AstmMessageKind = "host-query" | "results" | "orders" | "indeterminate";
|
|
|
1723
1841
|
* ```
|
|
1724
1842
|
*/
|
|
1725
1843
|
interface AstmMessageClassification {
|
|
1726
|
-
/**
|
|
1844
|
+
/**
|
|
1845
|
+
* The message kind.
|
|
1846
|
+
*
|
|
1847
|
+
* `indeterminate` also covers a message the reader declines to classify: when
|
|
1848
|
+
* {@link AstmMessageClassification.hasUnrecognized} is `true` and no `Q` was read, the kind is
|
|
1849
|
+
* withheld rather than guessed, because the unreadable letter may have been that `Q`. The `has*`
|
|
1850
|
+
* flags below stay truthful in that case, so a caller that wants the raw counts still has them.
|
|
1851
|
+
*/
|
|
1727
1852
|
readonly kind: AstmMessageKind;
|
|
1728
1853
|
/** At least one `Q` (request-information) record is present. */
|
|
1729
1854
|
readonly hasQuery: boolean;
|
|
@@ -1732,8 +1857,20 @@ interface AstmMessageClassification {
|
|
|
1732
1857
|
/** At least one `O` (order) record is present. */
|
|
1733
1858
|
readonly hasOrders: boolean;
|
|
1734
1859
|
/**
|
|
1735
|
-
*
|
|
1860
|
+
* At least one record's type letter was **not recognized** (an
|
|
1861
|
+
* {@link UnsupportedRecord}), so the letter counts above are known to be incomplete.
|
|
1862
|
+
*
|
|
1863
|
+
* Any of them may have been a `Q`, which is why a message carrying one is classified
|
|
1864
|
+
* `indeterminate` unless a `Q` was read outright. `ASTM_RECORD_UNKNOWN_TYPE` reports the same
|
|
1865
|
+
* condition on {@link AstmMessage.warnings}.
|
|
1866
|
+
*/
|
|
1867
|
+
readonly hasUnrecognized: boolean;
|
|
1868
|
+
/**
|
|
1869
|
+
* `true` **iff** `kind === "host-query"` (a `Q` record was read), the safety surface: gate on this
|
|
1736
1870
|
* before treating records as results, so a query is never misread as a result upload.
|
|
1871
|
+
*
|
|
1872
|
+
* `false` is **not** a warrant that the message is a result set. Read `kind` for that: it is
|
|
1873
|
+
* `indeterminate` whenever the reader could not account for every type letter.
|
|
1737
1874
|
*/
|
|
1738
1875
|
readonly isHostQueryRequest: boolean;
|
|
1739
1876
|
}
|
|
@@ -2436,6 +2573,26 @@ declare function query(msg: AstmMessage): readonly QueryRecord[];
|
|
|
2436
2573
|
* is made this way because it is the boundary this parser already enforces for delimiters,
|
|
2437
2574
|
* and because it never drops a record.
|
|
2438
2575
|
*
|
|
2576
|
+
* **What that costs, and where the cost is paid.** Grouping reads a record's **type letter**,
|
|
2577
|
+
* so it is only as good as the reader's ability to recognize one. A header carrying a stray
|
|
2578
|
+
* leading byte reads as an unsupported record, opens no message, and the two messages either
|
|
2579
|
+
* side of it merge: a patient from the first is then paired with results from the second, which
|
|
2580
|
+
* is the precise pairing {@link messages} exists to prevent. The parser does report it, as an
|
|
2581
|
+
* `ASTM_RECORD_UNKNOWN_TYPE` warning against that record, and a strict parse refuses the stream
|
|
2582
|
+
* outright. The cost is not only misattribution: delimiters are re-read at each `H` too, so an
|
|
2583
|
+
* unrecognized header does not re-scope them either, and where the sets differ the merged tail is
|
|
2584
|
+
* tokenized with the previous header's delimiters and its fields are lost rather than merely
|
|
2585
|
+
* misfiled. Because that warning is the only report the merge produces, a profile is **not**
|
|
2586
|
+
* permitted to tolerate the code and quiet it; the safety gate refuses the code when a profile is
|
|
2587
|
+
* defined, and declines to downgrade the warning whatever profile is in force. Read an
|
|
2588
|
+
* `ASTM_RECORD_UNKNOWN_TYPE` on a stream you are about to group as "a message boundary may be
|
|
2589
|
+
* missing here", not as cosmetic noise.
|
|
2590
|
+
*
|
|
2591
|
+
* Recognizing a mangled header *as* a header is deliberately not attempted. Deciding that an
|
|
2592
|
+
* unsupported record was "really" an `H` means guessing at a byte the sender did not send, and
|
|
2593
|
+
* that guess would silently split a stream a different way; a warning that the reader lost its
|
|
2594
|
+
* place is the honest answer, and refusing the stream in strict mode is the safe one.
|
|
2595
|
+
*
|
|
2439
2596
|
* **What is deliberately not modeled.** ASTM's within-message record hierarchy, which `P`
|
|
2440
2597
|
* a given `R` files against when one message carries several patients, is not modeled
|
|
2441
2598
|
* here. The clauses that would ground it (the message-level structure diagram, and the `P`
|
|
@@ -2604,6 +2761,17 @@ declare function messages(msg: AstmMessage): readonly AstmStreamMessage[];
|
|
|
2604
2761
|
* result set, even if a result record is also present (an anomaly, which the
|
|
2605
2762
|
* parser flags separately with `ASTM_RECORD_AMBIGUOUS_MESSAGE_KIND`). This is the
|
|
2606
2763
|
* whole point: a `Q`-bearing message can never silently read as a result upload.
|
|
2764
|
+
*
|
|
2765
|
+
* **A letter the reader could not recognize blocks a positive answer.** The rule
|
|
2766
|
+
* above is stated on a count of type letters, so it only holds while every letter
|
|
2767
|
+
* was legible. A record whose type is unsupported may be the very `Q` the rule
|
|
2768
|
+
* exists to honor, and reading such a message as a result set is exactly the
|
|
2769
|
+
* outcome the rule forbids. So an unsupported record, with no `Q` to settle the
|
|
2770
|
+
* question, yields `indeterminate` rather than `results` or `orders`: the honest
|
|
2771
|
+
* answer is that the kind is not knowable, and the parser reports the unreadable
|
|
2772
|
+
* letter separately with `ASTM_RECORD_UNKNOWN_TYPE`. A `Q` that *was* read still
|
|
2773
|
+
* dominates, because an unreadable letter can only ever add a kind, never remove
|
|
2774
|
+
* the query that is already there.
|
|
2607
2775
|
*/
|
|
2608
2776
|
|
|
2609
2777
|
/**
|
|
@@ -2614,6 +2782,12 @@ declare function messages(msg: AstmMessage): readonly AstmStreamMessage[];
|
|
|
2614
2782
|
* a result (`R`) record is also present, so a query is never misread as a result
|
|
2615
2783
|
* upload. (The `Q`+`R` anomaly is separately warned at parse time.)
|
|
2616
2784
|
*
|
|
2785
|
+
* An **unsupported** record letter with no `Q` alongside it yields `indeterminate`,
|
|
2786
|
+
* never `results` or `orders`: an unreadable letter may have been the `Q`, and
|
|
2787
|
+
* claiming a positive kind over it is how a query comes to read as a result set.
|
|
2788
|
+
* {@link AstmMessageClassification.hasUnrecognized} reports why the answer is
|
|
2789
|
+
* withheld, and the `has*` counts stay truthful throughout.
|
|
2790
|
+
*
|
|
2617
2791
|
* @param records - The parsed records, in wire order.
|
|
2618
2792
|
* @returns The message classification.
|
|
2619
2793
|
* @example
|
|
@@ -2642,8 +2816,10 @@ declare function classifyMessage(records: readonly AstmRecord[]): AstmMessageCla
|
|
|
2642
2816
|
*
|
|
2643
2817
|
* @param record - The raw record text.
|
|
2644
2818
|
* @param d - The delimiters resolved from the header.
|
|
2645
|
-
* @param onUnknownEscape - Called (with the
|
|
2819
|
+
* @param onUnknownEscape - Called (with the 0-based field index) for each
|
|
2646
2820
|
* unrecognized escape sequence encountered, so the caller can warn.
|
|
2821
|
+
* @param onUnpairedEscape - Called (with the 0-based field index) for each escape
|
|
2822
|
+
* character that heads no escape sequence and was therefore read as a literal.
|
|
2647
2823
|
* @returns The record's fields.
|
|
2648
2824
|
* @example
|
|
2649
2825
|
* ```ts
|
|
@@ -2652,18 +2828,19 @@ declare function classifyMessage(records: readonly AstmRecord[]): AstmMessageCla
|
|
|
2652
2828
|
* fields[3].components[0]; // "28.6"
|
|
2653
2829
|
* ```
|
|
2654
2830
|
*/
|
|
2655
|
-
declare function tokenizeRecord(record: string, d: Delimiters, onUnknownEscape?: (fieldIndex: number) => void): AstmField[];
|
|
2831
|
+
declare function tokenizeRecord(record: string, d: Delimiters, onUnknownEscape?: (fieldIndex: number) => void, onUnpairedEscape?: (fieldIndex: number) => void): AstmField[];
|
|
2656
2832
|
/**
|
|
2657
2833
|
* Tokenize an `H` (header) record into its fields.
|
|
2658
2834
|
*
|
|
2659
2835
|
* A header cannot go through {@link tokenizeRecord}: its second field is the
|
|
2660
|
-
* **delimiter declaration**, which carries
|
|
2661
|
-
* rather than as
|
|
2662
|
-
*
|
|
2663
|
-
*
|
|
2664
|
-
*
|
|
2665
|
-
*
|
|
2666
|
-
*
|
|
2836
|
+
* **delimiter declaration**, which carries all three non-field delimiters
|
|
2837
|
+
* *literally* rather than as escape sequences. Run through the generic tokenizer
|
|
2838
|
+
* the declaration would be split on its own repeat and component characters and
|
|
2839
|
+
* its escape character would be decoded and reported as unpaired, so what the
|
|
2840
|
+
* header declares would come back as fragments plus a spurious warning. This
|
|
2841
|
+
* tokenizer instead takes the declaration verbatim as one opaque field, never
|
|
2842
|
+
* decoded, and applies the ordinary escape-aware tokenizer to the data portion
|
|
2843
|
+
* that follows it.
|
|
2667
2844
|
*
|
|
2668
2845
|
* `fields[0]` is the type-letter field and `fields[1]` is the delimiter
|
|
2669
2846
|
* declaration (verbatim, never escape-decoded); the header's ASTM data fields
|
|
@@ -2673,6 +2850,9 @@ declare function tokenizeRecord(record: string, d: Delimiters, onUnknownEscape?:
|
|
|
2673
2850
|
* @param d - The delimiters declared by this header.
|
|
2674
2851
|
* @param onUnknownEscape - Called with the 0-based whole-record field index for
|
|
2675
2852
|
* each unrecognized escape sequence in the data portion.
|
|
2853
|
+
* @param onUnpairedEscape - Called with the 0-based whole-record field index for
|
|
2854
|
+
* each unpaired escape character in the data portion. The declaration itself is
|
|
2855
|
+
* opaque, so the escape character it names never reports here.
|
|
2676
2856
|
* @returns The header's fields.
|
|
2677
2857
|
* @example
|
|
2678
2858
|
* ```ts
|
|
@@ -2682,7 +2862,7 @@ declare function tokenizeRecord(record: string, d: Delimiters, onUnknownEscape?:
|
|
|
2682
2862
|
* fields[4].raw; // "sender"
|
|
2683
2863
|
* ```
|
|
2684
2864
|
*/
|
|
2685
|
-
declare function tokenizeHeader(record: string, d: Delimiters, onUnknownEscape?: (fieldIndex: number) => void): AstmField[];
|
|
2865
|
+
declare function tokenizeHeader(record: string, d: Delimiters, onUnknownEscape?: (fieldIndex: number) => void, onUnpairedEscape?: (fieldIndex: number) => void): AstmField[];
|
|
2686
2866
|
/**
|
|
2687
2867
|
* The primary scalar of a field: its first repeat's first component, decoded.
|
|
2688
2868
|
* Returns `undefined` for a truly empty field so callers can distinguish
|
|
@@ -2798,6 +2978,26 @@ declare class AstmParseError extends Error {
|
|
|
2798
2978
|
* leaf**: the escape sequence survives the split intact and only then decodes to
|
|
2799
2979
|
* a single literal, so `1&S&40` reads as exactly one component.
|
|
2800
2980
|
*
|
|
2981
|
+
* **An escape sequence is exactly three characters** (the escape character, one
|
|
2982
|
+
* body character, and the escape character again), and both functions here agree
|
|
2983
|
+
* on that, because they have to: a split that thinks `&X…&` is one atom while the
|
|
2984
|
+
* decoder thinks otherwise is the same class of mis-read the ordering above
|
|
2985
|
+
* exists to prevent. An escape character that does not sit at the head of such a
|
|
2986
|
+
* triple is not an escape at all: it is read as the **literal character it is**,
|
|
2987
|
+
* it opens no atom, and the parse layer reports it
|
|
2988
|
+
* (`ASTM_UNPAIRED_ESCAPE_CHARACTER`). Both functions used to scan forward for the
|
|
2989
|
+
* next escape character with no bound, which meant a single unescaped `&` in a
|
|
2990
|
+
* value merged every field after it into that value, in silence.
|
|
2991
|
+
*
|
|
2992
|
+
* **The atom rule is unchanged and still costs a boundary in one case.** A triple
|
|
2993
|
+
* is opaque by design, so where its body is itself a delimiter (`&|&` under the
|
|
2994
|
+
* canonical set) that delimiter does not split. That is deliberate: it is what
|
|
2995
|
+
* keeps `&F&` one token under a declared set that names `F` as a delimiter. Where
|
|
2996
|
+
* the body is not a recognized mnemonic the parse layer reports it as
|
|
2997
|
+
* `ASTM_UNKNOWN_ESCAPE_SEQUENCE`, so it is never silent, but that code is
|
|
2998
|
+
* tolerable and a field boundary has still gone. Do not "fix" that by narrowing
|
|
2999
|
+
* the atom: the narrowing would break the guarantee the atom exists for.
|
|
3000
|
+
*
|
|
2801
3001
|
* Re-escaping (the inverse, for spec-clean emit) lives in the serializer and is
|
|
2802
3002
|
* deliberately not implemented here.
|
|
2803
3003
|
*/
|
|
@@ -2809,11 +3009,19 @@ declare class AstmParseError extends Error {
|
|
|
2809
3009
|
* warning. Optional so the codec can be used purely.
|
|
2810
3010
|
*/
|
|
2811
3011
|
type UnknownEscapeSink = () => void;
|
|
3012
|
+
/**
|
|
3013
|
+
* A callback the codec calls when it encounters an escape character that does not
|
|
3014
|
+
* head a three-character escape sequence. The character is preserved verbatim as a
|
|
3015
|
+
* literal; the callback lets the parser surface a value-free
|
|
3016
|
+
* `ASTM_UNPAIRED_ESCAPE_CHARACTER` warning. Optional so the codec can be used purely.
|
|
3017
|
+
*/
|
|
3018
|
+
type UnpairedEscapeSink = () => void;
|
|
2812
3019
|
/**
|
|
2813
3020
|
* Decode the four recognized ASTM escape mnemonics in a single already-split
|
|
2814
3021
|
* leaf (a component string), substituting the *active* delimiters read from the
|
|
2815
|
-
* header. Unrecognized
|
|
2816
|
-
* `onUnknown` (never dropped, never guessed)
|
|
3022
|
+
* header. Unrecognized `&X&` bodies are preserved verbatim and reported through
|
|
3023
|
+
* `onUnknown` (never dropped, never guessed), and an escape character that heads
|
|
3024
|
+
* no sequence at all is preserved as a literal and reported through `onUnpaired`.
|
|
2817
3025
|
*
|
|
2818
3026
|
* This runs **after** splitting, so a decoded delimiter becomes ordinary literal
|
|
2819
3027
|
* text and can never introduce a new split boundary.
|
|
@@ -2821,23 +3029,34 @@ type UnknownEscapeSink = () => void;
|
|
|
2821
3029
|
* @param leaf - One component string, escape-aware split already applied.
|
|
2822
3030
|
* @param d - The delimiters resolved from the header.
|
|
2823
3031
|
* @param onUnknown - Called once per unrecognized escape body encountered.
|
|
3032
|
+
* @param onUnpaired - Called once per escape character that heads no sequence.
|
|
2824
3033
|
* @returns The decoded string.
|
|
2825
3034
|
* @example
|
|
2826
3035
|
* ```ts
|
|
2827
3036
|
* import { decodeEscapes, CANONICAL_DELIMITERS } from "@cosyte/astm";
|
|
2828
3037
|
* decodeEscapes("1&S&40", CANONICAL_DELIMITERS); // "1^40"
|
|
3038
|
+
* decodeEscapes("O&Brien", CANONICAL_DELIMITERS); // "O&Brien" (literal, reported)
|
|
2829
3039
|
* ```
|
|
2830
3040
|
*/
|
|
2831
|
-
declare function decodeEscapes(leaf: string, d: Delimiters, onUnknown?: UnknownEscapeSink): string;
|
|
3041
|
+
declare function decodeEscapes(leaf: string, d: Delimiters, onUnknown?: UnknownEscapeSink, onUnpaired?: UnpairedEscapeSink): string;
|
|
2832
3042
|
/**
|
|
2833
|
-
* Split `text` on `delimiter`, treating
|
|
2834
|
-
* escape
|
|
2835
|
-
* never causes a split. Returns the raw (still-encoded) segments:
|
|
2836
|
-
* the caller's next step, per the escape-aware-split-then-decode
|
|
3043
|
+
* Split `text` on `delimiter`, treating an escape sequence (escape, one body
|
|
3044
|
+
* character, escape) as an opaque atom so a delimiter that appears *inside* an
|
|
3045
|
+
* escape body never causes a split. Returns the raw (still-encoded) segments:
|
|
3046
|
+
* decoding is the caller's next step, per the escape-aware-split-then-decode
|
|
3047
|
+
* contract.
|
|
3048
|
+
*
|
|
3049
|
+
* For the four canonical mnemonics the opacity is belt-and-suspenders (their
|
|
3050
|
+
* bodies are letters, not delimiters), but it makes the "an escaped delimiter is
|
|
3051
|
+
* one token" guarantee hold for any declared delimiter set, including adversarial
|
|
3052
|
+
* input. A single body character is all that guarantee needs.
|
|
2837
3053
|
*
|
|
2838
|
-
*
|
|
2839
|
-
*
|
|
2840
|
-
*
|
|
3054
|
+
* An escape character that heads no sequence is **not** an escape: it is ordinary
|
|
3055
|
+
* text, and it opens no atom. Reading it as the opening of a sequence that never
|
|
3056
|
+
* closes is what used to merge the whole remainder of a record into one field.
|
|
3057
|
+
* Decoding the resulting leaf is what reports it, so this function stays a pure
|
|
3058
|
+
* split. Note the two rules together: a delimiter after such a character does
|
|
3059
|
+
* split, and a delimiter sitting inside a real three-character atom does not.
|
|
2841
3060
|
*
|
|
2842
3061
|
* @param text - The field or repeat string to split.
|
|
2843
3062
|
* @param delimiter - The delimiter to split on.
|
|
@@ -2848,6 +3067,7 @@ declare function decodeEscapes(leaf: string, d: Delimiters, onUnknown?: UnknownE
|
|
|
2848
3067
|
* import { splitEscapeAware } from "@cosyte/astm";
|
|
2849
3068
|
* splitEscapeAware("a^b^c", "^", "&"); // ["a", "b", "c"]
|
|
2850
3069
|
* splitEscapeAware("1&S&40", "^", "&"); // ["1&S&40"] (escape body is opaque)
|
|
3070
|
+
* splitEscapeAware("O&Brien^John", "^", "&"); // ["O&Brien", "John"] (unpaired: literal)
|
|
2851
3071
|
* ```
|
|
2852
3072
|
*/
|
|
2853
3073
|
declare function splitEscapeAware(text: string, delimiter: string, escape: string): string[];
|
|
@@ -2912,7 +3132,7 @@ declare function deepFreeze<T>(value: T): T;
|
|
|
2912
3132
|
* description: "Acme's inbound raw-TCP analyzer",
|
|
2913
3133
|
* transport: "raw",
|
|
2914
3134
|
* tolerate: [
|
|
2915
|
-
* { code: "
|
|
3135
|
+
* { code: "ASTM_NONSTANDARD_DELIMITERS", rationale: "declares its own delimiter set" },
|
|
2916
3136
|
* ],
|
|
2917
3137
|
* provenance: { source: "Acme host-interface manual", reference: "internal-2026" },
|
|
2918
3138
|
* });
|
|
@@ -2938,7 +3158,8 @@ declare function defineAstmProfile(opts: DefineAstmProfileOptions): AstmProfile;
|
|
|
2938
3158
|
* `tolerate` entry whose code is unknown, whose `rationale` is empty, or, the
|
|
2939
3159
|
* load-bearing safety rule, whose code is **safety-critical** (a result value,
|
|
2940
3160
|
* abnormal flag, result status, reference range, units, patient/comment context,
|
|
2941
|
-
* message-kind, code system, or any frame/LTP
|
|
3161
|
+
* message-kind, code system, an unrecognized record type, or any frame/LTP
|
|
3162
|
+
* integrity warning). Carries the
|
|
2942
3163
|
* offending profile's `name` when it is known, so a multi-profile build names the
|
|
2943
3164
|
* culprit.
|
|
2944
3165
|
*
|
|
@@ -3059,6 +3280,17 @@ declare function getDefaultAstmProfile(): AstmProfile | undefined;
|
|
|
3059
3280
|
* never reallocated). A warning that is already `expected` (e.g. re-processed) is
|
|
3060
3281
|
* passed through untouched.
|
|
3061
3282
|
*
|
|
3283
|
+
* **The safety gate is re-checked here, and that is not redundant.**
|
|
3284
|
+
* {@link defineAstmProfile} refuses a safety-critical code at definition time, but
|
|
3285
|
+
* {@link AstmProfile} is a plain interface: a hand-authored object literal
|
|
3286
|
+
* type-checks, and `parseAstmRecords(raw, { profile })` and `setDefaultAstmProfile`
|
|
3287
|
+
* both accept one without re-running the factory. A definition-time-only gate
|
|
3288
|
+
* therefore guards a door that has a second entrance. A safety-critical code is
|
|
3289
|
+
* **not downgraded here whatever the profile says**, so the original warning
|
|
3290
|
+
* survives and `strict` still escalates it. Silently declining to downgrade, rather
|
|
3291
|
+
* than throwing, is the fail-safe direction: the signal is preserved either way, and
|
|
3292
|
+
* a parse is not turned into an exception by a profile the caller built by hand.
|
|
3293
|
+
*
|
|
3062
3294
|
* @param profile - The active profile.
|
|
3063
3295
|
* @param warning - One accumulated warning.
|
|
3064
3296
|
* @returns The re-badged warning when tolerated, else the original.
|
|
@@ -3109,7 +3341,7 @@ declare function resolveProfileTransport(profile: AstmProfile | undefined): Astm
|
|
|
3109
3341
|
* The safety spine of the profile subsystem: the set of warning codes a profile is
|
|
3110
3342
|
* **forbidden** to tolerate. A vendor profile exists to quiet known, benign
|
|
3111
3343
|
* structural noise: never to hide a deviation that could change a clinical
|
|
3112
|
-
* reading or corrupt the wire.
|
|
3344
|
+
* reading, lose track of where one message ends, or corrupt the wire.
|
|
3113
3345
|
*
|
|
3114
3346
|
* **Design: default-deny.** Rather than enumerate the *forbidden* codes (a list you
|
|
3115
3347
|
* can forget to extend when a new warning ships, the exact incompleteness a
|
|
@@ -3121,43 +3353,153 @@ declare function resolveProfileTransport(profile: AstmProfile | undefined): Astm
|
|
|
3121
3353
|
* the allow-list below. Widening the allow-list can only ever be an explicit,
|
|
3122
3354
|
* argued act; forgetting to touch this file can never *weaken* the gate.
|
|
3123
3355
|
*
|
|
3124
|
-
* The
|
|
3125
|
-
*
|
|
3126
|
-
*
|
|
3127
|
-
*
|
|
3128
|
-
* profile
|
|
3356
|
+
* **The test for admitting a code has two parts, and the second one is easy to
|
|
3357
|
+
* miss.** A code may sit on the allow-list only while
|
|
3358
|
+
*
|
|
3359
|
+
* 1. it is structural or syntactic noise that **cannot alter, drop, or fabricate
|
|
3360
|
+
* an extracted value** (a profile re-badges a *warning*, it never re-parses, so
|
|
3361
|
+
* the underlying datum is surfaced identically with or without the profile);
|
|
3362
|
+
* **and**
|
|
3363
|
+
* 2. **nothing else in this package reads the condition the warning reports.**
|
|
3364
|
+
*
|
|
3365
|
+
* Part 2 is a claim about the whole library, not about the warning, and it is the
|
|
3366
|
+
* part that can stop being true without anyone touching this file. A code admitted
|
|
3367
|
+
* as benign stops being benign the moment a later feature starts making a decision
|
|
3368
|
+
* out of the very condition the warning is reporting, and adding that feature does
|
|
3369
|
+
* not force this file to be revisited. That is not hypothetical: it happened once
|
|
3370
|
+
* here, to `ASTM_RECORD_UNKNOWN_TYPE`, and the consequence was a wrong-patient
|
|
3371
|
+
* path.
|
|
3372
|
+
*
|
|
3373
|
+
* **A record's type letter acquired two load-bearing readers, and the code was
|
|
3374
|
+
* admitted before either existed.** Message grouping reads the letter to decide
|
|
3375
|
+
* where one message ends and the next begins, so an unrecognized header opens no
|
|
3376
|
+
* message and two messages are read as one, pairing a patient with results that
|
|
3377
|
+
* arrived under a different header. Message classification counts `Q` / `R` / `O`
|
|
3378
|
+
* records by the same letter, so an unrecognized `Q` cost a host-query request its
|
|
3379
|
+
* `Q`-dominates guarantee: it read as a result set, and the
|
|
3380
|
+
* `ASTM_RECORD_AMBIGUOUS_MESSAGE_KIND` warning that flags that contradiction went
|
|
3381
|
+
* with it. In both cases `ASTM_RECORD_UNKNOWN_TYPE` is the only warning left on the
|
|
3382
|
+
* stream, while the allow-list still described it as harmless. Tolerating it
|
|
3383
|
+
* re-badged the single report that the reader had lost its place, and a strict parse
|
|
3384
|
+
* then accepted the stream. It is off the list for that reason, and its removal is
|
|
3385
|
+
* what part 2 above is written down to prevent a repeat of.
|
|
3386
|
+
*
|
|
3387
|
+
* The classifier no longer answers `results` over a letter it could not read (it
|
|
3388
|
+
* withholds the kind instead), so that particular misreading is closed at the
|
|
3389
|
+
* source as well as reported. The allow-list conclusion is unchanged either way:
|
|
3390
|
+
* part 2 asks whether anything **reads** the reported condition, and the classifier
|
|
3391
|
+
* still does, now to decide whether it may answer at all.
|
|
3392
|
+
*
|
|
3393
|
+
* **A third reader of record structure landed with
|
|
3394
|
+
* `ASTM_RECORD_FIELDS_UNSEPARATED`, and the allow-list was re-derived against it.**
|
|
3395
|
+
* That code reports a record the delimiters in force could not split at all, which
|
|
3396
|
+
* is one signature of a record being read in a set that is not its own. It is
|
|
3397
|
+
* safety-critical (it reports lost values, so it fails part 1 outright) and it is
|
|
3398
|
+
* on the forbidden side by construction, because that side is computed rather than
|
|
3399
|
+
* listed. What it required was re-reading the survivors below against it:
|
|
3400
|
+
* none of them is a statement about whether a record split, so none of them is now
|
|
3401
|
+
* hiding it. Note the code is deliberately **partial** (see its own docs): it does
|
|
3402
|
+
* not fire wherever a field went missing to a foreign delimiter set, only where the
|
|
3403
|
+
* field separator was absent outright, so it is a reader of record structure that
|
|
3404
|
+
* cannot be relied on as a sweep.
|
|
3405
|
+
*
|
|
3406
|
+
* **So the ones that remain are recorded with the reading each one survives**, not
|
|
3407
|
+
* merely with the value it preserves. Each was re-derived against both readers of
|
|
3408
|
+
* record structure above, plus the single-message / single-patient guards:
|
|
3129
3409
|
*
|
|
3130
|
-
* - `ASTM_RECORD_UNKNOWN_TYPE`: an unmodeled record type, surfaced verbatim as an
|
|
3131
|
-
* `unsupported` record carrying no interpreted clinical field.
|
|
3132
3410
|
* - `ASTM_NONSTANDARD_DELIMITERS`: the header's own declared delimiters are read
|
|
3133
|
-
* and honored either way
|
|
3134
|
-
*
|
|
3135
|
-
*
|
|
3136
|
-
*
|
|
3137
|
-
* the
|
|
3138
|
-
*
|
|
3139
|
-
*
|
|
3140
|
-
*
|
|
3411
|
+
* and honored either way, and this notes only that they differ from the canonical
|
|
3412
|
+
* set. No reader sees them: the stream is cut into records on a line break,
|
|
3413
|
+
* and a record's type letter is the first character of its line, both taken before
|
|
3414
|
+
* any delimiter-driven tokenization runs. It survives the split reader too, and
|
|
3415
|
+
* the distinction is worth stating precisely: this code says the declared set is
|
|
3416
|
+
* unusual, `ASTM_RECORD_FIELDS_UNSEPARATED` says a record did not match the set in
|
|
3417
|
+
* force. A stream may be wholly non-canonical and split perfectly, and a wholly
|
|
3418
|
+
* canonical stream can still carry a record that does not split, so tolerating the
|
|
3419
|
+
* first can never quiet the second.
|
|
3420
|
+
* - `ASTM_UNKNOWN_ESCAPE_SEQUENCE`: an unrecognized escape body is **preserved
|
|
3421
|
+
* byte-for-byte** in the decoded value (the escape codec does not guess at one),
|
|
3422
|
+
* so the value is identical with or without the profile. No reader sees it
|
|
3423
|
+
* either: a decoded field value is never cut back into records, the type
|
|
3424
|
+
* letter is read before decoding, and the split reader counts fields, which the
|
|
3425
|
+
* escape-aware tokenizer has already finished dividing before any body is decoded.
|
|
3426
|
+
*
|
|
3427
|
+
* **This entry is on the list and is recorded as questionable, deliberately.**
|
|
3428
|
+
* The argument above is about the *decoded value*, and it holds. What it does not
|
|
3429
|
+
* cover is that the escape-aware split itself treats an `&X&` triple as opaque, so
|
|
3430
|
+
* where `X` is a delimiter that delimiter never became a boundary: the split the
|
|
3431
|
+
* argument says has "already finished dividing" divided one time too few, and this
|
|
3432
|
+
* code is the only report of it. Measured on the canonical set:
|
|
3433
|
+
* `R|1|^^^687|28.6&|&U/L||||F` yields a value of `28.6&|&U/L`, no units, and status
|
|
3434
|
+
* `unspecified` rather than `final`, with this as the sole warning, so a profile
|
|
3435
|
+
* tolerating it (the shipped `referenceCorpus` does) lets `{ strict: true }` accept
|
|
3436
|
+
* it. That fails part 1 of the two-clause test on the reading, not on the value.
|
|
3437
|
+
* It is **left on the list on purpose**: removing it would change behavior for
|
|
3438
|
+
* every profile naming it, and what should report a swallowed boundary instead, and
|
|
3439
|
+
* at what severity, is a question this file cannot settle on its own. Do not read
|
|
3440
|
+
* this bullet as an endorsement; read it as the open question it is, and do not
|
|
3441
|
+
* close it by narrowing the atom, which is what keeps `&F&` one token under a
|
|
3442
|
+
* delimiter set that names `F` as a delimiter.
|
|
3443
|
+
* - `ASTM_UNPAIRED_ESCAPE_CHARACTER`: an escape character heading no escape
|
|
3444
|
+
* sequence, read as the **literal character it is** and kept byte-for-byte in the
|
|
3445
|
+
* decoded value, so the value is identical with or without the profile. Note what
|
|
3446
|
+
* makes this admissible is a property of the *parse*, not of the warning: reading
|
|
3447
|
+
* the character as a literal is unconditional, and tolerating the code changes
|
|
3448
|
+
* nothing about how the record splits. No reader sees it, on the same grounds as
|
|
3449
|
+
* the entry above: the type letter is read before any decoding, the split reader
|
|
3450
|
+
* counts fields the escape-aware tokenizer has already finished dividing, and a
|
|
3451
|
+
* decoded value is never cut back into records. Contrast the condition it
|
|
3452
|
+
* replaced, which **would** have failed part 1: reading the same character as an
|
|
3453
|
+
* unterminated sequence merged every field after it into one, and a code reporting
|
|
3454
|
+
* that would have been a report of lost values.
|
|
3455
|
+
* - `ASTM_RECORD_UNINTERPRETED_QUERY_STATUS`: a request-information status carried
|
|
3456
|
+
* verbatim as a leaf field on a `Q` record; the code set is paywalled and is not
|
|
3457
|
+
* interpreted, profile or not. Classification reads whether a `Q` record is
|
|
3458
|
+
* *present*, never what this field says; grouping does not read `Q` at all; and the
|
|
3459
|
+
* split reader is a count of fields, not a reading of any one of them, so a record
|
|
3460
|
+
* carrying this warning has by definition already split.
|
|
3141
3461
|
*
|
|
3142
3462
|
* Everything a wrong value could hide from: a result value split ambiguity, an
|
|
3143
3463
|
* undefined abnormal flag or result status, an unparseable reference range, absent
|
|
3144
3464
|
* units, a mis-attached comment, a partial timestamp, a query-vs-result ambiguity,
|
|
3145
|
-
*
|
|
3146
|
-
*
|
|
3147
|
-
* forbidden.
|
|
3465
|
+
* an unrecognized record type, a record the delimiters in force could not split, a
|
|
3466
|
+
* bad frame checksum / sequence gap / unterminated / oversize frame, an ambiguous
|
|
3467
|
+
* transport, an unexpected protocol event, or a rejected frame: is forbidden.
|
|
3468
|
+
*
|
|
3469
|
+
* **What this file cannot do for you.** Part 2 is a review obligation, not a
|
|
3470
|
+
* mechanical one. There is no honest automatic check for "nothing load-bearing
|
|
3471
|
+
* reads this", because deciding whether a future reader of record structure has put
|
|
3472
|
+
* an allow-listed code back in the path takes reading the reader. What is
|
|
3473
|
+
* mechanical is the direction of the failure: default-deny means a *new* code is
|
|
3474
|
+
* refused until argued in, so the residual risk is confined to the small, explicit
|
|
3475
|
+
* list below. Re-derive that list whenever something new starts reading record
|
|
3476
|
+
* structure.
|
|
3148
3477
|
*/
|
|
3149
3478
|
|
|
3150
3479
|
/**
|
|
3151
|
-
* The **only** warning codes a profile may list in its `tolerate` set: benign
|
|
3152
|
-
* structural
|
|
3153
|
-
* extracted value
|
|
3154
|
-
*
|
|
3155
|
-
*
|
|
3480
|
+
* The **only** warning codes a profile may list in its `tolerate` set: benign
|
|
3481
|
+
* structural or syntactic vendor noise that cannot alter, drop, or fabricate an
|
|
3482
|
+
* extracted value, **and** whose reported condition nothing else in this package
|
|
3483
|
+
* reads: not to decide where one message ends and the next begins, and not to
|
|
3484
|
+
* decide what kind of message it is. Frozen so it cannot be mutated at runtime to
|
|
3485
|
+
* smuggle a code in. Adding to this set is a deliberate, reviewable act, and an
|
|
3486
|
+
* addition has to satisfy both halves of that test, not just the first.
|
|
3487
|
+
*
|
|
3488
|
+
* `ASTM_RECORD_UNKNOWN_TYPE` is **not** on this list, and used to be. An
|
|
3489
|
+
* unrecognized record type may be a header the reader did not recognize as one, in
|
|
3490
|
+
* which case two messages have been read as one, or a `Q` it did not recognize, in
|
|
3491
|
+
* which case the message kind is no longer knowable. Tolerating it can quiet the
|
|
3492
|
+
* only report either happened.
|
|
3493
|
+
*
|
|
3494
|
+
* `ASTM_RECORD_FIELDS_UNSEPARATED` is not on this list either, and never was. It
|
|
3495
|
+
* reports a record the delimiters in force could not split, so every modeled field
|
|
3496
|
+
* of that record is missing: it fails the first half of the test outright.
|
|
3156
3497
|
*
|
|
3157
3498
|
* @example
|
|
3158
3499
|
* ```ts
|
|
3159
3500
|
* import { TOLERABLE_CODES } from "@cosyte/astm";
|
|
3160
3501
|
* TOLERABLE_CODES.has("ASTM_UNKNOWN_ESCAPE_SEQUENCE"); // true
|
|
3502
|
+
* TOLERABLE_CODES.has("ASTM_RECORD_UNKNOWN_TYPE"); // false
|
|
3161
3503
|
* TOLERABLE_CODES.has("ASTM_FRAME_BAD_CHECKSUM"); // false
|
|
3162
3504
|
* ```
|
|
3163
3505
|
*/
|
|
@@ -3182,6 +3524,7 @@ declare const ALL_ASTM_WARNING_CODES: ReadonlySet<AnyAstmWarningCode>;
|
|
|
3182
3524
|
* ```ts
|
|
3183
3525
|
* import { SAFETY_CRITICAL_CODES } from "@cosyte/astm";
|
|
3184
3526
|
* SAFETY_CRITICAL_CODES.has("ASTM_RECORD_UNDEFINED_RESULT_STATUS"); // true
|
|
3527
|
+
* SAFETY_CRITICAL_CODES.has("ASTM_RECORD_UNKNOWN_TYPE"); // true
|
|
3185
3528
|
* SAFETY_CRITICAL_CODES.has("ASTM_FRAME_BAD_CHECKSUM"); // true
|
|
3186
3529
|
* ```
|
|
3187
3530
|
*/
|
|
@@ -3199,6 +3542,7 @@ declare const SAFETY_CRITICAL_CODES: ReadonlySet<AnyAstmWarningCode>;
|
|
|
3199
3542
|
* import { isSafetyCriticalCode } from "@cosyte/astm";
|
|
3200
3543
|
* isSafetyCriticalCode("ASTM_UNKNOWN_ESCAPE_SEQUENCE"); // false (tolerable)
|
|
3201
3544
|
* isSafetyCriticalCode("ASTM_RECORD_AMBIGUOUS_VALUE_SPLIT"); // true
|
|
3545
|
+
* isSafetyCriticalCode("ASTM_RECORD_UNKNOWN_TYPE"); // true (it can mean a lost message boundary)
|
|
3202
3546
|
* ```
|
|
3203
3547
|
*/
|
|
3204
3548
|
declare function isSafetyCriticalCode(code: string): boolean;
|
|
@@ -3255,8 +3599,11 @@ declare const defaultProfile: AstmProfile;
|
|
|
3255
3599
|
* only re-badges the *warning*: no value, flag, status, or identifier is altered
|
|
3256
3600
|
* or dropped. And by the safety gate in `defineAstmProfile`, it **cannot** tolerate
|
|
3257
3601
|
* a result value / flag / status / range / units, a patient or comment context, a
|
|
3258
|
-
* message-kind ambiguity, or any frame / LTP integrity
|
|
3259
|
-
* surface, profile or not.
|
|
3602
|
+
* message-kind ambiguity, an unrecognized record type, or any frame / LTP integrity
|
|
3603
|
+
* warning: those always surface, profile or not. The unrecognized-record-type case
|
|
3604
|
+
* matters because message grouping reads a record's type letter, so a letter the
|
|
3605
|
+
* reader does not recognize may be a header, and a header read as something else
|
|
3606
|
+
* joins two messages into one.
|
|
3260
3607
|
*
|
|
3261
3608
|
* **Honesty.** This is a *reference-corpus* profile, not a claim about any named
|
|
3262
3609
|
* analyzer vendor. No per-vendor ("cobas does X") behaviour is asserted; a named
|
|
@@ -4312,4 +4659,4 @@ declare class AstmFrameStrictError extends Error {
|
|
|
4312
4659
|
*/
|
|
4313
4660
|
declare const VERSION: string;
|
|
4314
4661
|
|
|
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 };
|
|
4662
|
+
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 UnpairedEscapeSink, 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, fieldsUnseparated, 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, unpairedEscapeCharacter, unparseableReferenceRange, unreadableRedeclaration };
|