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/missing-permissions-test.sh b/.github/integration-test/missing-permissions-test.sh new file mode 100755 index 0000000..4832335 --- /dev/null +++ b/.github/integration-test/missing-permissions-test.sh @@ -0,0 +1,19 @@ +#!/bin/bash -e + +BASE_OUTPUT_DIR=$PWD/.github/integration-test/missing-permissions-test +mkdir "$BASE_OUTPUT_DIR" + +# Allow docker to exit with an error +set +e +OUTPUT=$(docker run -v "$PWD"/.github/integration-test/measures/code-measure.json:/app/measure.json \ + -v "$BASE_OUTPUT_DIR":/app/output:ro --network integration-test_testing-network -e FHIR_SERVER=http://fhir-server:8080/fhir \ + fhir-data-evaluator 2>&1) +EXIT_CODE=$? +set -e + +if [ "$OUTPUT" = "Missing writing permissions on output directory" ] && [ $EXIT_CODE = 1 ]; then + echo "OK 👍: docker container exited with code 1 and with the correct error message" +else + echo "Fail 😞: docker container did not exit with code 1 or with a different error message" + exit 1 +fi 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 2152a62..c0efdbb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -142,6 +142,27 @@ jobs: - name: Run Integration Test for Unique Count with Components and with CSV run: .github/integration-test/evaluate-unique-count-with-components-to-csv.sh /${PWD}/.github/integration-test/measures/unique-count-with-components-measure.json + - 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/.gitignore b/.gitignore index 2e9cba3..48b477c 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,6 @@ build/ ### VS Code ### .vscode/ +output + +docker/.env diff --git a/CHANGELOG.md b/CHANGELOG.md index a34f457..8a3af40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,21 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Fixed ### Security +## [1.0.0] - 2024-09-25 + +### Added +- Add Retry to DataStore +- Add Writing Permission Check to Docker Entrypoint +- Add Docker Compose File +- Add Immutable Individuals of Populations to Prevent Further Bugs + +### Changed +- Update KDS Measure +- Improve Readme + +### Fixed +- Fix Wrong Unique Count + ## [0.1.0] - 2024-08-08 ### Added diff --git a/Documentation/example-measures/example-measure-kds.json b/Documentation/example-measures/example-measure-kds.json index 4fa34b7..ac72a92 100644 --- a/Documentation/example-measures/example-measure-kds.json +++ b/Documentation/example-measures/example-measure-kds.json @@ -2,18 +2,17 @@ "resourceType": "Measure", "meta": { "profile": [ - "http://fhir-data-evaluator/StructureDefinition/FhirDataEvaluatorBasicMeasure" + "http://fhir-data-evaluator/StructureDefinition/FhirDataEvaluatorContinuousVariableMeasure" ] }, "version": "1.0", - "url": "https://medizininformatik-initiative.de/fhir/fdpg/Measure/ExampleCdsCodeStratifier", + "url": "https://medizininformatik-initiative.de/fhir/fdpg/Measure/ExampleCdsCriteria", "status": "active", "experimental": false, "publisher": "FDPG-Plus", - "name": "cds-stratifier", - "title": "Example Measure to count basic cds code stratifier", - "description": "Example Measure to count basic cds code stratifier", - "date": "2024-05-10", + "name": "ExampleCdsCriteria", + "title": "Example Measure To Count CDS Criteria", + "description": "Example measure to count CDS criteria", "group": [ { "population": [ @@ -28,9 +27,49 @@ }, "criteria": { "language": "text/x-fhir-query", - "expression": "Condition?_profile=https://www.medizininformatik-initiative.de/fhir/core/modul-diagnose/StructureDefinition/Diagnose" + "expression": "Condition?_profile:below=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": [ @@ -112,7 +151,6 @@ ] }, { - "population": [ { "code": { @@ -125,9 +163,49 @@ }, "criteria": { "language": "text/x-fhir-query", - "expression": "Observation?_profile=https://www.medizininformatik-initiative.de/fhir/core/modul-labor/StructureDefinition/ObservationLab" + "expression": "Observation?_profile:below=https://www.medizininformatik-initiative.de/fhir/core/modul-labor/StructureDefinition/ObservationLab" }, "id": "initial-population-identifier-2" + }, + { + "code": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-population", + "code": "measure-population" + } + ] + }, + "criteria": { + "language": "text/fhirpath", + "expression": "Observation" + }, + "id": "measure-population-identifier-2" + }, + { + "code": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-population", + "code": "measure-observation" + } + ] + }, + "criteria": { + "language": "text/fhirpath", + "expression": "Observation.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-2" + } + ], + "id": "measure-observation-identifier-2" } ], "stratifier": [ @@ -144,7 +222,7 @@ } ] }, - "id": "strat-6" + "id": "strat-7" }, { "criteria": { @@ -159,12 +237,11 @@ } ] }, - "id": "strat-7" + "id": "strat-8" } ] }, { - "population": [ { "code": { @@ -177,46 +254,85 @@ }, "criteria": { "language": "text/x-fhir-query", - "expression": "Procedure?_profile=https://www.medizininformatik-initiative.de/fhir/core/modul-prozedur/StructureDefinition/Procedure" + "expression": "Patient?_profile:below=https://www.medizininformatik-initiative.de/fhir/core/modul-person/StructureDefinition/Patient" }, "id": "initial-population-identifier-3" + }, + { + "code": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-population", + "code": "measure-population" + } + ] + }, + "criteria": { + "language": "text/fhirpath", + "expression": "Patient" + }, + "id": "measure-population-identifier-3" + }, + { + "code": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-population", + "code": "measure-observation" + } + ] + }, + "criteria": { + "language": "text/fhirpath", + "expression": "Patient.id.value" + }, + "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-3" + } + ], + "id": "measure-observation-identifier-3" } ], "stratifier": [ { "criteria": { "language": "text/fhirpath", - "expression": "Procedure.code.coding.where(system='http://fhir.de/CodeSystem/bfarm/ops')" + "expression": "Patient.birthDate.exists()" }, "code": { "coding": [ { - "system": "http://fhir-evaluator/strat/system", - "code": "procedure-ops-code" + "system": "http://fhir-data-evaluator/strat/system", + "code": "patient-birthdate-exists" } ] }, - "id": "strat-8" + "id": "strat-9" }, { "criteria": { "language": "text/fhirpath", - "expression": "Procedure.code.coding.where(system='http://snomed.info/sct')" + "expression": "Patient.gender" }, "code": { "coding": [ { - "system": "http://fhir-evaluator/strat/system", - "code": "procedure-sct-code" + "system": "http://fhir-data-evaluator/strat/system", + "code": "patient-gender" } ] }, - "id": "strat-9" + "id": "strat-10" } ] }, { - "population": [ { "code": { @@ -229,31 +345,85 @@ }, "criteria": { "language": "text/x-fhir-query", - "expression": "Consent?_profile=http://fhir.de/ConsentManagement/StructureDefinition/Consent" + "expression": "Patient?_profile:below=https://www.medizininformatik-initiative.de/fhir/core/modul-person/StructureDefinition/PatientPseudonymisiert" }, - "id": "initial-population-identifier-4" + "id": "initial-population-identifier-3-1" + }, + { + "code": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-population", + "code": "measure-population" + } + ] + }, + "criteria": { + "language": "text/fhirpath", + "expression": "Patient" + }, + "id": "measure-population-identifier-3-1" + }, + { + "code": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-population", + "code": "measure-observation" + } + ] + }, + "criteria": { + "language": "text/fhirpath", + "expression": "Patient.id.value" + }, + "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-3-1" + } + ], + "id": "measure-observation-identifier-3-1" } ], "stratifier": [ { "criteria": { "language": "text/fhirpath", - "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.7')" + "expression": "Patient.birthDate.exists()" }, "code": { "coding": [ { - "system": "http://fhir-evaluator/strat/system", - "code": "consent-mdat-vearbeiten-speichern" + "system": "http://fhir-data-evaluator/strat/system", + "code": "patient-pseudonymised-birthdate-exists" } ] }, - "id": "strat-10" + "id": "strat-9-1" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Patient.gender" + }, + "code": { + "coding": [ + { + "system": "http://fhir-data-evaluator/strat/system", + "code": "patient-pseudonymised-gender" + } + ] + }, + "id": "strat-10-1" } ] }, { - "population": [ { "code": { @@ -266,22 +436,62 @@ }, "criteria": { "language": "text/x-fhir-query", - "expression": "Specimen?_profile=https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Specimen" + "expression": "MedicationAdministration?_profile:below=https://www.medizininformatik-initiative.de/fhir/core/modul-medikation/StructureDefinition/MedicationAdministration" }, - "id": "initial-population-identifier-5" + "id": "initial-population-identifier-4" + }, + { + "code": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-population", + "code": "measure-population" + } + ] + }, + "criteria": { + "language": "text/fhirpath", + "expression": "MedicationAdministration" + }, + "id": "measure-population-identifier-4" + }, + { + "code": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-population", + "code": "measure-observation" + } + ] + }, + "criteria": { + "language": "text/fhirpath", + "expression": "MedicationAdministration.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-4" + } + ], + "id": "measure-observation-identifier-4" } ], "stratifier": [ { "criteria": { "language": "text/fhirpath", - "expression": "Specimen.type.coding.where(system='http://snomed.info/sct')" + "expression": "MedicationAdministration.medicationCodeableConcept.coding.where(system='http://fhir.de/CodeSystem/ifa/pzn').code" }, "code": { "coding": [ { - "system": "http://fhir-evaluator/strat/system", - "code": "specimen-type-sct-code" + "system": "http://fhir-data-evaluator/strat/system", + "code": "medicationadministration-medication-code-pzn" } ] }, @@ -290,32 +500,1115 @@ { "criteria": { "language": "text/fhirpath", - "expression": "Specimen.collection.bodySite.coding.where(system='http://snomed.info/sct')" + "expression": "MedicationAdministration.medicationCodeableConcept.coding.where(system='http://fhir.de/CodeSystem/bfarm/atc').code" }, "code": { "coding": [ { - "system": "http://fhir-evaluator/strat/system", - "code": "specimen-bodysite-sct-code" + "system": "http://fhir-data-evaluator/strat/system", + "code": "medicationadministration-medication-code-atc" } ] }, "id": "strat-12" + } + ] + }, + { + "population": [ + { + "code": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-population", + "code": "initial-population" + } + ] + }, + "criteria": { + "language": "text/x-fhir-query", + "expression": "Procedure?_profile:below=https://www.medizininformatik-initiative.de/fhir/core/modul-prozedur/StructureDefinition/Procedure" + }, + "id": "initial-population-identifier-5" }, { + "code": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-population", + "code": "measure-population" + } + ] + }, "criteria": { "language": "text/fhirpath", - "expression": "Specimen.collection.bodySite.coding.where(system='http://terminology.hl7.org/CodeSystem/icd-o-3')" + "expression": "Procedure" + }, + "id": "measure-population-identifier-5" + }, + { + "code": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-population", + "code": "measure-observation" + } + ] + }, + "criteria": { + "language": "text/fhirpath", + "expression": "Procedure.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-5" + } + ], + "id": "measure-observation-identifier-5" + } + ], + "stratifier": [ + { + "criteria": { + "language": "text/fhirpath", + "expression": "Procedure.code.coding.where(system='http://fhir.de/CodeSystem/bfarm/ops')" }, "code": { "coding": [ { "system": "http://fhir-evaluator/strat/system", - "code": "specimen-bodysite-icdo3-code" + "code": "procedure-ops-code" } ] }, "id": "strat-13" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Procedure.code.coding.where(system='http://snomed.info/sct')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "procedure-sct-code" + } + ] + }, + "id": "strat-14" + } + ] + }, + { + "population": [ + { + "code": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-population", + "code": "initial-population" + } + ] + }, + "criteria": { + "language": "text/x-fhir-query", + "expression": "Consent?_profile:below=https://www.medizininformatik-initiative.de/fhir/modul-consent/StructureDefinition/mii-pr-consent-einwilligung" + }, + "id": "initial-population-identifier-6" + }, + { + "code": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-population", + "code": "measure-population" + } + ] + }, + "criteria": { + "language": "text/fhirpath", + "expression": "Consent" + }, + "id": "measure-population-identifier-6" + }, + { + "code": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-population", + "code": "measure-observation" + } + ] + }, + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.patient.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-6" + } + ], + "id": "measure-observation-identifier-6" + } + ], + "stratifier": [ + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.1')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-patientendaten-erheben,-speichern,-nutzen" + } + ] + }, + "id": "consent-strat-0" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.2')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-idat-erheben" + } + ] + }, + "id": "consent-strat-1" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.3')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-idat-speichern,-verarbeiten" + } + ] + }, + "id": "consent-strat-2" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.4')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-idat-zusammenfuehren-dritte" + } + ] + }, + "id": "consent-strat-3" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.5')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-idat-bereitstellen-eu-dsgvo-niveau" + } + ] + }, + "id": "consent-strat-4" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.6')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-mdat-erheben" + } + ] + }, + "id": "consent-strat-5" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.7')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-mdat-speichern,-verarbeiten" + } + ] + }, + "id": "consent-strat-6" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.8')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-mdat-wissenschaftlich-nutzen-eu-dsgvo-niveau" + } + ] + }, + "id": "consent-strat-7" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.9')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-mdat-zusammenfuehren-dritte" + } + ] + }, + "id": "consent-strat-8" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.37')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-rekontaktierung-ergebnisse-erheblicher-bedeutung" + } + ] + }, + "id": "consent-strat-9" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.44')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-patientendaten-retrospektiv-verarbeiten,-nutzen" + } + ] + }, + "id": "consent-strat-10" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.45')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-mdat-retrospektiv-speichern-verarbeiten" + } + ] + }, + "id": "consent-strat-11" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.46')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-mdat-retrospektiv-wissenschaftlich-nutzen-eu-dsgvo-niveau" + } + ] + }, + "id": "consent-strat-12" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.47')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-mdat-retrospektiv-zusammenfuehren-dritte" + } + ] + }, + "id": "consent-strat-13" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.48')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-patientendaten-weitergabe-non-dsgvo-niveau" + } + ] + }, + "id": "consent-strat-14" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.49')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-mdat-bereitstellen-non-eu-dsgvo-niveau" + } + ] + }, + "id": "consent-strat-15" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.10')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-krankenkassendaten-retrospektiv-uebertragen,-speichern,-nutzen" + } + ] + }, + "id": "consent-strat-16" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.11')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-kkdat-5j-retrospektiv-uebertragen" + } + ] + }, + "id": "consent-strat-17" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.12')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-kkdat-5j-retrospektiv-speichern-verarbeiten" + } + ] + }, + "id": "consent-strat-18" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.13')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-kkdat-5j-retrospektiv-wissenschaftlich-nutzen" + } + ] + }, + "id": "consent-strat-19" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.38')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-kkdat-5j-retrospektiv-uebertragen-kvnr" + } + ] + }, + "id": "consent-strat-20" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.14')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-kkdat-prospektiv-uebertragen-speichern-nutzen" + } + ] + }, + "id": "consent-strat-21" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.15')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-kkdat-5j-prospektiv-uebertragen" + } + ] + }, + "id": "consent-strat-22" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.16')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-kkdat-5j-prospektiv-speichern-verarbeiten" + } + ] + }, + "id": "consent-strat-23" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.17')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-kkdat-5j-prospektiv-wissenschaftlich-nutzen" + } + ] + }, + "id": "consent-strat-24" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.39')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-kkdat-5j-prospektiv-uebertragen-kvnr" + } + ] + }, + "id": "consent-strat-25" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.18')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-biomaterial-erheben,-lagern,-nutzen" + } + ] + }, + "id": "consent-strat-26" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.19')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-biomat-erheben" + } + ] + }, + "id": "consent-strat-27" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.20')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-biomat-lagern-verarbeiten" + } + ] + }, + "id": "consent-strat-28" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.21')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-biomat-eigentum-\u00fcbertragen" + } + ] + }, + "id": "consent-strat-29" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.22')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-biomat-wissenschaftlich-nutzen-eu-dsgvo-niveau" + } + ] + }, + "id": "consent-strat-30" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.23')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-biomat-analysedaten-zusammenfuehren-dritte" + } + ] + }, + "id": "consent-strat-31" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.24')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-biomaterial-zusatzentnahme" + } + ] + }, + "id": "consent-strat-32" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.25')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-biomat-zusatzmengen-entnehmen" + } + ] + }, + "id": "consent-strat-33" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.50')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-biomaterial-retrospektiv-speichern,-nutzen" + } + ] + }, + "id": "consent-strat-34" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.51')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-biomat-retrospektiv-lagern-verarbeiten" + } + ] + }, + "id": "consent-strat-35" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.52')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-biomat-retrospektiv-wissenschaftlich-nutzen-eu-dsgvo-niveau" + } + ] + }, + "id": "consent-strat-36" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.53')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-biomat-retrospektiv-analysedaten-zusammenfuehren-dritte" + } + ] + }, + "id": "consent-strat-37" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.54')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-biomaterial-weitergabe-non-eu-dsgvo-niveau" + } + ] + }, + "id": "consent-strat-38" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.55')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-biomat-bereitstellen-ohne-eu-dsgvo-niveau" + } + ] + }, + "id": "consent-strat-39" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.26')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-rekontaktierung-erg\u00e4nzungen" + } + ] + }, + "id": "consent-strat-40" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.27')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-rekontaktierung-verkn\u00fcpfung-datenbanken" + } + ] + }, + "id": "consent-strat-41" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.28')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-rekontaktierung-weitere-erhebung" + } + ] + }, + "id": "consent-strat-42" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.29')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-rekontaktierung-weitere-studien" + } + ] + }, + "id": "consent-strat-43" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.30')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-rekontaktierung-zusatzbefund" + } + ] + }, + "id": "consent-strat-44" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.31')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-rekontaktierung-zusatzbefund" + } + ] + }, + "id": "consent-strat-45" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.32')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-z1-gecco83-nutzung-num/codex" + } + ] + }, + "id": "consent-strat-46" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.40')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-mdat-gecco83-komplettieren-einmalig" + } + ] + }, + "id": "consent-strat-47" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.43')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-mdat-gecco83-erheben" + } + ] + }, + "id": "consent-strat-48" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.33')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-mdat-gecco83-bereitstellen-num/codex" + } + ] + }, + "id": "consent-strat-49" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.34')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-mdat-gecco83-speichern-verarbeiten-num/codex" + } + ] + }, + "id": "consent-strat-50" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.41')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-mdat-gecco83-wissenschaftlich-nutzen-covid-19-forschung-eu-dsgvo-konform" + } + ] + }, + "id": "consent-strat-51" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.42')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-mdat-gecco83-wissenschaftlich-nutzen-pandemie-forschung-eu-dsgvo-konform" + } + ] + }, + "id": "consent-strat-52" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.56')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-mdat-gecco83-wissenschaftlich-nutzen-num/codex-eu-dsgvo-niveau" + } + ] + }, + "id": "consent-strat-53" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.35')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-z1-gecco83-weitergabe-num/codex-non-eu-dsgvo-niveau" + } + ] + }, + "id": "consent-strat-54" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Consent.provision.provision.code.coding.where(code='2.16.840.1.113883.3.1937.777.24.5.3.36')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "consent-mdat-gecco83-bereitstellen-num/codex-ohne-eu-dsgvo-niveau" + } + ] + }, + "id": "consent-strat-55" + } + ] + }, + { + "population": [ + { + "code": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-population", + "code": "initial-population" + } + ] + }, + "criteria": { + "language": "text/x-fhir-query", + "expression": "Specimen?_profile:below=https://www.medizininformatik-initiative.de/fhir/ext/modul-biobank/StructureDefinition/Specimen" + }, + "id": "initial-population-identifier-7" + }, + { + "code": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-population", + "code": "measure-population" + } + ] + }, + "criteria": { + "language": "text/fhirpath", + "expression": "Specimen" + }, + "id": "measure-population-identifier-7" + }, + { + "code": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-population", + "code": "measure-observation" + } + ] + }, + "criteria": { + "language": "text/fhirpath", + "expression": "Specimen.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-7" + } + ], + "id": "measure-observation-identifier-7" + } + ], + "stratifier": [ + { + "criteria": { + "language": "text/fhirpath", + "expression": "Specimen.type.coding.where(system='http://snomed.info/sct')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "specimen-type-sct-code" + } + ] + }, + "id": "strat-16" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Specimen.collection.bodySite.coding.where(system='http://snomed.info/sct')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "specimen-bodysite-sct-code" + } + ] + }, + "id": "strat-17" + }, + { + "criteria": { + "language": "text/fhirpath", + "expression": "Specimen.collection.bodySite.coding.where(system='http://terminology.hl7.org/CodeSystem/icd-o-3')" + }, + "code": { + "coding": [ + { + "system": "http://fhir-evaluator/strat/system", + "code": "specimen-bodysite-icdo3-code" + } + ] + }, + "id": "strat-18" } ] } diff --git a/README.md b/README.md index 6351fa8..ba62a20 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,34 @@ # Fhir Data Evaluator -## Goal +## Overview -The goal of this project is to provide a service that allows for evaluation of FHIR Measure resources. +The aim of the project is to provide a tool, which can be used to extract metadata information from multiple FHIR servers and combine the data to: +1. Get an overview of how many patients are available for each criterion as identified by the coding of a resources main identifier (e.g. Condition.code.coding, Observation.code, Specimen.type) +2. Get a better understanding of the available data and the actual values in a FHIR server +3. Identify missing and incorrect values + +The FHIR Data Evaluator is a command line program, which based on a FHIR Input Measure configuration iterates through FHIR resources on a FHIR server +and calculates stratifier or statistical counts for values of fields of the evaluated resources. The output is a FHIR MeasureReport (and if configured multiple csv files). + +For example configuring the evaluator to stratify the icd10 code field of the condition resource (FHIR path: Condition.code.coding.where(system='http://fhir.de/CodeSystem/bfarm/icd-10-gm')) +would lead to the following output (once converted to csv): + +```csv +"system","code","display","count","unique count" +"http://fhir.de/CodeSystem/bfarm/icd-10-gm","I95.0",,10811,10797 +"http://fhir.de/CodeSystem/bfarm/icd-10-gm","I60.1",,4,4 +"http://fhir.de/CodeSystem/bfarm/icd-10-gm","I22.8",,4,4 +``` +It further counts the statistical counts per patient (as in: how many patients have this resource with this specific value - unique count above). + +The following types of fields/expressions are currently supported: + +* [Coding](https://www.hl7.org/fhir/datatypes.html#Coding), example: `Condition.code.coding` +* [boolean](https://www.hl7.org/fhir/datatypes.html#boolean), example: `Condition.code.exists()` +* [code](https://www.hl7.org/fhir/datatypes.html#code), example: `Patient.gender` + +For a more detailed Documentation see: * [Documentation](Documentation/Documentation.md) ## Run @@ -16,12 +41,12 @@ An example of a Measure can be found [here](Documentation/example-measures/examp ### MeasureReport as Output only: ```sh -docker run -v :/app/measure.json -v :/app/output -e FHIR_SERVER= -it ghcr.io/medizininformatik-initiative/fhir-data-evaluator:0.1.0 +docker run -v :/app/measure.json -v :/app/output -e FHIR_SERVER= -it ghcr.io/medizininformatik-initiative/fhir-data-evaluator:1.0.0 ``` ### MeasureReport and CSV Output: ```sh -docker run -v :/app/measure.json -v :/app/output -e CONVERT_TO_CSV=true -e FHIR_SERVER= -it ghcr.io/medizininformatik-initiative/fhir-data-evaluator:0.1.0 +docker run -v :/app/measure.json -v :/app/output -e CONVERT_TO_CSV=true -e FHIR_SERVER= -it ghcr.io/medizininformatik-initiative/fhir-data-evaluator:1.0.0 ``` * this generates a CSV file for each Stratifier and stores the files in a directory named after the current date combined with the Measure's name @@ -30,7 +55,7 @@ with the Measure's name ### Usage with Docker Networks * to any of the listed docker run commands add ```--network ``` to run the container inside a Docker network ```sh -docker run -v :/app/measure.json -v :/app/output -e CONVERT_TO_CSV=true -e FHIR_SERVER= --network -it ghcr.io/medizininformatik-initiative/fhir-data-evaluator:0.1.0 +docker run -v :/app/measure.json -v :/app/output -e CONVERT_TO_CSV=true -e FHIR_SERVER= --network -it ghcr.io/medizininformatik-initiative/fhir-data-evaluator:1.0.0 ``` ### Time Zones @@ -38,7 +63,7 @@ When generating the CSV files from the MeasureReport, the files will be saved in combined with the Measure's name. Since it is run inside a Docker container, the time zone might differ from the one on the host machine. If you want to match the time zones, add for example ```-e TZ=Europe/Berlin```: ```sh -docker run -v :/app/measure.json -v :/app/output -e CONVERT_TO_CSV=true -e FHIR_SERVER= -e TZ=Europe/Berlin -it ghcr.io/medizininformatik-initiative/fhir-data-evaluator:0.1.0 +docker run -v :/app/measure.json -v :/app/output -e CONVERT_TO_CSV=true -e FHIR_SERVER= -e TZ=Europe/Berlin -it ghcr.io/medizininformatik-initiative/fhir-data-evaluator:1.0.0 ``` ### Passing Additional Environment Variables: @@ -47,7 +72,7 @@ The environment variables are used inside the docker container, so if they are s be visible in the container. Each additional environment variable can be passed using the `-e` flag. * Example of passing a page count of 50: ```sh -docker run -v :/app/measure.json -v :/app/output -e FHIR_SERVER= -e FHIR_PAGE_COUNT=50 -it ghcr.io/medizininformatik-initiative/fhir-data-evaluator:0.1.0 +docker run -v :/app/measure.json -v :/app/output -e FHIR_SERVER= -e FHIR_PAGE_COUNT=50 -it ghcr.io/medizininformatik-initiative/fhir-data-evaluator:1.0.0 ``` ## Environment Variables @@ -62,7 +87,7 @@ docker run -v :/app/measure.json -v :/ap | FHIR_PAGE_COUNT | 1000 | The number of resources per page to request from the FHIR server. | | FHIR_BEARER_TOKEN | | Bearer token for authentication. | | MAX_IN_MEMORY_SIZE_MIB | 10 | The maximum in-memory buffer size for the webclient in MiB. | -| GENERATE_CSV | false | Whether for the MeasureReport should be generated CSV files. | +| CONVERT_TO_CSV | false | Whether for the MeasureReport should be generated CSV files. | ## Documentation diff --git a/docker/.env.default b/docker/.env.default new file mode 100644 index 0000000..7005a27 --- /dev/null +++ b/docker/.env.default @@ -0,0 +1,11 @@ +FDE_CONVERT_TO_CSV=true +FDE_FHIR_SERVER=http://fhir-server:8080/fhir +FDE_FHIR_USER= +FDE_FHIR_PASSWORD= +FDE_FHIR_MAX_CONNECTIONS=4 +FDE_FHIR_MAX_QUEUE_SIZE=500 +FDE_FHIR_PAGE_COUNT=1000 +FDE_FHIR_BEARER_TOKEN= +FDE_MAX_IN_MEMORY_SIZE_MIB=10 +FDE_INPUT_MEASURE=../Documentation/example-measures/example-measure-kds.json +FDE_OUTPUT_DIR=../output \ No newline at end of file diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000..10cf4e7 --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,21 @@ +services: + fhir-data-evaluator: + image: ghcr.io/medizininformatik-initiative/fhir-data-evaluator:develop + environment: + CONVERT_TO_CSV: ${FDE_CONVERT_TO_CSV:-true} + FHIR_SERVER: ${FDE_FHIR_SERVER:-http://fhir-server:8080/fhir} + FHIR_USER: ${FDE_FHIR_USER:-} + FHIR_PASSWORD: ${FDE_FHIR_PASSWORD:-} + FHIR_MAX_CONNECTIONS: ${FDE_FHIR_MAX_CONNECTIONS:-4} + FHIR_MAX_QUEUE_SIZE: ${FDE_FHIR_MAX_QUEUE_SIZE:-500} + FHIR_PAGE_COUNT: ${FDE_FHIR_PAGE_COUNT:-1000} + FHIR_BEARER_TOKEN: ${FDE_FHIR_BEARER_TOKEN:-} + MAX_IN_MEMORY_SIZE_MIB: ${FDE_MAX_IN_MEMORY_SIZE_MIB:-10} + volumes: + - "${FDE_INPUT_MEASURE:-../Documentation/example-measures/example-measure-kds.json}:/app/measure.json" + - "${FDE_OUTPUT_DIR:-../output}:/app/output" + + + + + diff --git a/docker/docker-entrypoint.sh b/docker/docker-entrypoint.sh index be0e4ed..4fce5a2 100644 --- a/docker/docker-entrypoint.sh +++ b/docker/docker-entrypoint.sh @@ -1,5 +1,10 @@ #!/bin/bash -e +if [ ! -w /app/output ]; then + echo "Missing writing permissions on output directory" >&2 + exit 1 +fi + today=$(date +"%Y-%m-%d_%H-%M-%S") measureName="$(jq -c --raw-output '.name' /app/measure.json)" outputDir="$today-$measureName" diff --git a/pom.xml b/pom.xml index 1f4ff62..22f1bc9 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ de.medizininformatikinitiative fhir_data_evaluator - 0.1.0 + 1.0.0 Fhir Data Evaluator Fhir Data Evaluator @@ -80,6 +80,16 @@ 1.19.7 test + + com.squareup.okhttp3 + mockwebserver + test + + + io.projectreactor + reactor-test + test + 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/DataStore.java b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/DataStore.java index 1573cb2..3718859 100644 --- a/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/DataStore.java +++ b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/DataStore.java @@ -4,12 +4,16 @@ import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Resource; import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatusCode; import org.springframework.stereotype.Component; import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.reactive.function.client.WebClientResponseException; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import reactor.util.retry.Retry; import java.net.URI; +import java.time.Duration; import java.util.Optional; @Component @@ -41,9 +45,16 @@ public Flux getPopulation(String populationQuery) { .expand(bundle -> Optional.ofNullable(bundle.getLink("next")) .map(link -> fetchPage(client, link.getUrl())) .orElse(Mono.empty())) + .retryWhen(Retry.backoff(3, Duration.ofSeconds(1)) + .filter(e -> e instanceof WebClientResponseException && + shouldRetry(((WebClientResponseException) e).getStatusCode()))) .flatMap(bundle -> Flux.fromStream(bundle.getEntry().stream().map(Bundle.BundleEntryComponent::getResource))); } + private static boolean shouldRetry(HttpStatusCode code) { + return code.is5xxServerError() || code.value() == 404; + } + private Mono fetchPage(WebClient client, String url) { return client.get() .uri(URI.create(url)) 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..9907d35 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.individuals.InitialAndMeasureAndObsIndividual; +import de.medizininformatikinitiative.fhir_data_evaluator.populations.individuals.InitialAndMeasureIndividual; +import de.medizininformatikinitiative.fhir_data_evaluator.populations.individuals.InitialIndividual; +import de.medizininformatikinitiative.fhir_data_evaluator.populations.mutable.AggregateUniqueCounter; +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); @@ -158,14 +163,14 @@ private Optional findObservationPopulationExpression(Measure.Mea throw new IllegalArgumentException("Value of Criteria Reference of Measure Observation Population must be equal to the ID of the Measure Population"); } - var aggregateMethods = foundObservationPopulation.getExtensionsByUrl(AggregateUniqueCount.EXTENSION_URL); + var aggregateMethods = foundObservationPopulation.getExtensionsByUrl(AggregateUniqueCounter.EXTENSION_URL); if (aggregateMethods.size() != 1) throw new IllegalArgumentException("Measure Observation Population did not contain exactly one aggregate method"); if (!aggregateMethods.get(0).hasValue()) throw new IllegalArgumentException("Aggregate Method of Measure Observation Population has no value"); - if (!aggregateMethods.get(0).getValue().toString().equals(AggregateUniqueCount.EXTENSION_VALUE)) { - throw new IllegalArgumentException("Aggregate Method of Measure Observation Population has not value '%s'".formatted(AggregateUniqueCount.EXTENSION_VALUE)); + if (!aggregateMethods.get(0).getValue().toString().equals(AggregateUniqueCounter.EXTENSION_VALUE)) { + throw new IllegalArgumentException("Aggregate Method of Measure Observation Population has not value '%s'".formatted(AggregateUniqueCounter.EXTENSION_VALUE)); } return Optional.of(fhirPathEngine.parse(foundObservationPopulation.getCriteria().getExpression())); 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..4420487 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.individuals.InitialIndividual; 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, InitialIndividual.INSTANCE); } } 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..d17c038 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.individuals.InitialAndMeasureIndividual; 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) { - return groupResult.applyResource(stratifierReduceOps, resource, calcIncrementPopulation(resource)); + public GroupResult apply( + GroupResult groupResult, + Resource resource) { + return groupResult.applyResource(stratifierReduceOps, resource, calcIncrementIndividual(resource)); } - private InitialAndMeasurePopulation calcIncrementPopulation(Resource resource) { - Optional measurePopResource = MeasurePopulation.evaluateMeasurePopResource(resource, measurePopulationExpression, fhirPathEngine); - var evaluatedMeasurePop = measurePopResource.isPresent() ? MeasurePopulation.ONE : MeasurePopulation.ZERO; + private InitialAndMeasureIndividual calcIncrementIndividual(Resource resource) { + Optional measurePopResource = MeasurePopulation.evaluateMeasurePopResource(resource, measurePopulationExpression, + fhirPathEngine); - return new InitialAndMeasurePopulation(InitialPopulation.ONE, evaluatedMeasurePop); + return new InitialAndMeasureIndividual(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..584e00c 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.individuals.InitialAndMeasureAndObsIndividual; +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) { - return groupResult.applyResource(stratifierReduceOps, resource, calcIncrementPopulation(resource)); + public GroupResult apply( + GroupResult groupResult, + Resource resource) { + return groupResult.applyResource(stratifierReduceOps, resource, calcIncrementIndividual(resource)); } - private InitialAndMeasureAndObsPopulation calcIncrementPopulation(Resource resource) { + private InitialAndMeasureAndObsIndividual calcIncrementIndividual(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 InitialAndMeasureAndObsIndividual(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 c076ccb..51c76e6 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.individuals.Individual; 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 Individual>(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 Individual> 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 incrementIndividual) { assert stratifierResults.size() == stratifierOperations.size(); - var newPopulation = populations.merge(incrementPopulation); - return new GroupResult(newPopulation, applyEachStratifier(stratifierOperations, resource, incrementPopulation)); + var newPopulation = populations.increment(incrementIndividual); + + return new GroupResult(newPopulation, applyEachStratifier(stratifierOperations, resource, incrementIndividual)); } /** * 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 incrementIndividual) { return IntStream.range(0, stratifierOperations.size()).mapToObj(i -> - stratifierOperations.get(i).apply(stratifierResults.get(i), resource, incrementPopulation)).toList(); + stratifierOperations.get(i).apply(stratifierResults.get(i), resource, incrementIndividual)).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..1436e3a 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.individuals.Individual; 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 Individual>( + 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 incrementIndividual) { + return s.mergeStratumComponents(evaluateStratifier(resource), incrementIndividual); } 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..9b460b8 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.individuals.Individual; 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,36 @@ * 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, Individual)} 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 Individual>( + Optional code, + Map, T> populations) { public StratifierResult { requireNonNull(code); requireNonNull(populations); } - public static > StratifierResult initial(Measure.MeasureGroupStratifierComponent s, Class type) { + public static , I extends Individual> 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 individual} 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 - * @return the mutated {@code StratifierResult} itself + * @param components the key of the population to increment + * @param individual the {@link Individual} used to initialize or increment the strata + * @return the mutated {@link StratifierResult} itself */ - public StratifierResult mergeStratumComponents(Set components, T newPopulations) { - populations.merge(components, newPopulations, T::merge); + public StratifierResult mergeStratumComponents(Set components, I individual) { + populations.compute(components, (k, p) -> p == null ? individual.toPopulation() : p.increment(individual)); return this; } @@ -54,7 +62,7 @@ public MeasureReport.MeasureReportGroupStratifierComponent toReportGroupStratifi return reportStratifier; } - private static > MeasureReport.StratifierGroupComponent entryToReport(Map.Entry, T> entry) { + private static , I extends Individual> 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/AggregateUniqueCount.java b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/AggregateUniqueCount.java deleted file mode 100755 index ae30555..0000000 --- a/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/AggregateUniqueCount.java +++ /dev/null @@ -1,42 +0,0 @@ -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))); - } - - /** - * 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(); - } -} 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 679633c..9d1c9f7 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,8 @@ package de.medizininformatikinitiative.fhir_data_evaluator.populations; +import de.medizininformatikinitiative.fhir_data_evaluator.populations.individuals.Individual; +import de.medizininformatikinitiative.fhir_data_evaluator.populations.individuals.InitialAndMeasureIndividual; +import de.medizininformatikinitiative.fhir_data_evaluator.populations.individuals.InitialIndividual; import org.hl7.fhir.r4.model.MeasureReport; import java.util.List; @@ -14,15 +17,20 @@ * @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 individual the {@link Individual} used to increment the initial population and the measure population + */ @Override - public InitialAndMeasurePopulation merge(InitialAndMeasurePopulation other) { + public InitialAndMeasurePopulation increment(InitialAndMeasureIndividual individual) { return new InitialAndMeasurePopulation( - initialPopulation.merge(other.initialPopulation), - measurePopulation.merge(other.measurePopulation)); + initialPopulation.increment(InitialIndividual.INSTANCE), + individual.containsMeasurePop() ? measurePopulation.increment() : measurePopulation); } @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 37f0cde..770d112 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,7 @@ package de.medizininformatikinitiative.fhir_data_evaluator.populations; +import de.medizininformatikinitiative.fhir_data_evaluator.populations.individuals.Individual; +import de.medizininformatikinitiative.fhir_data_evaluator.populations.individuals.InitialIndividual; import org.hl7.fhir.r4.model.MeasureReport; import java.util.List; @@ -11,14 +13,19 @@ * * @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 individual the {@link Individual} used to increment the initial population + */ + @Override + public InitialPopulation increment(InitialIndividual individual) { + return new InitialPopulation(count + individual.count()); } @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 1d4536c..069af02 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,8 +21,11 @@ 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); + /** + * Increments the count of the measure population. + */ + public MeasurePopulation increment() { + return new MeasurePopulation(count + 1); } 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 16b373a..05d540f 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.individuals.Individual; import org.hl7.fhir.r4.model.MeasureReport; /** @@ -11,16 +12,17 @@ * there is a different implementation of this interface. * * @param the type of the population + * @param the corresponding individual of the population type, which is used to increment the population */ -public interface Population> { +public interface Population, I extends Individual> { /** - * Merges all populations of two populations. + * Adds the values of the {@link Individual} to this population. * - * @param population the population to merge into the current population - * @return the new population containing the merged populations + * @param individual the {@link Individual} used to increment the current population + * @return the new population containing the data of both the old population and the individual */ - T merge(T population); + T increment(I individual); MeasureReport.StratifierGroupComponent toReportStratifierGroupComponent(); diff --git a/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/individuals/Individual.java b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/individuals/Individual.java new file mode 100644 index 0000000..56b8615 --- /dev/null +++ b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/individuals/Individual.java @@ -0,0 +1,20 @@ +package de.medizininformatikinitiative.fhir_data_evaluator.populations.individuals; + +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. So + * this class represents one individual of a population. + * + * @param

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

>> { + + /** + * Creates a {@link Population} from an individual that is used as a new initial value in the populations map of a + * {@link de.medizininformatikinitiative.fhir_data_evaluator.StratifierResult}. + */ + P toPopulation(); +} diff --git a/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/individuals/InitialAndMeasureAndObsIndividual.java b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/individuals/InitialAndMeasureAndObsIndividual.java new file mode 100644 index 0000000..e4a40a4 --- /dev/null +++ b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/individuals/InitialAndMeasureAndObsIndividual.java @@ -0,0 +1,27 @@ +package de.medizininformatikinitiative.fhir_data_evaluator.populations.individuals; + +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 an individual of an initial population, a measure population and an observation population. + * + * @param containsMeasurePop whether the measure population is part of the individual and thus should be incremented by + * one + * @param obsValue the value that might be added to the aggregate method of an observation population + */ +public record InitialAndMeasureAndObsIndividual(boolean containsMeasurePop, + Optional obsValue) implements Individual { + + @Override + public InitialAndMeasureAndObsPopulation toPopulation() { + return new InitialAndMeasureAndObsPopulation( + InitialPopulation.ONE, + containsMeasurePop ? MeasurePopulation.ONE : MeasurePopulation.ZERO, + obsValue.map(ObservationPopulation::initialWithValue).orElse(ObservationPopulation.empty())); + } +} diff --git a/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/individuals/InitialAndMeasureIndividual.java b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/individuals/InitialAndMeasureIndividual.java new file mode 100644 index 0000000..1c00077 --- /dev/null +++ b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/individuals/InitialAndMeasureIndividual.java @@ -0,0 +1,22 @@ +package de.medizininformatikinitiative.fhir_data_evaluator.populations.individuals; + +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 an individual of an initial population and a measure population. + * + * @param containsMeasurePop whether the measure population is part of the individual and thus should be incremented by + * one + */ +public record InitialAndMeasureIndividual( + boolean containsMeasurePop) implements Individual { + + @Override + public InitialAndMeasurePopulation toPopulation() { + return new InitialAndMeasurePopulation( + InitialPopulation.ONE, + containsMeasurePop ? MeasurePopulation.ONE : MeasurePopulation.ZERO); + } +} diff --git a/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/individuals/InitialIndividual.java b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/individuals/InitialIndividual.java new file mode 100644 index 0000000..42a7eca --- /dev/null +++ b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/individuals/InitialIndividual.java @@ -0,0 +1,21 @@ +package de.medizininformatikinitiative.fhir_data_evaluator.populations.individuals; + +import de.medizininformatikinitiative.fhir_data_evaluator.populations.InitialPopulation; + +/** + * Represents an individual of an initial population. + *

+ * This does not hold any data because an initial population is always incremented by 1. + */ +public enum InitialIndividual implements Individual { + INSTANCE; + + public int count() { + return 1; + } + + @Override + public InitialPopulation toPopulation() { + return InitialPopulation.ONE; + } +} diff --git a/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/mutable/AggregateUniqueCounter.java b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/mutable/AggregateUniqueCounter.java new file mode 100644 index 0000000..0098e42 --- /dev/null +++ b/src/main/java/de/medizininformatikinitiative/fhir_data_evaluator/populations/mutable/AggregateUniqueCounter.java @@ -0,0 +1,48 @@ +package de.medizininformatikinitiative.fhir_data_evaluator.populations.mutable; + +import org.hl7.fhir.r4.model.Quantity; + +import java.util.HashSet; +import java.util.Set; + +import static java.util.Objects.requireNonNull; + +/** + * 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 AggregateUniqueCounter(Set 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 AggregateUniqueCounter { + aggregatedValues = new HashSet<>(aggregatedValues); + } + + public static AggregateUniqueCounter of() { + return new AggregateUniqueCounter(Set.of()); + } + + public static AggregateUniqueCounter of(String value) { + return new AggregateUniqueCounter(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 AggregateUniqueCounter addValue(String val) { + aggregatedValues.add(requireNonNull(val)); + return this; + } + + public Quantity score() { + return new Quantity(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 53% 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 2a18cd4..d260945 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,7 +1,12 @@ -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.individuals.Individual; +import de.medizininformatikinitiative.fhir_data_evaluator.populations.individuals.InitialAndMeasureAndObsIndividual; +import de.medizininformatikinitiative.fhir_data_evaluator.populations.individuals.InitialIndividual; import org.hl7.fhir.r4.model.MeasureReport; -import org.hl7.fhir.r4.model.Quantity; import java.util.List; @@ -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 AggregateUniqueCounter}. * * @param initialPopulation the initial population * @param measurePopulation the measure population @@ -18,19 +25,26 @@ 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()); } + + /** + * 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 individual}. + * + * @param individual the {@link Individual} used to increment the initial population, measure population and + * observation population + */ @Override - public InitialAndMeasureAndObsPopulation merge(InitialAndMeasureAndObsPopulation other) { + public InitialAndMeasureAndObsPopulation increment(InitialAndMeasureAndObsIndividual individual) { return new InitialAndMeasureAndObsPopulation( - initialPopulation.merge(other.initialPopulation), - measurePopulation.merge(other.measurePopulation), - observationPopulation.merge(other.observationPopulation) - ); + initialPopulation.increment(InitialIndividual.INSTANCE), + individual.containsMeasurePop() ? measurePopulation.increment() : measurePopulation, + individual.obsValue().map(observationPopulation::increment).orElse(observationPopulation)); } @Override @@ -41,7 +55,7 @@ public MeasureReport.StratifierGroupComponent toReportStratifierGroupComponent() measurePopulation.toReportStratifierPopulation(), observationPopulation.toReportStratifierPopulation()) ) - .setMeasureScore(new Quantity(observationPopulation.aggregateMethod().getScore())); + .setMeasureScore(observationPopulation.aggregateMethod().score()); } @Override @@ -52,6 +66,6 @@ public MeasureReport.MeasureReportGroupComponent toReportGroupComponent() { measurePopulation.toReportGroupPopulation(), observationPopulation.toReportGroupPopulation()) ) - .setMeasureScore(new Quantity(observationPopulation.aggregateMethod().getScore())); + .setMeasureScore(observationPopulation.aggregateMethod().score()); } } 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 70% 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 0bf1f71..eb923df --- 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,43 +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 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 is mutable because it holds a mutable {@link AggregateUniqueCounter}. + * + * @param count the number of members in the observation population + * @param aggregateMethod the method to aggregate the members of this population + */ +public record ObservationPopulation(int count, AggregateUniqueCounter aggregateMethod) { + + public ObservationPopulation { + requireNonNull(aggregateMethod); + } + + public static ObservationPopulation empty() { + return new ObservationPopulation(0, AggregateUniqueCounter.of()); + } + + public static ObservationPopulation initialWithValue(String value) { + return new ObservationPopulation(1, AggregateUniqueCounter.of(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(requireNonNull(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/DataStoreTest.java b/src/test/java/de/medizininformatikinitiative/fhir_data_evaluator/DataStoreTest.java new file mode 100644 index 0000000..0b0af6e --- /dev/null +++ b/src/test/java/de/medizininformatikinitiative/fhir_data_evaluator/DataStoreTest.java @@ -0,0 +1,82 @@ +package de.medizininformatikinitiative.fhir_data_evaluator; + + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.parser.IParser; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.reactive.function.client.WebClientResponseException; +import reactor.test.StepVerifier; + +import java.io.IOException; + +class DataStoreTest { + private static MockWebServer mockStore; + + private DataStore dataStore; + + @BeforeAll + static void setUp() throws IOException { + mockStore = new MockWebServer(); + mockStore.start(); + } + + @AfterAll + static void tearDown() throws IOException { + mockStore.shutdown(); + } + + @BeforeEach + void initialize() { + WebClient client = WebClient.builder() + .baseUrl("http://localhost:%d/fhir".formatted(mockStore.getPort())) + .defaultHeader("Accept", "application/fhir+json") + .build(); + IParser parser = FhirContext.forR4().newJsonParser(); + dataStore = new DataStore(client, parser, 1000); + } + + @ParameterizedTest + @DisplayName("retires the request") + @ValueSource(ints = {404, 500, 503, 504}) + void execute_retry(int statusCode) { + mockStore.enqueue(new MockResponse().setResponseCode(statusCode)); + mockStore.enqueue(new MockResponse().setResponseCode(200) + .setBody("{\"resourceType\":\"Bundle\", \"entry\": [{\"resource\": {\"resourceType\":\"Observation\"}}]}")); + + var result = dataStore.getPopulation("/Observation"); + StepVerifier.create(result).expectNextCount(1).verifyComplete(); + } + + @Test + @DisplayName("fails after 3 unsuccessful retires") + void execute_retry_fails() { + mockStore.enqueue(new MockResponse().setResponseCode(500)); + mockStore.enqueue(new MockResponse().setResponseCode(500)); + mockStore.enqueue(new MockResponse().setResponseCode(500)); + mockStore.enqueue(new MockResponse().setResponseCode(500)); + mockStore.enqueue(new MockResponse().setResponseCode(200)); + + var result = dataStore.getPopulation("/Observation"); + + StepVerifier.create(result).expectErrorMessage("Retries exhausted: 3/3").verify(); + } + + @Test + @DisplayName("doesn't retry a 400") + void execute_retry_400() { + mockStore.enqueue(new MockResponse().setResponseCode(400)); + + var result = dataStore.getPopulation("/Observation"); + + StepVerifier.create(result).expectError(WebClientResponseException.BadRequest.class).verify(); + } +} 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..fa35a11 100644 --- a/src/test/java/de/medizininformatikinitiative/fhir_data_evaluator/GroupEvaluatorTest.java +++ b/src/test/java/de/medizininformatikinitiative/fhir_data_evaluator/GroupEvaluatorTest.java @@ -3,11 +3,23 @@ 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.AggregateUniqueCounter; import org.hl7.fhir.r4.context.IWorkerContext; import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext; -import org.hl7.fhir.r4.model.*; +import org.hl7.fhir.r4.model.CodeType; +import org.hl7.fhir.r4.model.CodeableConcept; +import org.hl7.fhir.r4.model.Coding; +import org.hl7.fhir.r4.model.Condition; import org.hl7.fhir.r4.model.Enumerations.AdministrativeGender; +import org.hl7.fhir.r4.model.Expression; +import org.hl7.fhir.r4.model.Extension; +import org.hl7.fhir.r4.model.Measure; +import org.hl7.fhir.r4.model.MeasureReport; +import org.hl7.fhir.r4.model.Observation; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Quantity; +import org.hl7.fhir.r4.model.Reference; +import org.hl7.fhir.r4.model.StringType; import org.hl7.fhir.r4.utils.FHIRPathEngine; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -21,7 +33,13 @@ import java.math.BigDecimal; import java.util.List; -import static de.medizininformatikinitiative.fhir_data_evaluator.HashableCoding.*; +import static de.medizininformatikinitiative.fhir_data_evaluator.HashableCoding.FAIL_INVALID_TYPE; +import static de.medizininformatikinitiative.fhir_data_evaluator.HashableCoding.FAIL_MISSING_FIELDS; +import static de.medizininformatikinitiative.fhir_data_evaluator.HashableCoding.FAIL_NO_VALUE_FOUND; +import static de.medizininformatikinitiative.fhir_data_evaluator.HashableCoding.FAIL_TOO_MANY_VALUES; +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 org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.when; @@ -115,7 +133,7 @@ public static Measure.MeasureGroupPopulationComponent getObservationPopulation(S .setCriteria(new Expression().setExpression(fhirpath).setLanguage(FHIR_PATH)) .setCode(new CodeableConcept(new Coding().setSystem(POPULATION_SYSTEM).setCode(OBSERVATION_POPULATION_CODE))) .setExtension(List.of( - new Extension(AggregateUniqueCount.EXTENSION_URL).setValue(new CodeType(AggregateUniqueCount.EXTENSION_VALUE)), + new Extension(AggregateUniqueCounter.EXTENSION_URL).setValue(new CodeType(AggregateUniqueCounter.EXTENSION_VALUE)), new Extension(CRITERIA_REFERENCE_URL).setValue(new CodeType(MEASURE_POPULATION_ID)))); } @@ -327,7 +345,7 @@ public void test_observationPopulation_withoutCriteriaReference() { getMeasurePopulation(MEASURE_POPULATION_PATH), (Measure.MeasureGroupPopulationComponent) getObservationPopulation(OBSERVATION_POPULATION_PATH) .setExtension(List.of( - new Extension(AggregateUniqueCount.EXTENSION_URL).setValue(new CodeType(AggregateUniqueCount.EXTENSION_VALUE)))))); + new Extension(AggregateUniqueCounter.EXTENSION_URL).setValue(new CodeType(AggregateUniqueCounter.EXTENSION_VALUE)))))); assertThatThrownBy(() -> groupEvaluator.evaluateGroup(measureGroup).block()) .isInstanceOf(IllegalArgumentException.class) @@ -344,7 +362,7 @@ public void test_observationPopulation_withTooManyCriteriaReferences() { getMeasurePopulation(MEASURE_POPULATION_PATH), (Measure.MeasureGroupPopulationComponent) getObservationPopulation(OBSERVATION_POPULATION_PATH) .setExtension(List.of( - new Extension(AggregateUniqueCount.EXTENSION_URL).setValue(new CodeType(AggregateUniqueCount.EXTENSION_VALUE)), + new Extension(AggregateUniqueCounter.EXTENSION_URL).setValue(new CodeType(AggregateUniqueCounter.EXTENSION_VALUE)), new Extension(CRITERIA_REFERENCE_URL).setValue(new CodeType(MEASURE_POPULATION_ID)), new Extension(CRITERIA_REFERENCE_URL).setValue(new CodeType(MEASURE_POPULATION_ID)))))); @@ -363,7 +381,7 @@ public void test_observationPopulation_criteriaReferenceWithNoValue() { getMeasurePopulation(MEASURE_POPULATION_PATH), (Measure.MeasureGroupPopulationComponent) getObservationPopulation(OBSERVATION_POPULATION_PATH) .setExtension(List.of( - new Extension(AggregateUniqueCount.EXTENSION_URL).setValue(new CodeType(AggregateUniqueCount.EXTENSION_VALUE)), + new Extension(AggregateUniqueCounter.EXTENSION_URL).setValue(new CodeType(AggregateUniqueCounter.EXTENSION_VALUE)), new Extension(CRITERIA_REFERENCE_URL))))); assertThatThrownBy(() -> groupEvaluator.evaluateGroup(measureGroup).block()) @@ -381,7 +399,7 @@ public void test_obesrvationPopulation_criteriaReferenceWithWrongValue() { getMeasurePopulation(MEASURE_POPULATION_PATH), (Measure.MeasureGroupPopulationComponent) getObservationPopulation(OBSERVATION_POPULATION_PATH) .setExtension(List.of( - new Extension(AggregateUniqueCount.EXTENSION_URL).setValue(new CodeType(AggregateUniqueCount.EXTENSION_VALUE)), + new Extension(AggregateUniqueCounter.EXTENSION_URL).setValue(new CodeType(AggregateUniqueCounter.EXTENSION_VALUE)), new Extension(CRITERIA_REFERENCE_URL).setValue(new CodeType("some-other-value")))))); assertThatThrownBy(() -> groupEvaluator.evaluateGroup(measureGroup).block()) @@ -416,8 +434,8 @@ public void test_observationPopulation_withTooManyAggregateMethods() { getMeasurePopulation(MEASURE_POPULATION_PATH), (Measure.MeasureGroupPopulationComponent) getObservationPopulation(OBSERVATION_POPULATION_PATH) .setExtension(List.of( - new Extension(AggregateUniqueCount.EXTENSION_URL).setValue(new CodeType(AggregateUniqueCount.EXTENSION_VALUE)), - new Extension(AggregateUniqueCount.EXTENSION_URL).setValue(new CodeType(AggregateUniqueCount.EXTENSION_VALUE)), + new Extension(AggregateUniqueCounter.EXTENSION_URL).setValue(new CodeType(AggregateUniqueCounter.EXTENSION_VALUE)), + new Extension(AggregateUniqueCounter.EXTENSION_URL).setValue(new CodeType(AggregateUniqueCounter.EXTENSION_VALUE)), new Extension(CRITERIA_REFERENCE_URL).setValue(new CodeType(MEASURE_POPULATION_ID)))))); assertThatThrownBy(() -> groupEvaluator.evaluateGroup(measureGroup).block()) @@ -435,7 +453,7 @@ public void test_observationPopulation_aggregateMethodWithoutValue() { getMeasurePopulation(MEASURE_POPULATION_PATH), (Measure.MeasureGroupPopulationComponent) getObservationPopulation(OBSERVATION_POPULATION_PATH) .setExtension(List.of( - new Extension(AggregateUniqueCount.EXTENSION_URL), + new Extension(AggregateUniqueCounter.EXTENSION_URL), new Extension(CRITERIA_REFERENCE_URL).setValue(new CodeType(MEASURE_POPULATION_ID)))))); assertThatThrownBy(() -> groupEvaluator.evaluateGroup(measureGroup).block()) @@ -453,12 +471,12 @@ public void test_observationPopulation_aggregateMethodWithWrongValue() { getMeasurePopulation(MEASURE_POPULATION_PATH), (Measure.MeasureGroupPopulationComponent) getObservationPopulation(OBSERVATION_POPULATION_PATH) .setExtension(List.of( - new Extension(AggregateUniqueCount.EXTENSION_URL).setValue(new CodeType("some-value")), + new Extension(AggregateUniqueCounter.EXTENSION_URL).setValue(new CodeType("some-value")), new Extension(CRITERIA_REFERENCE_URL).setValue(new CodeType(MEASURE_POPULATION_ID)))))); assertThatThrownBy(() -> groupEvaluator.evaluateGroup(measureGroup).block()) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("Aggregate Method of Measure Observation Population has not value '%s'".formatted(AggregateUniqueCount.EXTENSION_VALUE)); + .hasMessage("Aggregate Method of Measure Observation Population has not value '%s'".formatted(AggregateUniqueCounter.EXTENSION_VALUE)); } } diff --git a/src/test/java/de/medizininformatikinitiative/fhir_data_evaluator/MeasureEvaluatorUnitTest.java b/src/test/java/de/medizininformatikinitiative/fhir_data_evaluator/MeasureEvaluatorUnitTest.java index 0f3fdf8..c150b32 100644 --- a/src/test/java/de/medizininformatikinitiative/fhir_data_evaluator/MeasureEvaluatorUnitTest.java +++ b/src/test/java/de/medizininformatikinitiative/fhir_data_evaluator/MeasureEvaluatorUnitTest.java @@ -14,7 +14,26 @@ import java.util.List; -import static de.medizininformatikinitiative.fhir_data_evaluator.GroupEvaluatorTest.*; +import static de.medizininformatikinitiative.fhir_data_evaluator.GroupEvaluatorTest.CONDITION_QUERY; +import static de.medizininformatikinitiative.fhir_data_evaluator.GroupEvaluatorTest.COND_CODE_PATH; +import static de.medizininformatikinitiative.fhir_data_evaluator.GroupEvaluatorTest.COND_DEF_CODE; +import static de.medizininformatikinitiative.fhir_data_evaluator.GroupEvaluatorTest.COND_DEF_CODING; +import static de.medizininformatikinitiative.fhir_data_evaluator.GroupEvaluatorTest.COND_DEF_SYSTEM; +import static de.medizininformatikinitiative.fhir_data_evaluator.GroupEvaluatorTest.COND_STATUS_PATH; +import static de.medizininformatikinitiative.fhir_data_evaluator.GroupEvaluatorTest.COND_VALUE_CODE; +import static de.medizininformatikinitiative.fhir_data_evaluator.GroupEvaluatorTest.COND_VALUE_SYSTEM; +import static de.medizininformatikinitiative.fhir_data_evaluator.GroupEvaluatorTest.INITIAL_POPULATION_CODE; +import static de.medizininformatikinitiative.fhir_data_evaluator.GroupEvaluatorTest.POPULATION_SYSTEM; +import static de.medizininformatikinitiative.fhir_data_evaluator.GroupEvaluatorTest.SOME_DISPLAY; +import static de.medizininformatikinitiative.fhir_data_evaluator.GroupEvaluatorTest.STATUS_DEF_CODE; +import static de.medizininformatikinitiative.fhir_data_evaluator.GroupEvaluatorTest.STATUS_DEF_CODING; +import static de.medizininformatikinitiative.fhir_data_evaluator.GroupEvaluatorTest.STATUS_DEF_SYSTEM; +import static de.medizininformatikinitiative.fhir_data_evaluator.GroupEvaluatorTest.STATUS_VALUE_CODE; +import static de.medizininformatikinitiative.fhir_data_evaluator.GroupEvaluatorTest.STATUS_VALUE_SYSTEM; +import static de.medizininformatikinitiative.fhir_data_evaluator.GroupEvaluatorTest.createPathEngine; +import static de.medizininformatikinitiative.fhir_data_evaluator.GroupEvaluatorTest.getCondition; +import static de.medizininformatikinitiative.fhir_data_evaluator.GroupEvaluatorTest.getInitialPopulation; +import static de.medizininformatikinitiative.fhir_data_evaluator.GroupEvaluatorTest.getMeasureGroup; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.when;