Skip to content
Closed
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
12 changes: 2 additions & 10 deletions Java/measureeval/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -95,22 +95,14 @@
<scope>runtime</scope>
</dependency>

<dependency>
<groupId>info.cqframework</groupId>
<artifactId>engine</artifactId>
<version>3.15.0</version>
</dependency>

<dependency>
<groupId>org.opencds.cqf.fhir</groupId>
<artifactId>cqf-fhir-cr</artifactId>
<artifactId>cqf-fhir-cql</artifactId>
</dependency>


<dependency>
<groupId>org.opencds.cqf.fhir</groupId>
<artifactId>cqf-fhir-jackson</artifactId>
<type>pom</type>
<artifactId>cqf-fhir-cr</artifactId>
</dependency>

<!-- for logback to use conditional expressions -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,19 @@
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import com.lantanagroup.link.measureeval.repositories.LinkInMemoryFhirRepository;
import com.lantanagroup.link.measureeval.utils.DateUtils;
import com.lantanagroup.link.measureeval.utils.ParametersUtils;
import com.lantanagroup.link.measureeval.utils.StreamUtils;
import lombok.Getter;
import org.cqframework.cql.cql2elm.LibraryBuilder;
import org.hl7.fhir.r4.model.*;
import org.opencds.cqf.fhir.api.Repository;
import org.opencds.cqf.fhir.cql.EvaluationSettings;
import org.opencds.cqf.fhir.cql.engine.retrieve.RetrieveSettings;
import org.opencds.cqf.fhir.cql.engine.terminology.TerminologySettings;
import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions;
import org.opencds.cqf.fhir.cr.measure.common.MeasurePeriodValidator;
import org.opencds.cqf.fhir.cr.measure.r4.R4MeasureService;
import org.opencds.cqf.fhir.cr.measure.r4.utils.R4MeasureServiceUtils;
import org.opencds.cqf.fhir.utility.monad.Eithers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -106,12 +108,13 @@ private MeasureReport doEvaluate(
DateTimeType periodEnd,
StringType subject,
Bundle additionalData) {
Repository repository = new LinkInMemoryFhirRepository(fhirContext, bundle);
R4MeasureService measureService = new R4MeasureService(repository, options);
var repository = new LinkInMemoryFhirRepository(fhirContext, bundle);
R4MeasureService measureService = new R4MeasureService(repository, options, new MeasurePeriodValidator());

return measureService.evaluate(
Eithers.forRight3(measure),
periodStart == null ? null : periodStart.asStringValue(),
periodEnd == null ? null : periodEnd.asStringValue(),
periodStart == null ? null : DateUtils.getZonedDateTime(periodStart),
periodEnd == null ? null : DateUtils.getZonedDateTime(periodEnd),
null,
subject.asStringValue(),
null,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.lantanagroup.link.measureeval.utils;

import org.hl7.fhir.r4.model.DateTimeType;

import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;

public class DateUtils {

public static ZonedDateTime getZonedDateTime(DateTimeType dateTime) {
if (dateTime == null) {
return null;
}
var zoneId = dateTime.getTimeZone() == null ? ZoneId.systemDefault() : dateTime.getTimeZone().toZoneId();
return getZonedDateTime(dateTime.getValue().toInstant(), zoneId);
}

public static ZonedDateTime getZonedDateTime(Instant instant, ZoneId zoneId) {
return ZonedDateTime.ofInstant(instant, zoneId);
}
}
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -128,66 +128,25 @@ define function ConditionResource(condition Condition):
false""";

public static final String SIMPLE_PROPORTION_ALL_FALSE = """
library ProportionLibraryFalse version '1.0.0'
library ProportionLibraryAllFalse version '1.0.0'

using FHIR version '4.0.1'

context Patient

define "Initial Population":
false

define "Numerator":
false

define "Numerator Exclusion":
false

define "Denominator":
false

define "Denominator Exclusion":
false""";

public static final String SIMPLE_RATIO = """
library RatioLibrary version '1.0.0'

using FHIR version '4.0.1'

context Patient

define "Initial Population":
["Encounter"]

define "Numerator":
"Initial Population"

define "Denominator":
"Initial Population"

define function "Denominator Observation"(Encounter "Encounter"):
24

define function "Numerator Observation"(Encounter "Encounter"):
1""";

public static final String SIMPLE_CONTINUOUS_VARIABLE = """
library ContinuousVariableLibrary version '1.0.0'

using FHIR version '4.0.1'

context Patient

define "Initial Population":
["Encounter"]

define "Measure Population":
"Initial Population"

define "Measure Population Exclusion":
false

define function "Measure Observation"(Encounter "Encounter"):
24""";

}
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,9 @@ static class CohortMeasureWithValueSetTrue {
* @return A {@link Measure} resource with the cohort scoring type and an initial population definition.
*/
public static Measure measure() {
return MeasureBuilder.build(MEASURE_ID, MEASURE_URL, LIBRARY_URL, "cohort", MeasurePopulationGroup.initialPopulation());
var measure = MeasureBuilder.build(MEASURE_ID, MEASURE_URL, LIBRARY_URL, "cohort", MeasurePopulationGroup.initialPopulation());
measure.addExtension("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-populationBasis", new CodeType("Encounter"));
return measure;
}

/**
Expand Down Expand Up @@ -418,90 +420,6 @@ public static Bundle bundle() {
}
}

/**
* Nested utility class for building resources for a "Simple Ratio Measure" scenario.
* This class creates a {@link Measure}, {@link Library}, and {@link Bundle} to represent a ratio measure
* with an initial population, numerator, and denominator, including exclusions.
*/
static class SimpleRatioMeasure {
private static final String MEASURE_ID = "RatioMeasure";
private static final String LIBRARY_ID = "RatioLibrary";
private static final String MEASURE_URL = BASE_MEASURE_URL + MEASURE_ID;
private static final String LIBRARY_URL = BASE_LIBRARY_URL + LIBRARY_ID;

/**
* Builds the {@link Measure} resource for the "Simple Ratio Measure" scenario.
* The measure is configured with the ratio scoring type, an initial population, numerator, and denominator,
* including numerator and denominator exclusions.
*
* @return A {@link Measure} resource with the ratio scoring type and specified population groups.
*/
public static Measure measure() {
return MeasureBuilder.build(MEASURE_ID, MEASURE_URL, LIBRARY_URL, "ratio", MeasurePopulationGroup.initialPopulation(), MeasurePopulationGroup.numerator(), MeasurePopulationGroup.numeratorExclusion(), MeasurePopulationGroup.denominator(), MeasurePopulationGroup.denominatorExclusion());
}

/**
* Builds the {@link Library} resource for the "Simple Ratio Measure" scenario.
* The library contains CQL logic for evaluating the measure.
*
* @return A {@link Library} resource containing CQL logic for the ratio measure.
*/
public static Library library() {
return LibraryBuilder.build(LIBRARY_ID, "1.0.0", LIBRARY_ID, LIBRARY_URL, CqlLibraries.SIMPLE_RATIO);
}

/**
* Builds a {@link Bundle} resource containing the {@link Measure} and {@link Library} for the "Simple Ratio Measure" scenario.
*
* @return A {@link Bundle} resource containing the measure and library.
*/
public static Bundle bundle() {
return BundleBuilder.build(library(), measure());
}
}

/**
* Nested utility class for building resources for a "Simple Continuous Variable Measure" scenario.
* This class creates a {@link Measure}, {@link Library}, and {@link Bundle} to represent a continuous variable measure
* with an initial population, measure population, and measure population exclusions.
*/
static class SimpleContinuousVariableMeasure {
private static final String MEASURE_ID = "ContinuousVariableMeasure";
private static final String LIBRARY_ID = "ContinuousVariableLibrary";
private static final String MEASURE_URL = BASE_MEASURE_URL + MEASURE_ID;
private static final String LIBRARY_URL = BASE_LIBRARY_URL + LIBRARY_ID;

/**
* Builds the {@link Measure} resource for the "Simple Continuous Variable Measure" scenario.
* The measure is configured with the continuous variable scoring type, an initial population,
* a measure population, and measure population exclusions.
*
* @return A {@link Measure} resource with the continuous variable scoring type and specified population groups.
*/
public static Measure measure() {
return MeasureBuilder.build(MEASURE_ID, MEASURE_URL, LIBRARY_URL, "continuous-variable", MeasurePopulationGroup.initialPopulation(), MeasurePopulationGroup.measurePopulation(), MeasurePopulationGroup.measurePopulationExclusion());
}

/**
* Builds the {@link Library} resource for the "Simple Continuous Variable Measure" scenario.
* The library contains CQL logic for evaluating the measure.
*
* @return A {@link Library} resource containing CQL logic for the continuous variable measure.
*/
public static Library library() {
return LibraryBuilder.build(LIBRARY_ID, "1.0.0", LIBRARY_ID, LIBRARY_URL, CqlLibraries.SIMPLE_CONTINUOUS_VARIABLE);
}

/**
* Builds a {@link Bundle} resource containing the {@link Measure} and {@link Library} for the "Simple Continuous Variable Measure" scenario.
*
* @return A {@link Bundle} resource containing the measure and library.
*/
public static Bundle bundle() {
return BundleBuilder.build(library(), measure());
}
}

/**
* Utility class for building {@link Measure} resources.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,11 @@ void cohortMeasureWithValueSetTrueTest() {
Assertions.assertEquals(1, getPopulation("initial-population", report).getCount());

// test evaluated resources
// Note: clinical-reasoning 4.x changed evaluated resource tracking - Patient is no longer included
Assertions.assertTrue(report.hasEvaluatedResource());
Assertions.assertEquals(2, report.getEvaluatedResource().size());
Assertions.assertEquals(1, report.getEvaluatedResource().size());
Assertions.assertTrue(report.getEvaluatedResourceFirstRep().hasReference());
Assertions.assertEquals("Encounter/simple-encounter", report.getEvaluatedResourceFirstRep().getReference());
Assertions.assertTrue(report.getEvaluatedResource().get(1).hasReference());
Assertions.assertEquals("Patient/simple-patient", report.getEvaluatedResource().get(1).getReference());
}

/**
Expand Down Expand Up @@ -152,19 +151,20 @@ void cohortMeasureWithSDETest() {
Assertions.assertEquals(1, getPopulation("initial-population", report).getCount());

// test evaluated resources
// Note: clinical-reasoning 4.x changed evaluated resource tracking - Patient is no longer included
Assertions.assertTrue(report.hasEvaluatedResource());
Assertions.assertEquals(3, report.getEvaluatedResource().size());
Assertions.assertEquals(2, report.getEvaluatedResource().size());

// test extensions, references, and contained
Assertions.assertTrue(report.hasExtension("http://hl7.org/fhir/5.0/StructureDefinition/extension-MeasureReport.supplementalDataElement.reference"));
var extension = report.getExtensionByUrl("http://hl7.org/fhir/5.0/StructureDefinition/extension-MeasureReport.supplementalDataElement.reference");
Assertions.assertTrue(extension.hasValue());
Assertions.assertTrue(extension.getValue() instanceof Reference);
Assertions.assertInstanceOf(Reference.class, extension.getValue());
var reference = (Reference) extension.getValue();
Assertions.assertEquals("#TST-simple-condition", reference.getReference());
Assertions.assertTrue(report.hasContained());
Assertions.assertEquals(1, report.getContained().size());
Assertions.assertTrue(report.getContained().get(0) instanceof Condition);
Assertions.assertInstanceOf(Condition.class, report.getContained().get(0));
var condition = (Condition) report.getContained().get(0);
Assertions.assertEquals("TST-simple-condition", condition.getIdPart());
}
Expand Down Expand Up @@ -233,74 +233,6 @@ void simpleProportionMeasureAllFalseTest() {
Assertions.assertEquals(0, getPopulation("denominator-exclusion", report).getCount());
}

/**
* Tests a ratio measure with valid numerator and denominator values.
* Validations:
* β€’ The measurement period matches the expected dates.
* β€’ The measure score is 1.0.
* β€’ Population counts:
* β€’ initial-population, numerator, denominator: 1
* β€’ Evaluated resources include the encounter.
*/
@Test
void simpleRatioMeasureTest() {
var measurePackage = KnowledgeArtifactBuilder.SimpleRatioMeasure.bundle();
validateMeasurePackage(measurePackage);
var evaluator = MeasureEvaluator.compile(fhirContext, measurePackage, false);
var report = evaluator.evaluate(new DateTimeType("2024-01-01"), new DateTimeType("2024-12-31"),
new StringType("Patient/simple-patient"), PatientDataBuilder.simplePatientAndEncounterBundle());

// test measurement period results
validateMeasurementPeriod(report.getPeriod(), 2024, 0, 1, 2024, 11, 31);

// test measure score
Assertions.assertEquals(1.0, report.getGroupFirstRep().getMeasureScore().getValue().doubleValue());

// test population results
Assertions.assertEquals(1, getPopulation("initial-population", report).getCount());
Assertions.assertEquals(1, getPopulation("numerator", report).getCount());
Assertions.assertEquals(1, getPopulation("denominator", report).getCount());

// test evaluated resources
Assertions.assertTrue(report.hasEvaluatedResource());
Assertions.assertEquals("Encounter/simple-encounter", report.getEvaluatedResourceFirstRep().getReference());
}

/**
* Tests a continuous variable measure with a valid measure-population.
* Validations:
* β€’ The measurement period matches the expected dates.
* β€’ The measure score is null.
* β€’ Population counts:
* β€’ initial-population: 1
* β€’ measure-population: 1
* β€’ measure-population-exclusion: 0
* β€’ Evaluated resources include the encounter.
*/
@Test
void simpleContinuousVariableMeasureTest() {
var measurePackage = KnowledgeArtifactBuilder.SimpleContinuousVariableMeasure.bundle();
validateMeasurePackage(measurePackage);
var evaluator = MeasureEvaluator.compile(fhirContext, KnowledgeArtifactBuilder.SimpleContinuousVariableMeasure.bundle(), false);
var report = evaluator.evaluate(new DateTimeType("2024-01-01"), new DateTimeType("2024-12-31"),
new StringType("Patient/simple-patient"), PatientDataBuilder.simplePatientAndEncounterBundle());

// test measurement period results
validateMeasurementPeriod(report.getPeriod(), 2024, 0, 1, 2024, 11, 31);

// test measure score is null
Assertions.assertNull(report.getGroupFirstRep().getMeasureScore().getValue());

// test population results
Assertions.assertEquals(1, getPopulation("initial-population", report).getCount());
Assertions.assertEquals(1, getPopulation("measure-population", report).getCount());
Assertions.assertEquals(0, getPopulation("measure-population-exclusion", report).getCount());

// test evaluated resources
Assertions.assertTrue(report.hasEvaluatedResource());
Assertions.assertEquals("Encounter/simple-encounter", report.getEvaluatedResourceFirstRep().getReference());
}

/**
* Validates the measure package for errors using the MeasureDefinitionBundleValidator. Asserts that the measure
* package has no validation errors.
Expand Down
Loading
Loading