From 64db33a2a9df9d9793e93e5ce0aa55296f081f98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathias=20R=C3=BChle?= Date: Mon, 11 Dec 2023 19:09:45 +0100 Subject: [PATCH] Only Copy Necessary Information into Obfuscated Measure Report Check measure report for containing an initial-population. Always use default time period in measure report. --- .../service/EvaluateCqlMeasure.java | 6 +- .../EvaluateStructuredQueryMeasure.java | 7 +- .../service/ObfuscateEvaluationResult.java | 39 +++- .../variables/ConstantsFeasibility.java | 7 + .../ObfuscateEvaluationResultTest.java | 193 ++++++++++++++++-- 5 files changed, 222 insertions(+), 30 deletions(-) diff --git a/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/service/EvaluateCqlMeasure.java b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/service/EvaluateCqlMeasure.java index 10835e3..8cb01b3 100755 --- a/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/service/EvaluateCqlMeasure.java +++ b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/service/EvaluateCqlMeasure.java @@ -14,6 +14,8 @@ import java.util.Objects; +import static de.medizininformatik_initiative.feasibility_dsf_process.variables.ConstantsFeasibility.MEASURE_REPORT_PERIOD_END; +import static de.medizininformatik_initiative.feasibility_dsf_process.variables.ConstantsFeasibility.MEASURE_REPORT_PERIOD_START; import static de.medizininformatik_initiative.feasibility_dsf_process.variables.ConstantsFeasibility.VARIABLE_MEASURE_ID; import static de.medizininformatik_initiative.feasibility_dsf_process.variables.ConstantsFeasibility.VARIABLE_MEASURE_REPORT; @@ -52,8 +54,8 @@ protected void doExecute(DelegateExecution execution, Variables variables) { private MeasureReport executeEvaluateMeasure(String measureId) { logger.debug("Evaluate measure {}", measureId); return storeClient.operation().onInstance("Measure/" + measureId).named("evaluate-measure") - .withParameter(Parameters.class, "periodStart", new DateType(1900, 1, 1)) - .andParameter("periodEnd", new DateType(2100, 1, 1)) + .withParameter(Parameters.class, "periodStart", new DateType(MEASURE_REPORT_PERIOD_START)) + .andParameter("periodEnd", new DateType(MEASURE_REPORT_PERIOD_END)) .useHttpGet() .returnResourceType(MeasureReport.class) .execute(); diff --git a/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/service/EvaluateStructuredQueryMeasure.java b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/service/EvaluateStructuredQueryMeasure.java index f2f02eb..51120e3 100644 --- a/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/service/EvaluateStructuredQueryMeasure.java +++ b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/service/EvaluateStructuredQueryMeasure.java @@ -13,7 +13,6 @@ import org.hl7.fhir.r4.model.MeasureReport.MeasureReportGroupComponent; import org.hl7.fhir.r4.model.MeasureReport.MeasureReportGroupPopulationComponent; import org.hl7.fhir.r4.model.Period; -import org.joda.time.LocalDate; import org.springframework.beans.factory.InitializingBean; import java.io.IOException; @@ -23,6 +22,8 @@ import static de.medizininformatik_initiative.feasibility_dsf_process.variables.ConstantsFeasibility.CODESYSTEM_MEASURE_POPULATION; import static de.medizininformatik_initiative.feasibility_dsf_process.variables.ConstantsFeasibility.CODESYSTEM_MEASURE_POPULATION_VALUE_INITIAL_POPULATION; +import static de.medizininformatik_initiative.feasibility_dsf_process.variables.ConstantsFeasibility.MEASURE_REPORT_PERIOD_END; +import static de.medizininformatik_initiative.feasibility_dsf_process.variables.ConstantsFeasibility.MEASURE_REPORT_PERIOD_START; import static de.medizininformatik_initiative.feasibility_dsf_process.variables.ConstantsFeasibility.VARIABLE_LIBRARY; import static de.medizininformatik_initiative.feasibility_dsf_process.variables.ConstantsFeasibility.VARIABLE_MEASURE; import static de.medizininformatik_initiative.feasibility_dsf_process.variables.ConstantsFeasibility.VARIABLE_MEASURE_REPORT; @@ -78,8 +79,8 @@ private MeasureReport buildMeasureReport(String measureRef, int feasibility) { .setDate(new Date()) .setMeasure(measureRef) .setPeriod(new Period() - .setStart(new LocalDate(1900, 1, 1).toDate()) - .setEnd(new LocalDate(2100, 1, 1).toDate())); + .setStart(MEASURE_REPORT_PERIOD_START) + .setEnd(MEASURE_REPORT_PERIOD_END)); var populationGroup = new MeasureReportGroupPopulationComponent() .setCount(feasibility) diff --git a/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/service/ObfuscateEvaluationResult.java b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/service/ObfuscateEvaluationResult.java index bad722f..c704d10 100644 --- a/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/service/ObfuscateEvaluationResult.java +++ b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/service/ObfuscateEvaluationResult.java @@ -5,12 +5,22 @@ import dev.dsf.bpe.v1.activity.AbstractServiceDelegate; import dev.dsf.bpe.v1.variables.Variables; import org.camunda.bpm.engine.delegate.DelegateExecution; +import org.hl7.fhir.r4.model.CodeableConcept; +import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.MeasureReport; +import org.hl7.fhir.r4.model.MeasureReport.MeasureReportGroupPopulationComponent; +import org.hl7.fhir.r4.model.Period; import org.springframework.beans.factory.InitializingBean; import java.util.Objects; +import static de.medizininformatik_initiative.feasibility_dsf_process.variables.ConstantsFeasibility.CODESYSTEM_MEASURE_POPULATION; +import static de.medizininformatik_initiative.feasibility_dsf_process.variables.ConstantsFeasibility.CODESYSTEM_MEASURE_POPULATION_VALUE_INITIAL_POPULATION; +import static de.medizininformatik_initiative.feasibility_dsf_process.variables.ConstantsFeasibility.MEASURE_REPORT_PERIOD_END; +import static de.medizininformatik_initiative.feasibility_dsf_process.variables.ConstantsFeasibility.MEASURE_REPORT_PERIOD_START; import static de.medizininformatik_initiative.feasibility_dsf_process.variables.ConstantsFeasibility.VARIABLE_MEASURE_REPORT; +import static java.lang.String.format; +import static org.hl7.fhir.r4.model.MeasureReport.MeasureReportType.SUMMARY; public class ObfuscateEvaluationResult extends AbstractServiceDelegate implements InitializingBean { @@ -35,11 +45,32 @@ protected void doExecute(DelegateExecution execution, Variables variables) { } private MeasureReport obfuscateFeasibilityCount(MeasureReport measureReport) { - var obfuscatedFeasibilityCount = obfuscator.obfuscate(measureReport.getGroupFirstRep(). - getPopulationFirstRep().getCount()); + var obfuscatedFeasibilityCount = obfuscator.obfuscate(extractInitialPopulation(measureReport).getCount()); + + var obfuscatedMeasureReport = new MeasureReport() + .setStatus(COMPLETE) + .setType(SUMMARY) + .setMeasure(measureReport.getMeasure()) + .setPeriod(new Period() + .setStart(MEASURE_REPORT_PERIOD_START) + .setEnd(MEASURE_REPORT_PERIOD_END)); + + obfuscatedMeasureReport.getGroupFirstRep().getPopulationFirstRep() + .setCode(new CodeableConcept().addCoding(new Coding().setSystem(CODESYSTEM_MEASURE_POPULATION) + .setCode(CODESYSTEM_MEASURE_POPULATION_VALUE_INITIAL_POPULATION))) + .setCount(obfuscatedFeasibilityCount); - var obfuscatedMeasureReport = measureReport.copy(); - obfuscatedMeasureReport.getGroupFirstRep().getPopulationFirstRep().setCount(obfuscatedFeasibilityCount); return obfuscatedMeasureReport; } + + private MeasureReportGroupPopulationComponent extractInitialPopulation(MeasureReport measureReport) { + return measureReport.getGroupFirstRep().getPopulation().stream() + .filter((p) -> p.getCode().getCoding().stream() + .anyMatch((c) -> c.getSystem().equals(CODESYSTEM_MEASURE_POPULATION) && c.getCode() + .equals(CODESYSTEM_MEASURE_POPULATION_VALUE_INITIAL_POPULATION))) + .findFirst() + .orElseThrow(() -> new RuntimeException( + format("Missing population with coding '%s' in measure report (id '%s').", + CODESYSTEM_MEASURE_POPULATION_VALUE_INITIAL_POPULATION, measureReport.getId()))); + } } diff --git a/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/variables/ConstantsFeasibility.java b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/variables/ConstantsFeasibility.java index ef5d0d4..e361a26 100644 --- a/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/variables/ConstantsFeasibility.java +++ b/feasibility-dsf-process/src/main/java/de/medizininformatik_initiative/feasibility_dsf_process/variables/ConstantsFeasibility.java @@ -1,5 +1,9 @@ package de.medizininformatik_initiative.feasibility_dsf_process.variables; +import org.joda.time.LocalDate; + +import java.util.Date; + public interface ConstantsFeasibility { String VARIABLE_MEASURE = "measure"; String VARIABLE_LIBRARY = "library"; @@ -23,4 +27,7 @@ public interface ConstantsFeasibility { String FEASIBILITY_REQUEST_PROCESS_ID = "medizininformatik-initiativede_feasibilityRequest"; String FEASIBILITY_EXECUTE_PROCESS_ID = "medizininformatik-initiativede_feasibilityExecute"; + + Date MEASURE_REPORT_PERIOD_START = new LocalDate(1900, 1, 1).toDate(); + Date MEASURE_REPORT_PERIOD_END = new LocalDate(2100, 1, 1).toDate(); } diff --git a/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/service/ObfuscateEvaluationResultTest.java b/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/service/ObfuscateEvaluationResultTest.java index ebcc4b8..80045c5 100644 --- a/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/service/ObfuscateEvaluationResultTest.java +++ b/feasibility-dsf-process/src/test/java/de/medizininformatik_initiative/feasibility_dsf_process/service/ObfuscateEvaluationResultTest.java @@ -6,6 +6,8 @@ import dev.dsf.bpe.v1.service.TaskHelper; import dev.dsf.bpe.v1.variables.Variables; import dev.dsf.fhir.authorization.read.ReadAccessHelper; +import org.camunda.bpm.engine.ProcessEngine; +import org.camunda.bpm.engine.RuntimeService; import org.camunda.bpm.engine.delegate.DelegateExecution; import org.hl7.fhir.r4.model.CodeableConcept; import org.hl7.fhir.r4.model.Coding; @@ -16,6 +18,7 @@ import org.hl7.fhir.r4.model.Task; import org.joda.time.LocalDate; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; @@ -28,11 +31,14 @@ import static de.medizininformatik_initiative.feasibility_dsf_process.variables.ConstantsFeasibility.CODESYSTEM_MEASURE_POPULATION; import static de.medizininformatik_initiative.feasibility_dsf_process.variables.ConstantsFeasibility.CODESYSTEM_MEASURE_POPULATION_VALUE_INITIAL_POPULATION; +import static de.medizininformatik_initiative.feasibility_dsf_process.variables.ConstantsFeasibility.MEASURE_REPORT_PERIOD_END; +import static de.medizininformatik_initiative.feasibility_dsf_process.variables.ConstantsFeasibility.MEASURE_REPORT_PERIOD_START; import static de.medizininformatik_initiative.feasibility_dsf_process.variables.ConstantsFeasibility.VARIABLE_MEASURE_REPORT; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.hl7.fhir.r4.model.MeasureReport.MeasureReportStatus.COMPLETE; import static org.hl7.fhir.r4.model.MeasureReport.MeasureReportType.SUMMARY; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -44,6 +50,8 @@ public class ObfuscateEvaluationResultTest { @Mock private FhirWebserviceClientProvider clientProvider; @Mock private TaskHelper taskHelper; + @Mock private ProcessEngine processEngine; + @Mock private RuntimeService runtimeService; @Mock private ReadAccessHelper readAccessHelper; @Mock private DelegateExecution execution; @Mock private ProcessPluginApi api; @@ -52,31 +60,39 @@ public class ObfuscateEvaluationResultTest { private ObfuscateEvaluationResult service; - - @BeforeEach public void setUp() { - var incrementFeasibilityCountObfuscator = new FeasibilityCountIncrementObfuscator(); + var incrementFeasibilityCountObfuscator = new Obfuscator() { + + @Override + public Integer obfuscate(Integer value) { + return value + 1; + } + }; service = new ObfuscateEvaluationResult(incrementFeasibilityCountObfuscator, api); } @Test - public void testDoExecute() throws Exception { + @DisplayName("execution succeeds on measure report containing single population with single coding '" + + CODESYSTEM_MEASURE_POPULATION_VALUE_INITIAL_POPULATION + "'") + public void doExecuteSucceedsSinglePopulationSingleCoding() throws Exception { var feasibilityCount = 5; + var reportDate = new Date(); + var measureUrl = "http://localhost/fhir/Measure/123456"; var measureReport = new MeasureReport() .setStatus(COMPLETE) .setType(SUMMARY) - .setDate(new Date()) - .setMeasure("http://localhost/fhir/Measure/123456") + .setMeasure(measureUrl) + .setDate(reportDate) .setPeriod(new Period() - .setStart(new LocalDate(1900, 1, 1).toDate()) - .setEnd(new LocalDate(2100, 1, 1).toDate())); + .setStart(new LocalDate(1312, 4, 9).toDate()) + .setEnd(new LocalDate(1314, 1, 8).toDate())); var populationGroup = new MeasureReportGroupPopulationComponent() - .setCount(feasibilityCount) .setCode(new CodeableConcept() .addCoding(new Coding() .setSystem(CODESYSTEM_MEASURE_POPULATION) - .setCode(CODESYSTEM_MEASURE_POPULATION_VALUE_INITIAL_POPULATION))); + .setCode(CODESYSTEM_MEASURE_POPULATION_VALUE_INITIAL_POPULATION))) + .setCount(feasibilityCount); measureReport.getGroup() .add(new MeasureReportGroupComponent() .setPopulation(List.of(populationGroup))); @@ -88,19 +104,154 @@ public void testDoExecute() throws Exception { verify(variables).setResource(eq(VARIABLE_MEASURE_REPORT), measureReportCaptor.capture()); var expectedFeasibilityCount = feasibilityCount + 1; - var expectedObfuscatedMeasureReport = measureReport.copy(); - expectedObfuscatedMeasureReport.getGroupFirstRep().getPopulationFirstRep().setCount(expectedFeasibilityCount); var obfuscatedMeasureReport = measureReportCaptor.getValue(); - assertEquals(expectedFeasibilityCount, obfuscatedMeasureReport.getGroupFirstRep().getPopulationFirstRep() - .getCount()); - assertTrue(expectedObfuscatedMeasureReport.equalsDeep(obfuscatedMeasureReport)); + var reportPopulation = obfuscatedMeasureReport.getGroupFirstRep().getPopulationFirstRep(); + assertThat(obfuscatedMeasureReport.getStatus()).isEqualTo(COMPLETE); + assertThat(obfuscatedMeasureReport.getType()).isEqualTo(SUMMARY); + assertThat(obfuscatedMeasureReport.getMeasure()).isEqualTo(measureUrl); + assertThat(obfuscatedMeasureReport.hasDate()).isFalse(); + assertThat(obfuscatedMeasureReport.getPeriod().getStart()).isEqualTo(MEASURE_REPORT_PERIOD_START); + assertThat(obfuscatedMeasureReport.getPeriod().getEnd()).isEqualTo(MEASURE_REPORT_PERIOD_END); + assertThat(reportPopulation.getCode().getCodingFirstRep().getSystem()) + .isEqualTo(CODESYSTEM_MEASURE_POPULATION); + assertThat(reportPopulation.getCode().getCodingFirstRep().getCode()) + .isEqualTo(CODESYSTEM_MEASURE_POPULATION_VALUE_INITIAL_POPULATION); + assertEquals(expectedFeasibilityCount, reportPopulation.getCount()); + } + + @Test + @DisplayName("execution succeeds on measure report containing multiple populations with one having single coding '" + + CODESYSTEM_MEASURE_POPULATION_VALUE_INITIAL_POPULATION + "'") + public void doExecuteSucceedsMultiplePopulationsSingleCoding() throws Exception { + var feasibilityCount = 5; + var reportDate = new Date(); + var measureUrl = "http://localhost/fhir/Measure/123456"; + var measureReport = new MeasureReport() + .setStatus(COMPLETE) + .setType(SUMMARY) + .setMeasure(measureUrl) + .setDate(reportDate) + .setPeriod(new Period() + .setStart(new LocalDate(1316, 4, 9).toDate()) + .setEnd(new LocalDate(1317, 11, 18).toDate())); + var fooPopulationGroup = new MeasureReportGroupPopulationComponent() + .setCode(new CodeableConcept() + .addCoding(new Coding() + .setSystem(CODESYSTEM_MEASURE_POPULATION) + .setCode("measure-population"))) + .setCount(134430); + var initialPopulationGroup = new MeasureReportGroupPopulationComponent() + .setCode(new CodeableConcept() + .addCoding(new Coding() + .setSystem(CODESYSTEM_MEASURE_POPULATION) + .setCode(CODESYSTEM_MEASURE_POPULATION_VALUE_INITIAL_POPULATION))) + .setCount(feasibilityCount); + measureReport.getGroup() + .add(new MeasureReportGroupComponent() + .setPopulation(List.of(fooPopulationGroup, initialPopulationGroup))); + when(api.getVariables(execution)).thenReturn(variables); + when(variables.getResource(VARIABLE_MEASURE_REPORT)).thenReturn(measureReport); + + service.execute(execution); + + verify(variables).setResource(eq(VARIABLE_MEASURE_REPORT), measureReportCaptor.capture()); + assertThat(measureReportCaptor.getValue().getGroupFirstRep().getPopulationFirstRep().getCount()) + .isEqualTo(feasibilityCount + 1); } - private static class FeasibilityCountIncrementObfuscator implements Obfuscator { - @Override - public Integer obfuscate(Integer value) { - return ++value; - } + @Test + @DisplayName("execution succeeds on measure report containing single population with multiple codings including '" + + CODESYSTEM_MEASURE_POPULATION_VALUE_INITIAL_POPULATION + "'") + public void doExecuteSucceedsSinglePopulationsMultipleCodings() throws Exception { + var feasibilityCount = 5; + var reportDate = new Date(); + var measureUrl = "http://localhost/fhir/Measure/123456"; + var measureReport = new MeasureReport() + .setStatus(COMPLETE) + .setType(SUMMARY) + .setMeasure(measureUrl) + .setDate(reportDate) + .setPeriod(new Period() + .setStart(new LocalDate(1312, 4, 9).toDate()) + .setEnd(new LocalDate(1314, 1, 8).toDate())); + var initialPopulationGroup = new MeasureReportGroupPopulationComponent() + .setCode(new CodeableConcept() + .addCoding(new Coding() + .setSystem(CODESYSTEM_MEASURE_POPULATION) + .setCode("measure-population")) + .addCoding(new Coding() + .setSystem(CODESYSTEM_MEASURE_POPULATION) + .setCode(CODESYSTEM_MEASURE_POPULATION_VALUE_INITIAL_POPULATION))) + .setCount(feasibilityCount); + measureReport.getGroup() + .add(new MeasureReportGroupComponent() + .setPopulation(List.of(initialPopulationGroup))); + + when(api.getVariables(execution)).thenReturn(variables); + when(variables.getResource(VARIABLE_MEASURE_REPORT)).thenReturn(measureReport); + + service.execute(execution); + + verify(variables).setResource(eq(VARIABLE_MEASURE_REPORT), measureReportCaptor.capture()); + assertThat(measureReportCaptor.getValue().getGroupFirstRep().getPopulationFirstRep().getCount()) + .isEqualTo(feasibilityCount + 1); + } + + @Test + @DisplayName("execution fails on measure report containing single population with missing coding '" + + CODESYSTEM_MEASURE_POPULATION_VALUE_INITIAL_POPULATION + "'") + void doExecuteFailsOnMissingInitialPopulationInMeasureReport() throws Exception { + var feasibilityCount = 5; + var reportDate = new Date(); + var measureUrl = "http://localhost/fhir/Measure/123456"; + var measureReport = new MeasureReport() + .setStatus(COMPLETE) + .setType(SUMMARY) + .setMeasure(measureUrl) + .setDate(reportDate) + .setPeriod(new Period() + .setStart(new LocalDate(1312, 4, 9).toDate()) + .setEnd(new LocalDate(1314, 1, 8).toDate())); + var reportId = "id-205925"; + measureReport.setId(reportId); + var populationGroup = new MeasureReportGroupPopulationComponent() + .setCode(new CodeableConcept() + .addCoding(new Coding() + .setSystem(CODESYSTEM_MEASURE_POPULATION) + .setCode("measure-population"))) + .setCount(feasibilityCount); + measureReport.getGroup() + .add(new MeasureReportGroupComponent() + .setPopulation(List.of(populationGroup))); + when(variables.getResource(VARIABLE_MEASURE_REPORT)).thenReturn(measureReport); + + assertThatThrownBy(() -> { + service.doExecute(execution, variables); + }).hasMessage("Missing population with coding '%s' in measure report (id '%s').", + CODESYSTEM_MEASURE_POPULATION_VALUE_INITIAL_POPULATION, reportId); + } + + @Test + @DisplayName("execution fails on measure report containing no population") + void doExecuteFailsOnNoPopulationInMeasureReport() throws Exception { + var reportDate = new Date(); + var measureUrl = "http://localhost/fhir/Measure/123456"; + var reportId = "id-205435"; + var measureReport = new MeasureReport() + .setStatus(COMPLETE) + .setType(SUMMARY) + .setMeasure(measureUrl) + .setDate(reportDate) + .setPeriod(new Period() + .setStart(new LocalDate(1312, 4, 9).toDate()) + .setEnd(new LocalDate(1314, 1, 8).toDate())) + .setId(reportId); + when(variables.getResource(VARIABLE_MEASURE_REPORT)).thenReturn(measureReport); + + assertThatThrownBy(() -> { + service.doExecute(execution, variables); + }).hasMessage("Missing population with coding '%s' in measure report (id '%s').", + CODESYSTEM_MEASURE_POPULATION_VALUE_INITIAL_POPULATION, reportId); } }