diff --git a/.github/integration-test/evaluate-multiple-stratifiers.sh b/.github/integration-test/evaluate-multiple-stratifiers.sh new file mode 100755 index 0000000..574f237 --- /dev/null +++ b/.github/integration-test/evaluate-multiple-stratifiers.sh @@ -0,0 +1,93 @@ +#!/bin/bash -e + +INPUT_MEASURE=$1 +BASE_OUTPUT_DIR=$PWD/.github/integration-test/evaluate-multiple-stratifiers +mkdir "$BASE_OUTPUT_DIR" + +docker run -v "$INPUT_MEASURE":/app/measure.json -v "$BASE_OUTPUT_DIR":/app/output/ -e FHIR_SERVER=http://fhir-server:8080/fhir \ + -e TZ="$(cat /etc/timezone)" --network integration-test_testing-network fhir-data-evaluator + +today=$(date +"%Y-%m-%d") +OUTPUT_DIR=$(find "$BASE_OUTPUT_DIR" -type d -name "*$today*" | head -n 1) +REPORT="$OUTPUT_DIR/measure-report.json" + +find_stratum() { + local STRATIFIER_INDEX="$1" + local EXPECTED_VALUE="$2" + local STRATUM=$(jq -c --arg EXPECTED_VALUE "$EXPECTED_VALUE" --argjson STRATIFIER_INDEX "$STRATIFIER_INDEX" ' + .group[0].stratifier[$STRATIFIER_INDEX].stratum[] | select(.value.coding[0].code == $EXPECTED_VALUE)' "$REPORT" ) + echo "$STRATUM" +} + +validate_stratum() { + local STRATUM="$1" + local STRATUM_VALUE="$2" + local EXPECTED_INITIAL_POP_COUNT="$3" + local EXPECTED_MEASURE_POP_COUNT="$4" + local EXPECTED_OBSERVATION_POP_COUNT="$5" + local EXPECTED_MEASURE_SCORE="$6" + + local INITIAL_POP_COUNT=$(jq '.population[] | select(.code.coding[0].code == "initial-population").count' <<< "$STRATUM") + local MEASURE_POP_COUNT=$(jq '.population[] | select(.code.coding[0].code == "measure-population").count' <<< "$STRATUM") + local OBSERVATION_POP_COUNT=$(jq '.population[] | select(.code.coding[0].code == "measure-observation").count' <<< "$STRATUM") + local MEASURE_SCORE=$(jq '.measureScore.value' <<< "$STRATUM") + + + + if [ "$INITIAL_POP_COUNT" = "$EXPECTED_INITIAL_POP_COUNT" ]; then + echo "OK 👍: initial population count ($INITIAL_POP_COUNT) equals the expected count for (value=$STRATUM_VALUE)" + else + echo "Fail 😞: stratum count ($INITIAL_POP_COUNT) != $EXPECTED_INITIAL_POP_COUNT for initial population (value=$STRATUM_VALUE)" + exit 1 + fi + + if [ "$MEASURE_POP_COUNT" = "$EXPECTED_MEASURE_POP_COUNT" ]; then + echo "OK 👍: measure population count ($MEASURE_POP_COUNT) equals the expected count for (value=$STRATUM_VALUE)" + else + echo "Fail 😞: stratum count ($MEASURE_POP_COUNT) != $EXPECTED_MEASURE_POP_COUNT for measure population (value=$STRATUM_VALUE)" + exit 1 + fi + +if [ "$OBSERVATION_POP_COUNT" = "$EXPECTED_OBSERVATION_POP_COUNT" ]; then + echo "OK 👍: observation population count ($OBSERVATION_POP_COUNT) equals the expected count for (value=$STRATUM_VALUE)" + else + echo "Fail 😞: stratum count ($OBSERVATION_POP_COUNT) != $EXPECTED_OBSERVATION_POP_COUNT for measure observation (value=$STRATUM_VALUE)" + exit 1 + fi + + if [ "$MEASURE_SCORE" = "$EXPECTED_MEASURE_SCORE" ]; then + echo "OK 👍: measure score ($MEASURE_SCORE) equals the expected count for (value=$STRATUM_VALUE)" + else + echo "Fail 😞: stratum count ($MEASURE_SCORE) != $EXPECTED_MEASURE_SCORE for measure score (value=$STRATUM_VALUE)" + exit 1 + fi +} + +get_stratum() { + local STRATIFIER_INDEX="$1" + local STRATUM_INDEX="$2" + echo $(jq --argjson STRATIFIER_INDEX "$STRATIFIER_INDEX" --argjson STRATUM_INDEX "STRATUM_INDEX" '.population[] + | select(.code.coding[0].code == "initial-population").count' "$STRATUM") +} + + +EXPECTED_STRATUM_VALUE_1="I48.0" +EXPECTED_STRATUM_INITIAL_POP_1=108 +EXPECTED_STRATUM_MEASURE_POP_1=108 +EXPECTED_STRATUM_OBESERVATION_POP_1=108 +EXPECTED_STRATUM_MEASURE_SCORE_1=108 + +EXPECTED_STRATUM_VALUE_2="fail-no-value-found" +EXPECTED_STRATUM_INITIAL_POP_2=12072 +EXPECTED_STRATUM_MEASURE_POP_2=12072 +EXPECTED_STRATUM_OBESERVATION_POP_2=12072 +EXPECTED_STRATUM_MEASURE_SCORE_2=12040 + + +STRATUM=$(find_stratum 0 $EXPECTED_STRATUM_VALUE_1) +validate_stratum "$STRATUM" $EXPECTED_STRATUM_VALUE_1 $EXPECTED_STRATUM_INITIAL_POP_1 $EXPECTED_STRATUM_MEASURE_POP_1 \ + $EXPECTED_STRATUM_OBESERVATION_POP_1 $EXPECTED_STRATUM_MEASURE_SCORE_1 + +STRATUM=$(find_stratum 1 $EXPECTED_STRATUM_VALUE_2) +validate_stratum "$STRATUM" $EXPECTED_STRATUM_VALUE_2 $EXPECTED_STRATUM_INITIAL_POP_2 $EXPECTED_STRATUM_MEASURE_POP_2 \ + $EXPECTED_STRATUM_OBESERVATION_POP_2 $EXPECTED_STRATUM_MEASURE_SCORE_2 diff --git a/.github/integration-test/measures/multiple-stratifiers.json b/.github/integration-test/measures/multiple-stratifiers.json new file mode 100644 index 0000000..56a0343 --- /dev/null +++ b/.github/integration-test/measures/multiple-stratifiers.json @@ -0,0 +1,109 @@ +{ + "resourceType": "Measure", + "meta": { + "profile": [ + "http://fhir-data-evaluator/StructureDefinition/FhirDataEvaluatorContinuousVariableMeasure" + ] + }, + "version": "1.0", + "url": "https://medizininformatik-initiative.de/fhir/fdpg/Measure/ExampleCdsCriteria", + "status": "active", + "experimental": false, + "publisher": "FDPG-Plus", + "name": "ExampleCdsCriteria", + "title": "Example Measure To Count CDS Criteria", + "description": "Example measure to count CDS criteria", + "group": [ + { + "population": [ + { + "code": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-population", + "code": "initial-population" + } + ] + }, + "criteria": { + "language": "text/x-fhir-query", + "expression": "Condition?_profile=https://www.medizininformatik-initiative.de/fhir/core/modul-diagnose/StructureDefinition/Diagnose" + }, + "id": "initial-population-identifier-1" + }, + { + "code": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-population", + "code": "measure-population" + } + ] + }, + "criteria": { + "language": "text/fhirpath", + "expression": "Condition" + }, + "id": "measure-population-identifier-1" + }, + { + "code": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-population", + "code": "measure-observation" + } + ] + }, + "criteria": { + "language": "text/fhirpath", + "expression": "Condition.subject.reference" + }, + "extension": [ + { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-aggregateMethod", + "valueCode": "unique-count" + }, + { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-criteriaReference", + "valueString": "measure-population-identifier-1" + } + ], + "id": "measure-observation-identifier-1" + } + ], + "stratifier": [ + { + "criteria": { + "language": "text/fhirpath", + "expression": "Condition.code.coding.where(system='http://fhir.de/CodeSystem/bfarm/icd-10-gm')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "condition-icd10-code" + } + ] + }, + "id": "strat-1" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Condition.code.coding.where(system='http://snomed.info/sct')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "condition-sct-code" + } + ] + }, + "id": "strat-2" + } + ] + } + ] +} diff --git a/.github/integration-test/test-data/get-mii-testdata.sh b/.github/integration-test/test-data/get-mii-testdata.sh new file mode 100755 index 0000000..4cdd8ce --- /dev/null +++ b/.github/integration-test/test-data/get-mii-testdata.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +MII_TESTDATA_DOWNLOAD_URL="https://health-atlas.de/data_files/594/download?version=1" + +wget -O testdata.zip "$MII_TESTDATA_DOWNLOAD_URL" +unzip testdata.zip -d testdata-temp +cd testdata-temp/Vorhofflimmern || exit + +for file in *.json.zip +do + unzip -o "$file" -d ../../.github/integration-test/Vorhofflimmern +done + +cd ../../ +rm testdata.zip +rm -rf testdata-temp \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 79278ce..c0efdbb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -145,6 +145,24 @@ jobs: - name: Run Integration Test to check if it correctly exits when there are insufficient writing permissions run: .github/integration-test/missing-permissions-test.sh + - name: Remove Blaze volumes + run: docker compose -f .github/integration-test/docker-compose.yml down -v + + - name: Run Blaze with fresh volumes + run: docker compose -f .github/integration-test/docker-compose.yml up -d + + - name: Wait for Blaze + run: .github/scripts/wait-for-url.sh http://localhost:8082/health + + - name: Download New Data + run: .github/integration-test/test-data/get-mii-testdata.sh + + - name: Upload New Data + run: blazectl --no-progress --server http://localhost:8082/fhir upload .github/integration-test/Vorhofflimmern + + - name: Run Integration Test multiple stratifiers + run: .github/integration-test/evaluate-multiple-stratifiers.sh /${PWD}/.github/integration-test/measures/multiple-stratifiers.json + push-image: needs: diff --git a/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/ComponentExpression.java b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/ComponentExpression.java index 2b9d6f5..a125e5f 100644 --- a/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/ComponentExpression.java +++ b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/ComponentExpression.java @@ -1,6 +1,13 @@ package de.medizininformatikinitiative.fhir_data_evaluator; -import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.Base; +import org.hl7.fhir.r4.model.BooleanType; +import org.hl7.fhir.r4.model.CodeType; +import org.hl7.fhir.r4.model.Coding; +import org.hl7.fhir.r4.model.Enumeration; +import org.hl7.fhir.r4.model.ExpressionNode; +import org.hl7.fhir.r4.model.Measure; +import org.hl7.fhir.r4.model.Resource; import org.hl7.fhir.r4.utils.FHIRPathEngine; import java.util.List; diff --git a/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/GroupEvaluator.java b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/GroupEvaluator.java index 6a271ed..bd5db59 100644 --- a/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/GroupEvaluator.java +++ b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/GroupEvaluator.java @@ -1,9 +1,12 @@ package de.medizininformatikinitiative.fhir_data_evaluator; -import de.medizininformatikinitiative.fhir_data_evaluator.populations.AggregateUniqueCount; -import de.medizininformatikinitiative.fhir_data_evaluator.populations.InitialAndMeasureAndObsPopulation; import de.medizininformatikinitiative.fhir_data_evaluator.populations.InitialAndMeasurePopulation; import de.medizininformatikinitiative.fhir_data_evaluator.populations.InitialPopulation; +import de.medizininformatikinitiative.fhir_data_evaluator.populations.increment_populations.InitialAndMeasureAndObsIncrement; +import de.medizininformatikinitiative.fhir_data_evaluator.populations.increment_populations.InitialAndMeasureIncrement; +import de.medizininformatikinitiative.fhir_data_evaluator.populations.increment_populations.InitialIncrement; +import de.medizininformatikinitiative.fhir_data_evaluator.populations.mutable.AggregateUniqueCount; +import de.medizininformatikinitiative.fhir_data_evaluator.populations.mutable.InitialAndMeasureAndObsPopulation; import org.hl7.fhir.r4.model.ExpressionNode; import org.hl7.fhir.r4.model.Measure; import org.hl7.fhir.r4.model.MeasureReport; @@ -15,7 +18,9 @@ import java.util.List; import java.util.Optional; -import static de.medizininformatikinitiative.fhir_data_evaluator.HashableCoding.*; +import static de.medizininformatikinitiative.fhir_data_evaluator.HashableCoding.INITIAL_POPULATION_CODING; +import static de.medizininformatikinitiative.fhir_data_evaluator.HashableCoding.MEASURE_OBSERVATION_CODING; +import static de.medizininformatikinitiative.fhir_data_evaluator.HashableCoding.MEASURE_POPULATION_CODING; import static java.util.Objects.requireNonNull; public class GroupEvaluator { @@ -61,9 +66,9 @@ public Mono evaluateGroup(Measure.Mea private Mono evaluateGroupOfInitial(Flux population, Measure.MeasureGroupComponent group) { var groupReduceOp = new GroupReduceOpInitial(group.getStratifier().stream().map(s -> - new StratifierReduceOp(getComponentExpressions(s))).toList()); + new StratifierReduceOp(getComponentExpressions(s))).toList()); - List> initialStratifierResults = group.getStratifier().stream().map(s -> + List> initialStratifierResults = group.getStratifier().stream().map(s -> StratifierResult.initial(s, InitialPopulation.class)).toList(); return population.reduce(new GroupResult<>(InitialPopulation.ZERO, initialStratifierResults), groupReduceOp) .map(GroupResult::toReportGroup); @@ -73,10 +78,10 @@ private Mono evaluateGroupOfInitialAn Measure.MeasureGroupComponent group, ExpressionNode measurePopulationExpression) { var groupReduceOp = new GroupReduceOpMeasure(group.getStratifier().stream().map(s -> - new StratifierReduceOp(getComponentExpressions(s))).toList(), + new StratifierReduceOp(getComponentExpressions(s))).toList(), measurePopulationExpression, fhirPathEngine); - List> initialStratifierResults = group.getStratifier().stream().map(s -> + List> initialStratifierResults = group.getStratifier().stream().map(s -> StratifierResult.initial(s, InitialAndMeasurePopulation.class)).toList(); return population.reduce(new GroupResult<>(InitialAndMeasurePopulation.ZERO, initialStratifierResults), groupReduceOp) .map(GroupResult::toReportGroup); @@ -87,10 +92,10 @@ private Mono evaluateGroupOfInitialAn ExpressionNode measurePopulationExpression, ExpressionNode observationPopulationExpression) { var groupReduceOp = new GroupReduceOpObservation(group.getStratifier().stream().map(s -> - new StratifierReduceOp(getComponentExpressions(s))).toList(), + new StratifierReduceOp(getComponentExpressions(s))).toList(), measurePopulationExpression, observationPopulationExpression, fhirPathEngine); - List> initialStratifierResults = group.getStratifier().stream().map(s -> + List> initialStratifierResults = group.getStratifier().stream().map(s -> StratifierResult.initial(s, InitialAndMeasureAndObsPopulation.class)).toList(); return population.reduce(new GroupResult<>(InitialAndMeasureAndObsPopulation.empty(), initialStratifierResults), groupReduceOp) .map(GroupResult::toReportGroup); diff --git a/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/GroupReduceOpInitial.java b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/GroupReduceOpInitial.java index cf74a9f..b32d681 100644 --- a/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/GroupReduceOpInitial.java +++ b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/GroupReduceOpInitial.java @@ -1,6 +1,7 @@ package de.medizininformatikinitiative.fhir_data_evaluator; import de.medizininformatikinitiative.fhir_data_evaluator.populations.InitialPopulation; +import de.medizininformatikinitiative.fhir_data_evaluator.populations.increment_populations.InitialIncrement; import org.hl7.fhir.r4.model.Resource; import java.util.List; @@ -19,15 +20,17 @@ * * @param stratifierReduceOps holds one {@link StratifierReduceOp} for each stratifier in a group */ -public record GroupReduceOpInitial(List> stratifierReduceOps) - implements BiFunction, Resource, GroupResult> { +public record GroupReduceOpInitial(List> stratifierReduceOps) + implements BiFunction, Resource, + GroupResult> { public GroupReduceOpInitial { requireNonNull(stratifierReduceOps); } @Override - public GroupResult apply(GroupResult groupResult, Resource resource) { - return groupResult.applyResource(stratifierReduceOps, resource, InitialPopulation.ONE); + public GroupResult apply(GroupResult groupResult, + Resource resource) { + return groupResult.applyResource(stratifierReduceOps, resource, new InitialIncrement()); } } diff --git a/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/GroupReduceOpMeasure.java b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/GroupReduceOpMeasure.java index 949d246..824c59f 100644 --- a/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/GroupReduceOpMeasure.java +++ b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/GroupReduceOpMeasure.java @@ -1,8 +1,8 @@ package de.medizininformatikinitiative.fhir_data_evaluator; import de.medizininformatikinitiative.fhir_data_evaluator.populations.InitialAndMeasurePopulation; -import de.medizininformatikinitiative.fhir_data_evaluator.populations.InitialPopulation; import de.medizininformatikinitiative.fhir_data_evaluator.populations.MeasurePopulation; +import de.medizininformatikinitiative.fhir_data_evaluator.populations.increment_populations.InitialAndMeasureIncrement; import org.hl7.fhir.r4.model.ExpressionNode; import org.hl7.fhir.r4.model.Resource; import org.hl7.fhir.r4.utils.FHIRPathEngine; @@ -25,10 +25,12 @@ * @param stratifierReduceOps holds one {@link StratifierReduceOp} for each stratifier in a group * @param measurePopulationExpression the expression to evaluate the measure population */ -public record GroupReduceOpMeasure(List> stratifierReduceOps, - ExpressionNode measurePopulationExpression, - FHIRPathEngine fhirPathEngine) - implements BiFunction, Resource, GroupResult> { +public record GroupReduceOpMeasure( + List> stratifierReduceOps, + ExpressionNode measurePopulationExpression, + FHIRPathEngine fhirPathEngine) + implements BiFunction, Resource, + GroupResult> { public GroupReduceOpMeasure { requireNonNull(stratifierReduceOps); @@ -37,14 +39,16 @@ public record GroupReduceOpMeasure(List apply(GroupResult groupResult, Resource resource) { + public GroupResult apply( + GroupResult groupResult, + Resource resource) { return groupResult.applyResource(stratifierReduceOps, resource, calcIncrementPopulation(resource)); } - private InitialAndMeasurePopulation calcIncrementPopulation(Resource resource) { - Optional measurePopResource = MeasurePopulation.evaluateMeasurePopResource(resource, measurePopulationExpression, fhirPathEngine); - var evaluatedMeasurePop = measurePopResource.isPresent() ? MeasurePopulation.ONE : MeasurePopulation.ZERO; + private InitialAndMeasureIncrement calcIncrementPopulation(Resource resource) { + Optional measurePopResource = MeasurePopulation.evaluateMeasurePopResource(resource, measurePopulationExpression, + fhirPathEngine); - return new InitialAndMeasurePopulation(InitialPopulation.ONE, evaluatedMeasurePop); + return new InitialAndMeasureIncrement(measurePopResource.isPresent()); } } diff --git a/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/GroupReduceOpObservation.java b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/GroupReduceOpObservation.java index 4828b23..3ba1c98 100644 --- a/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/GroupReduceOpObservation.java +++ b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/GroupReduceOpObservation.java @@ -1,9 +1,8 @@ package de.medizininformatikinitiative.fhir_data_evaluator; -import de.medizininformatikinitiative.fhir_data_evaluator.populations.InitialAndMeasureAndObsPopulation; -import de.medizininformatikinitiative.fhir_data_evaluator.populations.InitialPopulation; import de.medizininformatikinitiative.fhir_data_evaluator.populations.MeasurePopulation; -import de.medizininformatikinitiative.fhir_data_evaluator.populations.ObservationPopulation; +import de.medizininformatikinitiative.fhir_data_evaluator.populations.increment_populations.InitialAndMeasureAndObsIncrement; +import de.medizininformatikinitiative.fhir_data_evaluator.populations.mutable.InitialAndMeasureAndObsPopulation; import org.hl7.fhir.r4.model.Base; import org.hl7.fhir.r4.model.ExpressionNode; import org.hl7.fhir.r4.model.Resource; @@ -29,10 +28,14 @@ * @param measurePopulationExpression the expression to evaluate the measure population * @param observationPopulationExpression the expression to evaluate the observation population */ -public record GroupReduceOpObservation(List> stratifierReduceOps, - ExpressionNode measurePopulationExpression, - ExpressionNode observationPopulationExpression, FHIRPathEngine fhirPathEngine) - implements BiFunction, Resource, GroupResult> { +public record GroupReduceOpObservation( + List> stratifierReduceOps, + ExpressionNode measurePopulationExpression, + ExpressionNode observationPopulationExpression, + FHIRPathEngine fhirPathEngine) + implements BiFunction, + Resource, + GroupResult> { public GroupReduceOpObservation { requireNonNull(stratifierReduceOps); @@ -42,28 +45,21 @@ public record GroupReduceOpObservation(List apply(GroupResult groupResult, Resource resource) { + public GroupResult apply( + GroupResult groupResult, + Resource resource) { return groupResult.applyResource(stratifierReduceOps, resource, calcIncrementPopulation(resource)); } - private InitialAndMeasureAndObsPopulation calcIncrementPopulation(Resource resource) { + private InitialAndMeasureAndObsIncrement calcIncrementPopulation(Resource resource) { + Optional measurePopResource = MeasurePopulation.evaluateMeasurePopResource(resource, measurePopulationExpression, + fhirPathEngine); + var obsVal = measurePopResource.flatMap(r -> evaluateObservationPop(r, observationPopulationExpression)); - Optional measurePopResource = MeasurePopulation.evaluateMeasurePopResource(resource, measurePopulationExpression, fhirPathEngine); - var evaluatedMeasurePop = measurePopResource.isPresent() ? MeasurePopulation.ONE : MeasurePopulation.ZERO; - var evaluatedObsPop = measurePopResource - .map(r -> evaluateObservationPop(r, observationPopulationExpression)) - .orElse(ObservationPopulation.empty()); - - return new InitialAndMeasureAndObsPopulation(InitialPopulation.ONE, evaluatedMeasurePop, evaluatedObsPop); - } - - public ObservationPopulation evaluateObservationPop(Resource resource, ExpressionNode expression) { - Optional value = evaluateObservationPopResource(resource, expression); - - return value.map(ObservationPopulation::initialWithValue).orElse(ObservationPopulation.empty()); + return new InitialAndMeasureAndObsIncrement(measurePopResource.isPresent(), obsVal); } - private Optional evaluateObservationPopResource(Resource resource, ExpressionNode expression) { + private Optional evaluateObservationPop(Resource resource, ExpressionNode expression) { List found = fhirPathEngine.evaluate(resource, expression); if (found.isEmpty()) diff --git a/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/GroupResult.java b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/GroupResult.java index 8f9bcad..65ed76f 100644 --- a/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/GroupResult.java +++ b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/GroupResult.java @@ -1,6 +1,7 @@ package de.medizininformatikinitiative.fhir_data_evaluator; import de.medizininformatikinitiative.fhir_data_evaluator.populations.Population; +import de.medizininformatikinitiative.fhir_data_evaluator.populations.increment_populations.IncrementPopulation; import org.hl7.fhir.r4.model.MeasureReport; import org.hl7.fhir.r4.model.Resource; @@ -15,30 +16,35 @@ * @param populations the count of all resources of the group without any stratification * @param stratifierResults holds the results of each stratifier */ -public record GroupResult>(T populations, List> stratifierResults) { +public record GroupResult, I extends IncrementPopulation>(T populations, + List> stratifierResults) { public GroupResult { requireNonNull(populations); stratifierResults = List.copyOf(stratifierResults); } - public static > GroupResult initial(T populations, List> initialResults) { - return new GroupResult(populations, initialResults); + public static , I extends IncrementPopulation> GroupResult initial(T populations, + List> initialResults) { + return new GroupResult(populations, initialResults); } - public GroupResult applyResource(List> stratifierOperations, Resource resource, T incrementPopulation) { + public GroupResult applyResource(List> stratifierOperations, Resource resource, I incrementPopulation) { assert stratifierResults.size() == stratifierOperations.size(); - var newPopulation = populations.merge(incrementPopulation); - return new GroupResult(newPopulation, applyEachStratifier(stratifierOperations, resource, incrementPopulation)); + var newPopulation = populations.increment(incrementPopulation); + + return new GroupResult(newPopulation, applyEachStratifier(stratifierOperations, resource, incrementPopulation)); } /** * This method assumes that the {@code stratifierOperation} at index {@code i} belongs to the {@code stratifierResult} * at index {@code i}. */ - private List> applyEachStratifier(List> stratifierOperations, Resource resource, T incrementPopulation) { + private List> applyEachStratifier(List> stratifierOperations, + Resource resource, + I incrementPopulation) { return IntStream.range(0, stratifierOperations.size()).mapToObj(i -> - stratifierOperations.get(i).apply(stratifierResults.get(i), resource, incrementPopulation.deepCopy())).toList(); + stratifierOperations.get(i).apply(stratifierResults.get(i), resource, incrementPopulation)).toList(); } public MeasureReport.MeasureReportGroupComponent toReportGroup() { diff --git a/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/StratifierReduceOp.java b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/StratifierReduceOp.java index 777dd3b..dc31e6a 100644 --- a/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/StratifierReduceOp.java +++ b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/StratifierReduceOp.java @@ -1,6 +1,7 @@ package de.medizininformatikinitiative.fhir_data_evaluator; import de.medizininformatikinitiative.fhir_data_evaluator.populations.Population; +import de.medizininformatikinitiative.fhir_data_evaluator.populations.increment_populations.IncrementPopulation; import org.apache.commons.lang3.function.TriFunction; import org.hl7.fhir.r4.model.Resource; @@ -16,16 +17,17 @@ * * @param componentExpressions holds one {@link ComponentExpression} for each component of the stratifier */ -public record StratifierReduceOp>(List componentExpressions) - implements TriFunction, Resource, T, StratifierResult> { +public record StratifierReduceOp, I extends IncrementPopulation>( + List componentExpressions) + implements TriFunction, Resource, I, StratifierResult> { public StratifierReduceOp { componentExpressions = List.copyOf(componentExpressions); } @Override - public StratifierResult apply(StratifierResult s, Resource resource, T newPopulations) { - return s.mergeStratumComponents(evaluateStratifier(resource), newPopulations); + public StratifierResult apply(StratifierResult s, Resource resource, I incrementPopulation) { + return s.mergeStratumComponents(evaluateStratifier(resource), incrementPopulation); } private Set evaluateStratifier(Resource resource) { diff --git a/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/StratifierResult.java b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/StratifierResult.java index 3cb8164..9f3ba0d 100644 --- a/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/StratifierResult.java +++ b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/StratifierResult.java @@ -1,11 +1,16 @@ package de.medizininformatikinitiative.fhir_data_evaluator; import de.medizininformatikinitiative.fhir_data_evaluator.populations.Population; +import de.medizininformatikinitiative.fhir_data_evaluator.populations.increment_populations.IncrementPopulation; import org.hl7.fhir.r4.model.CodeableConcept; import org.hl7.fhir.r4.model.Measure; import org.hl7.fhir.r4.model.MeasureReport; -import java.util.*; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; import static java.util.Objects.requireNonNull; @@ -15,33 +20,39 @@ * In case the Stratifier does not consist of components but of criteria, a set will hold only one {@link StratumComponent}. *

* In the {@code MeasureReport} a {@link StratifierResult} is the equivalent to a {@link MeasureReport.MeasureReportGroupStratifierComponent stratifier}. + *

+ * Note that this record is mutable through the {@link StratifierResult#mergeStratumComponents(Set, IncrementPopulation)} method. * * @param code the code of the stratifier if the stratifier consists of criteria and code * @param populations mutable map of the populations of each found set of values */ -public record StratifierResult>(Optional code, - Map, T> populations) { +public record StratifierResult, I extends IncrementPopulation>( + Optional code, + Map, T> populations) { public StratifierResult { requireNonNull(code); requireNonNull(populations); } - public static > StratifierResult initial(Measure.MeasureGroupStratifierComponent s, Class type) { + public static , I extends IncrementPopulation> StratifierResult initial(Measure.MeasureGroupStratifierComponent s, Class type) { var code = s.hasCode() ? HashableCoding.ofFhirCoding(s.getCode().getCodingFirstRep()) : null; - return new StratifierResult(Optional.ofNullable(code), new HashMap<>()); + return new StratifierResult(Optional.ofNullable(code), new HashMap<>()); } /** - * Merges {@code components} and the corresponding {@code newPopulations} into the {@code StratifierResult} by + * Merges {@code components} and the corresponding {@code incrementPopulation} into the {@code StratifierResult} by * mutating it and then returns itself. * - * @param components the key of the populations to increment - * @param newPopulations the populations used to initialize or increment the strata + * @param components the key of the populations to increment + * @param incrementPopulation the populations used to initialize or increment the strata * @return the mutated {@code StratifierResult} itself */ - public StratifierResult mergeStratumComponents(Set components, T newPopulations) { - populations.merge(components, newPopulations, T::merge); + public StratifierResult mergeStratumComponents(Set components, I incrementPopulation) { + if (populations.containsKey(components)) + populations.put(components, populations.get(components).increment(incrementPopulation)); + else + populations.put(components, incrementPopulation.createNewPopulation()); return this; } @@ -54,7 +65,7 @@ public MeasureReport.MeasureReportGroupStratifierComponent toReportGroupStratifi return reportStratifier; } - private static > MeasureReport.StratifierGroupComponent entryToReport(Map.Entry, T> entry) { + private static , I extends IncrementPopulation> MeasureReport.StratifierGroupComponent entryToReport(Map.Entry, T> entry) { MeasureReport.StratifierGroupComponent stratum = entry.getValue().toReportStratifierGroupComponent(); diff --git a/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/InitialAndMeasurePopulation.java b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/InitialAndMeasurePopulation.java index 4b62bb6..3710b2a 100644 --- a/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/InitialAndMeasurePopulation.java +++ b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/InitialAndMeasurePopulation.java @@ -1,5 +1,7 @@ package de.medizininformatikinitiative.fhir_data_evaluator.populations; +import de.medizininformatikinitiative.fhir_data_evaluator.populations.increment_populations.InitialAndMeasureIncrement; +import de.medizininformatikinitiative.fhir_data_evaluator.populations.increment_populations.InitialIncrement; import org.hl7.fhir.r4.model.MeasureReport; import java.util.List; @@ -14,20 +16,21 @@ * @param measurePopulation the measure population */ public record InitialAndMeasurePopulation(InitialPopulation initialPopulation, MeasurePopulation measurePopulation) - implements Population { + implements Population { public static InitialAndMeasurePopulation ZERO = new InitialAndMeasurePopulation(InitialPopulation.ZERO, MeasurePopulation.ZERO); + /** + * Increments the count of the initial population and the measure population. + * + * @param incrementPopulation the {@link de.medizininformatikinitiative.fhir_data_evaluator.populations.increment_populations.IncrementPopulation} + * to increment the initial population and the measure population + */ @Override - public InitialAndMeasurePopulation merge(InitialAndMeasurePopulation other) { + public InitialAndMeasurePopulation increment(InitialAndMeasureIncrement incrementPopulation) { return new InitialAndMeasurePopulation( - initialPopulation.merge(other.initialPopulation), - measurePopulation.merge(other.measurePopulation)); - } - - @Override - public InitialAndMeasurePopulation deepCopy() { - return new InitialAndMeasurePopulation(initialPopulation.deepCopy(), measurePopulation.deepCopy()); + initialPopulation.increment(new InitialIncrement()), + measurePopulation.increment(incrementPopulation.incrementMeasurePop())); } @Override diff --git a/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/InitialPopulation.java b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/InitialPopulation.java index b9c9cbf..1d4f054 100644 --- a/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/InitialPopulation.java +++ b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/InitialPopulation.java @@ -1,5 +1,6 @@ package de.medizininformatikinitiative.fhir_data_evaluator.populations; +import de.medizininformatikinitiative.fhir_data_evaluator.populations.increment_populations.InitialIncrement; import org.hl7.fhir.r4.model.MeasureReport; import java.util.List; @@ -11,19 +12,20 @@ * * @param count the number of members in the initial population */ -public record InitialPopulation(int count) implements Population { +public record InitialPopulation(int count) implements Population { public static final InitialPopulation ZERO = new InitialPopulation(0); public static final InitialPopulation ONE = new InitialPopulation(1); - - public InitialPopulation merge(InitialPopulation other) { - return new InitialPopulation(count + other.count); - } - + /** + * Increments the count of the initial population. + * + * @param incrementPopulation the {@link de.medizininformatikinitiative.fhir_data_evaluator.populations.increment_populations.IncrementPopulation} + * to increment the initial population + */ @Override - public InitialPopulation deepCopy() { - return new InitialPopulation(count); + public InitialPopulation increment(InitialIncrement incrementPopulation) { + return new InitialPopulation(count + incrementPopulation.getCount()); } @Override diff --git a/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/MeasurePopulation.java b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/MeasurePopulation.java index f5b1c11..9f83460 100755 --- a/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/MeasurePopulation.java +++ b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/MeasurePopulation.java @@ -21,12 +21,13 @@ public record MeasurePopulation(int count) { public static MeasurePopulation ZERO = new MeasurePopulation(0); public static MeasurePopulation ONE = new MeasurePopulation(1); - public MeasurePopulation merge(MeasurePopulation other) { - return new MeasurePopulation(count + other.count); - } - - public MeasurePopulation deepCopy() { - return new MeasurePopulation(count); + /** + * Increments the count of the measure population. + * + * @param increment whether the count of the measure population should be incremented + */ + public MeasurePopulation increment(boolean increment) { + return increment ? new MeasurePopulation(count + 1) : new MeasurePopulation(count); } public static Optional evaluateMeasurePopResource(Resource resource, ExpressionNode expression, FHIRPathEngine fhirPathEngine) { diff --git a/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/Population.java b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/Population.java index ee514aa..eb94ad4 100644 --- a/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/Population.java +++ b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/Population.java @@ -1,5 +1,6 @@ package de.medizininformatikinitiative.fhir_data_evaluator.populations; +import de.medizininformatikinitiative.fhir_data_evaluator.populations.increment_populations.IncrementPopulation; import org.hl7.fhir.r4.model.MeasureReport; /** @@ -11,18 +12,17 @@ * there is a different implementation of this interface. * * @param the type of the population + * @param the increment population for the population type */ -public interface Population> { +public interface Population, I extends IncrementPopulation> { /** - * Merges all populations of two populations. + * Adds the values of the {@link IncrementPopulation} to itself. * - * @param population the population to merge into the current population + * @param incrementPopulation the population to increment the current population * @return the new population containing the merged populations */ - T merge(T population); - - T deepCopy(); + T increment(I incrementPopulation); MeasureReport.StratifierGroupComponent toReportStratifierGroupComponent(); diff --git a/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/increment_populations/IncrementPopulation.java b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/increment_populations/IncrementPopulation.java new file mode 100644 index 0000000..ba7494f --- /dev/null +++ b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/increment_populations/IncrementPopulation.java @@ -0,0 +1,19 @@ +package de.medizininformatikinitiative.fhir_data_evaluator.populations.increment_populations; + +import de.medizininformatikinitiative.fhir_data_evaluator.populations.Population; + +/** + * Immutable class that holds values that are used to increment an existing {@link Population}. + *

+ * Incrementing here means to increment by a single step from a resource evaluation, for example adding 1 to a count. + * + * @param

the type of the population that is incremented + */ +public interface IncrementPopulation

>> { + + /** + * Creates a {@link Population} from an increment population that is used as a new initial value in the populations + * map of a {@link de.medizininformatikinitiative.fhir_data_evaluator.StratifierResult}. + */ + P createNewPopulation(); +} diff --git a/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/increment_populations/InitialAndMeasureAndObsIncrement.java b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/increment_populations/InitialAndMeasureAndObsIncrement.java new file mode 100644 index 0000000..be2d69d --- /dev/null +++ b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/increment_populations/InitialAndMeasureAndObsIncrement.java @@ -0,0 +1,26 @@ +package de.medizininformatikinitiative.fhir_data_evaluator.populations.increment_populations; + +import de.medizininformatikinitiative.fhir_data_evaluator.populations.InitialPopulation; +import de.medizininformatikinitiative.fhir_data_evaluator.populations.MeasurePopulation; +import de.medizininformatikinitiative.fhir_data_evaluator.populations.mutable.InitialAndMeasureAndObsPopulation; +import de.medizininformatikinitiative.fhir_data_evaluator.populations.mutable.ObservationPopulation; + +import java.util.Optional; + +/** + * Represents the increment of an initial population, a measure population and an observation population. + * + * @param incrementMeasurePop whether the measure population should be incremented by one + * @param obsValue the value that might be added to the aggregate method of an observation population + */ +public record InitialAndMeasureAndObsIncrement(boolean incrementMeasurePop, + Optional obsValue) implements IncrementPopulation { + + @Override + public InitialAndMeasureAndObsPopulation createNewPopulation() { + MeasurePopulation measurePopulation = incrementMeasurePop ? MeasurePopulation.ONE : MeasurePopulation.ZERO; + ObservationPopulation observationPopulation = obsValue.map(ObservationPopulation::initialWithValue).orElse(ObservationPopulation.empty()); + + return new InitialAndMeasureAndObsPopulation(InitialPopulation.ONE, measurePopulation, observationPopulation); + } +} diff --git a/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/increment_populations/InitialAndMeasureIncrement.java b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/increment_populations/InitialAndMeasureIncrement.java new file mode 100644 index 0000000..6dd4557 --- /dev/null +++ b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/increment_populations/InitialAndMeasureIncrement.java @@ -0,0 +1,21 @@ +package de.medizininformatikinitiative.fhir_data_evaluator.populations.increment_populations; + +import de.medizininformatikinitiative.fhir_data_evaluator.populations.InitialAndMeasurePopulation; +import de.medizininformatikinitiative.fhir_data_evaluator.populations.InitialPopulation; +import de.medizininformatikinitiative.fhir_data_evaluator.populations.MeasurePopulation; + +/** + * Represents the increment of an initial population and a measure population. + * + * @param incrementMeasurePop whether the measure population should be incremented by one + */ +public record InitialAndMeasureIncrement( + boolean incrementMeasurePop) implements IncrementPopulation { + + @Override + public InitialAndMeasurePopulation createNewPopulation() { + MeasurePopulation measurePopulation = incrementMeasurePop ? MeasurePopulation.ONE : MeasurePopulation.ZERO; + + return new InitialAndMeasurePopulation(InitialPopulation.ONE, measurePopulation); + } +} diff --git a/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/increment_populations/InitialIncrement.java b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/increment_populations/InitialIncrement.java new file mode 100644 index 0000000..253e51a --- /dev/null +++ b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/increment_populations/InitialIncrement.java @@ -0,0 +1,20 @@ +package de.medizininformatikinitiative.fhir_data_evaluator.populations.increment_populations; + +import de.medizininformatikinitiative.fhir_data_evaluator.populations.InitialPopulation; + +/** + * Represents the increment of an initial population. + *

+ * This does not hold any data because an initial population is always incremented by 1. + */ +public record InitialIncrement() implements IncrementPopulation { + + public int getCount() { + return 1; + } + + @Override + public InitialPopulation createNewPopulation() { + return InitialPopulation.ONE; + } +} diff --git a/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/AggregateUniqueCount.java b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/mutable/AggregateUniqueCount.java old mode 100755 new mode 100644 similarity index 67% rename from src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/AggregateUniqueCount.java rename to src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/mutable/AggregateUniqueCount.java index 2d86e3e..526e9f8 --- a/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/AggregateUniqueCount.java +++ b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/mutable/AggregateUniqueCount.java @@ -1,46 +1,44 @@ -package de.medizininformatikinitiative.fhir_data_evaluator.populations; - -import java.util.HashSet; -import java.util.Set; - -/** - * Holds a set of unique {@link String}s. - * - * @param aggregatedValues the set of unique aggregated values - */ -public record AggregateUniqueCount(HashSet aggregatedValues) { - public static final String EXTENSION_URL = "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-aggregateMethod"; - public static final String EXTENSION_VALUE = "unique-count"; - - public AggregateUniqueCount { - aggregatedValues = new HashSet<>(aggregatedValues); - } - - public static AggregateUniqueCount empty() { - return new AggregateUniqueCount(new HashSet<>()); - } - - public static AggregateUniqueCount withValue(String value) { - return new AggregateUniqueCount(new HashSet<>(Set.of(value))); - } - - public AggregateUniqueCount deepCopy() { - return new AggregateUniqueCount(new HashSet<>(aggregatedValues)); - } - - /** - * Mutates {@code aggregatedValues} to merge the {@code aggregatedValues} of another {@link AggregateUniqueCount} - * into itself. - * - * @param a the {@link AggregateUniqueCount} to merge - * @return itself with the mutated {@code aggregatedValues} - */ - public AggregateUniqueCount merge(AggregateUniqueCount a) { - aggregatedValues.addAll(a.aggregatedValues); - return this; - } - - public int getScore() { - return aggregatedValues.size(); - } -} +package de.medizininformatikinitiative.fhir_data_evaluator.populations.mutable; + +import java.util.HashSet; +import java.util.Set; + +/** + * Holds a set of unique {@link String}s. + *

+ * This record is mutable because it holds a mutable {@link HashSet}, which is necessary to efficiently store and add + * many values without copying. + * + * @param aggregatedValues the set of unique aggregated values + */ +public record AggregateUniqueCount(HashSet aggregatedValues) { + public static final String EXTENSION_URL = "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-aggregateMethod"; + public static final String EXTENSION_VALUE = "unique-count"; + + public AggregateUniqueCount { + aggregatedValues = new HashSet<>(aggregatedValues); + } + + public static AggregateUniqueCount empty() { + return new AggregateUniqueCount(new HashSet<>()); + } + + public static AggregateUniqueCount withValue(String value) { + return new AggregateUniqueCount(new HashSet<>(Set.of(value))); + } + + /** + * Mutates {@code aggregatedValues} to add a new value. + * + * @param val the value to add to the aggregated values + * @return itself with the mutated {@code aggregatedValues} + */ + public AggregateUniqueCount addValue(String val) { + aggregatedValues.add(val); + return this; + } + + public int getScore() { + return aggregatedValues.size(); + } +} diff --git a/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/InitialAndMeasureAndObsPopulation.java b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/mutable/InitialAndMeasureAndObsPopulation.java similarity index 57% rename from src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/InitialAndMeasureAndObsPopulation.java rename to src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/mutable/InitialAndMeasureAndObsPopulation.java index 5d52726..cac058b 100644 --- a/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/InitialAndMeasureAndObsPopulation.java +++ b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/mutable/InitialAndMeasureAndObsPopulation.java @@ -1,5 +1,10 @@ -package de.medizininformatikinitiative.fhir_data_evaluator.populations; +package de.medizininformatikinitiative.fhir_data_evaluator.populations.mutable; +import de.medizininformatikinitiative.fhir_data_evaluator.populations.InitialPopulation; +import de.medizininformatikinitiative.fhir_data_evaluator.populations.MeasurePopulation; +import de.medizininformatikinitiative.fhir_data_evaluator.populations.Population; +import de.medizininformatikinitiative.fhir_data_evaluator.populations.increment_populations.InitialAndMeasureAndObsIncrement; +import de.medizininformatikinitiative.fhir_data_evaluator.populations.increment_populations.InitialIncrement; import org.hl7.fhir.r4.model.MeasureReport; import org.hl7.fhir.r4.model.Quantity; @@ -10,6 +15,8 @@ * population. *

* This collection of populations is used on group level and on stratifier level. + *

+ * This record by itself is not mutable, but it holds a mutable {@link AggregateUniqueCount}. * * @param initialPopulation the initial population * @param measurePopulation the measure population @@ -18,24 +25,28 @@ public record InitialAndMeasureAndObsPopulation(InitialPopulation initialPopulation, MeasurePopulation measurePopulation, ObservationPopulation observationPopulation) - implements Population { + implements Population { public static InitialAndMeasureAndObsPopulation empty() { return new InitialAndMeasureAndObsPopulation(InitialPopulation.ZERO, MeasurePopulation.ZERO, ObservationPopulation.empty()); } - @Override - public InitialAndMeasureAndObsPopulation merge(InitialAndMeasureAndObsPopulation other) { - return new InitialAndMeasureAndObsPopulation( - initialPopulation.merge(other.initialPopulation), - measurePopulation.merge(other.measurePopulation), - observationPopulation.merge(other.observationPopulation) - ); - } + /** + * Increments the count of the initial population, measure population and observation population and might add a + * value to the observation population if present in the {@code incrementPopulation}. + * + * @param incrementPopulation the {@link de.medizininformatikinitiative.fhir_data_evaluator.populations.increment_populations.IncrementPopulation} + * to increment the initial population, measure population and observation population + */ @Override - public InitialAndMeasureAndObsPopulation deepCopy() { - return new InitialAndMeasureAndObsPopulation(initialPopulation.deepCopy(), measurePopulation.deepCopy(), observationPopulation.deepCopy()); + public InitialAndMeasureAndObsPopulation increment(InitialAndMeasureAndObsIncrement incrementPopulation) { + ObservationPopulation incrementedObs = incrementPopulation.obsValue().map(observationPopulation::increment).orElse(observationPopulation); + + return new InitialAndMeasureAndObsPopulation( + initialPopulation.increment(new InitialIncrement()), + measurePopulation.increment(incrementPopulation.incrementMeasurePop()), + incrementedObs); } @Override diff --git a/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/ObservationPopulation.java b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/mutable/ObservationPopulation.java old mode 100755 new mode 100644 similarity index 77% rename from src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/ObservationPopulation.java rename to src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/mutable/ObservationPopulation.java index a1cf68c..cc0a35b --- a/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/ObservationPopulation.java +++ b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/mutable/ObservationPopulation.java @@ -1,47 +1,50 @@ -package de.medizininformatikinitiative.fhir_data_evaluator.populations; - -import org.hl7.fhir.r4.model.MeasureReport; - -import static de.medizininformatikinitiative.fhir_data_evaluator.HashableCoding.MEASURE_OBSERVATION_CODING; -import static java.util.Objects.requireNonNull; - -/** - * Represents a measure observation population either on group or on stratifier level. - * - * @param count the number of members in the measure population - * @param aggregateMethod the method to aggregate the members of this population - */ -public record ObservationPopulation(int count, AggregateUniqueCount aggregateMethod) { - - public ObservationPopulation { - requireNonNull(aggregateMethod); - } - - public static ObservationPopulation empty() { - return new ObservationPopulation(0, AggregateUniqueCount.empty()); - } - - public static ObservationPopulation initialWithValue(String value) { - return new ObservationPopulation(1, AggregateUniqueCount.withValue(value)); - } - - public ObservationPopulation deepCopy() { - return new ObservationPopulation(count, aggregateMethod.deepCopy()); - } - - public ObservationPopulation merge(ObservationPopulation other) { - return new ObservationPopulation(count + other.count, aggregateMethod.merge(other.aggregateMethod)); - } - - public MeasureReport.MeasureReportGroupPopulationComponent toReportGroupPopulation() { - return new MeasureReport.MeasureReportGroupPopulationComponent() - .setCode(MEASURE_OBSERVATION_CODING.toCodeableConcept()) - .setCount(count); - } - - public MeasureReport.StratifierGroupPopulationComponent toReportStratifierPopulation() { - return new MeasureReport.StratifierGroupPopulationComponent() - .setCode(MEASURE_OBSERVATION_CODING.toCodeableConcept()) - .setCount(count); - } -} +package de.medizininformatikinitiative.fhir_data_evaluator.populations.mutable; + +import org.hl7.fhir.r4.model.MeasureReport; + +import static de.medizininformatikinitiative.fhir_data_evaluator.HashableCoding.MEASURE_OBSERVATION_CODING; +import static java.util.Objects.requireNonNull; + +/** + * Represents a measure observation population either on group or on stratifier level. + *

+ * This record by itself is not mutable, but it holds a mutable {@link AggregateUniqueCount}. + * + * @param count the number of members in the measure population + * @param aggregateMethod the method to aggregate the members of this population + */ +public record ObservationPopulation(int count, AggregateUniqueCount aggregateMethod) { + + public ObservationPopulation { + requireNonNull(aggregateMethod); + } + + public static ObservationPopulation empty() { + return new ObservationPopulation(0, AggregateUniqueCount.empty()); + } + + public static ObservationPopulation initialWithValue(String value) { + return new ObservationPopulation(1, AggregateUniqueCount.withValue(value)); + } + + /** + * Increments the count of the observation population and adds a value to the aggregate method. + * + * @param value the value to add to the aggregate method + */ + public ObservationPopulation increment(String value) { + return new ObservationPopulation(count + 1, aggregateMethod.addValue(value)); + } + + public MeasureReport.MeasureReportGroupPopulationComponent toReportGroupPopulation() { + return new MeasureReport.MeasureReportGroupPopulationComponent() + .setCode(MEASURE_OBSERVATION_CODING.toCodeableConcept()) + .setCount(count); + } + + public MeasureReport.StratifierGroupPopulationComponent toReportStratifierPopulation() { + return new MeasureReport.StratifierGroupPopulationComponent() + .setCode(MEASURE_OBSERVATION_CODING.toCodeableConcept()) + .setCount(count); + } +} diff --git a/src/test/java/de/medizininformatikinitiative/fhir_data_evaluator/GroupEvaluatorTest.java b/src/test/java/de/medizininformatikinitiative/fhir_data_evaluator/GroupEvaluatorTest.java index bbe6e0a..12107eb 100644 --- a/src/test/java/de/medizininformatikinitiative/fhir_data_evaluator/GroupEvaluatorTest.java +++ b/src/test/java/de/medizininformatikinitiative/fhir_data_evaluator/GroupEvaluatorTest.java @@ -3,7 +3,7 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; -import de.medizininformatikinitiative.fhir_data_evaluator.populations.AggregateUniqueCount; +import de.medizininformatikinitiative.fhir_data_evaluator.populations.mutable.AggregateUniqueCount; import org.hl7.fhir.r4.context.IWorkerContext; import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext; import org.hl7.fhir.r4.model.*; @@ -893,24 +893,24 @@ public void test_oneStratifierElement_twoDifferentComponents_twoDifferentResultV assertThat(result.getStratifier().get(0).getStratum().get(3).getComponent().size()).isEqualTo(2); assertThat(HashableCoding.ofFhirCoding(result.getStratifier().get(0).getStratum().get(0).getComponent().get(0).getCode().getCodingFirstRep())) - .isEqualTo(statusValueKeypair_1.code()); + .isEqualTo(statusValueKeypair_2.code()); assertThat(HashableCoding.ofFhirCoding(result.getStratifier().get(0).getStratum().get(0).getComponent().get(0).getValue().getCodingFirstRep())) - .isEqualTo(statusValueKeypair_1.value()); + .isEqualTo(statusValueKeypair_2.value()); assertThat(HashableCoding.ofFhirCoding(result.getStratifier().get(0).getStratum().get(0).getComponent().get(1).getCode().getCodingFirstRep())) - .isEqualTo(condValueKeypair_2.code()); + .isEqualTo(condValueKeypair_1.code()); assertThat(HashableCoding.ofFhirCoding(result.getStratifier().get(0).getStratum().get(0).getComponent().get(1).getValue().getCodingFirstRep())) - .isEqualTo(condValueKeypair_2.value()); + .isEqualTo(condValueKeypair_1.value()); assertThat(findPopulationByCode(result.getStratifier().get(0).getStratum().get(0), INITIAL_POPULATION_CODING).getCount()) .isEqualTo(1); assertThat(HashableCoding.ofFhirCoding(result.getStratifier().get(0).getStratum().get(1).getComponent().get(0).getCode().getCodingFirstRep())) - .isEqualTo(statusValueKeypair_2.code()); + .isEqualTo(statusValueKeypair_1.code()); assertThat(HashableCoding.ofFhirCoding(result.getStratifier().get(0).getStratum().get(1).getComponent().get(0).getValue().getCodingFirstRep())) - .isEqualTo(statusValueKeypair_2.value()); + .isEqualTo(statusValueKeypair_1.value()); assertThat(HashableCoding.ofFhirCoding(result.getStratifier().get(0).getStratum().get(1).getComponent().get(1).getCode().getCodingFirstRep())) - .isEqualTo(condValueKeypair_1.code()); + .isEqualTo(condValueKeypair_2.code()); assertThat(HashableCoding.ofFhirCoding(result.getStratifier().get(0).getStratum().get(1).getComponent().get(1).getValue().getCodingFirstRep())) - .isEqualTo(condValueKeypair_1.value()); + .isEqualTo(condValueKeypair_2.value()); assertThat(findPopulationByCode(result.getStratifier().get(0).getStratum().get(0), INITIAL_POPULATION_CODING).getCount()) .isEqualTo(1);