29.5.1JavaScript Hooks on CDA Import / Export

 

CDA exchange allows consumers to supply JavaScript hooks that get executed before and after import and after export.

This allows users to alter/add additional information to documents before processing.

Below are some examples of the scripts that can be provided.

29.5.2CDA Pre Import JavaScript Hook

 

To provide a pre import hook, you need to supply JavaScript that includes the function onPreImportCDA.

If supplied, the function will be invoked with a parameters object that contains the input document (as a text string).

If supplied, this function must return the resulting XML document to process, even if no changes are made!

See below for an example of the onPreImportCDA handler.

29.5.3CDA Post Import JavaScript Hook

 

To provide a post import hook, you need to supply JavaScript that includes the function onPostImportCDA.

If supplied, the function will be invoked with a parameters object with accessors to retrieve the created bundle, the operation outcome, and the supplied document (as a text string).

See the examples below for how to utilize the onPostImportCDA handler.

29.5.4CDA Post Export JavaScript Hook

 

To provide a post export hook, you need to supply JavaScript that includes the function onPostExportCDA.

If supplied, the function will be invoked with a parameters object with accessors to retrieve the bundle and the created CDA document (provided as an XML string).

If supplied, this function must return the resulting XML document to save, even if no changes were made!

See the examples below for how to utilize the onPostExportCDA handler.

29.5.5Exposed APIs for use in JavaScript Execution Environment

 

For information on APIs in the JavaScript execution environment, refer to the standard APIs exposed to the JavaScript environment.

Because CDA utilizes XML documents in the JavaScript hooks, it may be useful to read up on the exposed XML API.

29.5.6Example Scripts

 

Examples of various JavaScript hooks.

Each example will be defined individually, but all handlers can be put into the same script.

29.5.6.1CDA Pre Import Scripts

Example 1: Modify the input document to append a new element. Return the newly constructed document.


function onPreImportCDA(params) {
   const xmlString = params.getDocument();
	const document = XML.createDocument(xmlString);
	const patientNode = document.getXPathElements("/ClinicalDocument/recordTarget/patientRole/patient")[0];

	// create new element
	const ethnicGroupEl = document.createNewElement('sdtc:ethnicGroupCode');
	ethnicGroupEl.setOrAddAttribute("code", "2137-8");
	ethnicGroupEl.setOrAddAttribute("codeSystem", "2.16.840.1.113883.6.238");
	ethnicGroupEl.setOrAddAttribute("codeSystemName", "Race & Ethnicity - CDC");
	ethnicGroupEl.setOrAddAttribute("displayName", "Spaniard");

	// add it to the appropriate spot in the xml
	patientNode.appendChild(ethnicGroupEl);

	// return the newly formed xml
	return document.toXMLString();
}

29.5.6.2CDA Post Import Scripts

Example 1: Modify HTTP Verbs on bundle entry requests from POST to GET


function onPostImportCDA(params) {
	const bundle = params.getBundle();
	const outcome = params.getOutcome();
   const strDoc = params.getDocument();

   if (bundle.entry && bundle.entry.length) {
   	const len = bundle.entry.length;
   	for (let i = 0; i < len; i++) {
   		const entry = bundle.entry[i];
   		if (entry.request && entry.request.method) {
   			if (entry.request.method == "POST") {
   				entry.request.method = "GET";
   			}
   		}
   	}
   }
}

Example 2: Parse sdtc:ethnicGroupCode from initial XML document and add a sub-extension to the Bundle's Patient's us-core-ethnicity extension with the relevant system, code, and display.


/* Main javascript hook */
function onPostImportCDA(params) {
   const docStr = params.getDocument();
   const bundle = params.getBundle();

   const patient = getPatientFromBundle(bundle);
   if (!patient) {
   	console.error("Could not find patient!");
   	return;
   }

   // parse out the nodes we are interested in from the xml
   const xmlDoc = XML.createDocument(docStr);
   const nodes = xmlDoc.getXPathElements("/ClinicalDocument/recordTarget/patientRole/patient/sdtc:ethnicGroupCode");

	// This node has a cardinality of 0..*
	const len = nodes.length;
	if (len) {
		for (let i = 0; i < len; i++) {
	   	const node = nodes[i];

			const usCoreEthnicityExtension = patient.getExtension("http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity");
			if (usCoreEthnicityExtension) {
				ext = usCoreEthnicityExtension.addExtension("detailed");
				const coding = ext.valueCoding;

			   // fill the coding in with the values from the node
				populateCodingWithNodeValues(node, coding);
			}
		}
	}
}

/* Helper function to extract patient from bundle */
function getPatientFromBundle(bundle) {
   const resources = bundle.entryResources();
   if (resources && resources.length) {
      const len = resources.length;
   	for (let i = 0; i < len; i++) {
   		const resource = resources[i];
			if (resource.resourceType == 'Patient') {
				// there should be exactly 1 patient in the bundle
				return resource;
			}
		}
   }

   // it should always find a patient
   return void 0;
}

/* Helper function to populate coding system values from provided node */
function populateCodingWithNodeValues(node, coding) {
   // returns arrays
   // but cardinality of any given xml node attributes is 0..1
	const codes = node.getXPathValues("@code");
	const displays = node.getXPathValues("@displayName");
	const codeSystems = node.getXPathValues("@codeSystem");

	let code = "";
	if (codes.length) {
		code = codes[0];
	}
	let display = "";
	if (displays.length) {
		display = displays[0];
	}
	let codeSystem = "";
	if (codeSystems.length) {
		codeSystem = "urn:oid:" + codeSystems[0];
	}

	// set to coding system
	coding.system = codeSystem;
	coding.display = display;
	coding.code = code;
}

29.5.6.3CDA Post Export Script

Example 1: Using the coding information on the extensions of the patient resource embedded on the bundle, create and append the stdc:ethnicGroupCode node to the created XML document. Return the newly parsed XML for saving.


/** The CDA Post Export JavaScript hook */
function onPostExportCDA(params) {
	// retrieve the bundle and the document from the parameters
	const bundle = params.getBundle();
	const cdaDoc = XML.createDocument(params.getDocument());

	// fetch the patient off the bundle
	const patient = getPatientFromBundle(bundle);
	const usCoreEthnicityExtension = patient.getExtension("http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity");

   /*
    * If the ethnicity extension is found on the embedded patient
	 * in the bundle, we'll extract any subextension with the url 'detailed'
	 * and use it to add the equivalent node to the XML document.
	 */
	if (usCoreEthnicityExtension) {
		const subExtension = usCoreEthnicityExtension.getExtension('detailed');
		if (subExtension && subExtension.valueCoding) {
   		const coding = subExtension.valueCoding;
						
   		addEthnicGroupCodeToPatientNode(coding, cdaDoc);
		}
	}
				
	// return the newly altered document for saving
	return cdaDoc.toXMLString();
}

/**
 * Retrieves the patient resource from the bundle
 */
function getPatientFromBundle(bundle) {
	const resources = bundle.entryResources();
	if (resources && resources.length) {
		const len = resources.length;
		for (let i = 0; i < len; i++) {
			const resource = resources[i];
			if (resource.resourceType == 'Patient') {
				// there should be exactly 1 patient in the bundle
				return resource;
			}
		}
	}

	// it should always find a patient
	return void 0;
}

/**
 * Retrieve the <patient> node, or create it if it doesn't exist,
 * and then return it.
 */
function getPatientNodeOnDoc(cdaDocument) {
	const paths = [
		'ClinicalDocument',
		'recordTarget',
		'patientRole',
		'patient'
	];
	const len = paths.length;

	// there will definitely always be a ClinicalDocument root
	let index = 0;
	let node = cdaDocument.getXPathElements('/' + paths[index++])[0];
	for (; index < len; index++) {
		const path = paths[index];
		const nodes = node.getXPathElements('/' + path);

		if (!nodes.length) {
			// node doesn't exist - add it
			const newChild = cdaDocument.createNewElement(path);
			node.appendChild(newChild);
			node = newChild;
		} else {
			node = nodes[0];
		}
	}

	return node;
}

/**
 * Adds the <sdtc:ethnicGroupCode> node to the document,
 * creating the patient hierarchy on the document if required.
 */
function addEthnicGroupCodeToPatientNode(coding, cdaDoc) {
	// find the patient node on this document
	const patientNode = getPatientNodeOnDoc(cdaDoc);

   // add the xmlns:sdtc namespace
	cdaDoc.addNamespace('xmlns:sdtc', 'urn:hl7-org:sdtc', 'http://www.w3.org/2000/xmlns/');

	// create the element
	const ethnicGroupCode = cdaDoc.createNewElement('sdtc:ethnicGroupCode');
	ethnicGroupCode.setOrAddAttribute('code', coding.code);
	ethnicGroupCode.setOrAddAttribute('displayName', coding.display);
	ethnicGroupCode.setOrAddAttribute('codeSystem', coding.system.replaceAll('urn:oid:', ''));
	ethnicGroupCode.setOrAddAttribute('codeSystemName', 'Race & Ethnicity - CDC');

	// add it to the dom
	patientNode.appendChild(ethnicGroupCode);
}