Narrative Generation

Narrative generation is a programmed mapping that automatically produces an HTML narrative for a FHIR resource during openEHR → FHIR mapping. It is powered by Thymeleaf templates and HAPI NarrativeGeneration logic and runs as part of the normal mapping pipeline.

Note

Narrative generation only applies in the openEHR → FHIR direction.

How it works

When generateNarrative is declared as a mappingCode on a mapping, openFHIR assembles the mapped FHIR resource (or the section entries referenced by it), selects the appropriate Thymeleaf template, renders the HTML, and sets the result to the fhirPath indicated in with.fhir.

generateNarrative accepts two arguments, first one is a fhirPath to a Resource (or resources) for which narrative will try to be generated. Second argument is optional and it’s a profile that will be used for narrowing down available HTML templates to pick.

Template selection is determined in the following order:

  1. If a profile URL is passed as the second argument, openFHIR looks for a template registered for that profile.

  2. Otherwise it falls back to a template registered for the resource type.

Defining it in a mapping

Add a mappingCode entry to any mapping in a model mapper. The with block is omitted — mappingCode replaces it.

Basic usage (template selected by resource type):

- name: "narrativeText"
  with:
    fhir: "text"
    openehr: "$composition"
  mappingCode: "generateNarrative(entry)"
  unidirectional: "openehr->fhir"

Profile-scoped usage (template selected by profile URL):

- name: "narrativeText"
  with:
    fhir: "text"
    openehr: "$composition"
  mappingCode: "generateNarrative(entry, http://hl7.org/fhir/uv/ips/StructureDefinition/Composition-uv-ips)"
  unidirectional: "openehr->fhir"

The entry argument refers to the section entries of the resource being mapped. The unidirectional: "openehr->fhir" field should always be set so the mapping is skipped in the FHIR → openEHR direction.

Configuring templates

openFHIR ships with built-in narrative templates for common IPS resource types. You can override these or register additional templates by pointing openFHIR at a properties file.

Step 1 — configure the properties file location:

openfhir:
  narrative:
    properties-file: file:/config/openfhir-narratives.properties

The value accepts both classpath: and file: prefixes. When using Docker, mount your properties file and templates into the container and use file: paths.

Step 2 — define templates in the properties file:

Each template entry consists of three keys sharing a common prefix:

bundle_conditions.resourceType=Bundle
bundle_conditions.narrative=file:/config/condition-narrative-template.html
bundle_conditions.profile=http://hl7.org/fhir/uv/ips/StructureDefinition/Composition-uv-ips
  • resourceType — the FHIR resource type this template applies to

  • narrative — path to the Thymeleaf HTML template (classpath: or file:)

  • profile(optional) profile URL; when set, this template is only selected when the profile matches the argument passed to generateNarrative

Multiple templates can be registered in the same file using different prefixes.

Writing a template

Templates are standard Thymeleaf HTML fragments. The root variable exposed to the template is ${resource}, which is the HAPI FHIR resource object being rendered. For Bundle resources, iterate over ${resource.entry} to access individual entries.

Example template for a Condition section:

<div xmlns:th="http://www.thymeleaf.org">
  <table>
    <thead>
      <tr><th>Condition</th><th>Status</th><th>Onset</th></tr>
    </thead>
    <tbody>
      <th:block th:each="entry : ${resource.entry}">
        <tr th:if="${entry.getResource().fhirType() == 'Condition'}"
            th:object="${entry.getResource()}">
          <td th:text="*{getCode().getText()}"></td>
          <td th:each="c : *{getClinicalStatus().getCoding()}"
              th:text="${c.getCode()}"></td>
          <td th:if="*{getOnset() != null}"
              th:text="*{getOnset().primitiveValue()}"></td>
        </tr>
      </th:block>
    </tbody>
  </table>
</div>

Note

Always guard rows with a resource type check (entry.getResource().fhirType() == 'Condition') when iterating over a Bundle that may contain mixed resource types. Without this guard, calling a type-specific method on the wrong resource type will throw a runtime error.

Note

Narrative generation leverages HAPI’s implementation. See https://hapifhir.io/hapi-fhir/docs/model/narrative_generation.html for more information.