12.8.1CDA Export Tutorial - Generating Narrative text

 

When generating a CDA Document using the CDA Exchange+ module a narrative is generated to populate the <text> element of each section.
This narrative can be edited to display the information necessary from the various resources it consists of.

This tutorial aims to help you familiarise yourself with the various ways narrative generation occurs in the CDA Exchange+ Module and how it can be customized to suit your needs. A global overview of all the features can also be found (here)[/docs/cda_exchange_plus/narrative_generation.html).

12.8.1.1Default behaviour

By default, if the Composition.section.text has been populated (be it manually or via generation) it will use its contents to populate the <text> element for the corresponding CDA section. Generally, this generated narrative will appear as a simple tabular summary of the most relevant fields of the entries.

12.8.1.1.1Overriding the default narrative templates

If the simple tabular summary is not sufficient, custom templates can be uploaded to use instead; these will override the default narrative templates using the Narrative Generator module. The following setup should be followed:

12.8.1.1.2Configure a Narrative Generator Module

Properties file:

module.narrative_generator.type=NARRATIVE_GENERATOR
module.narrative_generator.config.manifest_file={path_to_cda-narratives.properties}

Yaml file:

{module_id}:
	name: Narrative Generator
	enabled: true
	type: NARRATIVE_GENERATOR
	config:
		manifest_file={path_to_cda-narratives.properties}

Path_to_cda-narratives.properties is the location of the cda-narratives.properties file, this can be on the classpath: but can also be found via a local file path using file:/path/to/file.txt. How this file is set up can be found later in the tutorial.

Generally speaking it’s good practice to put this file in the config_seeding folder in the classes/ folder, Ie. classpath:config_seeding/cda-narrative.properties

Once this file has been loaded, the CDA Exchange+ Module needs to be updated with a Dependency to the Narrative Generator module.

12.8.1.1.3cda-narrative.properties

The cda-narrative.properties file specifies where the narrative generator module can find the templates for each section. Every section will require the following format:

{resource}.resourceType = Bundle
{resource}.tag = {resource_tag}
{resource}.narrative = {path-to-resource-html}

The resource_tag corresponds with the code value of the section as defined by the CDA Specification . Generally speaking there should be a specific LOINC code associated with the section.
The resource.html will be a thymeleaf template that indicates what needs to be generated. This will be discussed in the next section.
It is recommended to put the resource.html files in the same config_seeding/ folder as the cda-narratives.properties file. Ie. classpath:config_seeding/resource.html

12.8.1.1.4resource.html

The html for each FHIR resource is written in Thymeleaf and uses FhirPath to select which part of the resource needs to be displayed.

12.8.1.1.5Thymeleaf Example - Encounter

Below is an example of a Thymeleaf template for Encounter resources. It will display the resources via a table with the following fields: title, status, class, type, priority, period, location and participant.

  1. Each narrative gets a Bundle as input

  2. Using <th:block th:each=”entry : ${resource.entry}” th:object=”${entry.getResource()}”> will unpack each resource from the Bundle Entries

the resource can be retrieved from the entry and <th:block th:if="*{getResourceType().name() == 'Encounter'}"> is used to validate that this template only triggers on Encounter resources.

<th:block th:each="entry : ${resource.entry}" th:object="${entry.getResource()}">
   <th:block th:if="*{getResourceType().name() == 'Encounter'}">

Once the Encounter resource has been established, FhirPath queries can be leveraged to traverse the resource and resolve references. For example:

<td th:text="*{getDiagnosis().isEmpty() != true ? #fhirpath.evaluate(#object, 'diagnosis.condition.resolve().code.coding[0].display')[0] : 'No Diagnosis'}">Title</td>

The above snippet will populate the <td> element with the Diagnosis, it will first check if the diagnosis field is not empty (getDiagnosis().isEmpty()), after that, it uses FhirPath to find the condition,resolves the Reference to a Condition/Procedure and retrieve the first occurring coding.display, if the diagnosis is empty No Diagnosis is displayed.

For more information on how to use Fhirpath: here

The full Encounter resource Thymeleaf below can serve as a starting point when creating your own templates

12.8.1.2Sample of Encounter resource Thymeleaf:

<div xmlns:th="http://www.thymeleaf.org">
   <h5>Encounters</h5>

   <table>
      <thead>
      <tr>
         <th>Title</th>
         <th>Status</th>
         <th>Class</th>
         <th>Type</th>
         <th>Priority</th>
         <th>Period</th>
         <th>Location</th>
         <th>Participant</th>
      </tr>
      </thead>
      <tbody>
      <th:block th:each="entry : ${resource.entry}" th:object="${entry.getResource()}">
         <th:block th:if="*{getResourceType().name() == 'Encounter'}">
            <tr>
               <td th:text="*{getDiagnosis().isEmpty() != true ? #fhirpath.evaluate(#object, 'diagnosis.condition.resolve().code.coding[0].display')[0] : 'No Diagnosis'}">Title</td>
               <td th:text="*{getStatus() ? getStatus().getDisplay() : ''}">Status</td>
               <td th:text="*{getClass() ? getClass().get(0).getDisplay() : ''}">Class</td>
               <td th:text="*{getType() ? getType().get(0).getDisplay() : ''}">Type</td>
               <th:block th:if="*{hasPriority()}">
                  <td th:text="*{getPriority() ? getPriority().get(0).getDisplay() : ''}">Priority</td>
               </th:block>
               <th:block th:unless="*{hasPriority()}">
                  <td></td>
               </th:block>
               <th:block th:if="*{hasPeriod()}">
                  <td th:text="*{#strings.concatReplaceNulls('', getPeriod().getStartElement().getValueAsString(), ' - ', getPeriod().getEndElement().getValueAsString())}">Period</td>
               </th:block>
               <th:block th:unless="*{hasPeriod()}">
                  <td></td>
               </th:block>
               <th:block th:if="*{hasLocation()}">
                  <th:block th:with="location = ${#fhirpath.evaluateFirst(#object, 'location[0].location.resolve()')}">
                     <th:block th:if="${location != null}">
                        <td th:text="~{location ? getName() : ''}">Location</td>
                     </th:block>
                     <th:block th:unless="${location != null}">
                        <td></td>
                     </th:block>
                  </th:block>
               </th:block>
               <th:block th:unless="*{hasLocation()}">
                  <td></td>
               </th:block>
               <th:block th:if="*{hasParticipant()}" th:fragment="renderParticipantRef(encounter)">
                  <td th:text="${#fhirpath.evaluate(#object, 'participant[0].individual.reference.resolve().name[0].family')[0]} + ', ' + ${#fhirpath.evaluate(#object, 'participant[0].individual.reference.resolve().name[0].given[0]')[0]}" >Participant</td>
               </th:block>
               <th:block th:unless="*{hasParticipant()}">
                  <td></td>
               </th:block>
            </tr>
         </th:block>
      </th:block>
      </tbody>
   </table>
</div>