@cosyte/x12 0.0.1 → 0.0.3
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 +295 -145
- package/README.md +24 -14
- package/TRADEMARKS.md +31 -0
- package/dist/index.cjs +132 -132
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1291 -1291
- package/dist/index.d.ts +1291 -1291
- package/dist/index.mjs +132 -132
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -4
package/CHANGELOG.md
CHANGED
|
@@ -7,50 +7,201 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Brand lockup on the README, following the reader's colour scheme (ASSETS-P8).** The README now
|
|
13
|
+
opens with the shared Cosyte lockup, above the `# @cosyte/x12` heading. It is a `<picture>`
|
|
14
|
+
element: a `<source>` carrying the on-dark cut for `prefers-color-scheme: dark`
|
|
15
|
+
(`https://cosyte.com/tile/cosyte-lockup-tile-on-dark-1200x300.png`) and an `<img>` carrying the
|
|
16
|
+
on-light cut as the fallback (`https://cosyte.com/tile/cosyte-lockup-tile-on-light-1200x300.png`).
|
|
17
|
+
Both were verified `200` / `image/png` before landing. On GitHub a dark-mode reader gets the dark
|
|
18
|
+
cut; on npm the `<img>` is lifted out of the `<picture>` by the surrounding anchor so the light cut
|
|
19
|
+
renders, which is correct there because npmjs.com has no dark mode. The heading and the blockquote
|
|
20
|
+
under it are unchanged: the lockup reads "Cosyte" while the heading reads `@cosyte/x12`, so the two
|
|
21
|
+
strings do not collide and nothing is duplicated. The alt text describes the artwork rather than
|
|
22
|
+
the package, since that is what a screen reader announces on the npm page and what a reader gets if
|
|
23
|
+
the image fails to load.
|
|
24
|
+
|
|
25
|
+
**Recorded because it changed inside the unreleased window rather than after it.** This entry first
|
|
26
|
+
landed describing a _per-package_ banner (`cosyte-banner-x12-1200x300.png`), which baked the
|
|
27
|
+
package name and the tagline into pixels and duplicated the two lines beneath it. It explicitly
|
|
28
|
+
chose a plain markdown image over an `<img>` or a `<picture>` pair, on the ground that whether
|
|
29
|
+
npm's markdown sanitizer preserves `<picture>` was unverified. That was an accurate statement of
|
|
30
|
+
what was known at the time. It has since been measured on a published package page: the sanitizer
|
|
31
|
+
keeps the `<picture>`, and the anchor wrapper hoists the `<img>` out of it, so the light cut
|
|
32
|
+
renders. The per-package banner was replaced before any release carried it, so no published
|
|
33
|
+
version of this package ever had it, and this is written as one change rather than an addition
|
|
34
|
+
followed by a replacement. The superseded reason is kept here rather than removed, because a
|
|
35
|
+
record that quietly flips a stated reason is worse than one that shows the correction.
|
|
36
|
+
|
|
10
37
|
### Fixed
|
|
11
38
|
|
|
39
|
+
- **Stale version claim removed from five public pages (ASSETS-P8).** `README.md`,
|
|
40
|
+
`KNOWN-LIMITATIONS.md`, `docs-content/intro.md`, `docs-content/installation.md`, and
|
|
41
|
+
`docs-content/troubleshooting.md` all asserted the package was "published on npm at `0.0.1`". The registry says
|
|
42
|
+
`0.0.2`. The literal was pinned by a previous docs sweep and went stale on the very next release,
|
|
43
|
+
so it is now removed rather than re-pinned: the npm badge renders the live version on the README,
|
|
44
|
+
and each page points at `npm view @cosyte/x12 version` as the source of truth. Docs only, no
|
|
45
|
+
runtime or public-API change.
|
|
46
|
+
- **Scope claim corrected: the 270 and 276 inquiries have no typed model (ASSETS-P8).** The README
|
|
47
|
+
status line and "What's inside" list, and `docs-content/intro.md`, claimed "the full v1 read scope
|
|
48
|
+
(270/271, 276/277/277CA, …) and emit scope … are complete". There is no `get270` / `get276` reader
|
|
49
|
+
and no `build270` / `build276` builder, and no 270 or 276 dispatch anywhere in `src/`;
|
|
50
|
+
`get271Eligibility` and `get277Status` return `undefined` for any other `ST-01`. The inquiry
|
|
51
|
+
directions parse into segments and dot-paths like any other X12 input but decode into no typed
|
|
52
|
+
model. The claim is now stated per transaction on every page that made it: the README status line
|
|
53
|
+
and "What's inside" list, `docs-content/intro.md` (both the status block **and** the "transaction
|
|
54
|
+
sets it covers" list under it), `docs-content/spec-notes-transaction-sets.md` (the lead-in and the
|
|
55
|
+
`270 / 271` + `276 / 277` rows of the reader/builder map), and `docs-content/cookbook.md`, which
|
|
56
|
+
said the 270 was "a read-only surface" when it has no reader either. The gap is recorded in
|
|
57
|
+
`KNOWN-LIMITATIONS.md`, `docs-content/troubleshooting.md`, and this repo's `CLAUDE.md`. Docs only,
|
|
58
|
+
no runtime or public-API change.
|
|
59
|
+
- **Serializer description corrected to its actual defaults (ASSETS-P8).** The README described "a
|
|
60
|
+
strict, spec-clean serializer with recomputed envelope counts", which reads as default behaviour,
|
|
61
|
+
and `CLAUDE.md` said the serializer "always emits spec-clean X12". `serializeX12` is byte-faithful
|
|
62
|
+
by default; `{ specClean: true }` reconciles the envelope, and the corrected counts need
|
|
63
|
+
`{ specClean: true, recomputeCounts: true }` together, because `recomputeCounts` is inert on its
|
|
64
|
+
own (`const recompute = specClean && opts.recomputeCounts === true`). A mismatch is always warned
|
|
65
|
+
and never silently corrected. Docs only, no runtime or public-API change.
|
|
66
|
+
- **PHI claim narrowed to what the library actually guarantees (ASSETS-P8).** The README said
|
|
67
|
+
"warnings/errors that carry codes and positions but never patient data". Warning messages and
|
|
68
|
+
builder refusals are PHI-free by construction, but `X12ParseError.snippet` is a bounded (≤ 64
|
|
69
|
+
character) copy of the offending input, so on real traffic it can carry PHI, and the library does
|
|
70
|
+
not redact it. That was documented in the source JSDoc and nowhere a consumer reads. The README now
|
|
71
|
+
states the exception, and it is written up in `KNOWN-LIMITATIONS.md` and
|
|
72
|
+
`docs-content/troubleshooting.md`. Docs only, no runtime or public-API change.
|
|
73
|
+
- **Developer-docs publish status corrected to the published reality (README-ORG-SWEEP).** The
|
|
74
|
+
docs.cosyte.com pages (`docs-content/intro.md`, `installation.md`, `troubleshooting.md`) and
|
|
75
|
+
`KNOWN-LIMITATIONS.md` still said "not yet published to npm" / "gated on the coordinated public
|
|
76
|
+
launch" (and `KNOWN-LIMITATIONS.md` still read `0.0.0`), contradicting the live `npm install`
|
|
77
|
+
command already documented. Rewritten to state the package is published at `0.0.1`, public, still
|
|
78
|
+
pre-alpha on the `0.0.x`-until-first-alpha ladder; the install command is now described as live
|
|
79
|
+
rather than aspirational. Docs only, no runtime or public-API change.
|
|
80
|
+
- **README status line corrected to the published reality (README-ORG-SWEEP).** The status line still
|
|
81
|
+
read "pre-alpha (`0.0.x`, not yet published to npm) … the first npm publish is gated on the
|
|
82
|
+
coordinated public launch," which contradicted the npm-version badge and the `pnpm add @cosyte/x12`
|
|
83
|
+
install line already in the same README. The package is published on npm at `0.0.1` from a public
|
|
84
|
+
repo. Rewritten to state that it is published at `0.0.1`, in a public repo, still pre-alpha on the
|
|
85
|
+
`0.0.x`-until-first-alpha ladder; the read/emit scope claim is unchanged. Docs only, no runtime or
|
|
86
|
+
public-API change.
|
|
87
|
+
|
|
88
|
+
### Added
|
|
89
|
+
|
|
90
|
+
- **Em-dash brand gate in CI (`scripts/check-no-emdash.sh`, `pnpm check:no-emdash`,
|
|
91
|
+
`.github/workflows/no-emdash.yml`; `EMDASH-CONFORMANCE`).** The founder directive of 2026-07-24
|
|
92
|
+
(`knowledgebase/06-brand/voice-and-tone.md`) bans `U+2014` outright across every cosyte surface
|
|
93
|
+
and names commit messages explicitly, and the meta-repo's `documentation/conventions.md` has
|
|
94
|
+
described the rule as CI-gated; x12 was one of the repos where it was not. This ports
|
|
95
|
+
`knowledgebase`'s scanner (the text-only variant, correct here because x12 tracks no binaries:
|
|
96
|
+
all 264 tracked files decode as us-ascii or utf-8 and none holds a NUL byte, measured byte-level
|
|
97
|
+
2026-07-27) and wires it to a dedicated workflow that checks **both** the tracked files and the
|
|
98
|
+
**PR title, body, and branch commit messages**, the last of these on the non-default `edited`
|
|
99
|
+
activity type so a description retitled after the final push is re-checked before a squash merge
|
|
100
|
+
turns it into the commit message. It is a separate workflow rather than a job in `ci.yml` because
|
|
101
|
+
`ci.yml` calls the shared reusable pipeline (which runs no arbitrary repo script) and its triggers
|
|
102
|
+
drive the Node 22 + 24 matrix plus the `release-dry-run` job. x12 was already clean (0 of 21
|
|
103
|
+
markdown files carried an em dash), so **no content changed**: this is regression prevention only.
|
|
104
|
+
Dev tooling, no change to the published package surface, parser behavior, or warning codes.
|
|
105
|
+
**Two limits stated rather than claimed away.** The new check is not yet one of the org ruleset's
|
|
106
|
+
required contexts (`parser-ci-required-checks` requires `ci / verify (22|24, ubuntu-latest)` and
|
|
107
|
+
`ci / actionlint`), so today it reds visibly but does not itself block a merge; making it required
|
|
108
|
+
is an org-level change outside this repo. And a tracked text file holding a NUL byte **and a
|
|
109
|
+
pattern match** fails this shape closed: grep 3.8 reports `binary file matches` on stderr and the
|
|
110
|
+
scan refuses. The NUL alone does not trigger it, and the red is remediable by the same rewrite
|
|
111
|
+
the brand rule already demands. x12 has no NUL-bearing tracked file at all today (zero of 264,
|
|
112
|
+
measured byte-level).
|
|
113
|
+
- **`docs-content/` now ships the full canonical Diátaxis spine (DOCS-CONTENT-P4).** The sidebar was
|
|
114
|
+
Overview-only, with `cookbook.md` authored but orphaned (invisible to every reader). This wires the
|
|
115
|
+
cookbook into **Guides** and adds the rest of the spine every `@cosyte/*` package shares: four new
|
|
116
|
+
**Core Concepts** pages (the envelope/loop model; the 80/20 transaction sets, mapping each shipped
|
|
117
|
+
set to its reader/builder pair and the field it preserves verbatim; the tolerance tiers +
|
|
118
|
+
warning-code model; and decimal-exact money via `X12Decimal`), **Installation** and **Quickstart**
|
|
119
|
+
tutorials (parse an 835 and post the cash), and a **Troubleshooting & known limitations** page (the
|
|
120
|
+
fatal-vs-warn model, a symptom→cause table, PHI-in-logs discipline, and the v1 non-goals). Depth is
|
|
121
|
+
gated to the shipped surface: no unshipped API is documented. Synthetic-only fixtures throughout.
|
|
122
|
+
Docs only, no runtime or public-API change.
|
|
123
|
+
|
|
124
|
+
### Fixed
|
|
125
|
+
|
|
126
|
+
- **`scripts/sync-version.mjs` hardened against two latent defects, and gated in CI
|
|
127
|
+
(SYNC-VERSION-HARDENING).** Follow-up hardening on the VERSION-SYNC script; ported byte-identically
|
|
128
|
+
across `hl7`, `x12`, and `mllp`. (1) The version was spliced into `src/index.ts` via
|
|
129
|
+
`String.prototype.replace` with a _replacement string_, which interprets `$&`, `$1`, `` $` ``, etc.,
|
|
130
|
+
so a version like `1.2.3-$&x` would inject the matched text and corrupt the `VERSION` constant while
|
|
131
|
+
exiting 0. The replacement is now a replacer _function_, whose return value is inserted literally.
|
|
132
|
+
(2) The declaration regex was non-global, so `.replace` silently rewrote the _first_ match; a
|
|
133
|
+
column-0 decoy (e.g. inside a comment) ahead of the real declaration could be edited instead. The
|
|
134
|
+
script now matches globally, asserts exactly one declaration, and exits non-zero loudly otherwise.
|
|
135
|
+
Neither defect is reachable through Changesets today and both previously failed loud rather than
|
|
136
|
+
shipping a lying `VERSION`, so this is hardening, not a fix for an observed break. The
|
|
137
|
+
`format`/`format:check` globs now cover `scripts/**/*.mjs` so the script is prettier-gated in CI
|
|
138
|
+
(the `.mjs` scripts were matched by no format glob before; `scripts/**/*.ts` was already gated).
|
|
139
|
+
Build tooling only, no runtime or public-API change.
|
|
140
|
+
- **The `intro.md` status/roadmap section was stale**. It described Phase 1/2 as the frontier and
|
|
141
|
+
listed the now-shipped read + emit + profile surfaces as "coming in later phases." Refreshed to the
|
|
142
|
+
current shipped reality with an honest pre-alpha status banner.
|
|
143
|
+
- **A latent malformed-ISA fixture in `cookbook.md`.** The self-contained 835 example's ISA padded
|
|
144
|
+
the sender/receiver IDs to 16 bytes instead of the fixed 15, so the 106-byte ISA was misaligned and
|
|
145
|
+
delimiter detection would reject it. It went unnoticed because the cookbook block was illustrative,
|
|
146
|
+
never executed; making the examples runnable under the doc/code-agreement harness surfaced it.
|
|
147
|
+
|
|
148
|
+
### Changed
|
|
149
|
+
|
|
150
|
+
- **Every runnable docs snippet is gated by the shared doc/code-agreement harness.**
|
|
151
|
+
`test/docs-content.test.ts` runs `docSnippetSuite()` (from `@cosyte/vitest-config/snippets`) over
|
|
152
|
+
`docs-content/`, extracting each ` ```ts runnable ` block, compiling it, executing it against the
|
|
153
|
+
**built** ESM artifact, and asserting its inline `// =>` results, so a documented example can never
|
|
154
|
+
silently drift from the shipped code. Bumps the `@cosyte/vitest-config` devDependency to `^0.0.2`
|
|
155
|
+
for its `/snippets` export.
|
|
156
|
+
|
|
12
157
|
- **The Release workflow can actually start.** `.github/workflows/release.yml` calls the shared
|
|
13
158
|
`cosyte/.github` pipeline, which requests `contents`/`id-token`/`pull-requests: write`, but declared
|
|
14
|
-
no `permissions:` of its own
|
|
159
|
+
no `permissions:` of its own, so it inherited the repo default of `contents: read`. A called
|
|
15
160
|
workflow may only downgrade the caller's `GITHUB_TOKEN`, never escalate it, so GitHub rejected the
|
|
16
161
|
workflow at startup (~1s, no jobs, no logs). Every Release run from June 2026 until now failed this
|
|
17
162
|
way, unnoticed, because a `startup_failure` produces no logs to read. The caller job now declares
|
|
18
|
-
the three scopes explicitly. CI-only
|
|
163
|
+
the three scopes explicitly. CI-only, no runtime or API change.
|
|
19
164
|
|
|
20
165
|
- **The `VERSION` export now tracks `package.json`, and the missing `version`
|
|
21
166
|
script is restored (VERSION-SYNC).** Two latent release bugs, both of which
|
|
22
167
|
would have bitten at the first publish. (1) `VERSION` was hardcoded `"0.0.0"`
|
|
23
168
|
in `src/index.ts` while `changeset version` bumps only `package.json`, so a
|
|
24
|
-
published `0.0.1` would have shipped an export reading `"0.0.0"
|
|
169
|
+
published `0.0.1` would have shipped an export reading `"0.0.0"`. Every
|
|
25
170
|
consumer asserting on or logging `VERSION` told the wrong version of the
|
|
26
171
|
parser they were running. New `scripts/sync-version.mjs` rewrites the constant
|
|
27
172
|
from `package.json` (idempotent; exits non-zero if the declaration is renamed
|
|
28
|
-
rather than silently no-op'ing). (2) **No `version` script existed at all
|
|
29
|
-
|
|
173
|
+
rather than silently no-op'ing). (2) **No `version` script existed at all**.
|
|
174
|
+
The shared `cosyte/.github` release workflow drives Changesets with
|
|
30
175
|
`version: pnpm run version`, which would have failed with `ERR_PNPM_NO_SCRIPT`,
|
|
31
176
|
so the "Version Packages" PR could never have been opened. The guard in
|
|
32
177
|
`test/sanity.test.ts` was **inverted**: it asserted
|
|
33
|
-
`expect(VERSION).toBe("0.0.0")`
|
|
178
|
+
`expect(VERSION).toBe("0.0.0")` (literal against literal) which stays green
|
|
34
179
|
through exactly this drift and goes red on a _correct_ bump. It now compares
|
|
35
180
|
`VERSION` against `package.json` at test time. Ported from `@cosyte/mllp`
|
|
36
181
|
(MLLP-10), in the canonical form `@cosyte/hl7` carries. No version bumped,
|
|
37
|
-
nothing published
|
|
182
|
+
nothing published, still `0.0.0`.
|
|
38
183
|
|
|
39
184
|
### Added
|
|
40
185
|
|
|
41
|
-
- **
|
|
186
|
+
- **Trademark notice (`TRADEMARKS.md`).** This package names third-party systems to describe what it
|
|
187
|
+
interoperates with; the notice records that cosyte is not affiliated with, endorsed by, or
|
|
188
|
+
sponsored by any of them, that every reference is descriptive, and that the built-in profiles are
|
|
189
|
+
authored from public sources only. Added to `files` so it ships inside the published tarball, not
|
|
190
|
+
just on GitHub. Documentation only, no runtime or API change.
|
|
191
|
+
|
|
192
|
+
- **Phase 10: release hardening.** The v1 close-out; no new parser
|
|
42
193
|
surface, just the gates, tooling, and docs that make the package
|
|
43
194
|
trustworthy to publish.
|
|
44
195
|
- **Publish-pipeline proof.** A new `release-dry-run` CI job proves a
|
|
45
196
|
real release would succeed without burning a version or needing
|
|
46
|
-
registry auth
|
|
197
|
+
registry auth: `pnpm publish --dry-run` exercises the publish command
|
|
47
198
|
path and `npm pack --dry-run` asserts the publishable tarball assembles
|
|
48
199
|
with the right `files` set + built `dist/`. The real provenance publish
|
|
49
200
|
stays gated on the public launch.
|
|
50
201
|
- **Nightly amplified fuzz** (`.github/workflows/fuzz.yml`). Re-runs the
|
|
51
202
|
byte-flip / never-throw property targets at a higher iteration count
|
|
52
|
-
(`X12_FUZZ_RUNS`) with a rotating seed (`X12_FUZZ_SEED`)
|
|
53
|
-
search that would slow the per-commit run
|
|
203
|
+
(`X12_FUZZ_RUNS`) with a rotating seed (`X12_FUZZ_SEED`), the deep
|
|
204
|
+
search that would slow the per-commit run, and opens/auto-closes a
|
|
54
205
|
sticky issue on failure. The per-commit suite is unchanged (pinned
|
|
55
206
|
seed, base counts, coverage-stable); a finding is replayable via the
|
|
56
207
|
printed seed. New test helper `fuzzRuns()` scales only the true fuzz
|
|
@@ -70,20 +221,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
70
221
|
`RELEASE_CHAR`).
|
|
71
222
|
- **Known limitation carried forward:** an external-oracle differential
|
|
72
223
|
corpus (vs CMS Medicare 835) is not yet wired, pending a
|
|
73
|
-
redistribution-terms review
|
|
224
|
+
redistribution-terms review. See `KNOWN-LIMITATIONS.md`.
|
|
74
225
|
|
|
75
226
|
### Security
|
|
76
227
|
|
|
77
|
-
- **Dev-dependency advisory remediation (no runtime impact
|
|
228
|
+
- **Dev-dependency advisory remediation (no runtime impact:
|
|
78
229
|
`@cosyte/x12` ships zero runtime dependencies).** Added scoped
|
|
79
230
|
`pnpm.overrides` pinning two transitive **dev/build-time** packages to
|
|
80
231
|
their patched releases: `esbuild` (`>=0.27.3 <0.28.1` → `0.28.1`,
|
|
81
|
-
GHSA dev-server path-traversal
|
|
232
|
+
GHSA dev-server path-traversal, unreachable here: a library build
|
|
82
233
|
via `tsup`/`vitest`, never `esbuild serve`) and the
|
|
83
234
|
`@changesets/parse` copy of `js-yaml` (`>=4.0.0 <4.2.0` → `4.2.0`,
|
|
84
235
|
GHSA-h67p-54hq-rp68 merge-key DoS). The `js-yaml@3.14.2` pulled by
|
|
85
236
|
`read-yaml-file@1.1.0` (via `@manypkg/get-packages` →
|
|
86
|
-
`@changesets/cli`) is **intentionally left
|
|
237
|
+
`@changesets/cli`) is **intentionally left**. It calls
|
|
87
238
|
`yaml.safeLoad`, removed/throwing in js-yaml 4, so it cannot be
|
|
88
239
|
force-upgraded without breaking the release tooling; it only parses
|
|
89
240
|
trusted local repo YAML at release time. Verify gate green on the
|
|
@@ -93,8 +244,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
93
244
|
|
|
94
245
|
- **Segment splitting now honours the `?`-release-escaped terminator.**
|
|
95
246
|
`splitSegments` (the envelope tokenizer) used a naive `indexOf` for the
|
|
96
|
-
segment terminator, so a value carrying a literal terminator byte
|
|
97
|
-
emitted by `escapeRelease` as `?~` — was split mid-value: the segment
|
|
247
|
+
segment terminator, so a value carrying a literal terminator byte (emitted by `escapeRelease` as `?~`) was split mid-value: the segment
|
|
98
248
|
was truncated at the `?`, a phantom empty segment was injected, and the
|
|
99
249
|
round-trip silently corrupted the value (the element splitter
|
|
100
250
|
`splitWithRelease` had always been release-aware; only the segment
|
|
@@ -108,13 +258,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
108
258
|
|
|
109
259
|
### Added
|
|
110
260
|
|
|
111
|
-
- **Profile system
|
|
261
|
+
- **Profile system: descriptive, fixture-grounded clearinghouse / payer
|
|
112
262
|
companion-guide quirk attribution.** A `defineProfile()` API mirroring
|
|
113
263
|
the sibling `@cosyte/hl7` profile shape, plus a `profiles` namespace of
|
|
114
264
|
built-ins. The parser is already lenient and lossless, so a **v1 profile
|
|
115
265
|
is DESCRIPTIVE**: it attaches attribution metadata to the returned
|
|
116
266
|
`X12Interchange` (`ix.profile`) and powers `partitionWarnings`, but
|
|
117
|
-
NEVER alters the parse
|
|
267
|
+
NEVER alters the parse: `groups`, `warnings`, and `isa` are
|
|
118
268
|
byte-identical with and without a profile (proven by a divergence test).
|
|
119
269
|
- **`defineProfile(spec)`** validates the spec (fail-fast on name, then
|
|
120
270
|
Levenshtein "did you mean?" hints on unknown option keys, then the
|
|
@@ -123,21 +273,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
123
273
|
position; scalar `description` last-wins), re-validates the composed
|
|
124
274
|
set, and returns a frozen `X12Profile` whose `describe()` yields a
|
|
125
275
|
structured `X12ProfileDescription` bucketed by effect
|
|
126
|
-
(`relaxes` / `adds` / `requires`)
|
|
276
|
+
(`relaxes` / `adds` / `requires`): structured DATA, not hl7's
|
|
127
277
|
formatted string, so consumers can program against it.
|
|
128
|
-
- **The locked HARD RULE
|
|
278
|
+
- **The locked HARD RULE: no invented quirks.** Every quirk MUST cite a
|
|
129
279
|
`fixture` (a relative path under `test/fixtures/`) that actually
|
|
130
280
|
EXHIBITS the deviation; the field is required at the type level and
|
|
131
281
|
enforced in `defineProfile()`. The accuracy suite goes further: a
|
|
132
282
|
per-quirk DEMONSTRATOR registry asserts each cited fixture exhibits its
|
|
133
283
|
claimed deviation, and a shipped quirk with no demonstrator FAILS the
|
|
134
|
-
suite
|
|
284
|
+
suite, so a real-but-irrelevant fixture cannot slip past.
|
|
135
285
|
- **`setDefaultProfile()` / `getDefaultProfile()`** set a process-scoped
|
|
136
286
|
default applied when a `parseX12` call passes no `profile`. An explicit
|
|
137
287
|
`{ profile }` wins; `{ profile: null }` opts out of the default for
|
|
138
288
|
that call. `partitionWarnings(warnings, profile)` splits a parse's
|
|
139
289
|
warnings into `{ expected, unexpected }` on the union of the profile's
|
|
140
|
-
quirk `expectedWarnings
|
|
290
|
+
quirk `expectedWarnings`, the one behavioural hook a v1 profile
|
|
141
291
|
offers.
|
|
142
292
|
- **Built-ins ship ONLY where a Tier-2 fixture grounds them:**
|
|
143
293
|
`profiles.availity` (payer-loop `REF*2U` + service-line `REF*F8`
|
|
@@ -145,7 +295,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
145
295
|
`profiles.bcbsCommon` (backslash component separator, grounded in
|
|
146
296
|
`envelope/bcbs-subelement.edi`). Profiles whose only "deviation" would
|
|
147
297
|
be a canonical `:` baseline (e.g. a generic Medicare FFS profile) are
|
|
148
|
-
deliberately DEFERRED rather than invented
|
|
298
|
+
deliberately DEFERRED rather than invented. Shipping them would
|
|
149
299
|
violate the hard rule. Built-ins are reachable only through the
|
|
150
300
|
`profiles` namespace, never the top-level export (mirrors hl7).
|
|
151
301
|
- **API divergence from `@cosyte/hl7`, by design:** `describe()` returns
|
|
@@ -157,11 +307,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
157
307
|
and the `X12Profile` / `X12ProfileSpec` / `X12ProfileQuirk` /
|
|
158
308
|
`X12ProfileDescription` / `X12ProfileEffect` / `X12WarningPartition`
|
|
159
309
|
type tree.
|
|
160
|
-
- **Domain builders
|
|
310
|
+
- **Domain builders: `build820` (005010X218 Premium Payment) and
|
|
161
311
|
`build834` (005010X220A1 Benefit Enrollment and Maintenance).** The emit
|
|
162
312
|
counterparts to `get820Payments` and `get834Header` /
|
|
163
313
|
`get834Enrollments`, layered on the Phase 8 general builder and
|
|
164
|
-
mirroring the pure-function `build835` pattern
|
|
314
|
+
mirroring the pure-function `build835` pattern. They NEVER auto-send,
|
|
165
315
|
open a socket, or touch the filesystem, and return a frozen
|
|
166
316
|
`X12Interchange`. Completes the v1 emit scope: every v1 transaction now
|
|
167
317
|
has a domain builder.
|
|
@@ -175,7 +325,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
175
325
|
spec is reproduced field-for-field. **The 820 carries no TR3 balance
|
|
176
326
|
equation** (BPR-02 is not required to equal Σ of the RMR open items),
|
|
177
327
|
so the builder emits all monetary amounts VERBATIM and never raises a
|
|
178
|
-
balance-mismatch refusal
|
|
328
|
+
balance-mismatch refusal, a deliberate contrast with `build835`.
|
|
179
329
|
- **`build834(spec)`** assembles a complete interchange (one GS..GE
|
|
180
330
|
group, GS-01 `BE`; one ST..SE 834, ST-03 `005010X220A1`) from a typed
|
|
181
331
|
`Build834Spec` (envelope + BGN header + sponsor `N1*P5` / payer
|
|
@@ -185,7 +335,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
185
335
|
the first HD so the read side binds them to the member, not the
|
|
186
336
|
coverage loop. The output round-trips through `get834Header` /
|
|
187
337
|
`get834Enrollments` field-for-field.
|
|
188
|
-
- **Maintenance type is the 834's safety primitive
|
|
338
|
+
- **Maintenance type is the 834's safety primitive: emit verbatim,
|
|
189
339
|
refuse the unknown.** The builder places the caller-supplied INS-03 /
|
|
190
340
|
HD-01 code (X12 Code Source 875) into the segment VERBATIM and NEVER
|
|
191
341
|
infers or normalizes it; where the lenient read side only WARNS on an
|
|
@@ -196,15 +346,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
196
346
|
outside the validated subset is refused.
|
|
197
347
|
- **Refusal, not silent corruption.** `build820` REFUSES a structurally
|
|
198
348
|
impossible spec via a typed `Premium820BuildError`
|
|
199
|
-
(`X12_820_BUILD_INVALID_SPEC
|
|
349
|
+
(`X12_820_BUILD_INVALID_SPEC`: no TRN trace, no remittance, a
|
|
200
350
|
remittance with neither an `ENT` nor an `NM1` to open its loop, a
|
|
201
351
|
remittance with no `RMR` open item, an open item with no identity, an
|
|
202
352
|
over-long control number). `build834` REFUSES via a typed
|
|
203
353
|
`Enrollment834BuildError`
|
|
204
|
-
(`X12_834_BUILD_UNKNOWN_MAINTENANCE_TYPE
|
|
205
|
-
outside the X12 875 subset; `X12_834_BUILD_INVALID_SPEC
|
|
354
|
+
(`X12_834_BUILD_UNKNOWN_MAINTENANCE_TYPE`: an INS-03 / HD-01 code
|
|
355
|
+
outside the X12 875 subset; `X12_834_BUILD_INVALID_SPEC`: no member
|
|
206
356
|
loop, an empty required INS-03, an over-long control number). Both
|
|
207
|
-
messages carry structural indices / counts only
|
|
357
|
+
messages carry structural indices / counts only. `build834`
|
|
208
358
|
additionally names the offending maintenance code (an X12 control
|
|
209
359
|
code, never PHI), but never a member id or name (PHI discipline).
|
|
210
360
|
- New public exports: `build820`, `Premium820BuildError`,
|
|
@@ -212,11 +362,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
212
362
|
`Build820Spec` type tree; `build834`, `Enrollment834BuildError`,
|
|
213
363
|
`ENROLLMENT_834_BUILD_ERROR_CODES`, `Enrollment834BuildErrorCode`, and
|
|
214
364
|
the `Build834Spec` type tree.
|
|
215
|
-
- **Domain builders
|
|
216
|
-
Review
|
|
217
|
-
Review
|
|
365
|
+
- **Domain builders: `build278Request` (005010X217 Health Care Services
|
|
366
|
+
Review: Request for Review) and `build278Response` (005010X216 Services
|
|
367
|
+
Review: Response).** The emit counterparts to `get278Request` /
|
|
218
368
|
`get278Response`, layered on the Phase 8 general builder and mirroring
|
|
219
|
-
the pure-function `build277` / `build277CA` pattern
|
|
369
|
+
the pure-function `build277` / `build277CA` pattern. They NEVER
|
|
220
370
|
auto-send, open a socket, or touch the filesystem, and return a frozen
|
|
221
371
|
`X12Interchange`.
|
|
222
372
|
- **`build278Request(spec)` / `build278Response(spec)`** share one
|
|
@@ -234,7 +384,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
234
384
|
surface.** `build278Response` places the caller-supplied HCR-01
|
|
235
385
|
`actionCode` (`A1` certified / `A3` not-certified / `A4` pended / `A6`
|
|
236
386
|
modified / …) into the segment VERBATIM and NEVER infers, normalizes,
|
|
237
|
-
or upgrades it
|
|
387
|
+
or upgrades it. The round-tripped `decision.actionCode` is
|
|
238
388
|
byte-for-byte the input. `build278Request` REFUSES a review carrying a
|
|
239
389
|
decision (HCR is response-only); `build278Response` refuses a decision
|
|
240
390
|
with an empty action code.
|
|
@@ -245,22 +395,22 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
|
|
|
245
395
|
construction.
|
|
246
396
|
- **Refusal, not silent corruption.** The builder REFUSES a
|
|
247
397
|
structurally impossible spec via a typed `ServicesReview278BuildError`
|
|
248
|
-
(`X12_278_BUILD_INVALID_HIERARCHY
|
|
398
|
+
(`X12_278_BUILD_INVALID_HIERARCHY`: a subscriber with neither a
|
|
249
399
|
review nor a dependent, a dependent with no review;
|
|
250
|
-
`X12_278_BUILD_INVALID_SPEC
|
|
400
|
+
`X12_278_BUILD_INVALID_SPEC`: a review with no request category code,
|
|
251
401
|
a request review carrying an HCR decision, a response decision with an
|
|
252
402
|
empty action code, an over-long control number). The message carries
|
|
253
|
-
structural locators only (`subscriber.review[0]`, level codes)
|
|
403
|
+
structural locators only (`subscriber.review[0]`, level codes), never
|
|
254
404
|
a member name, member id, trace, or diagnosis code (PHI discipline).
|
|
255
405
|
- New public exports: `build278Request`, `build278Response`,
|
|
256
406
|
`ServicesReview278BuildError`, `AUTH_278_BUILD_ERROR_CODES`,
|
|
257
407
|
`ServicesReview278BuildErrorCode`, and the `Build278Spec` type tree.
|
|
258
|
-
- **Domain builders
|
|
408
|
+
- **Domain builders: `build271` (005010X279A1 Eligibility Benefit
|
|
259
409
|
Response) and `build277` / `build277CA` (005010X212 Claim Status
|
|
260
410
|
Response / 005010X214 Claim Acknowledgment).** The response-side emit
|
|
261
411
|
counterparts to `get271Eligibility` / `get277Status` /
|
|
262
412
|
`get277CADisposition`, layered on the Phase 8 general builder and
|
|
263
|
-
mirroring the pure-function `build835` / `build837` pattern
|
|
413
|
+
mirroring the pure-function `build835` / `build837` pattern. They
|
|
264
414
|
NEVER auto-send, open a socket, or touch the filesystem, and return a
|
|
265
415
|
frozen `X12Interchange`.
|
|
266
416
|
- **`build271(spec)`** assembles a complete interchange (one GS..GE
|
|
@@ -277,7 +427,7 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
|
|
|
277
427
|
through `parseX12` so a well-formed spec is reproduced field-for-field.
|
|
278
428
|
- **TRN echo is the safety-critical reassociation invariant.** The
|
|
279
429
|
builder places the caller-supplied trace into TRN-02 verbatim and
|
|
280
|
-
NEVER fabricates, normalizes, or mutates it
|
|
430
|
+
NEVER fabricates, normalizes, or mutates it. A build-side property
|
|
281
431
|
test feeds random trace tokens through all three builders and asserts
|
|
282
432
|
the round-tripped `referenceId` is byte-for-byte the input.
|
|
283
433
|
- **The HL spine is computed, never caller-supplied.** The builder
|
|
@@ -287,26 +437,26 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
|
|
|
287
437
|
hierarchy is unrepresentable and SE-01 is correct by construction.
|
|
288
438
|
- **Refusal, not silent corruption.** The builder REFUSES a
|
|
289
439
|
structurally impossible spec via a typed `Eligibility271BuildError`
|
|
290
|
-
(`X12_271_BUILD_INVALID_HIERARCHY
|
|
291
|
-
a childless receiver; `X12_271_BUILD_INVALID_SPEC
|
|
440
|
+
(`X12_271_BUILD_INVALID_HIERARCHY`: no source / a childless source /
|
|
441
|
+
a childless receiver; `X12_271_BUILD_INVALID_SPEC`: over-long control
|
|
292
442
|
number) or `ClaimStatus277BuildError`
|
|
293
|
-
(`X12_277_BUILD_INVALID_HIERARCHY
|
|
443
|
+
(`X12_277_BUILD_INVALID_HIERARCHY`: no source / a childless source /
|
|
294
444
|
receiver / provider / a subscriber with neither claim nor dependent /
|
|
295
|
-
a childless dependent; `X12_277_BUILD_INVALID_SPEC
|
|
445
|
+
a childless dependent; `X12_277_BUILD_INVALID_SPEC`: a claim with no
|
|
296
446
|
trace / status / service line, an STC with no category code, an
|
|
297
447
|
over-long control number). The message carries structural locators
|
|
298
448
|
only (`source[0].receiver[0].provider[0].subscriber[0]`, level codes,
|
|
299
|
-
counts)
|
|
449
|
+
counts), never a member name, member id, or trace (PHI discipline).
|
|
300
450
|
- New public exports: `build271`, `Eligibility271BuildError`,
|
|
301
451
|
`ELIGIBILITY_271_BUILD_ERROR_CODES`, `Eligibility271BuildErrorCode`,
|
|
302
452
|
the `Build271Spec` type tree; `build277`, `build277CA`,
|
|
303
453
|
`ClaimStatus277BuildError`, `CLAIM_STATUS_277_BUILD_ERROR_CODES`,
|
|
304
454
|
`ClaimStatus277BuildErrorCode`, and the `Build277Spec` type tree.
|
|
305
|
-
- **Domain builders
|
|
455
|
+
- **Domain builders: `build837P` / `build837I` / `build837D` (005010
|
|
306
456
|
837 Health Care Claim: Professional `X222A2`, Institutional `X223A3`,
|
|
307
457
|
Dental `X224A2`).** The claim-submission emit counterpart to
|
|
308
458
|
`get837Claims`, layered on the Phase 8 general builder and mirroring
|
|
309
|
-
the pure-function `build835` pattern
|
|
459
|
+
the pure-function `build835` pattern. They NEVER auto-send, open a
|
|
310
460
|
socket, or touch the filesystem.
|
|
311
461
|
- **`build837P/I/D(spec)`** each assemble a complete `X12Interchange`
|
|
312
462
|
(one GS..GE group, GS-01 `HC`; one ST..SE 837, ST-03 per variant)
|
|
@@ -334,17 +484,17 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
|
|
|
334
484
|
line whose `variant` mismatches the builder, an empty procedure /
|
|
335
485
|
revenue code, an over-long control number). The message carries
|
|
336
486
|
structural locators only (`billing[0].subscriber[0].claim[0]`, level
|
|
337
|
-
codes, counts)
|
|
487
|
+
codes, counts), never the `claimId` or a member id (PHI discipline).
|
|
338
488
|
- New public exports: `build837P`, `build837I`, `build837D`,
|
|
339
489
|
`Claim837BuildError`, `CLAIM_837_BUILD_ERROR_CODES`,
|
|
340
490
|
`Claim837BuildErrorCode`, and the `Build837Spec` type tree.
|
|
341
491
|
- Known limitation: claim-/line-level provider addresses (Loop
|
|
342
|
-
2310/2420 N3/N4) are a documented read-side limitation
|
|
492
|
+
2310/2420 N3/N4) are a documented read-side limitation: the NM1
|
|
343
493
|
fields round-trip, the address does not.
|
|
344
|
-
- **Domain builder
|
|
494
|
+
- **Domain builder: `build835` (005010X221A1 ERA).** The first
|
|
345
495
|
per-transaction emit helper layers the safety-critical TR3 invariants
|
|
346
496
|
on top of the Phase 8 general builder, mirroring the pure-function
|
|
347
|
-
`build999` / `buildTA1` pattern
|
|
497
|
+
`build999` / `buildTA1` pattern. It NEVER auto-sends, opens a socket,
|
|
348
498
|
or touches the filesystem.
|
|
349
499
|
- **`build835(spec)`** assembles a complete `X12Interchange` (one
|
|
350
500
|
GS..GE group, GS-01 `HP`; one ST..SE 835, ST-03 `005010X221A1`) from
|
|
@@ -354,7 +504,7 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
|
|
|
354
504
|
claims → Loop 2110 service lines → PLB) and the output round-trips
|
|
355
505
|
through `parseX12` so a balanced spec is reproduced by `get835`
|
|
356
506
|
field-for-field. Composites (CLP-08, SVC-01, SVC-06, PLB) escape
|
|
357
|
-
each component then join with the raw component separator
|
|
507
|
+
each component then join with the raw component separator. The
|
|
358
508
|
envelope is emitted inline (not via `buildInterchange`) to avoid
|
|
359
509
|
double-escaping a pre-composed element. Same-group CAS and
|
|
360
510
|
same-provider/period PLB adjustments pack into one segment (≤ 6
|
|
@@ -366,10 +516,10 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
|
|
|
366
516
|
(`checkServiceLineBalance` / `checkClaimBalance` /
|
|
367
517
|
`checkRemitTotalBalance`) against a materialized read model so emit
|
|
368
518
|
guard and parse warning share one source of truth. Codes:
|
|
369
|
-
`X12_835_BUILD_BALANCE_MISMATCH` (any §1.10.2 invariant
|
|
519
|
+
`X12_835_BUILD_BALANCE_MISMATCH` (any §1.10.2 invariant: line,
|
|
370
520
|
claim, or top-of-remit) and `X12_835_BUILD_INVALID_SPEC` (no TRN
|
|
371
521
|
trace, an empty CLP-01, an over-long ISA-13). The thrown message
|
|
372
|
-
carries numeric totals only
|
|
522
|
+
carries numeric totals only, never a patient-control number or
|
|
373
523
|
member id (PHI discipline).
|
|
374
524
|
- **New exports.** `build835`, `Remit835BuildError`,
|
|
375
525
|
`REMIT_835_BUILD_ERROR_CODES`, `Remit835BuildErrorCode`, and the
|
|
@@ -383,14 +533,14 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
|
|
|
383
533
|
`build820` / `build834`) layer on the same general surface and are
|
|
384
534
|
NOT in this change.
|
|
385
535
|
|
|
386
|
-
- **Phase 8
|
|
536
|
+
- **Phase 8: spec-clean serializer + general interchange builder (the
|
|
387
537
|
emit half lands).** Two new public surfaces close the read↔write loop.
|
|
388
538
|
- **`serializeX12(interchange, opts?)`** turns any parsed
|
|
389
539
|
`X12Interchange` back into an X12 byte stream. Default mode is
|
|
390
|
-
byte-faithful
|
|
540
|
+
byte-faithful: reconstructed purely from the verbatim `.raw`
|
|
391
541
|
strings the parser preserved (ISA + terminator, then each
|
|
392
542
|
TA1 / GS / segment / GE / IEA terminator-joined, then any
|
|
393
|
-
`trailingBytes`)
|
|
543
|
+
`trailingBytes`), so for a Tier-1 input it reproduces the source
|
|
394
544
|
bytes exactly: the idempotency fixed point
|
|
395
545
|
`serialize(parse(s)) === s`. With `{ specClean: true }` it ALSO
|
|
396
546
|
reconciles the envelope (SE-01 / GE-01 / IEA-01 counts + the
|
|
@@ -406,7 +556,7 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
|
|
|
406
556
|
body values via the `?` release char, and round-trips its output
|
|
407
557
|
back through `parseX12` so the returned interchange is bit-identical
|
|
408
558
|
to the parsed form. Structurally impossible specs are REFUSED with a
|
|
409
|
-
typed `X12BuildError` (`X12_BUILD_INVALID_SPEC`)
|
|
559
|
+
typed `X12BuildError` (`X12_BUILD_INVALID_SPEC`): an over-long
|
|
410
560
|
ISA-13, a body segment with no id.
|
|
411
561
|
- **New warning + exports.** `X12_SEGMENT_COUNT_MISMATCH` is a
|
|
412
562
|
serializer-only diagnostic (the parser never validated SE-01);
|
|
@@ -428,14 +578,14 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
|
|
|
428
578
|
validated (it checks GE-01 / IEA-01 / control pairs but not SE-01):
|
|
429
579
|
SE-01 miscounts in `837i-canonical` (30→33), `837d-canonical`
|
|
430
580
|
(25→26), `999-accept` (5→6), and a GS-06/GE-02 mismatch in
|
|
431
|
-
`278-response` (GS-06 2→1)
|
|
581
|
+
`278-response` (GS-06 2→1), an accuracy-gate win.
|
|
432
582
|
- **Known limitation (deferred).** Domain per-transaction builders
|
|
433
583
|
(`build835` / `build837P/I/D` / `build271` / …, the safety-critical
|
|
434
584
|
emit code enforcing per-TR3 balance + certification invariants) are
|
|
435
585
|
NOT in this phase; the general envelope surface they layer on top of
|
|
436
586
|
is.
|
|
437
587
|
|
|
438
|
-
- **Phase 7
|
|
588
|
+
- **Phase 7: 278 services review + 834 enrollment + 820 premium
|
|
439
589
|
payment (the v1 transaction scope rounds out).** Four new read-side
|
|
440
590
|
helpers: `get278Request` / `get278Response` (TR3 `005010X217` /
|
|
441
591
|
`005010X216`), `get820Payments` (TR3 `005010X218`), and the streaming
|
|
@@ -444,13 +594,13 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
|
|
|
444
594
|
278 response `HCR-01` certification action (certified /
|
|
445
595
|
not-certified / pended / modified) is captured as-is on each event /
|
|
446
596
|
service review; the 834 `INS-03` / `HD-01` maintenance type (X12 0875) is preserved and an unknown code raises
|
|
447
|
-
`X12_834_UNKNOWN_MAINTENANCE_TYPE` on the affected member only
|
|
597
|
+
`X12_834_UNKNOWN_MAINTENANCE_TYPE` on the affected member only. No
|
|
448
598
|
action is ever synthesized.
|
|
449
599
|
- **834 streaming.** `get834Enrollments` is an
|
|
450
600
|
`AsyncIterable<X12Enrollment>` yielding one member per `INS` loop;
|
|
451
601
|
a streaming property test drives a 10MB+ synthetic roster with
|
|
452
602
|
early-break. (Honest limitation: v1 still parses into `tx.segments`
|
|
453
|
-
up front
|
|
603
|
+
up front. A true file→iterator source is a v2 item.)
|
|
454
604
|
- **278 HL spine** `20 → 21 → 22 → 23` validated via the shared
|
|
455
605
|
`validateHl`; the `EV` / `SS` event + service levels are
|
|
456
606
|
deliberately tolerant (omitted from the expected-parent map).
|
|
@@ -466,7 +616,7 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
|
|
|
466
616
|
(H-PHI invariant). Synthetic fixtures across all three surfaces,
|
|
467
617
|
unit tests, and the 834 streaming property. Serialization is
|
|
468
618
|
Phase 8.
|
|
469
|
-
- **PHI commit-gate
|
|
619
|
+
- **PHI commit-gate: a zero-dependency, X12-shape-aware PHI scanner
|
|
470
620
|
(`scripts/phi-scan.ts`, run via `pnpm phi-scan`).** Guards the
|
|
471
621
|
synthetic fixture corpus: it refuses any test fixture or `src/` file
|
|
472
622
|
carrying real-PHI-shaped tokens so a developer cannot commit a
|
|
@@ -476,7 +626,7 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
|
|
|
476
626
|
`scripts/verify.sh` summary from `phi-scan SKIP` to `phi-scan ✓`.
|
|
477
627
|
- **Synthetic allow-list, not an inline header.** X12 `.edi` is
|
|
478
628
|
byte-strict (ISA must start at byte 0), so an inline
|
|
479
|
-
`# synthetic: true` marker is impossible
|
|
629
|
+
`# synthetic: true` marker is impossible. It would break every
|
|
480
630
|
parser test. Same constraint DICOM hits with binary `.dcm`, so the
|
|
481
631
|
same proven solution: `scripts/phi-allow-list.txt` positively
|
|
482
632
|
declares which names / dates-of-birth / ids / email-domains are
|
|
@@ -491,17 +641,17 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
|
|
|
491
641
|
- **Audited bypass.** A whole-file `--allow-fixture <path>` is
|
|
492
642
|
rejected unless `phi-scan-overrides.md` carries a matching
|
|
493
643
|
`### <path>` entry, so a silenced file is always a recorded act.
|
|
494
|
-
Every subprocess is `git` via `execFileSync` array args
|
|
644
|
+
Every subprocess is `git` via `execFileSync` array args, no shell
|
|
495
645
|
form. Unit tests cover the clean interchange, each violator class,
|
|
496
646
|
the plain-text pass, and both arms of the override gate.
|
|
497
647
|
|
|
498
|
-
- **Phase 6
|
|
499
|
-
Status
|
|
648
|
+
- **Phase 6: 271 Eligibility Benefit Response + 277 / 277CA Claim
|
|
649
|
+
Status, TR3s `005010X279A1` (270/271), `005010X212` (276/277),
|
|
500
650
|
`005010X214` (277CA).** Three new public walkers:
|
|
501
651
|
`get271Eligibility(delimiters, tx)`, `get277Status(delimiters, tx)`,
|
|
502
652
|
and `get277CADisposition(delimiters, tx)`. 277 and 277CA share one
|
|
503
653
|
internal walk disambiguated by the `ST-03` implementation-convention
|
|
504
|
-
reference
|
|
654
|
+
reference: `get277CADisposition` admits only `005010X214`;
|
|
505
655
|
`get277Status` admits either. Each returns `undefined` only on a
|
|
506
656
|
mis-routed call (wrong `ST-01`); every recoverable deviation is a
|
|
507
657
|
warning, never a throw.
|
|
@@ -509,7 +659,7 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
|
|
|
509
659
|
requesting 270's `TRN-02` trace verbatim onto its enclosing
|
|
510
660
|
subscriber / dependent, and a 277 echoes the 276's onto its claim,
|
|
511
661
|
so the provider can re-associate the answer with the request it
|
|
512
|
-
sent
|
|
662
|
+
sent. The walkers NEVER mutate, normalize, or drop the trace. A
|
|
513
663
|
round-trip property test asserts byte-for-byte echo across an
|
|
514
664
|
arbitrary trace grammar.
|
|
515
665
|
- **Status-code fidelity (277 family).** Each STC composite
|
|
@@ -521,7 +671,7 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
|
|
|
521
671
|
`X12_UNKNOWN_CLAIM_STATUS`. A 277CA provider-level batch
|
|
522
672
|
acknowledgment opens a claim on a standalone STC (no TRN).
|
|
523
673
|
- **HL parent-pointer integrity.** Enforced through the shared
|
|
524
|
-
`validateHl` primitive
|
|
674
|
+
`validateHl` primitive: 271 spine `20 → 21 → 22 → 23`; 277 / 277CA
|
|
525
675
|
spine `20 → 21 → 19 → 22 → 23`. A dangling or mis-levelled parent
|
|
526
676
|
emits `X12_HL_PARENT_MISMATCH` / `X12_HL_PARENT_LEVEL_INVALID`; the
|
|
527
677
|
walker NEVER silently re-numbers and the verbatim declared parent id
|
|
@@ -545,16 +695,16 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
|
|
|
545
695
|
HSD detail, and III / LS / LE markers in the 271, plus QTY / AMT
|
|
546
696
|
claim-summary roll-ups in a 277CA Loop 2200, are preserved on
|
|
547
697
|
`tx.segments` verbatim but not yet typed onto the model.
|
|
548
|
-
- **Phase 5
|
|
698
|
+
- **Phase 5: 837 Healthcare Claim, TR3s `005010X222A2` (Professional),
|
|
549
699
|
`005010X223A3` (Institutional), `005010X224A2` (Dental).** The
|
|
550
|
-
claim-creation surface
|
|
700
|
+
claim-creation surface: the volume side of HIPAA EDI traffic.
|
|
551
701
|
`get837Claims(delimiters, tx, opts?)` walks a parsed 837 transaction
|
|
552
702
|
set into the typed `X12_837Submission` model: variant detection (from
|
|
553
703
|
ST-03 implementation-convention reference, falling back to SVx
|
|
554
704
|
segment id, then to `"unknown"` with `X12_837_UNKNOWN_VARIANT`),
|
|
555
705
|
submitter (Loop 1000A NM1\*41) + receiver (Loop 1000B NM1\*40), the
|
|
556
706
|
full HL hierarchy (Loops 2000A / 2000B / 2000C), every claim header
|
|
557
|
-
(Loop 2300
|
|
707
|
+
(Loop 2300: CLM with patient account number, total charge,
|
|
558
708
|
composite POS / facility-code-qualifier / claim-frequency-code,
|
|
559
709
|
signature / assignment / benefits / release-of-information
|
|
560
710
|
indicators), and every service line typed by variant (`SV1` →
|
|
@@ -562,18 +712,18 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
|
|
|
562
712
|
- **HL parent-pointer integrity.** The 837 family's safety primitive
|
|
563
713
|
is the HL hierarchy (`HL-01` own id, `HL-02` parent id, `HL-03`
|
|
564
714
|
level code: `20` Information Source / `22` Subscriber / `23`
|
|
565
|
-
Dependent). An off-by-one in `HL-02` is THE #1 837 bug
|
|
715
|
+
Dependent). An off-by-one in `HL-02` is THE #1 837 bug. The
|
|
566
716
|
walker validates that every non-top-level HL's `HL-02` references
|
|
567
717
|
an earlier-emitted `HL-01` AND that the parent's level matches the
|
|
568
718
|
TR3-required parent for this level (`22` → parent `20`; `23` →
|
|
569
719
|
parent `22`). Violations emit `X12_HL_PARENT_MISMATCH` or
|
|
570
|
-
`X12_HL_PARENT_LEVEL_INVALID
|
|
720
|
+
`X12_HL_PARENT_LEVEL_INVALID`. The parser NEVER silently
|
|
571
721
|
re-numbers. The verbatim declared parent id stays on the
|
|
572
722
|
`X12HierarchicalLevel` entry.
|
|
573
723
|
- **HI qualifier → code-system provenance.** `HI` carries
|
|
574
724
|
diagnoses, principal procedures, external cause of injury,
|
|
575
725
|
condition codes, occurrence codes, value codes, and DRG / PR
|
|
576
|
-
groupings under one segment id
|
|
726
|
+
groupings under one segment id, with the qualifier (first
|
|
577
727
|
component) governing the code system. The new
|
|
578
728
|
`src/code-lists/hi-qualifiers.ts` ships a frozen `HI_QUALIFIERS`
|
|
579
729
|
registry covering the qualifiers cited across the three TR3s
|
|
@@ -605,16 +755,16 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
|
|
|
605
755
|
re-parsing the literal.
|
|
606
756
|
- **Variant-specific service-line types.** The
|
|
607
757
|
{@link X12_837ServiceLine} discriminated union holds three shapes:
|
|
608
|
-
- `X12_837ServiceLineProfessional
|
|
758
|
+
- `X12_837ServiceLineProfessional`: `procedureQualifier` /
|
|
609
759
|
`procedureCode` / `modifiers` from SV1-01 composite; 1-4
|
|
610
760
|
`diagnosisPointers` from SV1-07; emergency / EPSDT / family-
|
|
611
761
|
planning indicators; optional `drug` (Loop 2410 LIN + CTP NDC +
|
|
612
762
|
UCUM unit).
|
|
613
|
-
- `X12_837ServiceLineInstitutional
|
|
763
|
+
- `X12_837ServiceLineInstitutional`: `revenueCode` (NUBC 4-digit
|
|
614
764
|
from SV2-01); optional procedure / modifiers from SV2-02
|
|
615
765
|
composite; `serviceLineRate` (SV2-06); `nonCoveredCharge`
|
|
616
766
|
(SV2-07).
|
|
617
|
-
- `X12_837ServiceLineDental
|
|
767
|
+
- `X12_837ServiceLineDental`: ADA CDT `procedureCode` from
|
|
618
768
|
SV3-01; `oralCavityArea` composite from SV3-04; per-line
|
|
619
769
|
`toothInformation` from `TOO*JP` (Universal Tooth Numbering)
|
|
620
770
|
with surface codes from TOO-03's composite components;
|
|
@@ -623,7 +773,7 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
|
|
|
623
773
|
`serviceLine.adjudications` as `X12LineAdjudication[]`. Each
|
|
624
774
|
adjudication ships the other-payer id (SVD-01), amount paid as
|
|
625
775
|
`X12Decimal` (SVD-02), the other payer's procedure code, paid
|
|
626
|
-
units, and any CAS adjustments
|
|
776
|
+
units, and any CAS adjustments, re-using `X12RemitAdjustment` /
|
|
627
777
|
`lookupCarc` from the 835 helper since CAS semantics are
|
|
628
778
|
identical.
|
|
629
779
|
- **Loop 2320 Other Subscriber (COB).** Captured at the surface
|
|
@@ -631,9 +781,9 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
|
|
|
631
781
|
individual relationship, claim filing indicator, and the
|
|
632
782
|
other-subscriber + other-payer NM1 entities. Detailed CAS / OI /
|
|
633
783
|
MOA breakdown inside Loop 2320 is deferred to Phase 9 (companion-
|
|
634
|
-
guide tolerance)
|
|
784
|
+
guide tolerance). Verbatim segments remain on `tx.segments`.
|
|
635
785
|
- **Eleven dogfooded `LoopSpec` artifacts** ship through the public
|
|
636
|
-
`defineLoopSpec()` API
|
|
786
|
+
`defineLoopSpec()` API, the dogfooding gate locked in Phase 2.
|
|
637
787
|
`CLAIM_837_LOOP_1000A` / `_1000B` (submitter / receiver),
|
|
638
788
|
`CLAIM_837_LOOP_2010AA` (billing provider name), `_2010BA`
|
|
639
789
|
(subscriber name), `_2010BB` (payer name), `_2010CA` (patient
|
|
@@ -642,7 +792,7 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
|
|
|
642
792
|
`CLAIM_837{P,I,D}_LOOP_2000A` / `_2300` / `_2400` trees.
|
|
643
793
|
- **Bundled HI qualifier registry under
|
|
644
794
|
`src/code-lists/hi-qualifiers.ts`** alongside the existing CARC /
|
|
645
|
-
RARC / CLP_STATUS / CAGC snapshots
|
|
795
|
+
RARC / CLP_STATUS / CAGC snapshots, formally part of the
|
|
646
796
|
code-list family, not a transaction-local table.
|
|
647
797
|
- **Two new exported constants for safety + ergonomics:**
|
|
648
798
|
`HL_LEVEL_CODES` (`INFORMATION_SOURCE` `"20"` / `INFORMATION_RECEIVER`
|
|
@@ -650,21 +800,21 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
|
|
|
650
800
|
`NM1_QUALIFIERS` (`SUBMITTER` `"41"` / `RECEIVER` `"40"` /
|
|
651
801
|
`BILLING_PROVIDER` `"85"` / `PAY_TO_ADDRESS` `"87"` /
|
|
652
802
|
`PAY_TO_PLAN` `"PE"` / `SUBSCRIBER` `"IL"` / `PAYER` `"PR"` /
|
|
653
|
-
`PATIENT` `"QC"`)
|
|
803
|
+
`PATIENT` `"QC"`), so the walker (and any consumer Phase 8
|
|
654
804
|
builder) never has to magic-string the safety-critical
|
|
655
805
|
discriminators.
|
|
656
|
-
- **Six new shared element-read helpers in `parser/segment.ts
|
|
806
|
+
- **Six new shared element-read helpers in `parser/segment.ts`**:
|
|
657
807
|
`elementValue` / `elementOptional` / `componentOptional` /
|
|
658
|
-
`elementDecimal` / `elementDecimalOrZero` / `collectElementValues
|
|
659
|
-
|
|
808
|
+
`elementDecimal` / `elementDecimalOrZero` / `collectElementValues`,
|
|
809
|
+
extracted out of the 835 and 837 walkers (both walkers had
|
|
660
810
|
byte-identical copies). New transaction walkers (Phase 6+ 270/271,
|
|
661
|
-
277, 834) inherit them. Public surface
|
|
811
|
+
277, 834) inherit them. Public surface: exported via
|
|
662
812
|
`@cosyte/x12`.
|
|
663
813
|
- **Public-surface additions** to the warning stability snapshot:
|
|
664
814
|
`X12_HL_PARENT_MISMATCH`, `X12_HL_PARENT_LEVEL_INVALID`,
|
|
665
815
|
`X12_UNKNOWN_HI_QUALIFIER`, `X12_MISSING_REQUIRED_LOOP`,
|
|
666
|
-
`X12_837_UNKNOWN_VARIANT` (13 → 18 Tier-2 codes; additions-only
|
|
667
|
-
|
|
816
|
+
`X12_837_UNKNOWN_VARIANT` (13 → 18 Tier-2 codes; additions-only,
|
|
817
|
+
fatal registry stays at 4). All new warning factories
|
|
668
818
|
(`hlParentMismatch` / `hlParentLevelInvalid` /
|
|
669
819
|
`unknownHiQualifier` / `missingRequiredLoop` /
|
|
670
820
|
`unknown837Variant`) shape-validate echoed values through
|
|
@@ -672,11 +822,11 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
|
|
|
672
822
|
`/^[0-9]{2}$/u` for level codes, `/^[A-Z][A-Z0-9]{1,2}$/u` for HI
|
|
673
823
|
qualifiers, `/^[0-9A-Z]{3,6}$/u` for loop ids,
|
|
674
824
|
`/^[0-9A-Z]{3,16}$/u` for ICR) and substitute `(non-spec)` for
|
|
675
|
-
hostile inputs
|
|
825
|
+
hostile inputs, the H-PHI invariant from `@cosyte/hl7`.
|
|
676
826
|
- **PHI discipline.** Warnings NEVER echo field VALUES; the
|
|
677
827
|
`missingRequiredLoop` rationale strings are hard-coded literals
|
|
678
828
|
(no element interpolation). Patient names / member IDs / NPIs /
|
|
679
|
-
claim numbers are surfaced verbatim on the typed model only
|
|
829
|
+
claim numbers are surfaced verbatim on the typed model only, the
|
|
680
830
|
documented consumer-redaction boundary (mirrors hl7 + the 835
|
|
681
831
|
helper). The `X12ClaimNote` JSDoc explicitly flags NTE-02 as
|
|
682
832
|
PHI-bearing (provider-supplied free text). Every Phase 5 fixture
|
|
@@ -685,10 +835,10 @@ CHILD`; sequential member IDs `MEMBER001`–`MEMBER011` etc.; NPI-
|
|
|
685
835
|
shaped sequential numbers; obvious test addresses) and matches
|
|
686
836
|
the established 835 fixture conventions.
|
|
687
837
|
- **Known limitations after this phase** (deliberate v1 scope; none
|
|
688
|
-
silent
|
|
838
|
+
silent, verbatim segments remain on `tx.segments` for raw
|
|
689
839
|
access):
|
|
690
840
|
- Loop 2320/2330 Other Subscriber / Other Payer captured at the
|
|
691
|
-
surface level only
|
|
841
|
+
surface level only. Detailed CAS / OI / MOA inside Loop 2320
|
|
692
842
|
deferred to Phase 9 (companion-guide profile system).
|
|
693
843
|
- Loop 2420 service-line provider names captured verbatim on
|
|
694
844
|
`serviceLine.providers`; per-provider PRV + address not yet
|
|
@@ -723,7 +873,7 @@ CHILD`; sequential member IDs `MEMBER001`–`MEMBER011` etc.; NPI-
|
|
|
723
873
|
coverage (96.91% stmts / 90.61% branches / 97.67% funcs / 98.49%
|
|
724
874
|
lines globally; per-dir ≥90 on `parser/` + `loops/` +
|
|
725
875
|
`transactions/` + `code-lists/`) + build + attw + verify:exports.
|
|
726
|
-
- **`phi-scan` SKIP
|
|
876
|
+
- **`phi-scan` SKIP**, unchanged from Phase 4. The runtime H-PHI
|
|
727
877
|
invariant is necessary but not sufficient; static fixture
|
|
728
878
|
scanning is tracked as the `X12-PHI-SCAN` backlog follow-up.
|
|
729
879
|
|
|
@@ -733,7 +883,7 @@ CHILD`; sequential member IDs `MEMBER001`–`MEMBER011` etc.; NPI-
|
|
|
733
883
|
`elementValue` / `elementOptional` / `componentOptional` /
|
|
734
884
|
`elementDecimal` / `elementDecimalOrZero` / `collectElementValues`.
|
|
735
885
|
Re-used by the 835 helper (`get835`) and the new 837 helper
|
|
736
|
-
(`get837Claims`)
|
|
886
|
+
(`get837Claims`). Both walkers previously defined byte-identical
|
|
737
887
|
copies of these inline. Additive; no breaking change.
|
|
738
888
|
|
|
739
889
|
- **`src/code-lists/` gains `hi-qualifiers.ts`** with `HI_QUALIFIERS`
|
|
@@ -741,26 +891,26 @@ CHILD`; sequential member IDs `MEMBER001`–`MEMBER011` etc.; NPI-
|
|
|
741
891
|
`isProcedureQualifier` and the `X12HiCategory` / `X12HiCodeSystem`
|
|
742
892
|
/ `X12HiQualifier` types. Re-exported from `@cosyte/x12` root.
|
|
743
893
|
|
|
744
|
-
- **Phase 4
|
|
745
|
-
`005010X221A1`.** The cash-posting surface
|
|
894
|
+
- **Phase 4: 835 Healthcare Claim Payment/Advice (ERA), TR3
|
|
895
|
+
`005010X221A1`.** The cash-posting surface: money, the consultant ask.
|
|
746
896
|
`get835(delimiters, tx)` walks a parsed 835 transaction set into the
|
|
747
897
|
typed `X12Remittance` model: payment header (BPR), trace numbers (TRN),
|
|
748
898
|
payer / payee parties (Loops 1000A / 1000B with address / contact /
|
|
749
|
-
additional identifiers), every claim payment (Loop 2100
|
|
899
|
+
additional identifiers), every claim payment (Loop 2100: CLP plus
|
|
750
900
|
patient / subscriber / service-provider NM1s, statement-period DTMs,
|
|
751
901
|
CAS adjustments at both claim and service-line scope, MIA / MOA / LQ
|
|
752
902
|
remarks, REF / AMT supplemental amounts), every service line (Loop
|
|
753
|
-
2110
|
|
903
|
+
2110: SVC with HCPCS / CPT / NDC / revenue-code / modifier
|
|
754
904
|
destructuring, service-date DTMs, line-level CAS / REF / AMT / LQ),
|
|
755
905
|
and provider-level adjustments (PLB with multi-pair flattening). The
|
|
756
906
|
loop hierarchy ships as three frozen `LoopSpec` artifacts
|
|
757
907
|
(`REMIT_835_LOOP_2000`, `REMIT_835_LOOP_2100`, `REMIT_835_LOOP_2110`)
|
|
758
|
-
authored through the public `defineLoopSpec()` API
|
|
908
|
+
authored through the public `defineLoopSpec()` API, the **dogfooding
|
|
759
909
|
gate** locked in Phase 2. Two payer-side loop specs (1000A / 1000B)
|
|
760
910
|
also ship as introspection artifacts.
|
|
761
911
|
- **Money discipline.** All monetary fields decode as the new
|
|
762
912
|
`X12Decimal` (`src/decimal.ts`): a string-backed decimal type with
|
|
763
|
-
`BigInt`-exact arithmetic. **NEVER `parseFloat
|
|
913
|
+
`BigInt`-exact arithmetic. **NEVER `parseFloat`**: float
|
|
764
914
|
representation silently destroys cents at scale; on an 835 a dropped
|
|
765
915
|
decimal is the wrong dollar amount in someone's cash post.
|
|
766
916
|
`X12Decimal` preserves the inbound lexical form for byte-exact
|
|
@@ -771,29 +921,29 @@ CHILD`; sequential member IDs `MEMBER001`–`MEMBER011` etc.; NPI-
|
|
|
771
921
|
`toNumber()` whose JSDoc warns about precision loss. `fromBigInt(value,
|
|
772
922
|
scale)` renders canonically with zero-padded fractions; the canonical
|
|
773
923
|
`X12Decimal.ZERO` is the additive identity. Empty inbound element →
|
|
774
|
-
`undefined` (not zero)
|
|
924
|
+
`undefined` (not zero): "not supplied" and "zero dollars" are
|
|
775
925
|
spec-distinct.
|
|
776
|
-
- **Balance invariants (per TR3 X221A1 §1.10.2
|
|
926
|
+
- **Balance invariants (per TR3 X221A1 §1.10.2: "Balancing the 835").**
|
|
777
927
|
Three checks run after the walk and emit
|
|
778
|
-
`X12_835_REMIT_BALANCE_MISMATCH` on mismatch
|
|
928
|
+
`X12_835_REMIT_BALANCE_MISMATCH` on mismatch. The model is **NEVER
|
|
779
929
|
silently rebalanced**: 1. Line: `SVC-02 === SVC-03 + Σ(line CAS)` per Loop 2110. 2. Claim: `CLP-03 === CLP-04 + Σ(all CAS in claim, claim AND line
|
|
780
|
-
level)
|
|
930
|
+
level)`, the X12 spec balance. CLP-05 (patient responsibility)
|
|
781
931
|
is informational, NOT part of the balance equation. The
|
|
782
932
|
implementation matches the TR3 §1.10.2 text directly; an earlier
|
|
783
933
|
roadmap sketch (`operations/roadmaps/x12.md` §4) used a slightly
|
|
784
|
-
different decomposition
|
|
934
|
+
different decomposition. `src/transactions/remit/balance.ts`
|
|
785
935
|
documents the divergence so the contract stays consistent. 3. Top-of-remit: `BPR-02 === Σ(CLP-04) - Σ(PLB amounts)`. PLB
|
|
786
936
|
amounts are stored with the **raw EDI sign** (positive = take-back
|
|
787
937
|
from provider; negative = credit to provider), so the equation
|
|
788
938
|
_subtracts_ PLB to balance.
|
|
789
939
|
Warning messages echo only the invariant label and `X12Decimal`
|
|
790
|
-
decimal text
|
|
940
|
+
decimal text, never patient identifiers, member ids, or account
|
|
791
941
|
numbers (H-PHI invariant).
|
|
792
942
|
- **CAS triple flattening.** A single CAS segment can carry up to 6
|
|
793
943
|
`(reason, amount, quantity)` triples under one `CAS-01` group code;
|
|
794
944
|
the walker flattens them into individual `X12RemitAdjustment`
|
|
795
945
|
entries. Different group codes (CO / PR / OA / PI) require separate
|
|
796
|
-
CAS segments
|
|
946
|
+
CAS segments (they cannot mix inside one) and the decoder honors
|
|
797
947
|
that contract.
|
|
798
948
|
- **Bundled WPC + X12-internal code-list snapshots** (initial
|
|
799
949
|
subsets, pre-launch). Versioned data artifacts at
|
|
@@ -805,24 +955,24 @@ level)` — the X12 spec balance. CLP-05 (patient responsibility)
|
|
|
805
955
|
`lookupRarc(code)` / `lookupClpStatus(code)` return `{ code,
|
|
806
956
|
description }` for known codes, `undefined` otherwise; unknown
|
|
807
957
|
codes preserve the verbatim value on the parsed adjustment AND
|
|
808
|
-
emit `X12_UNKNOWN_CARC` / `X12_UNKNOWN_RARC`. - `CARC` (Claim Adjustment Reason Codes)
|
|
809
|
-
observed codes (WPC, snapshotDate 2026-06-27). - `RARC` (Remittance Advice Remark Codes)
|
|
958
|
+
emit `X12_UNKNOWN_CARC` / `X12_UNKNOWN_RARC`. - `CARC` (Claim Adjustment Reason Codes): ~30 most commonly
|
|
959
|
+
observed codes (WPC, snapshotDate 2026-06-27). - `RARC` (Remittance Advice Remark Codes): ~15 most commonly
|
|
810
960
|
observed codes covering both `M`- and `N`-prefix conventions
|
|
811
|
-
(WPC, snapshotDate 2026-06-27). - `CLP_STATUS` (CLP-02 Claim Status Code, X12 Code Source 65)
|
|
961
|
+
(WPC, snapshotDate 2026-06-27). - `CLP_STATUS` (CLP-02 Claim Status Code, X12 Code Source 65):
|
|
812
962
|
10 dispositions (1 Processed as Primary, 4 Denied, 22 Reversal,
|
|
813
|
-
…). X12-internal list, stable. - `CLAIM_ADJUSTMENT_GROUP_CODES
|
|
963
|
+
…). X12-internal list, stable. - `CLAIM_ADJUSTMENT_GROUP_CODES`: the spec-fixed 4 values
|
|
814
964
|
(`CO` / `PR` / `OA` / `PI`) as a frozen literal-union map,
|
|
815
965
|
not a snapshot (this list never grows). `isClaimAdjustmentGroupCode`
|
|
816
966
|
narrows inbound strings.
|
|
817
967
|
- **Public-surface additions** to the warning / fatal stability
|
|
818
968
|
snapshot: `X12_835_REMIT_BALANCE_MISMATCH`,
|
|
819
969
|
`X12_UNKNOWN_CARC`, `X12_UNKNOWN_RARC` (10 → 13 Tier-2 codes;
|
|
820
|
-
additions-only
|
|
970
|
+
additions-only, fatal registry stays at 4). New warning factories
|
|
821
971
|
`remitBalanceMismatch` / `unknownCarc` / `unknownRarc` carry the
|
|
822
972
|
shape-validated echo discipline (CARC / RARC echoes pass
|
|
823
973
|
`/^[A-Z0-9]{1,5}$/u` or collapse to `(non-spec)`).
|
|
824
974
|
- **PHI discipline (H-PHI invariant holds suite-wide).** Warning
|
|
825
|
-
messages never echo field VALUES
|
|
975
|
+
messages never echo field VALUES, only positional context, the
|
|
826
976
|
invariant label, the shape-validated CARC / RARC code, or numeric
|
|
827
977
|
X12Decimal text. Patient names, member ids, NPIs, payer claim
|
|
828
978
|
control numbers, and account numbers are held verbatim on the
|
|
@@ -834,7 +984,7 @@ description }` for known codes, `undefined` otherwise; unknown
|
|
|
834
984
|
synthetic spec-clean (`835-medicare-canonical.edi`,
|
|
835
985
|
`835-multi-claim.edi`, `835-with-plb.edi`,
|
|
836
986
|
`835-carc-rarc-mix.edi`, `835-imbalance.edi`) and one Tier-2
|
|
837
|
-
synthetic quirk shape (`835-availity-quirk.edi
|
|
987
|
+
synthetic quirk shape (`835-availity-quirk.edi`: REF*2U + REF*F8
|
|
838
988
|
placements). The imbalance fixture is deliberately off-by-$10 to
|
|
839
989
|
prove the balance warning fires and the model preserves the
|
|
840
990
|
verbatim amounts.
|
|
@@ -846,7 +996,7 @@ description }` for known codes, `undefined` otherwise; unknown
|
|
|
846
996
|
the balance warning fires iff out of balance (100 + 50 runs).
|
|
847
997
|
`remit-835-fuzz.property.test.ts` byte-flips every committed
|
|
848
998
|
fixture 300 times per fixture and asserts `get835` never throws
|
|
849
|
-
outside the 4 Tier-3 fatals
|
|
999
|
+
outside the 4 Tier-3 fatals, the byte-level fuzz target the
|
|
850
1000
|
roadmap calls for.
|
|
851
1001
|
- **Coverage gates expanded** to per-directory ≥90 on `parser/`,
|
|
852
1002
|
`loops/`, `transactions/`, `code-lists/`. Phase 4 lands the gate
|
|
@@ -856,24 +1006,24 @@ description }` for known codes, `undefined` otherwise; unknown
|
|
|
856
1006
|
Code Source 65 for CLP-02; WPC public-domain lists for CARC / RARC;
|
|
857
1007
|
X12 Data Element 1033 for the Claim Adjustment Group Code.
|
|
858
1008
|
- **Known limitations after Phase 4:** no 835 _building_ yet (that's
|
|
859
|
-
Phase 8
|
|
1009
|
+
Phase 8: round-trip + spec-clean serializer + builder); the
|
|
860
1010
|
bundled CARC / RARC are an **initial subset** (`pnpm
|
|
861
1011
|
refresh:code-lists` arrives in Phase 10); no per-payer profile
|
|
862
1012
|
yet (Phase 9); CPT / ICD-10 / NDC descriptions are deliberately
|
|
863
|
-
NOT bundled (license-gated
|
|
1013
|
+
NOT bundled (license-gated, see `operations/roadmaps/x12.md` §5);
|
|
864
1014
|
`X12Decimal` does not yet expose multiply / divide (no balance
|
|
865
1015
|
invariant needs them in v1). `phi-scan` script not yet wired for
|
|
866
|
-
x12
|
|
1016
|
+
x12. The H-PHI property tests provide runtime coverage; an
|
|
867
1017
|
explicit pre-commit phi-scan ships in a future slice (tracked in
|
|
868
1018
|
`operations/prompts/x12-phi-scan.md`).
|
|
869
1019
|
|
|
870
|
-
- **Phase 3
|
|
1020
|
+
- **Phase 3: 999 + TA1 acknowledgments (TR3 005010X231A1).** Two
|
|
871
1021
|
pure-function ack surfaces ship side-by-side; neither auto-sends, opens
|
|
872
1022
|
a socket, or touches the filesystem. The cosyte ack archetype: the
|
|
873
1023
|
library MECHANICALLY builds the disposition it is told and REFUSES to
|
|
874
1024
|
fabricate an Accept against a non-empty error list. Mirrors hl7's
|
|
875
1025
|
upcoming `buildAck` boundary and mllp's commit-contract pattern.
|
|
876
|
-
- **999 (Implementation Acknowledgment)
|
|
1026
|
+
- **999 (Implementation Acknowledgment): TR3 005010X231A1.**
|
|
877
1027
|
`parse999(raw, opts?)` decodes the AK1 → AK2 → (IK3 [→ CTX] (IK4 [→
|
|
878
1028
|
CTX])\*)\* → IK5 → AK9 hierarchy into the typed `X12Ack999`. Standard
|
|
879
1029
|
X12 / pre-X231A1 legacy senders that emit `AK3`/`AK4`/`AK5` instead
|
|
@@ -882,15 +1032,15 @@ refresh:code-lists` arrives in Phase 10); no per-payer profile
|
|
|
882
1032
|
X231A1 names. `build999(spec)` assembles a complete `X12Interchange`
|
|
883
1033
|
wrapping a single ISA → GS → ST..SE → GE → IEA with one 999 inside,
|
|
884
1034
|
spec-clean and round-trippable through `parseX12`.
|
|
885
|
-
- **TA1 (Interchange Acknowledgment)
|
|
1035
|
+
- **TA1 (Interchange Acknowledgment): ASC X12 standard, envelope
|
|
886
1036
|
level.** The Phase 1 envelope walker now captures envelope-level
|
|
887
|
-
TA1 segments verbatim onto `X12Interchange.ta1Segments
|
|
1037
|
+
TA1 segments verbatim onto `X12Interchange.ta1Segments`. TA1
|
|
888
1038
|
between ISA and the first GS (the canonical position) is recognized
|
|
889
1039
|
as spec-conformant and NO `X12_UNEXPECTED_SEGMENT` warning fires;
|
|
890
1040
|
a TA1 inside an open functional group is still flagged as unexpected
|
|
891
1041
|
(non-spec). `parseTA1(interchange)` returns the typed `X12AckTA1`
|
|
892
1042
|
for the first captured TA1 (or `undefined`). `buildTA1(spec)` emits
|
|
893
|
-
a fixed-position 5-element `Ta1Segment` (`TA101`–`TA105`)
|
|
1043
|
+
a fixed-position 5-element `Ta1Segment` (`TA101`–`TA105`). Caller
|
|
894
1044
|
wraps it in their preferred envelope. Both standalone TA1-only
|
|
895
1045
|
interchanges (ISA → TA1 → IEA, no GS) and embedded TA1s round-trip.
|
|
896
1046
|
- **Safety guards (refused via `AckBuildError`):** `build999` refuses
|
|
@@ -916,13 +1066,13 @@ refresh:code-lists` arrives in Phase 10); no per-payer profile
|
|
|
916
1066
|
- **PHI discipline (acks are structurally PHI-free by design):**
|
|
917
1067
|
Control numbers, segment IDs, position counters, and structural
|
|
918
1068
|
error codes ONLY. The one variable-shape surface that COULD carry
|
|
919
|
-
PHI
|
|
1069
|
+
PHI, `IK4-04` (`copyOfBadDataElement`), is documented on both the
|
|
920
1070
|
parsed-model type AND the build-spec type as a caller-supplied
|
|
921
1071
|
field that callers SHOULD omit when the offending bytes are PHI.
|
|
922
1072
|
The library NEVER auto-populates `IK4-04`. Error messages
|
|
923
1073
|
interpolate only control numbers, disposition codes, and count
|
|
924
1074
|
integers; no PHI-shape paths. The `phi-redaction-review` crew gate
|
|
925
|
-
passed at commit time; locked `999
|
|
1075
|
+
passed at commit time; locked `999: PHI safety` and `TA1: PHI
|
|
926
1076
|
safety` test blocks assert no SSN / ISO-date / long-digit-run
|
|
927
1077
|
shapes appear in built output.
|
|
928
1078
|
- **Three Tier-1 999 fixtures** (`999-accept.edi`,
|
|
@@ -941,20 +1091,20 @@ safety` test blocks assert no SSN / ISO-date / long-digit-run
|
|
|
941
1091
|
`X12Interchange.ta1Segments: readonly Ta1Segment[]` (additive, no
|
|
942
1092
|
rename); no new entries to `WARNING_CODES` or `FATAL_CODES`
|
|
943
1093
|
(Phase 3 keeps both registries at the Phase-2-locked sizes of 10
|
|
944
|
-
and 4
|
|
1094
|
+
and 4, additions-only thereafter).
|
|
945
1095
|
- **Spec traceability:** TR3 `005010X231A1` (999); ASC X12 standard §
|
|
946
1096
|
TA1 Interchange Acknowledgment; code lists 715 / 716 / 723 / I13 / I18.
|
|
947
1097
|
- **Known limitations after Phase 3:** Acks reference STRUCTURAL
|
|
948
|
-
errors only
|
|
1098
|
+
errors only. They cannot report semantic / payment errors (those
|
|
949
1099
|
live in `277CA` Phase 6 / `835` Phase 4). No multi-TA1 fan-out
|
|
950
1100
|
helper (consumers iterate `ta1Segments` directly when more than
|
|
951
1101
|
one inbound interchange is being acknowledged). The 999
|
|
952
1102
|
transaction-set surface does not yet expose a public Loop-spec
|
|
953
|
-
artifact
|
|
1103
|
+
artifact. Phase 3 hand-walks the AK1/AK2/IK3/IK4/IK5/AK9 hierarchy
|
|
954
1104
|
in `parse-999.ts`; the dogfooding gate for `defineLoopSpec` lands
|
|
955
1105
|
fully with Phase 4's 835 + Phase 5's 837 work.
|
|
956
1106
|
|
|
957
|
-
- **Phase 2
|
|
1107
|
+
- **Phase 2. Syntactic core: segment / element / composite / repetition
|
|
958
1108
|
decode + warning registry + `defineLoopSpec`.** Every body segment inside
|
|
959
1109
|
a transaction is now decoded into an immutable `X12Segment` carrying its
|
|
960
1110
|
id, raw text, and 1-indexed element array. The verbatim source survives
|
|
@@ -969,7 +1119,7 @@ safety` test blocks assert no SSN / ISO-date / long-digit-run
|
|
|
969
1119
|
trailing `?` is preserved verbatim AND warned as
|
|
970
1120
|
`X12_DANGLING_RELEASE_CHAR`; a `?` followed by a non-delimiter is
|
|
971
1121
|
preserved verbatim with no warning (Postel's Law).
|
|
972
|
-
- **Dot-path traversal
|
|
1122
|
+
- **Dot-path traversal**: `getSegmentValue(seg, "03-1")` resolves
|
|
973
1123
|
composite sub-element 1 of element 3 (both 1-indexed, matching TR3);
|
|
974
1124
|
`"03[2]"` resolves the 3rd repetition (0-indexed); `"03[2]-1"` combines
|
|
975
1125
|
them. Returns `undefined` for out-of-range paths, throws `TypeError`
|
|
@@ -979,14 +1129,14 @@ safety` test blocks assert no SSN / ISO-date / long-digit-run
|
|
|
979
1129
|
structural validation + a typed `LoopSpecDefinitionError`. Phase 3+
|
|
980
1130
|
transaction extractors author their built-in 999 / TA1 / 835 / 837
|
|
981
1131
|
loops through the SAME public API consumers use for payer-specific
|
|
982
|
-
loops
|
|
1132
|
+
loops, the dogfooding gate locked in `documentation/repos/x12.md`.
|
|
983
1133
|
- **Warning registry expanded 8 → 10** (additions-only):
|
|
984
1134
|
`X12_DANGLING_RELEASE_CHAR` (unpaired `?` at element/segment end;
|
|
985
1135
|
bytes are preserved on the parent element) and
|
|
986
1136
|
`X12_UNEXPECTED_SEGMENT` (a `GE` with no open `GS`, `SE` with no open
|
|
987
|
-
`ST`, body segment outside any `ST..SE
|
|
1137
|
+
`ST`, body segment outside any `ST..SE`, cases the Phase 1 walker
|
|
988
1138
|
dropped silently). The PUBLIC `WARNING_CODES` snapshot test is the
|
|
989
|
-
breaking-change tripwire
|
|
1139
|
+
breaking-change tripwire: renaming a code is breaking, additions
|
|
990
1140
|
are not.
|
|
991
1141
|
- **PHI discipline (mirrors hl7's H-PHI invariant):**
|
|
992
1142
|
`X12_UNEXPECTED_SEGMENT` SHAPE-VALIDATES the echoed segment id
|
|
@@ -998,12 +1148,12 @@ safety` test blocks assert no SSN / ISO-date / long-digit-run
|
|
|
998
1148
|
- **Tier-1 fixture** (`syntactic-core-body.edi`) exercises every Phase 2
|
|
999
1149
|
surface end-to-end: composites (`HI*ABK:J45.50`), repetitions
|
|
1000
1150
|
(`EQ*30^35^88`), `?`-release-character escape (`REF*EA*ID?*WITH?*STAR`),
|
|
1001
|
-
and straight-element segments (BHT, NM1). Real-world synthetic
|
|
1151
|
+
and straight-element segments (BHT, NM1). Real-world synthetic, no
|
|
1002
1152
|
PHI. Parses with zero warnings.
|
|
1003
1153
|
- **Properties:** release-escape round-trip (any value, any delimiters),
|
|
1004
1154
|
escapeRelease output is fully decodable as `?<reserved>` pairs +
|
|
1005
1155
|
non-reserved bytes (500 runs each), and a streaming-decode invariant
|
|
1006
|
-
(parser output is independent of input chunking
|
|
1156
|
+
(parser output is independent of input chunking, locks the v2
|
|
1007
1157
|
streaming surface as a non-breaking future addition).
|
|
1008
1158
|
- **`X12TransactionSet.segments` shape changed** from
|
|
1009
1159
|
`readonly string[]` to `readonly X12Segment[]`; the raw form moves to
|
|
@@ -1011,10 +1161,10 @@ safety` test blocks assert no SSN / ISO-date / long-digit-run
|
|
|
1011
1161
|
migrate.** Library-internal change; no impact on `ix.warnings`,
|
|
1012
1162
|
`ix.delimiters`, or the envelope-level accessors.
|
|
1013
1163
|
|
|
1014
|
-
- **Phase 1
|
|
1164
|
+
- **Phase 1: envelope decoder.** `parseX12()` decodes the ISA / GS / GE / IEA
|
|
1015
1165
|
interchange envelope from a raw `string` or `Buffer`, detecting all four
|
|
1016
1166
|
delimiters (`element` byte 4, `repetition` ISA-11, `component` ISA-16,
|
|
1017
|
-
`segment` post-ISA-16) from fixed positions inside the ISA itself
|
|
1167
|
+
`segment` post-ISA-16) from fixed positions inside the ISA itself. The parser
|
|
1018
1168
|
NEVER assumes a delimiter. Transaction-set bodies inside each ST..SE are kept
|
|
1019
1169
|
opaque at this phase (raw segment strings, terminator stripped); Phase 2 adds
|
|
1020
1170
|
segment / element / composite / repetition decode on top.
|
|
@@ -1028,8 +1178,8 @@ safety` test blocks assert no SSN / ISO-date / long-digit-run
|
|
|
1028
1178
|
- `X12ParseError` carries `code`, `position` (interchange/group/transaction/
|
|
1029
1179
|
segment/element indices), and a bounded `snippet` (≤ 64 chars) that is the
|
|
1030
1180
|
documented consumer-redaction boundary. Warning messages NEVER echo field
|
|
1031
|
-
values
|
|
1032
|
-
control-number pairs)
|
|
1181
|
+
values. They carry positional context plus bounded metadata (counts,
|
|
1182
|
+
control-number pairs), mirroring the hl7 H-PHI invariant.
|
|
1033
1183
|
- Strict mode (`parseX12(raw, { strict: true })`) escalates the first Tier-2
|
|
1034
1184
|
warning into a thrown `X12ParseError` carrying the warning code.
|
|
1035
1185
|
- 4 Tier-1 envelope fixtures committed under `test/fixtures/envelope/`
|
|
@@ -1042,7 +1192,7 @@ safety` test blocks assert no SSN / ISO-date / long-digit-run
|
|
|
1042
1192
|
|
|
1043
1193
|
### Changed
|
|
1044
1194
|
|
|
1045
|
-
- Inherits `@cosyte/test-utils` and `fast-check` as devDependencies
|
|
1195
|
+
- Inherits `@cosyte/test-utils` and `fast-check` as devDependencies. The
|
|
1046
1196
|
conformance-kit runners (`lenientNeverThrowsProperty`) and the property/fuzz
|
|
1047
1197
|
arbitraries land alongside the Phase 1 envelope code.
|
|
1048
1198
|
|