@cosyte/astm 0.0.4 → 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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @cosyte/astm
2
2
 
3
- > ASTM parser, serializer, and builder for Node.js and TypeScript — **lenient on parse,
3
+ > ASTM parser, serializer, and builder for Node.js and TypeScript: **lenient on parse,
4
4
  > spec-clean on emit**.
5
5
 
6
6
  `@cosyte/astm` is a zero-dependency TypeScript toolkit that follows the cosyte parser archetype: a lenient
@@ -32,8 +32,8 @@ reference parser, [`@cosyte/hl7`](https://github.com/cosyte/hl7).
32
32
  - **Emit.** `serializeAstmRecords` and `buildAstmMessage` emit canonical `H|\^&` records with embedded
33
33
  delimiters re-escaped and nothing clinical fabricated; `composeAstmFrames` and `serializeFramedAstm`
34
34
  frame them with computed checksums, frame numbers, and the 240-byte split. Both layers round-trip by
35
- construction, and a delimiter set that fails any of the three conditions readback requires — one
36
- character per separator, no `CR`/`LF`, no two the same — is a typed error rather than bytes written
35
+ construction, and a delimiter set that fails any of the three conditions readback requires (one
36
+ character per separator, no `CR`/`LF`, no two the same) is a typed error rather than bytes written
37
37
  and lost.
38
38
  - **Vendor profiles.** `defineAstmProfile()` builds a provenance-backed profile whose tolerances
39
39
  downgrade _expected_, non-safety-critical deviations to a `PROFILE_QUIRK_APPLIED` warning without
@@ -69,7 +69,7 @@ A checksum mismatch, a sequence gap, an unterminated frame, and an oversize (>24
69
69
  ## Drive the transport (framed vs raw) + the LTP protocol
70
70
 
71
71
  ASTM transport is not uniform: **serial** always frames, but over **TCP it varies within a single
72
- vendor** — the cobas 4800 and Iguana keep the full `ENQ`/`ACK` + `STX`/checksum framing, while the
72
+ vendor**, the cobas 4800 and Iguana keep the full `ENQ`/`ACK` + `STX`/checksum framing, while the
73
73
  cobas b121 drops it and streams de-framed record bytes directly. Detect which you have, then drive the
74
74
  pure protocol reducer with your own socket I/O.
75
75
 
@@ -85,7 +85,7 @@ import {
85
85
  // 1. Route by the stream's leading byte (STX/ENQ ⇒ framed; a bare record letter ⇒ raw).
86
86
  const { framing } = detectFraming(leadingBytes); // "framed" | "raw" (override: { override: "raw" })
87
87
  if (framing === "raw") {
88
- // cobas b121 raw-TCP: no handshake, no frames — parse the record bytes directly.
88
+ // cobas b121 raw-TCP: no handshake, no frames, parse the record bytes directly.
89
89
  parseAstmRecords(rawBytes);
90
90
  }
91
91
 
@@ -95,19 +95,19 @@ function onControlOrFrame(event) {
95
95
  const { state: next, actions, warnings } = ltpReduce(state, event);
96
96
  state = next;
97
97
  for (const a of actions) {
98
- if (a.type === "sendAck") socket.write(Uint8Array.of(0x06)); // ACK — only ever for a good frame
99
- if (a.type === "sendNak") socket.write(Uint8Array.of(0x15)); // NAK — bad checksum ⇒ retransmit
98
+ if (a.type === "sendAck") socket.write(Uint8Array.of(0x06)); // ACK, only ever for a good frame
99
+ if (a.type === "sendNak") socket.write(Uint8Array.of(0x15)); // NAK, bad checksum ⇒ retransmit
100
100
  if (a.type === "deliverRecord") parseAstmRecords(a.record); // a complete, trusted record
101
101
  }
102
- void warnings; // ASTM_LTP_* — value-free (a code + at most a frame number)
102
+ void warnings; // ASTM_LTP_*, value-free (a code + at most a frame number)
103
103
  }
104
104
  // Feed events as you read them: { type: "enq" }, { type: "frame", frame: decodeAstmFrames(b).frames[0] }, …
105
105
  ```
106
106
 
107
107
  The reducer is deterministic and fully testable without a socket. Its inviolable rule: a frame the
108
- codec did not vouch for — bad checksum, unterminated, or out of sequence — yields `sendNak`, **never** a
108
+ codec did not vouch for (bad checksum, unterminated, or out of sequence) yields `sendNak`, **never** a
109
109
  fabricated `sendAck`, and is **never** appended to a record. A `NAK` drives retransmit, not acceptance.
110
- The interactive contention/timeout/retransmit **timing** is the consumer's — this layer models the
110
+ The interactive contention/timeout/retransmit **timing** is the consumer's: this layer models the
111
111
  state transitions, not the wall-clock timers.
112
112
 
113
113
  ## Install
@@ -130,7 +130,7 @@ patient(msg)?.practiceAssignedId; // kept distinct from laboratoryAssignedId (th
130
130
  msg.warnings; // stable, value-free positional tolerance warnings (never throws on quirks)
131
131
  ```
132
132
 
133
- The parser is **lenient by default** — vendor quirks become warnings, not failures — and refuses to
133
+ The parser is **lenient by default** (vendor quirks become warnings, not failures) and refuses to
134
134
  produce a confident wrong value: an embedded escaped delimiter reads as one component, an unknown
135
135
  record type is surfaced (never dropped), and a missing unit is flagged (never defaulted). A
136
136
  `{ strict: true }` mode escalates every tolerated deviation to a thrown error.
@@ -138,7 +138,7 @@ record type is surfaced (never dropped), and a missing unit is flagged (never de
138
138
  ### Several messages in one stream
139
139
 
140
140
  A message runs from its `H` header to its `L` terminator, so a stream can carry several. `messages()`
141
- splits a parsed stream into them, and each entry carries only its own records — so a patient is only
141
+ splits a parsed stream into them, and each entry carries only its own records, so a patient is only
142
142
  ever paired with the results that message actually carried:
143
143
 
144
144
  ```ts
@@ -168,7 +168,7 @@ already names the message.
168
168
  ## Map local codes to LOINC (LIVD, bring-your-own)
169
169
 
170
170
  An analyzer sends a proprietary local test code in the Universal Test ID; a standard LOINC is mapped
171
- downstream. Supply your own IICC LIVD ("LOINC to Vendor IVD") catalog and annotate a message — the
171
+ downstream. Supply your own IICC LIVD ("LOINC to Vendor IVD") catalog and annotate a message, the
172
172
  mapping is **additive and advisory**: it never touches the raw code or value, and an unmapped or
173
173
  ambiguous code is surfaced as such, **never a guessed LOINC**.
174
174
 
@@ -187,31 +187,31 @@ warnings; // ASTM_LIVD_UNMAPPED_CODE / ASTM_LIVD_AMBIGUOUS_MAPPING (value-free)
187
187
 
188
188
  **No LOINC / SNOMED / LIVD dictionary is bundled.** LOINC is © Regenstrief (redistributable only with
189
189
  its attribution notice) and the public CDC LIVD file is SARS-CoV-2-specific and carries
190
- separately-licensed SNOMED CT — so the package ships no terminology data and you bring the catalog (and
190
+ separately-licensed SNOMED CT, so the package ships no terminology data and you bring the catalog (and
191
191
  its license obligations).
192
192
 
193
193
  > **Scope your catalog to the source device fleet.** The ASTM Universal Test ID carries no manufacturer
194
194
  > to disambiguate against, so the catalog keys on the vendor transmission code alone. Two different
195
- > instruments that reuse the same code for different analytes would both match — supply a catalog built
195
+ > instruments that reuse the same code for different analytes would both match: supply a catalog built
196
196
  > for the analyzers you actually receive from. (Conflicting entries _within_ one catalog are caught and
197
197
  > surfaced as `ambiguous`, never resolved to a guess.)
198
198
 
199
199
  ## The cosyte parser archetype
200
200
 
201
- - **Postel's Law** — liberal parser (lenient default + warnings), conservative serializer (always
201
+ - **Postel's Law**: liberal parser (lenient default + warnings), conservative serializer (always
202
202
  spec-clean), so quirks don't propagate downstream on round-trip.
203
- - **Tiered tolerance** — Tier 0/1 silent, Tier 2 warning + recovery (escalates in strict mode),
203
+ - **Tiered tolerance**: Tier 0/1 silent, Tier 2 warning + recovery (escalates in strict mode),
204
204
  Tier 3 fatal always.
205
- - **Stable warning codes** — warnings carry stable string codes + positional context; consumers
205
+ - **Stable warning codes**: warnings carry stable string codes + positional context; consumers
206
206
  branch on `w.code`, so renaming a code is a breaking change.
207
- - **Zero runtime dependencies** — Node stdlib only (healthcare integrations vet every dependency).
208
- - **Dual ESM + CJS** — built with `tsup`, validated with `attw`.
209
- - **Immutability** — parsed models are immutable; mutation is via explicit methods.
210
- - **Profile system** — a `defineAstmProfile()` API for vendor quirks, with built-in profiles authored
207
+ - **Zero runtime dependencies**: Node stdlib only (healthcare integrations vet every dependency).
208
+ - **Dual ESM + CJS**: built with `tsup`, validated with `attw`.
209
+ - **Immutability**: parsed models are immutable; mutation is via explicit methods.
210
+ - **Profile system**: a `defineAstmProfile()` API for vendor quirks, with built-in profiles authored
211
211
  through the same public API. A profile only ever downgrades an _expected_, non-safety-critical warning
212
212
  to `PROFILE_QUIRK_APPLIED` (it never alters a value) and may force the raw-vs-framed transport; a
213
213
  default-deny safety gate refuses to tolerate any safety-critical deviation at definition time.
214
- - **Terminology recognizer, not a dictionary** — LIVD-aware LOINC recognition is bring-your-own
214
+ - **Terminology recognizer, not a dictionary.** LIVD-aware LOINC recognition is bring-your-own
215
215
  (`applyLivd` over a consumer-supplied catalog): additive, advisory, and never a guessed LOINC. No
216
216
  LOINC / SNOMED / LIVD data is bundled.
217
217
 
package/dist/index.cjs CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  // src/common/errors.ts
4
4
  var FATAL_CODES = {
5
- /** Input was empty or whitespace-only — there is nothing to parse. Shared across layers. */
5
+ /** Input was empty or whitespace-only: there is nothing to parse. Shared across layers. */
6
6
  EMPTY_INPUT: "EMPTY_INPUT",
7
- /** The first record is not an `H` (header) record — an ASTM message must lead with `H`. */
7
+ /** The first record is not an `H` (header) record: an ASTM message must lead with `H`. */
8
8
  ASTM_RECORD_NO_HEADER: "ASTM_RECORD_NO_HEADER",
9
9
  /** The `H` record is too short to declare the four delimiters (field/repeat/component/escape). */
10
10
  ASTM_RECORD_UNDECLARED_DELIMITERS: "ASTM_RECORD_UNDECLARED_DELIMITERS"
@@ -121,21 +121,21 @@ function primaryCode(u) {
121
121
 
122
122
  // src/common/warnings.ts
123
123
  var WARNING_CODES = {
124
- /** A record's type letter is not one of the modeled types — surfaced as an unsupported record. */
124
+ /** A record's type letter is not one of the modeled types: surfaced as an unsupported record. */
125
125
  ASTM_RECORD_UNKNOWN_TYPE: "ASTM_RECORD_UNKNOWN_TYPE",
126
- /** The header declared delimiters other than the canonical `H|\^&` — tolerated, noted. */
126
+ /** The header declared delimiters other than the canonical `H|\^&`: tolerated, noted. */
127
127
  ASTM_NONSTANDARD_DELIMITERS: "ASTM_NONSTANDARD_DELIMITERS",
128
- /** An escape sequence body was not one of `&F&`/`&S&`/`&R&`/`&E&` — preserved verbatim. */
128
+ /** An escape sequence body was not one of `&F&`/`&S&`/`&R&`/`&E&`: preserved verbatim. */
129
129
  ASTM_UNKNOWN_ESCAPE_SEQUENCE: "ASTM_UNKNOWN_ESCAPE_SEQUENCE",
130
130
  /**
131
131
  * A result value field carried an *unescaped* component delimiter, so it split into more than one
132
- * component. Both the full raw value and the split are surfaced and this warning fires — the
132
+ * component. Both the full raw value and the split are surfaced and this warning fires: the
133
133
  * ambiguity is never resolved silently into a truncated value.
134
134
  */
135
135
  ASTM_RECORD_AMBIGUOUS_VALUE_SPLIT: "ASTM_RECORD_AMBIGUOUS_VALUE_SPLIT",
136
136
  /**
137
137
  * A result's abnormal-flag field (`R` field 7) carried a letter outside HL7 Table 0078. The flag is
138
- * surfaced as `undefined` — never dropped, and **never coerced to `normal`** (a clinical error).
138
+ * surfaced as `undefined`: never dropped, and **never coerced to `normal`** (a clinical error).
139
139
  */
140
140
  ASTM_RECORD_UNDEFINED_ABNORMAL_FLAG: "ASTM_RECORD_UNDEFINED_ABNORMAL_FLAG",
141
141
  /**
@@ -145,7 +145,7 @@ var WARNING_CODES = {
145
145
  ASTM_RECORD_UNDEFINED_RESULT_STATUS: "ASTM_RECORD_UNDEFINED_RESULT_STATUS",
146
146
  /**
147
147
  * A result's reference-range field (`R` field 6) did not match a recognized form (`low-high`,
148
- * `<high`, `>low`). The text is surfaced verbatim as `unparsed` — a bound is **never fabricated**.
148
+ * `<high`, `>low`). The text is surfaced verbatim as `unparsed`: a bound is **never fabricated**.
149
149
  */
150
150
  ASTM_RECORD_UNPARSEABLE_REFERENCE_RANGE: "ASTM_RECORD_UNPARSEABLE_REFERENCE_RANGE",
151
151
  /**
@@ -154,25 +154,25 @@ var WARNING_CODES = {
154
154
  */
155
155
  ASTM_RECORD_UNITS_ABSENT: "ASTM_RECORD_UNITS_ABSENT",
156
156
  /**
157
- * A `C` (comment) record had no valid preceding `H`/`P`/`O`/`R` parent — an **orphan**. The comment
157
+ * A `C` (comment) record had no valid preceding `H`/`P`/`O`/`R` parent: an **orphan**. The comment
158
158
  * is attached to the message root (`attachedToRoot: true`) and surfaced, **never dropped**.
159
159
  */
160
160
  ASTM_RECORD_ORPHAN_COMMENT: "ASTM_RECORD_ORPHAN_COMMENT",
161
161
  /**
162
162
  * A `YYYYMMDDHHMMSS` timestamp had an odd digit run that truncates a two-digit component in half
163
163
  * (e.g. a partial day/hour). The raw run is preserved and the structured value stops at the last
164
- * **complete** component — the dangling digit is **never zero-filled into a fabricated time**.
164
+ * **complete** component: the dangling digit is **never zero-filled into a fabricated time**.
165
165
  */
166
166
  ASTM_RECORD_PARTIAL_TIMESTAMP: "ASTM_RECORD_PARTIAL_TIMESTAMP",
167
167
  /**
168
168
  * A `Q` (request-information) record carried a request-information status code (field 13). The code
169
169
  * *set* is `[OSS-derived / paywalled]` with no publicly-groundable enumeration, so the parser
170
170
  * interprets **none** of them: the status is surfaced verbatim and this value-free warning flags that
171
- * it was passed through **uninterpreted** — never mapped to a guessed meaning.
171
+ * it was passed through **uninterpreted**, never mapped to a guessed meaning.
172
172
  */
173
173
  ASTM_RECORD_UNINTERPRETED_QUERY_STATUS: "ASTM_RECORD_UNINTERPRETED_QUERY_STATUS",
174
174
  /**
175
- * A message carried **both** a `Q` (request) and an `R` (result) record — a contradictory shape. The
175
+ * A message carried **both** a `Q` (request) and an `R` (result) record: a contradictory shape. The
176
176
  * message is classified `host-query` (the `Q` **dominates**, so it is never read as a result set) and
177
177
  * this warning flags the anomaly. Positional context only; no field value.
178
178
  */
@@ -181,7 +181,7 @@ var WARNING_CODES = {
181
181
  * A later `H` (header) record declared a **different** delimiter set from the one in force, and the
182
182
  * parser **followed it**: that header and every record after it are read with the newly-declared
183
183
  * delimiters, until the next `H`. Records already read keep the set that was in force when they were
184
- * read — a redeclaration never reinterprets bytes that have already been consumed.
184
+ * read: a redeclaration never reinterprets bytes that have already been consumed.
185
185
  *
186
186
  * A stream carrying several messages back to back is ordinary, and a header that simply **repeats**
187
187
  * the delimiters already in force is a no-op that warns nothing. This code fires only when the set
@@ -190,13 +190,13 @@ var WARNING_CODES = {
190
190
  */
191
191
  ASTM_RECORD_DELIMITERS_REDECLARED: "ASTM_RECORD_DELIMITERS_REDECLARED",
192
192
  /**
193
- * A later `H` (header) record could not declare a usable delimiter set — it was too short, or the
193
+ * A later `H` (header) record could not declare a usable delimiter set: it was too short, or the
194
194
  * field separator it named also appeared among the other three, leaving the four roles
195
195
  * indistinguishable. The delimiters **already in force are kept** and every record is still surfaced;
196
196
  * a set is never guessed and no record is dropped.
197
197
  *
198
198
  * The same condition on the *first* header is unrecoverable and remains the
199
- * `ASTM_RECORD_UNDECLARED_DELIMITERS` fatal — there is no earlier set to fall back to.
199
+ * `ASTM_RECORD_UNDECLARED_DELIMITERS` fatal: there is no earlier set to fall back to.
200
200
  */
201
201
  ASTM_RECORD_UNREADABLE_REDECLARATION: "ASTM_RECORD_UNREADABLE_REDECLARATION",
202
202
  /**
@@ -204,7 +204,7 @@ var WARNING_CODES = {
204
204
  * (see `src/profiles/`). The original warning is **never dropped**: its code moves to
205
205
  * {@link AstmRecordWarning.toleratedCode}, the warning is re-badged `PROFILE_QUIRK_APPLIED` with
206
206
  * `expected: true` and the tolerating profile named, so a consumer can filter known, grounded noise
207
- * while the fact of the deviation — and where it was — survives. A profile can only ever reach this
207
+ * while the fact of the deviation, and where it was, survives. A profile can only ever reach this
208
208
  * path for a **non-safety-critical** code (enforced at profile-definition time); a safety-critical
209
209
  * deviation (a result value, flag, status, patient identifier, code system, or a frame-integrity
210
210
  * warning) can **never** be tolerated, so it can never be re-badged here.
@@ -214,28 +214,28 @@ var WARNING_CODES = {
214
214
  function unknownRecordType(position) {
215
215
  return {
216
216
  code: WARNING_CODES.ASTM_RECORD_UNKNOWN_TYPE,
217
- message: "Unrecognized record type \u2014 surfaced verbatim as an unsupported record.",
217
+ message: "Unrecognized record type, surfaced verbatim as an unsupported record.",
218
218
  position
219
219
  };
220
220
  }
221
221
  function nonStandardDelimiters(position) {
222
222
  return {
223
223
  code: WARNING_CODES.ASTM_NONSTANDARD_DELIMITERS,
224
- message: "Header declared non-canonical delimiters \u2014 read from the header and honored.",
224
+ message: "Header declared non-canonical delimiters, read from the header and honored.",
225
225
  position
226
226
  };
227
227
  }
228
228
  function delimitersRedeclared(position) {
229
229
  return {
230
230
  code: WARNING_CODES.ASTM_RECORD_DELIMITERS_REDECLARED,
231
- message: "A later header declared different delimiters \u2014 honored from that header onward; earlier records keep the set they were read with.",
231
+ message: "A later header declared different delimiters, honored from that header onward; earlier records keep the set they were read with.",
232
232
  position
233
233
  };
234
234
  }
235
235
  function unreadableRedeclaration(position) {
236
236
  return {
237
237
  code: WARNING_CODES.ASTM_RECORD_UNREADABLE_REDECLARATION,
238
- message: "A later header could not declare a usable delimiter set \u2014 the delimiters already in force were kept.",
238
+ message: "A later header could not declare a usable delimiter set, the delimiters already in force were kept.",
239
239
  position
240
240
  };
241
241
  }
@@ -249,63 +249,63 @@ function unknownEscapeSequence(position) {
249
249
  function ambiguousValueSplit(position) {
250
250
  return {
251
251
  code: WARNING_CODES.ASTM_RECORD_AMBIGUOUS_VALUE_SPLIT,
252
- message: "Result value contained an unescaped component delimiter \u2014 full raw value and split both surfaced.",
252
+ message: "Result value contained an unescaped component delimiter, full raw value and split both surfaced.",
253
253
  position
254
254
  };
255
255
  }
256
256
  function undefinedAbnormalFlag(position) {
257
257
  return {
258
258
  code: WARNING_CODES.ASTM_RECORD_UNDEFINED_ABNORMAL_FLAG,
259
- message: "Abnormal flag is not in HL7 Table 0078 \u2014 surfaced as undefined, never as normal.",
259
+ message: "Abnormal flag is not in HL7 Table 0078, surfaced as undefined, never as normal.",
260
260
  position
261
261
  };
262
262
  }
263
263
  function undefinedResultStatus(position) {
264
264
  return {
265
265
  code: WARNING_CODES.ASTM_RECORD_UNDEFINED_RESULT_STATUS,
266
- message: "Result status is not a recognized status letter \u2014 surfaced as undefined.",
266
+ message: "Result status is not a recognized status letter, surfaced as undefined.",
267
267
  position
268
268
  };
269
269
  }
270
270
  function unparseableReferenceRange(position) {
271
271
  return {
272
272
  code: WARNING_CODES.ASTM_RECORD_UNPARSEABLE_REFERENCE_RANGE,
273
- message: "Reference range did not match a recognized form \u2014 surfaced verbatim, no bound invented.",
273
+ message: "Reference range did not match a recognized form, surfaced verbatim, no bound invented.",
274
274
  position
275
275
  };
276
276
  }
277
277
  function unitsAbsent(position) {
278
278
  return {
279
279
  code: WARNING_CODES.ASTM_RECORD_UNITS_ABSENT,
280
- message: "Numeric result value carried no units \u2014 never defaulted, guessed, or converted.",
280
+ message: "Numeric result value carried no units, never defaulted, guessed, or converted.",
281
281
  position
282
282
  };
283
283
  }
284
284
  function orphanComment(position) {
285
285
  return {
286
286
  code: WARNING_CODES.ASTM_RECORD_ORPHAN_COMMENT,
287
- message: "Comment had no valid preceding parent \u2014 attached to the message root, never dropped.",
287
+ message: "Comment had no valid preceding parent, attached to the message root, never dropped.",
288
288
  position
289
289
  };
290
290
  }
291
291
  function partialTimestamp(position) {
292
292
  return {
293
293
  code: WARNING_CODES.ASTM_RECORD_PARTIAL_TIMESTAMP,
294
- message: "Timestamp digit run truncates a component \u2014 preserved verbatim, never zero-filled.",
294
+ message: "Timestamp digit run truncates a component, preserved verbatim, never zero-filled.",
295
295
  position
296
296
  };
297
297
  }
298
298
  function uninterpretedQueryStatus(position) {
299
299
  return {
300
300
  code: WARNING_CODES.ASTM_RECORD_UNINTERPRETED_QUERY_STATUS,
301
- message: "Query request-information status surfaced verbatim \u2014 code set paywalled, never interpreted.",
301
+ message: "Query request-information status surfaced verbatim, code set paywalled, never interpreted.",
302
302
  position
303
303
  };
304
304
  }
305
305
  function ambiguousMessageKind(position) {
306
306
  return {
307
307
  code: WARNING_CODES.ASTM_RECORD_AMBIGUOUS_MESSAGE_KIND,
308
- message: "Message carried both a Q (request) and an R (result) record \u2014 classified host-query; Q dominates.",
308
+ message: "Message carried both a Q (request) and an R (result) record, classified host-query; Q dominates.",
309
309
  position
310
310
  };
311
311
  }
@@ -569,12 +569,12 @@ var FRAME_WARNING_CODES = {
569
569
  * A frame's two-hex-char checksum did not match the modulo-256 sum recomputed over its bytes. The
570
570
  * frame is surfaced with `trusted: false` and its text is **never merged** into a reassembled
571
571
  * record (default **warn** in lenient mode, escalated to a thrown error in `strict`). Corruption is
572
- * never silently trusted — the "checksums are routinely not validated" claim was refuted; we
572
+ * never silently trusted: the "checksums are routinely not validated" claim was refuted; we
573
573
  * validate.
574
574
  */
575
575
  ASTM_FRAME_BAD_CHECKSUM: "ASTM_FRAME_BAD_CHECKSUM",
576
576
  /**
577
- * A frame's sequence number was not the expected next value (`1 → … → 7 → 0 → …`) — a frame was
577
+ * A frame's sequence number was not the expected next value (`1 → … → 7 → 0 → …`): a frame was
578
578
  * possibly dropped. The stream is **never silently concatenated across the gap** as if contiguous;
579
579
  * the in-progress record is tainted and not emitted as a clean reassembly.
580
580
  */
@@ -588,7 +588,7 @@ var FRAME_WARNING_CODES = {
588
588
  ASTM_FRAME_UNTERMINATED: "ASTM_FRAME_UNTERMINATED",
589
589
  /**
590
590
  * A frame's record text exceeded the 240-byte limit without a split. The frame is still surfaced
591
- * (and, if its checksum validates, reassembled) — the deviation is flagged, not silently dropped
591
+ * (and, if its checksum validates, reassembled): the deviation is flagged, not silently dropped
592
592
  * (warn in lenient, thrown in `strict`).
593
593
  */
594
594
  ASTM_FRAME_OVERSIZE: "ASTM_FRAME_OVERSIZE"
@@ -596,28 +596,28 @@ var FRAME_WARNING_CODES = {
596
596
  function frameBadChecksum(position) {
597
597
  return {
598
598
  code: FRAME_WARNING_CODES.ASTM_FRAME_BAD_CHECKSUM,
599
- message: "Frame checksum mismatch \u2014 surfaced untrusted, never merged into a record.",
599
+ message: "Frame checksum mismatch, surfaced untrusted, never merged into a record.",
600
600
  position
601
601
  };
602
602
  }
603
603
  function frameSequenceGap(position) {
604
604
  return {
605
605
  code: FRAME_WARNING_CODES.ASTM_FRAME_SEQUENCE_GAP,
606
- message: "Frame number out of sequence \u2014 possible dropped frame, never silently bridged.",
606
+ message: "Frame number out of sequence, possible dropped frame, never silently bridged.",
607
607
  position
608
608
  };
609
609
  }
610
610
  function frameUnterminated(position) {
611
611
  return {
612
612
  code: FRAME_WARNING_CODES.ASTM_FRAME_UNTERMINATED,
613
- message: "Unterminated frame \u2014 partial bytes surfaced untrusted, no partial record invented.",
613
+ message: "Unterminated frame, partial bytes surfaced untrusted, no partial record invented.",
614
614
  position
615
615
  };
616
616
  }
617
617
  function frameOversize(position) {
618
618
  return {
619
619
  code: FRAME_WARNING_CODES.ASTM_FRAME_OVERSIZE,
620
- message: "Frame text exceeded the 240-byte limit without a split \u2014 flagged, never dropped.",
620
+ message: "Frame text exceeded the 240-byte limit without a split, flagged, never dropped.",
621
621
  position
622
622
  };
623
623
  }
@@ -626,21 +626,21 @@ function frameOversize(position) {
626
626
  var LTP_WARNING_CODES = {
627
627
  /**
628
628
  * The transport detector could not tell a framed stream from a raw (unframed) one from its leading
629
- * byte — it was neither `STX`/`ENQ` nor a bare record letter. It **defaults to framed** and warns,
629
+ * byte: it was neither `STX`/`ENQ` nor a bare record letter. It **defaults to framed** and warns,
630
630
  * never guessing silently into data loss; a profile override forces the mode. (cobas b121 drops
631
- * framing over TCP; cobas 4800 / Iguana retain it — both realities exist, so an unrecognizable lead
631
+ * framing over TCP; cobas 4800 / Iguana retain it, both realities exist, so an unrecognizable lead
632
632
  * is defaulted, not assumed.)
633
633
  */
634
634
  ASTM_LTP_AMBIGUOUS_TRANSPORT: "ASTM_LTP_AMBIGUOUS_TRANSPORT",
635
635
  /**
636
- * A control event arrived in a protocol state that did not expect it — e.g. an inbound `ACK`/`NAK`
636
+ * A control event arrived in a protocol state that did not expect it: e.g. an inbound `ACK`/`NAK`
637
637
  * at a receiver (which sends, never receives, those), or an `ENQ` mid-transfer. The event is
638
638
  * surfaced and handled defensively (an unexpected `ACK`/`NAK` is **never** read as acceptance of
639
639
  * data); it never advances the transfer as if valid.
640
640
  */
641
641
  ASTM_LTP_UNEXPECTED_EVENT: "ASTM_LTP_UNEXPECTED_EVENT",
642
642
  /**
643
- * The receiver rejected a received frame with a `NAK` instead of accepting it — because its
643
+ * The receiver rejected a received frame with a `NAK` instead of accepting it: because its
644
644
  * checksum failed, it was unterminated, or its frame number was out of sequence. The frame's text
645
645
  * is **never** appended to the record and the transfer does **not** advance; the receiver awaits the
646
646
  * sender's retransmit. This is the protocol-level face of the frame codec's fail-safe: a bad frame
@@ -651,20 +651,20 @@ var LTP_WARNING_CODES = {
651
651
  function ltpAmbiguousTransport() {
652
652
  return {
653
653
  code: LTP_WARNING_CODES.ASTM_LTP_AMBIGUOUS_TRANSPORT,
654
- message: "Leading byte was neither a frame start (STX/ENQ) nor a record letter \u2014 defaulted to framed; override with a profile to force raw."
654
+ message: "Leading byte was neither a frame start (STX/ENQ) nor a record letter, defaulted to framed; override with a profile to force raw."
655
655
  };
656
656
  }
657
657
  function ltpUnexpectedEvent(frameNumber) {
658
658
  return {
659
659
  code: LTP_WARNING_CODES.ASTM_LTP_UNEXPECTED_EVENT,
660
- message: "Control event arrived in a state that did not expect it \u2014 handled defensively, never read as acceptance.",
660
+ message: "Control event arrived in a state that did not expect it, handled defensively, never read as acceptance.",
661
661
  ...frameNumber !== void 0 ? { frameNumber } : {}
662
662
  };
663
663
  }
664
664
  function ltpFrameRejected(frameNumber) {
665
665
  return {
666
666
  code: LTP_WARNING_CODES.ASTM_LTP_FRAME_REJECTED,
667
- message: "Frame rejected (bad checksum, unterminated, or out of sequence) \u2014 NAK sent, retransmit expected, never accepted.",
667
+ message: "Frame rejected (bad checksum, unterminated, or out of sequence), NAK sent, retransmit expected, never accepted.",
668
668
  ...frameNumber !== void 0 ? { frameNumber } : {}
669
669
  };
670
670
  }
@@ -772,7 +772,7 @@ function validateTolerations(tolerate, profileName) {
772
772
  }
773
773
  if (isSafetyCriticalCode(t.code)) {
774
774
  throw new AstmProfileDefinitionError(
775
- `Profile '${profileName}' may not tolerate '${t.code}' \u2014 it is a safety-critical warning code (a result value / flag / status / range / units, patient or comment context, message-kind ambiguity, code system, or a frame / LTP integrity warning). A profile quiets benign structural noise, never a deviation that could change a clinical reading or corrupt the wire \u2014 it can never make a bad checksum "ok" or a cancelled result read "final."`,
775
+ `Profile '${profileName}' may not tolerate '${t.code}': it is a safety-critical warning code (a result value / flag / status / range / units, patient or comment context, message-kind ambiguity, code system, or a frame / LTP integrity warning). A profile quiets benign structural noise, never a deviation that could change a clinical reading or corrupt the wire: it can never make a bad checksum "ok" or a cancelled result read "final."`,
776
776
  profileName
777
777
  );
778
778
  }
@@ -815,14 +815,14 @@ function defineAstmProfile(opts) {
815
815
  // src/profiles/default.ts
816
816
  var defaultProfile = defineAstmProfile({
817
817
  name: "default",
818
- description: "Conservative baseline \u2014 tolerates nothing; every deviation surfaces as a warning.",
818
+ description: "Conservative baseline: tolerates nothing; every deviation surfaces as a warning.",
819
819
  tolerate: []
820
820
  });
821
821
 
822
822
  // src/profiles/reference-corpus.ts
823
823
  var referenceCorpus = defineAstmProfile({
824
824
  name: "referenceCorpus",
825
- description: "Non-standard-escape tolerance grounded firsthand in the escape-agnostic OSS reference corpus (python-astm / senaite). Syntactic encoding noise only \u2014 the value is preserved verbatim, never a safety-critical value.",
825
+ description: "Non-standard-escape tolerance grounded firsthand in the escape-agnostic OSS reference corpus (python-astm / senaite). Syntactic encoding noise only: the value is preserved verbatim, never a safety-critical value.",
826
826
  provenance: {
827
827
  source: "kxepal/python-astm codec.py (BSD) + senaite.astm / senaite.lis2a",
828
828
  reference: "https://github.com/kxepal/python-astm/blob/master/astm/codec.py ; https://github.com/senaite/senaite.astm",
@@ -1086,7 +1086,7 @@ function buildRecord(line2, recordIndex, d, warnings) {
1086
1086
  case "Q":
1087
1087
  return buildQuery(recordIndex, fields, warnings);
1088
1088
  // `M` (manufacturer) and `S` (scientific) are vendor-defined free-form data surfaced VERBATIM and
1089
- // NEVER interpreted into typed clinical fields — the exact wire line is preserved for round-trip.
1089
+ // NEVER interpreted into typed clinical fields: the exact wire line is preserved for round-trip.
1090
1090
  case "M":
1091
1091
  return { type: "M", recordIndex, fields, rawLine: line2 };
1092
1092
  case "S":
@@ -1119,7 +1119,7 @@ function buildPatient(recordIndex, fields, warnings) {
1119
1119
  recordIndex,
1120
1120
  fields,
1121
1121
  ...definedString("seq", fieldScalar(astmField(fields, 2))),
1122
- // The three patient identifiers stay DISTINCT — practice-assigned (3), laboratory-assigned (4),
1122
+ // The three patient identifiers stay DISTINCT: practice-assigned (3), laboratory-assigned (4),
1123
1123
  // and a third ID (5) never collapse into one; conflating them is the primary misfiling path.
1124
1124
  ...definedString("practiceAssignedId", fieldScalar(astmField(fields, 3))),
1125
1125
  ...definedString("laboratoryAssignedId", fieldScalar(astmField(fields, 4))),
@@ -1142,7 +1142,7 @@ function buildOrder(recordIndex, fields) {
1142
1142
  ...hasContent(utidField) ? { universalTestId: recognizeUniversalTestId(utidField.components) } : {},
1143
1143
  // Priority (6), action code (~12), and report type (~26) are surfaced verbatim. The `~` field
1144
1144
  // indices and the code sets are `[OSS-derived]` (paywalled), so they are never mapped to a
1145
- // guessed meaning — see the JSDoc on `OrderRecord`.
1145
+ // guessed meaning: see the JSDoc on `OrderRecord`.
1146
1146
  ...definedString("priority", fieldScalar(astmField(fields, 6))),
1147
1147
  ...definedString("actionCode", fieldScalar(astmField(fields, 12))),
1148
1148
  ...definedString("reportType", fieldScalar(astmField(fields, 26)))
@@ -1541,7 +1541,7 @@ var AMBIGUOUS_CODES = {
1541
1541
  ASTM_AMBIGUOUS_MULTI_PATIENT: "ASTM_AMBIGUOUS_MULTI_PATIENT"
1542
1542
  };
1543
1543
  var AstmAmbiguousStreamError = class extends Error {
1544
- /** The stable discriminant — see {@link AMBIGUOUS_CODES}. */
1544
+ /** The stable discriminant: see {@link AMBIGUOUS_CODES}. */
1545
1545
  code;
1546
1546
  /** Where the ambiguity became visible: the second header, or the second `P`. Value-free. */
1547
1547
  position;
@@ -1686,14 +1686,14 @@ function defineLivdCatalog(entries) {
1686
1686
  var LIVD_WARNING_CODES = {
1687
1687
  /**
1688
1688
  * A record carried a vendor/local test code, but the consumer-supplied LIVD catalog held **no**
1689
- * entry for it. The code stays surfaced verbatim and the mapping is `unmapped` — a LOINC is
1689
+ * entry for it. The code stays surfaced verbatim and the mapping is `unmapped`: a LOINC is
1690
1690
  * **never** guessed. Purely advisory: the raw code and value are untouched.
1691
1691
  */
1692
1692
  ASTM_LIVD_UNMAPPED_CODE: "ASTM_LIVD_UNMAPPED_CODE",
1693
1693
  /**
1694
1694
  * A vendor/local test code matched **more than one distinct LOINC** in the catalog (e.g. the same
1695
1695
  * transmission code used by two devices for different analytes). The mapping is `ambiguous` and the
1696
- * candidate LOINCs are surfaced for inspection, but **none is chosen** — refusing to pick is the
1696
+ * candidate LOINCs are surfaced for inspection, but **none is chosen**: refusing to pick is the
1697
1697
  * fail-safe, since guessing wrong mis-identifies the test.
1698
1698
  */
1699
1699
  ASTM_LIVD_AMBIGUOUS_MAPPING: "ASTM_LIVD_AMBIGUOUS_MAPPING"
@@ -1701,14 +1701,14 @@ var LIVD_WARNING_CODES = {
1701
1701
  function livdUnmappedCode(position) {
1702
1702
  return {
1703
1703
  code: LIVD_WARNING_CODES.ASTM_LIVD_UNMAPPED_CODE,
1704
- message: "Reported test code had no LIVD mapping \u2014 surfaced unmapped, never a guessed LOINC.",
1704
+ message: "Reported test code had no LIVD mapping, surfaced unmapped, never a guessed LOINC.",
1705
1705
  position
1706
1706
  };
1707
1707
  }
1708
1708
  function livdAmbiguousMapping(position) {
1709
1709
  return {
1710
1710
  code: LIVD_WARNING_CODES.ASTM_LIVD_AMBIGUOUS_MAPPING,
1711
- message: "Reported test code mapped to multiple distinct LOINCs \u2014 surfaced ambiguous, never one guessed.",
1711
+ message: "Reported test code mapped to multiple distinct LOINCs, surfaced ambiguous, never one guessed.",
1712
1712
  position
1713
1713
  };
1714
1714
  }
@@ -2152,7 +2152,7 @@ function encodeFrame(text, frameNumber, isFinal) {
2152
2152
  }
2153
2153
  function composeAstmFrames(records, options = {}) {
2154
2154
  if (records.length === 0) {
2155
- throw new AstmFrameEncodeError("Cannot frame an empty record list \u2014 nothing to transmit.");
2155
+ throw new AstmFrameEncodeError("Cannot frame an empty record list: nothing to transmit.");
2156
2156
  }
2157
2157
  const out = [];
2158
2158
  let frameNumber = options.startFrameNumber ?? FIRST_FRAME_NUMBER;
@@ -2160,7 +2160,7 @@ function composeAstmFrames(records, options = {}) {
2160
2160
  const bytes = toBytes(record);
2161
2161
  if (bytes.length === 0) {
2162
2162
  throw new AstmFrameEncodeError(
2163
- "Cannot frame an empty record \u2014 a frame must carry record bytes.",
2163
+ "Cannot frame an empty record: a frame must carry record bytes.",
2164
2164
  recordIndex
2165
2165
  );
2166
2166
  }
@@ -2199,7 +2199,7 @@ function concatBytes3(chunks) {
2199
2199
  }
2200
2200
 
2201
2201
  // src/index.ts
2202
- var VERSION = "0.0.4";
2202
+ var VERSION = "0.0.5";
2203
2203
 
2204
2204
  exports.ALL_ASTM_WARNING_CODES = ALL_ASTM_WARNING_CODES;
2205
2205
  exports.AMBIGUOUS_CODES = AMBIGUOUS_CODES;