From 41674a5d6cab34fcab6c579bbafe577f185d92b3 Mon Sep 17 00:00:00 2001 From: SergiuPacurariu Date: Tue, 14 Jan 2025 14:10:18 +0200 Subject: [PATCH 1/2] #13207 - [LUX] Automatic case processing for Pertussis cases --- .../processing/AbstractProcessingFlow.java | 18 ++++ .../AutomaticLabMessageProcessorTest.java | 85 +++++++++++++++++++ 2 files changed, 103 insertions(+) diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/externalmessage/processing/AbstractProcessingFlow.java b/sormas-api/src/main/java/de/symeda/sormas/api/externalmessage/processing/AbstractProcessingFlow.java index 5fc5b5bf63f..1a279a25e5c 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/externalmessage/processing/AbstractProcessingFlow.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/externalmessage/processing/AbstractProcessingFlow.java @@ -22,13 +22,19 @@ import org.slf4j.LoggerFactory; import de.symeda.sormas.api.CountryHelper; +import de.symeda.sormas.api.Disease; +import de.symeda.sormas.api.caze.CaseClassification; import de.symeda.sormas.api.caze.CaseDataDto; +import de.symeda.sormas.api.caze.CaseOutcome; +import de.symeda.sormas.api.caze.InvestigationStatus; import de.symeda.sormas.api.externalmessage.ExternalMessageDto; import de.symeda.sormas.api.feature.FeatureType; import de.symeda.sormas.api.infrastructure.facility.FacilityDto; import de.symeda.sormas.api.infrastructure.facility.FacilityReferenceDto; import de.symeda.sormas.api.infrastructure.facility.FacilityType; import de.symeda.sormas.api.person.PersonDto; +import de.symeda.sormas.api.sample.PathogenTestResultType; +import de.symeda.sormas.api.sample.PathogenTestType; import de.symeda.sormas.api.user.UserDto; import de.symeda.sormas.api.utils.dataprocessing.EntitySelection; import de.symeda.sormas.api.utils.dataprocessing.HandlerCallback; @@ -175,6 +181,18 @@ protected CaseDataDto buildCase(PersonDto person, ExternalMessageDto externalMes caseDto.setHealthFacility(processingFacade.getFacilityReferenceByUuid(FacilityDto.NONE_FACILITY_UUID)); } + if (processingFacade.isConfiguredCountry(CountryHelper.COUNTRY_CODE_LUXEMBOURG)) { + if (externalMessageDto.getDisease().equals(Disease.PERTUSSIS) + && externalMessageDto.getSampleReports().get(0).getTestReports().get(0).getTestResult().equals(PathogenTestResultType.POSITIVE)) { + PathogenTestType testType = externalMessageDto.getSampleReports().get(0).getTestReports().get(0).getTestType(); + if (testType.equals(PathogenTestType.CULTURE) || testType.equals(PathogenTestType.PCR_RT_PCR)) { + caseDto.setCaseClassification(CaseClassification.CONFIRMED); + } + } + caseDto.setInvestigationStatus(InvestigationStatus.PENDING); + caseDto.setOutcome(CaseOutcome.NO_OUTCOME); + } + caseDto.setVaccinationStatus(externalMessageDto.getVaccinationStatus()); caseDto.getHospitalization().setAdmittedToHealthFacility(externalMessageDto.getAdmittedToHealthFacility()); diff --git a/sormas-backend/src/test/java/de/symeda/sormas/backend/externalmessage/labmessage/AutomaticLabMessageProcessorTest.java b/sormas-backend/src/test/java/de/symeda/sormas/backend/externalmessage/labmessage/AutomaticLabMessageProcessorTest.java index 85da716cf6f..fe82a6270d6 100644 --- a/sormas-backend/src/test/java/de/symeda/sormas/backend/externalmessage/labmessage/AutomaticLabMessageProcessorTest.java +++ b/sormas-backend/src/test/java/de/symeda/sormas/backend/externalmessage/labmessage/AutomaticLabMessageProcessorTest.java @@ -20,6 +20,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; import java.util.Collections; import java.util.Date; @@ -31,8 +32,11 @@ import org.junit.jupiter.api.Test; import de.symeda.sormas.api.Disease; +import de.symeda.sormas.api.caze.CaseClassification; import de.symeda.sormas.api.caze.CaseCriteria; import de.symeda.sormas.api.caze.CaseDataDto; +import de.symeda.sormas.api.caze.CaseOutcome; +import de.symeda.sormas.api.caze.InvestigationStatus; import de.symeda.sormas.api.externalmessage.ExternalMessageDto; import de.symeda.sormas.api.externalmessage.ExternalMessageStatus; import de.symeda.sormas.api.externalmessage.ExternalMessageType; @@ -58,7 +62,9 @@ import de.symeda.sormas.api.utils.DateHelper; import de.symeda.sormas.api.utils.dataprocessing.ProcessingResult; import de.symeda.sormas.backend.AbstractBeanTest; +import de.symeda.sormas.backend.MockProducer; import de.symeda.sormas.backend.TestDataCreator; +import de.symeda.sormas.backend.common.ConfigFacadeEjb; import de.symeda.sormas.backend.disease.DiseaseConfigurationFacadeEjb; public class AutomaticLabMessageProcessorTest extends AbstractBeanTest { @@ -217,6 +223,7 @@ public void testProcessWithExistingPersonAndCase() throws ExecutionException, In /** * External message with sample date in the threshold period should generate a new sample to the existing case + * * @throws ExecutionException * @throws InterruptedException */ @@ -410,6 +417,84 @@ public void testProcessMessageWithNoNationalHealthId() throws ExecutionException assertThat(pathogenTests, hasSize(1)); } + @Test + public void testProcessPertussisMessageTestTypeCulture() throws ExecutionException, InterruptedException { + MockProducer.getProperties().setProperty(ConfigFacadeEjb.COUNTRY_LOCALE, "lu"); + ExternalMessageDto cultureMessage = createExternalMessage((messageDto) -> { + messageDto.setDisease(Disease.PERTUSSIS); + messageDto.getSampleReports().get(0).getTestReports().get(0).setTestType(PathogenTestType.CULTURE); + messageDto.getSampleReports().get(0).getTestReports().get(0).setTestResult(PathogenTestResultType.POSITIVE); + }); + ProcessingResult result = runFlow(cultureMessage); + assertThat(result.getStatus(), is(DONE)); + assertThat(cultureMessage.getStatus(), is(ExternalMessageStatus.PROCESSED)); + assertThat(getExternalMessageFacade().getByUuid(cultureMessage.getUuid()).getStatus(), is(ExternalMessageStatus.PROCESSED)); + CaseDataDto positiveCase = getCaseData(); + assertThat(positiveCase, is(notNullValue())); + assertThat(positiveCase.getDisease(), is(cultureMessage.getDisease())); + assertThat(positiveCase.getCaseClassification(), is(CaseClassification.CONFIRMED)); + assertThat(positiveCase.getInvestigationStatus(), is(InvestigationStatus.PENDING)); + assertThat(positiveCase.getOutcome(), is(CaseOutcome.NO_OUTCOME)); + } + + @Test + public void testProcessPertussisTestTypePCR() throws ExecutionException, InterruptedException { + MockProducer.getProperties().setProperty(ConfigFacadeEjb.COUNTRY_LOCALE, "lu"); + ExternalMessageDto pcrMessage = createExternalMessage((messageDto) -> { + messageDto.setDisease(Disease.PERTUSSIS); + messageDto.getSampleReports().get(0).getTestReports().get(0).setTestType(PathogenTestType.PCR_RT_PCR); + messageDto.getSampleReports().get(0).getTestReports().get(0).setTestResult(PathogenTestResultType.POSITIVE); + }); + runFlow(pcrMessage); + assertThat(pcrMessage.getStatus(), is(ExternalMessageStatus.PROCESSED)); + CaseDataDto pcrCase = getCaseData(); + assertThat(pcrCase, is(notNullValue())); + assertThat(pcrCase.getDisease(), is(pcrMessage.getDisease())); + assertThat(pcrCase.getCaseClassification(), is(CaseClassification.CONFIRMED)); + assertThat(pcrCase.getInvestigationStatus(), is(InvestigationStatus.PENDING)); + assertThat(pcrCase.getOutcome(), is(CaseOutcome.NO_OUTCOME)); + } + + @Test + public void testProcessPertussisTestNegativeResult() throws ExecutionException, InterruptedException { + ExternalMessageDto negativeMessage = createExternalMessage((messageDto) -> { + messageDto.setDisease(Disease.PERTUSSIS); + messageDto.getSampleReports().get(0).getTestReports().get(0).setTestType(PathogenTestType.CULTURE); + messageDto.getSampleReports().get(0).getTestReports().get(0).setTestResult(PathogenTestResultType.NEGATIVE); + }); + runFlow(negativeMessage); + assertThat(negativeMessage.getStatus(), is(ExternalMessageStatus.PROCESSED)); + CaseDataDto negativeCase = getCaseData(); + assertThat(negativeCase, is(notNullValue())); + assertThat(negativeCase.getDisease(), is(negativeMessage.getDisease())); + assertThat(negativeCase.getCaseClassification(), is(CaseClassification.NOT_CLASSIFIED)); + assertThat(negativeCase.getInvestigationStatus(), is(InvestigationStatus.PENDING)); + assertThat(negativeCase.getOutcome(), is(CaseOutcome.NO_OUTCOME)); + } + + @Test + public void testProcessPertussisOtherTestType() throws ExecutionException, InterruptedException { + ExternalMessageDto rapidTestMessage = createExternalMessage((messageDto) -> { + messageDto.setDisease(Disease.PERTUSSIS); + messageDto.getSampleReports().get(0).getTestReports().get(0).setTestType(PathogenTestType.RAPID_TEST); + messageDto.getSampleReports().get(0).getTestReports().get(0).setTestResult(PathogenTestResultType.POSITIVE); + }); + runFlow(rapidTestMessage); + assertThat(rapidTestMessage.getStatus(), is(ExternalMessageStatus.PROCESSED)); + CaseDataDto rapidTestcase = getCaseData(); + assertThat(rapidTestcase, is(notNullValue())); + assertThat(rapidTestcase.getDisease(), is(rapidTestMessage.getDisease())); + assertThat(rapidTestcase.getCaseClassification(), is(CaseClassification.NOT_CLASSIFIED)); + assertThat(rapidTestcase.getInvestigationStatus(), is(InvestigationStatus.PENDING)); + assertThat(rapidTestcase.getOutcome(), is(CaseOutcome.NO_OUTCOME)); + } + + private CaseDataDto getCaseData() { + List persons = getPersonFacade().getAllAfter(new Date(0)); + List cases = getCaseFacade().getByPersonUuids(persons.stream().map(PersonDto::getUuid).collect(Collectors.toList())); + return cases.get(0); + } + private ProcessingResult runFlow(ExternalMessageDto labMessage) throws ExecutionException, InterruptedException { return flow.processLabMessage(labMessage); From 13f29a154163d1b6a16e4e2c81d91d6eafea2a0b Mon Sep 17 00:00:00 2001 From: SergiuPacurariu Date: Fri, 17 Jan 2025 14:44:06 +0200 Subject: [PATCH 2/2] #13207 - [LUX] Automatic case processing for Pertussis cases --- .../AbstractLabMessageProcessingFlowTest.java | 193 ++++++++++++++++++ 1 file changed, 193 insertions(+) diff --git a/sormas-backend/src/test/java/de/symeda/sormas/backend/externalmessage/processing/labmessage/AbstractLabMessageProcessingFlowTest.java b/sormas-backend/src/test/java/de/symeda/sormas/backend/externalmessage/processing/labmessage/AbstractLabMessageProcessingFlowTest.java index 0b1a28e061e..23aa5e1f53f 100644 --- a/sormas-backend/src/test/java/de/symeda/sormas/backend/externalmessage/processing/labmessage/AbstractLabMessageProcessingFlowTest.java +++ b/sormas-backend/src/test/java/de/symeda/sormas/backend/externalmessage/processing/labmessage/AbstractLabMessageProcessingFlowTest.java @@ -43,6 +43,9 @@ import java.util.function.Consumer; import java.util.function.Supplier; +import de.symeda.sormas.api.caze.CaseOutcome; +import de.symeda.sormas.backend.MockProducer; +import de.symeda.sormas.backend.common.ConfigFacadeEjb; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.junit.jupiter.api.Test; @@ -2816,6 +2819,196 @@ public void testCaseSurveillanceReportUnknownFacility() throws ExecutionExceptio assertThat(surveillanceReport.getFacilityDistrict(), is(nullValue())); } + @Test + public void testCreateCaseWithPertusisTestTypeCultureForLuServers() throws ExecutionException, InterruptedException { + MockProducer.getProperties().setProperty(ConfigFacadeEjb.COUNTRY_LOCALE, "lu"); + ArgumentCaptor personCaptor = ArgumentCaptor.forClass(PersonDto.class); + doAnswer(invocation -> { + HandlerCallback> callback = invocation.getArgument(1); + PersonDto person = invocation.getArgument(0); + + getPersonFacade().save(person); + + callback.done(new EntitySelection<>(person, true)); + + return null; + + }).when(handlePickOrCreatePerson).apply(personCaptor.capture(), any()); + + PickOrCreateEntryResult pickOrCreateEntryResult = new PickOrCreateEntryResult(); + pickOrCreateEntryResult.setNewCase(true); + doAnswer(answerPickOrCreateEntry(pickOrCreateEntryResult)).when(handlePickOrCreateEntry).handle(any(), any(), any(), any()); + + ArgumentCaptor caseCaptor = ArgumentCaptor.forClass(CaseDataDto.class); + doAnswer((invocation) -> { + CaseDataDto caze = invocation.getArgument(0); + caze.setResponsibleRegion(rdcf.region); + caze.setResponsibleDistrict(rdcf.district); + caze.setFacilityType(FacilityType.HOSPITAL); + caze.setHealthFacility(rdcf.facility); + getCaseFacade().save(caze); + getCallbackParam(invocation).done(caze); + return null; + }).when(handleCreateCase).handle(caseCaptor.capture(), any(), any()); + + doAnswer((invocation) -> { + SampleDto sample = invocation.getArgument(0); + sample.setSamplingReason(SamplingReason.PROFESSIONAL_REASON); + + List pathogenTests = invocation.getArgument(1); + pathogenTests.get(0).setTestResultText("Dummy test result text"); + + getCallbackParam(invocation).done(new SampleAndPathogenTests(sample, pathogenTests)); + return null; + }).when(handleCreateSampleAndPathogenTests).handle(any(), any(), any(), eq(true), any()); + + SampleReportDto sampleReport = SampleReportDto.build(); + ExternalMessageDto labMessage = createLabMessage(Disease.PERTUSSIS, "test-report-id", ExternalMessageStatus.UNPROCESSED); + labMessage.addSampleReport(sampleReport); + sampleReport.setSampleDateTime(new Date()); + sampleReport.setSampleMaterial(SampleMaterial.BLOOD); + + TestReportDto testReport1 = TestReportDto.build(); + testReport1.setTestType(PathogenTestType.CULTURE); + testReport1.setTestResult(PathogenTestResultType.POSITIVE); + sampleReport.addTestReport(testReport1); + + ProcessingResult result = runFlow(labMessage); + + assertThat(result.getStatus(), is(DONE)); + assertThat(getExternalMessageFacade().getByUuid(labMessage.getUuid()).getStatus(), is(ExternalMessageStatus.PROCESSED)); + assertThat(getSurveillanceReportFacade().getByCaseUuids(Collections.singletonList(result.getData().getCase().getUuid())), hasSize(1)); + + verify(handleCreateSampleAndPathogenTests).handle(argThat(sample -> { + assertThat(sample.getAssociatedCase(), is(caseCaptor.getValue().toReference())); + assertThat(sample.getSampleDateTime(), is(labMessage.getSampleReports().get(0).getSampleDateTime())); + assertThat(sample.getSampleMaterial(), is(SampleMaterial.BLOOD)); + assertThat(sample.getReportingUser(), is(user.toReference())); + + return true; + }), argThat(pathogenTests -> { + assertThat(pathogenTests, hasSize(1)); + + assertThat(pathogenTests.get(0).getTestType(), is(testReport1.getTestType())); + assertThat(pathogenTests.get(0).getTestResult(), is(testReport1.getTestResult())); + + return true; + }), argThat(entityCreated -> { + assertThat(entityCreated, is(true)); + + return true; + }), argThat(lastSample -> { + assertThat(lastSample, is(Boolean.TRUE)); + + return true; + }), any()); + + verify(handleCreateCase).handle(argThat(c -> { + assertThat(c.getPerson(), is(personCaptor.getValue().toReference())); + assertThat(c.getDisease(), is(Disease.PERTUSSIS)); + assertThat(c.getCaseClassification(), is(CaseClassification.CONFIRMED)); + assertThat(c.getInvestigationStatus(), is(InvestigationStatus.PENDING)); + assertThat(c.getOutcome(), is(CaseOutcome.NO_OUTCOME)); + assertThat(c.getReportingUser(), is(user.toReference())); + return true; + }), argThat(p -> p.equals(personCaptor.getValue())), any()); + } + + @Test + public void testCreateCaseWithPertusisOtherTestTypeForLuServers() throws ExecutionException, InterruptedException { + MockProducer.getProperties().setProperty(ConfigFacadeEjb.COUNTRY_LOCALE, "lu"); + ArgumentCaptor personCaptor = ArgumentCaptor.forClass(PersonDto.class); + doAnswer(invocation -> { + HandlerCallback> callback = invocation.getArgument(1); + PersonDto person = invocation.getArgument(0); + + getPersonFacade().save(person); + + callback.done(new EntitySelection<>(person, true)); + + return null; + + }).when(handlePickOrCreatePerson).apply(personCaptor.capture(), any()); + + PickOrCreateEntryResult pickOrCreateEntryResult = new PickOrCreateEntryResult(); + pickOrCreateEntryResult.setNewCase(true); + doAnswer(answerPickOrCreateEntry(pickOrCreateEntryResult)).when(handlePickOrCreateEntry).handle(any(), any(), any(), any()); + + ArgumentCaptor caseCaptor = ArgumentCaptor.forClass(CaseDataDto.class); + doAnswer((invocation) -> { + CaseDataDto caze = invocation.getArgument(0); + caze.setResponsibleRegion(rdcf.region); + caze.setResponsibleDistrict(rdcf.district); + caze.setFacilityType(FacilityType.HOSPITAL); + caze.setHealthFacility(rdcf.facility); + getCaseFacade().save(caze); + getCallbackParam(invocation).done(caze); + return null; + }).when(handleCreateCase).handle(caseCaptor.capture(), any(), any()); + + doAnswer((invocation) -> { + SampleDto sample = invocation.getArgument(0); + sample.setSamplingReason(SamplingReason.PROFESSIONAL_REASON); + + List pathogenTests = invocation.getArgument(1); + pathogenTests.get(0).setTestResultText("Dummy test result text"); + + getCallbackParam(invocation).done(new SampleAndPathogenTests(sample, pathogenTests)); + return null; + }).when(handleCreateSampleAndPathogenTests).handle(any(), any(), any(), eq(true), any()); + + SampleReportDto sampleReport = SampleReportDto.build(); + ExternalMessageDto labMessage = createLabMessage(Disease.PERTUSSIS, "test-report-id", ExternalMessageStatus.UNPROCESSED); + labMessage.addSampleReport(sampleReport); + sampleReport.setSampleDateTime(new Date()); + sampleReport.setSampleMaterial(SampleMaterial.BLOOD); + + TestReportDto testReport1 = TestReportDto.build(); + testReport1.setTestType(PathogenTestType.RAPID_TEST); + testReport1.setTestResult(PathogenTestResultType.POSITIVE); + sampleReport.addTestReport(testReport1); + + ProcessingResult result = runFlow(labMessage); + + assertThat(result.getStatus(), is(DONE)); + assertThat(getExternalMessageFacade().getByUuid(labMessage.getUuid()).getStatus(), is(ExternalMessageStatus.PROCESSED)); + assertThat(getSurveillanceReportFacade().getByCaseUuids(Collections.singletonList(result.getData().getCase().getUuid())), hasSize(1)); + + verify(handleCreateSampleAndPathogenTests).handle(argThat(sample -> { + assertThat(sample.getAssociatedCase(), is(caseCaptor.getValue().toReference())); + assertThat(sample.getSampleDateTime(), is(labMessage.getSampleReports().get(0).getSampleDateTime())); + assertThat(sample.getSampleMaterial(), is(SampleMaterial.BLOOD)); + assertThat(sample.getReportingUser(), is(user.toReference())); + + return true; + }), argThat(pathogenTests -> { + assertThat(pathogenTests, hasSize(1)); + + assertThat(pathogenTests.get(0).getTestType(), is(testReport1.getTestType())); + assertThat(pathogenTests.get(0).getTestResult(), is(testReport1.getTestResult())); + + return true; + }), argThat(entityCreated -> { + assertThat(entityCreated, is(true)); + + return true; + }), argThat(lastSample -> { + assertThat(lastSample, is(Boolean.TRUE)); + + return true; + }), any()); + + verify(handleCreateCase).handle(argThat(c -> { + assertThat(c.getPerson(), is(personCaptor.getValue().toReference())); + assertThat(c.getDisease(), is(Disease.PERTUSSIS)); + assertThat(c.getCaseClassification(), is(CaseClassification.NOT_CLASSIFIED)); + assertThat(c.getInvestigationStatus(), is(InvestigationStatus.PENDING)); + assertThat(c.getOutcome(), is(CaseOutcome.NO_OUTCOME)); + assertThat(c.getReportingUser(), is(user.toReference())); + return true; + }), argThat(p -> p.equals(personCaptor.getValue())), any()); + } + private ProcessingResult runFlow(ExternalMessageDto labMessage) throws ExecutionException, InterruptedException { ExternalMessageProcessingFacade processingFacade = getExternalMessageProcessingFacade(); AbstractLabMessageProcessingFlow flow = new AbstractLabMessageProcessingFlow(