Date: Wed, 25 Mar 2026 08:57:12 -0600
Subject: [PATCH 04/10] Introduce MeasureEnvironment: consolidate endpoint
params
contentEndpoint, terminologyEndpoint, dataEndpoint, and additionalData
were passed as four separate parameters through every layer of the
measure evaluation stack -- both public interfaces and internal methods.
Replace all four with MeasureEnvironment.
record MeasureEnvironment(
IBaseResource contentEndpoint,
IBaseResource terminologyEndpoint,
IBaseResource dataEndpoint,
IBaseBundle additionalData)
Per the pipeline architecture, environment resolution is step 2 --
separate from the operation parameters. HAPI providers (the transport
boundary) now construct MeasureEnvironment from the resolved endpoint
params before calling the service layer. All downstream code receives
a single typed object.
R4MultiMeasureService.evaluateToListOfList() now delegates repository
construction to a private resolveRepository(MeasureEnvironment) helper,
making the step explicit and testable in isolation.
MeasureEnvironment.EMPTY is used by R4CareGapsBundleBuilder (which has
no endpoint configuration) and by test helpers that only set additionalData.
IBaseResource / IBaseBundle carry the endpoints version-agnostically,
consistent with the domain-core invariant (no version-specific types).
---
.../fhir/benchmark/measure/r4/Measure.java | 6 +-
.../measure/MeasureOperationsProvider.java | 7 +-
.../r4/measure/MeasureOperationsProvider.java | 15 ++-
.../cr/measure/common/MeasureEnvironment.java | 33 +++++++
.../dstu3/Dstu3MeasureEvaluatorSingle.java | 8 +-
.../cr/measure/dstu3/Dstu3MeasureService.java | 19 ++--
.../measure/r4/R4CareGapsBundleBuilder.java | 6 +-
.../r4/R4MeasureEvaluatorMultiple.java | 8 +-
.../measure/r4/R4MeasureEvaluatorSingle.java | 8 +-
.../cr/measure/r4/R4MultiMeasureService.java | 95 ++++++++-----------
.../cqf/fhir/cr/measure/r4/Measure.java | 6 +-
.../cqf/fhir/cr/measure/r4/MultiMeasure.java | 6 +-
12 files changed, 108 insertions(+), 109 deletions(-)
create mode 100644 cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureEnvironment.java
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 a79d0446fa..a04f12eaf2 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
@@ -14,6 +14,7 @@
import org.opencds.cqf.fhir.cql.engine.retrieve.RetrieveSettings.TERMINOLOGY_FILTER_MODE;
import org.opencds.cqf.fhir.cql.engine.terminology.TerminologySettings.VALUESET_EXPANSION_MODE;
import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions;
+import org.opencds.cqf.fhir.cr.measure.common.MeasureEnvironment;
import org.opencds.cqf.fhir.cr.measure.common.MeasurePeriodValidator;
import org.opencds.cqf.fhir.cr.measure.common.MeasureReference;
import org.opencds.cqf.fhir.cr.measure.r4.R4MultiMeasureService;
@@ -147,10 +148,7 @@ public When evaluate() {
reportType,
subject,
null,
- null,
- null,
- null,
- additionalData,
+ new MeasureEnvironment(null, null, null, additionalData),
null,
null,
null);
diff --git a/cqf-fhir-cr-hapi/src/main/java/org/opencds/cqf/fhir/cr/hapi/dstu3/measure/MeasureOperationsProvider.java b/cqf-fhir-cr-hapi/src/main/java/org/opencds/cqf/fhir/cr/hapi/dstu3/measure/MeasureOperationsProvider.java
index a6db93e2fd..7869834063 100644
--- a/cqf-fhir-cr-hapi/src/main/java/org/opencds/cqf/fhir/cr/hapi/dstu3/measure/MeasureOperationsProvider.java
+++ b/cqf-fhir-cr-hapi/src/main/java/org/opencds/cqf/fhir/cr/hapi/dstu3/measure/MeasureOperationsProvider.java
@@ -18,6 +18,7 @@
import org.hl7.fhir.dstu3.model.Parameters.ParametersParameterComponent;
import org.hl7.fhir.exceptions.FHIRException;
import org.opencds.cqf.fhir.cr.hapi.dstu3.IMeasureServiceFactory;
+import org.opencds.cqf.fhir.cr.measure.common.MeasureEnvironment;
import org.springframework.stereotype.Component;
@Component
@@ -72,6 +73,7 @@ public MeasureReport evaluateMeasure(
RequestDetails requestDetails)
throws InternalErrorException, FHIRException {
var terminologyEndpointParam = (Endpoint) getEndpoint(fhirVersion, terminologyEndpoint);
+ var environment = new MeasureEnvironment(null, terminologyEndpointParam, null, additionalData);
return dstu3MeasureProcessorFactory
.create(requestDetails)
.evaluateMeasure(
@@ -83,8 +85,7 @@ public MeasureReport evaluateMeasure(
practitioner,
lastReceivedOn,
productLine,
- additionalData,
- parameters,
- terminologyEndpointParam);
+ environment,
+ parameters);
}
}
diff --git a/cqf-fhir-cr-hapi/src/main/java/org/opencds/cqf/fhir/cr/hapi/r4/measure/MeasureOperationsProvider.java b/cqf-fhir-cr-hapi/src/main/java/org/opencds/cqf/fhir/cr/hapi/r4/measure/MeasureOperationsProvider.java
index 342d696ef9..2570d733d4 100644
--- a/cqf-fhir-cr-hapi/src/main/java/org/opencds/cqf/fhir/cr/hapi/r4/measure/MeasureOperationsProvider.java
+++ b/cqf-fhir-cr-hapi/src/main/java/org/opencds/cqf/fhir/cr/hapi/r4/measure/MeasureOperationsProvider.java
@@ -20,6 +20,7 @@
import org.opencds.cqf.fhir.cr.hapi.common.StringTimePeriodHandler;
import org.opencds.cqf.fhir.cr.hapi.r4.R4MeasureEvaluatorMultipleFactory;
import org.opencds.cqf.fhir.cr.hapi.r4.R4MeasureEvaluatorSingleFactory;
+import org.opencds.cqf.fhir.cr.measure.common.MeasureEnvironment;
import org.opencds.cqf.fhir.cr.measure.common.MeasureReference;
@SuppressWarnings("java:S107")
@@ -87,6 +88,8 @@ public MeasureReport evaluateMeasure(
var contentEndpointParam = (Endpoint) getEndpoint(fhirVersion, contentEndpoint);
var terminologyEndpointParam = (Endpoint) getEndpoint(fhirVersion, terminologyEndpoint);
var dataEndpointParam = (Endpoint) getEndpoint(fhirVersion, dataEndpoint);
+ var environment = new MeasureEnvironment(
+ contentEndpointParam, terminologyEndpointParam, dataEndpointParam, additionalData);
return r4MeasureServiceFactory
.create(requestDetails)
.evaluate(
@@ -96,10 +99,7 @@ public MeasureReport evaluateMeasure(
reportType,
subject,
lastReceivedOn,
- contentEndpointParam,
- terminologyEndpointParam,
- dataEndpointParam,
- additionalData,
+ environment,
parameters,
productLine,
practitioner);
@@ -157,6 +157,8 @@ public Parameters evaluate(
var terminologyEndpointParam = (Endpoint) getEndpoint(fhirVersion, terminologyEndpoint);
var dataEndpointParam = (Endpoint) getEndpoint(fhirVersion, dataEndpoint);
var measureRefs = MeasureReference.fromOperationParams(measureId, measureIdentifier, measureUrl);
+ var environment = new MeasureEnvironment(
+ contentEndpointParam, terminologyEndpointParam, dataEndpointParam, additionalData);
return r4MultiMeasureServiceFactory
.create(requestDetails)
.evaluate(
@@ -165,10 +167,7 @@ public Parameters evaluate(
stringTimePeriodHandler.getEndZonedDateTime(periodEnd, requestDetails),
reportType,
subject,
- contentEndpointParam,
- terminologyEndpointParam,
- dataEndpointParam,
- additionalData,
+ environment,
parameters,
productLine,
reporter);
diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureEnvironment.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureEnvironment.java
new file mode 100644
index 0000000000..c746f0581c
--- /dev/null
+++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureEnvironment.java
@@ -0,0 +1,33 @@
+package org.opencds.cqf.fhir.cr.measure.common;
+
+import jakarta.annotation.Nullable;
+import org.hl7.fhir.instance.model.api.IBaseBundle;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+
+/**
+ * Version-agnostic environment configuration for measure evaluation.
+ *
+ * Separates the infrastructure inputs (where data, content, and terminology come from)
+ * from the operation parameters (what to evaluate). Per the pipeline architecture,
+ * environment resolution happens before domain logic: the service layer composes a
+ * {@code ProxyRepository} from these endpoints and passes it to the evaluator.
+ *
+ *
Endpoint resources are typed as {@link IBaseResource} so both R4 and DSTU3
+ * {@code Endpoint} instances can be carried without version coupling.
+ * {@code Repositories.proxy()} already accepts {@code IBaseResource} endpoints directly.
+ *
+ * @param contentEndpoint endpoint for library/content resolution (nullable)
+ * @param terminologyEndpoint endpoint for terminology resolution (nullable)
+ * @param dataEndpoint endpoint for clinical data retrieval (nullable)
+ * @param additionalData supplemental data bundle for repository federation and CQL engine
+ * configuration (nullable)
+ */
+public record MeasureEnvironment(
+ @Nullable IBaseResource contentEndpoint,
+ @Nullable IBaseResource terminologyEndpoint,
+ @Nullable IBaseResource dataEndpoint,
+ @Nullable IBaseBundle additionalData) {
+
+ /** Empty environment — no endpoints, no additional data. */
+ public static final MeasureEnvironment EMPTY = new MeasureEnvironment(null, null, null, null);
+}
diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureEvaluatorSingle.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureEvaluatorSingle.java
index 6c1a632164..e56e76ddc7 100644
--- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureEvaluatorSingle.java
+++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureEvaluatorSingle.java
@@ -1,10 +1,9 @@
package org.opencds.cqf.fhir.cr.measure.dstu3;
-import org.hl7.fhir.dstu3.model.Bundle;
-import org.hl7.fhir.dstu3.model.Endpoint;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.MeasureReport;
import org.hl7.fhir.dstu3.model.Parameters;
+import org.opencds.cqf.fhir.cr.measure.common.MeasureEnvironment;
/**
* Interface for {@link Dstu3MeasureService} and any other concrete classes that implement the same
@@ -21,7 +20,6 @@ MeasureReport evaluateMeasure(
String practitioner,
String lastReceivedOn,
String productLine,
- Bundle additionalData,
- Parameters parameters,
- Endpoint terminologyEndpoint);
+ MeasureEnvironment environment,
+ Parameters parameters);
}
diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureService.java
index 285b84f71a..946b65bbe0 100644
--- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureService.java
+++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureService.java
@@ -12,12 +12,10 @@
import ca.uhn.fhir.util.BundleBuilder;
import java.util.Collections;
import java.util.List;
-import org.hl7.fhir.dstu3.model.Bundle;
import org.hl7.fhir.dstu3.model.CodeableConcept;
import org.hl7.fhir.dstu3.model.Coding;
import org.hl7.fhir.dstu3.model.ContactDetail;
import org.hl7.fhir.dstu3.model.ContactPoint;
-import org.hl7.fhir.dstu3.model.Endpoint;
import org.hl7.fhir.dstu3.model.Enumerations;
import org.hl7.fhir.dstu3.model.Extension;
import org.hl7.fhir.dstu3.model.IdType;
@@ -26,6 +24,7 @@
import org.hl7.fhir.dstu3.model.SearchParameter;
import org.hl7.fhir.dstu3.model.StringType;
import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions;
+import org.opencds.cqf.fhir.cr.measure.common.MeasureEnvironment;
public class Dstu3MeasureService implements Dstu3MeasureEvaluatorSingle {
private final IRepository repository;
@@ -89,8 +88,7 @@ public Dstu3MeasureService(IRepository repository, MeasureEvaluationOptions meas
* received.
* @param productLine the productLine (e.g. Medicare, Medicaid, etc) to use
* for the evaluation. This is a non-standard parameter.
- * @param additionalData the data bundle containing additional data
- * @param terminologyEndpoint the endpoint of terminology services for your measure valuesets
+ * @param environment endpoint and supplemental data configuration
* @return the calculated MeasureReport
*/
@Override
@@ -103,16 +101,21 @@ public MeasureReport evaluateMeasure(
String practitioner,
String lastReceivedOn,
String productLine,
- Bundle additionalData,
- Parameters parameters,
- Endpoint terminologyEndpoint) {
+ MeasureEnvironment environment,
+ Parameters parameters) {
ensureSupplementalDataElementSearchParameter();
var dstu3MeasureProcessor = new Dstu3MeasureProcessor(repository, measureEvaluationOptions);
MeasureReport report = dstu3MeasureProcessor.evaluateMeasure(
- id, periodStart, periodEnd, reportType, Collections.singletonList(subject), additionalData, parameters);
+ id,
+ periodStart,
+ periodEnd,
+ reportType,
+ Collections.singletonList(subject),
+ environment.additionalData(),
+ parameters);
if (productLine != null) {
Extension ext = new Extension();
diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CareGapsBundleBuilder.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CareGapsBundleBuilder.java
index c8aad3a974..c2153cbea8 100644
--- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CareGapsBundleBuilder.java
+++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CareGapsBundleBuilder.java
@@ -45,6 +45,7 @@
import org.hl7.fhir.r4.model.StringType;
import org.opencds.cqf.fhir.cr.measure.CareGapsProperties;
import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions;
+import org.opencds.cqf.fhir.cr.measure.common.MeasureEnvironment;
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.MeasureReference;
@@ -111,10 +112,7 @@ public List makePatientBundles(
r4CareGapsParameters.getPeriodEnd(),
MeasureEvalType.SUBJECT.toCode(),
subject,
- null,
- null,
- null,
- null,
+ MeasureEnvironment.EMPTY,
null,
null,
reporter);
diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureEvaluatorMultiple.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureEvaluatorMultiple.java
index 4c2d1ec083..aece913097 100644
--- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureEvaluatorMultiple.java
+++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureEvaluatorMultiple.java
@@ -3,9 +3,8 @@
import jakarta.annotation.Nullable;
import java.time.ZonedDateTime;
import java.util.List;
-import org.hl7.fhir.r4.model.Bundle;
-import org.hl7.fhir.r4.model.Endpoint;
import org.hl7.fhir.r4.model.Parameters;
+import org.opencds.cqf.fhir.cr.measure.common.MeasureEnvironment;
import org.opencds.cqf.fhir.cr.measure.common.MeasureReference;
/**
@@ -20,10 +19,7 @@ Parameters evaluate(
@Nullable ZonedDateTime periodEnd,
String reportType,
String subject, // practitioner passed in here
- Endpoint contentEndpoint,
- Endpoint terminologyEndpoint,
- Endpoint dataEndpoint,
- Bundle additionalData,
+ MeasureEnvironment environment,
Parameters parameters,
String productLine,
String reporter);
diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureEvaluatorSingle.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureEvaluatorSingle.java
index 83ecd01e83..77cc2dbda5 100644
--- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureEvaluatorSingle.java
+++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureEvaluatorSingle.java
@@ -2,10 +2,9 @@
import jakarta.annotation.Nullable;
import java.time.ZonedDateTime;
-import org.hl7.fhir.r4.model.Bundle;
-import org.hl7.fhir.r4.model.Endpoint;
import org.hl7.fhir.r4.model.MeasureReport;
import org.hl7.fhir.r4.model.Parameters;
+import org.opencds.cqf.fhir.cr.measure.common.MeasureEnvironment;
import org.opencds.cqf.fhir.cr.measure.common.MeasureReference;
/**
@@ -20,10 +19,7 @@ MeasureReport evaluate(
String reportType,
String subjectId,
String lastReceivedOn,
- Endpoint contentEndpoint,
- Endpoint terminologyEndpoint,
- Endpoint dataEndpoint,
- Bundle additionalData,
+ MeasureEnvironment environment,
Parameters parameters,
String productLine,
String 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 8650c5f10e..ba159b17e3 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
@@ -17,10 +17,10 @@
import java.util.UUID;
import java.util.function.Consumer;
import org.apache.commons.lang3.StringUtils;
+import org.hl7.fhir.instance.model.api.IBaseBundle;
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.Endpoint;
import org.hl7.fhir.r4.model.Measure;
import org.hl7.fhir.r4.model.MeasureReport;
import org.hl7.fhir.r4.model.Parameters;
@@ -30,6 +30,7 @@
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.MeasureDef;
+import org.opencds.cqf.fhir.cr.measure.common.MeasureEnvironment;
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.MeasureReference;
@@ -89,10 +90,7 @@ public MeasureReport evaluate(
String reportType,
String subjectId,
String lastReceivedOn,
- Endpoint contentEndpoint,
- Endpoint terminologyEndpoint,
- Endpoint dataEndpoint,
- Bundle additionalData,
+ MeasureEnvironment environment,
Parameters parameters,
String productLine,
String practitioner) {
@@ -104,10 +102,7 @@ public MeasureReport evaluate(
reportType,
subjectId,
lastReceivedOn,
- contentEndpoint,
- terminologyEndpoint,
- dataEndpoint,
- additionalData,
+ environment,
parameters,
productLine,
null) // reporter is null in the single measure case
@@ -122,10 +117,7 @@ MeasureDefAndR4MeasureReport evaluateSingleMeasureCaptureDef(
String reportType,
String subjectId,
String lastReceivedOn,
- Endpoint contentEndpoint,
- Endpoint terminologyEndpoint,
- Endpoint dataEndpoint,
- Bundle additionalData,
+ MeasureEnvironment environment,
Parameters parameters,
String productLine,
String practitioner) {
@@ -137,10 +129,7 @@ MeasureDefAndR4MeasureReport evaluateSingleMeasureCaptureDef(
periodEnd,
reportType,
subjectId,
- contentEndpoint,
- terminologyEndpoint,
- dataEndpoint,
- additionalData,
+ environment,
parameters,
productLine,
null,
@@ -170,10 +159,7 @@ public Parameters evaluate(
@Nullable ZonedDateTime periodEnd,
String reportType,
String subject, // practitioner passed in here
- Endpoint contentEndpoint,
- Endpoint terminologyEndpoint,
- Endpoint dataEndpoint,
- Bundle additionalData,
+ MeasureEnvironment environment,
Parameters parameters,
String productLine,
String reporter) {
@@ -184,10 +170,7 @@ public Parameters evaluate(
periodEnd,
reportType,
subject,
- contentEndpoint,
- terminologyEndpoint,
- dataEndpoint,
- additionalData,
+ environment,
parameters,
productLine,
reporter)
@@ -210,10 +193,7 @@ public Parameters evaluate(
* @param periodEnd end date of Measurement Period
* @param reportType type of report
* @param subject the subject ID (or practitioner)
- * @param contentEndpoint content endpoint
- * @param terminologyEndpoint terminology endpoint
- * @param dataEndpoint data endpoint
- * @param additionalData additional data bundle
+ * @param environment endpoint and supplemental data configuration
* @param parameters CQL parameters
* @param productLine product line
* @param reporter reporter ID
@@ -226,10 +206,7 @@ MeasureDefAndR4ParametersWithMeasureReports evaluateWithDefs(
@Nullable ZonedDateTime periodEnd,
String reportType,
String subject,
- Endpoint contentEndpoint,
- Endpoint terminologyEndpoint,
- Endpoint dataEndpoint,
- Bundle additionalData,
+ MeasureEnvironment environment,
Parameters parameters,
String productLine,
String reporter) {
@@ -241,10 +218,7 @@ MeasureDefAndR4ParametersWithMeasureReports evaluateWithDefs(
periodEnd,
reportType,
subject,
- contentEndpoint,
- terminologyEndpoint,
- dataEndpoint,
- additionalData,
+ environment,
parameters,
productLine,
reporter,
@@ -258,10 +232,7 @@ private List> evaluateToListOfList(
@Nullable ZonedDateTime periodEnd,
String reportType,
String subject,
- Endpoint contentEndpoint,
- Endpoint terminologyEndpoint,
- Endpoint dataEndpoint,
- Bundle additionalData,
+ MeasureEnvironment environment,
Parameters parameters,
String productLine,
String reporter,
@@ -269,19 +240,13 @@ private List> evaluateToListOfList(
measurePeriodValidator.validatePeriodStartAndEnd(periodStart, periodEnd);
- final R4MeasureProcessor r4ProcessorToUse;
- final R4MeasureServiceUtils r4MeasureServiceUtilsToUse;
- if (dataEndpoint != null && contentEndpoint != null && terminologyEndpoint != null) {
- var repositoryToUse =
- Repositories.proxy(repository, true, dataEndpoint, contentEndpoint, terminologyEndpoint);
-
- r4ProcessorToUse = new R4MeasureProcessor(repositoryToUse, this.measureEvaluationOptions);
-
- r4MeasureServiceUtilsToUse = new R4MeasureServiceUtils(repositoryToUse);
- } else {
- r4ProcessorToUse = r4MeasureProcessorStandardRepository;
- r4MeasureServiceUtilsToUse = r4MeasureServiceUtilsStandardRepository;
- }
+ var effectiveRepo = resolveRepository(environment);
+ final R4MeasureProcessor r4ProcessorToUse = effectiveRepo == repository
+ ? r4MeasureProcessorStandardRepository
+ : new R4MeasureProcessor(effectiveRepo, this.measureEvaluationOptions);
+ final R4MeasureServiceUtils r4MeasureServiceUtilsToUse = effectiveRepo == repository
+ ? r4MeasureServiceUtilsStandardRepository
+ : new R4MeasureServiceUtils(effectiveRepo);
if (measureEvaluationOptions.isEnsureSearchParameters()) {
r4MeasureServiceUtilsToUse.ensureSupplementalDataElementSearchParameter();
@@ -312,14 +277,16 @@ private List> evaluateToListOfList(
// another flex point between single and multi measures
var subjects =
switch (singleOrMultiple) {
- case SINGLE -> getSubjectsForEvaluateSingle(subjectToUse, repository, additionalData);
+ case SINGLE ->
+ getSubjectsForEvaluateSingle(
+ subjectToUse, r4ProcessorToUse.getRepository(), environment.additionalData());
case MULTIPLE -> getSubjects(subjectProvider, subjectToUse);
};
var context = Engines.forRepository(
r4ProcessorToUse.getRepository(),
this.measureEvaluationOptions.getEvaluationSettings(),
- additionalData);
+ environment.additionalData());
final CompositeEvaluationResultsPerMeasure compositeEvaluationResultsPerMeasure =
r4ProcessorToUse.evaluateMultiMeasuresWithCqlEngine(
@@ -590,7 +557,7 @@ protected Bundle.BundleEntryComponent getBundleEntry(String serverBase, Resource
@Nonnull
private List getSubjectsForEvaluateSingle(
- String subjectId, IRepository proxyRepoForMeasureProcessor, Bundle additionalData) {
+ String subjectId, IRepository proxyRepoForMeasureProcessor, IBaseBundle additionalData) {
final IRepository repoToUseForSubjectProvider;
if (additionalData != null) {
repoToUseForSubjectProvider = new FederatedRepository(
@@ -605,4 +572,18 @@ private List getSubjectsForEvaluateSingle(
Optional.ofNullable(subjectId).map(List::of).orElse(List.of()))
.toList();
}
+
+ private IRepository resolveRepository(MeasureEnvironment environment) {
+ if (environment.dataEndpoint() != null
+ && environment.contentEndpoint() != null
+ && environment.terminologyEndpoint() != null) {
+ return Repositories.proxy(
+ repository,
+ true,
+ environment.dataEndpoint(),
+ environment.contentEndpoint(),
+ environment.terminologyEndpoint());
+ }
+ return repository;
+ }
}
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 f8db8d3025..c5fdd5ffcc 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
@@ -25,6 +25,7 @@
import org.opencds.cqf.fhir.cql.engine.retrieve.RetrieveSettings.TERMINOLOGY_FILTER_MODE;
import org.opencds.cqf.fhir.cql.engine.terminology.TerminologySettings.VALUESET_EXPANSION_MODE;
import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions;
+import org.opencds.cqf.fhir.cr.measure.common.MeasureEnvironment;
import org.opencds.cqf.fhir.cr.measure.common.MeasurePeriodValidator;
import org.opencds.cqf.fhir.cr.measure.common.MeasureReference;
import org.opencds.cqf.fhir.cr.measure.r4.selected.def.SelectedMeasureDef;
@@ -234,10 +235,7 @@ public When evaluate() {
reportType,
subject,
null,
- null,
- null,
- null,
- additionalData,
+ new MeasureEnvironment(null, null, null, additionalData),
parameters,
productLine,
practitioner);
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 181c22bef4..827fdd902a 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
@@ -46,6 +46,7 @@
import org.opencds.cqf.fhir.cql.engine.retrieve.RetrieveSettings.TERMINOLOGY_FILTER_MODE;
import org.opencds.cqf.fhir.cql.engine.terminology.TerminologySettings.VALUESET_EXPANSION_MODE;
import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions;
+import org.opencds.cqf.fhir.cr.measure.common.MeasureEnvironment;
import org.opencds.cqf.fhir.cr.measure.common.MeasurePeriodValidator;
import org.opencds.cqf.fhir.cr.measure.common.MeasureReference;
import org.opencds.cqf.fhir.cr.measure.constant.MeasureConstants;
@@ -256,10 +257,7 @@ public MultiMeasure.When evaluate() {
periodEnd,
reportType,
subject,
- null,
- null,
- null,
- additionalData,
+ new MeasureEnvironment(null, null, null, additionalData),
parameters,
productLine,
reporter);
From 622bafba8260ab1de689d377963cfd858484b044 Mon Sep 17 00:00:00 2001
From: Jonathan Percival
Date: Wed, 25 Mar 2026 09:00:33 -0600
Subject: [PATCH 05/10] Pull up additionalData federation as explicit pipeline
step
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
federateWithAdditionalData(IRepository, MeasureEnvironment) is now a
named private method alongside resolveRepository(MeasureEnvironment),
making the two-step environment setup explicit in evaluateToListOfList:
var effectiveRepo = resolveRepository(environment); // endpoint proxy
var subjectRepo = federateWithAdditionalData(effectiveRepo, environment); // bundle overlay
Previously the FederatedRepository construction was buried inside
getSubjectsForEvaluateSingle, which also hardcoded this.repository as
the federation base instead of effectiveRepo. That meant additional-data
subjects were resolved against the base repository even when an endpoint
proxy was active.
getSubjectsForEvaluateSingle now takes the already-prepared subjectRepo
directly — one job, one level of abstraction.
---
.../cr/measure/r4/R4MultiMeasureService.java | 32 +++++++++----------
1 file changed, 16 insertions(+), 16 deletions(-)
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 ba159b17e3..0394905f8f 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
@@ -17,7 +17,6 @@
import java.util.UUID;
import java.util.function.Consumer;
import org.apache.commons.lang3.StringUtils;
-import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Bundle.BundleType;
@@ -241,6 +240,7 @@ private List> evaluateToListOfList(
measurePeriodValidator.validatePeriodStartAndEnd(periodStart, periodEnd);
var effectiveRepo = resolveRepository(environment);
+ var subjectRepo = federateWithAdditionalData(effectiveRepo, environment);
final R4MeasureProcessor r4ProcessorToUse = effectiveRepo == repository
? r4MeasureProcessorStandardRepository
: new R4MeasureProcessor(effectiveRepo, this.measureEvaluationOptions);
@@ -274,12 +274,9 @@ private List> evaluateToListOfList(
var evalType = r4MeasureServiceUtilsToUse.getMeasureEvalType(reportType, subjectToUse);
- // another flex point between single and multi measures
var subjects =
switch (singleOrMultiple) {
- case SINGLE ->
- getSubjectsForEvaluateSingle(
- subjectToUse, r4ProcessorToUse.getRepository(), environment.additionalData());
+ case SINGLE -> getSubjectsForEvaluateSingle(subjectToUse, subjectRepo);
case MULTIPLE -> getSubjects(subjectProvider, subjectToUse);
};
@@ -556,23 +553,26 @@ protected Bundle.BundleEntryComponent getBundleEntry(String serverBase, Resource
}
@Nonnull
- private List getSubjectsForEvaluateSingle(
- String subjectId, IRepository proxyRepoForMeasureProcessor, IBaseBundle additionalData) {
- final IRepository repoToUseForSubjectProvider;
- if (additionalData != null) {
- repoToUseForSubjectProvider = new FederatedRepository(
- this.repository, new InMemoryFhirRepository(this.repository.fhirContext(), additionalData));
- } else {
- repoToUseForSubjectProvider = proxyRepoForMeasureProcessor;
- }
-
+ private List getSubjectsForEvaluateSingle(String subjectId, IRepository subjectRepo) {
return subjectProvider
.getSubjects(
- repoToUseForSubjectProvider,
+ subjectRepo,
Optional.ofNullable(subjectId).map(List::of).orElse(List.of()))
.toList();
}
+ /**
+ * Federates the base repository with additional data so subjects in the bundle
+ * are discoverable during subject resolution.
+ */
+ private IRepository federateWithAdditionalData(IRepository base, MeasureEnvironment environment) {
+ if (environment.additionalData() != null) {
+ return new FederatedRepository(
+ base, new InMemoryFhirRepository(base.fhirContext(), environment.additionalData()));
+ }
+ return base;
+ }
+
private IRepository resolveRepository(MeasureEnvironment environment) {
if (environment.dataEndpoint() != null
&& environment.contentEndpoint() != null
From 042b726e75f3300695ef3280ad6bcd63ce11ed27 Mon Sep 17 00:00:00 2001
From: Jonathan Percival
Date: Wed, 25 Mar 2026 09:04:04 -0600
Subject: [PATCH 06/10] resolveRepository is the single environment setup step
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
All environment config is resolved upfront in resolveRepository():
- endpoint proxy (all three endpoints → Repositories.proxy)
- additional data federation (bundle → FederatedRepository overlay)
The CQL engine receives resolvedRepo and null for additionalData —
the bundle is already accessible through the federated repository.
Passing it twice was wrong: the repo is the resolved environment.
---
.../cr/measure/r4/R4MultiMeasureService.java | 41 ++++++++-----------
1 file changed, 17 insertions(+), 24 deletions(-)
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 0394905f8f..61b16f7ec4 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
@@ -239,14 +239,13 @@ private List> evaluateToListOfList(
measurePeriodValidator.validatePeriodStartAndEnd(periodStart, periodEnd);
- var effectiveRepo = resolveRepository(environment);
- var subjectRepo = federateWithAdditionalData(effectiveRepo, environment);
- final R4MeasureProcessor r4ProcessorToUse = effectiveRepo == repository
+ var resolvedRepo = resolveRepository(environment);
+ final R4MeasureProcessor r4ProcessorToUse = resolvedRepo == repository
? r4MeasureProcessorStandardRepository
- : new R4MeasureProcessor(effectiveRepo, this.measureEvaluationOptions);
- final R4MeasureServiceUtils r4MeasureServiceUtilsToUse = effectiveRepo == repository
+ : new R4MeasureProcessor(resolvedRepo, this.measureEvaluationOptions);
+ final R4MeasureServiceUtils r4MeasureServiceUtilsToUse = resolvedRepo == repository
? r4MeasureServiceUtilsStandardRepository
- : new R4MeasureServiceUtils(effectiveRepo);
+ : new R4MeasureServiceUtils(resolvedRepo);
if (measureEvaluationOptions.isEnsureSearchParameters()) {
r4MeasureServiceUtilsToUse.ensureSupplementalDataElementSearchParameter();
@@ -276,14 +275,11 @@ private List> evaluateToListOfList(
var subjects =
switch (singleOrMultiple) {
- case SINGLE -> getSubjectsForEvaluateSingle(subjectToUse, subjectRepo);
+ case SINGLE -> getSubjectsForEvaluateSingle(subjectToUse, resolvedRepo);
case MULTIPLE -> getSubjects(subjectProvider, subjectToUse);
};
- var context = Engines.forRepository(
- r4ProcessorToUse.getRepository(),
- this.measureEvaluationOptions.getEvaluationSettings(),
- environment.additionalData());
+ var context = Engines.forRepository(resolvedRepo, this.measureEvaluationOptions.getEvaluationSettings(), null);
final CompositeEvaluationResultsPerMeasure compositeEvaluationResultsPerMeasure =
r4ProcessorToUse.evaluateMultiMeasuresWithCqlEngine(
@@ -562,28 +558,25 @@ private List getSubjectsForEvaluateSingle(String subjectId, IRepository
}
/**
- * Federates the base repository with additional data so subjects in the bundle
- * are discoverable during subject resolution.
+ * Fully resolves the repository from the environment: applies the three-endpoint proxy if all
+ * endpoints are configured, then overlays any additional data bundle via federation.
*/
- private IRepository federateWithAdditionalData(IRepository base, MeasureEnvironment environment) {
- if (environment.additionalData() != null) {
- return new FederatedRepository(
- base, new InMemoryFhirRepository(base.fhirContext(), environment.additionalData()));
- }
- return base;
- }
-
private IRepository resolveRepository(MeasureEnvironment environment) {
+ IRepository repo = repository;
if (environment.dataEndpoint() != null
&& environment.contentEndpoint() != null
&& environment.terminologyEndpoint() != null) {
- return Repositories.proxy(
- repository,
+ repo = Repositories.proxy(
+ repo,
true,
environment.dataEndpoint(),
environment.contentEndpoint(),
environment.terminologyEndpoint());
}
- return repository;
+ if (environment.additionalData() != null) {
+ repo = new FederatedRepository(
+ repo, new InMemoryFhirRepository(repo.fhirContext(), environment.additionalData()));
+ }
+ return repo;
}
}
From 7375997234440f7a77ad8027c7646dda24a5701e Mon Sep 17 00:00:00 2001
From: Jonathan Percival
Date: Wed, 25 Mar 2026 09:07:36 -0600
Subject: [PATCH 07/10] Remove repo branching: resolvedRepo is the only repo
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Once resolveRepository() runs, there is one repo. No switching.
The pre-cached standard processor and utils fields were a remnant
of the old conditional — delete them and construct directly from
resolvedRepo every time.
---
.../fhir/cr/measure/r4/R4MultiMeasureService.java | 12 ++----------
1 file changed, 2 insertions(+), 10 deletions(-)
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 61b16f7ec4..f419d50a04 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
@@ -54,8 +54,6 @@ public class R4MultiMeasureService implements R4MeasureEvaluatorSingle, R4Measur
private final MeasurePeriodValidator measurePeriodValidator;
private final String serverBase;
private final R4RepositorySubjectProvider subjectProvider;
- private final R4MeasureProcessor r4MeasureProcessorStandardRepository;
- private final R4MeasureServiceUtils r4MeasureServiceUtilsStandardRepository;
private enum SingleOrMultiple {
SINGLE,
@@ -72,8 +70,6 @@ public R4MultiMeasureService(
this.measurePeriodValidator = measurePeriodValidator;
this.serverBase = serverBase;
this.subjectProvider = new R4RepositorySubjectProvider(measureEvaluationOptions.getSubjectProviderOptions());
- this.r4MeasureProcessorStandardRepository = new R4MeasureProcessor(repository, this.measureEvaluationOptions);
- this.r4MeasureServiceUtilsStandardRepository = new R4MeasureServiceUtils(repository);
}
// We should eliminate this if/when we eliminate the Measure test class
@@ -240,12 +236,8 @@ private List> evaluateToListOfList(
measurePeriodValidator.validatePeriodStartAndEnd(periodStart, periodEnd);
var resolvedRepo = resolveRepository(environment);
- final R4MeasureProcessor r4ProcessorToUse = resolvedRepo == repository
- ? r4MeasureProcessorStandardRepository
- : new R4MeasureProcessor(resolvedRepo, this.measureEvaluationOptions);
- final R4MeasureServiceUtils r4MeasureServiceUtilsToUse = resolvedRepo == repository
- ? r4MeasureServiceUtilsStandardRepository
- : new R4MeasureServiceUtils(resolvedRepo);
+ var r4ProcessorToUse = new R4MeasureProcessor(resolvedRepo, this.measureEvaluationOptions);
+ var r4MeasureServiceUtilsToUse = new R4MeasureServiceUtils(resolvedRepo);
if (measureEvaluationOptions.isEnsureSearchParameters()) {
r4MeasureServiceUtilsToUse.ensureSupplementalDataElementSearchParameter();
From 38d0e49428732d725c35f4d98eea9e1d2877ef18 Mon Sep 17 00:00:00 2001
From: Jonathan Percival
Date: Wed, 25 Mar 2026 09:19:56 -0600
Subject: [PATCH 08/10] Environment resolved at construction:
MeasureEnvironment gone from eval signatures
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The resolved IRepository is now a constructor parameter, not a method
parameter threaded through the call stack.
MeasureEnvironment.resolve(IRepository) composes the final repository:
endpoint proxy (when all three endpoints present) then additional-data
federation. This is the single place that knows how to build the repo.
Operation providers (HAPI transport boundary) resolve environment
before constructing the service via the factory:
factory.create(requestDetails, environment)
→ environment.resolve(repositoryFactory.create(requestDetails))
→ new R4MultiMeasureService(resolvedRepo, ...)
R4MeasureEvaluatorSingle, R4MeasureEvaluatorMultiple, and
Dstu3MeasureEvaluatorSingle no longer carry MeasureEnvironment.
R4MultiMeasureService.resolveRepository() is deleted. The service
receives a repo that is already fully configured; it just uses it.
---
.../fhir/benchmark/measure/r4/Measure.java | 2 -
.../cr/hapi/config/dstu3/CrDstu3Config.java | 3 +-
.../fhir/cr/hapi/config/r4/CrR4Config.java | 11 +++--
.../cr/hapi/dstu3/IMeasureServiceFactory.java | 3 +-
.../measure/MeasureOperationsProvider.java | 3 +-
.../r4/R4MeasureEvaluatorMultipleFactory.java | 3 +-
.../r4/R4MeasureEvaluatorSingleFactory.java | 3 +-
.../r4/measure/MeasureOperationsProvider.java | 6 +--
.../cr/measure/common/MeasureEnvironment.java | 25 ++++++++++
.../dstu3/Dstu3MeasureEvaluatorSingle.java | 2 -
.../cr/measure/dstu3/Dstu3MeasureService.java | 10 +---
.../measure/r4/R4CareGapsBundleBuilder.java | 2 -
.../r4/R4MeasureEvaluatorMultiple.java | 2 -
.../measure/r4/R4MeasureEvaluatorSingle.java | 2 -
.../cr/measure/r4/R4MultiMeasureService.java | 46 ++++---------------
.../cqf/fhir/cr/measure/r4/Measure.java | 3 +-
.../cqf/fhir/cr/measure/r4/MultiMeasure.java | 2 +-
17 files changed, 55 insertions(+), 73 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 a04f12eaf2..e611583443 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
@@ -14,7 +14,6 @@
import org.opencds.cqf.fhir.cql.engine.retrieve.RetrieveSettings.TERMINOLOGY_FILTER_MODE;
import org.opencds.cqf.fhir.cql.engine.terminology.TerminologySettings.VALUESET_EXPANSION_MODE;
import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions;
-import org.opencds.cqf.fhir.cr.measure.common.MeasureEnvironment;
import org.opencds.cqf.fhir.cr.measure.common.MeasurePeriodValidator;
import org.opencds.cqf.fhir.cr.measure.common.MeasureReference;
import org.opencds.cqf.fhir.cr.measure.r4.R4MultiMeasureService;
@@ -148,7 +147,6 @@ public When evaluate() {
reportType,
subject,
null,
- new MeasureEnvironment(null, null, null, additionalData),
null,
null,
null);
diff --git a/cqf-fhir-cr-hapi/src/main/java/org/opencds/cqf/fhir/cr/hapi/config/dstu3/CrDstu3Config.java b/cqf-fhir-cr-hapi/src/main/java/org/opencds/cqf/fhir/cr/hapi/config/dstu3/CrDstu3Config.java
index 2363be9dcd..b0f736837b 100644
--- a/cqf-fhir-cr-hapi/src/main/java/org/opencds/cqf/fhir/cr/hapi/config/dstu3/CrDstu3Config.java
+++ b/cqf-fhir-cr-hapi/src/main/java/org/opencds/cqf/fhir/cr/hapi/config/dstu3/CrDstu3Config.java
@@ -26,7 +26,8 @@ public class CrDstu3Config {
@Bean
IMeasureServiceFactory dstu3MeasureServiceFactory(
IRepositoryFactory repositoryFactory, MeasureEvaluationOptions evaluationOptions) {
- return rd -> new Dstu3MeasureService(repositoryFactory.create(rd), evaluationOptions);
+ return (requestDetails, environment) -> new Dstu3MeasureService(
+ environment.resolve(repositoryFactory.create(requestDetails)), evaluationOptions);
}
@Bean
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 0695e47b6f..2cc201c943 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
@@ -75,8 +75,8 @@ R4MeasureEvaluatorSingleFactory r4MeasureServiceFactory(
MeasureEvaluationOptions evaluationOptions,
MeasurePeriodValidator measurePeriodValidator) {
// We are effectively returning an R4MeasureEvaluatorSingle her
- return requestDetails -> new R4MultiMeasureService(
- repositoryFactory.create(requestDetails),
+ return (requestDetails, environment) -> new R4MultiMeasureService(
+ environment.resolve(repositoryFactory.create(requestDetails)),
evaluationOptions,
requestDetails.getFhirServerBase(),
measurePeriodValidator);
@@ -87,8 +87,11 @@ R4MeasureEvaluatorMultipleFactory r4MeasureEvaluatorMultipleFactory(
IRepositoryFactory repositoryFactory,
MeasureEvaluationOptions evaluationOptions,
MeasurePeriodValidator measurePeriodValidator) {
- return rd -> new R4MultiMeasureService(
- repositoryFactory.create(rd), evaluationOptions, rd.getFhirServerBase(), measurePeriodValidator);
+ return (requestDetails, environment) -> new R4MultiMeasureService(
+ environment.resolve(repositoryFactory.create(requestDetails)),
+ evaluationOptions,
+ requestDetails.getFhirServerBase(),
+ measurePeriodValidator);
}
@Bean
diff --git a/cqf-fhir-cr-hapi/src/main/java/org/opencds/cqf/fhir/cr/hapi/dstu3/IMeasureServiceFactory.java b/cqf-fhir-cr-hapi/src/main/java/org/opencds/cqf/fhir/cr/hapi/dstu3/IMeasureServiceFactory.java
index 538a07ea2e..f754fe4160 100644
--- a/cqf-fhir-cr-hapi/src/main/java/org/opencds/cqf/fhir/cr/hapi/dstu3/IMeasureServiceFactory.java
+++ b/cqf-fhir-cr-hapi/src/main/java/org/opencds/cqf/fhir/cr/hapi/dstu3/IMeasureServiceFactory.java
@@ -1,9 +1,10 @@
package org.opencds.cqf.fhir.cr.hapi.dstu3;
import ca.uhn.fhir.rest.api.server.RequestDetails;
+import org.opencds.cqf.fhir.cr.measure.common.MeasureEnvironment;
import org.opencds.cqf.fhir.cr.measure.dstu3.Dstu3MeasureService;
@FunctionalInterface
public interface IMeasureServiceFactory {
- Dstu3MeasureService create(RequestDetails requestDetails);
+ Dstu3MeasureService create(RequestDetails requestDetails, MeasureEnvironment environment);
}
diff --git a/cqf-fhir-cr-hapi/src/main/java/org/opencds/cqf/fhir/cr/hapi/dstu3/measure/MeasureOperationsProvider.java b/cqf-fhir-cr-hapi/src/main/java/org/opencds/cqf/fhir/cr/hapi/dstu3/measure/MeasureOperationsProvider.java
index 7869834063..6fdc6c4aa2 100644
--- a/cqf-fhir-cr-hapi/src/main/java/org/opencds/cqf/fhir/cr/hapi/dstu3/measure/MeasureOperationsProvider.java
+++ b/cqf-fhir-cr-hapi/src/main/java/org/opencds/cqf/fhir/cr/hapi/dstu3/measure/MeasureOperationsProvider.java
@@ -75,7 +75,7 @@ public MeasureReport evaluateMeasure(
var terminologyEndpointParam = (Endpoint) getEndpoint(fhirVersion, terminologyEndpoint);
var environment = new MeasureEnvironment(null, terminologyEndpointParam, null, additionalData);
return dstu3MeasureProcessorFactory
- .create(requestDetails)
+ .create(requestDetails, environment)
.evaluateMeasure(
id,
periodStart,
@@ -85,7 +85,6 @@ public MeasureReport evaluateMeasure(
practitioner,
lastReceivedOn,
productLine,
- environment,
parameters);
}
}
diff --git a/cqf-fhir-cr-hapi/src/main/java/org/opencds/cqf/fhir/cr/hapi/r4/R4MeasureEvaluatorMultipleFactory.java b/cqf-fhir-cr-hapi/src/main/java/org/opencds/cqf/fhir/cr/hapi/r4/R4MeasureEvaluatorMultipleFactory.java
index 5e4434dc6b..77f32e4cc9 100644
--- a/cqf-fhir-cr-hapi/src/main/java/org/opencds/cqf/fhir/cr/hapi/r4/R4MeasureEvaluatorMultipleFactory.java
+++ b/cqf-fhir-cr-hapi/src/main/java/org/opencds/cqf/fhir/cr/hapi/r4/R4MeasureEvaluatorMultipleFactory.java
@@ -1,9 +1,10 @@
package org.opencds.cqf.fhir.cr.hapi.r4;
import ca.uhn.fhir.rest.api.server.RequestDetails;
+import org.opencds.cqf.fhir.cr.measure.common.MeasureEnvironment;
import org.opencds.cqf.fhir.cr.measure.r4.R4MeasureEvaluatorMultiple;
@FunctionalInterface
public interface R4MeasureEvaluatorMultipleFactory {
- R4MeasureEvaluatorMultiple create(RequestDetails requestDetails);
+ R4MeasureEvaluatorMultiple create(RequestDetails requestDetails, MeasureEnvironment environment);
}
diff --git a/cqf-fhir-cr-hapi/src/main/java/org/opencds/cqf/fhir/cr/hapi/r4/R4MeasureEvaluatorSingleFactory.java b/cqf-fhir-cr-hapi/src/main/java/org/opencds/cqf/fhir/cr/hapi/r4/R4MeasureEvaluatorSingleFactory.java
index a2279dc7dd..d213110b58 100644
--- a/cqf-fhir-cr-hapi/src/main/java/org/opencds/cqf/fhir/cr/hapi/r4/R4MeasureEvaluatorSingleFactory.java
+++ b/cqf-fhir-cr-hapi/src/main/java/org/opencds/cqf/fhir/cr/hapi/r4/R4MeasureEvaluatorSingleFactory.java
@@ -1,9 +1,10 @@
package org.opencds.cqf.fhir.cr.hapi.r4;
import ca.uhn.fhir.rest.api.server.RequestDetails;
+import org.opencds.cqf.fhir.cr.measure.common.MeasureEnvironment;
import org.opencds.cqf.fhir.cr.measure.r4.R4MeasureEvaluatorSingle;
@FunctionalInterface
public interface R4MeasureEvaluatorSingleFactory {
- R4MeasureEvaluatorSingle create(RequestDetails requestDetails);
+ R4MeasureEvaluatorSingle create(RequestDetails requestDetails, MeasureEnvironment environment);
}
diff --git a/cqf-fhir-cr-hapi/src/main/java/org/opencds/cqf/fhir/cr/hapi/r4/measure/MeasureOperationsProvider.java b/cqf-fhir-cr-hapi/src/main/java/org/opencds/cqf/fhir/cr/hapi/r4/measure/MeasureOperationsProvider.java
index 2570d733d4..3e1012f889 100644
--- a/cqf-fhir-cr-hapi/src/main/java/org/opencds/cqf/fhir/cr/hapi/r4/measure/MeasureOperationsProvider.java
+++ b/cqf-fhir-cr-hapi/src/main/java/org/opencds/cqf/fhir/cr/hapi/r4/measure/MeasureOperationsProvider.java
@@ -91,7 +91,7 @@ public MeasureReport evaluateMeasure(
var environment = new MeasureEnvironment(
contentEndpointParam, terminologyEndpointParam, dataEndpointParam, additionalData);
return r4MeasureServiceFactory
- .create(requestDetails)
+ .create(requestDetails, environment)
.evaluate(
new MeasureReference.ById(id),
stringTimePeriodHandler.getStartZonedDateTime(periodStart, requestDetails),
@@ -99,7 +99,6 @@ public MeasureReport evaluateMeasure(
reportType,
subject,
lastReceivedOn,
- environment,
parameters,
productLine,
practitioner);
@@ -160,14 +159,13 @@ public Parameters evaluate(
var environment = new MeasureEnvironment(
contentEndpointParam, terminologyEndpointParam, dataEndpointParam, additionalData);
return r4MultiMeasureServiceFactory
- .create(requestDetails)
+ .create(requestDetails, environment)
.evaluate(
measureRefs,
stringTimePeriodHandler.getStartZonedDateTime(periodStart, requestDetails),
stringTimePeriodHandler.getEndZonedDateTime(periodEnd, requestDetails),
reportType,
subject,
- environment,
parameters,
productLine,
reporter);
diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureEnvironment.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureEnvironment.java
index c746f0581c..471270ba30 100644
--- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureEnvironment.java
+++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureEnvironment.java
@@ -1,8 +1,12 @@
package org.opencds.cqf.fhir.cr.measure.common;
+import ca.uhn.fhir.repository.IRepository;
import jakarta.annotation.Nullable;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.opencds.cqf.fhir.utility.repository.FederatedRepository;
+import org.opencds.cqf.fhir.utility.repository.InMemoryFhirRepository;
+import org.opencds.cqf.fhir.utility.repository.Repositories;
/**
* Version-agnostic environment configuration for measure evaluation.
@@ -30,4 +34,25 @@ public record MeasureEnvironment(
/** Empty environment — no endpoints, no additional data. */
public static final MeasureEnvironment EMPTY = new MeasureEnvironment(null, null, null, null);
+
+ /**
+ * Resolves this environment against a base repository.
+ *
+ * If all three endpoints are present, wraps {@code base} in a proxy repository.
+ * If {@code additionalData} is present, federates the result with an in-memory repository
+ * seeded from that bundle.
+ *
+ * @param base the base repository to build on top of
+ * @return the resolved repository, possibly wrapped
+ */
+ public IRepository resolve(IRepository base) {
+ IRepository repo = base;
+ if (dataEndpoint() != null && contentEndpoint() != null && terminologyEndpoint() != null) {
+ repo = Repositories.proxy(repo, true, dataEndpoint(), contentEndpoint(), terminologyEndpoint());
+ }
+ if (additionalData() != null) {
+ repo = new FederatedRepository(repo, new InMemoryFhirRepository(repo.fhirContext(), additionalData()));
+ }
+ return repo;
+ }
}
diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureEvaluatorSingle.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureEvaluatorSingle.java
index e56e76ddc7..9900210ec7 100644
--- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureEvaluatorSingle.java
+++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureEvaluatorSingle.java
@@ -3,7 +3,6 @@
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.MeasureReport;
import org.hl7.fhir.dstu3.model.Parameters;
-import org.opencds.cqf.fhir.cr.measure.common.MeasureEnvironment;
/**
* Interface for {@link Dstu3MeasureService} and any other concrete classes that implement the same
@@ -20,6 +19,5 @@ MeasureReport evaluateMeasure(
String practitioner,
String lastReceivedOn,
String productLine,
- MeasureEnvironment environment,
Parameters parameters);
}
diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureService.java
index 946b65bbe0..2b23493a41 100644
--- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureService.java
+++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3MeasureService.java
@@ -24,7 +24,6 @@
import org.hl7.fhir.dstu3.model.SearchParameter;
import org.hl7.fhir.dstu3.model.StringType;
import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions;
-import org.opencds.cqf.fhir.cr.measure.common.MeasureEnvironment;
public class Dstu3MeasureService implements Dstu3MeasureEvaluatorSingle {
private final IRepository repository;
@@ -101,7 +100,6 @@ public MeasureReport evaluateMeasure(
String practitioner,
String lastReceivedOn,
String productLine,
- MeasureEnvironment environment,
Parameters parameters) {
ensureSupplementalDataElementSearchParameter();
@@ -109,13 +107,7 @@ public MeasureReport evaluateMeasure(
var dstu3MeasureProcessor = new Dstu3MeasureProcessor(repository, measureEvaluationOptions);
MeasureReport report = dstu3MeasureProcessor.evaluateMeasure(
- id,
- periodStart,
- periodEnd,
- reportType,
- Collections.singletonList(subject),
- environment.additionalData(),
- parameters);
+ id, periodStart, periodEnd, reportType, Collections.singletonList(subject), null, parameters);
if (productLine != null) {
Extension ext = new Extension();
diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CareGapsBundleBuilder.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CareGapsBundleBuilder.java
index c2153cbea8..8398a969ff 100644
--- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CareGapsBundleBuilder.java
+++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CareGapsBundleBuilder.java
@@ -45,7 +45,6 @@
import org.hl7.fhir.r4.model.StringType;
import org.opencds.cqf.fhir.cr.measure.CareGapsProperties;
import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions;
-import org.opencds.cqf.fhir.cr.measure.common.MeasureEnvironment;
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.MeasureReference;
@@ -112,7 +111,6 @@ public List makePatientBundles(
r4CareGapsParameters.getPeriodEnd(),
MeasureEvalType.SUBJECT.toCode(),
subject,
- MeasureEnvironment.EMPTY,
null,
null,
reporter);
diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureEvaluatorMultiple.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureEvaluatorMultiple.java
index aece913097..d78ebab926 100644
--- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureEvaluatorMultiple.java
+++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureEvaluatorMultiple.java
@@ -4,7 +4,6 @@
import java.time.ZonedDateTime;
import java.util.List;
import org.hl7.fhir.r4.model.Parameters;
-import org.opencds.cqf.fhir.cr.measure.common.MeasureEnvironment;
import org.opencds.cqf.fhir.cr.measure.common.MeasureReference;
/**
@@ -19,7 +18,6 @@ Parameters evaluate(
@Nullable ZonedDateTime periodEnd,
String reportType,
String subject, // practitioner passed in here
- MeasureEnvironment environment,
Parameters parameters,
String productLine,
String reporter);
diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureEvaluatorSingle.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureEvaluatorSingle.java
index 77cc2dbda5..fa8626ffa8 100644
--- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureEvaluatorSingle.java
+++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureEvaluatorSingle.java
@@ -4,7 +4,6 @@
import java.time.ZonedDateTime;
import org.hl7.fhir.r4.model.MeasureReport;
import org.hl7.fhir.r4.model.Parameters;
-import org.opencds.cqf.fhir.cr.measure.common.MeasureEnvironment;
import org.opencds.cqf.fhir.cr.measure.common.MeasureReference;
/**
@@ -19,7 +18,6 @@ MeasureReport evaluate(
String reportType,
String subjectId,
String lastReceivedOn,
- MeasureEnvironment environment,
Parameters parameters,
String productLine,
String 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 f419d50a04..2636221dfc 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,16 +29,12 @@
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.MeasureDef;
-import org.opencds.cqf.fhir.cr.measure.common.MeasureEnvironment;
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.MeasureReference;
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.repository.FederatedRepository;
-import org.opencds.cqf.fhir.utility.repository.InMemoryFhirRepository;
-import org.opencds.cqf.fhir.utility.repository.Repositories;
/**
* Alternate MeasureService call to Process MeasureEvaluation for the selected population of subjects against n-number
@@ -85,7 +81,6 @@ public MeasureReport evaluate(
String reportType,
String subjectId,
String lastReceivedOn,
- MeasureEnvironment environment,
Parameters parameters,
String productLine,
String practitioner) {
@@ -97,7 +92,7 @@ public MeasureReport evaluate(
reportType,
subjectId,
lastReceivedOn,
- environment,
+ repository,
parameters,
productLine,
null) // reporter is null in the single measure case
@@ -112,7 +107,7 @@ MeasureDefAndR4MeasureReport evaluateSingleMeasureCaptureDef(
String reportType,
String subjectId,
String lastReceivedOn,
- MeasureEnvironment environment,
+ IRepository resolvedRepo,
Parameters parameters,
String productLine,
String practitioner) {
@@ -124,7 +119,7 @@ MeasureDefAndR4MeasureReport evaluateSingleMeasureCaptureDef(
periodEnd,
reportType,
subjectId,
- environment,
+ resolvedRepo,
parameters,
productLine,
null,
@@ -154,7 +149,6 @@ public Parameters evaluate(
@Nullable ZonedDateTime periodEnd,
String reportType,
String subject, // practitioner passed in here
- MeasureEnvironment environment,
Parameters parameters,
String productLine,
String reporter) {
@@ -165,7 +159,7 @@ public Parameters evaluate(
periodEnd,
reportType,
subject,
- environment,
+ repository,
parameters,
productLine,
reporter)
@@ -188,7 +182,7 @@ public Parameters evaluate(
* @param periodEnd end date of Measurement Period
* @param reportType type of report
* @param subject the subject ID (or practitioner)
- * @param environment endpoint and supplemental data configuration
+ * @param resolvedRepo fully configured repository (endpoints proxied, data federated)
* @param parameters CQL parameters
* @param productLine product line
* @param reporter reporter ID
@@ -201,7 +195,7 @@ MeasureDefAndR4ParametersWithMeasureReports evaluateWithDefs(
@Nullable ZonedDateTime periodEnd,
String reportType,
String subject,
- MeasureEnvironment environment,
+ IRepository resolvedRepo,
Parameters parameters,
String productLine,
String reporter) {
@@ -213,7 +207,7 @@ MeasureDefAndR4ParametersWithMeasureReports evaluateWithDefs(
periodEnd,
reportType,
subject,
- environment,
+ resolvedRepo,
parameters,
productLine,
reporter,
@@ -227,7 +221,7 @@ private List> evaluateToListOfList(
@Nullable ZonedDateTime periodEnd,
String reportType,
String subject,
- MeasureEnvironment environment,
+ IRepository resolvedRepo,
Parameters parameters,
String productLine,
String reporter,
@@ -235,7 +229,6 @@ private List> evaluateToListOfList(
measurePeriodValidator.validatePeriodStartAndEnd(periodStart, periodEnd);
- var resolvedRepo = resolveRepository(environment);
var r4ProcessorToUse = new R4MeasureProcessor(resolvedRepo, this.measureEvaluationOptions);
var r4MeasureServiceUtilsToUse = new R4MeasureServiceUtils(resolvedRepo);
@@ -548,27 +541,4 @@ private List getSubjectsForEvaluateSingle(String subjectId, IRepository
Optional.ofNullable(subjectId).map(List::of).orElse(List.of()))
.toList();
}
-
- /**
- * Fully resolves the repository from the environment: applies the three-endpoint proxy if all
- * endpoints are configured, then overlays any additional data bundle via federation.
- */
- private IRepository resolveRepository(MeasureEnvironment environment) {
- IRepository repo = repository;
- if (environment.dataEndpoint() != null
- && environment.contentEndpoint() != null
- && environment.terminologyEndpoint() != null) {
- repo = Repositories.proxy(
- repo,
- true,
- environment.dataEndpoint(),
- environment.contentEndpoint(),
- environment.terminologyEndpoint());
- }
- if (environment.additionalData() != null) {
- repo = new FederatedRepository(
- repo, new InMemoryFhirRepository(repo.fhirContext(), environment.additionalData()));
- }
- return repo;
- }
}
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 c5fdd5ffcc..73cf093aa4 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
@@ -235,7 +235,8 @@ public When evaluate() {
reportType,
subject,
null,
- new MeasureEnvironment(null, null, null, additionalData),
+ new MeasureEnvironment(null, null, null, additionalData)
+ .resolve(multiMeasureService.getRepository()),
parameters,
productLine,
practitioner);
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 827fdd902a..5b0e0f838e 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
@@ -257,7 +257,7 @@ public MultiMeasure.When evaluate() {
periodEnd,
reportType,
subject,
- new MeasureEnvironment(null, null, null, additionalData),
+ new MeasureEnvironment(null, null, null, additionalData).resolve(service.getRepository()),
parameters,
productLine,
reporter);
From 79ba397c34e819d65206c1839544ac4b9553762e Mon Sep 17 00:00:00 2001
From: Jonathan Percival
Date: Thu, 26 Mar 2026 14:08:44 -0600
Subject: [PATCH 09/10] feat: test for measure enviornment
---
.../cr/measure/common/MeasureEnvironment.java | 8 +-
.../common/MeasureEnvironmentTest.java | 119 ++++++++++++++++++
2 files changed, 123 insertions(+), 4 deletions(-)
create mode 100644 cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/common/MeasureEnvironmentTest.java
diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureEnvironment.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureEnvironment.java
index 471270ba30..88e0a8fff7 100644
--- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureEnvironment.java
+++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureEnvironment.java
@@ -38,16 +38,16 @@ public record MeasureEnvironment(
/**
* Resolves this environment against a base repository.
*
- * If all three endpoints are present, wraps {@code base} in a proxy repository.
- * If {@code additionalData} is present, federates the result with an in-memory repository
- * seeded from that bundle.
+ *
If any endpoint is present, wraps {@code base} in a {@code ProxyRepository}; null
+ * endpoints fall back to {@code base}. If {@code additionalData} is present, federates
+ * the result with an in-memory repository seeded from that bundle.
*
* @param base the base repository to build on top of
* @return the resolved repository, possibly wrapped
*/
public IRepository resolve(IRepository base) {
IRepository repo = base;
- if (dataEndpoint() != null && contentEndpoint() != null && terminologyEndpoint() != null) {
+ if (dataEndpoint() != null || contentEndpoint() != null || terminologyEndpoint() != null) {
repo = Repositories.proxy(repo, true, dataEndpoint(), contentEndpoint(), terminologyEndpoint());
}
if (additionalData() != null) {
diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/common/MeasureEnvironmentTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/common/MeasureEnvironmentTest.java
new file mode 100644
index 0000000000..7b2ba7b63c
--- /dev/null
+++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/common/MeasureEnvironmentTest.java
@@ -0,0 +1,119 @@
+package org.opencds.cqf.fhir.cr.measure.common;
+
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.repository.IRepository;
+import org.hl7.fhir.r4.model.Bundle;
+import org.hl7.fhir.r4.model.Endpoint;
+import org.hl7.fhir.r4.model.Patient;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.opencds.cqf.fhir.utility.repository.FederatedRepository;
+import org.opencds.cqf.fhir.utility.repository.InMemoryFhirRepository;
+import org.opencds.cqf.fhir.utility.repository.ProxyRepository;
+
+/**
+ * Unit tests for {@link MeasureEnvironment#resolve(IRepository)}.
+ *
+ *
The critical invariant: a ProxyRepository must be created whenever any endpoint is
+ * provided, not only when all three are present. {@code Repositories.proxy()} already handles null
+ * per-endpoint entries by falling back to the local repository.
+ */
+class MeasureEnvironmentTest {
+
+ private static IRepository base;
+
+ // A real Endpoint is needed because Repositories.proxy() dispatches on fhirContext() and
+ // passes the endpoint to Clients.forEndpoint(). Client construction is lazy (no network call),
+ // so a stub address is safe in unit tests.
+ private static Endpoint dataEndpoint;
+ private static Endpoint contentEndpoint;
+ private static Endpoint terminologyEndpoint;
+
+ @BeforeAll
+ static void setup() {
+ base = new InMemoryFhirRepository(FhirContext.forR4Cached());
+ dataEndpoint = new Endpoint().setAddress("http://data.example.org/fhir");
+ contentEndpoint = new Endpoint().setAddress("http://content.example.org/fhir");
+ terminologyEndpoint = new Endpoint().setAddress("http://terminology.example.org/fhir");
+ }
+
+ // ── no-op cases ──────────────────────────────────────────────────────────
+
+ @Test
+ void resolve_emptyEnvironment_returnsBaseUnchanged() {
+ IRepository result = MeasureEnvironment.EMPTY.resolve(base);
+ assertSame(base, result, "EMPTY environment should return the base repository as-is");
+ }
+
+ @Test
+ void resolve_allEndpointsNull_noAdditionalData_returnsBaseUnchanged() {
+ var env = new MeasureEnvironment(null, null, null, null);
+ assertSame(base, env.resolve(base));
+ }
+
+ // ── single-endpoint cases (the cases the AND bug broke) ──────────────────
+
+ @Test
+ void resolve_onlyDataEndpoint_returnsProxyRepository() {
+ var env = new MeasureEnvironment(null, null, dataEndpoint, null);
+ assertInstanceOf(ProxyRepository.class, env.resolve(base));
+ }
+
+ @Test
+ void resolve_onlyContentEndpoint_returnsProxyRepository() {
+ var env = new MeasureEnvironment(contentEndpoint, null, null, null);
+ assertInstanceOf(ProxyRepository.class, env.resolve(base));
+ }
+
+ @Test
+ void resolve_onlyTerminologyEndpoint_returnsProxyRepository() {
+ var env = new MeasureEnvironment(null, terminologyEndpoint, null, null);
+ assertInstanceOf(ProxyRepository.class, env.resolve(base));
+ }
+
+ // ── all-endpoints case (worked before, must still work) ──────────────────
+
+ @Test
+ void resolve_allEndpoints_returnsProxyRepository() {
+ var env = new MeasureEnvironment(contentEndpoint, terminologyEndpoint, dataEndpoint, null);
+ assertInstanceOf(ProxyRepository.class, env.resolve(base));
+ }
+
+ // ── additionalData only ───────────────────────────────────────────────────
+
+ @Test
+ void resolve_onlyAdditionalData_returnsFederatedRepository() {
+ Bundle bundle = bundleWithPatient("p1");
+ var env = new MeasureEnvironment(null, null, null, bundle);
+ assertInstanceOf(FederatedRepository.class, env.resolve(base));
+ }
+
+ // ── endpoints + additionalData ────────────────────────────────────────────
+
+ @Test
+ void resolve_endpointsAndAdditionalData_returnsFederatedRepository() {
+ // FederatedRepository wraps the ProxyRepository; the outer type is FederatedRepository.
+ Bundle bundle = bundleWithPatient("p2");
+ var env = new MeasureEnvironment(contentEndpoint, terminologyEndpoint, dataEndpoint, bundle);
+ assertInstanceOf(FederatedRepository.class, env.resolve(base));
+ }
+
+ @Test
+ void resolve_singleEndpointAndAdditionalData_returnsFederatedRepository() {
+ Bundle bundle = bundleWithPatient("p3");
+ var env = new MeasureEnvironment(null, null, dataEndpoint, bundle);
+ assertInstanceOf(FederatedRepository.class, env.resolve(base));
+ }
+
+ // ── helpers ───────────────────────────────────────────────────────────────
+
+ private static Bundle bundleWithPatient(String id) {
+ var bundle = new Bundle();
+ bundle.setType(Bundle.BundleType.COLLECTION);
+ bundle.addEntry().setResource(new Patient().setId(id));
+ return bundle;
+ }
+}
From cd7bfaa366b6a538a41c71b6c262d581f19be5e0 Mon Sep 17 00:00:00 2001
From: Jonathan Percival
Date: Thu, 26 Mar 2026 15:13:57 -0600
Subject: [PATCH 10/10] bug: spotless issues
---
.../cqf/fhir/cr/measure/r4/R4MeasureEvaluatorMultiple.java | 2 --
.../cqf/fhir/cr/measure/r4/R4MeasureEvaluatorSingle.java | 2 --
.../opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java | 1 -
3 files changed, 5 deletions(-)
diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureEvaluatorMultiple.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureEvaluatorMultiple.java
index 3254628fb4..d78ebab926 100644
--- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureEvaluatorMultiple.java
+++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureEvaluatorMultiple.java
@@ -3,8 +3,6 @@
import jakarta.annotation.Nullable;
import java.time.ZonedDateTime;
import java.util.List;
-import org.hl7.fhir.r4.model.Bundle;
-import org.hl7.fhir.r4.model.Endpoint;
import org.hl7.fhir.r4.model.Parameters;
import org.opencds.cqf.fhir.cr.measure.common.MeasureReference;
diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureEvaluatorSingle.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureEvaluatorSingle.java
index 318078fb8f..fa8626ffa8 100644
--- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureEvaluatorSingle.java
+++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureEvaluatorSingle.java
@@ -2,8 +2,6 @@
import jakarta.annotation.Nullable;
import java.time.ZonedDateTime;
-import org.hl7.fhir.r4.model.Bundle;
-import org.hl7.fhir.r4.model.Endpoint;
import org.hl7.fhir.r4.model.MeasureReport;
import org.hl7.fhir.r4.model.Parameters;
import org.opencds.cqf.fhir.cr.measure.common.MeasureReference;
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 cb538086af..2636221dfc 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
@@ -20,7 +20,6 @@
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.Endpoint;
import org.hl7.fhir.r4.model.Measure;
import org.hl7.fhir.r4.model.MeasureReport;
import org.hl7.fhir.r4.model.Parameters;