From ed468131c897961051a4390b5682a353e0ca87a3 Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Tue, 24 Mar 2026 21:19:58 -0600 Subject: [PATCH 01/10] Introduce MeasureReference sealed interface Replace Either3 and the three-separate-lists pattern (List, List, List) with a single sealed type: sealed interface MeasureReference { record ById(IIdType id) record ByIdentifier(String identifier) record ByCanonicalUrl(String url) } HAPI providers convert at the boundary via fromOperationParams(). R4MeasureServiceUtils.getMeasures() dispatches on the sealed type. R4CareGapsParameters uses List instead of 3 fields. R4MultiMeasureService, R4CareGapsProcessor accept List. R4CareGapsService.liftMeasureParameters() deleted -- boundary conversion now happens once in the transport layer. R4CollectDataService reads Measure directly via repository.read() instead of wrapping in Either3. The type is self-documenting: a measure can be referenced by ID, identifier, or canonical URL. Compiler enforces exhaustive handling. --- .../r4/measure/CareGapsOperationProvider.java | 13 +-- .../r4/measure/MeasureOperationsProvider.java | 9 +-- .../cr/measure/common/MeasureReference.java | 81 +++++++++++++++++++ .../measure/r4/R4CareGapsBundleBuilder.java | 7 +- .../cr/measure/r4/R4CareGapsParameters.java | 10 +-- .../cr/measure/r4/R4CareGapsProcessor.java | 56 ++++++------- .../r4/R4CareGapsProcessorInterface.java | 12 ++- .../fhir/cr/measure/r4/R4CareGapsService.java | 64 +-------------- .../r4/R4CareGapsServiceInterface.java | 11 +-- .../cr/measure/r4/R4CollectDataService.java | 8 +- .../r4/R4MeasureEvaluatorMultiple.java | 6 +- .../measure/r4/R4MeasureEvaluatorSingle.java | 7 +- .../cr/measure/r4/R4MeasureProcessor.java | 38 +++++---- .../cr/measure/r4/R4MultiMeasureService.java | 54 +++---------- .../r4/utils/R4MeasureServiceUtils.java | 81 ++++--------------- .../cqf/fhir/cr/measure/r4/CareGaps.java | 22 ++--- .../cqf/fhir/cr/measure/r4/Measure.java | 4 +- .../cqf/fhir/cr/measure/r4/MultiMeasure.java | 21 ++--- .../fhir/cr/measure/r4/R4CareGapsTest.java | 2 +- .../cr/measure/r4/R4MeasureProcessorTest.java | 23 +++--- 20 files changed, 236 insertions(+), 293 deletions(-) create mode 100644 cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureReference.java diff --git a/cqf-fhir-cr-hapi/src/main/java/org/opencds/cqf/fhir/cr/hapi/r4/measure/CareGapsOperationProvider.java b/cqf-fhir-cr-hapi/src/main/java/org/opencds/cqf/fhir/cr/hapi/r4/measure/CareGapsOperationProvider.java index d261f15856..c83736c454 100644 --- a/cqf-fhir-cr-hapi/src/main/java/org/opencds/cqf/fhir/cr/hapi/r4/measure/CareGapsOperationProvider.java +++ b/cqf-fhir-cr-hapi/src/main/java/org/opencds/cqf/fhir/cr/hapi/r4/measure/CareGapsOperationProvider.java @@ -14,6 +14,7 @@ import org.hl7.fhir.r4.model.Parameters; import org.opencds.cqf.fhir.cr.hapi.common.StringTimePeriodHandler; import org.opencds.cqf.fhir.cr.hapi.r4.ICareGapsServiceFactory; +import org.opencds.cqf.fhir.cr.measure.common.MeasureReference; @SuppressWarnings("java:S107") public class CareGapsOperationProvider { @@ -84,6 +85,12 @@ public Parameters careGapsReport( @OperationParam(name = "measureUrl") List measureUrl, @OperationParam(name = "nonDocument") BooleanType nonDocument) { + var measureIds = + measureId == null ? null : measureId.stream().map(IdType::new).toList(); + var measureUrls = measureUrl == null + ? null + : measureUrl.stream().map(CanonicalType::getValueAsString).toList(); + var measureRefs = MeasureReference.fromOperationParams(measureIds, measureIdentifier, measureUrls); return r4CareGapsProcessorFactory .create(requestDetails) .getCareGapsReport( @@ -91,11 +98,7 @@ public Parameters careGapsReport( stringTimePeriodHandler.getEndZonedDateTime(reriodEnd, requestDetails), subject, status, - measureId == null - ? null - : measureId.stream().map(IdType::new).toList(), - measureIdentifier, - measureUrl, + measureRefs, Optional.ofNullable(nonDocument) .map(BooleanType::getValue) .orElse(false)); 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 74fc59d3ee..342d696ef9 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,7 +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.utility.monad.Eithers; +import org.opencds.cqf.fhir.cr.measure.common.MeasureReference; @SuppressWarnings("java:S107") public class MeasureOperationsProvider { @@ -90,7 +90,7 @@ public MeasureReport evaluateMeasure( return r4MeasureServiceFactory .create(requestDetails) .evaluate( - Eithers.forMiddle3(id), + new MeasureReference.ById(id), stringTimePeriodHandler.getStartZonedDateTime(periodStart, requestDetails), stringTimePeriodHandler.getEndZonedDateTime(periodEnd, requestDetails), reportType, @@ -156,12 +156,11 @@ public Parameters evaluate( var contentEndpointParam = (Endpoint) getEndpoint(fhirVersion, contentEndpoint); var terminologyEndpointParam = (Endpoint) getEndpoint(fhirVersion, terminologyEndpoint); var dataEndpointParam = (Endpoint) getEndpoint(fhirVersion, dataEndpoint); + var measureRefs = MeasureReference.fromOperationParams(measureId, measureIdentifier, measureUrl); return r4MultiMeasureServiceFactory .create(requestDetails) .evaluate( - measureId, // List - measureUrl, // List - measureIdentifier, // List + measureRefs, stringTimePeriodHandler.getStartZonedDateTime(periodStart, requestDetails), stringTimePeriodHandler.getEndZonedDateTime(periodEnd, requestDetails), reportType, diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureReference.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureReference.java new file mode 100644 index 0000000000..5bd816cb04 --- /dev/null +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/MeasureReference.java @@ -0,0 +1,81 @@ +package org.opencds.cqf.fhir.cr.measure.common; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import org.hl7.fhir.instance.model.api.IIdType; + +/** + * A typed reference to a Measure resource. Replaces the {@code Either3} + * pattern and the three-separate-lists pattern with a single self-documenting sealed hierarchy. + * + *

HAPI {@code @OperationParam} delivers measure references as three separate lists (by ID, + * by identifier, by canonical URL). The transport adapter converts these into a single + * {@code List} at the boundary, and all downstream code works with this type. + */ +public sealed interface MeasureReference { + + /** A measure referenced by its FHIR resource ID (e.g. {@code Measure/example-1}). */ + record ById(IIdType id) implements MeasureReference { + public ById { + Objects.requireNonNull(id, "Measure ID must not be null"); + } + + @Override + public String display() { + return id.getValue(); + } + } + + /** A measure referenced by its business identifier (e.g. {@code "CMS125"}). */ + record ByIdentifier(String identifier) implements MeasureReference { + public ByIdentifier { + Objects.requireNonNull(identifier, "Measure identifier must not be null"); + } + + @Override + public String display() { + return identifier; + } + } + + /** A measure referenced by its canonical URL (e.g. {@code "http://example.org/Measure/CMS125"}). */ + record ByCanonicalUrl(String url) implements MeasureReference { + public ByCanonicalUrl { + Objects.requireNonNull(url, "Measure canonical URL must not be null"); + } + + @Override + public String display() { + return url; + } + } + + /** Human-readable display string for error messages and logging. */ + String display(); + + /** + * Converts three HAPI {@code @OperationParam} lists into a single {@code List}. + * This is the boundary conversion — called once in the transport adapter. + * + * @param measureIds measure resource IDs (may be null) + * @param measureIdentifiers measure business identifiers (may be null) + * @param measureUrls measure canonical URLs (may be null) + * @return combined list preserving input order (IDs first, then identifiers, then URLs) + */ + static List fromOperationParams( + List measureIds, List measureIdentifiers, List measureUrls) { + var refs = new ArrayList(); + if (measureIds != null) { + measureIds.stream().filter(Objects::nonNull).forEach(id -> refs.add(new ById(id))); + } + if (measureIdentifiers != null) { + measureIdentifiers.stream().filter(Objects::nonNull).forEach(id -> refs.add(new ByIdentifier(id))); + } + if (measureUrls != null) { + measureUrls.stream().filter(Objects::nonNull).forEach(url -> refs.add(new ByCanonicalUrl(url))); + } + return Collections.unmodifiableList(refs); + } +} 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 edb2ac2abd..c8aad3a974 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 @@ -47,6 +47,7 @@ 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.MeasureReference; import org.opencds.cqf.fhir.cr.measure.enumeration.CareGapsStatusCode; import org.opencds.cqf.fhir.cr.measure.r4.utils.R4MeasureServiceUtils; import org.opencds.cqf.fhir.utility.Ids; @@ -95,7 +96,7 @@ public R4CareGapsBundleBuilder( } public List makePatientBundles( - List subjects, R4CareGapsParameters r4CareGapsParameters, List measureId) { + List subjects, R4CareGapsParameters r4CareGapsParameters, List measureRefs) { // retrieve reporter from configuration String reporter = RESOURCE_TYPE_ORGANIZATION.concat("/" + careGapsProperties.getCareGapsReporter()); @@ -105,9 +106,7 @@ public List makePatientBundles( for (String subject : subjects) { // Measure Reports var result = r4MultiMeasureService.evaluate( - measureId, - null, - null, + measureRefs, r4CareGapsParameters.getPeriodStart(), r4CareGapsParameters.getPeriodEnd(), MeasureEvalType.SUBJECT.toCode(), diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CareGapsParameters.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CareGapsParameters.java index 07226dda56..71e3ae0010 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CareGapsParameters.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CareGapsParameters.java @@ -2,9 +2,7 @@ import java.time.ZonedDateTime; import java.util.List; -import org.hl7.fhir.r4.model.CanonicalType; -import org.hl7.fhir.r4.model.IdType; -import org.opencds.cqf.fhir.utility.monad.Either3; +import org.opencds.cqf.fhir.cr.measure.common.MeasureReference; /** * Parameters class to manage input parameters for care-gaps service @@ -14,7 +12,7 @@ public class R4CareGapsParameters { private ZonedDateTime periodEnd; private String subject; private List status; - private List> measure; + private List measure; private boolean notDocument; public void setPeriodStart(ZonedDateTime periodStart) { @@ -49,11 +47,11 @@ public boolean isNotDocument() { return notDocument; } - public void setMeasure(List> measure) { + public void setMeasure(List measure) { this.measure = measure; } - public List> getMeasure() { + public List getMeasure() { return measure; } diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CareGapsProcessor.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CareGapsProcessor.java index d7443aa9a4..8c1a96cd04 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CareGapsProcessor.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CareGapsProcessor.java @@ -12,25 +12,22 @@ import java.util.List; import java.util.Map; import java.util.UUID; -import java.util.function.Function; import java.util.stream.Collectors; -import org.hl7.fhir.r4.model.CanonicalType; import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.Measure; import org.hl7.fhir.r4.model.Measure.MeasureGroupComponent; import org.hl7.fhir.r4.model.Organization; import org.hl7.fhir.r4.model.Parameters; -import org.hl7.fhir.r4.model.PrimitiveType; import org.hl7.fhir.r4.model.Resource; import org.opencds.cqf.fhir.cr.measure.CareGapsProperties; import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions; import org.opencds.cqf.fhir.cr.measure.common.GroupDef; 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.common.MeasureScoring; import org.opencds.cqf.fhir.cr.measure.constant.CareGapsConstants; import org.opencds.cqf.fhir.cr.measure.enumeration.CareGapsStatusCode; import org.opencds.cqf.fhir.cr.measure.r4.utils.R4MeasureServiceUtils; -import org.opencds.cqf.fhir.utility.monad.Either3; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -73,18 +70,21 @@ public Parameters getCareGapsReport( @Nullable ZonedDateTime periodEnd, String subject, List status, - List> measure, + List measureRefs, boolean notDocument) { // set Parameters R4CareGapsParameters r4CareGapsParams = - setCareGapParameters(periodStart, periodEnd, subject, status, measure, notDocument); + setCareGapParameters(periodStart, periodEnd, subject, status, measureRefs, notDocument); // validate and set required configuration resources for care-gaps checkConfigurationReferences(); // validate required parameter values - checkValidStatusCode(measure, r4CareGapsParams.getStatus()); + if (measureRefs.isEmpty()) { + throw new InvalidRequestException("no measure resolving parameter was specified for care-gaps"); + } + checkValidStatusCode(measureRefs, r4CareGapsParams.getStatus()); List measures = resolveMeasure(r4CareGapsParams.getMeasure()); measureCompatibilityCheck(measures); @@ -96,10 +96,8 @@ public Parameters getCareGapsReport( // Build Patient Bundles - List components = r4CareGapsBundleBuilder.makePatientBundles( - subjects, - r4CareGapsParams, - measures.stream().map(Resource::getIdElement).collect(Collectors.toList())); + List components = + r4CareGapsBundleBuilder.makePatientBundles(subjects, r4CareGapsParams, measureRefs); // Return Results with Bundles return result.setParameter(components); @@ -111,10 +109,10 @@ public R4CareGapsParameters setCareGapParameters( @Nullable ZonedDateTime periodEnd, String subject, List status, - List> measure, + List measureRefs, boolean notDocument) { R4CareGapsParameters r4CareGapsParams = new R4CareGapsParameters(); - r4CareGapsParams.setMeasure(measure); + r4CareGapsParams.setMeasure(measureRefs); r4CareGapsParams.setPeriodStart(periodStart); r4CareGapsParams.setPeriodEnd(periodEnd); r4CareGapsParams.setStatus(status); @@ -124,12 +122,18 @@ public R4CareGapsParameters setCareGapParameters( } @Override - public List resolveMeasure(List> measure) { - return measure.stream() - .map(x -> x.fold( - id -> repository.read(Measure.class, id), - r4MeasureServiceUtils::resolveByIdentifier, - canonical -> r4MeasureServiceUtils.resolveByUrl(canonical.asStringValue()))) + public List resolveMeasure(List measureRefs) { + return measureRefs.stream() + .map(ref -> { + if (ref instanceof MeasureReference.ById byId) { + return repository.read(Measure.class, byId.id()); + } else if (ref instanceof MeasureReference.ByIdentifier byIdent) { + return r4MeasureServiceUtils.resolveByIdentifier(byIdent.identifier()); + } else if (ref instanceof MeasureReference.ByCanonicalUrl byUrl) { + return r4MeasureServiceUtils.resolveByUrl(byUrl.url()); + } + throw new IllegalArgumentException("Unknown MeasureReference type: " + ref.getClass()); + }) .collect(Collectors.toList()); } @@ -175,7 +179,7 @@ public Parameters initializeResult() { } @Override - public void checkValidStatusCode(List> measure, List statuses) { + public void checkValidStatusCode(List measureRefs, List statuses) { r4MeasureServiceUtils.listThrowIllegalArgumentIfEmpty(statuses, "status"); for (String status : statuses) { @@ -185,7 +189,11 @@ public void checkValidStatusCode(List> me && !CareGapsStatusCode.PROSPECTIVE_GAP.toString().equals(status)) { throw new InvalidRequestException( "CareGap status parameter: %s, is not an accepted value for Measure: %s" - .formatted(status, printEithers(measure))); + .formatted( + status, + measureRefs.stream() + .map(MeasureReference::display) + .collect(Collectors.toList()))); } } } @@ -255,10 +263,4 @@ public void checkConfigurationReferences() { careGapsProperties.getCareGapsCompositionSectionAuthor(), CareGapsConstants.CARE_GAPS_SECTION_AUTHOR_KEY); } - - private static List printEithers(List> either) { - return either.stream() - .map(x -> x.fold(IdType::getIdPart, Function.identity(), PrimitiveType::asStringValue)) - .collect(Collectors.toList()); - } } diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CareGapsProcessorInterface.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CareGapsProcessorInterface.java index 8b4c662a9b..e986c977f3 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CareGapsProcessorInterface.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CareGapsProcessorInterface.java @@ -3,11 +3,9 @@ import jakarta.annotation.Nullable; import java.time.ZonedDateTime; import java.util.List; -import org.hl7.fhir.r4.model.CanonicalType; -import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.Measure; import org.hl7.fhir.r4.model.Parameters; -import org.opencds.cqf.fhir.utility.monad.Either3; +import org.opencds.cqf.fhir.cr.measure.common.MeasureReference; /** * Describe operations provided to process caregaps for downstream implementors @@ -19,7 +17,7 @@ Parameters getCareGapsReport( @Nullable ZonedDateTime periodEnd, String subject, List status, - List> measure, + List measureRefs, boolean notDocument); R4CareGapsParameters setCareGapParameters( @@ -27,10 +25,10 @@ R4CareGapsParameters setCareGapParameters( @Nullable ZonedDateTime periodEnd, String subject, List status, - List> measure, + List measureRefs, boolean notDocument); - List resolveMeasure(List> measure); + List resolveMeasure(List measureRefs); List getSubjects(String subject); @@ -40,7 +38,7 @@ R4CareGapsParameters setCareGapParameters( Parameters initializeResult(); - void checkValidStatusCode(List> measure, List statuses); + void checkValidStatusCode(List measureRefs, List statuses); void measureCompatibilityCheck(List measures); diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CareGapsService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CareGapsService.java index 9ee34ef490..b34abebf2a 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CareGapsService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CareGapsService.java @@ -1,23 +1,14 @@ package org.opencds.cqf.fhir.cr.measure.r4; import ca.uhn.fhir.repository.IRepository; -import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import jakarta.annotation.Nullable; import java.time.ZonedDateTime; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.stream.Collectors; -import org.hl7.fhir.r4.model.CanonicalType; -import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.Parameters; import org.opencds.cqf.fhir.cr.measure.CareGapsProperties; import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions; import org.opencds.cqf.fhir.cr.measure.common.MeasurePeriodValidator; -import org.opencds.cqf.fhir.utility.monad.Either3; -import org.opencds.cqf.fhir.utility.monad.Eithers; +import org.opencds.cqf.fhir.cr.measure.common.MeasureReference; /** * Care Gap service that processes and produces care-gaps report as a result @@ -44,9 +35,7 @@ public R4CareGapsService( * @param subject subject population to use for care-gap results. Accepted values [empty=all Patients, Patient/{id}, Group/{id} type {person, practitioner}, Practitioner/{id}] * @param status determines what care-gap statuses will be returned by the service if found. [prospective-gap, closed-gap, open-gap, not-applicable]. * If Result is 'not-applicable' for Measure, but status requested was 'open-gap', then no result will be returned. - * @param measureId Measures to check care-gap for by resolving by fhir resource id - * @param measureIdentifier Measures to check care-gap for by resolving identifier value or systemUrl|value - * @param measureUrl Measures to check care-gap for by resolving canonical url reference + * @param measureRefs Measures to check care-gap for, as a combined list of typed references (by ID, identifier, or canonical URL) * @param notDocument defaulted to 'false', if left empty. This parameter determines if standard care-gaps formatted 'document' bundle is returned with [composition, Detected Issue, * configured resources, evaluated resources, measure reports]. Or non-document mode, * which just returns a bundle with [Detected Issue(s)], with contained Measure Report. @@ -59,54 +48,9 @@ public Parameters getCareGapsReport( @Nullable ZonedDateTime periodEnd, @Nullable String subject, List status, - List measureId, - List measureIdentifier, - List measureUrl, + List measureRefs, boolean notDocument) { - return r4CareGapsProcessor.getCareGapsReport( - periodStart, - periodEnd, - subject, - status, - liftMeasureParameters(measureId, measureIdentifier, measureUrl), - notDocument); - } - - @Override - public List> liftMeasureParameters( - List measureId, List measureIdentifier, List measureUrl) { - - List> eitherList = new ArrayList<>(); - Optional.ofNullable(measureId).ifPresent(list -> measureId.stream() - .filter(this::isValidMeasureIdType) - .map(Eithers::forLeft3) - .forEach(eitherList::add)); - Optional.ofNullable(measureIdentifier).ifPresent(list -> measureIdentifier.stream() - .filter(Objects::nonNull) - .map(Eithers::forMiddle3) - .forEach(eitherList::add)); - Optional.ofNullable(measureUrl).ifPresent(list -> measureUrl.stream() - .filter(this::isValidCanonical) - .map(Eithers::forRight3) - .forEach(eitherList::add)); - if (eitherList.isEmpty()) { - final List measureIdsAsStrings = Optional.ofNullable(measureId) - .map(nonNullMeasureId -> - nonNullMeasureId.stream().map(IdType::getIdPart).collect(Collectors.toList())) - .orElse(Collections.emptyList()); - - throw new InvalidRequestException( - "no measure resolving parameter was specified for Measure: " + measureIdsAsStrings); - } - return eitherList; - } - - private boolean isValidCanonical(CanonicalType canonicalType) { - return canonicalType != null && !canonicalType.toString().contains("null"); - } - - private boolean isValidMeasureIdType(IdType measureId) { - return measureId != null && measureId.getIdPart() != null; + return r4CareGapsProcessor.getCareGapsReport(periodStart, periodEnd, subject, status, measureRefs, notDocument); } } diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CareGapsServiceInterface.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CareGapsServiceInterface.java index 0f41f88977..1ddbce3601 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CareGapsServiceInterface.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4CareGapsServiceInterface.java @@ -3,10 +3,8 @@ import jakarta.annotation.Nullable; import java.time.ZonedDateTime; import java.util.List; -import org.hl7.fhir.r4.model.CanonicalType; -import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.Parameters; -import org.opencds.cqf.fhir.utility.monad.Either3; +import org.opencds.cqf.fhir.cr.measure.common.MeasureReference; /** * Describe operations provided by caregaps services for downstream implementors @@ -19,11 +17,6 @@ Parameters getCareGapsReport( @Nullable ZonedDateTime periodEnd, @Nullable String subject, List status, - List measureId, - List measureIdentifier, - List measureUrl, + List measureRefs, boolean notDocument); - - List> liftMeasureParameters( - List measureId, List measureIdentifier, List measureUrl); } 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 bb49180363..a7f65e0187 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 @@ -12,6 +12,7 @@ import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; 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; @@ -20,9 +21,8 @@ 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.r4.utils.R4MeasureServiceUtils; +import org.opencds.cqf.fhir.cr.measure.common.MeasureReference; import org.opencds.cqf.fhir.utility.Ids; -import org.opencds.cqf.fhir.utility.monad.Eithers; @SuppressWarnings("squid:S107") public class R4CollectDataService { @@ -71,7 +71,7 @@ public Parameters collectData( var context = Engines.forRepository(this.repository, this.measureEvaluationOptions.getEvaluationSettings(), null); - var foldedMeasure = R4MeasureServiceUtils.foldMeasure(Eithers.forMiddle3(measureId), repository); + var foldedMeasure = repository.read(Measure.class, measureId); if (!subjectList.isEmpty()) { for (String patient : subjectList) { @@ -118,7 +118,7 @@ private void addReports( CompositeEvaluationResultsPerMeasure evaluateMeasureResults) { MeasureReport report = processor.evaluateMeasure( - Eithers.forMiddle3(measureId), + new MeasureReference.ById(measureId), periodStart, periodEnd, MeasureEvalType.SUBJECT.toCode(), 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 f59c3a1ad5..4c2d1ec083 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 @@ -5,8 +5,8 @@ import java.util.List; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Endpoint; -import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.Parameters; +import org.opencds.cqf.fhir.cr.measure.common.MeasureReference; /** * Interface for {@link R4MultiMeasureService} and any other concrete classes that implement the same @@ -15,9 +15,7 @@ public interface R4MeasureEvaluatorMultiple { Parameters evaluate( - List measureId, - List measureUrl, - List measureIdentifier, + List measures, @Nullable ZonedDateTime periodStart, @Nullable ZonedDateTime periodEnd, String reportType, 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 176e591124..83ecd01e83 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 @@ -3,13 +3,10 @@ import jakarta.annotation.Nullable; import java.time.ZonedDateTime; import org.hl7.fhir.r4.model.Bundle; -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.opencds.cqf.fhir.utility.monad.Either3; +import org.opencds.cqf.fhir.cr.measure.common.MeasureReference; /** * Interface to capture measure evaluation on a single measure. @@ -17,7 +14,7 @@ public interface R4MeasureEvaluatorSingle { MeasureReport evaluate( - Either3 measure, + MeasureReference measure, @Nullable ZonedDateTime periodStart, @Nullable ZonedDateTime periodEnd, String reportType, 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 bcbb30cc62..f3f0cec4e6 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 @@ -20,7 +20,6 @@ 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; import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.Library; import org.hl7.fhir.r4.model.Measure; @@ -37,11 +36,11 @@ import org.opencds.cqf.fhir.cr.measure.common.MeasureEvalType; import org.opencds.cqf.fhir.cr.measure.common.MeasureEvaluationResultHandler; import org.opencds.cqf.fhir.cr.measure.common.MeasureProcessorTimeUtils; +import org.opencds.cqf.fhir.cr.measure.common.MeasureReference; 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.r4.utils.R4DateHelper; import org.opencds.cqf.fhir.cr.measure.r4.utils.R4MeasureServiceUtils; -import org.opencds.cqf.fhir.utility.monad.Either3; import org.opencds.cqf.fhir.utility.search.Searches; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -70,7 +69,7 @@ public IRepository getRepository() { } public MeasureReport evaluateMeasure( - Either3 measure, + MeasureReference measure, @Nullable ZonedDateTime periodStart, @Nullable ZonedDateTime periodEnd, String reportType, @@ -79,7 +78,7 @@ public MeasureReport evaluateMeasure( CqlEngine context, CompositeEvaluationResultsPerMeasure compositeEvaluationResultsPerMeasure) { return this.evaluateMeasure( - R4MeasureServiceUtils.foldMeasure(measure, this.repository), + resolveMeasure(measure), periodStart, periodEnd, reportType, @@ -255,11 +254,11 @@ MeasureDefAndR4MeasureReport evaluateMeasureCaptureDef( * TEST INFRASTRUCTURE ONLY - DO NOT USE IN PRODUCTION CODE *

*

- * This overload accepts Either3 for flexible measure resolution (by URL, ID, or resource) - * and delegates to the Measure-based overload after resolution. + * This overload accepts a MeasureReference for flexible measure resolution (by ID, identifier, + * or canonical URL) and delegates to the Measure-based overload after resolution. *

* - * @param measure Either canonical URL, ID, or Measure resource + * @param measure a MeasureReference (ById, ByIdentifier, or ByCanonicalUrl) * @param periodStart start date of Measurement Period * @param periodEnd end date of Measurement Period * @param reportType type of report that defines MeasureReport Type @@ -271,7 +270,7 @@ MeasureDefAndR4MeasureReport evaluateMeasureCaptureDef( */ @VisibleForTesting MeasureDefAndR4MeasureReport evaluateMeasureCaptureDef( - Either3 measure, + MeasureReference measure, @Nullable ZonedDateTime periodStart, @Nullable ZonedDateTime periodEnd, String reportType, @@ -281,7 +280,7 @@ MeasureDefAndR4MeasureReport evaluateMeasureCaptureDef( CompositeEvaluationResultsPerMeasure compositeEvaluationResultsPerMeasure) { return evaluateMeasureCaptureDef( - R4MeasureServiceUtils.foldMeasure(measure, this.repository), + resolveMeasure(measure), periodStart, periodEnd, reportType, @@ -293,19 +292,26 @@ MeasureDefAndR4MeasureReport evaluateMeasureCaptureDef( public CompositeEvaluationResultsPerMeasure evaluateMeasureWithCqlEngine( List subjects, - Either3 measureEither, + MeasureReference measureRef, @Nullable ZonedDateTime periodStart, @Nullable ZonedDateTime periodEnd, Parameters parameters, CqlEngine context) { return evaluateMultiMeasuresWithCqlEngine( - subjects, - List.of(R4MeasureServiceUtils.foldMeasure(measureEither, repository)), - periodStart, - periodEnd, - parameters, - context); + subjects, List.of(resolveMeasure(measureRef)), periodStart, periodEnd, parameters, context); + } + + private Measure resolveMeasure(MeasureReference ref) { + var utils = new R4MeasureServiceUtils(this.repository); + if (ref instanceof MeasureReference.ById byId) { + return utils.resolveById((IdType) byId.id()); + } else if (ref instanceof MeasureReference.ByIdentifier byIdent) { + return utils.resolveByIdentifier(byIdent.identifier()); + } else if (ref instanceof MeasureReference.ByCanonicalUrl byUrl) { + return utils.resolveByUrl(byUrl.url()); + } + throw new IllegalArgumentException("Unknown MeasureReference type: " + ref.getClass()); } public CompositeEvaluationResultsPerMeasure evaluateMeasureIdWithCqlEngine( 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 0c73fccbab..8650c5f10e 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,9 +20,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; import org.hl7.fhir.r4.model.MeasureReport; import org.hl7.fhir.r4.model.Parameters; @@ -34,10 +32,10 @@ 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.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.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; @@ -85,7 +83,7 @@ public IRepository getRepository() { @Override public MeasureReport evaluate( - Either3 measure, + MeasureReference measure, @Nullable ZonedDateTime periodStart, @Nullable ZonedDateTime periodEnd, String reportType, @@ -118,7 +116,7 @@ public MeasureReport evaluate( @VisibleForTesting MeasureDefAndR4MeasureReport evaluateSingleMeasureCaptureDef( - Either3 measure, + MeasureReference measure, @Nullable ZonedDateTime periodStart, @Nullable ZonedDateTime periodEnd, String reportType, @@ -134,10 +132,7 @@ MeasureDefAndR4MeasureReport evaluateSingleMeasureCaptureDef( final List> resultsAsListOfList = evaluateToListOfList( SingleOrMultiple.SINGLE, - measure, - List.of(), - List.of(), - List.of(), + List.of(measure), periodStart, periodEnd, reportType, @@ -170,9 +165,7 @@ MeasureDefAndR4MeasureReport evaluateSingleMeasureCaptureDef( @Override public Parameters evaluate( - List measureId, - List measureUrl, - List measureIdentifier, + List measures, @Nullable ZonedDateTime periodStart, @Nullable ZonedDateTime periodEnd, String reportType, @@ -186,9 +179,7 @@ public Parameters evaluate( String reporter) { return evaluateWithDefs( - measureId, - measureUrl, - measureIdentifier, + measures, periodStart, periodEnd, reportType, @@ -214,9 +205,7 @@ public Parameters evaluate( * post-scoring state (MeasureReport) for each evaluated measure. *

* - * @param measureId list of Measure IDs - * @param measureUrl list of Measure URLs - * @param measureIdentifier list of Measure identifiers + * @param measures list of MeasureReferences * @param periodStart start date of Measurement Period * @param periodEnd end date of Measurement Period * @param reportType type of report @@ -232,9 +221,7 @@ public Parameters evaluate( */ @VisibleForTesting MeasureDefAndR4ParametersWithMeasureReports evaluateWithDefs( - List measureId, - List measureUrl, - List measureIdentifier, + List measures, @Nullable ZonedDateTime periodStart, @Nullable ZonedDateTime periodEnd, String reportType, @@ -249,10 +236,7 @@ MeasureDefAndR4ParametersWithMeasureReports evaluateWithDefs( return toMeasureDefAndParametersResults(evaluateToListOfList( SingleOrMultiple.MULTIPLE, - null, - measureId, - measureUrl, - measureIdentifier, + measures, periodStart, periodEnd, reportType, @@ -269,10 +253,7 @@ MeasureDefAndR4ParametersWithMeasureReports evaluateWithDefs( private List> evaluateToListOfList( SingleOrMultiple singleOrMultiple, - @Nullable Either3 measure, - List measureId, - List measureUrl, - List measureIdentifier, + List measureRefs, @Nullable ZonedDateTime periodStart, @Nullable ZonedDateTime periodEnd, String reportType, @@ -322,8 +303,7 @@ private List> evaluateToListOfList( subjectToUse = subject; } - final List measures = - getMeasures(measure, measureId, measureUrl, measureIdentifier, r4MeasureServiceUtilsToUse); + final List measures = r4MeasureServiceUtilsToUse.getMeasures(measureRefs); log.debug("multi-evaluate-measure, measures to evaluate: {}", measures.size()); @@ -378,18 +358,6 @@ private List> evaluateToListOfList( productLine); } - private List getMeasures( - @Nullable Either3 measureEither, - List measureId, - List measureUrl, - List measureIdentifier, - R4MeasureServiceUtils r4MeasureServiceUtilsToUse) { - return Optional.ofNullable(measureEither) - .map(nonNullMeasureEither -> - List.of(R4MeasureServiceUtils.foldMeasure(nonNullMeasureEither, this.repository))) - .orElse(r4MeasureServiceUtilsToUse.getMeasures(measureId, measureIdentifier, measureUrl)); - } - private List> populationOrSingleMeasureReport( R4MeasureProcessor r4Processor, R4MeasureServiceUtils r4MeasureServiceUtils, 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 a00edf8452..eff1208d9c 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,10 +37,7 @@ 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; import org.hl7.fhir.r4.model.ContactDetail; @@ -54,13 +51,11 @@ import org.hl7.fhir.r4.model.SearchParameter; import org.hl7.fhir.r4.model.StringType; import org.opencds.cqf.fhir.cr.measure.common.MeasureEvalType; +import org.opencds.cqf.fhir.cr.measure.common.MeasureReference; 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); @@ -186,6 +181,13 @@ public Measure resolveById(IdType id) { return this.repository.read(Measure.class, id); } + /** + * Resolves a Measure by resource ID. Static convenience for callers that have a repository in scope. + */ + public static Measure resolveById(IIdType id, IRepository repository) { + return repository.read(Measure.class, id); + } + public Measure resolveByUrl(String url) { Map> searchParameters = new HashMap<>(); if (url.contains("|")) { @@ -233,30 +235,17 @@ public Measure resolveByIdentifier(String identifier) { } } - public List getMeasures( - List measureIds, List measureIdentifiers, List measureCanonicals) { - List measures = new ArrayList<>(); - if (measureIds != null && !measureIds.isEmpty()) { - for (IdType measureId : measureIds) { - Measure measureById = resolveById(measureId); - measures.add(measureById); - } - } - - if (measureCanonicals != null && !measureCanonicals.isEmpty()) { - for (String measureCanonical : measureCanonicals) { - Measure measureByUrl = resolveByUrl(measureCanonical); - measures.add(measureByUrl); + public List getMeasures(List refs) { + var measures = new ArrayList(); + for (var ref : refs) { + if (ref instanceof MeasureReference.ById byId) { + measures.add(resolveById((IdType) byId.id())); + } else if (ref instanceof MeasureReference.ByIdentifier byIdent) { + measures.add(resolveByIdentifier(byIdent.identifier())); + } else if (ref instanceof MeasureReference.ByCanonicalUrl byUrl) { + measures.add(resolveByUrl(byUrl.url())); } } - - if (measureIdentifiers != null && !measureIdentifiers.isEmpty()) { - for (String measureIdentifier : measureIdentifiers) { - Measure measureByIdentifier = resolveByIdentifier(measureIdentifier); - measures.add(measureByIdentifier); - } - } - return distinctByKey(measures, Measure::getUrl); } @@ -348,40 +337,4 @@ public MeasureEvalType convertToNonVersionedMeasureEvalTypeOrDefault(R4MeasureEv 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), - measureIdType -> resolveById(measureIdType, repository), - Function.identity()); - } - - 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 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/CareGaps.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/CareGaps.java index 92c3e702f4..d839c4d3bd 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/CareGaps.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/CareGaps.java @@ -19,7 +19,6 @@ import java.util.function.Supplier; 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.Composition; import org.hl7.fhir.r4.model.DetectedIssue; @@ -39,6 +38,7 @@ import org.opencds.cqf.fhir.cr.measure.CareGapsProperties; 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.MeasureReference; import org.opencds.cqf.fhir.utility.repository.ig.IgRepository; public class CareGaps { @@ -159,9 +159,7 @@ public static class When { private ZonedDateTime periodEnd; private String subject; private List status = new ArrayList<>(); - private List measureId = new ArrayList<>(); - private List measureIdentifier = new ArrayList<>(); - private List measureUrl = new ArrayList<>(); + private List measureRefs = new ArrayList<>(); private Supplier operation; private boolean notDocument; @@ -187,17 +185,23 @@ public CareGaps.When status(String status) { } public CareGaps.When measureId(String measureId) { - this.measureId.add(new IdType("Measure", measureId)); + if (measureId != null) { + this.measureRefs.add(new MeasureReference.ById(new IdType("Measure", measureId))); + } return this; } public CareGaps.When measureUrl(String measureUrl) { - this.measureUrl.add(new CanonicalType(measureUrl)); + if (measureUrl != null) { + this.measureRefs.add(new MeasureReference.ByCanonicalUrl(measureUrl)); + } return this; } public CareGaps.When measureIdentifier(String measureIdentifier) { - this.measureIdentifier.add(measureIdentifier); + if (measureIdentifier != null) { + this.measureRefs.add(new MeasureReference.ByIdentifier(measureIdentifier)); + } return this; } @@ -207,8 +211,8 @@ public CareGaps.When notDocument(boolean notDocument) { } public CareGaps.When getCareGapsReport() { - this.operation = () -> service.getCareGapsReport( - periodStart, periodEnd, subject, status, measureId, measureIdentifier, measureUrl, notDocument); + this.operation = + () -> service.getCareGapsReport(periodStart, periodEnd, subject, status, measureRefs, notDocument); return this; } 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 5266eb7435..f8db8d3025 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 @@ -26,13 +26,13 @@ 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.MeasurePeriodValidator; +import org.opencds.cqf.fhir.cr.measure.common.MeasureReference; import org.opencds.cqf.fhir.cr.measure.r4.selected.def.SelectedMeasureDef; import org.opencds.cqf.fhir.cr.measure.r4.selected.report.SelectedMeasureReport; import org.opencds.cqf.fhir.cr.measure.r4.selected.report.SelectedMeasureReportContained; import org.opencds.cqf.fhir.cr.measure.r4.selected.report.SelectedMeasureReportExtension; import org.opencds.cqf.fhir.cr.measure.r4.selected.report.SelectedMeasureReportGroup; import org.opencds.cqf.fhir.cr.measure.r4.selected.report.SelectedMeasureReportReference; -import org.opencds.cqf.fhir.utility.monad.Eithers; import org.opencds.cqf.fhir.utility.repository.ig.IgRepository; import org.opencds.cqf.fhir.utility.search.Searches.SearchBuilder; @@ -228,7 +228,7 @@ public When productLine(String productLine) { public When evaluate() { this.operation = () -> multiMeasureService.evaluateSingleMeasureCaptureDef( - Eithers.forMiddle3(new IdType("Measure", measureId)), + new MeasureReference.ById(new IdType("Measure", measureId)), periodStart, periodEnd, reportType, 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 514fb83582..181c22bef4 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 @@ -47,6 +47,7 @@ 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.MeasurePeriodValidator; +import org.opencds.cqf.fhir.cr.measure.common.MeasureReference; import org.opencds.cqf.fhir.cr.measure.constant.MeasureConstants; import org.opencds.cqf.fhir.cr.measure.r4.selected.def.SelectedMeasureDefCollection; import org.opencds.cqf.fhir.utility.BundleHelper; @@ -173,9 +174,7 @@ public static class When { this.repository = repository; } - private List measureId = new ArrayList<>(); - private List measureUrl = new ArrayList<>(); - private List measureIdentifier = new ArrayList<>(); + private List measureRefs = new ArrayList<>(); private ZonedDateTime periodStart; private ZonedDateTime periodEnd; private String subject; @@ -188,17 +187,23 @@ public static class When { private String reporter; public MultiMeasure.When measureId(String measureId) { - this.measureId.add(new IdType("Measure", measureId)); + if (measureId != null) { + this.measureRefs.add(new MeasureReference.ById(new IdType("Measure", measureId))); + } return this; } public MultiMeasure.When measureIdentifier(String measureIdentifier) { - this.measureIdentifier.add(measureIdentifier); + if (measureIdentifier != null) { + this.measureRefs.add(new MeasureReference.ByIdentifier(measureIdentifier)); + } return this; } public MultiMeasure.When measureUrl(String measureUrl) { - this.measureUrl.add(measureUrl); + if (measureUrl != null) { + this.measureRefs.add(new MeasureReference.ByCanonicalUrl(measureUrl)); + } return this; } @@ -246,9 +251,7 @@ public MultiMeasure.When reporter(String reporter) { public MultiMeasure.When evaluate() { this.operation = () -> service.evaluateWithDefs( - measureId, - measureUrl, - measureIdentifier, + measureRefs, periodStart, periodEnd, reportType, diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/R4CareGapsTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/R4CareGapsTest.java index 7a0ba71d95..8656eebe96 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/R4CareGapsTest.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/R4CareGapsTest.java @@ -318,7 +318,7 @@ void exm125_careGaps_error_wrongsStatusParam() { Assertions.assertTrue( e.getMessage() .contains( - "CareGap status parameter: closed-ga, is not an accepted value for Measure: [BreastCancerScreeningFHIR]")); + "CareGap status parameter: closed-ga, is not an accepted value for Measure: [Measure/BreastCancerScreeningFHIR]")); } } 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 index dda32e764b..bb94691b18 100644 --- 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 @@ -7,9 +7,7 @@ import java.util.List; import java.util.function.Function; import java.util.stream.Stream; -import org.hl7.fhir.r4.model.CanonicalType; import org.hl7.fhir.r4.model.IdType; -import org.hl7.fhir.r4.model.Measure; import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.ResourceType; import org.junit.jupiter.api.Test; @@ -19,9 +17,8 @@ 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.MeasureEvalType; +import org.opencds.cqf.fhir.cr.measure.common.MeasureReference; import org.opencds.cqf.fhir.cr.measure.r4.MultiMeasure.Given; -import org.opencds.cqf.fhir.utility.monad.Either3; -import org.opencds.cqf.fhir.utility.monad.Eithers; class R4MeasureProcessorTest { private static final Given GIVEN_REPO = MultiMeasure.given().repositoryFor("MinimalMeasureEvaluation"); @@ -34,7 +31,7 @@ class R4MeasureProcessorTest { record CaptureDefParams( String description, - Function> measureFactory, + Function measureFactory, String reportType, MeasureEvalType evalType, List subjectIds) { @@ -49,25 +46,25 @@ static Stream captureDefParams() { return Stream.of( new CaptureDefParams( "idType_subject", - repo -> Eithers.forMiddle3(new IdType("Measure", MEASURE_ID)), + repo -> new MeasureReference.ById(new IdType("Measure", MEASURE_ID)), "subject", MeasureEvalType.SUBJECT, List.of(SUBJECT_ID)), new CaptureDefParams( "measureResource_subject", - repo -> Eithers.forRight3(repo.read(Measure.class, new IdType("Measure", MEASURE_ID))), + repo -> new MeasureReference.ById(new IdType("Measure", MEASURE_ID)), "subject", MeasureEvalType.SUBJECT, List.of(SUBJECT_ID)), new CaptureDefParams( "idType_nullEvalType_derivedFromReportType", - repo -> Eithers.forMiddle3(new IdType("Measure", MEASURE_ID)), + repo -> new MeasureReference.ById(new IdType("Measure", MEASURE_ID)), "subject", null, List.of(SUBJECT_ID)), new CaptureDefParams( "idType_population", - repo -> Eithers.forMiddle3(new IdType("Measure", MEASURE_ID)), + repo -> new MeasureReference.ById(new IdType("Measure", MEASURE_ID)), "population", MeasureEvalType.POPULATION, List.of(SUBJECT_ID))); @@ -132,15 +129,15 @@ void evaluateMeasureIdWithCqlEngine() { } @Test - void evaluateMeasureWithCqlEngine_withEither3() { + void evaluateMeasureWithCqlEngine_withMeasureReference() { var repository = GIVEN_REPO.getRepository(); var r4MeasureProcessor = new R4MeasureProcessor(repository, MeasureEvaluationOptions.defaultOptions()); var cqlEngine = Engines.forRepository(repository); - Either3 measureEither = Eithers.forMiddle3(new IdType("Measure", MEASURE_ID)); + var measureRef = new MeasureReference.ById(new IdType("Measure", MEASURE_ID)); var results = r4MeasureProcessor.evaluateMeasureWithCqlEngine( - List.of(SUBJECT_ID), measureEither, null, null, new Parameters(), cqlEngine); + List.of(SUBJECT_ID), measureRef, null, null, new Parameters(), cqlEngine); assertNotNull(results); var evaluationResults = results.processMeasureForSuccessOrFailure(MINIMAL_COHORT_BOOLEAN_BASIS_SINGLE_GROUP); @@ -157,7 +154,7 @@ void evaluateMeasureWithCqlEngine_withEither3() { @ParameterizedTest(name = "{0}") @MethodSource("captureDefParams") - void evaluateMeasureCaptureDef_withEither3(CaptureDefParams params) { + void evaluateMeasureCaptureDef_withMeasureReference(CaptureDefParams params) { var repository = GIVEN_REPO.getRepository(); var r4MeasureProcessor = new R4MeasureProcessor(repository, MeasureEvaluationOptions.defaultOptions()); var cqlEngine = Engines.forRepository(repository); From 74d1ea6e026c9186f18ab19015980a9a46199164 Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Wed, 25 Mar 2026 08:37:16 -0600 Subject: [PATCH 02/10] Fix benchmark test: replace Either3 with MeasureReference --- .../org/opencds/cqf/fhir/benchmark/measure/r4/Measure.java | 4 ++-- 1 file changed, 2 insertions(+), 2 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 0d0ebeb11c..90ba780914 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 @@ -17,7 +17,7 @@ import org.opencds.cqf.fhir.cr.measure.common.MeasurePeriodValidator; import org.opencds.cqf.fhir.cr.measure.r4.R4MultiMeasureService; import org.opencds.cqf.fhir.cr.measure.r4.utils.R4MeasureServiceUtils; -import org.opencds.cqf.fhir.utility.monad.Eithers; +import org.opencds.cqf.fhir.cr.measure.common.MeasureReference; import org.opencds.cqf.fhir.utility.repository.ig.IgRepository; public class Measure { @@ -141,7 +141,7 @@ public When additionalData(Bundle additionalData) { public When evaluate() { this.operation = () -> service.evaluate( - Eithers.forMiddle3(new IdType("Measure", measureId)), + new MeasureReference.ById(new IdType("Measure", measureId)), periodStart, periodEnd, reportType, From 39d8d1a388fd3a14639a8fb3da528e19fc37ebf1 Mon Sep 17 00:00:00 2001 From: Jonathan Percival Date: Wed, 25 Mar 2026 08:49:52 -0600 Subject: [PATCH 03/10] spotless --- .../java/org/opencds/cqf/fhir/benchmark/measure/r4/Measure.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 90ba780914..a79d0446fa 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 @@ -15,9 +15,9 @@ 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.MeasurePeriodValidator; +import org.opencds.cqf.fhir.cr.measure.common.MeasureReference; import org.opencds.cqf.fhir.cr.measure.r4.R4MultiMeasureService; import org.opencds.cqf.fhir.cr.measure.r4.utils.R4MeasureServiceUtils; -import org.opencds.cqf.fhir.cr.measure.common.MeasureReference; import org.opencds.cqf.fhir.utility.repository.ig.IgRepository; public class Measure { From 323c5c63585375da99a9271852e2eea622e6154b Mon Sep 17 00:00:00 2001 From: Jonathan Percival 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;