Skip to content

Commit

Permalink
Implement R4RepositorySubjectProvider logic for managingOrganization …
Browse files Browse the repository at this point in the history
…and partOf. Draft unit test class. No conditional logic for partOf yet.
  • Loading branch information
lukedegruchy committed Nov 15, 2024
1 parent f8b5dab commit 16bb417
Show file tree
Hide file tree
Showing 3 changed files with 161 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,29 @@
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;
import org.hl7.fhir.r4.model.IdType;
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);

@Override
public Stream<String> getSubjects(Repository repository, MeasureEvalType measureEvalType, String subjectId) {
return getSubjects(repository, measureEvalType, Collections.singletonList(subjectId));
return getSubjects(repository, measureEvalType, List.of(subjectId));
}

@Override
Expand All @@ -48,7 +54,7 @@ public Stream<String> getSubjects(Repository repository, MeasureEvalType measure
List<String> subjects = new ArrayList<>();
subjectIds.forEach(subjectId -> {
// add resource reference if missing
if (subjectId.indexOf("/") == -1) {
if (!subjectId.contains("/")) {
subjectId = "Patient/".concat(subjectId);
}
// Single Patient
Expand Down Expand Up @@ -91,7 +97,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));
}
Expand All @@ -115,7 +123,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);
Expand All @@ -128,4 +136,49 @@ public void addPractitionerSubjectIds(String practitioner, Repository repository
patients.add(refString);
}
}

private List<String> getOrganizationSubjectIds(String organization, Repository repository) {

return Stream.concat(
getManagingOrganizationSubjectIds(organization, repository),
getPartOfSubjectIds(organization, repository))
.toList();
}

private Stream<String> getManagingOrganizationSubjectIds(String organization, Repository repository) {
final Map<String, List<IQueryParameterType>> 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<String> getPartOfSubjectIds(String organization, Repository repository) {

final Map<String, List<IQueryParameterType>> 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -931,4 +931,27 @@ 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");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package org.opencds.cqf.fhir.cr.measure.r4;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;

import ca.uhn.fhir.context.FhirContext;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
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.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;

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<Arguments> 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<String> subjectIds, List<String> expectedSubjects) {
final List<String> 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);
}
}

0 comments on commit 16bb417

Please sign in to comment.