Skip to content

Commit

Permalink
Enhance vital signs narrative generation (#6590)
Browse files Browse the repository at this point in the history
* add support for formatting observation.component entries in vital signs narrative generation

* update
  • Loading branch information
davidraeside authored Jan 7, 2025
1 parent ee194f2 commit 37ab80e
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
type: add
issue: 6587
title: "Enhanced the IPS vital signs narrative template to include code and value information for
all entries in the `Observation.component` property."
Original file line number Diff line number Diff line change
Expand Up @@ -247,3 +247,18 @@
<th:block th:if="${!iter.last}" th:text="', '"/>
</th:block>
</th:block>

<!-- /* Component list */ -->
<th:block th:if="${list}" th:fragment="renderComponent (list)">
<th:block th:each="item,iter : ${list}" th:if="${!list.empty}">
<th:block th:if="${item.getCode().getCodingFirstRep().hasDisplay()}" th:text="${item.getCode().getCodingFirstRep().getDisplay()}">Display</th:block>
<th:block th:if="${!item.getCode().getCodingFirstRep().hasDisplay()}" th:text="${item.getCode().getCodingFirstRep().getCode()}">Code</th:block>
<th:block th:if="${item.hasValueQuantity()}" th:text="${item.getValueQuantity().getValue().toPlainString()}">Value</th:block>
<th:block th:if="${item.hasValueIntegerType()}" th:text="${item.getValueIntegerType().getValueAsString()}">Value</th:block>
<th:block th:if="${item.hasValueStringType()}" th:text="${item.getValueStringType().getValueAsString()}">Value</th:block>
<th:block th:if="${item.hasValueCodeableConcept()}">
<th:block th:insert=":: codeableConcept (cc=${item.getValueCodeableConcept()}, attr='display')"/>
</th:block>
<th:block th:if="${!iter.last}" th:text="', '"/>
</th:block>
</th:block>
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
Result: Observation.valueQuantity || Observation.valueDateTime || Observation.valueCodeableConcept.text || Observation.valueCodeableConcept.coding[x].display (separated by <br />) || Observation.valueString
Unit: Observation.valueQuantity.unit
Interpretation: Observation.interpretation[0].text || Observation.interpretation[0].coding[x].display (separated by <br />)
Component(s): Observation.component[x].display || Observation.component[x].code + Observation.component[x].value (items separated by comma)
Comments: Observation.note[x].text (separated by <br />)
Date: Observation.effectiveDateTime || Observation.effectivePeriod.start
*/-->
Expand All @@ -16,19 +17,21 @@ <h5>Vital Signs</h5>
<th>Result</th>
<th>Unit</th>
<th>Interpretation</th>
<th>Component(s)</th>
<th>Comments</th>
<th>Date</th>
</tr>
</thead>
<tbody>
<th:block th:each="entry : ${resource.entry}" th:object="${entry.getResource()}">
<th:block th:unless='*{getResourceType().name() == "Composition"}'>
<th:block th:with="extension=${entry.getResource().getExtensionByUrl('http://hl7.org/fhir/StructureDefinition/narrativeLink').getValue().getValue()}">
<tr th:id="${#strings.arraySplit(extension, '#')[1]}">
<th:block th:with="extension=${entry.getResource().getExtensionByUrl('http://hl7.org/fhir/StructureDefinition/narrativeLink')}">
<tr th:id="${extension != null} ? ${#strings.arraySplit(extension.getValue().getValue(), '#')[1]} : ''">
<td th:insert="~{IpsUtilityFragments :: codeableConcept (cc=*{getCode()},attr='display')}">Code</td>
<td th:insert="~{IpsUtilityFragments :: renderValue (value=*{getValue()})}">Result</td>
<td th:insert="~{IpsUtilityFragments :: renderValueUnit (value=*{getValue()})}">Unit</td>
<td th:replace="~{IpsUtilityFragments :: firstFromCodeableConceptList (list=*{getInterpretation()})}">Interpretation</td>
<td th:insert="~{IpsUtilityFragments :: firstFromCodeableConceptList (list=*{getInterpretation()})}">Interpretation</td>
<td th:insert="~{IpsUtilityFragments :: renderComponent (list=*{getComponent()})}">Component(s)</td>
<td th:insert="~{IpsUtilityFragments :: concat (list=*{getNote()},attr='text')}">Comments</td>
<td th:insert="~{IpsUtilityFragments :: renderEffective (effective=*{getEffective()})}">Date</td>
</tr>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.CarePlan;
import org.hl7.fhir.r4.model.ClinicalImpression;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Composition;
import org.hl7.fhir.r4.model.Condition;
import org.hl7.fhir.r4.model.Consent;
Expand All @@ -45,6 +47,7 @@
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.PositiveIntType;
import org.hl7.fhir.r4.model.Procedure;
import org.hl7.fhir.r4.model.Quantity;
import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.Resource;
import org.hl7.fhir.r4.model.StringType;
Expand Down Expand Up @@ -667,6 +670,59 @@ public void testSelectGenerator() {
assertSame(strategy2, svc.selectGenerationStrategy("http://2"));
}

@Test
public void testVitalSigns_withComponents() throws IOException {
// Setup Patient
initializeGenerationStrategy();
registerPatientDaoWithRead();

Observation observation1 = new Observation();
observation1.setId("Observation/1");
observation1.setStatus(Observation.ObservationStatus.FINAL);
observation1.setCode(
new CodeableConcept().addCoding(
new Coding("http://loinc.org", "85354-9", "Blood pressure panel with all children optional")
)
);
observation1.addComponent(
new Observation.ObservationComponentComponent(
new CodeableConcept(
new Coding("http://loinc.org", "8480-6", "Systolic blood pressure")
)
).setValue(new Quantity().setValue(125).setUnit("mmHg").setSystem("http://unitsofmeasure.org").setCode("mm[Hg]"))
);
observation1.addComponent(
new Observation.ObservationComponentComponent(
new CodeableConcept(
new Coding("http://loinc.org", "8462-4", "Diastolic blood pressure")
)
).setValue(new Quantity().setValue(75).setUnit("mmHg").setSystem("http://unitsofmeasure.org").setCode("mm[Hg]"))
);

ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.put(observation1, BundleEntrySearchModeEnum.MATCH);
IFhirResourceDao<Observation> observationDao = registerResourceDaoWithNoData(Observation.class);
when(observationDao.search(any(), any())).thenReturn(new SimpleBundleProvider(Lists.newArrayList(observation1)));
registerRemainingResourceDaos();

// Test
Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType(PATIENT_ID), null);

// Verify
Composition compositions = (Composition) outcome.getEntry().get(0).getResource();
Composition.SectionComponent section = findSection(compositions, DefaultJpaIpsGenerationStrategy.SECTION_CODE_VITAL_SIGNS);

HtmlPage narrativeHtml = HtmlUtil.parseAsHtml(section.getText().getDivAsString());
ourLog.info("Narrative:\n{}", narrativeHtml.asXml());

DomNodeList<DomElement> tables = narrativeHtml.getElementsByTagName("table");
assertThat(tables).hasSize(1);
HtmlTable table = (HtmlTable) tables.get(0);
assertEquals("Code", table.getHeader().getRows().get(0).getCell(0).asNormalizedText());
assertEquals("Blood pressure panel with all children optional", table.getBodies().get(0).getRows().get(0).getCell(0).asNormalizedText());
assertEquals("Component(s)", table.getHeader().getRows().get(0).getCell(4).asNormalizedText());
assertEquals("Systolic blood pressure 125 , Diastolic blood pressure 75", table.getBodies().get(0).getRows().get(0).getCell(4).asNormalizedText());
}

@Nonnull
private Composition.SectionComponent findSection(Composition compositions, String theSectionCode) {
return compositions
Expand Down

0 comments on commit 37ab80e

Please sign in to comment.