@cosyte/x12 0.0.13 → 0.0.15
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 +369 -2
- package/KNOWN-LIMITATIONS.md +325 -8
- package/dist/index.cjs +131 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +277 -25
- package/dist/index.d.ts +277 -25
- package/dist/index.mjs +130 -27
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,145 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
9
9
|
|
|
10
10
|
### Added
|
|
11
11
|
|
|
12
|
+
- **🩺 `Build837EnvelopeSpec.implementationConventionReference` - the caller now states the ST-03 /
|
|
13
|
+
GS-08 implementation convention reference `build837P` / `build837I` / `build837D` declares**
|
|
14
|
+
(`X12-837-EMIT-IDENTIFIER-FIXED`). One value, **both elements**; omit it and the builders emit
|
|
15
|
+
exactly what they always have.
|
|
16
|
+
|
|
17
|
+
**What this fixes, and it is a rejected claim rather than a mis-read one.** The builders stamped
|
|
18
|
+
`005010X222A2` / `005010X223A3` / `005010X224A2` with no way to change them. Two of those are not
|
|
19
|
+
what CMS and several state Medicaid companion guides require in ST-03 and GS-08 on production
|
|
20
|
+
professional and institutional claims, which ask for `005010X222A1` and `005010X223A2`, so **a
|
|
21
|
+
partner on one of those guides rejected every 837 this library built and a caller had no remedy.**
|
|
22
|
+
The read side of the same fact was grounded in the release before this one; this is the emit side.
|
|
23
|
+
|
|
24
|
+
**🛑 The defaults are UNCHANGED, on purpose, and that is the whole shape of the fix.** Which
|
|
25
|
+
published guide identifier a trading partner accepts is a **partner fact, not a spec fact**.
|
|
26
|
+
Re-stamping the default would silently change bytes this library already puts on the wire and
|
|
27
|
+
break the partners it works with today, so the default stays and the caller states what its own
|
|
28
|
+
partner asked for. Nothing about an existing call changes.
|
|
29
|
+
|
|
30
|
+
**What it refuses of its own, all `Claim837BuildError` with code `X12_837_BUILD_INVALID_SPEC`, and
|
|
31
|
+
none of them echoes the value you passed.** These sit on top of the element-type guard every string
|
|
32
|
+
slot in every builder already has, which refuses a non-string with the same code, so read the list
|
|
33
|
+
as what this field adds and not as a closed account of everything that can refuse. No total is
|
|
34
|
+
published.
|
|
35
|
+
- **Empty.** A trailing empty element is not emitted at all, so an empty reference would remove
|
|
36
|
+
ST-03 and GS-08 rather than send them empty - a silent structural loss on two required elements.
|
|
37
|
+
- **Carrying an active delimiter or the release character.** The value is refused rather than
|
|
38
|
+
escaped. A trading partner's parser is not obliged to honour the release escape, and a published
|
|
39
|
+
guide identifier has no legitimate use for a delimiter, so the refusal is kept even now that
|
|
40
|
+
this library's own reader would carry it (see the envelope-splitter entry below).
|
|
41
|
+
- **A reference this library's own reader resolves to a DIFFERENT 837 variant** (`005010X223A2`
|
|
42
|
+
handed to `build837P`). The emitted file would declare one variant and carry another's service
|
|
43
|
+
segments, and `get837Claims` would decode none of its service lines. Same class as the existing
|
|
44
|
+
refusal for a service line whose `variant` disagrees with the builder's. The message names the
|
|
45
|
+
variant the builder emits and deliberately not the one the reference belongs to.
|
|
46
|
+
|
|
47
|
+
**A reference outside the read table is emitted as given, and that is deliberate.** Nothing makes
|
|
48
|
+
the published-errata set provably exhaustive, so refusing an unrecognised identifier would claim
|
|
49
|
+
an exhaustiveness this library does not claim when reading either. The honest cost is pinned as a
|
|
50
|
+
test: on such a file this library's own reader falls back to the `SVx` scan, exactly as it does
|
|
51
|
+
for any unrecognised ST-03. The **length** is not bounded either, and the two elements' maxima
|
|
52
|
+
differ: GS-08 is data element 480 (`AN 1/12`), ST-03 is element 1705 (`AN 1/35`).
|
|
53
|
+
|
|
54
|
+
**🩺 A guard on this element cannot make the element trustworthy, and the disclosure above is
|
|
55
|
+
therefore about the whole envelope segment rather than these two elements.** An **unescaped**
|
|
56
|
+
active delimiter in a _different_ `GS` or `ST` element still ends its own element and shifts every
|
|
57
|
+
element after it, so ST-03 and GS-08 are then read out of a neighbour's slot, and no refusal here
|
|
58
|
+
can reach it. A **release-escaped** one no longer does - see the entry below, which landed after
|
|
59
|
+
this one and retracts the part of it that said escaping could not help.
|
|
60
|
+
|
|
61
|
+
- **🩺 `X12_837_SERVICE_SEGMENT_REPEATED`, the 33rd Tier-2 warning code, plus the public factory
|
|
62
|
+
`serviceSegmentRepeated(position)`** (`X12-837-SV1-OVERWRITE`). A **second** `SV1` / `SV2` / `SV3`
|
|
63
|
+
arriving inside an **already-open** Loop 2400 now says so, at that repeated segment. Through
|
|
64
|
+
`0.0.13` it said nothing at all.
|
|
65
|
+
|
|
66
|
+
**🩺 What that cost, and it is money and a procedure code.** A service line carries **one** service
|
|
67
|
+
segment's worth of slots, and every decoder writes **all** of the slots its kind writes. Under an
|
|
68
|
+
`ST-03` of `005010X222A2`, `SV1*HC:99213*8500*UN*4***1~` followed by `SV1*HC:99999*12*UN*1***1~`
|
|
69
|
+
inside one `LX` left ONE line reading `charge` **`12`** and `procedureCode` **`99999`**, with
|
|
70
|
+
`warnings: []`. `8500` became `12`, CPT `99213` became `99999`, and nothing was raised on any
|
|
71
|
+
channel. **The worst corner is a repeat whose own charge element is ABSENT:** it writes `undefined`
|
|
72
|
+
over the amount the first one stated, and `X12_837_SERVICE_LINE_NOT_DECODED` does **not** fire
|
|
73
|
+
there, because a service segment did decode.
|
|
74
|
+
|
|
75
|
+
**🛑 THIS CLOSES ONLY THE SILENCE, AND THE RESTRAINT IS THE POINT.** The decode is **not** narrowed:
|
|
76
|
+
last-wins is unchanged, element for element, so which values a document decodes to are byte-for-byte
|
|
77
|
+
what they were at `0.0.13`. This reader cannot tell a stray service segment from a conformant one,
|
|
78
|
+
so choosing the first would be inventing; and changing which occurrence wins changes how
|
|
79
|
+
**already-published documents decode**. That is the same call made for the `SVx` variant fallback.
|
|
80
|
+
|
|
81
|
+
**It fires on a repeat of ANY kind, decoded or not.** One whose kind does not match the resolved
|
|
82
|
+
variant is read into nothing and overwrites nothing - what it carries reaches no part of the typed
|
|
83
|
+
model - and it is reported the same way, before or after the matching one.
|
|
84
|
+
|
|
85
|
+
**Anchored at the repeated service segment**, with **no `elementIndex`**: what is reported is a
|
|
86
|
+
second occurrence of the segment, not a defect in an element of it. **Once per repeat**, and the
|
|
87
|
+
count is scoped to the LINE and never latched, so a first service segment under a later `LX` is a
|
|
88
|
+
first. It can never name the same segment as `X12_837_SERVICE_SEGMENT_WITHOUT_LX`, which requires
|
|
89
|
+
that no Loop 2400 be open.
|
|
90
|
+
|
|
91
|
+
**It is additive and nothing moved onto it**, pinned by committed tests asserting the whole warning
|
|
92
|
+
channel with the new code filtered out. **No consumer predicate written against any existing code
|
|
93
|
+
changes meaning.** Read that as invariance, not as a list of what else you will see.
|
|
94
|
+
|
|
95
|
+
**🛑 The package's own documentation was a consumer, and it was blind.** The cookbook's "gate before
|
|
96
|
+
you post a line amount" recipe named four codes and **none** of them fires on the overwrite
|
|
97
|
+
document, so a consumer following it posted `12` for a line the sender also sent as `8500`. The
|
|
98
|
+
cookbook, the troubleshooting table and `KNOWN-LIMITATIONS.md` now name this code beside the other
|
|
99
|
+
four, and a committed test pins that the four-code gate misses what the five-code gate catches.
|
|
100
|
+
**The recipe was not the only page:** `spec-notes-money` named `X12_837_SERVICE_LINE_NOT_DECODED`
|
|
101
|
+
as "the known instance" of an 837 charge reading `undefined` from a slot no reader read, and the
|
|
102
|
+
repeat corner is a second instance by a different route on which that code does not fire. It now
|
|
103
|
+
says so.
|
|
104
|
+
|
|
105
|
+
- **🩺 `X12_837_AMBIGUOUS_VARIANT`, the 32nd Tier-2 warning code, plus the public factory
|
|
106
|
+
`ambiguous837Variant(position)`** (`X12-837-RESIDUALS`). An 837 whose variant was decided by the
|
|
107
|
+
`SVx` fallback, in a transaction body that carries service segments for **more than one variant**,
|
|
108
|
+
now says so at the `ST`. Through `0.0.13` it said nothing at all about that resolution.
|
|
109
|
+
|
|
110
|
+
**What that cost.** Variant resolution runs before the walk as
|
|
111
|
+
`explicitType ?? variantFromIcr ?? variantFromSegment`. Absent a caller `type` option, and where
|
|
112
|
+
`ST-03` names no implementation convention this reader recognises, the reader falls back to the
|
|
113
|
+
**first** `SV1` / `SV2` / `SV3` in the body, orphans included. One stray `SV2` ahead of a conformant
|
|
114
|
+
Professional claim therefore re-types the whole submission Institutional: `submission.variant` reads
|
|
115
|
+
`"I"`, and a consumer routing on that field sends a Professional claim down an Institutional path.
|
|
116
|
+
The line-level consequences were reported; **the submission-level typing that produced them was on
|
|
117
|
+
no channel**, so `submission.variant` carried a confident value with nothing to contradict it.
|
|
118
|
+
|
|
119
|
+
**🛑 THIS CLOSES ONLY THE SILENCE, AND THE RESTRAINT IS THE POINT.** The fallback is **not**
|
|
120
|
+
narrowed and first-wins is unchanged, so on every document that reaches the fallback the variant
|
|
121
|
+
resolved and the lines decoded are byte-for-byte what they were at `0.0.13`, and this code is added
|
|
122
|
+
beside whatever the walk already raised. (`X12-VARIANT-ICR-UNGROUNDED`, in this same release,
|
|
123
|
+
changed WHICH documents reach the fallback; read its entry for what decodes differently.) Excluding orphans from
|
|
124
|
+
the fallback would change how already-published documents decode and is its own slice.
|
|
125
|
+
|
|
126
|
+
**🩺 Which service segment is the stray one is NOT decided.** This reader cannot tell a stray
|
|
127
|
+
service segment from a conformant one, and the fallback takes the first whether or not a Loop 2400
|
|
128
|
+
was open at it. Reporting the conflict is honest; picking a winner would be inventing. Re-read with
|
|
129
|
+
`get837Claims(delimiters, tx, { type })` to decode against a variant you trust.
|
|
130
|
+
|
|
131
|
+
**It reports the RESOLUTION, never the document.** A caller `type`, or an `ST-03` naming a known
|
|
132
|
+
convention, means no guess was made and this code is **not** raised however mixed the body is.
|
|
133
|
+
|
|
134
|
+
**Anchored at the `ST`** (`tx.segments[0]`, which carries `ST-03`), with **no `elementIndex`**: the
|
|
135
|
+
conflict is a property of the body rather than of an element, and one route into it is an `ST-03`
|
|
136
|
+
that is absent altogether. Raised **once per transaction**, because there is one resolution per
|
|
137
|
+
transaction, and it can never travel with `X12_837_UNKNOWN_VARIANT`, which is the other outcome of
|
|
138
|
+
that same resolution.
|
|
139
|
+
|
|
140
|
+
**🩺 It is additive, and nothing moved onto it.** `X12_837_SERVICE_LINE_NOT_DECODED`,
|
|
141
|
+
`X12_837_SERVICE_SEGMENT_WITHOUT_LX` and `X12_837_SERVICE_LINE_DROPPED` fire on exactly the
|
|
142
|
+
documents they fired on before **this code was added**, in the same positions, pinned by committed
|
|
143
|
+
tests that assert the whole warning channel with the new code filtered out. **No consumer predicate
|
|
144
|
+
changes meaning because of this code.** (`X12-VARIANT-ICR-UNGROUNDED`, in this same release, DID
|
|
145
|
+
change which documents reach the `SVx` fallback; read its entry above.) Read that as **invariance and not as a list of what else you will
|
|
146
|
+
see** on a contested document: it does not promise that any particular loss on one is reported at
|
|
147
|
+
all, and one that is not was not reported before this code existed either. A stray `LX` that opened
|
|
148
|
+
no line, for one, already suppressed `X12_837_SERVICE_SEGMENT_WITHOUT_LX` for the service segments
|
|
149
|
+
inside it, and still does.
|
|
150
|
+
|
|
12
151
|
- **🩺 `X12_STATED_AMOUNT_DISCARDED`, the 31st Tier-2 warning code, plus the public factory
|
|
13
152
|
`statedAmountDiscarded(position)`** (`X12-STATED-AMOUNT-DISCARDED`). The entry below reports a row
|
|
14
153
|
whose amount this library could not read. This one is the opposite case: **the reader discarded
|
|
@@ -270,6 +409,74 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
270
409
|
|
|
271
410
|
### Changed
|
|
272
411
|
|
|
412
|
+
- **🩺 `get837Claims` now recognises every published `ST-03` implementation-convention reference for
|
|
413
|
+
the three 837 guides, and some already-published files therefore decode differently**
|
|
414
|
+
(`X12-VARIANT-ICR-UNGROUNDED`). This is a grounding unit: the table `VARIANT_BY_ICR` had three keys
|
|
415
|
+
and they were grounded against nothing.
|
|
416
|
+
|
|
417
|
+
**🩺 What that cost, measured at `668afea`, which is `main` at published `0.0.13`.** The table held exactly
|
|
418
|
+
`005010X222A2`, `005010X223A3` and `005010X224A2`. **It contained none of the identifiers HIPAA
|
|
419
|
+
adopts at 45 CFR 162.1102** (`005010X222`; `005010X223` with its `005010X223A1` Type 1 errata;
|
|
420
|
+
`005010X224` with `005010X224A1`), **and it was missing `005010X222A1` and `005010X223A2`**, which
|
|
421
|
+
CMS and state Medicaid companion guides require in ST-03 and GS-08 on production professional and
|
|
422
|
+
institutional claims. So a conformant, HIPAA-mandated 837P declaring `005010X222A1` resolved to no
|
|
423
|
+
variant at all, fell through to the `SVx` scan, and one stray `SV2` anywhere in the body re-typed
|
|
424
|
+
the whole submission Institutional. **The `SVx` fallback was the NORMAL path on production
|
|
425
|
+
professional and institutional traffic rather than the exception**, and `X12_837_UNKNOWN_VARIANT`
|
|
426
|
+
on such a file was a fabricated non-conformance claim about a document that was not
|
|
427
|
+
non-conformant. The sources for every key are named beside the table and in
|
|
428
|
+
`documentation/agent-notes/x12-variant-icr-ungrounded.md`; the three later published errata guides
|
|
429
|
+
are the weakest leg and say so in place.
|
|
430
|
+
|
|
431
|
+
**🛑 This IS a behaviour change on already-published decoding, and it is disclosed rather than
|
|
432
|
+
buried.** On a file whose `ST-03` is now recognised: `submission.variant` can differ from what
|
|
433
|
+
`0.0.13` read, wherever the first `SVx` in the body disagreed with the declaration; and
|
|
434
|
+
**`X12_837_AMBIGUOUS_VARIANT` and `X12_837_UNKNOWN_VARIANT` no longer fire on it at all**, because
|
|
435
|
+
no guess was made. **A predicate written against either code goes quiet on such a file.**
|
|
436
|
+
|
|
437
|
+
**🩺 And a service line whose `SVx` kind disagrees with the declaration is no longer DECODED, so a
|
|
438
|
+
code STARTS firing on a document that may have carried `warnings: []`.** Under an `ST-03` of
|
|
439
|
+
`005010X222A1` with a body whose only service segment is an `SV2`, `0.0.13` read `variant` `"I"`,
|
|
440
|
+
`charge` `7300`, `units` `2` and `warnings: []`; this release reads `variant` `"P"`, `charge` and
|
|
441
|
+
`units` `undefined` with the rest of the service segment undecoded, and
|
|
442
|
+
`X12_837_SERVICE_LINE_NOT_DECODED` at that line's `LX`. **Read only the decimal slots as
|
|
443
|
+
`undefined`:** an undecoded line SEEDS its identity fields, so `procedureCode` is `""` on a P or D
|
|
444
|
+
line and `revenueCode` is `""` on an I one. A predicate of `procedureCode === undefined` does NOT
|
|
445
|
+
detect this.
|
|
446
|
+
A mis-stamped envelope is an ordinary vendor variant and this reader can no more tell one from a
|
|
447
|
+
conformant document than it can tell a stray `SVx` from a conformant one, so the loss is **warned
|
|
448
|
+
rather than silent**, and the cookbook's post-a-line-amount gate already names that code first.
|
|
449
|
+
|
|
450
|
+
**🛑 Read all of that as ONE property and never as a closed list of consequences:** where `ST-03`
|
|
451
|
+
is now recognised, **the document's own declaration decides the variant instead of its first
|
|
452
|
+
service segment**, and everything downstream follows from that single substitution. A first draft
|
|
453
|
+
of this entry published a census of three and a refuter measured it false by finding a fourth.
|
|
454
|
+
|
|
455
|
+
That is
|
|
456
|
+
the hazard a widening onto a new code carries, taken here in the opposite direction from the two
|
|
457
|
+
slices before it: they refused to narrow a fallback because the reader had no evidence beyond the
|
|
458
|
+
segments, and here the reader had the evidence in ST-03 and was ignoring it. Re-check any routing
|
|
459
|
+
driven off `submission.variant` for 837s you read on `0.0.13` or earlier.
|
|
460
|
+
|
|
461
|
+
**🛑 The `SVx` fallback is NOT narrowed.** First-wins still takes the first `SV1` / `SV2` / `SV3` in
|
|
462
|
+
the body, orphans included, on every document that still reaches it, and precedence is unchanged:
|
|
463
|
+
a caller `type` still wins ahead of ST-03, and ST-03 ahead of the segments. What changed is which
|
|
464
|
+
documents reach the fallback.
|
|
465
|
+
|
|
466
|
+
**It is a LIST of cited identifiers, never a pattern.** A reference outside the set, in a different
|
|
467
|
+
case, or carrying leading whitespace still falls through exactly as before. The set is **not
|
|
468
|
+
claimed exhaustive and no count of it is published**: both variant-resolution messages named the
|
|
469
|
+
three old keys literally, so both were wrong the moment the table was corrected, and neither
|
|
470
|
+
enumerates the set any more. A committed tripwire reds if any registry message quotes a TR3
|
|
471
|
+
identifier again.
|
|
472
|
+
|
|
473
|
+
**🩺 OPEN, and deliberately not fixed here: the EMIT side still stamps the old three.** `build837P`
|
|
474
|
+
/ `build837I` / `build837D` write `005010X222A2` / `005010X223A3` / `005010X224A2` and a caller
|
|
475
|
+
cannot override them, so **a partner that requires `005010X222A1` or `005010X223A2` will reject an
|
|
476
|
+
837 this builder emits.** Which published guide identifier a partner accepts is a partner fact
|
|
477
|
+
rather than a spec fact, and changing bytes this library already emitted would break the partners
|
|
478
|
+
it works with today. `KNOWN-LIMITATIONS.md` carries it as an open residual.
|
|
479
|
+
|
|
273
480
|
- **🩺 BREAKING (read model): every monetary, percent and quantity slot the readers used to fill with
|
|
274
481
|
a fabricated `X12Decimal.ZERO` is now `X12Decimal | undefined`** (`X12-837-SV-UNDEFINED-DECIMAL`).
|
|
275
482
|
Through `0.0.12` an **absent** `SV1-02` - a monetary field on an 837 claim service line - read back
|
|
@@ -743,6 +950,166 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
743
950
|
|
|
744
951
|
### Fixed
|
|
745
952
|
|
|
953
|
+
- **🩺 `buildTA1` now release-escapes its five caller-supplied elements, so an Accept acknowledgment
|
|
954
|
+
this library emits no longer reads back as a Reject** (`X12-TA1-EMIT-NOT-RELEASE-AWARE`).
|
|
955
|
+
Reproduced on the base tree at `e8f34b9`. `buildTA1` joined the five values with the element
|
|
956
|
+
separator and escaped none of them, so a value carrying an active delimiter took a slot of its own
|
|
957
|
+
and shifted every element after it down one. TA1-04 is the disposition, TA1-05 the note, and
|
|
958
|
+
`parseTA1` narrows an out-of-enum TA1-04 to `R`. Measured with `parseX12` + `parseTA1` over
|
|
959
|
+
`ISA … <what buildTA1 returned> … IEA`, `ackCode` `"A"` and `noteCode` `"000"` throughout:
|
|
960
|
+
|
|
961
|
+
| `interchangeControlNumber` | emitted at `0.0.14` | read `ackCode` | read TA1-01 | warnings |
|
|
962
|
+
| -------------------------- | --------------------------------- | -------------- | -------------------- | ------------------------ |
|
|
963
|
+
| `"000000001"` | `TA1*000000001*260601*1200*A*000` | `"A"` | `"000000001"` | none |
|
|
964
|
+
| `"00000001?"` | `TA1*00000001?*260601*1200*A*000` | `"R"` | `"00000001?*260601"` | none |
|
|
965
|
+
| `"0000*0001"` | `TA1*0000*0001*260601*1200*A*000` | `"R"` | `"0000"` | none |
|
|
966
|
+
| `"0000~0001"` | `TA1*0000~0001*260601*1200*A*000` | `"R"` | `"0000"` | `X12_UNEXPECTED_SEGMENT` |
|
|
967
|
+
|
|
968
|
+
**An Accept acknowledgment this library emitted read back as a Reject**, on the element that
|
|
969
|
+
reassociates it, with nothing raised on any channel. The `*` and `~` rows did that on every
|
|
970
|
+
released version; the `?` row is the one the entry below opened. **🩺 The inverse exists and is the
|
|
971
|
+
less safe direction:** the read narrows an out-of-enum TA1-04 to `R`, so a well-typed shift always
|
|
972
|
+
lands on Reject, but `noteCode` is checked by the type system and by nothing at run time, so a
|
|
973
|
+
`noteCode` of literally `"A"` shifted onto TA1-04 and made a **Reject read back as an Accept**, and
|
|
974
|
+
a sender who reads an Accept does not resubmit. All four now read back the disposition emitted.
|
|
975
|
+
|
|
976
|
+
**The grounding is inside this package rather than in a spec clause**, the same tiebreak the entry
|
|
977
|
+
below records: `buildTA1` emitted bytes that this package's own reader decoded into a different
|
|
978
|
+
disposition than the caller asked for, while every other builder already released the same class of
|
|
979
|
+
element through the same helper.
|
|
980
|
+
|
|
981
|
+
**🛑 It changes bytes this library already put on the wire, and that is the cost.** A value
|
|
982
|
+
containing none of the four delimiters and no `?` is emitted byte-for-byte as before, which is
|
|
983
|
+
every conformant TA1: TA1-01 echoes ISA-13, TA1-02 / TA1-03 echo ISA-09 / ISA-10, and TA1-04 /
|
|
984
|
+
TA1-05 are code list values.
|
|
985
|
+
|
|
986
|
+
**🛑 No warning code is added and no case moves onto a new code, but the consumer predicate MOVES
|
|
987
|
+
IN BOTH DIRECTIONS.** `parseTA1` of a `buildTA1` output now reports the disposition and note the
|
|
988
|
+
caller passed; before, it reported whatever element the shift left in TA1-04, which could be the
|
|
989
|
+
caller's, a coincidental in-enum value, or an out-of-enum one narrowed to `"R"`. So
|
|
990
|
+
`ackCode === "R"` **stops** firing where an Accept had been shifted onto it, and **starts** firing
|
|
991
|
+
where a Reject had been shifted off it: `interchangeTime: "12*A"` with `ackCode: "R"` read `"A"`
|
|
992
|
+
before and reads `"R"` now, with every field a valid member of its union. `ackCode === "A"` moves
|
|
993
|
+
the same two ways. What is one-directional is the safety, which is a different statement: nothing
|
|
994
|
+
now reports a disposition the caller did not ask for.
|
|
995
|
+
|
|
996
|
+
**What releasing the rest of the set costs, and where it does not cost.** Only `*`, `~` and a `?`
|
|
997
|
+
immediately before the separator ever shifted the segment's own element framing. **`^` and `:`
|
|
998
|
+
moved the dot-path reader instead, and releasing them is a gain there:**
|
|
999
|
+
`getSegmentValue(ta1, "01")` answered `"0000"` at `0.0.14` for a control number of `"0000^0001"`,
|
|
1000
|
+
silently truncating the reassociation key to the first repetition, and answers `"0000^0001"` now;
|
|
1001
|
+
the composite read `"01-1"` answered `"0000"` for `"0000:0001"` and answers the whole value now.
|
|
1002
|
+
**The measured pure cost is a mid-string `?`, and only on the surfaces documented as raw**: `raw`,
|
|
1003
|
+
`elements` and `parseTA1`'s fields read `"0000??0001"` where they read `"0000?0001"`, while every
|
|
1004
|
+
dot-path read unescapes and answered `"0000?0001"` on both. No total is published: that is what was
|
|
1005
|
+
measured, not a closed account. `getSegmentValue` takes an `X12Segment` and `Ta1Segment` carries no
|
|
1006
|
+
`id`, so add one to read a TA1 through it. If you were escaping the value yourself, as `KNOWN-LIMITATIONS.md`
|
|
1007
|
+
advised while this was open, drop that - you are now escaping twice on both kinds of surface
|
|
1008
|
+
(`"00000001??"` in, `TA1*00000001????*…` out, `getSegmentValue` answering `"00000001??"`).
|
|
1009
|
+
|
|
1010
|
+
**The read half did not move.** `parseTA1` still reads elements RAW, pre-`?`-unescape, exactly as
|
|
1011
|
+
`X12Segment.elements` has always documented, so a control number of `"00000001?"` now reads back as
|
|
1012
|
+
`"00000001??"` rather than as `"00000001?*260601"`. Apply `unescapeRelease` if you need the value
|
|
1013
|
+
rather than the bytes; unescaping on the read side would move every TA1 a consumer already reads
|
|
1014
|
+
and is not done here.
|
|
1015
|
+
|
|
1016
|
+
**`BuildTA1Options` gained `repetitionSeparator`, `componentSeparator` and `segmentTerminator`**
|
|
1017
|
+
beside the existing `elementSeparator`, the same four `build999` already takes. They exist for
|
|
1018
|
+
escaping and nothing else - `buildTA1` still emits no segment terminator, no repetition and no
|
|
1019
|
+
composite. Escaping against a guessed delimiter set is a value corruption rather than a safe
|
|
1020
|
+
default: `unescapeRelease` preserves `?X` verbatim for any `X` outside the reader's declared set,
|
|
1021
|
+
so a value released against the wrong delimiter comes back carrying a stray `?`. The defaults are
|
|
1022
|
+
unchanged and are the cosyte archetype, which this function cannot verify, so state the separators
|
|
1023
|
+
if you embed a TA1 in an envelope that declares different ones.
|
|
1024
|
+
|
|
1025
|
+
**A non-string element now refuses** with `AckBuildError` and the existing `X12_ACK_INVALID_SPEC`
|
|
1026
|
+
code, and that is a prerequisite of the escape rather than a separate guard: releasing a value
|
|
1027
|
+
routes it through the escape helper, and the bare `escapeRelease` underneath it returns its empty
|
|
1028
|
+
accumulator for a `number`, so escaping without the type check would have replaced a shifted TA1-01
|
|
1029
|
+
with a vanished one. A numeric `interchangeControlNumber` emitted `TA1*12345*260601*1200*A*000`
|
|
1030
|
+
before, with the number surviving onto `elements` inside a value typed `readonly string[]`, and an
|
|
1031
|
+
absent one emitted `TA1**250101*1200*A*000`. The accept-with-note refusal still runs first and
|
|
1032
|
+
still reports `X12_TA1_ACCEPT_WITH_NOTE`, so no existing refusal moves code.
|
|
1033
|
+
|
|
1034
|
+
**What this does not close:** `buildTA1` still uses no segment joiner, so its refusal names the
|
|
1035
|
+
builder and never `TA1-01`; `buildInterchange` still does not escape GS-04, GS-05 or GS-07; and an
|
|
1036
|
+
unescaped active delimiter is still not safe anywhere. Measured in `KNOWN-LIMITATIONS.md`.
|
|
1037
|
+
|
|
1038
|
+
- **🩺 A `?` immediately before the element separator inside an envelope segment now frames as ONE
|
|
1039
|
+
element, which fixes a released delimiter and is a SYMMETRIC change**
|
|
1040
|
+
(`X12-ENVELOPE-SPLITTER-NOT-RELEASE-AWARE`). Reproduced on the base
|
|
1041
|
+
tree at `1b71733`. `src/parser/envelope.ts`'s `splitElements` - the splitter for `GS`, `GE`, `ST`,
|
|
1042
|
+
`SE`, `IEA` and `TA1` - was a plain `String.prototype.split` on the element separator, so a `?*`
|
|
1043
|
+
ended the element anyway and every element after it moved down a slot:
|
|
1044
|
+
|
|
1045
|
+
```text
|
|
1046
|
+
GS*HC*S*R*20260601*1200*1*X*005010?*X222A1~ ten elements, GS-08 read "005010?"
|
|
1047
|
+
applicationSenderCode "SEND*ER" GS-08 read "X", the GS-07 agency code
|
|
1048
|
+
groupControlNumber "1*2" GS-08 read "X"; warnings: []
|
|
1049
|
+
transactionSetControlNumber "00*01" ST-03 read "01"; warnings: []
|
|
1050
|
+
CLM*PT?*ACCT*150.00~ three elements - the BODY control, always correct
|
|
1051
|
+
```
|
|
1052
|
+
|
|
1053
|
+
**What made it a defect rather than a tolerance is that this package's own two halves disagreed
|
|
1054
|
+
inside a single call:** `buildInterchange` maps its release escaper over GS-02 / GS-03 / GS-06 /
|
|
1055
|
+
GS-08 and ST-01 / ST-02 / ST-03, then returns `parseX12` of the bytes it just wrote, so
|
|
1056
|
+
`applicationSenderCode: "SEND*ER"` emitted a correct `GS*HC*SEND?*ER*...` and handed back a model
|
|
1057
|
+
whose GS-08 answered `"X"`, with `warnings: []`.
|
|
1058
|
+
|
|
1059
|
+
**🛑 READ THE CHANGE AS SYMMETRIC. IT IS NOT ONLY A CORRECTION.** A `?` before the separator has
|
|
1060
|
+
two readings and 005010 does not transmit which the sender meant. Where the sender **escaped** a
|
|
1061
|
+
delimiter, `0.0.14` framed it wrongly and this release frames it correctly: a correction, the rows
|
|
1062
|
+
above. Where the sender sent a **literal `?`** as the element's last byte, `0.0.14` framed it
|
|
1063
|
+
correctly and this release merges the element with its successor, so **the segment loses its LAST
|
|
1064
|
+
element**: a regression. `GS*HC*SUB1*RCV?*20260601*1200*000000123*X*005010X222A1~` read nine
|
|
1065
|
+
entries and reads **eight** here, GS-06 answering `"X"` and GS-08 gone. Every other release
|
|
1066
|
+
sequence in an envelope element (`??`, `?:`, `?^`, `?~`, `?A`) framed identically before and after
|
|
1067
|
+
and still does, pinned as invariance controls.
|
|
1068
|
+
|
|
1069
|
+
**No warning code is added, and `X12_CONTROL_NUMBER_MISMATCH` moves in BOTH DIRECTIONS.** Where the
|
|
1070
|
+
old shift displaced a control number it stops firing, so **a consumer that rejects on that code
|
|
1071
|
+
will now accept such a document**; where a literal `?` newly displaces one it starts firing, so
|
|
1072
|
+
that same consumer **will now reject a document `0.0.14` accepted**. Both are pinned, alongside a
|
|
1073
|
+
genuine mismatch that raises it on either tree.
|
|
1074
|
+
|
|
1075
|
+
**🩺 The regression direction reaches an 837's variant and its money, by one route.** `ST-03`
|
|
1076
|
+
decides the 837 variant, so an `ST-02` ending in a literal `?` destroys `ST-03` and the document
|
|
1077
|
+
falls back to the `SVx` scan. An 837 declaring `005010X222A1` whose only service segment is an
|
|
1078
|
+
`SV2` read `variant` `"P"`, `charge` `undefined` and `X12_837_SERVICE_LINE_NOT_DECODED` at
|
|
1079
|
+
`0.0.14`, and reads `variant` `"I"` with `charge` `150.00` and that warning silent here. **A warned
|
|
1080
|
+
non-decode becomes a decoded amount.** Re-check any routing driven off `submission.variant` if your
|
|
1081
|
+
partners send envelope elements that can end in a literal `?`.
|
|
1082
|
+
|
|
1083
|
+
**Why it was taken anyway, and it is CONSISTENCY rather than a spec clause.** The two readings are
|
|
1084
|
+
mutually exclusive and nothing in 005010 picks between them. `decodeSegment` has read BODY elements
|
|
1085
|
+
the escape-wins way on every released version (`REF*EA*RCV?*NEXT` has always been two elements), so
|
|
1086
|
+
the envelope now obeys the one rule the rest of the package already obeyed.
|
|
1087
|
+
|
|
1088
|
+
**🩺 The exposure is NOT inbound bytes only.** Envelope slots routed through the builders' release
|
|
1089
|
+
escaper are safe. Not every emit slot is routed through it, and no total is published: what follows
|
|
1090
|
+
is the routes measured to reach the regression direction, not a closed account of what bypasses the
|
|
1091
|
+
escaper. **`buildInterchange` does not escape GS-04, GS-05 or GS-07**: a `groupDate` of
|
|
1092
|
+
`"2026060?"` read nine GS elements at `0.0.14` and reads eight here, GS-08 gone. If you emit one
|
|
1093
|
+
of those, escape or reject a `?` yourself.
|
|
1094
|
+
|
|
1095
|
+
**Values are still RAW, pre-`?`-unescape**, exactly as `X12Segment.elements` has always documented:
|
|
1096
|
+
`gs.elements[2]` reads `"SEND?*ER"`, not `"SEND*ER"`. `elements.join(separator)` therefore still
|
|
1097
|
+
reproduces the segment byte for byte, which is what `serializeX12` relies on when it substitutes a
|
|
1098
|
+
recomputed `SE-01` / `GE-01` / `IEA-01` into a control segment.
|
|
1099
|
+
|
|
1100
|
+
**🩺 The ISA is deliberately exempt.** ASC X12 .5 makes it fixed-width, which is what lets the
|
|
1101
|
+
delimiter set be recovered from it before anything is parsed, so a `?` in an ISA element is content
|
|
1102
|
+
and never an escape; release-splitting it would collapse a well-formed ISA below its 17 entries.
|
|
1103
|
+
A degenerate delimiter set whose element separator IS `?` also falls back to the literal split,
|
|
1104
|
+
the same guard the segment-terminator scanner already carried. Both are pinned, each with a red
|
|
1105
|
+
negative control.
|
|
1106
|
+
|
|
1107
|
+
**🛑 An UNESCAPED delimiter is still not safe and nothing here claims otherwise** - a bare active
|
|
1108
|
+
delimiter ends its element, because that is what a delimiter is. `build837`'s
|
|
1109
|
+
`implementationConventionReference` still refuses a value carrying one, deliberately not relaxed:
|
|
1110
|
+
a partner's parser is not obliged to be release-aware either, and widening an emit surface is its
|
|
1111
|
+
own decision rather than a side effect of a reader fix.
|
|
1112
|
+
|
|
746
1113
|
- **🩺 An `AMT` / `ADX` row whose amount decodes nothing is no longer dropped in silence**
|
|
747
1114
|
(`X12-AMT-ADX-ABSENT-AMOUNT`). Reproduced on the base tree at `9db104b` across all four readers,
|
|
748
1115
|
and the report it now raises is the `X12_AMOUNT_ROW_DROPPED` entry under **Added** above. The
|
|
@@ -871,8 +1238,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
871
1238
|
are both the line charge, so reading a service segment into a line the walker never opened
|
|
872
1239
|
mis-reads money. Refusing to read is the safe half; doing it silently was the defect. **This says
|
|
873
1240
|
nothing about how the variant resolved**, and `KNOWN-LIMITATIONS.md` now discloses why: a caller's
|
|
874
|
-
`type` option wins first, and absent one, where `ST-03` names
|
|
875
|
-
|
|
1241
|
+
`type` option wins first, and absent one, where `ST-03` names no implementation convention this
|
|
1242
|
+
reader recognises, the reader falls back to the first `SVx` in the transaction body,
|
|
876
1243
|
orphans included, so a stray `SV2` re-types the whole submission. That is pre-existing behaviour, measured identical at `0.0.10`, and is deliberately
|
|
877
1244
|
not narrowed here.
|
|
878
1245
|
|