Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add unique count #19

Merged
merged 2 commits into from
Aug 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ jobs:
run: docker load --input /tmp/fhir-data-evaluator.tar

- name: Run Blaze
run: docker-compose -f .github/integration-test/docker-compose.yml up -d
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
Expand Down
72 changes: 57 additions & 15 deletions Documentation/Documentation.md
Original file line number Diff line number Diff line change
@@ -1,49 +1,91 @@
# Fhir Data Evaluator

The Fhir Data Evaluator takes a FHIR Measure resource as an input and returns a corresponding FHIR MeasureReport by
executing FHIR Search queries and evaluating the resulting FHIR resources.
executing FHIR Search queries and evaluating the resulting FHIR resources with FHIRPath.

## Measure

### Group
The Measure uses groups to distinguish between different populations.

In the FHIR specification, a population consists
of *at least* one population criteria, but the Fhir Data Evaluator currently only supports the population criteria of type
`initial-population` (Read more: https://build.fhir.org/valueset-measure-population.html). So currently groups are mainly
used to define the base resources that should be evaluated.
Every population must have one population criteria of type `initial-population`. The initial population defines the 'base',
on which the evaluator operates, with a FHIR Search Query. So if the evaluator should operate on Condition resources, the
FHIR Search Query expression for the initial population would be "`Condition`". There can also be other populations in
addition to the initial population. Read more about how to use the `measure-population`and `measure-observation`
[here](#other-populations) to for example count not all encountered resources but only unique patient ID's.


### Stratifier
A Stratifier further evaluates the resources of the group populations.
A Stratifier groups the resources of a population by the value returned by its expression(s).

The stratifier field is a list of stratifier elements, that each can consist of ether criteria or components, but not
The stratifier field is a list of stratifier elements, that each can consist of either criteria or components, but not
both at the same time. The code of a stratifier element can be a custom but unique coding that roughly describes the
stratifier. If the stratifier element consists of components, it still must have a code. Each component also
stratifier. Each component also
consists of a code and a criteria. A criteria consists of a language and an expression. As language, currently only
`text/fhirpath` is accepted. Accordingly, the expression must be a FHIRPath statement. It must start at the base resource
type and must evaluate into one of the following types:
* [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`

Multiple stratifier elements are evaluated separately
and only share the same base group population.
Multiple stratifier elements are evaluated separately and only share the same base group population.

As currently only the population of type `initial-population` is supported, a stratifier element simply counts the
occurrences of each value found at the path defined in the criteria expression, or in case the stratifier consists of
components, each unique found *set* of values.
Each found value (= stratum) has its own populations. The `initial-population` in a stratum represents
the count of the value found at the path defined in the criteria expression, or in case the stratifier
consists of components, it represents the count of each unique found *set* of values.
All [other populations](#other-populations) are also evaluated for each stratum if they are present.

* Example with a [single criteria](example-measures/example-measure-1.json)
* Example with [components](example-measures/example-measure-3.json)


### Other Populations

* Measure Population:
* used to further narrow down the base population using FHIRPath
* counts this reduced population
* acts as base for the measure observation, if the measure observation is present

* Measure Observation:
* currently must evaluate into type `String` with FHIRPath
* found values are also counted, but mainly used to aggregate a score
* must have an aggregate method extension with value type `unique-count`, which tells the Fhir Data Evaluator to
aggregate a unique count of the values
* must have a criteria reference extension that references the measure population
* the result of the aggregate method is separately calculated on both group and on stratum level and stored in the
`measureScore`

* in case these populations are used, the measure will be a continuous-variable measure, which requires
[this](http://fhir-data-evaluator/StructureDefinition/FhirDataEvaluatorContinuousVariableMeasure) profile
* other populations that are defined in FHIR, such as the `numerator`, are currently not supported by the Fhir Data Evaluator


## MeasureReport

Each group of the Measure results in a corresponding group in the MeasureReport. Also, each stratifier element in the
Measure results in a corresponding stratifier element in the MeasureReport. Each found value of a stratifier element,
or in case the stratifier consists of components, each unique found *set* of values results in a stratum element.
The population of the group indicates the overall count of the found resources. The population of a stratum element
indicates the count of the found values/ set of values.
The initial population of the group represents the overall count of the found resources. The initial population of a
stratum element represents the count of the found values/ set of values.

* Example [MeasureReport](example-measure-reports/example-measure-report-1.json)


## Profiles and Validation

There are two profiles that are supported by the Fhir Data Evaluator:
* Basic Measure:
* Profile: http://fhir-data-evaluator/StructureDefinition/FhirDataEvaluatorBasicMeasure
* is used when there is only an initial population without any other population
* Continuous Variable Measure:
* Profile: http://fhir-data-evaluator/StructureDefinition/FhirDataEvaluatorContinuousVariableMeasure
* is used when there is a need for a measure population and measure observation population
* in either case, the Fhir Data Evaluator adheres to the
[HL7 Quality Measure Implementation Guide ](https://hl7.org/fhir/us/cqfmeasures/measure-conformance.html)

The resources to validate a measure can be built with [SUSHI](https://github.com/FHIR/sushi). To do this, you must cd
into `shorthand/FhirDataEvaluatorIG` and run `sushi build`. The resulting resources will be saved at
`shorthand/FhirDataEvaluatorIG/fhs-generated/resources` and can be used for example with the
[FHIR Validator](https://confluence.hl7.org/display/FHIR/Using+the+FHIR+Validator). This also generates the measures that
are used in the [integration test](../src/test/resources/de/medizininformatikinitiative/fhir_data_evaluator/FhirDataEvaluatorTest/Measures)
and they are copied into the test resources directory during the maven build process.
95 changes: 95 additions & 0 deletions Documentation/example-measures/example-measure-patcount.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
{
"resourceType": "Measure",
"id": "ExampleConditionIcd10AndPatCount",
"meta": {
"profile": [
"http://fhir-data-evaluator/StructureDefinition/FhirDataEvaluatorContinuousVariableMeasure"
]
},
"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"
},
{
"code": {
"coding": [
{
"system": "http://terminology.hl7.org/CodeSystem/measure-population",
"code": "measure-population"
}
]
},
"criteria": {
"language": "text/fhirpath",
"expression": "Condition"
},
"id": "measure-population-identifier"
},
{
"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"
}
],
"id": "measure-observation-identifier"
}
],
"stratifier": [
{
"criteria": {
"language": "text/fhirpath",
"expression": "Condition.code.coding.where(system='http://fhir.de/CodeSystem/bfarm/icd-10-gm')"
},
"code": {
"coding": [
{
"code": "icd10-code",
"system": "http://fhir-data-evaluator/strat/system"
}
]
},
"id": "strat-1"
}
],
"id": "group-1"
}
],
"status": "active",
"url": "https://medizininformatik-initiative.de/fhir/fdpg/Measure/ExampleConditionIcd10AndPatCount",
"version": "1.0",
"name": "ExampleConditionIcd10AndPatCount",
"experimental": false,
"publisher": "FDPG-Plus",
"description": "Example Measure to count all ICD-10 codes and the patient count."
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,39 @@ Description: "Example Measure to count all Snomed codes with clinical status."
* group[0].stratifier.criteria.expression = "Observation.value.code.exists()"
* group[0].stratifier.code = http://fhir-data-evaluator/strat/system#"value-code-exists"
* group[0].stratifier.id = "strat-1"

Instance: IntegrationTest-Measure-5
InstanceOf: FhirDataEvaluatorContinuousVariableMeasure
Description: "Example Measure to count all ICD-10 codes and the patient count."
* status = #active
* url = "https://medizininformatik-initiative.de/fhir/fdpg/Measure/ExampleConditionIcd10AndPatCount"
* version = "1.0"
* name = "ExampleConditionIcd10AndPatCount"
* experimental = false
* publisher = "FDPG-Plus"
* description = "Example Measure to count all ICD-10 codes and the patient count."

* group[0].id = "group-1"
* group[0].population[initialPopulation].code.coding = $measure-population#initial-population
* group[0].population[initialPopulation].criteria.expression = "Condition?_profile=https://www.medizininformatik-initiative.de/fhir/core/modul-diagnose/StructureDefinition/Diagnose"
* group[0].population[initialPopulation].criteria.language = #text/x-fhir-query
* group[0].population[initialPopulation].id = "initial-population-identifier"

* group[0].population[measurePopulation].code.coding = $measure-population#measure-population
* group[0].population[measurePopulation].criteria.expression = "Condition"
* group[0].population[measurePopulation].criteria.language = #text/fhirpath
* group[0].population[measurePopulation].id = "measure-population-identifier"

* group[0].population[measureObservation].code.coding = $measure-population#measure-observation
* group[0].population[measureObservation].criteria.expression = "Condition.subject.reference"
* group[0].population[measureObservation].criteria.language = #text/fhirpath
* group[0].population[measureObservation].extension[aggregateMethod].valueCode = #unique-count
* group[0].population[measureObservation].extension[aggregateMethod].url = "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-aggregateMethod"
* group[0].population[measureObservation].extension[criteriaReference].valueString = "measure-population-identifier"
* group[0].population[measureObservation].extension[criteriaReference].url = "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-criteriaReference"
* group[0].population[measureObservation].id = "measure-observation-identifier"

* group[0].stratifier.criteria.language = #text/fhirpath
* group[0].stratifier.criteria.expression = "Condition.code.coding.where(system='http://fhir.de/CodeSystem/bfarm/icd-10-gm')"
* group[0].stratifier.code = http://fhir-data-evaluator/strat/system#"icd10-code"
* group[0].stratifier.id = "strat-1"
Loading