@cosyte/x12 0.0.11 → 0.0.13
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 +275 -0
- package/KNOWN-LIMITATIONS.md +183 -27
- package/README.md +2 -2
- package/dist/index.cjs +179 -61
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +326 -43
- package/dist/index.d.ts +326 -43
- package/dist/index.mjs +176 -62
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,137 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
9
9
|
|
|
10
10
|
### Added
|
|
11
11
|
|
|
12
|
+
- **🩺 `X12_STATED_AMOUNT_DISCARDED`, the 31st Tier-2 warning code, plus the public factory
|
|
13
|
+
`statedAmountDiscarded(position)`** (`X12-STATED-AMOUNT-DISCARDED`). The entry below reports a row
|
|
14
|
+
whose amount this library could not read. This one is the opposite case: **the reader discarded
|
|
15
|
+
the row for a reason that is not about the amount at all**, so the amount was never the problem
|
|
16
|
+
and, on one of the two routes, was never even looked at. The bytes stay verbatim on
|
|
17
|
+
`tx.segments[…].raw`; decode them yourself before posting. Through `0.0.12` both routes were
|
|
18
|
+
silent on every channel.
|
|
19
|
+
|
|
20
|
+
**The two routes, enumerated.** An **820 `RMR`** under an open remittance loop whose `RMR-01` and
|
|
21
|
+
`RMR-02` are **both empty** while `RMR-04` or `RMR-05` is populated: `decodeRmr` refuses the open
|
|
22
|
+
item on identity **before** either amount element is read, so `RMR****150.00*150.00~` gave
|
|
23
|
+
`openItems: []` and `warnings: []`, taking a stated payment, a stated amount due and `RMR-03`'s
|
|
24
|
+
payment action code together. And an **837 `AMT`** arriving while a Loop 2430 line adjudication
|
|
25
|
+
(`SVD`) is open, whose `AMT-02` decoded: the v1 adjudication model carries no amount row, and
|
|
26
|
+
attaching one to this submission's own service line would put another payer's figure on it, so
|
|
27
|
+
`AMT*EAF*75.00~` (Remaining Patient Liability) was skipped in silence.
|
|
28
|
+
|
|
29
|
+
**🩺 That second route is where the channel read BACKWARDS, and squaring it is the point.** Under
|
|
30
|
+
an open `SVD`, `AMT*EAF~` raised `X12_AMOUNT_ROW_DROPPED` and `AMT*EAF*75.00~` raised nothing: the
|
|
31
|
+
report was present exactly where **less** was lost. Both report now, and which code arrives is
|
|
32
|
+
what says which loss it was.
|
|
33
|
+
|
|
34
|
+
**It carries no `position.elementIndex`.** On the `RMR` route the loss spans `RMR-04` and
|
|
35
|
+
`RMR-05` and no single element names it; on the `AMT` route the element is fixed by the segment.
|
|
36
|
+
|
|
37
|
+
**🩺 It is additive, and nothing moved onto it.** `X12_AMOUNT_ROW_DROPPED` and
|
|
38
|
+
`X12_UNPARSEABLE_DECIMAL` fire on exactly the documents they fired on before, pinned by committed
|
|
39
|
+
tests. **The two amount-row codes are disjoint and can never name the same segment**, because this
|
|
40
|
+
one requires an amount element the sender populated and the other requires one that decoded no
|
|
41
|
+
value, so the code you get is the discriminant. **Gate on both.** `KNOWN-LIMITATIONS.md`, the
|
|
42
|
+
money spec-note and the troubleshooting table were corrected with the code.
|
|
43
|
+
|
|
44
|
+
**Bounds, stated as properties of the READ.** What is reported is a segment that populated its
|
|
45
|
+
amount element and arrived **while the loop that would carry its row was open**. One reaching a
|
|
46
|
+
reader with no such loop open is a different loss and is **still silent**: the 834's `AMT` with no
|
|
47
|
+
`HD` open, the 820's `ADX` with no remittance open, and the 835's and the 837's `AMT` before any
|
|
48
|
+
claim or service line. A bare `RMR~` states nothing and is silent, as is an identity-less `RMR`
|
|
49
|
+
carrying only a payment action code. And this code says **nothing** about whether the amount would
|
|
50
|
+
have decoded: the `RMR` route refuses the row before attempting the decode, so it is raised on
|
|
51
|
+
`RMR****1,234.56~` exactly as on `RMR****150.00*150.00~`, with **no `X12_UNPARSEABLE_DECIMAL`
|
|
52
|
+
beside it in either case**, on this release and on every earlier one. **Do not read an
|
|
53
|
+
unaccompanied instance as evidence the bytes are postable.** Only the `AMT` route guarantees a
|
|
54
|
+
decodable amount, because there `AMT-02` decoded before the row was skipped.
|
|
55
|
+
|
|
56
|
+
- **🩺 `X12_AMOUNT_ROW_DROPPED`, the 30th Tier-2 warning code, plus the public factory
|
|
57
|
+
`amountRowDropped(position)`** (`X12-AMT-ADX-ABSENT-AMOUNT`). An `AMT` or `ADX` is not a slot on a
|
|
58
|
+
bigger record: each one **is** a record, carrying an amount plus the thing the amount is about. So
|
|
59
|
+
when the amount element (`AMT-02`, `ADX-01`) decodes no value there is no row to build, and
|
|
60
|
+
`AMT-01`'s qualifier or `ADX-02`'s adjustment reason code is dropped with it. Through `0.0.12`
|
|
61
|
+
that happened with **no diagnostic on any channel**: `AMT*B6~` gave `claim.amounts: []` and
|
|
62
|
+
`warnings: []`, which reads exactly like a document that never carried the segment. It is now
|
|
63
|
+
reported, at the `AMT` / `ADX` itself.
|
|
64
|
+
|
|
65
|
+
Raised by four surfaces: the 835's `AMT`, the 837's `AMT`, the 834's coverage `AMT` and the 820's
|
|
66
|
+
`ADX`. The 834's lands on that **member's** own `warnings`, the same per-member scoping the
|
|
67
|
+
decimal sink beside it already used, because a roster-level report would say a premium was lost
|
|
68
|
+
without saying whose. **On the 835 and the 837 the `AMT` attaches to the open SERVICE LINE first
|
|
69
|
+
and to the claim only when there is none, so the row lost may be a line-level one** - an unchanged
|
|
70
|
+
`claim.amounts` is not evidence the warning is stale.
|
|
71
|
+
|
|
72
|
+
**It carries no `position.elementIndex`**, deliberately. One of its two routes is an absent
|
|
73
|
+
element, and an absent element has no index to name; the segment fixes which element was being
|
|
74
|
+
read anyway.
|
|
75
|
+
|
|
76
|
+
**🩺 It is additive, and no case moved onto it.** An amount that is present and does not decode
|
|
77
|
+
still raises `X12_UNPARSEABLE_DECIMAL` at its own `elementIndex`, now **alongside** this code
|
|
78
|
+
rather than instead of it, so a gate you already wrote against that code fires on exactly the
|
|
79
|
+
documents it fired on before. Whether one accompanies this code at the same `position.segmentIndex`
|
|
80
|
+
is what separates the absent route from the unparseable one, since this code is raised for both
|
|
81
|
+
and discriminates neither. What a one-code gate never caught, on any release, is the
|
|
82
|
+
absent-amount row: **gate on both.** `KNOWN-LIMITATIONS.md`, the money spec-note and the
|
|
83
|
+
troubleshooting table were corrected with the code, and a committed test pins both halves - that
|
|
84
|
+
the one-code gate misses the absent-amount document, and that it still fires where it always did.
|
|
85
|
+
|
|
86
|
+
**Bounds, stated because the wider reading is the tempting one, and stated as properties of the
|
|
87
|
+
READ rather than of the walker's control flow.** What is reported is a row whose amount was read
|
|
88
|
+
and decoded no value. A segment discarded **before** its amount is read is not on this channel
|
|
89
|
+
(the 834's `AMT` with no `HD` open, the 820's `ADX` with no remittance open), and neither is one
|
|
90
|
+
whose amount decoded and then found nothing open to attach the row to. **Do not shorten that to
|
|
91
|
+
"nothing open means silent"** - the 835 and the 837 decode first, so an `AMT` with an absent
|
|
92
|
+
amount and no claim open does raise this code. An 820 `RMR` is not on this channel either, and
|
|
93
|
+
**not** because its row survives: `decodeRmr` drops on open-item identity (`RMR-01` and `RMR-02`
|
|
94
|
+
both empty) **before** `RMR-04` is read, so an `RMR` stating an open item and no amount keeps its
|
|
95
|
+
row with `amountPaid` left `undefined`, while one stating an amount and **no** open item is
|
|
96
|
+
dropped whole. That second case, and the 837's Loop 2430 `AMT` which an open `SVD` discards
|
|
97
|
+
outright, are separate losses: nothing failed to decode in either, so neither is this code's
|
|
98
|
+
shape, and `X12_STATED_AMOUNT_DISCARDED` above is what reports them.
|
|
99
|
+
|
|
100
|
+
- **🩺 `X12_835_BALANCE_NOT_EVALUABLE`, the 29th Tier-2 warning code, plus the public factory
|
|
101
|
+
`balanceNotEvaluable(position, invariant)`** (`X12-837-SV-UNDEFINED-DECIMAL`). Raised where a term
|
|
102
|
+
of one of the three TR3 005010X221A1 §1.10.2 balance equations is `undefined` on the model, so the
|
|
103
|
+
equation has nothing to compare on one side. `invariant` is the same library-owned
|
|
104
|
+
`BALANCE_INVARIANTS` discriminant `X12_835_REMIT_BALANCE_MISMATCH` takes, and the message names the
|
|
105
|
+
equation and carries no amount, like every other warning in this registry.
|
|
106
|
+
|
|
107
|
+
**It is a different code from the mismatch on purpose.** The mismatch asserts a computed inequality
|
|
108
|
+
between amounts the sender supplied; this one asserts only that the comparison could not be made.
|
|
109
|
+
Through `0.0.12` an undecoded term collapsed to `X12Decimal.ZERO` and the equation then reported a
|
|
110
|
+
mismatch between the payer's own amounts and a zero this library invented, which is the reading
|
|
111
|
+
that made an absent `CLP-03` indistinguishable from a claim submitted at zero.
|
|
112
|
+
|
|
113
|
+
**An EMPTY list of adjustments is NOT an absent term** and does not raise it: a claim carrying no
|
|
114
|
+
`CAS` really did state no adjustments, so its sum is `X12Decimal.ZERO`. Only a term this library
|
|
115
|
+
decoded no value from stops the equation. `build835` reaches the same verdict as
|
|
116
|
+
`X12_835_BUILD_INVALID_SPEC` rather than as its balance-mismatch code, and is unreachable there
|
|
117
|
+
from TypeScript because every balance term on `Build835Spec` is a required `X12Decimal`.
|
|
118
|
+
|
|
119
|
+
**🩺 GATE ON BOTH CODES.** A posting gate written against `X12_835_REMIT_BALANCE_MISMATCH` alone
|
|
120
|
+
**stops firing** on these documents when you upgrade, because through `0.0.12` the undecoded term
|
|
121
|
+
collapsed to zero and raised the mismatch. This library warns either way; your gate has to look
|
|
122
|
+
for both. The recipes in `docs-content/quickstart.md` and `docs-content/cookbook.md` and the
|
|
123
|
+
triage table in `docs-content/troubleshooting.md` were corrected with the code, and a committed
|
|
124
|
+
test pins that the one-code gate misses a document the two-code gate catches.
|
|
125
|
+
|
|
126
|
+
- **🩺 `X12_837_PAY_TO_ADDRESS_REPEATED`, the 28th Tier-2 warning code, plus the public factory
|
|
127
|
+
`payToAddressRepeated(position)`** (`X12-PAY-TO-FUSION`). Raised at the **second and each
|
|
128
|
+
subsequent `NM1*87` within one Loop 2000A**, where the TR3s allow Loop 2010AB at most once.
|
|
129
|
+
`position.segmentIndex` names the repeated `NM1*87` itself and carries no `elementIndex`: what is
|
|
130
|
+
reported is a second occurrence of the segment, not a defect in any element of it. The counter
|
|
131
|
+
resets at the Loop 2000A `HL`, beside the pay-to slot it guards, so a first `NM1*87` under a later
|
|
132
|
+
billing provider is a first and not a repeat.
|
|
133
|
+
|
|
134
|
+
The model has **one** pay-to address slot, so it cannot carry two, and this code is the only thing
|
|
135
|
+
that says the document named two. It reports that the **document** repeated the loop; it does not
|
|
136
|
+
report that anything was mis-read, and it says nothing about the subjects of the other 837 codes.
|
|
137
|
+
An `NM1*87` arriving while a `CLM` is open never reaches the pay-to route at all, so it neither
|
|
138
|
+
warns nor arms the warning. **Where it lands instead is not stated as one destination, because two
|
|
139
|
+
were measured:** with a Loop 2400 open it joins that line's `serviceLine.providers` (TR3 Loop
|
|
140
|
+
2420), and with a claim but no line open it joins `claim.providers` (Loop 2310). Both are
|
|
141
|
+
pre-existing and identical at `0.0.12`.
|
|
142
|
+
|
|
12
143
|
- **🩺 `X12_837_ENTITY_SEGMENT_DISCARDED_AFTER_LX`, the 27th Tier-2 warning code, plus the public
|
|
13
144
|
factory `entitySegmentDiscardedAfterLx(position)`** (`X12-DISCARD-AFTER-STRAY-LX`). Raised for
|
|
14
145
|
each 837 `N3` / `N4` / `PER` / `REF` that reached **no party at all** because an earlier `LX` with
|
|
@@ -139,6 +270,62 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
139
270
|
|
|
140
271
|
### Changed
|
|
141
272
|
|
|
273
|
+
- **🩺 BREAKING (read model): every monetary, percent and quantity slot the readers used to fill with
|
|
274
|
+
a fabricated `X12Decimal.ZERO` is now `X12Decimal | undefined`** (`X12-837-SV-UNDEFINED-DECIMAL`).
|
|
275
|
+
Through `0.0.12` an **absent** `SV1-02` - a monetary field on an 837 claim service line - read back
|
|
276
|
+
as `X12Decimal.ZERO`, so a consumer could not tell "the sender stated zero" from "the sender stated
|
|
277
|
+
nothing" and this library presented the second as the first. Fourteen model slots carried that
|
|
278
|
+
fabrication: `X12Claim.totalCharge` (CLM-02), a service line's `charge` and `units` (SV1-02 /
|
|
279
|
+
SV2-03 / SV3-02 and SV1-04 / SV2-05 / SV3-06), `X12LineAdjudication.amountPaid` (SVD-02),
|
|
280
|
+
`X12RemitClaim.totalChargeAmount` / `totalPaymentAmount` / `patientResponsibilityAmount` (CLP-03 /
|
|
281
|
+
04 / 05), `X12RemitServiceLine.chargeAmount` / `paymentAmount` (SVC-02 / SVC-03),
|
|
282
|
+
`X12RemitPaymentHeader.totalActualPayment` and `X12PremiumPaymentHeader.totalPremiumAmount`
|
|
283
|
+
(BPR-02), `X12PremiumOpenItem.amountPaid` (RMR-04), `X12RemitAdjustment.amount` (a `CAS` triple,
|
|
284
|
+
read by both the 835 and the 837) and `X12RemitProviderAdjustment.amount` (a `PLB` pair).
|
|
285
|
+
|
|
286
|
+
**`undefined` means "this library decoded no value from that element", NOT "the element was
|
|
287
|
+
absent."** A present element holding bytes that do not decode lands there too, and
|
|
288
|
+
`X12_UNPARSEABLE_DECIMAL` at that `position.elementIndex` is the only thing that separates them.
|
|
289
|
+
Read it in exactly that direction. A **stated** zero is untouched: it still decodes, still reads
|
|
290
|
+
`0`, and keeps its lexical form, so `0.00` is still `0.00`.
|
|
291
|
+
|
|
292
|
+
**Migrating:** `x.toString()` becomes `x?.toString()` at every one of those slots, and each site is
|
|
293
|
+
a decision rather than a rewrite - an `undefined` amount is not zero, and a `?? 0` reinstates the
|
|
294
|
+
defect. If you posted cash off a slot that read `0` on `0.0.12` or earlier without also gating on
|
|
295
|
+
`.warnings`, re-read those files.
|
|
296
|
+
|
|
297
|
+
Two things this did **not** do. `elementDecimalOrZero` is a public export and is unchanged: it
|
|
298
|
+
still substitutes `X12Decimal.ZERO`, because that is its documented behaviour and a consumer
|
|
299
|
+
walking segments itself may still want it. What changed is that no reader in this library calls it
|
|
300
|
+
any more. And the 837's `X12_837_SERVICE_LINE_NOT_DECODED` is still the only thing that says WHY a
|
|
301
|
+
line's `charge` and `units` are empty: `undefined` alone does not separate "no `SVx` was decoded
|
|
302
|
+
onto this line" from "an `SVx` was decoded and carried no charge element".
|
|
303
|
+
|
|
304
|
+
- **🩺 BREAKING (emit side): `Build837ServiceLineSpec.units` is now required, and `build837P` /
|
|
305
|
+
`build837I` / `build837D` refuse a service line without it** (`X12-837-SV-UNDEFINED-DECIMAL`).
|
|
306
|
+
Through `0.0.12` an omitted `units` was emitted as the literal `0` into SV1-04 / SV2-05 / SV3-06,
|
|
307
|
+
so the builder stated a service unit count no caller supplied - the read-side fabrication above,
|
|
308
|
+
running the other way. Measured on `0.0.12`: a Professional line with no `units` emitted
|
|
309
|
+
`SV1*HC:99213*8500*UN*0*11**1~`. The refusal is `X12_837_BUILD_INVALID_SPEC`, naming the structural
|
|
310
|
+
locator (`billing[0].subscriber[0].claim[0]`) and no caller value.
|
|
311
|
+
|
|
312
|
+
**Refusing rather than emitting an empty element is the stance, and it is not new here:**
|
|
313
|
+
`build277` already refuses a service line without `SVC-07` on the same grounds. The parser is
|
|
314
|
+
liberal and the serializer conservative, and a count is not something a serializer may leave for
|
|
315
|
+
the receiver to guess at. A caller that supplies `units` is unaffected in every respect, including
|
|
316
|
+
one that supplies a zero, which is emitted because the caller did state it.
|
|
317
|
+
|
|
318
|
+
- **`build835` refuses a JS caller's `undefined` balance term as an invalid spec rather than throwing
|
|
319
|
+
an untyped `TypeError`** (`X12-837-SV-UNDEFINED-DECIMAL`). Unreachable from TypeScript: every term
|
|
320
|
+
of the three §1.10.2 invariants is a required `X12Decimal` on `Build835Spec`. Through `0.0.12` a JS
|
|
321
|
+
caller passing `undefined` reached `undefined.add` inside the balance guard. It is now
|
|
322
|
+
`X12_835_BUILD_INVALID_SPEC` with the registry text, and deliberately **not**
|
|
323
|
+
`X12_835_BUILD_BALANCE_MISMATCH`: nothing was measured out of balance, a required amount is simply
|
|
324
|
+
missing. Passing a raw `number` into a balance term still throws **an** untyped `TypeError` from
|
|
325
|
+
the same guard, so the documented dichotomy did not move; on some slots the message is now
|
|
326
|
+
`X12Decimal`'s own tampering text where it used to be `.add is not a function`, which the pinned
|
|
327
|
+
regex already admitted. Still disclosed in `KNOWN-LIMITATIONS.md`.
|
|
328
|
+
|
|
142
329
|
- **`KNOWN-LIMITATIONS.md` now ships in the published tarball** (`X12-837-LOOP-RESIDUALS`). Two
|
|
143
330
|
shipped warning messages, `X12_837_SERVICE_LINE_DROPPED` and
|
|
144
331
|
`X12_837_ENTITY_SEGMENT_DISCARDED_AFTER_LX`, name that document ("see KNOWN-LIMITATIONS.md", each
|
|
@@ -556,6 +743,57 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
556
743
|
|
|
557
744
|
### Fixed
|
|
558
745
|
|
|
746
|
+
- **🩺 An `AMT` / `ADX` row whose amount decodes nothing is no longer dropped in silence**
|
|
747
|
+
(`X12-AMT-ADX-ABSENT-AMOUNT`). Reproduced on the base tree at `9db104b` across all four readers,
|
|
748
|
+
and the report it now raises is the `X12_AMOUNT_ROW_DROPPED` entry under **Added** above. The
|
|
749
|
+
filing that named this defect stated the bound precisely and it is repeated here because the wider
|
|
750
|
+
form is false: an **absent** amount element was silent on every channel, while a **present** one
|
|
751
|
+
holding undecodable bytes was already reported by `X12_UNPARSEABLE_DECIMAL` at its own
|
|
752
|
+
`elementIndex`. Both now also raise the new code; nothing moved off the old one.
|
|
753
|
+
|
|
754
|
+
- **A cookbook sentence said an undecoded 837 service line ships `charge` and `units` as `0`.** It
|
|
755
|
+
reads `undefined` on the same release that made the rest of that page true, so the page contradicted
|
|
756
|
+
the money spec-note it links to. Corrected in passing while sweeping that paragraph for the new
|
|
757
|
+
code. This is a documentation correction only; no behaviour changed with it.
|
|
758
|
+
|
|
759
|
+
- **🩺 A repeated `NM1*87` in one 837 Loop 2000A no longer fuses two pay-to addresses into one the
|
|
760
|
+
sender never sent** (`X12-PAY-TO-FUSION`). Reproduced at `0.0.12`: `payToAddress` is a bare
|
|
761
|
+
accumulator with no entity object to own it, cleared only at the next Loop 2000A `HL`. Every other
|
|
762
|
+
party gets a fresh object at its `NM1`, so a trailing `N3` / `N4` found `address === undefined` and
|
|
763
|
+
its write replaced; the two pay-to arms had nothing replaced under them and wrote onto whatever the
|
|
764
|
+
previous `NM1*87` left, `withLines` appending and `mergeAddress` falling back. Two `NM1*87`s each
|
|
765
|
+
with an `N3` and an `N4` read back **a street line from each of two addresses** plus a
|
|
766
|
+
`countryCode` taken off the **first** `N4`, on an address whose own `N4` names no country, with
|
|
767
|
+
`warnings: []`. Re-emitted through `build837P` that became a single Loop 2010AB stating a payment
|
|
768
|
+
destination no sender had stated.
|
|
769
|
+
|
|
770
|
+
**The fix is the missing object identity, not a clear.** Each `NM1*87` now opens a fresh
|
|
771
|
+
accumulator that the address arms read and write, so values from two occurrences can never meet.
|
|
772
|
+
|
|
773
|
+
**🛑 Two earlier remedies were refuted, both for one reason, and it is why the emit side was in
|
|
774
|
+
scope from the start: on this slot an emptied value is not a neutral absence, because the emit side
|
|
775
|
+
reads it.** `build837P/I/D` gates Loop 2010AB on `payToAddress !== undefined` and `emitAddress`
|
|
776
|
+
writes `N3` only for non-empty `lines` and `N4` only for a defined field. Clearing the accumulator
|
|
777
|
+
at the `NM1*87` therefore erased an address a repeat carrying no `N3` / `N4` did state, re-emitting
|
|
778
|
+
as **no pay-to loop at all**; a flag consumed by the first write after it did the same on a
|
|
779
|
+
valueless `N3` or `N4`, re-emitting as a **bare `NM1*87`**. Both are positive statements about
|
|
780
|
+
where a payment goes that no sender made. So the rule is stated in the emit side's own terms: the
|
|
781
|
+
current occurrence takes the slot **only when it states an address the emit would write at least
|
|
782
|
+
one segment for**, and an occurrence that states none leaves a stated one alone. The predicate is a
|
|
783
|
+
single shared module (`src/transactions/claim/address-segments.ts`) that `emitAddress` also asks,
|
|
784
|
+
so the reader and the writer cannot drift apart. This is the discipline the 835 already uses, where
|
|
785
|
+
the emit guard reuses the read side's own balance validators.
|
|
786
|
+
|
|
787
|
+
**A document with at most one `NM1*87` per Loop 2000A is byte-for-byte unaffected**, warning
|
|
788
|
+
channel included, and that is pinned both ways.
|
|
789
|
+
|
|
790
|
+
**🩺 The cost, stated rather than argued away: a repeat that states only part of an address now
|
|
791
|
+
re-emits only that part.** A second `NM1*87` followed by an `N4` and no `N3` reads back that `N4`
|
|
792
|
+
with `lines: []` and re-emits a Loop 2010AB with no `N3`. Keeping the earlier occurrence's street
|
|
793
|
+
lines there **is** the fusion: one sender's street under another sender's city. The loop is still
|
|
794
|
+
present, the warning is on the channel, and what is gone is the fabricated street.
|
|
795
|
+
`KNOWN-LIMITATIONS.md` records it.
|
|
796
|
+
|
|
559
797
|
- **🩺 `build278Request` / `build278Response` refuse a review whose HL-03 level code is outside `EV`
|
|
560
798
|
and `SS`, instead of emitting a review its own reader cannot decode**
|
|
561
799
|
(`REFUSAL-MESSAGE-PHI-ECHO`). `Build278ReviewSpec.levelCode` is the **one** caller-supplied HL-03 in
|
|
@@ -1166,6 +1404,29 @@ string, …` - which `esc` cannot, being unary.
|
|
|
1166
1404
|
slot for a silent normalization; the tiers now name it, and note that a Tier-2 unexpected segment is
|
|
1167
1405
|
warned about but not kept.
|
|
1168
1406
|
|
|
1407
|
+
- **The universal about a stray 837 `LX` is cut back in the `src/` comments and 837 test-file headers
|
|
1408
|
+
the release before this one disclosed it in** (`X12-837-LOOP-RESIDUALS`). That release deleted it
|
|
1409
|
+
from the documents this package publishes and disclosed, rather than claimed closed, that it still
|
|
1410
|
+
stood in those two places; this is the rest of that sweep. **No count and no completeness claim is
|
|
1411
|
+
published**, because both are what went stale twice here: the sites swept are the `NM1` and `LX`
|
|
1412
|
+
case comments in `src/transactions/claim/get-837.ts`, and the headers, section comments and case
|
|
1413
|
+
titles of `test/transactions-claim-837-loop-residuals.test.ts`,
|
|
1414
|
+
`test/transactions-claim-837-discard-after-stray-lx.test.ts` and
|
|
1415
|
+
`test/transactions-claim-837-variant-lookup.test.ts`. Each said a trailing `N3` / `N4` / `PER` /
|
|
1416
|
+
`REF` "attached to" or "landed on" whichever party the last `NM1` left active, or that a party
|
|
1417
|
+
named after the `LX` "is addressable again", all of which read as all four kinds reaching every
|
|
1418
|
+
party. They do not: this reader does not surface every one of those kinds on every party. Each copy
|
|
1419
|
+
takes the qualifier already graded on the shipped surfaces, "wherever this reader surfaces that
|
|
1420
|
+
segment kind on that party at all", or is cut back to the measured instance beside it and that
|
|
1421
|
+
instance is named - a payer in every case but one, where it is a Loop 2320 other subscriber; no
|
|
1422
|
+
copy is given a new wording and **no per-kind, per-party map is published.** Counterfactual headings ("no longer attaches", "no longer leaves the last `NM1`
|
|
1423
|
+
addressable") lose the counterfactual only. The bullet in `documentation/agent-notes.md` beginning
|
|
1424
|
+
"RESIDUAL 1, MEASURED AT `93b2428`", and the identically-scoped paragraph in the test header it was
|
|
1425
|
+
written from, are **not** copies - both are scoped by "every trailing segment that attaches to a
|
|
1426
|
+
named party" - and are deliberately left alone. Nothing a consumer reads at runtime changed:
|
|
1427
|
+
neither the warning registry nor any `docs-content/` page nor the README carried the wording, and
|
|
1428
|
+
the executable behaviour of the swept comments is nil.
|
|
1429
|
+
|
|
1169
1430
|
### Documented, not fixed
|
|
1170
1431
|
|
|
1171
1432
|
> **Superseded in part by the "Fixed" entry above, which ships in the same release.** The model half
|
|
@@ -1174,6 +1435,20 @@ string, …` - which `esc` cannot, being unary.
|
|
|
1174
1435
|
> not re-emit an orphan, so the warning still does not survive. The two entries are kept separate
|
|
1175
1436
|
> because they were separate changes, and the sentences below describe the release before this one.
|
|
1176
1437
|
|
|
1438
|
+
- **🩺 A repeated `NM1*87` inside one 837 Loop 2000A fuses two pay-to addresses into one**
|
|
1439
|
+
(`X12-837-LOOP-RESIDUALS`). The `N3` line collector appends and the `N4` merge falls back, so
|
|
1440
|
+
`X12Claim.payToAddress` carries a street from each of two addresses and an `N4` field the second
|
|
1441
|
+
address omitted. Reproduces unchanged at `0.0.11` and is **not** fixed here: two remedies were
|
|
1442
|
+
built and both were refuted for moving the loss rather than removing it, so per ADR 0016 the unit
|
|
1443
|
+
was cut back rather than given a third. Clearing the accumulator at the `NM1*87` erases the
|
|
1444
|
+
address a repeat with no `N3` / `N4` of its own did state; replacing at the first following write
|
|
1445
|
+
erases it on a VALUELESS `N3~` / `N4~`. **Neither erasure is a neutral absence, and that is the
|
|
1446
|
+
constraint the next attempt starts from:** `build837P/I/D` emits Loop 2010AB only where the slot
|
|
1447
|
+
is defined, and `emitAddress` emits `N3` only for a non-empty `lines` and `N4` only for a defined
|
|
1448
|
+
field, so an emptied slot re-emits as **no pay-to loop at all** and a half-emptied one re-emits a
|
|
1449
|
+
bare `NM1*87` with neither. **The emit side is therefore in scope for that fix from the start**,
|
|
1450
|
+
which is what makes it its own unit. `documentation/agent-notes.md` carries the measurements.
|
|
1451
|
+
|
|
1177
1452
|
- **A segment that falls outside a transaction is dropped from the model, and its warning does not
|
|
1178
1453
|
survive a round trip.** The envelope walker keeps body segments only while an ST..SE transaction is
|
|
1179
1454
|
open; anything else raises `X12_UNEXPECTED_SEGMENT` and is then discarded. A **blank** line between
|
package/KNOWN-LIMITATIONS.md
CHANGED
|
@@ -56,28 +56,34 @@ model.
|
|
|
56
56
|
on the emit side. All of it is pre-existing and reproduces at `e3cdf49`; widening the guard would
|
|
57
57
|
have turned this into that audit, which is its own item.
|
|
58
58
|
|
|
59
|
-
- **🩺 An unparseable decimal
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
`X12Decimal
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
59
|
+
- **🩺 An unparseable decimal reads `undefined`, and it warns.** A decimal element that is present
|
|
60
|
+
and that this library cannot decode as a decimal (`1,234.56`, `$450.00`, `N/A`) yields no value,
|
|
61
|
+
and the reader has to put something in its place. **Through `0.0.12` a slot typed `X12Decimal` got
|
|
62
|
+
`X12Decimal.ZERO`**, so a consumer that read only the model saw a `0` where the payer sent
|
|
63
|
+
something unreadable; **as of `0.0.13` no reader substitutes `X12Decimal.ZERO` for a value it did
|
|
64
|
+
not decode** - every slot that used to get one is `X12Decimal | undefined` and reads `undefined`,
|
|
65
|
+
which is a breaking type change on the read model and is the point of it. **Read that as a rule
|
|
66
|
+
about the substitution, not as a census of the model:** an optional slot already read `undefined`,
|
|
67
|
+
and some rows are dropped whole, which is a different shape with a report of its own
|
|
68
|
+
(`X12_AMOUNT_ROW_DROPPED`, the next entry). **Every one of those
|
|
69
|
+
outcomes emits `X12_UNPARSEABLE_DECIMAL` at the failing `position.elementIndex`, because the
|
|
70
|
+
warning is a property of the READ rather than of what the reader then does with it.** No list of
|
|
71
|
+
the outcomes is published here, on purpose: a first draft enumerated three and a review measured a
|
|
72
|
+
fourth, and the rule is what holds, not the census.
|
|
72
73
|
|
|
73
74
|
Three scoping facts that are easy to get wrong in the other direction:
|
|
74
|
-
- **An ABSENT element does not
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
- **An ABSENT element does not raise this code.** It reads `undefined` and
|
|
76
|
+
`X12_UNPARSEABLE_DECIMAL` is not emitted for it, so `undefined` alone does not tell you which of
|
|
77
|
+
the two happened - the warning at that `elementIndex` does, and `readElementDecimal` gives you
|
|
78
|
+
the same distinction in band. Read that as a statement about **this code**, not as "an absent
|
|
79
|
+
element is silent": where the absent element takes a whole `AMT` / `ADX` row off the model, the
|
|
80
|
+
drop is reported by `X12_AMOUNT_ROW_DROPPED` at that segment (the next entry), which is
|
|
81
|
+
raised for both routes and therefore separates neither.
|
|
82
|
+
- **🩺 An unwarned value is not thereby trustworthy in general, and this is the one inversion to
|
|
77
83
|
refuse.** The warning is a property of a decimal READ, not a property of a model slot. A slot
|
|
78
84
|
the reader never read at all cannot warn, and it still holds whatever the accumulator was seeded
|
|
79
|
-
with. **What this guarantees is narrower and exact: an unwarned
|
|
80
|
-
DECODED is
|
|
85
|
+
with. **What this guarantees is narrower and exact: an unwarned value AT AN ELEMENT A READER
|
|
86
|
+
DECODED is what the sender sent.** The known slot of the other kind, an 837 service
|
|
81
87
|
line whose `SVx` never decoded, is covered by a warning of its own (next entry) rather than by
|
|
82
88
|
this one. **No census of never-read slots is published**, on purpose: the rule is what holds.
|
|
83
89
|
- **The public helpers are silent without a sink.** `elementDecimal` / `elementDecimalOrZero` take
|
|
@@ -91,23 +97,120 @@ model.
|
|
|
91
97
|
comments. It says nothing about a decimal decoded some other way, and no exhaustive census of
|
|
92
98
|
such routes is published here, on purpose.
|
|
93
99
|
|
|
94
|
-
- **🩺 An
|
|
95
|
-
|
|
100
|
+
- **🩺 An `AMT` or `ADX` whose amount does not decode loses its WHOLE row, and says so as of
|
|
101
|
+
`0.0.13`.** These segments carry an amount plus the thing the amount is about - AMT-01's qualifier,
|
|
102
|
+
ADX-02's adjustment reason code, ADX-03 / ADX-04's reference. The readers build no row at all when
|
|
103
|
+
the amount (AMT-02, ADX-01) decodes no value, so what the sender did state goes with it: through
|
|
104
|
+
`0.0.12` `AMT*B6~` gave `claim.amounts: []` and `warnings: []`, which reads exactly like a document
|
|
105
|
+
that never carried the segment. **`X12_AMOUNT_ROW_DROPPED` now reports it**, anchored at the
|
|
106
|
+
`AMT` / `ADX` and carrying **no `elementIndex`**, because one of its two routes is an absent
|
|
107
|
+
element and an absent element has no index to name. Four surfaces raise it: the 835's `AMT`, the
|
|
108
|
+
837's `AMT`, the 834's coverage `AMT` (on that **member's** own `warnings`, exactly as the decimal
|
|
109
|
+
sink beside it is scoped) and the 820's `ADX`. On the 835 and the 837 the `AMT` attaches to the
|
|
110
|
+
open **service line** first and to the claim only when there is none, so **the row lost may be a
|
|
111
|
+
line-level one** - do not read an unchanged `claim.amounts` as evidence the warning is stale. Four
|
|
112
|
+
bounds worth stating:
|
|
113
|
+
- **It is additive, and no case moved onto it.** A present-but-undecodable amount still raises
|
|
114
|
+
`X12_UNPARSEABLE_DECIMAL` at its own `elementIndex`, now alongside this code rather than instead
|
|
115
|
+
of it, so a predicate written against that code fires on exactly the documents it fired on
|
|
116
|
+
before. Whether one accompanies this code at the same `segmentIndex` is what tells the absent
|
|
117
|
+
route from the unparseable one, since this code is raised for both and discriminates neither.
|
|
118
|
+
- **It reports a row whose AMOUNT was read and decoded no value, and nothing wider.** State it as
|
|
119
|
+
a property of the READ, not of the walker's control flow. A segment discarded before its amount
|
|
120
|
+
is read is not on this channel (the 834's `AMT` with no `HD` open, the 820's `ADX` with no
|
|
121
|
+
remittance open), and neither is one whose amount decoded and then found nothing to attach to
|
|
122
|
+
(the 835's and the 837's `AMT` before any claim). **Do not write it as "nothing open means
|
|
123
|
+
silent":** the 835 and the 837 decode first, so an `AMT` with an absent amount and no claim open
|
|
124
|
+
does raise this code.
|
|
125
|
+
- **🔴 An 820 `RMR` is not on this channel, and the reason is NOT that its row survives.**
|
|
126
|
+
`decodeRmr` drops on open-item identity (RMR-01 and RMR-02 both empty), **before** RMR-04 is
|
|
127
|
+
read. So an `RMR` that states an open item and no amount keeps its row with `amountPaid` left
|
|
128
|
+
`undefined` and there is nothing to report - but an `RMR` that states an **amount and no open
|
|
129
|
+
item** is dropped whole, taking a stated payment, its payment-action code and its amount due
|
|
130
|
+
with it. Nothing failed to decode there, so it is not this code's shape;
|
|
131
|
+
`X12_STATED_AMOUNT_DISCARDED`, below, is what reports it.
|
|
132
|
+
- **A Loop 2430 `AMT` under an open `SVD` is discarded outright, and this code is not what says
|
|
133
|
+
so.** With a claim and a line open, the 837's adjudication skip drops an `AMT` that decoded
|
|
134
|
+
perfectly well. Through `0.0.12` that was silent while one that decoded nothing raised this
|
|
135
|
+
code, so the report was present exactly where less was lost;
|
|
136
|
+
`X12_STATED_AMOUNT_DISCARDED`, below, closed that.
|
|
137
|
+
|
|
138
|
+
- **🩺 A row the sender DID state can still be discarded, and says so as of `0.0.13`.** The code
|
|
139
|
+
above is about an amount this library could not read. This is the opposite case: **the reader
|
|
140
|
+
discarded the row for a reason that is not about the amount at all**, so the amount was never the
|
|
141
|
+
problem and, on one of the two routes, was never even looked at. The bytes stay verbatim on
|
|
142
|
+
`tx.segments[…].raw`; decode them yourself. Through `0.0.12` both routes were silent on every
|
|
143
|
+
channel. **`X12_STATED_AMOUNT_DISCARDED` reports them**, anchored at
|
|
144
|
+
the segment and carrying **no `elementIndex`**. The two routes, enumerated:
|
|
145
|
+
- An **820 `RMR`** under an open remittance loop whose RMR-01 and RMR-02 are **both empty** while
|
|
146
|
+
RMR-04 or RMR-05 is populated. `decodeRmr` refuses the open item on identity before either
|
|
147
|
+
amount element is read, so a stated payment, a stated amount due and RMR-03's payment action
|
|
148
|
+
code leave the model together. A bare `RMR~` states nothing and stays silent.
|
|
149
|
+
- An **837 `AMT`** arriving while a Loop 2430 line adjudication (`SVD`) is open, whose AMT-02
|
|
150
|
+
decoded. The v1 adjudication model carries no amount row, and attaching one to this
|
|
151
|
+
submission's own service line would put another payer's figure on it, so the row is skipped.
|
|
152
|
+
`AMT*EAF` (Remaining Patient Liability) on a Loop 2430 is exactly the shape being lost.
|
|
153
|
+
|
|
154
|
+
Three bounds worth stating:
|
|
155
|
+
- **It is additive, and nothing moved onto it.** `X12_AMOUNT_ROW_DROPPED` and
|
|
156
|
+
`X12_UNPARSEABLE_DECIMAL` fire on exactly the documents they fired on before, pinned by
|
|
157
|
+
committed tests.
|
|
158
|
+
- **The two amount-row codes are disjoint and can never name the same segment**, because this one
|
|
159
|
+
requires an amount element the sender populated and the other requires one that decoded no
|
|
160
|
+
value. The code you get is the discriminant.
|
|
161
|
+
- **It reports a segment that arrived while the loop that would carry its row was open.** An
|
|
162
|
+
`AMT` or `ADX` reaching a reader with **no such loop open** is a different loss and is **still
|
|
163
|
+
silent**: the 834's `AMT` with no `HD` open, the 820's `ADX` with no remittance open, and the
|
|
164
|
+
835's and the 837's `AMT` before any claim or service line is open. **🔴 Read "still silent"
|
|
165
|
+
literally and no wider: at those last two sites the inversion above SURVIVES.** With no claim
|
|
166
|
+
open an 835 `AMT*B6~` raises `X12_AMOUNT_ROW_DROPPED` while `AMT*B6*500.00~` raises nothing, so
|
|
167
|
+
the channel still reports the smaller loss there. `PRE-EXISTING` and unchanged by this release. And on the `RMR` route this
|
|
168
|
+
code says nothing about whether the amount would have decoded, because the row is refused before
|
|
169
|
+
the decode is attempted, so **no `X12_UNPARSEABLE_DECIMAL` accompanies it even where the bytes
|
|
170
|
+
are unreadable.** It is raised on `RMR****1,234.56~` exactly as on `RMR****150.00*150.00~`, so
|
|
171
|
+
**never read an unaccompanied instance as evidence the bytes are postable**; only the `AMT`
|
|
172
|
+
route guarantees a decodable amount, because there AMT-02 decoded before the row was skipped.
|
|
173
|
+
|
|
174
|
+
- **🩺 An 835 balance invariant with an undecoded term is reported as UNEVALUABLE, not as a
|
|
175
|
+
mismatch.** The three TR3 005010X221A1 §1.10.2 equations (line, claim, top-of-remit) read amounts
|
|
176
|
+
that are all `X12Decimal | undefined` as of `0.0.13`. Where any term of an equation is
|
|
177
|
+
`undefined`, the equation is not run and `X12_835_BALANCE_NOT_EVALUABLE` is emitted instead of
|
|
178
|
+
`X12_835_REMIT_BALANCE_MISMATCH`, because nothing was measured out of balance: substituting `0`
|
|
179
|
+
for the missing term would be this library asserting a total nobody sent. **Through `0.0.12` that
|
|
180
|
+
substitution is exactly what happened**, so an 835 with an absent `CLP-03` reported a mismatch
|
|
181
|
+
between the payer's own amounts and an invented zero. Two bounds worth stating:
|
|
182
|
+
- **An EMPTY list is not an absent term.** A claim carrying no `CAS` really did state no
|
|
183
|
+
adjustments, and that sums to `X12Decimal.ZERO`. Only a term this library decoded no value from
|
|
184
|
+
stops the equation.
|
|
185
|
+
- **`build835` cannot reach it from TypeScript.** Every balance term on `Build835Spec` is a
|
|
186
|
+
required `X12Decimal`. A JS caller passing `undefined` gets `X12_835_BUILD_INVALID_SPEC` (an
|
|
187
|
+
untyped `TypeError` through `0.0.12`), deliberately NOT the build-side balance-mismatch code.
|
|
188
|
+
|
|
189
|
+
- **The public `elementDecimalOrZero` helper still substitutes `X12Decimal.ZERO`, and no reader in
|
|
190
|
+
this library calls it any more.** Its documented behaviour is unchanged and it stays exported, so
|
|
191
|
+
a consumer walking segments itself can still opt into the substitution - knowingly, rather than by
|
|
192
|
+
inheriting a convention the `get*` readers no longer follow. `elementDecimal` and
|
|
193
|
+
`readElementDecimal` are the honest routes.
|
|
194
|
+
|
|
195
|
+
- **🩺 An 837 service line whose `SVx` never decoded ships with `charge` and `units` `undefined`,
|
|
196
|
+
and it warns.** `get837Claims` resolves ONE variant for the submission, from the
|
|
96
197
|
caller's `type` option, else ST-03's implementation-convention reference, else the first `SVx`
|
|
97
198
|
segment present. A Loop 2400 line is then decoded only by the `SV1` / `SV2` / `SV3` that matches
|
|
98
199
|
that variant. When none arrives, because the line carries an `SVx` for a different variant (an
|
|
99
200
|
ST-03 of `005010X222A2` on a file whose lines are `SV2`, or a `{ type: "P" }` over the same) or
|
|
100
201
|
because it carries no `SVx` at all, **nothing on the service segment is read**: the line's
|
|
101
|
-
`charge` and `units` hold the accumulator's seeded `
|
|
202
|
+
`charge` and `units` hold the accumulator's seeded `undefined`, and its procedure code,
|
|
102
203
|
modifiers, unit of measure and place of service are equally undecoded. **That line now emits
|
|
103
204
|
`X12_837_SERVICE_LINE_NOT_DECODED`, anchored at the `LX` that opened it.** Read the values off
|
|
104
205
|
`tx.segments[…].raw` and decide which of the two disagreeing signals the sender meant.
|
|
105
206
|
- **Ignoring the foreign `SVx` is deliberate and is not the limitation.** `SV1-02` and `SV2-03`
|
|
106
207
|
are both the line charge, so decoding an `SV2` into a Professional line would mis-read money.
|
|
107
208
|
Refusing to read is the safe half; doing it silently was the defect.
|
|
108
|
-
-
|
|
109
|
-
`
|
|
110
|
-
`
|
|
209
|
+
- **`undefined` does not say WHY, which is what the warning is for.** `charge` and `units` are
|
|
210
|
+
`X12Decimal | undefined` as of `0.0.13` (`X12Decimal` reading a fabricated `0` through
|
|
211
|
+
`0.0.12`), so a consumer that never reads `.warnings` at least sees that nothing was decoded -
|
|
212
|
+
but it cannot tell this case, where no `SVx` was read at all, from an `SVx` that WAS read and
|
|
213
|
+
carried no charge element. Only `X12_837_SERVICE_LINE_NOT_DECODED` separates them.
|
|
111
214
|
- **The line is still retained, and so are the bytes.** Nothing is dropped: the service line is
|
|
112
215
|
on the claim, and every segment, decoded or not, stays verbatim on `tx.segments`.
|
|
113
216
|
- **Neither cause is attributed to the sender.** A `type` option that disagrees with a perfectly
|
|
@@ -214,14 +317,15 @@ model.
|
|
|
214
317
|
- **The segment is NOT decoded into any line.** `SV1-02` and `SV2-03` are both the line charge, so
|
|
215
318
|
reading a service segment into a line the walker never opened mis-reads money. Refusing to read
|
|
216
319
|
is the safe half; doing it silently was the defect. **This does not change the `SV1-02` case
|
|
217
|
-
above:** an absent `SV1-02` on a line that DID open
|
|
218
|
-
|
|
320
|
+
above:** an absent `SV1-02` on a line that DID open reads `undefined` as of `0.0.13`, a
|
|
321
|
+
confident `0` through `0.0.12`, and this code has nothing to do with either.
|
|
219
322
|
- **🩺 IT SAYS NOTHING ABOUT THE VARIANT, AND A FIRST DRAFT OF THIS BOUND CLAIMED IT DID.** Variant
|
|
220
323
|
resolution runs before the walk. A caller-supplied `type` option wins first; absent one, and
|
|
221
324
|
where `ST-03` names none of the three known implementation conventions, it **falls back to the
|
|
222
325
|
first `SVx` segment id anywhere in the transaction body - orphans included**. So a stray `SV2`
|
|
223
326
|
under an `ST-03` of `005010X222A1` re-types the whole submission as Institutional, and every
|
|
224
|
-
conformant `SV1` line in it then reads `charge` `
|
|
327
|
+
conformant `SV1` line in it then reads `charge` `undefined`, `units` `undefined` (both a
|
|
328
|
+
fabricated `0` through `0.0.12`) and an `undefined` procedure
|
|
225
329
|
code - `undefined`, not `""`, which on such a line is the `revenueCode`. Passing
|
|
226
330
|
`{ type: "P" }` reads the same document correctly. Measured, both trees: `PRE-EXISTING`,
|
|
227
331
|
identical at `0.0.10`, **not** introduced or changed by this code, and **not** narrowed here - excluding
|
|
@@ -265,6 +369,58 @@ model.
|
|
|
265
369
|
following a **stray** `LX` belongs to is not derivable from the TR3s in either direction, so the
|
|
266
370
|
reader refuses to attribute it rather than guessing.
|
|
267
371
|
|
|
372
|
+
- **🩺 An 837 Loop 2000A that names the pay-to address more than once has only ONE slot on the model
|
|
373
|
+
to put it in, and the model carries one of the addresses rather than both.** The TR3s allow Loop
|
|
374
|
+
2010AB at most once per Loop 2000A, so a repeated `NM1*87` is a non-conformant document. Each one
|
|
375
|
+
raises `X12_837_PAY_TO_ADDRESS_REPEATED`, anchored at the repeated `NM1*87`, so the choice below is
|
|
376
|
+
never made in silence.
|
|
377
|
+
|
|
378
|
+
**🩺 Through `0.0.12` the two addresses were FUSED, and that is what changed.** `payToAddress` is a
|
|
379
|
+
bare accumulator with no entity object to own it, so unlike every other party there was nothing to
|
|
380
|
+
be replaced at the repeat: `withLines` appended and `mergeAddress` fell back, and two `NM1*87`s
|
|
381
|
+
each carrying an `N3` and an `N4` read back a street line from **each** address plus a
|
|
382
|
+
`countryCode` off the **first** `N4`, on an address whose own `N4` names no country, with
|
|
383
|
+
`warnings: []`. Re-emitted through `build837P` that was one Loop 2010AB naming a payment
|
|
384
|
+
destination no sender stated. **If you read a repeated `NM1*87` on `0.0.12` or earlier, treat that
|
|
385
|
+
claim's `payToAddress` as unreliable rather than as either sender's address.**
|
|
386
|
+
|
|
387
|
+
**The rule now applied, and it is stated in the emit side's terms because the emit side reads this
|
|
388
|
+
slot:** occurrences are never merged; the **last occurrence that states an address of its own**
|
|
389
|
+
takes the slot; and an occurrence that states none - one with no `N3` or `N4` at all, or only a
|
|
390
|
+
valueless `N3` / `N4` whose elements are empty - **does not blank one that did**. "States an
|
|
391
|
+
address" means exactly what `emitAddress` would write a segment for, and reader and writer share
|
|
392
|
+
one predicate so they cannot disagree. That last clause is not a nicety: `build837P/I/D` gates Loop
|
|
393
|
+
2010AB on `payToAddress !== undefined`, so a slot cleared or blanked by a repeat that stated
|
|
394
|
+
nothing would re-emit as **no pay-to loop**, or as a **bare `NM1*87`** carrying neither `N3` nor
|
|
395
|
+
`N4`. Both say something about where a payment goes that the sender did not.
|
|
396
|
+
|
|
397
|
+
**🩺 The cost, which is real and is not argued away: a repeat that states only PART of an address
|
|
398
|
+
puts only that part on the model, and re-emits only that part.** A second `NM1*87` followed by an
|
|
399
|
+
`N4` and no `N3` reads back that `N4` with `lines: []` and re-emits a Loop 2010AB with no `N3`,
|
|
400
|
+
where `0.0.12` emitted a complete-looking address assembled from two. Keeping the earlier street
|
|
401
|
+
lines is the fusion itself, so they are not kept. **Which occurrence the sender meant is not
|
|
402
|
+
derivable from the TR3s**, and the losing occurrence's address is **not on the model in any form** -
|
|
403
|
+
`tx.segments` is where its bytes are, and remains the only complete account of the document.
|
|
404
|
+
|
|
405
|
+
**Two bounds, both measured.** This is scoped to the pay-to route, which lives in Loop 2000A: an
|
|
406
|
+
`NM1*87` arriving while a `CLM` is open never reaches that route, and is neither resolved nor
|
|
407
|
+
warned by this code. **Where it lands instead depends on whether a service line is open, and this
|
|
408
|
+
document does not state a single destination for it**, because two were measured: with a Loop 2400
|
|
409
|
+
open it joins that line's `serviceLine.providers` (a line-level provider is TR3 Loop 2420), and
|
|
410
|
+
with a claim but no line open it joins `claim.providers` (Loop 2310). Both are pre-existing and
|
|
411
|
+
identical at `0.0.12`. And a document with **at most one** `NM1*87` per Loop 2000A is unaffected in
|
|
412
|
+
every respect, warning channel included - including the pre-existing case of a lone `NM1*87` with a
|
|
413
|
+
valueless `N3`, which still reads an address with no elements and still re-emits a bare `NM1*87`.
|
|
414
|
+
|
|
415
|
+
**🩺 One thing the cost above shares with the shape that refuted remedy 2, stated so the two are
|
|
416
|
+
not read as different in kind.** In Loop 2010AB the TR3s make `NM1`, `N3` **and** `N4` all Required,
|
|
417
|
+
so a re-emitted loop carrying only the winning occurrence's `N4` is short a Required `N3`, exactly
|
|
418
|
+
as a bare `NM1*87` is short both. The difference is not conformance, it is provenance: every
|
|
419
|
+
element that IS emitted was stated by the occurrence the model kept, where the fused address
|
|
420
|
+
contained elements no single occurrence stated. Emitting a Loop 2010AB short a Required segment is
|
|
421
|
+
a pre-existing property of `emitAddress`, reachable at `0.0.12` from a lone `NM1*87` with an
|
|
422
|
+
`N4` and no `N3`, and is not introduced here.
|
|
423
|
+
|
|
268
424
|
- **🩺 Through `0.0.9`, a lookup keyed by document bytes could be defeated by a key inherited from
|
|
269
425
|
`Object.prototype`, and the affected code paths reported nothing.** The bundled code lists, the
|
|
270
426
|
837's variant resolution, and the 837's HL parent-level map were built as plain object literals,
|
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ const ix = parseX12(rawEdi); // never throws except on 4 structural fatals
|
|
|
34
34
|
const tx = ix.groups[0]?.transactions.find((t) => t.st.elements[1] === "835");
|
|
35
35
|
const remit = tx ? get835(ix.delimiters, tx) : undefined;
|
|
36
36
|
|
|
37
|
-
remit?.payment.totalActualPayment
|
|
37
|
+
remit?.payment.totalActualPayment?.toString(); // "450.00": BigInt-exact, never a float
|
|
38
38
|
remit?.claims[0]?.patientControlNumber; // "PT-ACCT-001": your account number, echoed back
|
|
39
39
|
remit?.claims[0]?.serviceLines[0]?.adjustments[0]?.reasonCode; // "1": CARC (why it was adjusted)
|
|
40
40
|
```
|
|
@@ -45,7 +45,7 @@ That's the pitch: no schema upload, no spec knowledge. The parser accepts vendor
|
|
|
45
45
|
|
|
46
46
|
- **Typed read + emit, per transaction**: 271, 277/277CA, 278, 820, 834, 835, 837P/I/D, plus 999/TA1 acknowledgments. Per-transaction helpers (`get835`, `get837Claims`, `get271Eligibility`, …) and a matching domain builder for each (`build835`, `build837P/I/D`, `build271`, …). The 270 and 276 inquiries parse as generic segments; they have no typed model yet.
|
|
47
47
|
- **Postel's Law**: a lenient parser (deviations → warnings with a stable code + positional context) and a conservative serializer. `serializeX12` is byte-faithful **for the segments on the model** by default: each comes back verbatim, including element padding, composites, and `?`-release escapes. **`serialize(parse(s)) === s` is not guaranteed in general.** Six constructs are known not to survive: line breaks between segments (any run of CR / LF between segments is absorbed at parse, so a pretty-printed or double-spaced file emits compact), a doubled terminator outside a transaction, a missing final terminator (the emit supplies one), post-IEA `trailingBytes` (re-joined, not verbatim), a TA1 that followed a functional group (emitted right after the ISA, so it is **reordered**, though nothing is lost), and a segment whose first element is empty outside a transaction (skipped entirely, with no warning at all). A **segment outside a transaction is not on that list**: it is reported as `X12_UNEXPECTED_SEGMENT`, kept on the model at `ix.orphanSegments` with a structural `anchor`, and re-emitted at that anchor, so the segment, its value and its warning all survive the round trip - placement is by the anchor and never by `segmentIndex`, which indexes the input stream the emit does not follow. The last five break the round trip on inputs with no line breaks at all, so do not treat "no line breaks" as sufficient; five of the six are silent, so a clean warnings list does not mean byte-exact either. Measured across this repo's 56 fixtures: every emit is a fixed point and re-parses to an identical model with an identical warning stream, the 14 with no line breaks return byte-identical, and the other 42 differ by line breaks and nothing else. See [Line endings between segments](./docs-content/spec-notes-envelope.md). `{ specClean: true }` reconciles the envelope, and `{ specClean: true, recomputeCounts: true }` also emits the corrected counts (`recomputeCounts` does nothing on its own). A mismatch is always warned, never silently corrected. Only **4 structural failures** are ever fatal.
|
|
48
|
-
- **Money is exact**: every monetary/percent/quantity field decodes as `X12Decimal` (string-backed, BigInt arithmetic)
|
|
48
|
+
- **Money is exact, and never invented**: every monetary/percent/quantity field decodes as `X12Decimal` (string-backed, BigInt arithmetic), and the library **never `parseFloat`s** an EDI amount. **No slot holds an amount this library did not decode.** Where a reader used to substitute `X12Decimal.ZERO` the slot is now `X12Decimal | undefined` and reads `undefined`, so "the sender stated zero" and "the sender stated nothing" are different readings; through `0.0.12` they were the same `0`. That is a rule about the substitution, not a census of the model: some rows are dropped whole instead, which is a different and pre-existing shape, and no total is published here. Read [KNOWN-LIMITATIONS.md](./KNOWN-LIMITATIONS.md).
|
|
49
49
|
- **Safety-critical fidelity**: TRN reassociation traces, 835 balance invariants, 837 HL hierarchy integrity, 834 maintenance types, and 278 certification actions are preserved **verbatim** and never inferred; ambiguity yields a warning or a typed refusal, never a confident wrong answer.
|
|
50
50
|
- **PHI-disciplined**: synthetic-only fixtures, a PHI commit-gate, and a warning `message` that is a **lookup into a frozen registry**, not something built from your document. No warning factory in the library takes a value parameter, so no element can reach a diagnostic: the code and `position` say what and where, and the bytes stay on the model. `ALL_WARNING_MESSAGES` is exported so you can assert it. **Builder refusals are a different, and deliberately weaker, surface:** twenty-four refusal sites across ten builder modules name a value you passed in, so you can see which control number, count or code was refused. **What they will never name is a `claimId`, a member id, a member name, a trace or a diagnosis code** - a guarantee that held for the refusal templates and, until the release after `0.0.10`, did not hold for the shared type guards underneath them: a JSON-driven caller who sent a number where the types say string got `a number ("900412345678")` back, bounded but not redacted, from a guard standing on every element of every builder. Those guards report the type now and never the value. Only the segment-join guard names the slot as well; the `esc` and `X12Decimal` guards name the builder, which is a diagnostic cost stated rather than hidden. The array guard's primitive arm is redacted with them, but it still reports the `length` and the class tag of a forged array-like, bounded, because those describe the shape you forged rather than the contents of a document element. Each goes through `renderCallerValue`, capping the rendered **fragment** at `BUILD_REFUSAL_VALUE_MAX_RENDERED` (90 characters); all three names are exported so you can assert the ceiling. The whole `message` is that plus the site's own fixed text, so it is bounded by a constant but a larger one: a 120,000-character control number gave a 120,066-character `X12BuildError.message` before this and gives 150 now. That is robustness, not redaction: the value is one you supplied and bounding it hides nothing from you. It is also not escaped, and on the **ack** path it is not always strictly your own, since `build999`'s AK2-02 and `buildTA1` echo an inbound document's control numbers by design. Log `err.code`, not `err.message`, from a builder. **`defineProfile()` is bounded on the same terms since `0.0.6`** (twelve refusal sites, twenty-three caller values; the worst message measured **360,181 characters** before and **431** now, both at the `fixture` refusal, which names three caller values; the ceiling for that site is 443 and the suite asserts every site under 500; `X12ProfileError.profileName` is deliberately left unbounded so it still matches the name you passed). **And a forged non-array in a builder spec now refuses instead of hanging:** every indexed loop takes its bound from a checked array, so `{ length: "9".repeat(120000) }` draws a typed refusal rather than coercing to `Infinity` and looping forever. A few `for…of` reads still throw an untyped `TypeError` instead; both are in [KNOWN-LIMITATIONS.md](./KNOWN-LIMITATIONS.md). **The one deliberate exception:** `X12ParseError.snippet` on the four Tier-3 structural fatals is a bounded (≤ 64 character) copy of the start of the input, so on real traffic it can carry PHI. The library does not redact it. Redact at your call site, or log `err.code` and `err.position` instead. See [Keeping PHI out of logs](./docs-content/troubleshooting.md).
|
|
51
51
|
|