From eeaa0e03c9679902ac284c58ae46647fc39a9d65 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Fri, 15 Nov 2024 10:12:26 -0500 Subject: [PATCH 1/3] First checkin. --- .../cr/measure/common/SubjectProvider.java | 5 ++ .../dstu3/Dstu3RepositorySubjectProvider.java | 12 +++ .../r4/R4RepositorySubjectProvider.java | 89 ++++++++++++++++++- .../measure/r4/MultiMeasureServiceTest.java | 21 +++++ .../r4/R4RepositorySubjectProviderTest.java | 78 ++++++++++++++++ 5 files changed, 201 insertions(+), 4 deletions(-) create mode 100644 cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/R4RepositorySubjectProviderTest.java diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/SubjectProvider.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/SubjectProvider.java index 9ae21c6a6..71ef72c5f 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/SubjectProvider.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/common/SubjectProvider.java @@ -8,4 +8,9 @@ public interface SubjectProvider { Stream getSubjects(Repository repository, MeasureEvalType measureEvalType, String subjectId); Stream getSubjects(Repository repository, MeasureEvalType measureEvalType, List subjectIds); + + // LUKETODO: consider these contracts carefully + Stream getSubjectsWithPartOf(Repository repository, MeasureEvalType measureEvalType, String subjectId); + + Stream getSubjectsWithPartOf(Repository repository, MeasureEvalType measureEvalType, List subjectIds); } diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3RepositorySubjectProvider.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3RepositorySubjectProvider.java index 6828814cc..9205b00b0 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3RepositorySubjectProvider.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/dstu3/Dstu3RepositorySubjectProvider.java @@ -72,4 +72,16 @@ public Stream getSubjects(Repository repository, MeasureEvalType measure return subjects.stream(); } + + @Override + public Stream getSubjectsWithPartOf(Repository repository, + MeasureEvalType measureEvalType, String subjectId) { + throw new UnsupportedOperationException("Not implemented yet"); + } + + @Override + public Stream getSubjectsWithPartOf(Repository repository, + MeasureEvalType measureEvalType, List subjectIds) { + throw new UnsupportedOperationException("Not implemented yet"); + } } diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4RepositorySubjectProvider.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4RepositorySubjectProvider.java index 66143a2ce..447495be1 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4RepositorySubjectProvider.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4RepositorySubjectProvider.java @@ -8,9 +8,11 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import java.util.stream.Stream; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; import org.hl7.fhir.r4.model.Group; import org.hl7.fhir.r4.model.Group.GroupMemberComponent; import org.hl7.fhir.r4.model.Group.GroupType; @@ -18,16 +20,26 @@ import org.hl7.fhir.r4.model.Patient; import org.opencds.cqf.fhir.api.Repository; import org.opencds.cqf.fhir.cr.measure.common.MeasureEvalType; +import org.opencds.cqf.fhir.cr.measure.common.MeasureEvaluator; import org.opencds.cqf.fhir.cr.measure.common.SubjectProvider; import org.opencds.cqf.fhir.utility.iterable.BundleIterator; import org.opencds.cqf.fhir.utility.iterable.BundleMappingIterable; import org.opencds.cqf.fhir.utility.search.Searches; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class R4RepositorySubjectProvider implements SubjectProvider { + private static final Logger logger = LoggerFactory.getLogger(MeasureEvaluator.class); + + private enum OrganizationMode { + NONE, + PART_OF + } + @Override public Stream getSubjects(Repository repository, MeasureEvalType measureEvalType, String subjectId) { - return getSubjects(repository, measureEvalType, Collections.singletonList(subjectId)); + return getSubjects(repository, measureEvalType, List.of(subjectId)); } @Override @@ -48,7 +60,7 @@ public Stream getSubjects(Repository repository, MeasureEvalType measure List subjects = new ArrayList<>(); subjectIds.forEach(subjectId -> { // add resource reference if missing - if (subjectId.indexOf("/") == -1) { + if (!subjectId.contains("/")) { subjectId = "Patient/".concat(subjectId); } // Single Patient @@ -91,7 +103,9 @@ else if (r.getType().equals(GroupType.PRACTITIONER)) { addPractitionerSubjectIds(practitioner, repository, subjects); } } - + // LUKETODO: can we have a Group with Organizations? + } else if (subjectId.startsWith("Organization")) { + subjects.addAll(getOrganizationSubjectIds(subjectId, repository)); } else { throw new IllegalArgumentException(String.format("Unsupported subjectId: %s", subjectIds)); } @@ -100,6 +114,21 @@ else if (r.getType().equals(GroupType.PRACTITIONER)) { return subjects.stream(); } + + @Override + public Stream getSubjectsWithPartOf(Repository repository, + MeasureEvalType measureEvalType, String subjectId) { + // LUKETODO: + throw new UnsupportedOperationException("Not implemented yet"); + } + + @Override + public Stream getSubjectsWithPartOf(Repository repository, + MeasureEvalType measureEvalType, List subjectIds) { + // LUKETODO: + throw new UnsupportedOperationException("Not implemented yet"); + } + private List getMembersInGroup(Group group) { List members = new ArrayList<>(); @@ -115,7 +144,7 @@ public void addPractitionerSubjectIds(String practitioner, Repository repository map.put( "general-practitioner", - Collections.singletonList(new ReferenceParam( + List.of(new ReferenceParam( practitioner.startsWith("Practitioner/") ? practitioner : "Practitioner/" + practitioner))); var bundle = repository.search(Bundle.class, Patient.class, map); @@ -128,4 +157,56 @@ public void addPractitionerSubjectIds(String practitioner, Repository repository patients.add(refString); } } + + private List getOrganizationSubjectIds( + String organization, + Repository repository ) { + + return Stream.concat( + getManagingOrganizationSubjectIds(organization, repository), + getPartOfSubjectIds(organization, repository) + ).toList(); + } + + private Stream getManagingOrganizationSubjectIds(String organization, Repository repository) { + final Map> searchParams = new HashMap<>(); + + searchParams.put("organization", Collections.singletonList(new ReferenceParam(organization))); + + var bundle = repository.search(Bundle.class, Patient.class, searchParams); + + var bundleEntries = bundle.getEntry(); + + if (bundleEntries == null || bundleEntries.isEmpty()) { + return Stream.empty(); + } + + return bundleEntries + .stream() + .map(BundleEntryComponent::getResource) + .map(idElement -> idElement.getResourceType() + "/" + idElement.getIdPart()); + } + + private Stream getPartOfSubjectIds(String organization, Repository repository) { + + final Map> searchParam = new HashMap<>(); + + searchParam.put( + "organization", + Collections.singletonList(new ReferenceParam("organization", organization) + .setChain("partof")) + ); + + return repository.search(Bundle.class, Patient.class, searchParam) + .getEntry() + .stream() + .map(BundleEntryComponent::getResource) + .filter(Patient.class::isInstance) + .map(Patient.class::cast) + // LUKETODO: do we keep this limitation or not? if so, test for it + // TODO: JM, address next link if populated in future interation of feature. + // if results expand beyond paging limit of a bundle, a warning will pop to the user. + // This is unlikely to ever be an issue in a real deployment, but should be addressed at some point. + .map(Patient::getIdPart); + } } diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java index 8cd5237cb..bceaa5256 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/MultiMeasureServiceTest.java @@ -995,4 +995,25 @@ void MultiMeasure_EightMeasures_ReporterNotAcceptedResource() { .hasSubjectReference("Practitioner/tester") .hasReporter("Patient/male-2022")); } + + // LUKETODO: consider adding resources to test Bundle for richer test cases, such as multiple orgs, patients, partofs, etc + // LUKETODO: one org ... one patient managingOrganization pointing to that org + // LUKETODO: one org with part of pointing to the first org... one patient managingOrganization pointing to the second org + @Test + void MultiMeasure_EightMeasures_SubjectOrganization() { + var when = GIVEN_REPO + .when() + .measureId("MinimalProportionNoBasisSingleGroup") + .periodStart("2024-01-01") + .periodEnd("2024-12-31") + .reportType("population") + .subject("Organization/tester") + .evaluate(); + + when.then() + .hasMeasureReportCount(1) + .measureReport("http://example.com/Measure/MinimalProportionNoBasisSingleGroup") + .hasReportType("Summary") + .hasSubjectReference("Organization/tester"); + } } diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/R4RepositorySubjectProviderTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/R4RepositorySubjectProviderTest.java new file mode 100644 index 000000000..a416f15db --- /dev/null +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/R4RepositorySubjectProviderTest.java @@ -0,0 +1,78 @@ +package org.opencds.cqf.fhir.cr.measure.r4; + +import ca.uhn.fhir.context.FhirContext; +import org.hamcrest.Matchers; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.Organization; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Reference; +import org.hl7.fhir.r4.model.ResourceType; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.opencds.cqf.fhir.api.Repository; +import org.opencds.cqf.fhir.cr.measure.common.MeasureEvalType; +import org.opencds.cqf.fhir.utility.repository.InMemoryFhirRepository; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; + +class R4RepositorySubjectProviderTest { + + private static final String PAT_ID_1 = "pat1"; + private static final String PAT_ID_2 = "pat2"; + private static final String ORG_ID_1 = "org1"; + private static final String ORG_ID_2 = "org2"; + + private final Repository repository = new InMemoryFhirRepository(FhirContext.forR4Cached()); + private final R4RepositorySubjectProvider testSubject = new R4RepositorySubjectProvider(); + + @BeforeEach + void beforeEach() { + final Organization org1 = (Organization)new Organization() + .setId(new IdType(ResourceType.Organization.toString(), ORG_ID_1)); + final IIdType orgId1 = repository.update(org1).getId().toUnqualifiedVersionless(); + + final Organization org2 = (Organization)new Organization() + .setPartOf(new Reference(orgId1.toUnqualifiedVersionless().getValue())) + .setId(new IdType(ResourceType.Organization.toString(), ORG_ID_2)); + + final IIdType orgId2 = repository.update(org2).getId().toUnqualifiedVersionless(); + + final Patient patient1 = new Patient(); + patient1.setId(PAT_ID_1); + patient1.setManagingOrganization(new Reference(orgId1.toUnqualifiedVersionless().getValue())); + + final Patient patient2 = new Patient(); + patient2.setId(PAT_ID_2); + patient2.setManagingOrganization(new Reference(orgId2.toUnqualifiedVersionless().getValue())); + + final IIdType patientId1 = repository.update(patient1).getId().toUnqualifiedVersionless(); + final IIdType patientId2 = repository.update(patient2).getId().toUnqualifiedVersionless(); + } + + public static Stream getSubjectsParams() { + return Stream.of( + Arguments.of(MeasureEvalType.SUBJECT, List.of(resourcify(ResourceType.Organization, ORG_ID_1), Stream.of(PAT_ID_1, PAT_ID_2).map(id -> resourcify(ResourceType.Patient, id)).toList())) + ); + } + + @ParameterizedTest + @MethodSource("getSubjectsParams") + void getSubjects(MeasureEvalType measureEvalType, List subjectIds, List expectedSubjects) { + final List actualSubjects = testSubject.getSubjects(repository, measureEvalType, + subjectIds).collect(Collectors.toList()); + + assertThat(actualSubjects, equalTo(expectedSubjects)); + } + + private static String resourcify(ResourceType resourceType, String rawId) { + return String.format("%s/%s", resourceType.toString(), rawId); + } +} \ No newline at end of file From e7deda806e0224b79dd5cf33971bcff8c3ba41f4 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Mon, 18 Nov 2024 14:29:22 -0500 Subject: [PATCH 2/3] Introduce SubjectProviderOptions and associate it with MeasureEvaluationOptions. Use conditional logic in R4RepositorySubjectProvider to choose whether to perform a chained partOf query. --- .../cr/measure/MeasureEvaluationOptions.java | 19 ++++++++++++--- .../cr/measure/SubjectProviderOptions.java | 16 +++++++++++++ .../cr/measure/r4/R4CareGapsProcessor.java | 3 ++- .../cr/measure/r4/R4CollectDataService.java | 4 +++- .../fhir/cr/measure/r4/R4MeasureService.java | 5 +++- .../cr/measure/r4/R4MultiMeasureService.java | 2 +- .../r4/R4RepositorySubjectProvider.java | 23 +++++++++++++------ 7 files changed, 58 insertions(+), 14 deletions(-) create mode 100644 cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/SubjectProviderOptions.java diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/MeasureEvaluationOptions.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/MeasureEvaluationOptions.java index e73ca6266..c42f19900 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/MeasureEvaluationOptions.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/MeasureEvaluationOptions.java @@ -14,6 +14,7 @@ public static MeasureEvaluationOptions defaultOptions() { private boolean isValidationEnabled = false; private Map validationProfiles = new HashMap<>(); + private SubjectProviderOptions subjectProviderOptions; private EvaluationSettings evaluationSettings = null; @@ -21,23 +22,35 @@ public boolean isValidationEnabled() { return this.isValidationEnabled; } - public void setValidationEnabled(boolean enableValidation) { + public MeasureEvaluationOptions setValidationEnabled(boolean enableValidation) { this.isValidationEnabled = enableValidation; + return this; } public Map getValidationProfiles() { return validationProfiles; } - public void setValidationProfiles(Map validationProfiles) { + public MeasureEvaluationOptions setValidationProfiles(Map validationProfiles) { this.validationProfiles = validationProfiles; + return this; } - public void setEvaluationSettings(EvaluationSettings evaluationSettings) { + public MeasureEvaluationOptions setEvaluationSettings(EvaluationSettings evaluationSettings) { this.evaluationSettings = evaluationSettings; + return this; } public EvaluationSettings getEvaluationSettings() { return this.evaluationSettings; } + + public SubjectProviderOptions getSubjectProviderOptions() { + return subjectProviderOptions; + } + + public MeasureEvaluationOptions setSubjectProviderOptions(SubjectProviderOptions theSubjectProviderOptions) { + this.subjectProviderOptions = theSubjectProviderOptions; + return this; + } } diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/SubjectProviderOptions.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/SubjectProviderOptions.java new file mode 100644 index 000000000..ea917de94 --- /dev/null +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/SubjectProviderOptions.java @@ -0,0 +1,16 @@ +package org.opencds.cqf.fhir.cr.measure; + +// LUKETODO: javadoc +public class SubjectProviderOptions { + + private boolean isPartOfEnabled; + + public boolean isPartOfEnabled() { + return isPartOfEnabled; + } + + public SubjectProviderOptions setPartOfEnabled(boolean thePartOfEnabled) { + isPartOfEnabled = thePartOfEnabled; + return this; + } +} 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 9509d9536..ac0736768 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 @@ -41,6 +41,7 @@ public class R4CareGapsProcessor { private final Map configuredResources = new HashMap<>(); private final R4MeasureServiceUtils r4MeasureServiceUtils; private final R4CareGapsBundleBuilder r4CareGapsBundleBuilder; + private final R4RepositorySubjectProvider subjectProvider; public R4CareGapsProcessor( CareGapsProperties careGapsProperties, @@ -59,6 +60,7 @@ public R4CareGapsProcessor( serverBase, configuredResources, measurePeriodValidator); + subjectProvider = new R4RepositorySubjectProvider(measureEvaluationOptions.getSubjectProviderOptions()); } public Parameters getCareGapsReport( @@ -125,7 +127,6 @@ protected List resolveMeasure(List getSubjects(String subject) { - R4RepositorySubjectProvider subjectProvider = new R4RepositorySubjectProvider(); var subjects = subjectProvider.getSubjects(repository, null, subject).collect(Collectors.toList()); if (!subjects.isEmpty()) { ourLog.info(String.format("care-gaps report requested for: %s subjects.", subjects.size())); 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 6b5547982..9172c0b58 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 @@ -23,10 +23,13 @@ public class R4CollectDataService { private final Repository repository; private final MeasureEvaluationOptions measureEvaluationOptions; + private final R4RepositorySubjectProvider subjectProvider; public R4CollectDataService(Repository repository, MeasureEvaluationOptions measureEvaluationOptions) { this.repository = repository; this.measureEvaluationOptions = measureEvaluationOptions; + subjectProvider = new R4RepositorySubjectProvider( + measureEvaluationOptions.getSubjectProviderOptions()); } /** @@ -57,7 +60,6 @@ public Parameters collectData( String practitioner) { Parameters parameters = new Parameters(); - var subjectProvider = new R4RepositorySubjectProvider(); var processor = new R4MeasureProcessor(this.repository, this.measureEvaluationOptions, subjectProvider); // getSubjects diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java index 28081f866..5cf30b3c8 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MeasureService.java @@ -22,6 +22,7 @@ public class R4MeasureService implements R4MeasureEvaluatorSingle { private final Repository repository; private final MeasureEvaluationOptions measureEvaluationOptions; private final MeasurePeriodValidator measurePeriodValidator; + private R4RepositorySubjectProvider subjectProvider; public R4MeasureService( Repository repository, @@ -30,6 +31,8 @@ public R4MeasureService( this.repository = repository; this.measureEvaluationOptions = measureEvaluationOptions; this.measurePeriodValidator = measurePeriodValidator; + this.subjectProvider = new R4RepositorySubjectProvider( + measureEvaluationOptions.getSubjectProviderOptions()); } @Override @@ -51,7 +54,7 @@ public MeasureReport evaluate( measurePeriodValidator.validatePeriodStartAndEnd(periodStart, periodEnd); var repo = Repositories.proxy(repository, true, dataEndpoint, contentEndpoint, terminologyEndpoint); - var processor = new R4MeasureProcessor(repo, this.measureEvaluationOptions, new R4RepositorySubjectProvider()); + var processor = new R4MeasureProcessor(repo, this.measureEvaluationOptions, subjectProvider); R4MeasureServiceUtils r4MeasureServiceUtils = new R4MeasureServiceUtils(repository); r4MeasureServiceUtils.ensureSupplementalDataElementSearchParameter(); diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4MultiMeasureService.java index 5559085f7..0e7c34369 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,7 +54,7 @@ public R4MultiMeasureService( this.measurePeriodValidator = measurePeriodValidator; this.serverBase = serverBase; - subjectProvider = new R4RepositorySubjectProvider(); + subjectProvider = new R4RepositorySubjectProvider(measureEvaluationOptions.getSubjectProviderOptions()); r4Processor = new R4MeasureProcessor(repository, this.measureEvaluationOptions, subjectProvider); diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4RepositorySubjectProvider.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4RepositorySubjectProvider.java index 447495be1..6151f32fa 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4RepositorySubjectProvider.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4RepositorySubjectProvider.java @@ -10,6 +10,7 @@ import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; +import jakarta.annotation.Nullable; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; @@ -17,8 +18,11 @@ import org.hl7.fhir.r4.model.Group.GroupMemberComponent; import org.hl7.fhir.r4.model.Group.GroupType; import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.Organization; import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.ResourceType; import org.opencds.cqf.fhir.api.Repository; +import org.opencds.cqf.fhir.cr.measure.SubjectProviderOptions; import org.opencds.cqf.fhir.cr.measure.common.MeasureEvalType; import org.opencds.cqf.fhir.cr.measure.common.MeasureEvaluator; import org.opencds.cqf.fhir.cr.measure.common.SubjectProvider; @@ -32,14 +36,15 @@ public class R4RepositorySubjectProvider implements SubjectProvider { private static final Logger logger = LoggerFactory.getLogger(MeasureEvaluator.class); - private enum OrganizationMode { - NONE, - PART_OF + private final SubjectProviderOptions subjectProviderOptions; + + public R4RepositorySubjectProvider(SubjectProviderOptions subjectProviderOptions) { + this.subjectProviderOptions = subjectProviderOptions; } @Override - public Stream getSubjects(Repository repository, MeasureEvalType measureEvalType, String subjectId) { - return getSubjects(repository, measureEvalType, List.of(subjectId)); + public Stream getSubjects(Repository repository, MeasureEvalType measureEvalType, @Nullable String subjectId) { + return getSubjects(repository, measureEvalType, Collections.singletonList(subjectId)); } @Override @@ -165,7 +170,7 @@ private List getOrganizationSubjectIds( return Stream.concat( getManagingOrganizationSubjectIds(organization, repository), getPartOfSubjectIds(organization, repository) - ).toList(); + ).collect(Collectors.toUnmodifiableList()); } private Stream getManagingOrganizationSubjectIds(String organization, Repository repository) { @@ -189,6 +194,10 @@ private Stream getManagingOrganizationSubjectIds(String organization, Re private Stream getPartOfSubjectIds(String organization, Repository repository) { + if (! subjectProviderOptions.isPartOfEnabled()) { + return Stream.empty(); + } + final Map> searchParam = new HashMap<>(); searchParam.put( @@ -207,6 +216,6 @@ private Stream getPartOfSubjectIds(String organization, Repository repos // TODO: JM, address next link if populated in future interation of feature. // if results expand beyond paging limit of a bundle, a warning will pop to the user. // This is unlikely to ever be an issue in a real deployment, but should be addressed at some point. - .map(Patient::getIdPart); + .map(idElement -> String.format("%s/%s", ResourceType.Patient, idElement.getIdPart())); } } From e3a4c5797f08a39dfde45a3c1b06b0f3c3a471c1 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Mon, 25 Nov 2024 09:56:52 -0500 Subject: [PATCH 3/3] More tweaks. --- .../r4/R4RepositorySubjectProvider.java | 8 +++-- .../cqf/fhir/cr/measure/r4/Measure.java | 6 +++- .../cqf/fhir/cr/measure/r4/MultiMeasure.java | 6 +++- .../r4/R4RepositorySubjectProviderTest.java | 31 ++++++++++++++----- 4 files changed, 40 insertions(+), 11 deletions(-) diff --git a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4RepositorySubjectProvider.java b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4RepositorySubjectProvider.java index 6151f32fa..4bbbbb74f 100644 --- a/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4RepositorySubjectProvider.java +++ b/cqf-fhir-cr/src/main/java/org/opencds/cqf/fhir/cr/measure/r4/R4RepositorySubjectProvider.java @@ -167,9 +167,13 @@ private List getOrganizationSubjectIds( String organization, Repository repository ) { + final List managingOrganizationSubjectIds = getManagingOrganizationSubjectIds( + organization, repository).collect(Collectors.toList()); + final List partOfSubjectIds = getPartOfSubjectIds(organization, repository).collect( + Collectors.toList()); return Stream.concat( - getManagingOrganizationSubjectIds(organization, repository), - getPartOfSubjectIds(organization, repository) + managingOrganizationSubjectIds.stream(), + partOfSubjectIds.stream() ).collect(Collectors.toUnmodifiableList()); } 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 fc2ff4fa6..f9d93dc3a 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 @@ -104,6 +104,7 @@ public static class Given { private Repository repository; private MeasureEvaluationOptions evaluationOptions; private MeasurePeriodValidator measurePeriodValidator; + private R4RepositorySubjectProvider subjectProvider; public Given() { this.evaluationOptions = MeasureEvaluationOptions.defaultOptions(); @@ -119,6 +120,9 @@ public Given() { .setValuesetExpansionMode(VALUESET_EXPANSION_MODE.PERFORM_NAIVE_EXPANSION); this.measurePeriodValidator = new MeasurePeriodValidator(); + + this.subjectProvider = new R4RepositorySubjectProvider( + evaluationOptions.getSubjectProviderOptions()); } public Given repository(Repository repository) { @@ -140,7 +144,7 @@ public Given evaluationOptions(MeasureEvaluationOptions evaluationOptions) { } private R4MeasureProcessor buildProcessor() { - return new R4MeasureProcessor(repository, evaluationOptions, new R4RepositorySubjectProvider()); + return new R4MeasureProcessor(repository, evaluationOptions, subjectProvider); } private R4MeasureService buildMeasureService() { 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 03bf79349..a0855e79a 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 @@ -92,6 +92,7 @@ public static class Given { private MeasureEvaluationOptions evaluationOptions; private String serverBase; private MeasurePeriodValidator measurePeriodValidator; + private R4RepositorySubjectProvider subjectProvider; public Given() { this.evaluationOptions = MeasureEvaluationOptions.defaultOptions(); @@ -109,6 +110,9 @@ public Given() { this.serverBase = "http://localhost"; this.measurePeriodValidator = new MeasurePeriodValidator(); + + this.subjectProvider = new R4RepositorySubjectProvider( + evaluationOptions.getSubjectProviderOptions()); } public MultiMeasure.Given repository(Repository repository) { @@ -134,7 +138,7 @@ public MultiMeasure.Given serverBase(String serverBase) { } private R4MeasureProcessor buildProcessor() { - return new R4MeasureProcessor(repository, evaluationOptions, new R4RepositorySubjectProvider()); + return new R4MeasureProcessor(repository, evaluationOptions, subjectProvider); } private R4MultiMeasureService buildMeasureService() { diff --git a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/R4RepositorySubjectProviderTest.java b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/R4RepositorySubjectProviderTest.java index a416f15db..e3e3ab8bb 100644 --- a/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/R4RepositorySubjectProviderTest.java +++ b/cqf-fhir-cr/src/test/java/org/opencds/cqf/fhir/cr/measure/r4/R4RepositorySubjectProviderTest.java @@ -1,7 +1,6 @@ package org.opencds.cqf.fhir.cr.measure.r4; import ca.uhn.fhir.context.FhirContext; -import org.hamcrest.Matchers; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.Organization; @@ -9,11 +8,11 @@ import org.hl7.fhir.r4.model.Reference; import org.hl7.fhir.r4.model.ResourceType; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.opencds.cqf.fhir.api.Repository; +import org.opencds.cqf.fhir.cr.measure.SubjectProviderOptions; import org.opencds.cqf.fhir.cr.measure.common.MeasureEvalType; import org.opencds.cqf.fhir.utility.repository.InMemoryFhirRepository; import java.util.List; @@ -30,8 +29,10 @@ class R4RepositorySubjectProviderTest { private static final String ORG_ID_1 = "org1"; private static final String ORG_ID_2 = "org2"; + private static final R4RepositorySubjectProvider TEST_SUBJECT_ENABLE_PART_OF = new R4RepositorySubjectProvider(new SubjectProviderOptions().setPartOfEnabled(true)); + private static final R4RepositorySubjectProvider TEST_SUBJECT_DISABLE_PART_OF = new R4RepositorySubjectProvider(new SubjectProviderOptions().setPartOfEnabled(false)); + private final Repository repository = new InMemoryFhirRepository(FhirContext.forR4Cached()); - private final R4RepositorySubjectProvider testSubject = new R4RepositorySubjectProvider(); @BeforeEach void beforeEach() { @@ -53,19 +54,35 @@ void beforeEach() { patient2.setId(PAT_ID_2); patient2.setManagingOrganization(new Reference(orgId2.toUnqualifiedVersionless().getValue())); - final IIdType patientId1 = repository.update(patient1).getId().toUnqualifiedVersionless(); - final IIdType patientId2 = repository.update(patient2).getId().toUnqualifiedVersionless(); + repository.update(patient1).getId().toUnqualifiedVersionless(); + repository.update(patient2).getId().toUnqualifiedVersionless(); } public static Stream getSubjectsParams() { return Stream.of( - Arguments.of(MeasureEvalType.SUBJECT, List.of(resourcify(ResourceType.Organization, ORG_ID_1), Stream.of(PAT_ID_1, PAT_ID_2).map(id -> resourcify(ResourceType.Patient, id)).toList())) + Arguments.of( + TEST_SUBJECT_ENABLE_PART_OF, + MeasureEvalType.SUBJECT, + List.of(resourcify(ResourceType.Organization, ORG_ID_1)), + // LUKETODO: comment about why this doesn't work + Stream.of(PAT_ID_1, PAT_ID_2) + .map(id -> resourcify(ResourceType.Patient, id)) + .collect(Collectors.toUnmodifiableList()) + ), + Arguments.of( + TEST_SUBJECT_DISABLE_PART_OF, + MeasureEvalType.SUBJECT, + List.of(resourcify(ResourceType.Organization, ORG_ID_1)), + Stream.of(PAT_ID_1) + .map(id -> resourcify(ResourceType.Patient, id)) + .collect(Collectors.toUnmodifiableList()) + ) ); } @ParameterizedTest @MethodSource("getSubjectsParams") - void getSubjects(MeasureEvalType measureEvalType, List subjectIds, List expectedSubjects) { + void getSubjects(R4RepositorySubjectProvider testSubject, MeasureEvalType measureEvalType, List subjectIds, List expectedSubjects) { final List actualSubjects = testSubject.getSubjects(repository, measureEvalType, subjectIds).collect(Collectors.toList());