From 49078eb3ce6900aa44336f9c72ed3c0def0c6790 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 25 Sep 2026 21:33:13 +0000 Subject: [PATCH] Version Packages --- .changeset/brave-moons-measure.md | 84 ---- .changeset/eighty-jars-argue.md | 34 -- .changeset/first-minor-release.md | 33 -- .changeset/lucky-pandas-refuse.md | 50 -- .changeset/olive-bats-report.md | 48 -- .changeset/olive-otters-gather.md | 36 -- .changeset/olive-pumas-greet.md | 39 -- .changeset/pink-cobras-invent.md | 65 --- .changeset/plenty-donuts-attack.md | 31 -- .changeset/quiet-jars-repeat.md | 74 --- .changeset/rare-owls-listen.md | 69 --- .changeset/short-hounds-author.md | 70 --- .changeset/spotty-cases-admit.md | 70 --- .changeset/wide-clocks-convert.md | 63 --- CHANGELOG.md | 721 +++++++++++++++++++++++++++++ README.md | 2 +- package.json | 2 +- src/index.ts | 2 +- 18 files changed, 724 insertions(+), 769 deletions(-) delete mode 100644 .changeset/brave-moons-measure.md delete mode 100644 .changeset/eighty-jars-argue.md delete mode 100644 .changeset/first-minor-release.md delete mode 100644 .changeset/lucky-pandas-refuse.md delete mode 100644 .changeset/olive-bats-report.md delete mode 100644 .changeset/olive-otters-gather.md delete mode 100644 .changeset/olive-pumas-greet.md delete mode 100644 .changeset/pink-cobras-invent.md delete mode 100644 .changeset/plenty-donuts-attack.md delete mode 100644 .changeset/quiet-jars-repeat.md delete mode 100644 .changeset/rare-owls-listen.md delete mode 100644 .changeset/short-hounds-author.md delete mode 100644 .changeset/spotty-cases-admit.md delete mode 100644 .changeset/wide-clocks-convert.md diff --git a/.changeset/brave-moons-measure.md b/.changeset/brave-moons-measure.md deleted file mode 100644 index 30a6727..0000000 --- a/.changeset/brave-moons-measure.md +++ /dev/null @@ -1,84 +0,0 @@ ---- -"@cosyte/ccda": minor ---- - -feat: measure conformance against the normative artifacts, and fix what the measurement found - -A built document's conformance was expected rather than proven. The README said so: -the builder's gap had been "grounded against the raw C-CDA R2.1 IG text rather than a -validator run". It is measured now, and measuring it found 86 error-severity results -across the populated document the builder emits, then eight more once the measured set -covered every section it can emit rather than one populated init shape. - -`pnpm conformance` fetches the normative C-CDA R2.1 Schematron, its vocabulary file -and the CDA R2 XML schema at run time from pinned immutable references, validates -every document the builder emits against the schema and then the Schematron's -error-severity phase, re-runs the Schematron over a public sample corpus parsed and -re-serialized through this library, and writes `documentation/conformance-report.md`. -The run fails if the committed report differs from what it just produced, and the -ordinary suite fails unless the README's conformance statement agrees with the report -on the artifact revision, the document types validated, the count of error-severity -results and the count of round trips whose error sets differed. The current result is -zero and zero. - -**This is why the bump is minor rather than patch.** It adds one exported type -(`BuildCcdaSelfCareActivity`) and one warning code (`MISSING_SELF_CARE_ACTIVITY`), and -it moves what the builder emits and therefore what a consumer reads back from it. The -emitted changes: the patient carries `raceCode` and `ethnicGroupCode` as -`nullFlavor="UNK"`; the device author carries a `representedOrganization`; a CCD -carries `documentationOf/serviceEvent`; a Referral Note carries an -`informationRecipient`; an empty required section declares the entries-required -`templateId` alongside the entries-optional one; both Concern Acts carry the `CONC` -code the guide fixes and the CDA R2 `Act` sequence makes mandatory; a Reaction -Observation carries an `id`; a no-known-allergy entry carries the substance -participant with a `nullFlavor="NA"` code; the Vital Signs Organizer carries the -LOINC code its 2.1-only branch requires rather than the SNOMED one; an Immunization -Activity states `negationInd` on both arms; a Result Organizer emits its -`effectiveTime` as an interval; and an act-variant procedure carries the -`effectiveTime` that variant alone requires at error severity. - -Two reads change with them. A no-known-allergy entry's `allergen` is now present and -explicitly `nullFlavor="NA"` rather than absent, which is what an entry from any other -system has always parsed to here; `noKnownAllergy` and `negated` are unchanged and -there is no code to mistake for an allergen. An administered immunization now reads -back `refused: false` rather than `undefined`, because the document states it. - -Widening the measured set from one populated init shape to every section the builder -emits found eight more, and closing them moved five more things. The Age Observation's -fixed `code` is SNOMED CT `445518008` "Age At Onset" rather than `397659008` "Age"; the -Family History Death Observation's fixed `value` is `419099009` "Dead" rather than -`419620001` "Death"; a Family History Observation's fixed `code` carries the LOINC -`75315-2` translation its template requires; a Mental Status Observation's fixed `code` -carries the LOINC `75275-8` translation its template requires; and a Functional Status -Organizer is written only when it carries the Self-Care Activities (ADL and IADL) -observation the template SHALL contain. - -That last one is the only change a caller has to do anything about. -`BuildCcdaFunctionalStatusOrganizer` takes `selfCareActivities`, each written as a -`…22.4.128` observation and read back as an ordinary functional-status finding. Supply -none and the organizer is not written: its findings go out as standalone Functional -Status Observations, every one of them present and unchanged, and the returned document -carries `MISSING_SELF_CARE_ACTIVITY` saying the grouping, its categorization code and -its `effectiveTime` were dropped and why. Fabricating an all-`nullFlavor` activity would -assert an assessment nobody performed, and writing the organizer anyway would stamp a -template the document does not satisfy. - -The XML schema the harness validates against is the SDTC-extended tree at the same -pinned commit rather than `schema/normative`, because the builder emits the HL7-approved -`sdtc:deceasedInd` extension and the normative schema declares no extension elements. - -Every fix keeps the never-invent-content rule: a `SHALL` slot with no known value is -`nullFlavor`, and no clinical time, dose, route or code is invented to satisfy a -cardinality. - -**What the measurement does not cover**, stated where the capability is rather than -below it: the two document types the builder emits and not the other ten, the -error-severity phase and not the warning phase, value-set membership only where the -Schematron's own vocabulary file checks it, and a round trip that is differential -rather than absolute. It is an assessment against a published artifact, not a -certification, and no accredited body has reviewed it. The harness needs network -egress and fails explicitly without it; there is no offline pass. Nothing it fetches -is committed or ever sits in a tracked path. - -Two development dependencies, neither reaching the runtime set: `xmllint-wasm` for -XML Schema validation and `xpath` for XPath 1.0 evaluation. diff --git a/.changeset/eighty-jars-argue.md b/.changeset/eighty-jars-argue.md deleted file mode 100644 index e123487..0000000 --- a/.changeset/eighty-jars-argue.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -"@cosyte/ccda": patch ---- - -docs: cover the profile, terminology and conformance surfaces, and gate the bundle - -The published narrative documentation gained a page and a guard. Three surfaces -that a caller has to configure correctly were described only in passing inside -the limitations page, with nothing a reader could copy and run: the vendor -profile system, the bring-your-own `TerminologyAdapter`, and the required-section -conformance status. Each now has its own section on a new "Conformance, profiles -& terminology" page with an executable example, and every one of those examples -is compiled and run against the built package by the existing doc/code agreement -suite, so a documented call that stops working fails the build rather than -misleading a reader. The terminology example uses an in-process stub adapter: it -opens no socket and needs no licensed terminology service, which is also what -makes it a usable template for testing an adapter of your own. - -Three pages claimed the package was "published on npm at `0.0.3`" while it was -twelve patches past that. No page names a published version now; they point at -`npm view @cosyte/ccda version`, which cannot go stale. Every historical note -that dates a behaviour change to a past version is untouched and is now held in -place by a retention floor, because those sentences are the change record for a -reader pinned to an older version, not staleness. - -Page frontmatter was inconsistent (`sidebar_label` on five pages of nine) and -`sidebar_position` collided across five pages. Every page now carries the same -four keys, and each position agrees with the page's place in `sidebars.json` -rather than being a second, disagreeing answer to what comes next. - -A new guard holds all of it: every symbol the public entry point exports, types -included, must be named by a page or carry a stated reason in a committed -exemption record that cannot outlive the symbol it excuses. The formatter now -covers the bundle as well, so the pages cannot drift out of format. diff --git a/.changeset/first-minor-release.md b/.changeset/first-minor-release.md deleted file mode 100644 index 1b1df64..0000000 --- a/.changeset/first-minor-release.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -"@cosyte/ccda": 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: - -- Reading C-CDA R2.1 documents: all twelve US Realm document types are recognized, the header and - the patient are typed, and fourteen entry families are extracted (problems, medications, - allergies, results, vital signs, immunizations, procedures, encounters, smoking status, plan of - treatment, functional status, mental status, family history and past medical history), each coded - value keeping the code system it was sent with. -- Lenient parsing with stable warning codes and a `{ strict: true }` mode that refuses the first - tolerated deviation; hostile XML (a DTD, entity expansion, an oversized or over-deep document) is - always refused. -- Building a spec-clean CCD, Referral Note or inpatient Discharge Summary with `buildCcda`, editing - a parsed document section by section with `editCcda` (which stamps a CDA R2 revision), and - re-serializing a parsed document, where parse and serialize are a fixed point. -- Required-section validation that reports, for every document type, how much of its obligation it - asserts, and UCUM checks on the quantities in results and vital signs. - -What the version promises. The exported names, options, return shapes and warning codes are the -surface we keep stable. 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. - -What is not covered yet. `buildCcda` emits three of the twelve document types and throws for the -other nine. Editing replaces or adds whole sections only: no entry-level append and no section -removal. Required-section validation under-warns for most document types, so a quiet parse is not a -conformance result. A terminology adapter is consulted at five coded slots only, and this package -ships value set identifiers, never their member codes. UCUM checking is grammatical over a curated -set of atoms. diff --git a/.changeset/lucky-pandas-refuse.md b/.changeset/lucky-pandas-refuse.md deleted file mode 100644 index b5412fe..0000000 --- a/.changeset/lucky-pandas-refuse.md +++ /dev/null @@ -1,50 +0,0 @@ ---- -"@cosyte/ccda": minor ---- - -required sections: every document type now reports how far it was verified - -Six of the twelve required-section (SHALL) tables asserted nothing, and nothing -in the API said whether that meant "no requirement", "not checked yet" or "the -requirement is unassertable". A consumer could not tell a verified all-clear from -an unverified silence, and only the prose warned them not to. - -Five of those six were read off the normative C-CDA R2.1 base implementation -guide's document-level conformance statements. Consultation Note now asserts -History of Present Illness (CONF:1198-28907), Allergies and Intolerances -(-28911) and Problems (-28929); each is scoped to the R2.1 `@extension` stamp its -constraint's rule context carries, so an R1.1-origin document is asserted exactly -as before. Progress Note, Procedure Note, Operative Note and Diagnostic Imaging -Report assert nothing still, and that is now a traced result rather than an -unread table: every SHALL section their templates name is either outside this -parser's recognized catalog or a choice, and each is reported by name with the -reason. Unstructured Document carries no `structuredBody` at all (its component -SHALL be a `nonXMLBody`, CONF:1198-31086), so it has no section obligation. - -The addition is **additive**: nothing is removed, narrowed or renamed. -`requiredSectionKeys` and `missingRequiredSections` keep their signatures and -their shape, and the six document types that already asserted keys (CCD, -Discharge Summary, Referral Note, History and Physical, Care Plan, Transfer -Summary) return exactly the same values as before, including under the unstamped -reading, with every conformance id they already carried left in place. - -New exports: - -- `requiredSectionStatus(documentType, options?)`: the asserted `keys` plus a - `verification` of `traced-complete`, `traced-partial`, `untraced` or - `not-applicable`, the `traced` provenance (conformance id and the source's own - section name) behind each traced key, and every SHALL section left - `unasserted` with the reason it is unassertable. -- `requiredSectionStatuses(options?)`: all twelve, so the whole picture is - enumerable by a consumer. -- `DOCUMENT_TYPES`: the runtime enumeration behind the `DocumentType` union. -- The types `RequiredSectionStatus`, `RequiredSectionVerification`, - `TracedRequiredSection`, `UnassertedRequiredSection` and - `UnassertedSectionReason`. - -The only behaviour change is the one the trace bought: a Consultation Note that -carries the R2.1 stamp and omits History of Present Illness, Allergies or -Problems now draws `REQUIRED_SECTION_MISSING` (a warning, never a fatal) where it -previously parsed silent. A traced state is a claim about what was read for that -type, so the six types nobody re-read report `untraced` whether or not their keys -already cite a conformance id. diff --git a/.changeset/olive-bats-report.md b/.changeset/olive-bats-report.md deleted file mode 100644 index 4b2f204..0000000 --- a/.changeset/olive-bats-report.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -"@cosyte/ccda": minor ---- - -feat: report a coded value that sits outside the value set its slot is bound to - -A consumer could learn that a coded value's code system was unexpected, and, with a terminology -adapter, that its code was not a real member of that system. Neither question is the one a -certifier asks. C-CDA binds each checked slot to a **value set**, and where that binding is -Required the standard states membership as a SHALL, so a document can carry the right system and a -real code and still be non-conformant at a safety-critical slot. Nothing in this package could see -that case. - -`parseCcda(xml, { valueSets })` now takes a bring-your-own `ValueSetSource` beside the existing -`terminology` adapter. It answers one question, `isMember`, and it declares the `release` of the -package it answers from. A code the source reports outside a Required binding's value set raises -`VALUE_SET_BINDING_VIOLATED`; a value set the source holds no expansion for raises -`VALUE_SET_BINDING_NOT_EVALUATED`, never silence, because a skipped value set reads exactly like a -clean one; returning `undefined` is "no opinion" and is silent. An exception your source raises -reaches you rather than being swallowed into a document that merely looks checked. - -**This package ships value set identifiers and never their member codes.** Expansions are licensed -data. `valueSetBinding(slot)` and `valueSetBindings()` expose the declaration: for each of the five -checked slots, the bound value set's OID, whether C-CDA R2.1 binds it as `required` or `preferred`, -and the conformance statement, template and artifact revision the row was read from. Every row was -read off the normative C-CDA R2.1 Schematron this repository already pins by commit and digest, so -no binding is asserted that the normative source was not read for. - -**Four of the five bindings are Required and one is Preferred, and the difference is reported, not -rounded off.** A Preferred binding never produces the Required-binding finding: the Problem -Observation's value SHALL be present and its code only SHOULD come from the Problem value set, and -reporting a departure from that as a SHALL violation would turn a conformant problem list into a -defect. Binding strength is the verb attached to "be selected from", not the cardinality beside it: -a `routeCode` a template only SHOULD contain, whose code SHALL come from the route value set, is a -Required binding. - -Nothing a parse returns changes. No value is refused, rewritten, reordered or dropped on a negative -verdict, the document re-serializes identically, and a parse that supplies no source produces -exactly the findings it always did: membership is never inferred from the code system alone. - -**This is why the bump is minor rather than patch.** It adds two warning codes and eleven exported -symbols (`ValueSetSource`, `ValueSetMembershipQuery`, `ValueSetMembershipAnswer`, `ValueSetBinding`, -`ValueSetBindingStrength`, `ValueSetBindingSource`, `BoundValueSet`, `valueSetBinding`, -`valueSetBindings`, `valueSetBindingViolated`, `valueSetBindingNotEvaluated`), and renames, removes -and repurposes nothing. `CcdaWarning` gains two optional fields, `valueSet` and `valueSetRelease`, -carried only by the two new codes: a message still comes whole from the frozen registry, so the -value set OID and the release ride as data rather than as interpolated text, and the coded value -that caused the finding appears nowhere in it. diff --git a/.changeset/olive-otters-gather.md b/.changeset/olive-otters-gather.md deleted file mode 100644 index 84341af..0000000 --- a/.changeset/olive-otters-gather.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -"@cosyte/ccda": patch ---- - -Bring this repo to the shared package baseline: install hardening, the required -js-yaml override, and a CLAUDE.md under its line ceiling - -Three drifts the meta-repo's `config/drift-manifest.json` measures against this -repo are closed, and nothing else changes. - -`pnpm-workspace.yaml` is new and carries `minimumReleaseAge: 1440` and -`trustPolicy: no-downgrade`, the publication cooldown and trust policy the -standard requires of every `@cosyte/*` package repo. Both keys are switched off -by default in pnpm 10, and both are ignored outright by a pnpm older than -10.16.0 and 10.21.0 respectively, so `packageManager` is raised from `pnpm@10.0.0` -to `pnpm@10.34.5`: a settings file the pinned package manager ignores decorates -rather than defends. A cold-store `pnpm install --frozen-lockfile` was measured -against the new policy and raised no `ERR_PNPM_TRUST_DOWNGRADE`, so no -`trustPolicyExclude` entry is carried and the exemption lists are absent rather -than empty. - -`pnpm.overrides` gains the required `js-yaml@>=4.0.0 <4.3.0` entry. It is listed -BEFORE this repo's own `js-yaml@>=4.0.0 <4.3.2` entry on purpose: pnpm applies -the LAST matching override, measured on this tree, and the other order resolved -js-yaml 4.x down from 4.3.2 to 4.3.0, which would have been an advisory -remediation quietly undone by a compliance edit. The lockfile still resolves -js-yaml at 4.3.2, and no existing override was removed, narrowed or repointed. - -`CLAUDE.md` goes from 459 lines to 232, under the ceiling of 300 the standard -declares, entirely by RELOCATION. Every block of prose that left it is -reproduced verbatim in `documentation/agent-notes.md`, under the very heading -the imperative's `Why:` pointer already resolved to, and what is left in -`CLAUDE.md` is the rule plus that pointer. Nothing was deleted, no imperative -lost its file, and no claim was reworded, softened or strengthened. - -No published API, warning code, parser behaviour or emitted XML changes. diff --git a/.changeset/olive-pumas-greet.md b/.changeset/olive-pumas-greet.md deleted file mode 100644 index efe8f35..0000000 --- a/.changeset/olive-pumas-greet.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -"@cosyte/ccda": patch ---- - -README: standardize the page, and make its usage example a test - -The npm package page and the GitHub landing page are the same file, and for most -consumers it is the only page they will ever read. It was 1086 lines of deep -reference with no answer above the fold to the three questions a reader arrives -with: what is this for, is it safe to depend on, and what does it do with my -patients' data. It also carried a status line claiming the package was published -at `0.0.3`, twelve patch releases out of date, and a hand-written `alt` string on -the banner that disagreed with the one the brand assets declare for that tile. - -The page now opens with the banner (carrying the declared `alt` string), the -title, a one-line hook, the four house badges and the `package.json` -`description` verbatim, followed by a table of contents. Four sections are new: - -- **Why this exists**, which names the problem, the nearest alternative a reader - would otherwise reach for, and why this is not that. -- **Status**, which states the version this package declares, says what that - version does and does not claim about the public API, and names the surfaces - that are still moving instead of implying they are settled. -- **PHI and safety**, which states what the library does with patient data for - each of logging, in-memory retention and writing to disk, and names what the - consuming application still owns. -- **Contributing**, which says where to ask, whether unsolicited pull requests - are accepted, and every check a contribution has to clear. - -`Install` now states the Node engine floor and the module format. The parse -walkthrough is consolidated into a single `Usage` example that builds a complete -synthetic CCD, reads it back and shows what every call returns, and that block is -now executed by the test suite against the built entry point: an example whose -shown output stops matching what the code produces fails the build. Roughly half -of documentation traffic is agents that lift a usage block verbatim, so a wrong -example is wrong generated code at scale. - -No parse, emit, builder or profile behaviour changes, and no documented behaviour -was dropped: every reference section the page carried is still on it. diff --git a/.changeset/pink-cobras-invent.md b/.changeset/pink-cobras-invent.md deleted file mode 100644 index 92e3e53..0000000 --- a/.changeset/pink-cobras-invent.md +++ /dev/null @@ -1,65 +0,0 @@ ---- -"@cosyte/ccda": minor ---- - -required sections: a SHALL warning fires exactly where the normative source says it should - -The per-document-type required-section (SHALL) tables now cover all twelve -recognized types. Six of them (CCD, Discharge Summary, Referral Note, History and -Physical, Care Plan, Transfer Summary) had asserted sets that predated anyone -reading the normative source for them, and reported `verification: "untraced"` to -say so. Their document-level `errors` and `warnings` rules have been read off the -HL7 C-CDA R2.1 normative Schematron and the tables corrected. **No recognized -document type reports `untraced` any more.** - -Two defects went in opposite directions and both are closed. - -- **A conformant Discharge Summary drew a false warning.** The table asserted a - Discharge Medications section as a SHALL. The normative source states it as a - **SHOULD**, in that document's warnings rule (CONF:1198-30525), so a conformant - Discharge Summary that omits the section was drawing a - `REQUIRED_SECTION_MISSING` it had not earned. It is withdrawn, in the stamped - and the unstamped reading alike. -- **A non-conformant document passed quietly.** The same Discharge Summary table - omitted Plan of Treatment (CONF:1198-30528), which the errors rule requires - unconditionally; a History and Physical asserted one of the ten SHALL sections - its errors rule names; a Transfer Summary asserted three of six. Every one of - those sections that this parser's catalog recognizes is asserted now. - -**What changed on the published surface**, for `requiredSectionKeys`, -`missingRequiredSections`, `requiredSectionStatus` and the parser warnings that -follow from them: - -- **Discharge Summary**: `dischargeMedications` withdrawn, `planOfTreatment` - added. -- **History and Physical**: `familyHistory`, `pastMedicalHistory`, `medications`, - `results`, `socialHistory` and `vitalSigns` added beside `allergies`. -- **Transfer Summary**: `results`, `vitalSigns` and `reasonForReferral` added. -- **CCD, Referral Note and Care Plan**: confirmed unchanged. - -Expect a document of the three changed types to draw warnings it did not draw -before, and a conformant Discharge Summary with no Discharge Medications section -to fall silent. `REQUIRED_SECTION_MISSING` remains a Tier-2 warning, never a -fatal, and remains safety-critical, so no vendor profile can quiet it. - -**Every key an added rule contributed is scoped to the R2.1 stamp**, because each -of those document-level rules matches only a `ClinicalDocument` whose -`templateId` carries `@extension="2015-08-01"`. An unstamped, R1.1-origin document -is therefore asserted exactly as it was before, with one exception in the safe -direction: a key withdrawn because the source states it as a SHOULD or as a choice -is withdrawn from the unstamped reading too, since "no sentence made this -unconditional" does not depend on a version stamp. - -**Provenance is on the surface, not in a commit message.** Every asserted key now -carries the conformance statement it was read from and the source's own name for -the section; every SHALL section the source names that this package cannot assert -is enumerated with its reason (`outside-section-catalog` for Hospital Course, -General Status, Physical Exam and Review of Systems; `not-unconditionally-required` -for each choice); and `requiredSectionStatus(type).source` names the normative -artifact and **that artifact's own revision date**, so a reader holding a later -revision can tell a table is stale without re-deriving it. The new -`RequiredSectionSource` type is exported alongside it. - -This is document-level validation only: which sections a document type SHALL -contain, never what a section or an entry SHALL contain. A quiet parse is still -not a conformance result. diff --git a/.changeset/plenty-donuts-attack.md b/.changeset/plenty-donuts-attack.md deleted file mode 100644 index a14efc1..0000000 --- a/.changeset/plenty-donuts-attack.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -"@cosyte/ccda": patch ---- - -phi-scan: a target enumerated and never read refuses the scan - -`--allow-fixture` withdrew a path from the target list and the run reported on -whatever remained, so the withdrawal never showed up in the verdict: four argv -shapes printed `OK, no hits` at exit 0 over a corpus holding a live, detectable -violator, including a whole-run sweep whose only violator was the withdrawn file. -CI could print a clean verdict over a corpus it never opened. - -The scanner now refuses (exit 2), in every mode, over any target this run -enumerated and never read, naming the paths. The comparison is a set difference -rather than a count, because a count counts the targets that did get read. A -bypass naming a path the run does not enumerate refuses too, under its own -message, since such a flag subtracts nothing (in `paths` mode a nonexistent path -is refused earlier still, by `File not found`). `--allow-fixture` no longer -selects the mode and is unioned into the target list in `paths` mode, so it means -the same thing in every argv. - -What it costs, stated rather than left to be discovered: `--allow-fixture` can no -longer reach exit 0 in any mode. The flag, `phi-scan-overrides.md` and the log -gate all stay, so an attempt is recorded and then refused rather than silently -honored, and `scripts/phi-allow-list.txt` is now the only mechanism that reaches -a clean run. The hit footer no longer advertises the flag as a remedy. - -No published API, warning code or parser behaviour changes; this is the commit -gate only. There was no real unread corpus in this repo: the all-mode sweep -enumerates and reads the same set, and the four states were reproduced with -planted fixtures rather than found. diff --git a/.changeset/quiet-jars-repeat.md b/.changeset/quiet-jars-repeat.md deleted file mode 100644 index 67de4ab..0000000 --- a/.changeset/quiet-jars-repeat.md +++ /dev/null @@ -1,74 +0,0 @@ ---- -"@cosyte/ccda": minor ---- - -version stamps: a document says which C-CDA release it was written for - -C-CDA 3.0.0 gave every document template a new `@extension`, `2024-05-01`, and -4.0.0 and 5.0.0 kept it, so a document written for a release later than R2.1 is -detectable from the `templateId` that resolves its type. Through `0.0.15` this -parser got that case wrong twice, once loudly and once in silence. - -Loudly: any `@extension` that was not `2015-08-01` drew -`TEMPLATE_EXTENSION_ABSENT`, whose frozen message reads "carries no @extension -version stamp ... (may pre-date R2.1)". For a `2024-05-01` document both halves -are false, because it carries a stamp and it post-dates R2.1. - -Silently, and worse: the same path reported the document as unstamped, and the -required-section tables read that as the R1.1-origin **reduction**, which drops -every R2.1-stamp-scoped SHALL key. A post-R2.1 CCD carrying neither Social -History nor Vital Signs therefore drew no `REQUIRED_SECTION_MISSING` at all. The -library had classified a document from the future as one from the past. - -**Two warning codes are added and none is renamed or removed.** -`TEMPLATE_EXTENSION_UNMODELED_RELEASE` fires when the resolving `templateId` -carries an `@extension` that is present and is not the R2.1 stamp; -`REQUIRED_SECTIONS_NOT_EVALUATED` fires once for such a document to say its SHALL -obligation was not computed. `TEMPLATE_EXTENSION_ABSENT` keeps its narrower -meaning ("no `@extension` at all", the R1.1-origin shape) and its registry message -byte for byte. - -**An unmodelled stamp is reported, never reduced.** Such a document draws no -`REQUIRED_SECTION_MISSING` in either reading, and -`requiredSectionStatus(type, { stamp: "unmodeled-release" })` reports -`evaluation: "not-evaluated"` so an empty key set still says which emptiness it -is. `evaluation` is a new field on `RequiredSectionStatus` and a separate axis -from `verification`, which remains a claim about the document type that no option -moves. - -**The release this package validates against is an exported value now.** -`CCDA_CONFORMANCE_RELEASE` is `"R2.1"`, beside the closed table of stamps this -package can name (`CCDA_RELEASE_STAMPS`, plus `R30_EXTENSION`, -`releaseForTemplateExtension` and the three-state `readTemplateStamp`). **This -does not retarget the library**: regulation still adopts R2.1 and SVAP use is -voluntary, so a later release is recognized and named, never read against. -4.0.0 also relaxed the US Realm Header and 5.0.0 added a Pregnancy Section, and -neither is modelled here. - -Four things are decisions rather than details: - -- **A reported stamp comes from a closed set of literals this package owns.** A - message names a member of `CCDA_RELEASE_STAMPS` or names no stamp at all, so a - sender-controlled `@extension` can never reach a `CcdaWarning.message` (or, - under `{ strict: true }`, a thrown error's stack). No factory gained a - parameter that a message interpolates. -- **The existential R2.1 rule is unchanged and beats a later stamp.** A document - carrying the resolving root twice, once stamped `2015-08-01` and once for a - later release, is inside the Schematron rule's context and is evaluated under - the full R2.1 obligation, in either sibling order. -- **`{ r21Stamped }` behaves exactly as it did.** - `requiredSectionKeys("ccd", { r21Stamped: false })` returns the same four keys - in the same order. A boolean cannot express three states, so the three-state - `{ stamp }` was added beside it rather than repurposing a published option. -- **`legacyR11` does not tolerate the new code.** It is receive-tolerance for - documents from the past, grounded in the receive-both-R2.1-and-R1.1 - requirement; quieting a future-release stamp there would restore the silence - this change removes. Neither new code is safety-critical, so a consumer who has - read the later guide can still write their own profile for it. - -Nothing became stricter. No fatal was added, no content the C-CDA open-template -rule permits is refused, and a post-R2.1 document's clinical reading (patient, -sections, entries, values) and its byte-faithful re-serialization are identical -to the same document stamped `2015-08-01`. Under `{ strict: true }` the new -warnings escalate like every other Tier-2 warning, which is pre-existing -caller-opted behaviour. diff --git a/.changeset/rare-owls-listen.md b/.changeset/rare-owls-listen.md deleted file mode 100644 index 0556a61..0000000 --- a/.changeset/rare-owls-listen.md +++ /dev/null @@ -1,69 +0,0 @@ ---- -"@cosyte/ccda": minor ---- - -subject override: an entry is never read as the patient's unless it is - -CDA R2 gives `Section.subject` cardinality `0..1` and defines it as the "primary -target of the entries recorded in a section"; C-CDA admits the same override on a -clinical statement. So a conformant document can carry a relative's, a donor's or -a contact's clinical statement inside the patient's document. This parser read -`` in exactly one place (a Family History Organizer's `relatedSubject`) -and every other extractor handed such an entry back as the patient's, silently. -That is another person's clinical data attributed to this patient, in a read path -documented as the patient's own. - -A top-level `` that a subject declaration governs is now **withheld** from -every record-target read path and flagged with a new safety-critical Tier-2 -warning, `SUBJECT_CONTEXT_OVERRIDE`, which no vendor profile may tolerate. An -entry is governed when it carries a declaration itself, when a clinical statement -nested inside it carries one, or when an enclosing section carries one (the -nearest enclosing declaration wins, which is the standard's own conduction rule). - -Four things are decisions rather than details: - -- **Presence is the trigger.** A declaration is an override whatever it names. - Nothing is compared with the record target and nothing is resolved to the - patient, because that would make a clinical answer depend on vendor identifier - hygiene. A document that redundantly restates the patient as an entry subject - therefore loses those entries from the record-target read paths and gains a - warning for each: the safe direction of the error, and the accepted cost. -- **The whole top-level entry is withheld**, its own statement and every statement - nested inside it, and the same unit does the counting. A declaration on the - second Problem Observation of a Problem Concern Act withholds that whole concern - act and emits one warning at the act's locus, never a concern act handed back - one observation short. -- **Family History is untouched.** A Family History Organizer's own subject slot - is that template's mechanism for naming the relative, whatever it contains, so - it is never an override, draws no warning, and re-overrides an enclosing section - declaration. `getFamilyHistory()`, the `familyHistory` field, - `extractFamilyHistory` and the aggregate's family-history slot return exactly - what they returned before, in every document shape. That carve-out is read-side - and reaches only the organizer the family-history path itself reads: an entry - that carries the Family History Organizer template beside one a record-target - read path returns (a Result Organizer, a Problem Concern Act) is withheld and - reported like any other, because a `templateId` is one element and C-CDA entries - carry several. A declaration nested deeper inside an entry is never that slot. -- **Withholding is read-side only.** The withheld entry is not dropped: the - byte-faithful round trip through `toString()` / `serializeCcda` reproduces it - unchanged, and section narrative is returned unredacted, unreordered and - unannotated. - -The count is per section and sums over the document: N governed top-level entries -in a section produce exactly N instances, in document order, each naming that -entry's own bounded locus (an element path from the CDA vocabulary, the section's -LOINC code, line and column, with `` for anything that fails its bound), -and a section that declares an override but governs no entry anywhere beneath it -produces exactly one instance at its own locus instead. The same withholding -applies to a per-family or aggregate extraction invoked directly on a section, -including a nested subsection governed by an ancestor's declaration, with the -warnings delivered on the parse context those functions already accept: no -signature, parameter or return type changes. `extractFamilyHistory` delivers them -too, while returning its own contents whole. - -Two behaviour changes to expect. A document carrying an override loses those -entries from `getProblems()` / `getMedications()` / `getAllergies()` and the other -ten record-target families where it previously returned them as the patient's, -and under `{ strict: true }` it now throws, because the new code escalates like -every other safety-critical Tier-2 warning. Nothing is modelled for third-party -subjects: a withheld entry is reachable only through the re-serialized document. diff --git a/.changeset/short-hounds-author.md b/.changeset/short-hounds-author.md deleted file mode 100644 index 2d2319c..0000000 --- a/.changeset/short-hounds-author.md +++ /dev/null @@ -1,70 +0,0 @@ ---- -"@cosyte/ccda": minor ---- - -A parsed document now says who authored it: the parser reads the header participations. - -A consumer could ask whose document it was and what was in it, but not who wrote it. -`CcdaHeader` carried ten fields and not one of them was a participation. Every -conforming US Realm document carries at least one `author` and exactly one `custodian`, -and an inpatient Discharge Summary carries the encounter it summarises, so the parser -was dropping data the source document was required to supply. Provenance is the answer -to "who said this", which is what a clinician asks of a medication list assembled by -three systems. - -`header.authorship` carries every document-level `` in document order: its -identifiers, its assigned person's name or its assigned authoring device's identity, -the organization the document states it acted for, and its author `time` at exactly the -precision the document stated. A partial author time stays partial. `header.custodian` -carries the custodian organization with its identifiers and its name. -`header.encompassingEncounter` carries the `componentOf/encompassingEncounter` frame: -its `effectiveTime` bounds, each keeping its own precision and any `nullFlavor`, and its -`dischargeDispositionCode`. - -**An inherited author reading is marked, never asserted.** CDA conducts an author down -from the document to a section and on to that section's entries, and this reports the -conduction rather than performing it silently. `CcdaSection.authorship` and each entry's -reading in `CcdaSection.entryAuthorship` carry an `inherited` flag: `false` when the -level stated these authors itself, `true` when it stated none and these are the nearest -enclosing level's. "The nearest enclosing author is this clinician" and "this entry was -authored by this clinician" are different claims and only the first is one the document -made. When no level carries an author the reading is absent at every level: the record -target, the custodian, a legal authenticator and an informant are never read as the -author, and no `componentOf` means no encounter frame rather than one derived from the -document `effectiveTime` or a `documentationOf` service event. - -One warning code is added and none is renamed, removed or repurposed. An `` -whose `assignedAuthor` carries neither an `assignedPerson` nor an -`assignedAuthoringDevice`, which the US Realm Header requires one of (CONF:1198-8456), -is kept, marked `unidentified`, and reported with `UNIDENTIFIED_AUTHOR`. It still -conducts to nested levels. Dropping it would turn "the document names an author whose -identity it never states" into "the document names no author", which is a more -reassuring claim than the document supports. The factory takes only a position and the -message comes whole from the frozen registry, so a clinician's name cannot reach a -diagnostic. - -Entries an overriding `` declaration governs are absent from `entryAuthorship` -entirely, not even their `ids`: it is a record-target read path, and a governed entry -appearing in one would put another person's entry back on the model through a second -door. - -**This is why the bump is minor rather than patch.** It adds seven exported types -(`CcdaAuthor`, `CcdaAuthoringDevice`, `CcdaAuthorship`, `CcdaCustodian`, -`CcdaEncompassingEncounter`, `CcdaEntryAuthorship`, `CcdaOrganization`) and one warning -code. `buildSection` takes an optional third argument, the enclosing author reading; -every existing call keeps working and reads the section as having no enclosing author, -which is what a caller framing a detached `
` is looking at. - -**Nothing here is breaking, and the new `CcdaSection` field is optional so that stays -true.** `CcdaSection` is an input surface as well as an output one, reachable through -`CcdaDocumentInit.sections`, which the public `CcdaDocument` constructor takes: a -required `entryAuthorship` would stop a consumer's existing section literal from -compiling, which is not a minor change. The parser populates the field on every section -it frames, empty where the section has no entry act to read. - -Framing a section reads each entry act's ``s without reporting on them, because the -entry-extraction walk already parses those same elements. No document's warning output -moves: not the count, not the order, and not whether `strict: true` throws. - -This is the READ side only. Nothing the builder emits changes, and emitting -`componentOf` is not part of it. diff --git a/.changeset/spotty-cases-admit.md b/.changeset/spotty-cases-admit.md deleted file mode 100644 index 09d3a16..0000000 --- a/.changeset/spotty-cases-admit.md +++ /dev/null @@ -1,70 +0,0 @@ ---- -"@cosyte/ccda": minor ---- - -Build an inpatient Discharge Summary, the third of the twelve document types - -`buildCcda` emits a Discharge Summary for `documentType: "dischargeSummary"`, -alongside the CCD and the Referral Note. `minor` rather than `patch` because the -package entry point gains two exported types: `BuildableDocumentType` (the union -of the three types the builder emits) and `BuildCcdaEncompassingEncounter`, and -the published `UnassertedSectionReason` union gains a third member, -`"assertion-would-tighten-parse"`. Nothing published was removed or renamed, and a -CCD or a Referral Note built from an init that names no Discharge Summary input -does not change by a byte. - -The document specializes the US Realm header with its own document `templateId` -root (`2.16.840.1.113883.10.20.22.1.8`, stamped `2015-08-01`), its LOINC document -code (`18842-5`), and the four SHALL sections its errors rule names: Allergies, -the narrative-only Hospital Course (`1.3.6.1.4.1.19376.1.5.3.1.3.5`, LOINC -`8648-8`), Discharge Diagnosis (V3) and Plan of Treatment (V2). Neither Problems -nor Medications is in that set, so unlike a CCD or a Referral Note a Discharge -Summary emits them only when you supply content, the medications in the -Medications Section; Discharge Medications is a SHOULD in the document's warnings -rule and is not emitted at all. Three new inputs carry the content: -`hospitalCourse`, `dischargeDiagnoses` and `encompassingEncounter`. - -**What you supply is emitted or refused, never dropped.** Those three inputs throw -a `TypeError` on a CCD or a Referral Note instead of being ignored, and -`assessment` and `reasonForReferral`, the Referral Note's narratives, throw on a -Discharge Summary. A CCD still ignores `assessment` and `reasonForReferral`, as it -always has. - -It is the only type carrying `componentOf/encompassingEncounter`, the frame -naming the stay. Every slot in it is required by the template and none is ever -invented: an encounter period bound or a discharge disposition you did not supply -is emitted as an explicit `nullFlavor="UNK"`, which satisfies the cardinality, -says the fact is unknown rather than stating one, and reparses as absent. None of -the three is ever derived from the document `effectiveTime` or from a -`documentationOf` service event. A supplied bound keeps exactly the precision you -gave it. The disposition's `codeSystem` defaults to the NUBC UB-04 Patient -Discharge Status code set, the system every member of the value set the template -names carries, and your own `codeSystem` is emitted verbatim instead when you -supply one. - -**All three types reparse with zero warnings.** The Hospital Course Section is an -IHE PCC template outside the section catalog; `parseCcda` now recognizes it by its -root (`findSection("hospitalCourse")` resolves), in every document and not only a -built one, so a section carrying that root and matching nothing in the catalog no -longer raises `UNKNOWN_SECTION_CODE`. That is the only parse output that moves: -the root is consulted after every catalog root and the LOINC fallback, so no -section changes key; the LOINC code `8648-8` alone still raises -`UNKNOWN_SECTION_CODE`; no entry in such a section is newly flagged as misplaced; -and the section is not asserted as required, so no Discharge Summary that omits it -gains a `REQUIRED_SECTION_MISSING`. `requiredSectionStatus("dischargeSummary")` -names it as unasserted with the new reason `"assertion-would-tighten-parse"`. - -Asking for one of the other nine types still throws a `TypeError` naming what you -asked for. That refusal is now derived from the builder's own specialization -table rather than from a list of strings, so a thirteenth recognized type is -refused the day it is recognized, and the membership test is `Object.hasOwn` -rather than `in`, so an untyped caller passing a prototype key is refused instead -of crashing partway through a build. Only an omitted (`undefined`) `documentType` -defaults to a CCD: `null`, which used to build a CCD, is refused, and so is any -value that is not a string, before any lookup and without converting it. - -Measured, not asserted: `pnpm conformance` validates fifteen built documents, -two of them Discharge Summaries at both ends of the input range, against the CDA -R2 XML schema and the error-severity phase of the normative C-CDA R2.1 -Schematron, with zero error-severity results. The README conformance statement -and the tracked report move with it, from two of twelve to three. diff --git a/.changeset/wide-clocks-convert.md b/.changeset/wide-clocks-convert.md deleted file mode 100644 index 62d8ce1..0000000 --- a/.changeset/wide-clocks-convert.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -"@cosyte/ccda": minor ---- - -Add the shared `toObject` / `toISO` / `toDate` conversion surface for a parsed `TS` - -Every `@cosyte/*` standard parser now exports the same three conversions, under the same -names, with the same return shapes and the same timezone rule, so moving between two of -them costs nothing to relearn. Five names are added to the package entry point: -`toObject`, `toISO`, `toDate`, and the `DateParts` and `ToDateOptions` types. - -- `toObject(ts)` returns a frozen `DateParts` carrying **only** the components the document - stated. Nothing is zero-filled, so `Object.keys()` on a year-precision value is `["year"]` - and the value's precision survives the conversion. `month` is the spec-native 1 to 12, the - component names are singular, and there is no `raw`, `precision` or `valid` key. - `millisecond` is the first three digits of the stated fraction taken verbatim and - right-padded (`.5` is 500, `.0500` is 50), never a floating-point fraction multiplied by - 1000. `offsetMinutes` is present if and only if the value carried an explicit offset, a - stated zero included. Deleting `offsetMinutes` leaves an object - `Temporal.PlainDateTime.from` and luxon's `DateTime.fromObject` accept unchanged; neither - library is a dependency and neither is imported. -- `toISO(ts)` renders ISO-8601 truncated to that same precision and pads nothing out, with - fractional digits rendered exactly as written. A stated offset is appended (`Z` for a - stated zero, otherwise `+HH:MM` / `-HH:MM`); with no stated offset nothing is appended and - no `Z` is fabricated. Because a stated zero renders `Z`, this is not a byte round-trip of - the wire value, and `serializeCcda` remains the round-tripping route. -- `toDate(ts, options?)` returns a `Date` only when the zone is determinate: the value - carried an offset, or the caller passed `assumeOffsetMinutes` (signed minutes east of UTC, - where an explicit `0` means "read this naive value as UTC"). With neither, the answer is - `undefined`. The host machine's timezone is never read and UTC is never assumed. A value's - own offset always beats an assumed one, a year below 100 stays that year rather than being - remapped into the 1900s, and components below the stated precision fill to their lowest - legal value for the instant only. An `assumeOffsetMinutes` that names no usable zone is - refused the same way: `NaN` and the two infinities are not a number of minutes, and a finite - offset large enough to push the result outside the range a JS `Date` holds denotes no - instant. Both answer `undefined`. What never comes back is an `Invalid Date`, which - satisfies the declared return type and defeats its point. -- An offset the **document** states is bounded as well, at 23 hours 59 minutes, and past that - bound the value is refused whole by all three functions. The v3 `±ZZZZ` token is four digits - wide and constrains neither field, so `+2400`, `+9999` and `-9999` are legal literals stating - 1440, 6039 and -6039 minutes: finite numbers, none of them a zone, and none of them - expressible in the shared `+HH:MM` / `-HH:MM` slot, whose `HH` is a two-digit hour of the - day. Refusing them together is what keeps `toObject` from reporting a hundred-hour zone that - `toISO` renders as `+100:39`, a string no ISO-8601 reader accepts. `@cosyte/hl7` bounds at - the same 23:59; `@cosyte/dicom` bounds tighter, at 14:59. - -None of the three ever throws, for any input: an absent value, a `TS` with no `@value`, a -`TS` whose `@nullFlavor` contradicts a populated `@value` (the grounds on which `parseTs` -already withholds `date`), and a `@value` this package parses as malformed all answer -`undefined`. - -**`TS.date` is unchanged and stays unchanged.** It remains eager: it zero-fills a truncated -value, resolves an offset-less one as if it had said UTC, and resolves an unstatable stated -offset too, so there are values where `ts.date` is a populated `Date` and `toDate(ts)` is -`undefined`, on the same object. `parseTs`, `parseV3DateTime` and the warning codes are -untouched, and an out-of-range offset raises no new warning. That -divergence is deliberate rather than an oversight, it is documented in `README.md` and in the -docs bundle, and the old answer stays reachable by asking for it with -`{ assumeOffsetMinutes: 0 }`. Nothing reading `TS.date` today reads anything different. - -Parts are derived by re-parsing `TS.raw`, the document's own bytes; `TS.date` is never read. -No dependency of any kind was added, runtime or development, and `engines.node` is unchanged -at `>=22.0.0`. diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ec4731..e159423 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,726 @@ # Changelog +## 0.1.0 + +### Minor Changes + +- cbed1a0: feat: measure conformance against the normative artifacts, and fix what the measurement found + + A built document's conformance was expected rather than proven. The README said so: + the builder's gap had been "grounded against the raw C-CDA R2.1 IG text rather than a + validator run". It is measured now, and measuring it found 86 error-severity results + across the populated document the builder emits, then eight more once the measured set + covered every section it can emit rather than one populated init shape. + + `pnpm conformance` fetches the normative C-CDA R2.1 Schematron, its vocabulary file + and the CDA R2 XML schema at run time from pinned immutable references, validates + every document the builder emits against the schema and then the Schematron's + error-severity phase, re-runs the Schematron over a public sample corpus parsed and + re-serialized through this library, and writes `documentation/conformance-report.md`. + The run fails if the committed report differs from what it just produced, and the + ordinary suite fails unless the README's conformance statement agrees with the report + on the artifact revision, the document types validated, the count of error-severity + results and the count of round trips whose error sets differed. The current result is + zero and zero. + + **This is why the bump is minor rather than patch.** It adds one exported type + (`BuildCcdaSelfCareActivity`) and one warning code (`MISSING_SELF_CARE_ACTIVITY`), and + it moves what the builder emits and therefore what a consumer reads back from it. The + emitted changes: the patient carries `raceCode` and `ethnicGroupCode` as + `nullFlavor="UNK"`; the device author carries a `representedOrganization`; a CCD + carries `documentationOf/serviceEvent`; a Referral Note carries an + `informationRecipient`; an empty required section declares the entries-required + `templateId` alongside the entries-optional one; both Concern Acts carry the `CONC` + code the guide fixes and the CDA R2 `Act` sequence makes mandatory; a Reaction + Observation carries an `id`; a no-known-allergy entry carries the substance + participant with a `nullFlavor="NA"` code; the Vital Signs Organizer carries the + LOINC code its 2.1-only branch requires rather than the SNOMED one; an Immunization + Activity states `negationInd` on both arms; a Result Organizer emits its + `effectiveTime` as an interval; and an act-variant procedure carries the + `effectiveTime` that variant alone requires at error severity. + + Two reads change with them. A no-known-allergy entry's `allergen` is now present and + explicitly `nullFlavor="NA"` rather than absent, which is what an entry from any other + system has always parsed to here; `noKnownAllergy` and `negated` are unchanged and + there is no code to mistake for an allergen. An administered immunization now reads + back `refused: false` rather than `undefined`, because the document states it. + + Widening the measured set from one populated init shape to every section the builder + emits found eight more, and closing them moved five more things. The Age Observation's + fixed `code` is SNOMED CT `445518008` "Age At Onset" rather than `397659008` "Age"; the + Family History Death Observation's fixed `value` is `419099009` "Dead" rather than + `419620001` "Death"; a Family History Observation's fixed `code` carries the LOINC + `75315-2` translation its template requires; a Mental Status Observation's fixed `code` + carries the LOINC `75275-8` translation its template requires; and a Functional Status + Organizer is written only when it carries the Self-Care Activities (ADL and IADL) + observation the template SHALL contain. + + That last one is the only change a caller has to do anything about. + `BuildCcdaFunctionalStatusOrganizer` takes `selfCareActivities`, each written as a + `…22.4.128` observation and read back as an ordinary functional-status finding. Supply + none and the organizer is not written: its findings go out as standalone Functional + Status Observations, every one of them present and unchanged, and the returned document + carries `MISSING_SELF_CARE_ACTIVITY` saying the grouping, its categorization code and + its `effectiveTime` were dropped and why. Fabricating an all-`nullFlavor` activity would + assert an assessment nobody performed, and writing the organizer anyway would stamp a + template the document does not satisfy. + + The XML schema the harness validates against is the SDTC-extended tree at the same + pinned commit rather than `schema/normative`, because the builder emits the HL7-approved + `sdtc:deceasedInd` extension and the normative schema declares no extension elements. + + Every fix keeps the never-invent-content rule: a `SHALL` slot with no known value is + `nullFlavor`, and no clinical time, dose, route or code is invented to satisfy a + cardinality. + + **What the measurement does not cover**, stated where the capability is rather than + below it: the two document types the builder emits and not the other ten, the + error-severity phase and not the warning phase, value-set membership only where the + Schematron's own vocabulary file checks it, and a round trip that is differential + rather than absolute. It is an assessment against a published artifact, not a + certification, and no accredited body has reviewed it. The harness needs network + egress and fails explicitly without it; there is no offline pass. Nothing it fetches + is committed or ever sits in a tracked path. + + Two development dependencies, neither reaching the runtime set: `xmllint-wasm` for + XML Schema validation and `xpath` for XPath 1.0 evaluation. + +- cbed1a0: **This is 0.1.0, the first release whose public API we treat as settled.** + + What is covered, and what you can build against: + - Reading C-CDA R2.1 documents: all twelve US Realm document types are recognized, the header and + the patient are typed, and fourteen entry families are extracted (problems, medications, + allergies, results, vital signs, immunizations, procedures, encounters, smoking status, plan of + treatment, functional status, mental status, family history and past medical history), each coded + value keeping the code system it was sent with. + - Lenient parsing with stable warning codes and a `{ strict: true }` mode that refuses the first + tolerated deviation; hostile XML (a DTD, entity expansion, an oversized or over-deep document) is + always refused. + - Building a spec-clean CCD, Referral Note or inpatient Discharge Summary with `buildCcda`, editing + a parsed document section by section with `editCcda` (which stamps a CDA R2 revision), and + re-serializing a parsed document, where parse and serialize are a fixed point. + - Required-section validation that reports, for every document type, how much of its obligation it + asserts, and UCUM checks on the quantities in results and vital signs. + + What the version promises. The exported names, options, return shapes and warning codes are the + surface we keep stable. 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. + + What is not covered yet. `buildCcda` emits three of the twelve document types and throws for the + other nine. Editing replaces or adds whole sections only: no entry-level append and no section + removal. Required-section validation under-warns for most document types, so a quiet parse is not a + conformance result. A terminology adapter is consulted at five coded slots only, and this package + ships value set identifiers, never their member codes. UCUM checking is grammatical over a curated + set of atoms. + +- cbed1a0: required sections: every document type now reports how far it was verified + + Six of the twelve required-section (SHALL) tables asserted nothing, and nothing + in the API said whether that meant "no requirement", "not checked yet" or "the + requirement is unassertable". A consumer could not tell a verified all-clear from + an unverified silence, and only the prose warned them not to. + + Five of those six were read off the normative C-CDA R2.1 base implementation + guide's document-level conformance statements. Consultation Note now asserts + History of Present Illness (CONF:1198-28907), Allergies and Intolerances + (-28911) and Problems (-28929); each is scoped to the R2.1 `@extension` stamp its + constraint's rule context carries, so an R1.1-origin document is asserted exactly + as before. Progress Note, Procedure Note, Operative Note and Diagnostic Imaging + Report assert nothing still, and that is now a traced result rather than an + unread table: every SHALL section their templates name is either outside this + parser's recognized catalog or a choice, and each is reported by name with the + reason. Unstructured Document carries no `structuredBody` at all (its component + SHALL be a `nonXMLBody`, CONF:1198-31086), so it has no section obligation. + + The addition is **additive**: nothing is removed, narrowed or renamed. + `requiredSectionKeys` and `missingRequiredSections` keep their signatures and + their shape, and the six document types that already asserted keys (CCD, + Discharge Summary, Referral Note, History and Physical, Care Plan, Transfer + Summary) return exactly the same values as before, including under the unstamped + reading, with every conformance id they already carried left in place. + + New exports: + - `requiredSectionStatus(documentType, options?)`: the asserted `keys` plus a + `verification` of `traced-complete`, `traced-partial`, `untraced` or + `not-applicable`, the `traced` provenance (conformance id and the source's own + section name) behind each traced key, and every SHALL section left + `unasserted` with the reason it is unassertable. + - `requiredSectionStatuses(options?)`: all twelve, so the whole picture is + enumerable by a consumer. + - `DOCUMENT_TYPES`: the runtime enumeration behind the `DocumentType` union. + - The types `RequiredSectionStatus`, `RequiredSectionVerification`, + `TracedRequiredSection`, `UnassertedRequiredSection` and + `UnassertedSectionReason`. + + The only behaviour change is the one the trace bought: a Consultation Note that + carries the R2.1 stamp and omits History of Present Illness, Allergies or + Problems now draws `REQUIRED_SECTION_MISSING` (a warning, never a fatal) where it + previously parsed silent. A traced state is a claim about what was read for that + type, so the six types nobody re-read report `untraced` whether or not their keys + already cite a conformance id. + +- cbed1a0: feat: report a coded value that sits outside the value set its slot is bound to + + A consumer could learn that a coded value's code system was unexpected, and, with a terminology + adapter, that its code was not a real member of that system. Neither question is the one a + certifier asks. C-CDA binds each checked slot to a **value set**, and where that binding is + Required the standard states membership as a SHALL, so a document can carry the right system and a + real code and still be non-conformant at a safety-critical slot. Nothing in this package could see + that case. + + `parseCcda(xml, { valueSets })` now takes a bring-your-own `ValueSetSource` beside the existing + `terminology` adapter. It answers one question, `isMember`, and it declares the `release` of the + package it answers from. A code the source reports outside a Required binding's value set raises + `VALUE_SET_BINDING_VIOLATED`; a value set the source holds no expansion for raises + `VALUE_SET_BINDING_NOT_EVALUATED`, never silence, because a skipped value set reads exactly like a + clean one; returning `undefined` is "no opinion" and is silent. An exception your source raises + reaches you rather than being swallowed into a document that merely looks checked. + + **This package ships value set identifiers and never their member codes.** Expansions are licensed + data. `valueSetBinding(slot)` and `valueSetBindings()` expose the declaration: for each of the five + checked slots, the bound value set's OID, whether C-CDA R2.1 binds it as `required` or `preferred`, + and the conformance statement, template and artifact revision the row was read from. Every row was + read off the normative C-CDA R2.1 Schematron this repository already pins by commit and digest, so + no binding is asserted that the normative source was not read for. + + **Four of the five bindings are Required and one is Preferred, and the difference is reported, not + rounded off.** A Preferred binding never produces the Required-binding finding: the Problem + Observation's value SHALL be present and its code only SHOULD come from the Problem value set, and + reporting a departure from that as a SHALL violation would turn a conformant problem list into a + defect. Binding strength is the verb attached to "be selected from", not the cardinality beside it: + a `routeCode` a template only SHOULD contain, whose code SHALL come from the route value set, is a + Required binding. + + Nothing a parse returns changes. No value is refused, rewritten, reordered or dropped on a negative + verdict, the document re-serializes identically, and a parse that supplies no source produces + exactly the findings it always did: membership is never inferred from the code system alone. + + **This is why the bump is minor rather than patch.** It adds two warning codes and eleven exported + symbols (`ValueSetSource`, `ValueSetMembershipQuery`, `ValueSetMembershipAnswer`, `ValueSetBinding`, + `ValueSetBindingStrength`, `ValueSetBindingSource`, `BoundValueSet`, `valueSetBinding`, + `valueSetBindings`, `valueSetBindingViolated`, `valueSetBindingNotEvaluated`), and renames, removes + and repurposes nothing. `CcdaWarning` gains two optional fields, `valueSet` and `valueSetRelease`, + carried only by the two new codes: a message still comes whole from the frozen registry, so the + value set OID and the release ride as data rather than as interpolated text, and the coded value + that caused the finding appears nowhere in it. + +- cbed1a0: required sections: a SHALL warning fires exactly where the normative source says it should + + The per-document-type required-section (SHALL) tables now cover all twelve + recognized types. Six of them (CCD, Discharge Summary, Referral Note, History and + Physical, Care Plan, Transfer Summary) had asserted sets that predated anyone + reading the normative source for them, and reported `verification: "untraced"` to + say so. Their document-level `errors` and `warnings` rules have been read off the + HL7 C-CDA R2.1 normative Schematron and the tables corrected. **No recognized + document type reports `untraced` any more.** + + Two defects went in opposite directions and both are closed. + - **A conformant Discharge Summary drew a false warning.** The table asserted a + Discharge Medications section as a SHALL. The normative source states it as a + **SHOULD**, in that document's warnings rule (CONF:1198-30525), so a conformant + Discharge Summary that omits the section was drawing a + `REQUIRED_SECTION_MISSING` it had not earned. It is withdrawn, in the stamped + and the unstamped reading alike. + - **A non-conformant document passed quietly.** The same Discharge Summary table + omitted Plan of Treatment (CONF:1198-30528), which the errors rule requires + unconditionally; a History and Physical asserted one of the ten SHALL sections + its errors rule names; a Transfer Summary asserted three of six. Every one of + those sections that this parser's catalog recognizes is asserted now. + + **What changed on the published surface**, for `requiredSectionKeys`, + `missingRequiredSections`, `requiredSectionStatus` and the parser warnings that + follow from them: + - **Discharge Summary**: `dischargeMedications` withdrawn, `planOfTreatment` + added. + - **History and Physical**: `familyHistory`, `pastMedicalHistory`, `medications`, + `results`, `socialHistory` and `vitalSigns` added beside `allergies`. + - **Transfer Summary**: `results`, `vitalSigns` and `reasonForReferral` added. + - **CCD, Referral Note and Care Plan**: confirmed unchanged. + + Expect a document of the three changed types to draw warnings it did not draw + before, and a conformant Discharge Summary with no Discharge Medications section + to fall silent. `REQUIRED_SECTION_MISSING` remains a Tier-2 warning, never a + fatal, and remains safety-critical, so no vendor profile can quiet it. + + **Every key an added rule contributed is scoped to the R2.1 stamp**, because each + of those document-level rules matches only a `ClinicalDocument` whose + `templateId` carries `@extension="2015-08-01"`. An unstamped, R1.1-origin document + is therefore asserted exactly as it was before, with one exception in the safe + direction: a key withdrawn because the source states it as a SHOULD or as a choice + is withdrawn from the unstamped reading too, since "no sentence made this + unconditional" does not depend on a version stamp. + + **Provenance is on the surface, not in a commit message.** Every asserted key now + carries the conformance statement it was read from and the source's own name for + the section; every SHALL section the source names that this package cannot assert + is enumerated with its reason (`outside-section-catalog` for Hospital Course, + General Status, Physical Exam and Review of Systems; `not-unconditionally-required` + for each choice); and `requiredSectionStatus(type).source` names the normative + artifact and **that artifact's own revision date**, so a reader holding a later + revision can tell a table is stale without re-deriving it. The new + `RequiredSectionSource` type is exported alongside it. + + This is document-level validation only: which sections a document type SHALL + contain, never what a section or an entry SHALL contain. A quiet parse is still + not a conformance result. + +- cbed1a0: version stamps: a document says which C-CDA release it was written for + + C-CDA 3.0.0 gave every document template a new `@extension`, `2024-05-01`, and + 4.0.0 and 5.0.0 kept it, so a document written for a release later than R2.1 is + detectable from the `templateId` that resolves its type. Through `0.0.15` this + parser got that case wrong twice, once loudly and once in silence. + + Loudly: any `@extension` that was not `2015-08-01` drew + `TEMPLATE_EXTENSION_ABSENT`, whose frozen message reads "carries no @extension + version stamp ... (may pre-date R2.1)". For a `2024-05-01` document both halves + are false, because it carries a stamp and it post-dates R2.1. + + Silently, and worse: the same path reported the document as unstamped, and the + required-section tables read that as the R1.1-origin **reduction**, which drops + every R2.1-stamp-scoped SHALL key. A post-R2.1 CCD carrying neither Social + History nor Vital Signs therefore drew no `REQUIRED_SECTION_MISSING` at all. The + library had classified a document from the future as one from the past. + + **Two warning codes are added and none is renamed or removed.** + `TEMPLATE_EXTENSION_UNMODELED_RELEASE` fires when the resolving `templateId` + carries an `@extension` that is present and is not the R2.1 stamp; + `REQUIRED_SECTIONS_NOT_EVALUATED` fires once for such a document to say its SHALL + obligation was not computed. `TEMPLATE_EXTENSION_ABSENT` keeps its narrower + meaning ("no `@extension` at all", the R1.1-origin shape) and its registry message + byte for byte. + + **An unmodelled stamp is reported, never reduced.** Such a document draws no + `REQUIRED_SECTION_MISSING` in either reading, and + `requiredSectionStatus(type, { stamp: "unmodeled-release" })` reports + `evaluation: "not-evaluated"` so an empty key set still says which emptiness it + is. `evaluation` is a new field on `RequiredSectionStatus` and a separate axis + from `verification`, which remains a claim about the document type that no option + moves. + + **The release this package validates against is an exported value now.** + `CCDA_CONFORMANCE_RELEASE` is `"R2.1"`, beside the closed table of stamps this + package can name (`CCDA_RELEASE_STAMPS`, plus `R30_EXTENSION`, + `releaseForTemplateExtension` and the three-state `readTemplateStamp`). **This + does not retarget the library**: regulation still adopts R2.1 and SVAP use is + voluntary, so a later release is recognized and named, never read against. + 4.0.0 also relaxed the US Realm Header and 5.0.0 added a Pregnancy Section, and + neither is modelled here. + + Four things are decisions rather than details: + - **A reported stamp comes from a closed set of literals this package owns.** A + message names a member of `CCDA_RELEASE_STAMPS` or names no stamp at all, so a + sender-controlled `@extension` can never reach a `CcdaWarning.message` (or, + under `{ strict: true }`, a thrown error's stack). No factory gained a + parameter that a message interpolates. + - **The existential R2.1 rule is unchanged and beats a later stamp.** A document + carrying the resolving root twice, once stamped `2015-08-01` and once for a + later release, is inside the Schematron rule's context and is evaluated under + the full R2.1 obligation, in either sibling order. + - **`{ r21Stamped }` behaves exactly as it did.** + `requiredSectionKeys("ccd", { r21Stamped: false })` returns the same four keys + in the same order. A boolean cannot express three states, so the three-state + `{ stamp }` was added beside it rather than repurposing a published option. + - **`legacyR11` does not tolerate the new code.** It is receive-tolerance for + documents from the past, grounded in the receive-both-R2.1-and-R1.1 + requirement; quieting a future-release stamp there would restore the silence + this change removes. Neither new code is safety-critical, so a consumer who has + read the later guide can still write their own profile for it. + + Nothing became stricter. No fatal was added, no content the C-CDA open-template + rule permits is refused, and a post-R2.1 document's clinical reading (patient, + sections, entries, values) and its byte-faithful re-serialization are identical + to the same document stamped `2015-08-01`. Under `{ strict: true }` the new + warnings escalate like every other Tier-2 warning, which is pre-existing + caller-opted behaviour. + +- cbed1a0: subject override: an entry is never read as the patient's unless it is + + CDA R2 gives `Section.subject` cardinality `0..1` and defines it as the "primary + target of the entries recorded in a section"; C-CDA admits the same override on a + clinical statement. So a conformant document can carry a relative's, a donor's or + a contact's clinical statement inside the patient's document. This parser read + `` in exactly one place (a Family History Organizer's `relatedSubject`) + and every other extractor handed such an entry back as the patient's, silently. + That is another person's clinical data attributed to this patient, in a read path + documented as the patient's own. + + A top-level `` that a subject declaration governs is now **withheld** from + every record-target read path and flagged with a new safety-critical Tier-2 + warning, `SUBJECT_CONTEXT_OVERRIDE`, which no vendor profile may tolerate. An + entry is governed when it carries a declaration itself, when a clinical statement + nested inside it carries one, or when an enclosing section carries one (the + nearest enclosing declaration wins, which is the standard's own conduction rule). + + Four things are decisions rather than details: + - **Presence is the trigger.** A declaration is an override whatever it names. + Nothing is compared with the record target and nothing is resolved to the + patient, because that would make a clinical answer depend on vendor identifier + hygiene. A document that redundantly restates the patient as an entry subject + therefore loses those entries from the record-target read paths and gains a + warning for each: the safe direction of the error, and the accepted cost. + - **The whole top-level entry is withheld**, its own statement and every statement + nested inside it, and the same unit does the counting. A declaration on the + second Problem Observation of a Problem Concern Act withholds that whole concern + act and emits one warning at the act's locus, never a concern act handed back + one observation short. + - **Family History is untouched.** A Family History Organizer's own subject slot + is that template's mechanism for naming the relative, whatever it contains, so + it is never an override, draws no warning, and re-overrides an enclosing section + declaration. `getFamilyHistory()`, the `familyHistory` field, + `extractFamilyHistory` and the aggregate's family-history slot return exactly + what they returned before, in every document shape. That carve-out is read-side + and reaches only the organizer the family-history path itself reads: an entry + that carries the Family History Organizer template beside one a record-target + read path returns (a Result Organizer, a Problem Concern Act) is withheld and + reported like any other, because a `templateId` is one element and C-CDA entries + carry several. A declaration nested deeper inside an entry is never that slot. + - **Withholding is read-side only.** The withheld entry is not dropped: the + byte-faithful round trip through `toString()` / `serializeCcda` reproduces it + unchanged, and section narrative is returned unredacted, unreordered and + unannotated. + + The count is per section and sums over the document: N governed top-level entries + in a section produce exactly N instances, in document order, each naming that + entry's own bounded locus (an element path from the CDA vocabulary, the section's + LOINC code, line and column, with `` for anything that fails its bound), + and a section that declares an override but governs no entry anywhere beneath it + produces exactly one instance at its own locus instead. The same withholding + applies to a per-family or aggregate extraction invoked directly on a section, + including a nested subsection governed by an ancestor's declaration, with the + warnings delivered on the parse context those functions already accept: no + signature, parameter or return type changes. `extractFamilyHistory` delivers them + too, while returning its own contents whole. + + Two behaviour changes to expect. A document carrying an override loses those + entries from `getProblems()` / `getMedications()` / `getAllergies()` and the other + ten record-target families where it previously returned them as the patient's, + and under `{ strict: true }` it now throws, because the new code escalates like + every other safety-critical Tier-2 warning. Nothing is modelled for third-party + subjects: a withheld entry is reachable only through the re-serialized document. + +- cbed1a0: A parsed document now says who authored it: the parser reads the header participations. + + A consumer could ask whose document it was and what was in it, but not who wrote it. + `CcdaHeader` carried ten fields and not one of them was a participation. Every + conforming US Realm document carries at least one `author` and exactly one `custodian`, + and an inpatient Discharge Summary carries the encounter it summarises, so the parser + was dropping data the source document was required to supply. Provenance is the answer + to "who said this", which is what a clinician asks of a medication list assembled by + three systems. + + `header.authorship` carries every document-level `` in document order: its + identifiers, its assigned person's name or its assigned authoring device's identity, + the organization the document states it acted for, and its author `time` at exactly the + precision the document stated. A partial author time stays partial. `header.custodian` + carries the custodian organization with its identifiers and its name. + `header.encompassingEncounter` carries the `componentOf/encompassingEncounter` frame: + its `effectiveTime` bounds, each keeping its own precision and any `nullFlavor`, and its + `dischargeDispositionCode`. + + **An inherited author reading is marked, never asserted.** CDA conducts an author down + from the document to a section and on to that section's entries, and this reports the + conduction rather than performing it silently. `CcdaSection.authorship` and each entry's + reading in `CcdaSection.entryAuthorship` carry an `inherited` flag: `false` when the + level stated these authors itself, `true` when it stated none and these are the nearest + enclosing level's. "The nearest enclosing author is this clinician" and "this entry was + authored by this clinician" are different claims and only the first is one the document + made. When no level carries an author the reading is absent at every level: the record + target, the custodian, a legal authenticator and an informant are never read as the + author, and no `componentOf` means no encounter frame rather than one derived from the + document `effectiveTime` or a `documentationOf` service event. + + One warning code is added and none is renamed, removed or repurposed. An `` + whose `assignedAuthor` carries neither an `assignedPerson` nor an + `assignedAuthoringDevice`, which the US Realm Header requires one of (CONF:1198-8456), + is kept, marked `unidentified`, and reported with `UNIDENTIFIED_AUTHOR`. It still + conducts to nested levels. Dropping it would turn "the document names an author whose + identity it never states" into "the document names no author", which is a more + reassuring claim than the document supports. The factory takes only a position and the + message comes whole from the frozen registry, so a clinician's name cannot reach a + diagnostic. + + Entries an overriding `` declaration governs are absent from `entryAuthorship` + entirely, not even their `ids`: it is a record-target read path, and a governed entry + appearing in one would put another person's entry back on the model through a second + door. + + **This is why the bump is minor rather than patch.** It adds seven exported types + (`CcdaAuthor`, `CcdaAuthoringDevice`, `CcdaAuthorship`, `CcdaCustodian`, + `CcdaEncompassingEncounter`, `CcdaEntryAuthorship`, `CcdaOrganization`) and one warning + code. `buildSection` takes an optional third argument, the enclosing author reading; + every existing call keeps working and reads the section as having no enclosing author, + which is what a caller framing a detached `
` is looking at. + + **Nothing here is breaking, and the new `CcdaSection` field is optional so that stays + true.** `CcdaSection` is an input surface as well as an output one, reachable through + `CcdaDocumentInit.sections`, which the public `CcdaDocument` constructor takes: a + required `entryAuthorship` would stop a consumer's existing section literal from + compiling, which is not a minor change. The parser populates the field on every section + it frames, empty where the section has no entry act to read. + + Framing a section reads each entry act's ``s without reporting on them, because the + entry-extraction walk already parses those same elements. No document's warning output + moves: not the count, not the order, and not whether `strict: true` throws. + + This is the READ side only. Nothing the builder emits changes, and emitting + `componentOf` is not part of it. + +- cbed1a0: Build an inpatient Discharge Summary, the third of the twelve document types + + `buildCcda` emits a Discharge Summary for `documentType: "dischargeSummary"`, + alongside the CCD and the Referral Note. `minor` rather than `patch` because the + package entry point gains two exported types: `BuildableDocumentType` (the union + of the three types the builder emits) and `BuildCcdaEncompassingEncounter`, and + the published `UnassertedSectionReason` union gains a third member, + `"assertion-would-tighten-parse"`. Nothing published was removed or renamed, and a + CCD or a Referral Note built from an init that names no Discharge Summary input + does not change by a byte. + + The document specializes the US Realm header with its own document `templateId` + root (`2.16.840.1.113883.10.20.22.1.8`, stamped `2015-08-01`), its LOINC document + code (`18842-5`), and the four SHALL sections its errors rule names: Allergies, + the narrative-only Hospital Course (`1.3.6.1.4.1.19376.1.5.3.1.3.5`, LOINC + `8648-8`), Discharge Diagnosis (V3) and Plan of Treatment (V2). Neither Problems + nor Medications is in that set, so unlike a CCD or a Referral Note a Discharge + Summary emits them only when you supply content, the medications in the + Medications Section; Discharge Medications is a SHOULD in the document's warnings + rule and is not emitted at all. Three new inputs carry the content: + `hospitalCourse`, `dischargeDiagnoses` and `encompassingEncounter`. + + **What you supply is emitted or refused, never dropped.** Those three inputs throw + a `TypeError` on a CCD or a Referral Note instead of being ignored, and + `assessment` and `reasonForReferral`, the Referral Note's narratives, throw on a + Discharge Summary. A CCD still ignores `assessment` and `reasonForReferral`, as it + always has. + + It is the only type carrying `componentOf/encompassingEncounter`, the frame + naming the stay. Every slot in it is required by the template and none is ever + invented: an encounter period bound or a discharge disposition you did not supply + is emitted as an explicit `nullFlavor="UNK"`, which satisfies the cardinality, + says the fact is unknown rather than stating one, and reparses as absent. None of + the three is ever derived from the document `effectiveTime` or from a + `documentationOf` service event. A supplied bound keeps exactly the precision you + gave it. The disposition's `codeSystem` defaults to the NUBC UB-04 Patient + Discharge Status code set, the system every member of the value set the template + names carries, and your own `codeSystem` is emitted verbatim instead when you + supply one. + + **All three types reparse with zero warnings.** The Hospital Course Section is an + IHE PCC template outside the section catalog; `parseCcda` now recognizes it by its + root (`findSection("hospitalCourse")` resolves), in every document and not only a + built one, so a section carrying that root and matching nothing in the catalog no + longer raises `UNKNOWN_SECTION_CODE`. That is the only parse output that moves: + the root is consulted after every catalog root and the LOINC fallback, so no + section changes key; the LOINC code `8648-8` alone still raises + `UNKNOWN_SECTION_CODE`; no entry in such a section is newly flagged as misplaced; + and the section is not asserted as required, so no Discharge Summary that omits it + gains a `REQUIRED_SECTION_MISSING`. `requiredSectionStatus("dischargeSummary")` + names it as unasserted with the new reason `"assertion-would-tighten-parse"`. + + Asking for one of the other nine types still throws a `TypeError` naming what you + asked for. That refusal is now derived from the builder's own specialization + table rather than from a list of strings, so a thirteenth recognized type is + refused the day it is recognized, and the membership test is `Object.hasOwn` + rather than `in`, so an untyped caller passing a prototype key is refused instead + of crashing partway through a build. Only an omitted (`undefined`) `documentType` + defaults to a CCD: `null`, which used to build a CCD, is refused, and so is any + value that is not a string, before any lookup and without converting it. + + Measured, not asserted: `pnpm conformance` validates fifteen built documents, + two of them Discharge Summaries at both ends of the input range, against the CDA + R2 XML schema and the error-severity phase of the normative C-CDA R2.1 + Schematron, with zero error-severity results. The README conformance statement + and the tracked report move with it, from two of twelve to three. + +- cbed1a0: Add the shared `toObject` / `toISO` / `toDate` conversion surface for a parsed `TS` + + Every `@cosyte/*` standard parser now exports the same three conversions, under the same + names, with the same return shapes and the same timezone rule, so moving between two of + them costs nothing to relearn. Five names are added to the package entry point: + `toObject`, `toISO`, `toDate`, and the `DateParts` and `ToDateOptions` types. + - `toObject(ts)` returns a frozen `DateParts` carrying **only** the components the document + stated. Nothing is zero-filled, so `Object.keys()` on a year-precision value is `["year"]` + and the value's precision survives the conversion. `month` is the spec-native 1 to 12, the + component names are singular, and there is no `raw`, `precision` or `valid` key. + `millisecond` is the first three digits of the stated fraction taken verbatim and + right-padded (`.5` is 500, `.0500` is 50), never a floating-point fraction multiplied by 1000. `offsetMinutes` is present if and only if the value carried an explicit offset, a + stated zero included. Deleting `offsetMinutes` leaves an object + `Temporal.PlainDateTime.from` and luxon's `DateTime.fromObject` accept unchanged; neither + library is a dependency and neither is imported. + - `toISO(ts)` renders ISO-8601 truncated to that same precision and pads nothing out, with + fractional digits rendered exactly as written. A stated offset is appended (`Z` for a + stated zero, otherwise `+HH:MM` / `-HH:MM`); with no stated offset nothing is appended and + no `Z` is fabricated. Because a stated zero renders `Z`, this is not a byte round-trip of + the wire value, and `serializeCcda` remains the round-tripping route. + - `toDate(ts, options?)` returns a `Date` only when the zone is determinate: the value + carried an offset, or the caller passed `assumeOffsetMinutes` (signed minutes east of UTC, + where an explicit `0` means "read this naive value as UTC"). With neither, the answer is + `undefined`. The host machine's timezone is never read and UTC is never assumed. A value's + own offset always beats an assumed one, a year below 100 stays that year rather than being + remapped into the 1900s, and components below the stated precision fill to their lowest + legal value for the instant only. An `assumeOffsetMinutes` that names no usable zone is + refused the same way: `NaN` and the two infinities are not a number of minutes, and a finite + offset large enough to push the result outside the range a JS `Date` holds denotes no + instant. Both answer `undefined`. What never comes back is an `Invalid Date`, which + satisfies the declared return type and defeats its point. + - An offset the **document** states is bounded as well, at 23 hours 59 minutes, and past that + bound the value is refused whole by all three functions. The v3 `±ZZZZ` token is four digits + wide and constrains neither field, so `+2400`, `+9999` and `-9999` are legal literals stating + 1440, 6039 and -6039 minutes: finite numbers, none of them a zone, and none of them + expressible in the shared `+HH:MM` / `-HH:MM` slot, whose `HH` is a two-digit hour of the + day. Refusing them together is what keeps `toObject` from reporting a hundred-hour zone that + `toISO` renders as `+100:39`, a string no ISO-8601 reader accepts. `@cosyte/hl7` bounds at + the same 23:59; `@cosyte/dicom` bounds tighter, at 14:59. + + None of the three ever throws, for any input: an absent value, a `TS` with no `@value`, a + `TS` whose `@nullFlavor` contradicts a populated `@value` (the grounds on which `parseTs` + already withholds `date`), and a `@value` this package parses as malformed all answer + `undefined`. + + **`TS.date` is unchanged and stays unchanged.** It remains eager: it zero-fills a truncated + value, resolves an offset-less one as if it had said UTC, and resolves an unstatable stated + offset too, so there are values where `ts.date` is a populated `Date` and `toDate(ts)` is + `undefined`, on the same object. `parseTs`, `parseV3DateTime` and the warning codes are + untouched, and an out-of-range offset raises no new warning. That + divergence is deliberate rather than an oversight, it is documented in `README.md` and in the + docs bundle, and the old answer stays reachable by asking for it with + `{ assumeOffsetMinutes: 0 }`. Nothing reading `TS.date` today reads anything different. + + Parts are derived by re-parsing `TS.raw`, the document's own bytes; `TS.date` is never read. + No dependency of any kind was added, runtime or development, and `engines.node` is unchanged + at `>=22.0.0`. + +### Patch Changes + +- cbed1a0: docs: cover the profile, terminology and conformance surfaces, and gate the bundle + + The published narrative documentation gained a page and a guard. Three surfaces + that a caller has to configure correctly were described only in passing inside + the limitations page, with nothing a reader could copy and run: the vendor + profile system, the bring-your-own `TerminologyAdapter`, and the required-section + conformance status. Each now has its own section on a new "Conformance, profiles + & terminology" page with an executable example, and every one of those examples + is compiled and run against the built package by the existing doc/code agreement + suite, so a documented call that stops working fails the build rather than + misleading a reader. The terminology example uses an in-process stub adapter: it + opens no socket and needs no licensed terminology service, which is also what + makes it a usable template for testing an adapter of your own. + + Three pages claimed the package was "published on npm at `0.0.3`" while it was + twelve patches past that. No page names a published version now; they point at + `npm view @cosyte/ccda version`, which cannot go stale. Every historical note + that dates a behaviour change to a past version is untouched and is now held in + place by a retention floor, because those sentences are the change record for a + reader pinned to an older version, not staleness. + + Page frontmatter was inconsistent (`sidebar_label` on five pages of nine) and + `sidebar_position` collided across five pages. Every page now carries the same + four keys, and each position agrees with the page's place in `sidebars.json` + rather than being a second, disagreeing answer to what comes next. + + A new guard holds all of it: every symbol the public entry point exports, types + included, must be named by a page or carry a stated reason in a committed + exemption record that cannot outlive the symbol it excuses. The formatter now + covers the bundle as well, so the pages cannot drift out of format. + +- cbed1a0: Bring this repo to the shared package baseline: install hardening, the required + js-yaml override, and a CLAUDE.md under its line ceiling + + Three drifts the meta-repo's `config/drift-manifest.json` measures against this + repo are closed, and nothing else changes. + + `pnpm-workspace.yaml` is new and carries `minimumReleaseAge: 1440` and + `trustPolicy: no-downgrade`, the publication cooldown and trust policy the + standard requires of every `@cosyte/*` package repo. Both keys are switched off + by default in pnpm 10, and both are ignored outright by a pnpm older than + 10.16.0 and 10.21.0 respectively, so `packageManager` is raised from `pnpm@10.0.0` + to `pnpm@10.34.5`: a settings file the pinned package manager ignores decorates + rather than defends. A cold-store `pnpm install --frozen-lockfile` was measured + against the new policy and raised no `ERR_PNPM_TRUST_DOWNGRADE`, so no + `trustPolicyExclude` entry is carried and the exemption lists are absent rather + than empty. + + `pnpm.overrides` gains the required `js-yaml@>=4.0.0 <4.3.0` entry. It is listed + BEFORE this repo's own `js-yaml@>=4.0.0 <4.3.2` entry on purpose: pnpm applies + the LAST matching override, measured on this tree, and the other order resolved + js-yaml 4.x down from 4.3.2 to 4.3.0, which would have been an advisory + remediation quietly undone by a compliance edit. The lockfile still resolves + js-yaml at 4.3.2, and no existing override was removed, narrowed or repointed. + + `CLAUDE.md` goes from 459 lines to 232, under the ceiling of 300 the standard + declares, entirely by RELOCATION. Every block of prose that left it is + reproduced verbatim in `documentation/agent-notes.md`, under the very heading + the imperative's `Why:` pointer already resolved to, and what is left in + `CLAUDE.md` is the rule plus that pointer. Nothing was deleted, no imperative + lost its file, and no claim was reworded, softened or strengthened. + + No published API, warning code, parser behaviour or emitted XML changes. + +- cbed1a0: README: standardize the page, and make its usage example a test + + The npm package page and the GitHub landing page are the same file, and for most + consumers it is the only page they will ever read. It was 1086 lines of deep + reference with no answer above the fold to the three questions a reader arrives + with: what is this for, is it safe to depend on, and what does it do with my + patients' data. It also carried a status line claiming the package was published + at `0.0.3`, twelve patch releases out of date, and a hand-written `alt` string on + the banner that disagreed with the one the brand assets declare for that tile. + + The page now opens with the banner (carrying the declared `alt` string), the + title, a one-line hook, the four house badges and the `package.json` + `description` verbatim, followed by a table of contents. Four sections are new: + - **Why this exists**, which names the problem, the nearest alternative a reader + would otherwise reach for, and why this is not that. + - **Status**, which states the version this package declares, says what that + version does and does not claim about the public API, and names the surfaces + that are still moving instead of implying they are settled. + - **PHI and safety**, which states what the library does with patient data for + each of logging, in-memory retention and writing to disk, and names what the + consuming application still owns. + - **Contributing**, which says where to ask, whether unsolicited pull requests + are accepted, and every check a contribution has to clear. + + `Install` now states the Node engine floor and the module format. The parse + walkthrough is consolidated into a single `Usage` example that builds a complete + synthetic CCD, reads it back and shows what every call returns, and that block is + now executed by the test suite against the built entry point: an example whose + shown output stops matching what the code produces fails the build. Roughly half + of documentation traffic is agents that lift a usage block verbatim, so a wrong + example is wrong generated code at scale. + + No parse, emit, builder or profile behaviour changes, and no documented behaviour + was dropped: every reference section the page carried is still on it. + +- cbed1a0: phi-scan: a target enumerated and never read refuses the scan + + `--allow-fixture` withdrew a path from the target list and the run reported on + whatever remained, so the withdrawal never showed up in the verdict: four argv + shapes printed `OK, no hits` at exit 0 over a corpus holding a live, detectable + violator, including a whole-run sweep whose only violator was the withdrawn file. + CI could print a clean verdict over a corpus it never opened. + + The scanner now refuses (exit 2), in every mode, over any target this run + enumerated and never read, naming the paths. The comparison is a set difference + rather than a count, because a count counts the targets that did get read. A + bypass naming a path the run does not enumerate refuses too, under its own + message, since such a flag subtracts nothing (in `paths` mode a nonexistent path + is refused earlier still, by `File not found`). `--allow-fixture` no longer + selects the mode and is unioned into the target list in `paths` mode, so it means + the same thing in every argv. + + What it costs, stated rather than left to be discovered: `--allow-fixture` can no + longer reach exit 0 in any mode. The flag, `phi-scan-overrides.md` and the log + gate all stay, so an attempt is recorded and then refused rather than silently + honored, and `scripts/phi-allow-list.txt` is now the only mechanism that reaches + a clean run. The hit footer no longer advertises the flag as a remedy. + + No published API, warning code or parser behaviour changes; this is the commit + gate only. There was no real unread corpus in this repo: the all-mode sweep + enumerates and reads the same set, and the four states were reproduced with + planted fixtures rather than found. + ## 0.0.15 ### Patch Changes diff --git a/README.md b/README.md index 038f877..bc0a43e 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ silence. ## Status -`package.json` declares **`0.0.15`**. From `0.1.0` the public API is settled and safe to depend on: +`package.json` declares **`0.1.0`**. From `0.1.0` the public API is settled and safe to depend on: the behaviour documented on this page is implemented and covered by tests, the exported names, options, return shapes and warning codes are the surface we keep stable, and below 1.0 a breaking change bumps the minor version and is called out in the changelog with its migration. On a `0.0.x` diff --git a/package.json b/package.json index 6f43ca7..fc31ba5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cosyte/ccda", - "version": "0.0.15", + "version": "0.1.0", "description": "C-CDA parser, serializer, and builder for Node.js and TypeScript: lenient on parse, spec-clean on emit.", "keywords": [ "ccda", diff --git a/src/index.ts b/src/index.ts index a1415af..dd54d87 100644 --- a/src/index.ts +++ b/src/index.ts @@ -27,7 +27,7 @@ * console.log(VERSION); * ``` */ -export const VERSION: string = "0.0.15"; +export const VERSION: string = "0.1.0"; // Parser entry point + options. export { parseCcda } from "./parser/index.js";