Skip to content

Commit 467ad5a

Browse files
Merge pull request #13230 from SORMAS-Foundation/feature-13207_Automatic_case_classification_for_Pertusis
#13207 - [LUX] Automatic case processing for Pertussis cases
2 parents 8421332 + 13f29a1 commit 467ad5a

File tree

3 files changed

+296
-0
lines changed

3 files changed

+296
-0
lines changed

sormas-api/src/main/java/de/symeda/sormas/api/externalmessage/processing/AbstractProcessingFlow.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,19 @@
2222
import org.slf4j.LoggerFactory;
2323

2424
import de.symeda.sormas.api.CountryHelper;
25+
import de.symeda.sormas.api.Disease;
26+
import de.symeda.sormas.api.caze.CaseClassification;
2527
import de.symeda.sormas.api.caze.CaseDataDto;
28+
import de.symeda.sormas.api.caze.CaseOutcome;
29+
import de.symeda.sormas.api.caze.InvestigationStatus;
2630
import de.symeda.sormas.api.externalmessage.ExternalMessageDto;
2731
import de.symeda.sormas.api.feature.FeatureType;
2832
import de.symeda.sormas.api.infrastructure.facility.FacilityDto;
2933
import de.symeda.sormas.api.infrastructure.facility.FacilityReferenceDto;
3034
import de.symeda.sormas.api.infrastructure.facility.FacilityType;
3135
import de.symeda.sormas.api.person.PersonDto;
36+
import de.symeda.sormas.api.sample.PathogenTestResultType;
37+
import de.symeda.sormas.api.sample.PathogenTestType;
3238
import de.symeda.sormas.api.user.UserDto;
3339
import de.symeda.sormas.api.utils.dataprocessing.EntitySelection;
3440
import de.symeda.sormas.api.utils.dataprocessing.HandlerCallback;
@@ -175,6 +181,18 @@ protected CaseDataDto buildCase(PersonDto person, ExternalMessageDto externalMes
175181
caseDto.setHealthFacility(processingFacade.getFacilityReferenceByUuid(FacilityDto.NONE_FACILITY_UUID));
176182
}
177183

184+
if (processingFacade.isConfiguredCountry(CountryHelper.COUNTRY_CODE_LUXEMBOURG)) {
185+
if (externalMessageDto.getDisease().equals(Disease.PERTUSSIS)
186+
&& externalMessageDto.getSampleReports().get(0).getTestReports().get(0).getTestResult().equals(PathogenTestResultType.POSITIVE)) {
187+
PathogenTestType testType = externalMessageDto.getSampleReports().get(0).getTestReports().get(0).getTestType();
188+
if (testType.equals(PathogenTestType.CULTURE) || testType.equals(PathogenTestType.PCR_RT_PCR)) {
189+
caseDto.setCaseClassification(CaseClassification.CONFIRMED);
190+
}
191+
}
192+
caseDto.setInvestigationStatus(InvestigationStatus.PENDING);
193+
caseDto.setOutcome(CaseOutcome.NO_OUTCOME);
194+
}
195+
178196
caseDto.setVaccinationStatus(externalMessageDto.getVaccinationStatus());
179197
caseDto.getHospitalization().setAdmittedToHealthFacility(externalMessageDto.getAdmittedToHealthFacility());
180198

sormas-backend/src/test/java/de/symeda/sormas/backend/externalmessage/labmessage/AutomaticLabMessageProcessorTest.java

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import static org.hamcrest.MatcherAssert.assertThat;
2121
import static org.hamcrest.Matchers.hasSize;
2222
import static org.hamcrest.Matchers.is;
23+
import static org.hamcrest.Matchers.notNullValue;
2324

2425
import java.util.Collections;
2526
import java.util.Date;
@@ -31,8 +32,11 @@
3132
import org.junit.jupiter.api.Test;
3233

3334
import de.symeda.sormas.api.Disease;
35+
import de.symeda.sormas.api.caze.CaseClassification;
3436
import de.symeda.sormas.api.caze.CaseCriteria;
3537
import de.symeda.sormas.api.caze.CaseDataDto;
38+
import de.symeda.sormas.api.caze.CaseOutcome;
39+
import de.symeda.sormas.api.caze.InvestigationStatus;
3640
import de.symeda.sormas.api.externalmessage.ExternalMessageDto;
3741
import de.symeda.sormas.api.externalmessage.ExternalMessageStatus;
3842
import de.symeda.sormas.api.externalmessage.ExternalMessageType;
@@ -58,7 +62,9 @@
5862
import de.symeda.sormas.api.utils.DateHelper;
5963
import de.symeda.sormas.api.utils.dataprocessing.ProcessingResult;
6064
import de.symeda.sormas.backend.AbstractBeanTest;
65+
import de.symeda.sormas.backend.MockProducer;
6166
import de.symeda.sormas.backend.TestDataCreator;
67+
import de.symeda.sormas.backend.common.ConfigFacadeEjb;
6268
import de.symeda.sormas.backend.disease.DiseaseConfigurationFacadeEjb;
6369

6470
public class AutomaticLabMessageProcessorTest extends AbstractBeanTest {
@@ -217,6 +223,7 @@ public void testProcessWithExistingPersonAndCase() throws ExecutionException, In
217223

218224
/**
219225
* External message with sample date in the threshold period should generate a new sample to the existing case
226+
*
220227
* @throws ExecutionException
221228
* @throws InterruptedException
222229
*/
@@ -410,6 +417,84 @@ public void testProcessMessageWithNoNationalHealthId() throws ExecutionException
410417
assertThat(pathogenTests, hasSize(1));
411418
}
412419

420+
@Test
421+
public void testProcessPertussisMessageTestTypeCulture() throws ExecutionException, InterruptedException {
422+
MockProducer.getProperties().setProperty(ConfigFacadeEjb.COUNTRY_LOCALE, "lu");
423+
ExternalMessageDto cultureMessage = createExternalMessage((messageDto) -> {
424+
messageDto.setDisease(Disease.PERTUSSIS);
425+
messageDto.getSampleReports().get(0).getTestReports().get(0).setTestType(PathogenTestType.CULTURE);
426+
messageDto.getSampleReports().get(0).getTestReports().get(0).setTestResult(PathogenTestResultType.POSITIVE);
427+
});
428+
ProcessingResult<ExternalMessageProcessingResult> result = runFlow(cultureMessage);
429+
assertThat(result.getStatus(), is(DONE));
430+
assertThat(cultureMessage.getStatus(), is(ExternalMessageStatus.PROCESSED));
431+
assertThat(getExternalMessageFacade().getByUuid(cultureMessage.getUuid()).getStatus(), is(ExternalMessageStatus.PROCESSED));
432+
CaseDataDto positiveCase = getCaseData();
433+
assertThat(positiveCase, is(notNullValue()));
434+
assertThat(positiveCase.getDisease(), is(cultureMessage.getDisease()));
435+
assertThat(positiveCase.getCaseClassification(), is(CaseClassification.CONFIRMED));
436+
assertThat(positiveCase.getInvestigationStatus(), is(InvestigationStatus.PENDING));
437+
assertThat(positiveCase.getOutcome(), is(CaseOutcome.NO_OUTCOME));
438+
}
439+
440+
@Test
441+
public void testProcessPertussisTestTypePCR() throws ExecutionException, InterruptedException {
442+
MockProducer.getProperties().setProperty(ConfigFacadeEjb.COUNTRY_LOCALE, "lu");
443+
ExternalMessageDto pcrMessage = createExternalMessage((messageDto) -> {
444+
messageDto.setDisease(Disease.PERTUSSIS);
445+
messageDto.getSampleReports().get(0).getTestReports().get(0).setTestType(PathogenTestType.PCR_RT_PCR);
446+
messageDto.getSampleReports().get(0).getTestReports().get(0).setTestResult(PathogenTestResultType.POSITIVE);
447+
});
448+
runFlow(pcrMessage);
449+
assertThat(pcrMessage.getStatus(), is(ExternalMessageStatus.PROCESSED));
450+
CaseDataDto pcrCase = getCaseData();
451+
assertThat(pcrCase, is(notNullValue()));
452+
assertThat(pcrCase.getDisease(), is(pcrMessage.getDisease()));
453+
assertThat(pcrCase.getCaseClassification(), is(CaseClassification.CONFIRMED));
454+
assertThat(pcrCase.getInvestigationStatus(), is(InvestigationStatus.PENDING));
455+
assertThat(pcrCase.getOutcome(), is(CaseOutcome.NO_OUTCOME));
456+
}
457+
458+
@Test
459+
public void testProcessPertussisTestNegativeResult() throws ExecutionException, InterruptedException {
460+
ExternalMessageDto negativeMessage = createExternalMessage((messageDto) -> {
461+
messageDto.setDisease(Disease.PERTUSSIS);
462+
messageDto.getSampleReports().get(0).getTestReports().get(0).setTestType(PathogenTestType.CULTURE);
463+
messageDto.getSampleReports().get(0).getTestReports().get(0).setTestResult(PathogenTestResultType.NEGATIVE);
464+
});
465+
runFlow(negativeMessage);
466+
assertThat(negativeMessage.getStatus(), is(ExternalMessageStatus.PROCESSED));
467+
CaseDataDto negativeCase = getCaseData();
468+
assertThat(negativeCase, is(notNullValue()));
469+
assertThat(negativeCase.getDisease(), is(negativeMessage.getDisease()));
470+
assertThat(negativeCase.getCaseClassification(), is(CaseClassification.NOT_CLASSIFIED));
471+
assertThat(negativeCase.getInvestigationStatus(), is(InvestigationStatus.PENDING));
472+
assertThat(negativeCase.getOutcome(), is(CaseOutcome.NO_OUTCOME));
473+
}
474+
475+
@Test
476+
public void testProcessPertussisOtherTestType() throws ExecutionException, InterruptedException {
477+
ExternalMessageDto rapidTestMessage = createExternalMessage((messageDto) -> {
478+
messageDto.setDisease(Disease.PERTUSSIS);
479+
messageDto.getSampleReports().get(0).getTestReports().get(0).setTestType(PathogenTestType.RAPID_TEST);
480+
messageDto.getSampleReports().get(0).getTestReports().get(0).setTestResult(PathogenTestResultType.POSITIVE);
481+
});
482+
runFlow(rapidTestMessage);
483+
assertThat(rapidTestMessage.getStatus(), is(ExternalMessageStatus.PROCESSED));
484+
CaseDataDto rapidTestcase = getCaseData();
485+
assertThat(rapidTestcase, is(notNullValue()));
486+
assertThat(rapidTestcase.getDisease(), is(rapidTestMessage.getDisease()));
487+
assertThat(rapidTestcase.getCaseClassification(), is(CaseClassification.NOT_CLASSIFIED));
488+
assertThat(rapidTestcase.getInvestigationStatus(), is(InvestigationStatus.PENDING));
489+
assertThat(rapidTestcase.getOutcome(), is(CaseOutcome.NO_OUTCOME));
490+
}
491+
492+
private CaseDataDto getCaseData() {
493+
List<PersonDto> persons = getPersonFacade().getAllAfter(new Date(0));
494+
List<CaseDataDto> cases = getCaseFacade().getByPersonUuids(persons.stream().map(PersonDto::getUuid).collect(Collectors.toList()));
495+
return cases.get(0);
496+
}
497+
413498
private ProcessingResult<ExternalMessageProcessingResult> runFlow(ExternalMessageDto labMessage) throws ExecutionException, InterruptedException {
414499

415500
return flow.processLabMessage(labMessage);

sormas-backend/src/test/java/de/symeda/sormas/backend/externalmessage/processing/labmessage/AbstractLabMessageProcessingFlowTest.java

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@
4343
import java.util.function.Consumer;
4444
import java.util.function.Supplier;
4545

46+
import de.symeda.sormas.api.caze.CaseOutcome;
47+
import de.symeda.sormas.backend.MockProducer;
48+
import de.symeda.sormas.backend.common.ConfigFacadeEjb;
4649
import org.jetbrains.annotations.NotNull;
4750
import org.jetbrains.annotations.Nullable;
4851
import org.junit.jupiter.api.Test;
@@ -2816,6 +2819,196 @@ public void testCaseSurveillanceReportUnknownFacility() throws ExecutionExceptio
28162819
assertThat(surveillanceReport.getFacilityDistrict(), is(nullValue()));
28172820
}
28182821

2822+
@Test
2823+
public void testCreateCaseWithPertusisTestTypeCultureForLuServers() throws ExecutionException, InterruptedException {
2824+
MockProducer.getProperties().setProperty(ConfigFacadeEjb.COUNTRY_LOCALE, "lu");
2825+
ArgumentCaptor<PersonDto> personCaptor = ArgumentCaptor.forClass(PersonDto.class);
2826+
doAnswer(invocation -> {
2827+
HandlerCallback<EntitySelection<PersonDto>> callback = invocation.getArgument(1);
2828+
PersonDto person = invocation.getArgument(0);
2829+
2830+
getPersonFacade().save(person);
2831+
2832+
callback.done(new EntitySelection<>(person, true));
2833+
2834+
return null;
2835+
2836+
}).when(handlePickOrCreatePerson).apply(personCaptor.capture(), any());
2837+
2838+
PickOrCreateEntryResult pickOrCreateEntryResult = new PickOrCreateEntryResult();
2839+
pickOrCreateEntryResult.setNewCase(true);
2840+
doAnswer(answerPickOrCreateEntry(pickOrCreateEntryResult)).when(handlePickOrCreateEntry).handle(any(), any(), any(), any());
2841+
2842+
ArgumentCaptor<CaseDataDto> caseCaptor = ArgumentCaptor.forClass(CaseDataDto.class);
2843+
doAnswer((invocation) -> {
2844+
CaseDataDto caze = invocation.getArgument(0);
2845+
caze.setResponsibleRegion(rdcf.region);
2846+
caze.setResponsibleDistrict(rdcf.district);
2847+
caze.setFacilityType(FacilityType.HOSPITAL);
2848+
caze.setHealthFacility(rdcf.facility);
2849+
getCaseFacade().save(caze);
2850+
getCallbackParam(invocation).done(caze);
2851+
return null;
2852+
}).when(handleCreateCase).handle(caseCaptor.capture(), any(), any());
2853+
2854+
doAnswer((invocation) -> {
2855+
SampleDto sample = invocation.getArgument(0);
2856+
sample.setSamplingReason(SamplingReason.PROFESSIONAL_REASON);
2857+
2858+
List<PathogenTestDto> pathogenTests = invocation.getArgument(1);
2859+
pathogenTests.get(0).setTestResultText("Dummy test result text");
2860+
2861+
getCallbackParam(invocation).done(new SampleAndPathogenTests(sample, pathogenTests));
2862+
return null;
2863+
}).when(handleCreateSampleAndPathogenTests).handle(any(), any(), any(), eq(true), any());
2864+
2865+
SampleReportDto sampleReport = SampleReportDto.build();
2866+
ExternalMessageDto labMessage = createLabMessage(Disease.PERTUSSIS, "test-report-id", ExternalMessageStatus.UNPROCESSED);
2867+
labMessage.addSampleReport(sampleReport);
2868+
sampleReport.setSampleDateTime(new Date());
2869+
sampleReport.setSampleMaterial(SampleMaterial.BLOOD);
2870+
2871+
TestReportDto testReport1 = TestReportDto.build();
2872+
testReport1.setTestType(PathogenTestType.CULTURE);
2873+
testReport1.setTestResult(PathogenTestResultType.POSITIVE);
2874+
sampleReport.addTestReport(testReport1);
2875+
2876+
ProcessingResult<ExternalMessageProcessingResult> result = runFlow(labMessage);
2877+
2878+
assertThat(result.getStatus(), is(DONE));
2879+
assertThat(getExternalMessageFacade().getByUuid(labMessage.getUuid()).getStatus(), is(ExternalMessageStatus.PROCESSED));
2880+
assertThat(getSurveillanceReportFacade().getByCaseUuids(Collections.singletonList(result.getData().getCase().getUuid())), hasSize(1));
2881+
2882+
verify(handleCreateSampleAndPathogenTests).handle(argThat(sample -> {
2883+
assertThat(sample.getAssociatedCase(), is(caseCaptor.getValue().toReference()));
2884+
assertThat(sample.getSampleDateTime(), is(labMessage.getSampleReports().get(0).getSampleDateTime()));
2885+
assertThat(sample.getSampleMaterial(), is(SampleMaterial.BLOOD));
2886+
assertThat(sample.getReportingUser(), is(user.toReference()));
2887+
2888+
return true;
2889+
}), argThat(pathogenTests -> {
2890+
assertThat(pathogenTests, hasSize(1));
2891+
2892+
assertThat(pathogenTests.get(0).getTestType(), is(testReport1.getTestType()));
2893+
assertThat(pathogenTests.get(0).getTestResult(), is(testReport1.getTestResult()));
2894+
2895+
return true;
2896+
}), argThat(entityCreated -> {
2897+
assertThat(entityCreated, is(true));
2898+
2899+
return true;
2900+
}), argThat(lastSample -> {
2901+
assertThat(lastSample, is(Boolean.TRUE));
2902+
2903+
return true;
2904+
}), any());
2905+
2906+
verify(handleCreateCase).handle(argThat(c -> {
2907+
assertThat(c.getPerson(), is(personCaptor.getValue().toReference()));
2908+
assertThat(c.getDisease(), is(Disease.PERTUSSIS));
2909+
assertThat(c.getCaseClassification(), is(CaseClassification.CONFIRMED));
2910+
assertThat(c.getInvestigationStatus(), is(InvestigationStatus.PENDING));
2911+
assertThat(c.getOutcome(), is(CaseOutcome.NO_OUTCOME));
2912+
assertThat(c.getReportingUser(), is(user.toReference()));
2913+
return true;
2914+
}), argThat(p -> p.equals(personCaptor.getValue())), any());
2915+
}
2916+
2917+
@Test
2918+
public void testCreateCaseWithPertusisOtherTestTypeForLuServers() throws ExecutionException, InterruptedException {
2919+
MockProducer.getProperties().setProperty(ConfigFacadeEjb.COUNTRY_LOCALE, "lu");
2920+
ArgumentCaptor<PersonDto> personCaptor = ArgumentCaptor.forClass(PersonDto.class);
2921+
doAnswer(invocation -> {
2922+
HandlerCallback<EntitySelection<PersonDto>> callback = invocation.getArgument(1);
2923+
PersonDto person = invocation.getArgument(0);
2924+
2925+
getPersonFacade().save(person);
2926+
2927+
callback.done(new EntitySelection<>(person, true));
2928+
2929+
return null;
2930+
2931+
}).when(handlePickOrCreatePerson).apply(personCaptor.capture(), any());
2932+
2933+
PickOrCreateEntryResult pickOrCreateEntryResult = new PickOrCreateEntryResult();
2934+
pickOrCreateEntryResult.setNewCase(true);
2935+
doAnswer(answerPickOrCreateEntry(pickOrCreateEntryResult)).when(handlePickOrCreateEntry).handle(any(), any(), any(), any());
2936+
2937+
ArgumentCaptor<CaseDataDto> caseCaptor = ArgumentCaptor.forClass(CaseDataDto.class);
2938+
doAnswer((invocation) -> {
2939+
CaseDataDto caze = invocation.getArgument(0);
2940+
caze.setResponsibleRegion(rdcf.region);
2941+
caze.setResponsibleDistrict(rdcf.district);
2942+
caze.setFacilityType(FacilityType.HOSPITAL);
2943+
caze.setHealthFacility(rdcf.facility);
2944+
getCaseFacade().save(caze);
2945+
getCallbackParam(invocation).done(caze);
2946+
return null;
2947+
}).when(handleCreateCase).handle(caseCaptor.capture(), any(), any());
2948+
2949+
doAnswer((invocation) -> {
2950+
SampleDto sample = invocation.getArgument(0);
2951+
sample.setSamplingReason(SamplingReason.PROFESSIONAL_REASON);
2952+
2953+
List<PathogenTestDto> pathogenTests = invocation.getArgument(1);
2954+
pathogenTests.get(0).setTestResultText("Dummy test result text");
2955+
2956+
getCallbackParam(invocation).done(new SampleAndPathogenTests(sample, pathogenTests));
2957+
return null;
2958+
}).when(handleCreateSampleAndPathogenTests).handle(any(), any(), any(), eq(true), any());
2959+
2960+
SampleReportDto sampleReport = SampleReportDto.build();
2961+
ExternalMessageDto labMessage = createLabMessage(Disease.PERTUSSIS, "test-report-id", ExternalMessageStatus.UNPROCESSED);
2962+
labMessage.addSampleReport(sampleReport);
2963+
sampleReport.setSampleDateTime(new Date());
2964+
sampleReport.setSampleMaterial(SampleMaterial.BLOOD);
2965+
2966+
TestReportDto testReport1 = TestReportDto.build();
2967+
testReport1.setTestType(PathogenTestType.RAPID_TEST);
2968+
testReport1.setTestResult(PathogenTestResultType.POSITIVE);
2969+
sampleReport.addTestReport(testReport1);
2970+
2971+
ProcessingResult<ExternalMessageProcessingResult> result = runFlow(labMessage);
2972+
2973+
assertThat(result.getStatus(), is(DONE));
2974+
assertThat(getExternalMessageFacade().getByUuid(labMessage.getUuid()).getStatus(), is(ExternalMessageStatus.PROCESSED));
2975+
assertThat(getSurveillanceReportFacade().getByCaseUuids(Collections.singletonList(result.getData().getCase().getUuid())), hasSize(1));
2976+
2977+
verify(handleCreateSampleAndPathogenTests).handle(argThat(sample -> {
2978+
assertThat(sample.getAssociatedCase(), is(caseCaptor.getValue().toReference()));
2979+
assertThat(sample.getSampleDateTime(), is(labMessage.getSampleReports().get(0).getSampleDateTime()));
2980+
assertThat(sample.getSampleMaterial(), is(SampleMaterial.BLOOD));
2981+
assertThat(sample.getReportingUser(), is(user.toReference()));
2982+
2983+
return true;
2984+
}), argThat(pathogenTests -> {
2985+
assertThat(pathogenTests, hasSize(1));
2986+
2987+
assertThat(pathogenTests.get(0).getTestType(), is(testReport1.getTestType()));
2988+
assertThat(pathogenTests.get(0).getTestResult(), is(testReport1.getTestResult()));
2989+
2990+
return true;
2991+
}), argThat(entityCreated -> {
2992+
assertThat(entityCreated, is(true));
2993+
2994+
return true;
2995+
}), argThat(lastSample -> {
2996+
assertThat(lastSample, is(Boolean.TRUE));
2997+
2998+
return true;
2999+
}), any());
3000+
3001+
verify(handleCreateCase).handle(argThat(c -> {
3002+
assertThat(c.getPerson(), is(personCaptor.getValue().toReference()));
3003+
assertThat(c.getDisease(), is(Disease.PERTUSSIS));
3004+
assertThat(c.getCaseClassification(), is(CaseClassification.NOT_CLASSIFIED));
3005+
assertThat(c.getInvestigationStatus(), is(InvestigationStatus.PENDING));
3006+
assertThat(c.getOutcome(), is(CaseOutcome.NO_OUTCOME));
3007+
assertThat(c.getReportingUser(), is(user.toReference()));
3008+
return true;
3009+
}), argThat(p -> p.equals(personCaptor.getValue())), any());
3010+
}
3011+
28193012
private ProcessingResult<ExternalMessageProcessingResult> runFlow(ExternalMessageDto labMessage) throws ExecutionException, InterruptedException {
28203013
ExternalMessageProcessingFacade processingFacade = getExternalMessageProcessingFacade();
28213014
AbstractLabMessageProcessingFlow flow = new AbstractLabMessageProcessingFlow(

0 commit comments

Comments
 (0)