CDA Export - Custom Narrative Generation Tutorial - Smile CDR Documentation
CDA 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.
Default 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.
Overriding 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: Configure a Narrative Generator Module Properties file:
module.narrative_generator.type=NARRATIVE_GENERATOR
module.narrative_generator.config.manifest_file={path_to_cda-narratives.properties}
Copy
Yaml file:
{module_id}:
name: Narrative Generator
enabled: true
type: NARRATIVE_GENERATOR
config:
manifest_file: {path_to_cda-narratives.properties}
Copy
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.
cda-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}
Copy
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
resource.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. Thymeleaf 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.
Each narrative gets a Bundle as input
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'}">
Copy
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>
Copy
The above snippet will populate the
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
Sample 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>
Copy
CDA Export - Custom Narrative Generation Tutorial
Tutorials
Introduction Integration Testing Tutorial Custom Operations Tutorial HL7 v2.x Ingestion Tutorial Federated OAuth2/OIDC Tutorial MDM Tutorial MDM UI Tutorial MDM First Golden Resource Tutorial MDM Advanced Rule Authoring Tutorial MDM Web UI Admin Setup Tutorial CDA Import Tutorial CDA Export Tutorial CDA Export - Custom Narrative Generation Tutorial CDA Import JSON example CDA Export Template JSON example Consent Tutorial CMS-0057-F IG Setup Tutorial