@cosyte/x12 0.0.17 → 0.1.0
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 +233 -7
- package/KNOWN-LIMITATIONS.md +472 -74
- package/README.md +268 -24
- package/dist/index.cjs +5865 -1153
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8098 -1566
- package/dist/index.d.ts +8098 -1566
- package/dist/index.mjs +5778 -1154
- package/dist/index.mjs.map +1 -1
- package/package.json +58 -26
package/KNOWN-LIMITATIONS.md
CHANGED
|
@@ -18,7 +18,7 @@ model.
|
|
|
18
18
|
|
|
19
19
|
- **🩺 An ISA element carrying the element separator makes the header split into more than
|
|
20
20
|
`ISA` + 16 elements. Through `0.0.16` no check saw that at all; it is now reported as
|
|
21
|
-
`X12_ISA_EXTRA_ELEMENT_SEPARATOR
|
|
21
|
+
`X12_ISA_EXTRA_ELEMENT_SEPARATOR`.** **Nothing is
|
|
22
22
|
re-framed: this is a report, not a repair, and no existing warning was suppressed or narrowed.**
|
|
23
23
|
|
|
24
24
|
`detectDelimiters` verifies the element separator at all 16 fixed 005010 byte positions, so the
|
|
@@ -81,12 +81,12 @@ model.
|
|
|
81
81
|
a decision about the build side and is not taken here.
|
|
82
82
|
|
|
83
83
|
- **🩺 `parseTA1`'s five decoded fields are POST-`?`-unescape as of this release, and an EMPTY
|
|
84
|
-
TA1-02 / TA1-03 / TA1-04 / TA1-05 is REFUSED on emit. Both are behaviour changes
|
|
85
|
-
|
|
84
|
+
TA1-02 / TA1-03 / TA1-04 / TA1-05 is REFUSED on emit. Both are behaviour changes.**
|
|
85
|
+
They are one change because they are the two ends of the same disagreement:
|
|
86
86
|
this package's emit half releases a TA1 element and its read half decoded the escape rather than
|
|
87
87
|
the value.
|
|
88
88
|
|
|
89
|
-
**The read half.**
|
|
89
|
+
**The read half.** An earlier release made `buildTA1` release all five caller
|
|
90
90
|
elements, and `parseTA1` kept reading `elements` verbatim, so a round trip through this package's
|
|
91
91
|
own halves was not an identity for any value carrying a delimiter or the release character.
|
|
92
92
|
Measured at `0.0.15`, over `parseX12` + `parseTA1` of what `buildTA1` had just emitted:
|
|
@@ -133,12 +133,40 @@ model.
|
|
|
133
133
|
first, so an `ackCode: "A"` with an empty note still reports `X12_TA1_ACCEPT_WITH_NOTE`; TA1-01
|
|
134
134
|
still draws the control-number refusal; and every wrong-TYPED element still draws the escape
|
|
135
135
|
helper's refusal, because all five escapes run before any emptiness test. **No spec that was
|
|
136
|
-
refused before this
|
|
137
|
-
- **No census of other builders' required elements is published here.** This
|
|
136
|
+
refused before this change is refused differently by it.**
|
|
137
|
+
- **No census of other builders' required elements is published here.** This change measured TA1.
|
|
138
|
+
|
|
139
|
+
- **🩺 Every typed reader checks the implementation guide a transaction set declares against the
|
|
140
|
+
guides that reader implements, and WARNS rather than refuses where they differ.**
|
|
141
|
+
`X12_GUIDE_NOT_IMPLEMENTED` is raised where the declared guide is outside the reader's set and
|
|
142
|
+
`X12_GUIDE_NOT_DECLARED` where nothing is declared, both anchored at the ST. The reading is
|
|
143
|
+
decoded and returned either way, against the element positions of the guide the reader
|
|
144
|
+
implements, so on a warned document a value can sit at a position the declared guide defines
|
|
145
|
+
differently. What the check keys on, exactly:
|
|
146
|
+
- **The declaration is `ST-03` decoded, then `GS-08` decoded.** `ST-03` is read post-`?`-unescape,
|
|
147
|
+
exactly as `implementationConventionReference` is published. Only where `ST-03` is absent or
|
|
148
|
+
decodes to `""` is GS-08 of the enclosing functional group read, decoded the same way; a parsed
|
|
149
|
+
transaction set carries its group's GS header on `tx.gs` for this, and one assembled by hand
|
|
150
|
+
without it is read as GS-08 absent. Nothing is trimmed, case-folded or prefix-matched, so a
|
|
151
|
+
whitespace-only or lower-cased declaration is reported as not implemented.
|
|
152
|
+
- **🛑 `ST-03` and `GS-08` are NOT reconciled.** Where `ST-03` is non-empty it alone decides and
|
|
153
|
+
GS-08 is never read, so a document whose two declarations disagree is judged on `ST-03` and
|
|
154
|
+
nothing reports the disagreement.
|
|
155
|
+
- **The implemented set is `X12_TR3_CONFORMANCE`**, each of the reader's rows contributing its
|
|
156
|
+
`tr3` and every `cfrAdopted` entry. **The 837 reader also recognises every identifier its
|
|
157
|
+
variant table resolves**, which includes the companion-guide identifiers production claims carry,
|
|
158
|
+
so a conformant claim declaring one is not accused. A 278 declaring `005010X216`, the 278
|
|
159
|
+
notification guide, is warned on both 278 readers.
|
|
160
|
+
- **What the check decides is a warning, plus one label.** A 277 whose declared guide is outside
|
|
161
|
+
`get277Status`'s set, or absent, reads `transactionType: "unrecognized-guide"`. Everything else
|
|
162
|
+
that keys on `ST-03` - the 837 variant, the choice between `claim-status` and
|
|
163
|
+
`claim-acknowledgment`, and `get277CADisposition`'s admission - still keys on the raw text and
|
|
164
|
+
did not move, so the check and those decisions can read the same `ST-03` differently where a
|
|
165
|
+
release escape is involved (see the next entry).
|
|
138
166
|
|
|
139
167
|
- **🩺 `implementationConventionReference` is POST-`?`-unescape as of this release, in every typed
|
|
140
168
|
reader that publishes it, and that is a behaviour change on documents whose `ST-03` carries a
|
|
141
|
-
release escape
|
|
169
|
+
release escape.** `tx.st.elements` is the ST segment as framed:
|
|
142
170
|
post-element-split and PRE-unescape. Five public
|
|
143
171
|
readers were handing one of those strings straight back on the model - `get837Claims`,
|
|
144
172
|
`get277Status`, `get277CADisposition`, `get278Request` and `get278Response` - so a sender that
|
|
@@ -149,8 +177,8 @@ model.
|
|
|
149
177
|
publishes `A*B`, `A?:B` publishes `A:B`, `A?~B` publishes `A~B`, `A?^B` publishes `A^B`. Every one
|
|
150
178
|
of those published the framed bytes before.
|
|
151
179
|
- **🛑 What decides an outcome did NOT move, deliberately.** The 837 variant lookup, the 277 /
|
|
152
|
-
277CA `transactionType`
|
|
153
|
-
the RAW element text, so no document changes variant, discriminator or admission because of this
|
|
180
|
+
277CA `transactionType` choice between its two claim labels and `get277CADisposition`'s
|
|
181
|
+
admission gate all still key on the RAW element text, so no document changes variant, discriminator or admission because of this
|
|
154
182
|
entry. That matters because the two can differ: with `componentSeparator: "X"` - a letter is an
|
|
155
183
|
admissible delimiter - an `ST-03` framed as `005010?X222A1` decodes to `005010X222A1`, an
|
|
156
184
|
identifier the variant table holds. Keying on the decoded text would make the declaration beat
|
|
@@ -183,8 +211,8 @@ model.
|
|
|
183
211
|
`unescapeRelease` to `submission.implementationConventionReference` yourself, drop that call.
|
|
184
212
|
|
|
185
213
|
- **🩺 An EMPTY control number is REFUSED on emit as of this release, where it used to be
|
|
186
|
-
FABRICATED, and that is a behaviour change for any caller passing one
|
|
187
|
-
|
|
214
|
+
FABRICATED, and that is a behaviour change for any caller passing one.**
|
|
215
|
+
Every builder that assembles an ISA zero-pads its control
|
|
188
216
|
number to the nine characters ASC X12 .5 fixes ISA-13 at. `padControl("1", 9)` answering
|
|
189
217
|
`"000000001"` is the point of that; `padControl("", 9)` answering `"000000000"` was not, and
|
|
190
218
|
nothing stood in front of it, so `interchangeControlNumber: ""` produced a frozen, well-formed
|
|
@@ -237,7 +265,7 @@ model.
|
|
|
237
265
|
`X12_ACK_INVALID_SPEC` now. If you branch on a specific builder error code, that pairing moved.
|
|
238
266
|
|
|
239
267
|
- **🩺 Which `ST-03` implementation-convention references resolve to an 837 variant CHANGED in this
|
|
240
|
-
release, and some already-published files therefore decode differently
|
|
268
|
+
release, and some already-published files therefore decode differently.**
|
|
241
269
|
Through `0.0.13` `get837Claims` recognised exactly three references: `005010X222A2`, `005010X223A3`
|
|
242
270
|
and `005010X224A2`. **That set contained none of the identifiers HIPAA adopts at 45 CFR 162.1102**
|
|
243
271
|
(`005010X222`, `005010X223` + `005010X223A1`, `005010X224` + `005010X224A1`), **and it was missing
|
|
@@ -273,8 +301,8 @@ model.
|
|
|
273
301
|
- **The set is not claimed exhaustive and no count of it is published**, here or in any warning
|
|
274
302
|
message. Both variant messages named the three old keys literally and were wrong the moment the
|
|
275
303
|
table was corrected, so neither enumerates the set any more.
|
|
276
|
-
- **🩺 CLOSED, in the
|
|
277
|
-
|
|
304
|
+
- **🩺 CLOSED, in the change after this one: the emit side takes a caller override.**
|
|
305
|
+
`build837P` / `build837I` / `build837D` still DEFAULT to
|
|
278
306
|
`005010X222A2` / `005010X223A3` / `005010X224A2` in ST-03 and GS-08, and
|
|
279
307
|
**`Build837EnvelopeSpec.implementationConventionReference` now states another** - one value,
|
|
280
308
|
both elements. Two of the defaults are not what the companion guides above require, so a partner
|
|
@@ -301,8 +329,7 @@ model.
|
|
|
301
329
|
ST-03.
|
|
302
330
|
|
|
303
331
|
- **🩺 A `?` immediately before the element separator inside an envelope segment now frames as ONE
|
|
304
|
-
element, and that is a SYMMETRIC behaviour change on already-published decoding
|
|
305
|
-
(`X12-ENVELOPE-SPLITTER-NOT-RELEASE-AWARE`).
|
|
332
|
+
element, and that is a SYMMETRIC behaviour change on already-published decoding.**
|
|
306
333
|
Through `0.0.14` the envelope segments' element splitter was a plain `String.prototype.split`, so a
|
|
307
334
|
released element separator (`?*`) still ended the element and SHIFTED every element after it down a
|
|
308
335
|
slot. It was a property of the whole envelope segment and never of one element, so the element a
|
|
@@ -357,7 +384,7 @@ model.
|
|
|
357
384
|
`responsibleAgencyCode`) at this release. A `groupDate` of `"2026060?"` emitted
|
|
358
385
|
`GS*HC*SENDER*RECEIVER*2026060?*1200*1*X*005010X222A1`, which its own return value read as nine
|
|
359
386
|
elements with GS-08 intact through `0.0.14` and read as **eight** at `0.0.15`, GS-08 gone, plus
|
|
360
|
-
`X12_CONTROL_NUMBER_MISMATCH`. **Closed by
|
|
387
|
+
`X12_CONTROL_NUMBER_MISMATCH`. **Closed by a later release** - that
|
|
361
388
|
entry is at the top of this section and it is the one that describes the current tree.
|
|
362
389
|
|
|
363
390
|
- **An envelope element ending in a literal `?` is a dangling release character and is NOT warned.**
|
|
@@ -393,8 +420,8 @@ model.
|
|
|
393
420
|
at `0.0.14` it released GS-02 on emit and then answered GS-08 as `"X"` from its own return value.
|
|
394
421
|
|
|
395
422
|
- **🩺 A BODY segment in an interchange whose ELEMENT SEPARATOR is `?` now frames its elements,
|
|
396
|
-
where it used to come back as ONE element with an id of `(non-spec)
|
|
397
|
-
|
|
423
|
+
where it used to come back as ONE element with an id of `(non-spec)`.**
|
|
424
|
+
`detectDelimiters` reads the element separator
|
|
398
425
|
positionally out of ISA byte 4 and rejects only control characters, whitespace and a non-distinct
|
|
399
426
|
set, so a sender may declare `?` there, and `buildInterchange` accepted `elementSeparator: "?"`
|
|
400
427
|
from a caller when this was measured (it refuses now - see the emit-side entry below).
|
|
@@ -431,8 +458,8 @@ model.
|
|
|
431
458
|
of them. Splitting those two roles literally would re-frame that as two empty components, so it
|
|
432
459
|
would stop reading a value this library itself wrote. **That reason survives the emit-side
|
|
433
460
|
refusal below - those documents exist**, which is why the read side did not move with it.
|
|
434
|
-
- **🟢 CLOSED, in the
|
|
435
|
-
|
|
461
|
+
- **🟢 CLOSED, in the change after this one, and WIDER than it was filed:
|
|
462
|
+
every builder REFUSES a delimiter set in which any of
|
|
436
463
|
the four roles is `?`.** The entry as filed named the element separator and one mechanism - a
|
|
437
464
|
caller VALUE the escape cannot protect, because the protecting `?` is itself the separator. Two
|
|
438
465
|
things were measured wrong about that:
|
|
@@ -497,7 +524,7 @@ model.
|
|
|
497
524
|
terminator.** `findUnescapedTerminator` guards its own role only, so with `?` as the element
|
|
498
525
|
separator a segment that ends in an EMPTY LAST ELEMENT puts a `?` immediately before the
|
|
499
526
|
terminator and the scanner reads it as an escape: `PER?IC?NAME?TE?5551234?EX?~SE?3?0001~` frames
|
|
500
|
-
as ONE segment and raises `X12_MISSING_SE`. This
|
|
527
|
+
as ONE segment and raises `X12_MISSING_SE`. This change does not touch framing. But **the READ of
|
|
501
528
|
that merged blob did move, so do not take "framing is untouched" as "nothing about this residual
|
|
502
529
|
moved"**: at `0.0.15` the merge produced one `(non-spec)` element no walker looked at, and here
|
|
503
530
|
it frames, so `~SE` and the SE's own control number land in `PER`'s communication-number slots.
|
|
@@ -510,8 +537,8 @@ model.
|
|
|
510
537
|
gives: it is fixed-width, which is what lets the delimiters be recovered from it at all.
|
|
511
538
|
|
|
512
539
|
- **🩺 `buildInterchange` now RELEASES GS-04, GS-05 and GS-07, so the interchange it hands back
|
|
513
|
-
reports the values you passed, and the bytes it writes for such a value CHANGED
|
|
514
|
-
|
|
540
|
+
reports the values you passed, and the bytes it writes for such a value CHANGED.**
|
|
541
|
+
Through `0.0.15` it mapped its release escaper over
|
|
515
542
|
GS-01, GS-02, GS-03, GS-06 and GS-08 and wrote `groupDate` (GS-04), `groupTime` (GS-05) and
|
|
516
543
|
`responsibleAgencyCode` (GS-07) raw. It returns `parseX12` of the bytes it just wrote, so a value
|
|
517
544
|
carrying an active delimiter in one of those three took a slot of its own and shifted every element
|
|
@@ -572,8 +599,8 @@ model.
|
|
|
572
599
|
active delimiter is still not safe anywhere, because that is what a delimiter is.
|
|
573
600
|
|
|
574
601
|
- **🩺 `buildTA1` now RELEASES its five caller-supplied elements, so an Accept acknowledgment this
|
|
575
|
-
library emits no longer reads back as a Reject, and the bytes it writes for such a value CHANGED
|
|
576
|
-
|
|
602
|
+
library emits no longer reads back as a Reject, and the bytes it writes for such a value CHANGED.**
|
|
603
|
+
Through `0.0.14` `buildTA1` joined the five values with the
|
|
577
604
|
element separator and escaped none of them, so a value carrying an active delimiter took a slot of
|
|
578
605
|
its own and shifted every element after it down one. TA1-04 is the disposition and TA1-05 the note,
|
|
579
606
|
and `parseTA1` narrows an out-of-enum TA1-04 to `R`. Measured with `parseX12` + `parseTA1` over
|
|
@@ -624,19 +651,19 @@ model.
|
|
|
624
651
|
No total is published: that is what was measured, not a closed account. (`getSegmentValue`
|
|
625
652
|
takes an `X12Segment` and `Ta1Segment` carries no `id`, so add one to read a TA1 through it.)
|
|
626
653
|
**🩺 A clause naming `parseTA1`'s fields as a third such surface stood here and is DELETED, not
|
|
627
|
-
reworded** -
|
|
654
|
+
reworded** - a later release made those fields post-unescape, so it is measured false.
|
|
628
655
|
- **A caller who was hand-rolling the escape** (the remedy this file named while the defect was
|
|
629
656
|
open) regresses on both kinds of surface: `"00000001??"` in, `TA1*00000001????*…` out, and
|
|
630
657
|
`getSegmentValue` answering `"00000001??"` where it answered `"00000001?"`. The framing and the
|
|
631
658
|
disposition stay correct, but **drop the hand-rolled escape.**
|
|
632
|
-
- **An EMPTY control number was not refused by this
|
|
659
|
+
- **An EMPTY control number was not refused by this change, and is refused by the NEXT one.**
|
|
633
660
|
`escapeRelease` early-returns on `""` and `buildTA1` carried no required-field guard, so
|
|
634
661
|
`interchangeControlNumber: ""` emitted `TA1**260601*1200*A*000` with `warnings: []`, here and at
|
|
635
|
-
every earlier release.
|
|
662
|
+
every earlier release. A later release closed it across every builder; see
|
|
636
663
|
the entry at the top of this file, including the whitespace-only residual it leaves open.
|
|
637
|
-
- **🩺 The READ half did not move IN THIS
|
|
664
|
+
- **🩺 The READ half did not move IN THIS CHANGE, and a later release is the one that moved
|
|
638
665
|
it** - see the entry at the top of this file. A control number of `"00000001?"` read back as
|
|
639
|
-
`"00000001?*260601"` before
|
|
666
|
+
`"00000001?*260601"` before the EMIT half moved and as `"00000001??"` after it; the clause that stood
|
|
640
667
|
here telling callers to apply `unescapeRelease` themselves is **deleted, not reworded**, because
|
|
641
668
|
the library now does it.
|
|
642
669
|
- **The release is scoped to the delimiter set the CALLER states.** `BuildTA1Options` gained
|
|
@@ -657,12 +684,12 @@ model.
|
|
|
657
684
|
`X12_TA1_ACCEPT_WITH_NOTE` and still runs first.
|
|
658
685
|
- **What this does NOT close.** `buildTA1` still uses no segment joiner, so its refusal names the
|
|
659
686
|
builder and never the slot; `buildInterchange` did not escape GS-04 / GS-05 / GS-07 at this
|
|
660
|
-
release, which
|
|
687
|
+
release, which a later one then closed; and an unescaped active
|
|
661
688
|
delimiter is still not safe anywhere, because that is what a delimiter is.
|
|
662
689
|
|
|
663
690
|
- **🩺 BREAKING for `build277` callers: a 277 service line now REQUIRES `unitsOfService`, because
|
|
664
|
-
SVC-07 is a required element in `005010X212` and this library was not emitting it at all
|
|
665
|
-
|
|
691
|
+
SVC-07 is a required element in `005010X212` and this library was not emitting it at all.**
|
|
692
|
+
Through the release before this one `get277Status` read SVC-01
|
|
666
693
|
through SVC-04 and stopped, and `build277` emitted exactly those four, so **every X212 277 this
|
|
667
694
|
library produced with a Loop 2220 service line was short a required element** and every 277 it read
|
|
668
695
|
silently discarded the submitted units. Both sides are fixed: `X12ServiceLineStatus.unitsOfService`
|
|
@@ -696,7 +723,7 @@ model.
|
|
|
696
723
|
string** - at base the same spec emitted a bare `SVC~`, because the SVC-07 slot did not exist. `SVC-03` (Line Item Payment
|
|
697
724
|
Amount) is usage **R** in X212 and usage **N** in X214 and is optional in both builders. And the
|
|
698
725
|
read side is unchanged and still lenient: an X212 277 arriving with no SVC-07 raises **no
|
|
699
|
-
warning**, because that would need a new Tier-2 registry code and the defect this
|
|
726
|
+
warning**, because that would need a new Tier-2 registry code and the defect this change closes is
|
|
700
727
|
on the emit side. All of it is pre-existing and reproduces at `e3cdf49`; widening the guard would
|
|
701
728
|
have turned this into that audit, which is its own item.
|
|
702
729
|
|
|
@@ -1242,7 +1269,7 @@ Code"`, `SVC05 / 380 / "Units of Service Paid Count"`, `SVC06 / C003`,
|
|
|
1242
1269
|
|
|
1243
1270
|
**`undefined` on either quantity still means "not decoded", not "absent".** The element may have
|
|
1244
1271
|
been missing, or present and unparseable as a decimal; the two share one `undefined` on the model.
|
|
1245
|
-
They no longer share a silence: as of
|
|
1272
|
+
They no longer share a silence: as of an earlier release the unparseable case raises
|
|
1246
1273
|
`X12_UNPARSEABLE_DECIMAL` at that `position.elementIndex` and the absent case raises nothing, so a
|
|
1247
1274
|
warning at the element is what tells them apart. The verbatim element is still on `tx.segments`.
|
|
1248
1275
|
|
|
@@ -1252,7 +1279,7 @@ Code"`, `SVC05 / 380 / "Units of Service Paid Count"`, `SVC06 / C003`,
|
|
|
1252
1279
|
no warning. Re-emitting from the model with this release produces conformant bytes.
|
|
1253
1280
|
|
|
1254
1281
|
**The 277's SVC-07 is a different element in a different TR3 and is now decoded and emitted in its
|
|
1255
|
-
own right** (
|
|
1282
|
+
own right** (see the 277 entry below). Do not read either field off the other.
|
|
1256
1283
|
|
|
1257
1284
|
- **Bundled code-list snapshots are pre-launch initial subsets, not the full WPC-published lists.**
|
|
1258
1285
|
CARC, RARC, Claim-Status-Category (CSCC), Claim-Status (CSC), service-type, CLP-status, and
|
|
@@ -1260,9 +1287,11 @@ Code"`, `SVC05 / 380 / "Units of Service Paid Count"`, `SVC06 / C003`,
|
|
|
1260
1287
|
the long-tail codes most workflows branch on. An inbound code **outside** a snapshot still parses:
|
|
1261
1288
|
the verbatim code is preserved on the model and an `X12_UNKNOWN_*` warning is raised. Only the
|
|
1262
1289
|
human-readable **description** is absent (`undefined`). A stale or partial snapshot therefore yields
|
|
1263
|
-
a missing description, **never a wrong code**.
|
|
1264
|
-
|
|
1265
|
-
|
|
1290
|
+
a missing description, **never a wrong code**. Every snapshot states which it is: `meta.completeness`
|
|
1291
|
+
is `"cited-subset"` on all seven today, so a lookup miss means this package does not carry the code
|
|
1292
|
+
and never that the publisher did not issue it. Run `pnpm refresh:code-lists` to audit snapshot
|
|
1293
|
+
freshness; regenerating the full lists (`--fetch`) is a release step gated per list on that list's
|
|
1294
|
+
own recorded redistribution terms (see below), not a runtime fetch.
|
|
1266
1295
|
|
|
1267
1296
|
- **`serialize(parse(s)) === s` is NOT guaranteed.** `serializeX12` rebuilds the interchange from the
|
|
1268
1297
|
model, so every segment the parser recorded comes back verbatim (element padding, composites, and
|
|
@@ -1329,14 +1358,15 @@ Code"`, `SVC05 / 380 / "Units of Service Paid Count"`, `SVC06 / C003`,
|
|
|
1329
1358
|
context and anchor), `ta1Segments`, `trailingBytes` and the warning multiset are unchanged by the
|
|
1330
1359
|
round trip, and every emit is a fixed point.
|
|
1331
1360
|
|
|
1332
|
-
What is **measured** across
|
|
1361
|
+
What is **measured** across every committed fixture: each emit is a fixed point (serializing it
|
|
1333
1362
|
again is a byte-level no-op) and re-parses to an identical model with an identical warning stream;
|
|
1334
|
-
the
|
|
1363
|
+
the fixtures with no line breaks return byte-identical; and the rest differ from their source
|
|
1335
1364
|
by **line breaks and nothing else**, with no element value lost, altered, reordered, or re-escaped.
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1365
|
+
No corpus size is quoted: it moves with every fixture added, and `test/serialize.test.ts`
|
|
1366
|
+
re-derives the sweep from the tree on every run. Two caveats on that corpus, both of which limit
|
|
1367
|
+
how far the sweep can be pushed: it contains **no instance of cases 2 to 6** (zero fixtures produce
|
|
1368
|
+
an `orphanSegments` entry, which is why the orphan sweep above is constructed rather than drawn
|
|
1369
|
+
from it), and **all but one of the byte-identical fixtures are `golden/*.edi`**, which
|
|
1340
1370
|
are serializer output by construction, leaving `envelope/no-trailing-crlf.edi` as the only
|
|
1341
1371
|
independent witness. Preserving the original framing byte-for-byte would need the model to carry
|
|
1342
1372
|
per-segment framing and TA1 position it does not have today; that is a tracked model change, not a
|
|
@@ -1509,7 +1539,7 @@ N-char spec limit` refusal, one per emitting module, where the branch fires **be
|
|
|
1509
1539
|
**Read the "through a builder's segment joiner" qualifier literally.** `buildTA1` does not use one,
|
|
1510
1540
|
and is therefore **not covered by the segment guard**. Through `0.0.14` that meant nothing checked
|
|
1511
1541
|
its five elements at all, and a numeric or `undefined` `interchangeControlNumber` was emitted
|
|
1512
|
-
silently (`TA1**250101*1200*A*000`);
|
|
1542
|
+
silently (`TA1**250101*1200*A*000`); a later release closed that by routing them
|
|
1513
1543
|
through the escape helper, which type-checks first and refuses with `AckBuildError` /
|
|
1514
1544
|
`X12_ACK_INVALID_SPEC`. What being outside the segment guard still costs is the **slot** in the
|
|
1515
1545
|
refusal message: it names `buildTA1`, never `TA1-01`.
|
|
@@ -1579,12 +1609,14 @@ N-char spec limit` refusal, one per emitting module, where the branch fires **be
|
|
|
1579
1609
|
|
|
1580
1610
|
- **A forged non-array in a builder spec refuses; in a few places it throws an untyped `TypeError`.**
|
|
1581
1611
|
The types say `readonly T[]`, but a JavaScript or JSON caller can hand a builder something else. As
|
|
1582
|
-
of `0.0.6` every indexed loop in every builder takes its bound from a checked array
|
|
1583
|
-
|
|
1612
|
+
of `0.0.6` every indexed loop in every builder takes its bound from a checked array, so an object
|
|
1613
|
+
like `{ length: "9".repeat(120000) }` draws that builder's own typed
|
|
1584
1614
|
refusal - previously the length coerced to `Infinity` and the builder **looped forever instead of
|
|
1585
|
-
refusing**.
|
|
1586
|
-
|
|
1587
|
-
`
|
|
1615
|
+
refusing**. No loop or probe count is quoted here: both move with every builder added, and
|
|
1616
|
+
`test/builder-array-bounds.test.ts` re-derives them from the source on every run. Measured over the
|
|
1617
|
+
probes that suite shipped at the `0.0.6` base: most **hung** with no refusal and the rest threw an
|
|
1618
|
+
untyped `TypeError`. Each of those probes now refuses with a typed, coded error, except the
|
|
1619
|
+
`for…of` reads below, which still throw the untyped `TypeError`.
|
|
1588
1620
|
|
|
1589
1621
|
**`null` is treated as absent, not forged**, exactly as the `?? []` this replaced did, so an
|
|
1590
1622
|
optional list you send as `null` still builds. On a required list `null` draws that builder's own
|
|
@@ -1594,11 +1626,24 @@ N-char spec limit` refusal, one per emitting module, where the branch fires **be
|
|
|
1594
1626
|
|
|
1595
1627
|
Not covered, and disclosed rather than fixed: the places a builder reads a caller array with
|
|
1596
1628
|
`for…of` - `buildInterchange`'s `spec.groups`, `build999`'s `functionalGroup.transactionResponses`,
|
|
1597
|
-
and
|
|
1629
|
+
and the optional leaf arrays such as `claim.dates` or `member.references`. Those throw
|
|
1598
1630
|
`TypeError: … is not iterable` immediately. They terminate, so they are not the hang, but they carry
|
|
1599
1631
|
**no `code`**, so `err.code` is `undefined` and you cannot branch on it. Validate your spec shape at
|
|
1600
1632
|
your own boundary if it comes from JSON.
|
|
1601
1633
|
|
|
1634
|
+
**`build270` is the exception, and it is deliberately stricter than its siblings.** EVERY list slot
|
|
1635
|
+
on `Build270Spec` - the spine (`informationSources`, `receivers`, `subscribers`, `dependents`,
|
|
1636
|
+
`inquiries`, `serviceTypeCodes`) and the leaves (`traces`, `references`, `dates`, `address.lines`,
|
|
1637
|
+
`diagnosisCodePointers`, `procedure.modifiers`) alike - is read through the same checked-array
|
|
1638
|
+
chokepoint, so a forged array-like anywhere in a 270 spec draws a typed, code-tagged
|
|
1639
|
+
`Eligibility270BuildError` rather than an untyped `TypeError`. **The same slots are checked for
|
|
1640
|
+
HOLES at the same point**, because a real array with `undefined` or `null` sitting in a slot is a
|
|
1641
|
+
list and clears the chokepoint: that is the shape a JSON payload with a dropped record carries, and
|
|
1642
|
+
unguarded it reached an emitter and threw the untyped `TypeError` this whole section is about. A
|
|
1643
|
+
hole refuses wherever it stands, naming the slot and nothing out of the inquiry. A name loop sent
|
|
1644
|
+
as `null` is refused the same way, at every level. The other builders are unchanged: on those, a
|
|
1645
|
+
hole in a list still reaches whatever dereferences it.
|
|
1646
|
+
|
|
1602
1647
|
**One qualification worth stating precisely: on the acknowledgment path the value is not always
|
|
1603
1648
|
strictly your own.** TR3 005010X231A1 requires AK2-02 to be a verbatim copy of the acknowledged
|
|
1604
1649
|
transaction set's ST-02, and `buildTA1` exists to echo an inbound ISA-13, so a _document's_ control
|
|
@@ -1645,41 +1690,394 @@ N-char spec limit` refusal, one per emitting module, where the branch fires **be
|
|
|
1645
1690
|
mismatch and preserve the inbound values verbatim. The library will not "fix" a payer artifact for
|
|
1646
1691
|
you. Gate your own posting/adjudication on the warning.
|
|
1647
1692
|
|
|
1648
|
-
##
|
|
1649
|
-
|
|
1650
|
-
- **
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1693
|
+
## Trading-partner profiles: what a conformance classification is, and is not
|
|
1694
|
+
|
|
1695
|
+
- **A profile never changes what this library emits.** A profile describes what a trading partner
|
|
1696
|
+
SENDS. It attaches attribution to a parse and partitions that parse's warnings into expected and
|
|
1697
|
+
unexpected, and that is the whole of its behaviour. It cannot loosen, tighten or otherwise touch
|
|
1698
|
+
the emit side: `serializeX12`, `buildInterchange` and every `build*` domain builder produce
|
|
1699
|
+
byte-identical output with any profile active and with none, and refuse the same specs with the
|
|
1700
|
+
same error type, code and message. There is no opt-in flag that would change this, and
|
|
1701
|
+
`test/profiles-emit-independence.test.ts` fails the build if the property is ever broken. The
|
|
1702
|
+
reason it is a locked invariant rather than a convention: a profile that widened the emit would
|
|
1703
|
+
put a non-conformant document on the wire, and 45 CFR 162.915 forbids the trading partner from
|
|
1704
|
+
having asked for that in the first place.
|
|
1705
|
+
|
|
1706
|
+
- **Each quirk's conformance classification is a RECORDED HUMAN JUDGEMENT, not a check this
|
|
1707
|
+
library performed.** `describe()` states, per quirk, whether the deviation is one a trading partner
|
|
1708
|
+
may lawfully require. Read that as documentation someone wrote down, and confirm it against your
|
|
1709
|
+
own copy of the relevant implementation guide before you rely on it. In particular, **a
|
|
1710
|
+
classification that turns on an element being marked "not used" in an adopted TR3 rests on someone
|
|
1711
|
+
having read that TR3.** This package bundles none: 45 CFR 162.920(a) states a fee is charged for
|
|
1712
|
+
the implementation specifications, so "is this element marked not used?" is not a question the
|
|
1713
|
+
library can answer at run time, and it does not pretend to. Nothing in the classification is
|
|
1714
|
+
derived from your document, from the quirk's `effect`, or from any code list shipped here.
|
|
1715
|
+
|
|
1716
|
+
- **The axis is 45 CFR 162.915, and it has three states, not two.** `permitted` and `not-permitted`
|
|
1717
|
+
are measured against that rule, which forbids a covered entity from entering a trading partner
|
|
1718
|
+
agreement that would change the definition, data condition or use of a data element or segment in
|
|
1719
|
+
a standard, add data elements or segments to the maximum defined data set, use any code or data
|
|
1720
|
+
element marked "not used" in (or absent from) the standard's implementation specification, or
|
|
1721
|
+
change the meaning or intent of that implementation specification. The third state,
|
|
1722
|
+
`undetermined`, means **no judgement was recorded**, and it is the fail-safe default: a quirk that
|
|
1723
|
+
says nothing about conformance renders as `undetermined`, never as `permitted`. Read
|
|
1724
|
+
`undetermined` as "this package makes no claim", which is not the same as "harmless". Two of the
|
|
1725
|
+
three quirks shipped by the built-in profiles are `undetermined` today for exactly the fee reason
|
|
1726
|
+
above.
|
|
1727
|
+
|
|
1728
|
+
- **A `requires` quirk is a record of what a partner MANDATES, never a claim that the standard
|
|
1729
|
+
supports the mandate.** Those are two different statements and the buckets do not conflate them:
|
|
1730
|
+
membership of `requires` confers nothing on the conformance axis, and a partner mandating an
|
|
1731
|
+
element the adopted guide marks "not used" classifies as a deviation from the standard. If you
|
|
1732
|
+
need to know whether your partner may lawfully require something, read `conformance`, not the
|
|
1733
|
+
effect bucket.
|
|
1734
|
+
|
|
1735
|
+
## What the differential corpus proves, and what it does not
|
|
1736
|
+
|
|
1737
|
+
- **Every synthetic document this repository ships is read by this library and, where an independent
|
|
1738
|
+
reader maps its transaction, by that reader too, and the two readings are compared element
|
|
1739
|
+
position by element position.** Two independent, open-source X12 readers serve as oracles, each
|
|
1740
|
+
pinned to one exact version and used only as a development tool: neither is ever a dependency of
|
|
1741
|
+
this package, whose published runtime dependency set is empty. The first is pyx12, a BSD-licensed
|
|
1742
|
+
HIPAA X12 parser and validator. The second is LinuxForHealth x12, an Apache-2.0-licensed X12
|
|
1743
|
+
reader with a transaction model per implementation guide, pinned together with every package it
|
|
1744
|
+
runs on and the Python version it runs under, because its own metadata leaves the Pydantic release
|
|
1745
|
+
it needs free. A transaction is compared against the first oracle that maps it and against no
|
|
1746
|
+
other. `pnpm run differential` runs the comparison and writes `test/differential/report.json`,
|
|
1747
|
+
which carries each oracle's name, version and licence as the installed reader declares them (for
|
|
1748
|
+
the second, also the Python version and every companion package version it ran with), the commit
|
|
1749
|
+
this library was read from, which oracle each transaction was compared against with the
|
|
1750
|
+
implementation guide and the map or model that oracle bound it to, the documents that went through
|
|
1751
|
+
both readers, the number of element positions compared, every document the run looked at and did
|
|
1752
|
+
not compare with the reason, and every disagreement found, with both readings left exactly as each
|
|
1753
|
+
reader returned them. `pnpm run differential:check` runs the same comparison and succeeds only when
|
|
1754
|
+
it reproduces the committed report. Which comparison a document belongs to is decided by the
|
|
1755
|
+
document's own GS-08 and ST-01, never by where it is filed, so the awkward documents are in the
|
|
1756
|
+
corpus rather than beside it.
|
|
1757
|
+
- **Which reader compares what.** pyx12 compares the 277 claim acknowledgment (005010X214), the 820,
|
|
1758
|
+
the 834, the 835, the professional claim, the institutional claim and the 999. LinuxForHealth x12
|
|
1759
|
+
compares the 270, the 271, the 276 and the 277 claim status response (005010X212), for which pyx12
|
|
1760
|
+
publishes no 005010 implementation guide map. LinuxForHealth x12 also models the 834, the 835 and
|
|
1761
|
+
both claims, and those are still compared against pyx12 alone: no transaction here is read by both
|
|
1762
|
+
oracles, so no row carries two readers' agreement.
|
|
1763
|
+
- **🩺 The second oracle reads a document only through its transaction model for the guide the
|
|
1764
|
+
document declares, and that model refuses much of this corpus, so its comparisons are narrower than
|
|
1765
|
+
the documents filed under them.** A document the model refuses is recorded in the report as
|
|
1766
|
+
unevaluated, with the oracle and the kind of refusal, and never counted as agreement. Among the
|
|
1767
|
+
documents here that it refuses are the ones that deliberately break the hierarchy or a row, 271
|
|
1768
|
+
documents whose AAA rejection reasons fall outside the codes it enumerates for that loop, the 270
|
|
1769
|
+
and 271 documents carrying a dependent level, and the 276 documents whose subscriber carries a
|
|
1770
|
+
dependent level beside a claim of its own, as the canonical 276 fixture does. A model can also
|
|
1771
|
+
accept a document and set one of its segments aside without raising, so a document is compared only
|
|
1772
|
+
when the model kept every segment of every transaction set, ST through SE, that the oracle framed
|
|
1773
|
+
for it; one whose model kept fewer is recorded as unevaluated with its own refusal kind, and none of
|
|
1774
|
+
its positions is counted as agreement. 🩺 The two readers place the 276 subscriber's demographics
|
|
1775
|
+
segment (DMG) differently: that model reads it ahead of the subscriber's name and this library
|
|
1776
|
+
after it. A document carrying one where this library puts it loses it to that model, which then
|
|
1777
|
+
refuses the document; and this library's 276 reader keeps a DMG only after the name already read at
|
|
1778
|
+
its level, so one sent ahead of the name is not on the decoded inquiry and no warning says so. Which
|
|
1779
|
+
position the implementation guide specifies is not settled here, and nothing about either reader
|
|
1780
|
+
moved to produce this. So the one 276 compared against it carries no subscriber DMG. Agreement on the
|
|
1781
|
+
270, the 271, the 276 and the 277 claim status response is agreement over the documents that model
|
|
1782
|
+
accepted whole, which the report lists by name, and nothing wider: in particular no document
|
|
1783
|
+
compared against it carries a dependent level, and no 276 compared against it carries a DMG, so that
|
|
1784
|
+
comparison says nothing about either.
|
|
1785
|
+
- **🩺 This library and pyx12 disagree about the release character, and the report records the
|
|
1786
|
+
disagreement rather than settling it.** One synthetic document escapes the element separator with
|
|
1787
|
+
`?` inside a transaction set body. This library reads `?` as a release character there, as it has
|
|
1788
|
+
on every released version, so `REF*EA*ID?*WITH?*STAR` is two elements whose second value is
|
|
1789
|
+
`ID*WITH*STAR`; pyx12 reads no release character, so the same segment is four elements whose second
|
|
1790
|
+
value is `ID?`. 005010 transmits no release character at all and nothing in it picks between the
|
|
1791
|
+
two readings, which is the same ambiguity the envelope-splitter entry above is about, and it is the
|
|
1792
|
+
one input class in this corpus on which two conforming readers can genuinely differ. **The
|
|
1793
|
+
disagreement is published, not resolved:** the report holds both readings verbatim for that
|
|
1794
|
+
position, nothing about how this library reads the release character moved to produce it, and
|
|
1795
|
+
`pnpm run differential` exits non-zero for as long as it stands. If you exchange documents that
|
|
1796
|
+
escape a delimiter in a body element, do not assume your counterparty's parser frames that segment
|
|
1797
|
+
the way this one does.
|
|
1798
|
+
- **The rows of this library's read scope that neither oracle maps are compared against nothing, and
|
|
1799
|
+
the report names every one with the reason.** Neither reader publishes a 005010 implementation
|
|
1800
|
+
guide map or model for the 278 in either direction or for the dental claim, neither has one for the
|
|
1801
|
+
275 (006020X314) or the 277 request for additional information (006020X313), and neither can bind
|
|
1802
|
+
the TA1 interchange acknowledgment, which sits in the envelope rather than in a functional group
|
|
1803
|
+
and carries no implementation guide identifier. Read that as a statement about the maps and models
|
|
1804
|
+
those readers publish and not about what they can frame: what is missing is the implementation
|
|
1805
|
+
guide, not the ability to read bytes. Either way this library's reading of those transactions is
|
|
1806
|
+
compared against nothing here, and silence about them is silence, never agreement.
|
|
1807
|
+
- **For two of the compared transactions the two implementations target different published errata of
|
|
1808
|
+
the same implementation guide, and the report records both identifiers rather than implying one.**
|
|
1809
|
+
pyx12 maps an earlier errata of the professional claim and of the institutional claim than this
|
|
1810
|
+
library implements, so agreement there is agreement between two revisions of one guide.
|
|
1811
|
+
- **Agreement with either reader is not conformance to a Technical Report Type 3, and it is not a
|
|
1812
|
+
compliance statement.** Two parsers can agree and both be wrong about the same clause; neither
|
|
1813
|
+
reader is the standard, and each transaction here is compared against one of them, never both. No
|
|
1814
|
+
report produced here says anything about HIPAA compliance, which is a property of a deployment and
|
|
1815
|
+
never of a library. Conformance still rests on the three-tier synthetic corpus (spec-clean,
|
|
1816
|
+
vendor-quirk, round-trip goldens), property and round-trip tests, and a nightly amplified byte-flip
|
|
1817
|
+
fuzz job. Do not assume byte-for-byte agreement with any specific vendor parser.
|
|
1818
|
+
- **The corpus is this repository's own synthetic fixtures and nothing else.** No CMS Medicare 835
|
|
1819
|
+
public sample material is in it: the redistribution-terms review of that material is not cleared,
|
|
1820
|
+
and it is not used here until it is. What that bounds is the evidence, not the claim: two readers
|
|
1821
|
+
agreeing over synthetic documents have agreed over synthetic documents.
|
|
1656
1822
|
|
|
1657
1823
|
## Scope (non-goals for v1)
|
|
1658
1824
|
|
|
1659
|
-
- **Healthcare HIPAA 005010
|
|
1660
|
-
|
|
1661
|
-
|
|
1825
|
+
- **Healthcare HIPAA 005010, and two 006020 guides: the claims attachments pair.** Beside the
|
|
1826
|
+
005010 transaction sets, the package reads and builds the 277 Health Care Claim Request for
|
|
1827
|
+
Additional Information (`006020X313`) and the 275 Additional Information to Support a Health Care
|
|
1828
|
+
Claim or Encounter (`006020X314`), which 45 CFR 162.920(a)(20) and (a)(19) name and 45 CFR
|
|
1829
|
+
162.2002 adopts for the period on and after May 26, 2028. No other 006020 guide is read or built:
|
|
1830
|
+
the services review pair the same rule proposed and did not adopt, the 278 `006020X315` and the
|
|
1831
|
+
275 Additional Information to Support a Health Care Services Review `006020X316`, is out of
|
|
1832
|
+
scope, and a 275 declaring `006020X316` is read as a guide the 275 reader does not implement.
|
|
1833
|
+
Non-healthcare transaction sets
|
|
1834
|
+
(850/856/810/204, etc.), the EDIFACT syntax family, and pre-005010 versions are out of v1 scope.
|
|
1835
|
+
Pre-005010 input is tolerated and flagged (`X12_PRE_005010`), not decoded to those older field
|
|
1836
|
+
maps. What the attachments pair leaves open, stated here beside it:
|
|
1837
|
+
- **The base X12 006020 transaction sets are typed; no implementation-guide usage is asserted.**
|
|
1838
|
+
Both guides are sold and no source this package carries states which segments, qualifiers or
|
|
1839
|
+
codes they make required, situational or repeatable. So the readers carry every code and
|
|
1840
|
+
qualifier verbatim and decide nothing about whether it is valid for the guide, and the builders
|
|
1841
|
+
write every BHT code, HL level and child code, entity, reference and date qualifier exactly as
|
|
1842
|
+
given and supply none. `build277RequestForAdditionalInformation` refuses only the absences the
|
|
1843
|
+
base 006020 277 makes mandatory (no level, no claim-level request, a request with no TRN, an
|
|
1844
|
+
empty C043-01 or C043-02, a service line with no SVC); `build275` refuses only what would make a
|
|
1845
|
+
BDS-02 untrue or a BDS empty (no attachment, empty data, a filter code that is not three
|
|
1846
|
+
characters, a character above U+00FF). A document conformant to either guide can carry a segment
|
|
1847
|
+
or qualifier these models leave untyped: the 277's SBR, PAT, DMG, heading name loop, PWK loops
|
|
1848
|
+
and TOO, the 275's DTM, IN1, DMG, PRV, PER, NX1 and each line's NM1, HI, SVC, DTP, CAT, PID and
|
|
1849
|
+
OOI. They stay verbatim on the transaction set, and neither builder writes them: `build275`
|
|
1850
|
+
writes each BDS directly after its line's LX, TRN, STC and REF, with no DTP, CAT or OOI before
|
|
1851
|
+
it, so a trading partner whose guide places the BDS inside those loops needs them added by hand
|
|
1852
|
+
through `buildInterchange`.
|
|
1853
|
+
- **The 277 request reader validates no hierarchy.** Each HL is carried with HL-01, HL-02, HL-03
|
|
1854
|
+
and HL-04 as sent; no parent pointer is checked and no level code is expected, because no
|
|
1855
|
+
carried source states the guide's hierarchy. A claim-level segment sent before the first HL
|
|
1856
|
+
reaches no level and stays on the transaction set.
|
|
1857
|
+
- **ISA-12 is not decided here.** Which ISA-12 a 006020 interchange carries is an open question no
|
|
1858
|
+
carried source answers. Both builders write this package's default, `00501`, unless the caller
|
|
1859
|
+
supplies `interchangeControlVersion`, and both readers key on ST-03 and GS-08 and never on
|
|
1860
|
+
ISA-12.
|
|
1861
|
+
- **`06020X314` is not an adopted identifier.** 45 CFR 162.2002(c) prints the 275's guide one digit
|
|
1862
|
+
short, as `06020X314`, where 162.920(a)(19) prints `006020X314`. The conformance row adopts the
|
|
1863
|
+
162.920 spelling and records the other on its note as a typographical variant, so a 275 declaring
|
|
1864
|
+
`06020X314` is warned `X12_GUIDE_NOT_IMPLEMENTED`.
|
|
1865
|
+
- **Only the attachment data refuses to print.** A 275 attachment's BDS-03 is held in
|
|
1866
|
+
`X12AttachmentData`, whose string, JSON and `util.inspect` forms carry its octet count and no
|
|
1867
|
+
octet. Every other field of both readings, the names, member and claim identifiers and trace
|
|
1868
|
+
numbers included, is a plain string, as on every other reading in this package.
|
|
1869
|
+
- **An attachment is octets, and a string is not.** Pass an interchange carrying attachments to
|
|
1870
|
+
`parseX12` as a `Buffer`, which is read one character per octet. `serializeX12` of a built 275
|
|
1871
|
+
is a string of one character per octet: write it as latin1 (`Buffer.from(text, "latin1")`),
|
|
1872
|
+
because encoding a character above U+007F as UTF-8 makes two octets of one and BDS-02 stops being
|
|
1873
|
+
true of what was sent. Nothing inside an attachment is decoded; BDS-01 names a filter the sender
|
|
1874
|
+
applied and this package applies none.
|
|
1662
1875
|
- **No transport.** AS2, SFTP, and MLLP-style delivery are out of scope. This is a parser/serializer,
|
|
1663
1876
|
not a communications stack.
|
|
1664
1877
|
- **Published, still pre-alpha.** The package is published on npm as `@cosyte/x12` from a public
|
|
1665
1878
|
repo, but it stays on the `0.0.x`-until-first-alpha ladder. `npm view @cosyte/x12 version` is the
|
|
1666
1879
|
only source of truth for the current version, so this page does not restate one. Treat the API as
|
|
1667
1880
|
pre-alpha and pin the exact version until the first alpha.
|
|
1668
|
-
- **
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1881
|
+
- **A 276 hierarchical level whose declared parent does not resolve is left off the returned tree.**
|
|
1882
|
+
The 276 reader attaches a level by its own HL-02 and by nothing else, so a pointer naming a level
|
|
1883
|
+
that is not present, a pointer naming a level of the wrong kind, and a parent chain that returns
|
|
1884
|
+
to itself each leave that level, and everything transmitted beneath it, absent from the model. The
|
|
1885
|
+
loss is reported (`X12_276_LEVEL_DETACHED`, beside the code for the pointer defect itself), the
|
|
1886
|
+
declared pointer stays verbatim on `hierarchies`, and the segments stay verbatim on the
|
|
1887
|
+
transaction set. Nothing is re-parented onto whichever level happened to be open, because that
|
|
1888
|
+
would be this library inventing the one structure a 276 exists to state: which provider is asking
|
|
1889
|
+
about which patient's claim.
|
|
1890
|
+
- **A 276 REF, DTP or AMT short of what its row is built from loses the whole row, and says so.**
|
|
1891
|
+
Each is a record and not a slot. Without both REF-01 and REF-02 there is no reference row; without
|
|
1892
|
+
both DTP-01 and DTP-03 there is no date row, and the format qualifier that says single date or
|
|
1893
|
+
range goes with it; without a decodable AMT-02 there is no amount row. The reader builds none,
|
|
1894
|
+
fabricates nothing to stand in, and reports the loss (`X12_276_REFERENCE_ROW_DROPPED`,
|
|
1895
|
+
`X12_276_DATE_ROW_DROPPED`, `X12_AMOUNT_ROW_DROPPED`) at that segment. **The report is bounded to
|
|
1896
|
+
the row the reader tried to build.** A segment that decoded and then found no claim or service
|
|
1897
|
+
line open to sit on is a different loss, it stays silent, and it is not on any of those channels.
|
|
1898
|
+
- **The 276 reader surfaces four of the SVC elements and no postal address.** SVC-01, SVC-02, SVC-04
|
|
1899
|
+
and SVC-07 reach the typed service line; SVC-03, SVC-05 and SVC-06 are left unread, because this
|
|
1900
|
+
is the request direction and a submitter states what it billed rather than what was paid. An N3 or
|
|
1901
|
+
N4 a sender transmits under a 276 name loop reaches no typed field either, as it reaches none on
|
|
1902
|
+
the 277 beside it. All of them stay verbatim on `tx.segments`; read them there.
|
|
1903
|
+
- **A 270 hierarchical level whose declared parent does not resolve is left off the returned tree.**
|
|
1904
|
+
The 270 reader attaches a level by its own HL-02 and by nothing else, so a pointer naming a level
|
|
1905
|
+
that is not present, a pointer naming a level of the wrong kind, and a parent chain that returns to
|
|
1906
|
+
itself each leave that level, and everything transmitted beneath it, absent from the model. The
|
|
1907
|
+
loss is reported (`X12_270_LEVEL_DETACHED`, beside the code for the pointer defect itself), the
|
|
1908
|
+
declared pointer stays verbatim on `hierarchies`, and the segments stay verbatim on the transaction
|
|
1909
|
+
set. Nothing is re-parented onto whichever level happened to be open, because that would be this
|
|
1910
|
+
library inventing the one structure a 270 exists to state.
|
|
1911
|
+
- **A 270 DTP short of what a date row is built from loses the whole row, and says so.** A DTP is a
|
|
1912
|
+
record and not a slot: without both the qualifier that says what the date is for (DTP-01) and the
|
|
1913
|
+
value itself (DTP-03) there is no row to build, and the format qualifier that says single date or
|
|
1914
|
+
range goes with it. The reader builds none, fabricates nothing to stand in, and reports the loss
|
|
1915
|
+
(`X12_270_DATE_ROW_DROPPED`) at that segment, so an empty `dates` list can be told apart from a
|
|
1916
|
+
date the sender stated and this reader could build no row from. **The report is bounded to the row
|
|
1917
|
+
the reader tried to build.** A segment that would have attached to a level or an inquiry but
|
|
1918
|
+
arrives before the first HL opens one reaches no part of the model and stays SILENT, the date row
|
|
1919
|
+
included, so an empty list is evidence about a level this reader did return and about nothing else.
|
|
1920
|
+
The segments stay verbatim on the transaction set either way.
|
|
1921
|
+
- **A 270's tolerated deviations are reported once per transaction set, not once per occurrence.**
|
|
1922
|
+
A declared non-conventional delimiter raises `X12_270_NON_CONVENTIONAL_DELIMITER` once however many
|
|
1923
|
+
of the four roles deviate, and a line break between segments raises
|
|
1924
|
+
`X12_270_INTER_SEGMENT_LINE_BREAK` once however many runs the document carries. Both anchor at the
|
|
1925
|
+
ISA. The framing run is also consumed by the shared parse and recorded nowhere on the model, so
|
|
1926
|
+
only `parse270Inquiries`, which receives the bytes, can report it: `get270Inquiry` is handed the
|
|
1927
|
+
model and reports the delimiter half alone.
|
|
1928
|
+
- **Between segments, only CR and LF are absorbed. A space or a tab there costs you the whole
|
|
1929
|
+
interchange, on every transaction set.** The shared parse consumes a run of CR / LF before the next
|
|
1930
|
+
segment opens; any other whitespace becomes the head of the next segment's identifier, so `GS` is
|
|
1931
|
+
read as a non-spec segment id, no functional group frames, every segment after the ISA lands on
|
|
1932
|
+
`ix.orphanSegments` with an `X12_UNEXPECTED_SEGMENT` warning, and the interchange also reports
|
|
1933
|
+
`X12_MISSING_IEA`. Nothing is silent at the interchange level, but the typed readers see no
|
|
1934
|
+
transaction set at all: `parse270Inquiries` answers the EMPTY list, which is the same value it uses
|
|
1935
|
+
for an interchange that genuinely carries no 270, so **do not read an empty result as evidence the
|
|
1936
|
+
sender sent no inquiry without checking `ix.warnings` first.** This is long-standing shared-parse
|
|
1937
|
+
behaviour and the 270 work deliberately did not widen it: framing tolerance is shared by every
|
|
1938
|
+
transaction set, and moving it is a change to all of them rather than to one reader.
|
|
1939
|
+
- **A BDS or BIN segment's binary data is framed by the octet count its length element declares,
|
|
1940
|
+
never by delimiters, and where that count cannot be honoured it is reported, never corrected.**
|
|
1941
|
+
BDS-03 and BIN-02 are read as exactly the number of octets BDS-02 or BIN-01 declares, anywhere after
|
|
1942
|
+
the ISA, so an element, repetition or component separator, a segment terminator, a `?`, a CR or an
|
|
1943
|
+
LF inside the data is data: the segment is one segment, the data is one element, and the segments
|
|
1944
|
+
after it frame as sent. **`?` has no effect inside the declared span**, not even immediately before
|
|
1945
|
+
the terminator that follows it. `getSegmentValue`, `getAllSegmentValues` and `elementValue` return
|
|
1946
|
+
the data element verbatim, with no release unescape and no split on the repetition or component
|
|
1947
|
+
separator. **Nothing inside it is decoded**: BDS-01's filter (`B64`, for example) is not applied,
|
|
1948
|
+
and no typed reader decodes an attachment. **The count is in octets.** A `Buffer` is read one character
|
|
1949
|
+
per octet, so its count is exact; a string is counted one UTF-16 code unit per octet, which is exact
|
|
1950
|
+
at or below U+00FF, and a span holding anything above that raises `X12_BINARY_LENGTH_UNVERIFIABLE`
|
|
1951
|
+
and is still carried verbatim, so pass an interchange that carries attachments as a `Buffer`. The
|
|
1952
|
+
three ways the count can fail, each warned at the segment:
|
|
1953
|
+
- `X12_BINARY_LENGTH_INVALID`: the length element is absent, empty, or not 1 to 15 ASCII digits (a
|
|
1954
|
+
sign, a decimal point or whitespace is malformed; leading zeros are not). **No length is inferred,
|
|
1955
|
+
so this is the one case the count does not protect**: the segment is framed by its delimiters
|
|
1956
|
+
exactly as a segment of any other id is, and a delimiter inside its data splits it and can
|
|
1957
|
+
mis-frame every segment after it.
|
|
1958
|
+
- `X12_BINARY_DATA_TRUNCATED`: the input ends inside the declared span. The data element holds
|
|
1959
|
+
only the octets present and nothing past the end is read, and because the span runs to the end
|
|
1960
|
+
of the input, the transaction, group and interchange trailers are reported missing.
|
|
1961
|
+
- `X12_BINARY_LENGTH_MISMATCH`: the byte after the declared span is not the segment terminator (a
|
|
1962
|
+
length shorter or longer than the data), or the segment ends before a data element begins
|
|
1963
|
+
although a non-zero length was declared. The data element is exactly the declared span; the
|
|
1964
|
+
bytes from its end to the next terminator stay on the segment's `raw`, so a length longer than
|
|
1965
|
+
the data carries the start of the next segment into this one, and the default `serializeX12` emit
|
|
1966
|
+
still reproduces the input byte for byte.
|
|
1967
|
+
|
|
1968
|
+
**The length element is never rewritten on emit**, in either `serializeX12` mode, including where
|
|
1969
|
+
it disagrees with the data: that would be a silent correction. A span that ends exactly at the end
|
|
1970
|
+
of the input is complete and raises no binary warning; its missing terminator is supplied on emit,
|
|
1971
|
+
as for any unterminated final segment, so where a truncated span fell exactly one octet short, a
|
|
1972
|
+
re-parse of that emit reads the supplied terminator as the last octet of the data. Where `?` is the
|
|
1973
|
+
declared element separator or segment terminator, the elements before the data element are split
|
|
1974
|
+
literally, as that role's splitter already does.
|
|
1975
|
+
|
|
1976
|
+
## Code-list validity dates
|
|
1977
|
+
|
|
1978
|
+
- **🩺 A published Stop date is read as the FIRST day the code is no longer valid, and the
|
|
1979
|
+
maintainer does not say whether that is right.** `x12.org` renders a code's dates as
|
|
1980
|
+
`Start: 01/01/1995 | Last Modified: 11/01/2017 | Stop: 05/01/2018` and states nowhere whether the
|
|
1981
|
+
Stop date is the LAST day the code was valid or the FIRST day it was not. The two readings differ
|
|
1982
|
+
by exactly one day, on exactly the boundary a document produced that day sits on.
|
|
1983
|
+
|
|
1984
|
+
This package reads it as the first day the code is NOT valid, so the valid interval is half-open:
|
|
1985
|
+
a document date on or after the Stop date reports `not-valid`, and the day before it reports
|
|
1986
|
+
`valid`. The ground is the shape of the source and not a statement by the maintainer: the
|
|
1987
|
+
published dates are first-of-month publication-cycle dates, so the Stop date reads as the
|
|
1988
|
+
publication on which the code left the list rather than the last day it was on it. **That is a
|
|
1989
|
+
judgement, not a fact, and a consumer whose contract turns on the boundary day itself should
|
|
1990
|
+
confirm it with the maintainer rather than with this package.** `CARC.dates` and `RARC.dates`
|
|
1991
|
+
carry the published values unchanged, so a consumer who reads the boundary the other way has
|
|
1992
|
+
everything needed to compute it.
|
|
1993
|
+
|
|
1994
|
+
Two further bounds on the same data, neither of them a defect:
|
|
1995
|
+
- **A code with no published start date is `indeterminate`, never `valid` and never `not-valid`.**
|
|
1996
|
+
Validity is an interval and there is no interval without a start. No bundled code is in this
|
|
1997
|
+
state at the capture the dates were transcribed from; the answer exists because the fail-safe
|
|
1998
|
+
has to hold if one ever is.
|
|
1999
|
+
- **The dates describe the bundled subset and nothing wider.** A code outside it answers
|
|
2000
|
+
`indeterminate` with the inbound value echoed verbatim, which is a statement about what this
|
|
2001
|
+
package bundles rather than about what the maintainer publishes.
|
|
1672
2002
|
|
|
1673
2003
|
## Code-list `--fetch` regeneration
|
|
1674
2004
|
|
|
1675
2005
|
`pnpm refresh:code-lists` (default) validates the bundled snapshots and prints a freshness audit,
|
|
1676
2006
|
offline and CI-safe. The `--fetch` mode that would **regenerate** the full lists from their canonical
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
2007
|
+
sources is deliberately **not** run in automation: regeneration needs outbound network and is a human
|
|
2008
|
+
release step, and the tool fetches nothing and fabricates no description a maintainer has not
|
|
2009
|
+
published.
|
|
2010
|
+
|
|
2011
|
+
**Whether a bundled list's descriptions may be redistributed is answered PER LIST, and the lists do
|
|
2012
|
+
not agree.** Each bundled snapshot carries its own maintaining organisation and its own
|
|
2013
|
+
redistribution record on `meta`, so a consumer deciding whether a description is theirs to display,
|
|
2014
|
+
cache or re-publish reads that list's answer rather than a single sentence covering all seven:
|
|
2015
|
+
|
|
2016
|
+
| Bundled list | External code list | Maintained by | Redistribution | Approach for permission |
|
|
2017
|
+
| ------------------------------------- | ------------------ | ------------- | --------------------- | ----------------------- |
|
|
2018
|
+
| CARC (Claim Adjustment Reason Codes) | 139 | ASC X12 | licence required | ASC X12 (see below) |
|
|
2019
|
+
| RARC (Remittance Advice Remark Codes) | 411 | CMS | permitted, no licence | none needed |
|
|
2020
|
+
| CSCC (Claim Status Category Codes) | 507 | ASC X12 | licence required | ASC X12 (see below) |
|
|
2021
|
+
| CSC (Claim Status Codes) | 508 | ASC X12 | licence required | ASC X12 (see below) |
|
|
2022
|
+
| Service type (EB-03) | 958 | ASC X12 | licence required | ASC X12 (see below) |
|
|
2023
|
+
| CLP-02 claim status | none | ASC X12 | **not established** | ASC X12 (see below) |
|
|
2024
|
+
| INS-03 maintenance type | none | ASC X12 | **not established** | ASC X12 (see below) |
|
|
2025
|
+
|
|
2026
|
+
The **External code list** column is the list's id on the X12 External Code Lists index. It is the
|
|
2027
|
+
column that decides the one beside it: a list with a row there is answered from that row, and only a
|
|
2028
|
+
list with no row can be one the carried sources never name. `none` means exactly that the index
|
|
2029
|
+
publishes no row for the list, not that the list is unimportant.
|
|
2030
|
+
|
|
2031
|
+
**The approach, for every row that needs one:** ASC X12, which publishes that it is the only
|
|
2032
|
+
organisation authorised to grant permission for the use of X12 products. Permission to reproduce X12
|
|
2033
|
+
intellectual property is requested through the licensing programme X12 publishes at
|
|
2034
|
+
<https://x12.org/products/licensing-program>, by email to the intellectual-property address given
|
|
2035
|
+
there, naming the requester and their organisation and describing both the artifact and its intended
|
|
2036
|
+
distribution. Subscriptions to X12-maintained code lists are bought through the Code List Update
|
|
2037
|
+
Subscription page linked from <https://x12.org/codes>.
|
|
2038
|
+
|
|
2039
|
+
The evidence behind each row is quoted on the list itself, at `meta.redistribution.terms`. In short:
|
|
2040
|
+
the X12 External Code Lists index records CARC as external code list 139, the two claim-status lists
|
|
2041
|
+
as 507 and 508, and the service type codes as 958, all maintained by an X12 code maintenance
|
|
2042
|
+
committee group, under the statement that all X12 work products are copyrighted and that
|
|
2043
|
+
subscriptions to X12-maintained code lists are purchased. X12's own intellectual-property policy,
|
|
2044
|
+
reproduced in HL7's CARIN Consumer Directed Payer Data Exchange code system stubs, adds that all X12
|
|
2045
|
+
products are subject to it and that X12 is the only organisation authorised to grant permission for
|
|
2046
|
+
their use. The same index records RARC as external code list 411 maintained by CMS, and HL7's CARIN
|
|
2047
|
+
terminology licensure page puts CARC among the code systems that require a purchased licence and RARC
|
|
2048
|
+
among those requiring none.
|
|
2049
|
+
|
|
2050
|
+
**Being cited from a TR3 is not evidence of a missing publisher.** Three bundled lists point their
|
|
2051
|
+
`meta.source` at a purchased Technical Report Type 3: the service type codes, the CLP-02 dispositions
|
|
2052
|
+
and the INS-03 maintenance types. Only the last two are absent from the External Code Lists index;
|
|
2053
|
+
the service type codes have a row there, so they are answered from it. Where a bundled subset was
|
|
2054
|
+
read is a different question from what the publisher publishes, and only the second decides the
|
|
2055
|
+
terms.
|
|
2056
|
+
|
|
2057
|
+
**"Not established" is a recorded answer here, not a gap waiting to be tidied.** The last two lists,
|
|
2058
|
+
the CLP-02 dispositions and the INS-03 maintenance types, are printed inside a purchased ASC X12
|
|
2059
|
+
Technical Report Type 3 and have no row on the External Code Lists index at all, and no source
|
|
2060
|
+
obtained for this package names them or states whether their descriptions may be redistributed. They
|
|
2061
|
+
are therefore treated as **not redistributable** wherever a permission decision is made, exactly as a
|
|
2062
|
+
licence-restricted list is: an unsettled question is never read as a permission.
|
|
2063
|
+
|
|
2064
|
+
Consequences you can rely on:
|
|
2065
|
+
|
|
2066
|
+
- **A list whose descriptions may not be redistributed stays exactly the cited part it already
|
|
2067
|
+
ships.** Nothing is enlarged, refetched or relicensed by recording the terms, and the record names
|
|
2068
|
+
the licensor who could change that.
|
|
2069
|
+
- **`--fetch` answers each list on its own terms.** It reports each bundled list's regeneration
|
|
2070
|
+
permission independently, refuses only the lists whose own record forbids it, prints the licensor
|
|
2071
|
+
to approach beside each refusal, and never refuses a list because a different list is restricted.
|
|
2072
|
+
It exits `2` while any list is refused and `0` when none is.
|
|
2073
|
+
- **Snapshot validation refuses an unlabelled list.** A bundled list carrying no maintaining
|
|
2074
|
+
organisation, or no redistribution record, fails `pnpm refresh:code-lists` with a message naming
|
|
2075
|
+
that list, so it cannot reach a release unlabelled.
|
|
2076
|
+
- **`codeListRedistributionIsPermitted(meta)` is the single decision point**, and it is exported.
|
|
2077
|
+
Only a recorded permission answers `true`; a licence requirement, an unsettled status and a missing
|
|
2078
|
+
record all answer `false`.
|
|
1681
2079
|
|
|
1682
2080
|
---
|
|
1683
2081
|
|
|
1684
|
-
For the
|
|
1685
|
-
[`
|
|
2082
|
+
For the per-transaction read and emit surface, and the exact fields each helper decodes, see
|
|
2083
|
+
[`README.md`](./README.md) and the Cookbook on the documentation site.
|