Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Fixed

* Prevent duplicate processing: after a medication request is processed, the system no longer pulls a subsequent non‑consultation item based on the same request.

### Added
* The GP2GP Adaptor now validates references inside condition objects to test whether they actually exist. Otherwise, the bundle is rejected.
* The GP2GP Adaptor now adds the EhrComposition / confidentialityCode field when Encounter.meta.security contains NOPAT security entry
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.hl7.fhir.dstu3.model.DiagnosticReport;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.ListResource;
import org.hl7.fhir.dstu3.model.MedicationRequest;
import org.hl7.fhir.dstu3.model.Observation;
import org.hl7.fhir.dstu3.model.Reference;
import org.hl7.fhir.dstu3.model.Resource;
Expand All @@ -25,6 +26,7 @@
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import uk.nhs.adaptors.gp2gp.common.service.RandomIdGeneratorService;
import uk.nhs.adaptors.gp2gp.ehr.exception.EhrMapperException;
import uk.nhs.adaptors.gp2gp.ehr.mapper.parameters.EncounterTemplateParameters;
import uk.nhs.adaptors.gp2gp.ehr.mapper.parameters.EncounterTemplateParameters.EncounterTemplateParametersBuilder;
import uk.nhs.adaptors.gp2gp.ehr.utils.BloodPressureValidator;
Expand Down Expand Up @@ -72,12 +74,12 @@ public class NonConsultationResourceMapper {
);

public List<String> mapRemainingResourcesToEhrCompositions(Bundle bundle) {
var mappedResources = bundle.getEntry()
.stream()

var mappedResources = bundle.getEntry().stream()
.map(Bundle.BundleEntryComponent::getResource)
.filter(this::isMappableNonConsultationResource)
.sorted(this::compareProcessingOrder)
.filter(resource -> !hasIdBeenMapped(resource) && !isIgnoredResource(resource))
.filter(this::shouldMapResource)
.map(this::mapResourceToEhrComposition)
.flatMap(Optional::stream)
.collect(Collectors.toList());
Expand All @@ -101,6 +103,32 @@ public List<String> mapRemainingResourcesToEhrCompositions(Bundle bundle) {
return mappedResources;
}

boolean shouldMapResource(Resource resource) {
if (hasIdBeenMapped(resource) || isIgnoredResource(resource)) {
return false;
}
if (resource instanceof MedicationRequest medicationRequest) {
return shouldMapMedicationRequest(medicationRequest);
}
return true;
}

boolean shouldMapMedicationRequest(MedicationRequest medicationRequest) {
if (!medicationRequest.hasBasedOn()) {
return true;
}

String referenceId = medicationRequest.getBasedOn().getFirst().getReference();

try {
Optional<Resource> referencedResource = messageContext.getInputBundleHolder().getResource(new IdType(referenceId));
return referencedResource.isEmpty() || !hasIdBeenMapped(referencedResource.get());
} catch (EhrMapperException e) {
LOGGER.info("MedicationRequest {} cannot be mapped", referenceId);
return true;
}
}

private Resource replaceId(Resource resource) {
resource.setIdElement(new IdType(resource.getResourceType().name(), randomIdGeneratorService.createNewId()));
return resource;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,23 @@

@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
public class EhrExtractMapperComponentTest {
class EhrExtractMapperComponentTest {

private static final String TEST_FILE_DIRECTORY = "/ehr/request/fhir/";
private static final String INPUT_DIRECTORY = "input/";
private static final String OUTPUT_DIRECTORY = "output/";
private static final String INPUT_PATH = TEST_FILE_DIRECTORY + INPUT_DIRECTORY;
private static final String OUTPUT_PATH = TEST_FILE_DIRECTORY + OUTPUT_DIRECTORY;

private static final String JSON_INPUT_FILE = "gpc-access-structured.json";
private static final String JSON_INPUT_FILE_WITH_NOPAT = "gpc-access-structured-with-nopat.json";
private static final String DUPLICATE_RESOURCE_BUNDLE = INPUT_PATH + "duplicated-resource-bundle.json";
private static final String ONE_CONSULTATION_RESOURCE_BUNDLE = INPUT_PATH + "1-consultation-resource.json";
private static final String FHIR_BUNDLE_WITH_DUPLICATED_MEDICATION_REQUESTS = "fhir_bundle_with_duplicated_medication_requests.json";
private static final String EXPECTED_XML_FOR_ONE_CONSULTATION_RESOURCE = "ExpectedResponseFrom1ConsultationResponse.xml";

private static final String EXPECTED_XML_TO_JSON_FILE = "expected-ehr-extract-response-from-json.xml";
private static final String EXPECTED_XML_TO_JSON_FILE_WITH_NOPAT = "expected-ehr-extract-response-from-json-with-nopat.xml";

private static final String FHIR_BUNDLE_WITHOUT_EFFECTIVE_TIME = "fhir-bundle-without-effective-time.json";
private static final String FHIR_BUNDLE_WITHOUT_HIGH_EFFECTIVE_TIME = "fhir-bundle-without-high-effective-time.json";
private static final String FHIR_BUNDLE_WITH_EFFECTIVE_TIME = "fhir-bundle-with-effective-time.json";
Expand All @@ -63,12 +69,7 @@ public class EhrExtractMapperComponentTest {
private static final String FHIR_BUNDLE_WITH_OBSERVATIONS_UNRELATED_TO_DIAGNOSTIC_REPORT =
"fhir-bundle-observations-unrelated-to-diagnostic-report.json";
private static final String FHIR_BUNDLE_WITH_OBSERVATIONS_WITH_RELATED_OBSERVATIONS =
"fhir-bundle-observations-with-related-observations.json";

private static final String EXPECTED_XML_FOR_ONE_CONSULTATION_RESOURCE = "ExpectedResponseFrom1ConsultationResponse.xml";

private static final String EXPECTED_XML_TO_JSON_FILE = "expected-ehr-extract-response-from-json.xml";
private static final String EXPECTED_XML_TO_JSON_FILE_WITH_NOPAT = "expected-ehr-extract-response-from-json-with-nopat.xml";
"fhir-bundle-observations-with-related-observations.json";
private static final String EXPECTED_XML_WITHOUT_EFFECTIVE_TIME = "expected-xml-without-effective-time.xml";
private static final String EXPECTED_XML_WITHOUT_HIGH_EFFECTIVE_TIME = "expected-xml-without-high-effective-time.xml";
private static final String EXPECTED_XML_WITH_EFFECTIVE_TIME = "expected-xml-with-effective-time.xml";
Expand Down Expand Up @@ -231,7 +232,7 @@ public void tearDown() {
}

@Test
public void When_MappingUncategorizedObservationWithNOPAT_Expect_ObservationStatementWithConfidentialityCode() {
void When_MappingUncategorizedObservationWithNOPAT_Expect_ObservationStatementWithConfidentialityCode() {

String expectedJsonToXmlContent = ResourceTestFileUtils.getFileContent(OUTPUT_PATH + EXPECTED_XML_TO_JSON_FILE_WITH_NOPAT);
String inputJsonFileContent = ResourceTestFileUtils.getFileContent(INPUT_PATH + JSON_INPUT_FILE_WITH_NOPAT);
Expand Down Expand Up @@ -259,7 +260,7 @@ public void When_MappingUncategorizedObservationWithNOPAT_Expect_ObservationStat

@ParameterizedTest
@MethodSource("testData")
public void When_MappingProperJsonRequestBody_Expect_ProperXmlOutput(String input, String expected) {
void When_MappingProperJsonRequestBody_Expect_ProperXmlOutput(String input, String expected) {
String expectedJsonToXmlContent = ResourceTestFileUtils.getFileContent(OUTPUT_PATH + expected);
String inputJsonFileContent = ResourceTestFileUtils.getFileContent(INPUT_PATH + input);
Bundle bundle = new FhirParseService().parseResource(inputJsonFileContent, Bundle.class);
Expand All @@ -274,6 +275,20 @@ public void When_MappingProperJsonRequestBody_Expect_ProperXmlOutput(String inpu
assertThat(output).isEqualToIgnoringWhitespace(expectedJsonToXmlContent);
}

@Test
void When_MappingProperJsonRequestBody_Expect_NonDuplicatedMedicationRequestRemainingResources() {
String inputJsonFileContent =
ResourceTestFileUtils.getFileContent(INPUT_PATH + FHIR_BUNDLE_WITH_DUPLICATED_MEDICATION_REQUESTS);
Bundle bundle = new FhirParseService().parseResource(inputJsonFileContent, Bundle.class);
messageContext.initialize(bundle);

EhrExtractTemplateParameters ehrExtractTemplateParameters = ehrExtractMapper.mapBundleToEhrFhirExtractParams(
getGpcStructuredTaskDefinition,
bundle);

assertThat(ehrExtractTemplateParameters.getComponents()).hasSize(2);
}

private static Stream<Arguments> testData() {
return Stream.of(
Arguments.of(JSON_INPUT_FILE, EXPECTED_XML_TO_JSON_FILE),
Expand All @@ -288,7 +303,7 @@ private static Stream<Arguments> testData() {
}

@Test
public void When_MappingJsonBody_Expect_OnlyOneConsultationResource() {
void When_MappingJsonBody_Expect_OnlyOneConsultationResource() {
String expectedJsonToXmlContent = ResourceTestFileUtils.getFileContent(OUTPUT_PATH + EXPECTED_XML_FOR_ONE_CONSULTATION_RESOURCE);
String inputJsonFileContent = ResourceTestFileUtils.getFileContent(ONE_CONSULTATION_RESOURCE_BUNDLE);
Bundle bundle = new FhirParseService().parseResource(inputJsonFileContent, Bundle.class);
Expand All @@ -302,7 +317,7 @@ public void When_MappingJsonBody_Expect_OnlyOneConsultationResource() {
}

@Test
public void When_TransformingResourceToEhrComp_Expect_NoDuplicateMappings() {
void When_TransformingResourceToEhrComp_Expect_NoDuplicateMappings() {
String bundle = ResourceTestFileUtils.getFileContent(DUPLICATE_RESOURCE_BUNDLE);
Bundle parsedBundle = new FhirParseService().parseResource(bundle, Bundle.class);
messageContext.initialize(parsedBundle);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package uk.nhs.adaptors.gp2gp.ehr.mapper;

import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.MedicationRequest;
import org.hl7.fhir.dstu3.model.QuestionnaireResponse;
import org.hl7.fhir.dstu3.model.Reference;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import uk.nhs.adaptors.gp2gp.ehr.utils.IgnoredResourcesUtils;
import org.hl7.fhir.dstu3.model.ResourceType;
import java.util.Optional;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
class EhrExtractResourceMapperTest {

@Mock
private MessageContext messageContext;

@Mock
private InputBundle inputBundleHolder;

@Mock
private IdMapper idMapper;

@InjectMocks
private NonConsultationResourceMapper resourceMapper;

@BeforeEach
public void setUp() {
when(messageContext.getInputBundleHolder()).thenReturn(inputBundleHolder);
when(messageContext.getIdMapper()).thenReturn(idMapper);
when(idMapper.hasIdBeenMapped(any(), any())).thenReturn(false);
}

@AfterEach
public void tearDown() {
messageContext.resetMessageContext();
}

@Test
void When_ReferencedResourceIsEmpty_Expect_MapMedicationRequest() {

MedicationRequest medRequest = new MedicationRequest();
medRequest.setId("MedicationRequest/1");
medRequest.addBasedOn(new Reference("ServiceRequest/123"));
when(inputBundleHolder.getResource(new IdType("ServiceRequest/123"))).thenReturn(Optional.empty());

boolean result = resourceMapper.shouldMapResource(medRequest);

assertTrue(result);
}

@Test
void When_ReferencedResourceHasNotBeenMapped_Expect_MapMedicationRequest() {

MedicationRequest medRequest = new MedicationRequest();
medRequest.setId("MedicationRequest/1");
medRequest.addBasedOn(new Reference("ServiceRequest/123"));
when(inputBundleHolder.getResource(new IdType("ServiceRequest/123"))).thenReturn(
Optional.ofNullable(new MedicationRequest().setId("111")));

boolean result = resourceMapper.shouldMapResource(medRequest);

assertTrue(result);
}

@Test
void When_ReferencedResourceHasBeenMapped_Expect_MedicationRequestIsNotMapped() {

MedicationRequest medRequest = new MedicationRequest();
medRequest.setId("MedicationRequest/1");
medRequest.addBasedOn(new Reference("ServiceRequest/123"));
when(inputBundleHolder.getResource(new IdType("ServiceRequest/123"))).thenReturn(Optional.empty());
when(idMapper.hasIdBeenMapped(any(), any())).thenReturn(true);

boolean result = resourceMapper.shouldMapResource(medRequest);

assertFalse(result);
}

@Test
void When_ReferencedResourceHasNoBasedOn_Expect_MedicationRequestShouldBeMapped() {

MedicationRequest medRequest = new MedicationRequest();
medRequest.setId("MedicationRequest/1");

boolean result = resourceMapper.shouldMapResource(medRequest);

assertTrue(result);
}

@Test
void When_ReferencedResourceHasBeenIgnored_Expect_MedicationRequestShouldNotBeMapped() {
QuestionnaireResponse questionnaireResp = new QuestionnaireResponse();
questionnaireResp.setId("MedicationRequest/1");

try (MockedStatic<IgnoredResourcesUtils> ignoredMock = Mockito.mockStatic(IgnoredResourcesUtils.class)) {

ignoredMock.when(() -> IgnoredResourcesUtils.isIgnoredResourceType(ResourceType.QuestionnaireResponse)).thenReturn(true);

boolean result = resourceMapper.shouldMapResource(questionnaireResp);

assertFalse(result);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -115,14 +115,15 @@ public void When_TransformingResourceToEhrComp_Expect_CorrectValuesToBeExtracted
String bundle = ResourceTestFileUtils.getFileContent(inputBundle);
String expectedOutput = ResourceTestFileUtils.getFileContent(output);
Bundle parsedBundle = fhirParseService.parseResource(bundle, Bundle.class);
messageContext.initialize(parsedBundle);

var translatedOutput = nonConsultationResourceMapper.mapRemainingResourcesToEhrCompositions(parsedBundle).get(0);
assertThat(translatedOutput).isEqualToIgnoringWhitespace(expectedOutput);
}

@ParameterizedTest
@MethodSource("endedAllergiesArgs")
public void When_TransformingEndedAllergyListToEhrComp_Expect_CorrectValuesToBeExtracted(String inputBundle, String output) {
void When_TransformingEndedAllergyListToEhrComp_Expect_CorrectValuesToBeExtracted(String inputBundle, String output) {
setupMock(ResourceTestFileUtils.getFileContent(ALLERGY_INTOLERANCE_XML));
String bundle = ResourceTestFileUtils.getFileContent(inputBundle);
String expectedOutput = ResourceTestFileUtils.getFileContent(output);
Expand Down
Loading