Smile CDR v2024.05.PRE
On this page:

28.1.1CDA JavaScript Templating System
Trial

 

Before you can create a CDA document, you will need to create a CDA document template.

Included in Smile CDR is an API with many FHIR and CDA specific functions that you can read all about in the JavaScript Execution Environment (JSEE) section of the docs. You also have access to the ES5 JavaScript standard library for your templates. The JSEE currently only supports ES5 syntax, but ES6 will be supported in the future.

Keep in mind that the JavaScript CDA template is only responsible for creating a FHIR Composition resource from your existing FHIR repository. If you already have a properly formatted Composition, or you have your own workflow for creating Compositions, you can skip this step.

28.1.2The simplest CDA template script
Trial

 

The only requirement for your template script to work is a function called generateCdaExchangeComposition that returns a Composition.

Thus, the most basic CDA template script would look like this:

function generateCdaExchangeComposition() {
	return ResourceBuilder.build('Composition');
}

If you were to post this script to your cda module and then apply it, you would successfully create a CDA document! Of course, this CDA document would not be very useful since there is no information in it.

You can learn more about ResourceBuilder here.

28.1.3Adding header information to our CDA template script
Trial

 

To make your CDA document more useful, we should populate our Composition with some resources. A Composition resource created with ResourceBuilder has some additional functionality beyond that which is available to other resource types. This additional functionality has been designed specifically to assist with the creation of CDA document templates. All methods performed on the composition variable are documented in the Composition Resource and Composition Section docs under the JSEE.

function generateCdaExchangeComposition() {
	var composition = ResourceBuilder.build('Composition');
	composition.setType('ContinuityOfCareDocument');

	composition.setSubject('Patient/123');
	composition.setAuthor('Device/124');
	composition.setCustodian('Organization/125');
	composition.setEncounter('Encounter/126');

	return composition;
}

setType: The CDA module will create a Continuity of Care C-CDA document by default, but this is not guaranteed to always be the case, and so we should explicitly set the document type in our script. Additionally, if you were to want to persist the intermediate Composition and/or Bundle created by this module, setting the type here would ensure that the appropriate fields are populated in your FHIR Composition. You can see a full list of available document types in the Available Types and Mappings section of the docs.

set*: The additional setters here populate the appropriate fields of the Composition which will in turn be used to populate the appropriate tags in the US Realm Header C-CDA template.

If you were to replace the reference strings in the template above with valid references in your FHIR database, you would be able to generate a CDA document with a valid and fully populated US Realm Header section. However, all of your sections would be empty because you did not explicitly populate them. The CDA parser creates empty sections for every empty required section for your selected document type even if you did not add them to the Composition.

28.1.4Adding and populating a section in our CDA template script
Trial

 

In order to make our CDA document truly meaningful, we need to add and populate sections with the Patient's data.

You can see a full list of available sections and what FHIR Resources can be used to populate them in the Available Types and Mappings section of this document.

function generateCdaExchangeComposition() {
	var composition = ResourceBuilder.build('Composition');
	composition.setType('ContinuityOfCareDocument');

	composition.setSubject('Patient/1359');
	composition.setAuthor('Device/1453');
	composition.setCustodian('Organization/1452');
	composition.setEncounter('Encounter/1454');

	var allergySection = composition.addSection('allergyintolerance');
	allergySection.populate([Fhir.read('AllergyIntolerance/1366'), Fhir.read('AllergyIntolerance/1367')]);

	return composition;
}

Composition.addSection: Attaches a section to the Composition and returns that section.

Section.populate: Adds resource references to the section.

Additional information about Composition sections can be found in the Composition Section portion of the JavaScript Execution Environment docs.

If the references in the example above existed in your FHIR database, then your resultant CDA document would have a populated Allergies and Intolerances section, along with 5 auto-generated empty sections. With a slight modification, you could repeat the section code for all other mandatory Continuity of Care sections to have them all populated as well.

28.1.5Using the Fhir.search() API to dynamically populate sections
Trial

 

Hard-coding section entries will work, but it is not particularly useful if you want to create your CDA documents dynamically.

Instead, you can use the Fhir.search() API to create search parameter maps that are executed at runtime. This allows you to specify the rules by which to populate a section ahead of time and then get the most up to date resources at the time of document creation.

function generateCdaExchangeComposition() {
	var composition = ResourceBuilder.build('Composition');
	composition.setType('ContinuityOfCareDocument');

	composition.setSubject('Patient/1359');
	composition.setAuthor('Device/1453');
	composition.setCustodian('Organization/1452');
	composition.setEncounter('Encounter/1454');

	var allergySection = composition.addSection('allergyintolerance');
	
	var allergyResources = Fhir
	  .search()
	  .forResource('AllergyIntolerance')
	  .where('patient', 'Patient/1359')
	  .asList();

	allergySection.populate(allergyResources);

	return composition;
}

In the example above, instead of specifying specific AllergyIntolerance resources, we used the search feature to find the ones where the patient is Patient/1359. There are many more advanced searches we could perform. Please refer to the Fhir.search() API section of the docs for a full list of available search features.

28.1.6Specifying input parameters
Trial

 

The example above would work great if we were only ever interested in Patient/1359. In order to make this template more versatile, we can add an argument to our generateCdaExchangeComposition function called inputMap. This argument is a JavaScript Object and is essentially a collection of key-value pairs. The following example uses the inputMap and assumes the key patient will be provided.

function generateCdaExchangeComposition(inputMap) {
	var composition = ResourceBuilder.build('Composition');
	composition.setType('ContinuityOfCareDocument');

    var patient = inputMap['patient'];
	composition.setSubject(patient);

	composition.setAuthor('Device/1453');
	composition.setCustodian('Organization/1452');
	composition.setEncounter('Encounter/1454');

	var allergySection = composition.addSection('allergyintolerance');
	
	var allergyResources = Fhir
	  .search()
	  .forResource('AllergyIntolerance')
	  .where('patient', patient)
	  .asList();

	allergySection.populate(allergyResources);

	return composition;
}

If a user were to call the apply rest method for this template and include the following body param, the result would be the same as calling the previous hard-coded version with no params:

{
  "inputParams": {
    "patient": "Patient/1359"
  }
}

However, now this template could be used to report on the allegies of any patient in the FHIR database. A minor modification to the script would allow us to parametrize Author, Custodian, and Encounter as well.

28.1.7More complicated input parameters
Trial

 

Your input parameters do not have to be resource references. The following example would require additional input parameters, but makes the template more modular as well. In particular, pay attention to the use of maxAllergies and maxAllergyAge.

function generateCdaExchangeComposition(inputMap) {
	// store our inputMap params in their own variables
    var patient = inputMap['patient'];
    var author = inputMap['author'];
    var custodian = inputMap['custodian'];
    var encounter = inputMap['encounter'];
    var maxAllergies = inputMap['maxAllergies'];
    var maxAllergyAge = inputMap['maxAllergyAge'];
	
	var composition = ResourceBuilder.build('Composition');
	composition.setType('ContinuityOfCareDocument');

	composition.setSubject(patient);
	composition.setAuthor(author);
	composition.setCustodian(custodian);
	composition.setEncounter(encounter);
	
    // using JavaScript's native Date object, we construct a string 
    // to find only allergies asserted within the last `maxAllergyAge` days
    var xDaysAgo= new Date();
    xDaysAgo.setDate(xDaysAgo.getDate() - maxAllergyAge);
    // this will be of the form 'ge2018-07-31'
    var xDaysAgoIsoString = 'ge' + xDaysAgo.toISOString().slice(0,10);
	
	var allergySection = composition.addSection('allergyintolerance');
	var allergyResources = Fhir
	  .search()
	  .forResource('AllergyIntolerance')
	  .where('patient', patient)
	  .where('date', xDaysAgoIsoString)
	  .count(maxAllergies)
	  .sort('-date')
	  .asList();

	allergySection.populate(allergyResources);

	return composition;
}

With these additional parameters, we are able to modify the allergy search by specifying the maximum age in days of an allergy as well as the maximum number of allergies to report. We add the descending sort by date to ensure that the most recent allergies are included if the total number of matching allergies exceeds the maximum specified.

An example call for this script could include the following inputParams argument:

{
  "inputParams": {
    "patient": "Patient/1359",
    "author": "Device/1453",
    "custodian": "Organization/1452",
    "encounter": "Encounter/1454",
    "maxAllergies": 50,
    "maxAllergyAge": 30    
  }
}

There are many additional body params one could include when calling /apply for a template. For complete details on how the REST api works, check out the CDA Exchange Endpoint documentation.

28.1.8Specifying section author participation
Trial

 

In order to support authors of a section that are distinct from the author of the CDA document, whenever you add an entry to a section, you may also add a Provenance object to that section to define the author information for that section.

let problemSection = composition.addSection("problem");
let problemList = Fhir
    .search()
    .forResource('Condition')
    .where('subject', subject)
    .asList();
problemSection.populate(problemList);

let provenanceList = Fhir
    .search()
    .forResource('Provenance')
    .where([search criteria to identify the desired Provenance resource])
    .asList();
problemSection.populate(provenanceList);

28.1.9Putting it all together
Trial

 

From the examples above, it should be fairly clear how to extend the functionality and modularity of a CDA Template script. To learn more about what functions are available to you for use in your script, please read the JavaScript Execution Environment docs. The sections most relevant to CDA Templating would be the JavaScript FHIR REST API, the Composition Resource section, and the Composition Section section.

Once you have a template script you are happy with, the next step is to add it to your Smile CDR CDA module. A high level overview is available in the next section of these docs, or to view the more granular details, check out the CDA Exchange Endpoint part of the JSON Admin API docs.

28.1.10Configuring the $docref CCD template
Trial

 

In order to use the DocumentReference/$docref operation, it is necessary to first configure a document template with the name default-ccd-template. This template will be responsible for gathering the necessary resources to populate a Continuity of Care Document (CCD). It should contain sections for medication, result, vitalsign, problem, socialhistory and, allergyintolerance. If any of these required sections is absent from the composition, the document generator will add an empty section with a nullFlavor attribute. The template may also contain any other supported sections.

This template must accept an input parameter with the key patient, whose corresponding value will be the id of the Patient for whom the document will be generated. The script must not be dependent on any other input parameters.

function generateCdaExchangeComposition(inputMap) {
    // create the composition for our document
    let composition = ResourceBuilder.build('Composition');
    // currently CCD is the document type with the most support. There is limited support for "TransferSummary" as well.
    composition.setType("ContinuityOfCareDocument");

    let patient = inputMap['patient'];

    composition.setCustodian('Organization/1703');
    composition.setAuthor('Device/4318');

    composition.setSubject(patient);

    // get current timeStamp for use in auto-generated IDs and timestamps
    let currentDate = Date.now();
    composition.setDate(currentDate);

    // boilerplate fields for this document-generation template
    composition.language = "en-US";
    composition.identifier.system = "1.23.456.7.890123.4.5678";
    let splitSubject = patient.split('/');
    if (splitSubject.length > 1) {
      composition.identifier.value = patient.split('/')[1] + currentDate;
    } else {
      composition.identifier.value = patient + currentDate;
    }

    composition.title = "Continuity of Care Document";

    let problemSection = composition.addSection("problem");
    let problemList = Fhir
        .search()
        .forResource('Condition')
        .where('subject', patient)
        .asList();
    problemSection.populate(problemList);

    let medicationSection = composition.addSection('medication');

    let medicationList = Fhir
        .search()
        .forResource('MedicationRequest')
        .where('subject', patient)
        .asList();

    let medicationList2 = Fhir
        .search()
        .forResource('MedicationAdministration')
        .where('subject', patient)
        .asList();

    medicationSection
        .populate(medicationList)
        .populate(medicationList2);

    let resultSection = composition.addSection("result");

    let resultList = Fhir
       .search()
       .forResource('DiagnosticReport')
       .where('subject', patient)
       .asList();
    resultSection.populate(resultList);

    let vitalSection = composition.addSection("vitalsign");
    let vitalList = Fhir
        .search()
        .forResource('Observation')
        .where('subject', patient)
        .where('category', 'vital-signs')
        .asList();
    vitalSection.populate(vitalList);

    let allergySection = composition.addSection("allergyIntolerance");
    let allergyList = Fhir
        .search()
        .forResource('AllergyIntolerance')
        .where('patient', patient)
        .asList();
    allergySection.populate(allergyList);

    let socialHistorySection = composition.addSection("socialHistory");
    let historyList = Fhir
        .search()
        .forResource('Observation')
        .where('subject', patient)
        .where('category', 'social-history')
        .asList();
    socialHistorySection.populate(historyList);

    let immunizationSection = composition.addSection("immunization");
    let immunizationList = Fhir
        .search()
        .forResource('Immunization')
        .where('patient', patient)
        .asList();
    immunizationSection.populate(immunizationList);

    let proceduresSection = composition.addSection("procedures");
    let proceduresList = Fhir
        .search()
        .forResource('Procedure')
        .where('subject', patient)
        .asList();
    proceduresSection.populate(proceduresList);

    let medicalEquipmentSection = composition.addSection("medicalequipment");
    let deviceList = Fhir
        .search()
        .forResource('DeviceUseStatement')
        .where('subject', patient)
        .asList();

	medicalEquipmentSection.populate(deviceList);

    return composition;
}