From 3d40fb02b98e83b41ce782273ae8e6f4a061d944 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Fri, 27 Jun 2025 17:09:01 -0400 Subject: [PATCH 01/66] First attempt to flip measure and subject looping for population based measure reports. --- .../spring/measure/MeasureConfiguration.java | 6 +- .../cr/measure/r4/R4CollectDataService.java | 5 +- .../cr/measure/r4/R4MeasureProcessor.java | 94 +++++++++++++++++- .../cr/measure/r4/R4MeasureReportBuilder.java | 2 +- .../fhir/cr/measure/r4/R4MeasureService.java | 5 +- .../cr/measure/r4/R4MultiMeasureService.java | 97 ++++++++++++++++--- .../measure/r4/MultiMeasureServiceTest.java | 1 + 7 files changed, 191 insertions(+), 19 deletions(-) diff --git a/cqf-fhir-cr-spring/src/main/java/org/opencds/cqf/fhir/cr/spring/measure/MeasureConfiguration.java b/cqf-fhir-cr-spring/src/main/java/org/opencds/cqf/fhir/cr/spring/measure/MeasureConfiguration.java index 642b28e555..9273f71437 100644 --- a/cqf-fhir-cr-spring/src/main/java/org/opencds/cqf/fhir/cr/spring/measure/MeasureConfiguration.java +++ b/cqf-fhir-cr-spring/src/main/java/org/opencds/cqf/fhir/cr/spring/measure/MeasureConfiguration.java @@ -2,6 +2,7 @@ import ca.uhn.fhir.repository.IRepository; import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions; +import org.opencds.cqf.fhir.cr.measure.common.MeasureProcessorUtils; import org.opencds.cqf.fhir.cr.measure.common.SubjectProvider; import org.opencds.cqf.fhir.cr.measure.dstu3.Dstu3MeasureProcessor; import org.opencds.cqf.fhir.cr.measure.r4.R4MeasureProcessor; @@ -27,7 +28,8 @@ R4MeasureProcessor r4MeasureProcessor( IRepository repository, MeasureEvaluationOptions measureEvaluationOptions, SubjectProvider subjectProvider, - R4MeasureServiceUtils measureServiceUtils) { - return new R4MeasureProcessor(repository, measureEvaluationOptions, subjectProvider, measureServiceUtils); + R4MeasureServiceUtils measureServiceUtils, + MeasureProcessorUtils measureProcessorUtils) { + return new R4MeasureProcessor(repository, measureEvaluationOptions, subjectProvider, measureServiceUtils, measureProcessorUtils); } } diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java index b8cedd31b2..668459b65d 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java @@ -17,6 +17,7 @@ import org.hl7.fhir.r4.model.Resource; import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions; import org.opencds.cqf.fhir.cr.measure.common.MeasureEvalType; +import org.opencds.cqf.fhir.cr.measure.common.MeasureProcessorUtils; import org.opencds.cqf.fhir.cr.measure.r4.utils.R4MeasureServiceUtils; import org.opencds.cqf.fhir.utility.Ids; import org.opencds.cqf.fhir.utility.monad.Eithers; @@ -26,6 +27,7 @@ public class R4CollectDataService { private final MeasureEvaluationOptions measureEvaluationOptions; private final R4RepositorySubjectProvider subjectProvider; private final R4MeasureServiceUtils measureServiceUtils; + private final MeasureProcessorUtils measureProcessorUtils = new MeasureProcessorUtils(); public R4CollectDataService( IRepository repository, @@ -66,7 +68,8 @@ public Parameters collectData( Parameters parameters = new Parameters(); var processor = new R4MeasureProcessor( - this.repository, this.measureEvaluationOptions, this.subjectProvider, this.measureServiceUtils); + this.repository, this.measureEvaluationOptions, this.subjectProvider, this.measureServiceUtils, + measureProcessorUtils); // getSubjects List subjectList = getSubjects(subject, practitioner, subjectProvider); diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java index bc6673ec85..62af223a5e 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java @@ -6,6 +6,7 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; import java.time.ZonedDateTime; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -54,18 +55,20 @@ public class R4MeasureProcessor { private final MeasureEvaluationOptions measureEvaluationOptions; private final SubjectProvider subjectProvider; private final R4MeasureServiceUtils r4MeasureServiceUtils; - private final MeasureProcessorUtils measureProcessorUtils = new MeasureProcessorUtils(); + private final MeasureProcessorUtils measureProcessorUtils; public R4MeasureProcessor( IRepository repository, MeasureEvaluationOptions measureEvaluationOptions, SubjectProvider subjectProvider, - R4MeasureServiceUtils r4MeasureServiceUtils) { + R4MeasureServiceUtils r4MeasureServiceUtils, + MeasureProcessorUtils measureProcessorUtils) { this.repository = Objects.requireNonNull(repository); this.measureEvaluationOptions = measureEvaluationOptions != null ? measureEvaluationOptions : MeasureEvaluationOptions.defaultOptions(); this.subjectProvider = subjectProvider; this.r4MeasureServiceUtils = r4MeasureServiceUtils; + this.measureProcessorUtils = measureProcessorUtils; } public MeasureReport evaluateMeasure( @@ -104,6 +107,39 @@ public MeasureReport evaluateMeasure( m, periodStart, periodEnd, reportType, subjectIds, additionalData, parameters, evalType); } + public List evaluateMeasureSubjectsDriven( + List> measures, + @Nullable ZonedDateTime periodStart, + @Nullable ZonedDateTime periodEnd, + String subjectId, + IBaseBundle additionalData, + Parameters parameters, + MeasureEvalType measureEvalType) { + + var foldedMeasures = measures.stream().map(measure -> measure.fold(this::resolveByUrl, this::resolveById, Function.identity())) + .toList(); + + foldedMeasures.forEach(this::checkMeasureLibrary); + + // Measurement Period: operation parameter defined measurement period + var measurementPeriodParams = buildMeasurementPeriod(periodStart, periodEnd); + + // CQL Engine context + var context = Engines.forRepository( + this.repository, this.measureEvaluationOptions.getEvaluationSettings(), additionalData); + + // extract measurement Period from CQL to pass to report Builder + Interval measurementPeriod = + measureProcessorUtils.getDefaultMeasurementPeriod(measurementPeriodParams, context); + + // set offset of operation parameter measurement period + ZonedDateTime zonedMeasurementPeriod = MeasureProcessorUtils.getZonedTimeZoneForEval(measurementPeriod); + + return this.evaluateMeasureSubjectsDriven( + foldedMeasures, zonedMeasurementPeriod, measurementPeriod , subjectId, context, parameters, measureEvalType); + } + + // LUKETODO: this is no doubt used by CQL-CLI /** * Evaluation method that consumes pre-calculated CQL results, Processes results, builds Measure Report * @param measure Measure resource @@ -153,6 +189,58 @@ public MeasureReport evaluateMeasureResults( subjectIds); } + protected List evaluateMeasureSubjectsDriven( + List measures, + ZonedDateTime zonedMeasurementPeriod, + Interval measurementPeriod, + String subjectId, + CqlEngine context, + Parameters parameters, + MeasureEvalType measureEvalType) { + + var measureReports = new ArrayList(); + + for (Measure measure : measures) { + + // setup MeasureDef + var measureDef = new R4MeasureDefBuilder().build(measure); + + var libraryVersionIdentifier = getLibraryVersionIdentifier(measure); + // library engine setup + var libraryEngine = getLibraryEngine(parameters, libraryVersionIdentifier, context); + + // populate results from Library $evaluate + var results = measureProcessorUtils.getEvaluationResults( + List.of(subjectId), measureDef, zonedMeasurementPeriod, context, libraryEngine, libraryVersionIdentifier); + + // Process Criteria Expression Results + measureProcessorUtils.processResults( + results, + measureDef, + measureEvalType, + this.measureEvaluationOptions.getApplyScoringSetMembership(), + new R4PopulationBasisValidator()); + + // Populate populationDefs that require MeasureDef results + measureProcessorUtils.continuousVariableObservation(measureDef, context); + + // LUKETODO: how do we combine with multiple measures? + // LUKETODO: do we just return a List of MeasureReports here? + // Build Measure Report with Results + var measureReport = new R4MeasureReportBuilder() + .build( + measure, + measureDef, + r4EvalTypeToReportType(measureEvalType, measure), + measurementPeriod, + List.of(subjectId)); + measureReports.add(measureReport); + } + + + return measureReports; + } + /** * Evaluation method that generates CQL results, Processes results, builds Measure Report * @param measure Measure resource @@ -175,6 +263,8 @@ protected MeasureReport evaluateMeasure( Parameters parameters, MeasureEvalType evalType) { + // LUKETODO: group the pre-measure engine setup tasks before the measure-specific tasks + checkMeasureLibrary(measure); MeasureEvalType evaluationType = measureProcessorUtils.getEvalType(evalType, reportType, subjectIds); diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureReportBuilder.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureReportBuilder.java index 9efad25e8e..43a24efceb 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureReportBuilder.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureReportBuilder.java @@ -81,7 +81,7 @@ public R4MeasureReportBuilder() { this.measureReportScorer = new R4MeasureReportScorer(); } - private static class BuilderContext { + protected static class BuilderContext { private final Measure measure; private final MeasureDef measureDef; private final MeasureReport measureReport; diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java index 78630e44b3..498d25d44d 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java @@ -14,6 +14,7 @@ import org.hl7.fhir.r4.model.Parameters; import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions; import org.opencds.cqf.fhir.cr.measure.common.MeasurePeriodValidator; +import org.opencds.cqf.fhir.cr.measure.common.MeasureProcessorUtils; import org.opencds.cqf.fhir.cr.measure.r4.utils.R4MeasureServiceUtils; import org.opencds.cqf.fhir.utility.monad.Either3; import org.opencds.cqf.fhir.utility.repository.Repositories; @@ -24,6 +25,7 @@ public class R4MeasureService implements R4MeasureEvaluatorSingle { private final MeasurePeriodValidator measurePeriodValidator; private final R4RepositorySubjectProvider subjectProvider; private final R4MeasureServiceUtils measureServiceUtils; + private final MeasureProcessorUtils measureProcessorUtils = new MeasureProcessorUtils(); public R4MeasureService( IRepository repository, @@ -57,7 +59,8 @@ public MeasureReport evaluate( var repo = Repositories.proxy(repository, true, dataEndpoint, contentEndpoint, terminologyEndpoint); var processor = new R4MeasureProcessor( - repo, this.measureEvaluationOptions, this.subjectProvider, this.measureServiceUtils); + repo, this.measureEvaluationOptions, this.subjectProvider, this.measureServiceUtils, + measureProcessorUtils); R4MeasureServiceUtils r4MeasureServiceUtils = new R4MeasureServiceUtils(repository); r4MeasureServiceUtils.ensureSupplementalDataElementSearchParameter(); diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java index 340915fa5f..8add2af3a2 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java @@ -13,6 +13,7 @@ import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Bundle.BundleType; +import org.hl7.fhir.r4.model.CanonicalType; import org.hl7.fhir.r4.model.Endpoint; import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.Measure; @@ -22,9 +23,12 @@ import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions; import org.opencds.cqf.fhir.cr.measure.common.MeasureEvalType; import org.opencds.cqf.fhir.cr.measure.common.MeasurePeriodValidator; +import org.opencds.cqf.fhir.cr.measure.common.MeasureProcessorUtils; import org.opencds.cqf.fhir.cr.measure.r4.utils.R4MeasureServiceUtils; import org.opencds.cqf.fhir.utility.Ids; import org.opencds.cqf.fhir.utility.builder.BundleBuilder; +import org.opencds.cqf.fhir.utility.monad.Either3; +import org.opencds.cqf.fhir.utility.monad.Eithers; import org.opencds.cqf.fhir.utility.repository.Repositories; /** @@ -33,10 +37,11 @@ */ public class R4MultiMeasureService implements R4MeasureEvaluatorMultiple { private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(R4MultiMeasureService.class); - private IRepository repository; + private final IRepository repository; private final MeasureEvaluationOptions measureEvaluationOptions; private final MeasurePeriodValidator measurePeriodValidator; - private String serverBase; + private final MeasureProcessorUtils measureProcessorUtils = new MeasureProcessorUtils(); + private final String serverBase; private final R4RepositorySubjectProvider subjectProvider; @@ -57,7 +62,7 @@ public R4MultiMeasureService( subjectProvider = new R4RepositorySubjectProvider(measureEvaluationOptions.getSubjectProviderOptions()); r4Processor = new R4MeasureProcessor( - repository, this.measureEvaluationOptions, subjectProvider, r4MeasureServiceUtils); + repository, this.measureEvaluationOptions, subjectProvider, r4MeasureServiceUtils, measureProcessorUtils); r4MeasureServiceUtils = new R4MeasureServiceUtils(repository); } @@ -83,12 +88,13 @@ public Bundle evaluate( if (dataEndpoint != null && contentEndpoint != null && terminologyEndpoint != null) { // if needing to use proxy repository, override constructors - repository = Repositories.proxy(repository, true, dataEndpoint, contentEndpoint, terminologyEndpoint); + var repositoryToUse = Repositories.proxy(repository, true, dataEndpoint, contentEndpoint, terminologyEndpoint); r4Processor = new R4MeasureProcessor( - repository, this.measureEvaluationOptions, subjectProvider, r4MeasureServiceUtils); + repositoryToUse, this.measureEvaluationOptions, subjectProvider, r4MeasureServiceUtils, + measureProcessorUtils); - r4MeasureServiceUtils = new R4MeasureServiceUtils(repository); + r4MeasureServiceUtils = new R4MeasureServiceUtils(repositoryToUse); } r4MeasureServiceUtils.ensureSupplementalDataElementSearchParameter(); List measures = r4MeasureServiceUtils.getMeasures(measureId, measureIdentifier, measureUrl); @@ -106,7 +112,8 @@ public Bundle evaluate( // evaluate Measures if (evalType.equals(MeasureEvalType.POPULATION) || evalType.equals(MeasureEvalType.SUBJECTLIST)) { - populationMeasureReport( +// populationMeasureReport( + populationMeasureReportSubjectsDriven( bundle, measures, periodStart, @@ -151,6 +158,8 @@ protected void populationMeasureReport( String productLine, String reporter) { + System.out.println("subjects = " + subjects); + // one aggregated MeasureReport per Measure var totalMeasures = measures.size(); for (Measure measure : measures) { @@ -187,6 +196,70 @@ protected void populationMeasureReport( } } + protected void populationMeasureReportSubjectsDriven( + Bundle bundle, + List measures, + @Nullable ZonedDateTime periodStart, + @Nullable ZonedDateTime periodEnd, + String reportType, + MeasureEvalType evalType, + String subjectParam, + List subjects, + Parameters parameters, + Bundle additionalData, + String productLine, + String reporter) { + + System.out.println("subjects = " + subjects); + + // We have the list of subjects here, so figure out the measure eval type now + var measureEvalType = measureProcessorUtils.getEvalType(evalType, reportType, subjects); + + // one aggregated MeasureReport per Measure + var totalMeasures = measures.size(); + // LUKETODO: flip the logic here to go over each subject instead + for (String subject : subjects) { + // LUKETODO: this is gross: do I really need to do this? + final List> eithers = measures.stream() + .map(measure -> { + Either3 either = Eithers.forRight3(measure); + return either; + }) + .toList(); + var measureReports = r4Processor.evaluateMeasureSubjectsDriven( + eithers, periodStart, periodEnd, subject, additionalData, parameters, measureEvalType); + + // LUKETODO: are we supposed to merge the measure reports among subjects? + for (MeasureReport measureReport : measureReports) { + // add ProductLine after report is generated + measureReport = r4MeasureServiceUtils.addProductLineExtension(measureReport, productLine); + + // add subject reference for non-individual reportTypes + measureReport = r4MeasureServiceUtils.addSubjectReference(measureReport, null, subjectParam); + + // add reporter if available + if (reporter != null && !reporter.isEmpty()) { + measureReport.setReporter( + r4MeasureServiceUtils.getReporter(reporter).orElse(null)); + } + // add id to measureReport + initializeReport(measureReport); + + // add report to bundle + bundle.addEntry(getBundleEntry(serverBase, measureReport)); + + // progress feedback + var measureUrl = measureReport.getMeasure(); + if (!measureUrl.isEmpty()) { + log.debug( + "Completed evaluation for Measure: {}, Measures remaining to evaluate: {}", + measureUrl, + totalMeasures--); + } + } + } + } + protected void subjectMeasureReport( Bundle bundle, List measures, @@ -207,8 +280,8 @@ protected void subjectMeasureReport( "Evaluating individual MeasureReports for {} patients, and {} measures", subjects.size(), measures.size()); - for (Measure measure : measures) { - for (String subject : subjects) { + for (String subject : subjects) { + for (Measure measure : measures) { MeasureReport measureReport; // evaluate each measure measureReport = r4Processor.evaluateMeasure( @@ -240,12 +313,12 @@ protected void subjectMeasureReport( if (!measureUrl.isEmpty()) { log.debug("MeasureReports remaining to evaluate {}", totalReports--); } - } - if (measure.hasUrl()) { - log.info( + if (measure.hasUrl()) { + log.info( "Completed evaluation for Measure: {}, Measures remaining to evaluate: {}", measure.getUrl(), totalMeasures--); + } } } } diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java index 90359d90a2..b297f8d842 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java @@ -14,6 +14,7 @@ class MultiMeasureServiceTest { private static final Given GIVEN_REPO = MultiMeasure.given().repositoryFor("MinimalMeasureEvaluation"); + // LUKETODO: this test fails because we expect 2 measure reports but we got 20 @Test void MultiMeasure_AllSubjects_MeasureIdentifier() { var when = GIVEN_REPO From 593ede25f56cf1a226526245a06dbb34d4e59697 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Wed, 2 Jul 2025 09:33:45 -0400 Subject: [PATCH 02/66] Document new unit test failures. --- .../cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java index b297f8d842..01795bc574 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java @@ -29,6 +29,7 @@ void MultiMeasure_AllSubjects_MeasureIdentifier() { when.then().hasMeasureReportCount(2).report(); } + // LUKETODO: this test fails because we expect 7 measure reports but we got 70 @Test void MultiMeasure_EightMeasures_AllSubjects_MeasureId() { var when = GIVEN_REPO @@ -167,6 +168,7 @@ void MultiMeasure_EightMeasures_AllSubjects_MeasureId() { .up(); } + // LUKETODO: this test fails because we expect 7 measure reports but we got 70 @Test void MultiMeasure_EightMeasures_AllSubjects_MeasureUrl() { var when = GIVEN_REPO @@ -586,6 +588,7 @@ void MultiMeasure_EightMeasures_Patient() { .up(); } + // LUKETODO: expected count for initial-population is 1, but we got 0 @Test void MultiMeasure_EightMeasures_SubjectList() { var when = GIVEN_REPO @@ -753,6 +756,7 @@ void MultiMeasure_EightMeasures_SubjectList() { .up(); } + // LUKETODO: expected count for population "initial-population" did not match expected: 1, actual: 0 @Test void MultiMeasure_EightMeasures_Practitioner() { var when = GIVEN_REPO From bee1e0f5fdca1dbf44a584a86b1e94b0c5cc0370 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Wed, 2 Jul 2025 16:41:03 -0400 Subject: [PATCH 03/66] Abandon previous experiment. Push up evaluate measure callers to the service layers for single and multi measures and collect data. Leave old evaluate measure code within processor for the time being as there are still unit test failures otherwise, hopefully limited to single measure evaluation. --- .../cr/measure/r4/R4CollectDataService.java | 33 +++- .../cr/measure/r4/R4MeasureProcessor.java | 176 ++++++------------ .../cr/measure/r4/R4MeasureReportBuilder.java | 72 +++++++ .../fhir/cr/measure/r4/R4MeasureService.java | 47 ++++- .../cr/measure/r4/R4MultiMeasureService.java | 107 ++++------- .../r4/utils/R4MeasureServiceUtils.java | 23 +++ .../measure/r4/MultiMeasureServiceTest.java | 5 - 7 files changed, 259 insertions(+), 204 deletions(-) diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java index 668459b65d..40bf41b6fa 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java @@ -7,6 +7,7 @@ import java.time.ZonedDateTime; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -15,6 +16,7 @@ import org.hl7.fhir.r4.model.MeasureReport; import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.Resource; +import org.opencds.cqf.cql.engine.execution.EvaluationResult; import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions; import org.opencds.cqf.fhir.cr.measure.common.MeasureEvalType; import org.opencds.cqf.fhir.cr.measure.common.MeasureProcessorUtils; @@ -71,20 +73,34 @@ public Parameters collectData( this.repository, this.measureEvaluationOptions, this.subjectProvider, this.measureServiceUtils, measureProcessorUtils); + // LUKETODO: subjectList should be null but we get a list of subjects here: // getSubjects List subjectList = getSubjects(subject, practitioner, subjectProvider); - // if(subjectList.isEmpty() || subjectList == null){ - // } - // loop over subjects + var foldedMeasure = measureServiceUtils.foldMeasure(Eithers.forMiddle3(measureId), repository); + + // LUKETODO: reuse this within measureServiceUtils + var measureDef = new R4MeasureDefBuilder().build(foldedMeasure); + + var evaluationResults = + Map.of(foldedMeasure.getId(), + processor.evaluateMeasureWithCqlEngine( + subjectList, + foldedMeasure, + periodStart, + periodEnd, + parameters, + measureDef, + null)); + if (!subjectList.isEmpty()) { for (String patient : subjectList) { var subjects = Collections.singletonList(patient); // add resources per subject to Parameters - addReports(processor, measureId, periodStart, periodEnd, subjects, parameters); + addReports(processor, measureId, periodStart, periodEnd, subjects, parameters, evaluationResults); } } else { - addReports(processor, measureId, periodStart, periodEnd, subjectList, parameters); + addReports(processor, measureId, periodStart, periodEnd, subjectList, parameters, evaluationResults); } return parameters; } @@ -95,7 +111,9 @@ private void addReports( @Nullable ZonedDateTime periodStart, @Nullable ZonedDateTime periodEnd, List subjects, - Parameters parameters) { + Parameters parameters, + Map> evaluateMeasureResults) { + MeasureReport report = processor.evaluateMeasure( Eithers.forMiddle3(measureId), periodStart, @@ -104,7 +122,8 @@ private void addReports( subjects, null, null, - null); + null, + evaluateMeasureResults); report.setType(MeasureReport.MeasureReportType.DATACOLLECTION); report.setGroup(null); diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java index 62af223a5e..88028c7d61 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java @@ -6,7 +6,6 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; import java.time.ZonedDateTime; -import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -71,28 +70,6 @@ public R4MeasureProcessor( this.measureProcessorUtils = measureProcessorUtils; } - public MeasureReport evaluateMeasure( - Either3 measure, - @Nullable ZonedDateTime periodStart, - @Nullable ZonedDateTime periodEnd, - String reportType, - List subjectIds, - IBaseBundle additionalData, - Parameters parameters) { - - var evalType = r4MeasureServiceUtils.getMeasureEvalType(reportType, subjectIds); - - var actualRepo = this.repository; - if (additionalData != null) { - actualRepo = new FederatedRepository( - this.repository, new InMemoryFhirRepository(this.repository.fhirContext(), additionalData)); - } - var subjects = subjectProvider.getSubjects(actualRepo, subjectIds).collect(Collectors.toList()); - - return this.evaluateMeasure( - measure, periodStart, periodEnd, reportType, subjects, additionalData, parameters, evalType); - } - public MeasureReport evaluateMeasure( Either3 measure, @Nullable ZonedDateTime periodStart, @@ -101,42 +78,11 @@ public MeasureReport evaluateMeasure( List subjectIds, IBaseBundle additionalData, Parameters parameters, - MeasureEvalType evalType) { + MeasureEvalType evalType, + Map> evaluateMeasureResults) { var m = measure.fold(this::resolveByUrl, this::resolveById, Function.identity()); return this.evaluateMeasure( - m, periodStart, periodEnd, reportType, subjectIds, additionalData, parameters, evalType); - } - - public List evaluateMeasureSubjectsDriven( - List> measures, - @Nullable ZonedDateTime periodStart, - @Nullable ZonedDateTime periodEnd, - String subjectId, - IBaseBundle additionalData, - Parameters parameters, - MeasureEvalType measureEvalType) { - - var foldedMeasures = measures.stream().map(measure -> measure.fold(this::resolveByUrl, this::resolveById, Function.identity())) - .toList(); - - foldedMeasures.forEach(this::checkMeasureLibrary); - - // Measurement Period: operation parameter defined measurement period - var measurementPeriodParams = buildMeasurementPeriod(periodStart, periodEnd); - - // CQL Engine context - var context = Engines.forRepository( - this.repository, this.measureEvaluationOptions.getEvaluationSettings(), additionalData); - - // extract measurement Period from CQL to pass to report Builder - Interval measurementPeriod = - measureProcessorUtils.getDefaultMeasurementPeriod(measurementPeriodParams, context); - - // set offset of operation parameter measurement period - ZonedDateTime zonedMeasurementPeriod = MeasureProcessorUtils.getZonedTimeZoneForEval(measurementPeriod); - - return this.evaluateMeasureSubjectsDriven( - foldedMeasures, zonedMeasurementPeriod, measurementPeriod , subjectId, context, parameters, measureEvalType); + m, periodStart, periodEnd, reportType, subjectIds, additionalData, parameters, evalType, evaluateMeasureResults); } // LUKETODO: this is no doubt used by CQL-CLI @@ -189,58 +135,6 @@ public MeasureReport evaluateMeasureResults( subjectIds); } - protected List evaluateMeasureSubjectsDriven( - List measures, - ZonedDateTime zonedMeasurementPeriod, - Interval measurementPeriod, - String subjectId, - CqlEngine context, - Parameters parameters, - MeasureEvalType measureEvalType) { - - var measureReports = new ArrayList(); - - for (Measure measure : measures) { - - // setup MeasureDef - var measureDef = new R4MeasureDefBuilder().build(measure); - - var libraryVersionIdentifier = getLibraryVersionIdentifier(measure); - // library engine setup - var libraryEngine = getLibraryEngine(parameters, libraryVersionIdentifier, context); - - // populate results from Library $evaluate - var results = measureProcessorUtils.getEvaluationResults( - List.of(subjectId), measureDef, zonedMeasurementPeriod, context, libraryEngine, libraryVersionIdentifier); - - // Process Criteria Expression Results - measureProcessorUtils.processResults( - results, - measureDef, - measureEvalType, - this.measureEvaluationOptions.getApplyScoringSetMembership(), - new R4PopulationBasisValidator()); - - // Populate populationDefs that require MeasureDef results - measureProcessorUtils.continuousVariableObservation(measureDef, context); - - // LUKETODO: how do we combine with multiple measures? - // LUKETODO: do we just return a List of MeasureReports here? - // Build Measure Report with Results - var measureReport = new R4MeasureReportBuilder() - .build( - measure, - measureDef, - r4EvalTypeToReportType(measureEvalType, measure), - measurementPeriod, - List.of(subjectId)); - measureReports.add(measureReport); - } - - - return measureReports; - } - /** * Evaluation method that generates CQL results, Processes results, builds Measure Report * @param measure Measure resource @@ -261,10 +155,12 @@ protected MeasureReport evaluateMeasure( List subjectIds, IBaseBundle additionalData, Parameters parameters, - MeasureEvalType evalType) { + MeasureEvalType evalType, + Map> evaluateMeasureResults) { // LUKETODO: group the pre-measure engine setup tasks before the measure-specific tasks + // LUKETODO: push this up checkMeasureLibrary(measure); MeasureEvalType evaluationType = measureProcessorUtils.getEvalType(evalType, reportType, subjectIds); @@ -282,20 +178,19 @@ protected MeasureReport evaluateMeasure( // library engine setup var libraryEngine = getLibraryEngine(parameters, libraryVersionIdentifier, context); - // set measurement Period from CQL if operation parameters are empty measureProcessorUtils.setMeasurementPeriod(measureDef, measurementPeriodParams, context); // extract measurement Period from CQL to pass to report Builder Interval measurementPeriod = measureProcessorUtils.getDefaultMeasurementPeriod(measurementPeriodParams, context); - // set offset of operation parameter measurement period - ZonedDateTime zonedMeasurementPeriod = MeasureProcessorUtils.getZonedTimeZoneForEval(measurementPeriod); + System.out.println("EVALUATE MEASURE LATE!"); // populate results from Library $evaluate - var results = measureProcessorUtils.getEvaluationResults( - subjectIds, measureDef, zonedMeasurementPeriod, context, libraryEngine, libraryVersionIdentifier); + Map resultsFromNewCqlEngine2 = + evaluateMeasureWithCqlEngine(subjectIds, measure, periodStart, periodEnd, parameters, measureDef, additionalData); // Process Criteria Expression Results measureProcessorUtils.processResults( - results, + resultsFromNewCqlEngine2, +// evaluateMeasureResults.get(measure.getId()), measureDef, evaluationType, this.measureEvaluationOptions.getApplyScoringSetMembership(), @@ -314,6 +209,55 @@ protected MeasureReport evaluateMeasure( subjectIds); } + public Map evaluateMeasureWithCqlEngine( + List subjects, + Measure measure, + @Nullable ZonedDateTime periodStart, + @Nullable ZonedDateTime periodEnd, + Parameters parameters, + MeasureDef measureDef, + @Nullable IBaseBundle additionalData) { + + var context = Engines.forRepository( + this.repository, this.measureEvaluationOptions.getEvaluationSettings(), additionalData); + + var measurementPeriodParams = buildMeasurementPeriod(periodStart, periodEnd); + var zonedMeasurementPeriod = MeasureProcessorUtils.getZonedTimeZoneForEval( + measureProcessorUtils.getDefaultMeasurementPeriod(measurementPeriodParams, context)); + final VersionedIdentifier libraryVersionIdentifier = getLibraryVersionIdentifier(measure); + + var libraryEngine = getLibraryEngine(parameters, libraryVersionIdentifier, context); + + // set measurement Period from CQL if operation parameters are empty + measureProcessorUtils.setMeasurementPeriod(measureDef, measurementPeriodParams, context); + + // populate results from Library $evaluate + return measureProcessorUtils.getEvaluationResults(subjects, measureDef, zonedMeasurementPeriod, context, libraryEngine, libraryVersionIdentifier); + } + + private String cqlEngineToString(CqlEngine cqlEngine) { + return "CqlEngine{environment=%s, state=%s}" + .formatted(cqlEngine.getEnvironment(), cqlEngine.getState()); + } + + private String measureDefToString(MeasureDef measureDef) { + return "MeasureDef{id='%s', url='%s', version='%s', defaultMeasurementPeriod=%s, groups=%s, sdes=%s, errors=%s}" + .formatted( + measureDef.id(), + measureDef.url(), + measureDef.version(), + measureDef.getDefaultMeasurementPeriod(), + measureDef.groups().stream().map(Object::hashCode).toList(), + measureDef.sdes(), + measureDef.errors()); + } + +// private String groupDefToString(GroupDef groupDef) { +// return "GroupDef{id='%s', measureScoring=%s, populations=%s, stratifiers=%s, measurePopulationType=%s}" +// .formatted(groupDef.id(), groupDef.measureScoring(), groupDef.populations(), groupDef.stratifiers(), +// groupDef.get +// } + /** Temporary check for Measures that are being blocked from use by evaluateResults method * * @param measureDef defined measure definition object used to capture criteria expression results diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureReportBuilder.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureReportBuilder.java index 43a24efceb..f6e4f35f1a 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureReportBuilder.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureReportBuilder.java @@ -16,11 +16,13 @@ import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.StringJoiner; import java.util.UUID; import java.util.function.Function; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.r4.model.CanonicalType; import org.hl7.fhir.r4.model.CodeableConcept; @@ -211,6 +213,72 @@ private OperationOutcome createOperationOutcome(String errorMsg) { } } + // LUKETODO: rename + public static class MeasureReportBuilderMeasureDetails { + private final Measure measure; + private final MeasureDef measureDef; + private final MeasureReportType measureReportType; + private final Interval measurementPeriod; + + public MeasureReportBuilderMeasureDetails(Measure measure, MeasureDef measureDef, + MeasureReportType measureReportType, Interval measurementPeriod) { + this.measure = measure; + this.measureDef = measureDef; + this.measureReportType = measureReportType; + this.measurementPeriod = measurementPeriod; + } + + public Measure getMeasure() { + return measure; + } + + public MeasureDef getMeasureDef() { + return measureDef; + } + + public MeasureReportType getMeasureReportType() { + return measureReportType; + } + + public Interval getMeasurementPeriod() { + return measurementPeriod; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } + MeasureReportBuilderMeasureDetails that = (MeasureReportBuilderMeasureDetails) o; + return Objects.equals(measure, that.measure) && Objects.equals(measureDef, + that.measureDef) && measureReportType == that.measureReportType && Objects.equals( + measurementPeriod, that.measurementPeriod); + } + + @Override + public int hashCode() { + return Objects.hash(measure, measureDef, measureReportType, measurementPeriod); + } + + @Override + public String toString() { + return new StringJoiner(", ", MeasureReportBuilderMeasureDetails.class.getSimpleName() + "[", "]") + .add("measure=" + measure) + .add("measureDef=" + measureDef) + .add("measureReportType=" + measureReportType) + .add("measurementPeriod=" + measurementPeriod) + .toString(); + } + } + + public MeasureReport build2(MeasureReportBuilderMeasureDetails measureDetails) { + final MeasureReport measureReport = createMeasureReport(measureDetails.getMeasure(), + measureDetails.getMeasureDef(), measureDetails.getMeasureReportType(), List.of(), + measureDetails.getMeasurementPeriod()); + + return measureReport; + } + @Override public MeasureReport build( Measure measure, @@ -674,9 +742,13 @@ private void buildPopulation( PopulationDef populationDef, GroupDef groupDef) { + System.out.printf("1234: boolean basis = %s populationDef subjects: %s, populationDef resources: %s\n", groupDef.isBooleanBasis(), populationDef.getSubjects(), populationDef.getResources().stream().filter( + IBaseResource.class::isInstance).map(IBaseResource.class::cast).map(IBaseResource::getIdElement).toList()); + reportPopulation.setCode(measurePopulation.getCode()); reportPopulation.setId(measurePopulation.getId()); + // LUKETODO: here, we are returning an initial-population of 0 instead of 1 because this is a boolean basis and the population def subjects is empty if (groupDef.isBooleanBasis()) { reportPopulation.setCount(populationDef.getSubjects().size()); } else { diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java index 498d25d44d..acf864d22e 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java @@ -4,6 +4,10 @@ import jakarta.annotation.Nullable; import java.time.ZonedDateTime; import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.CanonicalType; @@ -17,6 +21,8 @@ import org.opencds.cqf.fhir.cr.measure.common.MeasureProcessorUtils; import org.opencds.cqf.fhir.cr.measure.r4.utils.R4MeasureServiceUtils; import org.opencds.cqf.fhir.utility.monad.Either3; +import org.opencds.cqf.fhir.utility.repository.FederatedRepository; +import org.opencds.cqf.fhir.utility.repository.InMemoryFhirRepository; import org.opencds.cqf.fhir.utility.repository.Repositories; public class R4MeasureService implements R4MeasureEvaluatorSingle { @@ -65,6 +71,13 @@ public MeasureReport evaluate( R4MeasureServiceUtils r4MeasureServiceUtils = new R4MeasureServiceUtils(repository); r4MeasureServiceUtils.ensureSupplementalDataElementSearchParameter(); + var foldedMeasure = measureServiceUtils.foldMeasure(measure, repo); + + processor.checkMeasureLibrary(foldedMeasure); + + // LUKETODO: reuse this within measureServiceUtils + var measureDef = new R4MeasureDefBuilder().build(foldedMeasure); + MeasureReport measureReport; if (StringUtils.isNotBlank(practitioner)) { @@ -74,14 +87,44 @@ public MeasureReport evaluate( subjectId = practitioner; } + var actualRepo = repo; + if (additionalData != null) { + actualRepo = new FederatedRepository( + this.repository, new InMemoryFhirRepository(this.repository.fhirContext(), additionalData)); + } + + var evalType = + r4MeasureServiceUtils.getMeasureEvalType( + reportType, + Optional.ofNullable(subjectId).map(List::of).orElse(List.of())); + + var subjects = subjectProvider.getSubjects( + actualRepo, + Optional.ofNullable(subjectId) + .map(List::of) + .orElse(List.of())) + .toList(); + + var evaluationResults = + processor.evaluateMeasureWithCqlEngine( + subjects, + foldedMeasure, + periodStart, + periodEnd, + parameters, + measureDef, + additionalData); + measureReport = processor.evaluateMeasure( measure, periodStart, periodEnd, reportType, - Collections.singletonList(subjectId), + subjects, additionalData, - parameters); + parameters, + evalType, + Map.of(foldedMeasure.getId(), evaluationResults)); // add ProductLine after report is generated measureReport = r4MeasureServiceUtils.addProductLineExtension(measureReport, productLine); diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java index 8add2af3a2..c1a73d9323 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java @@ -7,19 +7,21 @@ import jakarta.annotation.Nullable; import java.time.ZonedDateTime; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.UUID; import java.util.stream.Collectors; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Bundle.BundleType; -import org.hl7.fhir.r4.model.CanonicalType; import org.hl7.fhir.r4.model.Endpoint; import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.Measure; import org.hl7.fhir.r4.model.MeasureReport; import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.Resource; +import org.opencds.cqf.cql.engine.execution.EvaluationResult; import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions; import org.opencds.cqf.fhir.cr.measure.common.MeasureEvalType; import org.opencds.cqf.fhir.cr.measure.common.MeasurePeriodValidator; @@ -27,8 +29,6 @@ import org.opencds.cqf.fhir.cr.measure.r4.utils.R4MeasureServiceUtils; import org.opencds.cqf.fhir.utility.Ids; import org.opencds.cqf.fhir.utility.builder.BundleBuilder; -import org.opencds.cqf.fhir.utility.monad.Either3; -import org.opencds.cqf.fhir.utility.monad.Eithers; import org.opencds.cqf.fhir.utility.repository.Repositories; /** @@ -112,8 +112,7 @@ public Bundle evaluate( // evaluate Measures if (evalType.equals(MeasureEvalType.POPULATION) || evalType.equals(MeasureEvalType.SUBJECTLIST)) { -// populationMeasureReport( - populationMeasureReportSubjectsDriven( + populationMeasureReport( bundle, measures, periodStart, @@ -158,7 +157,17 @@ protected void populationMeasureReport( String productLine, String reporter) { - System.out.println("subjects = " + subjects); + var evaluateMeasureResultsByMeasureId = new HashMap>(); + + // LUKETODO: comment on exactly what this is for and what it does: + for (Measure measure : measures) { + var measureDef = new R4MeasureDefBuilder().build(measure); + + var evaluationResults = + r4Processor.evaluateMeasureWithCqlEngine(subjects, measure, periodStart, periodEnd, parameters, measureDef, additionalData); + + evaluateMeasureResultsByMeasureId.put(measure.getId(), evaluationResults); + } // one aggregated MeasureReport per Measure var totalMeasures = measures.size(); @@ -166,7 +175,7 @@ protected void populationMeasureReport( MeasureReport measureReport; // evaluate each measure measureReport = r4Processor.evaluateMeasure( - measure, periodStart, periodEnd, reportType, subjects, additionalData, parameters, evalType); + measure, periodStart, periodEnd, reportType, subjects, additionalData, parameters, evalType, evaluateMeasureResultsByMeasureId); // add ProductLine after report is generated measureReport = r4MeasureServiceUtils.addProductLineExtension(measureReport, productLine); @@ -196,70 +205,6 @@ protected void populationMeasureReport( } } - protected void populationMeasureReportSubjectsDriven( - Bundle bundle, - List measures, - @Nullable ZonedDateTime periodStart, - @Nullable ZonedDateTime periodEnd, - String reportType, - MeasureEvalType evalType, - String subjectParam, - List subjects, - Parameters parameters, - Bundle additionalData, - String productLine, - String reporter) { - - System.out.println("subjects = " + subjects); - - // We have the list of subjects here, so figure out the measure eval type now - var measureEvalType = measureProcessorUtils.getEvalType(evalType, reportType, subjects); - - // one aggregated MeasureReport per Measure - var totalMeasures = measures.size(); - // LUKETODO: flip the logic here to go over each subject instead - for (String subject : subjects) { - // LUKETODO: this is gross: do I really need to do this? - final List> eithers = measures.stream() - .map(measure -> { - Either3 either = Eithers.forRight3(measure); - return either; - }) - .toList(); - var measureReports = r4Processor.evaluateMeasureSubjectsDriven( - eithers, periodStart, periodEnd, subject, additionalData, parameters, measureEvalType); - - // LUKETODO: are we supposed to merge the measure reports among subjects? - for (MeasureReport measureReport : measureReports) { - // add ProductLine after report is generated - measureReport = r4MeasureServiceUtils.addProductLineExtension(measureReport, productLine); - - // add subject reference for non-individual reportTypes - measureReport = r4MeasureServiceUtils.addSubjectReference(measureReport, null, subjectParam); - - // add reporter if available - if (reporter != null && !reporter.isEmpty()) { - measureReport.setReporter( - r4MeasureServiceUtils.getReporter(reporter).orElse(null)); - } - // add id to measureReport - initializeReport(measureReport); - - // add report to bundle - bundle.addEntry(getBundleEntry(serverBase, measureReport)); - - // progress feedback - var measureUrl = measureReport.getMeasure(); - if (!measureUrl.isEmpty()) { - log.debug( - "Completed evaluation for Measure: {}, Measures remaining to evaluate: {}", - measureUrl, - totalMeasures--); - } - } - } - } - protected void subjectMeasureReport( Bundle bundle, List measures, @@ -280,8 +225,21 @@ protected void subjectMeasureReport( "Evaluating individual MeasureReports for {} patients, and {} measures", subjects.size(), measures.size()); - for (String subject : subjects) { - for (Measure measure : measures) { + + var evaluateMeasureResultsByMeasureId = new HashMap>(); + + for (Measure measure : measures) { + var measureDef = new R4MeasureDefBuilder().build(measure); + + System.out.println("PRE EVALUATE MEASURE WITH CQL ENGINE 2"); + var evaluationResults = + r4Processor.evaluateMeasureWithCqlEngine(subjects, measure, periodStart, periodEnd, parameters, measureDef, additionalData); + + evaluateMeasureResultsByMeasureId.put(measure.getId(), evaluationResults); + } + + for (Measure measure : measures) { + for (String subject : subjects) { MeasureReport measureReport; // evaluate each measure measureReport = r4Processor.evaluateMeasure( @@ -292,7 +250,8 @@ protected void subjectMeasureReport( Collections.singletonList(subject), additionalData, parameters, - evalType); + evalType, + evaluateMeasureResultsByMeasureId); // add ProductLine after report is generated measureReport = r4MeasureServiceUtils.addProductLineExtension(measureReport, productLine); diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/utils/R4MeasureServiceUtils.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/utils/R4MeasureServiceUtils.java index 0bd00baf53..8a54224fd7 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/utils/R4MeasureServiceUtils.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/utils/R4MeasureServiceUtils.java @@ -36,6 +36,7 @@ import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.CanonicalType; import org.hl7.fhir.r4.model.CodeableConcept; import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.ContactDetail; @@ -52,7 +53,10 @@ import org.opencds.cqf.fhir.cr.measure.common.MeasureReportType; import org.opencds.cqf.fhir.cr.measure.common.MeasureScoring; import org.opencds.cqf.fhir.cr.measure.r4.R4MeasureEvalType; +import org.opencds.cqf.fhir.utility.Canonicals; import org.opencds.cqf.fhir.utility.Ids; +import org.opencds.cqf.fhir.utility.monad.Either3; +import org.opencds.cqf.fhir.utility.search.Searches; public class R4MeasureServiceUtils { private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(R4MeasureServiceUtils.class); @@ -339,4 +343,23 @@ public MeasureEvalType convertToNonVersionedMeasureEvalTypeOrDefault(R4MeasureEv public boolean isSubjectListEffectivelyEmpty(List subjectIds) { return subjectIds == null || subjectIds.isEmpty() || subjectIds.get(0) == null; } + + // LUKETODO: code reuse? + public Measure foldMeasure(Either3 measure, IRepository repository) { + return measure.fold( + measureCanonicalType -> resolveByUrl(measureCanonicalType, repository), + measureIdType -> resolveById(measureIdType, repository), + Function.identity()); + } + + private Measure resolveByUrl(CanonicalType url, IRepository repository) { + var parts = Canonicals.getParts(url); + var result = repository.search( + Bundle.class, Measure.class, Searches.byNameAndVersion(parts.idPart(), parts.version())); + return (Measure) result.getEntryFirstRep().getResource(); + } + + private Measure resolveById(IdType id, IRepository repository) { + return repository.read(Measure.class, id); + } } diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java index 01795bc574..90359d90a2 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java @@ -14,7 +14,6 @@ class MultiMeasureServiceTest { private static final Given GIVEN_REPO = MultiMeasure.given().repositoryFor("MinimalMeasureEvaluation"); - // LUKETODO: this test fails because we expect 2 measure reports but we got 20 @Test void MultiMeasure_AllSubjects_MeasureIdentifier() { var when = GIVEN_REPO @@ -29,7 +28,6 @@ void MultiMeasure_AllSubjects_MeasureIdentifier() { when.then().hasMeasureReportCount(2).report(); } - // LUKETODO: this test fails because we expect 7 measure reports but we got 70 @Test void MultiMeasure_EightMeasures_AllSubjects_MeasureId() { var when = GIVEN_REPO @@ -168,7 +166,6 @@ void MultiMeasure_EightMeasures_AllSubjects_MeasureId() { .up(); } - // LUKETODO: this test fails because we expect 7 measure reports but we got 70 @Test void MultiMeasure_EightMeasures_AllSubjects_MeasureUrl() { var when = GIVEN_REPO @@ -588,7 +585,6 @@ void MultiMeasure_EightMeasures_Patient() { .up(); } - // LUKETODO: expected count for initial-population is 1, but we got 0 @Test void MultiMeasure_EightMeasures_SubjectList() { var when = GIVEN_REPO @@ -756,7 +752,6 @@ void MultiMeasure_EightMeasures_SubjectList() { .up(); } - // LUKETODO: expected count for population "initial-population" did not match expected: 1, actual: 0 @Test void MultiMeasure_EightMeasures_Practitioner() { var when = GIVEN_REPO From bab5c88ba92cbc62531d0c44339ac5b740332ae1 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Wed, 2 Jul 2025 17:58:56 -0400 Subject: [PATCH 04/66] Capture discussion with Justin and requirements but lots of compile errors. --- .../measure/common/MeasureProcessorUtils.java | 45 +++++++++++-------- .../cr/measure/r4/R4CollectDataService.java | 21 ++++++++- .../cr/measure/r4/R4MeasureProcessor.java | 20 ++++++--- .../cr/measure/r4/R4MultiMeasureService.java | 5 ++- 4 files changed, 61 insertions(+), 30 deletions(-) diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java index d5ad15d9ef..ce34a8211b 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java @@ -347,37 +347,44 @@ public Object evaluateObservationCriteria( * @param id library Version identifier used by library engine * @return CQL results for Library defined in the Measure resource */ - public Map getEvaluationResults( + public Map> getEvaluationResults( List subjectIds, + // LUKETODO: pass down the measure ID MeasureDef measureDef, ZonedDateTime zonedMeasurementPeriod, CqlEngine context, - LibraryEngine libraryEngine, - VersionedIdentifier id) { + List> idsToLib) { - Map result = new HashMap<>(); + // measure -> subject -> results + Map> result = new HashMap<>(); // Library $evaluate each subject for (String subjectId : subjectIds) { if (subjectId == null) { throw new NullPointerException("SubjectId is required in order to calculate."); } - Pair subjectInfo = this.getSubjectTypeAndId(subjectId); - String subjectTypePart = subjectInfo.getLeft(); - String subjectIdPart = subjectInfo.getRight(); - context.getState().setContextValue(subjectTypePart, subjectIdPart); - try { - result.put( + for (Pair pair : idsToLib) { + var innerMap = new HashMap(); + Pair subjectInfo = this.getSubjectTypeAndId(subjectId); + String subjectTypePart = subjectInfo.getLeft(); + String subjectIdPart = subjectInfo.getRight(); + context.getState().setContextValue(subjectTypePart, subjectIdPart); + try { + innerMap.put( subjectId, - libraryEngine.getEvaluationResult( - id, subjectId, null, null, null, null, null, zonedMeasurementPeriod, context)); - } catch (Exception e) { - // Catch Exceptions from evaluation per subject, but allow rest of subjects to be processed (if - // applicable) - var error = "Exception for subjectId: %s, Message: %s".formatted(subjectId, e.getMessage()); - // Capture error for MeasureReportBuilder - measureDef.addError(error); - logger.error(error, e); + pair.getRight().getEvaluationResult( + pair.getLeft(), subjectId, null, null, null, null, null, zonedMeasurementPeriod, context)); + } catch (Exception e) { + // Catch Exceptions from evaluation per subject, but allow rest of subjects to be processed (if + // applicable) + var error = "Exception for subjectId: %s, Message: %s".formatted(subjectId, e.getMessage()); + // LUKETODO: figure out another way to capture errors + // Capture error for MeasureReportBuilder + measureDef.addError(error); + logger.error(error, e); + } + // LUKETODO: consider url insetead + result.put(measureDef.id(), innerMap); } } diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java index 40bf41b6fa..38b80054df 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java @@ -5,6 +5,7 @@ import ca.uhn.fhir.repository.IRepository; import jakarta.annotation.Nullable; import java.time.ZonedDateTime; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; @@ -82,7 +83,8 @@ public Parameters collectData( // LUKETODO: reuse this within measureServiceUtils var measureDef = new R4MeasureDefBuilder().build(foldedMeasure); - var evaluationResults = + // LUKETODO: in the old world, we call this for each subject in a loop. in the new world, we do all 4 at once + var evaluationResultsOuter = Map.of(foldedMeasure.getId(), processor.evaluateMeasureWithCqlEngine( subjectList, @@ -95,12 +97,27 @@ public Parameters collectData( if (!subjectList.isEmpty()) { for (String patient : subjectList) { + // LUKETODO: in the old world, we call this for each subject in a loop. in the new world, we do all 4 at once var subjects = Collections.singletonList(patient); + + var mutableList = new ArrayList<>(subjects); + + var evaluationResults = + Map.of(foldedMeasure.getId(), + processor.evaluateMeasureWithCqlEngine( + mutableList, + foldedMeasure, + periodStart, + periodEnd, + parameters, + measureDef, + null)); + // add resources per subject to Parameters addReports(processor, measureId, periodStart, periodEnd, subjects, parameters, evaluationResults); } } else { - addReports(processor, measureId, periodStart, periodEnd, subjectList, parameters, evaluationResults); + addReports(processor, measureId, periodStart, periodEnd, subjectList, parameters, evaluationResultsOuter); } return parameters; } diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java index 88028c7d61..b4cc5e43a8 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java @@ -189,8 +189,8 @@ protected MeasureReport evaluateMeasure( // Process Criteria Expression Results measureProcessorUtils.processResults( - resultsFromNewCqlEngine2, -// evaluateMeasureResults.get(measure.getId()), +// resultsFromNewCqlEngine2, + evaluateMeasureResults.get(measure.getId()), measureDef, evaluationType, this.measureEvaluationOptions.getApplyScoringSetMembership(), @@ -209,30 +209,36 @@ protected MeasureReport evaluateMeasure( subjectIds); } + + // LUKETODO: multiple measures: + // LUKETODO: single subject public Map evaluateMeasureWithCqlEngine( - List subjects, - Measure measure, + String subject, + List measures, @Nullable ZonedDateTime periodStart, @Nullable ZonedDateTime periodEnd, Parameters parameters, MeasureDef measureDef, @Nullable IBaseBundle additionalData) { + // LUKETODO: make this a parameter var context = Engines.forRepository( this.repository, this.measureEvaluationOptions.getEvaluationSettings(), additionalData); var measurementPeriodParams = buildMeasurementPeriod(periodStart, periodEnd); var zonedMeasurementPeriod = MeasureProcessorUtils.getZonedTimeZoneForEval( measureProcessorUtils.getDefaultMeasurementPeriod(measurementPeriodParams, context)); - final VersionedIdentifier libraryVersionIdentifier = getLibraryVersionIdentifier(measure); + final List libraryVersionIdentifiers = measures.stream().map( + this::getLibraryVersionIdentifier).toList(); - var libraryEngine = getLibraryEngine(parameters, libraryVersionIdentifier, context); + // LUKETODO: pass in only LibraryEngine logic to the other method + List libraryEngines = getLibraryEngine(parameters, libraryVersionIdentifier, context); // set measurement Period from CQL if operation parameters are empty measureProcessorUtils.setMeasurementPeriod(measureDef, measurementPeriodParams, context); // populate results from Library $evaluate - return measureProcessorUtils.getEvaluationResults(subjects, measureDef, zonedMeasurementPeriod, context, libraryEngine, libraryVersionIdentifier); + return measureProcessorUtils.getEvaluationResults(List.of(subject), measureDef, zonedMeasurementPeriod, context, libraryEngine, libraryVersionIdentifier); } private String cqlEngineToString(CqlEngine cqlEngine) { diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java index c1a73d9323..c82265d7a7 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java @@ -226,6 +226,7 @@ protected void subjectMeasureReport( subjects.size(), measures.size()); + // measureId -> Map var evaluateMeasureResultsByMeasureId = new HashMap>(); for (Measure measure : measures) { @@ -238,8 +239,8 @@ protected void subjectMeasureReport( evaluateMeasureResultsByMeasureId.put(measure.getId(), evaluationResults); } - for (Measure measure : measures) { - for (String subject : subjects) { + for (String subject : subjects) { + for (Measure measure : measures) { MeasureReport measureReport; // evaluate each measure measureReport = r4Processor.evaluateMeasure( From 74a1b9779e1e8bea9fe3e8464dc486dff77e6fd2 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Thu, 3 Jul 2025 11:48:06 -0400 Subject: [PATCH 05/66] Put in place changes agreed with Justin and all tests except one are passing. --- .../CompositeEvaluationResultsPerMeasure.java | 44 ++++ .../measure/common/MeasureProcessorUtils.java | 93 ++++++-- .../measure/dstu3/Dstu3MeasureProcessor.java | 4 +- .../cr/measure/r4/R4CollectDataService.java | 38 ++- .../cr/measure/r4/R4MeasureProcessor.java | 86 ++++--- .../fhir/cr/measure/r4/R4MeasureService.java | 10 +- .../cr/measure/r4/R4MultiMeasureService.java | 225 +++++++++++++----- .../fhir/cr/measure/r4/CollectDataTest.java | 12 + .../r4/MeasureConditionCategoryPOCTest.java | 1 + 9 files changed, 369 insertions(+), 144 deletions(-) create mode 100644 cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java new file mode 100644 index 0000000000..3537f5e513 --- /dev/null +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java @@ -0,0 +1,44 @@ +package org.opencds.cqf.fhir.cr.measure.common; + +import com.google.common.collect.ImmutableMap; +import java.util.HashMap; +import java.util.Map; +import org.hl7.fhir.instance.model.api.IIdType; +import org.opencds.cqf.cql.engine.execution.EvaluationResult; + +// LUKETODO: javadoc +public class CompositeEvaluationResultsPerMeasure { + private final Map> resultsPerMeasure; + + private CompositeEvaluationResultsPerMeasure(Builder builder) { + this.resultsPerMeasure = builder.resultsPerMeasure.entrySet() + .stream() + .collect(ImmutableMap.toImmutableMap( + Map.Entry::getKey, + entry -> ImmutableMap.copyOf(entry.getValue()) + )); + } + + public static Builder builder() { + return new Builder(); + } + + public Map getResultForMeasure(IIdType measureId) { + return resultsPerMeasure.getOrDefault(measureId, Map.of()); + } + + public static class Builder { + private final Map> resultsPerMeasure = new HashMap<>(); + + public CompositeEvaluationResultsPerMeasure build() { + return new CompositeEvaluationResultsPerMeasure(this); + } + + public void addResult(IIdType measureId, String subjectId, EvaluationResult evaluationResult) { + var resultPerMeasure = + resultsPerMeasure.computeIfAbsent( measureId.toUnqualifiedVersionless(), k -> new HashMap<>()); + + resultPerMeasure.put(subjectId, evaluationResult); + } + } +} diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java index ce34a8211b..6c31849d95 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java @@ -2,6 +2,7 @@ import static org.opencds.cqf.fhir.cr.measure.common.MeasurePopulationType.MEASUREPOPULATION; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; @@ -30,6 +31,7 @@ import org.opencds.cqf.fhir.cql.LibraryEngine; import org.opencds.cqf.fhir.cr.measure.constant.MeasureConstants; import org.opencds.cqf.fhir.cr.measure.helper.DateHelper; +import org.opencds.cqf.fhir.cr.measure.r4.R4MeasureProcessor.MeasureLibraryIdEngineDetails; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -152,7 +154,7 @@ public ParameterDef getMeasurementPeriodParameterDef(CqlEngine context) { * @param context cql engine context used to set measurement period parameter */ @SuppressWarnings({"deprecation", "removal"}) - public void setMeasurementPeriod(MeasureDef measureDef, Interval measurementPeriod, CqlEngine context) { + public void setMeasurementPeriod(Interval measurementPeriod, CqlEngine context) { ParameterDef pd = this.getMeasurementPeriodParameterDef(context); if (pd == null) { logger.warn( @@ -172,6 +174,7 @@ public void setMeasurementPeriod(MeasureDef measureDef, Interval measurementPeri // Use the default, skip validation if (measurementPeriod == null) { + // LUKETODO: what do we replace this with???? measurementPeriod = (Interval) context.getEvaluationVisitor().visitParameterDef(pd, context.getState()); context.getState() @@ -194,7 +197,7 @@ public void setMeasurementPeriod(MeasureDef measureDef, Interval measurementPeri NamedTypeSpecifier pointType = (NamedTypeSpecifier) intervalTypeSpecifier.getPointType(); String targetType = pointType.getName().getLocalPart(); - Interval convertedPeriod = convertInterval(measureDef, measurementPeriod, targetType); + Interval convertedPeriod = convertIntervalSansMeasureDef(measurementPeriod, targetType); context.getState().setParameter(null, MeasureConstants.MEASUREMENT_PERIOD_PARAMETER_NAME, convertedPeriod); } @@ -269,6 +272,30 @@ public Interval convertInterval(MeasureDef measureDef, Interval interval, String .formatted(sourceType, targetType, measureDef.url())); } + // LUKETODO: rename + public Interval convertIntervalSansMeasureDef(Interval interval, String targetType) { + String sourceTypeQualified = interval.getPointType().getTypeName(); + String sourceType = sourceTypeQualified.substring(sourceTypeQualified.lastIndexOf(".") + 1); + if (sourceType.equals(targetType)) { + return interval; + } + + if (sourceType.equals("DateTime") && targetType.equals("Date")) { + logger.debug( + "A DateTime interval was provided and a Date interval was expected. The DateTime will be truncated."); + return new Interval( + truncateDateTime((DateTime) interval.getLow()), + interval.getLowClosed(), + truncateDateTime((DateTime) interval.getHigh()), + interval.getHighClosed()); + } + + // LUKETODO: is there some other context we can pass in here? + throw new InvalidRequestException( + "The interval type of %s did not match the expected type of %s and no conversion was possible" + .formatted(sourceType, targetType)); + } + public Date truncateDateTime(DateTime dateTime) { OffsetDateTime odt = dateTime.getDateTime(); return new Date(odt.getYear(), odt.getMonthValue(), odt.getDayOfMonth()); @@ -337,6 +364,7 @@ public Object evaluateObservationCriteria( return result; } + // LUKETODO: update javadoc /** * method used to execute generate CQL results via Library $evaluate * @param subjectIds subjects to generate results for @@ -347,44 +375,79 @@ public Object evaluateObservationCriteria( * @param id library Version identifier used by library engine * @return CQL results for Library defined in the Measure resource */ - public Map> getEvaluationResults( + public CompositeEvaluationResultsPerMeasure getEvaluationResults( List subjectIds, // LUKETODO: pass down the measure ID - MeasureDef measureDef, ZonedDateTime zonedMeasurementPeriod, CqlEngine context, - List> idsToLib) { + List measureLibraryIdEngineDetailsList) { // measure -> subject -> results - Map> result = new HashMap<>(); + var resultsBuilder = CompositeEvaluationResultsPerMeasure.builder(); // Library $evaluate each subject for (String subjectId : subjectIds) { if (subjectId == null) { - throw new NullPointerException("SubjectId is required in order to calculate."); + throw new InternalErrorException("SubjectId is required in order to calculate."); } - for (Pair pair : idsToLib) { - var innerMap = new HashMap(); + for (var measureLibraryIdEngine : measureLibraryIdEngineDetailsList) { Pair subjectInfo = this.getSubjectTypeAndId(subjectId); String subjectTypePart = subjectInfo.getLeft(); String subjectIdPart = subjectInfo.getRight(); context.getState().setContextValue(subjectTypePart, subjectIdPart); try { - innerMap.put( + resultsBuilder.addResult( + measureLibraryIdEngine.measureId(), subjectId, - pair.getRight().getEvaluationResult( - pair.getLeft(), subjectId, null, null, null, null, null, zonedMeasurementPeriod, context)); + measureLibraryIdEngine.engine().getEvaluationResult( + measureLibraryIdEngine.libraryId(), subjectId, null, null, null, null, null, zonedMeasurementPeriod, context)); } catch (Exception e) { // Catch Exceptions from evaluation per subject, but allow rest of subjects to be processed (if // applicable) var error = "Exception for subjectId: %s, Message: %s".formatted(subjectId, e.getMessage()); // LUKETODO: figure out another way to capture errors // Capture error for MeasureReportBuilder - measureDef.addError(error); +// measureDef.addError(error); logger.error(error, e); } - // LUKETODO: consider url insetead - result.put(measureDef.id(), innerMap); + } + } + + return resultsBuilder.build(); + } + + // LUKETODO: get rid of this when all is said and done + public Map getEvaluationResultsOld( + List subjectIds, + MeasureDef measureDef, + ZonedDateTime zonedMeasurementPeriod, + CqlEngine context, + LibraryEngine libraryEngine, + VersionedIdentifier id) { + + Map result = new HashMap<>(); + + // Library $evaluate each subject + for (String subjectId : subjectIds) { + if (subjectId == null) { + throw new NullPointerException("SubjectId is required in order to calculate."); + } + Pair subjectInfo = this.getSubjectTypeAndId(subjectId); + String subjectTypePart = subjectInfo.getLeft(); + String subjectIdPart = subjectInfo.getRight(); + context.getState().setContextValue(subjectTypePart, subjectIdPart); + try { + result.put( + subjectId, + libraryEngine.getEvaluationResult( + id, subjectId, null, null, null, null, null, zonedMeasurementPeriod, context)); + } catch (Exception e) { + // Catch Exceptions from evaluation per subject, but allow rest of subjects to be processed (if + // applicable) + var error = "Exception for subjectId: %s, Message: %s".formatted(subjectId, e.getMessage()); + // Capture error for MeasureReportBuilder + measureDef.addError(error); + logger.error(error, e); } } diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureProcessor.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureProcessor.java index c5fb762fb2..9669f1caa2 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureProcessor.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureProcessor.java @@ -97,7 +97,7 @@ protected MeasureReport evaluateMeasure( var libraryVersionIdentifier = getLibraryVersionIdentifier(measure); var libraryEngine = getLibraryEngine(parameters, libraryVersionIdentifier, context); // set measurement Period from CQL if operation parameters are empty - measureProcessorUtils.setMeasurementPeriod(measureDef, measurementPeriodParams, context); + measureProcessorUtils.setMeasurementPeriod(measurementPeriodParams, context); // extract measurement Period from CQL to pass to report Builder Interval measurementPeriod = measureProcessorUtils.getDefaultMeasurementPeriod(measurementPeriodParams, context); @@ -105,7 +105,7 @@ protected MeasureReport evaluateMeasure( ZonedDateTime zonedMeasurementPeriod = MeasureProcessorUtils.getZonedTimeZoneForEval(measurementPeriod); // populate results from Library $evaluate if (!subjects.isEmpty()) { - var results = measureProcessorUtils.getEvaluationResults( + var results = measureProcessorUtils.getEvaluationResultsOld( subjectIds, measureDef, zonedMeasurementPeriod, context, libraryEngine, libraryVersionIdentifier); // Process Criteria Expression Results diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java index 38b80054df..0a0e15639d 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java @@ -8,7 +8,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -17,8 +16,8 @@ import org.hl7.fhir.r4.model.MeasureReport; import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.Resource; -import org.opencds.cqf.cql.engine.execution.EvaluationResult; import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions; +import org.opencds.cqf.fhir.cr.measure.common.CompositeEvaluationResultsPerMeasure; import org.opencds.cqf.fhir.cr.measure.common.MeasureEvalType; import org.opencds.cqf.fhir.cr.measure.common.MeasureProcessorUtils; import org.opencds.cqf.fhir.cr.measure.r4.utils.R4MeasureServiceUtils; @@ -80,21 +79,6 @@ public Parameters collectData( var foldedMeasure = measureServiceUtils.foldMeasure(Eithers.forMiddle3(measureId), repository); - // LUKETODO: reuse this within measureServiceUtils - var measureDef = new R4MeasureDefBuilder().build(foldedMeasure); - - // LUKETODO: in the old world, we call this for each subject in a loop. in the new world, we do all 4 at once - var evaluationResultsOuter = - Map.of(foldedMeasure.getId(), - processor.evaluateMeasureWithCqlEngine( - subjectList, - foldedMeasure, - periodStart, - periodEnd, - parameters, - measureDef, - null)); - if (!subjectList.isEmpty()) { for (String patient : subjectList) { // LUKETODO: in the old world, we call this for each subject in a loop. in the new world, we do all 4 at once @@ -103,21 +87,27 @@ public Parameters collectData( var mutableList = new ArrayList<>(subjects); var evaluationResults = - Map.of(foldedMeasure.getId(), - processor.evaluateMeasureWithCqlEngine( + processor.evaluateMeasureWithCqlEngineNew( mutableList, - foldedMeasure, + List.of(foldedMeasure), periodStart, periodEnd, parameters, - measureDef, - null)); + null); // add resources per subject to Parameters addReports(processor, measureId, periodStart, periodEnd, subjects, parameters, evaluationResults); } } else { - addReports(processor, measureId, periodStart, periodEnd, subjectList, parameters, evaluationResultsOuter); + var evaluationResults = + processor.evaluateMeasureWithCqlEngineNew( + subjectList, + List.of(foldedMeasure), + periodStart, + periodEnd, + parameters, + null); + addReports(processor, measureId, periodStart, periodEnd, subjectList, parameters, evaluationResults); } return parameters; } @@ -129,7 +119,7 @@ private void addReports( @Nullable ZonedDateTime periodEnd, List subjects, Parameters parameters, - Map> evaluateMeasureResults) { + CompositeEvaluationResultsPerMeasure evaluateMeasureResults) { MeasureReport report = processor.evaluateMeasure( Eithers.forMiddle3(measureId), diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java index b4cc5e43a8..00e6205835 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java @@ -12,11 +12,11 @@ import java.util.Map; import java.util.Objects; import java.util.function.Function; -import java.util.stream.Collectors; import org.cqframework.cql.cql2elm.CqlIncludeException; import org.cqframework.cql.cql2elm.model.CompiledLibrary; import org.hl7.elm.r1.VersionedIdentifier; import org.hl7.fhir.instance.model.api.IBaseBundle; +import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.CanonicalType; @@ -33,6 +33,7 @@ import org.opencds.cqf.fhir.cql.LibraryEngine; import org.opencds.cqf.fhir.cql.VersionedIdentifiers; import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions; +import org.opencds.cqf.fhir.cr.measure.common.CompositeEvaluationResultsPerMeasure; import org.opencds.cqf.fhir.cr.measure.common.GroupDef; import org.opencds.cqf.fhir.cr.measure.common.MeasureDef; import org.opencds.cqf.fhir.cr.measure.common.MeasureEvalType; @@ -45,8 +46,6 @@ import org.opencds.cqf.fhir.cr.measure.r4.utils.R4MeasureServiceUtils; import org.opencds.cqf.fhir.utility.Canonicals; import org.opencds.cqf.fhir.utility.monad.Either3; -import org.opencds.cqf.fhir.utility.repository.FederatedRepository; -import org.opencds.cqf.fhir.utility.repository.InMemoryFhirRepository; import org.opencds.cqf.fhir.utility.search.Searches; public class R4MeasureProcessor { @@ -79,10 +78,10 @@ public MeasureReport evaluateMeasure( IBaseBundle additionalData, Parameters parameters, MeasureEvalType evalType, - Map> evaluateMeasureResults) { + CompositeEvaluationResultsPerMeasure compositeEvaluationResultsPerMeasure) { var m = measure.fold(this::resolveByUrl, this::resolveById, Function.identity()); return this.evaluateMeasure( - m, periodStart, periodEnd, reportType, subjectIds, additionalData, parameters, evalType, evaluateMeasureResults); + m, periodStart, periodEnd, reportType, subjectIds, additionalData, parameters, evalType, compositeEvaluationResultsPerMeasure); } // LUKETODO: this is no doubt used by CQL-CLI @@ -156,7 +155,7 @@ protected MeasureReport evaluateMeasure( IBaseBundle additionalData, Parameters parameters, MeasureEvalType evalType, - Map> evaluateMeasureResults) { + CompositeEvaluationResultsPerMeasure compositeEvaluationResultsPerMeasure) { // LUKETODO: group the pre-measure engine setup tasks before the measure-specific tasks @@ -178,19 +177,23 @@ protected MeasureReport evaluateMeasure( // library engine setup var libraryEngine = getLibraryEngine(parameters, libraryVersionIdentifier, context); - measureProcessorUtils.setMeasurementPeriod(measureDef, measurementPeriodParams, context); + measureProcessorUtils.setMeasurementPeriod(measurementPeriodParams, context); // extract measurement Period from CQL to pass to report Builder Interval measurementPeriod = measureProcessorUtils.getDefaultMeasurementPeriod(measurementPeriodParams, context); System.out.println("EVALUATE MEASURE LATE!"); // populate results from Library $evaluate Map resultsFromNewCqlEngine2 = - evaluateMeasureWithCqlEngine(subjectIds, measure, periodStart, periodEnd, parameters, measureDef, additionalData); + evaluateMeasureWithCqlEngineOld(subjectIds, measure, periodStart, periodEnd, parameters, measureDef, additionalData); // Process Criteria Expression Results + final IIdType measureId = measure.getIdElement().toUnqualifiedVersionless(); + final Map resultForThisMeasure = + compositeEvaluationResultsPerMeasure.getResultForMeasure(measureId); + measureProcessorUtils.processResults( // resultsFromNewCqlEngine2, - evaluateMeasureResults.get(measure.getId()), + resultForThisMeasure, measureDef, evaluationType, this.measureEvaluationOptions.getApplyScoringSetMembership(), @@ -209,16 +212,40 @@ protected MeasureReport evaluateMeasure( subjectIds); } + public Map evaluateMeasureWithCqlEngineOld( + List subjects, + Measure measure, + @Nullable ZonedDateTime periodStart, + @Nullable ZonedDateTime periodEnd, + Parameters parameters, + MeasureDef measureDef, + @Nullable IBaseBundle additionalData) { + + var context = Engines.forRepository( + this.repository, this.measureEvaluationOptions.getEvaluationSettings(), additionalData); + + var measurementPeriodParams = buildMeasurementPeriod(periodStart, periodEnd); + var zonedMeasurementPeriod = MeasureProcessorUtils.getZonedTimeZoneForEval( + measureProcessorUtils.getDefaultMeasurementPeriod(measurementPeriodParams, context)); + + final VersionedIdentifier libraryVersionIdentifier = getLibraryVersionIdentifier(measure); - // LUKETODO: multiple measures: - // LUKETODO: single subject - public Map evaluateMeasureWithCqlEngine( - String subject, + var libraryEngine = getLibraryEngine(parameters, libraryVersionIdentifier, context); + + // set measurement Period from CQL if operation parameters are empty + measureProcessorUtils.setMeasurementPeriod(measurementPeriodParams, context); + + // populate results from Library $evaluate + return measureProcessorUtils.getEvaluationResultsOld(subjects, measureDef, zonedMeasurementPeriod, context, libraryEngine, libraryVersionIdentifier); + } + + // LUKETODO: consider a single measure variant + public CompositeEvaluationResultsPerMeasure evaluateMeasureWithCqlEngineNew( + List subjects, // LUKETODO: I have a doubt about whether this should be a single subject or a list List measures, @Nullable ZonedDateTime periodStart, @Nullable ZonedDateTime periodEnd, Parameters parameters, - MeasureDef measureDef, @Nullable IBaseBundle additionalData) { // LUKETODO: make this a parameter @@ -228,35 +255,26 @@ public Map evaluateMeasureWithCqlEngine( var measurementPeriodParams = buildMeasurementPeriod(periodStart, periodEnd); var zonedMeasurementPeriod = MeasureProcessorUtils.getZonedTimeZoneForEval( measureProcessorUtils.getDefaultMeasurementPeriod(measurementPeriodParams, context)); - final List libraryVersionIdentifiers = measures.stream().map( - this::getLibraryVersionIdentifier).toList(); - // LUKETODO: pass in only LibraryEngine logic to the other method - List libraryEngines = getLibraryEngine(parameters, libraryVersionIdentifier, context); + final List measureLibraryIdEngineDetailsList = + measures.stream() + .map( measure -> buildLibraryIdEngineDetails(measure, parameters, context)) + .toList(); // set measurement Period from CQL if operation parameters are empty - measureProcessorUtils.setMeasurementPeriod(measureDef, measurementPeriodParams, context); + measureProcessorUtils.setMeasurementPeriod(measurementPeriodParams, context); // populate results from Library $evaluate - return measureProcessorUtils.getEvaluationResults(List.of(subject), measureDef, zonedMeasurementPeriod, context, libraryEngine, libraryVersionIdentifier); + return measureProcessorUtils.getEvaluationResults(subjects, zonedMeasurementPeriod, context, measureLibraryIdEngineDetailsList); } - private String cqlEngineToString(CqlEngine cqlEngine) { - return "CqlEngine{environment=%s, state=%s}" - .formatted(cqlEngine.getEnvironment(), cqlEngine.getState()); + // Ideally this would be done in MeasureProcessorUtils, but it's too much work to change for now + private MeasureLibraryIdEngineDetails buildLibraryIdEngineDetails(Measure measure, Parameters parameters, CqlEngine context) { + var libraryVersionIdentifier = getLibraryVersionIdentifier(measure); + return new MeasureLibraryIdEngineDetails(measure.getIdElement(), libraryVersionIdentifier, getLibraryEngine(parameters, libraryVersionIdentifier, context)); } - private String measureDefToString(MeasureDef measureDef) { - return "MeasureDef{id='%s', url='%s', version='%s', defaultMeasurementPeriod=%s, groups=%s, sdes=%s, errors=%s}" - .formatted( - measureDef.id(), - measureDef.url(), - measureDef.version(), - measureDef.getDefaultMeasurementPeriod(), - measureDef.groups().stream().map(Object::hashCode).toList(), - measureDef.sdes(), - measureDef.errors()); - } + public record MeasureLibraryIdEngineDetails(IIdType measureId, VersionedIdentifier libraryId, LibraryEngine engine) {} // private String groupDefToString(GroupDef groupDef) { // return "GroupDef{id='%s', measureScoring=%s, populations=%s, stratifiers=%s, measurePopulationType=%s}" diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java index acf864d22e..494d403204 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java @@ -3,11 +3,8 @@ import ca.uhn.fhir.repository.IRepository; import jakarta.annotation.Nullable; import java.time.ZonedDateTime; -import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.Optional; -import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.CanonicalType; @@ -106,13 +103,12 @@ public MeasureReport evaluate( .toList(); var evaluationResults = - processor.evaluateMeasureWithCqlEngine( + processor.evaluateMeasureWithCqlEngineNew( subjects, - foldedMeasure, + List.of(foldedMeasure), periodStart, periodEnd, parameters, - measureDef, additionalData); measureReport = processor.evaluateMeasure( @@ -124,7 +120,7 @@ public MeasureReport evaluate( additionalData, parameters, evalType, - Map.of(foldedMeasure.getId(), evaluationResults)); + evaluationResults); // add ProductLine after report is generated measureReport = r4MeasureServiceUtils.addProductLineExtension(measureReport, productLine); diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java index c82265d7a7..f87a408ca0 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java @@ -7,9 +7,7 @@ import jakarta.annotation.Nullable; import java.time.ZonedDateTime; import java.util.Collections; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.UUID; import java.util.stream.Collectors; import org.hl7.fhir.instance.model.api.IIdType; @@ -21,8 +19,8 @@ import org.hl7.fhir.r4.model.MeasureReport; import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.Resource; -import org.opencds.cqf.cql.engine.execution.EvaluationResult; import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions; +import org.opencds.cqf.fhir.cr.measure.common.CompositeEvaluationResultsPerMeasure; import org.opencds.cqf.fhir.cr.measure.common.MeasureEvalType; import org.opencds.cqf.fhir.cr.measure.common.MeasurePeriodValidator; import org.opencds.cqf.fhir.cr.measure.common.MeasureProcessorUtils; @@ -157,52 +155,119 @@ protected void populationMeasureReport( String productLine, String reporter) { - var evaluateMeasureResultsByMeasureId = new HashMap>(); - - // LUKETODO: comment on exactly what this is for and what it does: - for (Measure measure : measures) { - var measureDef = new R4MeasureDefBuilder().build(measure); - - var evaluationResults = - r4Processor.evaluateMeasureWithCqlEngine(subjects, measure, periodStart, periodEnd, parameters, measureDef, additionalData); - - evaluateMeasureResultsByMeasureId.put(measure.getId(), evaluationResults); - } - - // one aggregated MeasureReport per Measure - var totalMeasures = measures.size(); - for (Measure measure : measures) { - MeasureReport measureReport; - // evaluate each measure - measureReport = r4Processor.evaluateMeasure( - measure, periodStart, periodEnd, reportType, subjects, additionalData, parameters, evalType, evaluateMeasureResultsByMeasureId); + /* + * subject1, subject2, subject3 + * + * measureA, measureB + * + * subject 1 -> + * measureA -> subject1 -> result1 + * measureB -> subject1 -> result2 + * subject 2 -> + * measureA -> subject2 -> result3 + * measureB -> subject2 -> result4 + * subject 3 -> + * measureA -> subject3 -> result5 + * measureB -> subject3 -> result6 + * + * measureReport1 -> measureA + * measureReport2 -> measureB + */ + + /* + population/summary + + compositeResultsPerMeasure = cqlEvaluate(measures, subjects) + + foreach measure in measures: + processedResults = processResults(compositeResultsPerMeasure.get(measure.getId())) + measureReport = builder.buildMeasureReport(processedResults) + */ + + /* + subject/individual + + compositeResultsPerMeasure = cqlEvaluate(measures, subjects) + + foreach measure in measures: + foreach subject in subjects: + MeasureReport = processResults(compositeResultsPerMeasure.get(measure.getId())) + + */ + + // This is basically a Map of measure -> subject -> EvaluationResult + final CompositeEvaluationResultsPerMeasure compositeEvaluationResultsPerMeasure = r4Processor.evaluateMeasureWithCqlEngineNew( + subjects, measures, periodStart, periodEnd, parameters, additionalData); + + // LUKETODO: think about how this is used and how to log more effectively or to just leave it as is + var totalMeasures = measures.size(); + for (Measure measure : measures) { + MeasureReport measureReport; + // evaluate each measure + measureReport = r4Processor.evaluateMeasure( + measure, periodStart, periodEnd, reportType, subjects, additionalData, parameters, evalType, compositeEvaluationResultsPerMeasure); - // add ProductLine after report is generated - measureReport = r4MeasureServiceUtils.addProductLineExtension(measureReport, productLine); + // add ProductLine after report is generated + measureReport = r4MeasureServiceUtils.addProductLineExtension(measureReport, productLine); - // add subject reference for non-individual reportTypes - measureReport = r4MeasureServiceUtils.addSubjectReference(measureReport, null, subjectParam); + // add subject reference for non-individual reportTypes + measureReport = r4MeasureServiceUtils.addSubjectReference(measureReport, null, subjectParam); - // add reporter if available - if (reporter != null && !reporter.isEmpty()) { - measureReport.setReporter( + // add reporter if available + if (reporter != null && !reporter.isEmpty()) { + measureReport.setReporter( r4MeasureServiceUtils.getReporter(reporter).orElse(null)); - } - // add id to measureReport - initializeReport(measureReport); + } + // add id to measureReport + initializeReport(measureReport); - // add report to bundle - bundle.addEntry(getBundleEntry(serverBase, measureReport)); + // add report to bundle + bundle.addEntry(getBundleEntry(serverBase, measureReport)); - // progress feedback - var measureUrl = measureReport.getMeasure(); - if (!measureUrl.isEmpty()) { - log.debug( + // progress feedback + var measureUrl = measureReport.getMeasure(); + if (!measureUrl.isEmpty()) { + log.debug( "Completed evaluation for Measure: {}, Measures remaining to evaluate: {}", measureUrl, totalMeasures--); + } } - } + +// // one aggregated MeasureReport per Measure +// var totalMeasures = measures.size(); +// for (Measure measure : measures) { +// MeasureReport measureReport; +// // evaluate each measure +// measureReport = r4Processor.evaluateMeasure( +// measure, periodStart, periodEnd, reportType, subjects, additionalData, parameters, evalType, evaluateMeasureResultsByMeasureId); +// +// // add ProductLine after report is generated +// measureReport = r4MeasureServiceUtils.addProductLineExtension(measureReport, productLine); +// +// // add subject reference for non-individual reportTypes +// measureReport = r4MeasureServiceUtils.addSubjectReference(measureReport, null, subjectParam); +// +// // add reporter if available +// if (reporter != null && !reporter.isEmpty()) { +// measureReport.setReporter( +// r4MeasureServiceUtils.getReporter(reporter).orElse(null)); +// } +// // add id to measureReport +// initializeReport(measureReport); +// +// // add report to bundle +// bundle.addEntry(getBundleEntry(serverBase, measureReport)); +// +// // progress feedback +// var measureUrl = measureReport.getMeasure(); +// if (!measureUrl.isEmpty()) { +// log.debug( +// "Completed evaluation for Measure: {}, Measures remaining to evaluate: {}", +// measureUrl, +// totalMeasures--); +// } +// } } protected void subjectMeasureReport( @@ -226,33 +291,25 @@ protected void subjectMeasureReport( subjects.size(), measures.size()); - // measureId -> Map - var evaluateMeasureResultsByMeasureId = new HashMap>(); + // This is basically a Map of measure -> subject -> EvaluationResult + final CompositeEvaluationResultsPerMeasure compositeEvaluationResultsPerMeasure = r4Processor.evaluateMeasureWithCqlEngineNew( + subjects, measures, periodStart, periodEnd, parameters, additionalData); +// // LUKETODO: comment on exactly what this is for and what it does: for (Measure measure : measures) { - var measureDef = new R4MeasureDefBuilder().build(measure); - - System.out.println("PRE EVALUATE MEASURE WITH CQL ENGINE 2"); - var evaluationResults = - r4Processor.evaluateMeasureWithCqlEngine(subjects, measure, periodStart, periodEnd, parameters, measureDef, additionalData); - - evaluateMeasureResultsByMeasureId.put(measure.getId(), evaluationResults); - } - - for (String subject : subjects) { - for (Measure measure : measures) { + for (String subject : subjects) { MeasureReport measureReport; // evaluate each measure measureReport = r4Processor.evaluateMeasure( - measure, - periodStart, - periodEnd, - reportType, - Collections.singletonList(subject), - additionalData, - parameters, - evalType, - evaluateMeasureResultsByMeasureId); + measure, + periodStart, + periodEnd, + reportType, + Collections.singletonList(subject), + additionalData, + parameters, + evalType, + compositeEvaluationResultsPerMeasure); // add ProductLine after report is generated measureReport = r4MeasureServiceUtils.addProductLineExtension(measureReport, productLine); @@ -260,7 +317,7 @@ protected void subjectMeasureReport( // add reporter if available if (reporter != null && !reporter.isEmpty()) { measureReport.setReporter( - r4MeasureServiceUtils.getReporter(reporter).orElse(null)); + r4MeasureServiceUtils.getReporter(reporter).orElse(null)); } // add id to measureReport initializeReport(measureReport); @@ -281,6 +338,50 @@ protected void subjectMeasureReport( } } } + + +// for (String subject : subjects) { +// for (Measure measure : measures) { +// MeasureReport measureReport; +// // evaluate each measure +// measureReport = r4Processor.evaluateMeasure( +// measure, +// periodStart, +// periodEnd, +// reportType, +// Collections.singletonList(subject), +// additionalData, +// parameters, +// evalType, +// evaluateMeasureResultsByMeasureId); +// +// // add ProductLine after report is generated +// measureReport = r4MeasureServiceUtils.addProductLineExtension(measureReport, productLine); +// +// // add reporter if available +// if (reporter != null && !reporter.isEmpty()) { +// measureReport.setReporter( +// r4MeasureServiceUtils.getReporter(reporter).orElse(null)); +// } +// // add id to measureReport +// initializeReport(measureReport); +// +// // add report to bundle +// bundle.addEntry(getBundleEntry(serverBase, measureReport)); +// +// // progress feedback +// var measureUrl = measureReport.getMeasure(); +// if (!measureUrl.isEmpty()) { +// log.debug("MeasureReports remaining to evaluate {}", totalReports--); +// } +// if (measure.hasUrl()) { +// log.info( +// "Completed evaluation for Measure: {}, Measures remaining to evaluate: {}", +// measure.getUrl(), +// totalMeasures--); +// } +// } +// } } protected List getSubjects(R4RepositorySubjectProvider subjectProvider, String subjectId) { diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/CollectDataTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/CollectDataTest.java index aeab724192..97c6624f2c 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/CollectDataTest.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/CollectDataTest.java @@ -57,6 +57,18 @@ void collectData_resourceBasisMeasure_subject() { .report(); } + // LUKETODO: fix this: + /* + java.lang.UnsupportedOperationException + at java.base/java.util.AbstractList.add(AbstractList.java:155) + at java.base/java.util.AbstractList.add(AbstractList.java:113) + at org.opencds.cqf.fhir.cr.measure.r4.R4MeasureProcessor.lambda$resolveParameterMap$3(R4MeasureProcessor.java:428) + at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) + at org.opencds.cqf.fhir.cr.measure.r4.R4MeasureProcessor.resolveParameterMap(R4MeasureProcessor.java:411) + at org.opencds.cqf.fhir.cr.measure.r4.R4MeasureProcessor.setArgParameters(R4MeasureProcessor.java:379) + at org.opencds.cqf.fhir.cr.measure.r4.R4MeasureProcessor.getLibraryEngine(R4MeasureProcessor.java:358) + at org.opencds.cqf.fhir.cr.measure.r4.R4MeasureProcessor.buildLibraryIdEngineDetails(R4MeasureProcessor.java:274) + */ @Test void collectData_resourceBasisMeasure_population() { CollectData.Given given = CollectData.given().repositoryFor("DischargedonAntithromboticTherapyFHIR"); diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MeasureConditionCategoryPOCTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MeasureConditionCategoryPOCTest.java index 097161be0a..563fe9f8a7 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MeasureConditionCategoryPOCTest.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MeasureConditionCategoryPOCTest.java @@ -8,6 +8,7 @@ public class MeasureConditionCategoryPOCTest { protected static Given given = Measure.given().repositoryFor("ConditionCategoryPoc"); + // LUKETODO: fix with MeasureDef error logic that we removed /** * This is a POC Measure that creates a resource in CQL based on patient chart data * This test only validates that an SDE can create an inline resource based on patient data. From bc1b7166718497b059d10449646155c733fbd89e Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Thu, 3 Jul 2025 12:15:25 -0400 Subject: [PATCH 06/66] Fix CollectData unit test by making resolveParameterMap work with a mutable list. --- .../opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java index 00e6205835..9d59998506 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java @@ -6,6 +6,7 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; import java.time.ZonedDateTime; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -428,7 +429,8 @@ private Map resolveParameterMap(Parameters parameters) { list.add(value); } } else { - parameterMap.put(param.getName(), Arrays.asList(parameterMap.get(param.getName()), value)); + // We need a mutable list here, otherwise, retrieving the list above will fail with UnsupportedOperationException + parameterMap.put(param.getName(), new ArrayList<>(Arrays.asList(parameterMap.get(param.getName()), value))); } } else { parameterMap.put(param.getName(), value); From 07c550b08031e4a61e00744c59eea4497fc9aa10 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Thu, 3 Jul 2025 12:55:53 -0400 Subject: [PATCH 07/66] Spotless. Delete some cruft. --- .../spring/measure/MeasureConfiguration.java | 3 +- .../CompositeEvaluationResultsPerMeasure.java | 11 +- .../measure/common/MeasureProcessorUtils.java | 52 ++-- .../cr/measure/r4/R4CollectDataService.java | 30 +- .../cr/measure/r4/R4MeasureProcessor.java | 82 +++--- .../cr/measure/r4/R4MeasureReportBuilder.java | 47 ++- .../fhir/cr/measure/r4/R4MeasureService.java | 35 +-- .../cr/measure/r4/R4MultiMeasureService.java | 276 ++++++++++-------- .../r4/utils/R4MeasureServiceUtils.java | 10 +- .../fhir/cr/measure/r4/CollectDataTest.java | 12 - 10 files changed, 294 insertions(+), 264 deletions(-) diff --git a/cqf-fhir-cr-spring/src/main/java/org/opencds/cqf/fhir/cr/spring/measure/MeasureConfiguration.java b/cqf-fhir-cr-spring/src/main/java/org/opencds/cqf/fhir/cr/spring/measure/MeasureConfiguration.java index 9273f71437..ffaabbb7a4 100644 --- a/cqf-fhir-cr-spring/src/main/java/org/opencds/cqf/fhir/cr/spring/measure/MeasureConfiguration.java +++ b/cqf-fhir-cr-spring/src/main/java/org/opencds/cqf/fhir/cr/spring/measure/MeasureConfiguration.java @@ -30,6 +30,7 @@ R4MeasureProcessor r4MeasureProcessor( SubjectProvider subjectProvider, R4MeasureServiceUtils measureServiceUtils, MeasureProcessorUtils measureProcessorUtils) { - return new R4MeasureProcessor(repository, measureEvaluationOptions, subjectProvider, measureServiceUtils, measureProcessorUtils); + return new R4MeasureProcessor( + repository, measureEvaluationOptions, subjectProvider, measureServiceUtils, measureProcessorUtils); } } diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java index 3537f5e513..7b99163851 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java @@ -11,12 +11,9 @@ public class CompositeEvaluationResultsPerMeasure { private final Map> resultsPerMeasure; private CompositeEvaluationResultsPerMeasure(Builder builder) { - this.resultsPerMeasure = builder.resultsPerMeasure.entrySet() - .stream() - .collect(ImmutableMap.toImmutableMap( - Map.Entry::getKey, - entry -> ImmutableMap.copyOf(entry.getValue()) - )); + this.resultsPerMeasure = builder.resultsPerMeasure.entrySet().stream() + .collect( + ImmutableMap.toImmutableMap(Map.Entry::getKey, entry -> ImmutableMap.copyOf(entry.getValue()))); } public static Builder builder() { @@ -36,7 +33,7 @@ public CompositeEvaluationResultsPerMeasure build() { public void addResult(IIdType measureId, String subjectId, EvaluationResult evaluationResult) { var resultPerMeasure = - resultsPerMeasure.computeIfAbsent( measureId.toUnqualifiedVersionless(), k -> new HashMap<>()); + resultsPerMeasure.computeIfAbsent(measureId.toUnqualifiedVersionless(), k -> new HashMap<>()); resultPerMeasure.put(subjectId, evaluationResult); } diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java index 6c31849d95..9c726d1b92 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java @@ -282,18 +282,18 @@ public Interval convertIntervalSansMeasureDef(Interval interval, String targetTy if (sourceType.equals("DateTime") && targetType.equals("Date")) { logger.debug( - "A DateTime interval was provided and a Date interval was expected. The DateTime will be truncated."); + "A DateTime interval was provided and a Date interval was expected. The DateTime will be truncated."); return new Interval( - truncateDateTime((DateTime) interval.getLow()), - interval.getLowClosed(), - truncateDateTime((DateTime) interval.getHigh()), - interval.getHighClosed()); + truncateDateTime((DateTime) interval.getLow()), + interval.getLowClosed(), + truncateDateTime((DateTime) interval.getHigh()), + interval.getHighClosed()); } // LUKETODO: is there some other context we can pass in here? throw new InvalidRequestException( - "The interval type of %s did not match the expected type of %s and no conversion was possible" - .formatted(sourceType, targetType)); + "The interval type of %s did not match the expected type of %s and no conversion was possible" + .formatted(sourceType, targetType)); } public Date truncateDateTime(DateTime dateTime) { @@ -397,17 +397,27 @@ public CompositeEvaluationResultsPerMeasure getEvaluationResults( context.getState().setContextValue(subjectTypePart, subjectIdPart); try { resultsBuilder.addResult( - measureLibraryIdEngine.measureId(), - subjectId, - measureLibraryIdEngine.engine().getEvaluationResult( - measureLibraryIdEngine.libraryId(), subjectId, null, null, null, null, null, zonedMeasurementPeriod, context)); + measureLibraryIdEngine.measureId(), + subjectId, + measureLibraryIdEngine + .engine() + .getEvaluationResult( + measureLibraryIdEngine.libraryId(), + subjectId, + null, + null, + null, + null, + null, + zonedMeasurementPeriod, + context)); } catch (Exception e) { // Catch Exceptions from evaluation per subject, but allow rest of subjects to be processed (if // applicable) var error = "Exception for subjectId: %s, Message: %s".formatted(subjectId, e.getMessage()); // LUKETODO: figure out another way to capture errors // Capture error for MeasureReportBuilder -// measureDef.addError(error); + // measureDef.addError(error); logger.error(error, e); } } @@ -418,12 +428,12 @@ public CompositeEvaluationResultsPerMeasure getEvaluationResults( // LUKETODO: get rid of this when all is said and done public Map getEvaluationResultsOld( - List subjectIds, - MeasureDef measureDef, - ZonedDateTime zonedMeasurementPeriod, - CqlEngine context, - LibraryEngine libraryEngine, - VersionedIdentifier id) { + List subjectIds, + MeasureDef measureDef, + ZonedDateTime zonedMeasurementPeriod, + CqlEngine context, + LibraryEngine libraryEngine, + VersionedIdentifier id) { Map result = new HashMap<>(); @@ -438,9 +448,9 @@ public Map getEvaluationResultsOld( context.getState().setContextValue(subjectTypePart, subjectIdPart); try { result.put( - subjectId, - libraryEngine.getEvaluationResult( - id, subjectId, null, null, null, null, null, zonedMeasurementPeriod, context)); + subjectId, + libraryEngine.getEvaluationResult( + id, subjectId, null, null, null, null, null, zonedMeasurementPeriod, context)); } catch (Exception e) { // Catch Exceptions from evaluation per subject, but allow rest of subjects to be processed (if // applicable) diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java index 0a0e15639d..e8c21759f0 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java @@ -70,8 +70,11 @@ public Parameters collectData( Parameters parameters = new Parameters(); var processor = new R4MeasureProcessor( - this.repository, this.measureEvaluationOptions, this.subjectProvider, this.measureServiceUtils, - measureProcessorUtils); + this.repository, + this.measureEvaluationOptions, + this.subjectProvider, + this.measureServiceUtils, + measureProcessorUtils); // LUKETODO: subjectList should be null but we get a list of subjects here: // getSubjects @@ -81,32 +84,21 @@ public Parameters collectData( if (!subjectList.isEmpty()) { for (String patient : subjectList) { - // LUKETODO: in the old world, we call this for each subject in a loop. in the new world, we do all 4 at once + // LUKETODO: in the old world, we call this for each subject in a loop. in the new world, we do all 4 + // at once var subjects = Collections.singletonList(patient); var mutableList = new ArrayList<>(subjects); - var evaluationResults = - processor.evaluateMeasureWithCqlEngineNew( - mutableList, - List.of(foldedMeasure), - periodStart, - periodEnd, - parameters, - null); + var evaluationResults = processor.evaluateMeasureWithCqlEngineNew( + mutableList, List.of(foldedMeasure), periodStart, periodEnd, parameters, null); // add resources per subject to Parameters addReports(processor, measureId, periodStart, periodEnd, subjects, parameters, evaluationResults); } } else { - var evaluationResults = - processor.evaluateMeasureWithCqlEngineNew( - subjectList, - List.of(foldedMeasure), - periodStart, - periodEnd, - parameters, - null); + var evaluationResults = processor.evaluateMeasureWithCqlEngineNew( + subjectList, List.of(foldedMeasure), periodStart, periodEnd, parameters, null); addReports(processor, measureId, periodStart, periodEnd, subjectList, parameters, evaluationResults); } return parameters; diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java index 9d59998506..9d19c9dab4 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java @@ -82,7 +82,15 @@ public MeasureReport evaluateMeasure( CompositeEvaluationResultsPerMeasure compositeEvaluationResultsPerMeasure) { var m = measure.fold(this::resolveByUrl, this::resolveById, Function.identity()); return this.evaluateMeasure( - m, periodStart, periodEnd, reportType, subjectIds, additionalData, parameters, evalType, compositeEvaluationResultsPerMeasure); + m, + periodStart, + periodEnd, + reportType, + subjectIds, + additionalData, + parameters, + evalType, + compositeEvaluationResultsPerMeasure); } // LUKETODO: this is no doubt used by CQL-CLI @@ -184,17 +192,17 @@ protected MeasureReport evaluateMeasure( measureProcessorUtils.getDefaultMeasurementPeriod(measurementPeriodParams, context); System.out.println("EVALUATE MEASURE LATE!"); // populate results from Library $evaluate - Map resultsFromNewCqlEngine2 = - evaluateMeasureWithCqlEngineOld(subjectIds, measure, periodStart, periodEnd, parameters, measureDef, additionalData); + Map resultsFromNewCqlEngine2 = evaluateMeasureWithCqlEngineOld( + subjectIds, measure, periodStart, periodEnd, parameters, measureDef, additionalData); // Process Criteria Expression Results final IIdType measureId = measure.getIdElement().toUnqualifiedVersionless(); final Map resultForThisMeasure = - compositeEvaluationResultsPerMeasure.getResultForMeasure(measureId); + compositeEvaluationResultsPerMeasure.getResultForMeasure(measureId); measureProcessorUtils.processResults( -// resultsFromNewCqlEngine2, - resultForThisMeasure, + // resultsFromNewCqlEngine2, + resultForThisMeasure, measureDef, evaluationType, this.measureEvaluationOptions.getApplyScoringSetMembership(), @@ -214,20 +222,20 @@ protected MeasureReport evaluateMeasure( } public Map evaluateMeasureWithCqlEngineOld( - List subjects, - Measure measure, - @Nullable ZonedDateTime periodStart, - @Nullable ZonedDateTime periodEnd, - Parameters parameters, - MeasureDef measureDef, - @Nullable IBaseBundle additionalData) { + List subjects, + Measure measure, + @Nullable ZonedDateTime periodStart, + @Nullable ZonedDateTime periodEnd, + Parameters parameters, + MeasureDef measureDef, + @Nullable IBaseBundle additionalData) { var context = Engines.forRepository( - this.repository, this.measureEvaluationOptions.getEvaluationSettings(), additionalData); + this.repository, this.measureEvaluationOptions.getEvaluationSettings(), additionalData); var measurementPeriodParams = buildMeasurementPeriod(periodStart, periodEnd); var zonedMeasurementPeriod = MeasureProcessorUtils.getZonedTimeZoneForEval( - measureProcessorUtils.getDefaultMeasurementPeriod(measurementPeriodParams, context)); + measureProcessorUtils.getDefaultMeasurementPeriod(measurementPeriodParams, context)); final VersionedIdentifier libraryVersionIdentifier = getLibraryVersionIdentifier(measure); @@ -237,12 +245,13 @@ public Map evaluateMeasureWithCqlEngineOld( measureProcessorUtils.setMeasurementPeriod(measurementPeriodParams, context); // populate results from Library $evaluate - return measureProcessorUtils.getEvaluationResultsOld(subjects, measureDef, zonedMeasurementPeriod, context, libraryEngine, libraryVersionIdentifier); + return measureProcessorUtils.getEvaluationResultsOld( + subjects, measureDef, zonedMeasurementPeriod, context, libraryEngine, libraryVersionIdentifier); } // LUKETODO: consider a single measure variant public CompositeEvaluationResultsPerMeasure evaluateMeasureWithCqlEngineNew( - List subjects, // LUKETODO: I have a doubt about whether this should be a single subject or a list + List subjects, // LUKETODO: I have a doubt about whether this should be a single subject or a list List measures, @Nullable ZonedDateTime periodStart, @Nullable ZonedDateTime periodEnd, @@ -251,37 +260,42 @@ public CompositeEvaluationResultsPerMeasure evaluateMeasureWithCqlEngineNew( // LUKETODO: make this a parameter var context = Engines.forRepository( - this.repository, this.measureEvaluationOptions.getEvaluationSettings(), additionalData); + this.repository, this.measureEvaluationOptions.getEvaluationSettings(), additionalData); var measurementPeriodParams = buildMeasurementPeriod(periodStart, periodEnd); var zonedMeasurementPeriod = MeasureProcessorUtils.getZonedTimeZoneForEval( - measureProcessorUtils.getDefaultMeasurementPeriod(measurementPeriodParams, context)); + measureProcessorUtils.getDefaultMeasurementPeriod(measurementPeriodParams, context)); - final List measureLibraryIdEngineDetailsList = - measures.stream() - .map( measure -> buildLibraryIdEngineDetails(measure, parameters, context)) + final List measureLibraryIdEngineDetailsList = measures.stream() + .map(measure -> buildLibraryIdEngineDetails(measure, parameters, context)) .toList(); // set measurement Period from CQL if operation parameters are empty measureProcessorUtils.setMeasurementPeriod(measurementPeriodParams, context); // populate results from Library $evaluate - return measureProcessorUtils.getEvaluationResults(subjects, zonedMeasurementPeriod, context, measureLibraryIdEngineDetailsList); + return measureProcessorUtils.getEvaluationResults( + subjects, zonedMeasurementPeriod, context, measureLibraryIdEngineDetailsList); } // Ideally this would be done in MeasureProcessorUtils, but it's too much work to change for now - private MeasureLibraryIdEngineDetails buildLibraryIdEngineDetails(Measure measure, Parameters parameters, CqlEngine context) { + private MeasureLibraryIdEngineDetails buildLibraryIdEngineDetails( + Measure measure, Parameters parameters, CqlEngine context) { var libraryVersionIdentifier = getLibraryVersionIdentifier(measure); - return new MeasureLibraryIdEngineDetails(measure.getIdElement(), libraryVersionIdentifier, getLibraryEngine(parameters, libraryVersionIdentifier, context)); + return new MeasureLibraryIdEngineDetails( + measure.getIdElement(), + libraryVersionIdentifier, + getLibraryEngine(parameters, libraryVersionIdentifier, context)); } - public record MeasureLibraryIdEngineDetails(IIdType measureId, VersionedIdentifier libraryId, LibraryEngine engine) {} + public record MeasureLibraryIdEngineDetails( + IIdType measureId, VersionedIdentifier libraryId, LibraryEngine engine) {} -// private String groupDefToString(GroupDef groupDef) { -// return "GroupDef{id='%s', measureScoring=%s, populations=%s, stratifiers=%s, measurePopulationType=%s}" -// .formatted(groupDef.id(), groupDef.measureScoring(), groupDef.populations(), groupDef.stratifiers(), -// groupDef.get -// } + // private String groupDefToString(GroupDef groupDef) { + // return "GroupDef{id='%s', measureScoring=%s, populations=%s, stratifiers=%s, measurePopulationType=%s}" + // .formatted(groupDef.id(), groupDef.measureScoring(), groupDef.populations(), groupDef.stratifiers(), + // groupDef.get + // } /** Temporary check for Measures that are being blocked from use by evaluateResults method * @@ -429,8 +443,10 @@ private Map resolveParameterMap(Parameters parameters) { list.add(value); } } else { - // We need a mutable list here, otherwise, retrieving the list above will fail with UnsupportedOperationException - parameterMap.put(param.getName(), new ArrayList<>(Arrays.asList(parameterMap.get(param.getName()), value))); + // We need a mutable list here, otherwise, retrieving the list above will fail with + // UnsupportedOperationException + parameterMap.put( + param.getName(), new ArrayList<>(Arrays.asList(parameterMap.get(param.getName()), value))); } } else { parameterMap.put(param.getName(), value); diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureReportBuilder.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureReportBuilder.java index f6e4f35f1a..09f9495d52 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureReportBuilder.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureReportBuilder.java @@ -220,8 +220,11 @@ public static class MeasureReportBuilderMeasureDetails { private final MeasureReportType measureReportType; private final Interval measurementPeriod; - public MeasureReportBuilderMeasureDetails(Measure measure, MeasureDef measureDef, - MeasureReportType measureReportType, Interval measurementPeriod) { + public MeasureReportBuilderMeasureDetails( + Measure measure, + MeasureDef measureDef, + MeasureReportType measureReportType, + Interval measurementPeriod) { this.measure = measure; this.measureDef = measureDef; this.measureReportType = measureReportType; @@ -250,9 +253,10 @@ public boolean equals(Object o) { return false; } MeasureReportBuilderMeasureDetails that = (MeasureReportBuilderMeasureDetails) o; - return Objects.equals(measure, that.measure) && Objects.equals(measureDef, - that.measureDef) && measureReportType == that.measureReportType && Objects.equals( - measurementPeriod, that.measurementPeriod); + return Objects.equals(measure, that.measure) + && Objects.equals(measureDef, that.measureDef) + && measureReportType == that.measureReportType + && Objects.equals(measurementPeriod, that.measurementPeriod); } @Override @@ -263,18 +267,21 @@ public int hashCode() { @Override public String toString() { return new StringJoiner(", ", MeasureReportBuilderMeasureDetails.class.getSimpleName() + "[", "]") - .add("measure=" + measure) - .add("measureDef=" + measureDef) - .add("measureReportType=" + measureReportType) - .add("measurementPeriod=" + measurementPeriod) - .toString(); + .add("measure=" + measure) + .add("measureDef=" + measureDef) + .add("measureReportType=" + measureReportType) + .add("measurementPeriod=" + measurementPeriod) + .toString(); } } public MeasureReport build2(MeasureReportBuilderMeasureDetails measureDetails) { - final MeasureReport measureReport = createMeasureReport(measureDetails.getMeasure(), - measureDetails.getMeasureDef(), measureDetails.getMeasureReportType(), List.of(), - measureDetails.getMeasurementPeriod()); + final MeasureReport measureReport = createMeasureReport( + measureDetails.getMeasure(), + measureDetails.getMeasureDef(), + measureDetails.getMeasureReportType(), + List.of(), + measureDetails.getMeasurementPeriod()); return measureReport; } @@ -742,13 +749,21 @@ private void buildPopulation( PopulationDef populationDef, GroupDef groupDef) { - System.out.printf("1234: boolean basis = %s populationDef subjects: %s, populationDef resources: %s\n", groupDef.isBooleanBasis(), populationDef.getSubjects(), populationDef.getResources().stream().filter( - IBaseResource.class::isInstance).map(IBaseResource.class::cast).map(IBaseResource::getIdElement).toList()); + System.out.printf( + "1234: boolean basis = %s populationDef subjects: %s, populationDef resources: %s\n", + groupDef.isBooleanBasis(), + populationDef.getSubjects(), + populationDef.getResources().stream() + .filter(IBaseResource.class::isInstance) + .map(IBaseResource.class::cast) + .map(IBaseResource::getIdElement) + .toList()); reportPopulation.setCode(measurePopulation.getCode()); reportPopulation.setId(measurePopulation.getId()); - // LUKETODO: here, we are returning an initial-population of 0 instead of 1 because this is a boolean basis and the population def subjects is empty + // LUKETODO: here, we are returning an initial-population of 0 instead of 1 because this is a boolean basis and + // the population def subjects is empty if (groupDef.isBooleanBasis()) { reportPopulation.setCount(populationDef.getSubjects().size()); } else { diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java index 494d403204..80c6cfadf0 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java @@ -62,8 +62,11 @@ public MeasureReport evaluate( var repo = Repositories.proxy(repository, true, dataEndpoint, contentEndpoint, terminologyEndpoint); var processor = new R4MeasureProcessor( - repo, this.measureEvaluationOptions, this.subjectProvider, this.measureServiceUtils, - measureProcessorUtils); + repo, + this.measureEvaluationOptions, + this.subjectProvider, + this.measureServiceUtils, + measureProcessorUtils); R4MeasureServiceUtils r4MeasureServiceUtils = new R4MeasureServiceUtils(repository); r4MeasureServiceUtils.ensureSupplementalDataElementSearchParameter(); @@ -87,29 +90,19 @@ public MeasureReport evaluate( var actualRepo = repo; if (additionalData != null) { actualRepo = new FederatedRepository( - this.repository, new InMemoryFhirRepository(this.repository.fhirContext(), additionalData)); + this.repository, new InMemoryFhirRepository(this.repository.fhirContext(), additionalData)); } - var evalType = - r4MeasureServiceUtils.getMeasureEvalType( - reportType, - Optional.ofNullable(subjectId).map(List::of).orElse(List.of())); + var evalType = r4MeasureServiceUtils.getMeasureEvalType( + reportType, Optional.ofNullable(subjectId).map(List::of).orElse(List.of())); - var subjects = subjectProvider.getSubjects( - actualRepo, - Optional.ofNullable(subjectId) - .map(List::of) - .orElse(List.of())) - .toList(); + var subjects = subjectProvider + .getSubjects( + actualRepo, Optional.ofNullable(subjectId).map(List::of).orElse(List.of())) + .toList(); - var evaluationResults = - processor.evaluateMeasureWithCqlEngineNew( - subjects, - List.of(foldedMeasure), - periodStart, - periodEnd, - parameters, - additionalData); + var evaluationResults = processor.evaluateMeasureWithCqlEngineNew( + subjects, List.of(foldedMeasure), periodStart, periodEnd, parameters, additionalData); measureReport = processor.evaluateMeasure( measure, diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java index f87a408ca0..63dedb1c6f 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java @@ -60,7 +60,11 @@ public R4MultiMeasureService( subjectProvider = new R4RepositorySubjectProvider(measureEvaluationOptions.getSubjectProviderOptions()); r4Processor = new R4MeasureProcessor( - repository, this.measureEvaluationOptions, subjectProvider, r4MeasureServiceUtils, measureProcessorUtils); + repository, + this.measureEvaluationOptions, + subjectProvider, + r4MeasureServiceUtils, + measureProcessorUtils); r4MeasureServiceUtils = new R4MeasureServiceUtils(repository); } @@ -86,11 +90,15 @@ public Bundle evaluate( if (dataEndpoint != null && contentEndpoint != null && terminologyEndpoint != null) { // if needing to use proxy repository, override constructors - var repositoryToUse = Repositories.proxy(repository, true, dataEndpoint, contentEndpoint, terminologyEndpoint); + var repositoryToUse = + Repositories.proxy(repository, true, dataEndpoint, contentEndpoint, terminologyEndpoint); r4Processor = new R4MeasureProcessor( - repositoryToUse, this.measureEvaluationOptions, subjectProvider, r4MeasureServiceUtils, - measureProcessorUtils); + repositoryToUse, + this.measureEvaluationOptions, + subjectProvider, + r4MeasureServiceUtils, + measureProcessorUtils); r4MeasureServiceUtils = new R4MeasureServiceUtils(repositoryToUse); } @@ -161,14 +169,14 @@ protected void populationMeasureReport( * measureA, measureB * * subject 1 -> - * measureA -> subject1 -> result1 - * measureB -> subject1 -> result2 + * measureA -> subject1 -> result1 + * measureB -> subject1 -> result2 * subject 2 -> - * measureA -> subject2 -> result3 - * measureB -> subject2 -> result4 + * measureA -> subject2 -> result3 + * measureB -> subject2 -> result4 * subject 3 -> - * measureA -> subject3 -> result5 - * measureB -> subject3 -> result6 + * measureA -> subject3 -> result5 + * measureB -> subject3 -> result6 * * measureReport1 -> measureA * measureReport2 -> measureB @@ -196,78 +204,88 @@ protected void populationMeasureReport( */ // This is basically a Map of measure -> subject -> EvaluationResult - final CompositeEvaluationResultsPerMeasure compositeEvaluationResultsPerMeasure = r4Processor.evaluateMeasureWithCqlEngineNew( - subjects, measures, periodStart, periodEnd, parameters, additionalData); + final CompositeEvaluationResultsPerMeasure compositeEvaluationResultsPerMeasure = + r4Processor.evaluateMeasureWithCqlEngineNew( + subjects, measures, periodStart, periodEnd, parameters, additionalData); - // LUKETODO: think about how this is used and how to log more effectively or to just leave it as is - var totalMeasures = measures.size(); - for (Measure measure : measures) { - MeasureReport measureReport; - // evaluate each measure - measureReport = r4Processor.evaluateMeasure( - measure, periodStart, periodEnd, reportType, subjects, additionalData, parameters, evalType, compositeEvaluationResultsPerMeasure); + // LUKETODO: think about how this is used and how to log more effectively or to just leave it as is + var totalMeasures = measures.size(); + for (Measure measure : measures) { + MeasureReport measureReport; + // evaluate each measure + measureReport = r4Processor.evaluateMeasure( + measure, + periodStart, + periodEnd, + reportType, + subjects, + additionalData, + parameters, + evalType, + compositeEvaluationResultsPerMeasure); - // add ProductLine after report is generated - measureReport = r4MeasureServiceUtils.addProductLineExtension(measureReport, productLine); + // add ProductLine after report is generated + measureReport = r4MeasureServiceUtils.addProductLineExtension(measureReport, productLine); - // add subject reference for non-individual reportTypes - measureReport = r4MeasureServiceUtils.addSubjectReference(measureReport, null, subjectParam); + // add subject reference for non-individual reportTypes + measureReport = r4MeasureServiceUtils.addSubjectReference(measureReport, null, subjectParam); - // add reporter if available - if (reporter != null && !reporter.isEmpty()) { - measureReport.setReporter( + // add reporter if available + if (reporter != null && !reporter.isEmpty()) { + measureReport.setReporter( r4MeasureServiceUtils.getReporter(reporter).orElse(null)); - } - // add id to measureReport - initializeReport(measureReport); + } + // add id to measureReport + initializeReport(measureReport); - // add report to bundle - bundle.addEntry(getBundleEntry(serverBase, measureReport)); + // add report to bundle + bundle.addEntry(getBundleEntry(serverBase, measureReport)); - // progress feedback - var measureUrl = measureReport.getMeasure(); - if (!measureUrl.isEmpty()) { - log.debug( + // progress feedback + var measureUrl = measureReport.getMeasure(); + if (!measureUrl.isEmpty()) { + log.debug( "Completed evaluation for Measure: {}, Measures remaining to evaluate: {}", measureUrl, totalMeasures--); - } } + } -// // one aggregated MeasureReport per Measure -// var totalMeasures = measures.size(); -// for (Measure measure : measures) { -// MeasureReport measureReport; -// // evaluate each measure -// measureReport = r4Processor.evaluateMeasure( -// measure, periodStart, periodEnd, reportType, subjects, additionalData, parameters, evalType, evaluateMeasureResultsByMeasureId); -// -// // add ProductLine after report is generated -// measureReport = r4MeasureServiceUtils.addProductLineExtension(measureReport, productLine); -// -// // add subject reference for non-individual reportTypes -// measureReport = r4MeasureServiceUtils.addSubjectReference(measureReport, null, subjectParam); -// -// // add reporter if available -// if (reporter != null && !reporter.isEmpty()) { -// measureReport.setReporter( -// r4MeasureServiceUtils.getReporter(reporter).orElse(null)); -// } -// // add id to measureReport -// initializeReport(measureReport); -// -// // add report to bundle -// bundle.addEntry(getBundleEntry(serverBase, measureReport)); -// -// // progress feedback -// var measureUrl = measureReport.getMeasure(); -// if (!measureUrl.isEmpty()) { -// log.debug( -// "Completed evaluation for Measure: {}, Measures remaining to evaluate: {}", -// measureUrl, -// totalMeasures--); -// } -// } + // // one aggregated MeasureReport per Measure + // var totalMeasures = measures.size(); + // for (Measure measure : measures) { + // MeasureReport measureReport; + // // evaluate each measure + // measureReport = r4Processor.evaluateMeasure( + // measure, periodStart, periodEnd, reportType, subjects, additionalData, parameters, + // evalType, evaluateMeasureResultsByMeasureId); + // + // // add ProductLine after report is generated + // measureReport = r4MeasureServiceUtils.addProductLineExtension(measureReport, productLine); + // + // // add subject reference for non-individual reportTypes + // measureReport = r4MeasureServiceUtils.addSubjectReference(measureReport, null, subjectParam); + // + // // add reporter if available + // if (reporter != null && !reporter.isEmpty()) { + // measureReport.setReporter( + // r4MeasureServiceUtils.getReporter(reporter).orElse(null)); + // } + // // add id to measureReport + // initializeReport(measureReport); + // + // // add report to bundle + // bundle.addEntry(getBundleEntry(serverBase, measureReport)); + // + // // progress feedback + // var measureUrl = measureReport.getMeasure(); + // if (!measureUrl.isEmpty()) { + // log.debug( + // "Completed evaluation for Measure: {}, Measures remaining to evaluate: {}", + // measureUrl, + // totalMeasures--); + // } + // } } protected void subjectMeasureReport( @@ -292,24 +310,25 @@ protected void subjectMeasureReport( measures.size()); // This is basically a Map of measure -> subject -> EvaluationResult - final CompositeEvaluationResultsPerMeasure compositeEvaluationResultsPerMeasure = r4Processor.evaluateMeasureWithCqlEngineNew( - subjects, measures, periodStart, periodEnd, parameters, additionalData); + final CompositeEvaluationResultsPerMeasure compositeEvaluationResultsPerMeasure = + r4Processor.evaluateMeasureWithCqlEngineNew( + subjects, measures, periodStart, periodEnd, parameters, additionalData); -// // LUKETODO: comment on exactly what this is for and what it does: + // // LUKETODO: comment on exactly what this is for and what it does: for (Measure measure : measures) { for (String subject : subjects) { MeasureReport measureReport; // evaluate each measure measureReport = r4Processor.evaluateMeasure( - measure, - periodStart, - periodEnd, - reportType, - Collections.singletonList(subject), - additionalData, - parameters, - evalType, - compositeEvaluationResultsPerMeasure); + measure, + periodStart, + periodEnd, + reportType, + Collections.singletonList(subject), + additionalData, + parameters, + evalType, + compositeEvaluationResultsPerMeasure); // add ProductLine after report is generated measureReport = r4MeasureServiceUtils.addProductLineExtension(measureReport, productLine); @@ -317,7 +336,7 @@ protected void subjectMeasureReport( // add reporter if available if (reporter != null && !reporter.isEmpty()) { measureReport.setReporter( - r4MeasureServiceUtils.getReporter(reporter).orElse(null)); + r4MeasureServiceUtils.getReporter(reporter).orElse(null)); } // add id to measureReport initializeReport(measureReport); @@ -332,56 +351,55 @@ protected void subjectMeasureReport( } if (measure.hasUrl()) { log.info( - "Completed evaluation for Measure: {}, Measures remaining to evaluate: {}", - measure.getUrl(), - totalMeasures--); + "Completed evaluation for Measure: {}, Measures remaining to evaluate: {}", + measure.getUrl(), + totalMeasures--); } } } - -// for (String subject : subjects) { -// for (Measure measure : measures) { -// MeasureReport measureReport; -// // evaluate each measure -// measureReport = r4Processor.evaluateMeasure( -// measure, -// periodStart, -// periodEnd, -// reportType, -// Collections.singletonList(subject), -// additionalData, -// parameters, -// evalType, -// evaluateMeasureResultsByMeasureId); -// -// // add ProductLine after report is generated -// measureReport = r4MeasureServiceUtils.addProductLineExtension(measureReport, productLine); -// -// // add reporter if available -// if (reporter != null && !reporter.isEmpty()) { -// measureReport.setReporter( -// r4MeasureServiceUtils.getReporter(reporter).orElse(null)); -// } -// // add id to measureReport -// initializeReport(measureReport); -// -// // add report to bundle -// bundle.addEntry(getBundleEntry(serverBase, measureReport)); -// -// // progress feedback -// var measureUrl = measureReport.getMeasure(); -// if (!measureUrl.isEmpty()) { -// log.debug("MeasureReports remaining to evaluate {}", totalReports--); -// } -// if (measure.hasUrl()) { -// log.info( -// "Completed evaluation for Measure: {}, Measures remaining to evaluate: {}", -// measure.getUrl(), -// totalMeasures--); -// } -// } -// } + // for (String subject : subjects) { + // for (Measure measure : measures) { + // MeasureReport measureReport; + // // evaluate each measure + // measureReport = r4Processor.evaluateMeasure( + // measure, + // periodStart, + // periodEnd, + // reportType, + // Collections.singletonList(subject), + // additionalData, + // parameters, + // evalType, + // evaluateMeasureResultsByMeasureId); + // + // // add ProductLine after report is generated + // measureReport = r4MeasureServiceUtils.addProductLineExtension(measureReport, productLine); + // + // // add reporter if available + // if (reporter != null && !reporter.isEmpty()) { + // measureReport.setReporter( + // r4MeasureServiceUtils.getReporter(reporter).orElse(null)); + // } + // // add id to measureReport + // initializeReport(measureReport); + // + // // add report to bundle + // bundle.addEntry(getBundleEntry(serverBase, measureReport)); + // + // // progress feedback + // var measureUrl = measureReport.getMeasure(); + // if (!measureUrl.isEmpty()) { + // log.debug("MeasureReports remaining to evaluate {}", totalReports--); + // } + // if (measure.hasUrl()) { + // log.info( + // "Completed evaluation for Measure: {}, Measures remaining to evaluate: {}", + // measure.getUrl(), + // totalMeasures--); + // } + // } + // } } protected List getSubjects(R4RepositorySubjectProvider subjectProvider, String subjectId) { diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/utils/R4MeasureServiceUtils.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/utils/R4MeasureServiceUtils.java index 8a54224fd7..d3d629585c 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/utils/R4MeasureServiceUtils.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/utils/R4MeasureServiceUtils.java @@ -347,19 +347,19 @@ public boolean isSubjectListEffectivelyEmpty(List subjectIds) { // LUKETODO: code reuse? public Measure foldMeasure(Either3 measure, IRepository repository) { return measure.fold( - measureCanonicalType -> resolveByUrl(measureCanonicalType, repository), - measureIdType -> resolveById(measureIdType, repository), - Function.identity()); + measureCanonicalType -> resolveByUrl(measureCanonicalType, repository), + measureIdType -> resolveById(measureIdType, repository), + Function.identity()); } private Measure resolveByUrl(CanonicalType url, IRepository repository) { var parts = Canonicals.getParts(url); var result = repository.search( - Bundle.class, Measure.class, Searches.byNameAndVersion(parts.idPart(), parts.version())); + Bundle.class, Measure.class, Searches.byNameAndVersion(parts.idPart(), parts.version())); return (Measure) result.getEntryFirstRep().getResource(); } - private Measure resolveById(IdType id, IRepository repository) { + public Measure resolveById(IdType id, IRepository repository) { return repository.read(Measure.class, id); } } diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/CollectDataTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/CollectDataTest.java index 97c6624f2c..aeab724192 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/CollectDataTest.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/CollectDataTest.java @@ -57,18 +57,6 @@ void collectData_resourceBasisMeasure_subject() { .report(); } - // LUKETODO: fix this: - /* - java.lang.UnsupportedOperationException - at java.base/java.util.AbstractList.add(AbstractList.java:155) - at java.base/java.util.AbstractList.add(AbstractList.java:113) - at org.opencds.cqf.fhir.cr.measure.r4.R4MeasureProcessor.lambda$resolveParameterMap$3(R4MeasureProcessor.java:428) - at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) - at org.opencds.cqf.fhir.cr.measure.r4.R4MeasureProcessor.resolveParameterMap(R4MeasureProcessor.java:411) - at org.opencds.cqf.fhir.cr.measure.r4.R4MeasureProcessor.setArgParameters(R4MeasureProcessor.java:379) - at org.opencds.cqf.fhir.cr.measure.r4.R4MeasureProcessor.getLibraryEngine(R4MeasureProcessor.java:358) - at org.opencds.cqf.fhir.cr.measure.r4.R4MeasureProcessor.buildLibraryIdEngineDetails(R4MeasureProcessor.java:274) - */ @Test void collectData_resourceBasisMeasure_population() { CollectData.Given given = CollectData.given().repositoryFor("DischargedonAntithromboticTherapyFHIR"); From f72d57b668434fa1d1debdea1274fdf12e0a3083 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Thu, 3 Jul 2025 13:58:41 -0400 Subject: [PATCH 08/66] Cleanup and new TODOs. --- .../cr/spring/measure/MeasureConfiguration.java | 6 +----- .../cr/measure/common/MeasureProcessorUtils.java | 1 - .../fhir/cr/measure/r4/R4CollectDataService.java | 11 +++-------- .../fhir/cr/measure/r4/R4MultiMeasureService.java | 15 +++------------ .../measure/r4/utils/R4MeasureServiceUtils.java | 7 +++---- .../r4/MeasureConditionCategoryPOCTest.java | 1 - .../cr/measure/r4/MultiMeasureServiceTest.java | 1 + 7 files changed, 11 insertions(+), 31 deletions(-) diff --git a/cqf-fhir-cr-spring/src/main/java/org/opencds/cqf/fhir/cr/spring/measure/MeasureConfiguration.java b/cqf-fhir-cr-spring/src/main/java/org/opencds/cqf/fhir/cr/spring/measure/MeasureConfiguration.java index ffaabbb7a4..7e093c1377 100644 --- a/cqf-fhir-cr-spring/src/main/java/org/opencds/cqf/fhir/cr/spring/measure/MeasureConfiguration.java +++ b/cqf-fhir-cr-spring/src/main/java/org/opencds/cqf/fhir/cr/spring/measure/MeasureConfiguration.java @@ -6,7 +6,6 @@ import org.opencds.cqf.fhir.cr.measure.common.SubjectProvider; import org.opencds.cqf.fhir.cr.measure.dstu3.Dstu3MeasureProcessor; import org.opencds.cqf.fhir.cr.measure.r4.R4MeasureProcessor; -import org.opencds.cqf.fhir.cr.measure.r4.utils.R4MeasureServiceUtils; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @@ -27,10 +26,7 @@ Dstu3MeasureProcessor dstu3MeasureProcessor( R4MeasureProcessor r4MeasureProcessor( IRepository repository, MeasureEvaluationOptions measureEvaluationOptions, - SubjectProvider subjectProvider, - R4MeasureServiceUtils measureServiceUtils, MeasureProcessorUtils measureProcessorUtils) { - return new R4MeasureProcessor( - repository, measureEvaluationOptions, subjectProvider, measureServiceUtils, measureProcessorUtils); + return new R4MeasureProcessor(repository, measureEvaluationOptions, measureProcessorUtils); } } diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java index 9c726d1b92..304d4135f1 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java @@ -368,7 +368,6 @@ public Object evaluateObservationCriteria( /** * method used to execute generate CQL results via Library $evaluate * @param subjectIds subjects to generate results for - * @param measureDef Measure definition object used to store results of criteria expressions * @param zonedMeasurementPeriod offset defined measurement period for evaluation * @param context cql engine context * @param libraryEngine library engine to use for evaluation of cql diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java index e8c21759f0..06d30086af 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java @@ -69,12 +69,7 @@ public Parameters collectData( String practitioner) { Parameters parameters = new Parameters(); - var processor = new R4MeasureProcessor( - this.repository, - this.measureEvaluationOptions, - this.subjectProvider, - this.measureServiceUtils, - measureProcessorUtils); + var processor = new R4MeasureProcessor(this.repository, this.measureEvaluationOptions, measureProcessorUtils); // LUKETODO: subjectList should be null but we get a list of subjects here: // getSubjects @@ -90,14 +85,14 @@ public Parameters collectData( var mutableList = new ArrayList<>(subjects); - var evaluationResults = processor.evaluateMeasureWithCqlEngineNew( + var evaluationResults = processor.evaluateMeasureWithCqlEngine( mutableList, List.of(foldedMeasure), periodStart, periodEnd, parameters, null); // add resources per subject to Parameters addReports(processor, measureId, periodStart, periodEnd, subjects, parameters, evaluationResults); } } else { - var evaluationResults = processor.evaluateMeasureWithCqlEngineNew( + var evaluationResults = processor.evaluateMeasureWithCqlEngine( subjectList, List.of(foldedMeasure), periodStart, periodEnd, parameters, null); addReports(processor, measureId, periodStart, periodEnd, subjectList, parameters, evaluationResults); } diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java index 63dedb1c6f..7f80daf198 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java @@ -62,8 +62,6 @@ public R4MultiMeasureService( r4Processor = new R4MeasureProcessor( repository, this.measureEvaluationOptions, - subjectProvider, - r4MeasureServiceUtils, measureProcessorUtils); r4MeasureServiceUtils = new R4MeasureServiceUtils(repository); @@ -93,12 +91,7 @@ public Bundle evaluate( var repositoryToUse = Repositories.proxy(repository, true, dataEndpoint, contentEndpoint, terminologyEndpoint); - r4Processor = new R4MeasureProcessor( - repositoryToUse, - this.measureEvaluationOptions, - subjectProvider, - r4MeasureServiceUtils, - measureProcessorUtils); + r4Processor = new R4MeasureProcessor(repositoryToUse, this.measureEvaluationOptions, measureProcessorUtils); r4MeasureServiceUtils = new R4MeasureServiceUtils(repositoryToUse); } @@ -205,10 +198,9 @@ protected void populationMeasureReport( // This is basically a Map of measure -> subject -> EvaluationResult final CompositeEvaluationResultsPerMeasure compositeEvaluationResultsPerMeasure = - r4Processor.evaluateMeasureWithCqlEngineNew( + r4Processor.evaluateMeasureWithCqlEngine( subjects, measures, periodStart, periodEnd, parameters, additionalData); - // LUKETODO: think about how this is used and how to log more effectively or to just leave it as is var totalMeasures = measures.size(); for (Measure measure : measures) { MeasureReport measureReport; @@ -311,10 +303,9 @@ protected void subjectMeasureReport( // This is basically a Map of measure -> subject -> EvaluationResult final CompositeEvaluationResultsPerMeasure compositeEvaluationResultsPerMeasure = - r4Processor.evaluateMeasureWithCqlEngineNew( + r4Processor.evaluateMeasureWithCqlEngine( subjects, measures, periodStart, periodEnd, parameters, additionalData); - // // LUKETODO: comment on exactly what this is for and what it does: for (Measure measure : measures) { for (String subject : subjects) { MeasureReport measureReport; diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/utils/R4MeasureServiceUtils.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/utils/R4MeasureServiceUtils.java index d3d629585c..2d87a3fc48 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/utils/R4MeasureServiceUtils.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/utils/R4MeasureServiceUtils.java @@ -344,22 +344,21 @@ public boolean isSubjectListEffectivelyEmpty(List subjectIds) { return subjectIds == null || subjectIds.isEmpty() || subjectIds.get(0) == null; } - // LUKETODO: code reuse? - public Measure foldMeasure(Either3 measure, IRepository repository) { + public static Measure foldMeasure(Either3 measure, IRepository repository) { return measure.fold( measureCanonicalType -> resolveByUrl(measureCanonicalType, repository), measureIdType -> resolveById(measureIdType, repository), Function.identity()); } - private Measure resolveByUrl(CanonicalType url, IRepository repository) { + private static Measure resolveByUrl(CanonicalType url, IRepository repository) { var parts = Canonicals.getParts(url); var result = repository.search( Bundle.class, Measure.class, Searches.byNameAndVersion(parts.idPart(), parts.version())); return (Measure) result.getEntryFirstRep().getResource(); } - public Measure resolveById(IdType id, IRepository repository) { + public static Measure resolveById(IdType id, IRepository repository) { return repository.read(Measure.class, id); } } diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MeasureConditionCategoryPOCTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MeasureConditionCategoryPOCTest.java index 563fe9f8a7..097161be0a 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MeasureConditionCategoryPOCTest.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MeasureConditionCategoryPOCTest.java @@ -8,7 +8,6 @@ public class MeasureConditionCategoryPOCTest { protected static Given given = Measure.given().repositoryFor("ConditionCategoryPoc"); - // LUKETODO: fix with MeasureDef error logic that we removed /** * This is a POC Measure that creates a resource in CQL based on patient chart data * This test only validates that an SDE can create an inline resource based on patient data. diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java index 90359d90a2..55e4097861 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java @@ -945,6 +945,7 @@ void MultiMeasure_EightMeasures_SubjectOrganization() { .hasSubjectReference("Organization/organization-linked-by-managingOrganization"); } + // LUKETODO: fix error handling so this test will pass /** * Test designed to show how error messages are captured in a Measure Report, when encountered. * The associated Encounter has an invalid period that prevents evaluation and will populate an From aab88f93be810a1e8f526268ce8515e788d8c74d Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Thu, 3 Jul 2025 14:04:59 -0400 Subject: [PATCH 09/66] Fix compile errors. --- .../cqf/fhir/cr/measure/r4/R4CollectDataService.java | 2 +- .../cqf/fhir/cr/measure/r4/R4MeasureProcessor.java | 10 +--------- .../cqf/fhir/cr/measure/r4/R4MeasureService.java | 6 ++---- .../cqf/fhir/cr/measure/r4/R4MultiMeasureService.java | 9 +++------ 4 files changed, 7 insertions(+), 20 deletions(-) diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java index 06d30086af..1b5c9ebc44 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java @@ -69,7 +69,7 @@ public Parameters collectData( String practitioner) { Parameters parameters = new Parameters(); - var processor = new R4MeasureProcessor(this.repository, this.measureEvaluationOptions, measureProcessorUtils); + var processor = new R4MeasureProcessor(this.repository, this.measureEvaluationOptions, this.measureProcessorUtils); // LUKETODO: subjectList should be null but we get a list of subjects here: // getSubjects diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java index 9d19c9dab4..371478b52c 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java @@ -42,9 +42,7 @@ import org.opencds.cqf.fhir.cr.measure.common.MeasureProcessorUtils; import org.opencds.cqf.fhir.cr.measure.common.MeasureReportType; import org.opencds.cqf.fhir.cr.measure.common.MeasureScoring; -import org.opencds.cqf.fhir.cr.measure.common.SubjectProvider; import org.opencds.cqf.fhir.cr.measure.r4.utils.R4DateHelper; -import org.opencds.cqf.fhir.cr.measure.r4.utils.R4MeasureServiceUtils; import org.opencds.cqf.fhir.utility.Canonicals; import org.opencds.cqf.fhir.utility.monad.Either3; import org.opencds.cqf.fhir.utility.search.Searches; @@ -52,21 +50,15 @@ public class R4MeasureProcessor { private final IRepository repository; private final MeasureEvaluationOptions measureEvaluationOptions; - private final SubjectProvider subjectProvider; - private final R4MeasureServiceUtils r4MeasureServiceUtils; private final MeasureProcessorUtils measureProcessorUtils; public R4MeasureProcessor( IRepository repository, MeasureEvaluationOptions measureEvaluationOptions, - SubjectProvider subjectProvider, - R4MeasureServiceUtils r4MeasureServiceUtils, MeasureProcessorUtils measureProcessorUtils) { this.repository = Objects.requireNonNull(repository); this.measureEvaluationOptions = measureEvaluationOptions != null ? measureEvaluationOptions : MeasureEvaluationOptions.defaultOptions(); - this.subjectProvider = subjectProvider; - this.r4MeasureServiceUtils = r4MeasureServiceUtils; this.measureProcessorUtils = measureProcessorUtils; } @@ -250,7 +242,7 @@ public Map evaluateMeasureWithCqlEngineOld( } // LUKETODO: consider a single measure variant - public CompositeEvaluationResultsPerMeasure evaluateMeasureWithCqlEngineNew( + public CompositeEvaluationResultsPerMeasure evaluateMeasureWithCqlEngine( List subjects, // LUKETODO: I have a doubt about whether this should be a single subject or a list List measures, @Nullable ZonedDateTime periodStart, diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java index 80c6cfadf0..97190cc2a9 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java @@ -64,9 +64,7 @@ public MeasureReport evaluate( var processor = new R4MeasureProcessor( repo, this.measureEvaluationOptions, - this.subjectProvider, - this.measureServiceUtils, - measureProcessorUtils); + measureProcessorUtils); R4MeasureServiceUtils r4MeasureServiceUtils = new R4MeasureServiceUtils(repository); r4MeasureServiceUtils.ensureSupplementalDataElementSearchParameter(); @@ -101,7 +99,7 @@ public MeasureReport evaluate( actualRepo, Optional.ofNullable(subjectId).map(List::of).orElse(List.of())) .toList(); - var evaluationResults = processor.evaluateMeasureWithCqlEngineNew( + var evaluationResults = processor.evaluateMeasureWithCqlEngine( subjects, List.of(foldedMeasure), periodStart, periodEnd, parameters, additionalData); measureReport = processor.evaluateMeasure( diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java index 7f80daf198..97a9a52c29 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java @@ -59,10 +59,7 @@ public R4MultiMeasureService( subjectProvider = new R4RepositorySubjectProvider(measureEvaluationOptions.getSubjectProviderOptions()); - r4Processor = new R4MeasureProcessor( - repository, - this.measureEvaluationOptions, - measureProcessorUtils); + r4Processor = new R4MeasureProcessor(repository, this.measureEvaluationOptions, this.measureProcessorUtils); r4MeasureServiceUtils = new R4MeasureServiceUtils(repository); } @@ -91,7 +88,7 @@ public Bundle evaluate( var repositoryToUse = Repositories.proxy(repository, true, dataEndpoint, contentEndpoint, terminologyEndpoint); - r4Processor = new R4MeasureProcessor(repositoryToUse, this.measureEvaluationOptions, measureProcessorUtils); + r4Processor = new R4MeasureProcessor(repositoryToUse, this.measureEvaluationOptions, this.measureProcessorUtils); r4MeasureServiceUtils = new R4MeasureServiceUtils(repositoryToUse); } @@ -199,7 +196,7 @@ protected void populationMeasureReport( // This is basically a Map of measure -> subject -> EvaluationResult final CompositeEvaluationResultsPerMeasure compositeEvaluationResultsPerMeasure = r4Processor.evaluateMeasureWithCqlEngine( - subjects, measures, periodStart, periodEnd, parameters, additionalData); + subjects, measures, periodStart, periodEnd, parameters, additionalData); var totalMeasures = measures.size(); for (Measure measure : measures) { From 0c960ffd4843f6ec76cd8431abf98762a8cca2ac Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Thu, 3 Jul 2025 14:13:22 -0400 Subject: [PATCH 10/66] Small tweaks and TODOs. --- .../cr/measure/common/MeasureProcessorUtils.java | 1 + .../fhir/cr/measure/r4/R4MeasureProcessor.java | 16 ++++++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java index 304d4135f1..9230f347cd 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java @@ -146,6 +146,7 @@ public ParameterDef getMeasurementPeriodParameterDef(CqlEngine context) { return null; } + // LUKETODO: update javadoc /** * method to set measurement period on cql engine context. * Priority is operation parameter defined value, otherwise default CQL value is used diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java index 371478b52c..56bd32b87d 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java @@ -56,6 +56,7 @@ public R4MeasureProcessor( IRepository repository, MeasureEvaluationOptions measureEvaluationOptions, MeasureProcessorUtils measureProcessorUtils) { + this.repository = Objects.requireNonNull(repository); this.measureEvaluationOptions = measureEvaluationOptions != null ? measureEvaluationOptions : MeasureEvaluationOptions.defaultOptions(); @@ -182,18 +183,16 @@ protected MeasureReport evaluateMeasure( // extract measurement Period from CQL to pass to report Builder Interval measurementPeriod = measureProcessorUtils.getDefaultMeasurementPeriod(measurementPeriodParams, context); - System.out.println("EVALUATE MEASURE LATE!"); - // populate results from Library $evaluate - Map resultsFromNewCqlEngine2 = evaluateMeasureWithCqlEngineOld( - subjectIds, measure, periodStart, periodEnd, parameters, measureDef, additionalData); // Process Criteria Expression Results final IIdType measureId = measure.getIdElement().toUnqualifiedVersionless(); + // populate results from Library $evaluate final Map resultForThisMeasure = compositeEvaluationResultsPerMeasure.getResultForMeasure(measureId); + // LUKETODO: process MeasureDef error here! + measureProcessorUtils.processResults( - // resultsFromNewCqlEngine2, resultForThisMeasure, measureDef, evaluationType, @@ -213,6 +212,8 @@ protected MeasureReport evaluateMeasure( subjectIds); } + // LUKETODO: delete this once I no longer need to reference it + /* public Map evaluateMeasureWithCqlEngineOld( List subjects, Measure measure, @@ -240,6 +241,7 @@ public Map evaluateMeasureWithCqlEngineOld( return measureProcessorUtils.getEvaluationResultsOld( subjects, measureDef, zonedMeasurementPeriod, context, libraryEngine, libraryVersionIdentifier); } + */ // LUKETODO: consider a single measure variant public CompositeEvaluationResultsPerMeasure evaluateMeasureWithCqlEngine( @@ -266,8 +268,9 @@ public CompositeEvaluationResultsPerMeasure evaluateMeasureWithCqlEngine( measureProcessorUtils.setMeasurementPeriod(measurementPeriodParams, context); // populate results from Library $evaluate + return measureProcessorUtils.getEvaluationResults( - subjects, zonedMeasurementPeriod, context, measureLibraryIdEngineDetailsList); + subjects, zonedMeasurementPeriod, context, measureLibraryIdEngineDetailsList); } // Ideally this would be done in MeasureProcessorUtils, but it's too much work to change for now @@ -280,6 +283,7 @@ private MeasureLibraryIdEngineDetails buildLibraryIdEngineDetails( getLibraryEngine(parameters, libraryVersionIdentifier, context)); } + // LUKETODO: find a better place for this public record MeasureLibraryIdEngineDetails( IIdType measureId, VersionedIdentifier libraryId, LibraryEngine engine) {} From 49f6de3d7da4bd30001d5a6461c007d9e41e4a14 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Thu, 3 Jul 2025 15:35:25 -0400 Subject: [PATCH 11/66] Overhaul CompositeEvaluationResultsPerMeasure to include errors as well as successful results, and enhancing it to mutate a given MeasureDef with errors should they exist. Start cleaning up. Spotless. --- .../CompositeEvaluationResultsPerMeasure.java | 56 +++++++++++++++++-- .../measure/common/MeasureProcessorUtils.java | 4 +- .../cr/measure/r4/R4CollectDataService.java | 3 +- .../cr/measure/r4/R4MeasureProcessor.java | 15 ++--- .../fhir/cr/measure/r4/R4MeasureService.java | 10 +--- .../cr/measure/r4/R4MultiMeasureService.java | 5 +- 6 files changed, 63 insertions(+), 30 deletions(-) diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java index 7b99163851..d3c509ca41 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java @@ -1,31 +1,67 @@ package org.opencds.cqf.fhir.cr.measure.common; import com.google.common.collect.ImmutableMap; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Map.Entry; import org.hl7.fhir.instance.model.api.IIdType; import org.opencds.cqf.cql.engine.execution.EvaluationResult; // LUKETODO: javadoc public class CompositeEvaluationResultsPerMeasure { + // The same measure may have successful results AND errors, so account for both private final Map> resultsPerMeasure; + // We may get several errors for a given measure + private final Map> errorsPerMeasure; private CompositeEvaluationResultsPerMeasure(Builder builder) { - this.resultsPerMeasure = builder.resultsPerMeasure.entrySet().stream() - .collect( - ImmutableMap.toImmutableMap(Map.Entry::getKey, entry -> ImmutableMap.copyOf(entry.getValue()))); + + var resultsBuilder = ImmutableMap.>builder(); + var errorsBuilder = ImmutableMap.>builder(); + + builder.resultsPerMeasure.forEach((key, value) -> resultsBuilder.put(key, ImmutableMap.copyOf(value))); + + resultsPerMeasure = resultsBuilder.build(); + + builder.errorsPerMeasure.forEach((key, value) -> errorsBuilder.put(key, List.copyOf(value))); + + errorsPerMeasure = errorsBuilder.build(); } public static Builder builder() { return new Builder(); } - public Map getResultForMeasure(IIdType measureId) { - return resultsPerMeasure.getOrDefault(measureId, Map.of()); + // measureDef will occasionally be prepended with the version, which means we need to parse it into an IIdType which + // is too much work, so pass in the measureId directly + public Map processMeasureForSuccessOrFailure(IIdType measureId, MeasureDef measureDef) { + var hasFoundErrors = errorsPerMeasure.entrySet().stream() + .filter(entry -> isMeasureDefFound(entry.getKey(), measureId)) + .map(Entry::getValue) + .flatMap(List::stream) + .peek(measureDef::addError) + .findAny() + .isPresent(); + + var resultForMeasure = resultsPerMeasure.entrySet().stream() + .filter(entry -> isMeasureDefFound(entry.getKey(), measureId)) + .map(Entry::getValue) + .findFirst(); + + // We are explicitly maintaining the logic of accepting the lack of any sort of results + // either errors or successes, and returning an empty map. + return resultForMeasure.orElseGet(Map::of); + } + + private boolean isMeasureDefFound(IIdType entryKey, IIdType measureId) { + return measureId.toUnqualifiedVersionless().equals(entryKey.toUnqualifiedVersionless()); } public static class Builder { private final Map> resultsPerMeasure = new HashMap<>(); + private final Map> errorsPerMeasure = new HashMap<>(); public CompositeEvaluationResultsPerMeasure build() { return new CompositeEvaluationResultsPerMeasure(this); @@ -37,5 +73,15 @@ public void addResult(IIdType measureId, String subjectId, EvaluationResult eval resultPerMeasure.put(subjectId, evaluationResult); } + + public void addError(IIdType measureId, String error) { + if (error == null || error.isBlank()) { + return; + } + + errorsPerMeasure + .computeIfAbsent(measureId.toUnqualifiedVersionless(), k -> new ArrayList<>()) + .add(error); + } } } diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java index 9230f347cd..e2938793ff 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java @@ -415,9 +415,7 @@ public CompositeEvaluationResultsPerMeasure getEvaluationResults( // Catch Exceptions from evaluation per subject, but allow rest of subjects to be processed (if // applicable) var error = "Exception for subjectId: %s, Message: %s".formatted(subjectId, e.getMessage()); - // LUKETODO: figure out another way to capture errors - // Capture error for MeasureReportBuilder - // measureDef.addError(error); + resultsBuilder.addError(measureLibraryIdEngine.measureId(), error); logger.error(error, e); } } diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java index 1b5c9ebc44..052597d21b 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java @@ -69,7 +69,8 @@ public Parameters collectData( String practitioner) { Parameters parameters = new Parameters(); - var processor = new R4MeasureProcessor(this.repository, this.measureEvaluationOptions, this.measureProcessorUtils); + var processor = + new R4MeasureProcessor(this.repository, this.measureEvaluationOptions, this.measureProcessorUtils); // LUKETODO: subjectList should be null but we get a list of subjects here: // getSubjects diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java index 56bd32b87d..928ca04847 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java @@ -12,7 +12,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.function.Function; import org.cqframework.cql.cql2elm.CqlIncludeException; import org.cqframework.cql.cql2elm.model.CompiledLibrary; import org.hl7.elm.r1.VersionedIdentifier; @@ -43,6 +42,7 @@ import org.opencds.cqf.fhir.cr.measure.common.MeasureReportType; import org.opencds.cqf.fhir.cr.measure.common.MeasureScoring; import org.opencds.cqf.fhir.cr.measure.r4.utils.R4DateHelper; +import org.opencds.cqf.fhir.cr.measure.r4.utils.R4MeasureServiceUtils; import org.opencds.cqf.fhir.utility.Canonicals; import org.opencds.cqf.fhir.utility.monad.Either3; import org.opencds.cqf.fhir.utility.search.Searches; @@ -73,9 +73,8 @@ public MeasureReport evaluateMeasure( Parameters parameters, MeasureEvalType evalType, CompositeEvaluationResultsPerMeasure compositeEvaluationResultsPerMeasure) { - var m = measure.fold(this::resolveByUrl, this::resolveById, Function.identity()); return this.evaluateMeasure( - m, + R4MeasureServiceUtils.foldMeasure(measure, this.repository), periodStart, periodEnd, reportType, @@ -188,7 +187,7 @@ protected MeasureReport evaluateMeasure( final IIdType measureId = measure.getIdElement().toUnqualifiedVersionless(); // populate results from Library $evaluate final Map resultForThisMeasure = - compositeEvaluationResultsPerMeasure.getResultForMeasure(measureId); + compositeEvaluationResultsPerMeasure.processMeasureForSuccessOrFailure(measureId, measureDef); // LUKETODO: process MeasureDef error here! @@ -270,7 +269,7 @@ public CompositeEvaluationResultsPerMeasure evaluateMeasureWithCqlEngine( // populate results from Library $evaluate return measureProcessorUtils.getEvaluationResults( - subjects, zonedMeasurementPeriod, context, measureLibraryIdEngineDetailsList); + subjects, zonedMeasurementPeriod, context, measureLibraryIdEngineDetailsList); } // Ideally this would be done in MeasureProcessorUtils, but it's too much work to change for now @@ -287,12 +286,6 @@ private MeasureLibraryIdEngineDetails buildLibraryIdEngineDetails( public record MeasureLibraryIdEngineDetails( IIdType measureId, VersionedIdentifier libraryId, LibraryEngine engine) {} - // private String groupDefToString(GroupDef groupDef) { - // return "GroupDef{id='%s', measureScoring=%s, populations=%s, stratifiers=%s, measurePopulationType=%s}" - // .formatted(groupDef.id(), groupDef.measureScoring(), groupDef.populations(), groupDef.stratifiers(), - // groupDef.get - // } - /** Temporary check for Measures that are being blocked from use by evaluateResults method * * @param measureDef defined measure definition object used to capture criteria expression results diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java index 97190cc2a9..c266968cd9 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java @@ -61,21 +61,15 @@ public MeasureReport evaluate( measurePeriodValidator.validatePeriodStartAndEnd(periodStart, periodEnd); var repo = Repositories.proxy(repository, true, dataEndpoint, contentEndpoint, terminologyEndpoint); - var processor = new R4MeasureProcessor( - repo, - this.measureEvaluationOptions, - measureProcessorUtils); + var processor = new R4MeasureProcessor(repo, this.measureEvaluationOptions, measureProcessorUtils); R4MeasureServiceUtils r4MeasureServiceUtils = new R4MeasureServiceUtils(repository); r4MeasureServiceUtils.ensureSupplementalDataElementSearchParameter(); - var foldedMeasure = measureServiceUtils.foldMeasure(measure, repo); + var foldedMeasure = R4MeasureServiceUtils.foldMeasure(measure, repo); processor.checkMeasureLibrary(foldedMeasure); - // LUKETODO: reuse this within measureServiceUtils - var measureDef = new R4MeasureDefBuilder().build(foldedMeasure); - MeasureReport measureReport; if (StringUtils.isNotBlank(practitioner)) { diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java index 97a9a52c29..6d4d5a571a 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java @@ -88,7 +88,8 @@ public Bundle evaluate( var repositoryToUse = Repositories.proxy(repository, true, dataEndpoint, contentEndpoint, terminologyEndpoint); - r4Processor = new R4MeasureProcessor(repositoryToUse, this.measureEvaluationOptions, this.measureProcessorUtils); + r4Processor = + new R4MeasureProcessor(repositoryToUse, this.measureEvaluationOptions, this.measureProcessorUtils); r4MeasureServiceUtils = new R4MeasureServiceUtils(repositoryToUse); } @@ -196,7 +197,7 @@ protected void populationMeasureReport( // This is basically a Map of measure -> subject -> EvaluationResult final CompositeEvaluationResultsPerMeasure compositeEvaluationResultsPerMeasure = r4Processor.evaluateMeasureWithCqlEngine( - subjects, measures, periodStart, periodEnd, parameters, additionalData); + subjects, measures, periodStart, periodEnd, parameters, additionalData); var totalMeasures = measures.size(); for (Measure measure : measures) { From 0d589ed3cd67e70a60efc77f98df1d8db6a22ca7 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Thu, 3 Jul 2025 16:09:43 -0400 Subject: [PATCH 12/66] Cleanup. Spotless. --- .../CompositeEvaluationResultsPerMeasure.java | 9 ++- .../measure/common/MeasureProcessorUtils.java | 13 ++-- .../measure/dstu3/Dstu3MeasureProcessor.java | 2 +- .../cr/measure/r4/R4MeasureReportBuilder.java | 75 ------------------- .../measure/r4/MultiMeasureServiceTest.java | 1 - 5 files changed, 11 insertions(+), 89 deletions(-) diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java index d3c509ca41..694fdf1642 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java @@ -10,6 +10,7 @@ import org.opencds.cqf.cql.engine.execution.EvaluationResult; // LUKETODO: javadoc +// This is basically a Map of measure -> subject -> EvaluationResult public class CompositeEvaluationResultsPerMeasure { // The same measure may have successful results AND errors, so account for both private final Map> resultsPerMeasure; @@ -30,10 +31,6 @@ private CompositeEvaluationResultsPerMeasure(Builder builder) { errorsPerMeasure = errorsBuilder.build(); } - public static Builder builder() { - return new Builder(); - } - // measureDef will occasionally be prepended with the version, which means we need to parse it into an IIdType which // is too much work, so pass in the measureId directly public Map processMeasureForSuccessOrFailure(IIdType measureId, MeasureDef measureDef) { @@ -59,6 +56,10 @@ private boolean isMeasureDefFound(IIdType entryKey, IIdType measureId) { return measureId.toUnqualifiedVersionless().equals(entryKey.toUnqualifiedVersionless()); } + public static Builder builder() { + return new Builder(); + } + public static class Builder { private final Map> resultsPerMeasure = new HashMap<>(); private final Map> errorsPerMeasure = new HashMap<>(); diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java index e2938793ff..9d9a245b43 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java @@ -146,16 +146,14 @@ public ParameterDef getMeasurementPeriodParameterDef(CqlEngine context) { return null; } - // LUKETODO: update javadoc /** * method to set measurement period on cql engine context. * Priority is operation parameter defined value, otherwise default CQL value is used - * @param measureDef Measure defined objects to populate with criteria results * @param measurementPeriod Interval defined by operation parameters to override default CQL value * @param context cql engine context used to set measurement period parameter */ @SuppressWarnings({"deprecation", "removal"}) - public void setMeasurementPeriod(Interval measurementPeriod, CqlEngine context) { + public void setMeasurementPeriod(Interval measurementPeriod, CqlEngine context, List measureUrls) { ParameterDef pd = this.getMeasurementPeriodParameterDef(context); if (pd == null) { logger.warn( @@ -175,7 +173,6 @@ public void setMeasurementPeriod(Interval measurementPeriod, CqlEngine context) // Use the default, skip validation if (measurementPeriod == null) { - // LUKETODO: what do we replace this with???? measurementPeriod = (Interval) context.getEvaluationVisitor().visitParameterDef(pd, context.getState()); context.getState() @@ -198,7 +195,7 @@ public void setMeasurementPeriod(Interval measurementPeriod, CqlEngine context) NamedTypeSpecifier pointType = (NamedTypeSpecifier) intervalTypeSpecifier.getPointType(); String targetType = pointType.getName().getLocalPart(); - Interval convertedPeriod = convertIntervalSansMeasureDef(measurementPeriod, targetType); + Interval convertedPeriod = convertInterval(measurementPeriod, targetType, measureUrls); context.getState().setParameter(null, MeasureConstants.MEASUREMENT_PERIOD_PARAMETER_NAME, convertedPeriod); } @@ -251,7 +248,7 @@ private static DateTime cloneDateTimeWithUtc(DateTime dateTime) { return newDateTime; } - public Interval convertInterval(MeasureDef measureDef, Interval interval, String targetType) { + public Interval convertInterval(Interval interval, String targetType, List measureUrls) { String sourceTypeQualified = interval.getPointType().getTypeName(); String sourceType = sourceTypeQualified.substring(sourceTypeQualified.lastIndexOf(".") + 1); if (sourceType.equals(targetType)) { @@ -269,8 +266,8 @@ public Interval convertInterval(MeasureDef measureDef, Interval interval, String } throw new InvalidRequestException( - "The interval type of %s did not match the expected type of %s and no conversion was possible for MeasureDef: %s." - .formatted(sourceType, targetType, measureDef.url())); + "The interval type of %s did not match the expected type of %s and no conversion was possible for measure URLs (first 5 only shown): %s." + .formatted(sourceType, targetType, measureUrls.stream().limit(5).toList())); } // LUKETODO: rename diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureProcessor.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureProcessor.java index 9669f1caa2..216336f265 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureProcessor.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureProcessor.java @@ -97,7 +97,7 @@ protected MeasureReport evaluateMeasure( var libraryVersionIdentifier = getLibraryVersionIdentifier(measure); var libraryEngine = getLibraryEngine(parameters, libraryVersionIdentifier, context); // set measurement Period from CQL if operation parameters are empty - measureProcessorUtils.setMeasurementPeriod(measurementPeriodParams, context); + measureProcessorUtils.setMeasurementPeriod(measurementPeriodParams, context, List.of(measure.getUrl())); // extract measurement Period from CQL to pass to report Builder Interval measurementPeriod = measureProcessorUtils.getDefaultMeasurementPeriod(measurementPeriodParams, context); diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureReportBuilder.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureReportBuilder.java index 09f9495d52..0479e643f2 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureReportBuilder.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureReportBuilder.java @@ -213,79 +213,6 @@ private OperationOutcome createOperationOutcome(String errorMsg) { } } - // LUKETODO: rename - public static class MeasureReportBuilderMeasureDetails { - private final Measure measure; - private final MeasureDef measureDef; - private final MeasureReportType measureReportType; - private final Interval measurementPeriod; - - public MeasureReportBuilderMeasureDetails( - Measure measure, - MeasureDef measureDef, - MeasureReportType measureReportType, - Interval measurementPeriod) { - this.measure = measure; - this.measureDef = measureDef; - this.measureReportType = measureReportType; - this.measurementPeriod = measurementPeriod; - } - - public Measure getMeasure() { - return measure; - } - - public MeasureDef getMeasureDef() { - return measureDef; - } - - public MeasureReportType getMeasureReportType() { - return measureReportType; - } - - public Interval getMeasurementPeriod() { - return measurementPeriod; - } - - @Override - public boolean equals(Object o) { - if (o == null || getClass() != o.getClass()) { - return false; - } - MeasureReportBuilderMeasureDetails that = (MeasureReportBuilderMeasureDetails) o; - return Objects.equals(measure, that.measure) - && Objects.equals(measureDef, that.measureDef) - && measureReportType == that.measureReportType - && Objects.equals(measurementPeriod, that.measurementPeriod); - } - - @Override - public int hashCode() { - return Objects.hash(measure, measureDef, measureReportType, measurementPeriod); - } - - @Override - public String toString() { - return new StringJoiner(", ", MeasureReportBuilderMeasureDetails.class.getSimpleName() + "[", "]") - .add("measure=" + measure) - .add("measureDef=" + measureDef) - .add("measureReportType=" + measureReportType) - .add("measurementPeriod=" + measurementPeriod) - .toString(); - } - } - - public MeasureReport build2(MeasureReportBuilderMeasureDetails measureDetails) { - final MeasureReport measureReport = createMeasureReport( - measureDetails.getMeasure(), - measureDetails.getMeasureDef(), - measureDetails.getMeasureReportType(), - List.of(), - measureDetails.getMeasurementPeriod()); - - return measureReport; - } - @Override public MeasureReport build( Measure measure, @@ -762,8 +689,6 @@ private void buildPopulation( reportPopulation.setCode(measurePopulation.getCode()); reportPopulation.setId(measurePopulation.getId()); - // LUKETODO: here, we are returning an initial-population of 0 instead of 1 because this is a boolean basis and - // the population def subjects is empty if (groupDef.isBooleanBasis()) { reportPopulation.setCount(populationDef.getSubjects().size()); } else { diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java index 55e4097861..90359d90a2 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java @@ -945,7 +945,6 @@ void MultiMeasure_EightMeasures_SubjectOrganization() { .hasSubjectReference("Organization/organization-linked-by-managingOrganization"); } - // LUKETODO: fix error handling so this test will pass /** * Test designed to show how error messages are captured in a Measure Report, when encountered. * The associated Encounter has an invalid period that prevents evaluation and will populate an From 044d14bc628725129c5ce69bd8b15c6528919599 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Thu, 3 Jul 2025 16:10:22 -0400 Subject: [PATCH 13/66] Spotless. --- .../cqf/fhir/cr/measure/common/MeasureProcessorUtils.java | 5 ++++- .../cqf/fhir/cr/measure/r4/R4MeasureReportBuilder.java | 1 - 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java index 9d9a245b43..876fd79b45 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java @@ -267,7 +267,10 @@ public Interval convertInterval(Interval interval, String targetType, List Date: Thu, 3 Jul 2025 16:39:46 -0400 Subject: [PATCH 14/66] Handle new MeasureProcessorUtils#setMeasurePeriod, ensure all callers call it correctly, and get rid of duplicate methods. --- .../measure/common/MeasureProcessorUtils.java | 24 ------------------- .../measure/dstu3/Dstu3MeasureProcessor.java | 6 ++++- .../cr/measure/r4/R4MeasureProcessor.java | 15 ++++++++++-- .../cr/measure/r4/R4MeasureReportBuilder.java | 11 --------- 4 files changed, 18 insertions(+), 38 deletions(-) diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java index 876fd79b45..7992c83b17 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java @@ -273,30 +273,6 @@ public Interval convertInterval(Interval interval, String targetType, List Optional.ofNullable(url).orElse("Unknown Measure URL")) + .toList()); // populate results from Library $evaluate diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureReportBuilder.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureReportBuilder.java index 46f9c25e86..43a24efceb 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureReportBuilder.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureReportBuilder.java @@ -21,7 +21,6 @@ import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.exceptions.FHIRException; -import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.r4.model.CanonicalType; import org.hl7.fhir.r4.model.CodeableConcept; @@ -675,16 +674,6 @@ private void buildPopulation( PopulationDef populationDef, GroupDef groupDef) { - System.out.printf( - "1234: boolean basis = %s populationDef subjects: %s, populationDef resources: %s\n", - groupDef.isBooleanBasis(), - populationDef.getSubjects(), - populationDef.getResources().stream() - .filter(IBaseResource.class::isInstance) - .map(IBaseResource.class::cast) - .map(IBaseResource::getIdElement) - .toList()); - reportPopulation.setCode(measurePopulation.getCode()); reportPopulation.setId(measurePopulation.getId()); From c6ce09883341bb58ac5fff0eae99e29342ea9c20 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Thu, 3 Jul 2025 16:51:47 -0400 Subject: [PATCH 15/66] Clean up todos and ensure library checking is done in only one place. --- .../CompositeEvaluationResultsPerMeasure.java | 13 ++++++++-- .../measure/common/MeasureProcessorUtils.java | 5 +--- .../cr/measure/r4/R4CollectDataService.java | 10 +++----- .../cr/measure/r4/R4MeasureProcessor.java | 24 +++++++++++-------- .../fhir/cr/measure/r4/R4MeasureService.java | 4 +--- .../cr/measure/r4/R4MultiMeasureService.java | 4 ++-- 6 files changed, 32 insertions(+), 28 deletions(-) diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java index 694fdf1642..fcb8ff5bdd 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java @@ -9,8 +9,17 @@ import org.hl7.fhir.instance.model.api.IIdType; import org.opencds.cqf.cql.engine.execution.EvaluationResult; -// LUKETODO: javadoc -// This is basically a Map of measure -> subject -> EvaluationResult +/** + * Container meant to hold the results or an early and cached CQL measure evaluation that holds + * two data points: + *
    + *
  1. To hold the results of a measure evaluation, grouped by measure ID, with each measure evaluation grouped by Subject ID
  2. + *
  3. To hold any errors that occurred during the evaluation, grouped by measure ID
  4. + *
+ * These data points are not mutually exclusive, meaning a measure may have both successful results and errors. + *

+ * This class also allows the caller to mutate a {@link MeasureDef} with the errors that occurred during the evaluation + */ public class CompositeEvaluationResultsPerMeasure { // The same measure may have successful results AND errors, so account for both private final Map> resultsPerMeasure; diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java index 7992c83b17..2cf4470537 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java @@ -341,19 +341,16 @@ public Object evaluateObservationCriteria( return result; } - // LUKETODO: update javadoc /** * method used to execute generate CQL results via Library $evaluate * @param subjectIds subjects to generate results for * @param zonedMeasurementPeriod offset defined measurement period for evaluation * @param context cql engine context - * @param libraryEngine library engine to use for evaluation of cql - * @param id library Version identifier used by library engine + * @param measureLibraryIdEngineDetailsList contains details of measureId, libraryId, and LibraryEngine * @return CQL results for Library defined in the Measure resource */ public CompositeEvaluationResultsPerMeasure getEvaluationResults( List subjectIds, - // LUKETODO: pass down the measure ID ZonedDateTime zonedMeasurementPeriod, CqlEngine context, List measureLibraryIdEngineDetailsList) { diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java index 052597d21b..4de2de68be 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java @@ -72,29 +72,25 @@ public Parameters collectData( var processor = new R4MeasureProcessor(this.repository, this.measureEvaluationOptions, this.measureProcessorUtils); - // LUKETODO: subjectList should be null but we get a list of subjects here: - // getSubjects List subjectList = getSubjects(subject, practitioner, subjectProvider); - var foldedMeasure = measureServiceUtils.foldMeasure(Eithers.forMiddle3(measureId), repository); + var foldedMeasure = R4MeasureServiceUtils.foldMeasure(Eithers.forMiddle3(measureId), repository); if (!subjectList.isEmpty()) { for (String patient : subjectList) { - // LUKETODO: in the old world, we call this for each subject in a loop. in the new world, we do all 4 - // at once var subjects = Collections.singletonList(patient); var mutableList = new ArrayList<>(subjects); var evaluationResults = processor.evaluateMeasureWithCqlEngine( - mutableList, List.of(foldedMeasure), periodStart, periodEnd, parameters, null); + mutableList, foldedMeasure, periodStart, periodEnd, parameters, null); // add resources per subject to Parameters addReports(processor, measureId, periodStart, periodEnd, subjects, parameters, evaluationResults); } } else { var evaluationResults = processor.evaluateMeasureWithCqlEngine( - subjectList, List.of(foldedMeasure), periodStart, periodEnd, parameters, null); + subjectList, foldedMeasure, periodStart, periodEnd, parameters, null); addReports(processor, measureId, periodStart, periodEnd, subjectList, parameters, evaluationResults); } return parameters; diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java index 648a8bb643..133d80b1a8 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java @@ -86,7 +86,6 @@ public MeasureReport evaluateMeasure( compositeEvaluationResultsPerMeasure); } - // LUKETODO: this is no doubt used by CQL-CLI /** * Evaluation method that consumes pre-calculated CQL results, Processes results, builds Measure Report * @param measure Measure resource @@ -159,11 +158,6 @@ protected MeasureReport evaluateMeasure( MeasureEvalType evalType, CompositeEvaluationResultsPerMeasure compositeEvaluationResultsPerMeasure) { - // LUKETODO: group the pre-measure engine setup tasks before the measure-specific tasks - - // LUKETODO: push this up - checkMeasureLibrary(measure); - MeasureEvalType evaluationType = measureProcessorUtils.getEvalType(evalType, reportType, subjectIds); // Measurement Period: operation parameter defined measurement period Interval measurementPeriodParams = buildMeasurementPeriod(periodStart, periodEnd); @@ -194,8 +188,6 @@ protected MeasureReport evaluateMeasure( final Map resultForThisMeasure = compositeEvaluationResultsPerMeasure.processMeasureForSuccessOrFailure(measureId, measureDef); - // LUKETODO: process MeasureDef error here! - measureProcessorUtils.processResults( resultForThisMeasure, measureDef, @@ -247,15 +239,27 @@ public Map evaluateMeasureWithCqlEngineOld( } */ - // LUKETODO: consider a single measure variant public CompositeEvaluationResultsPerMeasure evaluateMeasureWithCqlEngine( - List subjects, // LUKETODO: I have a doubt about whether this should be a single subject or a list + List subjects, + Measure measure, + @Nullable ZonedDateTime periodStart, + @Nullable ZonedDateTime periodEnd, + Parameters parameters, + @Nullable IBaseBundle additionalData) { + return evaluateMultiMeasuresWithCqlEngine( + subjects, List.of(measure), periodStart, periodEnd, parameters, additionalData); + } + + public CompositeEvaluationResultsPerMeasure evaluateMultiMeasuresWithCqlEngine( + List subjects, List measures, @Nullable ZonedDateTime periodStart, @Nullable ZonedDateTime periodEnd, Parameters parameters, @Nullable IBaseBundle additionalData) { + measures.forEach(this::checkMeasureLibrary); + // LUKETODO: make this a parameter var context = Engines.forRepository( this.repository, this.measureEvaluationOptions.getEvaluationSettings(), additionalData); diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java index c266968cd9..83f4a2c650 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java @@ -68,8 +68,6 @@ public MeasureReport evaluate( var foldedMeasure = R4MeasureServiceUtils.foldMeasure(measure, repo); - processor.checkMeasureLibrary(foldedMeasure); - MeasureReport measureReport; if (StringUtils.isNotBlank(practitioner)) { @@ -94,7 +92,7 @@ public MeasureReport evaluate( .toList(); var evaluationResults = processor.evaluateMeasureWithCqlEngine( - subjects, List.of(foldedMeasure), periodStart, periodEnd, parameters, additionalData); + subjects, foldedMeasure, periodStart, periodEnd, parameters, additionalData); measureReport = processor.evaluateMeasure( measure, diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java index 6d4d5a571a..5222d75e46 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java @@ -196,7 +196,7 @@ protected void populationMeasureReport( // This is basically a Map of measure -> subject -> EvaluationResult final CompositeEvaluationResultsPerMeasure compositeEvaluationResultsPerMeasure = - r4Processor.evaluateMeasureWithCqlEngine( + r4Processor.evaluateMultiMeasuresWithCqlEngine( subjects, measures, periodStart, periodEnd, parameters, additionalData); var totalMeasures = measures.size(); @@ -301,7 +301,7 @@ protected void subjectMeasureReport( // This is basically a Map of measure -> subject -> EvaluationResult final CompositeEvaluationResultsPerMeasure compositeEvaluationResultsPerMeasure = - r4Processor.evaluateMeasureWithCqlEngine( + r4Processor.evaluateMultiMeasuresWithCqlEngine( subjects, measures, periodStart, periodEnd, parameters, additionalData); for (Measure measure : measures) { From 32a33401b87bd9375e00c5450ba92be223959be6 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Thu, 3 Jul 2025 17:08:54 -0400 Subject: [PATCH 16/66] More refactoring and resolve TODOs. --- .../common/MeasureLibraryIdEngineDetails.java | 7 ++++ .../measure/common/MeasureProcessorUtils.java | 8 ++-- .../measure/dstu3/Dstu3MeasureProcessor.java | 2 +- .../cr/measure/r4/R4MeasureProcessor.java | 37 +++++-------------- .../r4/utils/R4MeasureServiceUtils.java | 3 +- 5 files changed, 25 insertions(+), 32 deletions(-) create mode 100644 cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureLibraryIdEngineDetails.java diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureLibraryIdEngineDetails.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureLibraryIdEngineDetails.java new file mode 100644 index 0000000000..450a234b3a --- /dev/null +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureLibraryIdEngineDetails.java @@ -0,0 +1,7 @@ +package org.opencds.cqf.fhir.cr.measure.common; + +import org.hl7.elm.r1.VersionedIdentifier; +import org.hl7.fhir.instance.model.api.IIdType; +import org.opencds.cqf.fhir.cql.LibraryEngine; + +public record MeasureLibraryIdEngineDetails(IIdType measureId, VersionedIdentifier libraryId, LibraryEngine engine) {} diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java index 2cf4470537..3d22bdd7d0 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java @@ -31,7 +31,6 @@ import org.opencds.cqf.fhir.cql.LibraryEngine; import org.opencds.cqf.fhir.cr.measure.constant.MeasureConstants; import org.opencds.cqf.fhir.cr.measure.helper.DateHelper; -import org.opencds.cqf.fhir.cr.measure.r4.R4MeasureProcessor.MeasureLibraryIdEngineDetails; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -397,8 +396,11 @@ public CompositeEvaluationResultsPerMeasure getEvaluationResults( return resultsBuilder.build(); } - // LUKETODO: get rid of this when all is said and done - public Map getEvaluationResultsOld( + /** + * This is the legacy CQL measure evaluation logic that at this point should be used only + * by DSTU3, but it should never be used by R4 or later. + */ + public Map getEvaluationResultsLegacy( List subjectIds, MeasureDef measureDef, ZonedDateTime zonedMeasurementPeriod, diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureProcessor.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureProcessor.java index 03f2d0be47..f361c350e4 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureProcessor.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureProcessor.java @@ -109,7 +109,7 @@ protected MeasureReport evaluateMeasure( ZonedDateTime zonedMeasurementPeriod = MeasureProcessorUtils.getZonedTimeZoneForEval(measurementPeriod); // populate results from Library $evaluate if (!subjects.isEmpty()) { - var results = measureProcessorUtils.getEvaluationResultsOld( + var results = measureProcessorUtils.getEvaluationResultsLegacy( subjectIds, measureDef, zonedMeasurementPeriod, context, libraryEngine, libraryVersionIdentifier); // Process Criteria Expression Results diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java index 133d80b1a8..b4af5d35c5 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java @@ -38,6 +38,7 @@ import org.opencds.cqf.fhir.cr.measure.common.GroupDef; import org.opencds.cqf.fhir.cr.measure.common.MeasureDef; import org.opencds.cqf.fhir.cr.measure.common.MeasureEvalType; +import org.opencds.cqf.fhir.cr.measure.common.MeasureLibraryIdEngineDetails; import org.opencds.cqf.fhir.cr.measure.common.MeasurePopulationType; import org.opencds.cqf.fhir.cr.measure.common.MeasureProcessorUtils; import org.opencds.cqf.fhir.cr.measure.common.MeasureReportType; @@ -208,36 +209,22 @@ protected MeasureReport evaluateMeasure( subjectIds); } - // LUKETODO: delete this once I no longer need to reference it - /* - public Map evaluateMeasureWithCqlEngineOld( + public CompositeEvaluationResultsPerMeasure evaluateMeasureIdWithCqlEngine( List subjects, - Measure measure, + IIdType measureId, @Nullable ZonedDateTime periodStart, @Nullable ZonedDateTime periodEnd, Parameters parameters, - MeasureDef measureDef, @Nullable IBaseBundle additionalData) { - var context = Engines.forRepository( - this.repository, this.measureEvaluationOptions.getEvaluationSettings(), additionalData); - - var measurementPeriodParams = buildMeasurementPeriod(periodStart, periodEnd); - var zonedMeasurementPeriod = MeasureProcessorUtils.getZonedTimeZoneForEval( - measureProcessorUtils.getDefaultMeasurementPeriod(measurementPeriodParams, context)); - - final VersionedIdentifier libraryVersionIdentifier = getLibraryVersionIdentifier(measure); - - var libraryEngine = getLibraryEngine(parameters, libraryVersionIdentifier, context); - - // set measurement Period from CQL if operation parameters are empty - measureProcessorUtils.setMeasurementPeriod(measurementPeriodParams, context); - - // populate results from Library $evaluate - return measureProcessorUtils.getEvaluationResultsOld( - subjects, measureDef, zonedMeasurementPeriod, context, libraryEngine, libraryVersionIdentifier); + return evaluateMultiMeasuresWithCqlEngine( + subjects, + List.of(R4MeasureServiceUtils.resolveById(measureId, repository)), + periodStart, + periodEnd, + parameters, + additionalData); } - */ public CompositeEvaluationResultsPerMeasure evaluateMeasureWithCqlEngine( List subjects, @@ -297,10 +284,6 @@ private MeasureLibraryIdEngineDetails buildLibraryIdEngineDetails( getLibraryEngine(parameters, libraryVersionIdentifier, context)); } - // LUKETODO: find a better place for this - public record MeasureLibraryIdEngineDetails( - IIdType measureId, VersionedIdentifier libraryId, LibraryEngine engine) {} - /** Temporary check for Measures that are being blocked from use by evaluateResults method * * @param measureDef defined measure definition object used to capture criteria expression results diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/utils/R4MeasureServiceUtils.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/utils/R4MeasureServiceUtils.java index 2d87a3fc48..1754d52f91 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/utils/R4MeasureServiceUtils.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/utils/R4MeasureServiceUtils.java @@ -35,6 +35,7 @@ import javax.annotation.Nonnull; import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.CanonicalType; import org.hl7.fhir.r4.model.CodeableConcept; @@ -358,7 +359,7 @@ private static Measure resolveByUrl(CanonicalType url, IRepository repository) { return (Measure) result.getEntryFirstRep().getResource(); } - public static Measure resolveById(IdType id, IRepository repository) { + public static Measure resolveById(IIdType id, IRepository repository) { return repository.read(Measure.class, id); } } From 957e72c4aed79f8f3f2a41c2f0920e57e9dd4f98 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Thu, 3 Jul 2025 17:18:44 -0400 Subject: [PATCH 17/66] Resolve sonar warnings. --- .../measure/common/MeasureProcessorUtils.java | 8 ++-- .../cr/measure/r4/R4MeasureProcessor.java | 5 --- .../cr/measure/r4/R4MultiMeasureService.java | 40 ------------------- 3 files changed, 5 insertions(+), 48 deletions(-) diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java index 3d22bdd7d0..6baae9e943 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java @@ -36,6 +36,8 @@ public class MeasureProcessorUtils { private static final Logger logger = LoggerFactory.getLogger(MeasureProcessorUtils.class); + private static final String EXCEPTION_FOR_SUBJECT_ID_MESSAGE_TEMPLATE = "Exception for subjectId: %s, Message: %s"; + /** * Method that processes CQL Results into Measure defined fields that reference associated CQL expressions * @param results criteria expression results @@ -67,7 +69,7 @@ public void processResults( } catch (Exception e) { // Catch Exceptions from evaluation per subject, but allow rest of subjects to be processed (if // applicable) - var error = "Exception for subjectId: %s, Message: %s".formatted(subjectId, e.getMessage()); + var error = EXCEPTION_FOR_SUBJECT_ID_MESSAGE_TEMPLATE.formatted(subjectId, e.getMessage()); // Capture error for MeasureReportBuilder measureDef.addError(error); logger.error(error, e); @@ -386,7 +388,7 @@ public CompositeEvaluationResultsPerMeasure getEvaluationResults( } catch (Exception e) { // Catch Exceptions from evaluation per subject, but allow rest of subjects to be processed (if // applicable) - var error = "Exception for subjectId: %s, Message: %s".formatted(subjectId, e.getMessage()); + var error = EXCEPTION_FOR_SUBJECT_ID_MESSAGE_TEMPLATE.formatted(subjectId, e.getMessage()); resultsBuilder.addError(measureLibraryIdEngine.measureId(), error); logger.error(error, e); } @@ -427,7 +429,7 @@ public Map getEvaluationResultsLegacy( } catch (Exception e) { // Catch Exceptions from evaluation per subject, but allow rest of subjects to be processed (if // applicable) - var error = "Exception for subjectId: %s, Message: %s".formatted(subjectId, e.getMessage()); + var error = EXCEPTION_FOR_SUBJECT_ID_MESSAGE_TEMPLATE.formatted(subjectId, e.getMessage()); // Capture error for MeasureReportBuilder measureDef.addError(error); logger.error(error, e); diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java index b4af5d35c5..e7399b05a1 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java @@ -170,10 +170,6 @@ protected MeasureReport evaluateMeasure( var context = Engines.forRepository( this.repository, this.measureEvaluationOptions.getEvaluationSettings(), additionalData); - var libraryVersionIdentifier = getLibraryVersionIdentifier(measure); - // library engine setup - var libraryEngine = getLibraryEngine(parameters, libraryVersionIdentifier, context); - measureProcessorUtils.setMeasurementPeriod( measurementPeriodParams, context, @@ -269,7 +265,6 @@ public CompositeEvaluationResultsPerMeasure evaluateMultiMeasuresWithCqlEngine( .toList()); // populate results from Library $evaluate - return measureProcessorUtils.getEvaluationResults( subjects, zonedMeasurementPeriod, context, measureLibraryIdEngineDetailsList); } diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java index 5222d75e46..b226548302 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java @@ -154,46 +154,6 @@ protected void populationMeasureReport( String productLine, String reporter) { - /* - * subject1, subject2, subject3 - * - * measureA, measureB - * - * subject 1 -> - * measureA -> subject1 -> result1 - * measureB -> subject1 -> result2 - * subject 2 -> - * measureA -> subject2 -> result3 - * measureB -> subject2 -> result4 - * subject 3 -> - * measureA -> subject3 -> result5 - * measureB -> subject3 -> result6 - * - * measureReport1 -> measureA - * measureReport2 -> measureB - */ - - /* - population/summary - - compositeResultsPerMeasure = cqlEvaluate(measures, subjects) - - foreach measure in measures: - processedResults = processResults(compositeResultsPerMeasure.get(measure.getId())) - measureReport = builder.buildMeasureReport(processedResults) - */ - - /* - subject/individual - - compositeResultsPerMeasure = cqlEvaluate(measures, subjects) - - foreach measure in measures: - foreach subject in subjects: - MeasureReport = processResults(compositeResultsPerMeasure.get(measure.getId())) - - */ - // This is basically a Map of measure -> subject -> EvaluationResult final CompositeEvaluationResultsPerMeasure compositeEvaluationResultsPerMeasure = r4Processor.evaluateMultiMeasuresWithCqlEngine( From 48baa7c9b39c52722ff5c19a38741f390820dc88 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Thu, 3 Jul 2025 17:24:34 -0400 Subject: [PATCH 18/66] Reverse a sonar change that caused errors. Fix other sonar issues. Tests pass now. --- .../common/CompositeEvaluationResultsPerMeasure.java | 6 ++---- .../opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java | 5 +++++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java index fcb8ff5bdd..85448771e5 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java @@ -43,13 +43,11 @@ private CompositeEvaluationResultsPerMeasure(Builder builder) { // measureDef will occasionally be prepended with the version, which means we need to parse it into an IIdType which // is too much work, so pass in the measureId directly public Map processMeasureForSuccessOrFailure(IIdType measureId, MeasureDef measureDef) { - var hasFoundErrors = errorsPerMeasure.entrySet().stream() + errorsPerMeasure.entrySet().stream() .filter(entry -> isMeasureDefFound(entry.getKey(), measureId)) .map(Entry::getValue) .flatMap(List::stream) - .peek(measureDef::addError) - .findAny() - .isPresent(); + .forEach(measureDef::addError); var resultForMeasure = resultsPerMeasure.entrySet().stream() .filter(entry -> isMeasureDefFound(entry.getKey(), measureId)) diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java index e7399b05a1..1e9d74e11d 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java @@ -170,6 +170,11 @@ protected MeasureReport evaluateMeasure( var context = Engines.forRepository( this.repository, this.measureEvaluationOptions.getEvaluationSettings(), additionalData); + // LUKETODO: why does this prevent errors? + var libraryVersionIdentifier = getLibraryVersionIdentifier(measure); + // library engine setup + var libraryEngine = getLibraryEngine(parameters, libraryVersionIdentifier, context); + measureProcessorUtils.setMeasurementPeriod( measurementPeriodParams, context, From 14778bb0a07e509a7ebdd0b9beaa8b2b62078086 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Thu, 3 Jul 2025 17:52:03 -0400 Subject: [PATCH 19/66] Initialize the CqlEngine context at the beginning, pass it to down to the CQL library evaluation, and then to the measure evaluation. --- .../cr/measure/r4/R4CollectDataService.java | 17 +++++-- .../cr/measure/r4/R4MeasureProcessor.java | 43 ++++++++++------ .../fhir/cr/measure/r4/R4MeasureService.java | 8 ++- .../cr/measure/r4/R4MultiMeasureService.java | 49 +++++-------------- 4 files changed, 60 insertions(+), 57 deletions(-) diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java index 4de2de68be..709e8d16c8 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java @@ -16,6 +16,8 @@ import org.hl7.fhir.r4.model.MeasureReport; import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.Resource; +import org.opencds.cqf.cql.engine.execution.CqlEngine; +import org.opencds.cqf.fhir.cql.Engines; import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions; import org.opencds.cqf.fhir.cr.measure.common.CompositeEvaluationResultsPerMeasure; import org.opencds.cqf.fhir.cr.measure.common.MeasureEvalType; @@ -74,6 +76,9 @@ public Parameters collectData( List subjectList = getSubjects(subject, practitioner, subjectProvider); + var context = + Engines.forRepository(this.repository, this.measureEvaluationOptions.getEvaluationSettings(), null); + var foldedMeasure = R4MeasureServiceUtils.foldMeasure(Eithers.forMiddle3(measureId), repository); if (!subjectList.isEmpty()) { @@ -83,15 +88,17 @@ public Parameters collectData( var mutableList = new ArrayList<>(subjects); var evaluationResults = processor.evaluateMeasureWithCqlEngine( - mutableList, foldedMeasure, periodStart, periodEnd, parameters, null); + mutableList, foldedMeasure, periodStart, periodEnd, parameters, context, null); // add resources per subject to Parameters - addReports(processor, measureId, periodStart, periodEnd, subjects, parameters, evaluationResults); + addReports( + processor, measureId, periodStart, periodEnd, subjects, parameters, context, evaluationResults); } } else { var evaluationResults = processor.evaluateMeasureWithCqlEngine( - subjectList, foldedMeasure, periodStart, periodEnd, parameters, null); - addReports(processor, measureId, periodStart, periodEnd, subjectList, parameters, evaluationResults); + subjectList, foldedMeasure, periodStart, periodEnd, parameters, context, null); + addReports( + processor, measureId, periodStart, periodEnd, subjectList, parameters, context, evaluationResults); } return parameters; } @@ -103,6 +110,7 @@ private void addReports( @Nullable ZonedDateTime periodEnd, List subjects, Parameters parameters, + CqlEngine context, CompositeEvaluationResultsPerMeasure evaluateMeasureResults) { MeasureReport report = processor.evaluateMeasure( @@ -114,6 +122,7 @@ private void addReports( null, null, null, + context, evaluateMeasureResults); report.setType(MeasureReport.MeasureReportType.DATACOLLECTION); diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java index 1e9d74e11d..4706d6d9e1 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java @@ -30,7 +30,6 @@ import org.opencds.cqf.cql.engine.execution.EvaluationResult; import org.opencds.cqf.cql.engine.fhir.model.R4FhirModelResolver; import org.opencds.cqf.cql.engine.runtime.Interval; -import org.opencds.cqf.fhir.cql.Engines; import org.opencds.cqf.fhir.cql.LibraryEngine; import org.opencds.cqf.fhir.cql.VersionedIdentifiers; import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions; @@ -74,6 +73,7 @@ public MeasureReport evaluateMeasure( IBaseBundle additionalData, Parameters parameters, MeasureEvalType evalType, + CqlEngine context, CompositeEvaluationResultsPerMeasure compositeEvaluationResultsPerMeasure) { return this.evaluateMeasure( R4MeasureServiceUtils.foldMeasure(measure, this.repository), @@ -84,6 +84,7 @@ public MeasureReport evaluateMeasure( additionalData, parameters, evalType, + context, compositeEvaluationResultsPerMeasure); } @@ -157,6 +158,7 @@ protected MeasureReport evaluateMeasure( IBaseBundle additionalData, Parameters parameters, MeasureEvalType evalType, + CqlEngine context, CompositeEvaluationResultsPerMeasure compositeEvaluationResultsPerMeasure) { MeasureEvalType evaluationType = measureProcessorUtils.getEvalType(evalType, reportType, subjectIds); @@ -166,14 +168,26 @@ protected MeasureReport evaluateMeasure( // setup MeasureDef var measureDef = new R4MeasureDefBuilder().build(measure); - // CQL Engine context - var context = Engines.forRepository( - this.repository, this.measureEvaluationOptions.getEvaluationSettings(), additionalData); - - // LUKETODO: why does this prevent errors? - var libraryVersionIdentifier = getLibraryVersionIdentifier(measure); - // library engine setup - var libraryEngine = getLibraryEngine(parameters, libraryVersionIdentifier, context); + // // CQL Engine context + // var context = Engines.forRepository( + // this.repository, this.measureEvaluationOptions.getEvaluationSettings(), additionalData); + + // var libraryEngine = getLibraryEngine(parameters, getLibraryVersionIdentifier(measure), context); + + // java.lang.NullPointerException: Cannot invoke "org.hl7.elm.r1.Library.getParameters()" because "lib" + // is null + // + // at + // org.opencds.cqf.fhir.cr.measure.common.MeasureProcessorUtils.getMeasurementPeriodParameterDef(MeasureProcessorUtils.java:135) + // at + // org.opencds.cqf.fhir.cr.measure.common.MeasureProcessorUtils.setMeasurementPeriod(MeasureProcessorUtils.java:158) + // at org.opencds.cqf.fhir.cr.measure.r4.R4MeasureProcessor.evaluateMeasure(R4MeasureProcessor.java:178) + // at org.opencds.cqf.fhir.cr.measure.r4.R4MeasureProcessor.evaluateMeasure(R4MeasureProcessor.java:78) + // at org.opencds.cqf.fhir.cr.measure.r4.R4MeasureService.evaluate(R4MeasureService.java:97) + // at org.opencds.cqf.fhir.cr.measure.r4.Measure$When.lambda$evaluate$0(Measure.java:255) + // at org.opencds.cqf.fhir.cr.measure.r4.Measure$When.then(Measure.java:278) + // at + // org.opencds.cqf.fhir.cr.measure.r4.MeasureConditionCategoryPOCTest.measure_eval_non_retrieve_resource(MeasureConditionCategoryPOCTest.java:25) measureProcessorUtils.setMeasurementPeriod( measurementPeriodParams, @@ -216,6 +230,7 @@ public CompositeEvaluationResultsPerMeasure evaluateMeasureIdWithCqlEngine( @Nullable ZonedDateTime periodStart, @Nullable ZonedDateTime periodEnd, Parameters parameters, + CqlEngine context, @Nullable IBaseBundle additionalData) { return evaluateMultiMeasuresWithCqlEngine( @@ -224,6 +239,7 @@ public CompositeEvaluationResultsPerMeasure evaluateMeasureIdWithCqlEngine( periodStart, periodEnd, parameters, + context, additionalData); } @@ -233,25 +249,24 @@ public CompositeEvaluationResultsPerMeasure evaluateMeasureWithCqlEngine( @Nullable ZonedDateTime periodStart, @Nullable ZonedDateTime periodEnd, Parameters parameters, + CqlEngine context, @Nullable IBaseBundle additionalData) { return evaluateMultiMeasuresWithCqlEngine( - subjects, List.of(measure), periodStart, periodEnd, parameters, additionalData); + subjects, List.of(measure), periodStart, periodEnd, parameters, context, additionalData); } + // LUKETODO: cleanup any parameters that are not needed public CompositeEvaluationResultsPerMeasure evaluateMultiMeasuresWithCqlEngine( List subjects, List measures, @Nullable ZonedDateTime periodStart, @Nullable ZonedDateTime periodEnd, Parameters parameters, + CqlEngine context, @Nullable IBaseBundle additionalData) { measures.forEach(this::checkMeasureLibrary); - // LUKETODO: make this a parameter - var context = Engines.forRepository( - this.repository, this.measureEvaluationOptions.getEvaluationSettings(), additionalData); - var measurementPeriodParams = buildMeasurementPeriod(periodStart, periodEnd); var zonedMeasurementPeriod = MeasureProcessorUtils.getZonedTimeZoneForEval( measureProcessorUtils.getDefaultMeasurementPeriod(measurementPeriodParams, context)); diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java index 83f4a2c650..c301c42e41 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java @@ -13,6 +13,7 @@ import org.hl7.fhir.r4.model.Measure; import org.hl7.fhir.r4.model.MeasureReport; import org.hl7.fhir.r4.model.Parameters; +import org.opencds.cqf.fhir.cql.Engines; import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions; import org.opencds.cqf.fhir.cr.measure.common.MeasurePeriodValidator; import org.opencds.cqf.fhir.cr.measure.common.MeasureProcessorUtils; @@ -91,8 +92,12 @@ public MeasureReport evaluate( actualRepo, Optional.ofNullable(subjectId).map(List::of).orElse(List.of())) .toList(); + // LUKETODO: which repository should we use for the context? + var context = Engines.forRepository( + this.repository, this.measureEvaluationOptions.getEvaluationSettings(), additionalData); + var evaluationResults = processor.evaluateMeasureWithCqlEngine( - subjects, foldedMeasure, periodStart, periodEnd, parameters, additionalData); + subjects, foldedMeasure, periodStart, periodEnd, parameters, context, additionalData); measureReport = processor.evaluateMeasure( measure, @@ -103,6 +108,7 @@ public MeasureReport evaluate( additionalData, parameters, evalType, + context, evaluationResults); // add ProductLine after report is generated diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java index b226548302..271e92a425 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java @@ -19,6 +19,7 @@ import org.hl7.fhir.r4.model.MeasureReport; import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.Resource; +import org.opencds.cqf.fhir.cql.Engines; import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions; import org.opencds.cqf.fhir.cr.measure.common.CompositeEvaluationResultsPerMeasure; import org.opencds.cqf.fhir.cr.measure.common.MeasureEvalType; @@ -154,10 +155,13 @@ protected void populationMeasureReport( String productLine, String reporter) { + var context = Engines.forRepository( + this.repository, this.measureEvaluationOptions.getEvaluationSettings(), additionalData); + // This is basically a Map of measure -> subject -> EvaluationResult final CompositeEvaluationResultsPerMeasure compositeEvaluationResultsPerMeasure = r4Processor.evaluateMultiMeasuresWithCqlEngine( - subjects, measures, periodStart, periodEnd, parameters, additionalData); + subjects, measures, periodStart, periodEnd, parameters, context, additionalData); var totalMeasures = measures.size(); for (Measure measure : measures) { @@ -172,6 +176,7 @@ protected void populationMeasureReport( additionalData, parameters, evalType, + context, compositeEvaluationResultsPerMeasure); // add ProductLine after report is generated @@ -200,42 +205,6 @@ protected void populationMeasureReport( totalMeasures--); } } - - // // one aggregated MeasureReport per Measure - // var totalMeasures = measures.size(); - // for (Measure measure : measures) { - // MeasureReport measureReport; - // // evaluate each measure - // measureReport = r4Processor.evaluateMeasure( - // measure, periodStart, periodEnd, reportType, subjects, additionalData, parameters, - // evalType, evaluateMeasureResultsByMeasureId); - // - // // add ProductLine after report is generated - // measureReport = r4MeasureServiceUtils.addProductLineExtension(measureReport, productLine); - // - // // add subject reference for non-individual reportTypes - // measureReport = r4MeasureServiceUtils.addSubjectReference(measureReport, null, subjectParam); - // - // // add reporter if available - // if (reporter != null && !reporter.isEmpty()) { - // measureReport.setReporter( - // r4MeasureServiceUtils.getReporter(reporter).orElse(null)); - // } - // // add id to measureReport - // initializeReport(measureReport); - // - // // add report to bundle - // bundle.addEntry(getBundleEntry(serverBase, measureReport)); - // - // // progress feedback - // var measureUrl = measureReport.getMeasure(); - // if (!measureUrl.isEmpty()) { - // log.debug( - // "Completed evaluation for Measure: {}, Measures remaining to evaluate: {}", - // measureUrl, - // totalMeasures--); - // } - // } } protected void subjectMeasureReport( @@ -259,10 +228,13 @@ protected void subjectMeasureReport( subjects.size(), measures.size()); + var context = Engines.forRepository( + this.repository, this.measureEvaluationOptions.getEvaluationSettings(), additionalData); + // This is basically a Map of measure -> subject -> EvaluationResult final CompositeEvaluationResultsPerMeasure compositeEvaluationResultsPerMeasure = r4Processor.evaluateMultiMeasuresWithCqlEngine( - subjects, measures, periodStart, periodEnd, parameters, additionalData); + subjects, measures, periodStart, periodEnd, parameters, context, additionalData); for (Measure measure : measures) { for (String subject : subjects) { @@ -277,6 +249,7 @@ protected void subjectMeasureReport( additionalData, parameters, evalType, + context, compositeEvaluationResultsPerMeasure); // add ProductLine after report is generated From 4b295c2a389a0d1a5ace294114b3600683e6f957 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Thu, 3 Jul 2025 18:08:35 -0400 Subject: [PATCH 20/66] More cleanup of dead code, commented out code, and dead parameters. --- .../cr/measure/r4/R4CollectDataService.java | 6 +-- .../cr/measure/r4/R4MeasureProcessor.java | 46 +++---------------- .../fhir/cr/measure/r4/R4MeasureService.java | 28 +++++------ .../cr/measure/r4/R4MultiMeasureService.java | 8 +--- 4 files changed, 21 insertions(+), 67 deletions(-) diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java index 709e8d16c8..2924184830 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java @@ -88,7 +88,7 @@ public Parameters collectData( var mutableList = new ArrayList<>(subjects); var evaluationResults = processor.evaluateMeasureWithCqlEngine( - mutableList, foldedMeasure, periodStart, periodEnd, parameters, context, null); + mutableList, foldedMeasure, periodStart, periodEnd, parameters, context); // add resources per subject to Parameters addReports( @@ -96,7 +96,7 @@ public Parameters collectData( } } else { var evaluationResults = processor.evaluateMeasureWithCqlEngine( - subjectList, foldedMeasure, periodStart, periodEnd, parameters, context, null); + subjectList, foldedMeasure, periodStart, periodEnd, parameters, context); addReports( processor, measureId, periodStart, periodEnd, subjectList, parameters, context, evaluationResults); } @@ -120,8 +120,6 @@ private void addReports( MeasureEvalType.SUBJECT.toCode(), subjects, null, - null, - null, context, evaluateMeasureResults); diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java index 4706d6d9e1..7b2e8fc73c 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java @@ -16,7 +16,6 @@ import org.cqframework.cql.cql2elm.CqlIncludeException; import org.cqframework.cql.cql2elm.model.CompiledLibrary; import org.hl7.elm.r1.VersionedIdentifier; -import org.hl7.fhir.instance.model.api.IBaseBundle; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.r4.model.Bundle; @@ -70,8 +69,6 @@ public MeasureReport evaluateMeasure( @Nullable ZonedDateTime periodEnd, String reportType, List subjectIds, - IBaseBundle additionalData, - Parameters parameters, MeasureEvalType evalType, CqlEngine context, CompositeEvaluationResultsPerMeasure compositeEvaluationResultsPerMeasure) { @@ -81,8 +78,6 @@ public MeasureReport evaluateMeasure( periodEnd, reportType, subjectIds, - additionalData, - parameters, evalType, context, compositeEvaluationResultsPerMeasure); @@ -144,8 +139,6 @@ public MeasureReport evaluateMeasureResults( * @param periodEnd end date of Measurement Period * @param reportType type of report that defines MeasureReport Type * @param subjectIds the subjectIds to process - * @param additionalData external bundle to process with results - * @param parameters cql parameters specified in parameters resource * @param evalType the type of evaluation to process, this is an output of reportType param * @return Measure Report resource */ @@ -155,8 +148,6 @@ protected MeasureReport evaluateMeasure( @Nullable ZonedDateTime periodEnd, String reportType, List subjectIds, - IBaseBundle additionalData, - Parameters parameters, MeasureEvalType evalType, CqlEngine context, CompositeEvaluationResultsPerMeasure compositeEvaluationResultsPerMeasure) { @@ -168,27 +159,6 @@ protected MeasureReport evaluateMeasure( // setup MeasureDef var measureDef = new R4MeasureDefBuilder().build(measure); - // // CQL Engine context - // var context = Engines.forRepository( - // this.repository, this.measureEvaluationOptions.getEvaluationSettings(), additionalData); - - // var libraryEngine = getLibraryEngine(parameters, getLibraryVersionIdentifier(measure), context); - - // java.lang.NullPointerException: Cannot invoke "org.hl7.elm.r1.Library.getParameters()" because "lib" - // is null - // - // at - // org.opencds.cqf.fhir.cr.measure.common.MeasureProcessorUtils.getMeasurementPeriodParameterDef(MeasureProcessorUtils.java:135) - // at - // org.opencds.cqf.fhir.cr.measure.common.MeasureProcessorUtils.setMeasurementPeriod(MeasureProcessorUtils.java:158) - // at org.opencds.cqf.fhir.cr.measure.r4.R4MeasureProcessor.evaluateMeasure(R4MeasureProcessor.java:178) - // at org.opencds.cqf.fhir.cr.measure.r4.R4MeasureProcessor.evaluateMeasure(R4MeasureProcessor.java:78) - // at org.opencds.cqf.fhir.cr.measure.r4.R4MeasureService.evaluate(R4MeasureService.java:97) - // at org.opencds.cqf.fhir.cr.measure.r4.Measure$When.lambda$evaluate$0(Measure.java:255) - // at org.opencds.cqf.fhir.cr.measure.r4.Measure$When.then(Measure.java:278) - // at - // org.opencds.cqf.fhir.cr.measure.r4.MeasureConditionCategoryPOCTest.measure_eval_non_retrieve_resource(MeasureConditionCategoryPOCTest.java:25) - measureProcessorUtils.setMeasurementPeriod( measurementPeriodParams, context, @@ -230,8 +200,7 @@ public CompositeEvaluationResultsPerMeasure evaluateMeasureIdWithCqlEngine( @Nullable ZonedDateTime periodStart, @Nullable ZonedDateTime periodEnd, Parameters parameters, - CqlEngine context, - @Nullable IBaseBundle additionalData) { + CqlEngine context) { return evaluateMultiMeasuresWithCqlEngine( subjects, @@ -239,8 +208,7 @@ public CompositeEvaluationResultsPerMeasure evaluateMeasureIdWithCqlEngine( periodStart, periodEnd, parameters, - context, - additionalData); + context); } public CompositeEvaluationResultsPerMeasure evaluateMeasureWithCqlEngine( @@ -249,21 +217,19 @@ public CompositeEvaluationResultsPerMeasure evaluateMeasureWithCqlEngine( @Nullable ZonedDateTime periodStart, @Nullable ZonedDateTime periodEnd, Parameters parameters, - CqlEngine context, - @Nullable IBaseBundle additionalData) { + CqlEngine context) { + return evaluateMultiMeasuresWithCqlEngine( - subjects, List.of(measure), periodStart, periodEnd, parameters, context, additionalData); + subjects, List.of(measure), periodStart, periodEnd, parameters, context); } - // LUKETODO: cleanup any parameters that are not needed public CompositeEvaluationResultsPerMeasure evaluateMultiMeasuresWithCqlEngine( List subjects, List measures, @Nullable ZonedDateTime periodStart, @Nullable ZonedDateTime periodEnd, Parameters parameters, - CqlEngine context, - @Nullable IBaseBundle additionalData) { + CqlEngine context) { measures.forEach(this::checkMeasureLibrary); diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java index c301c42e41..a4e4b130bf 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java @@ -61,13 +61,15 @@ public MeasureReport evaluate( measurePeriodValidator.validatePeriodStartAndEnd(periodStart, periodEnd); - var repo = Repositories.proxy(repository, true, dataEndpoint, contentEndpoint, terminologyEndpoint); - var processor = new R4MeasureProcessor(repo, this.measureEvaluationOptions, measureProcessorUtils); + var proxyRepoForMeasureProcessor = + Repositories.proxy(repository, true, dataEndpoint, contentEndpoint, terminologyEndpoint); + var processor = new R4MeasureProcessor( + proxyRepoForMeasureProcessor, this.measureEvaluationOptions, measureProcessorUtils); R4MeasureServiceUtils r4MeasureServiceUtils = new R4MeasureServiceUtils(repository); r4MeasureServiceUtils.ensureSupplementalDataElementSearchParameter(); - var foldedMeasure = R4MeasureServiceUtils.foldMeasure(measure, repo); + var foldedMeasure = R4MeasureServiceUtils.foldMeasure(measure, proxyRepoForMeasureProcessor); MeasureReport measureReport; @@ -78,7 +80,7 @@ public MeasureReport evaluate( subjectId = practitioner; } - var actualRepo = repo; + var actualRepo = proxyRepoForMeasureProcessor; if (additionalData != null) { actualRepo = new FederatedRepository( this.repository, new InMemoryFhirRepository(this.repository.fhirContext(), additionalData)); @@ -92,24 +94,16 @@ public MeasureReport evaluate( actualRepo, Optional.ofNullable(subjectId).map(List::of).orElse(List.of())) .toList(); - // LUKETODO: which repository should we use for the context? + // Replicate the old logic of using the repository used to initialize the measure processor + // as the repository for the CQL engine context. var context = Engines.forRepository( - this.repository, this.measureEvaluationOptions.getEvaluationSettings(), additionalData); + proxyRepoForMeasureProcessor, this.measureEvaluationOptions.getEvaluationSettings(), additionalData); var evaluationResults = processor.evaluateMeasureWithCqlEngine( - subjects, foldedMeasure, periodStart, periodEnd, parameters, context, additionalData); + subjects, foldedMeasure, periodStart, periodEnd, parameters, context); measureReport = processor.evaluateMeasure( - measure, - periodStart, - periodEnd, - reportType, - subjects, - additionalData, - parameters, - evalType, - context, - evaluationResults); + measure, periodStart, periodEnd, reportType, subjects, evalType, context, evaluationResults); // add ProductLine after report is generated measureReport = r4MeasureServiceUtils.addProductLineExtension(measureReport, productLine); diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java index 271e92a425..38368f7ead 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java @@ -161,7 +161,7 @@ protected void populationMeasureReport( // This is basically a Map of measure -> subject -> EvaluationResult final CompositeEvaluationResultsPerMeasure compositeEvaluationResultsPerMeasure = r4Processor.evaluateMultiMeasuresWithCqlEngine( - subjects, measures, periodStart, periodEnd, parameters, context, additionalData); + subjects, measures, periodStart, periodEnd, parameters, context); var totalMeasures = measures.size(); for (Measure measure : measures) { @@ -173,8 +173,6 @@ protected void populationMeasureReport( periodEnd, reportType, subjects, - additionalData, - parameters, evalType, context, compositeEvaluationResultsPerMeasure); @@ -234,7 +232,7 @@ protected void subjectMeasureReport( // This is basically a Map of measure -> subject -> EvaluationResult final CompositeEvaluationResultsPerMeasure compositeEvaluationResultsPerMeasure = r4Processor.evaluateMultiMeasuresWithCqlEngine( - subjects, measures, periodStart, periodEnd, parameters, context, additionalData); + subjects, measures, periodStart, periodEnd, parameters, context); for (Measure measure : measures) { for (String subject : subjects) { @@ -246,8 +244,6 @@ protected void subjectMeasureReport( periodEnd, reportType, Collections.singletonList(subject), - additionalData, - parameters, evalType, context, compositeEvaluationResultsPerMeasure); From f3b9075654d22c5898a0158cb3d857fc50c5e361 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Thu, 3 Jul 2025 19:06:25 -0400 Subject: [PATCH 21/66] Fix compile error. Fix more sonar. Spotless. Get rid of unused constructor param. --- .../cqf/fhir/cr/cli/command/CqlCommand.java | 10 +---- .../fhir/cr/hapi/config/r4/CrR4Config.java | 3 +- .../cr/measure/r4/R4CollectDataService.java | 8 +--- .../cr/measure/r4/R4MultiMeasureService.java | 43 ------------------- .../cqf/fhir/cr/measure/r4/CollectData.java | 2 +- 5 files changed, 6 insertions(+), 60 deletions(-) diff --git a/cqf-fhir-cr-cli/src/main/java/org/opencds/cqf/fhir/cr/cli/command/CqlCommand.java b/cqf-fhir-cr-cli/src/main/java/org/opencds/cqf/fhir/cr/cli/command/CqlCommand.java index 723e5e5cca..f7729eb7da 100644 --- a/cqf-fhir-cr-cli/src/main/java/org/opencds/cqf/fhir/cr/cli/command/CqlCommand.java +++ b/cqf-fhir-cr-cli/src/main/java/org/opencds/cqf/fhir/cr/cli/command/CqlCommand.java @@ -55,10 +55,8 @@ import org.opencds.cqf.fhir.cql.engine.terminology.TerminologySettings.VALUESET_PRE_EXPANSION_MODE; import org.opencds.cqf.fhir.cr.cli.command.CqlCommand.EvaluationParameter.ModelParameter; import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions; -import org.opencds.cqf.fhir.cr.measure.SubjectProviderOptions; +import org.opencds.cqf.fhir.cr.measure.common.MeasureProcessorUtils; import org.opencds.cqf.fhir.cr.measure.r4.R4MeasureProcessor; -import org.opencds.cqf.fhir.cr.measure.r4.R4RepositorySubjectProvider; -import org.opencds.cqf.fhir.cr.measure.r4.utils.R4MeasureServiceUtils; import org.opencds.cqf.fhir.utility.repository.ProxyRepository; import org.opencds.cqf.fhir.utility.repository.ig.IgRepository; import org.slf4j.LoggerFactory; @@ -341,11 +339,7 @@ private R4MeasureProcessor getR4MeasureProcessor(EvaluationSettings evaluationSe evaluationOptions.setApplyScoringSetMembership(false); evaluationOptions.setEvaluationSettings(evaluationSettings); - return new R4MeasureProcessor( - repository, - evaluationOptions, - new R4RepositorySubjectProvider(new SubjectProviderOptions()), - new R4MeasureServiceUtils(repository)); + return new R4MeasureProcessor(repository, evaluationOptions, new MeasureProcessorUtils()); } @Nonnull diff --git a/cqf-fhir-cr-hapi/src/main/java/org/opencds/cqf/fhir/cr/hapi/config/r4/CrR4Config.java b/cqf-fhir-cr-hapi/src/main/java/org/opencds/cqf/fhir/cr/hapi/config/r4/CrR4Config.java index 5f230aa24b..da8cdcc60d 100644 --- a/cqf-fhir-cr-hapi/src/main/java/org/opencds/cqf/fhir/cr/hapi/config/r4/CrR4Config.java +++ b/cqf-fhir-cr-hapi/src/main/java/org/opencds/cqf/fhir/cr/hapi/config/r4/CrR4Config.java @@ -90,8 +90,7 @@ ICollectDataServiceFactory collectDataServiceFactory( IRepositoryFactory repositoryFactory, MeasureEvaluationOptions measureEvaluationOptions, R4MeasureServiceUtilsFactory r4MeasureServiceUtilsFactory) { - return rd -> new R4CollectDataService( - repositoryFactory.create(rd), measureEvaluationOptions, r4MeasureServiceUtilsFactory.create(rd)); + return rd -> new R4CollectDataService(repositoryFactory.create(rd), measureEvaluationOptions); } @Bean diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java index 2924184830..22f87c1d46 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java @@ -26,21 +26,17 @@ import org.opencds.cqf.fhir.utility.Ids; import org.opencds.cqf.fhir.utility.monad.Eithers; +@SuppressWarnings("squid:S107") public class R4CollectDataService { private final IRepository repository; private final MeasureEvaluationOptions measureEvaluationOptions; private final R4RepositorySubjectProvider subjectProvider; - private final R4MeasureServiceUtils measureServiceUtils; private final MeasureProcessorUtils measureProcessorUtils = new MeasureProcessorUtils(); - public R4CollectDataService( - IRepository repository, - MeasureEvaluationOptions measureEvaluationOptions, - R4MeasureServiceUtils measureServiceUtils) { + public R4CollectDataService(IRepository repository, MeasureEvaluationOptions measureEvaluationOptions) { this.repository = repository; this.measureEvaluationOptions = measureEvaluationOptions; this.subjectProvider = new R4RepositorySubjectProvider(measureEvaluationOptions.getSubjectProviderOptions()); - this.measureServiceUtils = measureServiceUtils; } /** diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java index 38368f7ead..ab6a55b414 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java @@ -275,49 +275,6 @@ protected void subjectMeasureReport( } } } - - // for (String subject : subjects) { - // for (Measure measure : measures) { - // MeasureReport measureReport; - // // evaluate each measure - // measureReport = r4Processor.evaluateMeasure( - // measure, - // periodStart, - // periodEnd, - // reportType, - // Collections.singletonList(subject), - // additionalData, - // parameters, - // evalType, - // evaluateMeasureResultsByMeasureId); - // - // // add ProductLine after report is generated - // measureReport = r4MeasureServiceUtils.addProductLineExtension(measureReport, productLine); - // - // // add reporter if available - // if (reporter != null && !reporter.isEmpty()) { - // measureReport.setReporter( - // r4MeasureServiceUtils.getReporter(reporter).orElse(null)); - // } - // // add id to measureReport - // initializeReport(measureReport); - // - // // add report to bundle - // bundle.addEntry(getBundleEntry(serverBase, measureReport)); - // - // // progress feedback - // var measureUrl = measureReport.getMeasure(); - // if (!measureUrl.isEmpty()) { - // log.debug("MeasureReports remaining to evaluate {}", totalReports--); - // } - // if (measure.hasUrl()) { - // log.info( - // "Completed evaluation for Measure: {}, Measures remaining to evaluate: {}", - // measure.getUrl(), - // totalMeasures--); - // } - // } - // } } protected List getSubjects(R4RepositorySubjectProvider subjectProvider, String subjectId) { diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/CollectData.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/CollectData.java index fc0fcb7503..960c2ac810 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/CollectData.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/CollectData.java @@ -97,7 +97,7 @@ public Given repositoryFor(String repositoryPath) { } private R4CollectDataService buildR4CollectDataService() { - return new R4CollectDataService(repository, evaluationOptions, measureServiceUtils); + return new R4CollectDataService(repository, evaluationOptions); } public When when() { From d937b34e3e26f997c8a48156fa65d536399b2570 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Fri, 4 Jul 2025 09:17:51 -0400 Subject: [PATCH 22/66] Get rid of legacy CQL evaluation and have DSTU3 use the new code. --- .../measure/common/MeasureProcessorUtils.java | 54 ++++--------------- .../measure/dstu3/Dstu3MeasureProcessor.java | 22 ++++++-- 2 files changed, 29 insertions(+), 47 deletions(-) diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java index 6baae9e943..12f5414127 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java @@ -10,7 +10,6 @@ import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.time.ZonedDateTime; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -20,7 +19,6 @@ import org.hl7.elm.r1.IntervalTypeSpecifier; import org.hl7.elm.r1.NamedTypeSpecifier; import org.hl7.elm.r1.ParameterDef; -import org.hl7.elm.r1.VersionedIdentifier; import org.opencds.cqf.cql.engine.execution.CqlEngine; import org.opencds.cqf.cql.engine.execution.EvaluationResult; import org.opencds.cqf.cql.engine.execution.Libraries; @@ -28,7 +26,6 @@ import org.opencds.cqf.cql.engine.runtime.Date; import org.opencds.cqf.cql.engine.runtime.DateTime; import org.opencds.cqf.cql.engine.runtime.Interval; -import org.opencds.cqf.fhir.cql.LibraryEngine; import org.opencds.cqf.fhir.cr.measure.constant.MeasureConstants; import org.opencds.cqf.fhir.cr.measure.helper.DateHelper; import org.slf4j.Logger; @@ -342,6 +339,16 @@ public Object evaluateObservationCriteria( return result; } + public CompositeEvaluationResultsPerMeasure getEvaluationResults( + List subjectIds, + ZonedDateTime zonedMeasurementPeriod, + CqlEngine context, + MeasureLibraryIdEngineDetails measureLibraryIdEngineDetails) { + + return getEvaluationResults( + subjectIds, zonedMeasurementPeriod, context, List.of(measureLibraryIdEngineDetails)); + } + /** * method used to execute generate CQL results via Library $evaluate * @param subjectIds subjects to generate results for @@ -398,47 +405,6 @@ public CompositeEvaluationResultsPerMeasure getEvaluationResults( return resultsBuilder.build(); } - /** - * This is the legacy CQL measure evaluation logic that at this point should be used only - * by DSTU3, but it should never be used by R4 or later. - */ - public Map getEvaluationResultsLegacy( - List subjectIds, - MeasureDef measureDef, - ZonedDateTime zonedMeasurementPeriod, - CqlEngine context, - LibraryEngine libraryEngine, - VersionedIdentifier id) { - - Map result = new HashMap<>(); - - // Library $evaluate each subject - for (String subjectId : subjectIds) { - if (subjectId == null) { - throw new NullPointerException("SubjectId is required in order to calculate."); - } - Pair subjectInfo = this.getSubjectTypeAndId(subjectId); - String subjectTypePart = subjectInfo.getLeft(); - String subjectIdPart = subjectInfo.getRight(); - context.getState().setContextValue(subjectTypePart, subjectIdPart); - try { - result.put( - subjectId, - libraryEngine.getEvaluationResult( - id, subjectId, null, null, null, null, null, zonedMeasurementPeriod, context)); - } catch (Exception e) { - // Catch Exceptions from evaluation per subject, but allow rest of subjects to be processed (if - // applicable) - var error = EXCEPTION_FOR_SUBJECT_ID_MESSAGE_TEMPLATE.formatted(subjectId, e.getMessage()); - // Capture error for MeasureReportBuilder - measureDef.addError(error); - logger.error(error, e); - } - } - - return result; - } - public Pair getSubjectTypeAndId(String subjectId) { if (subjectId.contains("/")) { String[] subjectIdParts = subjectId.split("/"); diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureProcessor.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureProcessor.java index f361c350e4..00ea33c6f3 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureProcessor.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureProcessor.java @@ -27,6 +27,7 @@ import org.opencds.cqf.fhir.cql.LibraryEngine; import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions; import org.opencds.cqf.fhir.cr.measure.common.MeasureEvalType; +import org.opencds.cqf.fhir.cr.measure.common.MeasureLibraryIdEngineDetails; import org.opencds.cqf.fhir.cr.measure.common.MeasureProcessorUtils; import org.opencds.cqf.fhir.cr.measure.common.MeasureReportType; import org.opencds.cqf.fhir.cr.measure.common.SubjectProvider; @@ -109,12 +110,15 @@ protected MeasureReport evaluateMeasure( ZonedDateTime zonedMeasurementPeriod = MeasureProcessorUtils.getZonedTimeZoneForEval(measurementPeriod); // populate results from Library $evaluate if (!subjects.isEmpty()) { - var results = measureProcessorUtils.getEvaluationResultsLegacy( - subjectIds, measureDef, zonedMeasurementPeriod, context, libraryEngine, libraryVersionIdentifier); + var results = measureProcessorUtils.getEvaluationResults( + subjectIds, + zonedMeasurementPeriod, + context, + buildLibraryIdEngineDetails(measure, parameters, context)); // Process Criteria Expression Results measureProcessorUtils.processResults( - results, + results.processMeasureForSuccessOrFailure(measure.getIdElement(), measureDef), measureDef, evalType, measureEvaluationOptions.getApplyScoringSetMembership(), @@ -128,6 +132,18 @@ protected MeasureReport evaluateMeasure( .build(measure, measureDef, evalTypeToReportType(evalType), measurementPeriod, subjects); } + // Ideally this would be done in MeasureProcessorUtils, but it's too much work to change for now + private MeasureLibraryIdEngineDetails buildLibraryIdEngineDetails( + Measure measure, Parameters parameters, CqlEngine context) { + + var libraryVersionIdentifier = getLibraryVersionIdentifier(measure); + + return new MeasureLibraryIdEngineDetails( + measure.getIdElement(), + libraryVersionIdentifier, + getLibraryEngine(parameters, libraryVersionIdentifier, context)); + } + protected MeasureReportType evalTypeToReportType(MeasureEvalType measureEvalType) { switch (measureEvalType) { case PATIENT: From 626995b78b640e6a1e336e5b3f96397b3660a505 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Fri, 4 Jul 2025 09:32:59 -0400 Subject: [PATCH 23/66] Fix sonar and reverse some minor changes. --- .../fhir/cr/measure/dstu3/Dstu3MeasureProcessor.java | 10 ++++------ .../cqf/fhir/cr/measure/r4/R4CollectDataService.java | 5 +---- .../cqf/fhir/cr/measure/r4/R4MeasureReportBuilder.java | 2 +- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureProcessor.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureProcessor.java index 00ea33c6f3..62784e4c2f 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureProcessor.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureProcessor.java @@ -9,7 +9,6 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.stream.Collectors; import org.cqframework.cql.cql2elm.CqlIncludeException; import org.cqframework.cql.cql2elm.model.CompiledLibrary; import org.hl7.elm.r1.VersionedIdentifier; @@ -34,6 +33,7 @@ import org.opencds.cqf.fhir.utility.repository.FederatedRepository; import org.opencds.cqf.fhir.utility.repository.InMemoryFhirRepository; +@SuppressWarnings("squid:S1135") public class Dstu3MeasureProcessor { private final IRepository repository; private final MeasureEvaluationOptions measureEvaluationOptions; @@ -91,7 +91,7 @@ protected MeasureReport evaluateMeasure( actualRepo = new FederatedRepository( this.repository, new InMemoryFhirRepository(this.repository.fhirContext(), additionalData)); } - var subjects = subjectProvider.getSubjects(actualRepo, subjectIds).collect(Collectors.toList()); + var subjects = subjectProvider.getSubjects(actualRepo, subjectIds).toList(); var evalType = getMeasureEvalType(reportType, subjects); var context = Engines.forRepository( this.repository, this.measureEvaluationOptions.getEvaluationSettings(), additionalData); @@ -146,11 +146,9 @@ private MeasureLibraryIdEngineDetails buildLibraryIdEngineDetails( protected MeasureReportType evalTypeToReportType(MeasureEvalType measureEvalType) { switch (measureEvalType) { - case PATIENT: - case SUBJECT: + case PATIENT, SUBJECT: return MeasureReportType.INDIVIDUAL; - case PATIENTLIST: - case SUBJECTLIST: + case PATIENTLIST, SUBJECTLIST: return MeasureReportType.PATIENTLIST; case POPULATION: return MeasureReportType.SUMMARY; diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java index 22f87c1d46..edf9fbee3e 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java @@ -5,7 +5,6 @@ import ca.uhn.fhir.repository.IRepository; import jakarta.annotation.Nullable; import java.time.ZonedDateTime; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; @@ -81,10 +80,8 @@ public Parameters collectData( for (String patient : subjectList) { var subjects = Collections.singletonList(patient); - var mutableList = new ArrayList<>(subjects); - var evaluationResults = processor.evaluateMeasureWithCqlEngine( - mutableList, foldedMeasure, periodStart, periodEnd, parameters, context); + subjects, foldedMeasure, periodStart, periodEnd, parameters, context); // add resources per subject to Parameters addReports( diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureReportBuilder.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureReportBuilder.java index 43a24efceb..9efad25e8e 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureReportBuilder.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureReportBuilder.java @@ -81,7 +81,7 @@ public R4MeasureReportBuilder() { this.measureReportScorer = new R4MeasureReportScorer(); } - protected static class BuilderContext { + private static class BuilderContext { private final Measure measure; private final MeasureDef measureDef; private final MeasureReport measureReport; From 9a56b55b28dde7e0b64c5dec0df20e3db75b7ac8 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Fri, 4 Jul 2025 09:50:02 -0400 Subject: [PATCH 24/66] Fix DSTU3 tests and resolve last sonar. --- .../cr/measure/dstu3/Dstu3MeasureProcessor.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureProcessor.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureProcessor.java index 62784e4c2f..99f9bbf244 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureProcessor.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureProcessor.java @@ -3,6 +3,7 @@ import ca.uhn.fhir.repository.IRepository; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import java.time.ZonedDateTime; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -96,8 +97,10 @@ protected MeasureReport evaluateMeasure( var context = Engines.forRepository( this.repository, this.measureEvaluationOptions.getEvaluationSettings(), additionalData); - var libraryVersionIdentifier = getLibraryVersionIdentifier(measure); - var libraryEngine = getLibraryEngine(parameters, libraryVersionIdentifier, context); + // Note that we must build the LibraryEngine BEFORE we call + // measureProcessorUtils.setMeasurementPeriod(), otherwise, we get an NPE. + var measureLibraryIdEngineDetails = buildLibraryIdEngineDetails(measure, parameters, context); + // set measurement Period from CQL if operation parameters are empty measureProcessorUtils.setMeasurementPeriod( measurementPeriodParams, @@ -111,10 +114,7 @@ protected MeasureReport evaluateMeasure( // populate results from Library $evaluate if (!subjects.isEmpty()) { var results = measureProcessorUtils.getEvaluationResults( - subjectIds, - zonedMeasurementPeriod, - context, - buildLibraryIdEngineDetails(measure, parameters, context)); + subjectIds, zonedMeasurementPeriod, context, measureLibraryIdEngineDetails); // Process Criteria Expression Results measureProcessorUtils.processResults( @@ -237,7 +237,10 @@ private Map resolveParameterMap(Parameters parameters) { list.add(value); } } else { - parameterMap.put(param.getName(), Arrays.asList(parameterMap.get(param.getName()), value)); + // We need a mutable list here, otherwise, retrieving the list above will fail with + // UnsupportedOperationException + parameterMap.put( + param.getName(), new ArrayList<>(Arrays.asList(parameterMap.get(param.getName()), value))); } } else { parameterMap.put(param.getName(), value); From de88779d1c70a2d3b9134cec12319c9a74bcb6ec Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Fri, 4 Jul 2025 10:51:28 -0400 Subject: [PATCH 25/66] More refactoring to DRY and to remove unused fields. --- .../fhir/benchmark/measure/r4/Measure.java | 2 +- .../fhir/cr/hapi/config/r4/CrR4Config.java | 9 +-- .../cr/measure/r4/R4CollectDataService.java | 22 ++++-- .../cr/measure/r4/R4MeasureProcessor.java | 25 +++++++ .../fhir/cr/measure/r4/R4MeasureService.java | 11 +-- .../cr/measure/r4/R4MultiMeasureService.java | 72 +++++++++---------- .../cqf/fhir/cr/measure/r4/Measure.java | 2 +- 7 files changed, 84 insertions(+), 59 deletions(-) diff --git a/cqf-fhir-benchmark/src/test/java/org/opencds/cqf/fhir/benchmark/measure/r4/Measure.java b/cqf-fhir-benchmark/src/test/java/org/opencds/cqf/fhir/benchmark/measure/r4/Measure.java index eabe909b40..38cb2cb54c 100644 --- a/cqf-fhir-benchmark/src/test/java/org/opencds/cqf/fhir/benchmark/measure/r4/Measure.java +++ b/cqf-fhir-benchmark/src/test/java/org/opencds/cqf/fhir/benchmark/measure/r4/Measure.java @@ -82,7 +82,7 @@ public Given evaluationOptions(MeasureEvaluationOptions evaluationOptions) { } private R4MeasureService buildMeasureService() { - return new R4MeasureService(repository, evaluationOptions, measurePeriodValidator, measureServiceUtils); + return new R4MeasureService(repository, evaluationOptions, measurePeriodValidator); } public When when() { diff --git a/cqf-fhir-cr-hapi/src/main/java/org/opencds/cqf/fhir/cr/hapi/config/r4/CrR4Config.java b/cqf-fhir-cr-hapi/src/main/java/org/opencds/cqf/fhir/cr/hapi/config/r4/CrR4Config.java index da8cdcc60d..9c3711cc6d 100644 --- a/cqf-fhir-cr-hapi/src/main/java/org/opencds/cqf/fhir/cr/hapi/config/r4/CrR4Config.java +++ b/cqf-fhir-cr-hapi/src/main/java/org/opencds/cqf/fhir/cr/hapi/config/r4/CrR4Config.java @@ -48,13 +48,8 @@ public class CrR4Config { R4MeasureEvaluatorSingleFactory r4MeasureServiceFactory( IRepositoryFactory repositoryFactory, MeasureEvaluationOptions evaluationOptions, - MeasurePeriodValidator measurePeriodValidator, - R4MeasureServiceUtilsFactory r4MeasureServiceUtilsFactory) { - return rd -> new R4MeasureService( - repositoryFactory.create(rd), - evaluationOptions, - measurePeriodValidator, - r4MeasureServiceUtilsFactory.create(rd)); + MeasurePeriodValidator measurePeriodValidator) { + return rd -> new R4MeasureService(repositoryFactory.create(rd), evaluationOptions, measurePeriodValidator); } @Bean diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java index edf9fbee3e..46b3a0aa75 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CollectDataService.java @@ -80,18 +80,32 @@ public Parameters collectData( for (String patient : subjectList) { var subjects = Collections.singletonList(patient); - var evaluationResults = processor.evaluateMeasureWithCqlEngine( + var compositeEvaluationResultsPerMeasure = processor.evaluateMeasureWithCqlEngine( subjects, foldedMeasure, periodStart, periodEnd, parameters, context); // add resources per subject to Parameters addReports( - processor, measureId, periodStart, periodEnd, subjects, parameters, context, evaluationResults); + processor, + measureId, + periodStart, + periodEnd, + subjects, + parameters, + context, + compositeEvaluationResultsPerMeasure); } } else { - var evaluationResults = processor.evaluateMeasureWithCqlEngine( + var compositeEvaluationResultsPerMeasure = processor.evaluateMeasureWithCqlEngine( subjectList, foldedMeasure, periodStart, periodEnd, parameters, context); addReports( - processor, measureId, periodStart, periodEnd, subjectList, parameters, context, evaluationResults); + processor, + measureId, + periodStart, + periodEnd, + subjectList, + parameters, + context, + compositeEvaluationResultsPerMeasure); } return parameters; } diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java index 7b2e8fc73c..ee11fd672a 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java @@ -63,6 +63,12 @@ public R4MeasureProcessor( this.measureProcessorUtils = measureProcessorUtils; } + // Expose this so CQL measure evaluation can use the same Repository as the one passed to the + // processor: this may be some sort of federated proxy repository initialized at runtime + public IRepository getRepository() { + return repository; + } + public MeasureReport evaluateMeasure( Either3 measure, @Nullable ZonedDateTime periodStart, @@ -194,6 +200,23 @@ protected MeasureReport evaluateMeasure( subjectIds); } + public CompositeEvaluationResultsPerMeasure evaluateMeasureEitherWithCqlEngine( + List subjects, + Either3 measureEither, + @Nullable ZonedDateTime periodStart, + @Nullable ZonedDateTime periodEnd, + Parameters parameters, + CqlEngine context) { + + return evaluateMultiMeasuresWithCqlEngine( + subjects, + List.of(R4MeasureServiceUtils.foldMeasure(measureEither, repository)), + periodStart, + periodEnd, + parameters, + context); + } + public CompositeEvaluationResultsPerMeasure evaluateMeasureIdWithCqlEngine( List subjects, IIdType measureId, @@ -258,7 +281,9 @@ public CompositeEvaluationResultsPerMeasure evaluateMultiMeasuresWithCqlEngine( // Ideally this would be done in MeasureProcessorUtils, but it's too much work to change for now private MeasureLibraryIdEngineDetails buildLibraryIdEngineDetails( Measure measure, Parameters parameters, CqlEngine context) { + var libraryVersionIdentifier = getLibraryVersionIdentifier(measure); + return new MeasureLibraryIdEngineDetails( measure.getIdElement(), libraryVersionIdentifier, diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java index a4e4b130bf..d076ca54d8 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java @@ -28,19 +28,16 @@ public class R4MeasureService implements R4MeasureEvaluatorSingle { private final MeasureEvaluationOptions measureEvaluationOptions; private final MeasurePeriodValidator measurePeriodValidator; private final R4RepositorySubjectProvider subjectProvider; - private final R4MeasureServiceUtils measureServiceUtils; private final MeasureProcessorUtils measureProcessorUtils = new MeasureProcessorUtils(); public R4MeasureService( IRepository repository, MeasureEvaluationOptions measureEvaluationOptions, - MeasurePeriodValidator measurePeriodValidator, - R4MeasureServiceUtils measureServiceUtils) { + MeasurePeriodValidator measurePeriodValidator) { this.repository = repository; this.measureEvaluationOptions = measureEvaluationOptions; this.measurePeriodValidator = measurePeriodValidator; this.subjectProvider = new R4RepositorySubjectProvider(measureEvaluationOptions.getSubjectProviderOptions()); - this.measureServiceUtils = measureServiceUtils; } @Override @@ -69,8 +66,6 @@ public MeasureReport evaluate( R4MeasureServiceUtils r4MeasureServiceUtils = new R4MeasureServiceUtils(repository); r4MeasureServiceUtils.ensureSupplementalDataElementSearchParameter(); - var foldedMeasure = R4MeasureServiceUtils.foldMeasure(measure, proxyRepoForMeasureProcessor); - MeasureReport measureReport; if (StringUtils.isNotBlank(practitioner)) { @@ -99,8 +94,8 @@ public MeasureReport evaluate( var context = Engines.forRepository( proxyRepoForMeasureProcessor, this.measureEvaluationOptions.getEvaluationSettings(), additionalData); - var evaluationResults = processor.evaluateMeasureWithCqlEngine( - subjects, foldedMeasure, periodStart, periodEnd, parameters, context); + var evaluationResults = processor.evaluateMeasureEitherWithCqlEngine( + subjects, measure, periodStart, periodEnd, parameters, context); measureReport = processor.evaluateMeasure( measure, periodStart, periodEnd, reportType, subjects, evalType, context, evaluationResults); diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java index ab6a55b414..84b55957e7 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java @@ -9,7 +9,6 @@ import java.util.Collections; import java.util.List; import java.util.UUID; -import java.util.stream.Collectors; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Bundle.BundleType; @@ -19,6 +18,7 @@ import org.hl7.fhir.r4.model.MeasureReport; import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.Resource; +import org.opencds.cqf.cql.engine.execution.CqlEngine; import org.opencds.cqf.fhir.cql.Engines; import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions; import org.opencds.cqf.fhir.cr.measure.common.CompositeEvaluationResultsPerMeasure; @@ -34,20 +34,18 @@ * Alternate MeasureService call to Process MeasureEvaluation for the selected population of subjects against n-number * of measure resources. The output of this operation would be a bundle of MeasureReports instead of MeasureReport. */ +@SuppressWarnings("squid:S107") public class R4MultiMeasureService implements R4MeasureEvaluatorMultiple { + private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(R4MultiMeasureService.class); + private final IRepository repository; private final MeasureEvaluationOptions measureEvaluationOptions; private final MeasurePeriodValidator measurePeriodValidator; private final MeasureProcessorUtils measureProcessorUtils = new MeasureProcessorUtils(); private final String serverBase; - private final R4RepositorySubjectProvider subjectProvider; - private R4MeasureProcessor r4Processor; - - private R4MeasureServiceUtils r4MeasureServiceUtils; - public R4MultiMeasureService( IRepository repository, MeasureEvaluationOptions measureEvaluationOptions, @@ -57,12 +55,7 @@ public R4MultiMeasureService( this.measureEvaluationOptions = measureEvaluationOptions; this.measurePeriodValidator = measurePeriodValidator; this.serverBase = serverBase; - - subjectProvider = new R4RepositorySubjectProvider(measureEvaluationOptions.getSubjectProviderOptions()); - - r4Processor = new R4MeasureProcessor(repository, this.measureEvaluationOptions, this.measureProcessorUtils); - - r4MeasureServiceUtils = new R4MeasureServiceUtils(repository); + this.subjectProvider = new R4RepositorySubjectProvider(measureEvaluationOptions.getSubjectProviderOptions()); } @Override @@ -84,6 +77,8 @@ public Bundle evaluate( measurePeriodValidator.validatePeriodStartAndEnd(periodStart, periodEnd); + final R4MeasureProcessor r4Processor; + final R4MeasureServiceUtils r4MeasureServiceUtils; if (dataEndpoint != null && contentEndpoint != null && terminologyEndpoint != null) { // if needing to use proxy repository, override constructors var repositoryToUse = @@ -93,6 +88,9 @@ public Bundle evaluate( new R4MeasureProcessor(repositoryToUse, this.measureEvaluationOptions, this.measureProcessorUtils); r4MeasureServiceUtils = new R4MeasureServiceUtils(repositoryToUse); + } else { + r4Processor = new R4MeasureProcessor(repository, this.measureEvaluationOptions, this.measureProcessorUtils); + r4MeasureServiceUtils = new R4MeasureServiceUtils(repository); } r4MeasureServiceUtils.ensureSupplementalDataElementSearchParameter(); List measures = r4MeasureServiceUtils.getMeasures(measureId, measureIdentifier, measureUrl); @@ -108,9 +106,20 @@ public Bundle evaluate( .withType(BundleType.SEARCHSET.toString()) .build(); + var context = Engines.forRepository( + r4Processor.getRepository(), this.measureEvaluationOptions.getEvaluationSettings(), additionalData); + + // This is basically a Map of measure -> subject -> EvaluationResult + var compositeEvaluationResultsPerMeasure = r4Processor.evaluateMultiMeasuresWithCqlEngine( + subjects, measures, periodStart, periodEnd, parameters, context); + // evaluate Measures if (evalType.equals(MeasureEvalType.POPULATION) || evalType.equals(MeasureEvalType.SUBJECTLIST)) { populationMeasureReport( + r4Processor, + r4MeasureServiceUtils, + compositeEvaluationResultsPerMeasure, + context, bundle, measures, periodStart, @@ -119,12 +128,14 @@ public Bundle evaluate( evalType, subject, subjects, - parameters, - additionalData, productLine, reporter); } else { subjectMeasureReport( + r4Processor, + r4MeasureServiceUtils, + compositeEvaluationResultsPerMeasure, + context, bundle, measures, periodStart, @@ -132,8 +143,6 @@ public Bundle evaluate( reportType, evalType, subjects, - parameters, - additionalData, productLine, reporter); } @@ -142,6 +151,10 @@ public Bundle evaluate( } protected void populationMeasureReport( + R4MeasureProcessor r4Processor, + R4MeasureServiceUtils r4MeasureServiceUtils, + CompositeEvaluationResultsPerMeasure compositeEvaluationResultsPerMeasure, + CqlEngine context, Bundle bundle, List measures, @Nullable ZonedDateTime periodStart, @@ -150,19 +163,9 @@ protected void populationMeasureReport( MeasureEvalType evalType, String subjectParam, List subjects, - Parameters parameters, - Bundle additionalData, String productLine, String reporter) { - var context = Engines.forRepository( - this.repository, this.measureEvaluationOptions.getEvaluationSettings(), additionalData); - - // This is basically a Map of measure -> subject -> EvaluationResult - final CompositeEvaluationResultsPerMeasure compositeEvaluationResultsPerMeasure = - r4Processor.evaluateMultiMeasuresWithCqlEngine( - subjects, measures, periodStart, periodEnd, parameters, context); - var totalMeasures = measures.size(); for (Measure measure : measures) { MeasureReport measureReport; @@ -206,6 +209,10 @@ protected void populationMeasureReport( } protected void subjectMeasureReport( + R4MeasureProcessor r4Processor, + R4MeasureServiceUtils r4MeasureServiceUtils, + CompositeEvaluationResultsPerMeasure compositeEvaluationResultsPerMeasure, + CqlEngine context, Bundle bundle, List measures, @Nullable ZonedDateTime periodStart, @@ -213,8 +220,6 @@ protected void subjectMeasureReport( String reportType, MeasureEvalType evalType, List subjects, - Parameters parameters, - Bundle additionalData, String productLine, String reporter) { @@ -226,14 +231,6 @@ protected void subjectMeasureReport( subjects.size(), measures.size()); - var context = Engines.forRepository( - this.repository, this.measureEvaluationOptions.getEvaluationSettings(), additionalData); - - // This is basically a Map of measure -> subject -> EvaluationResult - final CompositeEvaluationResultsPerMeasure compositeEvaluationResultsPerMeasure = - r4Processor.evaluateMultiMeasuresWithCqlEngine( - subjects, measures, periodStart, periodEnd, parameters, context); - for (Measure measure : measures) { for (String subject : subjects) { MeasureReport measureReport; @@ -278,8 +275,7 @@ protected void subjectMeasureReport( } protected List getSubjects(R4RepositorySubjectProvider subjectProvider, String subjectId) { - - return subjectProvider.getSubjects(repository, subjectId).collect(Collectors.toList()); + return subjectProvider.getSubjects(repository, subjectId).toList(); } protected void initializeReport(MeasureReport measureReport) { diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/Measure.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/Measure.java index 9288ca08ad..52e0836eb7 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/Measure.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/Measure.java @@ -167,7 +167,7 @@ public Given evaluationOptions(MeasureEvaluationOptions evaluationOptions) { } private R4MeasureService buildMeasureService() { - return new R4MeasureService(repository, evaluationOptions, measurePeriodValidator, measureServiceUtils); + return new R4MeasureService(repository, evaluationOptions, measurePeriodValidator); } public When when() { From 07a678ded674ec5ec6e02e590825ad4d97dbb9b7 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Fri, 4 Jul 2025 11:31:06 -0400 Subject: [PATCH 26/66] Small refactoring for multi-measure service. --- .../cr/measure/r4/R4MeasureProcessor.java | 4 +- .../cr/measure/r4/R4MultiMeasureService.java | 40 +++++++++++-------- 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java index ee11fd672a..e9505dad23 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java @@ -260,7 +260,9 @@ public CompositeEvaluationResultsPerMeasure evaluateMultiMeasuresWithCqlEngine( var zonedMeasurementPeriod = MeasureProcessorUtils.getZonedTimeZoneForEval( measureProcessorUtils.getDefaultMeasurementPeriod(measurementPeriodParams, context)); - final List measureLibraryIdEngineDetailsList = measures.stream() + // Note that we must build the LibraryEngine BEFORE we call + // measureProcessorUtils.setMeasurementPeriod(), otherwise, we get an NPE. + var measureLibraryIdEngineDetailsList = measures.stream() .map(measure -> buildLibraryIdEngineDetails(measure, parameters, context)) .toList(); diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java index 84b55957e7..689388fc1d 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java @@ -45,6 +45,8 @@ public class R4MultiMeasureService implements R4MeasureEvaluatorMultiple { private final MeasureProcessorUtils measureProcessorUtils = new MeasureProcessorUtils(); private final String serverBase; private final R4RepositorySubjectProvider subjectProvider; + private final R4MeasureProcessor r4MeasureProcessorStandardRepository; + private final R4MeasureServiceUtils r4MeasureServiceUtilsStandardRepository; public R4MultiMeasureService( IRepository repository, @@ -56,6 +58,9 @@ public R4MultiMeasureService( this.measurePeriodValidator = measurePeriodValidator; this.serverBase = serverBase; this.subjectProvider = new R4RepositorySubjectProvider(measureEvaluationOptions.getSubjectProviderOptions()); + this.r4MeasureProcessorStandardRepository = + new R4MeasureProcessor(repository, this.measureEvaluationOptions, this.measureProcessorUtils); + this.r4MeasureServiceUtilsStandardRepository = new R4MeasureServiceUtils(repository); } @Override @@ -77,26 +82,27 @@ public Bundle evaluate( measurePeriodValidator.validatePeriodStartAndEnd(periodStart, periodEnd); - final R4MeasureProcessor r4Processor; - final R4MeasureServiceUtils r4MeasureServiceUtils; + final R4MeasureProcessor r4ProcessorToUse; + final R4MeasureServiceUtils r4MeasureServiceUtilsToUse; if (dataEndpoint != null && contentEndpoint != null && terminologyEndpoint != null) { - // if needing to use proxy repository, override constructors + // if needing to use proxy repository, initialize new R4MeasureProcessor and R4MeasureServiceUtils var repositoryToUse = Repositories.proxy(repository, true, dataEndpoint, contentEndpoint, terminologyEndpoint); - r4Processor = + r4ProcessorToUse = new R4MeasureProcessor(repositoryToUse, this.measureEvaluationOptions, this.measureProcessorUtils); - r4MeasureServiceUtils = new R4MeasureServiceUtils(repositoryToUse); + r4MeasureServiceUtilsToUse = new R4MeasureServiceUtils(repositoryToUse); } else { - r4Processor = new R4MeasureProcessor(repository, this.measureEvaluationOptions, this.measureProcessorUtils); - r4MeasureServiceUtils = new R4MeasureServiceUtils(repository); + r4ProcessorToUse = r4MeasureProcessorStandardRepository; + r4MeasureServiceUtilsToUse = r4MeasureServiceUtilsStandardRepository; } - r4MeasureServiceUtils.ensureSupplementalDataElementSearchParameter(); - List measures = r4MeasureServiceUtils.getMeasures(measureId, measureIdentifier, measureUrl); + + r4MeasureServiceUtilsToUse.ensureSupplementalDataElementSearchParameter(); + List measures = r4MeasureServiceUtilsToUse.getMeasures(measureId, measureIdentifier, measureUrl); log.info("multi-evaluate-measure, measures to evaluate: {}", measures.size()); - var evalType = r4MeasureServiceUtils.getMeasureEvalType(reportType, subject); + var evalType = r4MeasureServiceUtilsToUse.getMeasureEvalType(reportType, subject); // get subjects var subjects = getSubjects(subjectProvider, subject); @@ -107,17 +113,19 @@ public Bundle evaluate( .build(); var context = Engines.forRepository( - r4Processor.getRepository(), this.measureEvaluationOptions.getEvaluationSettings(), additionalData); + r4ProcessorToUse.getRepository(), + this.measureEvaluationOptions.getEvaluationSettings(), + additionalData); // This is basically a Map of measure -> subject -> EvaluationResult - var compositeEvaluationResultsPerMeasure = r4Processor.evaluateMultiMeasuresWithCqlEngine( + var compositeEvaluationResultsPerMeasure = r4ProcessorToUse.evaluateMultiMeasuresWithCqlEngine( subjects, measures, periodStart, periodEnd, parameters, context); // evaluate Measures if (evalType.equals(MeasureEvalType.POPULATION) || evalType.equals(MeasureEvalType.SUBJECTLIST)) { populationMeasureReport( - r4Processor, - r4MeasureServiceUtils, + r4ProcessorToUse, + r4MeasureServiceUtilsToUse, compositeEvaluationResultsPerMeasure, context, bundle, @@ -132,8 +140,8 @@ public Bundle evaluate( reporter); } else { subjectMeasureReport( - r4Processor, - r4MeasureServiceUtils, + r4ProcessorToUse, + r4MeasureServiceUtilsToUse, compositeEvaluationResultsPerMeasure, context, bundle, From c4a8dc139e649e23f668db38c1547f7fe8ecaa4c Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Fri, 4 Jul 2025 16:22:44 -0400 Subject: [PATCH 27/66] Optimize performance of CompositeEvaluationResultsPerMeasure. --- .../CompositeEvaluationResultsPerMeasure.java | 39 ++++++++----------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java index 85448771e5..9863c4d367 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java @@ -5,7 +5,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import org.hl7.fhir.instance.model.api.IIdType; import org.opencds.cqf.cql.engine.execution.EvaluationResult; @@ -29,38 +28,34 @@ public class CompositeEvaluationResultsPerMeasure { private CompositeEvaluationResultsPerMeasure(Builder builder) { var resultsBuilder = ImmutableMap.>builder(); - var errorsBuilder = ImmutableMap.>builder(); - builder.resultsPerMeasure.forEach((key, value) -> resultsBuilder.put(key, ImmutableMap.copyOf(value))); - resultsPerMeasure = resultsBuilder.build(); + var errorsBuilder = ImmutableMap.>builder(); builder.errorsPerMeasure.forEach((key, value) -> errorsBuilder.put(key, List.copyOf(value))); - errorsPerMeasure = errorsBuilder.build(); } - // measureDef will occasionally be prepended with the version, which means we need to parse it into an IIdType which - // is too much work, so pass in the measureId directly + /** + * Retrieves results and populates errors for a given measure. + * This method uses direct map lookups for efficient data retrieval. + * measureDef will occasionally be prepended with the version, which means we need to parse it into an IIdType which + * is too much work, so pass in the measureId directly + * + * @param measureId the ID of the measure to process + * @param measureDef the MeasureDef to populate with errors + * + * @return a map of evaluation results per subject, or an empty map if none exist + */ public Map processMeasureForSuccessOrFailure(IIdType measureId, MeasureDef measureDef) { - errorsPerMeasure.entrySet().stream() - .filter(entry -> isMeasureDefFound(entry.getKey(), measureId)) - .map(Entry::getValue) - .flatMap(List::stream) - .forEach(measureDef::addError); + var unqualifiedMeasureId = measureId.toUnqualifiedVersionless(); - var resultForMeasure = resultsPerMeasure.entrySet().stream() - .filter(entry -> isMeasureDefFound(entry.getKey(), measureId)) - .map(Entry::getValue) - .findFirst(); + errorsPerMeasure.getOrDefault(unqualifiedMeasureId, List.of()) + .forEach(measureDef::addError); - // We are explicitly maintaining the logic of accepting the lack of any sort of results + // We are explicitly maintaining the logic of accepting the lack of any sort of results, // either errors or successes, and returning an empty map. - return resultForMeasure.orElseGet(Map::of); - } - - private boolean isMeasureDefFound(IIdType entryKey, IIdType measureId) { - return measureId.toUnqualifiedVersionless().equals(entryKey.toUnqualifiedVersionless()); + return resultsPerMeasure.getOrDefault(unqualifiedMeasureId, Map.of()); } public static Builder builder() { From 56f23b54b56697b3a02f4c6b4b31d1a66d1b94d5 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Fri, 4 Jul 2025 16:24:17 -0400 Subject: [PATCH 28/66] Spotless. --- .../measure/common/CompositeEvaluationResultsPerMeasure.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java index 9863c4d367..8e900b1e2b 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java @@ -50,8 +50,7 @@ private CompositeEvaluationResultsPerMeasure(Builder builder) { public Map processMeasureForSuccessOrFailure(IIdType measureId, MeasureDef measureDef) { var unqualifiedMeasureId = measureId.toUnqualifiedVersionless(); - errorsPerMeasure.getOrDefault(unqualifiedMeasureId, List.of()) - .forEach(measureDef::addError); + errorsPerMeasure.getOrDefault(unqualifiedMeasureId, List.of()).forEach(measureDef::addError); // We are explicitly maintaining the logic of accepting the lack of any sort of results, // either errors or successes, and returning an empty map. From c86c2c02df128e61a7fa08eba0f67873dc34f76d Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Fri, 4 Jul 2025 16:57:38 -0400 Subject: [PATCH 29/66] Small refactoring. --- .../fhir/cr/measure/r4/R4MeasureService.java | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java index d076ca54d8..b5f97f4296 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java @@ -22,6 +22,7 @@ import org.opencds.cqf.fhir.utility.repository.FederatedRepository; import org.opencds.cqf.fhir.utility.repository.InMemoryFhirRepository; import org.opencds.cqf.fhir.utility.repository.Repositories; +import javax.annotation.Nonnull; public class R4MeasureService implements R4MeasureEvaluatorSingle { private final IRepository repository; @@ -75,19 +76,10 @@ public MeasureReport evaluate( subjectId = practitioner; } - var actualRepo = proxyRepoForMeasureProcessor; - if (additionalData != null) { - actualRepo = new FederatedRepository( - this.repository, new InMemoryFhirRepository(this.repository.fhirContext(), additionalData)); - } - var evalType = r4MeasureServiceUtils.getMeasureEvalType( reportType, Optional.ofNullable(subjectId).map(List::of).orElse(List.of())); - var subjects = subjectProvider - .getSubjects( - actualRepo, Optional.ofNullable(subjectId).map(List::of).orElse(List.of())) - .toList(); + var subjects = getSubjects(subjectId, proxyRepoForMeasureProcessor, additionalData); // Replicate the old logic of using the repository used to initialize the measure processor // as the repository for the CQL engine context. @@ -106,4 +98,21 @@ public MeasureReport evaluate( // add subject reference for non-individual reportTypes return r4MeasureServiceUtils.addSubjectReference(measureReport, practitioner, subjectId); } + + @Nonnull + private List getSubjects(String subjectId, IRepository proxyRepoForMeasureProcessor, Bundle additionalData) { + final IRepository repoToUseForSubjectProvider; + if (additionalData != null) { + repoToUseForSubjectProvider = new FederatedRepository( + this.repository, new InMemoryFhirRepository(this.repository.fhirContext(), additionalData)); + } else { + repoToUseForSubjectProvider = proxyRepoForMeasureProcessor; + } + + return subjectProvider + .getSubjects( + repoToUseForSubjectProvider, + Optional.ofNullable(subjectId).map(List::of).orElse(List.of())) + .toList(); + } } From 48f42d545876324002d0678d5de623b962abd9df Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Mon, 7 Jul 2025 11:33:37 -0400 Subject: [PATCH 30/66] Spotless. --- .../cqf/fhir/cr/measure/r4/R4MeasureService.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java index b5f97f4296..d195dfc534 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java @@ -1,6 +1,7 @@ package org.opencds.cqf.fhir.cr.measure.r4; import ca.uhn.fhir.repository.IRepository; +import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; import java.time.ZonedDateTime; import java.util.List; @@ -22,7 +23,6 @@ import org.opencds.cqf.fhir.utility.repository.FederatedRepository; import org.opencds.cqf.fhir.utility.repository.InMemoryFhirRepository; import org.opencds.cqf.fhir.utility.repository.Repositories; -import javax.annotation.Nonnull; public class R4MeasureService implements R4MeasureEvaluatorSingle { private final IRepository repository; @@ -100,19 +100,20 @@ public MeasureReport evaluate( } @Nonnull - private List getSubjects(String subjectId, IRepository proxyRepoForMeasureProcessor, Bundle additionalData) { + private List getSubjects( + String subjectId, IRepository proxyRepoForMeasureProcessor, Bundle additionalData) { final IRepository repoToUseForSubjectProvider; if (additionalData != null) { repoToUseForSubjectProvider = new FederatedRepository( - this.repository, new InMemoryFhirRepository(this.repository.fhirContext(), additionalData)); + this.repository, new InMemoryFhirRepository(this.repository.fhirContext(), additionalData)); } else { repoToUseForSubjectProvider = proxyRepoForMeasureProcessor; } return subjectProvider - .getSubjects( - repoToUseForSubjectProvider, - Optional.ofNullable(subjectId).map(List::of).orElse(List.of())) - .toList(); + .getSubjects( + repoToUseForSubjectProvider, + Optional.ofNullable(subjectId).map(List::of).orElse(List.of())) + .toList(); } } From 261967a49295bc7e94a0aadcc9db0bceefdb7e50 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Mon, 7 Jul 2025 11:35:21 -0400 Subject: [PATCH 31/66] Replace all java.annotation imports with jakarta.annotation. --- .../src/test/java/org/opencds/cqf/fhir/cql/EnginesTest.java | 2 +- .../cqf/fhir/cr/measure/r4/utils/R4MeasureServiceUtils.java | 2 +- .../opencds/cqf/fhir/cr/measure/helper/DateHelperTest.java | 2 +- .../test/java/org/opencds/cqf/fhir/cr/measure/r4/Measure.java | 2 +- .../opencds/cqf/fhir/cr/measure/r4/MeasureDefBuilderTest.java | 2 +- .../cqf/fhir/cr/measure/r4/R4MeasureReportBuilderTest.java | 4 ++-- .../fhir/cr/measure/r4/R4PopulationBasisValidatorTest.java | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cqf-fhir-cql/src/test/java/org/opencds/cqf/fhir/cql/EnginesTest.java b/cqf-fhir-cql/src/test/java/org/opencds/cqf/fhir/cql/EnginesTest.java index 30407a90c1..f48af72171 100644 --- a/cqf-fhir-cql/src/test/java/org/opencds/cqf/fhir/cql/EnginesTest.java +++ b/cqf-fhir-cql/src/test/java/org/opencds/cqf/fhir/cql/EnginesTest.java @@ -14,11 +14,11 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.util.BundleBuilder; +import jakarta.annotation.Nonnull; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.stream.StreamSupport; -import javax.annotation.Nonnull; import org.cqframework.cql.cql2elm.StringLibrarySourceProvider; import org.cqframework.fhir.npm.NpmPackageManager; import org.cqframework.fhir.npm.NpmProcessor; diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/utils/R4MeasureServiceUtils.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/utils/R4MeasureServiceUtils.java index 1754d52f91..30416b6656 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/utils/R4MeasureServiceUtils.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/utils/R4MeasureServiceUtils.java @@ -21,6 +21,7 @@ import ca.uhn.fhir.rest.param.UriParam; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException; +import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; import java.util.ArrayList; import java.util.Collections; @@ -32,7 +33,6 @@ import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; -import javax.annotation.Nonnull; import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/helper/DateHelperTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/helper/DateHelperTest.java index 288a678430..017766d1ae 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/helper/DateHelperTest.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/helper/DateHelperTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import jakarta.annotation.Nonnull; import java.time.LocalDateTime; import java.time.Month; import java.time.OffsetDateTime; @@ -10,7 +11,6 @@ import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.stream.Stream; -import javax.annotation.Nonnull; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/Measure.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/Measure.java index 52e0836eb7..aaf7139edd 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/Measure.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/Measure.java @@ -17,6 +17,7 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.repository.IRepository; +import jakarta.annotation.Nullable; import java.nio.file.Path; import java.time.LocalDate; import java.time.ZoneId; @@ -30,7 +31,6 @@ import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Collectors; -import javax.annotation.Nullable; import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.CodeableConcept; diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MeasureDefBuilderTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MeasureDefBuilderTest.java index f4b2fe3d24..842f1ea3c5 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MeasureDefBuilderTest.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MeasureDefBuilderTest.java @@ -13,6 +13,7 @@ import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; import java.util.Arrays; import java.util.List; @@ -21,7 +22,6 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; -import javax.annotation.Nonnull; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.r4.model.CodeType; import org.hl7.fhir.r4.model.CodeableConcept; diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureReportBuilderTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureReportBuilderTest.java index e3c29a38cc..d39b269699 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureReportBuilderTest.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureReportBuilderTest.java @@ -7,6 +7,8 @@ import static org.junit.jupiter.api.Assertions.fail; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; import java.time.LocalDate; import java.time.Month; import java.util.ArrayList; @@ -16,8 +18,6 @@ import java.util.Set; import java.util.stream.Collectors; import java.util.stream.IntStream; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.Measure; import org.hl7.fhir.r4.model.Measure.MeasureGroupComponent; diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/R4PopulationBasisValidatorTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/R4PopulationBasisValidatorTest.java index 20114495b3..b1f1652d8e 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/R4PopulationBasisValidatorTest.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/R4PopulationBasisValidatorTest.java @@ -7,12 +7,12 @@ import static org.opencds.cqf.fhir.cr.measure.common.MeasurePopulationType.NUMERATOR; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import jakarta.annotation.Nonnull; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Stream; -import javax.annotation.Nonnull; import org.hl7.fhir.r4.model.CodeableConcept; import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.Encounter; From 17953f24e0ce41459c2a3d4a49e7947e84198be5 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Mon, 7 Jul 2025 14:23:21 -0400 Subject: [PATCH 32/66] Add comment about new approach, as per code review. --- .../cqf/fhir/cr/measure/common/MeasureProcessorUtils.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java index 12f5414127..f5bb18ca60 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java @@ -351,6 +351,7 @@ public CompositeEvaluationResultsPerMeasure getEvaluationResults( /** * method used to execute generate CQL results via Library $evaluate + * * @param subjectIds subjects to generate results for * @param zonedMeasurementPeriod offset defined measurement period for evaluation * @param context cql engine context @@ -367,6 +368,9 @@ public CompositeEvaluationResultsPerMeasure getEvaluationResults( var resultsBuilder = CompositeEvaluationResultsPerMeasure.builder(); // Library $evaluate each subject + // The goal here is to do each measure/library evaluation within the context of a single subject. + // This means that we will not switch between subject contexts while evaluating measures. + // Once we've switched to a different subject context, the previous expression cache is dropped. for (String subjectId : subjectIds) { if (subjectId == null) { throw new InternalErrorException("SubjectId is required in order to calculate."); From 9708cfb3b3c097de850d9c360d6432b9dd92d7b4 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Tue, 8 Jul 2025 16:59:22 -0400 Subject: [PATCH 33/66] Slight fix to algorithm and TODO for CQL fix. --- .../cr/measure/common/MeasureProcessorUtils.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java index f5bb18ca60..d9b638af5d 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java @@ -375,17 +375,21 @@ public CompositeEvaluationResultsPerMeasure getEvaluationResults( if (subjectId == null) { throw new InternalErrorException("SubjectId is required in order to calculate."); } + // LUKETODO: we reset the cache outside of the measure loop + Pair subjectInfo = this.getSubjectTypeAndId(subjectId); + String subjectTypePart = subjectInfo.getLeft(); + String subjectIdPart = subjectInfo.getRight(); + context.getState().setContextValue(subjectTypePart, subjectIdPart); for (var measureLibraryIdEngine : measureLibraryIdEngineDetailsList) { - Pair subjectInfo = this.getSubjectTypeAndId(subjectId); - String subjectTypePart = subjectInfo.getLeft(); - String subjectIdPart = subjectInfo.getRight(); - context.getState().setContextValue(subjectTypePart, subjectIdPart); try { resultsBuilder.addResult( measureLibraryIdEngine.measureId(), subjectId, measureLibraryIdEngine .engine() + // LUKETODO: make CQL CqlEngine changes to take multiple versioned identifiers and then initState for mulitple libraries + // this seems to also reset the cache + // LUKETODO: call the new mulitple versionidentifier method once it's available in CQL .getEvaluationResult( measureLibraryIdEngine.libraryId(), subjectId, From 902e3a80e3433d21c96c97ee8a893c0784ec68a5 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Thu, 10 Jul 2025 09:36:12 -0400 Subject: [PATCH 34/66] Start integrating with new CQL branch with caching optimizations. --- .../measure/common/MeasureProcessorUtils.java | 65 ++++++++++++++++++- .../cr/measure/r4/R4MeasureProcessor.java | 47 ++++++++++++++ pom.xml | 3 +- 3 files changed, 111 insertions(+), 4 deletions(-) diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java index d9b638af5d..0890042ad1 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java @@ -314,13 +314,18 @@ public Object evaluateObservationCriteria( var ed = Libraries.resolveExpressionRef( criteriaExpression, context.getState().getCurrentLibrary()); - if (!(ed instanceof FunctionDef)) { + if (!(ed instanceof FunctionDef functionDef)) { throw new InvalidRequestException( "Measure observation %s does not reference a function definition".formatted(criteriaExpression)); } Object result; - context.getState().pushWindow(); + + + context.getState().pushActivationFrame(functionDef, functionDef.getContext()); + // LUKETODO: get rid of this +// context.getState().pushWindow(); + try { if (!isBooleanBasis) { // subject based observations don't have a parameter to pass in @@ -331,7 +336,7 @@ public Object evaluateObservationCriteria( } result = context.getEvaluationVisitor().visitExpression(ed.getExpression(), context.getState()); } finally { - context.getState().popWindow(); + context.getState().popActivationFrame(); } captureEvaluatedResources(outEvaluatedResources, context); @@ -413,6 +418,60 @@ public CompositeEvaluationResultsPerMeasure getEvaluationResults( return resultsBuilder.build(); } + public CompositeEvaluationResultsPerMeasure getEvaluationResults2( + List subjectIds, + ZonedDateTime zonedMeasurementPeriod, + CqlEngine context) { + + // measure -> subject -> results + var resultsBuilder = CompositeEvaluationResultsPerMeasure.builder(); + + // Library $evaluate each subject + // The goal here is to do each measure/library evaluation within the context of a single subject. + // This means that we will not switch between subject contexts while evaluating measures. + // Once we've switched to a different subject context, the previous expression cache is dropped. + for (String subjectId : subjectIds) { + if (subjectId == null) { + throw new InternalErrorException("SubjectId is required in order to calculate."); + } + // LUKETODO: we reset the cache outside of the measure loop + Pair subjectInfo = this.getSubjectTypeAndId(subjectId); + String subjectTypePart = subjectInfo.getLeft(); + String subjectIdPart = subjectInfo.getRight(); + context.getState().setContextValue(subjectTypePart, subjectIdPart); +// for (var measureLibraryIdEngine : measureLibraryIdEngineDetailsList) { +// try { +// resultsBuilder.addResult( +// measureLibraryIdEngine.measureId(), +// subjectId, +// measureLibraryIdEngine +// .engine() +// // LUKETODO: make CQL CqlEngine changes to take multiple versioned identifiers and then initState for mulitple libraries +// // this seems to also reset the cache +// // LUKETODO: call the new mulitple versionidentifier method once it's available in CQL +// .getEvaluationResult( +// measureLibraryIdEngine.libraryId(), +// subjectId, +// null, +// null, +// null, +// null, +// null, +// zonedMeasurementPeriod, +// context)); +// } catch (Exception e) { +// // Catch Exceptions from evaluation per subject, but allow rest of subjects to be processed (if +// // applicable) +// var error = EXCEPTION_FOR_SUBJECT_ID_MESSAGE_TEMPLATE.formatted(subjectId, e.getMessage()); +// resultsBuilder.addError(measureLibraryIdEngine.measureId(), error); +// logger.error(error, e); +// } +// } + } + + return resultsBuilder.build(); + } + public Pair getSubjectTypeAndId(String subjectId) { if (subjectId.contains("/")) { String[] subjectIdParts = subjectId.split("/"); diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java index e9505dad23..47f212a364 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java @@ -370,6 +370,34 @@ protected LibraryEngine getLibraryEngine(Parameters parameters, VersionedIdentif return new LibraryEngine(repository, this.measureEvaluationOptions.getEvaluationSettings()); } + protected LibraryEngine getLibraryEngine(Parameters parameters, List ids, CqlEngine context) { + + var compileLibraries = ids.stream() + .map(id -> getCompiledLibrary(id, context)) + .toList(); + + var libraries = compileLibraries.stream() + .map(CompiledLibrary::getLibrary) + .toList(); + + context.getState().init(libraries); + + setArgParameters(parameters, context, compileLibraries); + + return new LibraryEngine(repository, this.measureEvaluationOptions.getEvaluationSettings()); + } + + private CompiledLibrary getCompiledLibrary(VersionedIdentifier id, CqlEngine context) { + try { + return context.getEnvironment().getLibraryManager().resolveLibrary(id); + } catch (CqlIncludeException e) { + throw new IllegalStateException( + "Unable to load CQL/ELM for library: %s. Verify that the Library resource is available in your environment and has CQL/ELM content embedded." + .formatted(id.getId()), + e); + } + } + protected void checkMeasureLibrary(Measure measure) { if (!measure.hasLibrary()) { throw new InvalidRequestException( @@ -399,6 +427,25 @@ protected void setArgParameters(Parameters parameters, CqlEngine context, Compil } } + // LUKETODO: javadoc + protected void setArgParameters(Parameters parameters, CqlEngine context, List libs) { + if (parameters != null) { + Map paramMap = resolveParameterMap(parameters); + for (CompiledLibrary lib : libs) { + context.getState().setParameters(lib.getLibrary(), paramMap); + + if (lib.getLibrary().getIncludes() != null) { + lib.getLibrary() + .getIncludes() + .getDef() + .forEach(includeDef -> paramMap.forEach((paramKey, paramValue) -> context.getState() + .setParameter(includeDef.getLocalIdentifier(), paramKey, paramValue))); + } + } + } + } + + protected Measure resolveByUrl(CanonicalType url) { var parts = Canonicals.getParts(url); var result = this.repository.search( diff --git a/pom.xml b/pom.xml index 8781292ea2..bd944c1ee5 100644 --- a/pom.xml +++ b/pom.xml @@ -22,7 +22,8 @@ 5.10.2 2.0.4 - 3.26.0 + + 3.27.0-SNAPSHOT 1.36 6.1.14 8.2.0 From 0a79bc73a3bfac01184f09d5066aca1d2d9a3892 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Fri, 11 Jul 2025 11:53:47 -0400 Subject: [PATCH 35/66] Use the same made-up version of CQL that the CQL branch is using. --- pom.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index bd944c1ee5..b49d6e734a 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,8 @@ 5.10.2 2.0.4 - 3.27.0-SNAPSHOT + + 3.28.0-SNAPSHOT 1.36 6.1.14 8.2.0 From ebccc3b8e9895ac73c9d8170f1caf0b60ea83ef3 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Mon, 14 Jul 2025 16:27:59 -0400 Subject: [PATCH 36/66] Low risk changes including adding having CompositeEvaluationResultsPerMeasure handle adding multiple errors at once and a TODO to get rid of a record once fixes are complete. --- .../CompositeEvaluationResultsPerMeasure.java | 14 ++++++++++++-- .../common/MeasureLibraryIdEngineDetails.java | 1 + 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java index 8e900b1e2b..09910f81e2 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java @@ -76,14 +76,24 @@ public void addResult(IIdType measureId, String subjectId, EvaluationResult eval resultPerMeasure.put(subjectId, evaluationResult); } + public void addErrors(List measureIds, String error) { + if (error == null || error.isEmpty()) { + return; + } + + for (IIdType measureId : measureIds) { + addError(measureId, error); + } + } + public void addError(IIdType measureId, String error) { if (error == null || error.isBlank()) { return; } errorsPerMeasure - .computeIfAbsent(measureId.toUnqualifiedVersionless(), k -> new ArrayList<>()) - .add(error); + .computeIfAbsent(measureId.toUnqualifiedVersionless(), k -> new ArrayList<>()) + .add(error); } } } diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureLibraryIdEngineDetails.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureLibraryIdEngineDetails.java index 450a234b3a..9120c65404 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureLibraryIdEngineDetails.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureLibraryIdEngineDetails.java @@ -4,4 +4,5 @@ import org.hl7.fhir.instance.model.api.IIdType; import org.opencds.cqf.fhir.cql.LibraryEngine; +// LUKETODO: get rid of this once the new code is firmly in place public record MeasureLibraryIdEngineDetails(IIdType measureId, VersionedIdentifier libraryId, LibraryEngine engine) {} From cf047b6135e7a1d66894df667991d41e694ef29c Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Mon, 14 Jul 2025 17:50:18 -0400 Subject: [PATCH 37/66] Grab changes from branch to integrate with CQL changes to validate library identifiers. Spotless. --- cqf-fhir-benchmark/pom.xml | 7 ++ .../CompositeEvaluationResultsPerMeasure.java | 4 +- .../measure/common/MeasureProcessorUtils.java | 75 ++++++++++--------- .../cr/measure/r4/R4MeasureProcessor.java | 19 ++--- .../cr/cpg/r4/CqlEvaluationServiceTest.java | 10 +-- .../resources/library/asthmatest.json | 2 +- ...{commonCql.cql => SimpleCqlParameters.cql} | 2 +- .../library/SimpleCqlParameters.json | 2 +- .../cql/{SimpleCql.cql => LibrarySimple.cql} | 2 +- .../library/Library-LibrarySimple.json | 2 +- pom.xml | 68 +++++++++-------- 11 files changed, 102 insertions(+), 91 deletions(-) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlParameters/cql/{commonCql.cql => SimpleCqlParameters.cql} (99%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MeasureTest/cql/{SimpleCql.cql => LibrarySimple.cql} (99%) diff --git a/cqf-fhir-benchmark/pom.xml b/cqf-fhir-benchmark/pom.xml index 95f0dfbf25..51a3489457 100644 --- a/cqf-fhir-benchmark/pom.xml +++ b/cqf-fhir-benchmark/pom.xml @@ -54,6 +54,13 @@ + + org.apache.maven.plugins + maven-deploy-plugin + + true + + org.apache.maven.plugins maven-compiler-plugin diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java index 09910f81e2..226cf03c5e 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java @@ -92,8 +92,8 @@ public void addError(IIdType measureId, String error) { } errorsPerMeasure - .computeIfAbsent(measureId.toUnqualifiedVersionless(), k -> new ArrayList<>()) - .add(error); + .computeIfAbsent(measureId.toUnqualifiedVersionless(), k -> new ArrayList<>()) + .add(error); } } } diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java index 0890042ad1..4ed88d0a11 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java @@ -321,10 +321,9 @@ public Object evaluateObservationCriteria( Object result; - context.getState().pushActivationFrame(functionDef, functionDef.getContext()); // LUKETODO: get rid of this -// context.getState().pushWindow(); + // context.getState().pushWindow(); try { if (!isBooleanBasis) { @@ -392,9 +391,11 @@ public CompositeEvaluationResultsPerMeasure getEvaluationResults( subjectId, measureLibraryIdEngine .engine() - // LUKETODO: make CQL CqlEngine changes to take multiple versioned identifiers and then initState for mulitple libraries - // this seems to also reset the cache - // LUKETODO: call the new mulitple versionidentifier method once it's available in CQL + // LUKETODO: make CQL CqlEngine changes to take multiple versioned identifiers and + // then initState for mulitple libraries + // this seems to also reset the cache + // LUKETODO: call the new mulitple versionidentifier method once it's available in + // CQL .getEvaluationResult( measureLibraryIdEngine.libraryId(), subjectId, @@ -419,9 +420,7 @@ public CompositeEvaluationResultsPerMeasure getEvaluationResults( } public CompositeEvaluationResultsPerMeasure getEvaluationResults2( - List subjectIds, - ZonedDateTime zonedMeasurementPeriod, - CqlEngine context) { + List subjectIds, ZonedDateTime zonedMeasurementPeriod, CqlEngine context) { // measure -> subject -> results var resultsBuilder = CompositeEvaluationResultsPerMeasure.builder(); @@ -439,34 +438,38 @@ public CompositeEvaluationResultsPerMeasure getEvaluationResults2( String subjectTypePart = subjectInfo.getLeft(); String subjectIdPart = subjectInfo.getRight(); context.getState().setContextValue(subjectTypePart, subjectIdPart); -// for (var measureLibraryIdEngine : measureLibraryIdEngineDetailsList) { -// try { -// resultsBuilder.addResult( -// measureLibraryIdEngine.measureId(), -// subjectId, -// measureLibraryIdEngine -// .engine() -// // LUKETODO: make CQL CqlEngine changes to take multiple versioned identifiers and then initState for mulitple libraries -// // this seems to also reset the cache -// // LUKETODO: call the new mulitple versionidentifier method once it's available in CQL -// .getEvaluationResult( -// measureLibraryIdEngine.libraryId(), -// subjectId, -// null, -// null, -// null, -// null, -// null, -// zonedMeasurementPeriod, -// context)); -// } catch (Exception e) { -// // Catch Exceptions from evaluation per subject, but allow rest of subjects to be processed (if -// // applicable) -// var error = EXCEPTION_FOR_SUBJECT_ID_MESSAGE_TEMPLATE.formatted(subjectId, e.getMessage()); -// resultsBuilder.addError(measureLibraryIdEngine.measureId(), error); -// logger.error(error, e); -// } -// } + // for (var measureLibraryIdEngine : measureLibraryIdEngineDetailsList) { + // try { + // resultsBuilder.addResult( + // measureLibraryIdEngine.measureId(), + // subjectId, + // measureLibraryIdEngine + // .engine() + // // LUKETODO: make CQL CqlEngine changes to take multiple versioned + // identifiers and then initState for mulitple libraries + // // this seems to also reset the cache + // // LUKETODO: call the new mulitple versionidentifier method once it's + // available in CQL + // .getEvaluationResult( + // measureLibraryIdEngine.libraryId(), + // subjectId, + // null, + // null, + // null, + // null, + // null, + // zonedMeasurementPeriod, + // context)); + // } catch (Exception e) { + // // Catch Exceptions from evaluation per subject, but allow rest of subjects to be + // processed (if + // // applicable) + // var error = EXCEPTION_FOR_SUBJECT_ID_MESSAGE_TEMPLATE.formatted(subjectId, + // e.getMessage()); + // resultsBuilder.addError(measureLibraryIdEngine.measureId(), error); + // logger.error(error, e); + // } + // } } return resultsBuilder.build(); diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java index 47f212a364..13b41e0e45 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java @@ -372,13 +372,11 @@ protected LibraryEngine getLibraryEngine(Parameters parameters, VersionedIdentif protected LibraryEngine getLibraryEngine(Parameters parameters, List ids, CqlEngine context) { - var compileLibraries = ids.stream() - .map(id -> getCompiledLibrary(id, context)) - .toList(); + var compileLibraries = + ids.stream().map(id -> getCompiledLibrary(id, context)).toList(); - var libraries = compileLibraries.stream() - .map(CompiledLibrary::getLibrary) - .toList(); + var libraries = + compileLibraries.stream().map(CompiledLibrary::getLibrary).toList(); context.getState().init(libraries); @@ -436,16 +434,15 @@ protected void setArgParameters(Parameters parameters, CqlEngine context, List paramMap.forEach((paramKey, paramValue) -> context.getState() - .setParameter(includeDef.getLocalIdentifier(), paramKey, paramValue))); + .getIncludes() + .getDef() + .forEach(includeDef -> paramMap.forEach((paramKey, paramValue) -> context.getState() + .setParameter(includeDef.getLocalIdentifier(), paramKey, paramValue))); } } } } - protected Measure resolveByUrl(CanonicalType url) { var parts = Canonicals.getParts(url); var result = this.repository.search( diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/cpg/r4/CqlEvaluationServiceTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/cpg/r4/CqlEvaluationServiceTest.java index 97a8510c5c..d54770db5f 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/cpg/r4/CqlEvaluationServiceTest.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/cpg/r4/CqlEvaluationServiceTest.java @@ -19,7 +19,7 @@ class CqlEvaluationServiceTest { void libraryEvaluationService_inlineAsthma() { var content = """ - library AsthmaTest version '1.0.0' + library asthmatest version '1.0.0' using FHIR version '4.0.1' @@ -31,11 +31,11 @@ void libraryEvaluationService_inlineAsthma() { context Patient - define "Asthma Diagnosis" + define "Asthma Diagnosis": + [Condition: "Asthma"] - [Condition: "Asthma"] - define "Has Asthma Diagnosis": - exists("Asthma Diagnosis") + define "Has Asthma Diagnosis": + exists("Asthma Diagnosis") """; var when = Library.given() .repositoryFor("libraryeval") diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryeval/resources/library/asthmatest.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryeval/resources/library/asthmatest.json index d9ecb7717f..b824bf180a 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryeval/resources/library/asthmatest.json +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryeval/resources/library/asthmatest.json @@ -2,7 +2,7 @@ "resourceType": "Library", "id": "asthmatest", "version": "1.0.0", - "name": "AsthmaTest", + "name": "asthmatest", "status": "draft", "type": { "coding": [ diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlParameters/cql/commonCql.cql b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlParameters/cql/SimpleCqlParameters.cql similarity index 99% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlParameters/cql/commonCql.cql rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlParameters/cql/SimpleCqlParameters.cql index 3895373542..bb54b6d16f 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlParameters/cql/commonCql.cql +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlParameters/cql/SimpleCqlParameters.cql @@ -1,7 +1,7 @@ /* CQL to be used across a variety of tests involving CQL parameters. */ -library commonCql +library SimpleCqlParameters using FHIR version '4.0.1' diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlParameters/resources/library/SimpleCqlParameters.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlParameters/resources/library/SimpleCqlParameters.json index 20e9d27c32..bcb7d0bbea 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlParameters/resources/library/SimpleCqlParameters.json +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlParameters/resources/library/SimpleCqlParameters.json @@ -13,7 +13,7 @@ }, "content": [ { "contentType": "text/cql", - "url": "../../cql/commonCql.cql" + "url": "../../cql/SimpleCqlParameters.cql" } ] } \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MeasureTest/cql/SimpleCql.cql b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MeasureTest/cql/LibrarySimple.cql similarity index 99% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MeasureTest/cql/SimpleCql.cql rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MeasureTest/cql/LibrarySimple.cql index fb6f219048..5011b09a48 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MeasureTest/cql/SimpleCql.cql +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MeasureTest/cql/LibrarySimple.cql @@ -1,4 +1,4 @@ -library SimpleCql +library LibrarySimple using FHIR version '4.0.1' diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MeasureTest/resources/library/Library-LibrarySimple.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MeasureTest/resources/library/Library-LibrarySimple.json index dccc58b656..e689c1b3cf 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MeasureTest/resources/library/Library-LibrarySimple.json +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MeasureTest/resources/library/Library-LibrarySimple.json @@ -12,6 +12,6 @@ }, "content": [ { "contentType": "text/cql", - "url": "../../cql/SimpleCql.cql" + "url": "../../cql/LibrarySimple.cql" } ] } \ No newline at end of file diff --git a/pom.xml b/pom.xml index c83e6dded6..b49d6e734a 100644 --- a/pom.xml +++ b/pom.xml @@ -57,25 +57,30 @@ - - central - https://repo1.maven.org/maven2 - - true - - - central-snapshots - https://central.sonatype.com/repository/maven-snapshots/ - - false - + oss-sonatype + https://oss.sonatype.org/content/repositories/snapshots true + + oss-sonatype-public + https://oss.sonatype.org/content/groups/public/ + + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + + ossrh + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + cqf-fhir-api cqf-fhir-utility @@ -380,22 +385,6 @@ package - - org.sonatype.central - central-publishing-maven-plugin - 0.8.0 - true - - central - clinical-reasoning - - cqf-fhir-benchmark - docs - - true - published - - org.codehaus.mojo buildnumber-maven-plugin @@ -426,6 +415,11 @@ + + org.apache.maven.plugins + maven-deploy-plugin + 3.1.1 + com.diffplug.spotless spotless-maven-plugin @@ -524,6 +518,16 @@ maven-shade-plugin 3.2.4 + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.13 + true + + ossrh + https://oss.sonatype.org/ + + org.apache.maven.plugins maven-enforcer-plugin @@ -661,10 +665,6 @@ org.jacoco jacoco-maven-plugin - - org.sonatype.central - central-publishing-maven-plugin - @@ -692,6 +692,10 @@ org.apache.maven.plugins maven-gpg-plugin + + org.sonatype.plugins + nexus-staging-maven-plugin + From 9226d6b1de28c02c19d93e4dda97e8faa22e5610 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Mon, 14 Jul 2025 18:00:17 -0400 Subject: [PATCH 38/66] Fix compile error. --- .../cqf/fhir/cr/cli/command/MeasureCommand.java | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/cqf-fhir-cr-cli/src/main/java/org/opencds/cqf/fhir/cr/cli/command/MeasureCommand.java b/cqf-fhir-cr-cli/src/main/java/org/opencds/cqf/fhir/cr/cli/command/MeasureCommand.java index 574436e25c..86a60e7db7 100644 --- a/cqf-fhir-cr-cli/src/main/java/org/opencds/cqf/fhir/cr/cli/command/MeasureCommand.java +++ b/cqf-fhir-cr-cli/src/main/java/org/opencds/cqf/fhir/cr/cli/command/MeasureCommand.java @@ -26,10 +26,8 @@ import org.opencds.cqf.fhir.cr.cli.argument.MeasureCommandArgument; import org.opencds.cqf.fhir.cr.cli.command.CqlCommand.SubjectAndResult; import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions; -import org.opencds.cqf.fhir.cr.measure.SubjectProviderOptions; +import org.opencds.cqf.fhir.cr.measure.common.MeasureProcessorUtils; import org.opencds.cqf.fhir.cr.measure.r4.R4MeasureProcessor; -import org.opencds.cqf.fhir.cr.measure.r4.R4RepositorySubjectProvider; -import org.opencds.cqf.fhir.cr.measure.r4.utils.R4MeasureServiceUtils; import picocli.CommandLine.ArgGroup; import picocli.CommandLine.Command; @@ -139,11 +137,7 @@ private static R4MeasureProcessor getR4MeasureProcessor( evaluationOptions.setApplyScoringSetMembership(false); evaluationOptions.setEvaluationSettings(evaluationSettings); - return new R4MeasureProcessor( - repository, - evaluationOptions, - new R4RepositorySubjectProvider(new SubjectProviderOptions()), - new R4MeasureServiceUtils(repository)); + return new R4MeasureProcessor(repository, evaluationOptions, new MeasureProcessorUtils()); } private void writeJsonToFile(String json, String patientId, Path path) { From 45ad38c7b67d32a67d6c7f504616086b601a3704 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Tue, 15 Jul 2025 14:04:51 -0400 Subject: [PATCH 39/66] Experiment more with algorithms. Thus far, if we pass more than one library identifier at a time, we cause definite failures in MultiMeasureServiceTest and intermittent failures on at least one other test if we run the whole module test suite. --- .../opencds/cqf/fhir/cql/LibraryEngine.java | 77 +++++++- .../measure/common/MeasureProcessorUtils.java | 185 +++++++++++++----- .../cr/measure/r4/R4MeasureProcessor.java | 85 +++++++- 3 files changed, 281 insertions(+), 66 deletions(-) diff --git a/cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/LibraryEngine.java b/cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/LibraryEngine.java index 60c6566054..b7a4941274 100644 --- a/cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/LibraryEngine.java +++ b/cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/LibraryEngine.java @@ -25,6 +25,7 @@ import org.hl7.fhir.instance.model.api.IBaseParameters; import org.opencds.cqf.cql.engine.execution.CqlEngine; import org.opencds.cqf.cql.engine.execution.EvaluationResult; +import org.opencds.cqf.cql.engine.execution.SearchableLibraryIdentifier; import org.opencds.cqf.fhir.cql.engine.parameters.CqlFhirParametersConverter; import org.opencds.cqf.fhir.cql.engine.parameters.CqlParameterDefinition; import org.opencds.cqf.fhir.utility.CqfExpression; @@ -325,6 +326,37 @@ public List resolveExpression( return result; } + public Map getEvaluationResult( + List ids, + String patientId, + IBaseParameters parameters, + Map rawParameters, + IBaseBundle additionalData, + Set expressions, + CqlFhirParametersConverter cqlFhirParametersConverter, + @Nullable ZonedDateTime zonedDateTime, + CqlEngine engine) { + + logger.info("ids: {}, patientId: {}, zonedDateTime: {}", ids.stream().map( + VersionedIdentifier::getId).toList(), patientId, zonedDateTime); + + // LUKETODO: better name + var stuff = pre(parameters, rawParameters, additionalData, cqlFhirParametersConverter, engine); + + // LUKETODO: better name + var idsCloned = ids.stream() + .map(id -> new VersionedIdentifier().withId(id.getId())) + .toList(); + + return stuff.engine().evaluate( + idsCloned, + expressions, + buildContextParameter(patientId), + stuff.evaluationParameters(), + null, + zonedDateTime); + } + public EvaluationResult getEvaluationResult( VersionedIdentifier id, String patientId, @@ -336,25 +368,50 @@ public EvaluationResult getEvaluationResult( @Nullable ZonedDateTime zonedDateTime, CqlEngine engine) { + logger.info("id: {}, patientId: {}, zonedDateTime: {}", id.getId(), patientId, zonedDateTime); + + var stuff = pre(parameters, rawParameters, additionalData, cqlFhirParametersConverter, + engine); + + return stuff.engine().evaluate( + new VersionedIdentifier().withId(id.getId()), + expressions, + buildContextParameter(patientId), + stuff.evaluationParameters(), + null, + zonedDateTime); + } + + private Stuff pre( + IBaseParameters parameters, + Map rawParameters, + IBaseBundle additionalData, + CqlFhirParametersConverter cqlFhirParametersConverter, + CqlEngine engine) { + + final CqlFhirParametersConverter cqlFhirParametersConverterToUse; if (cqlFhirParametersConverter == null) { - cqlFhirParametersConverter = Engines.getCqlFhirParametersConverter(repository.fhirContext()); + cqlFhirParametersConverterToUse = Engines.getCqlFhirParametersConverter(repository.fhirContext()); + } else { + cqlFhirParametersConverterToUse = cqlFhirParametersConverter; } // engine context built externally of LibraryEngine? + final CqlEngine engineToUse; if (engine == null) { - engine = Engines.forRepository(repository, settings, additionalData); + engineToUse = Engines.forRepository(repository, settings, additionalData); + } else { + engineToUse = engine; } - var evaluationParameters = cqlFhirParametersConverter.toCqlParameters(parameters); + var evaluationParameters = cqlFhirParametersConverterToUse.toCqlParameters(parameters); if (rawParameters != null && !rawParameters.isEmpty()) { evaluationParameters.putAll(rawParameters); } - return engine.evaluate( - new VersionedIdentifier().withId(id.getId()), - expressions, - buildContextParameter(patientId), - evaluationParameters, - null, - zonedDateTime); + return new Stuff(engineToUse, evaluationParameters); } + + // LUKETODO: + // LUKETODO: better name + private record Stuff(CqlEngine engine, Map evaluationParameters) {} } diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java index 4ed88d0a11..0b99b0c787 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java @@ -4,6 +4,7 @@ import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import com.google.common.collect.ListMultimap; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; import java.time.LocalDateTime; @@ -14,18 +15,24 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import org.apache.commons.collections4.keyvalue.DefaultMapEntry; import org.apache.commons.lang3.tuple.Pair; import org.hl7.elm.r1.FunctionDef; import org.hl7.elm.r1.IntervalTypeSpecifier; import org.hl7.elm.r1.NamedTypeSpecifier; import org.hl7.elm.r1.ParameterDef; +import org.hl7.elm.r1.VersionedIdentifier; +import org.hl7.fhir.instance.model.api.IIdType; import org.opencds.cqf.cql.engine.execution.CqlEngine; import org.opencds.cqf.cql.engine.execution.EvaluationResult; +import org.opencds.cqf.cql.engine.execution.ExpressionResult; import org.opencds.cqf.cql.engine.execution.Libraries; +import org.opencds.cqf.cql.engine.execution.SearchableLibraryIdentifier; import org.opencds.cqf.cql.engine.execution.Variable; import org.opencds.cqf.cql.engine.runtime.Date; import org.opencds.cqf.cql.engine.runtime.DateTime; import org.opencds.cqf.cql.engine.runtime.Interval; +import org.opencds.cqf.fhir.cql.LibraryEngine; import org.opencds.cqf.fhir.cr.measure.constant.MeasureConstants; import org.opencds.cqf.fhir.cr.measure.helper.DateHelper; import org.slf4j.Logger; @@ -322,8 +329,6 @@ public Object evaluateObservationCriteria( Object result; context.getState().pushActivationFrame(functionDef, functionDef.getContext()); - // LUKETODO: get rid of this - // context.getState().pushWindow(); try { if (!isBooleanBasis) { @@ -353,6 +358,7 @@ public CompositeEvaluationResultsPerMeasure getEvaluationResults( subjectIds, zonedMeasurementPeriod, context, List.of(measureLibraryIdEngineDetails)); } + // LUKETODO: redo javadoc /** * method used to execute generate CQL results via Library $evaluate * @@ -386,26 +392,27 @@ public CompositeEvaluationResultsPerMeasure getEvaluationResults( context.getState().setContextValue(subjectTypePart, subjectIdPart); for (var measureLibraryIdEngine : measureLibraryIdEngineDetailsList) { try { - resultsBuilder.addResult( - measureLibraryIdEngine.measureId(), + var libraryId = measureLibraryIdEngine.libraryId(); + logger.info("1234: libraryId: {}", libraryId.getId()); + var evaluationResult = measureLibraryIdEngine + .engine() + // LUKETODO: make CQL CqlEngine changes to take multiple versioned identifiers and + // then initState for mulitple libraries + // this seems to also reset the cache + // LUKETODO: call the new mulitple versionidentifier method once it's available in + // CQL + .getEvaluationResult( + libraryId, subjectId, - measureLibraryIdEngine - .engine() - // LUKETODO: make CQL CqlEngine changes to take multiple versioned identifiers and - // then initState for mulitple libraries - // this seems to also reset the cache - // LUKETODO: call the new mulitple versionidentifier method once it's available in - // CQL - .getEvaluationResult( - measureLibraryIdEngine.libraryId(), - subjectId, - null, - null, - null, - null, - null, - zonedMeasurementPeriod, - context)); + null, + null, + null, + null, + null, + zonedMeasurementPeriod, + context); + + resultsBuilder.addResult(measureLibraryIdEngine.measureId(), subjectId, evaluationResult); } catch (Exception e) { // Catch Exceptions from evaluation per subject, but allow rest of subjects to be processed (if // applicable) @@ -420,7 +427,11 @@ public CompositeEvaluationResultsPerMeasure getEvaluationResults( } public CompositeEvaluationResultsPerMeasure getEvaluationResults2( - List subjectIds, ZonedDateTime zonedMeasurementPeriod, CqlEngine context) { + List subjectIds, + ZonedDateTime zonedMeasurementPeriod, + CqlEngine context, + LibraryEngine libraryEngineForMultipleLibraries, + ListMultimap versionedIdMeasureIdPairs) {// LUKETODO: rename // measure -> subject -> results var resultsBuilder = CompositeEvaluationResultsPerMeasure.builder(); @@ -438,43 +449,109 @@ public CompositeEvaluationResultsPerMeasure getEvaluationResults2( String subjectTypePart = subjectInfo.getLeft(); String subjectIdPart = subjectInfo.getRight(); context.getState().setContextValue(subjectTypePart, subjectIdPart); - // for (var measureLibraryIdEngine : measureLibraryIdEngineDetailsList) { - // try { - // resultsBuilder.addResult( - // measureLibraryIdEngine.measureId(), - // subjectId, - // measureLibraryIdEngine - // .engine() - // // LUKETODO: make CQL CqlEngine changes to take multiple versioned - // identifiers and then initState for mulitple libraries - // // this seems to also reset the cache - // // LUKETODO: call the new mulitple versionidentifier method once it's - // available in CQL - // .getEvaluationResult( - // measureLibraryIdEngine.libraryId(), - // subjectId, - // null, - // null, - // null, - // null, - // null, - // zonedMeasurementPeriod, - // context)); - // } catch (Exception e) { - // // Catch Exceptions from evaluation per subject, but allow rest of subjects to be - // processed (if - // // applicable) - // var error = EXCEPTION_FOR_SUBJECT_ID_MESSAGE_TEMPLATE.formatted(subjectId, - // e.getMessage()); - // resultsBuilder.addError(measureLibraryIdEngine.measureId(), error); - // logger.error(error, e); - // } - // } + try { + // LUKETODO: is library identifier ordering important here? + var libraryIdentifiers = List.copyOf(versionedIdMeasureIdPairs.keySet()); + // LUKETODO: try to do one library at a time and see if there are the same errors: + + for (VersionedIdentifier libraryVersionedIdentifier : libraryIdentifiers) { + var evaluationResultsPerLibraryIdentifier = libraryEngineForMultipleLibraries.getEvaluationResult( + libraryVersionedIdentifier, + subjectId, + null, + null, + null, + null, + null, + zonedMeasurementPeriod, + context); + + var measureIds = versionedIdMeasureIdPairs.get(libraryVersionedIdentifier); + + for (IIdType measureId : measureIds) { + resultsBuilder.addResult( + measureId, + subjectId, + evaluationResultsPerLibraryIdentifier); + } + } + + // LUKETODO: look into the right way to implement this later? + // LUKETODO: if we do it this way, we get invalid interval CQL errors among others: why?????? +// var evaluationResultsPerLibraryIdentifier = libraryEngineForMultipleLibraries.getEvaluationResult( +// libraryIdentifiers, +// subjectId, +// null, +// null, +// null, +// null, +// null, +// zonedMeasurementPeriod, +// context); +// +// for (var libraryVersionedIdentifier: libraryIdentifiers) { +// validateEvaluationResultExistsForIdentifier( +// libraryVersionedIdentifier, +// evaluationResultsPerLibraryIdentifier); +// +// var evaluationResultForLibraryIdentifier = evaluationResultsPerLibraryIdentifier.get(SearchableLibraryIdentifier.fromIdentifier(libraryVersionedIdentifier)); +// +// var measureIds = versionedIdMeasureIdPairs.get(libraryVersionedIdentifier); +// +// for (IIdType measureId : measureIds) { +// resultsBuilder.addResult( +// measureId, +// subjectId, +// evaluationResultForLibraryIdentifier); +// } +// } + + } catch (Exception e) { + // Catch Exceptions from evaluation per subject, but allow rest of subjects to be processed (if + // applicable) + var error = EXCEPTION_FOR_SUBJECT_ID_MESSAGE_TEMPLATE.formatted(subjectId, e.getMessage()); + var measureIds = List.copyOf(versionedIdMeasureIdPairs.values()); + + resultsBuilder.addErrors(measureIds, error); + logger.error(error, e); + } } return resultsBuilder.build(); } + private void validateEvaluationResultExistsForIdentifier( + VersionedIdentifier versionedIdentifierFromQuery, + Map evaluationResultsPerVersionedIdentifier) { + + var doNoneMatch = evaluationResultsPerVersionedIdentifier.entrySet().stream() + .noneMatch( entry -> entry.getKey().matches(versionedIdentifierFromQuery)); + + if (doNoneMatch) { + throw new InternalErrorException( + "Evaluation result in versionless search not found for identifier with ID: %s" + .formatted(versionedIdentifierFromQuery.getId())); + } + } + + private static String printEvaluationResult(EvaluationResult evaluationResult) { + if (evaluationResult == null) { + return "null"; + } + return "EvaluationResult{" + + "expressionResults=" + evaluationResult.expressionResults.entrySet().stream().map(entry -> new DefaultMapEntry<>( + entry.getKey(), printExpressionResult(entry.getValue()))).toList() + "}"; + } + + private static String printExpressionResult(ExpressionResult expressionResult) { + if (expressionResult == null) { + return "null"; + } + + return "ExpressionResult{value=" + expressionResult.value() + + ", type=" + expressionResult.evaluatedResources() + '}'; + } + public Pair getSubjectTypeAndId(String subjectId) { if (subjectId.contains("/")) { String[] subjectIdParts = subjectId.split("/"); diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java index 13b41e0e45..4b05d53899 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java @@ -3,16 +3,22 @@ import ca.uhn.fhir.repository.IRepository; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableListMultimap; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.ListMultimap; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; +import org.apache.commons.lang3.tuple.Pair; import org.cqframework.cql.cql2elm.CqlIncludeException; import org.cqframework.cql.cql2elm.model.CompiledLibrary; import org.hl7.elm.r1.VersionedIdentifier; @@ -275,9 +281,84 @@ public CompositeEvaluationResultsPerMeasure evaluateMultiMeasuresWithCqlEngine( .map(url -> Optional.ofNullable(url).orElse("Unknown Measure URL")) .toList()); + var libraryVersionedIdentifiers = + measures.stream().map(this::getLibraryVersionIdentifier).toList(); + + var libraryEngineForMultipleLibraries = getLibraryEngine(parameters, libraryVersionedIdentifiers, context); + + final List measureIds = + measures.stream().map(Measure::getIdElement).toList(); + +// var versionedIdMeasureIdPairs = getPairs(measures); +// var versionedIdMeasureIdPairs = getPairs2(measures); +// var versionedIdMeasureIdPairs = getPairs3(measures); + var versionedIdMeasureIdPairs = getPairs4(measures); + // populate results from Library $evaluate - return measureProcessorUtils.getEvaluationResults( - subjects, zonedMeasurementPeriod, context, measureLibraryIdEngineDetailsList); + return getEvaluationResults( + subjects, + zonedMeasurementPeriod, + context, + measureLibraryIdEngineDetailsList, + libraryEngineForMultipleLibraries, + versionedIdMeasureIdPairs); + } + + private CompositeEvaluationResultsPerMeasure getEvaluationResults( + List subjects, + ZonedDateTime zonedMeasurementPeriod, + CqlEngine context, + List measureLibraryIdEngineDetailsList, + LibraryEngine libraryEngineForMultipleLibraries, + ListMultimap versionedIdMeasureIdPairs){ + // LUKETODO: flip between these to test +// return measureProcessorUtils.getEvaluationResults( +// subjects, zonedMeasurementPeriod, context, measureLibraryIdEngineDetailsList); + return measureProcessorUtils.getEvaluationResults2( + subjects, + zonedMeasurementPeriod, + context, + libraryEngineForMultipleLibraries, + versionedIdMeasureIdPairs); + } + + private List> getPairs(List measures) { + return measures.stream() + .map(measure -> Pair.of(getLibraryVersionIdentifier(measure), (IIdType) measure.getIdElement())) + .toList(); + } + + private ListMultimap getPairs2(List measures) { + return measures.stream() + .map(measure -> + Map.entry(getLibraryVersionIdentifier(measure), + (IIdType) measure.getIdElement())) + + .collect(ImmutableListMultimap.toImmutableListMultimap( + Map.Entry::getKey, + Map.Entry::getValue)); + } + + private LinkedHashMap> getPairs3(List measures) { + var result = new LinkedHashMap>(); + + for (Measure measure : measures) { + var libraryVersionIdentifier = getLibraryVersionIdentifier(measure); + var measureIds = + result.computeIfAbsent(libraryVersionIdentifier, k -> new ArrayList<>()); + + measureIds.add(measure.getIdElement()); + } + + return result; + } + + private ListMultimap getPairs4(List measures) { + return measures.stream() + .collect(ImmutableListMultimap.toImmutableListMultimap( + this::getLibraryVersionIdentifier, // Key extractor + Measure::getIdElement // Value extractor + )); } // Ideally this would be done in MeasureProcessorUtils, but it's too much work to change for now From 4566eb2927920599e707091264089f286272f0ee Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Tue, 15 Jul 2025 17:04:15 -0400 Subject: [PATCH 40/66] Flip to the new code from the old code in MeasureProcessorUtils. Add a TODO about what needs to be done to the test to make it pass under the new code. Add a TODO about another unexpected failure as well. --- .../measure/common/MeasureProcessorUtils.java | 68 +++++++++---------- .../measure/r4/MultiMeasureServiceTest.java | 8 +++ 2 files changed, 42 insertions(+), 34 deletions(-) diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java index 0b99b0c787..c898834e90 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java @@ -454,32 +454,9 @@ public CompositeEvaluationResultsPerMeasure getEvaluationResults2( var libraryIdentifiers = List.copyOf(versionedIdMeasureIdPairs.keySet()); // LUKETODO: try to do one library at a time and see if there are the same errors: - for (VersionedIdentifier libraryVersionedIdentifier : libraryIdentifiers) { - var evaluationResultsPerLibraryIdentifier = libraryEngineForMultipleLibraries.getEvaluationResult( - libraryVersionedIdentifier, - subjectId, - null, - null, - null, - null, - null, - zonedMeasurementPeriod, - context); - - var measureIds = versionedIdMeasureIdPairs.get(libraryVersionedIdentifier); - - for (IIdType measureId : measureIds) { - resultsBuilder.addResult( - measureId, - subjectId, - evaluationResultsPerLibraryIdentifier); - } - } - - // LUKETODO: look into the right way to implement this later? - // LUKETODO: if we do it this way, we get invalid interval CQL errors among others: why?????? -// var evaluationResultsPerLibraryIdentifier = libraryEngineForMultipleLibraries.getEvaluationResult( -// libraryIdentifiers, +// for (VersionedIdentifier libraryVersionedIdentifier : libraryIdentifiers) { +// var evaluationResultsPerLibraryIdentifier = libraryEngineForMultipleLibraries.getEvaluationResult( +// libraryVersionedIdentifier, // subjectId, // null, // null, @@ -489,23 +466,46 @@ public CompositeEvaluationResultsPerMeasure getEvaluationResults2( // zonedMeasurementPeriod, // context); // -// for (var libraryVersionedIdentifier: libraryIdentifiers) { -// validateEvaluationResultExistsForIdentifier( -// libraryVersionedIdentifier, -// evaluationResultsPerLibraryIdentifier); -// -// var evaluationResultForLibraryIdentifier = evaluationResultsPerLibraryIdentifier.get(SearchableLibraryIdentifier.fromIdentifier(libraryVersionedIdentifier)); -// // var measureIds = versionedIdMeasureIdPairs.get(libraryVersionedIdentifier); // // for (IIdType measureId : measureIds) { // resultsBuilder.addResult( // measureId, // subjectId, -// evaluationResultForLibraryIdentifier); +// evaluationResultsPerLibraryIdentifier); // } // } + // LUKETODO: look into the right way to implement this later? + // LUKETODO: if we do it this way, we get invalid interval CQL errors among others: why?????? + var evaluationResultsPerLibraryIdentifier = libraryEngineForMultipleLibraries.getEvaluationResult( + libraryIdentifiers, + subjectId, + null, + null, + null, + null, + null, + zonedMeasurementPeriod, + context); + + for (var libraryVersionedIdentifier: libraryIdentifiers) { + validateEvaluationResultExistsForIdentifier( + libraryVersionedIdentifier, + evaluationResultsPerLibraryIdentifier); + + var evaluationResultForLibraryIdentifier = evaluationResultsPerLibraryIdentifier.get(SearchableLibraryIdentifier.fromIdentifier(libraryVersionedIdentifier)); + + var measureIds = versionedIdMeasureIdPairs.get(libraryVersionedIdentifier); + + for (IIdType measureId : measureIds) { + resultsBuilder.addResult( + measureId, + subjectId, + evaluationResultForLibraryIdentifier); + } + } + } catch (Exception e) { // Catch Exceptions from evaluation per subject, but allow rest of subjects to be processed (if // applicable) diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java index 90359d90a2..d97e6abc87 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java @@ -304,6 +304,12 @@ void MultiMeasure_EightMeasures_AllSubjects_MeasureUrl() { .up(); } + // LUKETODO: this test is failing because we've exposed an issue with the Encounter for Patient/female-1988-2 + // this encounter has an invalid period, the start later than the end + // we need to remove that encounter (and possibly patient) and add them to a different "IG" and put into a + // different set of failing tests + // then we need to adjust the assertions on this test to cover one less encounter and possibly one less patient + // it's a mystery as to why this test fails under the new caching code but not under the old per library CQL evaluation @Test void MultiMeasure_EightMeasures_SubjectEvalType_AllSubjects() { var when = GIVEN_REPO @@ -427,6 +433,8 @@ void MultiMeasure_EightMeasures_SubjectEvalType_Practitioner() { .hasReportType("Individual"); } + // LUKETODO: this test is not failing because of the invalid interval on the Encounter + // it's failing because evaluatedResourceCount is 3 and not 1, as expected: debug this to figure out why @Test void MultiMeasure_EightMeasures_Patient() { var when = GIVEN_REPO From a8afb99b17fadfa6cab9df44e749ea2793927e4a Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Wed, 16 Jul 2025 16:41:58 -0400 Subject: [PATCH 41/66] Assert that the mulitmeasure "allsubjects" tests still pass but contain InvalidInterval errors. --- .../measure/common/MeasureProcessorUtils.java | 68 +++++++++---------- .../cqf/fhir/cr/measure/r4/Measure.java | 2 +- .../cqf/fhir/cr/measure/r4/MultiMeasure.java | 30 +++++++- .../measure/r4/MultiMeasureServiceTest.java | 22 ++++-- 4 files changed, 79 insertions(+), 43 deletions(-) diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java index c898834e90..0b99b0c787 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java @@ -454,32 +454,9 @@ public CompositeEvaluationResultsPerMeasure getEvaluationResults2( var libraryIdentifiers = List.copyOf(versionedIdMeasureIdPairs.keySet()); // LUKETODO: try to do one library at a time and see if there are the same errors: -// for (VersionedIdentifier libraryVersionedIdentifier : libraryIdentifiers) { -// var evaluationResultsPerLibraryIdentifier = libraryEngineForMultipleLibraries.getEvaluationResult( -// libraryVersionedIdentifier, -// subjectId, -// null, -// null, -// null, -// null, -// null, -// zonedMeasurementPeriod, -// context); -// -// var measureIds = versionedIdMeasureIdPairs.get(libraryVersionedIdentifier); -// -// for (IIdType measureId : measureIds) { -// resultsBuilder.addResult( -// measureId, -// subjectId, -// evaluationResultsPerLibraryIdentifier); -// } -// } - - // LUKETODO: look into the right way to implement this later? - // LUKETODO: if we do it this way, we get invalid interval CQL errors among others: why?????? - var evaluationResultsPerLibraryIdentifier = libraryEngineForMultipleLibraries.getEvaluationResult( - libraryIdentifiers, + for (VersionedIdentifier libraryVersionedIdentifier : libraryIdentifiers) { + var evaluationResultsPerLibraryIdentifier = libraryEngineForMultipleLibraries.getEvaluationResult( + libraryVersionedIdentifier, subjectId, null, null, @@ -489,23 +466,46 @@ public CompositeEvaluationResultsPerMeasure getEvaluationResults2( zonedMeasurementPeriod, context); - for (var libraryVersionedIdentifier: libraryIdentifiers) { - validateEvaluationResultExistsForIdentifier( - libraryVersionedIdentifier, - evaluationResultsPerLibraryIdentifier); - - var evaluationResultForLibraryIdentifier = evaluationResultsPerLibraryIdentifier.get(SearchableLibraryIdentifier.fromIdentifier(libraryVersionedIdentifier)); - var measureIds = versionedIdMeasureIdPairs.get(libraryVersionedIdentifier); for (IIdType measureId : measureIds) { resultsBuilder.addResult( measureId, subjectId, - evaluationResultForLibraryIdentifier); + evaluationResultsPerLibraryIdentifier); } } + // LUKETODO: look into the right way to implement this later? + // LUKETODO: if we do it this way, we get invalid interval CQL errors among others: why?????? +// var evaluationResultsPerLibraryIdentifier = libraryEngineForMultipleLibraries.getEvaluationResult( +// libraryIdentifiers, +// subjectId, +// null, +// null, +// null, +// null, +// null, +// zonedMeasurementPeriod, +// context); +// +// for (var libraryVersionedIdentifier: libraryIdentifiers) { +// validateEvaluationResultExistsForIdentifier( +// libraryVersionedIdentifier, +// evaluationResultsPerLibraryIdentifier); +// +// var evaluationResultForLibraryIdentifier = evaluationResultsPerLibraryIdentifier.get(SearchableLibraryIdentifier.fromIdentifier(libraryVersionedIdentifier)); +// +// var measureIds = versionedIdMeasureIdPairs.get(libraryVersionedIdentifier); +// +// for (IIdType measureId : measureIds) { +// resultsBuilder.addResult( +// measureId, +// subjectId, +// evaluationResultForLibraryIdentifier); +// } +// } + } catch (Exception e) { // Catch Exceptions from evaluation per subject, but allow rest of subjects to be processed (if // applicable) diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/Measure.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/Measure.java index aaf7139edd..4f93a42aed 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/Measure.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/Measure.java @@ -76,7 +76,7 @@ interface Validator { } @FunctionalInterface - interface Selector { + public interface Selector { T select(S from); } diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasure.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasure.java index 3971c844ca..ff422fd204 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasure.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasure.java @@ -55,7 +55,7 @@ interface Validator { } @FunctionalInterface - interface Selector { + public interface Selector { T select(S from); } @@ -294,6 +294,16 @@ public SelectedMeasureReport measureReport(String measureUrl) { measureUrl)); } + public SelectedMeasureReport measureReport(String measureUrl, String subject) { + return this.measureReport(g -> resourceToMeasureReport( + g.getEntry().stream() + .filter(x -> + x.getResource().getResourceType().toString().equals("MeasureReport")) + .toList(), + measureUrl, + subject)); + } + public SelectedMeasureReport getFirstMeasureReport() { var mr = (MeasureReport) report().getEntryFirstRep().getResource(); return this.measureReport(g -> mr); @@ -312,9 +322,23 @@ public MeasureReport resourceToMeasureReport(List entries, } return matchedReport; } + + public MeasureReport resourceToMeasureReport(List entries, String measureUrl, String subject) { + IParser parser = FhirContext.forR4Cached().newJsonParser(); + MeasureReport matchedReport = null; + for (int i = 0; i < entries.size(); i++) { + MeasureReport report = (MeasureReport) parser.parseResource( + parser.encodeResourceToString(entries.get(i).getResource())); + if (report.getMeasure().equals(measureUrl) && report.getSubject().getReference().equals(subject)) { + matchedReport = report; + break; + } + } + return matchedReport; + } } - static class SelectedMeasureReport extends MultiMeasure.Selected { + public static class SelectedMeasureReport extends MultiMeasure.Selected { public MeasureReport report() { return this.value(); } @@ -502,7 +526,7 @@ public SelectedStratifier stratifier( } } - static class SelectedReference

extends MultiMeasure.Selected { + public static class SelectedReference

extends MultiMeasure.Selected { public SelectedReference(Reference value, P parent) { super(value, parent); diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java index d97e6abc87..4071f780d3 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java @@ -69,6 +69,10 @@ void MultiMeasure_EightMeasures_AllSubjects_MeasureId() { .up() .hasScore("0.5714285714285714") .up() + .hasMeasureReportStatus(MeasureReportStatus.ERROR) + .hasContainedOperationOutcome() + .hasContainedOperationOutcomeMsg("Patient/female-1988-2") + .hasContainedOperationOutcomeMsg("Invalid Interval") .up() .measureReport("http://example.com/Measure/MinimalProportionBooleanBasisSingleGroup") .hasReportType("Summary") @@ -207,6 +211,10 @@ void MultiMeasure_EightMeasures_AllSubjects_MeasureUrl() { .up() .hasScore("0.5714285714285714") .up() + .hasMeasureReportStatus(MeasureReportStatus.ERROR) + .hasContainedOperationOutcome() + .hasContainedOperationOutcomeMsg("Patient/female-1988-2") + .hasContainedOperationOutcomeMsg("Invalid Interval") .up() .measureReport("http://example.com/Measure/MinimalProportionBooleanBasisSingleGroup") .hasReportType("Summary") @@ -298,10 +306,7 @@ void MultiMeasure_EightMeasures_AllSubjects_MeasureUrl() { .hasCount(2) .up() .population("measure-observation") - .hasCount(10) - .up() - .up() - .up(); + .hasCount(10); } // LUKETODO: this test is failing because we've exposed an issue with the Encounter for Patient/female-1988-2 @@ -310,6 +315,7 @@ void MultiMeasure_EightMeasures_AllSubjects_MeasureUrl() { // different set of failing tests // then we need to adjust the assertions on this test to cover one less encounter and possibly one less patient // it's a mystery as to why this test fails under the new caching code but not under the old per library CQL evaluation + // LUKETODO: so this test does include the failure, but with the old code we swallow it but with the new code we blow up @Test void MultiMeasure_EightMeasures_SubjectEvalType_AllSubjects() { var when = GIVEN_REPO @@ -337,7 +343,13 @@ void MultiMeasure_EightMeasures_SubjectEvalType_AllSubjects() { .hasMeasureReportCountPerUrl( 10, "http://example.com/Measure/MinimalContinuousVariableResourceBasisSingleGroup") .getFirstMeasureReport() - .hasReportType("Individual"); + .hasReportType("Individual") + .up() + .measureReport("http://example.com/Measure/MinimalProportionNoBasisSingleGroup", "Patient/female-1988-2") + .hasMeasureReportStatus(MeasureReportStatus.ERROR) + .hasContainedOperationOutcome() + .hasContainedOperationOutcomeMsg("Patient/female-1988-2") + .hasContainedOperationOutcomeMsg("Invalid Interval"); } @Test From b95c057549e22d231cdc6d1ff60736aa675ca5c3 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Thu, 17 Jul 2025 11:55:45 -0400 Subject: [PATCH 42/66] Leverage multi-measure error handling code added to CQL. Spotless. Still one test failing that I need to figure out. --- .../opencds/cqf/fhir/cql/LibraryEngine.java | 46 ++--- .../CompositeEvaluationResultsPerMeasure.java | 6 + .../measure/common/MeasureProcessorUtils.java | 160 +++++++++--------- .../cr/measure/r4/R4MeasureProcessor.java | 46 +++-- .../measure/r4/MultiMeasureServiceTest.java | 6 +- 5 files changed, 137 insertions(+), 127 deletions(-) diff --git a/cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/LibraryEngine.java b/cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/LibraryEngine.java index b7a4941274..fb601787c5 100644 --- a/cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/LibraryEngine.java +++ b/cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/LibraryEngine.java @@ -24,8 +24,8 @@ import org.hl7.fhir.instance.model.api.IBaseBundle; import org.hl7.fhir.instance.model.api.IBaseParameters; import org.opencds.cqf.cql.engine.execution.CqlEngine; +import org.opencds.cqf.cql.engine.execution.CqlEngine.EvaluationResultsForMultiLib; import org.opencds.cqf.cql.engine.execution.EvaluationResult; -import org.opencds.cqf.cql.engine.execution.SearchableLibraryIdentifier; import org.opencds.cqf.fhir.cql.engine.parameters.CqlFhirParametersConverter; import org.opencds.cqf.fhir.cql.engine.parameters.CqlParameterDefinition; import org.opencds.cqf.fhir.utility.CqfExpression; @@ -326,7 +326,7 @@ public List resolveExpression( return result; } - public Map getEvaluationResult( + public EvaluationResultsForMultiLib getEvaluationResult( List ids, String patientId, IBaseParameters parameters, @@ -337,8 +337,11 @@ public Map getEvaluationResult( @Nullable ZonedDateTime zonedDateTime, CqlEngine engine) { - logger.info("ids: {}, patientId: {}, zonedDateTime: {}", ids.stream().map( - VersionedIdentifier::getId).toList(), patientId, zonedDateTime); + logger.info( + "ids: {}, patientId: {}, zonedDateTime: {}", + ids.stream().map(VersionedIdentifier::getId).toList(), + patientId, + zonedDateTime); // LUKETODO: better name var stuff = pre(parameters, rawParameters, additionalData, cqlFhirParametersConverter, engine); @@ -348,13 +351,14 @@ public Map getEvaluationResult( .map(id -> new VersionedIdentifier().withId(id.getId())) .toList(); - return stuff.engine().evaluate( - idsCloned, - expressions, - buildContextParameter(patientId), - stuff.evaluationParameters(), - null, - zonedDateTime); + return stuff.engine() + .evaluate( + idsCloned, + expressions, + buildContextParameter(patientId), + stuff.evaluationParameters(), + null, + zonedDateTime); } public EvaluationResult getEvaluationResult( @@ -370,16 +374,16 @@ public EvaluationResult getEvaluationResult( logger.info("id: {}, patientId: {}, zonedDateTime: {}", id.getId(), patientId, zonedDateTime); - var stuff = pre(parameters, rawParameters, additionalData, cqlFhirParametersConverter, - engine); + var stuff = pre(parameters, rawParameters, additionalData, cqlFhirParametersConverter, engine); - return stuff.engine().evaluate( - new VersionedIdentifier().withId(id.getId()), - expressions, - buildContextParameter(patientId), - stuff.evaluationParameters(), - null, - zonedDateTime); + return stuff.engine() + .evaluate( + new VersionedIdentifier().withId(id.getId()), + expressions, + buildContextParameter(patientId), + stuff.evaluationParameters(), + null, + zonedDateTime); } private Stuff pre( @@ -413,5 +417,5 @@ private Stuff pre( // LUKETODO: // LUKETODO: better name - private record Stuff(CqlEngine engine, Map evaluationParameters) {} + private record Stuff(CqlEngine engine, Map evaluationParameters) {} } diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java index 226cf03c5e..78a0e8ab52 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java @@ -69,6 +69,12 @@ public CompositeEvaluationResultsPerMeasure build() { return new CompositeEvaluationResultsPerMeasure(this); } + public void addResults(List measureIds, String subjectId, EvaluationResult evaluationResult) { + for (IIdType measureId : measureIds) { + addResult(measureId, subjectId, evaluationResult); + } + } + public void addResult(IIdType measureId, String subjectId, EvaluationResult evaluationResult) { var resultPerMeasure = resultsPerMeasure.computeIfAbsent(measureId.toUnqualifiedVersionless(), k -> new HashMap<>()); diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java index 0b99b0c787..df7bed8baf 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java @@ -24,6 +24,7 @@ import org.hl7.elm.r1.VersionedIdentifier; import org.hl7.fhir.instance.model.api.IIdType; import org.opencds.cqf.cql.engine.execution.CqlEngine; +import org.opencds.cqf.cql.engine.execution.CqlEngine.EvaluationResultsForMultiLib; import org.opencds.cqf.cql.engine.execution.EvaluationResult; import org.opencds.cqf.cql.engine.execution.ExpressionResult; import org.opencds.cqf.cql.engine.execution.Libraries; @@ -318,6 +319,8 @@ public Object evaluateObservationCriteria( boolean isBooleanBasis, CqlEngine context) { + // LUKETODO: org.opencds.cqf.cql.engine.exception.CqlException: Could not resolve expression reference + // 'MeasureObservation' in library 'MinimalProportionBooleanBasisSingleGroup'. var ed = Libraries.resolveExpressionRef( criteriaExpression, context.getState().getCurrentLibrary()); @@ -395,22 +398,22 @@ public CompositeEvaluationResultsPerMeasure getEvaluationResults( var libraryId = measureLibraryIdEngine.libraryId(); logger.info("1234: libraryId: {}", libraryId.getId()); var evaluationResult = measureLibraryIdEngine - .engine() - // LUKETODO: make CQL CqlEngine changes to take multiple versioned identifiers and - // then initState for mulitple libraries - // this seems to also reset the cache - // LUKETODO: call the new mulitple versionidentifier method once it's available in - // CQL - .getEvaluationResult( - libraryId, - subjectId, - null, - null, - null, - null, - null, - zonedMeasurementPeriod, - context); + .engine() + // LUKETODO: make CQL CqlEngine changes to take multiple versioned identifiers and + // then initState for mulitple libraries + // this seems to also reset the cache + // LUKETODO: call the new mulitple versionidentifier method once it's available in + // CQL + .getEvaluationResult( + libraryId, + subjectId, + null, + null, + null, + null, + null, + zonedMeasurementPeriod, + context); resultsBuilder.addResult(measureLibraryIdEngine.measureId(), subjectId, evaluationResult); } catch (Exception e) { @@ -431,7 +434,7 @@ public CompositeEvaluationResultsPerMeasure getEvaluationResults2( ZonedDateTime zonedMeasurementPeriod, CqlEngine context, LibraryEngine libraryEngineForMultipleLibraries, - ListMultimap versionedIdMeasureIdPairs) {// LUKETODO: rename + ListMultimap versionedIdMeasureIdPairs) { // LUKETODO: rename // measure -> subject -> results var resultsBuilder = CompositeEvaluationResultsPerMeasure.builder(); @@ -454,61 +457,60 @@ public CompositeEvaluationResultsPerMeasure getEvaluationResults2( var libraryIdentifiers = List.copyOf(versionedIdMeasureIdPairs.keySet()); // LUKETODO: try to do one library at a time and see if there are the same errors: - for (VersionedIdentifier libraryVersionedIdentifier : libraryIdentifiers) { - var evaluationResultsPerLibraryIdentifier = libraryEngineForMultipleLibraries.getEvaluationResult( - libraryVersionedIdentifier, - subjectId, - null, - null, - null, - null, - null, - zonedMeasurementPeriod, - context); + // for (VersionedIdentifier libraryVersionedIdentifier : libraryIdentifiers) { + // var evaluationResultsForMultiLib = + // libraryEngineForMultipleLibraries.getEvaluationResult( + // libraryVersionedIdentifier, + // subjectId, + // null, + // null, + // null, + // null, + // null, + // zonedMeasurementPeriod, + // context); + // + // var measureIds = versionedIdMeasureIdPairs.get(libraryVersionedIdentifier); + // + // for (IIdType measureId : measureIds) { + // resultsBuilder.addResult( + // measureId, + // subjectId, + // evaluationResultsForMultiLib); + // } + // } + + // LUKETODO: look into the right way to implement this later? + // LUKETODO: if we do it this way, we get invalid interval CQL errors among others: why?????? + var evaluationResultsForMultiLib = libraryEngineForMultipleLibraries.getEvaluationResult( + libraryIdentifiers, subjectId, null, null, null, null, null, zonedMeasurementPeriod, context); + + var errorsById = evaluationResultsForMultiLib.getErrors(); + + for (var libraryVersionedIdentifier : libraryIdentifiers) { + var searchableLibraryIdentifier = + SearchableLibraryIdentifier.fromIdentifier(libraryVersionedIdentifier); + + validateEvaluationResultExistsForIdentifier( + libraryVersionedIdentifier, evaluationResultsForMultiLib); + + var evaluationResult = evaluationResultsForMultiLib + .getResults() + .getOrDefault(searchableLibraryIdentifier, new EvaluationResult()); var measureIds = versionedIdMeasureIdPairs.get(libraryVersionedIdentifier); - for (IIdType measureId : measureIds) { - resultsBuilder.addResult( - measureId, - subjectId, - evaluationResultsPerLibraryIdentifier); - } - } + resultsBuilder.addResults(measureIds, subjectId, evaluationResult); - // LUKETODO: look into the right way to implement this later? - // LUKETODO: if we do it this way, we get invalid interval CQL errors among others: why?????? -// var evaluationResultsPerLibraryIdentifier = libraryEngineForMultipleLibraries.getEvaluationResult( -// libraryIdentifiers, -// subjectId, -// null, -// null, -// null, -// null, -// null, -// zonedMeasurementPeriod, -// context); -// -// for (var libraryVersionedIdentifier: libraryIdentifiers) { -// validateEvaluationResultExistsForIdentifier( -// libraryVersionedIdentifier, -// evaluationResultsPerLibraryIdentifier); -// -// var evaluationResultForLibraryIdentifier = evaluationResultsPerLibraryIdentifier.get(SearchableLibraryIdentifier.fromIdentifier(libraryVersionedIdentifier)); -// -// var measureIds = versionedIdMeasureIdPairs.get(libraryVersionedIdentifier); -// -// for (IIdType measureId : measureIds) { -// resultsBuilder.addResult( -// measureId, -// subjectId, -// evaluationResultForLibraryIdentifier); -// } -// } + var nullableError = errorsById.get(searchableLibraryIdentifier); + + Optional.ofNullable(errorsById.get(searchableLibraryIdentifier)) + .map(nonNull -> EXCEPTION_FOR_SUBJECT_ID_MESSAGE_TEMPLATE.formatted(subjectId, nonNull)) + .ifPresent(error -> resultsBuilder.addErrors(measureIds, error)); + } } catch (Exception e) { - // Catch Exceptions from evaluation per subject, but allow rest of subjects to be processed (if - // applicable) + // If there's any error we didn't anticipate, catch it here: var error = EXCEPTION_FOR_SUBJECT_ID_MESSAGE_TEMPLATE.formatted(subjectId, e.getMessage()); var measureIds = List.copyOf(versionedIdMeasureIdPairs.values()); @@ -522,15 +524,17 @@ public CompositeEvaluationResultsPerMeasure getEvaluationResults2( private void validateEvaluationResultExistsForIdentifier( VersionedIdentifier versionedIdentifierFromQuery, - Map evaluationResultsPerVersionedIdentifier) { + EvaluationResultsForMultiLib evaluationResultsForMultiLib) { + + var searchableLibraryIdentifier = SearchableLibraryIdentifier.fromIdentifier(versionedIdentifierFromQuery); - var doNoneMatch = evaluationResultsPerVersionedIdentifier.entrySet().stream() - .noneMatch( entry -> entry.getKey().matches(versionedIdentifierFromQuery)); + var containsResults = evaluationResultsForMultiLib.getResults().containsKey(searchableLibraryIdentifier); + var containsError = evaluationResultsForMultiLib.getErrors().containsKey(searchableLibraryIdentifier); - if (doNoneMatch) { + if (!containsResults && !containsError) { throw new InternalErrorException( - "Evaluation result in versionless search not found for identifier with ID: %s" - .formatted(versionedIdentifierFromQuery.getId())); + "Evaluation result in versionless search not found for identifier with ID: %s" + .formatted(versionedIdentifierFromQuery.getId())); } } @@ -538,9 +542,11 @@ private static String printEvaluationResult(EvaluationResult evaluationResult) { if (evaluationResult == null) { return "null"; } - return "EvaluationResult{" + - "expressionResults=" + evaluationResult.expressionResults.entrySet().stream().map(entry -> new DefaultMapEntry<>( - entry.getKey(), printExpressionResult(entry.getValue()))).toList() + "}"; + return "EvaluationResult{" + "expressionResults=" + + evaluationResult.expressionResults.entrySet().stream() + .map(entry -> new DefaultMapEntry<>(entry.getKey(), printExpressionResult(entry.getValue()))) + .toList() + + "}"; } private static String printExpressionResult(ExpressionResult expressionResult) { @@ -548,8 +554,8 @@ private static String printExpressionResult(ExpressionResult expressionResult) { return "null"; } - return "ExpressionResult{value=" + expressionResult.value() + - ", type=" + expressionResult.evaluatedResources() + '}'; + return "ExpressionResult{value=" + expressionResult.value() + ", type=" + expressionResult.evaluatedResources() + + '}'; } public Pair getSubjectTypeAndId(String subjectId) { diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java index 4b05d53899..9c036b965d 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java @@ -3,9 +3,7 @@ import ca.uhn.fhir.repository.IRepository; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableListMultimap; -import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ListMultimap; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; @@ -289,9 +287,9 @@ public CompositeEvaluationResultsPerMeasure evaluateMultiMeasuresWithCqlEngine( final List measureIds = measures.stream().map(Measure::getIdElement).toList(); -// var versionedIdMeasureIdPairs = getPairs(measures); -// var versionedIdMeasureIdPairs = getPairs2(measures); -// var versionedIdMeasureIdPairs = getPairs3(measures); + // var versionedIdMeasureIdPairs = getPairs(measures); + // var versionedIdMeasureIdPairs = getPairs2(measures); + // var versionedIdMeasureIdPairs = getPairs3(measures); var versionedIdMeasureIdPairs = getPairs4(measures); // populate results from Library $evaluate @@ -310,16 +308,16 @@ private CompositeEvaluationResultsPerMeasure getEvaluationResults( CqlEngine context, List measureLibraryIdEngineDetailsList, LibraryEngine libraryEngineForMultipleLibraries, - ListMultimap versionedIdMeasureIdPairs){ + ListMultimap versionedIdMeasureIdPairs) { // LUKETODO: flip between these to test -// return measureProcessorUtils.getEvaluationResults( -// subjects, zonedMeasurementPeriod, context, measureLibraryIdEngineDetailsList); - return measureProcessorUtils.getEvaluationResults2( - subjects, - zonedMeasurementPeriod, - context, - libraryEngineForMultipleLibraries, - versionedIdMeasureIdPairs); + // return measureProcessorUtils.getEvaluationResults( + // subjects, zonedMeasurementPeriod, context, measureLibraryIdEngineDetailsList); + return measureProcessorUtils.getEvaluationResults2( + subjects, + zonedMeasurementPeriod, + context, + libraryEngineForMultipleLibraries, + versionedIdMeasureIdPairs); } private List> getPairs(List measures) { @@ -330,13 +328,8 @@ private List> getPairs(List measures private ListMultimap getPairs2(List measures) { return measures.stream() - .map(measure -> - Map.entry(getLibraryVersionIdentifier(measure), - (IIdType) measure.getIdElement())) - - .collect(ImmutableListMultimap.toImmutableListMultimap( - Map.Entry::getKey, - Map.Entry::getValue)); + .map(measure -> Map.entry(getLibraryVersionIdentifier(measure), (IIdType) measure.getIdElement())) + .collect(ImmutableListMultimap.toImmutableListMultimap(Map.Entry::getKey, Map.Entry::getValue)); } private LinkedHashMap> getPairs3(List measures) { @@ -344,8 +337,7 @@ private LinkedHashMap> getPairs3(List new ArrayList<>()); + var measureIds = result.computeIfAbsent(libraryVersionIdentifier, k -> new ArrayList<>()); measureIds.add(measure.getIdElement()); } @@ -355,10 +347,10 @@ private LinkedHashMap> getPairs3(List getPairs4(List measures) { return measures.stream() - .collect(ImmutableListMultimap.toImmutableListMultimap( - this::getLibraryVersionIdentifier, // Key extractor - Measure::getIdElement // Value extractor - )); + .collect(ImmutableListMultimap.toImmutableListMultimap( + this::getLibraryVersionIdentifier, // Key extractor + Measure::getIdElement // Value extractor + )); } // Ideally this would be done in MeasureProcessorUtils, but it's too much work to change for now diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java index f9aed5ee65..0fb6dfebd1 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java @@ -313,8 +313,10 @@ void MultiMeasure_EightMeasures_AllSubjects_MeasureUrl() { // we need to remove that encounter (and possibly patient) and add them to a different "IG" and put into a // different set of failing tests // then we need to adjust the assertions on this test to cover one less encounter and possibly one less patient - // it's a mystery as to why this test fails under the new caching code but not under the old per library CQL evaluation - // LUKETODO: so this test does include the failure, but with the old code we swallow it but with the new code we blow up + // it's a mystery as to why this test fails under the new caching code but not under the old per library CQL + // evaluation + // LUKETODO: so this test does include the failure, but with the old code we swallow it but with the new code we + // blow up @Test void MultiMeasure_EightMeasures_SubjectEvalType_AllSubjects() { var when = GIVEN_REPO From d21c61a267b9511bb2b3bf93ab3057e379fd3dd1 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Fri, 25 Jul 2025 15:37:54 -0400 Subject: [PATCH 43/66] Set custom version in the pom for now. --- cqf-fhir-api/pom.xml | 4 ++-- cqf-fhir-benchmark/pom.xml | 10 +++++----- cqf-fhir-bom/pom.xml | 20 ++++++++++---------- cqf-fhir-cql/pom.xml | 12 ++++++------ cqf-fhir-cr-cli/pom.xml | 16 ++++++++-------- cqf-fhir-cr-hapi/pom.xml | 2 +- cqf-fhir-cr-spring/pom.xml | 6 +++--- cqf-fhir-cr/pom.xml | 14 +++++++------- cqf-fhir-jackson/pom.xml | 4 ++-- cqf-fhir-jaxb/pom.xml | 4 ++-- cqf-fhir-test/pom.xml | 6 +++--- cqf-fhir-utility/pom.xml | 8 ++++---- docs/pom.xml | 6 +++--- pom.xml | 2 +- 14 files changed, 57 insertions(+), 57 deletions(-) diff --git a/cqf-fhir-api/pom.xml b/cqf-fhir-api/pom.xml index 775768816c..39a6689f2e 100644 --- a/cqf-fhir-api/pom.xml +++ b/cqf-fhir-api/pom.xml @@ -5,14 +5,14 @@ org.opencds.cqf.fhir cqf-fhir-api - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT FHIR Clinical Reasoning (APIs) FHIR Repository APIs used by this project. Implement these to incorporate clinical reasoning on your platform org.opencds.cqf.fhir cqf-fhir - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT diff --git a/cqf-fhir-benchmark/pom.xml b/cqf-fhir-benchmark/pom.xml index f89e5e4476..5452560413 100644 --- a/cqf-fhir-benchmark/pom.xml +++ b/cqf-fhir-benchmark/pom.xml @@ -5,26 +5,26 @@ org.opencds.cqf.fhir cqf-fhir-benchmark - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT FHIR Clinical Reasoning (Benchmarks) Tests validating performance of FHIR Clinical Reasoning operations org.opencds.cqf.fhir cqf-fhir - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT org.opencds.cqf.fhir cqf-fhir-cr - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT org.opencds.cqf.fhir cqf-fhir-test - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT org.openjdk.jmh @@ -39,7 +39,7 @@ org.opencds.cqf.fhir cqf-fhir-jackson - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT pom test diff --git a/cqf-fhir-bom/pom.xml b/cqf-fhir-bom/pom.xml index fbcd41695e..edf4e4a710 100644 --- a/cqf-fhir-bom/pom.xml +++ b/cqf-fhir-bom/pom.xml @@ -5,7 +5,7 @@ org.opencds.cqf.fhir cqf-fhir-bom - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT pom FHIR Clinical Reasoning (Bill Of Materials) This bom can be used to simplify dependency management when using this project @@ -13,7 +13,7 @@ org.opencds.cqf.fhir cqf-fhir - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT @@ -21,44 +21,44 @@ org.opencds.cqf.fhir cqf-fhir-api - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT org.opencds.cqf.fhir cqf-fhir-test - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT org.opencds.cqf.fhir cqf-fhir-utility - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT org.opencds.cqf.fhir cqf-fhir-cql - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT org.opencds.cqf.fhir cqf-fhir-jackson - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT pom org.opencds.cqf.fhir cqf-fhir-jaxb - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT pom org.opencds.cqf.fhir cqf-fhir-cr - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT org.opencds.cqf.fhir cqf-fhir-cr-cli - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT diff --git a/cqf-fhir-cql/pom.xml b/cqf-fhir-cql/pom.xml index c9bb2c8e93..8cdfd7e633 100644 --- a/cqf-fhir-cql/pom.xml +++ b/cqf-fhir-cql/pom.xml @@ -5,26 +5,26 @@ org.opencds.cqf.fhir cqf-fhir-cql - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT FHIR Clinical Reasoning (CQL) Tools, utilities, code gen to support CQL in FHIR Clinical Reasoning operations org.opencds.cqf.fhir cqf-fhir - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT org.opencds.cqf.fhir cqf-fhir-api - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT org.opencds.cqf.fhir cqf-fhir-utility - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT org.mockito @@ -62,13 +62,13 @@ org.opencds.cqf.fhir cqf-fhir-test - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT test org.opencds.cqf.fhir cqf-fhir-jackson - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT pom test diff --git a/cqf-fhir-cr-cli/pom.xml b/cqf-fhir-cr-cli/pom.xml index cc9a0b006a..5836a62d32 100644 --- a/cqf-fhir-cr-cli/pom.xml +++ b/cqf-fhir-cr-cli/pom.xml @@ -6,36 +6,36 @@ org.opencds.cqf.fhir cqf-fhir-cr-cli - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT FHIR Clinical Reasoning (CLI) CLI for running FHIR Clincial Reasoning operations org.opencds.cqf.fhir cqf-fhir - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT org.opencds.cqf.fhir cqf-fhir-api - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT org.opencds.cqf.fhir cqf-fhir-utility - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT org.opencds.cqf.fhir cqf-fhir-cql - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT org.opencds.cqf.fhir cqf-fhir-jackson - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT pom @@ -69,13 +69,13 @@ org.opencds.cqf.fhir cqf-fhir-test - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT test org.opencds.cqf.fhir cqf-fhir-cr - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT compile diff --git a/cqf-fhir-cr-hapi/pom.xml b/cqf-fhir-cr-hapi/pom.xml index 3d63d6cb13..34f425648f 100644 --- a/cqf-fhir-cr-hapi/pom.xml +++ b/cqf-fhir-cr-hapi/pom.xml @@ -6,7 +6,7 @@ org.opencds.cqf.fhir cqf-fhir - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT cqf-fhir-cr-hapi diff --git a/cqf-fhir-cr-spring/pom.xml b/cqf-fhir-cr-spring/pom.xml index 098a294be7..7e519f07fc 100644 --- a/cqf-fhir-cr-spring/pom.xml +++ b/cqf-fhir-cr-spring/pom.xml @@ -7,21 +7,21 @@ org.opencds.cqf.fhir cqf-fhir-cr-spring - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT FHIR Clinical Reasoning (Spring) Spring configurations for FHIR Clinical Reasoning org.opencds.cqf.fhir cqf-fhir - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT org.opencds.cqf.fhir cqf-fhir-cr - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT org.springframework diff --git a/cqf-fhir-cr/pom.xml b/cqf-fhir-cr/pom.xml index 83996fbc3f..6638766e77 100644 --- a/cqf-fhir-cr/pom.xml +++ b/cqf-fhir-cr/pom.xml @@ -5,45 +5,45 @@ org.opencds.cqf.fhir cqf-fhir-cr - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT FHIR Clinical Reasoning (Operations) Implementations of clinical reasoning operations org.opencds.cqf.fhir cqf-fhir - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT org.opencds.cqf.fhir cqf-fhir-api - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT org.opencds.cqf.fhir cqf-fhir-cql - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT org.opencds.cqf.fhir cqf-fhir-utility - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT org.opencds.cqf.fhir cqf-fhir-jackson - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT pom test org.opencds.cqf.fhir cqf-fhir-test - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT test diff --git a/cqf-fhir-jackson/pom.xml b/cqf-fhir-jackson/pom.xml index 290a3e43dc..d50220352d 100644 --- a/cqf-fhir-jackson/pom.xml +++ b/cqf-fhir-jackson/pom.xml @@ -5,7 +5,7 @@ org.opencds.cqf.fhir cqf-fhir-jackson - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT pom FHIR Clinical Reasoning (Jackson) Aggregator module for Jackson dependencies @@ -13,7 +13,7 @@ org.opencds.cqf.fhir cqf-fhir - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT diff --git a/cqf-fhir-jaxb/pom.xml b/cqf-fhir-jaxb/pom.xml index 97258fd1d0..4b52755cf6 100644 --- a/cqf-fhir-jaxb/pom.xml +++ b/cqf-fhir-jaxb/pom.xml @@ -5,7 +5,7 @@ org.opencds.cqf.fhir cqf-fhir-jaxb - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT pom FHIR Clinical Reasoning (JAXB) Aggregator module for JAXB dependencies @@ -13,7 +13,7 @@ org.opencds.cqf.fhir cqf-fhir - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT diff --git a/cqf-fhir-test/pom.xml b/cqf-fhir-test/pom.xml index 26be3bb20f..1bdd08587c 100644 --- a/cqf-fhir-test/pom.xml +++ b/cqf-fhir-test/pom.xml @@ -5,21 +5,21 @@ org.opencds.cqf.fhir cqf-fhir-test - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT FHIR Clinical Reasoning (Test Utilities) Utilities to support unit testing clinical reasoning operations org.opencds.cqf.fhir cqf-fhir - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT org.opencds.cqf.fhir cqf-fhir-api - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT ca.uhn.hapi.fhir diff --git a/cqf-fhir-utility/pom.xml b/cqf-fhir-utility/pom.xml index 4530ac6eb0..a0819de0e9 100644 --- a/cqf-fhir-utility/pom.xml +++ b/cqf-fhir-utility/pom.xml @@ -5,21 +5,21 @@ org.opencds.cqf.fhir cqf-fhir-utility - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT FHIR Clinical Reasoning (Utilities) Utilities to help develop clinical reasoning operations org.opencds.cqf.fhir cqf-fhir - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT org.opencds.cqf.fhir cqf-fhir-api - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT info.cqframework @@ -89,7 +89,7 @@ org.opencds.cqf.fhir cqf-fhir-test - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT test diff --git a/docs/pom.xml b/docs/pom.xml index 3f8a9acb29..2fe483abef 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -5,7 +5,7 @@ org.opencds.cqf.fhir docs - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT FHIR Clinical Reasoning (Documentation) Documentation website for FHIR Clinical Reasoning @@ -13,7 +13,7 @@ org.opencds.cqf.fhir cqf-fhir - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT @@ -23,7 +23,7 @@ cqf-fhir-bom pom import - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT diff --git a/pom.xml b/pom.xml index 2e35b13186..28c3a0c9b7 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.opencds.cqf.fhir cqf-fhir - 3.24.0-SNAPSHOT + 3.25.0-SNAPSHOT pom FHIR Clinical Reasoning FHIR Clinical Reasoning Implementations From d4eefb540d7e4691881ccebc331f6f6378175cb6 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Fri, 25 Jul 2025 15:42:26 -0400 Subject: [PATCH 44/66] Add TODO. Reverse a bunch of logging changes. Ensure we don't queue up an evaluation result if the library did not evaluate successfully. --- .../CompositeEvaluationResultsPerMeasure.java | 6 ++ .../measure/common/MeasureProcessorUtils.java | 60 +++++++++++++++---- .../cr/measure/r4/R4MultiMeasureService.java | 1 + .../measure/r4/MultiMeasureServiceTest.java | 9 --- 4 files changed, 55 insertions(+), 21 deletions(-) diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java index 78a0e8ab52..90379973d9 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java @@ -76,6 +76,12 @@ public void addResults(List measureIds, String subjectId, EvaluationRes } public void addResult(IIdType measureId, String subjectId, EvaluationResult evaluationResult) { + // LUKETODO: figure out how to test this: + // if we have no results, we don't need to add anything + if (evaluationResult == null || evaluationResult.expressionResults.isEmpty()) { + return; + } + var resultPerMeasure = resultsPerMeasure.computeIfAbsent(measureId.toUnqualifiedVersionless(), k -> new HashMap<>()); diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java index df7bed8baf..efdcff593a 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java @@ -11,10 +11,12 @@ import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.time.ZonedDateTime; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; import org.apache.commons.collections4.keyvalue.DefaultMapEntry; import org.apache.commons.lang3.tuple.Pair; import org.hl7.elm.r1.FunctionDef; @@ -22,6 +24,7 @@ import org.hl7.elm.r1.NamedTypeSpecifier; import org.hl7.elm.r1.ParameterDef; import org.hl7.elm.r1.VersionedIdentifier; +import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.opencds.cqf.cql.engine.execution.CqlEngine; import org.opencds.cqf.cql.engine.execution.CqlEngine.EvaluationResultsForMultiLib; @@ -296,6 +299,7 @@ public void captureEvaluatedResources(Set outEvaluatedResources, CqlEngi clearEvaluatedResources(context); } + // LUKETODO: why do we bother if the next evaluation will flush them anyway? // reset evaluated resources followed by a context evaluation private void clearEvaluatedResources(CqlEngine context) { context.getState().clearEvaluatedResources(); @@ -337,9 +341,7 @@ public Object evaluateObservationCriteria( if (!isBooleanBasis) { // subject based observations don't have a parameter to pass in context.getState() - .push(new Variable( - ((FunctionDef) ed).getOperand().get(0).getName()) - .withValue(resource)); + .push(new Variable(functionDef.getOperand().get(0).getName()).withValue(resource)); } result = context.getEvaluationVisitor().visitExpression(ed.getExpression(), context.getState()); } finally { @@ -459,7 +461,7 @@ public CompositeEvaluationResultsPerMeasure getEvaluationResults2( // for (VersionedIdentifier libraryVersionedIdentifier : libraryIdentifiers) { // var evaluationResultsForMultiLib = - // libraryEngineForMultipleLibraries.getEvaluationResult( + // libraryEngineForMultipleLibraries.getEvaluationResult( // libraryVersionedIdentifier, // subjectId, // null, @@ -496,14 +498,21 @@ public CompositeEvaluationResultsPerMeasure getEvaluationResults2( var evaluationResult = evaluationResultsForMultiLib .getResults() - .getOrDefault(searchableLibraryIdentifier, new EvaluationResult()); + .getOrDefault( + searchableLibraryIdentifier, + null); // LUKETODO: I think this is the problem: we pass an empty EvaluationResult + // instead of doing what the old code did + + logger.info( + "evalResult for id: {}: {}, ", + libraryVersionedIdentifier.getId(), + printEvaluationResult(evaluationResult)); var measureIds = versionedIdMeasureIdPairs.get(libraryVersionedIdentifier); resultsBuilder.addResults(measureIds, subjectId, evaluationResult); - var nullableError = errorsById.get(searchableLibraryIdentifier); - + // LUKETODO: add this to the composite class instead? Optional.ofNullable(errorsById.get(searchableLibraryIdentifier)) .map(nonNull -> EXCEPTION_FOR_SUBJECT_ID_MESSAGE_TEMPLATE.formatted(subjectId, nonNull)) .ifPresent(error -> resultsBuilder.addErrors(measureIds, error)); @@ -522,6 +531,16 @@ public CompositeEvaluationResultsPerMeasure getEvaluationResults2( return resultsBuilder.build(); } + private static String showEvaluatedResource(Object evaluatedResourceOrSomething) { + if (evaluatedResourceOrSomething instanceof IBaseResource resource) { + return resource.getIdElement().getValueAsString(); + } else if (evaluatedResourceOrSomething != null) { + return evaluatedResourceOrSomething.toString(); + } else { + return "null"; + } + } + private void validateEvaluationResultExistsForIdentifier( VersionedIdentifier versionedIdentifierFromQuery, EvaluationResultsForMultiLib evaluationResultsForMultiLib) { @@ -542,20 +561,37 @@ private static String printEvaluationResult(EvaluationResult evaluationResult) { if (evaluationResult == null) { return "null"; } - return "EvaluationResult{" + "expressionResults=" + return "\nEvaluationResult{" + "expressionResults=\n" + evaluationResult.expressionResults.entrySet().stream() .map(entry -> new DefaultMapEntry<>(entry.getKey(), printExpressionResult(entry.getValue()))) - .toList() - + "}"; + .map(entry -> entry.getKey() + ": " + entry.getValue()) + .collect(Collectors.joining("\n")) + + "}\n"; } private static String printExpressionResult(ExpressionResult expressionResult) { if (expressionResult == null) { + return "\nnull"; + } + + return "\nExpressionResult{value=" + showValue(expressionResult.value()) + ", type=" + + showEvaluatedResources(expressionResult.evaluatedResources()) + '}'; + } + + private static String showValue(Object valueOrCollection) { + if (valueOrCollection == null) { return "null"; } + if (valueOrCollection instanceof Collection collection) { + return showEvaluatedResources(collection); + } + return showEvaluatedResource(valueOrCollection); + } - return "ExpressionResult{value=" + expressionResult.value() + ", type=" + expressionResult.evaluatedResources() - + '}'; + public static String showEvaluatedResources(Collection evaluatedResourcesOrSomethings) { + return evaluatedResourcesOrSomethings.stream() + .map(MeasureProcessorUtils::showEvaluatedResource) + .collect(Collectors.joining(", ", "[", "]")); } public Pair getSubjectTypeAndId(String subjectId) { diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java index 689388fc1d..5fbb03ffc9 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java @@ -174,6 +174,7 @@ protected void populationMeasureReport( String productLine, String reporter) { + // LUKETODO: ensure this counter works correctly in the new world var totalMeasures = measures.size(); for (Measure measure : measures) { MeasureReport measureReport; diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java index 0fb6dfebd1..fdc4cdf196 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java @@ -308,15 +308,6 @@ void MultiMeasure_EightMeasures_AllSubjects_MeasureUrl() { .hasCount(10); } - // LUKETODO: this test is failing because we've exposed an issue with the Encounter for Patient/female-1988-2 - // this encounter has an invalid period, the start later than the end - // we need to remove that encounter (and possibly patient) and add them to a different "IG" and put into a - // different set of failing tests - // then we need to adjust the assertions on this test to cover one less encounter and possibly one less patient - // it's a mystery as to why this test fails under the new caching code but not under the old per library CQL - // evaluation - // LUKETODO: so this test does include the failure, but with the old code we swallow it but with the new code we - // blow up @Test void MultiMeasure_EightMeasures_SubjectEvalType_AllSubjects() { var when = GIVEN_REPO From 0a32fbb9deb7c3b9b848c8961b81cab1bdc1ade7 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Fri, 25 Jul 2025 17:26:04 -0400 Subject: [PATCH 45/66] Attempt to set up a successful complex layer library test but no success yet. --- .../cqf/fhir/cr/measure/r4/MultiMeasure.java | 6 +- .../r4/MultiLibEvalComplexCql/cql/Level1A.cql | 28 ++++++++ .../r4/MultiLibEvalComplexCql/cql/Level1B.cql | 28 ++++++++ .../r4/MultiLibEvalComplexCql/cql/Level2.cql | 14 ++++ .../r4/MultiLibEvalComplexCql/cql/Level3A.cql | 13 ++++ .../r4/MultiLibEvalComplexCql/cql/Level3B.cql | 11 ++++ .../r4/MultiLibEvalComplexCql/cql/Level4.cql | 12 ++++ .../r4/MultiLibEvalComplexCql/cql/Level5.cql | 26 ++++++++ .../resources/library/Level1A.json | 17 +++++ .../resources/library/Level1B.json | 17 +++++ .../resources/library/Level2.json | 17 +++++ .../resources/library/Level3A.json | 17 +++++ .../resources/library/Level3B.json | 17 +++++ .../resources/library/Level4.json | 17 +++++ .../resources/library/Level5.json | 17 +++++ .../resources/measure/Level1A.json | 64 +++++++++++++++++++ .../resources/measure/Level1B.json | 64 +++++++++++++++++++ .../tests/encounter/enc_arrived_patient1.json | 12 ++++ .../tests/encounter/enc_arrived_patient2.json | 12 ++++ .../tests/encounter/enc_arrived_patient3.json | 12 ++++ .../encounter/enc_cancelled_patient1.json | 12 ++++ .../encounter/enc_cancelled_patient2.json | 12 ++++ .../encounter/enc_cancelled_patient3.json | 12 ++++ .../encounter/enc_finished_patient1.json | 12 ++++ .../encounter/enc_finished_patient2.json | 12 ++++ .../encounter/enc_finished_patient3.json | 12 ++++ .../tests/encounter/enc_planned_patient1.json | 12 ++++ .../tests/encounter/enc_planned_patient2.json | 12 ++++ .../tests/encounter/enc_planned_patient3.json | 12 ++++ .../tests/encounter/enc_triaged_patient1.json | 12 ++++ .../tests/encounter/enc_triaged_patient2.json | 12 ++++ .../tests/encounter/enc_triaged_patient3.json | 12 ++++ .../tests/patient/patient1.json | 6 ++ .../tests/patient/patient2.json | 6 ++ .../tests/patient/patient3.json | 6 ++ 35 files changed, 580 insertions(+), 3 deletions(-) create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level1A.cql create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level1B.cql create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level2.cql create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level3A.cql create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level3B.cql create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level4.cql create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level5.cql create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/library/Level1A.json create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/library/Level1B.json create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/library/Level2.json create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/library/Level3A.json create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/library/Level3B.json create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/library/Level4.json create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/library/Level5.json create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/measure/Level1A.json create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/measure/Level1B.json create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_patient1.json create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_patient2.json create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_patient3.json create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_cancelled_patient1.json create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_cancelled_patient2.json create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_cancelled_patient3.json create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_finished_patient1.json create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_finished_patient2.json create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_finished_patient3.json create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_patient1.json create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_patient2.json create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_patient3.json create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_triaged_patient1.json create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_triaged_patient2.json create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_triaged_patient3.json create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/patient/patient1.json create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/patient/patient2.json create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/patient/patient3.json diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasure.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasure.java index 67c579a3ae..3937cb8a36 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasure.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasure.java @@ -50,7 +50,7 @@ class MultiMeasure { public static final String CLASS_PATH = "org/opencds/cqf/fhir/cr/measure/r4"; @FunctionalInterface - interface Validator { + public interface Validator { void validate(T value); } @@ -480,7 +480,7 @@ public SelectedMeasureReport hasPeriodEnd(Date periodEnd) { } } - static class SelectedGroup extends MultiMeasure.Selected { + public static class SelectedGroup extends MultiMeasure.Selected { public SelectedGroup(MeasureReportGroupComponent value, SelectedMeasureReport parent) { super(value, parent); @@ -558,7 +558,7 @@ public SelectedReference

hasPopulations(String... population) { } } - static class SelectedPopulation + public static class SelectedPopulation extends MultiMeasure.Selected { public SelectedPopulation(MeasureReportGroupPopulationComponent value, SelectedGroup parent) { diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level1A.cql b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level1A.cql new file mode 100644 index 0000000000..7282d71f3c --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level1A.cql @@ -0,0 +1,28 @@ +library Level1A + +using FHIR version '4.0.1' + +include FHIRHelpers version '4.0.1' + +// Level 5 > { 'arrived', 'finished', 'in-progress', 'planned', 'triaged' } +// Level 3A > { 'arrived', 'in-progress', 'planned', 'triaged' } +// Level 2 > { 'arrived', 'in-progress', 'planned' } +include Level2 called Level2 + +parameter "Measurement Period" Interval default Interval[@2021-01-01T00:00:00.000, @2022-01-01T00:00:00.000) + +define "Encounters A": + Level2."Encounters" E + where E.status.value in { 'arrived', 'cancelled', 'in-progress', 'planned' } + return E.status.value + +define "Encounters A Combined": + Combine("Encounters A", ', ') + +define "Encounters B": + Level2."Encounters" E + where E.status.value in { 'cancelled', 'finished', 'in-progress', 'planned' } + return E.status.value + +define "Encounters B Combined": + Combine("Encounters B", ', ') diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level1B.cql b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level1B.cql new file mode 100644 index 0000000000..56ddab2c49 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level1B.cql @@ -0,0 +1,28 @@ +library Level1B + +using FHIR version '4.0.1' + +include FHIRHelpers version '4.0.1' + +include Level2 called Level2 +include Level3B called Level3B + +parameter "Measurement Period" Interval default Interval[@2021-07-01T00:00:00.000, @2022-07-01T00:00:00.000) + +define "Encounters A": + Level2."Encounters" E + where E.status.value in { 'arrived', 'cancelled', 'in-progress', 'planned' } and + E.subject.reference in Level3B."Encounter Subjects" + return E.id + +define "Encounters A Combined": + Combine("Encounters A", ', ') + +define "Encounter B": + Level2."Encounters" E + where E.status.value in { 'finished', 'cancelled', 'in-progress', 'planned' } and + E.subject.reference in Level3B."Encounter Subjects" + return E.id + +define "Encounters B Combined": + Combine("Encounters B", ', ') diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level2.cql b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level2.cql new file mode 100644 index 0000000000..1f463516be --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level2.cql @@ -0,0 +1,14 @@ +library Level2 + +using FHIR version '4.0.1' + +include FHIRHelpers version '4.0.1' + +// Level 5 > { 'arrived', 'finished', 'in-progress', 'planned', 'triaged' } +// Level 3A > { 'arrived', 'in-progress', 'planned', 'triaged' } +include Level3A called Level3A + +// Level 2 > { 'arrived', 'in-progress', 'planned' } +define "Encounters": + Level3A."Encounters" E + where not(E.status.value in { 'triaged' }) \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level3A.cql b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level3A.cql new file mode 100644 index 0000000000..6d29c55108 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level3A.cql @@ -0,0 +1,13 @@ +library Level3A + +using FHIR version '4.0.1' + +include FHIRHelpers version '4.0.1' + +// Level 5 > { 'arrived', 'finished', 'in-progress', 'planned', 'triaged' } +include Level5 called Level5 + +// Level 3A > { 'arrived', 'in-progress', 'planned', 'triaged' } +define "Encounters": + Level5."Encounters" E + where not(E.status.value in { 'finished' }) diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level3B.cql b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level3B.cql new file mode 100644 index 0000000000..d3b8092601 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level3B.cql @@ -0,0 +1,11 @@ +library Level3B + +using FHIR version '4.0.1' + +include FHIRHelpers version '4.0.1' + +include Level4 called Level4 + +define "Encounter Subjects": + Level4."Encounter Subjects" S + where S != 'Patient/pat2' diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level4.cql b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level4.cql new file mode 100644 index 0000000000..2797a27f94 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level4.cql @@ -0,0 +1,12 @@ +library Level4 + +using FHIR version '4.0.1' + +include FHIRHelpers version '4.0.1' + +include Level5 called Level5 + +define "Encounter Subjects": + Level5."Encounters" E + where E.subject.reference != 'Patient/pat3' + return E.subject.reference diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level5.cql b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level5.cql new file mode 100644 index 0000000000..d28763e8da --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level5.cql @@ -0,0 +1,26 @@ +library Level5 + +using FHIR version '4.0.1' + +include FHIRHelpers version '4.0.1' + +context Patient + +/* +all statuses: +---- +planned +arrived +triaged +in-progress +onleave +finished +cancelled +entered-in-error +unknown +*/ + +// Level 5 > { 'arrived', 'finished', 'in-progress', 'planned', 'triaged' } +define "Encounters": + [Encounter] E + where E.status.value in { 'arrived', 'planned', 'triaged', 'finished', 'in-progress' } diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/library/Level1A.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/library/Level1A.json new file mode 100644 index 0000000000..0d0a36c46e --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/library/Level1A.json @@ -0,0 +1,17 @@ +{ + "resourceType": "Library", + "id": "Level1A", + "url": "http://example.com/Library/Level1A", + "name": "Level1A", + "status": "active", + "type": { + "coding": [ { + "system": "http://terminology.hl7.org/CodeSystem/library-type", + "code": "logic-library" + } ] + }, + "content": [ { + "contentType": "text/cql", + "url": "../../cql/Level1A.cql" + } ] +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/library/Level1B.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/library/Level1B.json new file mode 100644 index 0000000000..95896df10c --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/library/Level1B.json @@ -0,0 +1,17 @@ +{ + "resourceType": "Library", + "id": "Level1B", + "url": "http://example.com/Library/Level1B", + "name": "Level1B", + "status": "active", + "type": { + "coding": [ { + "system": "http://terminology.hl7.org/CodeSystem/library-type", + "code": "logic-library" + } ] + }, + "content": [ { + "contentType": "text/cql", + "url": "../../cql/Level1B.cql" + } ] +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/library/Level2.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/library/Level2.json new file mode 100644 index 0000000000..0ddf2d4277 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/library/Level2.json @@ -0,0 +1,17 @@ +{ + "resourceType": "Library", + "id": "Level2", + "url": "http://example.com/Library/Level2", + "name": "Level2", + "status": "active", + "type": { + "coding": [ { + "system": "http://terminology.hl7.org/CodeSystem/library-type", + "code": "logic-library" + } ] + }, + "content": [ { + "contentType": "text/cql", + "url": "../../cql/Level2.cql" + } ] +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/library/Level3A.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/library/Level3A.json new file mode 100644 index 0000000000..6b07d64303 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/library/Level3A.json @@ -0,0 +1,17 @@ +{ + "resourceType": "Library", + "id": "Level3A", + "url": "http://example.com/Library/Level3A", + "name": "Level3A", + "status": "active", + "type": { + "coding": [ { + "system": "http://terminology.hl7.org/CodeSystem/library-type", + "code": "logic-library" + } ] + }, + "content": [ { + "contentType": "text/cql", + "url": "../../cql/Level3A.cql" + } ] +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/library/Level3B.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/library/Level3B.json new file mode 100644 index 0000000000..0d56d7abee --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/library/Level3B.json @@ -0,0 +1,17 @@ +{ + "resourceType": "Library", + "id": "Level3B", + "url": "http://example.com/Library/Level3B", + "name": "Level3B", + "status": "active", + "type": { + "coding": [ { + "system": "http://terminology.hl7.org/CodeSystem/library-type", + "code": "logic-library" + } ] + }, + "content": [ { + "contentType": "text/cql", + "url": "../../cql/Level3B.cql" + } ] +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/library/Level4.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/library/Level4.json new file mode 100644 index 0000000000..66aa069b51 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/library/Level4.json @@ -0,0 +1,17 @@ +{ + "resourceType": "Library", + "id": "Level4", + "url": "http://example.com/Library/Level4", + "name": "Level4", + "status": "active", + "type": { + "coding": [ { + "system": "http://terminology.hl7.org/CodeSystem/library-type", + "code": "logic-library" + } ] + }, + "content": [ { + "contentType": "text/cql", + "url": "../../cql/Level4.cql" + } ] +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/library/Level5.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/library/Level5.json new file mode 100644 index 0000000000..f55154f74b --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/library/Level5.json @@ -0,0 +1,17 @@ +{ + "resourceType": "Library", + "id": "Level5", + "url": "http://example.com/Library/Level5", + "name": "Level5", + "status": "active", + "type": { + "coding": [ { + "system": "http://terminology.hl7.org/CodeSystem/library-type", + "code": "logic-library" + } ] + }, + "content": [ { + "contentType": "text/cql", + "url": "../../cql/Level5.cql" + } ] +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/measure/Level1A.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/measure/Level1A.json new file mode 100644 index 0000000000..8f96142032 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/measure/Level1A.json @@ -0,0 +1,64 @@ + +{ + "id": "Level1A", + "resourceType": "Measure", + "name": "Level1A", + "url": "http://example.com/Measure/Level1A", + "library": [ + "http://example.com/Library/Level1A" + ], + "identifier": [ { + "use": "official", + "value": "124" + } ], + "extension": [ { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-populationBasis", + "valueCode": "boolean" + } ], + "scoring": { + "coding": [ + { + "system": "http://hl7.org/fhir/measure-scoring", + "code": "cohort" + } + ] + }, + "group": [ + { + "population": [ + { + "id": "initial-population", + "code": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-population", + "code": "initial-population", + "display": "Initial Population" + } + ] + }, + "criteria": { + "language": "text/cql-identifier", + "expression": "Initial Population" + } + } + ], + "stratifier": [ + { + "id": "stratifier-enc-a", + "criteria": { + "language": "text/cql.identifier", + "expression": "Encounters A Combined" + } + }, + { + "id": "stratifier-enc-b", + "criteria": { + "language": "text/cql.identifier", + "expression": "Encounters B Combined" + } + } + ] + } + ] +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/measure/Level1B.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/measure/Level1B.json new file mode 100644 index 0000000000..e120091964 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/measure/Level1B.json @@ -0,0 +1,64 @@ + +{ + "id": "Level1B", + "resourceType": "Measure", + "name": "Level1B", + "url": "http://example.com/Measure/Level1B", + "library": [ + "http://example.com/Library/Level1B" + ], + "identifier": [ { + "use": "official", + "value": "124" + } ], + "extension": [ { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-populationBasis", + "valueCode": "boolean" + } ], + "scoring": { + "coding": [ + { + "system": "http://hl7.org/fhir/measure-scoring", + "code": "cohort" + } + ] + }, + "group": [ + { + "population": [ + { + "id": "initial-population", + "code": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-population", + "code": "initial-population", + "display": "Initial Population" + } + ] + }, + "criteria": { + "language": "text/cql-identifier", + "expression": "Initial Population" + } + } + ], + "stratifier": [ + { + "id": "stratifier-enc-a", + "criteria": { + "language": "text/cql.identifier", + "expression": "Encounters A Combined" + } + }, + { + "id": "stratifier-enc-b", + "criteria": { + "language": "text/cql.identifier", + "expression": "Encounters B Combined" + } + } + ] + } + ] +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_patient1.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_patient1.json new file mode 100644 index 0000000000..40d4a01829 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_patient1.json @@ -0,0 +1,12 @@ +{ + "resourceType": "Encounter", + "id": "enc_arrived_patient1", + "status": "arrived", + "subject": { + "reference": "Patient/patient1" + }, + "period": { + "start": "2000-01-01T00:00:00-05:00", + "end": "2000-12-31T00:00:00-05:00" + } +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_patient2.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_patient2.json new file mode 100644 index 0000000000..17d4905a68 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_patient2.json @@ -0,0 +1,12 @@ +{ + "resourceType": "Encounter", + "id": "enc_arrived_patient2", + "status": "arrived", + "subject": { + "reference": "Patient/patient2" + }, + "period": { + "start": "2000-01-01T00:00:00-05:00", + "end": "2000-12-31T00:00:00-05:00" + } +} diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_patient3.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_patient3.json new file mode 100644 index 0000000000..75dec35118 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_patient3.json @@ -0,0 +1,12 @@ +{ + "resourceType": "Encounter", + "id": "enc_arrived_patient3", + "status": "arrived", + "subject": { + "reference": "Patient/patient3" + }, + "period": { + "start": "2000-01-01T00:00:00-05:00", + "end": "2000-12-31T00:00:00-05:00" + } +} diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_cancelled_patient1.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_cancelled_patient1.json new file mode 100644 index 0000000000..98f8f88845 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_cancelled_patient1.json @@ -0,0 +1,12 @@ +{ + "resourceType": "Encounter", + "id": "enc_cancelled_patient1", + "status": "cancelled", + "subject": { + "reference": "Patient/patient1" + }, + "period": { + "start": "2001-01-01T00:00:00-05:00", + "end": "2001-12-31T00:00:00-05:00" + } +} diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_cancelled_patient2.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_cancelled_patient2.json new file mode 100644 index 0000000000..19fdf66d1d --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_cancelled_patient2.json @@ -0,0 +1,12 @@ +{ + "resourceType": "Encounter", + "id": "enc_cancelled_patient2", + "status": "cancelled", + "subject": { + "reference": "Patient/patient2" + }, + "period": { + "start": "2001-01-01T00:00:00-05:00", + "end": "2001-12-31T00:00:00-05:00" + } +} diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_cancelled_patient3.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_cancelled_patient3.json new file mode 100644 index 0000000000..c5c11b5eb9 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_cancelled_patient3.json @@ -0,0 +1,12 @@ +{ + "resourceType": "Encounter", + "id": "enc_cancelled_patient3", + "status": "cancelled", + "subject": { + "reference": "Patient/patient3" + }, + "period": { + "start": "2001-01-01T00:00:00-05:00", + "end": "2001-12-31T00:00:00-05:00" + } +} diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_finished_patient1.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_finished_patient1.json new file mode 100644 index 0000000000..fa4bec1714 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_finished_patient1.json @@ -0,0 +1,12 @@ +{ + "resourceType": "Encounter", + "id": "enc_finished_patient1", + "status": "finished", + "subject": { + "reference": "Patient/patient1" + }, + "period": { + "start": "2001-01-01T00:00:00-05:00", + "end": "2001-12-31T00:00:00-05:00" + } +} diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_finished_patient2.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_finished_patient2.json new file mode 100644 index 0000000000..422c3679db --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_finished_patient2.json @@ -0,0 +1,12 @@ +{ + "resourceType": "Encounter", + "id": "enc_finished_patient2", + "status": "finished", + "subject": { + "reference": "Patient/patient2" + }, + "period": { + "start": "2001-01-01T00:00:00-05:00", + "end": "2001-12-31T00:00:00-05:00" + } +} diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_finished_patient3.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_finished_patient3.json new file mode 100644 index 0000000000..99c3724cd3 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_finished_patient3.json @@ -0,0 +1,12 @@ +{ + "resourceType": "Encounter", + "id": "enc_finished_patient3", + "status": "finished", + "subject": { + "reference": "Patient/patient3" + }, + "period": { + "start": "2001-01-01T00:00:00-05:00", + "end": "2001-12-31T00:00:00-05:00" + } +} diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_patient1.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_patient1.json new file mode 100644 index 0000000000..8c8d518368 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_patient1.json @@ -0,0 +1,12 @@ +{ + "resourceType": "Encounter", + "id": "enc_planned_patient1", + "status": "planned", + "subject": { + "reference": "Patient/patient1" + }, + "period": { + "start": "2001-01-01T00:00:00-05:00", + "end": "2001-12-31T00:00:00-05:00" + } +} diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_patient2.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_patient2.json new file mode 100644 index 0000000000..282521390c --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_patient2.json @@ -0,0 +1,12 @@ +{ + "resourceType": "Encounter", + "id": "enc_planned_patient2", + "status": "planned", + "subject": { + "reference": "Patient/patient2" + }, + "period": { + "start": "2001-01-01T00:00:00-05:00", + "end": "2001-12-31T00:00:00-05:00" + } +} diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_patient3.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_patient3.json new file mode 100644 index 0000000000..45e4c49d25 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_patient3.json @@ -0,0 +1,12 @@ +{ + "resourceType": "Encounter", + "id": "enc_planned_patient3", + "status": "planned", + "subject": { + "reference": "Patient/patient3" + }, + "period": { + "start": "2001-01-01T00:00:00-05:00", + "end": "2001-12-31T00:00:00-05:00" + } +} diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_triaged_patient1.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_triaged_patient1.json new file mode 100644 index 0000000000..698d613acd --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_triaged_patient1.json @@ -0,0 +1,12 @@ +{ + "resourceType": "Encounter", + "id": "enc_triaged_patient1", + "status": "triaged", + "subject": { + "reference": "Patient/patient1" + }, + "period": { + "start": "2001-01-01T00:00:00-05:00", + "end": "2001-12-31T00:00:00-05:00" + } +} diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_triaged_patient2.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_triaged_patient2.json new file mode 100644 index 0000000000..af2fabf4ee --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_triaged_patient2.json @@ -0,0 +1,12 @@ +{ + "resourceType": "Encounter", + "id": "enc_triaged_patient2", + "status": "triaged", + "subject": { + "reference": "Patient/patient2" + }, + "period": { + "start": "2001-01-01T00:00:00-05:00", + "end": "2001-12-31T00:00:00-05:00" + } +} diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_triaged_patient3.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_triaged_patient3.json new file mode 100644 index 0000000000..58e9b342d7 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_triaged_patient3.json @@ -0,0 +1,12 @@ +{ + "resourceType": "Encounter", + "id": "enc_triaged_patient3", + "status": "triaged", + "subject": { + "reference": "Patient/patient3" + }, + "period": { + "start": "2001-01-01T00:00:00-05:00", + "end": "2001-12-31T00:00:00-05:00" + } +} diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/patient/patient1.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/patient/patient1.json new file mode 100644 index 0000000000..179f54afb8 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/patient/patient1.json @@ -0,0 +1,6 @@ +{ + "resourceType": "Patient", + "id": "patient1", + "gender": "female", + "birthDate": "1904-01-01" +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/patient/patient2.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/patient/patient2.json new file mode 100644 index 0000000000..fc94a30ed6 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/patient/patient2.json @@ -0,0 +1,6 @@ +{ + "resourceType": "Patient", + "id": "patient2", + "gender": "female", + "birthDate": "1914-04-14" +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/patient/patient3.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/patient/patient3.json new file mode 100644 index 0000000000..ef742ba3f5 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/patient/patient3.json @@ -0,0 +1,6 @@ +{ + "resourceType": "Patient", + "id": "patient3", + "gender": "male", + "birthDate": "1973-07-25" +} \ No newline at end of file From 0d83941e5b36c38e0ba2ac6f13ea9401f1f49f60 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Fri, 25 Jul 2025 17:29:08 -0400 Subject: [PATCH 46/66] Add unit test. --- .../r4/MultiLibEvalComplexCqlTest.java | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCqlTest.java diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCqlTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCqlTest.java new file mode 100644 index 0000000000..040ff62600 --- /dev/null +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCqlTest.java @@ -0,0 +1,55 @@ +package org.opencds.cqf.fhir.cr.measure.r4; + +import org.junit.jupiter.api.Test; +import org.opencds.cqf.fhir.cr.measure.r4.MultiMeasure.Given; + +@SuppressWarnings({"java:S2699"}) +class MultiLibEvalComplexCqlTest { + private static final Given GIVEN_REPO = MultiMeasure.given().repositoryFor("MultiLibEvalComplexCql"); + + @Test + void singleLibraryTest_1A() { + + var when = GIVEN_REPO + .when() + .measureId("Level1A") + .reportType("population") + .evaluate(); + + when.then() + .hasMeasureReportCount(1) + .getFirstMeasureReport() + .firstGroup() + .firstStratifier() + .stratum("Encounters A"); + } + + @Test + void singleLibraryTest_1B() { + + var when = GIVEN_REPO + .when() + .measureId("Level1A") + .reportType("population") + .evaluate(); + + when.then() + .hasMeasureReportCount(1) + .getFirstMeasureReport(); + } + + @Test + void multipleLibraryTest_1A_and_1B() { + + var when = GIVEN_REPO + .when() + .measureId("Level1A") + .measureId("Level1B") + .reportType("population") + .evaluate(); + + when.then() + .hasMeasureReportCount(2) + .getFirstMeasureReport(); + } +} From 61e502549242f6d9431ff472f67bdf4fd3126093 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Mon, 28 Jul 2025 10:29:15 -0400 Subject: [PATCH 47/66] Change approach to 1B testing to filter based on meta profiles. Add meta profiles to all encounters and change IDs accordingly. Adjust CQLs. Start trying to assert stratifiers from tests. --- .../measure/r4/MultiLibEvalComplexCqlTest.java | 12 +++++++++--- .../cqf/fhir/cr/measure/r4/MultiMeasure.java | 6 ++++++ .../r4/MultiLibEvalComplexCql/cql/Level1A.cql | 4 ++-- .../r4/MultiLibEvalComplexCql/cql/Level1B.cql | 6 +++--- .../r4/MultiLibEvalComplexCql/cql/Level3B.cql | 6 +++--- .../r4/MultiLibEvalComplexCql/cql/Level4.cql | 6 +++--- .../r4/MultiLibEvalComplexCql/cql/Level5.cql | 18 ++---------------- ...nt1.json => enc_arrived_cat2_patient1.json} | 7 ++++++- ...nt2.json => enc_arrived_cat2_patient2.json} | 7 ++++++- ...nt3.json => enc_arrived_cat2_patient3.json} | 7 ++++++- ...1.json => enc_cancelled_cat3_patient1.json} | 7 ++++++- ...2.json => enc_cancelled_cat3_patient2.json} | 7 ++++++- ...3.json => enc_cancelled_cat3_patient3.json} | 7 ++++++- ...t1.json => enc_finished_cat3_patient1.json} | 7 ++++++- ...t2.json => enc_finished_cat3_patient2.json} | 7 ++++++- ...t3.json => enc_finished_cat3_patient3.json} | 7 ++++++- ...nt1.json => enc_planned_cat1_patient1.json} | 7 ++++++- ...nt2.json => enc_planned_cat1_patient2.json} | 5 +++++ ...nt3.json => enc_planned_cat1_patient3.json} | 7 ++++++- ...nt1.json => enc_triaged_cat3_patient1.json} | 5 +++++ ...nt2.json => enc_triaged_cat3_patient2.json} | 7 ++++++- ...nt3.json => enc_triaged_cat3_patient3.json} | 7 ++++++- 22 files changed, 116 insertions(+), 43 deletions(-) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/{enc_arrived_patient1.json => enc_arrived_cat2_patient1.json} (65%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/{enc_arrived_patient2.json => enc_arrived_cat2_patient2.json} (65%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/{enc_arrived_patient3.json => enc_arrived_cat2_patient3.json} (65%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/{enc_cancelled_patient1.json => enc_cancelled_cat3_patient1.json} (64%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/{enc_cancelled_patient2.json => enc_cancelled_cat3_patient2.json} (64%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/{enc_cancelled_patient3.json => enc_cancelled_cat3_patient3.json} (64%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/{enc_finished_patient1.json => enc_finished_cat3_patient1.json} (65%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/{enc_finished_patient2.json => enc_finished_cat3_patient2.json} (65%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/{enc_finished_patient3.json => enc_finished_cat3_patient3.json} (65%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/{enc_planned_patient1.json => enc_planned_cat1_patient1.json} (64%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/{enc_planned_patient2.json => enc_planned_cat1_patient2.json} (76%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/{enc_planned_patient3.json => enc_planned_cat1_patient3.json} (65%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/{enc_triaged_patient1.json => enc_triaged_cat3_patient1.json} (76%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/{enc_triaged_patient2.json => enc_triaged_cat3_patient2.json} (65%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/{enc_triaged_patient3.json => enc_triaged_cat3_patient3.json} (65%) diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCqlTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCqlTest.java index 040ff62600..00061e4129 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCqlTest.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCqlTest.java @@ -21,7 +21,8 @@ void singleLibraryTest_1A() { .getFirstMeasureReport() .firstGroup() .firstStratifier() - .stratum("Encounters A"); + .firstStratum() + .hasValue("arrived, planned"); } @Test @@ -29,13 +30,18 @@ void singleLibraryTest_1B() { var when = GIVEN_REPO .when() - .measureId("Level1A") + .measureId("Level1B") .reportType("population") .evaluate(); when.then() .hasMeasureReportCount(1) - .getFirstMeasureReport(); + .getFirstMeasureReport() + .firstGroup() + .firstStratifier() + .firstStratum() + // LUKETODO: why is this: enc_arrived_patient3, enc_planned_patient3 + .hasValue("arrived, planned"); } @Test diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasure.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasure.java index 3937cb8a36..23362327b2 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasure.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasure.java @@ -627,6 +627,12 @@ public SelectedStratum hasScore(String score) { return this; } + public SelectedStratum hasValue(String text) { + assertEquals(text, value().getValue().getText()); + return this; + } + + public SelectedStratumPopulation firstPopulation() { return population(MeasureReport.StratifierGroupComponent::getPopulationFirstRep); } diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level1A.cql b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level1A.cql index 7282d71f3c..c836a14a99 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level1A.cql +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level1A.cql @@ -14,7 +14,7 @@ parameter "Measurement Period" Interval default Interval[@2021-01-01T0 define "Encounters A": Level2."Encounters" E where E.status.value in { 'arrived', 'cancelled', 'in-progress', 'planned' } - return E.status.value + return E.id define "Encounters A Combined": Combine("Encounters A", ', ') @@ -22,7 +22,7 @@ define "Encounters A Combined": define "Encounters B": Level2."Encounters" E where E.status.value in { 'cancelled', 'finished', 'in-progress', 'planned' } - return E.status.value + return E.id define "Encounters B Combined": Combine("Encounters B", ', ') diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level1B.cql b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level1B.cql index 56ddab2c49..fbf51fdb89 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level1B.cql +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level1B.cql @@ -12,16 +12,16 @@ parameter "Measurement Period" Interval default Interval[@2021-07-01T0 define "Encounters A": Level2."Encounters" E where E.status.value in { 'arrived', 'cancelled', 'in-progress', 'planned' } and - E.subject.reference in Level3B."Encounter Subjects" + E.meta.profile[0] in Level3B."Encounter Profiles" return E.id define "Encounters A Combined": Combine("Encounters A", ', ') -define "Encounter B": +define "Encounters B": Level2."Encounters" E where E.status.value in { 'finished', 'cancelled', 'in-progress', 'planned' } and - E.subject.reference in Level3B."Encounter Subjects" + E.meta.profile[0] in Level3B."Encounter Profiles" return E.id define "Encounters B Combined": diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level3B.cql b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level3B.cql index d3b8092601..aa0737aba6 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level3B.cql +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level3B.cql @@ -6,6 +6,6 @@ include FHIRHelpers version '4.0.1' include Level4 called Level4 -define "Encounter Subjects": - Level4."Encounter Subjects" S - where S != 'Patient/pat2' +define "Encounter Profiles": + Level4."Encounter Profiles" P + where not (P in { 'http://example.com/category2' }) diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level4.cql b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level4.cql index 2797a27f94..c581fcc63a 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level4.cql +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level4.cql @@ -6,7 +6,7 @@ include FHIRHelpers version '4.0.1' include Level5 called Level5 -define "Encounter Subjects": +define "Encounter Profiles": Level5."Encounters" E - where E.subject.reference != 'Patient/pat3' - return E.subject.reference + where not (E.meta.profile[0] in { 'http://example.com/category3' }) + return E.meta.profile[0] diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level5.cql b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level5.cql index d28763e8da..92157e4a52 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level5.cql +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level5.cql @@ -6,21 +6,7 @@ include FHIRHelpers version '4.0.1' context Patient -/* -all statuses: ----- -planned -arrived -triaged -in-progress -onleave -finished -cancelled -entered-in-error -unknown -*/ - -// Level 5 > { 'arrived', 'finished', 'in-progress', 'planned', 'triaged' } +// Level 5 > { 'cat1', cat2', 'cat3' } define "Encounters": [Encounter] E - where E.status.value in { 'arrived', 'planned', 'triaged', 'finished', 'in-progress' } + where E.meta.profile[0] in { 'http://example.com/category1', 'http://example.com/category2', 'http://example.com/category3' } diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_patient1.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_cat2_patient1.json similarity index 65% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_patient1.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_cat2_patient1.json index 40d4a01829..43bcbf3333 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_patient1.json +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_cat2_patient1.json @@ -1,6 +1,11 @@ { "resourceType": "Encounter", - "id": "enc_arrived_patient1", + "id": "enc_arrived_cat2_patient1", + "meta": { + "profile": [ + "http://example.com/category2" + ] + }, "status": "arrived", "subject": { "reference": "Patient/patient1" diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_patient2.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_cat2_patient2.json similarity index 65% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_patient2.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_cat2_patient2.json index 17d4905a68..7bf6270271 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_patient2.json +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_cat2_patient2.json @@ -1,6 +1,11 @@ { "resourceType": "Encounter", - "id": "enc_arrived_patient2", + "id": "enc_arrived_cat2_patient2", + "meta": { + "profile": [ + "http://example.com/category2" + ] + }, "status": "arrived", "subject": { "reference": "Patient/patient2" diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_patient3.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_cat2_patient3.json similarity index 65% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_patient3.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_cat2_patient3.json index 75dec35118..2194aaf58c 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_patient3.json +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_cat2_patient3.json @@ -1,6 +1,11 @@ { "resourceType": "Encounter", - "id": "enc_arrived_patient3", + "id": "enc_arrived_cat2_patient3", + "meta": { + "profile": [ + "http://example.com/category2" + ] + }, "status": "arrived", "subject": { "reference": "Patient/patient3" diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_cancelled_patient1.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_cancelled_cat3_patient1.json similarity index 64% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_cancelled_patient1.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_cancelled_cat3_patient1.json index 98f8f88845..0c47d13bb9 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_cancelled_patient1.json +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_cancelled_cat3_patient1.json @@ -1,6 +1,11 @@ { "resourceType": "Encounter", - "id": "enc_cancelled_patient1", + "id": "enc_cancelled_cat3_patient1", + "meta": { + "profile": [ + "http://example.com/category3" + ] + }, "status": "cancelled", "subject": { "reference": "Patient/patient1" diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_cancelled_patient2.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_cancelled_cat3_patient2.json similarity index 64% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_cancelled_patient2.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_cancelled_cat3_patient2.json index 19fdf66d1d..5a6e7caf99 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_cancelled_patient2.json +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_cancelled_cat3_patient2.json @@ -1,6 +1,11 @@ { "resourceType": "Encounter", - "id": "enc_cancelled_patient2", + "id": "enc_cancelled_cat3_patient2", + "meta": { + "profile": [ + "http://example.com/category3" + ] + }, "status": "cancelled", "subject": { "reference": "Patient/patient2" diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_cancelled_patient3.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_cancelled_cat3_patient3.json similarity index 64% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_cancelled_patient3.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_cancelled_cat3_patient3.json index c5c11b5eb9..536e64dd32 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_cancelled_patient3.json +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_cancelled_cat3_patient3.json @@ -1,6 +1,11 @@ { "resourceType": "Encounter", - "id": "enc_cancelled_patient3", + "id": "enc_cancelled_cat3_patient3", + "meta": { + "profile": [ + "http://example.com/category3" + ] + }, "status": "cancelled", "subject": { "reference": "Patient/patient3" diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_finished_patient1.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_finished_cat3_patient1.json similarity index 65% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_finished_patient1.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_finished_cat3_patient1.json index fa4bec1714..d7a968783f 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_finished_patient1.json +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_finished_cat3_patient1.json @@ -1,6 +1,11 @@ { "resourceType": "Encounter", - "id": "enc_finished_patient1", + "id": "enc_finished_cat3_patient1", + "meta": { + "profile": [ + "http://example.com/category3" + ] + }, "status": "finished", "subject": { "reference": "Patient/patient1" diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_finished_patient2.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_finished_cat3_patient2.json similarity index 65% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_finished_patient2.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_finished_cat3_patient2.json index 422c3679db..ebf508a710 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_finished_patient2.json +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_finished_cat3_patient2.json @@ -1,6 +1,11 @@ { "resourceType": "Encounter", - "id": "enc_finished_patient2", + "id": "enc_finished_cat3_patient2", + "meta": { + "profile": [ + "http://example.com/category3" + ] + }, "status": "finished", "subject": { "reference": "Patient/patient2" diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_finished_patient3.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_finished_cat3_patient3.json similarity index 65% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_finished_patient3.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_finished_cat3_patient3.json index 99c3724cd3..90c07439b7 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_finished_patient3.json +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_finished_cat3_patient3.json @@ -1,6 +1,11 @@ { "resourceType": "Encounter", - "id": "enc_finished_patient3", + "id": "enc_finished_cat3_patient3", + "meta": { + "profile": [ + "http://example.com/category3" + ] + }, "status": "finished", "subject": { "reference": "Patient/patient3" diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_patient1.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_cat1_patient1.json similarity index 64% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_patient1.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_cat1_patient1.json index 8c8d518368..e443bb1815 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_patient1.json +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_cat1_patient1.json @@ -1,6 +1,11 @@ { "resourceType": "Encounter", - "id": "enc_planned_patient1", + "id": "enc_planned_cat1_cat1_patient1", + "meta": { + "profile": [ + "http://example.com/category1" + ] + }, "status": "planned", "subject": { "reference": "Patient/patient1" diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_patient2.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_cat1_patient2.json similarity index 76% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_patient2.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_cat1_patient2.json index 282521390c..749bdbe9ee 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_patient2.json +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_cat1_patient2.json @@ -1,6 +1,11 @@ { "resourceType": "Encounter", "id": "enc_planned_patient2", + "meta": { + "profile": [ + "http://example.com/category1" + ] + }, "status": "planned", "subject": { "reference": "Patient/patient2" diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_patient3.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_cat1_patient3.json similarity index 65% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_patient3.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_cat1_patient3.json index 45e4c49d25..e81383a98d 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_patient3.json +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_cat1_patient3.json @@ -1,6 +1,11 @@ { "resourceType": "Encounter", - "id": "enc_planned_patient3", + "id": "enc_planned_cat1_patient3", + "meta": { + "profile": [ + "http://example.com/category1" + ] + }, "status": "planned", "subject": { "reference": "Patient/patient3" diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_triaged_patient1.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_triaged_cat3_patient1.json similarity index 76% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_triaged_patient1.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_triaged_cat3_patient1.json index 698d613acd..2cb59f894a 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_triaged_patient1.json +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_triaged_cat3_patient1.json @@ -1,6 +1,11 @@ { "resourceType": "Encounter", "id": "enc_triaged_patient1", + "meta": { + "profile": [ + "http://example.com/category3" + ] + }, "status": "triaged", "subject": { "reference": "Patient/patient1" diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_triaged_patient2.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_triaged_cat3_patient2.json similarity index 65% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_triaged_patient2.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_triaged_cat3_patient2.json index af2fabf4ee..7bc0184e73 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_triaged_patient2.json +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_triaged_cat3_patient2.json @@ -1,6 +1,11 @@ { "resourceType": "Encounter", - "id": "enc_triaged_patient2", + "id": "enc_triaged_cat3_patient2", + "meta": { + "profile": [ + "http://example.com/category3" + ] + }, "status": "triaged", "subject": { "reference": "Patient/patient2" diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_triaged_patient3.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_triaged_cat3_patient3.json similarity index 65% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_triaged_patient3.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_triaged_cat3_patient3.json index 58e9b342d7..fc628d1b4e 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_triaged_patient3.json +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_triaged_cat3_patient3.json @@ -1,6 +1,11 @@ { "resourceType": "Encounter", - "id": "enc_triaged_patient3", + "id": "enc_triaged_cat3_patient3", + "meta": { + "profile": [ + "http://example.com/category3" + ] + }, "status": "triaged", "subject": { "reference": "Patient/patient3" From a0dd3bbc4d295df02dcc09f11b035ff8dcdcdf9b Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Tue, 29 Jul 2025 14:36:45 -0400 Subject: [PATCH 48/66] Update to a new stub version ahead of the current master version of clinical-reasoning. Make R4PopulationBasisValidator more error tolerant, especially if CQL returns Iterators instead of Lists. Enhance MultiMeasure test util. --- .../measure/common/MeasureProcessorUtils.java | 29 ++++++++++--------- .../r4/R4PopulationBasisValidator.java | 21 ++++++++++++-- .../cqf/fhir/cr/measure/r4/MultiMeasure.java | 24 +++++++++++---- pom.xml | 2 +- 4 files changed, 54 insertions(+), 22 deletions(-) diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java index efdcff593a..51003c3cd7 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java @@ -11,12 +11,12 @@ import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.time.ZonedDateTime; -import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.StreamSupport; import org.apache.commons.collections4.keyvalue.DefaultMapEntry; import org.apache.commons.lang3.tuple.Pair; import org.hl7.elm.r1.FunctionDef; @@ -398,7 +398,7 @@ public CompositeEvaluationResultsPerMeasure getEvaluationResults( for (var measureLibraryIdEngine : measureLibraryIdEngineDetailsList) { try { var libraryId = measureLibraryIdEngine.libraryId(); - logger.info("1234: libraryId: {}", libraryId.getId()); + // logger.info("1234: libraryId: {}", libraryId.getId()); var evaluationResult = measureLibraryIdEngine .engine() // LUKETODO: make CQL CqlEngine changes to take multiple versioned identifiers and @@ -503,10 +503,10 @@ public CompositeEvaluationResultsPerMeasure getEvaluationResults2( null); // LUKETODO: I think this is the problem: we pass an empty EvaluationResult // instead of doing what the old code did - logger.info( - "evalResult for id: {}: {}, ", - libraryVersionedIdentifier.getId(), - printEvaluationResult(evaluationResult)); + // logger.info( + // "1234: evalResult for id: {}: {}, ", + // libraryVersionedIdentifier.getId(), + // printEvaluationResult(evaluationResult)); var measureIds = versionedIdMeasureIdPairs.get(libraryVersionedIdentifier); @@ -557,7 +557,8 @@ private void validateEvaluationResultExistsForIdentifier( } } - private static String printEvaluationResult(EvaluationResult evaluationResult) { + // LUKETODO: add these to another static class somewhere + public static String printEvaluationResult(EvaluationResult evaluationResult) { if (evaluationResult == null) { return "null"; } @@ -569,27 +570,27 @@ private static String printEvaluationResult(EvaluationResult evaluationResult) { + "}\n"; } - private static String printExpressionResult(ExpressionResult expressionResult) { + public static String printExpressionResult(ExpressionResult expressionResult) { if (expressionResult == null) { return "\nnull"; } - return "\nExpressionResult{value=" + showValue(expressionResult.value()) + ", type=" + return "\nExpressionResult{\n value=" + showValue(expressionResult.value()) + "\n type=" + showEvaluatedResources(expressionResult.evaluatedResources()) + '}'; } - private static String showValue(Object valueOrCollection) { + public static String showValue(Object valueOrCollection) { if (valueOrCollection == null) { return "null"; } - if (valueOrCollection instanceof Collection collection) { - return showEvaluatedResources(collection); + if (valueOrCollection instanceof Iterable iterable) { + return showEvaluatedResources(iterable); } return showEvaluatedResource(valueOrCollection); } - public static String showEvaluatedResources(Collection evaluatedResourcesOrSomethings) { - return evaluatedResourcesOrSomethings.stream() + public static String showEvaluatedResources(Iterable evaluatedResourcesOrSomethings) { + return StreamSupport.stream(evaluatedResourcesOrSomethings.spliterator(), false) .map(MeasureProcessorUtils::showEvaluatedResource) .collect(Collectors.joining(", ", "[", "]")); } diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4PopulationBasisValidator.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4PopulationBasisValidator.java index cd9cab0496..a3b6bb67b5 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4PopulationBasisValidator.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4PopulationBasisValidator.java @@ -10,6 +10,7 @@ import java.util.Optional; import java.util.Set; import java.util.stream.Stream; +import java.util.stream.StreamSupport; import org.hl7.fhir.r4.model.CodeableConcept; import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.Enumeration; @@ -21,15 +22,20 @@ import org.opencds.cqf.cql.engine.runtime.Code; import org.opencds.cqf.fhir.cr.measure.common.GroupDef; import org.opencds.cqf.fhir.cr.measure.common.MeasureDef; +import org.opencds.cqf.fhir.cr.measure.common.MeasureProcessorUtils; import org.opencds.cqf.fhir.cr.measure.common.PopulationBasisValidator; import org.opencds.cqf.fhir.cr.measure.common.PopulationDef; import org.opencds.cqf.fhir.cr.measure.common.StratifierDef; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Validates group populations and stratifiers against population basis-es for R4 only. */ public class R4PopulationBasisValidator implements PopulationBasisValidator { + private static final Logger logger = LoggerFactory.getLogger(R4PopulationBasisValidator.class); + private static final String BOOLEAN_BASIS = "boolean"; /** @@ -67,6 +73,8 @@ public void validateStratifiers(MeasureDef measureDef, GroupDef groupDef, Evalua private void validateGroupPopulationBasisType( String url, GroupDef groupDef, PopulationDef populationDef, EvaluationResult evaluationResult) { + logger.info("1234: evaluationResult:\n{}", MeasureProcessorUtils.printEvaluationResult(evaluationResult)); + // PROPORTION var scoring = groupDef.measureScoring(); // Numerator @@ -175,17 +183,26 @@ private List> extractClassesFromSingleOrListResult(Object result) { return Collections.emptyList(); } - if (!(result instanceof List list)) { + if (!(result instanceof Iterable iterable)) { return Collections.singletonList(result.getClass()); } // Need to this to return List> and get rid of Sonar warnings. final Stream> classStream = - list.stream().filter(Objects::nonNull).map(Object::getClass); + getStream(iterable).filter(Objects::nonNull).map(Object::getClass); return classStream.toList(); } + private Stream getStream(Iterable iterable) { + if (iterable instanceof List list) { + return list.stream(); + } + + // It's entirely possible CQL returns an Iterable that is not a List, so we need to handle that case + return StreamSupport.stream(iterable.spliterator(), false); + } + private List prettyClassNames(List> classes) { return classes.stream().map(Class::getSimpleName).toList(); } diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasure.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasure.java index 23362327b2..835c032457 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasure.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasure.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import static org.opencds.cqf.fhir.test.Resources.getResourcePath; import ca.uhn.fhir.context.FhirContext; @@ -309,6 +310,19 @@ public SelectedMeasureReport getFirstMeasureReport() { return this.measureReport(g -> mr); } + public SelectedMeasureReport getSecondMeasureReport() { + var entries = report().getEntry(); + if (entries.size() < 2) { + fail("There are not enough entries in the report to get the second one."); + } + var secondEntryResource = entries.get(1).getResource(); + if (!(secondEntryResource instanceof MeasureReport measureReport)) { + fail("The second entry in the report is not a MeasureReport resource."); + return null; + } + return this.measureReport(g -> measureReport); + } + public MeasureReport resourceToMeasureReport(List entries, String measureUrl) { IParser parser = FhirContext.forR4Cached().newJsonParser(); MeasureReport matchedReport = null; @@ -480,7 +494,8 @@ public SelectedMeasureReport hasPeriodEnd(Date periodEnd) { } } - public static class SelectedGroup extends MultiMeasure.Selected { + public static class SelectedGroup + extends MultiMeasure.Selected { public SelectedGroup(MeasureReportGroupComponent value, SelectedMeasureReport parent) { super(value, parent); @@ -512,7 +527,7 @@ public SelectedPopulation firstPopulation() { } public SelectedGroup hasStratifierCount(int count) { - assertEquals(this.value().getStratifier().size(), count); + assertEquals(count, this.value().getStratifier().size()); return this; } @@ -582,7 +597,7 @@ public SelectedPopulation passes( } } - static class SelectedStratifier + public static class SelectedStratifier extends MultiMeasure.Selected { public SelectedStratifier(MeasureReportGroupStratifierComponent value, SelectedGroup parent) { @@ -616,7 +631,7 @@ public SelectedStratum stratum( } } - static class SelectedStratum extends MultiMeasure.Selected { + public static class SelectedStratum extends MultiMeasure.Selected { public SelectedStratum(MeasureReport.StratifierGroupComponent value, SelectedStratifier parent) { super(value, parent); @@ -632,7 +647,6 @@ public SelectedStratum hasValue(String text) { return this; } - public SelectedStratumPopulation firstPopulation() { return population(MeasureReport.StratifierGroupComponent::getPopulationFirstRep); } diff --git a/pom.xml b/pom.xml index 28c3a0c9b7..baa3a2c3dd 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,7 @@ 2.0.4 - 3.28.0-SNAPSHOT + 3.29.0-SNAPSHOT 1.36 6.1.14 8.2.0 From bc34f4de7e7745a96c7a54e348a37aa258436ce1 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Tue, 29 Jul 2025 14:55:41 -0400 Subject: [PATCH 49/66] Make final changes to MultiLibEvalComplexCqlTest. Add back some logging. --- .../opencds/cqf/fhir/cql/LibraryEngine.java | 4 +- .../r4/MultiLibEvalComplexCqlTest.java | 115 ++++++++++++------ .../enc_arrived_cat2_patient2.json | 0 .../enc_arrived_cat2_patient3.json | 0 .../OLD/enc_arrived_cat3_patient2.json | 17 +++ .../OLD/enc_arrived_cat3_patient3.json | 17 +++ .../enc_cancelled_cat3_patient2.json | 0 .../enc_cancelled_cat3_patient3.json | 0 .../enc_finished_cat3_patient2.json | 0 .../enc_finished_cat3_patient3.json | 0 .../OLD/enc_in_progress_cat1_patient1.json | 17 +++ .../enc_planned_cat1_patient2.json | 2 +- .../enc_planned_cat1_patient3.json | 0 .../enc_triaged_cat3_patient2.json | 0 .../enc_triaged_cat3_patient3.json | 0 .../r4/MultiLibEvalComplexCql/cql/Level1A.cql | 17 ++- .../r4/MultiLibEvalComplexCql/cql/Level1B.cql | 15 +-- .../r4/MultiLibEvalComplexCql/cql/Level5.cql | 8 +- .../resources/measure/Level1A.json | 38 ++++-- .../resources/measure/Level1B.json | 38 ++++-- .../tests/encounter/enc_arrived_cat1_v1.json | 17 +++ .../tests/encounter/enc_arrived_cat1_v2.json | 17 +++ ...t2_patient1.json => enc_arrived_cat2.json} | 2 +- .../tests/encounter/enc_arrived_cat3.json | 17 +++ ..._patient1.json => enc_cancelled_cat3.json} | 2 +- ...3_patient1.json => enc_finished_cat3.json} | 2 +- ...t1_patient1.json => enc_planned_cat1.json} | 2 +- .../tests/encounter/enc_planned_cat2.json | 17 +++ .../tests/encounter/enc_planned_cat3.json | 17 +++ ...t3_patient1.json => enc_triaged_cat3.json} | 2 +- .../tests/patient/patient2.json | 6 - .../tests/patient/patient3.json | 6 - 32 files changed, 293 insertions(+), 102 deletions(-) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/{tests/encounter => OLD}/enc_arrived_cat2_patient2.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/{tests/encounter => OLD}/enc_arrived_cat2_patient3.json (100%) create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_arrived_cat3_patient2.json create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_arrived_cat3_patient3.json rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/{tests/encounter => OLD}/enc_cancelled_cat3_patient2.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/{tests/encounter => OLD}/enc_cancelled_cat3_patient3.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/{tests/encounter => OLD}/enc_finished_cat3_patient2.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/{tests/encounter => OLD}/enc_finished_cat3_patient3.json (100%) create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_in_progress_cat1_patient1.json rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/{tests/encounter => OLD}/enc_planned_cat1_patient2.json (88%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/{tests/encounter => OLD}/enc_planned_cat1_patient3.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/{tests/encounter => OLD}/enc_triaged_cat3_patient2.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/{tests/encounter => OLD}/enc_triaged_cat3_patient3.json (100%) create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_cat1_v1.json create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_cat1_v2.json rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/{enc_arrived_cat2_patient1.json => enc_arrived_cat2.json} (88%) create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_cat3.json rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/{enc_cancelled_cat3_patient1.json => enc_cancelled_cat3.json} (88%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/{enc_finished_cat3_patient1.json => enc_finished_cat3.json} (88%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/{enc_planned_cat1_patient1.json => enc_planned_cat1.json} (87%) create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_cat2.json create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_cat3.json rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/{enc_triaged_cat3_patient1.json => enc_triaged_cat3.json} (90%) delete mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/patient/patient2.json delete mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/patient/patient3.json diff --git a/cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/LibraryEngine.java b/cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/LibraryEngine.java index fb601787c5..e741103101 100644 --- a/cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/LibraryEngine.java +++ b/cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/LibraryEngine.java @@ -338,7 +338,7 @@ public EvaluationResultsForMultiLib getEvaluationResult( CqlEngine engine) { logger.info( - "ids: {}, patientId: {}, zonedDateTime: {}", + "1234: ids: {}, patientId: {}, zonedDateTime: {}", ids.stream().map(VersionedIdentifier::getId).toList(), patientId, zonedDateTime); @@ -372,7 +372,7 @@ public EvaluationResult getEvaluationResult( @Nullable ZonedDateTime zonedDateTime, CqlEngine engine) { - logger.info("id: {}, patientId: {}, zonedDateTime: {}", id.getId(), patientId, zonedDateTime); + logger.info("1234: id: {}, patientId: {}, zonedDateTime: {}", id.getId(), patientId, zonedDateTime); var stuff = pre(parameters, rawParameters, additionalData, cqlFhirParametersConverter, engine); diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCqlTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCqlTest.java index 00061e4129..56b7c3a58e 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCqlTest.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCqlTest.java @@ -1,61 +1,100 @@ package org.opencds.cqf.fhir.cr.measure.r4; import org.junit.jupiter.api.Test; +import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions; import org.opencds.cqf.fhir.cr.measure.r4.MultiMeasure.Given; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; @SuppressWarnings({"java:S2699"}) class MultiLibEvalComplexCqlTest { - private static final Given GIVEN_REPO = MultiMeasure.given().repositoryFor("MultiLibEvalComplexCql"); + private static final Logger logger = LoggerFactory.getLogger(MultiLibEvalComplexCqlTest.class); + + private static final MeasureEvaluationOptions EVALUATION_OPTIONS = MeasureEvaluationOptions.defaultOptions() + // We're not doing this not necessarily for HEDIS but just so we can assert different counts for numerator + // and denominator + .setApplyScoringSetMembership(false); + + private static final Given GIVEN_REPO = + MultiMeasure.given().repositoryFor("MultiLibEvalComplexCql").evaluationOptions(EVALUATION_OPTIONS); @Test void singleLibraryTest_1A() { - var when = GIVEN_REPO - .when() - .measureId("Level1A") - .reportType("population") - .evaluate(); - - when.then() - .hasMeasureReportCount(1) - .getFirstMeasureReport() - .firstGroup() - .firstStratifier() - .firstStratum() - .hasValue("arrived, planned"); + GIVEN_REPO + .when() + .measureId("Level1A") + .reportType("subject") + .evaluate() + .then() + .hasMeasureReportCount(1) + .getFirstMeasureReport() + .firstGroup() + .population("initial-population") + .hasCount(10) + .up() + .population("denominator") + .hasCount(7) + .up() + .population("numerator") + .hasCount(3); } @Test void singleLibraryTest_1B() { - var when = GIVEN_REPO - .when() - .measureId("Level1B") - .reportType("population") - .evaluate(); - - when.then() - .hasMeasureReportCount(1) - .getFirstMeasureReport() - .firstGroup() - .firstStratifier() - .firstStratum() - // LUKETODO: why is this: enc_arrived_patient3, enc_planned_patient3 - .hasValue("arrived, planned"); + GIVEN_REPO + .when() + .measureId("Level1B") + .reportType("subject") + .evaluate() + .then() + .hasMeasureReportCount(1) + .getFirstMeasureReport() + .firstGroup() + .population("initial-population") + .hasCount(10) + .up() + .population("denominator") + .hasCount(3) + .up() + .population("numerator") + .hasCount(1); } @Test void multipleLibraryTest_1A_and_1B() { - var when = GIVEN_REPO - .when() - .measureId("Level1A") - .measureId("Level1B") - .reportType("population") - .evaluate(); - - when.then() - .hasMeasureReportCount(2) - .getFirstMeasureReport(); + GIVEN_REPO + .when() + .measureId("Level1A") + .measureId("Level1B") + .reportType("subject") + .evaluate() + .then() + .hasMeasureReportCount(2) + .getFirstMeasureReport() + .firstGroup() + .population("initial-population") + .hasCount(10) + .up() + .population("denominator") + .hasCount(7) + .up() + .population("numerator") + .hasCount(3) + .up() + .up() + .up() + .getSecondMeasureReport() + .firstGroup() + .population("initial-population") + .hasCount(10) + .up() + .population("denominator") + .hasCount(3) + .up() + .population("numerator") + .hasCount(1); } } diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_cat2_patient2.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_arrived_cat2_patient2.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_cat2_patient2.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_arrived_cat2_patient2.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_cat2_patient3.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_arrived_cat2_patient3.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_cat2_patient3.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_arrived_cat2_patient3.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_arrived_cat3_patient2.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_arrived_cat3_patient2.json new file mode 100644 index 0000000000..12ea07b90f --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_arrived_cat3_patient2.json @@ -0,0 +1,17 @@ +{ + "resourceType": "Encounter", + "id": "enc_arrived_cat3_patient2", + "meta": { + "profile": [ + "http://example.com/category3" + ] + }, + "status": "arrived", + "subject": { + "reference": "Patient/patient2" + }, + "period": { + "start": "2000-01-01T00:00:00-05:00", + "end": "2000-12-31T00:00:00-05:00" + } +} diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_arrived_cat3_patient3.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_arrived_cat3_patient3.json new file mode 100644 index 0000000000..2a6242a089 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_arrived_cat3_patient3.json @@ -0,0 +1,17 @@ +{ + "resourceType": "Encounter", + "id": "enc_arrived_cat3_patient3", + "meta": { + "profile": [ + "http://example.com/category3" + ] + }, + "status": "arrived", + "subject": { + "reference": "Patient/patient3" + }, + "period": { + "start": "2000-01-01T00:00:00-05:00", + "end": "2000-12-31T00:00:00-05:00" + } +} diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_cancelled_cat3_patient2.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_cancelled_cat3_patient2.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_cancelled_cat3_patient2.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_cancelled_cat3_patient2.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_cancelled_cat3_patient3.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_cancelled_cat3_patient3.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_cancelled_cat3_patient3.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_cancelled_cat3_patient3.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_finished_cat3_patient2.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_finished_cat3_patient2.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_finished_cat3_patient2.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_finished_cat3_patient2.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_finished_cat3_patient3.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_finished_cat3_patient3.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_finished_cat3_patient3.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_finished_cat3_patient3.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_in_progress_cat1_patient1.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_in_progress_cat1_patient1.json new file mode 100644 index 0000000000..940642142b --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_in_progress_cat1_patient1.json @@ -0,0 +1,17 @@ +{ + "resourceType": "Encounter", + "id": "enc_in_progress_cat1_patient1", + "meta": { + "profile": [ + "http://example.com/category1" + ] + }, + "status": "in-progress", + "subject": { + "reference": "Patient/patient1" + }, + "period": { + "start": "2001-01-01T00:00:00-05:00", + "end": "2001-12-31T00:00:00-05:00" + } +} diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_cat1_patient2.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_planned_cat1_patient2.json similarity index 88% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_cat1_patient2.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_planned_cat1_patient2.json index 749bdbe9ee..7d5479cc44 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_cat1_patient2.json +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_planned_cat1_patient2.json @@ -1,6 +1,6 @@ { "resourceType": "Encounter", - "id": "enc_planned_patient2", + "id": "enc_planned_cat1_patient2", "meta": { "profile": [ "http://example.com/category1" diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_cat1_patient3.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_planned_cat1_patient3.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_cat1_patient3.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_planned_cat1_patient3.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_triaged_cat3_patient2.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_triaged_cat3_patient2.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_triaged_cat3_patient2.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_triaged_cat3_patient2.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_triaged_cat3_patient3.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_triaged_cat3_patient3.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_triaged_cat3_patient3.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_triaged_cat3_patient3.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level1A.cql b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level1A.cql index c836a14a99..5b01e7616c 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level1A.cql +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level1A.cql @@ -11,18 +11,15 @@ include Level2 called Level2 parameter "Measurement Period" Interval default Interval[@2021-01-01T00:00:00.000, @2022-01-01T00:00:00.000) -define "Encounters A": +context Patient + +define "Initial Population": + [Encounter] + +define "Denominator": Level2."Encounters" E where E.status.value in { 'arrived', 'cancelled', 'in-progress', 'planned' } - return E.id -define "Encounters A Combined": - Combine("Encounters A", ', ') - -define "Encounters B": +define "Numerator": Level2."Encounters" E where E.status.value in { 'cancelled', 'finished', 'in-progress', 'planned' } - return E.id - -define "Encounters B Combined": - Combine("Encounters B", ', ') diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level1B.cql b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level1B.cql index fbf51fdb89..0407621cb6 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level1B.cql +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level1B.cql @@ -9,20 +9,15 @@ include Level3B called Level3B parameter "Measurement Period" Interval default Interval[@2021-07-01T00:00:00.000, @2022-07-01T00:00:00.000) -define "Encounters A": +define "Initial Population": + [Encounter] + +define "Denominator": Level2."Encounters" E where E.status.value in { 'arrived', 'cancelled', 'in-progress', 'planned' } and E.meta.profile[0] in Level3B."Encounter Profiles" - return E.id - -define "Encounters A Combined": - Combine("Encounters A", ', ') -define "Encounters B": +define "Numerator": Level2."Encounters" E where E.status.value in { 'finished', 'cancelled', 'in-progress', 'planned' } and E.meta.profile[0] in Level3B."Encounter Profiles" - return E.id - -define "Encounters B Combined": - Combine("Encounters B", ', ') diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level5.cql b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level5.cql index 92157e4a52..7a8292979f 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level5.cql +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level5.cql @@ -6,7 +6,11 @@ include FHIRHelpers version '4.0.1' context Patient -// Level 5 > { 'cat1', cat2', 'cat3' } +//// Level 5 > { 'cat1', cat2', 'cat3' } +//define "Encounters": +// [Encounter] E +// where E.meta.profile[0] in { 'http://example.com/category1', 'http://example.com/category2', 'http://example.com/category3' } +// Level 5 > { 'arrived', 'finished', 'in-progress', 'planned', 'triaged' } define "Encounters": [Encounter] E - where E.meta.profile[0] in { 'http://example.com/category1', 'http://example.com/category2', 'http://example.com/category3' } + where E.status.value in { 'arrived', 'planned', 'triaged', 'finished', 'in-progress' } diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/measure/Level1A.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/measure/Level1A.json index 8f96142032..bd9a38edee 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/measure/Level1A.json +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/measure/Level1A.json @@ -13,13 +13,13 @@ } ], "extension": [ { "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-populationBasis", - "valueCode": "boolean" + "valueCode": "Encounter" } ], "scoring": { "coding": [ { "system": "http://hl7.org/fhir/measure-scoring", - "code": "cohort" + "code": "proportion" } ] }, @@ -41,21 +41,37 @@ "language": "text/cql-identifier", "expression": "Initial Population" } - } - ], - "stratifier": [ + }, { - "id": "stratifier-enc-a", + "id": "numerator", + "code": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-population", + "code": "numerator", + "display": "Numerator" + } + ] + }, "criteria": { - "language": "text/cql.identifier", - "expression": "Encounters A Combined" + "language": "text/cql", + "expression": "Numerator" } }, { - "id": "stratifier-enc-b", + "id": "denominator", + "code": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-population", + "code": "denominator", + "display": "Denominator" + } + ] + }, "criteria": { - "language": "text/cql.identifier", - "expression": "Encounters B Combined" + "language": "text/cql", + "expression": "Denominator" } } ] diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/measure/Level1B.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/measure/Level1B.json index e120091964..26c014417a 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/measure/Level1B.json +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/measure/Level1B.json @@ -13,13 +13,13 @@ } ], "extension": [ { "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-populationBasis", - "valueCode": "boolean" + "valueCode": "Encounter" } ], "scoring": { "coding": [ { "system": "http://hl7.org/fhir/measure-scoring", - "code": "cohort" + "code": "proportion" } ] }, @@ -41,21 +41,37 @@ "language": "text/cql-identifier", "expression": "Initial Population" } - } - ], - "stratifier": [ + }, { - "id": "stratifier-enc-a", + "id": "numerator", + "code": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-population", + "code": "numerator", + "display": "Numerator" + } + ] + }, "criteria": { - "language": "text/cql.identifier", - "expression": "Encounters A Combined" + "language": "text/cql", + "expression": "Numerator" } }, { - "id": "stratifier-enc-b", + "id": "denominator", + "code": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-population", + "code": "denominator", + "display": "Denominator" + } + ] + }, "criteria": { - "language": "text/cql.identifier", - "expression": "Encounters B Combined" + "language": "text/cql", + "expression": "Denominator" } } ] diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_cat1_v1.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_cat1_v1.json new file mode 100644 index 0000000000..88fb8c559d --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_cat1_v1.json @@ -0,0 +1,17 @@ +{ + "resourceType": "Encounter", + "id": "enc_arrived_cat1_v1", + "meta": { + "profile": [ + "http://example.com/category1" + ] + }, + "status": "arrived", + "subject": { + "reference": "Patient/patient1" + }, + "period": { + "start": "2000-01-01T00:00:00-05:00", + "end": "2000-12-31T00:00:00-05:00" + } +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_cat1_v2.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_cat1_v2.json new file mode 100644 index 0000000000..ed8dbc3ffc --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_cat1_v2.json @@ -0,0 +1,17 @@ +{ + "resourceType": "Encounter", + "id": "enc_arrived_cat1_v2", + "meta": { + "profile": [ + "http://example.com/category1" + ] + }, + "status": "arrived", + "subject": { + "reference": "Patient/patient1" + }, + "period": { + "start": "2000-01-01T00:00:00-05:00", + "end": "2000-12-31T00:00:00-05:00" + } +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_cat2_patient1.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_cat2.json similarity index 88% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_cat2_patient1.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_cat2.json index 43bcbf3333..8a84193482 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_cat2_patient1.json +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_cat2.json @@ -1,6 +1,6 @@ { "resourceType": "Encounter", - "id": "enc_arrived_cat2_patient1", + "id": "enc_arrived_cat2", "meta": { "profile": [ "http://example.com/category2" diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_cat3.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_cat3.json new file mode 100644 index 0000000000..4bb869bfe9 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_cat3.json @@ -0,0 +1,17 @@ +{ + "resourceType": "Encounter", + "id": "enc_arrived_cat3", + "meta": { + "profile": [ + "http://example.com/category3" + ] + }, + "status": "arrived", + "subject": { + "reference": "Patient/patient1" + }, + "period": { + "start": "2000-01-01T00:00:00-05:00", + "end": "2000-12-31T00:00:00-05:00" + } +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_cancelled_cat3_patient1.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_cancelled_cat3.json similarity index 88% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_cancelled_cat3_patient1.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_cancelled_cat3.json index 0c47d13bb9..a4d312f17f 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_cancelled_cat3_patient1.json +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_cancelled_cat3.json @@ -1,6 +1,6 @@ { "resourceType": "Encounter", - "id": "enc_cancelled_cat3_patient1", + "id": "enc_cancelled_cat3", "meta": { "profile": [ "http://example.com/category3" diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_finished_cat3_patient1.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_finished_cat3.json similarity index 88% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_finished_cat3_patient1.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_finished_cat3.json index d7a968783f..e13acc6d31 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_finished_cat3_patient1.json +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_finished_cat3.json @@ -1,6 +1,6 @@ { "resourceType": "Encounter", - "id": "enc_finished_cat3_patient1", + "id": "enc_finished_cat3", "meta": { "profile": [ "http://example.com/category3" diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_cat1_patient1.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_cat1.json similarity index 87% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_cat1_patient1.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_cat1.json index e443bb1815..a4bf19ee8f 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_cat1_patient1.json +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_cat1.json @@ -1,6 +1,6 @@ { "resourceType": "Encounter", - "id": "enc_planned_cat1_cat1_patient1", + "id": "enc_planned_cat1", "meta": { "profile": [ "http://example.com/category1" diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_cat2.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_cat2.json new file mode 100644 index 0000000000..69e8a2ee74 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_cat2.json @@ -0,0 +1,17 @@ +{ + "resourceType": "Encounter", + "id": "enc_planned_cat2", + "meta": { + "profile": [ + "http://example.com/category2" + ] + }, + "status": "planned", + "subject": { + "reference": "Patient/patient1" + }, + "period": { + "start": "2001-01-01T00:00:00-05:00", + "end": "2001-12-31T00:00:00-05:00" + } +} diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_cat3.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_cat3.json new file mode 100644 index 0000000000..6e0fdf3ee3 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_cat3.json @@ -0,0 +1,17 @@ +{ + "resourceType": "Encounter", + "id": "enc_planned_cat3", + "meta": { + "profile": [ + "http://example.com/category3" + ] + }, + "status": "planned", + "subject": { + "reference": "Patient/patient1" + }, + "period": { + "start": "2001-01-01T00:00:00-05:00", + "end": "2001-12-31T00:00:00-05:00" + } +} diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_triaged_cat3_patient1.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_triaged_cat3.json similarity index 90% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_triaged_cat3_patient1.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_triaged_cat3.json index 2cb59f894a..bc4d8cbf34 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_triaged_cat3_patient1.json +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_triaged_cat3.json @@ -1,6 +1,6 @@ { "resourceType": "Encounter", - "id": "enc_triaged_patient1", + "id": "enc_triaged_cat3", "meta": { "profile": [ "http://example.com/category3" diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/patient/patient2.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/patient/patient2.json deleted file mode 100644 index fc94a30ed6..0000000000 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/patient/patient2.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "resourceType": "Patient", - "id": "patient2", - "gender": "female", - "birthDate": "1914-04-14" -} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/patient/patient3.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/patient/patient3.json deleted file mode 100644 index ef742ba3f5..0000000000 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/patient/patient3.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "resourceType": "Patient", - "id": "patient3", - "gender": "male", - "birthDate": "1973-07-25" -} \ No newline at end of file From e6d0685eef9ebf85ef82179a8d4bf979597ba579 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Tue, 29 Jul 2025 15:55:55 -0400 Subject: [PATCH 50/66] First set of refactoring changes. --- .../opencds/cqf/fhir/cql/LibraryEngine.java | 77 ++++++------------- .../cr/measure/r4/R4MeasureProcessor.java | 2 +- .../fhir/cr/measure/r4/R4MeasureService.java | 4 +- .../cpg/r4/LibraryEvaluationServiceTest.java | 2 +- 4 files changed, 29 insertions(+), 56 deletions(-) diff --git a/cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/LibraryEngine.java b/cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/LibraryEngine.java index e741103101..f1a94dc674 100644 --- a/cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/LibraryEngine.java +++ b/cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/LibraryEngine.java @@ -14,6 +14,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; @@ -343,22 +344,25 @@ public EvaluationResultsForMultiLib getEvaluationResult( patientId, zonedDateTime); - // LUKETODO: better name - var stuff = pre(parameters, rawParameters, additionalData, cqlFhirParametersConverter, engine); + final CqlFhirParametersConverter cqlFhirParametersConverterToUse = Objects.requireNonNullElseGet( + cqlFhirParametersConverter, () -> Engines.getCqlFhirParametersConverter(repository.fhirContext())); + + // engine context built externally of LibraryEngine? + final CqlEngine engineToUse = Objects.requireNonNullElseGet( + engine, () -> Engines.forRepository(repository, settings, additionalData)); + + var evaluationParameters = cqlFhirParametersConverterToUse.toCqlParameters(parameters); + if (rawParameters != null && !rawParameters.isEmpty()) { + evaluationParameters.putAll(rawParameters); + } // LUKETODO: better name var idsCloned = ids.stream() .map(id -> new VersionedIdentifier().withId(id.getId())) .toList(); - return stuff.engine() - .evaluate( - idsCloned, - expressions, - buildContextParameter(patientId), - stuff.evaluationParameters(), - null, - zonedDateTime); + return engineToUse.evaluate( + idsCloned, expressions, buildContextParameter(patientId), evaluationParameters, null, zonedDateTime); } public EvaluationResult getEvaluationResult( @@ -374,48 +378,17 @@ public EvaluationResult getEvaluationResult( logger.info("1234: id: {}, patientId: {}, zonedDateTime: {}", id.getId(), patientId, zonedDateTime); - var stuff = pre(parameters, rawParameters, additionalData, cqlFhirParametersConverter, engine); - - return stuff.engine() - .evaluate( - new VersionedIdentifier().withId(id.getId()), - expressions, - buildContextParameter(patientId), - stuff.evaluationParameters(), - null, - zonedDateTime); - } - - private Stuff pre( - IBaseParameters parameters, - Map rawParameters, - IBaseBundle additionalData, - CqlFhirParametersConverter cqlFhirParametersConverter, - CqlEngine engine) { - - final CqlFhirParametersConverter cqlFhirParametersConverterToUse; - if (cqlFhirParametersConverter == null) { - cqlFhirParametersConverterToUse = Engines.getCqlFhirParametersConverter(repository.fhirContext()); - } else { - cqlFhirParametersConverterToUse = cqlFhirParametersConverter; - } - // engine context built externally of LibraryEngine? - final CqlEngine engineToUse; - if (engine == null) { - engineToUse = Engines.forRepository(repository, settings, additionalData); - } else { - engineToUse = engine; - } - - var evaluationParameters = cqlFhirParametersConverterToUse.toCqlParameters(parameters); - if (rawParameters != null && !rawParameters.isEmpty()) { - evaluationParameters.putAll(rawParameters); - } + var evaluationResultsForMultiLib = getEvaluationResult( + List.of(id), + patientId, + parameters, + rawParameters, + additionalData, + expressions, + cqlFhirParametersConverter, + zonedDateTime, + engine); - return new Stuff(engineToUse, evaluationParameters); + return evaluationResultsForMultiLib.getSingleResultOrThrow(); } - - // LUKETODO: - // LUKETODO: better name - private record Stuff(CqlEngine engine, Map evaluationParameters) {} } diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java index 9c036b965d..5520f25f24 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java @@ -204,7 +204,7 @@ protected MeasureReport evaluateMeasure( subjectIds); } - public CompositeEvaluationResultsPerMeasure evaluateMeasureEitherWithCqlEngine( + public CompositeEvaluationResultsPerMeasure evaluateMeasureWithCqlEngine( List subjects, Either3 measureEither, @Nullable ZonedDateTime periodStart, diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java index d195dfc534..06be1e14e8 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java @@ -86,8 +86,8 @@ public MeasureReport evaluate( var context = Engines.forRepository( proxyRepoForMeasureProcessor, this.measureEvaluationOptions.getEvaluationSettings(), additionalData); - var evaluationResults = processor.evaluateMeasureEitherWithCqlEngine( - subjects, measure, periodStart, periodEnd, parameters, context); + var evaluationResults = + processor.evaluateMeasureWithCqlEngine(subjects, measure, periodStart, periodEnd, parameters, context); measureReport = processor.evaluateMeasure( measure, periodStart, periodEnd, reportType, subjects, evalType, context, evaluationResults); diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/cpg/r4/LibraryEvaluationServiceTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/cpg/r4/LibraryEvaluationServiceTest.java index 7c24ab62ab..c0855b75ba 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/cpg/r4/LibraryEvaluationServiceTest.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/cpg/r4/LibraryEvaluationServiceTest.java @@ -98,7 +98,7 @@ void libraryEvaluationService_ErrorLibrary() { var issue = outcome.getIssueFirstRep(); assertEquals(OperationOutcome.IssueSeverity.ERROR, issue.getSeverity()); assertEquals( - "Example Failure Code: This is an error message", + "Exception for Library: ErrorLibrary, Message: Example Failure Code: This is an error message", issue.getDetails().getText().replaceAll("[\\r\\n]", "")); } From 60d86cc40197a110e6b766a614e64c202a61ec50 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Tue, 29 Jul 2025 16:54:30 -0400 Subject: [PATCH 51/66] More cleanup. --- .../opencds/cqf/fhir/cql/LibraryEngine.java | 10 +- .../common/EvaluationResultsDisplay.java | 59 +++++++++++ .../measure/common/MeasureProcessorUtils.java | 97 +------------------ .../cr/measure/r4/R4MeasureProcessor.java | 75 ++------------ .../r4/R4PopulationBasisValidator.java | 4 +- .../measure/r4/MultiMeasureServiceTest.java | 2 - 6 files changed, 81 insertions(+), 166 deletions(-) create mode 100644 cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/EvaluationResultsDisplay.java diff --git a/cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/LibraryEngine.java b/cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/LibraryEngine.java index f1a94dc674..6c9b72230a 100644 --- a/cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/LibraryEngine.java +++ b/cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/LibraryEngine.java @@ -356,13 +356,17 @@ public EvaluationResultsForMultiLib getEvaluationResult( evaluationParameters.putAll(rawParameters); } - // LUKETODO: better name - var idsCloned = ids.stream() + var versionlessIdentifiers = ids.stream() .map(id -> new VersionedIdentifier().withId(id.getId())) .toList(); return engineToUse.evaluate( - idsCloned, expressions, buildContextParameter(patientId), evaluationParameters, null, zonedDateTime); + versionlessIdentifiers, + expressions, + buildContextParameter(patientId), + evaluationParameters, + null, + zonedDateTime); } public EvaluationResult getEvaluationResult( diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/EvaluationResultsDisplay.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/EvaluationResultsDisplay.java new file mode 100644 index 0000000000..a4845d0273 --- /dev/null +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/EvaluationResultsDisplay.java @@ -0,0 +1,59 @@ +package org.opencds.cqf.fhir.cr.measure.common; + +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; +import org.apache.commons.collections4.keyvalue.DefaultMapEntry; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.opencds.cqf.cql.engine.execution.EvaluationResult; +import org.opencds.cqf.cql.engine.execution.ExpressionResult; + +// LUKETODO: consider deleting this once all is said and done +public class EvaluationResultsDisplay { + // LUKETODO: add these to another static class somewhere + public static String printEvaluationResult(EvaluationResult evaluationResult) { + if (evaluationResult == null) { + return "null"; + } + return "\nEvaluationResult{" + "expressionResults=\n" + + evaluationResult.expressionResults.entrySet().stream() + .map(entry -> new DefaultMapEntry<>(entry.getKey(), printExpressionResult(entry.getValue()))) + .map(entry -> entry.getKey() + ": " + entry.getValue()) + .collect(Collectors.joining("\n")) + + "}\n"; + } + + public static String printExpressionResult(ExpressionResult expressionResult) { + if (expressionResult == null) { + return "\nnull"; + } + + return "\nExpressionResult{\n value=" + showValue(expressionResult.value()) + "\n type=" + + showEvaluatedResources(expressionResult.evaluatedResources()) + '}'; + } + + public static String showValue(Object valueOrCollection) { + if (valueOrCollection == null) { + return "null"; + } + if (valueOrCollection instanceof Iterable iterable) { + return showEvaluatedResources(iterable); + } + return showEvaluatedResource(valueOrCollection); + } + + public static String showEvaluatedResources(Iterable evaluatedResourcesOrSomethings) { + return StreamSupport.stream(evaluatedResourcesOrSomethings.spliterator(), false) + .map(EvaluationResultsDisplay::showEvaluatedResource) + .collect(Collectors.joining(", ", "[", "]")); + } + + private static String showEvaluatedResource(Object evaluatedResourceOrSomething) { + if (evaluatedResourceOrSomething instanceof IBaseResource resource) { + return resource.getIdElement().getValueAsString(); + } else if (evaluatedResourceOrSomething != null) { + return evaluatedResourceOrSomething.toString(); + } else { + return "null"; + } + } +} diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java index 51003c3cd7..eca1bc5e13 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java @@ -15,21 +15,16 @@ import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.StreamSupport; -import org.apache.commons.collections4.keyvalue.DefaultMapEntry; import org.apache.commons.lang3.tuple.Pair; import org.hl7.elm.r1.FunctionDef; import org.hl7.elm.r1.IntervalTypeSpecifier; import org.hl7.elm.r1.NamedTypeSpecifier; import org.hl7.elm.r1.ParameterDef; import org.hl7.elm.r1.VersionedIdentifier; -import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.opencds.cqf.cql.engine.execution.CqlEngine; import org.opencds.cqf.cql.engine.execution.CqlEngine.EvaluationResultsForMultiLib; import org.opencds.cqf.cql.engine.execution.EvaluationResult; -import org.opencds.cqf.cql.engine.execution.ExpressionResult; import org.opencds.cqf.cql.engine.execution.Libraries; import org.opencds.cqf.cql.engine.execution.SearchableLibraryIdentifier; import org.opencds.cqf.cql.engine.execution.Variable; @@ -431,7 +426,7 @@ public CompositeEvaluationResultsPerMeasure getEvaluationResults( return resultsBuilder.build(); } - public CompositeEvaluationResultsPerMeasure getEvaluationResults2( + public CompositeEvaluationResultsPerMeasure getEvaluationResults( List subjectIds, ZonedDateTime zonedMeasurementPeriod, CqlEngine context, @@ -449,41 +444,12 @@ public CompositeEvaluationResultsPerMeasure getEvaluationResults2( if (subjectId == null) { throw new InternalErrorException("SubjectId is required in order to calculate."); } - // LUKETODO: we reset the cache outside of the measure loop Pair subjectInfo = this.getSubjectTypeAndId(subjectId); String subjectTypePart = subjectInfo.getLeft(); String subjectIdPart = subjectInfo.getRight(); context.getState().setContextValue(subjectTypePart, subjectIdPart); try { - // LUKETODO: is library identifier ordering important here? var libraryIdentifiers = List.copyOf(versionedIdMeasureIdPairs.keySet()); - // LUKETODO: try to do one library at a time and see if there are the same errors: - - // for (VersionedIdentifier libraryVersionedIdentifier : libraryIdentifiers) { - // var evaluationResultsForMultiLib = - // libraryEngineForMultipleLibraries.getEvaluationResult( - // libraryVersionedIdentifier, - // subjectId, - // null, - // null, - // null, - // null, - // null, - // zonedMeasurementPeriod, - // context); - // - // var measureIds = versionedIdMeasureIdPairs.get(libraryVersionedIdentifier); - // - // for (IIdType measureId : measureIds) { - // resultsBuilder.addResult( - // measureId, - // subjectId, - // evaluationResultsForMultiLib); - // } - // } - - // LUKETODO: look into the right way to implement this later? - // LUKETODO: if we do it this way, we get invalid interval CQL errors among others: why?????? var evaluationResultsForMultiLib = libraryEngineForMultipleLibraries.getEvaluationResult( libraryIdentifiers, subjectId, null, null, null, null, null, zonedMeasurementPeriod, context); @@ -496,17 +462,8 @@ public CompositeEvaluationResultsPerMeasure getEvaluationResults2( validateEvaluationResultExistsForIdentifier( libraryVersionedIdentifier, evaluationResultsForMultiLib); - var evaluationResult = evaluationResultsForMultiLib - .getResults() - .getOrDefault( - searchableLibraryIdentifier, - null); // LUKETODO: I think this is the problem: we pass an empty EvaluationResult - // instead of doing what the old code did - - // logger.info( - // "1234: evalResult for id: {}: {}, ", - // libraryVersionedIdentifier.getId(), - // printEvaluationResult(evaluationResult)); + var evaluationResult = + evaluationResultsForMultiLib.getResults().getOrDefault(searchableLibraryIdentifier, null); var measureIds = versionedIdMeasureIdPairs.get(libraryVersionedIdentifier); @@ -531,16 +488,6 @@ public CompositeEvaluationResultsPerMeasure getEvaluationResults2( return resultsBuilder.build(); } - private static String showEvaluatedResource(Object evaluatedResourceOrSomething) { - if (evaluatedResourceOrSomething instanceof IBaseResource resource) { - return resource.getIdElement().getValueAsString(); - } else if (evaluatedResourceOrSomething != null) { - return evaluatedResourceOrSomething.toString(); - } else { - return "null"; - } - } - private void validateEvaluationResultExistsForIdentifier( VersionedIdentifier versionedIdentifierFromQuery, EvaluationResultsForMultiLib evaluationResultsForMultiLib) { @@ -557,44 +504,6 @@ private void validateEvaluationResultExistsForIdentifier( } } - // LUKETODO: add these to another static class somewhere - public static String printEvaluationResult(EvaluationResult evaluationResult) { - if (evaluationResult == null) { - return "null"; - } - return "\nEvaluationResult{" + "expressionResults=\n" - + evaluationResult.expressionResults.entrySet().stream() - .map(entry -> new DefaultMapEntry<>(entry.getKey(), printExpressionResult(entry.getValue()))) - .map(entry -> entry.getKey() + ": " + entry.getValue()) - .collect(Collectors.joining("\n")) - + "}\n"; - } - - public static String printExpressionResult(ExpressionResult expressionResult) { - if (expressionResult == null) { - return "\nnull"; - } - - return "\nExpressionResult{\n value=" + showValue(expressionResult.value()) + "\n type=" - + showEvaluatedResources(expressionResult.evaluatedResources()) + '}'; - } - - public static String showValue(Object valueOrCollection) { - if (valueOrCollection == null) { - return "null"; - } - if (valueOrCollection instanceof Iterable iterable) { - return showEvaluatedResources(iterable); - } - return showEvaluatedResource(valueOrCollection); - } - - public static String showEvaluatedResources(Iterable evaluatedResourcesOrSomethings) { - return StreamSupport.stream(evaluatedResourcesOrSomethings.spliterator(), false) - .map(MeasureProcessorUtils::showEvaluatedResource) - .collect(Collectors.joining(", ", "[", "]")); - } - public Pair getSubjectTypeAndId(String subjectId) { if (subjectId.contains("/")) { String[] subjectIdParts = subjectId.split("/"); diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java index 5520f25f24..b2c8fb8391 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java @@ -11,12 +11,10 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; -import org.apache.commons.lang3.tuple.Pair; import org.cqframework.cql.cql2elm.CqlIncludeException; import org.cqframework.cql.cql2elm.model.CompiledLibrary; import org.hl7.elm.r1.VersionedIdentifier; @@ -47,7 +45,6 @@ import org.opencds.cqf.fhir.cr.measure.common.MeasureScoring; import org.opencds.cqf.fhir.cr.measure.r4.utils.R4DateHelper; import org.opencds.cqf.fhir.cr.measure.r4.utils.R4MeasureServiceUtils; -import org.opencds.cqf.fhir.utility.Canonicals; import org.opencds.cqf.fhir.utility.monad.Either3; import org.opencds.cqf.fhir.utility.search.Searches; @@ -283,13 +280,7 @@ public CompositeEvaluationResultsPerMeasure evaluateMultiMeasuresWithCqlEngine( measures.stream().map(this::getLibraryVersionIdentifier).toList(); var libraryEngineForMultipleLibraries = getLibraryEngine(parameters, libraryVersionedIdentifiers, context); - - final List measureIds = - measures.stream().map(Measure::getIdElement).toList(); - - // var versionedIdMeasureIdPairs = getPairs(measures); - // var versionedIdMeasureIdPairs = getPairs2(measures); - // var versionedIdMeasureIdPairs = getPairs3(measures); + // LUKETODO: this is awkward: can we redo this? var versionedIdMeasureIdPairs = getPairs4(measures); // populate results from Library $evaluate @@ -297,7 +288,6 @@ public CompositeEvaluationResultsPerMeasure evaluateMultiMeasuresWithCqlEngine( subjects, zonedMeasurementPeriod, context, - measureLibraryIdEngineDetailsList, libraryEngineForMultipleLibraries, versionedIdMeasureIdPairs); } @@ -306,13 +296,9 @@ private CompositeEvaluationResultsPerMeasure getEvaluationResults( List subjects, ZonedDateTime zonedMeasurementPeriod, CqlEngine context, - List measureLibraryIdEngineDetailsList, LibraryEngine libraryEngineForMultipleLibraries, ListMultimap versionedIdMeasureIdPairs) { - // LUKETODO: flip between these to test - // return measureProcessorUtils.getEvaluationResults( - // subjects, zonedMeasurementPeriod, context, measureLibraryIdEngineDetailsList); - return measureProcessorUtils.getEvaluationResults2( + return measureProcessorUtils.getEvaluationResults( subjects, zonedMeasurementPeriod, context, @@ -320,31 +306,6 @@ private CompositeEvaluationResultsPerMeasure getEvaluationResults( versionedIdMeasureIdPairs); } - private List> getPairs(List measures) { - return measures.stream() - .map(measure -> Pair.of(getLibraryVersionIdentifier(measure), (IIdType) measure.getIdElement())) - .toList(); - } - - private ListMultimap getPairs2(List measures) { - return measures.stream() - .map(measure -> Map.entry(getLibraryVersionIdentifier(measure), (IIdType) measure.getIdElement())) - .collect(ImmutableListMultimap.toImmutableListMultimap(Map.Entry::getKey, Map.Entry::getValue)); - } - - private LinkedHashMap> getPairs3(List measures) { - var result = new LinkedHashMap>(); - - for (Measure measure : measures) { - var libraryVersionIdentifier = getLibraryVersionIdentifier(measure); - var measureIds = result.computeIfAbsent(libraryVersionIdentifier, k -> new ArrayList<>()); - - measureIds.add(measure.getIdElement()); - } - - return result; - } - private ListMultimap getPairs4(List measures) { return measures.stream() .collect(ImmutableListMultimap.toImmutableListMultimap( @@ -484,21 +445,16 @@ protected void checkMeasureLibrary(Measure measure) { * @param context CQL engine generated */ protected void setArgParameters(Parameters parameters, CqlEngine context, CompiledLibrary lib) { - if (parameters != null) { - Map paramMap = resolveParameterMap(parameters); - context.getState().setParameters(lib.getLibrary(), paramMap); - - if (lib.getLibrary().getIncludes() != null) { - lib.getLibrary() - .getIncludes() - .getDef() - .forEach(includeDef -> paramMap.forEach((paramKey, paramValue) -> context.getState() - .setParameter(includeDef.getLocalIdentifier(), paramKey, paramValue))); - } - } + setArgParameters(parameters, context, List.of(lib)); } - // LUKETODO: javadoc + /** + * Set parameters for included libraries, which may be multiple + * Note: this may not be the optimal method (e.g. libraries with the same + * parameter name, but different values) + * @param parameters CQL parameters passed in from operation + * @param context CQL engine generated + */ protected void setArgParameters(Parameters parameters, CqlEngine context, List libs) { if (parameters != null) { Map paramMap = resolveParameterMap(parameters); @@ -516,17 +472,6 @@ protected void setArgParameters(Parameters parameters, CqlEngine context, List Date: Tue, 29 Jul 2025 17:57:20 -0400 Subject: [PATCH 52/66] Start merging old and new code bases. Clean up more cruft. --- .../CompositeEvaluationResultsPerMeasure.java | 1 - .../measure/common/MeasureProcessorUtils.java | 75 +++---------------- .../MultiLibraryIdMeasureEngineDetails.java | 55 ++++++++++++++ .../cr/measure/r4/R4MeasureProcessor.java | 11 +-- 4 files changed, 69 insertions(+), 73 deletions(-) create mode 100644 cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MultiLibraryIdMeasureEngineDetails.java diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java index 90379973d9..dd750c76f2 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java @@ -76,7 +76,6 @@ public void addResults(List measureIds, String subjectId, EvaluationRes } public void addResult(IIdType measureId, String subjectId, EvaluationResult evaluationResult) { - // LUKETODO: figure out how to test this: // if we have no results, we don't need to add anything if (evaluationResult == null || evaluationResult.expressionResults.isEmpty()) { return; diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java index eca1bc5e13..0945ee474d 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java @@ -4,6 +4,7 @@ import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ListMultimap; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; @@ -294,7 +295,6 @@ public void captureEvaluatedResources(Set outEvaluatedResources, CqlEngi clearEvaluatedResources(context); } - // LUKETODO: why do we bother if the next evaluation will flush them anyway? // reset evaluated resources followed by a context evaluation private void clearEvaluatedResources(CqlEngine context) { context.getState().clearEvaluatedResources(); @@ -318,8 +318,6 @@ public Object evaluateObservationCriteria( boolean isBooleanBasis, CqlEngine context) { - // LUKETODO: org.opencds.cqf.cql.engine.exception.CqlException: Could not resolve expression reference - // 'MeasureObservation' in library 'MinimalProportionBooleanBasisSingleGroup'. var ed = Libraries.resolveExpressionRef( criteriaExpression, context.getState().getCurrentLibrary()); @@ -348,82 +346,29 @@ public Object evaluateObservationCriteria( return result; } - public CompositeEvaluationResultsPerMeasure getEvaluationResults( - List subjectIds, - ZonedDateTime zonedMeasurementPeriod, - CqlEngine context, - MeasureLibraryIdEngineDetails measureLibraryIdEngineDetails) { - - return getEvaluationResults( - subjectIds, zonedMeasurementPeriod, context, List.of(measureLibraryIdEngineDetails)); - } - // LUKETODO: redo javadoc + // LUKETODO: redo contract with DSTU3 /** * method used to execute generate CQL results via Library $evaluate * * @param subjectIds subjects to generate results for * @param zonedMeasurementPeriod offset defined measurement period for evaluation * @param context cql engine context - * @param measureLibraryIdEngineDetailsList contains details of measureId, libraryId, and LibraryEngine * @return CQL results for Library defined in the Measure resource */ public CompositeEvaluationResultsPerMeasure getEvaluationResults( List subjectIds, ZonedDateTime zonedMeasurementPeriod, CqlEngine context, - List measureLibraryIdEngineDetailsList) { - - // measure -> subject -> results - var resultsBuilder = CompositeEvaluationResultsPerMeasure.builder(); - - // Library $evaluate each subject - // The goal here is to do each measure/library evaluation within the context of a single subject. - // This means that we will not switch between subject contexts while evaluating measures. - // Once we've switched to a different subject context, the previous expression cache is dropped. - for (String subjectId : subjectIds) { - if (subjectId == null) { - throw new InternalErrorException("SubjectId is required in order to calculate."); - } - // LUKETODO: we reset the cache outside of the measure loop - Pair subjectInfo = this.getSubjectTypeAndId(subjectId); - String subjectTypePart = subjectInfo.getLeft(); - String subjectIdPart = subjectInfo.getRight(); - context.getState().setContextValue(subjectTypePart, subjectIdPart); - for (var measureLibraryIdEngine : measureLibraryIdEngineDetailsList) { - try { - var libraryId = measureLibraryIdEngine.libraryId(); - // logger.info("1234: libraryId: {}", libraryId.getId()); - var evaluationResult = measureLibraryIdEngine - .engine() - // LUKETODO: make CQL CqlEngine changes to take multiple versioned identifiers and - // then initState for mulitple libraries - // this seems to also reset the cache - // LUKETODO: call the new mulitple versionidentifier method once it's available in - // CQL - .getEvaluationResult( - libraryId, - subjectId, - null, - null, - null, - null, - null, - zonedMeasurementPeriod, - context); - - resultsBuilder.addResult(measureLibraryIdEngine.measureId(), subjectId, evaluationResult); - } catch (Exception e) { - // Catch Exceptions from evaluation per subject, but allow rest of subjects to be processed (if - // applicable) - var error = EXCEPTION_FOR_SUBJECT_ID_MESSAGE_TEMPLATE.formatted(subjectId, e.getMessage()); - resultsBuilder.addError(measureLibraryIdEngine.measureId(), error); - logger.error(error, e); - } - } - } + MeasureLibraryIdEngineDetails measureLibraryIdEngineDetails) { - return resultsBuilder.build(); + return getEvaluationResults( + subjectIds, + zonedMeasurementPeriod, + context, + measureLibraryIdEngineDetails.engine(), + ImmutableListMultimap.of( + measureLibraryIdEngineDetails.libraryId(), measureLibraryIdEngineDetails.measureId())); } public CompositeEvaluationResultsPerMeasure getEvaluationResults( diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MultiLibraryIdMeasureEngineDetails.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MultiLibraryIdMeasureEngineDetails.java new file mode 100644 index 0000000000..7c7e5679ad --- /dev/null +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MultiLibraryIdMeasureEngineDetails.java @@ -0,0 +1,55 @@ +package org.opencds.cqf.fhir.cr.measure.common; + +import com.google.common.collect.ImmutableListMultimap; +import com.google.common.collect.ListMultimap; +import org.hl7.elm.r1.VersionedIdentifier; +import org.hl7.fhir.instance.model.api.IIdType; +import org.opencds.cqf.fhir.cql.LibraryEngine; +import java.util.List; + +// LUKETODO: javadoc +public class MultiLibraryIdMeasureEngineDetails { + private final LibraryEngine libraryEngine; + private final ListMultimap libraryIdToMeasureIds; + + private MultiLibraryIdMeasureEngineDetails(Builder builder) { + this.libraryEngine = builder.libraryEngine; + this.libraryIdToMeasureIds = builder.libraryIdToMeasureIdsBuilder.build(); + } + + public LibraryEngine getLibraryEngine() { + return libraryEngine; + } + + public List getLibraryIdentifiers() { + // Assuming we want the first library identifier + return List.copyOf(libraryIdToMeasureIds.keySet()); + } + + public List getMeasureIdsForLibrary(VersionedIdentifier libraryId) { + return libraryIdToMeasureIds.get(libraryId); + } + + public static Builder builder(LibraryEngine engine) { + return new Builder(engine); + } + + public static class Builder { + private final LibraryEngine libraryEngine; + private final ImmutableListMultimap.Builder libraryIdToMeasureIdsBuilder = + ImmutableListMultimap.builder(); + + public Builder(LibraryEngine libraryEngine) { + this.libraryEngine = libraryEngine; + } + + public Builder addLibraryIdToMeasureId(VersionedIdentifier libraryId, IIdType measureId) { + libraryIdToMeasureIdsBuilder.put(libraryId, measureId); + return this; + } + + public MultiLibraryIdMeasureEngineDetails build() { + return new MultiLibraryIdMeasureEngineDetails(this); + } + } +} diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java index b2c8fb8391..61d90cd297 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java @@ -261,11 +261,12 @@ public CompositeEvaluationResultsPerMeasure evaluateMultiMeasuresWithCqlEngine( var zonedMeasurementPeriod = MeasureProcessorUtils.getZonedTimeZoneForEval( measureProcessorUtils.getDefaultMeasurementPeriod(measurementPeriodParams, context)); + var libraryVersionedIdentifiers = + measures.stream().map(this::getLibraryVersionIdentifier).toList(); + // Note that we must build the LibraryEngine BEFORE we call // measureProcessorUtils.setMeasurementPeriod(), otherwise, we get an NPE. - var measureLibraryIdEngineDetailsList = measures.stream() - .map(measure -> buildLibraryIdEngineDetails(measure, parameters, context)) - .toList(); + var libraryEngineForMultipleLibraries = getLibraryEngine(parameters, libraryVersionedIdentifiers, context); // set measurement Period from CQL if operation parameters are empty measureProcessorUtils.setMeasurementPeriod( @@ -276,10 +277,6 @@ public CompositeEvaluationResultsPerMeasure evaluateMultiMeasuresWithCqlEngine( .map(url -> Optional.ofNullable(url).orElse("Unknown Measure URL")) .toList()); - var libraryVersionedIdentifiers = - measures.stream().map(this::getLibraryVersionIdentifier).toList(); - - var libraryEngineForMultipleLibraries = getLibraryEngine(parameters, libraryVersionedIdentifiers, context); // LUKETODO: this is awkward: can we redo this? var versionedIdMeasureIdPairs = getPairs4(measures); From 75831ee4d63eb4875c4a2a05a0716ef28e71f39d Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Tue, 29 Jul 2025 18:12:53 -0400 Subject: [PATCH 53/66] More refactoring to merge old and new code branches. More cleanup. --- .../common/MeasureLibraryIdEngineDetails.java | 8 --- .../measure/common/MeasureProcessorUtils.java | 44 ++++++-------- .../MultiLibraryIdMeasureEngineDetails.java | 4 ++ .../measure/dstu3/Dstu3MeasureProcessor.java | 32 +++++----- .../cr/measure/r4/R4MeasureProcessor.java | 60 +++++++------------ 5 files changed, 58 insertions(+), 90 deletions(-) delete mode 100644 cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureLibraryIdEngineDetails.java diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureLibraryIdEngineDetails.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureLibraryIdEngineDetails.java deleted file mode 100644 index 9120c65404..0000000000 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureLibraryIdEngineDetails.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.opencds.cqf.fhir.cr.measure.common; - -import org.hl7.elm.r1.VersionedIdentifier; -import org.hl7.fhir.instance.model.api.IIdType; -import org.opencds.cqf.fhir.cql.LibraryEngine; - -// LUKETODO: get rid of this once the new code is firmly in place -public record MeasureLibraryIdEngineDetails(IIdType measureId, VersionedIdentifier libraryId, LibraryEngine engine) {} diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java index 0945ee474d..92ebb3029d 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java @@ -4,8 +4,6 @@ import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; -import com.google.common.collect.ImmutableListMultimap; -import com.google.common.collect.ListMultimap; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; import java.time.LocalDateTime; @@ -22,7 +20,6 @@ import org.hl7.elm.r1.NamedTypeSpecifier; import org.hl7.elm.r1.ParameterDef; import org.hl7.elm.r1.VersionedIdentifier; -import org.hl7.fhir.instance.model.api.IIdType; import org.opencds.cqf.cql.engine.execution.CqlEngine; import org.opencds.cqf.cql.engine.execution.CqlEngine.EvaluationResultsForMultiLib; import org.opencds.cqf.cql.engine.execution.EvaluationResult; @@ -32,7 +29,6 @@ import org.opencds.cqf.cql.engine.runtime.Date; import org.opencds.cqf.cql.engine.runtime.DateTime; import org.opencds.cqf.cql.engine.runtime.Interval; -import org.opencds.cqf.fhir.cql.LibraryEngine; import org.opencds.cqf.fhir.cr.measure.constant.MeasureConstants; import org.opencds.cqf.fhir.cr.measure.helper.DateHelper; import org.slf4j.Logger; @@ -360,23 +356,7 @@ public CompositeEvaluationResultsPerMeasure getEvaluationResults( List subjectIds, ZonedDateTime zonedMeasurementPeriod, CqlEngine context, - MeasureLibraryIdEngineDetails measureLibraryIdEngineDetails) { - - return getEvaluationResults( - subjectIds, - zonedMeasurementPeriod, - context, - measureLibraryIdEngineDetails.engine(), - ImmutableListMultimap.of( - measureLibraryIdEngineDetails.libraryId(), measureLibraryIdEngineDetails.measureId())); - } - - public CompositeEvaluationResultsPerMeasure getEvaluationResults( - List subjectIds, - ZonedDateTime zonedMeasurementPeriod, - CqlEngine context, - LibraryEngine libraryEngineForMultipleLibraries, - ListMultimap versionedIdMeasureIdPairs) { // LUKETODO: rename + MultiLibraryIdMeasureEngineDetails multiLibraryIdMeasureEngineDetails) { // measure -> subject -> results var resultsBuilder = CompositeEvaluationResultsPerMeasure.builder(); @@ -394,9 +374,20 @@ public CompositeEvaluationResultsPerMeasure getEvaluationResults( String subjectIdPart = subjectInfo.getRight(); context.getState().setContextValue(subjectTypePart, subjectIdPart); try { - var libraryIdentifiers = List.copyOf(versionedIdMeasureIdPairs.keySet()); - var evaluationResultsForMultiLib = libraryEngineForMultipleLibraries.getEvaluationResult( - libraryIdentifiers, subjectId, null, null, null, null, null, zonedMeasurementPeriod, context); + var libraryIdentifiers = multiLibraryIdMeasureEngineDetails.getLibraryIdentifiers(); + + var evaluationResultsForMultiLib = + multiLibraryIdMeasureEngineDetails.getLibraryEngine() + .getEvaluationResult( + libraryIdentifiers, + subjectId, + null, + null, + null, + null, + null, + zonedMeasurementPeriod, + context); var errorsById = evaluationResultsForMultiLib.getErrors(); @@ -410,7 +401,8 @@ public CompositeEvaluationResultsPerMeasure getEvaluationResults( var evaluationResult = evaluationResultsForMultiLib.getResults().getOrDefault(searchableLibraryIdentifier, null); - var measureIds = versionedIdMeasureIdPairs.get(libraryVersionedIdentifier); + var measureIds = + multiLibraryIdMeasureEngineDetails.getMeasureIdsForLibrary(libraryVersionedIdentifier); resultsBuilder.addResults(measureIds, subjectId, evaluationResult); @@ -423,7 +415,7 @@ public CompositeEvaluationResultsPerMeasure getEvaluationResults( } catch (Exception e) { // If there's any error we didn't anticipate, catch it here: var error = EXCEPTION_FOR_SUBJECT_ID_MESSAGE_TEMPLATE.formatted(subjectId, e.getMessage()); - var measureIds = List.copyOf(versionedIdMeasureIdPairs.values()); + var measureIds = multiLibraryIdMeasureEngineDetails.getAllMeasureIds(); resultsBuilder.addErrors(measureIds, error); logger.error(error, e); diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MultiLibraryIdMeasureEngineDetails.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MultiLibraryIdMeasureEngineDetails.java index 7c7e5679ad..6c9ab1900c 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MultiLibraryIdMeasureEngineDetails.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MultiLibraryIdMeasureEngineDetails.java @@ -34,6 +34,10 @@ public static Builder builder(LibraryEngine engine) { return new Builder(engine); } + public List getAllMeasureIds() { + return List.copyOf(libraryIdToMeasureIds.values()); + } + public static class Builder { private final LibraryEngine libraryEngine; private final ImmutableListMultimap.Builder libraryIdToMeasureIdsBuilder = diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureProcessor.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureProcessor.java index 99f9bbf244..60586482d1 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureProcessor.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureProcessor.java @@ -27,9 +27,9 @@ import org.opencds.cqf.fhir.cql.LibraryEngine; import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions; import org.opencds.cqf.fhir.cr.measure.common.MeasureEvalType; -import org.opencds.cqf.fhir.cr.measure.common.MeasureLibraryIdEngineDetails; import org.opencds.cqf.fhir.cr.measure.common.MeasureProcessorUtils; import org.opencds.cqf.fhir.cr.measure.common.MeasureReportType; +import org.opencds.cqf.fhir.cr.measure.common.MultiLibraryIdMeasureEngineDetails; import org.opencds.cqf.fhir.cr.measure.common.SubjectProvider; import org.opencds.cqf.fhir.utility.repository.FederatedRepository; import org.opencds.cqf.fhir.utility.repository.InMemoryFhirRepository; @@ -133,29 +133,27 @@ protected MeasureReport evaluateMeasure( } // Ideally this would be done in MeasureProcessorUtils, but it's too much work to change for now - private MeasureLibraryIdEngineDetails buildLibraryIdEngineDetails( + private MultiLibraryIdMeasureEngineDetails buildLibraryIdEngineDetails( Measure measure, Parameters parameters, CqlEngine context) { var libraryVersionIdentifier = getLibraryVersionIdentifier(measure); - return new MeasureLibraryIdEngineDetails( - measure.getIdElement(), - libraryVersionIdentifier, - getLibraryEngine(parameters, libraryVersionIdentifier, context)); + final LibraryEngine libraryEngine = getLibraryEngine(parameters, libraryVersionIdentifier, + context); + + return MultiLibraryIdMeasureEngineDetails.builder(libraryEngine) + .addLibraryIdToMeasureId( + getLibraryVersionIdentifier(measure), + measure.getIdElement()) + .build(); } protected MeasureReportType evalTypeToReportType(MeasureEvalType measureEvalType) { - switch (measureEvalType) { - case PATIENT, SUBJECT: - return MeasureReportType.INDIVIDUAL; - case PATIENTLIST, SUBJECTLIST: - return MeasureReportType.PATIENTLIST; - case POPULATION: - return MeasureReportType.SUMMARY; - default: - throw new InvalidRequestException( - "Unsupported MeasureEvalType: %s".formatted(measureEvalType.toCode())); - } + return switch (measureEvalType) { + case PATIENT, SUBJECT -> MeasureReportType.INDIVIDUAL; + case PATIENTLIST, SUBJECTLIST -> MeasureReportType.PATIENTLIST; + case POPULATION -> MeasureReportType.SUMMARY; + }; } protected LibraryEngine getLibraryEngine(Parameters parameters, VersionedIdentifier id, CqlEngine context) { diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java index 61d90cd297..13e2e10838 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java @@ -3,8 +3,6 @@ import ca.uhn.fhir.repository.IRepository; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; -import com.google.common.collect.ImmutableListMultimap; -import com.google.common.collect.ListMultimap; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; import java.time.ZonedDateTime; @@ -38,11 +36,11 @@ import org.opencds.cqf.fhir.cr.measure.common.GroupDef; import org.opencds.cqf.fhir.cr.measure.common.MeasureDef; import org.opencds.cqf.fhir.cr.measure.common.MeasureEvalType; -import org.opencds.cqf.fhir.cr.measure.common.MeasureLibraryIdEngineDetails; import org.opencds.cqf.fhir.cr.measure.common.MeasurePopulationType; import org.opencds.cqf.fhir.cr.measure.common.MeasureProcessorUtils; import org.opencds.cqf.fhir.cr.measure.common.MeasureReportType; import org.opencds.cqf.fhir.cr.measure.common.MeasureScoring; +import org.opencds.cqf.fhir.cr.measure.common.MultiLibraryIdMeasureEngineDetails; import org.opencds.cqf.fhir.cr.measure.r4.utils.R4DateHelper; import org.opencds.cqf.fhir.cr.measure.r4.utils.R4MeasureServiceUtils; import org.opencds.cqf.fhir.utility.monad.Either3; @@ -261,12 +259,11 @@ public CompositeEvaluationResultsPerMeasure evaluateMultiMeasuresWithCqlEngine( var zonedMeasurementPeriod = MeasureProcessorUtils.getZonedTimeZoneForEval( measureProcessorUtils.getDefaultMeasurementPeriod(measurementPeriodParams, context)); - var libraryVersionedIdentifiers = - measures.stream().map(this::getLibraryVersionIdentifier).toList(); // Note that we must build the LibraryEngine BEFORE we call // measureProcessorUtils.setMeasurementPeriod(), otherwise, we get an NPE. - var libraryEngineForMultipleLibraries = getLibraryEngine(parameters, libraryVersionedIdentifiers, context); + var multiLibraryIdMeasureEngineDetails = + getMultiLibraryIdMeasureEngineDetails(measures, parameters, context); // set measurement Period from CQL if operation parameters are empty measureProcessorUtils.setMeasurementPeriod( @@ -277,50 +274,35 @@ public CompositeEvaluationResultsPerMeasure evaluateMultiMeasuresWithCqlEngine( .map(url -> Optional.ofNullable(url).orElse("Unknown Measure URL")) .toList()); - // LUKETODO: this is awkward: can we redo this? - var versionedIdMeasureIdPairs = getPairs4(measures); // populate results from Library $evaluate - return getEvaluationResults( - subjects, - zonedMeasurementPeriod, - context, - libraryEngineForMultipleLibraries, - versionedIdMeasureIdPairs); - } - - private CompositeEvaluationResultsPerMeasure getEvaluationResults( - List subjects, - ZonedDateTime zonedMeasurementPeriod, - CqlEngine context, - LibraryEngine libraryEngineForMultipleLibraries, - ListMultimap versionedIdMeasureIdPairs) { return measureProcessorUtils.getEvaluationResults( subjects, zonedMeasurementPeriod, context, - libraryEngineForMultipleLibraries, - versionedIdMeasureIdPairs); + multiLibraryIdMeasureEngineDetails); } - private ListMultimap getPairs4(List measures) { - return measures.stream() - .collect(ImmutableListMultimap.toImmutableListMultimap( - this::getLibraryVersionIdentifier, // Key extractor - Measure::getIdElement // Value extractor - )); - } + private MultiLibraryIdMeasureEngineDetails getMultiLibraryIdMeasureEngineDetails( + List measures, + Parameters parameters, + CqlEngine context) { + var libraryVersionedIdentifiers = + measures.stream() + .map(this::getLibraryVersionIdentifier) + .toList(); + + var libraryEngine = getLibraryEngine(parameters, libraryVersionedIdentifiers, context); - // Ideally this would be done in MeasureProcessorUtils, but it's too much work to change for now - private MeasureLibraryIdEngineDetails buildLibraryIdEngineDetails( - Measure measure, Parameters parameters, CqlEngine context) { + var builder = MultiLibraryIdMeasureEngineDetails.builder(libraryEngine); - var libraryVersionIdentifier = getLibraryVersionIdentifier(measure); + measures.forEach(measure -> { + builder.addLibraryIdToMeasureId( + this.getLibraryVersionIdentifier(measure), + measure.getIdElement()); + }); - return new MeasureLibraryIdEngineDetails( - measure.getIdElement(), - libraryVersionIdentifier, - getLibraryEngine(parameters, libraryVersionIdentifier, context)); + return builder.build(); } /** Temporary check for Measures that are being blocked from use by evaluateResults method From bbc2c4040cb0a36c1388a6010043dfe50841fc06 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Wed, 30 Jul 2025 09:35:33 -0400 Subject: [PATCH 54/66] Small tweaks and integrate with latest CQL branch changes. --- .../opencds/cqf/fhir/cql/LibraryEngine.java | 2 +- .../CompositeEvaluationResultsPerMeasure.java | 1 + .../common/EvaluationResultsDisplay.java | 1 - .../measure/common/MeasureProcessorUtils.java | 13 +++++------ .../MultiLibraryIdMeasureEngineDetails.java | 4 ++-- .../measure/dstu3/Dstu3MeasureProcessor.java | 9 +++----- .../cr/measure/r4/R4MeasureProcessor.java | 22 +++++-------------- 7 files changed, 18 insertions(+), 34 deletions(-) diff --git a/cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/LibraryEngine.java b/cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/LibraryEngine.java index 6c9b72230a..94c3058e85 100644 --- a/cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/LibraryEngine.java +++ b/cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/LibraryEngine.java @@ -25,8 +25,8 @@ import org.hl7.fhir.instance.model.api.IBaseBundle; import org.hl7.fhir.instance.model.api.IBaseParameters; import org.opencds.cqf.cql.engine.execution.CqlEngine; -import org.opencds.cqf.cql.engine.execution.CqlEngine.EvaluationResultsForMultiLib; import org.opencds.cqf.cql.engine.execution.EvaluationResult; +import org.opencds.cqf.cql.engine.execution.EvaluationResultsForMultiLib; import org.opencds.cqf.fhir.cql.engine.parameters.CqlFhirParametersConverter; import org.opencds.cqf.fhir.cql.engine.parameters.CqlParameterDefinition; import org.opencds.cqf.fhir.utility.CqfExpression; diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java index dd750c76f2..5956f8335e 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java @@ -22,6 +22,7 @@ public class CompositeEvaluationResultsPerMeasure { // The same measure may have successful results AND errors, so account for both private final Map> resultsPerMeasure; + // LUKETODO: Exceptions instead of Strings? // We may get several errors for a given measure private final Map> errorsPerMeasure; diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/EvaluationResultsDisplay.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/EvaluationResultsDisplay.java index a4845d0273..b2b0f88b7e 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/EvaluationResultsDisplay.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/EvaluationResultsDisplay.java @@ -9,7 +9,6 @@ // LUKETODO: consider deleting this once all is said and done public class EvaluationResultsDisplay { - // LUKETODO: add these to another static class somewhere public static String printEvaluationResult(EvaluationResult evaluationResult) { if (evaluationResult == null) { return "null"; diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java index 92ebb3029d..a1909b3d8b 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java @@ -21,8 +21,8 @@ import org.hl7.elm.r1.ParameterDef; import org.hl7.elm.r1.VersionedIdentifier; import org.opencds.cqf.cql.engine.execution.CqlEngine; -import org.opencds.cqf.cql.engine.execution.CqlEngine.EvaluationResultsForMultiLib; import org.opencds.cqf.cql.engine.execution.EvaluationResult; +import org.opencds.cqf.cql.engine.execution.EvaluationResultsForMultiLib; import org.opencds.cqf.cql.engine.execution.Libraries; import org.opencds.cqf.cql.engine.execution.SearchableLibraryIdentifier; import org.opencds.cqf.cql.engine.execution.Variable; @@ -342,14 +342,13 @@ public Object evaluateObservationCriteria( return result; } - // LUKETODO: redo javadoc - // LUKETODO: redo contract with DSTU3 /** * method used to execute generate CQL results via Library $evaluate * * @param subjectIds subjects to generate results for * @param zonedMeasurementPeriod offset defined measurement period for evaluation * @param context cql engine context + * @param multiLibraryIdMeasureEngineDetails container for engine, library and measure IDs * @return CQL results for Library defined in the Measure resource */ public CompositeEvaluationResultsPerMeasure getEvaluationResults( @@ -376,10 +375,10 @@ public CompositeEvaluationResultsPerMeasure getEvaluationResults( try { var libraryIdentifiers = multiLibraryIdMeasureEngineDetails.getLibraryIdentifiers(); - var evaluationResultsForMultiLib = - multiLibraryIdMeasureEngineDetails.getLibraryEngine() + var evaluationResultsForMultiLib = multiLibraryIdMeasureEngineDetails + .getLibraryEngine() .getEvaluationResult( - libraryIdentifiers, + libraryIdentifiers, subjectId, null, null, @@ -402,7 +401,7 @@ public CompositeEvaluationResultsPerMeasure getEvaluationResults( evaluationResultsForMultiLib.getResults().getOrDefault(searchableLibraryIdentifier, null); var measureIds = - multiLibraryIdMeasureEngineDetails.getMeasureIdsForLibrary(libraryVersionedIdentifier); + multiLibraryIdMeasureEngineDetails.getMeasureIdsForLibrary(libraryVersionedIdentifier); resultsBuilder.addResults(measureIds, subjectId, evaluationResult); diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MultiLibraryIdMeasureEngineDetails.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MultiLibraryIdMeasureEngineDetails.java index 6c9ab1900c..4cfdf5d9db 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MultiLibraryIdMeasureEngineDetails.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MultiLibraryIdMeasureEngineDetails.java @@ -2,10 +2,10 @@ import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ListMultimap; +import java.util.List; import org.hl7.elm.r1.VersionedIdentifier; import org.hl7.fhir.instance.model.api.IIdType; import org.opencds.cqf.fhir.cql.LibraryEngine; -import java.util.List; // LUKETODO: javadoc public class MultiLibraryIdMeasureEngineDetails { @@ -41,7 +41,7 @@ public List getAllMeasureIds() { public static class Builder { private final LibraryEngine libraryEngine; private final ImmutableListMultimap.Builder libraryIdToMeasureIdsBuilder = - ImmutableListMultimap.builder(); + ImmutableListMultimap.builder(); public Builder(LibraryEngine libraryEngine) { this.libraryEngine = libraryEngine; diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureProcessor.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureProcessor.java index 60586482d1..ea4dd7682e 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureProcessor.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureProcessor.java @@ -138,14 +138,11 @@ private MultiLibraryIdMeasureEngineDetails buildLibraryIdEngineDetails( var libraryVersionIdentifier = getLibraryVersionIdentifier(measure); - final LibraryEngine libraryEngine = getLibraryEngine(parameters, libraryVersionIdentifier, - context); + final LibraryEngine libraryEngine = getLibraryEngine(parameters, libraryVersionIdentifier, context); return MultiLibraryIdMeasureEngineDetails.builder(libraryEngine) - .addLibraryIdToMeasureId( - getLibraryVersionIdentifier(measure), - measure.getIdElement()) - .build(); + .addLibraryIdToMeasureId(getLibraryVersionIdentifier(measure), measure.getIdElement()) + .build(); } protected MeasureReportType evalTypeToReportType(MeasureEvalType measureEvalType) { diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java index 13e2e10838..2bc118e6a6 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java @@ -259,11 +259,9 @@ public CompositeEvaluationResultsPerMeasure evaluateMultiMeasuresWithCqlEngine( var zonedMeasurementPeriod = MeasureProcessorUtils.getZonedTimeZoneForEval( measureProcessorUtils.getDefaultMeasurementPeriod(measurementPeriodParams, context)); - // Note that we must build the LibraryEngine BEFORE we call // measureProcessorUtils.setMeasurementPeriod(), otherwise, we get an NPE. - var multiLibraryIdMeasureEngineDetails = - getMultiLibraryIdMeasureEngineDetails(measures, parameters, context); + var multiLibraryIdMeasureEngineDetails = getMultiLibraryIdMeasureEngineDetails(measures, parameters, context); // set measurement Period from CQL if operation parameters are empty measureProcessorUtils.setMeasurementPeriod( @@ -274,32 +272,22 @@ public CompositeEvaluationResultsPerMeasure evaluateMultiMeasuresWithCqlEngine( .map(url -> Optional.ofNullable(url).orElse("Unknown Measure URL")) .toList()); - // populate results from Library $evaluate return measureProcessorUtils.getEvaluationResults( - subjects, - zonedMeasurementPeriod, - context, - multiLibraryIdMeasureEngineDetails); + subjects, zonedMeasurementPeriod, context, multiLibraryIdMeasureEngineDetails); } private MultiLibraryIdMeasureEngineDetails getMultiLibraryIdMeasureEngineDetails( - List measures, - Parameters parameters, - CqlEngine context) { + List measures, Parameters parameters, CqlEngine context) { var libraryVersionedIdentifiers = - measures.stream() - .map(this::getLibraryVersionIdentifier) - .toList(); + measures.stream().map(this::getLibraryVersionIdentifier).toList(); var libraryEngine = getLibraryEngine(parameters, libraryVersionedIdentifiers, context); var builder = MultiLibraryIdMeasureEngineDetails.builder(libraryEngine); measures.forEach(measure -> { - builder.addLibraryIdToMeasureId( - this.getLibraryVersionIdentifier(measure), - measure.getIdElement()); + builder.addLibraryIdToMeasureId(this.getLibraryVersionIdentifier(measure), measure.getIdElement()); }); return builder.build(); From c213ead485a3336ceb816ca2829623d4a268d2d1 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Wed, 30 Jul 2025 14:12:28 -0400 Subject: [PATCH 55/66] Add temporary logging for db queries. --- .../fhir/cr/measure/r4/R4MeasureService.java | 3 +- .../cr/measure/r4/R4MultiMeasureService.java | 3 +- .../cpg/r4/LibraryEvaluationServiceTest.java | 2 + .../repository/RepositoryLoggingProxy.java | 98 +++++++++++++++++++ 4 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/repository/RepositoryLoggingProxy.java diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java index 06be1e14e8..574e09bac7 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java @@ -23,6 +23,7 @@ import org.opencds.cqf.fhir.utility.repository.FederatedRepository; import org.opencds.cqf.fhir.utility.repository.InMemoryFhirRepository; import org.opencds.cqf.fhir.utility.repository.Repositories; +import org.opencds.cqf.fhir.utility.repository.RepositoryLoggingProxy; public class R4MeasureService implements R4MeasureEvaluatorSingle { private final IRepository repository; @@ -35,7 +36,7 @@ public R4MeasureService( IRepository repository, MeasureEvaluationOptions measureEvaluationOptions, MeasurePeriodValidator measurePeriodValidator) { - this.repository = repository; + this.repository = RepositoryLoggingProxy.init(repository); this.measureEvaluationOptions = measureEvaluationOptions; this.measurePeriodValidator = measurePeriodValidator; this.subjectProvider = new R4RepositorySubjectProvider(measureEvaluationOptions.getSubjectProviderOptions()); diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java index 5fbb03ffc9..cf5873d12d 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java @@ -29,6 +29,7 @@ import org.opencds.cqf.fhir.utility.Ids; import org.opencds.cqf.fhir.utility.builder.BundleBuilder; import org.opencds.cqf.fhir.utility.repository.Repositories; +import org.opencds.cqf.fhir.utility.repository.RepositoryLoggingProxy; /** * Alternate MeasureService call to Process MeasureEvaluation for the selected population of subjects against n-number @@ -53,7 +54,7 @@ public R4MultiMeasureService( MeasureEvaluationOptions measureEvaluationOptions, String serverBase, MeasurePeriodValidator measurePeriodValidator) { - this.repository = repository; + this.repository = RepositoryLoggingProxy.init(repository); this.measureEvaluationOptions = measureEvaluationOptions; this.measurePeriodValidator = measurePeriodValidator; this.serverBase = serverBase; diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/cpg/r4/LibraryEvaluationServiceTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/cpg/r4/LibraryEvaluationServiceTest.java index c0855b75ba..95c8bea209 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/cpg/r4/LibraryEvaluationServiceTest.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/cpg/r4/LibraryEvaluationServiceTest.java @@ -78,6 +78,8 @@ void libraryEvaluationService_SimpleLibraryExpression() { assertTrue(((BooleanType) report.getParameter("Numerator").getValue()).booleanValue()); } + // LUKETODO: as an experiment, try the same type of test as MultiLibEvalComplexCqlTest.java + @Test void libraryEvaluationService_ErrorLibrary() { Parameters params = parameters(stringPart("subject", "Patient/SimplePatient")); diff --git a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/repository/RepositoryLoggingProxy.java b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/repository/RepositoryLoggingProxy.java new file mode 100644 index 0000000000..f94d586fb0 --- /dev/null +++ b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/repository/RepositoryLoggingProxy.java @@ -0,0 +1,98 @@ +package org.opencds.cqf.fhir.utility.repository; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.model.api.IQueryParameterType; +import ca.uhn.fhir.repository.IRepository; +import ca.uhn.fhir.rest.api.MethodOutcome; +import com.google.common.collect.Multimap; +import java.util.List; +import java.util.Map; +import org.hl7.fhir.instance.model.api.IBaseBundle; +import org.hl7.fhir.instance.model.api.IBaseParameters; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +// LUKETODO: this is temporary until performance testing is complete +public class RepositoryLoggingProxy implements IRepository { + + private static final Logger logger = LoggerFactory.getLogger(RepositoryLoggingProxy.class); + + private final IRepository repository; + + public static RepositoryLoggingProxy init(IRepository repository) { + if (repository == null) { + throw new IllegalArgumentException("Repository cannot be null"); + } + + // Don't wrap a RepositoryLoggingProxy in a RepositoryLoggingProxy + if (repository instanceof RepositoryLoggingProxy castedRepository) { + logger.info("This is already a RepositoryLoggingProxy, so returning it directly"); + return castedRepository; + } + + logger.info( + "Initializing RepositoryLoggingProxy for repository: {}", + repository.getClass().getName()); + return new RepositoryLoggingProxy(repository); + } + + private RepositoryLoggingProxy(IRepository repository) { + this.repository = repository; + } + + @Override + public T read(Class aClass, I i, Map map) { + logger.info("1234567890 - read()"); + return repository.read(aClass, i, map); + } + + @Override + public MethodOutcome create(T t, Map map) { + logger.info("1234567890 - create()"); + return repository.create(t, map); + } + + @Override + public MethodOutcome update(T t, Map map) { + logger.info("1234567890 - update()"); + return repository.update(t, map); + } + + @Override + public MethodOutcome delete( + Class aClass, I i, Map map) { + logger.info("1234567890 - delete()"); + return repository.delete(aClass, i, map); + } + + @Override + public B search( + Class aClass, + Class aClass1, + Multimap> multimap, + Map map) { + logger.info("1234567890 - search()"); + return repository.search(aClass, aClass1, multimap, map); + } + + @Override + public R invoke( + Class aClass, String s, P p, Class aClass1, Map map) { + logger.info("1234567890 - invoke()"); + return repository.invoke(aClass, s, p, aClass1, map); + } + + @Override + public R invoke( + I i, String s, P p, Class aClass, Map map) { + logger.info("1234567890 - invoke()"); + return repository.invoke(aClass, s, p, aClass, map); + } + + @Override + public FhirContext fhirContext() { + return repository.fhirContext(); + } +} From f2a065810deeb96756baef7691bf6f27d8fa887d Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Thu, 31 Jul 2025 09:30:16 -0400 Subject: [PATCH 56/66] Set up complex $evaluate test, which doesn't yet exercise multiple libraries. --- .../opencds/cqf/fhir/cql/LibraryEngine.java | 12 +- .../r4/R4PopulationBasisValidator.java | 4 +- ...braryEvaluationServiceComplexDepsTest.java | 220 ++++++++++++++++++ .../r4/libraryevalcomplexdeps/cql/Level1A.cql | 25 ++ .../r4/libraryevalcomplexdeps/cql/Level1B.cql | 23 ++ .../r4/libraryevalcomplexdeps/cql/Level2.cql | 14 ++ .../r4/libraryevalcomplexdeps/cql/Level3A.cql | 13 ++ .../r4/libraryevalcomplexdeps/cql/Level3B.cql | 11 + .../r4/libraryevalcomplexdeps/cql/Level4.cql | 12 + .../r4/libraryevalcomplexdeps/cql/Level5.cql | 16 ++ .../resources/library/Level1A.json | 17 ++ .../resources/library/Level1B.json | 17 ++ .../resources/library/Level2.json | 17 ++ .../resources/library/Level3A.json | 17 ++ .../resources/library/Level3B.json | 17 ++ .../resources/library/Level4.json | 17 ++ .../resources/library/Level5.json | 17 ++ .../tests/encounter/enc_arrived_cat1_v1.json} | 8 +- .../tests/encounter/enc_arrived_cat1_v2.json} | 8 +- .../tests/encounter/enc_arrived_cat2.json} | 6 +- .../tests/encounter/enc_arrived_cat3.json} | 6 +- .../tests/encounter/enc_cancelled_cat3.json} | 4 +- .../tests/encounter/enc_finished_cat3.json} | 4 +- .../tests/encounter/enc_planned_cat1.json} | 4 +- .../tests/encounter/enc_planned_cat2.json} | 6 +- .../tests/encounter/enc_planned_cat3.json} | 6 +- .../tests/encounter/enc_triaged_cat3.json} | 4 +- .../tests/patient/patient1.json | 6 + .../OLD/enc_cancelled_cat3_patient3.json | 17 -- .../OLD/enc_finished_cat3_patient3.json | 17 -- .../OLD/enc_planned_cat1_patient2.json | 17 -- 31 files changed, 495 insertions(+), 87 deletions(-) create mode 100644 cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/cpg/r4/LibraryEvaluationServiceComplexDepsTest.java create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/cql/Level1A.cql create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/cql/Level1B.cql create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/cql/Level2.cql create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/cql/Level3A.cql create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/cql/Level3B.cql create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/cql/Level4.cql create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/cql/Level5.cql create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/resources/library/Level1A.json create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/resources/library/Level1B.json create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/resources/library/Level2.json create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/resources/library/Level3A.json create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/resources/library/Level3B.json create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/resources/library/Level4.json create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/resources/library/Level5.json rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/{measure/r4/MultiLibEvalComplexCql/OLD/enc_arrived_cat2_patient3.json => cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_arrived_cat1_v1.json} (65%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/{measure/r4/MultiLibEvalComplexCql/OLD/enc_arrived_cat3_patient3.json => cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_arrived_cat1_v2.json} (65%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/{measure/r4/MultiLibEvalComplexCql/OLD/enc_arrived_cat2_patient2.json => cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_arrived_cat2.json} (77%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/{measure/r4/MultiLibEvalComplexCql/OLD/enc_arrived_cat3_patient2.json => cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_arrived_cat3.json} (77%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/{measure/r4/MultiLibEvalComplexCql/OLD/enc_cancelled_cat3_patient2.json => cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_cancelled_cat3.json} (77%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/{measure/r4/MultiLibEvalComplexCql/OLD/enc_finished_cat3_patient2.json => cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_finished_cat3.json} (77%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/{measure/r4/MultiLibEvalComplexCql/OLD/enc_in_progress_cat1_patient1.json => cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_planned_cat1.json} (79%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/{measure/r4/MultiLibEvalComplexCql/OLD/enc_planned_cat1_patient3.json => cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_planned_cat2.json} (66%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/{measure/r4/MultiLibEvalComplexCql/OLD/enc_triaged_cat3_patient3.json => cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_planned_cat3.json} (70%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/{measure/r4/MultiLibEvalComplexCql/OLD/enc_triaged_cat3_patient2.json => cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_triaged_cat3.json} (77%) create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/patient/patient1.json delete mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_cancelled_cat3_patient3.json delete mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_finished_cat3_patient3.json delete mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_planned_cat1_patient2.json diff --git a/cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/LibraryEngine.java b/cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/LibraryEngine.java index 94c3058e85..d04e232e64 100644 --- a/cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/LibraryEngine.java +++ b/cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/LibraryEngine.java @@ -338,11 +338,11 @@ public EvaluationResultsForMultiLib getEvaluationResult( @Nullable ZonedDateTime zonedDateTime, CqlEngine engine) { - logger.info( - "1234: ids: {}, patientId: {}, zonedDateTime: {}", - ids.stream().map(VersionedIdentifier::getId).toList(), - patientId, - zonedDateTime); + // logger.info( + // "1234: ids: {}, patientId: {}, zonedDateTime: {}", + // ids.stream().map(VersionedIdentifier::getId).toList(), + // patientId, + // zonedDateTime); final CqlFhirParametersConverter cqlFhirParametersConverterToUse = Objects.requireNonNullElseGet( cqlFhirParametersConverter, () -> Engines.getCqlFhirParametersConverter(repository.fhirContext())); @@ -380,7 +380,7 @@ public EvaluationResult getEvaluationResult( @Nullable ZonedDateTime zonedDateTime, CqlEngine engine) { - logger.info("1234: id: {}, patientId: {}, zonedDateTime: {}", id.getId(), patientId, zonedDateTime); + // logger.info("1234: id: {}, patientId: {}, zonedDateTime: {}", id.getId(), patientId, zonedDateTime); var evaluationResultsForMultiLib = getEvaluationResult( List.of(id), diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4PopulationBasisValidator.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4PopulationBasisValidator.java index 7f2028107e..bcdd68b1cd 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4PopulationBasisValidator.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4PopulationBasisValidator.java @@ -20,7 +20,6 @@ import org.hl7.fhir.r4.model.ResourceType; import org.opencds.cqf.cql.engine.execution.EvaluationResult; import org.opencds.cqf.cql.engine.runtime.Code; -import org.opencds.cqf.fhir.cr.measure.common.EvaluationResultsDisplay; import org.opencds.cqf.fhir.cr.measure.common.GroupDef; import org.opencds.cqf.fhir.cr.measure.common.MeasureDef; import org.opencds.cqf.fhir.cr.measure.common.PopulationBasisValidator; @@ -73,7 +72,8 @@ public void validateStratifiers(MeasureDef measureDef, GroupDef groupDef, Evalua private void validateGroupPopulationBasisType( String url, GroupDef groupDef, PopulationDef populationDef, EvaluationResult evaluationResult) { - logger.info("1234: evaluationResult:\n{}", EvaluationResultsDisplay.printEvaluationResult(evaluationResult)); + // logger.info("1234: evaluationResult:\n{}", + // EvaluationResultsDisplay.printEvaluationResult(evaluationResult)); // PROPORTION var scoring = groupDef.measureScoring(); diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/cpg/r4/LibraryEvaluationServiceComplexDepsTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/cpg/r4/LibraryEvaluationServiceComplexDepsTest.java new file mode 100644 index 0000000000..46534b5d18 --- /dev/null +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/cpg/r4/LibraryEvaluationServiceComplexDepsTest.java @@ -0,0 +1,220 @@ +package org.opencds.cqf.fhir.cr.cpg.r4; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.opencds.cqf.fhir.utility.r4.Parameters.parameters; +import static org.opencds.cqf.fhir.utility.r4.Parameters.stringPart; + +import java.util.Set; +import java.util.stream.Collectors; +import org.hl7.fhir.r4.model.Encounter; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.Parameters; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +@SuppressWarnings("squid:S1135") +class LibraryEvaluationServiceComplexDepsTest { + + @Test + void singleLibraryTest_1A() { + + var params = parameters(stringPart("subject", "Patient/patient1")); + var libId = new IdType("Library", "Level1A"); + var when = Library.given() + .repositoryFor("libraryevalcomplexdeps") + .when() + .id(libId) + .parameters(params) + .evaluateLibrary(); + var report = when.then().parameters(); + + assertNotNull(report); + assertTrue(report.hasParameter("Initial Population")); + assertTrue(report.hasParameter("Numerator")); + assertTrue(report.hasParameter("Denominator")); + + var initialPopulations = report.getParameters("Initial Population"); + assertNotNull(initialPopulations); + assertFalse(initialPopulations.isEmpty()); + assertEquals(10, initialPopulations.size()); + + initialPopulations.forEach(component -> { + assertNotNull(component); + assertNull(component.getValue()); + assertEquals("Initial Population", component.getName()); + assertInstanceOf(Encounter.class, component.getResource()); + }); + + var expectedInitialPopulationEncounterIds = Set.of( + "enc_planned_cat1", + "enc_cancelled_cat3", + "enc_planned_cat2", + "enc_triaged_cat3", + "enc_planned_cat3", + "enc_arrived_cat1_v2", + "enc_arrived_cat3", + "enc_arrived_cat1_v1", + "enc_arrived_cat2", + "enc_finished_cat3"); + + assertEquals( + expectedInitialPopulationEncounterIds, + initialPopulations.stream() + .map(Parameters.ParametersParameterComponent::getResource) + .map(r -> r.getIdElement().getIdPart()) + .collect(Collectors.toUnmodifiableSet())); + + var denominators = report.getParameters("Denominator"); + assertNotNull(denominators); + assertFalse(denominators.isEmpty()); + assertEquals(7, denominators.size()); + + denominators.forEach(component -> { + assertNotNull(component); + assertNull(component.getValue()); + assertEquals("Denominator", component.getName()); + assertInstanceOf(Encounter.class, component.getResource()); + }); + + var expectedDenominatorEncounterIds = Set.of( + "enc_planned_cat1", + "enc_planned_cat2", + "enc_planned_cat3", + "enc_arrived_cat1_v2", + "enc_arrived_cat3", + "enc_arrived_cat1_v1", + "enc_arrived_cat2"); + + assertEquals( + expectedDenominatorEncounterIds, + denominators.stream() + .map(Parameters.ParametersParameterComponent::getResource) + .map(r -> r.getIdElement().getIdPart()) + .collect(Collectors.toUnmodifiableSet())); + + var numerators = report.getParameters("Numerator"); + assertNotNull(numerators); + assertFalse(numerators.isEmpty()); + assertEquals(3, numerators.size()); + + numerators.forEach(component -> { + assertNotNull(component); + assertNull(component.getValue()); + assertEquals("Numerator", component.getName()); + assertInstanceOf(Encounter.class, component.getResource()); + }); + + var expectedNumeratorEncounterIds = Set.of("enc_planned_cat1", "enc_planned_cat2", "enc_planned_cat3"); + + assertEquals( + expectedNumeratorEncounterIds, + numerators.stream() + .map(Parameters.ParametersParameterComponent::getResource) + .map(r -> r.getIdElement().getIdPart()) + .collect(Collectors.toUnmodifiableSet())); + } + + @Test + void singleLibraryTest_1B() { + var params = parameters(stringPart("subject", "Patient/patient1")); + var libId = new IdType("Library", "Level1B"); + var when = Library.given() + .repositoryFor("libraryevalcomplexdeps") + .when() + .id(libId) + .parameters(params) + .evaluateLibrary(); + var report = when.then().parameters(); + + assertNotNull(report); + assertTrue(report.hasParameter("Initial Population")); + assertTrue(report.hasParameter("Numerator")); + assertTrue(report.hasParameter("Denominator")); + + var initialPopulations = report.getParameters("Initial Population"); + assertNotNull(initialPopulations); + assertFalse(initialPopulations.isEmpty()); + assertEquals(10, initialPopulations.size()); + + initialPopulations.forEach(component -> { + assertNotNull(component); + assertNull(component.getValue()); + assertEquals("Initial Population", component.getName()); + assertInstanceOf(Encounter.class, component.getResource()); + }); + + var expectedInitialPopulationEncounterIds = Set.of( + "enc_planned_cat1", + "enc_cancelled_cat3", + "enc_planned_cat2", + "enc_triaged_cat3", + "enc_planned_cat3", + "enc_arrived_cat1_v2", + "enc_arrived_cat3", + "enc_arrived_cat1_v1", + "enc_arrived_cat2", + "enc_finished_cat3"); + + assertEquals( + expectedInitialPopulationEncounterIds, + initialPopulations.stream() + .map(Parameters.ParametersParameterComponent::getResource) + .map(r -> r.getIdElement().getIdPart()) + .collect(Collectors.toUnmodifiableSet())); + + var denominators = report.getParameters("Denominator"); + assertNotNull(denominators); + assertFalse(denominators.isEmpty()); + assertEquals(3, denominators.size()); + + denominators.forEach(component -> { + assertNotNull(component); + assertNull(component.getValue()); + assertEquals("Denominator", component.getName()); + assertInstanceOf(Encounter.class, component.getResource()); + }); + + var expectedDenominatorEncounterIds = Set.of("enc_planned_cat1", "enc_arrived_cat1_v2", "enc_arrived_cat1_v1"); + + assertEquals( + expectedDenominatorEncounterIds, + denominators.stream() + .map(Parameters.ParametersParameterComponent::getResource) + .map(r -> r.getIdElement().getIdPart()) + .collect(Collectors.toUnmodifiableSet())); + + var numerators = report.getParameters("Numerator"); + assertNotNull(numerators); + assertFalse(numerators.isEmpty()); + assertEquals(1, numerators.size()); + + numerators.forEach(component -> { + assertNotNull(component); + assertNull(component.getValue()); + assertEquals("Numerator", component.getName()); + assertInstanceOf(Encounter.class, component.getResource()); + }); + + var expectedNumeratorEncounterIds = Set.of("enc_planned_cat1"); + + assertEquals( + expectedNumeratorEncounterIds, + numerators.stream() + .map(Parameters.ParametersParameterComponent::getResource) + .map(r -> r.getIdElement().getIdPart()) + .collect(Collectors.toUnmodifiableSet())); + } + + @Disabled + @Test + void multipleLibraryTest_1A_and_1B() { + // LUKETODO: how to do 2 libraries in one test? + fail("Not implemeted yet"); + } +} diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/cql/Level1A.cql b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/cql/Level1A.cql new file mode 100644 index 0000000000..b0417b9dc3 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/cql/Level1A.cql @@ -0,0 +1,25 @@ +library Level1A + +using FHIR version '4.0.1' + +include FHIRHelpers version '4.0.1' + +// Level 5 > { 'arrived', 'finished', 'in-progress', 'planned', 'triaged' } +// Level 3A > { 'arrived', 'in-progress', 'planned', 'triaged' } +// Level 2 > { 'arrived', 'in-progress', 'planned' } +include Level2 called Level2 + +parameter "Measurement Period" Interval default Interval[@2021-01-01T00:00:00.000, @2022-01-01T00:00:00.000) + +context Patient + +define "Initial Population": + [Encounter] E + +define "Denominator": + Level2."Encounters" E + where E.status.value in { 'arrived', 'cancelled', 'in-progress', 'planned' } + +define "Numerator": + Level2."Encounters" E + where E.status.value in { 'cancelled', 'finished', 'in-progress', 'planned' } diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/cql/Level1B.cql b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/cql/Level1B.cql new file mode 100644 index 0000000000..0407621cb6 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/cql/Level1B.cql @@ -0,0 +1,23 @@ +library Level1B + +using FHIR version '4.0.1' + +include FHIRHelpers version '4.0.1' + +include Level2 called Level2 +include Level3B called Level3B + +parameter "Measurement Period" Interval default Interval[@2021-07-01T00:00:00.000, @2022-07-01T00:00:00.000) + +define "Initial Population": + [Encounter] + +define "Denominator": + Level2."Encounters" E + where E.status.value in { 'arrived', 'cancelled', 'in-progress', 'planned' } and + E.meta.profile[0] in Level3B."Encounter Profiles" + +define "Numerator": + Level2."Encounters" E + where E.status.value in { 'finished', 'cancelled', 'in-progress', 'planned' } and + E.meta.profile[0] in Level3B."Encounter Profiles" diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/cql/Level2.cql b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/cql/Level2.cql new file mode 100644 index 0000000000..1f463516be --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/cql/Level2.cql @@ -0,0 +1,14 @@ +library Level2 + +using FHIR version '4.0.1' + +include FHIRHelpers version '4.0.1' + +// Level 5 > { 'arrived', 'finished', 'in-progress', 'planned', 'triaged' } +// Level 3A > { 'arrived', 'in-progress', 'planned', 'triaged' } +include Level3A called Level3A + +// Level 2 > { 'arrived', 'in-progress', 'planned' } +define "Encounters": + Level3A."Encounters" E + where not(E.status.value in { 'triaged' }) \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/cql/Level3A.cql b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/cql/Level3A.cql new file mode 100644 index 0000000000..6d29c55108 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/cql/Level3A.cql @@ -0,0 +1,13 @@ +library Level3A + +using FHIR version '4.0.1' + +include FHIRHelpers version '4.0.1' + +// Level 5 > { 'arrived', 'finished', 'in-progress', 'planned', 'triaged' } +include Level5 called Level5 + +// Level 3A > { 'arrived', 'in-progress', 'planned', 'triaged' } +define "Encounters": + Level5."Encounters" E + where not(E.status.value in { 'finished' }) diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/cql/Level3B.cql b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/cql/Level3B.cql new file mode 100644 index 0000000000..aa0737aba6 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/cql/Level3B.cql @@ -0,0 +1,11 @@ +library Level3B + +using FHIR version '4.0.1' + +include FHIRHelpers version '4.0.1' + +include Level4 called Level4 + +define "Encounter Profiles": + Level4."Encounter Profiles" P + where not (P in { 'http://example.com/category2' }) diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/cql/Level4.cql b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/cql/Level4.cql new file mode 100644 index 0000000000..c581fcc63a --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/cql/Level4.cql @@ -0,0 +1,12 @@ +library Level4 + +using FHIR version '4.0.1' + +include FHIRHelpers version '4.0.1' + +include Level5 called Level5 + +define "Encounter Profiles": + Level5."Encounters" E + where not (E.meta.profile[0] in { 'http://example.com/category3' }) + return E.meta.profile[0] diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/cql/Level5.cql b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/cql/Level5.cql new file mode 100644 index 0000000000..7a8292979f --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/cql/Level5.cql @@ -0,0 +1,16 @@ +library Level5 + +using FHIR version '4.0.1' + +include FHIRHelpers version '4.0.1' + +context Patient + +//// Level 5 > { 'cat1', cat2', 'cat3' } +//define "Encounters": +// [Encounter] E +// where E.meta.profile[0] in { 'http://example.com/category1', 'http://example.com/category2', 'http://example.com/category3' } +// Level 5 > { 'arrived', 'finished', 'in-progress', 'planned', 'triaged' } +define "Encounters": + [Encounter] E + where E.status.value in { 'arrived', 'planned', 'triaged', 'finished', 'in-progress' } diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/resources/library/Level1A.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/resources/library/Level1A.json new file mode 100644 index 0000000000..0d0a36c46e --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/resources/library/Level1A.json @@ -0,0 +1,17 @@ +{ + "resourceType": "Library", + "id": "Level1A", + "url": "http://example.com/Library/Level1A", + "name": "Level1A", + "status": "active", + "type": { + "coding": [ { + "system": "http://terminology.hl7.org/CodeSystem/library-type", + "code": "logic-library" + } ] + }, + "content": [ { + "contentType": "text/cql", + "url": "../../cql/Level1A.cql" + } ] +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/resources/library/Level1B.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/resources/library/Level1B.json new file mode 100644 index 0000000000..95896df10c --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/resources/library/Level1B.json @@ -0,0 +1,17 @@ +{ + "resourceType": "Library", + "id": "Level1B", + "url": "http://example.com/Library/Level1B", + "name": "Level1B", + "status": "active", + "type": { + "coding": [ { + "system": "http://terminology.hl7.org/CodeSystem/library-type", + "code": "logic-library" + } ] + }, + "content": [ { + "contentType": "text/cql", + "url": "../../cql/Level1B.cql" + } ] +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/resources/library/Level2.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/resources/library/Level2.json new file mode 100644 index 0000000000..0ddf2d4277 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/resources/library/Level2.json @@ -0,0 +1,17 @@ +{ + "resourceType": "Library", + "id": "Level2", + "url": "http://example.com/Library/Level2", + "name": "Level2", + "status": "active", + "type": { + "coding": [ { + "system": "http://terminology.hl7.org/CodeSystem/library-type", + "code": "logic-library" + } ] + }, + "content": [ { + "contentType": "text/cql", + "url": "../../cql/Level2.cql" + } ] +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/resources/library/Level3A.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/resources/library/Level3A.json new file mode 100644 index 0000000000..6b07d64303 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/resources/library/Level3A.json @@ -0,0 +1,17 @@ +{ + "resourceType": "Library", + "id": "Level3A", + "url": "http://example.com/Library/Level3A", + "name": "Level3A", + "status": "active", + "type": { + "coding": [ { + "system": "http://terminology.hl7.org/CodeSystem/library-type", + "code": "logic-library" + } ] + }, + "content": [ { + "contentType": "text/cql", + "url": "../../cql/Level3A.cql" + } ] +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/resources/library/Level3B.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/resources/library/Level3B.json new file mode 100644 index 0000000000..0d56d7abee --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/resources/library/Level3B.json @@ -0,0 +1,17 @@ +{ + "resourceType": "Library", + "id": "Level3B", + "url": "http://example.com/Library/Level3B", + "name": "Level3B", + "status": "active", + "type": { + "coding": [ { + "system": "http://terminology.hl7.org/CodeSystem/library-type", + "code": "logic-library" + } ] + }, + "content": [ { + "contentType": "text/cql", + "url": "../../cql/Level3B.cql" + } ] +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/resources/library/Level4.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/resources/library/Level4.json new file mode 100644 index 0000000000..66aa069b51 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/resources/library/Level4.json @@ -0,0 +1,17 @@ +{ + "resourceType": "Library", + "id": "Level4", + "url": "http://example.com/Library/Level4", + "name": "Level4", + "status": "active", + "type": { + "coding": [ { + "system": "http://terminology.hl7.org/CodeSystem/library-type", + "code": "logic-library" + } ] + }, + "content": [ { + "contentType": "text/cql", + "url": "../../cql/Level4.cql" + } ] +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/resources/library/Level5.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/resources/library/Level5.json new file mode 100644 index 0000000000..f55154f74b --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/resources/library/Level5.json @@ -0,0 +1,17 @@ +{ + "resourceType": "Library", + "id": "Level5", + "url": "http://example.com/Library/Level5", + "name": "Level5", + "status": "active", + "type": { + "coding": [ { + "system": "http://terminology.hl7.org/CodeSystem/library-type", + "code": "logic-library" + } ] + }, + "content": [ { + "contentType": "text/cql", + "url": "../../cql/Level5.cql" + } ] +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_arrived_cat2_patient3.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_arrived_cat1_v1.json similarity index 65% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_arrived_cat2_patient3.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_arrived_cat1_v1.json index 2194aaf58c..88fb8c559d 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_arrived_cat2_patient3.json +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_arrived_cat1_v1.json @@ -1,17 +1,17 @@ { "resourceType": "Encounter", - "id": "enc_arrived_cat2_patient3", + "id": "enc_arrived_cat1_v1", "meta": { "profile": [ - "http://example.com/category2" + "http://example.com/category1" ] }, "status": "arrived", "subject": { - "reference": "Patient/patient3" + "reference": "Patient/patient1" }, "period": { "start": "2000-01-01T00:00:00-05:00", "end": "2000-12-31T00:00:00-05:00" } -} +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_arrived_cat3_patient3.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_arrived_cat1_v2.json similarity index 65% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_arrived_cat3_patient3.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_arrived_cat1_v2.json index 2a6242a089..ed8dbc3ffc 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_arrived_cat3_patient3.json +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_arrived_cat1_v2.json @@ -1,17 +1,17 @@ { "resourceType": "Encounter", - "id": "enc_arrived_cat3_patient3", + "id": "enc_arrived_cat1_v2", "meta": { "profile": [ - "http://example.com/category3" + "http://example.com/category1" ] }, "status": "arrived", "subject": { - "reference": "Patient/patient3" + "reference": "Patient/patient1" }, "period": { "start": "2000-01-01T00:00:00-05:00", "end": "2000-12-31T00:00:00-05:00" } -} +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_arrived_cat2_patient2.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_arrived_cat2.json similarity index 77% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_arrived_cat2_patient2.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_arrived_cat2.json index 7bf6270271..8a84193482 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_arrived_cat2_patient2.json +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_arrived_cat2.json @@ -1,6 +1,6 @@ { "resourceType": "Encounter", - "id": "enc_arrived_cat2_patient2", + "id": "enc_arrived_cat2", "meta": { "profile": [ "http://example.com/category2" @@ -8,10 +8,10 @@ }, "status": "arrived", "subject": { - "reference": "Patient/patient2" + "reference": "Patient/patient1" }, "period": { "start": "2000-01-01T00:00:00-05:00", "end": "2000-12-31T00:00:00-05:00" } -} +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_arrived_cat3_patient2.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_arrived_cat3.json similarity index 77% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_arrived_cat3_patient2.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_arrived_cat3.json index 12ea07b90f..4bb869bfe9 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_arrived_cat3_patient2.json +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_arrived_cat3.json @@ -1,6 +1,6 @@ { "resourceType": "Encounter", - "id": "enc_arrived_cat3_patient2", + "id": "enc_arrived_cat3", "meta": { "profile": [ "http://example.com/category3" @@ -8,10 +8,10 @@ }, "status": "arrived", "subject": { - "reference": "Patient/patient2" + "reference": "Patient/patient1" }, "period": { "start": "2000-01-01T00:00:00-05:00", "end": "2000-12-31T00:00:00-05:00" } -} +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_cancelled_cat3_patient2.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_cancelled_cat3.json similarity index 77% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_cancelled_cat3_patient2.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_cancelled_cat3.json index 5a6e7caf99..a4d312f17f 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_cancelled_cat3_patient2.json +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_cancelled_cat3.json @@ -1,6 +1,6 @@ { "resourceType": "Encounter", - "id": "enc_cancelled_cat3_patient2", + "id": "enc_cancelled_cat3", "meta": { "profile": [ "http://example.com/category3" @@ -8,7 +8,7 @@ }, "status": "cancelled", "subject": { - "reference": "Patient/patient2" + "reference": "Patient/patient1" }, "period": { "start": "2001-01-01T00:00:00-05:00", diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_finished_cat3_patient2.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_finished_cat3.json similarity index 77% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_finished_cat3_patient2.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_finished_cat3.json index ebf508a710..e13acc6d31 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_finished_cat3_patient2.json +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_finished_cat3.json @@ -1,6 +1,6 @@ { "resourceType": "Encounter", - "id": "enc_finished_cat3_patient2", + "id": "enc_finished_cat3", "meta": { "profile": [ "http://example.com/category3" @@ -8,7 +8,7 @@ }, "status": "finished", "subject": { - "reference": "Patient/patient2" + "reference": "Patient/patient1" }, "period": { "start": "2001-01-01T00:00:00-05:00", diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_in_progress_cat1_patient1.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_planned_cat1.json similarity index 79% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_in_progress_cat1_patient1.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_planned_cat1.json index 940642142b..a4bf19ee8f 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_in_progress_cat1_patient1.json +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_planned_cat1.json @@ -1,12 +1,12 @@ { "resourceType": "Encounter", - "id": "enc_in_progress_cat1_patient1", + "id": "enc_planned_cat1", "meta": { "profile": [ "http://example.com/category1" ] }, - "status": "in-progress", + "status": "planned", "subject": { "reference": "Patient/patient1" }, diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_planned_cat1_patient3.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_planned_cat2.json similarity index 66% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_planned_cat1_patient3.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_planned_cat2.json index e81383a98d..69e8a2ee74 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_planned_cat1_patient3.json +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_planned_cat2.json @@ -1,14 +1,14 @@ { "resourceType": "Encounter", - "id": "enc_planned_cat1_patient3", + "id": "enc_planned_cat2", "meta": { "profile": [ - "http://example.com/category1" + "http://example.com/category2" ] }, "status": "planned", "subject": { - "reference": "Patient/patient3" + "reference": "Patient/patient1" }, "period": { "start": "2001-01-01T00:00:00-05:00", diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_triaged_cat3_patient3.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_planned_cat3.json similarity index 70% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_triaged_cat3_patient3.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_planned_cat3.json index fc628d1b4e..6e0fdf3ee3 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_triaged_cat3_patient3.json +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_planned_cat3.json @@ -1,14 +1,14 @@ { "resourceType": "Encounter", - "id": "enc_triaged_cat3_patient3", + "id": "enc_planned_cat3", "meta": { "profile": [ "http://example.com/category3" ] }, - "status": "triaged", + "status": "planned", "subject": { - "reference": "Patient/patient3" + "reference": "Patient/patient1" }, "period": { "start": "2001-01-01T00:00:00-05:00", diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_triaged_cat3_patient2.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_triaged_cat3.json similarity index 77% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_triaged_cat3_patient2.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_triaged_cat3.json index 7bc0184e73..bc4d8cbf34 100644 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_triaged_cat3_patient2.json +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_triaged_cat3.json @@ -1,6 +1,6 @@ { "resourceType": "Encounter", - "id": "enc_triaged_cat3_patient2", + "id": "enc_triaged_cat3", "meta": { "profile": [ "http://example.com/category3" @@ -8,7 +8,7 @@ }, "status": "triaged", "subject": { - "reference": "Patient/patient2" + "reference": "Patient/patient1" }, "period": { "start": "2001-01-01T00:00:00-05:00", diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/patient/patient1.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/patient/patient1.json new file mode 100644 index 0000000000..179f54afb8 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/patient/patient1.json @@ -0,0 +1,6 @@ +{ + "resourceType": "Patient", + "id": "patient1", + "gender": "female", + "birthDate": "1904-01-01" +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_cancelled_cat3_patient3.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_cancelled_cat3_patient3.json deleted file mode 100644 index 536e64dd32..0000000000 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_cancelled_cat3_patient3.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "resourceType": "Encounter", - "id": "enc_cancelled_cat3_patient3", - "meta": { - "profile": [ - "http://example.com/category3" - ] - }, - "status": "cancelled", - "subject": { - "reference": "Patient/patient3" - }, - "period": { - "start": "2001-01-01T00:00:00-05:00", - "end": "2001-12-31T00:00:00-05:00" - } -} diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_finished_cat3_patient3.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_finished_cat3_patient3.json deleted file mode 100644 index 90c07439b7..0000000000 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_finished_cat3_patient3.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "resourceType": "Encounter", - "id": "enc_finished_cat3_patient3", - "meta": { - "profile": [ - "http://example.com/category3" - ] - }, - "status": "finished", - "subject": { - "reference": "Patient/patient3" - }, - "period": { - "start": "2001-01-01T00:00:00-05:00", - "end": "2001-12-31T00:00:00-05:00" - } -} diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_planned_cat1_patient2.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_planned_cat1_patient2.json deleted file mode 100644 index 7d5479cc44..0000000000 --- a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/OLD/enc_planned_cat1_patient2.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "resourceType": "Encounter", - "id": "enc_planned_cat1_patient2", - "meta": { - "profile": [ - "http://example.com/category1" - ] - }, - "status": "planned", - "subject": { - "reference": "Patient/patient2" - }, - "period": { - "start": "2001-01-01T00:00:00-05:00", - "end": "2001-12-31T00:00:00-05:00" - } -} From 127efb6fee88811d71798e7f2abf5fab104b8d6e Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Thu, 31 Jul 2025 12:07:38 -0400 Subject: [PATCH 57/66] Adapt to new CQL result handling code, notably for error and exception handling. --- .../opencds/cqf/fhir/cql/LibraryEngine.java | 2 +- .../measure/common/MeasureProcessorUtils.java | 31 ++++++++++--------- .../measure/dstu3/Dstu3MeasureProcessor.java | 7 ++++- .../cr/measure/r4/R4MeasureProcessor.java | 19 +++++++++--- .../cpg/r4/LibraryEvaluationServiceTest.java | 4 +-- 5 files changed, 38 insertions(+), 25 deletions(-) diff --git a/cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/LibraryEngine.java b/cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/LibraryEngine.java index d04e232e64..9ef7d521e9 100644 --- a/cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/LibraryEngine.java +++ b/cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/LibraryEngine.java @@ -393,6 +393,6 @@ public EvaluationResult getEvaluationResult( zonedDateTime, engine); - return evaluationResultsForMultiLib.getSingleResultOrThrow(); + return evaluationResultsForMultiLib.getOnlyResultOrThrow(); } } diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java index a1909b3d8b..8ec61b610e 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java @@ -24,7 +24,6 @@ import org.opencds.cqf.cql.engine.execution.EvaluationResult; import org.opencds.cqf.cql.engine.execution.EvaluationResultsForMultiLib; import org.opencds.cqf.cql.engine.execution.Libraries; -import org.opencds.cqf.cql.engine.execution.SearchableLibraryIdentifier; import org.opencds.cqf.cql.engine.execution.Variable; import org.opencds.cqf.cql.engine.runtime.Date; import org.opencds.cqf.cql.engine.runtime.DateTime; @@ -388,27 +387,31 @@ public CompositeEvaluationResultsPerMeasure getEvaluationResults( zonedMeasurementPeriod, context); - var errorsById = evaluationResultsForMultiLib.getErrors(); - for (var libraryVersionedIdentifier : libraryIdentifiers) { - var searchableLibraryIdentifier = - SearchableLibraryIdentifier.fromIdentifier(libraryVersionedIdentifier); - validateEvaluationResultExistsForIdentifier( libraryVersionedIdentifier, evaluationResultsForMultiLib); - var evaluationResult = - evaluationResultsForMultiLib.getResults().getOrDefault(searchableLibraryIdentifier, null); + var evaluationResult = evaluationResultsForMultiLib.getResultFor(libraryVersionedIdentifier); var measureIds = multiLibraryIdMeasureEngineDetails.getMeasureIdsForLibrary(libraryVersionedIdentifier); resultsBuilder.addResults(measureIds, subjectId, evaluationResult); + var error = evaluationResultsForMultiLib.getErrorFor(libraryVersionedIdentifier); + + Optional.ofNullable(error) + .map(nonNullError -> + EXCEPTION_FOR_SUBJECT_ID_MESSAGE_TEMPLATE.formatted(subjectId, nonNullError)) + .ifPresent(nonNullError -> resultsBuilder.addErrors(measureIds, nonNullError)); + + var exceptions = evaluationResultsForMultiLib.getExceptionsFor(libraryVersionedIdentifier); + // LUKETODO: add this to the composite class instead? - Optional.ofNullable(errorsById.get(searchableLibraryIdentifier)) - .map(nonNull -> EXCEPTION_FOR_SUBJECT_ID_MESSAGE_TEMPLATE.formatted(subjectId, nonNull)) - .ifPresent(error -> resultsBuilder.addErrors(measureIds, error)); + exceptions.stream() + .map(exception -> EXCEPTION_FOR_SUBJECT_ID_MESSAGE_TEMPLATE.formatted( + subjectId, exception.getMessage())) + .forEach(exception -> resultsBuilder.addErrors(measureIds, exception)); } } catch (Exception e) { @@ -428,10 +431,8 @@ private void validateEvaluationResultExistsForIdentifier( VersionedIdentifier versionedIdentifierFromQuery, EvaluationResultsForMultiLib evaluationResultsForMultiLib) { - var searchableLibraryIdentifier = SearchableLibraryIdentifier.fromIdentifier(versionedIdentifierFromQuery); - - var containsResults = evaluationResultsForMultiLib.getResults().containsKey(searchableLibraryIdentifier); - var containsError = evaluationResultsForMultiLib.getErrors().containsKey(searchableLibraryIdentifier); + var containsResults = evaluationResultsForMultiLib.containsResultsFor(versionedIdentifierFromQuery); + var containsError = evaluationResultsForMultiLib.containsErrorsOrExceptionsFor(versionedIdentifierFromQuery); if (!containsResults && !containsError) { throw new InternalErrorException( diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureProcessor.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureProcessor.java index ea4dd7682e..2cd799bb4f 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureProcessor.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureProcessor.java @@ -141,7 +141,12 @@ private MultiLibraryIdMeasureEngineDetails buildLibraryIdEngineDetails( final LibraryEngine libraryEngine = getLibraryEngine(parameters, libraryVersionIdentifier, context); return MultiLibraryIdMeasureEngineDetails.builder(libraryEngine) - .addLibraryIdToMeasureId(getLibraryVersionIdentifier(measure), measure.getIdElement()) + .addLibraryIdToMeasureId( + new VersionedIdentifier() + .withId( + libraryVersionIdentifier + .getId()), // LUKETODO: is it wise to do this here and not later? + measure.getIdElement()) .build(); } diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java index 2bc118e6a6..9a74318e1e 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java @@ -3,6 +3,7 @@ import ca.uhn.fhir.repository.IRepository; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import com.google.common.collect.ImmutableListMultimap; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; import java.time.ZonedDateTime; @@ -25,6 +26,7 @@ import org.hl7.fhir.r4.model.Measure; import org.hl7.fhir.r4.model.MeasureReport; import org.hl7.fhir.r4.model.Parameters; +import org.hl7.fhir.r4.model.Resource; import org.opencds.cqf.cql.engine.execution.CqlEngine; import org.opencds.cqf.cql.engine.execution.EvaluationResult; import org.opencds.cqf.cql.engine.fhir.model.R4FhirModelResolver; @@ -277,17 +279,24 @@ public CompositeEvaluationResultsPerMeasure evaluateMultiMeasuresWithCqlEngine( subjects, zonedMeasurementPeriod, context, multiLibraryIdMeasureEngineDetails); } + // LUKETODO: this method is incoherent: who gets the versioned identifiers? private MultiLibraryIdMeasureEngineDetails getMultiLibraryIdMeasureEngineDetails( List measures, Parameters parameters, CqlEngine context) { - var libraryVersionedIdentifiers = - measures.stream().map(this::getLibraryVersionIdentifier).toList(); - var libraryEngine = getLibraryEngine(parameters, libraryVersionedIdentifiers, context); + var libraryIdentifiersToMeasureIds = measures.stream() + .collect(ImmutableListMultimap.toImmutableListMultimap( + this::getLibraryVersionIdentifier, // Key function + Resource::getIdElement // Value function + )); + + var libraryEngine = getLibraryEngine(parameters, List.copyOf(libraryIdentifiersToMeasureIds.keySet()), context); var builder = MultiLibraryIdMeasureEngineDetails.builder(libraryEngine); - measures.forEach(measure -> { - builder.addLibraryIdToMeasureId(this.getLibraryVersionIdentifier(measure), measure.getIdElement()); + libraryIdentifiersToMeasureIds.entries().forEach(entry -> { + builder.addLibraryIdToMeasureId( + new VersionedIdentifier().withId(entry.getKey().getId()), // LUKETODO: is it wise to do this here? + entry.getValue()); }); return builder.build(); diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/cpg/r4/LibraryEvaluationServiceTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/cpg/r4/LibraryEvaluationServiceTest.java index 95c8bea209..7c24ab62ab 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/cpg/r4/LibraryEvaluationServiceTest.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/cpg/r4/LibraryEvaluationServiceTest.java @@ -78,8 +78,6 @@ void libraryEvaluationService_SimpleLibraryExpression() { assertTrue(((BooleanType) report.getParameter("Numerator").getValue()).booleanValue()); } - // LUKETODO: as an experiment, try the same type of test as MultiLibEvalComplexCqlTest.java - @Test void libraryEvaluationService_ErrorLibrary() { Parameters params = parameters(stringPart("subject", "Patient/SimplePatient")); @@ -100,7 +98,7 @@ void libraryEvaluationService_ErrorLibrary() { var issue = outcome.getIssueFirstRep(); assertEquals(OperationOutcome.IssueSeverity.ERROR, issue.getSeverity()); assertEquals( - "Exception for Library: ErrorLibrary, Message: Example Failure Code: This is an error message", + "Example Failure Code: This is an error message", issue.getDetails().getText().replaceAll("[\\r\\n]", "")); } From 1a629568c6c23298aaad52cbf524065323393ab7 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Thu, 31 Jul 2025 15:58:07 -0400 Subject: [PATCH 58/66] Adapt to new CQL exception handling code. Comment out RepositoryLoggingProxy. --- .../org/opencds/cqf/fhir/cr/cli/CliTest.java | 2 +- .../measure/common/MeasureProcessorUtils.java | 25 +++++++------------ .../fhir/cr/measure/r4/R4MeasureService.java | 4 +-- .../cr/measure/r4/R4MultiMeasureService.java | 4 +-- 4 files changed, 14 insertions(+), 21 deletions(-) diff --git a/cqf-fhir-cr-cli/src/test/java/org/opencds/cqf/fhir/cr/cli/CliTest.java b/cqf-fhir-cr-cli/src/test/java/org/opencds/cqf/fhir/cr/cli/CliTest.java index a35d782cba..736ee5e7a2 100644 --- a/cqf-fhir-cr-cli/src/test/java/org/opencds/cqf/fhir/cr/cli/CliTest.java +++ b/cqf-fhir-cr-cli/src/test/java/org/opencds/cqf/fhir/cr/cli/CliTest.java @@ -334,7 +334,7 @@ void optionsFailure() { Main.run(args); String errOutput = errContent.toString(); - assertTrue(errOutput.contains("library FluentFunctions loaded, but had errors")); + assertTrue(errOutput.contains("Library FluentFunctions loaded, but had errors")); } @Test diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java index 8ec61b610e..d4afcb0154 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureProcessorUtils.java @@ -398,20 +398,13 @@ public CompositeEvaluationResultsPerMeasure getEvaluationResults( resultsBuilder.addResults(measureIds, subjectId, evaluationResult); - var error = evaluationResultsForMultiLib.getErrorFor(libraryVersionedIdentifier); - - Optional.ofNullable(error) - .map(nonNullError -> - EXCEPTION_FOR_SUBJECT_ID_MESSAGE_TEMPLATE.formatted(subjectId, nonNullError)) - .ifPresent(nonNullError -> resultsBuilder.addErrors(measureIds, nonNullError)); - - var exceptions = evaluationResultsForMultiLib.getExceptionsFor(libraryVersionedIdentifier); - - // LUKETODO: add this to the composite class instead? - exceptions.stream() - .map(exception -> EXCEPTION_FOR_SUBJECT_ID_MESSAGE_TEMPLATE.formatted( - subjectId, exception.getMessage())) - .forEach(exception -> resultsBuilder.addErrors(measureIds, exception)); + Optional.ofNullable(evaluationResultsForMultiLib.getExceptionFor(libraryVersionedIdentifier)) + .ifPresent(exception -> { + var error = EXCEPTION_FOR_SUBJECT_ID_MESSAGE_TEMPLATE.formatted( + subjectId, exception.getMessage()); + resultsBuilder.addErrors(measureIds, error); + logger.error(error, exception); + }); } } catch (Exception e) { @@ -432,9 +425,9 @@ private void validateEvaluationResultExistsForIdentifier( EvaluationResultsForMultiLib evaluationResultsForMultiLib) { var containsResults = evaluationResultsForMultiLib.containsResultsFor(versionedIdentifierFromQuery); - var containsError = evaluationResultsForMultiLib.containsErrorsOrExceptionsFor(versionedIdentifierFromQuery); + var containsExceptions = evaluationResultsForMultiLib.containsExceptionsFor(versionedIdentifierFromQuery); - if (!containsResults && !containsError) { + if (!containsResults && !containsExceptions) { throw new InternalErrorException( "Evaluation result in versionless search not found for identifier with ID: %s" .formatted(versionedIdentifierFromQuery.getId())); diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java index 574e09bac7..0c273e2858 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java @@ -23,7 +23,6 @@ import org.opencds.cqf.fhir.utility.repository.FederatedRepository; import org.opencds.cqf.fhir.utility.repository.InMemoryFhirRepository; import org.opencds.cqf.fhir.utility.repository.Repositories; -import org.opencds.cqf.fhir.utility.repository.RepositoryLoggingProxy; public class R4MeasureService implements R4MeasureEvaluatorSingle { private final IRepository repository; @@ -36,7 +35,8 @@ public R4MeasureService( IRepository repository, MeasureEvaluationOptions measureEvaluationOptions, MeasurePeriodValidator measurePeriodValidator) { - this.repository = RepositoryLoggingProxy.init(repository); + // this.repository = RepositoryLoggingProxy.init(repository); + this.repository = repository; this.measureEvaluationOptions = measureEvaluationOptions; this.measurePeriodValidator = measurePeriodValidator; this.subjectProvider = new R4RepositorySubjectProvider(measureEvaluationOptions.getSubjectProviderOptions()); diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java index cf5873d12d..ccf6f3f0b1 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java @@ -29,7 +29,6 @@ import org.opencds.cqf.fhir.utility.Ids; import org.opencds.cqf.fhir.utility.builder.BundleBuilder; import org.opencds.cqf.fhir.utility.repository.Repositories; -import org.opencds.cqf.fhir.utility.repository.RepositoryLoggingProxy; /** * Alternate MeasureService call to Process MeasureEvaluation for the selected population of subjects against n-number @@ -54,7 +53,8 @@ public R4MultiMeasureService( MeasureEvaluationOptions measureEvaluationOptions, String serverBase, MeasurePeriodValidator measurePeriodValidator) { - this.repository = RepositoryLoggingProxy.init(repository); + // this.repository = RepositoryLoggingProxy.init(repository); + this.repository = repository; this.measureEvaluationOptions = measureEvaluationOptions; this.measurePeriodValidator = measurePeriodValidator; this.serverBase = serverBase; From 68ce13ee32bd35630694dad1d34a94c3084f5dd4 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Fri, 8 Aug 2025 10:51:12 -0400 Subject: [PATCH 59/66] Adapt to new CQL library resolution/compilation code. Add a test that captures the scenario we're trying to optimize for. Start getting rid of logs. --- .../opencds/cqf/fhir/cql/LibraryEngine.java | 4 +- .../cr/measure/r4/R4MeasureProcessor.java | 82 +++++++++++++++++-- .../r4/utils/R4MeasureServiceUtils.java | 20 +++++ ...lExpressionDefCachingOptimizationTest.java | 46 +++++++++++ .../cr/measure/r4/InvalidMeasureTest.java | 2 +- .../r4/MultiLibEvalComplexCqlTest.java | 29 ++++++- .../measure/r4/CqlOptimize/cql/libraryA.cql | 11 +++ .../measure/r4/CqlOptimize/cql/libraryB.cql | 13 +++ .../r4/CqlOptimize/cql/libraryCommon.cql | 14 ++++ .../resources/library/libraryA.json | 17 ++++ .../resources/library/libraryB.json | 17 ++++ .../resources/library/libraryCommon.json | 17 ++++ .../resources/measure/MeasureA.json | 43 ++++++++++ .../resources/measure/MeasureB.json | 43 ++++++++++ .../Encounter/male-1988-encounter-1.json | 24 ++++++ .../Encounter/male-1988-encounter-2.json | 24 ++++++ .../Encounter/male-1988-encounter-3.json | 24 ++++++ .../Encounter/male-1988-encounter-4.json | 24 ++++++ .../CqlOptimize/tests/Patient/male-1988.json | 6 ++ .../repository/RepositoryLoggingProxy.java | 43 ++++++++-- 20 files changed, 487 insertions(+), 16 deletions(-) create mode 100644 cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/CqlExpressionDefCachingOptimizationTest.java create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/cql/libraryA.cql create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/cql/libraryB.cql create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/cql/libraryCommon.cql create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/resources/library/libraryA.json create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/resources/library/libraryB.json create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/resources/library/libraryCommon.json create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/resources/measure/MeasureA.json create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/resources/measure/MeasureB.json create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/tests/Encounter/male-1988-encounter-1.json create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/tests/Encounter/male-1988-encounter-2.json create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/tests/Encounter/male-1988-encounter-3.json create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/tests/Encounter/male-1988-encounter-4.json create mode 100644 cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/tests/Patient/male-1988.json diff --git a/cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/LibraryEngine.java b/cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/LibraryEngine.java index 9ef7d521e9..2eeae9eb3d 100644 --- a/cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/LibraryEngine.java +++ b/cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/LibraryEngine.java @@ -344,11 +344,11 @@ public EvaluationResultsForMultiLib getEvaluationResult( // patientId, // zonedDateTime); - final CqlFhirParametersConverter cqlFhirParametersConverterToUse = Objects.requireNonNullElseGet( + var cqlFhirParametersConverterToUse = Objects.requireNonNullElseGet( cqlFhirParametersConverter, () -> Engines.getCqlFhirParametersConverter(repository.fhirContext())); // engine context built externally of LibraryEngine? - final CqlEngine engineToUse = Objects.requireNonNullElseGet( + var engineToUse = Objects.requireNonNullElseGet( engine, () -> Engines.forRepository(repository, settings, additionalData)); var evaluationParameters = cqlFhirParametersConverterToUse.toCqlParameters(parameters); diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java index 9a74318e1e..c2c693c260 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java @@ -14,6 +14,7 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; +import org.cqframework.cql.cql2elm.CqlCompilerException; import org.cqframework.cql.cql2elm.CqlIncludeException; import org.cqframework.cql.cql2elm.model.CompiledLibrary; import org.hl7.elm.r1.VersionedIdentifier; @@ -149,7 +150,7 @@ public MeasureReport evaluateMeasureResults( * @param evalType the type of evaluation to process, this is an output of reportType param * @return Measure Report resource */ - protected MeasureReport evaluateMeasure( + public MeasureReport evaluateMeasure( Measure measure, @Nullable ZonedDateTime periodStart, @Nullable ZonedDateTime periodEnd, @@ -247,6 +248,26 @@ public CompositeEvaluationResultsPerMeasure evaluateMeasureWithCqlEngine( subjects, List.of(measure), periodStart, periodEnd, parameters, context); } + // LUKETODO: is this called from downstream? + public CompositeEvaluationResultsPerMeasure evaluateMultiMeasureIdsWithCqlEngine( + List subjects, + List measureIds, + @Nullable ZonedDateTime periodStart, + @Nullable ZonedDateTime periodEnd, + Parameters parameters, + CqlEngine context) { + return evaluateMultiMeasuresWithCqlEngine( + subjects, + measureIds.stream() + .map(IIdType::toUnqualifiedVersionless) + .map(id -> R4MeasureServiceUtils.resolveById(id, repository)) + .toList(), + periodStart, + periodEnd, + parameters, + context); + } + public CompositeEvaluationResultsPerMeasure evaluateMultiMeasuresWithCqlEngine( List subjects, List measures, @@ -261,6 +282,11 @@ public CompositeEvaluationResultsPerMeasure evaluateMultiMeasuresWithCqlEngine( var zonedMeasurementPeriod = MeasureProcessorUtils.getZonedTimeZoneForEval( measureProcessorUtils.getDefaultMeasurementPeriod(measurementPeriodParams, context)); + // LUKETODO: find a better way: + // Do this to be backwards compatible with the previous single-library evaluation: + // Trigger first-pass validation on measure scoring as well as other aspects of the Measures + measures.forEach(measure -> new R4MeasureDefBuilder().build(measure)); + // Note that we must build the LibraryEngine BEFORE we call // measureProcessorUtils.setMeasurementPeriod(), otherwise, we get an NPE. var multiLibraryIdMeasureEngineDetails = getMultiLibraryIdMeasureEngineDetails(measures, parameters, context); @@ -382,19 +408,65 @@ protected LibraryEngine getLibraryEngine(Parameters parameters, VersionedIdentif protected LibraryEngine getLibraryEngine(Parameters parameters, List ids, CqlEngine context) { - var compileLibraries = - ids.stream().map(id -> getCompiledLibrary(id, context)).toList(); + var compiledLibraries = getCompiledLibraries(ids, context); var libraries = - compileLibraries.stream().map(CompiledLibrary::getLibrary).toList(); + compiledLibraries.stream().map(CompiledLibrary::getLibrary).toList(); context.getState().init(libraries); - setArgParameters(parameters, context, compileLibraries); + // LUKETODO: if we comment this out MeasureScorerTest and other tests will fail with NPEs + setArgParameters(parameters, context, compiledLibraries); return new LibraryEngine(repository, this.measureEvaluationOptions.getEvaluationSettings()); } + private List getCompiledLibraries(List ids, CqlEngine context) { + try { + var resolvedLibraryResults = + context.getEnvironment().getLibraryManager().resolveLibraries(ids); + + var allErrors = resolvedLibraryResults.allErrors(); + if (resolvedLibraryResults.hasErrors() || ids.size() > allErrors.size()) { + return resolvedLibraryResults.allCompiledLibraries(); + } + + if (ids.size() == 1) { + final List cqlCompilerExceptions = + resolvedLibraryResults.getErrorsFor(ids.get(0)); + + if (cqlCompilerExceptions.size() == 1) { + throw new IllegalStateException( + "Unable to load CQL/ELM for library: %s. Verify that the Library resource is available in your environment and has CQL/ELM content embedded." + .formatted(ids.get(0).getId()), + cqlCompilerExceptions.get(0)); + } else { + throw new IllegalStateException( + "Unable to load CQL/ELM for library: %s. Verify that the Library resource is available in your environment and has CQL/ELM content embedded. Errors: %s" + .formatted( + ids.get(0).getId(), + cqlCompilerExceptions.stream() + .map(CqlCompilerException::getMessage) + .reduce((s1, s2) -> s1 + "; " + s2) + .orElse("No error messages found."))); + } + } + + // LUKETODO: handle multiple library errors here better: + throw new IllegalStateException( + "Unable to load CQL/ELM for libraries: %s Verify that the Library resource is available in your environment and has CQL/ELM content embedded. Errors: %s" + .formatted(ids, allErrors)); + + } catch (CqlIncludeException exception) { + throw new IllegalStateException( + "Unable to load CQL/ELM for libraries: %s. Verify that the Library resource is available in your environment and has CQL/ELM content embedded." + .formatted( + ids.stream().map(VersionedIdentifier::getId).toList()), + exception); + } + } + + // LUKETODO: consider getting rid of this: private CompiledLibrary getCompiledLibrary(VersionedIdentifier id, CqlEngine context) { try { return context.getEnvironment().getLibraryManager().resolveLibrary(id); diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/utils/R4MeasureServiceUtils.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/utils/R4MeasureServiceUtils.java index c48d67a11a..a00edf8452 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/utils/R4MeasureServiceUtils.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/utils/R4MeasureServiceUtils.java @@ -37,7 +37,9 @@ import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; import org.hl7.fhir.r4.model.CanonicalType; import org.hl7.fhir.r4.model.CodeableConcept; import org.hl7.fhir.r4.model.Coding; @@ -347,6 +349,13 @@ public boolean isSubjectListEffectivelyEmpty(List subjectIds) { return subjectIds == null || subjectIds.isEmpty() || subjectIds.get(0) == null; } + public static List foldMeasures( + List> measures, IRepository repository) { + return measures.stream() + .map(measure -> foldMeasure(measure, repository)) + .toList(); + } + public static Measure foldMeasure(Either3 measure, IRepository repository) { return measure.fold( measureCanonicalType -> resolveByUrl(measureCanonicalType, repository), @@ -361,6 +370,17 @@ private static Measure resolveByUrl(CanonicalType url, IRepository repository) { return (Measure) result.getEntryFirstRep().getResource(); } + public static List resolveByIds(List ids, IRepository repository) { + var idStringArray = ids.stream().map(IPrimitiveType::getValueAsString).toArray(String[]::new); + var searchParameters = Searches.byId(idStringArray); + + return repository.search(Bundle.class, Measure.class, searchParameters).getEntry().stream() + .map(BundleEntryComponent::getResource) + .filter(Measure.class::isInstance) + .map(Measure.class::cast) + .toList(); + } + public static Measure resolveById(IIdType id, IRepository repository) { return repository.read(Measure.class, id); } diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/CqlExpressionDefCachingOptimizationTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/CqlExpressionDefCachingOptimizationTest.java new file mode 100644 index 0000000000..9ad9014258 --- /dev/null +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/CqlExpressionDefCachingOptimizationTest.java @@ -0,0 +1,46 @@ +package org.opencds.cqf.fhir.cr.measure.r4; + +import org.cqframework.cql.cql2elm.CqlCompilerOptions; +import org.junit.jupiter.api.Test; +import org.opencds.cqf.fhir.cql.CqlOptions; +import org.opencds.cqf.fhir.cql.EvaluationSettings; +import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions; +import org.opencds.cqf.fhir.cr.measure.r4.MultiMeasure.Given; + +public class CqlExpressionDefCachingOptimizationTest { + private static final Given GIVEN_REPO = + MultiMeasure.given().repositoryFor("CqlOptimize").evaluationOptions(getEvaluationOptions()); + + @Test + void MultiMeasure_AllSubjects_MeasureIdentifier() { + var when = GIVEN_REPO + .when() + .measureId("MeasureA") + .measureId("MeasureB") + .reportType("population") + .evaluate(); + + when.then(); + // LUKETODO: assertions + } + + private static MeasureEvaluationOptions getEvaluationOptions() { + return MeasureEvaluationOptions.defaultOptions().setEvaluationSettings(getEvaluationSettings()); + } + + private static EvaluationSettings getEvaluationSettings() { + return EvaluationSettings.getDefault().withCqlOptions(getCqlOptions()); + } + + private static CqlOptions getCqlOptions() { + final CqlOptions cqlOptions = CqlOptions.defaultOptions(); + cqlOptions.setCqlCompilerOptions(getCqlCompilerOptions()); + return cqlOptions; + } + + private static CqlCompilerOptions getCqlCompilerOptions() { + final CqlCompilerOptions cqlCompilerOptions = new CqlCompilerOptions(); + cqlCompilerOptions.setEnableCqlOnly(true); + return cqlCompilerOptions; + } +} diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/InvalidMeasureTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/InvalidMeasureTest.java index 1aa2117a82..235a204172 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/InvalidMeasureTest.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/InvalidMeasureTest.java @@ -37,6 +37,6 @@ void evaluateThrowsErrorWhenLibraryIsMissingContent() { .measureId("LibraryMissingContent") .evaluate(); var e = assertThrows(IllegalStateException.class, when::then); - assertTrue(e.getMessage().contains("Unable to load CQL/ELM for library")); + assertTrue(e.getMessage().contains("Unable to load CQL/ELM for libraries")); } } diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCqlTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCqlTest.java index 56b7c3a58e..52faf3b87d 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCqlTest.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCqlTest.java @@ -1,6 +1,10 @@ package org.opencds.cqf.fhir.cr.measure.r4; +import org.cqframework.cql.cql2elm.CqlCompilerOptions; import org.junit.jupiter.api.Test; +import org.opencds.cqf.cql.engine.execution.CqlEngine; +import org.opencds.cqf.fhir.cql.CqlOptions; +import org.opencds.cqf.fhir.cql.EvaluationSettings; import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions; import org.opencds.cqf.fhir.cr.measure.r4.MultiMeasure.Given; import org.slf4j.Logger; @@ -16,7 +20,7 @@ class MultiLibEvalComplexCqlTest { .setApplyScoringSetMembership(false); private static final Given GIVEN_REPO = - MultiMeasure.given().repositoryFor("MultiLibEvalComplexCql").evaluationOptions(EVALUATION_OPTIONS); + MultiMeasure.given().repositoryFor("MultiLibEvalComplexCql").evaluationOptions(getEvaluationOptions()); @Test void singleLibraryTest_1A() { @@ -97,4 +101,27 @@ void multipleLibraryTest_1A_and_1B() { .population("numerator") .hasCount(1); } + + private static MeasureEvaluationOptions getEvaluationOptions() { + return MeasureEvaluationOptions.defaultOptions() + .setApplyScoringSetMembership(false) + .setEvaluationSettings(getEvaluationSettings()); + } + + private static EvaluationSettings getEvaluationSettings() { + return EvaluationSettings.getDefault().withCqlOptions(getCqlOptions()); + } + + private static CqlOptions getCqlOptions() { + final CqlOptions cqlOptions = CqlOptions.defaultOptions(); + cqlOptions.getCqlEngineOptions().getOptions().add(CqlEngine.Options.EnableHedisCompatibilityMode); + cqlOptions.setCqlCompilerOptions(getCqlCompilerOptions()); + return cqlOptions; + } + + private static CqlCompilerOptions getCqlCompilerOptions() { + final CqlCompilerOptions cqlCompilerOptions = new CqlCompilerOptions(); + cqlCompilerOptions.setEnableCqlOnly(true); + return cqlCompilerOptions; + } } diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/cql/libraryA.cql b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/cql/libraryA.cql new file mode 100644 index 0000000000..f98d0c84c7 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/cql/libraryA.cql @@ -0,0 +1,11 @@ +library libraryA + +using FHIR version '4.0.1' + +include libraryCommon called lc + +context Patient + +define "Initial Population": + exists (lc."All Encounters") + diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/cql/libraryB.cql b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/cql/libraryB.cql new file mode 100644 index 0000000000..aca7e785fa --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/cql/libraryB.cql @@ -0,0 +1,13 @@ +library libraryB + +using FHIR version '4.0.1' + +include libraryCommon called lc + +context Patient + +define "Initial Population": + exists ( + lc."finished encounters" + ) + diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/cql/libraryCommon.cql b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/cql/libraryCommon.cql new file mode 100644 index 0000000000..421286d2b2 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/cql/libraryCommon.cql @@ -0,0 +1,14 @@ +library libraryCommon + +using FHIR version '4.0.1' + +include FHIRHelpers version '4.0.1' called FHIRHelpers + +context Patient + +define "All Encounters": + ["Encounter"] + +define "finished encounters": + ("All Encounters") e + where e.status = 'finished' \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/resources/library/libraryA.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/resources/library/libraryA.json new file mode 100644 index 0000000000..060e640bda --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/resources/library/libraryA.json @@ -0,0 +1,17 @@ +{ + "resourceType": "Library", + "id": "libraryA", + "url": "http://example.com/Library/libraryA", + "name": "libraryA", + "status": "active", + "type": { + "coding": [ { + "system": "http://terminology.hl7.org/CodeSystem/library-type", + "code": "logic-library" + } ] + }, + "content": [ { + "contentType": "text/cql", + "url": "../../cql/libraryA.cql" + } ] +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/resources/library/libraryB.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/resources/library/libraryB.json new file mode 100644 index 0000000000..1edd58913f --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/resources/library/libraryB.json @@ -0,0 +1,17 @@ +{ + "resourceType": "Library", + "id": "libraryB", + "url": "http://example.com/Library/libraryB", + "name": "libraryB", + "status": "active", + "type": { + "coding": [ { + "system": "http://terminology.hl7.org/CodeSystem/library-type", + "code": "logic-library" + } ] + }, + "content": [ { + "contentType": "text/cql", + "url": "../../cql/libraryB.cql" + } ] +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/resources/library/libraryCommon.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/resources/library/libraryCommon.json new file mode 100644 index 0000000000..bfe9797749 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/resources/library/libraryCommon.json @@ -0,0 +1,17 @@ +{ + "resourceType": "Library", + "id": "libraryCommon", + "url": "http://example.com/Library/libraryCommon", + "name": "libraryCommon", + "status": "active", + "type": { + "coding": [ { + "system": "http://terminology.hl7.org/CodeSystem/library-type", + "code": "logic-library" + } ] + }, + "content": [ { + "contentType": "text/cql", + "url": "../../cql/libraryCommon.cql" + } ] +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/resources/measure/MeasureA.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/resources/measure/MeasureA.json new file mode 100644 index 0000000000..a88ff8bf9b --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/resources/measure/MeasureA.json @@ -0,0 +1,43 @@ + +{ + "id": "MeasureA", + "resourceType": "Measure", + "url": "http://example.com/Measure/MeasureA", + "library": [ + "http://example.com/Library/libraryA" + ], + "extension": [ { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-populationBasis", + "valueCode": "boolean" + } ], + "scoring": { + "coding": [ + { + "system": "http://hl7.org/fhir/measure-scoring", + "code": "cohort" + } + ] + }, + "group": [ + { + "population": [ + { + "id": "initial-population", + "code": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-population", + "code": "initial-population", + "display": "Initial Population" + } + ] + }, + "criteria": { + "language": "text/cql-identifier", + "expression": "Initial Population" + } + } + ] + } + ] +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/resources/measure/MeasureB.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/resources/measure/MeasureB.json new file mode 100644 index 0000000000..05964b1665 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/resources/measure/MeasureB.json @@ -0,0 +1,43 @@ + +{ + "id": "MeasureB", + "resourceType": "Measure", + "url": "http://example.com/Measure/MeasureB", + "library": [ + "http://example.com/Library/libraryB" + ], + "extension": [ { + "url": "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-populationBasis", + "valueCode": "boolean" + } ], + "scoring": { + "coding": [ + { + "system": "http://hl7.org/fhir/measure-scoring", + "code": "cohort" + } + ] + }, + "group": [ + { + "population": [ + { + "id": "initial-population", + "code": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/measure-population", + "code": "initial-population", + "display": "Initial Population" + } + ] + }, + "criteria": { + "language": "text/cql-identifier", + "expression": "Initial Population" + } + } + ] + } + ] +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/tests/Encounter/male-1988-encounter-1.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/tests/Encounter/male-1988-encounter-1.json new file mode 100644 index 0000000000..854fe4a7f8 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/tests/Encounter/male-1988-encounter-1.json @@ -0,0 +1,24 @@ +{ + "resourceType": "Encounter", + "id": "male-1988-encounter-1", + "status": "finished", + "class": { + "system": "http://terminology.hl7.org/CodeSystem/v3-ActCode", + "code": "IMP", + "display": "inpatient encounter" + }, + "type": [ { + "coding": [ { + "system": "http://snomed.info/sct", + "code": "32485007", + "display": "Hospital admission (procedure)" + } ] + } ], + "subject": { + "reference": "Patient/male-1988" + }, + "period": { + "start": "2024-01-16T08:30:00-07:00", + "end": "2024-01-20T08:30:00-07:00" + } +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/tests/Encounter/male-1988-encounter-2.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/tests/Encounter/male-1988-encounter-2.json new file mode 100644 index 0000000000..8e003b9cd0 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/tests/Encounter/male-1988-encounter-2.json @@ -0,0 +1,24 @@ +{ + "resourceType": "Encounter", + "id": "male-1988-encounter-2", + "status": "cancelled", + "class": { + "system": "http://terminology.hl7.org/CodeSystem/v3-ActCode", + "code": "IMP", + "display": "inpatient encounter" + }, + "type": [ { + "coding": [ { + "system": "http://snomed.info/sct", + "code": "32485007", + "display": "Hospital admission (procedure)" + } ] + } ], + "subject": { + "reference": "Patient/male-1988" + }, + "period": { + "start": "2024-01-16T08:30:00-07:00", + "end": "2024-01-20T08:30:00-07:00" + } +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/tests/Encounter/male-1988-encounter-3.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/tests/Encounter/male-1988-encounter-3.json new file mode 100644 index 0000000000..4353f33b01 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/tests/Encounter/male-1988-encounter-3.json @@ -0,0 +1,24 @@ +{ + "resourceType": "Encounter", + "id": "male-1988-encounter-3", + "status": "cancelled", + "class": { + "system": "http://terminology.hl7.org/CodeSystem/v3-ActCode", + "code": "IMP", + "display": "inpatient encounter" + }, + "type": [ { + "coding": [ { + "system": "http://snomed.info/sct", + "code": "32485007", + "display": "Hospital admission (procedure)" + } ] + } ], + "subject": { + "reference": "Patient/male-1988" + }, + "period": { + "start": "2025-01-16T08:30:00-07:00", + "end": "2025-01-20T08:30:00-07:00" + } +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/tests/Encounter/male-1988-encounter-4.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/tests/Encounter/male-1988-encounter-4.json new file mode 100644 index 0000000000..2892c8bd52 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/tests/Encounter/male-1988-encounter-4.json @@ -0,0 +1,24 @@ +{ + "resourceType": "Encounter", + "id": "male-1988-encounter-4", + "status": "finished", + "class": { + "system": "http://terminology.hl7.org/CodeSystem/v3-ActCode", + "code": "IMP", + "display": "inpatient encounter" + }, + "type": [ { + "coding": [ { + "system": "http://snomed.info/sct", + "code": "32485007", + "display": "Hospital admission (procedure)" + } ] + } ], + "subject": { + "reference": "Patient/male-1988" + }, + "period": { + "start": "2025-01-16T08:30:00-07:00", + "end": "2025-01-20T08:30:00-07:00" + } +} \ No newline at end of file diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/tests/Patient/male-1988.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/tests/Patient/male-1988.json new file mode 100644 index 0000000000..eb88176d97 --- /dev/null +++ b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/tests/Patient/male-1988.json @@ -0,0 +1,6 @@ +{ + "resourceType": "Patient", + "id": "male-1988", + "gender": "male", + "birthDate": "1988-01-01" +} \ No newline at end of file diff --git a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/repository/RepositoryLoggingProxy.java b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/repository/RepositoryLoggingProxy.java index f94d586fb0..b5dcfb32f5 100644 --- a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/repository/RepositoryLoggingProxy.java +++ b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/repository/RepositoryLoggingProxy.java @@ -7,6 +7,7 @@ import com.google.common.collect.Multimap; import java.util.List; import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; import org.hl7.fhir.instance.model.api.IBaseBundle; import org.hl7.fhir.instance.model.api.IBaseParameters; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -16,6 +17,13 @@ // LUKETODO: this is temporary until performance testing is complete public class RepositoryLoggingProxy implements IRepository { + private static final AtomicLong searchCounter = new AtomicLong(0); + private static final AtomicLong readCounter = new AtomicLong(0); + private static final AtomicLong createCounter = new AtomicLong(0); + private static final AtomicLong updateCounter = new AtomicLong(0); + private static final AtomicLong deleteCounter = new AtomicLong(0); + private static final AtomicLong invoke1Counter = new AtomicLong(0); + private static final AtomicLong invoke2Counter = new AtomicLong(0); private static final Logger logger = LoggerFactory.getLogger(RepositoryLoggingProxy.class); @@ -44,26 +52,30 @@ private RepositoryLoggingProxy(IRepository repository) { @Override public T read(Class aClass, I i, Map map) { - logger.info("1234567890 - read()"); + final long readCount = readCounter.incrementAndGet(); + logger.info("1234567890 - read() count: {}", readCount); return repository.read(aClass, i, map); } @Override public MethodOutcome create(T t, Map map) { - logger.info("1234567890 - create()"); + final long createCount = createCounter.incrementAndGet(); + logger.info("1234567890 - create() count: {}", createCount); return repository.create(t, map); } @Override public MethodOutcome update(T t, Map map) { - logger.info("1234567890 - update()"); + final long updateCount = updateCounter.incrementAndGet(); + logger.info("1234567890 - update() count: {}", updateCount); return repository.update(t, map); } @Override public MethodOutcome delete( Class aClass, I i, Map map) { - logger.info("1234567890 - delete()"); + final long deleteCount = deleteCounter.incrementAndGet(); + logger.info("1234567890 - delete() count: {}", deleteCount); return repository.delete(aClass, i, map); } @@ -73,24 +85,41 @@ public B search( Class aClass1, Multimap> multimap, Map map) { - logger.info("1234567890 - search()"); + final long searchCount = searchCounter.incrementAndGet(); + logger.info( + "1234567890 - search() count: {}, resource class: {}, search params: {}", + searchCount, + aClass1, + multimap); return repository.search(aClass, aClass1, multimap, map); } @Override public R invoke( Class aClass, String s, P p, Class aClass1, Map map) { - logger.info("1234567890 - invoke()"); + final long invoke1Count = invoke1Counter.incrementAndGet(); + logger.info("1234567890 - invoke1() count: {}", invoke1Count); return repository.invoke(aClass, s, p, aClass1, map); } @Override public R invoke( I i, String s, P p, Class aClass, Map map) { - logger.info("1234567890 - invoke()"); + final long invoke2Count = invoke2Counter.incrementAndGet(); + logger.info("1234567890 - invoke2() count: {}", invoke2Count); return repository.invoke(aClass, s, p, aClass, map); } + @Override + public B transaction(B bundle, Map headers) { + return repository.transaction(bundle, headers); + } + + @Override + public B link(Class bundleType, String url, Map headers) { + return repository.link(bundleType, url, headers); + } + @Override public FhirContext fhirContext() { return repository.fhirContext(); From 2496ed74627cd0c78334e623ac4aa5e6baf66d8c Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Mon, 11 Aug 2025 10:02:46 -0400 Subject: [PATCH 60/66] Fix test input directory structures to conform to new standard. Add unversioned local files. --- .../common/EvaluationResultsDisplay.java | 58 -------- .../cr/measure/r4/R4MultiMeasureService.java | 3 +- .../r4/MultiLibEvalComplexCqlTest.java | 9 -- .../{ => input}/cql/Level1A.cql | 0 .../{ => input}/cql/Level1B.cql | 0 .../{ => input}/cql/Level2.cql | 0 .../{ => input}/cql/Level3A.cql | 0 .../{ => input}/cql/Level3B.cql | 0 .../{ => input}/cql/Level4.cql | 0 .../{ => input}/cql/Level5.cql | 0 .../resources/library/Level1A.json | 0 .../resources/library/Level1B.json | 0 .../{ => input}/resources/library/Level2.json | 0 .../resources/library/Level3A.json | 0 .../resources/library/Level3B.json | 0 .../{ => input}/resources/library/Level4.json | 0 .../{ => input}/resources/library/Level5.json | 0 .../tests/encounter/enc_arrived_cat1_v1.json | 0 .../tests/encounter/enc_arrived_cat1_v2.json | 0 .../tests/encounter/enc_arrived_cat2.json | 0 .../tests/encounter/enc_arrived_cat3.json | 0 .../tests/encounter/enc_cancelled_cat3.json | 0 .../tests/encounter/enc_finished_cat3.json | 0 .../tests/encounter/enc_planned_cat1.json | 0 .../tests/encounter/enc_planned_cat2.json | 0 .../tests/encounter/enc_planned_cat3.json | 0 .../tests/encounter/enc_triaged_cat3.json | 0 .../{ => input}/tests/patient/patient1.json | 0 .../CqlOptimize/{ => input}/cql/libraryA.cql | 0 .../CqlOptimize/{ => input}/cql/libraryB.cql | 0 .../{ => input}/cql/libraryCommon.cql | 0 .../resources/library/libraryA.json | 0 .../resources/library/libraryB.json | 0 .../resources/library/libraryCommon.json | 0 .../resources/measure/MeasureA.json | 0 .../resources/measure/MeasureB.json | 0 .../Encounter/male-1988-encounter-1.json | 0 .../Encounter/male-1988-encounter-2.json | 0 .../Encounter/male-1988-encounter-3.json | 0 .../Encounter/male-1988-encounter-4.json | 0 .../{ => input}/tests/Patient/male-1988.json | 0 .../{ => input}/cql/Level1A.cql | 0 .../{ => input}/cql/Level1B.cql | 0 .../{ => input}/cql/Level2.cql | 0 .../{ => input}/cql/Level3A.cql | 0 .../{ => input}/cql/Level3B.cql | 0 .../{ => input}/cql/Level4.cql | 0 .../{ => input}/cql/Level5.cql | 0 .../resources/library/Level1A.json | 0 .../resources/library/Level1B.json | 0 .../{ => input}/resources/library/Level2.json | 0 .../resources/library/Level3A.json | 0 .../resources/library/Level3B.json | 0 .../{ => input}/resources/library/Level4.json | 0 .../{ => input}/resources/library/Level5.json | 0 .../resources/measure/Level1A.json | 0 .../resources/measure/Level1B.json | 0 .../tests/encounter/enc_arrived_cat1_v1.json | 0 .../tests/encounter/enc_arrived_cat1_v2.json | 0 .../tests/encounter/enc_arrived_cat2.json | 0 .../tests/encounter/enc_arrived_cat3.json | 0 .../tests/encounter/enc_cancelled_cat3.json | 0 .../tests/encounter/enc_finished_cat3.json | 0 .../tests/encounter/enc_planned_cat1.json | 0 .../tests/encounter/enc_planned_cat2.json | 0 .../tests/encounter/enc_planned_cat3.json | 0 .../tests/encounter/enc_triaged_cat3.json | 0 .../{ => input}/tests/patient/patient1.json | 0 .../repository/RepositoryLoggingProxy.java | 127 ------------------ pom.xml | 5 +- 70 files changed, 5 insertions(+), 197 deletions(-) delete mode 100644 cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/EvaluationResultsDisplay.java rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/{ => input}/cql/Level1A.cql (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/{ => input}/cql/Level1B.cql (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/{ => input}/cql/Level2.cql (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/{ => input}/cql/Level3A.cql (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/{ => input}/cql/Level3B.cql (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/{ => input}/cql/Level4.cql (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/{ => input}/cql/Level5.cql (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/{ => input}/resources/library/Level1A.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/{ => input}/resources/library/Level1B.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/{ => input}/resources/library/Level2.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/{ => input}/resources/library/Level3A.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/{ => input}/resources/library/Level3B.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/{ => input}/resources/library/Level4.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/{ => input}/resources/library/Level5.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/{ => input}/tests/encounter/enc_arrived_cat1_v1.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/{ => input}/tests/encounter/enc_arrived_cat1_v2.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/{ => input}/tests/encounter/enc_arrived_cat2.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/{ => input}/tests/encounter/enc_arrived_cat3.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/{ => input}/tests/encounter/enc_cancelled_cat3.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/{ => input}/tests/encounter/enc_finished_cat3.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/{ => input}/tests/encounter/enc_planned_cat1.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/{ => input}/tests/encounter/enc_planned_cat2.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/{ => input}/tests/encounter/enc_planned_cat3.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/{ => input}/tests/encounter/enc_triaged_cat3.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/{ => input}/tests/patient/patient1.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/{ => input}/cql/libraryA.cql (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/{ => input}/cql/libraryB.cql (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/{ => input}/cql/libraryCommon.cql (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/{ => input}/resources/library/libraryA.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/{ => input}/resources/library/libraryB.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/{ => input}/resources/library/libraryCommon.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/{ => input}/resources/measure/MeasureA.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/{ => input}/resources/measure/MeasureB.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/{ => input}/tests/Encounter/male-1988-encounter-1.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/{ => input}/tests/Encounter/male-1988-encounter-2.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/{ => input}/tests/Encounter/male-1988-encounter-3.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/{ => input}/tests/Encounter/male-1988-encounter-4.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/{ => input}/tests/Patient/male-1988.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/{ => input}/cql/Level1A.cql (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/{ => input}/cql/Level1B.cql (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/{ => input}/cql/Level2.cql (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/{ => input}/cql/Level3A.cql (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/{ => input}/cql/Level3B.cql (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/{ => input}/cql/Level4.cql (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/{ => input}/cql/Level5.cql (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/{ => input}/resources/library/Level1A.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/{ => input}/resources/library/Level1B.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/{ => input}/resources/library/Level2.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/{ => input}/resources/library/Level3A.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/{ => input}/resources/library/Level3B.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/{ => input}/resources/library/Level4.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/{ => input}/resources/library/Level5.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/{ => input}/resources/measure/Level1A.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/{ => input}/resources/measure/Level1B.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/{ => input}/tests/encounter/enc_arrived_cat1_v1.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/{ => input}/tests/encounter/enc_arrived_cat1_v2.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/{ => input}/tests/encounter/enc_arrived_cat2.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/{ => input}/tests/encounter/enc_arrived_cat3.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/{ => input}/tests/encounter/enc_cancelled_cat3.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/{ => input}/tests/encounter/enc_finished_cat3.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/{ => input}/tests/encounter/enc_planned_cat1.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/{ => input}/tests/encounter/enc_planned_cat2.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/{ => input}/tests/encounter/enc_planned_cat3.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/{ => input}/tests/encounter/enc_triaged_cat3.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/{ => input}/tests/patient/patient1.json (100%) delete mode 100644 cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/repository/RepositoryLoggingProxy.java diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/EvaluationResultsDisplay.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/EvaluationResultsDisplay.java deleted file mode 100644 index b2b0f88b7e..0000000000 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/EvaluationResultsDisplay.java +++ /dev/null @@ -1,58 +0,0 @@ -package org.opencds.cqf.fhir.cr.measure.common; - -import java.util.stream.Collectors; -import java.util.stream.StreamSupport; -import org.apache.commons.collections4.keyvalue.DefaultMapEntry; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.opencds.cqf.cql.engine.execution.EvaluationResult; -import org.opencds.cqf.cql.engine.execution.ExpressionResult; - -// LUKETODO: consider deleting this once all is said and done -public class EvaluationResultsDisplay { - public static String printEvaluationResult(EvaluationResult evaluationResult) { - if (evaluationResult == null) { - return "null"; - } - return "\nEvaluationResult{" + "expressionResults=\n" - + evaluationResult.expressionResults.entrySet().stream() - .map(entry -> new DefaultMapEntry<>(entry.getKey(), printExpressionResult(entry.getValue()))) - .map(entry -> entry.getKey() + ": " + entry.getValue()) - .collect(Collectors.joining("\n")) - + "}\n"; - } - - public static String printExpressionResult(ExpressionResult expressionResult) { - if (expressionResult == null) { - return "\nnull"; - } - - return "\nExpressionResult{\n value=" + showValue(expressionResult.value()) + "\n type=" - + showEvaluatedResources(expressionResult.evaluatedResources()) + '}'; - } - - public static String showValue(Object valueOrCollection) { - if (valueOrCollection == null) { - return "null"; - } - if (valueOrCollection instanceof Iterable iterable) { - return showEvaluatedResources(iterable); - } - return showEvaluatedResource(valueOrCollection); - } - - public static String showEvaluatedResources(Iterable evaluatedResourcesOrSomethings) { - return StreamSupport.stream(evaluatedResourcesOrSomethings.spliterator(), false) - .map(EvaluationResultsDisplay::showEvaluatedResource) - .collect(Collectors.joining(", ", "[", "]")); - } - - private static String showEvaluatedResource(Object evaluatedResourceOrSomething) { - if (evaluatedResourceOrSomething instanceof IBaseResource resource) { - return resource.getIdElement().getValueAsString(); - } else if (evaluatedResourceOrSomething != null) { - return evaluatedResourceOrSomething.toString(); - } else { - return "null"; - } - } -} diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java index ccf6f3f0b1..53b1431740 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java @@ -210,7 +210,8 @@ protected void populationMeasureReport( // progress feedback var measureUrl = measureReport.getMeasure(); if (!measureUrl.isEmpty()) { - log.debug( + // log.debug( + log.info( "Completed evaluation for Measure: {}, Measures remaining to evaluate: {}", measureUrl, totalMeasures--); diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCqlTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCqlTest.java index 52faf3b87d..a19ff607b3 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCqlTest.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCqlTest.java @@ -7,18 +7,9 @@ import org.opencds.cqf.fhir.cql.EvaluationSettings; import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions; import org.opencds.cqf.fhir.cr.measure.r4.MultiMeasure.Given; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; @SuppressWarnings({"java:S2699"}) class MultiLibEvalComplexCqlTest { - private static final Logger logger = LoggerFactory.getLogger(MultiLibEvalComplexCqlTest.class); - - private static final MeasureEvaluationOptions EVALUATION_OPTIONS = MeasureEvaluationOptions.defaultOptions() - // We're not doing this not necessarily for HEDIS but just so we can assert different counts for numerator - // and denominator - .setApplyScoringSetMembership(false); - private static final Given GIVEN_REPO = MultiMeasure.given().repositoryFor("MultiLibEvalComplexCql").evaluationOptions(getEvaluationOptions()); diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/cql/Level1A.cql b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/cql/Level1A.cql similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/cql/Level1A.cql rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/cql/Level1A.cql diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/cql/Level1B.cql b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/cql/Level1B.cql similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/cql/Level1B.cql rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/cql/Level1B.cql diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/cql/Level2.cql b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/cql/Level2.cql similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/cql/Level2.cql rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/cql/Level2.cql diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/cql/Level3A.cql b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/cql/Level3A.cql similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/cql/Level3A.cql rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/cql/Level3A.cql diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/cql/Level3B.cql b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/cql/Level3B.cql similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/cql/Level3B.cql rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/cql/Level3B.cql diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/cql/Level4.cql b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/cql/Level4.cql similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/cql/Level4.cql rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/cql/Level4.cql diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/cql/Level5.cql b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/cql/Level5.cql similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/cql/Level5.cql rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/cql/Level5.cql diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/resources/library/Level1A.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/resources/library/Level1A.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/resources/library/Level1A.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/resources/library/Level1A.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/resources/library/Level1B.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/resources/library/Level1B.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/resources/library/Level1B.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/resources/library/Level1B.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/resources/library/Level2.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/resources/library/Level2.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/resources/library/Level2.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/resources/library/Level2.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/resources/library/Level3A.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/resources/library/Level3A.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/resources/library/Level3A.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/resources/library/Level3A.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/resources/library/Level3B.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/resources/library/Level3B.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/resources/library/Level3B.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/resources/library/Level3B.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/resources/library/Level4.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/resources/library/Level4.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/resources/library/Level4.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/resources/library/Level4.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/resources/library/Level5.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/resources/library/Level5.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/resources/library/Level5.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/resources/library/Level5.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_arrived_cat1_v1.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/tests/encounter/enc_arrived_cat1_v1.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_arrived_cat1_v1.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/tests/encounter/enc_arrived_cat1_v1.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_arrived_cat1_v2.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/tests/encounter/enc_arrived_cat1_v2.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_arrived_cat1_v2.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/tests/encounter/enc_arrived_cat1_v2.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_arrived_cat2.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/tests/encounter/enc_arrived_cat2.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_arrived_cat2.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/tests/encounter/enc_arrived_cat2.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_arrived_cat3.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/tests/encounter/enc_arrived_cat3.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_arrived_cat3.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/tests/encounter/enc_arrived_cat3.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_cancelled_cat3.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/tests/encounter/enc_cancelled_cat3.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_cancelled_cat3.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/tests/encounter/enc_cancelled_cat3.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_finished_cat3.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/tests/encounter/enc_finished_cat3.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_finished_cat3.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/tests/encounter/enc_finished_cat3.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_planned_cat1.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/tests/encounter/enc_planned_cat1.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_planned_cat1.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/tests/encounter/enc_planned_cat1.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_planned_cat2.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/tests/encounter/enc_planned_cat2.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_planned_cat2.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/tests/encounter/enc_planned_cat2.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_planned_cat3.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/tests/encounter/enc_planned_cat3.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_planned_cat3.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/tests/encounter/enc_planned_cat3.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_triaged_cat3.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/tests/encounter/enc_triaged_cat3.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/encounter/enc_triaged_cat3.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/tests/encounter/enc_triaged_cat3.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/patient/patient1.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/tests/patient/patient1.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/tests/patient/patient1.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/cpg/r4/libraryevalcomplexdeps/input/tests/patient/patient1.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/cql/libraryA.cql b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/cql/libraryA.cql similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/cql/libraryA.cql rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/cql/libraryA.cql diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/cql/libraryB.cql b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/cql/libraryB.cql similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/cql/libraryB.cql rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/cql/libraryB.cql diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/cql/libraryCommon.cql b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/cql/libraryCommon.cql similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/cql/libraryCommon.cql rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/cql/libraryCommon.cql diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/resources/library/libraryA.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/resources/library/libraryA.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/resources/library/libraryA.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/resources/library/libraryA.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/resources/library/libraryB.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/resources/library/libraryB.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/resources/library/libraryB.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/resources/library/libraryB.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/resources/library/libraryCommon.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/resources/library/libraryCommon.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/resources/library/libraryCommon.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/resources/library/libraryCommon.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/resources/measure/MeasureA.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/resources/measure/MeasureA.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/resources/measure/MeasureA.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/resources/measure/MeasureA.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/resources/measure/MeasureB.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/resources/measure/MeasureB.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/resources/measure/MeasureB.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/resources/measure/MeasureB.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/tests/Encounter/male-1988-encounter-1.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/tests/Encounter/male-1988-encounter-1.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/tests/Encounter/male-1988-encounter-1.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/tests/Encounter/male-1988-encounter-1.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/tests/Encounter/male-1988-encounter-2.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/tests/Encounter/male-1988-encounter-2.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/tests/Encounter/male-1988-encounter-2.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/tests/Encounter/male-1988-encounter-2.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/tests/Encounter/male-1988-encounter-3.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/tests/Encounter/male-1988-encounter-3.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/tests/Encounter/male-1988-encounter-3.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/tests/Encounter/male-1988-encounter-3.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/tests/Encounter/male-1988-encounter-4.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/tests/Encounter/male-1988-encounter-4.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/tests/Encounter/male-1988-encounter-4.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/tests/Encounter/male-1988-encounter-4.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/tests/Patient/male-1988.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/tests/Patient/male-1988.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/tests/Patient/male-1988.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/tests/Patient/male-1988.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level1A.cql b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/cql/Level1A.cql similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level1A.cql rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/cql/Level1A.cql diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level1B.cql b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/cql/Level1B.cql similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level1B.cql rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/cql/Level1B.cql diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level2.cql b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/cql/Level2.cql similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level2.cql rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/cql/Level2.cql diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level3A.cql b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/cql/Level3A.cql similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level3A.cql rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/cql/Level3A.cql diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level3B.cql b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/cql/Level3B.cql similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level3B.cql rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/cql/Level3B.cql diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level4.cql b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/cql/Level4.cql similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level4.cql rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/cql/Level4.cql diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level5.cql b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/cql/Level5.cql similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/cql/Level5.cql rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/cql/Level5.cql diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/library/Level1A.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/resources/library/Level1A.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/library/Level1A.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/resources/library/Level1A.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/library/Level1B.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/resources/library/Level1B.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/library/Level1B.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/resources/library/Level1B.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/library/Level2.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/resources/library/Level2.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/library/Level2.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/resources/library/Level2.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/library/Level3A.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/resources/library/Level3A.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/library/Level3A.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/resources/library/Level3A.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/library/Level3B.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/resources/library/Level3B.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/library/Level3B.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/resources/library/Level3B.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/library/Level4.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/resources/library/Level4.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/library/Level4.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/resources/library/Level4.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/library/Level5.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/resources/library/Level5.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/library/Level5.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/resources/library/Level5.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/measure/Level1A.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/resources/measure/Level1A.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/measure/Level1A.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/resources/measure/Level1A.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/measure/Level1B.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/resources/measure/Level1B.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/resources/measure/Level1B.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/resources/measure/Level1B.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_cat1_v1.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/tests/encounter/enc_arrived_cat1_v1.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_cat1_v1.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/tests/encounter/enc_arrived_cat1_v1.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_cat1_v2.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/tests/encounter/enc_arrived_cat1_v2.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_cat1_v2.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/tests/encounter/enc_arrived_cat1_v2.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_cat2.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/tests/encounter/enc_arrived_cat2.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_cat2.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/tests/encounter/enc_arrived_cat2.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_cat3.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/tests/encounter/enc_arrived_cat3.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_arrived_cat3.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/tests/encounter/enc_arrived_cat3.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_cancelled_cat3.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/tests/encounter/enc_cancelled_cat3.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_cancelled_cat3.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/tests/encounter/enc_cancelled_cat3.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_finished_cat3.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/tests/encounter/enc_finished_cat3.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_finished_cat3.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/tests/encounter/enc_finished_cat3.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_cat1.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/tests/encounter/enc_planned_cat1.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_cat1.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/tests/encounter/enc_planned_cat1.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_cat2.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/tests/encounter/enc_planned_cat2.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_cat2.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/tests/encounter/enc_planned_cat2.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_cat3.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/tests/encounter/enc_planned_cat3.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_planned_cat3.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/tests/encounter/enc_planned_cat3.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_triaged_cat3.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/tests/encounter/enc_triaged_cat3.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/encounter/enc_triaged_cat3.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/tests/encounter/enc_triaged_cat3.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/patient/patient1.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/tests/patient/patient1.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/tests/patient/patient1.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/MultiLibEvalComplexCql/input/tests/patient/patient1.json diff --git a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/repository/RepositoryLoggingProxy.java b/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/repository/RepositoryLoggingProxy.java deleted file mode 100644 index b5dcfb32f5..0000000000 --- a/cqf-fhir-utility/src/main/java/org/opencds/cqf/fhir/utility/repository/RepositoryLoggingProxy.java +++ /dev/null @@ -1,127 +0,0 @@ -package org.opencds.cqf.fhir.utility.repository; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.model.api.IQueryParameterType; -import ca.uhn.fhir.repository.IRepository; -import ca.uhn.fhir.rest.api.MethodOutcome; -import com.google.common.collect.Multimap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.atomic.AtomicLong; -import org.hl7.fhir.instance.model.api.IBaseBundle; -import org.hl7.fhir.instance.model.api.IBaseParameters; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.instance.model.api.IIdType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -// LUKETODO: this is temporary until performance testing is complete -public class RepositoryLoggingProxy implements IRepository { - private static final AtomicLong searchCounter = new AtomicLong(0); - private static final AtomicLong readCounter = new AtomicLong(0); - private static final AtomicLong createCounter = new AtomicLong(0); - private static final AtomicLong updateCounter = new AtomicLong(0); - private static final AtomicLong deleteCounter = new AtomicLong(0); - private static final AtomicLong invoke1Counter = new AtomicLong(0); - private static final AtomicLong invoke2Counter = new AtomicLong(0); - - private static final Logger logger = LoggerFactory.getLogger(RepositoryLoggingProxy.class); - - private final IRepository repository; - - public static RepositoryLoggingProxy init(IRepository repository) { - if (repository == null) { - throw new IllegalArgumentException("Repository cannot be null"); - } - - // Don't wrap a RepositoryLoggingProxy in a RepositoryLoggingProxy - if (repository instanceof RepositoryLoggingProxy castedRepository) { - logger.info("This is already a RepositoryLoggingProxy, so returning it directly"); - return castedRepository; - } - - logger.info( - "Initializing RepositoryLoggingProxy for repository: {}", - repository.getClass().getName()); - return new RepositoryLoggingProxy(repository); - } - - private RepositoryLoggingProxy(IRepository repository) { - this.repository = repository; - } - - @Override - public T read(Class aClass, I i, Map map) { - final long readCount = readCounter.incrementAndGet(); - logger.info("1234567890 - read() count: {}", readCount); - return repository.read(aClass, i, map); - } - - @Override - public MethodOutcome create(T t, Map map) { - final long createCount = createCounter.incrementAndGet(); - logger.info("1234567890 - create() count: {}", createCount); - return repository.create(t, map); - } - - @Override - public MethodOutcome update(T t, Map map) { - final long updateCount = updateCounter.incrementAndGet(); - logger.info("1234567890 - update() count: {}", updateCount); - return repository.update(t, map); - } - - @Override - public MethodOutcome delete( - Class aClass, I i, Map map) { - final long deleteCount = deleteCounter.incrementAndGet(); - logger.info("1234567890 - delete() count: {}", deleteCount); - return repository.delete(aClass, i, map); - } - - @Override - public B search( - Class aClass, - Class aClass1, - Multimap> multimap, - Map map) { - final long searchCount = searchCounter.incrementAndGet(); - logger.info( - "1234567890 - search() count: {}, resource class: {}, search params: {}", - searchCount, - aClass1, - multimap); - return repository.search(aClass, aClass1, multimap, map); - } - - @Override - public R invoke( - Class aClass, String s, P p, Class aClass1, Map map) { - final long invoke1Count = invoke1Counter.incrementAndGet(); - logger.info("1234567890 - invoke1() count: {}", invoke1Count); - return repository.invoke(aClass, s, p, aClass1, map); - } - - @Override - public R invoke( - I i, String s, P p, Class aClass, Map map) { - final long invoke2Count = invoke2Counter.incrementAndGet(); - logger.info("1234567890 - invoke2() count: {}", invoke2Count); - return repository.invoke(aClass, s, p, aClass, map); - } - - @Override - public B transaction(B bundle, Map headers) { - return repository.transaction(bundle, headers); - } - - @Override - public B link(Class bundleType, String url, Map headers) { - return repository.link(bundleType, url, headers); - } - - @Override - public FhirContext fhirContext() { - return repository.fhirContext(); - } -} diff --git a/pom.xml b/pom.xml index baa3a2c3dd..3bcf48d97b 100644 --- a/pom.xml +++ b/pom.xml @@ -23,8 +23,9 @@ 5.10.2 2.0.4 - - 3.29.0-SNAPSHOT + + 3.28.0-SNAPSHOT + 1.36 6.1.14 8.2.0 From d91e540bb3cfdb9a807eb944074477f3fc5c18eb Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Mon, 11 Aug 2025 10:43:05 -0400 Subject: [PATCH 61/66] Fix some TODOs including tests, logging, and javadoc. --- .../MultiLibraryIdMeasureEngineDetails.java | 5 ++++- .../fhir/cr/measure/r4/R4MeasureProcessor.java | 15 +-------------- .../cr/measure/r4/R4MultiMeasureService.java | 13 ++++++------- ...qlExpressionDefCachingOptimizationTest.java | 18 ++++++++++++++++-- .../cqf/fhir/cr/measure/r4/MultiMeasure.java | 5 +++++ 5 files changed, 32 insertions(+), 24 deletions(-) diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MultiLibraryIdMeasureEngineDetails.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MultiLibraryIdMeasureEngineDetails.java index 4cfdf5d9db..10ed0f0ae8 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MultiLibraryIdMeasureEngineDetails.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MultiLibraryIdMeasureEngineDetails.java @@ -7,7 +7,10 @@ import org.hl7.fhir.instance.model.api.IIdType; import org.opencds.cqf.fhir.cql.LibraryEngine; -// LUKETODO: javadoc +/** + * Convenience class to hold a library engine and a mapping of library IDs to measure IDs for + * library and measure evaluation. + */ public class MultiLibraryIdMeasureEngineDetails { private final LibraryEngine libraryEngine; private final ListMultimap libraryIdToMeasureIds; diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java index c2c693c260..435fdb759f 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java @@ -248,7 +248,7 @@ public CompositeEvaluationResultsPerMeasure evaluateMeasureWithCqlEngine( subjects, List.of(measure), periodStart, periodEnd, parameters, context); } - // LUKETODO: is this called from downstream? + // LUKETODO: capture this with a unit test to get the code coverage up public CompositeEvaluationResultsPerMeasure evaluateMultiMeasureIdsWithCqlEngine( List subjects, List measureIds, @@ -305,7 +305,6 @@ public CompositeEvaluationResultsPerMeasure evaluateMultiMeasuresWithCqlEngine( subjects, zonedMeasurementPeriod, context, multiLibraryIdMeasureEngineDetails); } - // LUKETODO: this method is incoherent: who gets the versioned identifiers? private MultiLibraryIdMeasureEngineDetails getMultiLibraryIdMeasureEngineDetails( List measures, Parameters parameters, CqlEngine context) { @@ -466,18 +465,6 @@ private List getCompiledLibraries(List ids } } - // LUKETODO: consider getting rid of this: - private CompiledLibrary getCompiledLibrary(VersionedIdentifier id, CqlEngine context) { - try { - return context.getEnvironment().getLibraryManager().resolveLibrary(id); - } catch (CqlIncludeException e) { - throw new IllegalStateException( - "Unable to load CQL/ELM for library: %s. Verify that the Library resource is available in your environment and has CQL/ELM content embedded." - .formatted(id.getId()), - e); - } - } - protected void checkMeasureLibrary(Measure measure) { if (!measure.hasLibrary()) { throw new InvalidRequestException( diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java index 53b1431740..9f1fab4de2 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java @@ -175,7 +175,6 @@ protected void populationMeasureReport( String productLine, String reporter) { - // LUKETODO: ensure this counter works correctly in the new world var totalMeasures = measures.size(); for (Measure measure : measures) { MeasureReport measureReport; @@ -275,12 +274,12 @@ protected void subjectMeasureReport( if (!measureUrl.isEmpty()) { log.debug("MeasureReports remaining to evaluate {}", totalReports--); } - if (measure.hasUrl()) { - log.info( - "Completed evaluation for Measure: {}, Measures remaining to evaluate: {}", - measure.getUrl(), - totalMeasures--); - } + } + if (measure.hasUrl()) { + log.info( + "Completed evaluation for Measure: {}, Measures remaining to evaluate: {}", + measure.getUrl(), + totalMeasures--); } } } diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/CqlExpressionDefCachingOptimizationTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/CqlExpressionDefCachingOptimizationTest.java index 9ad9014258..e888b2eb5f 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/CqlExpressionDefCachingOptimizationTest.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/CqlExpressionDefCachingOptimizationTest.java @@ -20,10 +20,24 @@ void MultiMeasure_AllSubjects_MeasureIdentifier() { .reportType("population") .evaluate(); - when.then(); - // LUKETODO: assertions + when.then() + .hasMeasureReportCount(2) + .getFirstMeasureReport() + .hasMeasure("http://example.com/Measure/MeasureA") + .firstGroup() + .population("initial-population") + .hasCount(1) + .up() + .up() + .up() + .getSecondMeasureReport() + .hasMeasure("http://example.com/Measure/MeasureB") + .firstGroup() + .population("initial-population") + .hasCount(1); } + // The point of this chain of methods is to set HEDIS options and to disable ELM library compilation. private static MeasureEvaluationOptions getEvaluationOptions() { return MeasureEvaluationOptions.defaultOptions().setEvaluationSettings(getEvaluationSettings()); } diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasure.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasure.java index 835c032457..1e666a1d21 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasure.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasure.java @@ -382,6 +382,11 @@ public SelectedMeasureReport measureReportTypeIndividual() { return this; } + public SelectedMeasureReport hasMeasure(String measureUrl) { + assertEquals(measureUrl, report().getMeasure()); + return this; + } + public SelectedReference reference( org.opencds.cqf.fhir.cr.measure.r4.Measure.Selector referenceSelector) { var r = referenceSelector.select(value()); From 335855fb269ed698337ef88f1668aaac8d8b09de Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Mon, 11 Aug 2025 12:17:49 -0400 Subject: [PATCH 62/66] Address rest of TODOs and add another test. --- .../CompositeEvaluationResultsPerMeasure.java | 1 - .../measure/dstu3/Dstu3MeasureProcessor.java | 6 +- .../cr/measure/r4/R4MeasureDefBuilder.java | 42 +++++++++++-- .../cr/measure/r4/R4MeasureProcessor.java | 10 +--- ...braryEvaluationServiceComplexDepsTest.java | 9 --- .../cqf/fhir/cr/measure/r4/MultiMeasure.java | 10 ++++ .../cr/measure/r4/R4MeasureProcessorTest.java | 60 +++++++++++++++++++ 7 files changed, 110 insertions(+), 28 deletions(-) create mode 100644 cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessorTest.java diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java index 5956f8335e..dd750c76f2 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/CompositeEvaluationResultsPerMeasure.java @@ -22,7 +22,6 @@ public class CompositeEvaluationResultsPerMeasure { // The same measure may have successful results AND errors, so account for both private final Map> resultsPerMeasure; - // LUKETODO: Exceptions instead of Strings? // We may get several errors for a given measure private final Map> errorsPerMeasure; diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureProcessor.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureProcessor.java index 2cd799bb4f..3b569c79a5 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureProcessor.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureProcessor.java @@ -142,11 +142,7 @@ private MultiLibraryIdMeasureEngineDetails buildLibraryIdEngineDetails( return MultiLibraryIdMeasureEngineDetails.builder(libraryEngine) .addLibraryIdToMeasureId( - new VersionedIdentifier() - .withId( - libraryVersionIdentifier - .getId()), // LUKETODO: is it wise to do this here and not later? - measure.getIdElement()) + new VersionedIdentifier().withId(libraryVersionIdentifier.getId()), measure.getIdElement()) .build(); } diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureDefBuilder.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureDefBuilder.java index beeaf12911..c0d6dbba65 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureDefBuilder.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureDefBuilder.java @@ -149,6 +149,25 @@ && checkPopulationForCode(populations, DATEOFCOMPLIANCE) == null) { return new MeasureDef(measure.getId(), measure.getUrl(), measure.getVersion(), groups, sdes); } + public static void triggerFirstPassValidation(List measures) { + measures.forEach(R4MeasureDefBuilder::triggerFirstPassValidation); + } + + private static void triggerFirstPassValidation(Measure measure) { + + checkId(measure); + + // SDES + for (MeasureSupplementalDataComponent s : measure.getSupplementalData()) { + checkId(s); + checkSDEUsage(measure, s); + } + // scoring + getMeasureScoring(measure); + + validateMeasureImprovementNotation(measure); + } + private PopulationDef checkPopulationForCode( List populations, MeasurePopulationType measurePopType) { return populations.stream() @@ -164,7 +183,8 @@ private ConceptDef totalConceptDefCreator(MeasurePopulationType measurePopulatio null); } - private void checkSDEUsage(Measure measure, MeasureSupplementalDataComponent measureSupplementalDataComponent) { + private static void checkSDEUsage( + Measure measure, MeasureSupplementalDataComponent measureSupplementalDataComponent) { var hasUsage = measureSupplementalDataComponent.getUsage().stream() .filter(t -> t.getCodingFirstRep().getCode().equals(SDE_USAGE_CODE)) .collect(Collectors.toList()); @@ -191,19 +211,19 @@ private CodeDef codeToCodeDef(Coding coding) { return new CodeDef(coding.getSystem(), coding.getVersion(), coding.getCode(), coding.getDisplay()); } - private void checkId(Element e) { + private static void checkId(Element e) { if (e.getId() == null || StringUtils.isBlank(e.getId())) { throw new NullPointerException("id is required on all Elements of type: " + e.fhirType()); } } - private void checkId(Resource r) { + private static void checkId(Resource r) { if (r.getId() == null || StringUtils.isBlank(r.getId())) { throw new NullPointerException("id is required on all Resources of type: " + r.fhirType()); } } - private MeasureScoring getMeasureScoring(Measure measure, @Nullable String scoringCode) { + private static MeasureScoring getMeasureScoring(Measure measure, @Nullable String scoringCode) { if (scoringCode != null) { var code = MeasureScoring.fromCode(scoringCode); if (code == null) { @@ -217,12 +237,12 @@ private MeasureScoring getMeasureScoring(Measure measure, @Nullable String scori return null; } - private MeasureScoring getMeasureScoring(Measure measure) { + private static MeasureScoring getMeasureScoring(Measure measure) { var scoringCode = measure.getScoring().getCodingFirstRep().getCode(); return getMeasureScoring(measure, scoringCode); } - private void validateImprovementNotationCode(Measure measure, CodeDef improvementNotation) { + private static void validateImprovementNotationCode(Measure measure, CodeDef improvementNotation) { var code = improvementNotation.code(); var system = improvementNotation.system(); boolean hasValidSystem = system.equals(MEASUREREPORT_IMPROVEMENT_NOTATION_SYSTEM); @@ -264,6 +284,16 @@ public CodeDef getMeasureImprovementNotation(Measure measure) { return null; } + private static void validateMeasureImprovementNotation(Measure measure) { + if (measure.hasImprovementNotation()) { + var improvementNotationValue = measure.getImprovementNotation(); + var codeDef = new CodeDef( + improvementNotationValue.getCodingFirstRep().getSystem(), + improvementNotationValue.getCodingFirstRep().getCode()); + validateImprovementNotationCode(measure, codeDef); + } + } + public CodeDef getGroupImpNotation(Measure measure, MeasureGroupComponent group) { var ext = group.getExtensionByUrl(MEASUREREPORT_IMPROVEMENT_NOTATION_EXTENSION); if (ext != null) { diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java index 435fdb759f..608eb32463 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessor.java @@ -248,7 +248,6 @@ public CompositeEvaluationResultsPerMeasure evaluateMeasureWithCqlEngine( subjects, List.of(measure), periodStart, periodEnd, parameters, context); } - // LUKETODO: capture this with a unit test to get the code coverage up public CompositeEvaluationResultsPerMeasure evaluateMultiMeasureIdsWithCqlEngine( List subjects, List measureIds, @@ -282,10 +281,9 @@ public CompositeEvaluationResultsPerMeasure evaluateMultiMeasuresWithCqlEngine( var zonedMeasurementPeriod = MeasureProcessorUtils.getZonedTimeZoneForEval( measureProcessorUtils.getDefaultMeasurementPeriod(measurementPeriodParams, context)); - // LUKETODO: find a better way: // Do this to be backwards compatible with the previous single-library evaluation: // Trigger first-pass validation on measure scoring as well as other aspects of the Measures - measures.forEach(measure -> new R4MeasureDefBuilder().build(measure)); + R4MeasureDefBuilder.triggerFirstPassValidation(measures); // Note that we must build the LibraryEngine BEFORE we call // measureProcessorUtils.setMeasurementPeriod(), otherwise, we get an NPE. @@ -320,8 +318,7 @@ private MultiLibraryIdMeasureEngineDetails getMultiLibraryIdMeasureEngineDetails libraryIdentifiersToMeasureIds.entries().forEach(entry -> { builder.addLibraryIdToMeasureId( - new VersionedIdentifier().withId(entry.getKey().getId()), // LUKETODO: is it wise to do this here? - entry.getValue()); + new VersionedIdentifier().withId(entry.getKey().getId()), entry.getValue()); }); return builder.build(); @@ -414,7 +411,7 @@ protected LibraryEngine getLibraryEngine(Parameters parameters, List getCompiledLibraries(List ids } } - // LUKETODO: handle multiple library errors here better: throw new IllegalStateException( "Unable to load CQL/ELM for libraries: %s Verify that the Library resource is available in your environment and has CQL/ELM content embedded. Errors: %s" .formatted(ids, allErrors)); diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/cpg/r4/LibraryEvaluationServiceComplexDepsTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/cpg/r4/LibraryEvaluationServiceComplexDepsTest.java index 46534b5d18..8c9fc54f80 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/cpg/r4/LibraryEvaluationServiceComplexDepsTest.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/cpg/r4/LibraryEvaluationServiceComplexDepsTest.java @@ -6,7 +6,6 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; import static org.opencds.cqf.fhir.utility.r4.Parameters.parameters; import static org.opencds.cqf.fhir.utility.r4.Parameters.stringPart; @@ -15,7 +14,6 @@ import org.hl7.fhir.r4.model.Encounter; import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.Parameters; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @SuppressWarnings("squid:S1135") @@ -210,11 +208,4 @@ void singleLibraryTest_1B() { .map(r -> r.getIdElement().getIdPart()) .collect(Collectors.toUnmodifiableSet())); } - - @Disabled - @Test - void multipleLibraryTest_1A_and_1B() { - // LUKETODO: how to do 2 libraries in one test? - fail("Not implemeted yet"); - } } diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasure.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasure.java index 1e666a1d21..7bd4e2f45c 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasure.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasure.java @@ -138,6 +138,16 @@ public MultiMeasure.Given serverBase(String serverBase) { return this; } + // Exposed for unit tests that only want to use part of this framework when passing in + // an IgRepository + public IRepository getRepository() { + if (this.repository == null) { + throw new IllegalStateException( + "Repository has not been set. Use 'repository' or 'repositoryFor' to set it."); + } + return this.repository; + } + private R4MultiMeasureService buildMeasureService() { return new R4MultiMeasureService(repository, evaluationOptions, serverBase, measurePeriodValidator); } diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessorTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessorTest.java new file mode 100644 index 0000000000..6fb6d298e4 --- /dev/null +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureProcessorTest.java @@ -0,0 +1,60 @@ +package org.opencds.cqf.fhir.cr.measure.r4; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.List; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.Parameters; +import org.hl7.fhir.r4.model.ResourceType; +import org.junit.jupiter.api.Test; +import org.opencds.cqf.fhir.cql.Engines; +import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions; +import org.opencds.cqf.fhir.cr.measure.common.MeasureDef; +import org.opencds.cqf.fhir.cr.measure.common.MeasureProcessorUtils; +import org.opencds.cqf.fhir.cr.measure.r4.MultiMeasure.Given; + +class R4MeasureProcessorTest { + private static final Given GIVEN_REPO = MultiMeasure.given().repositoryFor("MinimalMeasureEvaluation"); + private static final IdType MINIMAL_COHORT_BOOLEAN_BASIS_SINGLE_GROUP = + new IdType(ResourceType.Measure.name(), "MinimalCohortBooleanBasisSingleGroup"); + private static final String SUBJECT_ID = "Patient/female-1914"; + + // This test could probably be improved with better data and more assertions, but it's to + // confirm that a method exposed for downstream works with reasonable sanity. + @Test + void evaluateMultiMeasureIdsWithCqlEngine() { + var repository = GIVEN_REPO.getRepository(); + var r4MeasureProcessor = new R4MeasureProcessor( + repository, MeasureEvaluationOptions.defaultOptions(), new MeasureProcessorUtils()); + + var cqlEngine = Engines.forRepository(repository); + + var results = r4MeasureProcessor.evaluateMultiMeasureIdsWithCqlEngine( + List.of(SUBJECT_ID), + List.of(MINIMAL_COHORT_BOOLEAN_BASIS_SINGLE_GROUP), + null, + null, + new Parameters(), + cqlEngine); + + assertNotNull(results); + var measureDef = new MeasureDef("", "", "", List.of(), List.of()); + var evaluationResults = + results.processMeasureForSuccessOrFailure(MINIMAL_COHORT_BOOLEAN_BASIS_SINGLE_GROUP, measureDef); + + assertNotNull(evaluationResults); + + var evaluationResult = evaluationResults.get(SUBJECT_ID); + assertNotNull(evaluationResult); + + var expressionResults = evaluationResult.expressionResults; + assertNotNull(expressionResults); + + var expressionResult = expressionResults.get("Initial Population"); + assertNotNull(expressionResult); + + var evaluatedResources = expressionResult.evaluatedResources(); + assertEquals(1, evaluatedResources.size()); + } +} From f45835bcd7fb99432c267aee6739da590d9538f5 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Mon, 11 Aug 2025 12:28:46 -0400 Subject: [PATCH 63/66] Sync up with pom versions from master. Clean up more cruft. --- cqf-fhir-api/pom.xml | 4 ++-- cqf-fhir-benchmark/pom.xml | 17 +++++----------- cqf-fhir-bom/pom.xml | 20 +++++++++---------- cqf-fhir-cql/pom.xml | 12 +++++------ .../opencds/cqf/fhir/cql/LibraryEngine.java | 8 -------- cqf-fhir-cr-cli/pom.xml | 16 +++++++-------- cqf-fhir-cr-hapi/pom.xml | 2 +- cqf-fhir-cr-spring/pom.xml | 6 +++--- cqf-fhir-cr/pom.xml | 14 ++++++------- .../fhir/cr/measure/r4/R4MeasureService.java | 1 - .../cr/measure/r4/R4MultiMeasureService.java | 5 +---- .../r4/R4PopulationBasisValidator.java | 7 ------- cqf-fhir-jackson/pom.xml | 4 ++-- cqf-fhir-jaxb/pom.xml | 4 ++-- cqf-fhir-test/pom.xml | 6 +++--- cqf-fhir-utility/pom.xml | 8 ++++---- docs/pom.xml | 6 +++--- pom.xml | 7 ++----- 18 files changed, 59 insertions(+), 88 deletions(-) diff --git a/cqf-fhir-api/pom.xml b/cqf-fhir-api/pom.xml index 39a6689f2e..775768816c 100644 --- a/cqf-fhir-api/pom.xml +++ b/cqf-fhir-api/pom.xml @@ -5,14 +5,14 @@ org.opencds.cqf.fhir cqf-fhir-api - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT FHIR Clinical Reasoning (APIs) FHIR Repository APIs used by this project. Implement these to incorporate clinical reasoning on your platform org.opencds.cqf.fhir cqf-fhir - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT diff --git a/cqf-fhir-benchmark/pom.xml b/cqf-fhir-benchmark/pom.xml index 5452560413..f1aca8f224 100644 --- a/cqf-fhir-benchmark/pom.xml +++ b/cqf-fhir-benchmark/pom.xml @@ -5,26 +5,26 @@ org.opencds.cqf.fhir cqf-fhir-benchmark - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT FHIR Clinical Reasoning (Benchmarks) Tests validating performance of FHIR Clinical Reasoning operations org.opencds.cqf.fhir cqf-fhir - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT org.opencds.cqf.fhir cqf-fhir-cr - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT org.opencds.cqf.fhir cqf-fhir-test - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT org.openjdk.jmh @@ -39,7 +39,7 @@ org.opencds.cqf.fhir cqf-fhir-jackson - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT pom test @@ -54,13 +54,6 @@ - - org.apache.maven.plugins - maven-deploy-plugin - - true - - org.apache.maven.plugins maven-compiler-plugin diff --git a/cqf-fhir-bom/pom.xml b/cqf-fhir-bom/pom.xml index edf4e4a710..fbcd41695e 100644 --- a/cqf-fhir-bom/pom.xml +++ b/cqf-fhir-bom/pom.xml @@ -5,7 +5,7 @@ org.opencds.cqf.fhir cqf-fhir-bom - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT pom FHIR Clinical Reasoning (Bill Of Materials) This bom can be used to simplify dependency management when using this project @@ -13,7 +13,7 @@ org.opencds.cqf.fhir cqf-fhir - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT @@ -21,44 +21,44 @@ org.opencds.cqf.fhir cqf-fhir-api - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT org.opencds.cqf.fhir cqf-fhir-test - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT org.opencds.cqf.fhir cqf-fhir-utility - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT org.opencds.cqf.fhir cqf-fhir-cql - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT org.opencds.cqf.fhir cqf-fhir-jackson - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT pom org.opencds.cqf.fhir cqf-fhir-jaxb - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT pom org.opencds.cqf.fhir cqf-fhir-cr - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT org.opencds.cqf.fhir cqf-fhir-cr-cli - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT diff --git a/cqf-fhir-cql/pom.xml b/cqf-fhir-cql/pom.xml index 8cdfd7e633..c9bb2c8e93 100644 --- a/cqf-fhir-cql/pom.xml +++ b/cqf-fhir-cql/pom.xml @@ -5,26 +5,26 @@ org.opencds.cqf.fhir cqf-fhir-cql - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT FHIR Clinical Reasoning (CQL) Tools, utilities, code gen to support CQL in FHIR Clinical Reasoning operations org.opencds.cqf.fhir cqf-fhir - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT org.opencds.cqf.fhir cqf-fhir-api - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT org.opencds.cqf.fhir cqf-fhir-utility - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT org.mockito @@ -62,13 +62,13 @@ org.opencds.cqf.fhir cqf-fhir-test - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT test org.opencds.cqf.fhir cqf-fhir-jackson - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT pom test diff --git a/cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/LibraryEngine.java b/cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/LibraryEngine.java index 2eeae9eb3d..55d3540f4d 100644 --- a/cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/LibraryEngine.java +++ b/cqf-fhir-cql/src/main/java/org/opencds/cqf/fhir/cql/LibraryEngine.java @@ -338,12 +338,6 @@ public EvaluationResultsForMultiLib getEvaluationResult( @Nullable ZonedDateTime zonedDateTime, CqlEngine engine) { - // logger.info( - // "1234: ids: {}, patientId: {}, zonedDateTime: {}", - // ids.stream().map(VersionedIdentifier::getId).toList(), - // patientId, - // zonedDateTime); - var cqlFhirParametersConverterToUse = Objects.requireNonNullElseGet( cqlFhirParametersConverter, () -> Engines.getCqlFhirParametersConverter(repository.fhirContext())); @@ -380,8 +374,6 @@ public EvaluationResult getEvaluationResult( @Nullable ZonedDateTime zonedDateTime, CqlEngine engine) { - // logger.info("1234: id: {}, patientId: {}, zonedDateTime: {}", id.getId(), patientId, zonedDateTime); - var evaluationResultsForMultiLib = getEvaluationResult( List.of(id), patientId, diff --git a/cqf-fhir-cr-cli/pom.xml b/cqf-fhir-cr-cli/pom.xml index 5836a62d32..cc9a0b006a 100644 --- a/cqf-fhir-cr-cli/pom.xml +++ b/cqf-fhir-cr-cli/pom.xml @@ -6,36 +6,36 @@ org.opencds.cqf.fhir cqf-fhir-cr-cli - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT FHIR Clinical Reasoning (CLI) CLI for running FHIR Clincial Reasoning operations org.opencds.cqf.fhir cqf-fhir - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT org.opencds.cqf.fhir cqf-fhir-api - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT org.opencds.cqf.fhir cqf-fhir-utility - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT org.opencds.cqf.fhir cqf-fhir-cql - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT org.opencds.cqf.fhir cqf-fhir-jackson - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT pom @@ -69,13 +69,13 @@ org.opencds.cqf.fhir cqf-fhir-test - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT test org.opencds.cqf.fhir cqf-fhir-cr - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT compile diff --git a/cqf-fhir-cr-hapi/pom.xml b/cqf-fhir-cr-hapi/pom.xml index 34f425648f..3d63d6cb13 100644 --- a/cqf-fhir-cr-hapi/pom.xml +++ b/cqf-fhir-cr-hapi/pom.xml @@ -6,7 +6,7 @@ org.opencds.cqf.fhir cqf-fhir - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT cqf-fhir-cr-hapi diff --git a/cqf-fhir-cr-spring/pom.xml b/cqf-fhir-cr-spring/pom.xml index 7e519f07fc..098a294be7 100644 --- a/cqf-fhir-cr-spring/pom.xml +++ b/cqf-fhir-cr-spring/pom.xml @@ -7,21 +7,21 @@ org.opencds.cqf.fhir cqf-fhir-cr-spring - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT FHIR Clinical Reasoning (Spring) Spring configurations for FHIR Clinical Reasoning org.opencds.cqf.fhir cqf-fhir - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT org.opencds.cqf.fhir cqf-fhir-cr - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT org.springframework diff --git a/cqf-fhir-cr/pom.xml b/cqf-fhir-cr/pom.xml index 6638766e77..83996fbc3f 100644 --- a/cqf-fhir-cr/pom.xml +++ b/cqf-fhir-cr/pom.xml @@ -5,45 +5,45 @@ org.opencds.cqf.fhir cqf-fhir-cr - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT FHIR Clinical Reasoning (Operations) Implementations of clinical reasoning operations org.opencds.cqf.fhir cqf-fhir - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT org.opencds.cqf.fhir cqf-fhir-api - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT org.opencds.cqf.fhir cqf-fhir-cql - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT org.opencds.cqf.fhir cqf-fhir-utility - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT org.opencds.cqf.fhir cqf-fhir-jackson - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT pom test org.opencds.cqf.fhir cqf-fhir-test - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT test diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java index 0c273e2858..06be1e14e8 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java @@ -35,7 +35,6 @@ public R4MeasureService( IRepository repository, MeasureEvaluationOptions measureEvaluationOptions, MeasurePeriodValidator measurePeriodValidator) { - // this.repository = RepositoryLoggingProxy.init(repository); this.repository = repository; this.measureEvaluationOptions = measureEvaluationOptions; this.measurePeriodValidator = measurePeriodValidator; diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java index 9f1fab4de2..14c3a85902 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java @@ -53,7 +53,6 @@ public R4MultiMeasureService( MeasureEvaluationOptions measureEvaluationOptions, String serverBase, MeasurePeriodValidator measurePeriodValidator) { - // this.repository = RepositoryLoggingProxy.init(repository); this.repository = repository; this.measureEvaluationOptions = measureEvaluationOptions; this.measurePeriodValidator = measurePeriodValidator; @@ -209,8 +208,7 @@ protected void populationMeasureReport( // progress feedback var measureUrl = measureReport.getMeasure(); if (!measureUrl.isEmpty()) { - // log.debug( - log.info( + log.debug( "Completed evaluation for Measure: {}, Measures remaining to evaluate: {}", measureUrl, totalMeasures--); @@ -240,7 +238,6 @@ protected void subjectMeasureReport( "Evaluating individual MeasureReports for {} patients, and {} measures", subjects.size(), measures.size()); - for (Measure measure : measures) { for (String subject : subjects) { MeasureReport measureReport; diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4PopulationBasisValidator.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4PopulationBasisValidator.java index bcdd68b1cd..fdaaae8e4c 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4PopulationBasisValidator.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4PopulationBasisValidator.java @@ -25,16 +25,12 @@ import org.opencds.cqf.fhir.cr.measure.common.PopulationBasisValidator; import org.opencds.cqf.fhir.cr.measure.common.PopulationDef; import org.opencds.cqf.fhir.cr.measure.common.StratifierDef; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Validates group populations and stratifiers against population basis-es for R4 only. */ public class R4PopulationBasisValidator implements PopulationBasisValidator { - private static final Logger logger = LoggerFactory.getLogger(R4PopulationBasisValidator.class); - private static final String BOOLEAN_BASIS = "boolean"; /** @@ -72,9 +68,6 @@ public void validateStratifiers(MeasureDef measureDef, GroupDef groupDef, Evalua private void validateGroupPopulationBasisType( String url, GroupDef groupDef, PopulationDef populationDef, EvaluationResult evaluationResult) { - // logger.info("1234: evaluationResult:\n{}", - // EvaluationResultsDisplay.printEvaluationResult(evaluationResult)); - // PROPORTION var scoring = groupDef.measureScoring(); // Numerator diff --git a/cqf-fhir-jackson/pom.xml b/cqf-fhir-jackson/pom.xml index d50220352d..290a3e43dc 100644 --- a/cqf-fhir-jackson/pom.xml +++ b/cqf-fhir-jackson/pom.xml @@ -5,7 +5,7 @@ org.opencds.cqf.fhir cqf-fhir-jackson - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT pom FHIR Clinical Reasoning (Jackson) Aggregator module for Jackson dependencies @@ -13,7 +13,7 @@ org.opencds.cqf.fhir cqf-fhir - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT diff --git a/cqf-fhir-jaxb/pom.xml b/cqf-fhir-jaxb/pom.xml index 4b52755cf6..97258fd1d0 100644 --- a/cqf-fhir-jaxb/pom.xml +++ b/cqf-fhir-jaxb/pom.xml @@ -5,7 +5,7 @@ org.opencds.cqf.fhir cqf-fhir-jaxb - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT pom FHIR Clinical Reasoning (JAXB) Aggregator module for JAXB dependencies @@ -13,7 +13,7 @@ org.opencds.cqf.fhir cqf-fhir - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT diff --git a/cqf-fhir-test/pom.xml b/cqf-fhir-test/pom.xml index 1bdd08587c..26be3bb20f 100644 --- a/cqf-fhir-test/pom.xml +++ b/cqf-fhir-test/pom.xml @@ -5,21 +5,21 @@ org.opencds.cqf.fhir cqf-fhir-test - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT FHIR Clinical Reasoning (Test Utilities) Utilities to support unit testing clinical reasoning operations org.opencds.cqf.fhir cqf-fhir - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT org.opencds.cqf.fhir cqf-fhir-api - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT ca.uhn.hapi.fhir diff --git a/cqf-fhir-utility/pom.xml b/cqf-fhir-utility/pom.xml index a0819de0e9..4530ac6eb0 100644 --- a/cqf-fhir-utility/pom.xml +++ b/cqf-fhir-utility/pom.xml @@ -5,21 +5,21 @@ org.opencds.cqf.fhir cqf-fhir-utility - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT FHIR Clinical Reasoning (Utilities) Utilities to help develop clinical reasoning operations org.opencds.cqf.fhir cqf-fhir - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT org.opencds.cqf.fhir cqf-fhir-api - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT info.cqframework @@ -89,7 +89,7 @@ org.opencds.cqf.fhir cqf-fhir-test - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT test diff --git a/docs/pom.xml b/docs/pom.xml index 2fe483abef..3f8a9acb29 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -5,7 +5,7 @@ org.opencds.cqf.fhir docs - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT FHIR Clinical Reasoning (Documentation) Documentation website for FHIR Clinical Reasoning @@ -13,7 +13,7 @@ org.opencds.cqf.fhir cqf-fhir - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT @@ -23,7 +23,7 @@ cqf-fhir-bom pom import - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT diff --git a/pom.xml b/pom.xml index 3bcf48d97b..81b8dd5de3 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.opencds.cqf.fhir cqf-fhir - 3.25.0-SNAPSHOT + 3.24.0-SNAPSHOT pom FHIR Clinical Reasoning FHIR Clinical Reasoning Implementations @@ -22,10 +22,7 @@ 5.10.2 2.0.4 - - - 3.28.0-SNAPSHOT - + 3.27.0 1.36 6.1.14 8.2.0 From 4dbebe76bb73b7155a424be254cf52c84c2ab65c Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Mon, 11 Aug 2025 18:03:42 -0400 Subject: [PATCH 64/66] Change CQL version to 3.28.0. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 81b8dd5de3..5a9e90e501 100644 --- a/pom.xml +++ b/pom.xml @@ -22,7 +22,7 @@ 5.10.2 2.0.4 - 3.27.0 + 3.28.0 1.36 6.1.14 8.2.0 From fd799f4279c579ce4f6c726e0c3f9647d743b0ee Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Mon, 11 Aug 2025 19:21:57 -0400 Subject: [PATCH 65/66] Fix tests for new evaluatedMeasures() logic. --- .../opencds/cqf/fhir/cr/measure/r4/CollectDataTest.java | 8 ++++---- .../fhir/cr/measure/r4/MeasureEvaluatedResourcesTest.java | 5 ----- .../cr/measure/r4/MeasureReportTypeIndividualTest.java | 4 ++-- .../cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java | 2 +- 4 files changed, 7 insertions(+), 12 deletions(-) diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/CollectDataTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/CollectDataTest.java index aeab724192..cb45cdbbc2 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/CollectDataTest.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/CollectDataTest.java @@ -16,10 +16,10 @@ void collectData_booleanBasisMeasure_subject() { .subject("Patient/eNeMVHWfNoTsMTbrwWQQ30A3") .collectData() .then() - .hasParameterCount(16) // 3 more? + .hasParameterCount(13) .measureReport() .hasDataCollectionReportType() - .hasEvaluatedResourceCount(15) // 3 more? + .hasEvaluatedResourceCount(12) .up() .report(); } @@ -34,7 +34,7 @@ void collectData_booleanBasisMeasure_population() { .periodEnd("2022-06-29") .collectData() .then() - .hasParameterCount(16) + .hasParameterCount(13) .hasMeasureReportCount(1) .report(); } @@ -66,7 +66,7 @@ void collectData_resourceBasisMeasure_population() { .periodEnd("2020-01-01") .collectData() .then() - .hasParameterCount(18) + .hasParameterCount(17) .hasMeasureReportCount(4) .report(); } diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MeasureEvaluatedResourcesTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MeasureEvaluatedResourcesTest.java index 1bb2226a88..0379f762ae 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MeasureEvaluatedResourcesTest.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MeasureEvaluatedResourcesTest.java @@ -59,11 +59,9 @@ void cohortBooleanSDE() { .hasNoDuplicateExtensions() .up() .evaluatedResource("Encounter/patient-9-encounter-1") - .referenceHasExtension("sde-patient-sex") .hasNoDuplicateExtensions() .up() .evaluatedResource("Encounter/patient-9-encounter-2") - .referenceHasExtension("sde-patient-sex") .hasNoDuplicateExtensions() .up() .report(); @@ -86,9 +84,6 @@ void ratioBooleanPopulationCheck() { .hasEvaluatedResourceCount(3) .evaluatedResourceHasNoDuplicateReferences() .evaluatedResource("Patient/patient-9") - .referenceHasExtension("initial-population") - .referenceHasExtension("denominator") - .referenceHasExtension("numerator") .hasNoDuplicateExtensions() .up() .evaluatedResource("Encounter/patient-9-encounter-1") diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MeasureReportTypeIndividualTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MeasureReportTypeIndividualTest.java index 96f4a5ddbb..e35099cb13 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MeasureReportTypeIndividualTest.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MeasureReportTypeIndividualTest.java @@ -55,7 +55,7 @@ void proportionResourceWithReportTypeParameter() { .hasMeasureReportPeriod() .hasStatus(MeasureReportStatus.COMPLETE) .hasMeasureUrl("http://example.com/Measure/ProportionResourceAllPopulations") - .hasEvaluatedResourceCount(3) + .hasEvaluatedResourceCount(2) .firstGroup() .population("initial-population") .hasCount(2) @@ -94,7 +94,7 @@ void proportionResourceWithNoReportType() { .hasMeasureReportPeriod() .hasStatus(MeasureReportStatus.COMPLETE) .hasMeasureUrl("http://example.com/Measure/ProportionResourceAllPopulations") - .hasEvaluatedResourceCount(3) + .hasEvaluatedResourceCount(2) .firstGroup() .population("initial-population") .hasCount(2) diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java index 775c5a994d..99ac6052fb 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java @@ -559,7 +559,7 @@ void MultiMeasure_EightMeasures_Patient() { .measureReport("http://example.com/Measure/MinimalCohortResourceBasisSingleGroup") .hasReportType("Individual") .hasSubjectReference("Patient/female-1988") - .hasEvaluatedResourceCount(3) + .hasEvaluatedResourceCount(2) .firstGroup() .population("initial-population") .hasCount(2) From dd4c01b20103b8bce00ffdc40acf77e2b5cf519b Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Mon, 11 Aug 2025 16:53:39 -0700 Subject: [PATCH 66/66] Fix capitalization --- .../tests/{Encounter => encounter}/male-1988-encounter-1.json | 0 .../tests/{Encounter => encounter}/male-1988-encounter-2.json | 0 .../tests/{Encounter => encounter}/male-1988-encounter-3.json | 0 .../tests/{Encounter => encounter}/male-1988-encounter-4.json | 0 .../CqlOptimize/input/tests/{Patient => patient}/male-1988.json | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/tests/{Encounter => encounter}/male-1988-encounter-1.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/tests/{Encounter => encounter}/male-1988-encounter-2.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/tests/{Encounter => encounter}/male-1988-encounter-3.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/tests/{Encounter => encounter}/male-1988-encounter-4.json (100%) rename cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/tests/{Patient => patient}/male-1988.json (100%) diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/tests/Encounter/male-1988-encounter-1.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/tests/encounter/male-1988-encounter-1.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/tests/Encounter/male-1988-encounter-1.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/tests/encounter/male-1988-encounter-1.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/tests/Encounter/male-1988-encounter-2.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/tests/encounter/male-1988-encounter-2.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/tests/Encounter/male-1988-encounter-2.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/tests/encounter/male-1988-encounter-2.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/tests/Encounter/male-1988-encounter-3.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/tests/encounter/male-1988-encounter-3.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/tests/Encounter/male-1988-encounter-3.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/tests/encounter/male-1988-encounter-3.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/tests/Encounter/male-1988-encounter-4.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/tests/encounter/male-1988-encounter-4.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/tests/Encounter/male-1988-encounter-4.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/tests/encounter/male-1988-encounter-4.json diff --git a/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/tests/Patient/male-1988.json b/cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/tests/patient/male-1988.json similarity index 100% rename from cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/tests/Patient/male-1988.json rename to cqf-fhir-cr/src/test/resources/org/opencds/cqf/fhir/cr/measure/r4/CqlOptimize/input/tests/patient/male-1988.json