@cosyte/astm 0.0.18 → 0.0.20
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 +359 -19
- package/README.md +75 -18
- package/dist/index.cjs +220 -53
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +539 -135
- package/dist/index.d.ts +539 -135
- package/dist/index.mjs +220 -54
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -1
package/dist/index.cjs
CHANGED
|
@@ -271,10 +271,11 @@ var WARNING_CODES = {
|
|
|
271
271
|
ASTM_RECORD_AMBIGUOUS_ESCAPE_ALIGNMENT: "ASTM_RECORD_AMBIGUOUS_ESCAPE_ALIGNMENT",
|
|
272
272
|
/**
|
|
273
273
|
* Two escape alignments of the same bytes disagreed about a **field** boundary, the reading taken
|
|
274
|
-
* kept it, and the escape character that reading resumes on **heads no sequence
|
|
275
|
-
* boundary was bought with
|
|
276
|
-
* exactly the reading that can. Every field after that point sits one place further
|
|
277
|
-
* competing alignment puts it
|
|
274
|
+
* kept it, and the escape character that reading resumes on **heads no sequence this codec can
|
|
275
|
+
* interpret**, so the boundary was bought with bytes this reading cannot read while the competing
|
|
276
|
+
* alignment is exactly the reading that can. Every field after that point sits one place further
|
|
277
|
+
* right than the competing alignment puts it, **except on the one class named at the end of this
|
|
278
|
+
* entry**, where the two readings instead read the same number of fields in different places.
|
|
278
279
|
*
|
|
279
280
|
* **The shift is the harm, and on a result record it reaches the status slot.** Measured on the
|
|
280
281
|
* canonical set, `R|1|^^^687|28.6&F&|&U/L||||F` reads **9** fields under the reading taken and
|
|
@@ -305,24 +306,43 @@ var WARNING_CODES = {
|
|
|
305
306
|
* modeled *inside* a field: a Universal Test ID's four components are the LOINC-candidate slot,
|
|
306
307
|
* the test name, the **coding scheme** and the **local code**, and a patient name's three are
|
|
307
308
|
* last, first and middle. A gained component boundary shifts those, so a local code can be read
|
|
308
|
-
* as a coding scheme and a given name as a middle name. Measured, and
|
|
309
|
-
* a recognized mnemonic
|
|
310
|
-
* {@link WARNING_CODES.ASTM_UNPAIRED_ESCAPE_CHARACTER}
|
|
311
|
-
*
|
|
312
|
-
* {@link WARNING_CODES.
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
* - **The tail is weighed one construct deep.** Where the escape
|
|
318
|
-
* resumes on heads a sequence this codec *recognizes*, the reading
|
|
319
|
-
*
|
|
320
|
-
*
|
|
321
|
-
*
|
|
322
|
-
*
|
|
323
|
-
*
|
|
324
|
-
*
|
|
325
|
-
*
|
|
309
|
+
* as a coding scheme and a given name as a middle name. Measured, and it used to be reported by
|
|
310
|
+
* nothing where the earlier body was a recognized mnemonic (only the tolerable
|
|
311
|
+
* {@link WARNING_CODES.ASTM_UNPAIRED_ESCAPE_CHARACTER} fired, so a gate-legal profile accepted
|
|
312
|
+
* it). It is now reported by
|
|
313
|
+
* {@link WARNING_CODES.ASTM_RECORD_ALIGNMENT_SHIFTED_COMPONENTS}, a separate code on the same
|
|
314
|
+
* tail test wired to the component split, because wiring this sink to another split is a
|
|
315
|
+
* different criterion needing its own population measurement. It is still not something **this**
|
|
316
|
+
* code covers. The repeat role costs the **value**, which this code does not report either, and
|
|
317
|
+
* which {@link WARNING_CODES.ASTM_RECORD_ALIGNMENT_TRUNCATED_FIELD} reports.
|
|
318
|
+
* - **The tail is weighed one construct deep, and exactly one tail is excluded.** Where the escape
|
|
319
|
+
* character the reading taken resumes on heads a sequence this codec *recognizes*, the reading
|
|
320
|
+
* taken interprets a construct and leaves nothing unread: under a set naming the field separator
|
|
321
|
+
* `F`, `28.6&F&F&F&U/L` is that separator escaped, written, and escaped again, entirely well
|
|
322
|
+
* formed, and refusing it would be an over-refusal. That is the only tail on which a stream's
|
|
323
|
+
* escaping can be clean, which is why it is the only exclusion. Where the tail heads a sequence
|
|
324
|
+
* whose body is *unrecognized*, the reading taken consumes a triple it cannot read, preserved
|
|
325
|
+
* verbatim and never guessed at, the field shift is the same shift, and **this reports it**.
|
|
326
|
+
* Consuming a triple is not interpreting one.
|
|
327
|
+
* - **That remaining silence is a TRADE, not a claim that nothing was lost.** The gained field
|
|
328
|
+
* boundary is exactly as real on the excluded tail, and there it is not merely under-reported:
|
|
329
|
+
* it is `warnings: []`. Under the canonical set `R|1|^^^687|28.6&F&|&F&U/L||||F` reads **nine**
|
|
330
|
+
* fields against the competing alignment's eight and hands back a status of **`final`** with an
|
|
331
|
+
* empty warning list. Reporting it would refuse a stream whose escaping is working, which is
|
|
332
|
+
* the trade this bound makes. **Read the raw line when an escape character sits next to a
|
|
333
|
+
* delimiter, whether or not anything fired.**
|
|
334
|
+
*
|
|
335
|
+
* **⚠️ THE ONE CLASS WHERE NO FIELD INDEX MOVES, NAMED RATHER THAN LEFT TO BE FOUND.** Where the
|
|
336
|
+
* sequence past the boundary carries the **field separator itself** as its body, the reading taken
|
|
337
|
+
* holds that character inside an opaque atom while the competing alignment splits on it, so the
|
|
338
|
+
* two readings return the **same number** of fields in **different places**. Under the canonical
|
|
339
|
+
* set `R|1|^^^687|28.6&F&|&|&U/L||||F` reads nine fields under both, the status `F` sits in field
|
|
340
|
+
* 9 under both, and what differs is the units: `&|&U/L` against `&U/L`. This still fires there,
|
|
341
|
+
* which is over-reporting relative to the field indexes and never under-reporting, and the class
|
|
342
|
+
* costs no stream its disposition: that tail body is a splitting delimiter in force, so
|
|
343
|
+
* {@link WARNING_CODES.ASTM_RECORD_DELIMITER_SWALLOWED_BY_ESCAPE} has already refused the record.
|
|
344
|
+
* What holds on **every** firing tuple is that the two readings disagree and that both consume
|
|
345
|
+
* every byte, so neither is forced.
|
|
326
346
|
*
|
|
327
347
|
* **Catch it on the first read.** Emit rewrites the preserved sequences into recognized mnemonics,
|
|
328
348
|
* and those bytes carry the reading that was taken unambiguously, so a second-generation read is
|
|
@@ -331,16 +351,17 @@ var WARNING_CODES = {
|
|
|
331
351
|
ASTM_RECORD_ALIGNMENT_SHIFTED_FIELDS: "ASTM_RECORD_ALIGNMENT_SHIFTED_FIELDS",
|
|
332
352
|
/**
|
|
333
353
|
* Two escape alignments of the same bytes disagreed about a **repeat** boundary, the reading
|
|
334
|
-
* taken kept it, and the escape character that reading resumes on **heads no sequence
|
|
335
|
-
* Same contested position and same tail test as
|
|
354
|
+
* taken kept it, and the escape character that reading resumes on **heads no sequence this codec
|
|
355
|
+
* can interpret**. Same contested position and same tail test as
|
|
336
356
|
* {@link WARNING_CODES.ASTM_RECORD_ALIGNMENT_SHIFTED_FIELDS}, on a different delimiter role, and
|
|
337
357
|
* what it costs is a different thing, which is why it is a different code.
|
|
338
358
|
*
|
|
339
359
|
* **Nothing shifts, and that is not the same as nothing being lost.** No field-indexed slot
|
|
340
360
|
* moves: the units and the result status are read out of the same field numbers under either
|
|
341
361
|
* alignment. What this reports is that **the field is read as more repeats than the competing
|
|
342
|
-
* alignment gives it**, which
|
|
343
|
-
*
|
|
362
|
+
* alignment gives it**, which holds wherever it fires **except on the one class named at the end
|
|
363
|
+
* of this entry**. What that costs depends on **which** boundary was gained, and the two cases
|
|
364
|
+
* are stated separately rather than folded into one claim.
|
|
344
365
|
*
|
|
345
366
|
* **Where it is the FIRST boundary in the field, a modeled slot is lost**, because a field's
|
|
346
367
|
* modeled value and components are taken from its **first repeat alone**. Everything past the
|
|
@@ -378,17 +399,36 @@ var WARNING_CODES = {
|
|
|
378
399
|
* **Two further deliberate bounds, both stated rather than left to be found.**
|
|
379
400
|
* - **Only the repeat role.** The **component** role reaches a modeled slot too, and differently:
|
|
380
401
|
* there the components stay in the record and move one slot along, so a local code reads as a
|
|
381
|
-
* coding scheme and a given name as a middle name. That is measured
|
|
382
|
-
*
|
|
383
|
-
*
|
|
384
|
-
*
|
|
385
|
-
*
|
|
386
|
-
*
|
|
387
|
-
*
|
|
388
|
-
*
|
|
389
|
-
*
|
|
390
|
-
*
|
|
391
|
-
*
|
|
402
|
+
* coding scheme and a given name as a middle name. That is measured and is now reported by
|
|
403
|
+
* {@link WARNING_CODES.ASTM_RECORD_ALIGNMENT_SHIFTED_COMPONENTS}, a third code on the same tail
|
|
404
|
+
* test wired to the component split. It is still not covered **here**, because wiring a sink to
|
|
405
|
+
* another split is another criterion, which is why it took its own population measurement.
|
|
406
|
+
* - **The tail is weighed one construct deep**, exactly as for the shift report, **and exactly
|
|
407
|
+
* one tail is excluded.** Where the escape character the reading resumes on heads a sequence
|
|
408
|
+
* this codec *recognizes*, it interprets a construct and the repeats are the ones the sender
|
|
409
|
+
* wrote: under a set naming the repeat separator `F`, `28.6&F&F&F&U/L` is that separator
|
|
410
|
+
* escaped, written, and escaped again, entirely well formed, and refusing it would be an
|
|
411
|
+
* over-refusal. That is the only tail on which a stream's escaping can be clean, which is why it
|
|
412
|
+
* is the only exclusion. Where the tail heads a sequence whose body is *unrecognized*, the
|
|
413
|
+
* reading taken consumes a triple it cannot read, preserved verbatim and never guessed at, the
|
|
414
|
+
* truncation is the same truncation, and **this reports it**.
|
|
415
|
+
* - **That remaining silence is a TRADE, not a claim that nothing was lost.** On the excluded tail
|
|
416
|
+
* the gained boundary is exactly as real and it is `warnings: []`: under the canonical set
|
|
417
|
+
* `R|1|^^^687|28.6&S&\&S&U/L|U/L||||F` reads a value of `28.6^`, and `^U/L` leaves every
|
|
418
|
+
* modeled slot, with an empty warning list. Reporting it would refuse a stream whose escaping
|
|
419
|
+
* is working.
|
|
420
|
+
*
|
|
421
|
+
* **⚠️ THE ONE CLASS WHERE THE FIELD IS NOT READ AS MORE REPEATS, NAMED RATHER THAN LEFT TO BE
|
|
422
|
+
* FOUND.** Where the sequence past the boundary carries the **repeat separator itself** as its
|
|
423
|
+
* body, the reading taken holds that character inside an opaque atom while the competing
|
|
424
|
+
* alignment splits on it, so the two readings return the **same number** of repeats in
|
|
425
|
+
* **different places** and what differs is their contents: under the canonical set
|
|
426
|
+
* `28.6&F&\&\&U/L` reads two repeats under both. This still fires there, which is over-reporting
|
|
427
|
+
* relative to the repeat count and never under-reporting, and the class costs no stream its
|
|
428
|
+
* disposition: that tail body is a splitting delimiter in force, so
|
|
429
|
+
* {@link WARNING_CODES.ASTM_RECORD_DELIMITER_SWALLOWED_BY_ESCAPE} has already refused the record.
|
|
430
|
+
* What holds on **every** firing tuple is that the two readings disagree and that both consume
|
|
431
|
+
* every byte, so neither is forced.
|
|
392
432
|
*
|
|
393
433
|
* **Catch it on the first read.** Emit rewrites the preserved sequences into recognized
|
|
394
434
|
* mnemonics, and those bytes carry the reading that was taken unambiguously, so a
|
|
@@ -396,6 +436,100 @@ var WARNING_CODES = {
|
|
|
396
436
|
* evidence.
|
|
397
437
|
*/
|
|
398
438
|
ASTM_RECORD_ALIGNMENT_TRUNCATED_FIELD: "ASTM_RECORD_ALIGNMENT_TRUNCATED_FIELD",
|
|
439
|
+
/**
|
|
440
|
+
* Two escape alignments of the same bytes disagreed about a **component** boundary, the reading
|
|
441
|
+
* taken kept it, and the escape character that reading resumes on **heads no sequence this codec
|
|
442
|
+
* can interpret**. Same contested position and same tail test as
|
|
443
|
+
* {@link WARNING_CODES.ASTM_RECORD_ALIGNMENT_SHIFTED_FIELDS} and
|
|
444
|
+
* {@link WARNING_CODES.ASTM_RECORD_ALIGNMENT_TRUNCATED_FIELD}, on the third and last splitting
|
|
445
|
+
* role, and what it costs is a third thing again, which is why it is a third code.
|
|
446
|
+
*
|
|
447
|
+
* **The components are neither shifted out of the record nor dropped from it: they MOVE one slot
|
|
448
|
+
* along.** A field's components are modeled *inside* it, so a gained component boundary shifts
|
|
449
|
+
* every component after it exactly as a gained field boundary shifts every later field, **except
|
|
450
|
+
* on the one class named at the end of this entry**, where the two readings instead read the same
|
|
451
|
+
* number of components in different places. Nothing leaves the record and nothing changes field
|
|
452
|
+
* number; what changes is which modeled slot each component lands in, and those slots are named
|
|
453
|
+
* things:
|
|
454
|
+
*
|
|
455
|
+
* - **A Universal Test ID's coding scheme and local code.** Under the canonical set,
|
|
456
|
+
* `R|1|&F&^&GLU^L^687|28.6|U/L||||F` reads four components, so `L` is the coding scheme and
|
|
457
|
+
* `687` the vendor's local code. The competing alignment reads three, so `L` is the test name
|
|
458
|
+
* and **`687` is the coding scheme**. A local code and a code-system selector are not the same
|
|
459
|
+
* thing, and a consumer routing on one of them routes on the alignment.
|
|
460
|
+
* - **A patient's given and middle names.** `P|1||MRN-0001||DOE&F&^&JANE^A||19700101|F` reads a
|
|
461
|
+
* given name of `&JANE` and a middle name of `A` under the reading taken, and under the
|
|
462
|
+
* competing one `A` is the **given** name with no middle name at all.
|
|
463
|
+
*
|
|
464
|
+
* Before this code the only warning on those streams was the **tolerable**
|
|
465
|
+
* {@link WARNING_CODES.ASTM_UNPAIRED_ESCAPE_CHARACTER}, so the widest gate-legal profile plus
|
|
466
|
+
* `{ strict: true }` accepted a test identity and a patient name whose parts were decided by the
|
|
467
|
+
* alignment rather than by the sender.
|
|
468
|
+
*
|
|
469
|
+
* **Every gained boundary at or before the LAST MODELED COMPONENT INDEX moves a slot, not only
|
|
470
|
+
* the first, and that is the difference from the repeat role.** There the field is modeled out of
|
|
471
|
+
* its first repeat alone, so only the first gained boundary reaches a modeled slot; here the shift
|
|
472
|
+
* propagates from wherever the boundary sits to the end of the component list.
|
|
473
|
+
*
|
|
474
|
+
* **TWO bounds run the other way, and this fires inside both**, which is **over-reporting, never
|
|
475
|
+
* under-reporting**: the direction this package errs in. Both axes are swept on their own rather
|
|
476
|
+
* than left to the shared corpus, which holds them fixed, and neither is closed by narrowing the
|
|
477
|
+
* guard, because narrowing changes which streams a published package refuses and wants its own
|
|
478
|
+
* measurement.
|
|
479
|
+
*
|
|
480
|
+
* - **Past the last modeled component index, nothing NAMED moves.** A model reads a fixed number
|
|
481
|
+
* of components: a patient name three (last, first, middle), a Universal Test ID four. A
|
|
482
|
+
* contested boundary further right than that still shifts the components after it while every
|
|
483
|
+
* named slot reads byte-identically under both alignments.
|
|
484
|
+
* - **Inside a LATER repeat nothing modeled moves at all**, because `components` is the first
|
|
485
|
+
* repeat, so what changes is `repeats[n]` for that `n` alone.
|
|
486
|
+
*
|
|
487
|
+
* It fires in both because the boundary is still one the bytes do not force, and a consumer
|
|
488
|
+
* reading `components` or `repeats` is still reading an alignment guess.
|
|
489
|
+
*
|
|
490
|
+
* **It is a report, not a repair.** The split is unchanged, every decoded byte is identical, and
|
|
491
|
+
* the components read are the components that were always read. Picking the other alignment would
|
|
492
|
+
* be a different guess with no more evidence behind it, on a published package. **Withholding the
|
|
493
|
+
* moved slots is a separate question and is deliberately not answered here**, for the reason the
|
|
494
|
+
* shift report already gives: declining to model a slot changes an extracted value for every
|
|
495
|
+
* consumer of a package already on the registry.
|
|
496
|
+
*
|
|
497
|
+
* **The tail is weighed one construct deep**, exactly as for the other two, **and exactly one tail
|
|
498
|
+
* is excluded.** Where the escape character the reading resumes on heads a sequence this codec
|
|
499
|
+
* *recognizes*, it interprets a construct and the components are the ones the sender wrote: under
|
|
500
|
+
* a set naming the component separator `F`, `GLU&F&F&F&L` is that separator escaped, written, and
|
|
501
|
+
* escaped again, entirely well formed, and refusing it would be an over-refusal. That is the only
|
|
502
|
+
* tail on which a stream's escaping can be clean, which is why it is the only exclusion. Where the
|
|
503
|
+
* tail heads a sequence whose body is *unrecognized*, the reading taken consumes a triple it
|
|
504
|
+
* cannot read, preserved verbatim and never guessed at, the slots move exactly as far, and **this
|
|
505
|
+
* reports it**.
|
|
506
|
+
*
|
|
507
|
+
* **That remaining silence is a TRADE, not a claim that nothing was lost.** On the excluded tail
|
|
508
|
+
* the gained boundary is exactly as real and it is `warnings: []`: under the canonical set
|
|
509
|
+
* `&F&^&F&GLU^L^687` reads one component more than the competing alignment, so `L` is the coding
|
|
510
|
+
* scheme and `687` the local code under one reading and `687` the **coding scheme** under the
|
|
511
|
+
* other, with an empty warning list. Reporting it would refuse a stream whose escaping is
|
|
512
|
+
* working.
|
|
513
|
+
*
|
|
514
|
+
* **⚠️ THE ONE CLASS WHERE NO COMPONENT INDEX MOVES, NAMED RATHER THAN LEFT TO BE FOUND.** It is a
|
|
515
|
+
* **third** bound running the other way, and unlike the two above it is about the bytes past the
|
|
516
|
+
* boundary rather than about where the boundary sits. Where the sequence past it carries the
|
|
517
|
+
* **component separator itself** as its body, the reading taken holds that character inside an
|
|
518
|
+
* opaque atom while the competing alignment splits on it, so the two readings return the **same
|
|
519
|
+
* number** of components in **different places** and what differs is their contents: under the
|
|
520
|
+
* canonical set `P|1||MRN-0001||DOE&F&^&^&JANE^A||19700101|F` reads three components under both,
|
|
521
|
+
* with `A` the middle name under both. This still fires there, and the class costs no stream its
|
|
522
|
+
* disposition: that tail body is a splitting delimiter in force, so
|
|
523
|
+
* {@link WARNING_CODES.ASTM_RECORD_DELIMITER_SWALLOWED_BY_ESCAPE} has already refused the record.
|
|
524
|
+
* What holds on **every** firing tuple is that the two readings disagree and that both consume
|
|
525
|
+
* every byte, so neither is forced.
|
|
526
|
+
*
|
|
527
|
+
* **Catch it on the first read.** Emit rewrites the preserved sequences into recognized
|
|
528
|
+
* mnemonics, and those bytes carry the reading that was taken unambiguously, so a
|
|
529
|
+
* second-generation read is silent and is correct about its own bytes. A clean re-read is not
|
|
530
|
+
* evidence.
|
|
531
|
+
*/
|
|
532
|
+
ASTM_RECORD_ALIGNMENT_SHIFTED_COMPONENTS: "ASTM_RECORD_ALIGNMENT_SHIFTED_COMPONENTS",
|
|
399
533
|
/** An escape sequence body was not one of `&F&`/`&S&`/`&R&`/`&E&`: preserved verbatim. */
|
|
400
534
|
ASTM_UNKNOWN_ESCAPE_SEQUENCE: "ASTM_UNKNOWN_ESCAPE_SEQUENCE",
|
|
401
535
|
/**
|
|
@@ -551,14 +685,21 @@ function ambiguousEscapeAlignment(position) {
|
|
|
551
685
|
function alignmentShiftedFields(position) {
|
|
552
686
|
return {
|
|
553
687
|
code: WARNING_CODES.ASTM_RECORD_ALIGNMENT_SHIFTED_FIELDS,
|
|
554
|
-
message: "A field 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.
|
|
688
|
+
message: "A field 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 this reader can interpret, which the other alignment uses to close one. The two readings disagree, and both consume every byte, so neither is forced. Usually every later field is one place further right than the other reading puts it, so a result's units and status may be read out of slots the sender did not put them in; one measured class does not shift at all, where the sequence past the boundary carries the field separator itself and the two readings read the same number of fields with different contents. The reading was kept and every byte is preserved.",
|
|
555
689
|
position
|
|
556
690
|
};
|
|
557
691
|
}
|
|
558
692
|
function alignmentTruncatedField(position) {
|
|
559
693
|
return {
|
|
560
694
|
code: WARNING_CODES.ASTM_RECORD_ALIGNMENT_TRUNCATED_FIELD,
|
|
561
|
-
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.
|
|
695
|
+
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 this reader can interpret, which the other alignment uses to close one. The two readings disagree, and both consume every byte, so neither is forced. Usually 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; one measured class reads the same number of repeats, where the sequence past the boundary carries the repeat separator itself. The reading was kept and every byte is preserved.",
|
|
696
|
+
position
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
function alignmentShiftedComponents(position) {
|
|
700
|
+
return {
|
|
701
|
+
code: WARNING_CODES.ASTM_RECORD_ALIGNMENT_SHIFTED_COMPONENTS,
|
|
702
|
+
message: "A component 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 this reader can interpret, which the other alignment uses to close one. The two readings disagree, and both consume every byte, so neither is forced. Usually every later component of this field sits one place further right than the other reading puts it, so a test identity's coding scheme and local code, or a patient's given and middle names, may be read out of slots the sender did not put them in; one measured class does not move at all, where the sequence past the boundary carries the component separator itself and the two readings read the same number of components with different contents. The reading was kept and every byte is preserved.",
|
|
562
703
|
position
|
|
563
704
|
};
|
|
564
705
|
}
|
|
@@ -1187,7 +1328,7 @@ var referenceCorpus = defineAstmProfile({
|
|
|
1187
1328
|
tolerate: [
|
|
1188
1329
|
{
|
|
1189
1330
|
code: "ASTM_UNKNOWN_ESCAPE_SEQUENCE",
|
|
1190
|
-
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.
|
|
1331
|
+
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 this reader can interpret, 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. Where it is a component boundary, nothing leaves the record and no field number changes, and every component after it moves one slot along, so a test identity's coding scheme and local code and a patient's given and middle names are read out of positions the other alignment does not put them in, which raises ASTM_RECORD_ALIGNMENT_SHIFTED_COMPONENTS, untolerable here as well. Each of those three shift claims has one measured exception, where the sequence past the boundary carries the splitting delimiter itself: the two readings then read the same number of segments in different places, nothing moves index, and the record was already refused by ASTM_RECORD_DELIMITER_SWALLOWED_BY_ESCAPE. All three carry the same tail bound and stay silent in exactly one case, where the escape character past the boundary heads a sequence this reader RECOGNIZES, which is the escape mechanism working. How often that happens on this corpus is NOT measured and must not be guessed at: the firsthand grounding above is of the DECODE path only, and a relayed stream can still carry a device-written mnemonic. Tolerating this code does not quiet those three, and their absence is not a statement that nothing was lost."
|
|
1191
1332
|
}
|
|
1192
1333
|
]
|
|
1193
1334
|
});
|
|
@@ -1215,6 +1356,9 @@ function getDefaultAstmProfile() {
|
|
|
1215
1356
|
function isEscapeSequenceAt(text, i, escape) {
|
|
1216
1357
|
return text.charAt(i) === escape && text.charAt(i + 2) === escape;
|
|
1217
1358
|
}
|
|
1359
|
+
function headsInterpretableSequence(text, i, escape) {
|
|
1360
|
+
return isEscapeSequenceAt(text, i, escape) && isMnemonicBody(text.charAt(i + 1));
|
|
1361
|
+
}
|
|
1218
1362
|
function decodeEscapes(leaf, d, onUnknown, onUnpaired, onSwallowedDelimiter) {
|
|
1219
1363
|
const esc = d.escape;
|
|
1220
1364
|
if (!leaf.includes(esc)) return leaf;
|
|
@@ -1261,7 +1405,7 @@ function isMnemonicBody(body) {
|
|
|
1261
1405
|
function escapeBody(body, d) {
|
|
1262
1406
|
return isMnemonicBody(body) ? d[MNEMONIC_ROLES[body]] : void 0;
|
|
1263
1407
|
}
|
|
1264
|
-
function splitEscapeAware(text, delimiter, escape, onAmbiguousAlignment, onShiftedFields, onTruncatedField) {
|
|
1408
|
+
function splitEscapeAware(text, delimiter, escape, onAmbiguousAlignment, onShiftedFields, onTruncatedField, onShiftedComponents) {
|
|
1265
1409
|
if (text.length === 0) return [""];
|
|
1266
1410
|
const out = [];
|
|
1267
1411
|
let current = "";
|
|
@@ -1273,9 +1417,10 @@ function splitEscapeAware(text, delimiter, escape, onAmbiguousAlignment, onShift
|
|
|
1273
1417
|
if (contested && !isMnemonicBody(text.charAt(i + 1))) {
|
|
1274
1418
|
onAmbiguousAlignment?.(out.length);
|
|
1275
1419
|
}
|
|
1276
|
-
if (contested && !
|
|
1420
|
+
if (contested && !headsInterpretableSequence(text, i + 4, escape)) {
|
|
1277
1421
|
onShiftedFields?.(out.length);
|
|
1278
1422
|
onTruncatedField?.(out.length);
|
|
1423
|
+
onShiftedComponents?.(out.length);
|
|
1279
1424
|
}
|
|
1280
1425
|
current += text.slice(i, i + 3);
|
|
1281
1426
|
i += 3;
|
|
@@ -1295,7 +1440,7 @@ function splitEscapeAware(text, delimiter, escape, onAmbiguousAlignment, onShift
|
|
|
1295
1440
|
}
|
|
1296
1441
|
|
|
1297
1442
|
// src/records/tokenize.ts
|
|
1298
|
-
function tokenizeRecord(record, d, onUnknownEscape, onUnpairedEscape, onSwallowedDelimiter, onAmbiguousAlignment, onAlignmentShiftedFields, onAlignmentTruncatedField) {
|
|
1443
|
+
function tokenizeRecord(record, d, onUnknownEscape, onUnpairedEscape, onSwallowedDelimiter, onAmbiguousAlignment, onAlignmentShiftedFields, onAlignmentTruncatedField, onAlignmentShiftedComponents) {
|
|
1299
1444
|
const rawFields = splitEscapeAware(
|
|
1300
1445
|
record,
|
|
1301
1446
|
d.field,
|
|
@@ -1314,11 +1459,14 @@ function tokenizeRecord(record, d, onUnknownEscape, onUnpairedEscape, onSwallowe
|
|
|
1314
1459
|
// The repeat split runs INSIDE one field, so the index it hands back is a repeat index and is
|
|
1315
1460
|
// discarded: what a caller needs is which field carries the contested boundary, and that is
|
|
1316
1461
|
// the index this map is already iterating.
|
|
1317
|
-
() => onAlignmentTruncatedField?.(fieldIndex)
|
|
1462
|
+
() => onAlignmentTruncatedField?.(fieldIndex),
|
|
1463
|
+
// Likewise for the component split, which runs inside one repeat of one field: the index it
|
|
1464
|
+
// hands back is a component index and is discarded for the same reason.
|
|
1465
|
+
() => onAlignmentShiftedComponents?.(fieldIndex)
|
|
1318
1466
|
)
|
|
1319
1467
|
);
|
|
1320
1468
|
}
|
|
1321
|
-
function tokenizeHeader(record, d, onUnknownEscape, onUnpairedEscape, onSwallowedDelimiter, onAmbiguousAlignment, onAlignmentShiftedFields, onAlignmentTruncatedField) {
|
|
1469
|
+
function tokenizeHeader(record, d, onUnknownEscape, onUnpairedEscape, onSwallowedDelimiter, onAmbiguousAlignment, onAlignmentShiftedFields, onAlignmentTruncatedField, onAlignmentShiftedComponents) {
|
|
1322
1470
|
const defEnd = record.indexOf(d.field, 2);
|
|
1323
1471
|
const definition = defEnd === -1 ? record.slice(2) : record.slice(2, defEnd);
|
|
1324
1472
|
const head = [opaqueField(record.slice(0, Math.min(1, record.length))), opaqueField(definition)];
|
|
@@ -1331,14 +1479,15 @@ function tokenizeHeader(record, d, onUnknownEscape, onUnpairedEscape, onSwallowe
|
|
|
1331
1479
|
(i) => onSwallowedDelimiter?.(i + 2),
|
|
1332
1480
|
(i) => onAmbiguousAlignment?.(i + 2),
|
|
1333
1481
|
(i) => onAlignmentShiftedFields?.(i + 2),
|
|
1334
|
-
(i) => onAlignmentTruncatedField?.(i + 2)
|
|
1482
|
+
(i) => onAlignmentTruncatedField?.(i + 2),
|
|
1483
|
+
(i) => onAlignmentShiftedComponents?.(i + 2)
|
|
1335
1484
|
);
|
|
1336
1485
|
return [...head, ...data];
|
|
1337
1486
|
}
|
|
1338
1487
|
function opaqueField(raw) {
|
|
1339
1488
|
return { raw, components: [raw], repeats: [[raw]] };
|
|
1340
1489
|
}
|
|
1341
|
-
function toField(raw, d, onUnknownEscape, onUnpairedEscape, onSwallowedDelimiter, onAmbiguousAlignment, onAlignmentTruncatedField) {
|
|
1490
|
+
function toField(raw, d, onUnknownEscape, onUnpairedEscape, onSwallowedDelimiter, onAmbiguousAlignment, onAlignmentTruncatedField, onAlignmentShiftedComponents) {
|
|
1342
1491
|
const rawRepeats = splitEscapeAware(
|
|
1343
1492
|
raw,
|
|
1344
1493
|
d.repeat,
|
|
@@ -1352,9 +1501,19 @@ function toField(raw, d, onUnknownEscape, onUnpairedEscape, onSwallowedDelimiter
|
|
|
1352
1501
|
}
|
|
1353
1502
|
);
|
|
1354
1503
|
const repeats = rawRepeats.map(
|
|
1355
|
-
(rep) => splitEscapeAware(
|
|
1356
|
-
|
|
1357
|
-
|
|
1504
|
+
(rep) => splitEscapeAware(
|
|
1505
|
+
rep,
|
|
1506
|
+
d.component,
|
|
1507
|
+
d.escape,
|
|
1508
|
+
() => {
|
|
1509
|
+
onAmbiguousAlignment();
|
|
1510
|
+
},
|
|
1511
|
+
void 0,
|
|
1512
|
+
void 0,
|
|
1513
|
+
() => {
|
|
1514
|
+
onAlignmentShiftedComponents();
|
|
1515
|
+
}
|
|
1516
|
+
).map(
|
|
1358
1517
|
(comp) => decodeEscapes(comp, d, onUnknownEscape, onUnpairedEscape, onSwallowedDelimiter)
|
|
1359
1518
|
)
|
|
1360
1519
|
);
|
|
@@ -1506,6 +1665,11 @@ function buildRecord(line2, recordIndex, d, warnings) {
|
|
|
1506
1665
|
alignmentTruncatedField({ recordIndex, recordType: rawType, fieldIndex: fieldIndex + 1 })
|
|
1507
1666
|
);
|
|
1508
1667
|
};
|
|
1668
|
+
const onAlignmentShiftedComponents = (fieldIndex) => {
|
|
1669
|
+
warnings.push(
|
|
1670
|
+
alignmentShiftedComponents({ recordIndex, recordType: rawType, fieldIndex: fieldIndex + 1 })
|
|
1671
|
+
);
|
|
1672
|
+
};
|
|
1509
1673
|
const fields = rawType === "H" ? tokenizeHeader(
|
|
1510
1674
|
line2,
|
|
1511
1675
|
d,
|
|
@@ -1514,7 +1678,8 @@ function buildRecord(line2, recordIndex, d, warnings) {
|
|
|
1514
1678
|
onSwallowedDelimiter,
|
|
1515
1679
|
onAmbiguousAlignment,
|
|
1516
1680
|
onAlignmentShiftedFields,
|
|
1517
|
-
onAlignmentTruncatedField
|
|
1681
|
+
onAlignmentTruncatedField,
|
|
1682
|
+
onAlignmentShiftedComponents
|
|
1518
1683
|
) : tokenizeRecord(
|
|
1519
1684
|
line2,
|
|
1520
1685
|
d,
|
|
@@ -1523,7 +1688,8 @@ function buildRecord(line2, recordIndex, d, warnings) {
|
|
|
1523
1688
|
onSwallowedDelimiter,
|
|
1524
1689
|
onAmbiguousAlignment,
|
|
1525
1690
|
onAlignmentShiftedFields,
|
|
1526
|
-
onAlignmentTruncatedField
|
|
1691
|
+
onAlignmentTruncatedField,
|
|
1692
|
+
onAlignmentShiftedComponents
|
|
1527
1693
|
);
|
|
1528
1694
|
if (fields.length === 1 && line2.slice(1).trim().length > 0) {
|
|
1529
1695
|
warnings.push(fieldsUnseparated({ recordIndex, recordType: rawType }));
|
|
@@ -2736,7 +2902,7 @@ function concatBytes3(chunks) {
|
|
|
2736
2902
|
}
|
|
2737
2903
|
|
|
2738
2904
|
// src/index.ts
|
|
2739
|
-
var VERSION = "0.0.
|
|
2905
|
+
var VERSION = "0.0.20";
|
|
2740
2906
|
|
|
2741
2907
|
exports.ALL_ASTM_WARNING_CODES = ALL_ASTM_WARNING_CODES;
|
|
2742
2908
|
exports.AMBIGUOUS_CODES = AMBIGUOUS_CODES;
|
|
@@ -2760,6 +2926,7 @@ exports.SAFETY_CRITICAL_CODES = SAFETY_CRITICAL_CODES;
|
|
|
2760
2926
|
exports.TOLERABLE_CODES = TOLERABLE_CODES;
|
|
2761
2927
|
exports.VERSION = VERSION;
|
|
2762
2928
|
exports.WARNING_CODES = WARNING_CODES;
|
|
2929
|
+
exports.alignmentShiftedComponents = alignmentShiftedComponents;
|
|
2763
2930
|
exports.alignmentShiftedFields = alignmentShiftedFields;
|
|
2764
2931
|
exports.alignmentTruncatedField = alignmentTruncatedField;
|
|
2765
2932
|
exports.ambiguousEscapeAlignment = ambiguousEscapeAlignment;
|