@cosyte/astm 0.0.17 → 0.0.18
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 +73 -1
- package/README.md +28 -7
- package/dist/index.cjs +110 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +249 -23
- package/dist/index.d.ts +249 -23
- package/dist/index.mjs +110 -14
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -238,6 +238,73 @@ declare const WARNING_CODES: {
|
|
|
238
238
|
* silent and is correct about its own bytes. A clean re-read is not evidence.
|
|
239
239
|
*/
|
|
240
240
|
readonly ASTM_RECORD_ALIGNMENT_SHIFTED_FIELDS: "ASTM_RECORD_ALIGNMENT_SHIFTED_FIELDS";
|
|
241
|
+
/**
|
|
242
|
+
* Two escape alignments of the same bytes disagreed about a **repeat** boundary, the reading
|
|
243
|
+
* taken kept it, and the escape character that reading resumes on **heads no sequence at all**.
|
|
244
|
+
* Same contested position and same tail test as
|
|
245
|
+
* {@link WARNING_CODES.ASTM_RECORD_ALIGNMENT_SHIFTED_FIELDS}, on a different delimiter role, and
|
|
246
|
+
* what it costs is a different thing, which is why it is a different code.
|
|
247
|
+
*
|
|
248
|
+
* **Nothing shifts, and that is not the same as nothing being lost.** No field-indexed slot
|
|
249
|
+
* moves: the units and the result status are read out of the same field numbers under either
|
|
250
|
+
* alignment. What this reports is that **the field is read as more repeats than the competing
|
|
251
|
+
* alignment gives it**, which is true wherever it fires. What that costs depends on **which**
|
|
252
|
+
* boundary was gained, and the two cases are stated separately rather than folded into one claim.
|
|
253
|
+
*
|
|
254
|
+
* **Where it is the FIRST boundary in the field, a modeled slot is lost**, because a field's
|
|
255
|
+
* modeled value and components are taken from its **first repeat alone**. Everything past the
|
|
256
|
+
* boundary stays on the wire and stays in `repeats`, and leaves every modeled slot:
|
|
257
|
+
*
|
|
258
|
+
* - **A value truncates.** Under `H|F^&`, `R|1|^^^687|28.6&S&F&U/L||||F` reads the value field as
|
|
259
|
+
* the two repeats `28.6^` and `&U/L`, so a consumer reads `28.6^` and the rest of the value is
|
|
260
|
+
* gone. The competing alignment reads one repeat carrying all of it.
|
|
261
|
+
* - **A modeled component list is deleted rather than shifted.** The same boundary inside a
|
|
262
|
+
* Universal Test ID leaves the components of the first repeat only: under `H|F^&`,
|
|
263
|
+
* `R|1|&F&F&687|28.6|U/L||||F` reads a UTID of one component holding a decoded field separator,
|
|
264
|
+
* with no coding scheme and no local code, where the bytes carry `687`. A patient name loses
|
|
265
|
+
* its given and middle names the same way. That is the half of this condition the shift report
|
|
266
|
+
* could not cover, because components are modeled **inside** a field.
|
|
267
|
+
*
|
|
268
|
+
* Before this code the only warnings on those streams were **tolerable** ones
|
|
269
|
+
* ({@link WARNING_CODES.ASTM_NONSTANDARD_DELIMITERS} and, where the tail applies,
|
|
270
|
+
* {@link WARNING_CODES.ASTM_UNPAIRED_ESCAPE_CHARACTER}), so the widest gate-legal profile plus
|
|
271
|
+
* `{ strict: true }` accepted a truncated value and an emptied Universal Test ID.
|
|
272
|
+
*
|
|
273
|
+
* **Where it is a LATER boundary, no modeled slot moves and this still fires.** The first repeat
|
|
274
|
+
* is identical under both alignments, so the value, the components and every slot taken from them
|
|
275
|
+
* read the same either way (`R|1|^^^687|5.0F28.6&S&F&U/L||||F` under `H|F^&` reads a value of
|
|
276
|
+
* `5.0` under both), and what differs is the repeat structure after the first. It fires there
|
|
277
|
+
* deliberately: the boundary is still one the bytes do not force and a consumer reading `repeats`
|
|
278
|
+
* is still reading an alignment guess. Relative to the modeled slots that is **over-reporting,
|
|
279
|
+
* never under-reporting**. Narrowing it to the first boundary would change which streams a
|
|
280
|
+
* published package refuses and wants its own measurement, so the bound is written down instead.
|
|
281
|
+
*
|
|
282
|
+
* **It is a report, not a repair.** The split is unchanged, every decoded byte is identical,
|
|
283
|
+
* `repeats` still carries every one of them, and the value read is the value that was always
|
|
284
|
+
* read. Picking the other alignment would be a different guess with no more evidence behind it,
|
|
285
|
+
* on a published package.
|
|
286
|
+
*
|
|
287
|
+
* **Two further deliberate bounds, both stated rather than left to be found.**
|
|
288
|
+
* - **Only the repeat role.** The **component** role reaches a modeled slot too, and differently:
|
|
289
|
+
* there the components stay in the record and move one slot along, so a local code reads as a
|
|
290
|
+
* coding scheme and a given name as a middle name. That is measured, reported by nothing where
|
|
291
|
+
* the earlier body is a recognized mnemonic, and open. It is not covered here, because wiring a
|
|
292
|
+
* sink to another split is another criterion wanting its own population measurement.
|
|
293
|
+
* - **The tail is weighed one construct deep**, exactly as for the shift report. Where the escape
|
|
294
|
+
* character the reading resumes on heads a sequence this codec *recognizes*, the bytes prefer
|
|
295
|
+
* the reading taken and the repeats are the ones the sender wrote: under a set naming the
|
|
296
|
+
* repeat separator `F`, `28.6&F&F&F&U/L` is that separator escaped, written, and escaped again,
|
|
297
|
+
* and refusing it would be an over-refusal of a well-formed stream. Where it heads a sequence
|
|
298
|
+
* whose body is *unrecognized*, the competing alignment would leave **two** escape characters
|
|
299
|
+
* bare, so the preference is stronger again, and this stays silent there even though the gained
|
|
300
|
+
* boundary is real. That last case is the named residue: measured, not overlooked.
|
|
301
|
+
*
|
|
302
|
+
* **Catch it on the first read.** Emit rewrites the preserved sequences into recognized
|
|
303
|
+
* mnemonics, and those bytes carry the reading that was taken unambiguously, so a
|
|
304
|
+
* second-generation read is silent and is correct about its own bytes. A clean re-read is not
|
|
305
|
+
* evidence.
|
|
306
|
+
*/
|
|
307
|
+
readonly ASTM_RECORD_ALIGNMENT_TRUNCATED_FIELD: "ASTM_RECORD_ALIGNMENT_TRUNCATED_FIELD";
|
|
241
308
|
/** An escape sequence body was not one of `&F&`/`&S&`/`&R&`/`&E&`: preserved verbatim. */
|
|
242
309
|
readonly ASTM_UNKNOWN_ESCAPE_SEQUENCE: "ASTM_UNKNOWN_ESCAPE_SEQUENCE";
|
|
243
310
|
/**
|
|
@@ -511,6 +578,32 @@ declare function ambiguousEscapeAlignment(position: AstmPosition): AstmRecordWar
|
|
|
511
578
|
* ```
|
|
512
579
|
*/
|
|
513
580
|
declare function alignmentShiftedFields(position: AstmPosition): AstmRecordWarning;
|
|
581
|
+
/**
|
|
582
|
+
* Build an `ASTM_RECORD_ALIGNMENT_TRUNCATED_FIELD` warning. Emitted when a competing escape
|
|
583
|
+
* alignment decided a **repeat** boundary and the escape character the reading taken resumes on
|
|
584
|
+
* heads no sequence of its own, so the field is read as more repeats than the competing alignment
|
|
585
|
+
* gives it. No field-indexed slot moves, and that is the difference from
|
|
586
|
+
* {@link alignmentShiftedFields}. Where the gained boundary is the **first** in the field it still
|
|
587
|
+
* reaches a modeled slot, because a field's modeled value and components are taken from its first
|
|
588
|
+
* repeat alone: everything past the boundary stays in `repeats` and leaves every modeled slot, so a
|
|
589
|
+
* result value truncates and a Universal Test ID or a patient name loses the components that sat
|
|
590
|
+
* after it. At a **later** boundary nothing modeled moves and this still fires, which is
|
|
591
|
+
* over-reporting relative to those slots and never under-reporting; see
|
|
592
|
+
* {@link WARNING_CODES.ASTM_RECORD_ALIGNMENT_TRUNCATED_FIELD}.
|
|
593
|
+
*
|
|
594
|
+
* A profile may **not** tolerate this code. It fires alongside
|
|
595
|
+
* {@link WARNING_CODES.ASTM_UNPAIRED_ESCAPE_CHARACTER}, which remains tolerable and reports the
|
|
596
|
+
* strictly weaker fact that one escape character was read as a literal, and alongside
|
|
597
|
+
* {@link WARNING_CODES.ASTM_RECORD_AMBIGUOUS_ESCAPE_ALIGNMENT} where that also applies. The reading
|
|
598
|
+
* is unchanged: this reports the gained boundary, it does not repair it.
|
|
599
|
+
*
|
|
600
|
+
* @example
|
|
601
|
+
* ```ts
|
|
602
|
+
* import { alignmentTruncatedField } from "@cosyte/astm";
|
|
603
|
+
* alignmentTruncatedField({ recordIndex: 2, recordType: "R", fieldIndex: 4 });
|
|
604
|
+
* ```
|
|
605
|
+
*/
|
|
606
|
+
declare function alignmentTruncatedField(position: AstmPosition): AstmRecordWarning;
|
|
514
607
|
/**
|
|
515
608
|
* Build an `ASTM_RECORD_DELIMITERS_REDECLARED` warning. Emitted when a later `H`
|
|
516
609
|
* record declares a delimiter set different from the one in force; the new set is
|
|
@@ -3247,6 +3340,13 @@ declare function classifyMessage(records: readonly AstmRecord[]): AstmMessageCla
|
|
|
3247
3340
|
* repeat or component boundary divides one field and so moves no field-indexed
|
|
3248
3341
|
* slot. That is a choice and not a consequence, because components are modeled
|
|
3249
3342
|
* inside a field; see {@link ShiftedFieldsSink}.
|
|
3343
|
+
* @param onAlignmentTruncatedField - Called (with the 0-based field index) for that
|
|
3344
|
+
* same condition on the **repeat** split, where no field-indexed slot moves and the
|
|
3345
|
+
* field is instead read as more repeats than the competing alignment gives it.
|
|
3346
|
+
* Where that boundary is the **first** in the field the field's own modeled reading
|
|
3347
|
+
* stops at it, because a field is modeled out of its first repeat alone; at a later
|
|
3348
|
+
* one nothing modeled moves and this still fires. See {@link TruncatedFieldSink}.
|
|
3349
|
+
* The component split is wired to neither, deliberately.
|
|
3250
3350
|
* @returns The record's fields.
|
|
3251
3351
|
* @example
|
|
3252
3352
|
* ```ts
|
|
@@ -3255,7 +3355,7 @@ declare function classifyMessage(records: readonly AstmRecord[]): AstmMessageCla
|
|
|
3255
3355
|
* fields[3].components[0]; // "28.6"
|
|
3256
3356
|
* ```
|
|
3257
3357
|
*/
|
|
3258
|
-
declare function tokenizeRecord(record: string, d: Delimiters, onUnknownEscape?: (fieldIndex: number) => void, onUnpairedEscape?: (fieldIndex: number) => void, onSwallowedDelimiter?: (fieldIndex: number) => void, onAmbiguousAlignment?: (fieldIndex: number) => void, onAlignmentShiftedFields?: (fieldIndex: number) => void): AstmField[];
|
|
3358
|
+
declare function tokenizeRecord(record: string, d: Delimiters, onUnknownEscape?: (fieldIndex: number) => void, onUnpairedEscape?: (fieldIndex: number) => void, onSwallowedDelimiter?: (fieldIndex: number) => void, onAmbiguousAlignment?: (fieldIndex: number) => void, onAlignmentShiftedFields?: (fieldIndex: number) => void, onAlignmentTruncatedField?: (fieldIndex: number) => void): AstmField[];
|
|
3259
3359
|
/**
|
|
3260
3360
|
* Tokenize an `H` (header) record into its fields.
|
|
3261
3361
|
*
|
|
@@ -3291,6 +3391,9 @@ declare function tokenizeRecord(record: string, d: Delimiters, onUnknownEscape?:
|
|
|
3291
3391
|
* for each contested field boundary in the data portion whose reading resumes on
|
|
3292
3392
|
* an escape character heading no sequence. The declaration is opaque, so it never
|
|
3293
3393
|
* reports here either.
|
|
3394
|
+
* @param onAlignmentTruncatedField - Called with the 0-based whole-record field index
|
|
3395
|
+
* for each contested **repeat** boundary in the data portion on that same tail
|
|
3396
|
+
* test. The declaration is opaque, so it never reports here either.
|
|
3294
3397
|
* @returns The header's fields.
|
|
3295
3398
|
* @example
|
|
3296
3399
|
* ```ts
|
|
@@ -3300,7 +3403,7 @@ declare function tokenizeRecord(record: string, d: Delimiters, onUnknownEscape?:
|
|
|
3300
3403
|
* fields[4].raw; // "sender"
|
|
3301
3404
|
* ```
|
|
3302
3405
|
*/
|
|
3303
|
-
declare function tokenizeHeader(record: string, d: Delimiters, onUnknownEscape?: (fieldIndex: number) => void, onUnpairedEscape?: (fieldIndex: number) => void, onSwallowedDelimiter?: (fieldIndex: number) => void, onAmbiguousAlignment?: (fieldIndex: number) => void, onAlignmentShiftedFields?: (fieldIndex: number) => void): AstmField[];
|
|
3406
|
+
declare function tokenizeHeader(record: string, d: Delimiters, onUnknownEscape?: (fieldIndex: number) => void, onUnpairedEscape?: (fieldIndex: number) => void, onSwallowedDelimiter?: (fieldIndex: number) => void, onAmbiguousAlignment?: (fieldIndex: number) => void, onAlignmentShiftedFields?: (fieldIndex: number) => void, onAlignmentTruncatedField?: (fieldIndex: number) => void): AstmField[];
|
|
3304
3407
|
/**
|
|
3305
3408
|
* The primary scalar of a field: its first repeat's first component, decoded.
|
|
3306
3409
|
* Returns `undefined` for a truly empty field so callers can distinguish
|
|
@@ -3483,9 +3586,19 @@ declare class AstmParseError extends Error {
|
|
|
3483
3586
|
* status** are read out of slots the competing alignment does not put them in, and
|
|
3484
3587
|
* a status of `final` can be a consequence of the alignment rather than something
|
|
3485
3588
|
* the sender wrote there. That is `ASTM_RECORD_ALIGNMENT_SHIFTED_FIELDS`; see
|
|
3486
|
-
* {@link ShiftedFieldsSink}, including the tail
|
|
3487
|
-
*
|
|
3488
|
-
*
|
|
3589
|
+
* {@link ShiftedFieldsSink}, including the tail it deliberately stays silent on, and
|
|
3590
|
+
* why that silence is not a claim that nothing was lost there.
|
|
3591
|
+
*
|
|
3592
|
+
* **The same question asked on the repeat separator has a different answer, so it
|
|
3593
|
+
* has a code of its own.** A gained repeat boundary moves no field-indexed slot, and
|
|
3594
|
+
* where it is the **first** boundary in the field it still reaches a modeled slot,
|
|
3595
|
+
* because a field's modeled reading is taken from its first repeat alone: everything
|
|
3596
|
+
* past that boundary leaves the record, so a result value truncates and a Universal
|
|
3597
|
+
* Test ID or a patient name loses the components that sat after it. That is
|
|
3598
|
+
* `ASTM_RECORD_ALIGNMENT_TRUNCATED_FIELD`; see {@link TruncatedFieldSink}, including
|
|
3599
|
+
* what it does at a later boundary, where it fires and no modeled slot moves. The
|
|
3600
|
+
* **component** separator is a third question again and is deliberately not answered
|
|
3601
|
+
* here, because there the components do not disappear, they shift.
|
|
3489
3602
|
*
|
|
3490
3603
|
* Re-escaping (the inverse, for spec-clean emit) lives in the serializer and is
|
|
3491
3604
|
* deliberately not implemented here.
|
|
@@ -3587,11 +3700,14 @@ type AmbiguousAlignmentSink = (segmentIndex: number) => void;
|
|
|
3587
3700
|
* reaches nothing outside it, so it moves no **field-indexed** slot and is
|
|
3588
3701
|
* deliberately outside this. **That bound is a choice, not a consequence, and the
|
|
3589
3702
|
* difference matters**: components are modeled *inside* a field (a Universal Test
|
|
3590
|
-
* ID's coding scheme and local code, a patient name's parts), so a gained
|
|
3591
|
-
* boundary does
|
|
3592
|
-
*
|
|
3593
|
-
*
|
|
3594
|
-
*
|
|
3703
|
+
* ID's coding scheme and local code, a patient name's parts), so a gained repeat or
|
|
3704
|
+
* component boundary does reach a modeled slot. The **repeat** half of that is
|
|
3705
|
+
* reported by {@link TruncatedFieldSink}, on the same tail test and under its own
|
|
3706
|
+
* code, because what it costs is a different thing. The **component** half is
|
|
3707
|
+
* measured, reported by nothing, and open: not something either sink covers. The
|
|
3708
|
+
* callback lets the parser surface a value-free
|
|
3709
|
+
* `ASTM_RECORD_ALIGNMENT_SHIFTED_FIELDS` warning. Optional so the split can be used
|
|
3710
|
+
* purely.
|
|
3595
3711
|
*
|
|
3596
3712
|
* **It is a report, not a repair.** The split is unchanged, every decoded byte is
|
|
3597
3713
|
* identical, and the status read is the status that was always read. What is new is
|
|
@@ -3629,6 +3745,68 @@ type AmbiguousAlignmentSink = (segmentIndex: number) => void;
|
|
|
3629
3745
|
* than the competing alignment puts it.
|
|
3630
3746
|
*/
|
|
3631
3747
|
type ShiftedFieldsSink = (segmentIndex: number) => void;
|
|
3748
|
+
/**
|
|
3749
|
+
* A callback the split calls on **exactly the condition {@link ShiftedFieldsSink}
|
|
3750
|
+
* reports**, wired instead to the split taken on the **repeat** separator. The two
|
|
3751
|
+
* sinks test the same predicate, because the predicate is about the bytes and the
|
|
3752
|
+
* split does not know which role it is being taken on; they are separate so the
|
|
3753
|
+
* caller that does know can name what the gained boundary costs, which is not the
|
|
3754
|
+
* same thing in the two roles. Do not "simplify" them into one: the codes would then
|
|
3755
|
+
* have to make one claim covering both, and the claim is different.
|
|
3756
|
+
*
|
|
3757
|
+
* **What a gained repeat boundary costs, and it is not a shift.** No field-indexed
|
|
3758
|
+
* slot moves: the units slot and the result-status slot are read out of the same
|
|
3759
|
+
* field numbers under either alignment. What the report says is that **the field is
|
|
3760
|
+
* read as more repeats than the competing alignment gives it**, and that is true
|
|
3761
|
+
* wherever it fires. What that costs depends on **which** boundary was gained, and
|
|
3762
|
+
* the two cases are stated separately rather than folded into one claim:
|
|
3763
|
+
*
|
|
3764
|
+
* - **The gained boundary is the FIRST in the field, and then a modeled slot is
|
|
3765
|
+
* lost.** A field's modeled value and its components are taken from its **first
|
|
3766
|
+
* repeat alone**, so everything past the boundary is still on the wire, still in
|
|
3767
|
+
* `repeats`, and gone from every modeled slot. Under a set naming the repeat
|
|
3768
|
+
* separator `F`, the value `28.6&S&F&U/L` reads as the two repeats `28.6^` and
|
|
3769
|
+
* `&U/L`, and every value extractor answers `28.6^`, while the competing alignment
|
|
3770
|
+
* reads one repeat carrying all of it. The same boundary inside a Universal Test ID
|
|
3771
|
+
* field leaves the components of the first repeat only, so a UTID whose bytes carry
|
|
3772
|
+
* a test code can come back as a single component holding a decoded delimiter, with
|
|
3773
|
+
* the coding scheme and local code read from nothing at all. A patient name loses
|
|
3774
|
+
* its given and middle names the same way.
|
|
3775
|
+
* - **The gained boundary is a LATER one, and then no modeled slot moves.** The first
|
|
3776
|
+
* repeat is identical under both alignments, so the value, the components and every
|
|
3777
|
+
* slot taken from them read the same either way; what differs is the repeat
|
|
3778
|
+
* structure after the first. **This still fires there, deliberately and measurably**
|
|
3779
|
+
* (`5.0F28.6&S&F&U/L` under that same set), because the boundary is still one the
|
|
3780
|
+
* bytes do not force and a consumer reading `repeats` is still reading an alignment
|
|
3781
|
+
* guess. Relative to the modeled slots that is **over-reporting, never
|
|
3782
|
+
* under-reporting**, which is the direction this package errs in. Narrowing the sink
|
|
3783
|
+
* to the first boundary would change which streams a published package refuses and
|
|
3784
|
+
* wants its own measurement, so the bound is written down instead of guessed at.
|
|
3785
|
+
*
|
|
3786
|
+
* **It is a report, not a repair.** The split is unchanged, every decoded byte is
|
|
3787
|
+
* identical, `repeats` still carries every one of them, and the value read is the
|
|
3788
|
+
* value that was always read. What is new is that the gained boundary is reported by
|
|
3789
|
+
* a code no profile may tolerate, where before it was covered only by tolerable ones.
|
|
3790
|
+
*
|
|
3791
|
+
* **The tail is weighed one construct deep, exactly as in {@link ShiftedFieldsSink},
|
|
3792
|
+
* and for the same reason.** Where the escape character the reading taken resumes on
|
|
3793
|
+
* heads a sequence this codec recognizes, the bytes prefer the reading taken and the
|
|
3794
|
+
* two repeats are the ones the sender wrote: under a set naming the repeat separator
|
|
3795
|
+
* `F`, `28.6&F&F&F&U/L` is that separator escaped, written, and escaped again.
|
|
3796
|
+
* Refusing that is the over-refusal that sank an earlier candidate criterion for this
|
|
3797
|
+
* family. Where it heads a sequence whose body is *unrecognized*, the competing
|
|
3798
|
+
* alignment would leave two escape characters bare, so the preference is stronger
|
|
3799
|
+
* again. **The truncation in that second case is real and this stays silent about
|
|
3800
|
+
* it**: a named residue, measured, not overlooked, and not closed by widening the
|
|
3801
|
+
* test, because widening it would report a boundary the bytes prefer.
|
|
3802
|
+
*
|
|
3803
|
+
* @param segmentIndex - The 0-based index of the repeat being accumulated when the
|
|
3804
|
+
* gained boundary was taken, so `0` is the boundary that ends the first repeat and
|
|
3805
|
+
* is the one that reaches a modeled slot. It is **not** a field index: the caller
|
|
3806
|
+
* splitting one field into repeats already knows which field it is in, and reports
|
|
3807
|
+
* that.
|
|
3808
|
+
*/
|
|
3809
|
+
type TruncatedFieldSink = (segmentIndex: number) => void;
|
|
3632
3810
|
/**
|
|
3633
3811
|
* Decode the four recognized ASTM escape mnemonics in a single already-split
|
|
3634
3812
|
* leaf (a component string), substituting the *active* delimiters read from the
|
|
@@ -3698,6 +3876,11 @@ declare function decodeEscapes(leaf: string, d: Delimiters, onUnknown?: UnknownE
|
|
|
3698
3876
|
* was bought with a byte the reading cannot read. Wired only by the split taken on
|
|
3699
3877
|
* the field separator: see {@link ShiftedFieldsSink} for why, and for the tail it
|
|
3700
3878
|
* deliberately does not report.
|
|
3879
|
+
* @param onTruncatedField - Called on that same condition, wired only by the split
|
|
3880
|
+
* taken on the repeat separator, where the cost is not a shift but a field read as
|
|
3881
|
+
* more repeats than the competing alignment gives it. Where that boundary is the
|
|
3882
|
+
* **first** in the field the field's modeled reading stops at it; at a later one
|
|
3883
|
+
* nothing modeled moves and this still fires. See {@link TruncatedFieldSink}.
|
|
3701
3884
|
* @returns The raw segments, in order.
|
|
3702
3885
|
* @example
|
|
3703
3886
|
* ```ts
|
|
@@ -3707,7 +3890,7 @@ declare function decodeEscapes(leaf: string, d: Delimiters, onUnknown?: UnknownE
|
|
|
3707
3890
|
* splitEscapeAware("O&Brien^John", "^", "&"); // ["O&Brien", "John"] (unpaired: literal)
|
|
3708
3891
|
* ```
|
|
3709
3892
|
*/
|
|
3710
|
-
declare function splitEscapeAware(text: string, delimiter: string, escape: string, onAmbiguousAlignment?: AmbiguousAlignmentSink, onShiftedFields?: ShiftedFieldsSink): string[];
|
|
3893
|
+
declare function splitEscapeAware(text: string, delimiter: string, escape: string, onAmbiguousAlignment?: AmbiguousAlignmentSink, onShiftedFields?: ShiftedFieldsSink, onTruncatedField?: TruncatedFieldSink): string[];
|
|
3711
3894
|
|
|
3712
3895
|
/**
|
|
3713
3896
|
* Deep-freeze helper for the immutable ASTM model.
|
|
@@ -4089,6 +4272,27 @@ declare function resolveProfileTransport(profile: AstmProfile | undefined): Astm
|
|
|
4089
4272
|
* in a value alters no boundary at all), and the case where it accompanies a shifted
|
|
4090
4273
|
* field is now reported by a code no profile may re-badge.
|
|
4091
4274
|
*
|
|
4275
|
+
* **`ASTM_RECORD_ALIGNMENT_TRUNCATED_FIELD` landed next, on the same contested
|
|
4276
|
+
* position and the same tail test, and it reaches a modeled slot by the other
|
|
4277
|
+
* route.** It reports a contested alignment deciding a **repeat** boundary. Nothing
|
|
4278
|
+
* shifts: no field-indexed slot moves, which is exactly why the shift report could
|
|
4279
|
+
* not cover it. What it reports is that the field is read as more repeats than the
|
|
4280
|
+
* competing alignment gives it, and **where that gained boundary is the first one in
|
|
4281
|
+
* the field** it reaches a modeled slot, because a field's modeled value and
|
|
4282
|
+
* components are taken from its **first repeat alone**: everything past it stays in
|
|
4283
|
+
* `repeats` and leaves every modeled slot, so a result value truncates and a
|
|
4284
|
+
* Universal Test ID or a patient name comes back holding only the components that sat
|
|
4285
|
+
* before it. Until it existed the warnings on those streams were
|
|
4286
|
+
* `ASTM_NONSTANDARD_DELIMITERS` and `ASTM_UNPAIRED_ESCAPE_CHARACTER`, both on the list
|
|
4287
|
+
* below, so the widest gate-legal profile plus `{ strict: true }` accepted a truncated
|
|
4288
|
+
* value and an emptied test identity. **At a later boundary it fires and no modeled
|
|
4289
|
+
* slot moves**, which is over-reporting relative to those slots and never
|
|
4290
|
+
* under-reporting: measured, and stated on the code itself rather than left to be
|
|
4291
|
+
* found. It is safety-critical by construction and must never be admitted.
|
|
4292
|
+
* Re-reading the survivors against it moved no admission, and the reason is the one
|
|
4293
|
+
* the entry above already turns on: each of them re-badges the code it names and no
|
|
4294
|
+
* other, and what this condition now raises is a code no profile may name.
|
|
4295
|
+
*
|
|
4092
4296
|
* **So the ones that remain are recorded with the reading each one survives**, not
|
|
4093
4297
|
* merely with the value it preserves. Each was re-derived against **every** reader of
|
|
4094
4298
|
* record structure named above (this sentence deliberately names no count: it has
|
|
@@ -4174,21 +4378,33 @@ declare function resolveProfileTransport(profile: AstmProfile | undefined): Astm
|
|
|
4174
4378
|
* decoded value, so the value is identical with or without the profile. Note what
|
|
4175
4379
|
* makes this admissible is a property of the *parse*, not of the warning: reading
|
|
4176
4380
|
* the character as a literal is unconditional, and tolerating the code changes
|
|
4177
|
-
* nothing about how the record splits.
|
|
4178
|
-
*
|
|
4381
|
+
* nothing about how the record splits. None of the three readers named at the top of
|
|
4382
|
+
* this file sees it: the type letter is read before any decoding, the split reader
|
|
4179
4383
|
* counts fields the escape-aware tokenizer has already finished dividing, and a
|
|
4180
|
-
* decoded value is never cut back into records.
|
|
4384
|
+
* decoded value is never cut back into records. **Two later readers DO see it**, and
|
|
4385
|
+
* they are named below rather than left out of this bullet. Contrast the condition it
|
|
4181
4386
|
* replaced, which **would** have failed part 1: reading the same character as an
|
|
4182
4387
|
* unterminated sequence merged every field after it into one, and a code reporting
|
|
4183
4388
|
* that would have been a report of lost values.
|
|
4184
4389
|
*
|
|
4185
|
-
* **The alignment reader is not a reader of this condition
|
|
4186
|
-
*
|
|
4187
|
-
*
|
|
4188
|
-
*
|
|
4189
|
-
*
|
|
4190
|
-
*
|
|
4191
|
-
*
|
|
4390
|
+
* **The alignment reader is not a reader of this condition**, and that much still
|
|
4391
|
+
* holds: it asks whether the character two positions past a sequence *is* the escape
|
|
4392
|
+
* character, never whether that character heads a sequence of its own. The two are
|
|
4393
|
+
* independent in both directions, measured: `28.6&Z&|&U/L` raises this code and the
|
|
4394
|
+
* alignment code together, and `28.6&Z&|&U&L` raises the alignment code with no
|
|
4395
|
+
* unpaired character anywhere in it.
|
|
4396
|
+
*
|
|
4397
|
+
* **The two TAIL readers are a different matter, and "no reader sees it" is
|
|
4398
|
+
* therefore FALSE of this entry and must not be restored.** Both
|
|
4399
|
+
* `ASTM_RECORD_ALIGNMENT_SHIFTED_FIELDS` and
|
|
4400
|
+
* `ASTM_RECORD_ALIGNMENT_TRUNCATED_FIELD` fire on `!isEscapeSequenceAt(text, i + 4,
|
|
4401
|
+
* escape)`, which is exactly the question this code answers, asked about the
|
|
4402
|
+
* character just past a contested boundary. That is a reader of the reported
|
|
4403
|
+
* condition, so this entry survives on the same narrower claim the unknown-sequence
|
|
4404
|
+
* entry above already uses, not on nothing reading it: a profile re-badges the code
|
|
4405
|
+
* it names and no other, and what those two readers raise is not tolerable in any
|
|
4406
|
+
* case. If a future reader of this condition ever produces something a profile
|
|
4407
|
+
* **can** tolerate, this entry stops being admissible and comes off the list.
|
|
4192
4408
|
* - `ASTM_RECORD_UNINTERPRETED_QUERY_STATUS`: a request-information status carried
|
|
4193
4409
|
* verbatim as a leaf field on a `Q` record; the code set is paywalled and is not
|
|
4194
4410
|
* interpreted, profile or not. Classification reads whether a `Q` record is
|
|
@@ -4202,7 +4418,8 @@ declare function resolveProfileTransport(profile: AstmProfile | undefined): Astm
|
|
|
4202
4418
|
* an unrecognized record type, a record the delimiters in force could not split, a
|
|
4203
4419
|
* declaration naming one character in two roles, a delimiter an unrecognized escape
|
|
4204
4420
|
* sequence kept out of the split, a boundary a competing escape alignment disagrees
|
|
4205
|
-
* about, a
|
|
4421
|
+
* about, a field whose modeled reading stops at a boundary only one alignment has,
|
|
4422
|
+
* a bad frame checksum / sequence gap / unterminated
|
|
4206
4423
|
* / oversize frame, an ambiguous transport, an unexpected protocol event, or a
|
|
4207
4424
|
* rejected frame: is forbidden.
|
|
4208
4425
|
*
|
|
@@ -4249,6 +4466,15 @@ declare function resolveProfileTransport(profile: AstmProfile | undefined): Astm
|
|
|
4249
4466
|
* reading `final` that the competing alignment of the same bytes puts in no field at
|
|
4250
4467
|
* all.
|
|
4251
4468
|
*
|
|
4469
|
+
* `ASTM_RECORD_ALIGNMENT_TRUNCATED_FIELD` is not on this list and must not be added
|
|
4470
|
+
* to it either. It reports the same contested alignment deciding a **repeat**
|
|
4471
|
+
* boundary, where no slot changes hands and a modeled reading is cut off instead: the
|
|
4472
|
+
* field is read out of its first repeat alone, so where that boundary is the first
|
|
4473
|
+
* one a value truncates and a Universal Test ID's coding scheme and local code are
|
|
4474
|
+
* read from bytes that are no longer in any modeled slot. Reporting a value that is
|
|
4475
|
+
* not the whole of what the sender wrote fails the first half of the two-clause test
|
|
4476
|
+
* outright.
|
|
4477
|
+
*
|
|
4252
4478
|
* @example
|
|
4253
4479
|
* ```ts
|
|
4254
4480
|
* import { TOLERABLE_CODES } from "@cosyte/astm";
|
|
@@ -5593,4 +5819,4 @@ declare class AstmFrameStrictError extends Error {
|
|
|
5593
5819
|
*/
|
|
5594
5820
|
declare const VERSION: string;
|
|
5595
5821
|
|
|
5596
|
-
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 AmbiguousAlignmentSink, type AmbiguousCode, type AnyAstmWarningCode, AstmAmbiguousStreamError, type AstmDate, type AstmDatePrecision, type AstmField, type AstmFrame, AstmFrameEncodeError, type AstmFrameEncodeErrorCode, 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 DelimiterDeclarationFault, type DelimiterReadResult, 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, type ShiftedFieldsSink, type SwallowedDelimiterSink, TOLERABLE_CODES, type TerminatorRecord, type UniversalTestId, type UniversalTestIdProvenance, type UnknownEscapeSink, type UnpairedEscapeSink, type UnsupportedRecord, VERSION, type VerbatimInput, WARNING_CODES, type WarningCode, alignmentShiftedFields, ambiguousEscapeAlignment, ambiguousMessageKind, ambiguousValueSplit, applyAstmProfile, applyAstmProfileToWarnings, applyLivd, astmDateToLocalISO, astmProfiles, attachComments, buildAstmMessage, classifyMessage, comments, commentsFor, composeAstmFrames, computeChecksum, decodeAstmFrames, decodeEscapes, deepFreeze, defineAstmProfile, defineLivdCatalog, delimiterRoleCollision, delimiterSwallowedByEscape, delimitersRedeclared, detectFraming, encodeComponent, fieldScalar, fieldsUnseparated, frameBadChecksum, frameOversize, frameSequenceGap, frameUnterminated, getAstmProfile, getDefaultAstmProfile, hasCollidingRoles, 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, readDelimiterDeclaration, readDelimiters, recognizeUniversalTestId, resolveProfileTransport, results, serializeAstmRecord, serializeAstmRecords, serializeField, serializeFramedAstm, setDefaultAstmProfile, splitEscapeAware, toChecksumHex, tokenizeHeader, tokenizeRecord, undefinedAbnormalFlag, undefinedResultStatus, uninterpretedQueryStatus, unitsAbsent, unknownEscapeSequence, unknownRecordType, unpairedEscapeCharacter, unparseableReferenceRange, unreadableRedeclaration };
|
|
5822
|
+
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 AmbiguousAlignmentSink, type AmbiguousCode, type AnyAstmWarningCode, AstmAmbiguousStreamError, type AstmDate, type AstmDatePrecision, type AstmField, type AstmFrame, AstmFrameEncodeError, type AstmFrameEncodeErrorCode, 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 DelimiterDeclarationFault, type DelimiterReadResult, 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, type ShiftedFieldsSink, type SwallowedDelimiterSink, TOLERABLE_CODES, type TerminatorRecord, type TruncatedFieldSink, type UniversalTestId, type UniversalTestIdProvenance, type UnknownEscapeSink, type UnpairedEscapeSink, type UnsupportedRecord, VERSION, type VerbatimInput, WARNING_CODES, type WarningCode, alignmentShiftedFields, alignmentTruncatedField, ambiguousEscapeAlignment, ambiguousMessageKind, ambiguousValueSplit, applyAstmProfile, applyAstmProfileToWarnings, applyLivd, astmDateToLocalISO, astmProfiles, attachComments, buildAstmMessage, classifyMessage, comments, commentsFor, composeAstmFrames, computeChecksum, decodeAstmFrames, decodeEscapes, deepFreeze, defineAstmProfile, defineLivdCatalog, delimiterRoleCollision, delimiterSwallowedByEscape, delimitersRedeclared, detectFraming, encodeComponent, fieldScalar, fieldsUnseparated, frameBadChecksum, frameOversize, frameSequenceGap, frameUnterminated, getAstmProfile, getDefaultAstmProfile, hasCollidingRoles, 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, readDelimiterDeclaration, readDelimiters, recognizeUniversalTestId, resolveProfileTransport, results, serializeAstmRecord, serializeAstmRecords, serializeField, serializeFramedAstm, setDefaultAstmProfile, splitEscapeAware, toChecksumHex, tokenizeHeader, tokenizeRecord, undefinedAbnormalFlag, undefinedResultStatus, uninterpretedQueryStatus, unitsAbsent, unknownEscapeSequence, unknownRecordType, unpairedEscapeCharacter, unparseableReferenceRange, unreadableRedeclaration };
|
package/dist/index.mjs
CHANGED
|
@@ -327,6 +327,73 @@ var WARNING_CODES = {
|
|
|
327
327
|
* silent and is correct about its own bytes. A clean re-read is not evidence.
|
|
328
328
|
*/
|
|
329
329
|
ASTM_RECORD_ALIGNMENT_SHIFTED_FIELDS: "ASTM_RECORD_ALIGNMENT_SHIFTED_FIELDS",
|
|
330
|
+
/**
|
|
331
|
+
* Two escape alignments of the same bytes disagreed about a **repeat** boundary, the reading
|
|
332
|
+
* taken kept it, and the escape character that reading resumes on **heads no sequence at all**.
|
|
333
|
+
* Same contested position and same tail test as
|
|
334
|
+
* {@link WARNING_CODES.ASTM_RECORD_ALIGNMENT_SHIFTED_FIELDS}, on a different delimiter role, and
|
|
335
|
+
* what it costs is a different thing, which is why it is a different code.
|
|
336
|
+
*
|
|
337
|
+
* **Nothing shifts, and that is not the same as nothing being lost.** No field-indexed slot
|
|
338
|
+
* moves: the units and the result status are read out of the same field numbers under either
|
|
339
|
+
* alignment. What this reports is that **the field is read as more repeats than the competing
|
|
340
|
+
* alignment gives it**, which is true wherever it fires. What that costs depends on **which**
|
|
341
|
+
* boundary was gained, and the two cases are stated separately rather than folded into one claim.
|
|
342
|
+
*
|
|
343
|
+
* **Where it is the FIRST boundary in the field, a modeled slot is lost**, because a field's
|
|
344
|
+
* modeled value and components are taken from its **first repeat alone**. Everything past the
|
|
345
|
+
* boundary stays on the wire and stays in `repeats`, and leaves every modeled slot:
|
|
346
|
+
*
|
|
347
|
+
* - **A value truncates.** Under `H|F^&`, `R|1|^^^687|28.6&S&F&U/L||||F` reads the value field as
|
|
348
|
+
* the two repeats `28.6^` and `&U/L`, so a consumer reads `28.6^` and the rest of the value is
|
|
349
|
+
* gone. The competing alignment reads one repeat carrying all of it.
|
|
350
|
+
* - **A modeled component list is deleted rather than shifted.** The same boundary inside a
|
|
351
|
+
* Universal Test ID leaves the components of the first repeat only: under `H|F^&`,
|
|
352
|
+
* `R|1|&F&F&687|28.6|U/L||||F` reads a UTID of one component holding a decoded field separator,
|
|
353
|
+
* with no coding scheme and no local code, where the bytes carry `687`. A patient name loses
|
|
354
|
+
* its given and middle names the same way. That is the half of this condition the shift report
|
|
355
|
+
* could not cover, because components are modeled **inside** a field.
|
|
356
|
+
*
|
|
357
|
+
* Before this code the only warnings on those streams were **tolerable** ones
|
|
358
|
+
* ({@link WARNING_CODES.ASTM_NONSTANDARD_DELIMITERS} and, where the tail applies,
|
|
359
|
+
* {@link WARNING_CODES.ASTM_UNPAIRED_ESCAPE_CHARACTER}), so the widest gate-legal profile plus
|
|
360
|
+
* `{ strict: true }` accepted a truncated value and an emptied Universal Test ID.
|
|
361
|
+
*
|
|
362
|
+
* **Where it is a LATER boundary, no modeled slot moves and this still fires.** The first repeat
|
|
363
|
+
* is identical under both alignments, so the value, the components and every slot taken from them
|
|
364
|
+
* read the same either way (`R|1|^^^687|5.0F28.6&S&F&U/L||||F` under `H|F^&` reads a value of
|
|
365
|
+
* `5.0` under both), and what differs is the repeat structure after the first. It fires there
|
|
366
|
+
* deliberately: the boundary is still one the bytes do not force and a consumer reading `repeats`
|
|
367
|
+
* is still reading an alignment guess. Relative to the modeled slots that is **over-reporting,
|
|
368
|
+
* never under-reporting**. Narrowing it to the first boundary would change which streams a
|
|
369
|
+
* published package refuses and wants its own measurement, so the bound is written down instead.
|
|
370
|
+
*
|
|
371
|
+
* **It is a report, not a repair.** The split is unchanged, every decoded byte is identical,
|
|
372
|
+
* `repeats` still carries every one of them, and the value read is the value that was always
|
|
373
|
+
* read. Picking the other alignment would be a different guess with no more evidence behind it,
|
|
374
|
+
* on a published package.
|
|
375
|
+
*
|
|
376
|
+
* **Two further deliberate bounds, both stated rather than left to be found.**
|
|
377
|
+
* - **Only the repeat role.** The **component** role reaches a modeled slot too, and differently:
|
|
378
|
+
* there the components stay in the record and move one slot along, so a local code reads as a
|
|
379
|
+
* coding scheme and a given name as a middle name. That is measured, reported by nothing where
|
|
380
|
+
* the earlier body is a recognized mnemonic, and open. It is not covered here, because wiring a
|
|
381
|
+
* sink to another split is another criterion wanting its own population measurement.
|
|
382
|
+
* - **The tail is weighed one construct deep**, exactly as for the shift report. Where the escape
|
|
383
|
+
* character the reading resumes on heads a sequence this codec *recognizes*, the bytes prefer
|
|
384
|
+
* the reading taken and the repeats are the ones the sender wrote: under a set naming the
|
|
385
|
+
* repeat separator `F`, `28.6&F&F&F&U/L` is that separator escaped, written, and escaped again,
|
|
386
|
+
* and refusing it would be an over-refusal of a well-formed stream. Where it heads a sequence
|
|
387
|
+
* whose body is *unrecognized*, the competing alignment would leave **two** escape characters
|
|
388
|
+
* bare, so the preference is stronger again, and this stays silent there even though the gained
|
|
389
|
+
* boundary is real. That last case is the named residue: measured, not overlooked.
|
|
390
|
+
*
|
|
391
|
+
* **Catch it on the first read.** Emit rewrites the preserved sequences into recognized
|
|
392
|
+
* mnemonics, and those bytes carry the reading that was taken unambiguously, so a
|
|
393
|
+
* second-generation read is silent and is correct about its own bytes. A clean re-read is not
|
|
394
|
+
* evidence.
|
|
395
|
+
*/
|
|
396
|
+
ASTM_RECORD_ALIGNMENT_TRUNCATED_FIELD: "ASTM_RECORD_ALIGNMENT_TRUNCATED_FIELD",
|
|
330
397
|
/** An escape sequence body was not one of `&F&`/`&S&`/`&R&`/`&E&`: preserved verbatim. */
|
|
331
398
|
ASTM_UNKNOWN_ESCAPE_SEQUENCE: "ASTM_UNKNOWN_ESCAPE_SEQUENCE",
|
|
332
399
|
/**
|
|
@@ -486,6 +553,13 @@ function alignmentShiftedFields(position) {
|
|
|
486
553
|
position
|
|
487
554
|
};
|
|
488
555
|
}
|
|
556
|
+
function alignmentTruncatedField(position) {
|
|
557
|
+
return {
|
|
558
|
+
code: WARNING_CODES.ASTM_RECORD_ALIGNMENT_TRUNCATED_FIELD,
|
|
559
|
+
message: "A repeat boundary here is one of two escape alignments the bytes carry, and the reading that took it resumes on an escape character heading no sequence, which the other alignment uses to close one. This field is read as more repeats than the other reading gives it, and its modeled value and components are taken from the first repeat alone, so where that boundary is the first one the rest of the field leaves every modeled slot. The reading was kept and every byte is preserved.",
|
|
560
|
+
position
|
|
561
|
+
};
|
|
562
|
+
}
|
|
489
563
|
function delimitersRedeclared(position) {
|
|
490
564
|
return {
|
|
491
565
|
code: WARNING_CODES.ASTM_RECORD_DELIMITERS_REDECLARED,
|
|
@@ -1111,7 +1185,7 @@ var referenceCorpus = defineAstmProfile({
|
|
|
1111
1185
|
tolerate: [
|
|
1112
1186
|
{
|
|
1113
1187
|
code: "ASTM_UNKNOWN_ESCAPE_SEQUENCE",
|
|
1114
|
-
rationale: "The OSS reference stack (python-astm codec.py, firsthand-verified) treats '&' as literal data and never decodes escape sequences, so streams from that ecosystem carry non-standard '&\u2026&' bodies our escape-aware tokenizer flags. The body is preserved byte-for-byte in the value. NOT always non-clinical, and this is measured rather than assumed: where the body is a delimiter, that delimiter never became a field boundary, so `28.6&|&U/L` reads as one value with no units and no final status. That subset now also raises ASTM_RECORD_DELIMITER_SWALLOWED_BY_ESCAPE, which this profile cannot tolerate, so tolerating this code no longer hides it. The same holds for the mirror case, where a sequence ends where another could have begun and a delimiter splits that the other alignment would have held: that raises ASTM_RECORD_AMBIGUOUS_ESCAPE_ALIGNMENT, which this profile cannot tolerate either. Where that gained boundary is a field boundary and the reading taken resumes on an escape character heading no sequence, every later field shifts and a result's units and status move with it, which raises ASTM_RECORD_ALIGNMENT_SHIFTED_FIELDS, untolerable here as well."
|
|
1188
|
+
rationale: "The OSS reference stack (python-astm codec.py, firsthand-verified) treats '&' as literal data and never decodes escape sequences, so streams from that ecosystem carry non-standard '&\u2026&' bodies our escape-aware tokenizer flags. The body is preserved byte-for-byte in the value. NOT always non-clinical, and this is measured rather than assumed: where the body is a delimiter, that delimiter never became a field boundary, so `28.6&|&U/L` reads as one value with no units and no final status. That subset now also raises ASTM_RECORD_DELIMITER_SWALLOWED_BY_ESCAPE, which this profile cannot tolerate, so tolerating this code no longer hides it. The same holds for the mirror case, where a sequence ends where another could have begun and a delimiter splits that the other alignment would have held: that raises ASTM_RECORD_AMBIGUOUS_ESCAPE_ALIGNMENT, which this profile cannot tolerate either. Where that gained boundary is a field boundary and the reading taken resumes on an escape character heading no sequence, every later field shifts and a result's units and status move with it, which raises ASTM_RECORD_ALIGNMENT_SHIFTED_FIELDS, untolerable here as well. Where that same gained boundary is a repeat boundary, nothing shifts but the field is read out of its first repeat alone, so a value truncates and a test identity empties, which raises ASTM_RECORD_ALIGNMENT_TRUNCATED_FIELD, untolerable here too. Both of those carry the same tail bound and stay silent where the escape character past the boundary heads a sequence of its own, recognized or not, which is much of this corpus: tolerating this code does not quiet them, but their absence is not a statement that nothing was lost."
|
|
1115
1189
|
}
|
|
1116
1190
|
]
|
|
1117
1191
|
});
|
|
@@ -1185,7 +1259,7 @@ function isMnemonicBody(body) {
|
|
|
1185
1259
|
function escapeBody(body, d) {
|
|
1186
1260
|
return isMnemonicBody(body) ? d[MNEMONIC_ROLES[body]] : void 0;
|
|
1187
1261
|
}
|
|
1188
|
-
function splitEscapeAware(text, delimiter, escape, onAmbiguousAlignment, onShiftedFields) {
|
|
1262
|
+
function splitEscapeAware(text, delimiter, escape, onAmbiguousAlignment, onShiftedFields, onTruncatedField) {
|
|
1189
1263
|
if (text.length === 0) return [""];
|
|
1190
1264
|
const out = [];
|
|
1191
1265
|
let current = "";
|
|
@@ -1199,6 +1273,7 @@ function splitEscapeAware(text, delimiter, escape, onAmbiguousAlignment, onShift
|
|
|
1199
1273
|
}
|
|
1200
1274
|
if (contested && !isEscapeSequenceAt(text, i + 4, escape)) {
|
|
1201
1275
|
onShiftedFields?.(out.length);
|
|
1276
|
+
onTruncatedField?.(out.length);
|
|
1202
1277
|
}
|
|
1203
1278
|
current += text.slice(i, i + 3);
|
|
1204
1279
|
i += 3;
|
|
@@ -1218,7 +1293,7 @@ function splitEscapeAware(text, delimiter, escape, onAmbiguousAlignment, onShift
|
|
|
1218
1293
|
}
|
|
1219
1294
|
|
|
1220
1295
|
// src/records/tokenize.ts
|
|
1221
|
-
function tokenizeRecord(record, d, onUnknownEscape, onUnpairedEscape, onSwallowedDelimiter, onAmbiguousAlignment, onAlignmentShiftedFields) {
|
|
1296
|
+
function tokenizeRecord(record, d, onUnknownEscape, onUnpairedEscape, onSwallowedDelimiter, onAmbiguousAlignment, onAlignmentShiftedFields, onAlignmentTruncatedField) {
|
|
1222
1297
|
const rawFields = splitEscapeAware(
|
|
1223
1298
|
record,
|
|
1224
1299
|
d.field,
|
|
@@ -1233,11 +1308,15 @@ function tokenizeRecord(record, d, onUnknownEscape, onUnpairedEscape, onSwallowe
|
|
|
1233
1308
|
() => onUnknownEscape?.(fieldIndex),
|
|
1234
1309
|
() => onUnpairedEscape?.(fieldIndex),
|
|
1235
1310
|
() => onSwallowedDelimiter?.(fieldIndex),
|
|
1236
|
-
() => onAmbiguousAlignment?.(fieldIndex)
|
|
1311
|
+
() => onAmbiguousAlignment?.(fieldIndex),
|
|
1312
|
+
// The repeat split runs INSIDE one field, so the index it hands back is a repeat index and is
|
|
1313
|
+
// discarded: what a caller needs is which field carries the contested boundary, and that is
|
|
1314
|
+
// the index this map is already iterating.
|
|
1315
|
+
() => onAlignmentTruncatedField?.(fieldIndex)
|
|
1237
1316
|
)
|
|
1238
1317
|
);
|
|
1239
1318
|
}
|
|
1240
|
-
function tokenizeHeader(record, d, onUnknownEscape, onUnpairedEscape, onSwallowedDelimiter, onAmbiguousAlignment, onAlignmentShiftedFields) {
|
|
1319
|
+
function tokenizeHeader(record, d, onUnknownEscape, onUnpairedEscape, onSwallowedDelimiter, onAmbiguousAlignment, onAlignmentShiftedFields, onAlignmentTruncatedField) {
|
|
1241
1320
|
const defEnd = record.indexOf(d.field, 2);
|
|
1242
1321
|
const definition = defEnd === -1 ? record.slice(2) : record.slice(2, defEnd);
|
|
1243
1322
|
const head = [opaqueField(record.slice(0, Math.min(1, record.length))), opaqueField(definition)];
|
|
@@ -1249,17 +1328,27 @@ function tokenizeHeader(record, d, onUnknownEscape, onUnpairedEscape, onSwallowe
|
|
|
1249
1328
|
(i) => onUnpairedEscape?.(i + 2),
|
|
1250
1329
|
(i) => onSwallowedDelimiter?.(i + 2),
|
|
1251
1330
|
(i) => onAmbiguousAlignment?.(i + 2),
|
|
1252
|
-
(i) => onAlignmentShiftedFields?.(i + 2)
|
|
1331
|
+
(i) => onAlignmentShiftedFields?.(i + 2),
|
|
1332
|
+
(i) => onAlignmentTruncatedField?.(i + 2)
|
|
1253
1333
|
);
|
|
1254
1334
|
return [...head, ...data];
|
|
1255
1335
|
}
|
|
1256
1336
|
function opaqueField(raw) {
|
|
1257
1337
|
return { raw, components: [raw], repeats: [[raw]] };
|
|
1258
1338
|
}
|
|
1259
|
-
function toField(raw, d, onUnknownEscape, onUnpairedEscape, onSwallowedDelimiter, onAmbiguousAlignment) {
|
|
1260
|
-
const rawRepeats = splitEscapeAware(
|
|
1261
|
-
|
|
1262
|
-
|
|
1339
|
+
function toField(raw, d, onUnknownEscape, onUnpairedEscape, onSwallowedDelimiter, onAmbiguousAlignment, onAlignmentTruncatedField) {
|
|
1340
|
+
const rawRepeats = splitEscapeAware(
|
|
1341
|
+
raw,
|
|
1342
|
+
d.repeat,
|
|
1343
|
+
d.escape,
|
|
1344
|
+
() => {
|
|
1345
|
+
onAmbiguousAlignment();
|
|
1346
|
+
},
|
|
1347
|
+
void 0,
|
|
1348
|
+
() => {
|
|
1349
|
+
onAlignmentTruncatedField();
|
|
1350
|
+
}
|
|
1351
|
+
);
|
|
1263
1352
|
const repeats = rawRepeats.map(
|
|
1264
1353
|
(rep) => splitEscapeAware(rep, d.component, d.escape, () => {
|
|
1265
1354
|
onAmbiguousAlignment();
|
|
@@ -1410,6 +1499,11 @@ function buildRecord(line2, recordIndex, d, warnings) {
|
|
|
1410
1499
|
alignmentShiftedFields({ recordIndex, recordType: rawType, fieldIndex: fieldIndex + 1 })
|
|
1411
1500
|
);
|
|
1412
1501
|
};
|
|
1502
|
+
const onAlignmentTruncatedField = (fieldIndex) => {
|
|
1503
|
+
warnings.push(
|
|
1504
|
+
alignmentTruncatedField({ recordIndex, recordType: rawType, fieldIndex: fieldIndex + 1 })
|
|
1505
|
+
);
|
|
1506
|
+
};
|
|
1413
1507
|
const fields = rawType === "H" ? tokenizeHeader(
|
|
1414
1508
|
line2,
|
|
1415
1509
|
d,
|
|
@@ -1417,7 +1511,8 @@ function buildRecord(line2, recordIndex, d, warnings) {
|
|
|
1417
1511
|
onUnpairedEscape,
|
|
1418
1512
|
onSwallowedDelimiter,
|
|
1419
1513
|
onAmbiguousAlignment,
|
|
1420
|
-
onAlignmentShiftedFields
|
|
1514
|
+
onAlignmentShiftedFields,
|
|
1515
|
+
onAlignmentTruncatedField
|
|
1421
1516
|
) : tokenizeRecord(
|
|
1422
1517
|
line2,
|
|
1423
1518
|
d,
|
|
@@ -1425,7 +1520,8 @@ function buildRecord(line2, recordIndex, d, warnings) {
|
|
|
1425
1520
|
onUnpairedEscape,
|
|
1426
1521
|
onSwallowedDelimiter,
|
|
1427
1522
|
onAmbiguousAlignment,
|
|
1428
|
-
onAlignmentShiftedFields
|
|
1523
|
+
onAlignmentShiftedFields,
|
|
1524
|
+
onAlignmentTruncatedField
|
|
1429
1525
|
);
|
|
1430
1526
|
if (fields.length === 1 && line2.slice(1).trim().length > 0) {
|
|
1431
1527
|
warnings.push(fieldsUnseparated({ recordIndex, recordType: rawType }));
|
|
@@ -2638,8 +2734,8 @@ function concatBytes3(chunks) {
|
|
|
2638
2734
|
}
|
|
2639
2735
|
|
|
2640
2736
|
// src/index.ts
|
|
2641
|
-
var VERSION = "0.0.
|
|
2737
|
+
var VERSION = "0.0.18";
|
|
2642
2738
|
|
|
2643
|
-
export { ALL_ASTM_WARNING_CODES, AMBIGUOUS_CODES, ACK as ASTM_ACK, ENQ as ASTM_ENQ, EOT as ASTM_EOT, NAK as ASTM_NAK, AstmAmbiguousStreamError, AstmFrameEncodeError, AstmFrameStrictError, AstmParseError, AstmProfileDefinitionError, AstmSerializeError, AstmStrictError, CANONICAL_DELIMITERS, FATAL_CODES, FRAME_WARNING_CODES, LIVD_WARNING_CODES, LTP_WARNING_CODES, SAFETY_CRITICAL_CODES, TOLERABLE_CODES, VERSION, WARNING_CODES, alignmentShiftedFields, ambiguousEscapeAlignment, ambiguousMessageKind, ambiguousValueSplit, applyAstmProfile, applyAstmProfileToWarnings, applyLivd, astmDateToLocalISO, astmProfiles, attachComments, buildAstmMessage, classifyMessage, comments, commentsFor, composeAstmFrames, computeChecksum, decodeAstmFrames, decodeEscapes, deepFreeze, defineAstmProfile, defineLivdCatalog, delimiterRoleCollision, delimiterSwallowedByEscape, delimitersRedeclared, detectFraming, encodeComponent, fieldScalar, fieldsUnseparated, frameBadChecksum, frameOversize, frameSequenceGap, frameUnterminated, getAstmProfile, getDefaultAstmProfile, hasCollidingRoles, 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, readDelimiterDeclaration, readDelimiters, recognizeUniversalTestId, resolveProfileTransport, results, serializeAstmRecord, serializeAstmRecords, serializeField, serializeFramedAstm, setDefaultAstmProfile, splitEscapeAware, toChecksumHex, tokenizeHeader, tokenizeRecord, undefinedAbnormalFlag, undefinedResultStatus, uninterpretedQueryStatus, unitsAbsent, unknownEscapeSequence, unknownRecordType, unpairedEscapeCharacter, unparseableReferenceRange, unreadableRedeclaration };
|
|
2739
|
+
export { ALL_ASTM_WARNING_CODES, AMBIGUOUS_CODES, ACK as ASTM_ACK, ENQ as ASTM_ENQ, EOT as ASTM_EOT, NAK as ASTM_NAK, AstmAmbiguousStreamError, AstmFrameEncodeError, AstmFrameStrictError, AstmParseError, AstmProfileDefinitionError, AstmSerializeError, AstmStrictError, CANONICAL_DELIMITERS, FATAL_CODES, FRAME_WARNING_CODES, LIVD_WARNING_CODES, LTP_WARNING_CODES, SAFETY_CRITICAL_CODES, TOLERABLE_CODES, VERSION, WARNING_CODES, alignmentShiftedFields, alignmentTruncatedField, ambiguousEscapeAlignment, ambiguousMessageKind, ambiguousValueSplit, applyAstmProfile, applyAstmProfileToWarnings, applyLivd, astmDateToLocalISO, astmProfiles, attachComments, buildAstmMessage, classifyMessage, comments, commentsFor, composeAstmFrames, computeChecksum, decodeAstmFrames, decodeEscapes, deepFreeze, defineAstmProfile, defineLivdCatalog, delimiterRoleCollision, delimiterSwallowedByEscape, delimitersRedeclared, detectFraming, encodeComponent, fieldScalar, fieldsUnseparated, frameBadChecksum, frameOversize, frameSequenceGap, frameUnterminated, getAstmProfile, getDefaultAstmProfile, hasCollidingRoles, 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, readDelimiterDeclaration, readDelimiters, recognizeUniversalTestId, resolveProfileTransport, results, serializeAstmRecord, serializeAstmRecords, serializeField, serializeFramedAstm, setDefaultAstmProfile, splitEscapeAware, toChecksumHex, tokenizeHeader, tokenizeRecord, undefinedAbnormalFlag, undefinedResultStatus, uninterpretedQueryStatus, unitsAbsent, unknownEscapeSequence, unknownRecordType, unpairedEscapeCharacter, unparseableReferenceRange, unreadableRedeclaration };
|
|
2644
2740
|
//# sourceMappingURL=index.mjs.map
|
|
2645
2741
|
//# sourceMappingURL=index.mjs.map
|