The clinical entry layer
Beyond identity + narrative, parseCcda extracts the structured clinical entries into typed accessors.
Each family is reached in one call; every safety-critical distinction is kept apart, never conflated.
| Accessor | What it returns |
|---|---|
getProblems() | Problem Concern Acts → coded conditions (SNOMED CT / ICD-10-CM) + active/resolved status. |
getMedications() | Medication Activities → RxNorm drug, dose, route, therapy window vs periodic frequency. |
getAllergies() | Allergy Concern Acts → allergen, reactions (manifestation + severity), criticality, the "No Known Allergies" flag. |
getResults() | Result Organizers → LOINC analyte, polymorphic UCUM-checked value, reference range, interpretation. |
getVitals() | Vital Signs Organizers → the same UCUM-checked value machinery. |
getImmunizations() | Immunization Activities → CVX vaccine, dose, route, date, the refused flag. |
getProcedures() | Performed or planned procedures, split by moodCode. |
getEncounters() | Encounter Activities → visit type, status, period. |
getSmokingStatus() | Smoking Status observations → SNOMED value + an explicit unknown flag. |
getPlannedItems() | Plan of Treatment entries, seven templates (act, encounter, procedure, medication, supply, observation, immunization): all future/ordered, never performed. Four of the section's eleven admissible entry templates are not planned items and are not returned; three of those four (Instruction, Handoff Communication Participants, Nutrition Recommendation) are reported as PLAN_ENTRY_NOT_MODELED rather than excluded in silence, and Goal Observation deliberately is not. An entry is read as a section entry's own act or nested inside a Planned Intervention Act (…22.4.146), the one container that holds all seven inline; a pointer (Entry Reference) is stepped over rather than followed. Nesting is not solved in general: a planned act inside a Nutrition Recommendation (…22.4.130) or an Intervention Act (…22.4.131) is still not reached. |
getFunctionalStatus() / getMentalStatus() | Functional / Mental Status findings + direct-entry Assessment Scale Observations (assessmentScale, with an INT score + supporting items), domain-tagged so the two never merge. |
getFamilyHistory() | One entry per relative: structured identity + their conditions. |
getPastMedicalHistory() | Bare historical Problem Observations (never double-counted as active). |
The safety-critical distinctions
These are the reconciliations where a silent guess could harm someone, so the parser refuses to guess:
- Performed vs planned (Procedures, Plan of Treatment): the
moodCodedrives adispositionof"performed"(EVN) vs"planned"(INT/RQO/…). A missing mood isPLANNED_VS_PERFORMED_AMBIGUOUSand an unrecognized one isPROCEDURE_MOOD_UNEXPECTED; both leavedispositionundefined rather than guess. A planned colonoscopy is never read as a performed one. - Severity vs criticality (Allergies): a reaction's
severity(how bad this event was) and the propensity'scriticality(how dangerous future exposure is) are different axes, kept on different fields, never merged. - Negated vs unknown: "No Known Allergies" (
noKnownAllergy, fromnegationInd) and a refused immunization (refused) are distinct from anullFlavor"unknown". A refusal is never read as an administration. - Code vs narrative: when a coded value disagrees with the narrative text it references, the
parser surfaces both (
CODE_NARRATIVE_MISMATCH) and picks no winner. - Missing safety fields: a missing
doseQuantity/routeCodeis preserved-as-absent and flagged, never defaulted.
The performed-vs-planned split, exercised:
import { parseCcda } from "@cosyte/ccda";
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<ClinicalDocument xmlns="urn:hl7-org:v3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<realmCode code="US"/>
<templateId root="2.16.840.1.113883.10.20.22.1.1" extension="2015-08-01"/>
<templateId root="2.16.840.1.113883.10.20.22.1.9" extension="2015-08-01"/>
<id root="2.16.840.1.113883.19.5.99999.1" extension="DOC-0006"/>
<code code="34133-9" codeSystem="2.16.840.1.113883.6.1"/>
<title>Synthetic Procedure Note</title>
<effectiveTime value="20240101"/>
<recordTarget><patientRole>
<id root="2.16.840.1.113883.19.5" extension="MRN-00042" assigningAuthorityName="Sample Hospital"/>
<patient>
<name><given>Jane</given><family>Doe</family></name>
<administrativeGenderCode code="F" codeSystem="2.16.840.1.113883.5.1"/>
</patient>
</patientRole></recordTarget>
<component><structuredBody>
<component><section>
<templateId root="2.16.840.1.113883.10.20.22.2.7.1" extension="2014-06-09"/>
<code code="47519-4" codeSystem="2.16.840.1.113883.6.1"/>
<title>Procedures</title>
<text><content ID="p1">Appendectomy</content></text>
<entry><procedure classCode="PROC" moodCode="EVN">
<templateId root="2.16.840.1.113883.10.20.22.4.14" extension="2014-06-09"/>
<code code="80146002" codeSystem="2.16.840.1.113883.6.96" displayName="Appendectomy"/>
<statusCode code="completed"/>
<effectiveTime value="20230615"/>
<text><reference value="#p1"/></text>
</procedure></entry>
<entry><procedure classCode="PROC" moodCode="INT">
<templateId root="2.16.840.1.113883.10.20.22.4.14" extension="2014-06-09"/>
<code code="73761001" codeSystem="2.16.840.1.113883.6.96" displayName="Colonoscopy"/>
<statusCode code="active"/>
</procedure></entry>
</section></component>
</structuredBody></component>
</ClinicalDocument>`;
const procs = doc(xml);
// The performed appendectomy and the planned colonoscopy are kept strictly apart.
procs[0]?.disposition; // => "performed"
procs[0]?.code?.code; // => "80146002"
procs[1]?.disposition; // => "planned"
procs[1]?.code?.code; // => "73761001"
function doc(x: string) {
return parseCcda(x).getProcedures();
}
Required-section (SHALL) validation
For a recognized document type, an absent required (SHALL) catalog section surfaces a
REQUIRED_SECTION_MISSING warning, never a fatal, so a missing section never blocks reading the
data that is present. The table is conservative: it asserts only unconditional, in-catalog,
high-confidence SHALL constraints and omits choice constraints (SHALL contain A OR B), SHOULD/MAY
sections, and SHALL sections outside the recognized catalog. requiredSectionKeys(documentType) and
missingRequiredSections(documentType, presentKeys) expose the table directly.
The CCD row is the one traced end to end. It asserts six sections, read off the normative
C-CDA R2.1 Schematron's CCD (V3) errors rule: Allergies (CONF:1198-30662), Medications (-30664),
Problems (-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. buildCcda emits exactly this set for a CCD, so the two halves cannot drift.
Those CONF ids are scoped to the R2.1 stamp: their Schematron rule matches only a document whose
CCD templateId carries @extension="2015-08-01". Social History and Vital Signs are therefore
asserted only against an R2.1-stamped document; an R1.1-origin CCD keeps the older four-key reading,
because there is no R1.1 Schematron in hand and narrowing would be as unsourced as broadening. Pass
{ r21Stamped: false } to requiredSectionKeys / missingRequiredSections for that reading.