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 @@ -98,7 +98,8 @@ public List<Parameters.ParametersParameterComponent> makePatientBundles(
@Nullable ZonedDateTime periodEnd,
List<String> subjects,
List<String> statuses,
List<IdType> measureIds) {
List<IdType> measureIds,
boolean isDocumentMode) {
Capt-Mac marked this conversation as resolved.
Show resolved Hide resolved

// retrieve reporter from configuration
String reporter = RESOURCE_TYPE_ORGANIZATION.concat("/" + careGapsProperties.getCareGapsReporter());
Expand Down Expand Up @@ -126,8 +127,9 @@ 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, statuses, patient, isDocumentMode);

// add parameter with results
if (bundle != null && bundle.hasEntry()) {
Expand All @@ -136,14 +138,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 isDocumentMode) {
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, isDocumentMode);

// get Evaluation Bundle Results
for (BundleEntryComponent entry : bundle.getEntry()) {
MeasureReport mr = (MeasureReport) entry.getResource();
Expand All @@ -154,28 +160,33 @@ 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 (isDocumentMode) {
// 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, isDocumentMode);
// add DetectedIssue list to set on Bundle
detectedIssues.add(issue);
// add sections for DetectedIssues created
composition.addSection(getSection(measure, mr, issue, careGapsStatusCode));
if (isDocumentMode) {
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, isDocumentMode);
} else {
// return nothing if not-applicable
return null;
Expand Down Expand Up @@ -215,18 +226,19 @@ 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();
private Composition getComposition(Patient patient, boolean isDocumentMode) {
Capt-Mac marked this conversation as resolved.
Show resolved Hide resolved
Composition composition = null;
if (isDocumentMode) {
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 +249,9 @@ private DetectedIssue getDetectedIssue(
Patient patient,
MeasureReport measureReport,
String measureReportGroupId,
CareGapsStatusCode careGapsStatusCode) {
CareGapsStatusCode careGapsStatusCode,
Measure measure,
boolean isDocumentMode) {

var detectedIssue = new DetectedIssueBuilder<>(DetectedIssue.class)
.withProfile(CARE_GAPS_DETECTED_ISSUE_PROFILE)
Expand All @@ -254,6 +268,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 +281,14 @@ private DetectedIssue getDetectedIssue(
groupIdExt.setValue(new StringType(measureReportGroupId));
detectedIssue.setExtension(Collections.singletonList(groupIdExt));
}
if (!isDocumentMode) {
Capt-Mac marked this conversation as resolved.
Show resolved Hide resolved
// 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 +353,20 @@ private Bundle addBundleEntries(
Composition composition,
List<DetectedIssue> detectedIssues,
List<MeasureReport> measureReports,
Map<String, Resource> evalPlusSDEs) {
Map<String, Resource> evalPlusSDEs,
boolean isDocumentMode) {
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 (isDocumentMode) {
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)));
} else {
detectedIssues.forEach(detectedIssue -> reportBundle.addEntry(getBundleEntry(serverBase, detectedIssue)));
}
return reportBundle;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ public class R4CareGapsProcessor {
private final R4MeasureServiceUtils r4MeasureServiceUtils;
private final R4CareGapsBundleBuilder r4CareGapsBundleBuilder;

private static final Boolean defaultDocumentMode = true;

public R4CareGapsProcessor(
CareGapsProperties careGapsProperties,
Repository repository,
Expand All @@ -68,7 +70,8 @@ public Parameters getCareGapsReport(
List<String> statuses,
List<IdType> measureIds,
List<String> measureIdentifiers,
List<CanonicalType> measureUrls) {
List<CanonicalType> measureUrls,
Boolean isDocumentMode) {
Capt-Mac marked this conversation as resolved.
Show resolved Hide resolved

// validate and set required configuration resources for care-gaps
checkConfigurationReferences();
Expand All @@ -83,6 +86,11 @@ public Parameters getCareGapsReport(
checkValidStatusCode(statuses);
measureCompatibilityCheck(measures);

// Set default for optional parameter value
if (isDocumentMode == null) {
isDocumentMode = defaultDocumentMode;
}

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

Expand All @@ -92,7 +100,7 @@ public Parameters getCareGapsReport(
// Build Patient Bundles

List<Parameters.ParametersParameterComponent> components = r4CareGapsBundleBuilder.makePatientBundles(
periodStart, periodEnd, subjects, statuses, collectedMeasureIds);
periodStart, periodEnd, subjects, statuses, collectedMeasureIds, isDocumentMode);

// Return Results with Bundles
return result.setParameter(components);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,26 +31,32 @@ public R4CareGapsService(
/**
* Calculate measures describing gaps in care
*
* @param periodStart
* @param periodEnd
* @param subject
* @param statuses
* @param measureIds
* @param measureIdentifiers
* @param measureUrls
* @param periodStart measurement period starting interval.
* @param periodEnd measurement period ending interval.
* @param subject subject population to use for care-gap results. Accepted values [empty=all Patients, Patient/{id}, Group/{id} type {person, practitioner}, Practitioner/{id}]
* @param statuses determines what care-gap statuses will be returned by the service if found. [prospective-gap, closed-gap, open-gap, not-applicable].
* If Result is 'not-applicable' for Measure, but status requested was 'open-gap', then no result will be returned.
* @param measureIds Measures to check care-gap for by resolving by fhir resource id
* @param measureIdentifiers Measures to check care-gap for by resolving identifier value or systemUrl|value
* @param measureUrls Measures to check care-gap for by resolving canonical url reference
* @param isDocumentMode optional, default is 'true'. This parameter determines if standard care-gaps
* formatted 'document' bundle is returned with [composition, Detected Issue,
* configured resources, evaluated resources, measure reports]. Or non-document mode,
* which just returns a bundle with [Detected Issue(s)], with contained Measure Report.
* @return Parameters that includes zero to many document bundles that include Care Gap Measure
* Reports will be returned.
*/
public Parameters getCareGapsReport(
@Nullable ZonedDateTime periodStart,
@Nullable ZonedDateTime periodEnd,
String subject,
@Nullable String subject,
List<String> statuses,
List<IdType> measureIds,
List<String> measureIdentifiers,
List<CanonicalType> measureUrls) {
List<CanonicalType> measureUrls,
@Nullable Boolean isDocumentMode) {

return r4CareGapsProcessor.getCareGapsReport(
periodStart, periodEnd, subject, statuses, measureIds, measureIdentifiers, measureUrls);
periodStart, periodEnd, subject, statuses, measureIds, measureIdentifiers, measureUrls, isDocumentMode);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,8 @@ public static class When {
private List<CanonicalType> measureUrls = new ArrayList<>();
private Supplier<Parameters> operation;

private Boolean isDocumentMode;

public CareGaps.When periodEnd(ZonedDateTime periodEnd) {
this.periodEnd = periodEnd;
return this;
Expand Down Expand Up @@ -196,9 +198,21 @@ public CareGaps.When measureIdentifiers(String measureIdentifiers) {
return this;
}

public CareGaps.When isDocumentMode(Boolean isDocumentMode) {
this.isDocumentMode = isDocumentMode;
return this;
}

public CareGaps.When getCareGapsReport() {
this.operation = () -> service.getCareGapsReport(
periodStart, periodEnd, subject, statuses, measureIds, measureIdentifiers, measureUrls);
periodStart,
periodEnd,
subject,
statuses,
measureIds,
measureIdentifiers,
measureUrls,
isDocumentMode);
return this;
}

Expand Down Expand Up @@ -283,7 +297,11 @@ public SelectedBundle detectedIssueCount(int detectedIssueCount) {
.size());
return this;
}
;

public SelectedBundle bundleEntryCount(int count) {
assertEquals(count, bundleReport().getEntry().size());
return this;
}

public DetectedIssue resourceToDetectedIssue(Resource theResource) {
IParser parser = FhirContext.forR4Cached().newJsonParser();
Expand Down Expand Up @@ -407,6 +425,32 @@ public SelectedDetectedIssue hasCareGapStatus(String gapStatus) {
return this;
}

public SelectedDetectedIssue hasIdentifiedPeriod() {
assertTrue(detectedIssueReport().hasIdentifiedPeriod());
return this;
}

public SelectedDetectedIssue hasImplicatedMeasureReference() {
assertTrue(
detectedIssueReport().getImplicatedFirstRep().getReference().startsWith("Measure"));
return this;
}

public SelectedDetectedIssue hasContainedMeasureReport() {
var containedResource = detectedIssueReport().getContained().get(0);
assertTrue(containedResource instanceof MeasureReport);
return this;
}

public SelectedDetectedIssue hasContainedEvidenceReference() {
assertTrue(detectedIssueReport()
.getEvidenceFirstRep()
.getDetailFirstRep()
.getReference()
.startsWith("#"));
return this;
}

public SelectedDetectedIssue hasPatientReference(String patientRef) {
assertEquals(
patientRef,
Expand Down
Loading