Skip to content

Commit

Permalink
542 care gaps non document mode (#543)
Browse files Browse the repository at this point in the history
* add non-document mode version of care-gaps

* add javadocs

* simplify makePatientBundle to remove dup code

* make var static

* reduce congnitive load of method

* constant naming convention fix

* parameters class, to reduce qty of parameter values, primitive boolean parameter instead of wrapper

* spotless fix
  • Loading branch information
Capt-Mac authored Oct 7, 2024
1 parent 92afb30 commit 1f35da4
Show file tree
Hide file tree
Showing 6 changed files with 408 additions and 178 deletions.
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

0 comments on commit 1f35da4

Please sign in to comment.