Producing C-CDA documents
This page describes how to populate C-CDA documents from FHIR data stored in Aidbox.
C-CDA / FHIR Converter provides bidirectional mapping for all data elements from the USCDI v1 list. Detailed list of supported C-CDA sections is also available.
To generate a C-CDA document from FHIR data, it is necessary to create a FHIR Document bundle containing a Composition resource that specifies the top-level document attributes, including the title, document type, author, subject (patient), and a list of document sections. Each section must be described by type, title and the FHIR resources to be included. Once the FHIR Document bundle is composed, it can be submitted to the /ccda/v2/to-ccda endpoint for conversion to a C-CDA document.
To pick the right
templateId
for a section, converter uses LOINC/OID mapping table which can be found on the List of supported sections page. "Entries Required" / "Entries Optional" variation can be specified via FHIR extension. In the example below document contains two sections: Social History Section (V3) and Allergies and Intolerances Section (entries required) (V3).{
"resourceType": "Composition",
...
"section": [
{
"title": "Social History",
"code" : {
"text" : "Social History",
"coding" : [ {
"code" : "29762-2",
"display" : "Social History",
"system" : "http://loinc.org"
} ]
},
"entry": [ ... ]
}, {
"title": "Allergies and Intolerances",
"extension" : [ {
"value" : {
"boolean" : true
},
"url" : "entries-required"
} ],
"code" : {
"coding" : [ {
"code" : "48765-2",
"display" : "48765-2",
"system" : "http://loinc.org"
} ]
},
"entry": [ ... ]
}
]
}
To simplify the creation of Document bundles, Aidbox offers a feature called Document Definition, which enables the description of document contents using the FHIR Search API. The example below illustrates how to define a Document Definition:
{:type {:code "34133-9"
:display "Summarization of Episode Note"
:system "http://loinc.org"}
:date "2020-02-02"
:title "Discharge summary example"
:subject {:method "GET"
:url "/Patient/{{pid}}"}
:custodian {:method "GET"
:url "/Organization/42"}
:author {:method "GET"
:url "/Practitioner/42"}
:section
[{:title "Allergies and Intolerances"
:code {:code "48765-2"
:display "Allergies"
:system "http://loinc.org"}
:entriesRequired true
:entry
{:method "GET"
:url "/AllergyIntolerance?patient=Patient/{{pid}}"}}
{:title "Results"
:code {:code "30954-2"
:display "Results"
:system "http://loinc.org"}
:entriesRequired true
:entry
{:method "GET"
:url "/Observation?patient=Patient/{{pid}}&category=laboratory&_assoc=hasMember"}}
{:title "Social History"
:code {:code "29762-2"
:display "Social History"
:system "http://loinc.org"}
:entry
{:method "GET"
:url "/Observation?patient=Patient/{{pid}}&category=social-history&_assoc=hasMember"}}
{:title "Problems"
:code {:code "11450-4"
:display "Problems"
:system "http://loinc.org"}
:entriesRequired true
:entry
{:method "GET"
:url "/Condition?patient=Patient/{{pid}}&category=problem-list-item"}}
{:title "Hospital Course"
:code {:code "8648-8" :display "Hospital course Narrative" :system "http://loinc.org"}
:text {:method "GET" :url "/Observation?subject=Patient/{{pid}}&code=8648-8"}}
{:title "Vital Signs"
:code {:code "8716-3"
:display "Vitals"
:system "http://loinc.org"}
:entriesRequired true
:entry
{:method "GET"
:url "/Observation?category=vital-signs&patient=Patient/{{pid}}"}}]}
Each resource attribute, such as
:subject
, :author
, or :section/:entry
, is specified as a HTTP request that returns a single FHIR resource or multiple FHIR resources. The full power of the FHIR Search API can be used to retrieve resources that meet specific criteria. Parameters interpolation is also supported. For example, in the Vitals Signs section of the sample above:
{:method "GET"
:url "/Observation?category=vital-signs&patient=Patient/{{pid}}"}
{{pid}}
will be replaced with the value passed in the query-string parameter pid
and this way vitals observations are scoped to the specific patient.Another frequent use-case is date filtering. It can be easily added with two parameters:
start-date
and end-date:
{:method "GET"
:url "/Observation?category=vital-signs&patient=Patient/{{pid}}&date={{start-date}}&date={{end-date}}"}
All the filtering logic is strictly described by the FHIR Search specification, so the date filtering in the example above is covered by
date
search param type.Multiple FHIR searches per section is also possible:
{:title "Procedures"
:code {:code "47519-4" :display "History of Procedures Document" :system "http://loinc.org"}
:entry
[{:method "GET" :url "/Procedure?subject=Patient/{{pid}}&category:not=225299006&status=completed&date=ge{{start-date}}&date=le{{end-date}}&_sort=date"}
{:method "GET" :url "/Procedure?subject=Patient/{{pid}}&status=completed&category=225299006"}]}
Along with structured entries, CDA section contains human-readable narrative describing section data. This narrative can be automatically generated from entries (if specific section supports it) or it can be retrieved from Observation resource. To retrieve narrative from Observation, provide corresponding request under the
:text
key:{:title "Discharge Instructions"
:code {:code "8653-8"
:display "Discharge instructions"
:system "http://loinc.org"}
:text {:method "GET" :url "/Observation?subject=Patient/{{pid}}&code=8653-8"}}
The best practice is to have Observation.code to be equal to section LOINC code for Observations containing section narratives. Narrative itself can be stored in either
Observation.note[0].text
or Observation.value.string
.Quite often it's needed to make ad-hoc changes here and there in the CDA document to meet specific requirements. For example, one may want to generate section narrative from section entries or discard all patient identifiers except for SSN. To make this possible, you can get FHIR Document populated by Document Definition via
/ccda/prepare-doc
endpoint and then make modifications to it. When all modifications are in place, you can submit result to the /ccda/v2/to-cda
endpoint to generate the final CDA document. Consider the following pseudo-code which removes all patient idenfitiers from the Patient resource except for SSN:var docdef = { ... };
var bundle = aidbox.post('/ccda/prepare-doc', docdef);
var composition = bundle.entry[0].resource;
var patient = bundle.findByRef(composition.subject);
for (i = 0; i < patient.identifier.length; i++) {
var ident = patient.identifier[i];
if (ident.system != 'http://hl7.org/fhir/sid/us-ssn') {
patient.identifier[i] = null;
}
}
var cda = aidbox.post('/ccda/v2/to-cсda', bundle);
Another pseudo-code example on how to populate section narrative from section entries:
function generateVitalSignsNarrative(section, bundle) {
var result = '';
for (i = 0; i < section.entry.length; i++) {
var vs = bundle.findByRef(section.entry[i]);
result += '<paragraph>' +
formatCode(vs.code) + " " +
formatDate(vs.effective) + " " +
formatValue(vs.value) + '</paragraph>';
}
if (section.entry.length == 0) {
result = 'No vital signs available';
}
return result;
}
var docdef = { ... };
var bundle = aidbox.post('/ccda/prepare-doc', docdef);
var composition = bundle.entry[0].resource;
for (i = 0; i < composition.section.length; i++) {
var section = composition.section[i];
if (section.code.coding[0].code == '8716-3') {
section.text = {
// this function receives all the data it needs to populate narrative
// it returns stringified HTML
div: generateVitalSignsNarrative(section, bundle),
status: 'generated'
};
}
}