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

542 care gaps non document mode #543

Merged
merged 10 commits into from
Oct 7, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import jakarta.annotation.Nullable;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
Expand Down Expand Up @@ -94,11 +93,7 @@ public R4CareGapsBundleBuilder(
}

public List<Parameters.ParametersParameterComponent> makePatientBundles(
@Nullable ZonedDateTime periodStart,
@Nullable ZonedDateTime periodEnd,
List<String> subjects,
List<String> statuses,
List<IdType> measureIds) {
List<String> subjects, R4CareGapsParameters r4CareGapsParameters, List<IdType> measureId) {

// retrieve reporter from configuration
String reporter = RESOURCE_TYPE_ORGANIZATION.concat("/" + careGapsProperties.getCareGapsReporter());
Expand All @@ -108,11 +103,11 @@ public List<Parameters.ParametersParameterComponent> makePatientBundles(
for (String subject : subjects) {
// Measure Reports
Bundle result = r4MultiMeasureService.evaluate(
measureIds,
measureId,
null,
null,
periodStart,
periodEnd,
r4CareGapsParameters.getPeriodStart(),
r4CareGapsParameters.getPeriodEnd(),
MeasureEvalType.SUBJECT.toCode(),
subject,
null,
Expand All @@ -126,8 +121,10 @@ public List<Parameters.ParametersParameterComponent> makePatientBundles(
// Patient, subject comes in as format "ResourceType/[id]", no resourceType required to be specified.
var patient = repository.read(Patient.class, new IdType(subject));

Bundle bundle;
// finalize patient Bundle results
var bundle = makePatientBundle(result, statuses, patient);
bundle = makePatientBundle(
result, r4CareGapsParameters.getStatus(), patient, r4CareGapsParameters.isNotDocument());

// add parameter with results
if (bundle != null && bundle.hasEntry()) {
Expand All @@ -136,14 +133,18 @@ public List<Parameters.ParametersParameterComponent> makePatientBundles(
}
return paramResults;
}

/**
* method to use for creating Care-Gaps Bundle per Patient. IsDocumentMode will control which
* resources are added or excluded from the final bundle
*/
@Nullable
public Bundle makePatientBundle(Bundle bundle, List<String> statuses, Patient patient) {
public Bundle makePatientBundle(Bundle bundle, List<String> statuses, Patient patient, boolean notDocument) {
Map<String, Resource> evalPlusSDE = new HashMap<>();
List<DetectedIssue> detectedIssues = new ArrayList<>();
List<MeasureReport> measureReports = new ArrayList<>();
var gapEvaluator = new R4CareGapStatusEvaluator();
Composition composition = getComposition(patient);
var composition = getComposition(patient, notDocument);

// get Evaluation Bundle Results
for (BundleEntryComponent entry : bundle.getEntry()) {
MeasureReport mr = (MeasureReport) entry.getResource();
Expand All @@ -154,28 +155,32 @@ public Bundle makePatientBundle(Bundle bundle, List<String> statuses, Patient pa
var gapStatus = gapEvaluator.getGroupGapStatus(measure, mr);
var filteredGapStatus = filteredGapStatus(gapStatus, statuses);
if (!filteredGapStatus.isEmpty()) {
// add Report to final Care-gap report
measureReports.add(mr);
if (!notDocument) {
// add document mode required elements to final Care-gap report
measureReports.add(mr);
populateEvaluatedResources(mr, evalPlusSDE);
populateSDEResources(mr, evalPlusSDE);
}
// Issue(s) Detected from MeasureReport
for (Map.Entry<String, CareGapsStatusCode> item : filteredGapStatus.entrySet()) {
String groupId = item.getKey();
CareGapsStatusCode careGapsStatusCode = item.getValue();
// create DetectedIssue per gap-status and MeasureReport.groupId
DetectedIssue issue = getDetectedIssue(patient, mr, groupId, careGapsStatusCode);
DetectedIssue issue =
getDetectedIssue(patient, mr, groupId, careGapsStatusCode, measure, notDocument);
// add DetectedIssue list to set on Bundle
detectedIssues.add(issue);
// add sections for DetectedIssues created
composition.addSection(getSection(measure, mr, issue, careGapsStatusCode));
if (!notDocument) {
composition.addSection(getSection(measure, mr, issue, careGapsStatusCode));
}
}
// Track evaluated Resources
populateEvaluatedResources(mr, evalPlusSDE);
populateSDEResources(mr, evalPlusSDE);
}
}

if (!measureReports.isEmpty()) {
if (!detectedIssues.isEmpty()) {
// only add if a DetectedIssue is found and has MeasureReports
return addBundleEntries(serverBase, composition, detectedIssues, measureReports, evalPlusSDE);
return addBundleEntries(serverBase, composition, detectedIssues, measureReports, evalPlusSDE, notDocument);
} else {
// return nothing if not-applicable
return null;
Expand Down Expand Up @@ -215,18 +220,20 @@ private Composition.SectionComponent getSection(
.build();
}

private Composition getComposition(Patient patient) {
return new CompositionBuilder<>(Composition.class)
.withProfile(CARE_GAPS_COMPOSITION_PROFILE)
.withType(CARE_GAPS_CODES.get("http://loinc.org/96315-7"))
.withStatus(Composition.CompositionStatus.FINAL.toString())
.withTitle("Care Gap Report for " + Ids.simplePart(patient))
.withSubject(Ids.simple(patient))
.withAuthor(Ids.simple(configuredResources.get("care_gaps_composition_section_author")))
// .withCustodian(organization) // TODO: Optional: identifies the organization
// who is responsible for ongoing maintenance of and accessing to this gaps in
// care report. Add as a setting and optionally read if it's there.
.build();
@Nullable
private Composition getComposition(Patient patient, boolean notDocument) {
Composition composition = null;
if (!notDocument) {
composition = new CompositionBuilder<>(Composition.class)
.withProfile(CARE_GAPS_COMPOSITION_PROFILE)
.withType(CARE_GAPS_CODES.get("http://loinc.org/96315-7"))
.withStatus(Composition.CompositionStatus.FINAL.toString())
.withTitle("Care Gap Report for " + Ids.simplePart(patient))
.withSubject(Ids.simple(patient))
.withAuthor(Ids.simple(configuredResources.get("care_gaps_composition_section_author")))
.build();
}
return composition;
}

private boolean isMultiRateMeasure(MeasureReport measureReport) {
Expand All @@ -237,7 +244,9 @@ private DetectedIssue getDetectedIssue(
Patient patient,
MeasureReport measureReport,
String measureReportGroupId,
CareGapsStatusCode careGapsStatusCode) {
CareGapsStatusCode careGapsStatusCode,
Measure measure,
boolean notDocument) {

var detectedIssue = new DetectedIssueBuilder<>(DetectedIssue.class)
.withProfile(CARE_GAPS_DETECTED_ISSUE_PROFILE)
Expand All @@ -254,6 +263,11 @@ private DetectedIssue getDetectedIssue(
careGapsStatusCode.toDisplayString())))
.build();

// add period from MeasureReport for Identified period of Issue
detectedIssue.setIdentified(measureReport.getPeriod());
// add Measure reference as Implicated reference for Issue
detectedIssue.setImplicated(Collections.singletonList(new Reference(Ids.simple(measure))));

if (measureReportGroupId != null && isMultiRateMeasure(measureReport)) {
// MeasureReportGroupComponent.id value set here to differentiate between DetectedIssue resources for the
// same MeasureReport
Expand All @@ -262,6 +276,14 @@ private DetectedIssue getDetectedIssue(
groupIdExt.setValue(new StringType(measureReportGroupId));
detectedIssue.setExtension(Collections.singletonList(groupIdExt));
}
if (notDocument) {
// add Report as contained resource
detectedIssue.setContained(Collections.singletonList(measureReport));
// update evidence reference to '#' prefixed reference to indicate it is contained.
detectedIssue
.getEvidenceFirstRep()
.setDetail(Collections.singletonList(new Reference("#" + measureReport.getId())));
}
return detectedIssue;
}

Expand Down Expand Up @@ -326,13 +348,20 @@ private Bundle addBundleEntries(
Composition composition,
List<DetectedIssue> detectedIssues,
List<MeasureReport> measureReports,
Map<String, Resource> evalPlusSDEs) {
Map<String, Resource> evalPlusSDEs,
boolean notDocument) {
Bundle reportBundle = makeNewBundle();
reportBundle.addEntry(getBundleEntry(serverBase, composition));
measureReports.forEach(report -> reportBundle.addEntry(getBundleEntry(serverBase, report)));
detectedIssues.forEach(detectedIssue -> reportBundle.addEntry(getBundleEntry(serverBase, detectedIssue)));
configuredResources.values().forEach(resource -> reportBundle.addEntry(getBundleEntry(serverBase, resource)));
evalPlusSDEs.values().forEach(resource -> reportBundle.addEntry(getBundleEntry(serverBase, resource)));
if (notDocument) {
detectedIssues.forEach(detectedIssue -> reportBundle.addEntry(getBundleEntry(serverBase, detectedIssue)));
} else {
reportBundle.addEntry(getBundleEntry(serverBase, composition));
measureReports.forEach(report -> reportBundle.addEntry(getBundleEntry(serverBase, report)));
detectedIssues.forEach(detectedIssue -> reportBundle.addEntry(getBundleEntry(serverBase, detectedIssue)));
configuredResources
.values()
.forEach(resource -> reportBundle.addEntry(getBundleEntry(serverBase, resource)));
evalPlusSDEs.values().forEach(resource -> reportBundle.addEntry(getBundleEntry(serverBase, resource)));
}
return reportBundle;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package org.opencds.cqf.fhir.cr.measure.r4;

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;

/**
* Parameters class to manage input parameters for care-gaps service
*/
public class R4CareGapsParameters {
private ZonedDateTime periodStart;
private ZonedDateTime periodEnd;
private String subject;
private List<String> status;
private List<Either3<IdType, String, CanonicalType>> measure;
private boolean notDocument;

public void setPeriodStart(ZonedDateTime periodStart) {
this.periodStart = periodStart;
}

public ZonedDateTime getPeriodStart() {
return periodStart;
}

public void setPeriodEnd(ZonedDateTime periodEnd) {
this.periodEnd = periodEnd;
}

public ZonedDateTime getPeriodEnd() {
return periodEnd;
}

public void setSubject(String subject) {
this.subject = subject;
}

public String getSubject() {
return subject;
}

public void setNotDocument(boolean notDocument) {
this.notDocument = notDocument;
}

public boolean isNotDocument() {
return notDocument;
}

public void setMeasure(List<Either3<IdType, String, CanonicalType>> measure) {
this.measure = measure;
}

public List<Either3<IdType, String, CanonicalType>> getMeasure() {
return measure;
}

public void setStatus(List<String> status) {
this.status = status;
}

public List<String> getStatus() {
return status;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
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;

Expand Down Expand Up @@ -65,39 +66,65 @@ public Parameters getCareGapsReport(
@Nullable ZonedDateTime periodStart,
@Nullable ZonedDateTime periodEnd,
String subject,
List<String> statuses,
List<IdType> measureIds,
List<String> measureIdentifiers,
List<CanonicalType> measureUrls) {
List<String> status,
List<Either3<IdType, String, CanonicalType>> measure,
boolean notDocument) {

// set Parameters
R4CareGapsParameters r4CareGapsParams =
setCareGapParameters(periodStart, periodEnd, subject, status, measure, notDocument);

// validate and set required configuration resources for care-gaps
checkConfigurationReferences();

// Collect Measures to Evaluate
List<Measure> measures =
r4MeasureServiceUtils.getMeasures(measureIds, measureIdentifiers, canonicalToString(measureUrls));
List<IdType> collectedMeasureIds =
measures.stream().map(Resource::getIdElement).collect(Collectors.toList());

// validate required parameter values
checkValidStatusCode(statuses);
checkValidStatusCode(r4CareGapsParams.getStatus());
List<Measure> measures = resolveMeasure(r4CareGapsParams.getMeasure());
measureCompatibilityCheck(measures);

// Subject Population for Report
List<String> subjects = getSubjects(subject);
List<String> subjects = getSubjects(r4CareGapsParams.getSubject());

// Build Results
Parameters result = initializeResult();

// Build Patient Bundles

List<Parameters.ParametersParameterComponent> components = r4CareGapsBundleBuilder.makePatientBundles(
periodStart, periodEnd, subjects, statuses, collectedMeasureIds);
subjects,
r4CareGapsParams,
measures.stream().map(Resource::getIdElement).collect(Collectors.toList()));

// Return Results with Bundles
return result.setParameter(components);
}

private R4CareGapsParameters setCareGapParameters(
@Nullable ZonedDateTime periodStart,
@Nullable ZonedDateTime periodEnd,
String subject,
List<String> status,
List<Either3<IdType, String, CanonicalType>> measure,
boolean notDocument) {
R4CareGapsParameters r4CareGapsParams = new R4CareGapsParameters();
r4CareGapsParams.setMeasure(measure);
r4CareGapsParams.setPeriodStart(periodStart);
r4CareGapsParams.setPeriodEnd(periodEnd);
r4CareGapsParams.setStatus(status);
r4CareGapsParams.setSubject(subject);
r4CareGapsParams.setNotDocument(notDocument);
return r4CareGapsParams;
}

private List<Measure> resolveMeasure(List<Either3<IdType, String, CanonicalType>> measure) {
return measure.stream()
.map(x -> x.fold(
id -> repository.read(Measure.class, id),
r4MeasureServiceUtils::resolveByIdentifier,
canonical -> r4MeasureServiceUtils.resolveByUrl(canonical.asStringValue())))
.collect(Collectors.toList());
}

protected List<String> getSubjects(String subject) {
R4RepositorySubjectProvider subjectProvider = new R4RepositorySubjectProvider();
var subjects = subjectProvider.getSubjects(repository, null, subject).collect(Collectors.toList());
Expand Down
Loading