Skip to content

Commit

Permalink
Do not error for INDIVIDUAL reports for multiple SDEs. Centralize the…
Browse files Browse the repository at this point in the history
… logic for converting report types to eval types. (#595)

* Do not error out when building an INDIVIDUAL report for multiple SDEs.  Centralize the logic for converting report types to eval types.

* Add new proper Measure test to prove effect of production code change.

* Add more scenarios to test.

* Spotless.
  • Loading branch information
lukedegruchy authored Nov 25, 2024
1 parent 187daa0 commit 70801bc
Show file tree
Hide file tree
Showing 12 changed files with 331 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.opencds.cqf.fhir.cr.measure.common.SubjectProvider;
import org.opencds.cqf.fhir.cr.measure.dstu3.Dstu3MeasureProcessor;
import org.opencds.cqf.fhir.cr.measure.r4.R4MeasureProcessor;
import org.opencds.cqf.fhir.cr.measure.r4.utils.R4MeasureServiceUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
Expand All @@ -21,7 +22,10 @@ Dstu3MeasureProcessor dstu3MeasureProcessor(

@Bean
R4MeasureProcessor r4MeasureProcessor(
Repository repository, MeasureEvaluationOptions measureEvaluationOptions, SubjectProvider subjectProvider) {
return new R4MeasureProcessor(repository, measureEvaluationOptions, subjectProvider);
Repository repository,
MeasureEvaluationOptions measureEvaluationOptions,
SubjectProvider subjectProvider,
R4MeasureServiceUtils measureServiceUtils) {
return new R4MeasureProcessor(repository, measureEvaluationOptions, subjectProvider, measureServiceUtils);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,24 @@
import org.opencds.cqf.fhir.api.Repository;
import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions;
import org.opencds.cqf.fhir.cr.measure.common.MeasureEvalType;
import org.opencds.cqf.fhir.cr.measure.r4.utils.R4MeasureServiceUtils;
import org.opencds.cqf.fhir.utility.Ids;
import org.opencds.cqf.fhir.utility.monad.Eithers;

public class R4CollectDataService {
private final Repository repository;
private final MeasureEvaluationOptions measureEvaluationOptions;
private final R4RepositorySubjectProvider subjectProvider;
private final R4MeasureServiceUtils measureServiceUtils;

public R4CollectDataService(Repository repository, MeasureEvaluationOptions measureEvaluationOptions) {
public R4CollectDataService(
Repository repository,
MeasureEvaluationOptions measureEvaluationOptions,
R4MeasureServiceUtils measureServiceUtils) {
this.repository = repository;
this.measureEvaluationOptions = measureEvaluationOptions;
subjectProvider = new R4RepositorySubjectProvider(measureEvaluationOptions.getSubjectProviderOptions());
this.subjectProvider = new R4RepositorySubjectProvider(measureEvaluationOptions.getSubjectProviderOptions());
this.measureServiceUtils = measureServiceUtils;
}

/**
Expand Down Expand Up @@ -59,7 +65,8 @@ public Parameters collectData(
String practitioner) {

Parameters parameters = new Parameters();
var processor = new R4MeasureProcessor(this.repository, this.measureEvaluationOptions, subjectProvider);
var processor = new R4MeasureProcessor(
this.repository, this.measureEvaluationOptions, this.subjectProvider, this.measureServiceUtils);

// getSubjects
List<String> subjectList = getSubjects(subject, practitioner, subjectProvider);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.opencds.cqf.fhir.cr.measure.common.MeasureEvalType;
import org.opencds.cqf.fhir.cr.measure.common.SubjectProvider;
import org.opencds.cqf.fhir.cr.measure.r4.utils.R4DateHelper;
import org.opencds.cqf.fhir.cr.measure.r4.utils.R4MeasureServiceUtils;
import org.opencds.cqf.fhir.utility.Canonicals;
import org.opencds.cqf.fhir.utility.monad.Either3;
import org.opencds.cqf.fhir.utility.repository.FederatedRepository;
Expand All @@ -41,13 +42,18 @@ public class R4MeasureProcessor {
private final Repository repository;
private final MeasureEvaluationOptions measureEvaluationOptions;
private final SubjectProvider subjectProvider;
private final R4MeasureServiceUtils r4MeasureServiceUtils;

public R4MeasureProcessor(
Repository repository, MeasureEvaluationOptions measureEvaluationOptions, SubjectProvider subjectProvider) {
Repository repository,
MeasureEvaluationOptions measureEvaluationOptions,
SubjectProvider subjectProvider,
R4MeasureServiceUtils r4MeasureServiceUtils) {
this.repository = Objects.requireNonNull(repository);
this.measureEvaluationOptions =
measureEvaluationOptions != null ? measureEvaluationOptions : MeasureEvaluationOptions.defaultOptions();
this.subjectProvider = subjectProvider;
this.r4MeasureServiceUtils = r4MeasureServiceUtils;
}

public MeasureReport evaluateMeasure(
Expand All @@ -59,16 +65,7 @@ public MeasureReport evaluateMeasure(
IBaseBundle additionalData,
Parameters parameters) {

var evalType = MeasureEvalType.fromCode(
// validate in R4 accepted values
R4MeasureEvalType.fromCode(reportType)
.orElse(
// map null reportType parameter to evalType if no subject parameter is provided
subjectIds == null || subjectIds.isEmpty() || subjectIds.get(0) == null
? R4MeasureEvalType.POPULATION
: R4MeasureEvalType.SUBJECT)
.toCode())
.orElse(MeasureEvalType.SUBJECT);
var evalType = r4MeasureServiceUtils.getMeasureEvalType(reportType, subjectIds);

var actualRepo = this.repository;
if (additionalData != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -699,12 +699,6 @@ protected void buildSDE(BuilderContext bc, SdeDef sde) {
return;
}

// This is an individual report... shouldn't have more than one subject!
if (report.getType() == MeasureReport.MeasureReportType.INDIVIDUAL
&& sde.getResults().keySet().size() > 1) {
throw new IllegalArgumentException();
}

// Add all evaluated resources
for (var e : sde.getResults().entrySet()) {
addEvaluatedResourceReferences(bc, sde.id(), e.getValue().evaluatedResources());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,19 @@ public class R4MeasureService implements R4MeasureEvaluatorSingle {
private final Repository repository;
private final MeasureEvaluationOptions measureEvaluationOptions;
private final MeasurePeriodValidator measurePeriodValidator;
private R4RepositorySubjectProvider subjectProvider;
private final R4RepositorySubjectProvider subjectProvider;
private final R4MeasureServiceUtils measureServiceUtils;

public R4MeasureService(
Repository repository,
MeasureEvaluationOptions measureEvaluationOptions,
MeasurePeriodValidator measurePeriodValidator) {
MeasurePeriodValidator measurePeriodValidator,
R4MeasureServiceUtils measureServiceUtils) {
this.repository = repository;
this.measureEvaluationOptions = measureEvaluationOptions;
this.measurePeriodValidator = measurePeriodValidator;
this.subjectProvider = new R4RepositorySubjectProvider(measureEvaluationOptions.getSubjectProviderOptions());
this.measureServiceUtils = measureServiceUtils;
}

@Override
Expand All @@ -53,7 +56,8 @@ public MeasureReport evaluate(
measurePeriodValidator.validatePeriodStartAndEnd(periodStart, periodEnd);

var repo = Repositories.proxy(repository, true, dataEndpoint, contentEndpoint, terminologyEndpoint);
var processor = new R4MeasureProcessor(repo, this.measureEvaluationOptions, subjectProvider);
var processor = new R4MeasureProcessor(
repo, this.measureEvaluationOptions, this.subjectProvider, this.measureServiceUtils);

R4MeasureServiceUtils r4MeasureServiceUtils = new R4MeasureServiceUtils(repository);
r4MeasureServiceUtils.ensureSupplementalDataElementSearchParameter();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ public R4MultiMeasureService(

subjectProvider = new R4RepositorySubjectProvider(measureEvaluationOptions.getSubjectProviderOptions());

r4Processor = new R4MeasureProcessor(repository, this.measureEvaluationOptions, subjectProvider);
r4Processor = new R4MeasureProcessor(
repository, this.measureEvaluationOptions, subjectProvider, r4MeasureServiceUtils);

r4MeasureServiceUtils = new R4MeasureServiceUtils(repository);
}
Expand Down Expand Up @@ -84,16 +85,16 @@ public Bundle evaluate(
// if needing to use proxy repository, override constructors
repository = Repositories.proxy(repository, true, dataEndpoint, contentEndpoint, terminologyEndpoint);

r4Processor = new R4MeasureProcessor(repository, this.measureEvaluationOptions, subjectProvider);
r4Processor = new R4MeasureProcessor(
repository, this.measureEvaluationOptions, subjectProvider, r4MeasureServiceUtils);

r4MeasureServiceUtils = new R4MeasureServiceUtils(repository);
}
r4MeasureServiceUtils.ensureSupplementalDataElementSearchParameter();
List<Measure> measures = r4MeasureServiceUtils.getMeasures(measureId, measureIdentifier, measureUrl);
log.info("multi-evaluate-measure, measures to evaluate: {}", measures.size());

var evalType = MeasureEvalType.fromCode(reportType)
.orElse(subject == null || subject.isEmpty() ? MeasureEvalType.POPULATION : MeasureEvalType.SUBJECT);
var evalType = r4MeasureServiceUtils.getMeasureEvalType(reportType, subject);

// get subjects
var subjects = getSubjects(subjectProvider, subject);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.param.UriParam;
import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException;
import jakarta.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
Expand All @@ -29,6 +30,7 @@
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.Bundle;
Expand All @@ -45,8 +47,10 @@
import org.hl7.fhir.r4.model.SearchParameter;
import org.hl7.fhir.r4.model.StringType;
import org.opencds.cqf.fhir.api.Repository;
import org.opencds.cqf.fhir.cr.measure.common.MeasureEvalType;
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.Ids;

public class R4MeasureServiceUtils {
Expand Down Expand Up @@ -303,4 +307,34 @@ public void listThrowIllegalArgumentIfEmpty(List<String> value, String parameter
throw new IllegalArgumentException(parameterName + " parameter requires a value.");
}
}

public MeasureEvalType getMeasureEvalType(String reportType, @Nullable String subjectId) {
return convertToNonVersionedMeasureEvalTypeOrDefault(
getR4MeasureEvalType(reportType, Collections.singletonList(subjectId)));
}

public MeasureEvalType getMeasureEvalType(String reportType, List<String> subjectIds) {
return convertToNonVersionedMeasureEvalTypeOrDefault(getR4MeasureEvalType(reportType, subjectIds));
}

@Nonnull
public R4MeasureEvalType getR4MeasureEvalType(String reportType, List<String> subjectIds) {
return
// validate in R4 accepted values
R4MeasureEvalType.fromCode(reportType)
.orElse(
// map null reportType parameter to evalType if no subject parameter is provided
isSubjectListEffectivelyEmpty(subjectIds)
? R4MeasureEvalType.POPULATION
: R4MeasureEvalType.SUBJECT);
}

@Nonnull
public MeasureEvalType convertToNonVersionedMeasureEvalTypeOrDefault(R4MeasureEvalType r4MeasureEvalType) {
return MeasureEvalType.fromCode(r4MeasureEvalType.toCode()).orElse(MeasureEvalType.SUBJECT);
}

public boolean isSubjectListEffectivelyEmpty(List<String> subjectIds) {
return subjectIds == null || subjectIds.isEmpty() || subjectIds.get(0) == null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,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.r4.utils.R4MeasureServiceUtils;
import org.opencds.cqf.fhir.utility.repository.ig.IgRepository;

public class CollectData {
Expand Down Expand Up @@ -65,6 +66,7 @@ public static Given given() {
public static class Given {
private Repository repository;
private final MeasureEvaluationOptions evaluationOptions;
private final R4MeasureServiceUtils measureServiceUtils;

public Given() {
this.evaluationOptions = MeasureEvaluationOptions.defaultOptions();
Expand All @@ -78,6 +80,8 @@ public Given() {
.getEvaluationSettings()
.getTerminologySettings()
.setValuesetExpansionMode(VALUESET_EXPANSION_MODE.PERFORM_NAIVE_EXPANSION);

this.measureServiceUtils = new R4MeasureServiceUtils(repository);
}

public Given repository(Repository repository) {
Expand All @@ -93,7 +97,7 @@ public Given repositoryFor(String repositoryPath) {
}

private R4CollectDataService buildR4CollectDataService() {
return new R4CollectDataService(repository, evaluationOptions);
return new R4CollectDataService(repository, evaluationOptions, measureServiceUtils);
}

public When when() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions;
import org.opencds.cqf.fhir.cr.measure.common.MeasurePeriodValidator;
import org.opencds.cqf.fhir.cr.measure.r4.Measure.SelectedGroup.SelectedReference;
import org.opencds.cqf.fhir.cr.measure.r4.utils.R4MeasureServiceUtils;
import org.opencds.cqf.fhir.utility.monad.Eithers;
import org.opencds.cqf.fhir.utility.r4.ContainedHelper;
import org.opencds.cqf.fhir.utility.repository.ig.IgRepository;
Expand Down Expand Up @@ -112,7 +113,8 @@ public static Given given() {
public static class Given {
private Repository repository;
private MeasureEvaluationOptions evaluationOptions;
private MeasurePeriodValidator measurePeriodValidator;
private final MeasurePeriodValidator measurePeriodValidator;
private final R4MeasureServiceUtils measureServiceUtils;

public Given() {
this.evaluationOptions = MeasureEvaluationOptions.defaultOptions();
Expand All @@ -128,6 +130,8 @@ public Given() {
.setValuesetExpansionMode(VALUESET_EXPANSION_MODE.PERFORM_NAIVE_EXPANSION);

this.measurePeriodValidator = new MeasurePeriodValidator();

this.measureServiceUtils = new R4MeasureServiceUtils(repository);
}

public Given repository(Repository repository) {
Expand All @@ -149,7 +153,7 @@ public Given evaluationOptions(MeasureEvaluationOptions evaluationOptions) {
}

private R4MeasureService buildMeasureService() {
return new R4MeasureService(repository, evaluationOptions, measurePeriodValidator);
return new R4MeasureService(repository, evaluationOptions, measurePeriodValidator, measureServiceUtils);
}

public When when() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package org.opencds.cqf.fhir.cr.measure.r4;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.hasSize;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;

import jakarta.annotation.Nullable;
import org.hl7.fhir.r4.model.Extension;
import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.StringType;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.opencds.cqf.fhir.cr.measure.r4.Measure.Given;

public class MeasureMultipleSdeTest {
private static final Given GIVEN_MULTIPLE_SDE_MEASURE_REPO =
Measure.given().repositoryFor("BreastCancerScreeningFHIR");

@ParameterizedTest
@CsvSource(
value = {
"null,null",
"subject,null",
"subject-list,null",
"population,null",
"null,Patient/numer-EXM125",
"subject,Patient/numer-EXM125",
"subject-list,Patient/numer-EXM125",
"population,Patient/numer-EXM125",
"null,Patient/denom-EXM125",
"subject,Patient/denom-EXM125",
"subject-list,Patient/denom-EXM125",
"population,Patient/denom-EXM125",
"null,Patient/numer-EXM125",
"subject,Patient/numer-EXM125",
"subject-list,Patient/numer-EXM125",
"population,Patient/numer-EXM125",
},
nullValues = {"null"})
void evaluateSucceedsMultipleSdesReportTypeSubjectAndSubjectNull(
@Nullable String reportType, @Nullable String subject) {
var when = GIVEN_MULTIPLE_SDE_MEASURE_REPO
.when()
.reportType(reportType)
.subject(subject)
.measureId("measure-EXM108-8.3.000")
.evaluate();

var report = when.then().report();
assertNotNull(report);
assertEquals(1, report.getGroup().size());
assertEquals(4, report.getGroupFirstRep().getPopulation().size());

var extensions = report.getExtension();
assertThat(extensions.size(), greaterThanOrEqualTo(4));

var extensionValues = extensions.stream()
.map(Extension::getValue)
.filter(Reference.class::isInstance)
.map(Reference.class::cast)
.map(Reference::getExtension)
.filter(innerExtensions -> innerExtensions.size() == 1)
.map(innerExtensions -> innerExtensions.get(0))
.map(Extension::getValue)
.filter(StringType.class::isInstance)
.map(StringType.class::cast)
.map(StringType::getValue)
.distinct()
.toList();

assertThat(extensionValues, hasSize(3));
assertThat(extensionValues, containsInAnyOrder("sde-sex", "sde-race", "sde-ethnicity"));
}
}
Loading

0 comments on commit 70801bc

Please sign in to comment.