Skip to content

Commit

Permalink
NIAD-2961-preserve-test-group-header-ordering (#693)
Browse files Browse the repository at this point in the history
* Add unit test to SpecimenCompoundsMapper to test ordering or result references.
Add associated test XML file.

* Update SpecimenCompoundsMapper to preserve ordering for result observation references when dealing with `CLUSTER` and `BATTERY`.

* Update PWTP9-output.json to reflect ordering changes above.
Update CHANGELOG.md
  • Loading branch information
MartinWheelerMT authored Jul 11, 2024
1 parent fdbb84a commit f408434
Show file tree
Hide file tree
Showing 5 changed files with 241 additions and 45 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
### Added
* The AllergyIntoleranceMapper has been enhanced to support the redaction fix. If an Allergy Intolerance record includes a confidentialityCode, the meta.security field of the corresponding FHIR resource will now be appropriately populated.

### Fixed
* DiagnosticReports now preserves original ordering for mapped result references.

## [3.0.0] - 2024-07-02

### Removed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13733,16 +13733,16 @@
} ],
"result": [ {
"reference": "Observation/00000000-0000-0000-0000-000000000006"
}, {
"reference": "Observation/B39D6784-5532-45FB-BDCC-7F7F21A70857"
}, {
"reference": "Observation/CDC2077F-F701-47F9-AFF9-87E8C07E8C69"
}, {
"reference": "Observation/05E6D1C6-9B66-4D02-8FC3-C5CF3CC0EB1C"
}, {
"reference": "Observation/CFAF8307-C6B6-4EB4-A69D-81DDE4D2CD91"
}, {
"reference": "Observation/45F8816D-FC99-4131-ABE6-01CD8A8DE49C"
}, {
"reference": "Observation/B39D6784-5532-45FB-BDCC-7F7F21A70857"
}, {
"reference": "Observation/CDC2077F-F701-47F9-AFF9-87E8C07E8C69"
} ],
"conclusion": " Clinical Details:\n NOTE:Specimen date"
}
Expand Down Expand Up @@ -13780,14 +13780,14 @@
} ],
"result": [ {
"reference": "Observation/00000000-0000-0000-0000-000000000007"
}, {
"reference": "Observation/15A8BA89-FF03-4880-B540-613745F6BFE3"
}, {
"reference": "Observation/471FD197-79B8-47C0-B5D1-6A4AFBA3D31D"
}, {
"reference": "Observation/59F968AD-21F1-4B97-96DE-D1AE22091132"
}, {
"reference": "Observation/36DA8895-114B-40D6-9FA1-82B543D86893"
}, {
"reference": "Observation/15A8BA89-FF03-4880-B540-613745F6BFE3"
} ]
}
}, {
Expand Down Expand Up @@ -13863,12 +13863,12 @@
} ],
"result": [ {
"reference": "Observation/00000000-0000-0000-0000-000000000008"
}, {
"reference": "Observation/0F1AD848-FA61-4278-B273-048B0E247D39"
}, {
"reference": "Observation/768BF90A-8E36-49D0-9D29-BD3FF0DE733D"
}, {
"reference": "Observation/69832EFE-727E-4270-BDF5-179851BDF295"
}, {
"reference": "Observation/0F1AD848-FA61-4278-B273-048B0E247D39"
} ],
"conclusion": " Infection"
}
Expand Down Expand Up @@ -13905,6 +13905,8 @@
"reference": "Specimen/EC494B1A-FFEF-41E2-94AB-B287AE516361"
} ],
"result": [ {
"reference": "Observation/8DDBEE57-3545-490B-BE7F-EEB352A5BB6F"
}, {
"reference": "Observation/E956E311-2809-400A-97E1-73F6CD15F98A"
}, {
"reference": "Observation/701F4CF2-3A60-4117-9DF5-8178D4FCA019"
Expand All @@ -13924,8 +13926,6 @@
"reference": "Observation/CF963013-BE3D-4845-865A-05722B1DB292"
}, {
"reference": "Observation/D04342AD-DD02-40A4-AE8B-CBB62588B448"
}, {
"reference": "Observation/8DDBEE57-3545-490B-BE7F-EEB352A5BB6F"
} ]
}
}, {
Expand Down Expand Up @@ -13961,11 +13961,11 @@
"reference": "Specimen/EFB6D2BC-B95B-42DE-A021-6C83225E7BAC"
} ],
"result": [ {
"reference": "Observation/5578BCAA-4C60-482E-ADFD-095CC8CAB102"
}, {
"reference": "Observation/EBEDD7EA-EE7A-4632-BEE4-FDDA9B4D4B11"
}, {
"reference": "Observation/2418B6B6-C4C0-46CB-9030-5B7DD39C80FC"
}, {
"reference": "Observation/5578BCAA-4C60-482E-ADFD-095CC8CAB102"
} ],
"conclusion": " No clinical details given"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import java.util.Objects;
import java.util.Optional;

import lombok.extern.slf4j.Slf4j;
import org.hl7.fhir.dstu3.model.CodeableConcept;
import org.hl7.fhir.dstu3.model.DiagnosticReport;
import org.hl7.fhir.dstu3.model.Encounter;
Expand All @@ -26,6 +27,7 @@
import org.hl7.v3.RCMRMT030101UKEhrExtract;
import org.hl7.v3.RCMRMT030101UKNarrativeStatement;
import org.hl7.v3.RCMRMT030101UKObservationStatement;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

Expand All @@ -36,6 +38,7 @@

import static uk.nhs.adaptors.common.util.CodeableConceptUtils.createCodeableConcept;

@Slf4j
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class SpecimenCompoundsMapper {
Expand All @@ -49,45 +52,52 @@ public class SpecimenCompoundsMapper {

private final SpecimenBatteryMapper batteryMapper;

public List<Observation> handleSpecimenChildComponents(RCMRMT030101UKEhrExtract ehrExtract, List<Observation> observations,
List<Observation> observationComments, List<DiagnosticReport> diagnosticReports,
Patient patient, List<Encounter> encounters, String practiseCode) {

public List<Observation> handleSpecimenChildComponents(
RCMRMT030101UKEhrExtract ehrExtract,
List<Observation> observations,
List<Observation> observationComments,
List<DiagnosticReport> diagnosticReports,
Patient patient,
List<Encounter> encounters,
String practiseCode
) {
final List<Observation> batteryObservations = new ArrayList<>();

for (var diagnosticReport : diagnosticReports) {

var diagnosticReportCompoundStatement = getCompoundStatementByDRId(ehrExtract, diagnosticReport.getId());
if (diagnosticReportCompoundStatement.isEmpty()) {
continue;
}

if (diagnosticReportCompoundStatement.isPresent()) {

for (var specimenCompoundStatement : getSpecimenCompoundStatements(diagnosticReportCompoundStatement.orElseThrow())) {
for (var specimenCompoundStatement : getSpecimenCompoundStatements(diagnosticReportCompoundStatement.get())) {
handleSpecimenObservationStatement(observations, diagnosticReport, specimenCompoundStatement);

for (var specimenObservationStatement : getObservationStatementsInCompound(specimenCompoundStatement)) {
getObservationById(observations, specimenObservationStatement.getId().getRoot())
.ifPresent(observation -> {
handleObservationStatement(specimenCompoundStatement, specimenObservationStatement, observation);
DiagnosticReportMapper.addResultToDiagnosticReport(observation, diagnosticReport);
});
}
var nestedSpecimenCompoundStatements = getNestedSpecimenCompoundStatements(specimenCompoundStatement);

for (var clusterCompoundStatement : getCompoundStatementsInSpecimenCompound(specimenCompoundStatement,
CLUSTER_CLASSCODE)) {
for (var nestedSpecimenCompoundStatement : nestedSpecimenCompoundStatements) {
if (CLUSTER_CLASSCODE.equals(nestedSpecimenCompoundStatement.getClassCode().get(0))) {
handleClusterCompoundStatement(
specimenCompoundStatement, clusterCompoundStatement, observations, observationComments, diagnosticReport,
specimenCompoundStatement,
nestedSpecimenCompoundStatement,
observations,
observationComments,
diagnosticReport,
false
);
}

for (var batteryCompoundStatement : getCompoundStatementsInSpecimenCompound(specimenCompoundStatement,
BATTERY_CLASSCODE)) {
if (BATTERY_CLASSCODE.equals(nestedSpecimenCompoundStatement.getClassCode().get(0))) {
handleBatteryCompoundStatement(
specimenCompoundStatement, batteryCompoundStatement, observations, observationComments, diagnosticReport
specimenCompoundStatement,
nestedSpecimenCompoundStatement,
observations,
observationComments,
diagnosticReport
);

final SpecimenBatteryParameters batteryParameters = SpecimenBatteryParameters.builder()
.ehrExtract(ehrExtract)
.batteryCompoundStatement(batteryCompoundStatement)
.batteryCompoundStatement(nestedSpecimenCompoundStatement)
.specimenCompoundStatement(specimenCompoundStatement)
.ehrComposition(getCurrentEhrComposition(ehrExtract, diagnosticReportCompoundStatement.orElseThrow()))
.diagnosticReport(diagnosticReport)
Expand All @@ -107,6 +117,30 @@ public List<Observation> handleSpecimenChildComponents(RCMRMT030101UKEhrExtract
return batteryObservations;
}

private static @NotNull List<RCMRMT030101UKCompoundStatement> getNestedSpecimenCompoundStatements(
RCMRMT030101UKCompoundStatement specimenCompoundStatement
) {
return specimenCompoundStatement.getComponent()
.stream()
.filter(RCMRMT030101UKComponent02::hasCompoundStatement)
.map(RCMRMT030101UKComponent02::getCompoundStatement)
.filter(SpecimenCompoundsMapper::hasBatteryOrClusterClassCode)
.toList();
}

private void handleSpecimenObservationStatement(
List<Observation> observations,
DiagnosticReport diagnosticReport,
RCMRMT030101UKCompoundStatement specimenCompoundStatement
) {
getObservationStatementsInCompound(specimenCompoundStatement).forEach(specimenObservationStatement ->
getObservationById(observations, specimenObservationStatement.getId().getRoot())
.ifPresent(observation -> {
handleObservationStatement(specimenCompoundStatement, specimenObservationStatement, observation);
DiagnosticReportMapper.addResultToDiagnosticReport(observation, diagnosticReport);
}));
}

private void handleObservationStatement(RCMRMT030101UKCompoundStatement specimenCompoundStatement,
RCMRMT030101UKObservationStatement observationStatement, Observation observation) {
final Reference specimenReference = new Reference(new IdType(
Expand Down Expand Up @@ -282,17 +316,6 @@ private List<RCMRMT030101UKNarrativeStatement> getNarrativeStatementsInCompound(
.toList();
}

private List<RCMRMT030101UKCompoundStatement> getCompoundStatementsInSpecimenCompound(
RCMRMT030101UKCompoundStatement specimenCompoundStatement, String classCode) {

return specimenCompoundStatement.getComponent()
.stream()
.filter(RCMRMT030101UKComponent02::hasCompoundStatement)
.map(RCMRMT030101UKComponent02::getCompoundStatement)
.filter(compoundStatement -> classCode.equals(compoundStatement.getClassCode().get(0)))
.toList();
}

private RCMRMT030101UKEhrComposition getCurrentEhrComposition(RCMRMT030101UKEhrExtract ehrExtract,
RCMRMT030101UKCompoundStatement parentCompoundStatement) {

Expand All @@ -306,4 +329,9 @@ private RCMRMT030101UKEhrComposition getCurrentEhrComposition(RCMRMT030101UKEhrE
.anyMatch(parentCompoundStatement::equals)
).findFirst().get();
}

private static boolean hasBatteryOrClusterClassCode(RCMRMT030101UKCompoundStatement compoundStatement) {
return CLUSTER_CLASSCODE.equals(compoundStatement.getClassCode().get(0))
|| BATTERY_CLASSCODE.equals(compoundStatement.getClassCode().get(0));
}
}
Original file line number Diff line number Diff line change
@@ -1,30 +1,40 @@
package uk.nhs.adaptors.pss.translator.mapper.diagnosticreport;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.mockito.AdditionalAnswers.answer;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.springframework.util.ResourceUtils.getFile;
import static uk.nhs.adaptors.pss.translator.mapper.diagnosticreport.SpecimenBatteryMapper.SpecimenBatteryParameters;
import static uk.nhs.adaptors.pss.translator.util.XmlUnmarshallUtil.unmarshallFile;

import java.util.ArrayList;
import java.util.List;

import org.hl7.fhir.dstu3.model.DiagnosticReport;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.Observation;
import org.hl7.fhir.dstu3.model.Patient;
import org.hl7.fhir.dstu3.model.Reference;
import org.hl7.fhir.dstu3.model.ResourceType;
import org.hl7.v3.RCMRMT030101UKEhrExtract;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.junit.jupiter.MockitoExtension;

import lombok.SneakyThrows;
import org.mockito.stubbing.Answer1;

@ExtendWith(MockitoExtension.class)
@RunWith(MockitoJUnitRunner.class)
public class SpecimenCompoundsMapperTest {

private static final String XML_RESOURCES_BASE = "xml/SpecimenComponents/";
Expand Down Expand Up @@ -159,6 +169,54 @@ public void testHandlingNonUserNarrativeStatement() {
assertThat(observation.getIssuedElement().asStringValue()).isNull();
}

@Test public void testOrderingIsPreservedForDiagnosticReportResults() {
final RCMRMT030101UKEhrExtract ehrExtract =
unmarshallEhrExtract("specimen_with_three_test_group_headers.xml");

final var testObservations = List.of(
(Observation) new Observation().setId("TEST-GROUP-HEADER-1"),
(Observation) new Observation().setId("OBSERVATION-STATEMENT-ID-1"),
(Observation) new Observation().setId("TEST-GROUP-HEADER-2"),
(Observation) new Observation().setId("OBSERVATION-STATEMENT-ID-2"),
(Observation) new Observation().setId("TEST-GROUP-HEADER-3"),
(Observation) new Observation().setId("OBSERVATION-STATEMENT-ID-3")
);

doAnswer(
answer(
(Answer1<Observation, SpecimenBatteryParameters>) batteryParameters -> {
var id = batteryParameters.getBatteryCompoundStatement().getId().get(0).getRoot();
var observation = new Observation();
observation.setId(id);

batteryParameters.getDiagnosticReport().addResult(
new Reference(new IdType(ResourceType.Observation.name(), id)));
return observation;
})
).when(specimenBatteryMapper).mapBatteryObservation(any(SpecimenBatteryMapper.SpecimenBatteryParameters.class));

specimenCompoundsMapper.handleSpecimenChildComponents(
ehrExtract,
testObservations,
observationComments,
diagnosticReports,
PATIENT,
List.of(),
TEST_PRACTISE_CODE
);

var diagnosticReport = diagnosticReports.get(0);

assertAll(
() -> assertThat(getReferenceId(diagnosticReport.getResult().get(0)))
.isEqualTo("Observation/TEST-GROUP-HEADER-1"),
() -> assertThat(getReferenceId(diagnosticReport.getResult().get(1)))
.isEqualTo("OBSERVATION-STATEMENT-ID-2"),
() -> assertThat(getReferenceId(diagnosticReport.getResult().get(2)))
.isEqualTo("OBSERVATION-STATEMENT-ID-3")
);
}

private void assertParentSpecimenIsReferenced(Observation observation) {
assertThat(observation.hasSpecimen()).isTrue();
assertThat(observation.getSpecimen().hasReference()).isTrue();
Expand Down Expand Up @@ -203,6 +261,13 @@ private List<Observation> createObservationComments() {
return observationComments;
}

private String getReferenceId(Reference reference) {
return reference.hasReference()
? reference.getReference()
: reference.getResource().getIdElement().getValue();
}


@SneakyThrows
private RCMRMT030101UKEhrExtract unmarshallEhrExtract(String fileName) {
return unmarshallFile(getFile("classpath:" + XML_RESOURCES_BASE + fileName), RCMRMT030101UKEhrExtract.class);
Expand Down
Loading

0 comments on commit f408434

Please sign in to comment.