diff --git a/.changeset/278-response-declares-x217.md b/.changeset/278-response-declares-x217.md deleted file mode 100644 index d8c0a6e..0000000 --- a/.changeset/278-response-declares-x217.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -"@cosyte/x12": patch ---- - -`build278Response` now declares `005010X217` in GS-08 and ST-03, where it -declared `005010X216`. This changes what goes on the wire: a trading partner -that routed a built 278 response on either element sees the new value. - -`005010X217` is the one guide 45 CFR 162.920 names for the 278 in both -directions, and it is the guide `build278Request` already wrote. `005010X216` is -the 278 notification guide, so a receiver routing on it could file a prior -authorization decision as an admission or discharge notification. - -Only those two identifiers move. The 278 body the builder writes is unchanged: -the HL spine, the HCR certification decision and every segment other than GS -and ST are what they were. The read side is unchanged too: a 278 declaring -`005010X216` still carries `X12_GUIDE_NOT_IMPLEMENTED` on both 278 readers. The -builder's own output now reads back through `get278Response` with no -guide-mismatch warning, where it carried that code before. - -The 278 response row of `X12_TR3_CONFORMANCE` no longer records a divergence -between the guide it names and the guide the builder emits, because they are now -the same: its `note` is `null`, like the request row's. diff --git a/.changeset/binary-segment-framing.md b/.changeset/binary-segment-framing.md deleted file mode 100644 index 97ba4e1..0000000 --- a/.changeset/binary-segment-framing.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -"@cosyte/x12": patch ---- - -A BDS or BIN segment's binary data is now framed by the octet count its length -element declares (BDS-02 / BIN-01) rather than by scanning for delimiters. Until -this release a BDS-03 or BIN-02 carrying the segment terminator ended the -segment inside the data, and one carrying the element or component separator -was split into elements and components, so an attachment came back as fragments -and every segment after it was mis-framed. - -**What moves on the wire model.** The data element holds exactly the declared -octets, whatever delimiter, `?`, CR or LF bytes they include, and the segment is -one segment. `getSegmentValue`, `getAllSegmentValues` and `elementValue` return -that element verbatim, with no release unescape and no split. `serializeX12` -writes the segment back unchanged in both modes, and SE-01 counts it once. -Nothing inside the data is decoded: BDS-01's filter is not applied. - -**Four new warning codes, additions only**, each anchored at the segment and -each built from the frozen registry, echoing no byte of the length or the data: - -- `X12_BINARY_DATA_TRUNCATED`: the input ends inside the declared span. The - data element holds only the octets present. -- `X12_BINARY_LENGTH_INVALID`: the length element is absent, empty, or not 1 to - 15 ASCII digits. No length is inferred; the segment is framed by its - delimiters as any other segment is. -- `X12_BINARY_LENGTH_MISMATCH`: the byte after the declared span is not the - segment terminator. The data element is the declared span, the bytes after it - stay on the segment's `raw`, and the default emit reproduces the input. -- `X12_BINARY_LENGTH_UNVERIFIABLE`: a string input's span holds a character - above U+00FF, so its octet count cannot be verified. A `Buffer` never raises - it. - -Their factories, `binaryDataTruncated`, `binaryLengthInvalid`, -`binaryLengthMismatch` and `binaryLengthUnverifiable`, take a position and -nothing else. The length element is never rewritten on emit, including where -it disagrees with the data. An interchange carrying no BDS or BIN parses and -serializes exactly as before. diff --git a/.changeset/claims-attachments-275-277.md b/.changeset/claims-attachments-275-277.md deleted file mode 100644 index 6d4ac75..0000000 --- a/.changeset/claims-attachments-275-277.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -"@cosyte/x12": patch ---- - -Typed read and emit support for the two claims attachments guides 45 CFR -162.920 now incorporates, at (a)(19) and (a)(20), and 45 CFR 162.2002 adopts for -the period on and after May 26, 2028: the 277 Health Care Claim Request for -Additional Information (`006020X313`) and the 275 Additional Information to -Support a Health Care Claim or Encounter (`006020X314`). - -**The 277 request for additional information.** -`get277RequestForAdditionalInformation` reads a 277 declaring `006020X313` into -a model labelled `"request-for-additional-information"`, which is neither -assignable to nor from the claim status response type and carries no claim -status label: every hierarchical level with its NM1 names, and under it each -claim-level request with its TRN, STC, REF, DTP, QTY and AMT segments and its SVC -service lines. Where an STC composite's C043-04 is present, C043-02 is not -looked up in the claim status code list, because C043-04 names its code source -(a LOINC code naming the attachment requested, say). It returns nothing for a -277 of any other guide, and `get277Status` still labels a `006020X313` document -`"unrecognized-guide"`. `build277RequestForAdditionalInformation` writes one, -GS-01 `HN`, refusing no level, no claim-level request, a request with no trace, -an empty C043-01 or C043-02, and a service line with no SVC. - -**The 275.** `get275Attachments` returns each BDS as one attachment in document -order, with BDS-01, BDS-02 and BDS-03 as sent and the LX line it was sent under. -The data is never decoded, and it is held in `X12AttachmentData`, whose string, -JSON and `util.inspect` forms say how many octets it holds and nothing else; -`readOctets()` returns them verbatim. An attachment whose length the parser -could not verify carries its framing warning on the reading and -`lengthVerified: false`. `build275` writes GS-01 `PI`, BDS-03 as exactly the -caller's octets with no release escaping, and BDS-02 as their count, refusing no -attachment, empty data, a filter code that is not three characters, and data -holding a character that is not one octet. - -**Additions only.** Two `X12_TR3_CONFORMANCE` rows, `006020X313` for the 277 -(variant `RFAI`) and `006020X314` for the 275, each incorporated by reference. -Four warning codes, each with a position-only factory: -`X12_277_RFAI_HEADER_ABSENT`, `X12_277_RFAI_LEVEL_ABSENT`, -`X12_277_RFAI_REQUEST_ABSENT` and `X12_275_ATTACHMENT_ABSENT`. No refusal of -either builder carries an attachment octet or any other document value. No -existing reader, builder, code or message changes. diff --git a/.changeset/code-list-redistribution-per-list.md b/.changeset/code-list-redistribution-per-list.md deleted file mode 100644 index b1ec09f..0000000 --- a/.changeset/code-list-redistribution-per-list.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -"@cosyte/x12": minor ---- - -**Every bundled code list now publishes who maintains it and whether its -descriptions may be redistributed.** Until this release all seven bundled -snapshots sat behind one shared sentence saying regeneration was blocked on a -redistribution-terms review. That single answer was wrong in the direction that -matters: the two lists this package leans on hardest have different owners and -different answers. The Claim Adjustment Reason Codes are maintained by X12 and -their descriptions require a purchased licence; the Remittance Advice Remark -Codes are maintained by CMS and require none. One answer over both hid a -permission this package already had and hid a restriction it must respect. - -**New public metadata on every `CodeListSnapshot`.** `meta.maintainingOrganization` -names the organisation that keeps the published list. `meta.redistribution` -carries the status (`"permitted"`, `"licence-required"` or `"not-established"`), -the terms it was read off, quoted or cited from the source that publishes them, -and the licensor to approach where the descriptions are not free to -redistribute. `meta.completeness` says whether the snapshot is the complete -published list or a cited part of it, so a code the lookup does not know can be -told from a code the publisher never issued. New exports: -`codeListRedistributionIsPermitted`, and the `CodeListRedistribution`, -`CodeListRedistributionStatus` and `CodeListCompleteness` types. - -**"Not established" is a recorded answer, and it is not a permission.** Two -bundled lists, the CLP-02 claim dispositions and the INS-03 maintenance types, -are printed inside a purchased Technical Report Type 3 and have no row on the X12 -External Code Lists index, and no source obtained for this package names them or -states whether their descriptions may be redistributed. Their status is recorded -as unsettled and they are treated as not redistributable wherever a permission -decision is made, exactly as a licence-restricted list is. -`codeListRedistributionIsPermitted` answers `true` only on a recorded -permission: a licence requirement, an unsettled status and a missing record all -answer `false`, so a permission is never inferred from an absence. - -The other five bundled lists each have a row on that index, and are answered from -it: 139 for CARC, 411 for RARC, 507 and 508 for the claim-status lists and 958 -for the service type codes. A list shipping as a subset cited from a TR3 is not -thereby a list the index is silent about, and the five index rows are what decide -those five answers. - -**`pnpm refresh:code-lists --fetch` now answers each list independently.** It -reports every bundled list's regeneration permission from that list's own -record, refuses only the lists whose own terms forbid it, prints the licensor to -approach beside each refusal, and never refuses a list because a different list -is restricted. It exits `2` while any list is refused and `0` when none is. The -default validating mode additionally refuses a bundled list that carries no -maintaining organisation or no redistribution record, naming the offending list, -so an unlabelled list cannot reach a release. - -**No code and no description changed.** This release records terms and -redistributes nothing new: every bundled list returns exactly the codes and -exactly the descriptions it returned before, and a list that may not be -redistributed is still exactly the cited part it already shipped. The bundled -tables are pinned by digest in the test suite so an addition or an edit fails -the build. diff --git a/.changeset/code-list-validity-dates.md b/.changeset/code-list-validity-dates.md deleted file mode 100644 index 89d414c..0000000 --- a/.changeset/code-list-validity-dates.md +++ /dev/null @@ -1,50 +0,0 @@ ---- -"@cosyte/x12": minor ---- - -The bundled CARC and RARC snapshots now carry the maintainer's per-code dates, -and two new queries answer whether a code was valid on the day a document was -produced rather than only whether this package bundles it. Nothing about what -this package parses or emits moves with it. - -**Why a date-aware answer is a different question.** 45 CFR 162.1011 scopes a -code set's validity to the dates the organisation maintaining it publishes, and -`x12.org` publishes a Start, a Last Modified and, for a retired code, a Stop -date for every code on both lists. Until now a code the maintainer retired years -before a remittance was produced came back with a confident description and no -signal at all. Bundled CARC 15 is exactly that case: it was stopped on -2018-05-01 and this subset has been shipping it as though it were current ever -since. - -**What is new.** `CARC.dates` and `RARC.dates` map each bundled code to its -`start`, and to its `lastModified` and `stop` where the maintainer publishes -them, as ISO `YYYY-MM-DD` calendar days. The same dates ride onto the entry -`lookupCarc` and `lookupRarc` return. `checkCarcValidity(code, documentDate)` -and `checkRarcValidity(code, documentDate)` answer `valid`, `not-valid` or -`indeterminate`, with `CODE_VALIDITY`, `CODE_VALIDITY_REASONS`, -`CodeValidityResult`, `CodeListEntryDates`, `DatedCodeListMeta` and -`DatedCodeListSnapshot` exported alongside them. - -**Three answers, and never `valid` for want of evidence.** `indeterminate` is a -real answer with a named reason: the code is outside the bundled subset, or it -is inside it with no published start date. An absent code comes back with its -own bytes echoed verbatim, no description, and no validity claim. An inherited -object-prototype key is an absent code on the new query exactly as it is on the -existing lookups. - -**A document date is a calendar day.** `YYYY-MM-DD` and the wire form -`CCYYMMDD` are accepted and checked against the real calendar, so `20260631` and -`2026-02-30` are refused with the new typed `X12CodeListError` rather than -silently rolling over. A JavaScript `Date` is an instant, and turning one into a -calendar day needs a timezone this library will not guess, so it is refused too. -No comparison here builds a `Date` or reads a timezone. - -**Where the Stop date's boundary is read.** The maintainer does not state -whether a published Stop date is the last valid day or the first invalid one. -This package reads it as the first invalid day, making the valid interval -half-open, and `KNOWN-LIMITATIONS.md` records that as a judgement rather than a -fact. - -No code was added to or removed from either subset and no bundled description -changed, which the suite asserts code by code and string by string. Every other -bundled code list keeps the exported values and the lookup behaviour it had. diff --git a/.changeset/core-code-combinations-check.md b/.changeset/core-code-combinations-check.md deleted file mode 100644 index cd5b236..0000000 --- a/.changeset/core-code-combinations-check.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -"@cosyte/x12": patch ---- - -A new check answers whether an 835 adjustment's group, reason and remark codes -are a combination a CORE Code Combinations table lists for a CORE-defined -business scenario you name, instead of only whether each code exists. Nothing -about what this package parses or emits moves with it: `get835`, `build835`, -the decoded 835 model and the warning codes are unchanged. - -**You supply the table and name its version.** No copy, excerpt or default of -the CORE Code Combinations table ships in this package. CAQH CORE revises it -several times a year and publishes it under its own notice, so the version a -posting system follows is that system's to load. `checkCoreCodeCombination` -takes your table (a `version` label of your choosing and one row per scenario, -group code, reason code and optional remark code), one of the four -CORE-defined business scenarios in `CORE_BUSINESS_SCENARIOS`, one adjustment, -and the remarks you pair with it. The 835 carries remarks per claim and per -service line rather than per adjustment, so which remarks accompany an -adjustment is your choice. - -**Three answers, and `unevaluated` is never permission.** `in-table` means the -table has a row for that scenario, group code and reason code and lists every -remark you passed with them; with no remarks, a row for the pair suffices. -`not-in-table` means the table has rows for the scenario and the combination -is not among them, including one it lists only under another scenario. -`unevaluated` names its reason: no table, a scenario outside the four, no rows -for that scenario, an empty group or reason code, or a remark whose code system -is not `HE`. Your table's version label comes back beside every answer given -against it, exactly as you supplied it. - -**Exact, and safe on odd codes.** Codes are compared exactly as written, with -no case folding and no trimming, and a code named after an `Object.prototype` -property is an ordinary code that the table lists only when a row names it. - -**A malformed table is refused.** A missing or empty version label, a row -naming an unknown scenario, a row with a missing or empty group or reason code, -or a non-string remark code throws the new `CoreCodeCombinationTableError` -with the code `X12_CORE_COMBINATION_TABLE_INVALID`. Its message names the -field and the row index and never a value from your table. - -Exported alongside the check: `CORE_BUSINESS_SCENARIOS`, -`CORE_CODE_COMBINATION_OUTCOMES`, `CORE_CODE_COMBINATION_UNEVALUATED_REASONS`, -`CORE_CODE_COMBINATION_ERROR_CODES`, `CoreCodeCombinationTableError`, and the -types `CoreBusinessScenario`, `CoreCodeCombinationTable`, -`CoreCodeCombinationRow`, `CoreCodeCombinationQuery`, -`CoreCodeCombinationResult`, `CoreCodeCombinationOutcome`, -`CoreCodeCombinationUnevaluatedReason`, `CoreCodeCombinationErrorCode` and -`CoreCodeCombinationTableField`. diff --git a/.changeset/date-conversion-surface.md b/.changeset/date-conversion-surface.md deleted file mode 100644 index 6c0a0b1..0000000 --- a/.changeset/date-conversion-surface.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -"@cosyte/x12": minor ---- - -Three new package-root exports, `toObject`, `toISO` and `toDate`, read a date -off any typed reader without the caller cutting the digits apart by hand at the -call site. Every `@cosyte/*` parser now exports the same three names with the -same return shapes and the same timezone rule, so moving between packages costs -nothing to relearn. `DateParts`, `ToDateOptions` and `X12DateValue` are exported -alongside them, from the package entry point, with no subpath import. - -**It adds a read route and changes nothing that already exists.** No exported -name is removed, renamed or altered, `parseDocumentDate` keeps its own contract -including its throwing one, no parse and no emit behaviour moves, and there is -still not a single runtime dependency. The engine floor stays `>=22.0.0`. - -**One surface over every date carrier.** The parameter is structural, so all -eight `X12*Date` types this package exports are accepted through the same three -names with no per-transaction variant: `X12ClaimDate`, `X12InquiryDate`, -`X12EligibilityDate`, `X12StatusInquiryDate`, `X12StatusDate`, `X12PremiumDate`, -`X12EnrollmentDate` and `X12AuthDate`. A ninth carrier added later needs no -change here. - -**It decodes exactly what this package already parses or builds, `D8` and -`RD8`, and invents nothing.** Those are the only two DTP-02 values that appear -anywhere in `src/`. `D8` is a single `CCYYMMDD` day and converts to -`{ year, month, day }`, with `month` spec-native 1 to 12. A qualifier outside -that pair, an absent one, a value whose digits do not match the shape its own -qualifier declares, and a day that is not on the calendar all convert to -`undefined`. None of the three ever throws, so a lenient parse stays lenient all -the way to the value a consumer reads. - -**`RD8` converts to `undefined` from all three, deliberately.** An interval is -not a point in time, so there is no single day, no single string and no single -instant for a service period or an eligibility span. Returning the first -endpoint would be a quiet wrong answer with a right-looking shape, which on a -service date is a clinical value read wrong. A caller who knows which end they -mean splits the range and converts that. - -**`toDate` returns an instant only when the caller states the zone.** No X12 -date element this package decodes carries a UTC offset, so -`assumeOffsetMinutes`, signed minutes east of UTC, is the only route one takes -into the result. Without it the answer is `undefined`: the host machine's -timezone is never read and UTC is never assumed. That is the stance -`parseDocumentDate` already took for the date-aware code-list queries, and this -extends it to the read side rather than softening it. A four-digit year below -100 stays that year, so `0050` is year 50 and never 1950. - -**Backed by the conformance suite every sibling parser carries.** The same -eleven-row case table is exercised at `test/conversion-surface.test.ts`, so a -divergence between the packages is a failing test rather than something a -consumer discovers. Five of its rows are recorded as skips with a written -reason, because this package decodes no year-precision form, no time of day, no -fraction of a second and no UTC offset, and each of those reasons is backed by a -live test that measures the property rather than asserting it in a comment. diff --git a/.changeset/differential-oracle-harness.md b/.changeset/differential-oracle-harness.md deleted file mode 100644 index d70dba3..0000000 --- a/.changeset/differential-oracle-harness.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -"@cosyte/x12": patch ---- - -A differential harness now reads this library's own synthetic corpus a second -time through an independent open-source X12 reader and records, as a committed -machine-readable artifact, where the two agree, where they disagree, and which -transactions the second reader has no 005010 map for at all. Nothing about what -this package parses or emits moves with it, and its published runtime dependency -set stays empty. - -**What runs.** `pnpm run differential` compares every synthetic document in -`test/fixtures/` element position by element position against pyx12, a -BSD-licensed HIPAA X12 parser and validator pinned to one exact version and -obtained only for the run. Which comparison a document belongs to is read from -the document's own GS-08 and ST-01 and never from the directory it sits in, and a -document those identifiers do not resolve to exactly one compared transaction is -recorded as skipped with the reason, so every document the run looked at is -accounted for. The comparison writes `test/differential/report.json` and exits -non-zero on a disagreement, on a compared transaction that put no document or no -element position through both readers, and on an oracle that cannot be invoked, -in which case nothing is written at all. The job is wired into this repository's -own CI, which runs the comparison for real on every push and pull request and -gates on the live run REPRODUCING the committed report, divergences included. - -**What the comparison found, and what was done about it.** The two readers -disagree about the release character: this library reads `?` as a release -character in a transaction set body, pyx12 does not, so the two split -`REF*EA*ID?*WITH?*STAR` into a different number of elements with different -values. 005010 transmits no release character and nothing in it picks between the -two readings. That disagreement is REPORTED AND LEFT STANDING, with both readings -verbatim in the committed report and stated in `KNOWN-LIMITATIONS.md`: no parse -behaviour changed to make it go away, and no document was taken out of the corpus -to stop it being visible. The consequence a reader should expect is that -`pnpm run differential` exits non-zero while it stands. - -**What the report carries.** The oracle's package name, the exact version it -reported about itself, its declared licence, and the commit of this library the -run was produced from. Per compared transaction: the implementation guide this -library implements AND the one the oracle's own map index binds, so the two -places where those differ by a published errata are visible rather than implied, -along with the documents compared, the element positions compared, and every -divergence with both readings left exactly as each reader returned them. - -**What is named as not compared, and why that matters.** The covered and -uncovered sets are both derived from the `X12_TR3_CONFORMANCE` export rather than -from any hand-maintained list, and their union is asserted to equal this -library's read scope at head, so a transaction cannot be added without the report -being regenerated. Eight rows of this library's read scope have no map in the -oracle's index and are named individually with the reason, which is that the -oracle publishes no implementation guide map for them rather than that it could -not read them: silence about the 270 must never read as agreement about the 270. - -**What it does not prove.** Agreement with one other reader is not conformance to -a Technical Report Type 3 and it is not a compliance statement. -`KNOWN-LIMITATIONS.md` is rewritten in the same change to say so, replacing the -statement that no external-oracle corpus was wired. - -Implements S0323-x12-10. diff --git a/.changeset/differential-second-oracle.md b/.changeset/differential-second-oracle.md deleted file mode 100644 index 04b8c2c..0000000 --- a/.changeset/differential-second-oracle.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -"@cosyte/x12": patch ---- - -The differential harness now reads the eligibility pair (270/271, 005010X279A1) and the claim -status pair (276/277, 005010X212) through a second independent open-source X12 reader, -LinuxForHealth x12, so the transactions pyx12 publishes no 005010 map for are compared element -position by element position instead of against nothing. Nothing about what this package parses or -emits moves with it, and its published runtime dependency set stays empty. - -**What runs.** `pnpm run differential` now takes an ordered list of oracles, pyx12 first. A read-scope -transaction is compared against the first oracle that maps it and against no other, so every -transaction pyx12 compared before is still compared against pyx12 alone, with the same documents, -positions and divergences. The second oracle reads each document through its own transaction model -for the implementation guide the document declares, and a document that model refuses is recorded as -unevaluated with the oracle and its refusal kind, never counted as agreement. So is a document the -model accepts after setting one of its segments aside: a transaction set is compared only when its -model kept every segment the oracle framed for it, ST through SE. A transaction that no document -reaches both readers for still fails the run. Its requirement pins the oracle, every package -it runs on (Pydantic included, which its own metadata leaves free) and the Python version exactly, -and the harness refuses to run, writing no report, when any of those is not what the running oracle -reports or when the requirement leaves one free. - -**What the report carries.** Each oracle's name, version and licence as the installed reader declares -them, and for the second also the Python version and the companion package versions it ran with. Each -compared transaction, divergence and oracle refusal names the oracle it belongs to, and each compared -transaction names the implementation guide and the map or model that oracle bound it to. The report's -`schemaVersion` is now 2. The transactions neither oracle maps (the 278 in both directions, the -dental claim, the two 006020 guides and the TA1) stay on the uncovered list, each with a reason naming -both oracles and their exact versions. - -**How it is checked.** `pnpm run differential:check` runs the same comparison and exits 0 only when the -live report reproduces the committed one, divergences included, with the provenance block set aside. -The repository's CI job now provisions the pinned Python version and both oracles at their pins and -runs that one command. - -**What it does not prove.** Agreement with either reader is not conformance to a Technical Report Type -3 and it is not a compliance statement, and the second oracle's model refuses a good part of the -synthetic corpus, so its comparisons cover only the documents it accepted whole. `KNOWN-LIMITATIONS.md` -is rewritten in the same change to say which reader compares what and where each stops. A synthetic -276 whose subscriber is the patient is added to the corpus, because the second oracle's 276 model -refuses every 276 fixture that carries a dependent level beside the subscriber's own claim. It carries -no subscriber demographics segment, because that model reads the subscriber's DMG ahead of the -subscriber's name, where this library reads it after, and sets aside one sent where this library -puts it. - -Implements S0382-x12-21. diff --git a/.changeset/eligibility-270-typed-model.md b/.changeset/eligibility-270-typed-model.md deleted file mode 100644 index a77fe3d..0000000 --- a/.changeset/eligibility-270-typed-model.md +++ /dev/null @@ -1,76 +0,0 @@ ---- -"@cosyte/x12": minor ---- - -Typed read and emit support for the 270 Health Care Eligibility Benefit Inquiry -(TR3 `005010X279A1`), which closes the 270 half of the inquiry gap. The 276 -claim status inquiry is unchanged and still has no typed model on either side. - -**Read.** `get270Inquiry(delimiters, tx)` decodes one 270 transaction set, and -answers `undefined` when the transaction set is not a 270, which is the refusal -shape every other per-transaction reader in this package uses. -`parse270Inquiries(raw)` decodes every 270 in an interchange, in transmitted -order, one model each with its own warnings, and answers an EMPTY list when the -interchange carries none. The two answers are deliberately different values: a -mis-routed call and an interchange with nothing to read are different facts. - -The model exposes the information source, information receiver, subscriber and -dependent levels NESTED in the parent-child relationship the sender -transmitted, each level's name, identifiers, address and demographics, its -trace numbers, its dates with the DTP-02 qualifier that says whether a value is -a single date or a range, and its `EQ` eligibility inquiries with their -requested Service Type Codes. A composite comes back as its separated -components and never as one joined string, so two documents differing only in -their declared delimiters decode to equal models. A dependent is its own level -with its own traces and inquiries and is never merged onto the subscriber it -hangs under. - -**A level attaches by its own HL-02 and by nothing else.** A pointer that names -no present level, one that names a level of the wrong kind, and a parent chain -that returns to itself each leave that level, and everything transmitted -beneath it, off the returned tree, reported by `X12_270_LEVEL_DETACHED` beside -the code for the defect itself. Nothing is re-parented onto whichever level -happened to be open and no pointer is ever re-numbered: the declared pointer -stays verbatim on `hierarchies` and the segments stay verbatim on the -transaction set. Where two levels are transmitted with the same HL-01, a child -naming it attaches to the FIRST in transmitted order, so the same bytes always -decode to the same model. - -**Emit.** `build270(spec)` emits a spec-clean 270 by construction: it owns the -HL spine, computing every HL-01, HL-02 and HL-04 from the nested spec, so a -structurally inconsistent hierarchy is unrepresentable and SE-01 is correct by -construction. It refuses, with the typed `Eligibility270BuildError`, anything it -cannot make spec-clean: no information source, a source with no receiver, a -receiver with no subscriber, a level with no name loop at ANY of the four -levels, a level that asks nothing, an inquiry carrying neither a service type -nor a procedure, an empty or over-long control number, a non-string element -value, a forged array-like where a list belongs, and a real list left with an -empty slot in it. The last is the shape a JSON payload with a dropped record -carries, and it is refused wherever it stands rather than dereferenced for an -untyped `TypeError` a consumer cannot branch on. Refusal messages name -structural indices and counts, never a member identifier, a member name, a -patient name, a trace or a diagnosis code. - -**Warning codes are added, and nothing is renamed, removed or renumbered.** -`X12_270_NON_CONVENTIONAL_DELIMITER` and `X12_270_INTER_SEGMENT_LINE_BREAK` -report the two tolerances the 270 path accepts without changing a value, once -per transaction set each, anchored at the ISA. The framing one is CR and LF and -nothing else, because that is the whole of what the shared parse absorbs: a -space or a tab between segments is not absorbed, the functional group does not -frame, and no typed reader sees a transaction set. That is unchanged -shared-parse behaviour, and `KNOWN-LIMITATIONS.md` now states it. -`X12_270_DUPLICATE_HIERARCHY_ID`, `X12_270_HIERARCHY_CYCLE` and -`X12_270_LEVEL_DETACHED` report the hierarchy hazards above. -`X12_270_DATE_ROW_DROPPED` reports a DTP that reached the reader short of the -qualifier (DTP-01) or the value (DTP-03) a date row is built from: a DTP is a -record and not a slot, so the whole row goes, the format qualifier with it, and -the loss is reported rather than left to look like a date the sender never -stated. Every one of them is raised on the 270 path only, so no fixture of any -other transaction set gains a warning it did not have, and -`X12_MISSING_REQUIRED_LOOP` carries the 270's structural regions through new -library-owned discriminants rather than more codes. - -Nothing in the shared interchange parse changed. The 270 path takes the declared -delimiters and the segment framing from the parse this package already performs -for every transaction set; it adds no delimiter or framing tolerance, narrows -none, and emits no warning from shared code. diff --git a/.changeset/eligibility-271-aaa-request-validation.md b/.changeset/eligibility-271-aaa-request-validation.md deleted file mode 100644 index 4ee669a..0000000 --- a/.changeset/eligibility-271-aaa-request-validation.md +++ /dev/null @@ -1,88 +0,0 @@ ---- -"@cosyte/x12": minor ---- - -**A rejected 271 no longer reads as a member with no benefits.** `AAA` -request-validation segments are typed onto the 271 model, at the information -source, information receiver, subscriber and dependent levels, on a new -`aaaConditions` collection. Through this release those two answers had the same -shape through the typed surface: the payer's rejection survived on -`tx.segments` and the DISTINCTION did not survive into the reading, so a caller -that trusted the model reported "no coverage" where the payer had actually said -"I could not process your inquiry, and here is why". - -This supersedes the deferral a previous release recorded for the 271, which -listed AAA request-validation segments among those it carried on `tx.segments` -alone. AAA on the 270 inquiry direction is unchanged and still untyped, and -`HSD` detail and the `III` / `LS` / `LE` markers stay as they were. - -**Always present, never absent.** `X12Eligibility.aaaConditions` is on every -271 result this library returns and is EMPTY for a document carrying no `AAA`. -A present, empty collection is a stated zero; an `undefined` field could not be -told from a reader that does not look, which is the exact ambiguity this -collection exists to remove. A document with no AAA produces no entry, and no -rejection is ever synthesised from an absent benefit list. - -**Each entry names the loop it came from.** The key is three parts: the level, -the hierarchical identifier the document assigned that loop (HL-01, as -`hierarchies` already reports it), and the zero-based index of that loop -occurrence among occurrences of the same level, counted DOCUMENT-WIDE in -transmitted order and never restarted per parent. So the third dependent loop -in a document is index 2 whether it is the first dependent of the second -subscriber or the third dependent of the first, and a caller can say WHICH -dependent of WHICH subscriber the payer rejected. Where a loop states no -HL-01, or no level of a named kind encloses the segment, that part of the key -is left absent rather than synthesised, the AAA is still surfaced, and -`X12_271_AAA_LOOP_UNIDENTIFIED` reports it. - -**Two AAA are two conditions.** Nothing is de-duplicated: two segments at one -level carrying the same reject reason code both surface, in transmitted order. -An `AAA` under a level whose loop carries nothing else surfaces against that -level, and creates no subscriber, no dependent and no hierarchy entry, so a -caller counting subscribers sees the same count as before this release. - -**Only two element positions are read, and both have a recorded source.** The -reject reason code and the follow-up action code are taken from positions -established by a third-party EDI reference reproducing the 005010 AAA layout, -recorded with its URL, its retrieval date and the sha256 of the bytes read, so -the citation names one document rather than whatever that page says next. It is -NOT the paid ASC X12 Technical Report Type 3, which was not purchased, so the -mapping is single-source rather than verified. Nothing beyond those two is -assigned a meaning: an element past them raises -`X12_271_AAA_SEGMENT_MALFORMED`, which reports that a position is occupied and -says nothing whatever about what occupies it. The segment's true maximum -element count is neither sourced nor asserted anywhere. - -**The bundled description snapshots ship EMPTY, deliberately.** -`AAA_REJECT_REASON_CODES` and `AAA_FOLLOW_UP_ACTION_CODES` carry a four-part -provenance record in the artifact itself (source, capture date, maintaining -organisation, redistribution terms) and `bundleAaaCodes` refuses to bundle a -description while any part is unestablished or the terms do not permit it. The -maintaining organisation requires permission for the use of its work products -and none has been obtained, so this package ships the verbatim inbound code -with no description rather than a description whose terms nobody recorded. Every -inbound AAA code is therefore unrecognised today and raises exactly one -`X12_271_AAA_UNKNOWN_CODE` per occurrence. No existing bundled code list is -touched, enlarged, re-fetched or re-licensed by this change. - -**Fail-safe, unchanged.** The four Tier-3 fatal codes are unchanged and AAA -content never makes reading a 271 fatal. A missing or empty reject reason code -surfaces as ABSENT with no placeholder and raises -`X12_271_AAA_REJECT_REASON_ABSENT`, which is a DIFFERENT code from the -unrecognised-code one so "no reason given" and "a reason this package cannot -describe" stay distinguishable. An absent follow-up action code is left absent -with no empty-string stand-in. Every diagnostic is a literal in the frozen -`ALL_WARNING_MESSAGES` registry carrying the level and the position and nothing -the sender sent; no factory added here takes a document element. - -**A level code is document bytes, and the table that reads it has no -prototype.** HL-03 reaches the level lookup verbatim off the wire, so that -lookup is built with a null prototype: an HL-03 naming a member of -`Object.prototype` leaves the level ABSENT and raises -`X12_271_AAA_LOOP_UNIDENTIFIED`, exactly as any other level code this reader -cannot name does, rather than resolving through the prototype chain to a value -that is not a level at all. - -Warning registry 40 to 44, additions-only. `tx.segments` is untouched: every -`AAA` is still preserved verbatim, with the same element values, element order -and segment order, and nothing was moved off it to build the typed surface. diff --git a/.changeset/first-minor-release.md b/.changeset/first-minor-release.md deleted file mode 100644 index ffd8fba..0000000 --- a/.changeset/first-minor-release.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -"@cosyte/x12": minor ---- - -**This is 0.1.0, the first release whose public API we treat as settled.** - -What is covered, and what you can build against: - -- Typed read and typed emit for the HIPAA 005010 transaction sets this package covers: 270, 271, - 276, 277 and 277CA, 278 request and response, 820, 834, 835, 837P, 837I and 837D, 999 and TA1, - each with a reader and a domain builder that enforces that guide's own invariants (balancing, - counts, the hierarchy spine). -- The two claims attachments guides for the period on and after 26 May 2028: the 277 request for - additional information (`006020X313`) and the 275 that answers it (`006020X314`), with the base - 006020 structure typed. -- Money as exact decimals: every amount, percent and quantity is an `X12Decimal`, never a float, - and a slot the sender left empty reads `undefined`, not zero. -- A lenient parser: only four structural failures are fatal, and every tolerated deviation arrives - on `warnings` as a stable code with its position. `X12_TR3_CONFORMANCE` states which - implementation guide each transaction set follows. - -What the version promises. The exported readers, builders, types and warning codes are the surface -we keep stable, and the warning registry only ever gains codes. While the package is below 1.0, a -breaking change bumps the minor version (0.1 to 0.2) and is called out in this changelog with its -migration; a fix that changes no public value ships as a patch. Upgrading from 0.0.x is itself -breaking in five places, listed in the entries below: `CodeListMeta`, `X12Eligibility` and -`X12ProfileDescription` each gain a required member, `X12ClaimStatusResponse` gains a third -`transactionType`, and `build278Response` declares `005010X217`. - -What is not covered yet. A byte-exact round trip is not guaranteed in general, and -`KNOWN-LIMITATIONS.md` lists the cases. For the two 006020 attachments guides no implementation -guide usage is checked, and what an attachment carries is not decoded. Non-healthcare transaction -sets, EDIFACT, transport (AS2, SFTP), revisions before 005010 and other 006020 guides are out of -scope. No CORE Code Combinations table ships: you supply it. diff --git a/.changeset/first-use-examples-executed.md b/.changeset/first-use-examples-executed.md deleted file mode 100644 index 84b10ee..0000000 --- a/.changeset/first-use-examples-executed.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -"@cosyte/x12": patch ---- - -Docs: the quickstart's first example now reads the same committed synthetic 835 fixture as the README usage example, byte for byte, and the test suite executes it with every claimed value asserted. - -A changed value in that example's interchange or in one of its claimed results now fails the suite, the README gate is pinned to the first block under `## Usage` and shown able to go red on a changed claim, and every install command the README and the installation page print is checked against the package's own name. - -The README usage example did not compile in a strict TypeScript project: `get835` returns `undefined` for a transaction set that is not an 835, and the example read `remit.payment` without checking. It now throws on `undefined` first, as the quickstart already did, and the suite compiles both first examples with the settings `tsc --init` writes for a new project, so an example that does not compile fails it. diff --git a/.changeset/package-description-names-both-halves.md b/.changeset/package-description-names-both-halves.md deleted file mode 100644 index 97f5f7d..0000000 --- a/.changeset/package-description-names-both-halves.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -"@cosyte/x12": patch ---- - -The package description now names both halves of what this library does and the -scope it covers. It changes no parse and no emit behaviour: the `description` -metadata field moves, and the README states that same sentence. - -The previous wording, "parser and utility library", described the read half -alone, while a matching domain builder ships for every transaction that has a -reader, beside the general serializer and interchange builder. It also omitted -"healthcare", which is the scope actually implemented (HIPAA 005010); -non-healthcare EDI is out of scope, so the old sentence invited the wrong -installer. The new wording states the audience as "Node.js and TypeScript", -which is how the rest of this family spells it. - -The registry copy for a published version cannot be edited afterwards, so this -takes effect for versions published from here on, not retroactively. diff --git a/.changeset/phi-scan-refuses-an-unread-target.md b/.changeset/phi-scan-refuses-an-unread-target.md deleted file mode 100644 index 87763f7..0000000 --- a/.changeset/phi-scan-refuses-an-unread-target.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -"@cosyte/x12": patch ---- - -`phi-scan` now REFUSES a run that enumerated a target and never read it, after -reporting the hits it did find. No parse and no emit behaviour changes, and no -published surface moves: the scanner is a development gate and is not part of -the package tarball. - -`--allow-fixture` is the only thing that produces such a target. It withdraws a -path from a list the run had already built, and that withdrawal used to be -silent, so the run reported on the targets it opened and said nothing about the -one it did not: a corpus whose only violator was withdrawn came back clean at -exit 0. A scan that did not open a file has no clean verdict about it, so such a -run now reports its hits and then exits with the same refusal code every other -refusal here uses. - -The order is part of the rule: the violator that WAS read is printed with its -locus and its offending value before the refusal, because a refusal carrying no -evidence tells a caller nothing about the corpus. A run that carries no -`--allow-fixture` cannot reach the rule at all, so the all-mode sweep, the -`--staged` pre-commit route and explicit-path runs are byte-identical to before. - -`phi-scan-overrides.md` is corrected with it. An override does not silence the -checks for a file; it withdraws the file, which now buys a refusal rather than a -pass. Declaring a fixture's synthetic tokens in `scripts/phi-allow-list.txt` -remains the way to make a run green. - -The package manager pin moves to a pnpm 10 release that supports the publication -cooldown and trust policy the new `pnpm-workspace.yaml` declares, and the -`js-yaml` override is raised to the advisory's current ceiling. diff --git a/.changeset/profiles-conformance-and-emit-independence.md b/.changeset/profiles-conformance-and-emit-independence.md deleted file mode 100644 index c9b04d3..0000000 --- a/.changeset/profiles-conformance-and-emit-independence.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -"@cosyte/x12": minor ---- - -Trading-partner profiles now say whether the deviation they describe is one a -trading partner may lawfully require, and the emit path's independence from -them is an enforced invariant rather than a property that held by construction. - -**Emit independence is locked.** A profile describes what a partner SENDS and -must never touch what this library emits. That was true at every release and -nothing asserted it, so the next profile-aware convenience added to the emit -path would have met no test and no type. `serializeX12`, `buildInterchange` and -every `build*` domain builder are now held to byte-identical output with each -built-in profile registered as the process default, with a caller-defined -profile carrying a quirk in each of the `relaxes`, `adds` and `requires` -buckets, and with none, over the whole committed fixture corpus and over each -builder's own inputs. Refusals are held to the same standard: the same error -type, the same code and the same message, so an active profile can turn neither -a refusal into an emission nor an emission into a refusal. The covered builder -set is DERIVED from the package root's exports rather than hand-listed, so a -builder added later fails the run naming itself instead of slipping past a -check nobody extended. Beside the behavioural lock is a structural one: the -emit modules are read from disk and asserted to import nothing from the profile -subsystem, which catches a profile that is consulted and happens not to move a -byte today. No emitted byte changed. - -**Every described quirk now carries a conformance classification.** A quirk -that says "this partner requires X" used to be indistinguishable from "X is a -supported requirement". `describe()` now renders each quirk with a -`conformance` state and groups the same quirks by that state: - -- `permitted` - a recorded judgement that 45 CFR 162.915 is not engaged, so a - partner may lawfully require the deviation. -- `not-permitted` - a recorded judgement that it is: the partner is deviating - from the adopted standard rather than the standard supporting the mandate. -- `undetermined` - no judgement was recorded. The FAIL-SAFE DEFAULT: a quirk - that says nothing renders as `undetermined` and never as `permitted`, and so - does an out-of-enum value from a profile object that bypassed - `defineProfile`. - -`conformance` is an OPTIONAL quirk field, so a profile written against the -older quirk shape defines and describes exactly as it did and gains no claim -nobody made. The `requires` bucket confers nothing on this axis: a partner -mandating an element the adopted implementation guide marks "not used" -classifies as a deviation from the standard. - -**Read a classification as documentation, not as a check.** Whether an element -is marked "not used" in an adopted implementation guide is not machine-checkable -here, because 45 CFR 162.920(a) states a fee is charged for the implementation -specifications and none is bundled with this package. Every classification is a -recorded human judgement; confirm it against your own copy of the guide before -you rely on it. Of the three quirks the built-in profiles ship, the BCBS -backslash component separator is recorded `permitted` (ISA-16 is the element -whose job is to declare the component separator, and the sender declares it in -band), and both Availity REF additions are recorded `undetermined` for the fee -reason above. `KNOWN-LIMITATIONS.md` carries the same caveat for consumers who -never open the type. diff --git a/.changeset/reader-declared-guide.md b/.changeset/reader-declared-guide.md deleted file mode 100644 index e1bab9e..0000000 --- a/.changeset/reader-declared-guide.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -"@cosyte/x12": patch ---- - -Every typed reader now checks the implementation guide a transaction set -declares against the guides that reader implements, and says so when they -differ. Until this release each reader admitted a transaction set on ST-01 -alone, so a 277 declaring `006020X313` (the claim request for additional -information, a payer asking for documentation) came back from `get277Status` -labelled `claim-status`, and a 004010, 006020 or 008020 document of any other -type was decoded against 005010 element positions with nothing on the warning -channel. - -**Two new warning codes, additions only.** `X12_GUIDE_NOT_IMPLEMENTED` is -raised where the declared guide is outside the reader's set, and -`X12_GUIDE_NOT_DECLARED` where nothing is declared at all. Both are anchored at -the ST (`segmentIndex: 0`, no `elementIndex`), both messages are frozen registry -literals that echo nothing the sender wrote, and both factories, -`guideNotImplemented` and `guideNotDeclared`, take a position and nothing else. -The reading is still decoded and returned exactly as before: nothing is refused, -dropped or re-decoded, and on a document declaring an implemented guide nothing -changes at all. - -**What is declared.** ST-03, decoded of any release escape exactly as -`implementationConventionReference` is published. Where ST-03 is absent or -empty, GS-08 of the enclosing functional group, decoded the same way. Nothing is -trimmed, case-folded or prefix-matched, so a whitespace-only declaration is -compared as it stands. Where ST-03 is non-empty it alone decides; a -disagreement between ST-03 and GS-08 is not reconciled. - -**What is implemented.** Derived from `X12_TR3_CONFORMANCE`, never from a -second list: for each reader, the `tr3` and every `cfrAdopted` entry of its -rows. The 837 reader also accepts every identifier its variant table resolves, -so the companion-guide identifiers `005010X222A1` and `005010X223A2` do not -warn. - -**GS-08 now reaches a reader.** A parsed `X12TransactionSet` carries an -optional `gs` member, the enclosing group's GS header by reference, raw like -every envelope element. Every reader keeps its `(delimiters, tx)` call; a -transaction set assembled by hand without `gs` is read as one whose GS-08 is -absent. `parseX12` raises no new warning and changes no existing field. - -**What a consumer will see move.** - -- `X12ClaimStatusResponse.transactionType` gains a third value, - `"unrecognized-guide"`, set where the declared guide is outside - `get277Status`'s set or nothing is declared. An exhaustive `switch` over the - old two values stops compiling. Every implemented declaration keeps the - existing derivation, which still keys on ST-03 as framed. - `get277CADisposition` admits exactly what it admitted before. -- A 278 declaring `005010X216` now carries `X12_GUIDE_NOT_IMPLEMENTED` on both - 278 readers, because the regulation names `005010X217` for both directions. - `build278Response` still writes `005010X216`, so reading back its own output - carries the code until that builder is corrected. -- A 999 declaring the base `005010X231` carries `X12_GUIDE_NOT_IMPLEMENTED`, - because the conformance row names only the errata `005010X231A1`. -- On the 834, the code is on the header reading and on every streamed - enrollment, because each member was decoded against the declared guide. diff --git a/.changeset/readme-house-standard.md b/.changeset/readme-house-standard.md deleted file mode 100644 index dd96cb7..0000000 --- a/.changeset/readme-house-standard.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -"@cosyte/x12": patch ---- - -`README.md` is rewritten to the house package-page standard, and a test now holds it there. No -library behaviour changes: this is the npm package page and the repository landing page, nothing -under `src/` moved. - -**What the page gains.** `Why this exists`, `Status`, `Install`, `PHI and safety` and -`Contributing` are all new sections, and `API` and `Compatibility` now hold the accuracy the old -`What's inside` had buried in one very long list. The tagline is a one-line hook inside the -120-character ceiling instead of a 200-character sentence, and the banner carries the declared alt -text for the light tile rather than a hand-written description of the mark. - -**What the page keeps, deliberately.** Four claims this page had already earned survive the rewrite -and are asserted by name, because a standardization sweep is exactly what deletes them: that only -four structural failures are ever fatal, that `serialize(parse(s)) === s` is not guaranteed in -general, that no EDI amount is ever handed to `parseFloat`, and that `X12ParseError.snippet` is a -bounded copy of the input that can carry PHI and is not redacted. The `Trademarks` section stays. - -**The usage example is now executed.** The `## Usage` block is extracted, compiled and run against -a build of this package on every `pnpm test`, and the values it shows as output are asserted, so a -reader (or an agent) lifting it verbatim gets working code. It declares its own input rather than -opening on an undeclared variable, and the interchange in it is a byte-for-byte copy of a synthetic -fixture the PHI gate already sweeps. - -**The page now follows `package.json` rather than restating it independently.** The description -paragraph, the version named in `Status` and the Node engine floor named in `Install` are each -pinned to the manifest by a drift check that names both values when they diverge, so changing -`description`, `version` or `engines.node` without updating the page fails the suite instead of -publishing a page that contradicts the manifest beside it. Relative links are checked to resolve, -and a repo-root markdown file cited from the page must be in `files`, because an installed tarball -cannot open one that is not. diff --git a/.changeset/status-276-typed-model.md b/.changeset/status-276-typed-model.md deleted file mode 100644 index d1e6508..0000000 --- a/.changeset/status-276-typed-model.md +++ /dev/null @@ -1,98 +0,0 @@ ---- -"@cosyte/x12": minor ---- - -Typed read and emit support for the 276 Health Care Claim Status Request -(TR3 `005010X212`), which closes the 276 half of the inquiry gap and, with the -270 half already shipped, leaves no v1 transaction needing raw segment and -dot-path work. The 270, 271, 277 and 277CA surfaces are unchanged: nothing is -renamed, widened or re-identified, and the 276 is added beside them. - -**Read.** `get276StatusInquiry(delimiters, tx)` decodes one 276 transaction set -and answers `undefined` when the transaction set is not a 276, which is the -refusal shape every other per-transaction reader in this package uses. -`parse276StatusInquiries(raw)` decodes every 276 in an interchange, in -transmitted order, one model each with its own warnings, and answers an EMPTY -list when the interchange carries none, with no warning about the absence: an -interchange full of 277s is not a defective 276. - -The model exposes the information source, information receiver, service -provider, subscriber and dependent levels NESTED in the parent-child -relationship the sender transmitted - a five-level spine, one level deeper than -the 270's, because the claim-status family carries the service provider between -the receiver and the patient. Each level carries its name, identifiers and -demographics; each claim carries the trace the answering 277 echoes back, its -REF identifiers, its AMT amount rows, its DTP date rows with the DTP-02 -qualifier that says whether a value is a single date or a range, and its Loop -2210 service lines. A composite comes back as its separated components and never -as one joined string, so two documents differing only in their declared -delimiters decode to equal models. A dependent is its own level with its own -claims and is never merged onto the subscriber it hangs under. - -**A level attaches by its own HL-02 and by nothing else.** A pointer that names -no present level, one that names a level of the wrong kind, and a parent chain -that returns to itself each leave that level, and everything transmitted beneath -it, off the returned tree, reported by `X12_276_LEVEL_DETACHED` beside the code -for the defect itself. Nothing is re-parented onto whichever level happened to -be open and no pointer is ever re-numbered: the declared pointer stays verbatim -on `hierarchies` and the segments stay verbatim on the transaction set. Where -two levels are transmitted with the same HL-01, a child naming it attaches to -the FIRST in transmitted order, so the same bytes always decode to the same -model. - -**Emit.** `build276(spec)` emits a spec-clean 276 by construction: it owns the -HL spine, computing every HL-01, HL-02 and HL-04 from the nested spec, so a -structurally inconsistent hierarchy is unrepresentable and SE-01 is correct by -construction. It stamps ST-01 `276` and `005010X212` into GS-08 and ST-03, and -takes GS-01 from a new cited data element 479 table -(`src/code-lists/functional-identifier.ts`) rather than restating a code from a -TR3 this package has not purchased. That table records the reference it was read -from, with the retrieval date and content digest, and bundles code values only: -descriptions ship from it no more than they do from the AAA snapshots beside it, -because the redistribution terms recorded there do not permit it. Eight of its -nine rows are cross-checked against the GS-01 this package's other builders -already declare, so the row the 276 uses arrives with the same eight-way -agreement behind it. The resulting GS-01 is `HR`, which is not the 277's -response identifier `HN`. - -It refuses, with the typed `ClaimStatus276BuildError` and its own stable -`CLAIM_STATUS_276_BUILD_ERROR_CODES`, anything it cannot make spec-clean: no -information source, a source with no receiver, a receiver with no service -provider, a provider with no subscriber, a level with no name loop at ANY of the -five levels, a subscriber that asks about no claim and carries no dependent that -does, a dependent with no claim, a claim carrying nothing a payer could find it -by, a claim with no trace, a service line identifying no service, an empty or -over-long control number, a non-string element value, a non-`X12Decimal` amount, -a forged array-like where a list belongs, and a real list left with an empty slot -in it. The error class is a SIBLING of `ClaimStatus277BuildError` and not a -widening of it: a consumer catching a `277` error out of `build276` would be -reading a name that lies about which direction refused. Refusal messages name -structural indices and counts, never a member identifier, a member name, a -patient name, a trace, a claim number or a diagnosis code, and the one caller -value any of them renders goes through the package's bounded renderer. - -**Warning codes are added as siblings, and nothing is renamed, removed or -renumbered.** `X12_276_DUPLICATE_HIERARCHY_ID`, `X12_276_HIERARCHY_CYCLE` and -`X12_276_LEVEL_DETACHED` report the hierarchy hazards above. -`X12_276_DATE_ROW_DROPPED` and `X12_276_REFERENCE_ROW_DROPPED` report a DTP or a -REF that reached the reader short of the elements its row is built from: each is -a record and not a slot, so the whole row goes and the loss is reported rather -than left to look like something the sender never stated. They are siblings of -the 270's codes rather than a widening of them, so a consumer narrowing on -`X12_270_LEVEL_DETACHED` does not start seeing claim-status requests. The 276's -AMT loss takes the EXISTING `X12_AMOUNT_ROW_DROPPED`, which already names that -loss for every AMT this library reads. Every 276 code is raised on the 276 path -only, so no fixture of any other transaction set gains a warning it did not -have. - -**`X12_TR3_CONFORMANCE` gains a 276 row** naming `005010X212` with its errata -`005010X212E1`, beside the 277 row that names the same guide: they are two halves -of one document and each half now carries a row. The 277 row's note said this -package implemented that half alone; it no longer does, so both notes now say -the pair is covered. - -**No external oracle is claimed.** Nothing reachable maps a 276 request at -005010, so the evidence for this surface is the build-then-read round trip, the -committed golden at `test/fixtures/golden/276.edi`, the property suite over a -delimiter-safe grammar, and the 277's own declarations in this package. Nothing -here promises or implies differential conformance. diff --git a/.changeset/tr3-conformance-manifest.md b/.changeset/tr3-conformance-manifest.md deleted file mode 100644 index 9313ef3..0000000 --- a/.changeset/tr3-conformance-manifest.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -"@cosyte/x12": minor ---- - -A new package-root export, `X12_TR3_CONFORMANCE`, states which implementation -guide this library implements for each transaction it reads or builds, and how -that identifier stands against the federal incorporation by reference at -45 CFR 162.920. It changes no parse and no emit behaviour: nothing about what -this package decodes or writes moves with it. - -**What a consumer can now assert on.** One frozen array of rows, each carrying -the transaction set number as this package's own export names spell it, the -variant where a transaction has more than one document (`P` / `I` / `D`, -`277CA`, the 278 `request` and `response`), the identifier implemented with its -errata suffix, the directions actually implemented, an `adoption` of -`incorporated-by-reference`, `errata-in-practice` or `not-adopted`, the -identifiers 45 CFR 162.920 names for that transaction, and a note where the row -is not the simple case. `X12Tr3Conformance`, `X12Tr3Direction` and -`X12Tr3Adoption` are exported alongside it, from the package entry point, with -no subpath import. - -**The identifiers come from the regulation, not from prose.** Every -`cfrAdopted` value is read from the official Government Publishing Office XML -of 45 CFR 162.920, title 45 volume 2, 2024 annual edition, retrieved -2026-08-25. Thirteen 005010 identifiers appear in that section and no others, -so each `not-adopted` row is a checkable negative over a complete list rather -than an absence of evidence: the 277CA (005010X214), the 999 (005010X231A1) and -the TA1 are named nowhere in it, and the TA1 carries a null identifier because -it is an envelope-level segment that no implementation guide identifier names -at all. - -**Where the industry and the regulation disagree, both are carried.** The 270, -271, 834, 835 and the three 837 variants implement errata revisions the section -does not name, so each row carries the errata identifier it implements AND the -identifier the section does name, with a note rather than a silent substitution. -The 270 and 271 notes record that an operating rule the same section -incorporates, CAQH CORE 259, calls 005010X279A1 the adopted document while the -incorporation list names 005010X279, and they state no verdict on which reading -a regulator would enforce. - -**The 278 declaration is corrected.** The package entry point declared the 278 -as a request guide plus a separate response guide, 005010X216, which -45 CFR 162.920 does not adopt for that transaction or name anywhere. It now -declares 005010X217 for both directions, which is the document the section -adopts and which covers request and response by its own title. `build278Response` -still writes 005010X216 into ST-03 and GS-08: that emitted value is deliberately -unchanged, and the 278 response row records the divergence so a consumer can see -it as data instead of discovering it on the wire. - -**Backed by a test that derives rather than transcribes.** The readers and -builders are enumerated from the package entry point, so a transaction that -gains one without gaining a row fails the suite naming the export, and a -transaction that arrives one-sided fails rather than implying a second -direction. Every build row is checked against the ST-03 its builder really -emits for a caller that states no implementation convention reference of its -own. diff --git a/README.md b/README.md index 433260e..df47a0d 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ a stable code instead of failing or guessing. ## Status -**Version 0.0.18**, published on npm from a public repository. +**Version 0.1.0**, published on npm from a public repository. From `0.1.0` the public API is settled and safe to depend on: the exported readers, builders, types and warning codes are the surface we keep stable, and the warning registry only ever gains diff --git a/package.json b/package.json index c8d1b73..b99d1b3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cosyte/x12", - "version": "0.0.18", + "version": "0.1.0", "description": "Developer-focused ASC X12 healthcare EDI parser, serializer, and builder for Node.js and TypeScript: lenient on parse, spec-clean on emit.", "license": "MIT", "author": "Cosyte (https://cosyte.com)", diff --git a/src/index.ts b/src/index.ts index b93d9a1..19e4568 100644 --- a/src/index.ts +++ b/src/index.ts @@ -18,7 +18,7 @@ * console.log(VERSION); * ``` */ -export const VERSION: string = "0.0.18"; +export const VERSION: string = "0.1.0"; // The package's conformance declaration: which implementation guide each // reader and builder implements, which identifiers 45 CFR 162.920 names for