Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First checkin. #588

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,43 @@ public static MeasureEvaluationOptions defaultOptions() {

private boolean isValidationEnabled = false;
private Map<String, ValidationProfile> validationProfiles = new HashMap<>();
private SubjectProviderOptions subjectProviderOptions;

private EvaluationSettings evaluationSettings = null;

public boolean isValidationEnabled() {
return this.isValidationEnabled;
}

public void setValidationEnabled(boolean enableValidation) {
public MeasureEvaluationOptions setValidationEnabled(boolean enableValidation) {
this.isValidationEnabled = enableValidation;
return this;
}

public Map<String, ValidationProfile> getValidationProfiles() {
return validationProfiles;
}

public void setValidationProfiles(Map<String, ValidationProfile> validationProfiles) {
public MeasureEvaluationOptions setValidationProfiles(Map<String, ValidationProfile> 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;
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,9 @@ public interface SubjectProvider {
Stream<String> getSubjects(Repository repository, MeasureEvalType measureEvalType, String subjectId);

Stream<String> getSubjects(Repository repository, MeasureEvalType measureEvalType, List<String> subjectIds);

// LUKETODO: consider these contracts carefully
Stream<String> getSubjectsWithPartOf(Repository repository, MeasureEvalType measureEvalType, String subjectId);

Stream<String> getSubjectsWithPartOf(Repository repository, MeasureEvalType measureEvalType, List<String> subjectIds);
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,16 @@ public Stream<String> getSubjects(Repository repository, MeasureEvalType measure

return subjects.stream();
}

@Override
public Stream<String> getSubjectsWithPartOf(Repository repository,
MeasureEvalType measureEvalType, String subjectId) {
throw new UnsupportedOperationException("Not implemented yet");
}

@Override
public Stream<String> getSubjectsWithPartOf(Repository repository,
MeasureEvalType measureEvalType, List<String> subjectIds) {
throw new UnsupportedOperationException("Not implemented yet");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public class R4CareGapsProcessor {
private final Map<String, Resource> configuredResources = new HashMap<>();
private final R4MeasureServiceUtils r4MeasureServiceUtils;
private final R4CareGapsBundleBuilder r4CareGapsBundleBuilder;
private final R4RepositorySubjectProvider subjectProvider;

public R4CareGapsProcessor(
CareGapsProperties careGapsProperties,
Expand All @@ -59,6 +60,7 @@ public R4CareGapsProcessor(
serverBase,
configuredResources,
measurePeriodValidator);
subjectProvider = new R4RepositorySubjectProvider(measureEvaluationOptions.getSubjectProviderOptions());
}

public Parameters getCareGapsReport(
Expand Down Expand Up @@ -125,7 +127,6 @@ protected List<Measure> resolveMeasure(List<Either3<IdType, String, CanonicalTyp
}

protected List<String> 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()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}

/**
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -30,6 +31,8 @@ public R4MeasureService(
this.repository = repository;
this.measureEvaluationOptions = measureEvaluationOptions;
this.measurePeriodValidator = measurePeriodValidator;
this.subjectProvider = new R4RepositorySubjectProvider(
measureEvaluationOptions.getSubjectProviderOptions());
}

@Override
Expand All @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,42 @@
import java.util.HashMap;
import java.util.List;
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;
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.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;
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 final SubjectProviderOptions subjectProviderOptions;

public R4RepositorySubjectProvider(SubjectProviderOptions subjectProviderOptions) {
this.subjectProviderOptions = subjectProviderOptions;
}

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

Expand All @@ -48,7 +65,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 +108,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 @@ -100,6 +119,21 @@ else if (r.getType().equals(GroupType.PRACTITIONER)) {
return subjects.stream();
}


@Override
public Stream<String> getSubjectsWithPartOf(Repository repository,
MeasureEvalType measureEvalType, String subjectId) {
// LUKETODO:
throw new UnsupportedOperationException("Not implemented yet");
}

@Override
public Stream<String> getSubjectsWithPartOf(Repository repository,
MeasureEvalType measureEvalType, List<String> subjectIds) {
// LUKETODO:
throw new UnsupportedOperationException("Not implemented yet");
}

private List<String> getMembersInGroup(Group group) {
List<String> members = new ArrayList<>();

Expand All @@ -115,7 +149,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 +162,64 @@ public void addPractitionerSubjectIds(String practitioner, Repository repository
patients.add(refString);
}
}

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

final List<String> managingOrganizationSubjectIds = getManagingOrganizationSubjectIds(
organization, repository).collect(Collectors.toList());
final List<String> partOfSubjectIds = getPartOfSubjectIds(organization, repository).collect(
Collectors.toList());
return Stream.concat(
managingOrganizationSubjectIds.stream(),
partOfSubjectIds.stream()
).collect(Collectors.toUnmodifiableList());
}

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) {

if (! subjectProviderOptions.isPartOfEnabled()) {
return Stream.empty();
}

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(idElement -> String.format("%s/%s", ResourceType.Patient, idElement.getIdPart()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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) {
Expand All @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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) {
Expand All @@ -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() {
Expand Down
Loading