Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions .changeset/brave-moons-measure.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
---
"@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.
34 changes: 34 additions & 0 deletions .changeset/eighty-jars-argue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
"@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.
33 changes: 33 additions & 0 deletions .changeset/first-minor-release.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
"@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.
50 changes: 50 additions & 0 deletions .changeset/lucky-pandas-refuse.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
"@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.
48 changes: 48 additions & 0 deletions .changeset/olive-bats-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
"@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.
36 changes: 36 additions & 0 deletions .changeset/olive-otters-gather.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
"@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.
Loading
Loading