Skip to content

Commit a8ce62e

Browse files
martin-nhsAlex-Nitastevenmccullaghmadetechadrianclay
authored
NIAD-3217 - Send Observation (Test Result) when they do not have a Specimen attached to them (#963)
--------- Co-authored-by: Alex-Nita <[email protected]> Co-authored-by: Steven McCullagh <[email protected]> Co-authored-by: Alex-Nita <[email protected]> Co-authored-by: Adrian Clay <[email protected]>
1 parent 117e72a commit a8ce62e

16 files changed

+203
-48
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77
## [Unreleased]
88

99
## Added
10-
10+
* When mapping a `DiagnosticReport` which contains a `TestResult` without a `Specimen` attached to it, a new `Specimen` is
11+
created to allow the `TestResult` to be mapped correctly.
1112
* When mapping a `DocumentReference` which contains a `NOPAT` `meta.security` or `NOPAT` `securityLabel` tag the resultant XML for that resource
1213
will contain a `NOPAT` `confidentialityCode` element.
1314
* When mapping `AllergyIntolerances` which contain a `NOPAT` `meta.security` tag the resultant XML for that resource

service/src/main/java/uk/nhs/adaptors/gp2gp/ehr/mapper/diagnosticreport/DiagnosticReportMapper.java

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@
88
import static uk.nhs.adaptors.gp2gp.ehr.mapper.CommentType.LABORATORY_RESULT_COMMENT;
99
import static uk.nhs.adaptors.gp2gp.ehr.mapper.diagnosticreport.ObservationMapper.NARRATIVE_STATEMENT_TEMPLATE;
1010

11+
import java.util.ArrayList;
1112
import java.util.Collections;
1213
import java.util.List;
1314
import java.util.Optional;
15+
import java.util.function.Predicate;
1416
import java.util.stream.Collectors;
1517
import java.util.stream.Stream;
1618

@@ -73,13 +75,19 @@ public class DiagnosticReportMapper {
7375
private final ConfidentialityService confidentialityService;
7476

7577
public String mapDiagnosticReportToCompoundStatement(DiagnosticReport diagnosticReport) {
76-
List<Specimen> specimens = fetchSpecimens(diagnosticReport);
7778
List<Observation> observations = fetchObservations(diagnosticReport);
79+
List<Specimen> specimens = fetchSpecimens(diagnosticReport, observations);
7880
final IdMapper idMapper = messageContext.getIdMapper();
7981
markObservationsAsProcessed(idMapper, observations);
8082

83+
List<Observation> observationsExcludingFilingComments = assignDummySpecimensToObservationsWithNoSpecimen(
84+
observations.stream()
85+
.filter(Predicate.not(DiagnosticReportMapper::isFilingComment))
86+
.toList(),
87+
specimens);
88+
8189
String mappedSpecimens = specimens.stream()
82-
.map(specimen -> specimenMapper.mapSpecimenToCompoundStatement(specimen, observations, diagnosticReport))
90+
.map(specimen -> specimenMapper.mapSpecimenToCompoundStatement(specimen, observationsExcludingFilingComments, diagnosticReport))
8391
.collect(Collectors.joining());
8492

8593
String reportLevelNarrativeStatements = prepareReportLevelNarrativeStatements(diagnosticReport, observations);
@@ -113,21 +121,60 @@ private String fetchExtensionId(List<Identifier> identifiers) {
113121
.orElse(StringUtils.EMPTY);
114122
}
115123

116-
private List<Specimen> fetchSpecimens(DiagnosticReport diagnosticReport) {
117-
if (!diagnosticReport.hasSpecimen()) {
118-
return Collections.singletonList(generateDefaultSpecimen(diagnosticReport));
124+
private List<Specimen> fetchSpecimens(DiagnosticReport diagnosticReport, List<Observation> observations) {
125+
126+
List<Specimen> specimens = new ArrayList<>();
127+
128+
// At least one specimen is required to exist for any DiagnosticReport, according to the mim
129+
if (!diagnosticReport.hasSpecimen() || hasObservationsWithoutSpecimen(observations)) {
130+
specimens.add(generateDummySpecimen(diagnosticReport));
119131
}
120132

121133
var inputBundleHolder = messageContext.getInputBundleHolder();
122-
return diagnosticReport.getSpecimen()
134+
List<Specimen> nonDummySpecimens = diagnosticReport.getSpecimen()
123135
.stream()
124136
.map(specimenReference -> inputBundleHolder.getResource(specimenReference.getReferenceElement()))
125137
.flatMap(Optional::stream)
126138
.map(Specimen.class::cast)
127139
.collect(Collectors.toList());
140+
141+
specimens.addAll(nonDummySpecimens);
142+
143+
return specimens;
144+
145+
}
146+
147+
private boolean hasObservationsWithoutSpecimen(List<Observation> observations) {
148+
return observations
149+
.stream()
150+
.filter(observation -> !isFilingComment(observation))
151+
.anyMatch(observation -> !observation.hasSpecimen());
152+
}
153+
154+
private List<Observation> assignDummySpecimensToObservationsWithNoSpecimen(
155+
List<Observation> observations, List<Specimen> specimens) {
156+
157+
if (!hasObservationsWithoutSpecimen(observations)) {
158+
return observations;
159+
}
160+
161+
// The assumption was made that all test results without a specimen will have the same dummy specimen referenced
162+
Specimen dummySpecimen = specimens.stream()
163+
.filter(specimen -> specimen.getId().contains(DUMMY_SPECIMEN_ID_PREFIX))
164+
.toList().getFirst();
165+
166+
Reference dummySpecimenReference = new Reference(dummySpecimen.getId());
167+
168+
for (Observation observation : observations) {
169+
if (!observation.hasSpecimen() && !isFilingComment(observation)) {
170+
observation.setSpecimen(dummySpecimenReference);
171+
}
172+
}
173+
174+
return observations;
128175
}
129176

130-
private Specimen generateDefaultSpecimen(DiagnosticReport diagnosticReport) {
177+
private Specimen generateDummySpecimen(DiagnosticReport diagnosticReport) {
131178
Specimen specimen = new Specimen();
132179

133180
specimen.setId(DUMMY_SPECIMEN_ID_PREFIX + randomIdGeneratorService.createNewId());

service/src/main/java/uk/nhs/adaptors/gp2gp/ehr/mapper/diagnosticreport/SpecimenMapper.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@
4343
import java.util.List;
4444
import java.util.Objects;
4545
import java.util.Optional;
46-
import java.util.function.Predicate;
4746
import java.util.stream.Collectors;
4847
import java.util.stream.Stream;
4948

@@ -149,7 +148,6 @@ private String mapObservationsAssociatedWithSpecimen(Specimen specimen, List<Obs
149148
}
150149

151150
return observationsAssociatedWithSpecimen.stream()
152-
.filter(Predicate.not(DiagnosticReportMapper::isFilingComment))
153151
.map(observationMapper::mapObservationToCompoundStatement)
154152
.collect(Collectors.joining());
155153
}

service/src/test/java/uk/nhs/adaptors/gp2gp/ehr/mapper/diagnosticreport/DiagnosticReportMapperTest.java

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import static org.mockito.Mockito.when;
77

88
import java.io.IOException;
9+
import java.util.ArrayList;
910
import java.util.Collections;
1011
import java.util.List;
1112
import java.util.Optional;
@@ -76,7 +77,6 @@ class DiagnosticReportMapperTest {
7677
private static final String INPUT_JSON_MULTIPLE_CODED_DIAGNOSIS = "diagnostic-report-with-multiple-coded-diagnosis.json";
7778
private static final String INPUT_JSON_EXTENSION_ID = "diagnostic-report-with-extension-id.json";
7879
private static final String INPUT_JSON_URN_OID_EXTENSION_ID = "diagnostic-report-with-urn-oid-extension-id.json";
79-
private static final String INPUT_JSON_UNRELATED_TEST_RESULT = "diagnostic-report-with-one-specimen-and-one-unrelated-observation.json";
8080

8181
private static final String OUTPUT_XML_REQUIRED_DATA = "diagnostic-report-with-required-data.xml";
8282
private static final String OUTPUT_XML_STATUS_NARRATIVE = "diagnostic-report-with-status-narrative.xml";
@@ -88,7 +88,6 @@ class DiagnosticReportMapperTest {
8888
private static final String OUTPUT_XML_MULTIPLE_CODED_DIAGNOSIS = "diagnostic-report-with-multiple-coded-diagnosis.xml";
8989
private static final String OUTPUT_XML_EXTENSION_ID = "diagnostic-report-with-extension-id.xml";
9090
private static final String OUTPUT_XML_MULTIPLE_RESULTS = "diagnostic-report-with-multiple-results.xml";
91-
private static final String OUTPUT_XML_UNRELATED_TEST_RESULT = "diagnostic-report-with-one-specimen-and-one-unrelated-observation.xml";
9291

9392
@Mock
9493
private CodeableConceptCdMapper codeableConceptCdMapper;
@@ -293,6 +292,44 @@ void When_DiagnosticReport_With_NoReferencedSpecimenAndFilingCommentWithNoCommen
293292
assertThat(actualXml).isEqualToIgnoringWhitespace(expectedXml);
294293
}
295294

295+
/**
296+
* A Diagnosis Report may have an Observation (Test Result) and Specimen. If the result and specimen are not
297+
* linked then we need to create a dummy specimen linked to the result.
298+
*/
299+
@Test
300+
void When_DiagnosticReport_Has_SpecimenAndUnlinkedTestResult_Expect_ADummySpecimenLinkedToTestResult() {
301+
final String diagnosticReportFileName = "diagnostic-report-with-one-specimen-and-one-unrelated-observation.json";
302+
final DiagnosticReport diagnosticReport = getDiagnosticReportResourceFromJson(diagnosticReportFileName);
303+
final Bundle bundle = getBundleResourceFromJson(INPUT_JSON_BUNDLE);
304+
final InputBundle inputBundle = new InputBundle(bundle);
305+
306+
when(messageContext.getInputBundleHolder()).thenReturn(inputBundle);
307+
308+
final String actualXml = mapper.mapDiagnosticReportToCompoundStatement(diagnosticReport);
309+
310+
// This checks that the unlinked test result is given a dummy specimen.
311+
assertThat(actualXml).containsIgnoringWhitespaces(
312+
"<!-- Mapped Specimen with id: DUMMY-SPECIMEN-5E496953-065B-41F2-9577-BE8F2FBD0757 "
313+
+ "with linked Observations: Observation/TestResult-WithoutSpecimenReference-->");
314+
}
315+
316+
@Test
317+
void When_DiagnosticReport_Has_SpecimenALinkedTestResultAndAnUnlinkedTestResult_Expect_ASpecimenOnAllTestResults() {
318+
final String diagnosticReportFileName =
319+
"diagnostic-report-with-one-specimen-one-linked-observation-and-one-unlinked-observation.json";
320+
final DiagnosticReport diagnosticReport = getDiagnosticReportResourceFromJson(diagnosticReportFileName);
321+
final Bundle bundle = getBundleResourceFromJson(INPUT_JSON_BUNDLE);
322+
final InputBundle inputBundle = new InputBundle(bundle);
323+
when(messageContext.getInputBundleHolder()).thenReturn(inputBundle);
324+
325+
final String actualXml = mapper.mapDiagnosticReportToCompoundStatement(diagnosticReport);
326+
// This checks that the unlinked test result is given a dummy specimen.
327+
assertThat(actualXml).containsIgnoringWhitespaces(
328+
"<!-- Mapped Specimen with id: DUMMY-SPECIMEN-5E496953-065B-41F2-9577-BE8F2FBD0757 "
329+
+ "with linked Observations: Observation/TestResult-WithoutSpecimenReference-->");
330+
331+
}
332+
296333
private Bundle getBundleResourceFromJson(String filename) {
297334
final String filePath = TEST_FILE_DIRECTORY + filename;
298335
return FileParsingUtility.parseResourceFromJsonFile(filePath, Bundle.class);
@@ -318,8 +355,7 @@ private static Stream<Arguments> resourceFileParams() {
318355
Arguments.of(INPUT_JSON_CODED_DIAGNOSIS, OUTPUT_XML_CODED_DIAGNOSIS),
319356
Arguments.of(INPUT_JSON_MULTIPLE_CODED_DIAGNOSIS, OUTPUT_XML_MULTIPLE_CODED_DIAGNOSIS),
320357
Arguments.of(INPUT_JSON_EXTENSION_ID, OUTPUT_XML_EXTENSION_ID),
321-
Arguments.of(INPUT_JSON_URN_OID_EXTENSION_ID, OUTPUT_XML_EXTENSION_ID),
322-
Arguments.of(INPUT_JSON_UNRELATED_TEST_RESULT, OUTPUT_XML_UNRELATED_TEST_RESULT)
358+
Arguments.of(INPUT_JSON_URN_OID_EXTENSION_ID, OUTPUT_XML_EXTENSION_ID)
323359
);
324360
}
325361

@@ -341,7 +377,23 @@ private Answer<String> mockIdForReference() {
341377
private Answer<String> mockSpecimenMapping() {
342378
return invocation -> {
343379
Specimen specimen = invocation.getArgument(0);
344-
return String.format("<!-- Mapped Specimen with id: %s -->", specimen.getId());
380+
List<Observation> observations = invocation.getArgument(1);
381+
382+
List<String> linkedObservations = new ArrayList<>();
383+
384+
for (Observation observation : observations) {
385+
if (observation.getSpecimen().getReference() != null
386+
&& observation.getSpecimen().getReference().equals(specimen.getId())) {
387+
linkedObservations.add(observation.getId());
388+
}
389+
}
390+
391+
if (linkedObservations.isEmpty()) {
392+
return String.format("<!-- Mapped Specimen with id: %s -->", specimen.getId());
393+
}
394+
return String.format("<!-- Mapped Specimen with id: %s with linked Observations: %s-->",
395+
specimen.getId(),
396+
String.join(",", linkedObservations));
345397
};
346398
}
347399
}

service/src/test/resources/ehr/mapper/diagnosticreport/diagnostic-report-with-coded-diagnosis.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,6 @@ Status: unknown</text>
3030
<availabilityTime value="20100225154100"/>
3131
</NarrativeStatement>
3232
</component>
33-
<!-- Mapped Specimen with id: DUMMY-SPECIMEN-5E496953-065B-41F2-9577-BE8F2FBD0757 -->
33+
<!-- Mapped Specimen with id: DUMMY-SPECIMEN-5E496953-065B-41F2-9577-BE8F2FBD0757 with linked Observations: DUMMY-OBSERVATION-5E496953-065B-41F2-9577-BE8F2FBD0757-->
3434
</CompoundStatement>
3535
</component>

service/src/test/resources/ehr/mapper/diagnosticreport/diagnostic-report-with-conclusion.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,6 @@ Status: unknown</text>
3030
<availabilityTime value="20100225154100"/>
3131
</NarrativeStatement>
3232
</component>
33-
<!-- Mapped Specimen with id: DUMMY-SPECIMEN-5E496953-065B-41F2-9577-BE8F2FBD0757 -->
33+
<!-- Mapped Specimen with id: DUMMY-SPECIMEN-5E496953-065B-41F2-9577-BE8F2FBD0757 with linked Observations: DUMMY-OBSERVATION-5E496953-065B-41F2-9577-BE8F2FBD0757-->
3434
</CompoundStatement>
3535
</component>

service/src/test/resources/ehr/mapper/diagnosticreport/diagnostic-report-with-extension-id.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,6 @@ Status: unknown</text>
2121
<availabilityTime value="20100225154100"/>
2222
</NarrativeStatement>
2323
</component>
24-
<!-- Mapped Specimen with id: DUMMY-SPECIMEN-5E496953-065B-41F2-9577-BE8F2FBD0757 -->
24+
<!-- Mapped Specimen with id: DUMMY-SPECIMEN-5E496953-065B-41F2-9577-BE8F2FBD0757 with linked Observations: DUMMY-OBSERVATION-5E496953-065B-41F2-9577-BE8F2FBD0757-->
2525
</CompoundStatement>
2626
</component>

service/src/test/resources/ehr/mapper/diagnosticreport/diagnostic-report-with-multi-specimens.xml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ Status: unknown</text>
2020
<availabilityTime value="20100225154100"/>
2121
</NarrativeStatement>
2222
</component>
23-
<!-- Mapped Specimen with id: Specimen/96B93E28-293D-46E7-B4C2-D477EEBF7098-SPEC-0 --><!-- Mapped Specimen with id: Specimen/96B93E28-293D-46E7-B4C2-D477EEBF7098-SPEC-1 --><!-- Mapped Specimen with id: Specimen/96B93E28-293D-46E7-B4C2-D477EEBF7098-SPEC-2 -->
23+
<!-- Mapped Specimen with id: DUMMY-SPECIMEN-5E496953-065B-41F2-9577-BE8F2FBD0757 with linked Observations: DUMMY-OBSERVATION-5E496953-065B-41F2-9577-BE8F2FBD0757-->
24+
<!-- Mapped Specimen with id: Specimen/96B93E28-293D-46E7-B4C2-D477EEBF7098-SPEC-0 -->
25+
<!-- Mapped Specimen with id: Specimen/96B93E28-293D-46E7-B4C2-D477EEBF7098-SPEC-1 -->
26+
<!-- Mapped Specimen with id: Specimen/96B93E28-293D-46E7-B4C2-D477EEBF7098-SPEC-2 -->
2427
</CompoundStatement>
2528
</component>

service/src/test/resources/ehr/mapper/diagnosticreport/diagnostic-report-with-multiple-coded-diagnosis.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,6 @@ Status: unknown</text>
3030
<availabilityTime value="20100225154100"/>
3131
</NarrativeStatement>
3232
</component>
33-
<!-- Mapped Specimen with id: DUMMY-SPECIMEN-5E496953-065B-41F2-9577-BE8F2FBD0757 -->
33+
<!-- Mapped Specimen with id: DUMMY-SPECIMEN-5E496953-065B-41F2-9577-BE8F2FBD0757 with linked Observations: DUMMY-OBSERVATION-5E496953-065B-41F2-9577-BE8F2FBD0757-->
3434
</CompoundStatement>
3535
</component>

service/src/test/resources/ehr/mapper/diagnosticreport/diagnostic-report-with-one-specimen-and-one-unrelated-observation.xml

Lines changed: 0 additions & 25 deletions
This file was deleted.

0 commit comments

Comments
 (0)