Skip to content

Version Packages - #109

Merged
NSchatz merged 1 commit into
mainfrom
changeset-release/main
Aug 10, 2026
Merged

Version Packages#109
NSchatz merged 1 commit into
mainfrom
changeset-release/main

Conversation

@NSchatz

@NSchatz NSchatz commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and publish to npm yourself or setup this action to publish automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.

Releases

@cosyte/ccda@0.0.14

Patch Changes

  • bdd551e: Settle the CCD required-section (SHALL) set against the normative C-CDA R2.1 Schematron: it is six sections, and the parser and builder now agree on it.

    buildCcda named five SHALL sections for a CCD (including Vital Signs) while requiredSectionKeys("ccd") named four (excluding it). The live consequence was an asymmetry: a built CCD always carried a Vital Signs section, but parseCcda stayed silent when a third-party CCD lacked one. Both halves now name the same six, read off the CCD (V3) errors rule of the normative Schematron published by HL7: Allergies and Intolerances (CONF:1198-30662), Medications (-30664), Problem (-30666), Results (-30670), Social History (-30688) and Vital Signs (-30690). Procedures (-30668) and Plan of Treatment (-30686) sit in that template's warnings rule as SHOULD, so neither is asserted.

    Those six CONF ids are scoped to the R2.1 stamp: the rule they live in matches only a ClinicalDocument whose CCD templateId carries @extension="2015-08-01". So socialHistory and vitalSigns are asserted only against an R2.1-stamped document. An R1.1-origin CCD (the same root with no extension, the condition that raises TEMPLATE_EXTENSION_ABSENT) keeps exactly its previous reading of Allergies, Medications, Problems and Results. That is not a claim that R1.1 omitted the other two; there is no R1.1 Schematron in hand, and narrowing the original four would be as unsourced as broadening them. requiredSectionKeys and missingRequiredSections both take an optional { r21Stamped: false } for the unstamped reading, and the new RequiredSectionOptions type is exported. The stamp is detected the way the Schematron's context predicate is written, existentially: a document is R2.1-scoped when any of its document-level templateIds carries the root together with @extension="2015-08-01", so the common dual-stamped backward-compatible shape (both the extension'd and the bare templateId) is read correctly whatever order the two appear in.

    What changes for you:

    • requiredSectionKeys("ccd") and missingRequiredSections("ccd", ...) now include socialHistory and vitalSigns. An R2.1-stamped CCD that lacks either now raises a REQUIRED_SECTION_MISSING warning it previously did not. This is a warning, never a fatal, so nothing stops parsing. R1.1-origin CCDs are unaffected.
    • buildCcda now always emits a Social History section for a CCD, as a spec-clean empty nullFlavor="NI" shell when no smokingStatus is supplied. That shell is conformant and invents no clinical fact: the Social History Section (V3) requires only code, title and text, and its Smoking Status entry is SHOULD, not SHALL. It has no entries-required template variant, so the shell still carries exactly the template the CCD SHALL constraint names.
    • A CCD that does supply a smokingStatus emits the Social History section exactly once, as before.

    Document types whose SHALL set excludes Social History, such as the Referral Note, are unchanged: they still emit it only when populated.

  • a0fba0a: Emit the Medications section under its own 2014-06-09 version stamp, so a CCD built with medications stops failing CONF:1198-30664.

    The six CCD SHALL sections do not share one version stamp, and the builder assumed they did. Each assert in the normative C-CDA R2.1 Schematron's CCD (V3) errors rule names a @root and an @extension, as a pair. Five are 2015-08-01: Allergies (…22.2.6.1), Problem (…22.2.5.1), Results (…22.2.3.1), Social History (…22.2.17) and Vital Signs (…22.2.4.1). Medications (…22.2.1.1) is 2014-06-09. R2.1 revised that section at the earlier stamp and never re-issued it at the later one: the Schematron carries section rule contexts for both Medications roots (…22.2.1 and …22.2.1.1) at 2014-06-09 and zero rule contexts for either root at 2015-08-01.

    medicationsSection called sectionElement and emptySection without an extension argument, so both took the builder's R21 default and stamped …22.2.1.1 with 2015-08-01. That pair is unsatisfiable by any rule in the IG, so every CCD and every Referral Note this builder produced failed its own document type's Medications assert, populated or empty: a CCD fails CONF:1198-30664, a Referral Note CONF:1198-30923. Both call sites now pass a named MED_SECTION_EXT. The two asserts are byte-identical in their test XPath, both requiring …22.2.1.1 at 2014-06-09, and differ only in the rule context that selects them, the CCD's …22.1.2 against the Referral Note's …22.1.14, so the section template's identity is document-type independent and one fix corrects both; editCcda rebuilds sections through the same function and inherits it.

    This changes the emitted bytes on every document carrying a Medications section, so a consumer keyed on the exact old root + extension pair stops matching. It is not a breaking API change: this package's own parser, sectionForTemplateRoot and the editable-section table all match on the root alone, so parse, edit, section lookup and round-trip are unaffected, and no conformant consumer could have been keyed on a stamp the IG does not define. The parse fixtures and both docs samples that carried the same non-existent stamp were corrected with it, including the quickstart sample that labels itself "Clean, spec-conformant input" while carrying a pair no rule in the IG defines; the serializer snapshot moves by exactly that one attribute, which is the round-trip fidelity check doing its job. Note that the executable docs gate could not have caught either, because section recognition matches the root alone.

    Why a root-only test never caught this: the pre-existing assertion checked for 2.16.840.1.113883.10.20.22.2.1.1 with no stamp, and passed throughout. The new tests assert the pair for all six SHALL sections, and additionally assert the 2015-08-01 pair is absent, so a reverted default reds instead of passing vacuously. Five of the eleven tests touching this are red at the previous commit and six are green there as controls, the five non-Medications SHALL rows plus the round-trip re-parse.

    Scope, stated rather than implied. This closes that one CONF for a populated document. It does not upgrade the package's general conformance claim: nothing in CI validates an emit against the Schematron, so every CONF id this code cites remains mechanically unenforced, and "a built document round-trips with zero warnings" still does not mean "a validator would pass it". A CCD with no medications emits the entries-optional root only, because declaring entries-required with zero entries violates that template's own "SHALL contain at least one entry", so such a document still fails CONF:1198-30664 (a Referral Note in the same state fails CONF:1198-30923, never -30664). That is inherent to having no medications rather than a residue of this change, and it applies equally to Allergies, Problems, Results and Vital Signs.

    What landed for enforcement is a transcription of the six asserts with the source artifact's sha256 recorded next to it, not a Schematron run. A real harness needs an XPath engine, correct sch:extends expansion and first-rule-wins semantics, and the .sch is deliberately not vendored, so it stays its own slice. Getting rule-context semantics wrong inside a harness would be worse than having none: the assert behind this defect sits in an abstract="true" rule that carries no context at all, and reading it without following the concrete rule that extends it is the same misreading that produced the bug.

@NSchatz
NSchatz merged commit dbdf84d into main Aug 10, 2026
8 checks passed
@NSchatz
NSchatz deleted the changeset-release/main branch August 10, 2026 14:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant