Skip to main content
Version: v0.0.3

Tolerance tiers & the warning-code model

@cosyte/ccda follows the cosyte parser archetype's tiered tolerance model. Real-world C-CDA is vendor-quirky; the parser is liberal on input (Postel's Law) so a deviation becomes a warning you triage, not an exception that halts your pipeline, while a genuinely unrecoverable or hostile document is a hard failure.

The tiers

TierBehaviorExample
0 / 1Accepted silently: conformant or trivially recoverable.A section recognized by its templateId.
2Warning with a stable code + PHI-free position; recovery continues. Escalates to a throw under { strict: true }.An unrecognized section LOINC code, a missing doseQuantity, a code/narrative mismatch.
3Fatal: a thrown CcdaParseError, always (even in lenient mode).Malformed XML, a non-ClinicalDocument root, a security tripwire.

The warning-code model

Every Tier-2 warning carries a stable string code (WARNING_CODES.*), a PHI-free message, and a structural position. Consumers branch on w.code, so renaming a code is a breaking change. The message and position interpolate only structural values (element names, OIDs, LOINC codes, positions), never a patient name, an identifier, or narrative text: you can log the whole .warnings array without leaking PHI.

Warnings are collected on doc.warnings and also delivered live to the onWarning callback, in discovery order:

import { parseCcda, WARNING_CODES } from "@cosyte/ccda";

// A medication with no doseQuantity and no routeCode: both safety-critical, both flagged.
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-0005"/>
<code code="34133-9" codeSystem="2.16.840.1.113883.6.1"/>
<title>Synthetic Progress 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.1.1" extension="2015-08-01"/>
<code code="10160-0" codeSystem="2.16.840.1.113883.6.1"/>
<title>Medications</title>
<text><content ID="m1">Aspirin</content></text>
<entry><substanceAdministration classCode="SBADM" moodCode="EVN">
<templateId root="2.16.840.1.113883.10.20.22.4.16" extension="2014-06-09"/>
<statusCode code="active"/>
<consumable><manufacturedProduct classCode="MANU">
<templateId root="2.16.840.1.113883.10.20.22.4.23" extension="2014-06-09"/>
<manufacturedMaterial><code code="1191" codeSystem="2.16.840.1.113883.6.88" displayName="Aspirin"/></manufacturedMaterial>
</manufacturedProduct></consumable>
<text><reference value="#m1"/></text>
</substanceAdministration></entry>
</section></component>
</structuredBody></component>
</ClinicalDocument>`;

const collected: string[] = [];
const doc = parseCcda(xml, { onWarning: (w) => collected.push(w.code) });

// The drug is read; the missing safety-critical fields are preserved-as-absent, never defaulted.
doc.getMedications()[0]?.drug?.code; // => "1191"
doc.getMedications()[0]?.dose; // => undefined
doc.warnings.some((w) => w.code === WARNING_CODES.MISSING_DOSE_QUANTITY); // => true
doc.warnings.some((w) => w.code === WARNING_CODES.MISSING_ROUTE_CODE); // => true
collected.length; // => 2

Strict mode

{ strict: true } escalates the first tolerated Tier-2 deviation to a thrown CcdaParseError carrying the same code: a spec-conformance gate for a trusted sender. Fail-safe by design: a clean, conformant document parses identically in both modes.

Fatal codes (always throw)

Seven Tier-3 codes are unrecoverable and throw a CcdaParseError regardless of strict. The first five are security fatals raised by the hardened XML substrate before/while building the DOM, the load-bearing defense against hostile XML:

Fatal codeMeaning
XXE_OR_DTD_PRESENTThe document declared a DTD or an external entity.
ENTITY_EXPANSION_LIMITToo many &…; entity references (billion-laughs).
INPUT_SIZE_LIMIT_EXCEEDEDDecoded input exceeds the byte cap.
ELEMENT_DEPTH_LIMIT_EXCEEDEDElement nesting too deep.
NODE_COUNT_LIMIT_EXCEEDEDToo many element nodes.
NOT_WELL_FORMED_XMLThe bytes did not parse as XML.
NOT_A_CLINICAL_DOCUMENTWell-formed, but the root element is not ClinicalDocument.

Narrow on err.code:

import { parseCcda, CcdaParseError, FATAL_CODES } from "@cosyte/ccda";

let code: string | undefined;
try {
// Well-formed XML, but the root is not a ClinicalDocument.
parseCcda("<Foo>hello</Foo>");
} catch (err) {
if (err instanceof CcdaParseError) code = err.code;
}
code; // => "NOT_A_CLINICAL_DOCUMENT"
code === FATAL_CODES.NOT_A_CLINICAL_DOCUMENT; // => true

The safety caps (maxInputBytes, maxDepth, maxNodeCount, maxEntityExpansions) have library defaults; tighten them (or, at your own risk, loosen them) via ParseCcdaOptions.limits.