Skip to content

Commit

Permalink
Make Increment Population Immutable
Browse files Browse the repository at this point in the history
Also added an integration test that failed with the previos mutable increment population
  • Loading branch information
bastianschaffer committed Sep 18, 2024
1 parent 62fbd16 commit 87e6717
Show file tree
Hide file tree
Showing 24 changed files with 587 additions and 213 deletions.
93 changes: 93 additions & 0 deletions .github/integration-test/evaluate-multiple-stratifiers.sh
Original file line number Diff line number Diff line change
@@ -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
109 changes: 109 additions & 0 deletions .github/integration-test/measures/multiple-stratifiers.json
Original file line number Diff line number Diff line change
@@ -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"
}
]
}
]
}
16 changes: 16 additions & 0 deletions .github/integration-test/test-data/get-mii-testdata.sh
Original file line number Diff line number Diff line change
@@ -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
18 changes: 18 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,24 @@ jobs:
- name: Run Integration Test to check if it correctly exits when there are insufficient writing permissions
run: .github/integration-test/missing-permissions-test.sh

- name: Remove Blaze volumes
run: docker compose -f .github/integration-test/docker-compose.yml down -v

- name: Run Blaze with fresh volumes
run: docker compose -f .github/integration-test/docker-compose.yml up -d

- name: Wait for Blaze
run: .github/scripts/wait-for-url.sh http://localhost:8082/health

- name: Download New Data
run: .github/integration-test/test-data/get-mii-testdata.sh

- name: Upload New Data
run: blazectl --no-progress --server http://localhost:8082/fhir upload .github/integration-test/Vorhofflimmern

- name: Run Integration Test multiple stratifiers
run: .github/integration-test/evaluate-multiple-stratifiers.sh /${PWD}/.github/integration-test/measures/multiple-stratifiers.json


push-image:
needs:
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package de.medizininformatikinitiative.fhir_data_evaluator;

import de.medizininformatikinitiative.fhir_data_evaluator.populations.AggregateUniqueCount;
import de.medizininformatikinitiative.fhir_data_evaluator.populations.InitialAndMeasureAndObsPopulation;
import de.medizininformatikinitiative.fhir_data_evaluator.populations.InitialAndMeasurePopulation;
import de.medizininformatikinitiative.fhir_data_evaluator.populations.InitialPopulation;
import de.medizininformatikinitiative.fhir_data_evaluator.populations.increment_populations.InitialAndMeasureAndObsIncrement;
import de.medizininformatikinitiative.fhir_data_evaluator.populations.increment_populations.InitialAndMeasureIncrement;
import de.medizininformatikinitiative.fhir_data_evaluator.populations.increment_populations.InitialIncrement;
import de.medizininformatikinitiative.fhir_data_evaluator.populations.mutable.AggregateUniqueCount;
import de.medizininformatikinitiative.fhir_data_evaluator.populations.mutable.InitialAndMeasureAndObsPopulation;
import org.hl7.fhir.r4.model.ExpressionNode;
import org.hl7.fhir.r4.model.Measure;
import org.hl7.fhir.r4.model.MeasureReport;
Expand All @@ -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 {
Expand Down Expand Up @@ -61,9 +66,9 @@ public Mono<MeasureReport.MeasureReportGroupComponent> evaluateGroup(Measure.Mea

private Mono<MeasureReport.MeasureReportGroupComponent> evaluateGroupOfInitial(Flux<Resource> population, Measure.MeasureGroupComponent group) {
var groupReduceOp = new GroupReduceOpInitial(group.getStratifier().stream().map(s ->
new StratifierReduceOp<InitialPopulation>(getComponentExpressions(s))).toList());
new StratifierReduceOp<InitialPopulation, InitialIncrement>(getComponentExpressions(s))).toList());

List<StratifierResult<InitialPopulation>> initialStratifierResults = group.getStratifier().stream().map(s ->
List<StratifierResult<InitialPopulation, InitialIncrement>> initialStratifierResults = group.getStratifier().stream().map(s ->
StratifierResult.initial(s, InitialPopulation.class)).toList();
return population.reduce(new GroupResult<>(InitialPopulation.ZERO, initialStratifierResults), groupReduceOp)
.map(GroupResult::toReportGroup);
Expand All @@ -73,10 +78,10 @@ private Mono<MeasureReport.MeasureReportGroupComponent> evaluateGroupOfInitialAn
Measure.MeasureGroupComponent group,
ExpressionNode measurePopulationExpression) {
var groupReduceOp = new GroupReduceOpMeasure(group.getStratifier().stream().map(s ->
new StratifierReduceOp<InitialAndMeasurePopulation>(getComponentExpressions(s))).toList(),
new StratifierReduceOp<InitialAndMeasurePopulation, InitialAndMeasureIncrement>(getComponentExpressions(s))).toList(),
measurePopulationExpression, fhirPathEngine);

List<StratifierResult<InitialAndMeasurePopulation>> initialStratifierResults = group.getStratifier().stream().map(s ->
List<StratifierResult<InitialAndMeasurePopulation, InitialAndMeasureIncrement>> initialStratifierResults = group.getStratifier().stream().map(s ->
StratifierResult.initial(s, InitialAndMeasurePopulation.class)).toList();
return population.reduce(new GroupResult<>(InitialAndMeasurePopulation.ZERO, initialStratifierResults), groupReduceOp)
.map(GroupResult::toReportGroup);
Expand All @@ -87,10 +92,10 @@ private Mono<MeasureReport.MeasureReportGroupComponent> evaluateGroupOfInitialAn
ExpressionNode measurePopulationExpression,
ExpressionNode observationPopulationExpression) {
var groupReduceOp = new GroupReduceOpObservation(group.getStratifier().stream().map(s ->
new StratifierReduceOp<InitialAndMeasureAndObsPopulation>(getComponentExpressions(s))).toList(),
new StratifierReduceOp<InitialAndMeasureAndObsPopulation, InitialAndMeasureAndObsIncrement>(getComponentExpressions(s))).toList(),
measurePopulationExpression, observationPopulationExpression, fhirPathEngine);

List<StratifierResult<InitialAndMeasureAndObsPopulation>> initialStratifierResults = group.getStratifier().stream().map(s ->
List<StratifierResult<InitialAndMeasureAndObsPopulation, InitialAndMeasureAndObsIncrement>> initialStratifierResults = group.getStratifier().stream().map(s ->
StratifierResult.initial(s, InitialAndMeasureAndObsPopulation.class)).toList();
return population.reduce(new GroupResult<>(InitialAndMeasureAndObsPopulation.empty(), initialStratifierResults), groupReduceOp)
.map(GroupResult::toReportGroup);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package de.medizininformatikinitiative.fhir_data_evaluator;

import de.medizininformatikinitiative.fhir_data_evaluator.populations.InitialPopulation;
import de.medizininformatikinitiative.fhir_data_evaluator.populations.increment_populations.InitialIncrement;
import org.hl7.fhir.r4.model.Resource;

import java.util.List;
Expand All @@ -19,15 +20,17 @@
*
* @param stratifierReduceOps holds one {@link StratifierReduceOp} for each stratifier in a group
*/
public record GroupReduceOpInitial(List<StratifierReduceOp<InitialPopulation>> stratifierReduceOps)
implements BiFunction<GroupResult<InitialPopulation>, Resource, GroupResult<InitialPopulation>> {
public record GroupReduceOpInitial(List<StratifierReduceOp<InitialPopulation, InitialIncrement>> stratifierReduceOps)
implements BiFunction<GroupResult<InitialPopulation, InitialIncrement>, Resource,
GroupResult<InitialPopulation, InitialIncrement>> {

public GroupReduceOpInitial {
requireNonNull(stratifierReduceOps);
}

@Override
public GroupResult<InitialPopulation> apply(GroupResult<InitialPopulation> groupResult, Resource resource) {
return groupResult.applyResource(stratifierReduceOps, resource, InitialPopulation.ONE);
public GroupResult<InitialPopulation, InitialIncrement> apply(GroupResult<InitialPopulation, InitialIncrement> groupResult,
Resource resource) {
return groupResult.applyResource(stratifierReduceOps, resource, new InitialIncrement());
}
}
Loading

0 comments on commit 87e6717

Please sign in to comment.