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

Resolve and Approve operation implementation #675

Merged
merged 8 commits into from
Nov 30, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ public static String getFullUrl(String serverAddress, String fhirType, String el
* @return the full url as a String
*/
public static String getFullUrl(String serverAddress, IBaseResource resource) {
checkArgument(resource.getIdElement().hasIdPart(),
"Cannot generate a fullUrl because the resource does not have an id.");
return getFullUrl(serverAddress, resource.fhirType(), Ids.simplePart(resource));
}

Expand Down
4 changes: 2 additions & 2 deletions plugin/cr/src/test/resources/BCSEHEDISMY2022-bundle.json
Original file line number Diff line number Diff line change
Expand Up @@ -18047,8 +18047,8 @@
"reference": "Patient/Patient-7"
},
"period": {
"start": "2020-10-01T00:00:00.000Z",
"end": "2020-12-31T23:59:59.000Z"
"start": "2020-10-01",
"end": "2020-12-31"
},
"type": {
"coding": [
Expand Down
13 changes: 13 additions & 0 deletions plugin/ra/src/main/java/org/opencds/cqf/ruler/ra/RAConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
import org.opencds.cqf.external.annotations.OnR4Condition;
import org.opencds.cqf.ruler.api.OperationProvider;
import org.opencds.cqf.ruler.cr.CrConfig;
import org.opencds.cqf.ruler.ra.r4.ApproveProvider;
import org.opencds.cqf.ruler.ra.r4.AssistedServlet;
import org.opencds.cqf.ruler.ra.r4.RACodingGapsProvider;
import org.opencds.cqf.ruler.ra.r4.RemediateProvider;
import org.opencds.cqf.ruler.ra.r4.ResolveProvider;
import org.opencds.cqf.ruler.ra.r4.RiskAdjustmentProvider;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
Expand Down Expand Up @@ -43,6 +45,17 @@ public OperationProvider r4RemediateProvider() {
return new RemediateProvider();
}

@Bean
@Conditional(OnR4Condition.class)
public OperationProvider r4ApproveProvider() {
return new ApproveProvider();
}
@Bean
@Conditional(OnR4Condition.class)
public OperationProvider r4ResolveProvider() {
return new ResolveProvider();
}

@Bean
@Conditional(OnR4Condition.class)
public ServletRegistrationBean<AssistedServlet> assistedServletServletRegistrationBeanR4(
Expand Down
64 changes: 61 additions & 3 deletions plugin/ra/src/main/java/org/opencds/cqf/ruler/ra/RAConstants.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package org.opencds.cqf.ruler.ra;

import java.util.Collections;
import java.util.Date;

import org.hl7.fhir.r4.model.CanonicalType;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Extension;
Expand All @@ -15,12 +17,68 @@ private RAConstants() {
// DaVinci IG constants
public static final String REPORT_ID_PREFIX = "coding-gaps-";
public static final String REMEDIATE_ID_PREFIX = "remediate-coding-gaps-";
public static final String PATIENT_REPORT_PROFILE_URL = "http://hl7.org/fhir/us/davinci-ra/StructureDefinition/ra-measurereport-bundle";
public static final String RESOLVE_ID_PREFIX = "resolve-coding-gaps-";
public static final String APPROVE_ID_PREFIX = "approve-coding-gaps-";
public static final String PATIENT_REPORT_URL = "http://hl7.org/fhir/us/davinci-ra/StructureDefinition/ra-measurereport";
public static final String CODING_GAP_BUNDLE_URL = "http://hl7.org/fhir/us/davinci-ra/StructureDefinition/ra-coding-gap-bundle";
public static final String SUSPECT_TYPE_URL = "http://hl7.org/fhir/us/davinci-ra/StructureDefinition/ra-suspectType";
public static final String EVIDENCE_STATUS_URL = "http://hl7.org/fhir/us/davinci-ra/StructureDefinition/ra-evidenceStatus";
public static final String EVIDENCE_STATUS_DATE_URL = "http://hl7.org/fhir/us/davinci-ra/StructureDefinition/ra-evidenceStatusDate";
// Suspect Type
public static final String SUSPECT_TYPE_SYSTEM = "http://hl7.org/fhir/us/davinci-ra/CodeSystem/suspect-type";
public static final String HISTORIC_CODE = "historic";
public static final String SUSPECTED_CODE = "suspected";
public static final String NET_NEW_CODE = "net-new";
public static final String HISTORIC_DISPLAY = "Historic Condition Category Gap";
public static final String SUSPECTED_DISPLAY = "Suspected Condition Category Gap";
public static final String NET_NEW_DISPLAY = "Net-New Condition Category Gap";
public static final CodeableConcept HISTORIC_CONCEPT = new CodeableConcept().addCoding(new Coding()
.setSystem(SUSPECT_TYPE_SYSTEM).setCode(HISTORIC_CODE).setDisplay(HISTORIC_DISPLAY));
public static final CodeableConcept SUSPECTED_CONCEPT = new CodeableConcept().addCoding(new Coding()
.setSystem(SUSPECT_TYPE_SYSTEM).setCode(SUSPECTED_CODE).setDisplay(SUSPECTED_DISPLAY));
public static final CodeableConcept NET_NEW_CONCEPT = new CodeableConcept().addCoding(new Coding()
.setSystem(SUSPECT_TYPE_SYSTEM).setCode(NET_NEW_CODE).setDisplay(NET_NEW_DISPLAY));
public static final Extension SUSPECT_TYPE_HISTORIC_EXT = new Extension().setUrl(SUSPECT_TYPE_URL)
.setValue(HISTORIC_CONCEPT);
public static final Extension SUSPECT_TYPE_SUSPECTED_EXT = new Extension().setUrl(SUSPECT_TYPE_URL)
.setValue(SUSPECTED_CONCEPT);
public static final Extension SUSPECT_TYPE_NET_NEW_EXT = new Extension().setUrl(SUSPECT_TYPE_URL)
.setValue(NET_NEW_CONCEPT);
// Evidence Status
public static final String EVIDENCE_STATUS_SYSTEM = "http://hl7.org/fhir/us/davinci-ra/CodeSystem/evidence-status";
public static final String CLOSED_GAP_CODE = "closed-gap";
public static final String OPEN_GAP_CODE = "open-gap";
public static final String INVALID_GAP_CODE = "invalid-gap";
public static final String CLOSED_GAP_DISPLAY = "Closed Condition Category Gap";
public static final String OPEN_GAP_DISPLAY = "Open Condition Category Gap";
public static final String INVALID_GAP_DISPLAY = "Invalid Condition Category Gap";
public static final CodeableConcept CLOSED_GAP_CONCEPT = new CodeableConcept().addCoding(new Coding()
.setSystem(EVIDENCE_STATUS_SYSTEM).setCode(CLOSED_GAP_CODE).setDisplay(CLOSED_GAP_DISPLAY));
public static final CodeableConcept OPEN_GAP_CONCEPT = new CodeableConcept().addCoding(new Coding()
.setSystem(EVIDENCE_STATUS_SYSTEM).setCode(OPEN_GAP_CODE).setDisplay(OPEN_GAP_DISPLAY));
public static final CodeableConcept INVALID_GAP_CONCEPT = new CodeableConcept().addCoding(new Coding()
.setSystem(EVIDENCE_STATUS_SYSTEM).setCode(INVALID_GAP_CODE).setDisplay(INVALID_GAP_DISPLAY));
public static final Extension EVIDENCE_STATUS_CLOSED_EXT = new Extension().setUrl(EVIDENCE_STATUS_URL)
.setValue(CLOSED_GAP_CONCEPT);
public static final Extension EVIDENCE_STATUS_OPEN_EXT = new Extension().setUrl(EVIDENCE_STATUS_URL)
.setValue(OPEN_GAP_CONCEPT);
public static final Extension EVIDENCE_STATUS_INVALID_EXT = new Extension().setUrl(EVIDENCE_STATUS_URL)
.setValue(INVALID_GAP_CONCEPT);

// MeasureReport constants
public static final Meta PATIENT_REPORT_META = new Meta().setProfile(
Collections.singletonList(new CanonicalType(RAConstants.PATIENT_REPORT_URL)))
.setLastUpdated(new Date());

// Bundle constants
public static final Meta CODING_GAP_REPORT_BUNDLE_META = new Meta().setProfile(
Collections.singletonList(new CanonicalType(RAConstants.CODING_GAP_BUNDLE_URL)))
.setLastUpdated(new Date());

// Composition constants
public static final Meta COMPOSITION_META = new Meta().addProfile(
"http://hl7.org/fhir/us/davinci-ra/StructureDefinition/ra-composition").setLastUpdated(new Date());
"http://hl7.org/fhir/us/davinci-ra/StructureDefinition/ra-coding-gap-composition")
.setLastUpdated(new Date());
public static final CodeableConcept COMPOSITION_TYPE = new CodeableConcept().addCoding(
new Coding().setSystem("http://loinc.org").setCode("96315-7").setDisplay("Gaps in care report"));

Expand All @@ -29,7 +87,7 @@ private RAConstants() {
public static final String CLINICAL_EVALUATION_ISSUE_PROFILE_URL = "http://hl7.org/fhir/us/davinci-ra/StructureDefinition/ra-coding-gap-clinical-evaluation-detectedissue";
public static final String GROUP_REFERENCE_URL = "http://hl7.org/fhir/us/davinci-ra/StructureDefinition/ra-groupReference";
public static final String CODING_GAP_REQUEST_URL = "http://hl7.org/fhir/us/davinci-ra/StructureDefinition/ra-codingGapRequest";
public static final Extension CODING_GAP_TYPE_EXTENSION = new Extension().addExtension()
public static final Extension CODING_GAP_REQUEST_EXTENSION = new Extension().addExtension()
.setUrl(CODING_GAP_REQUEST_URL)
.setValue(
new CodeableConcept().addCoding(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package org.opencds.cqf.ruler.ra.r4;

import static org.opencds.cqf.cql.evaluator.fhir.util.r4.Parameters.parameters;
import static org.opencds.cqf.cql.evaluator.fhir.util.r4.Parameters.part;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;

import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Composition;
import org.hl7.fhir.r4.model.DetectedIssue;
import org.hl7.fhir.r4.model.MeasureReport;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.Resource;
import org.opencds.cqf.ruler.behavior.ResourceCreator;
import org.opencds.cqf.ruler.behavior.r4.ParameterUser;
import org.opencds.cqf.ruler.provider.DaoRegistryOperationProvider;
import org.opencds.cqf.ruler.ra.RAConstants;
import org.opencds.cqf.ruler.utility.Operations;

import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.server.RequestDetails;

public class ApproveProvider extends DaoRegistryOperationProvider
implements ParameterUser, ResourceCreator, RiskAdjustmentUser {

/**
* Implements the <a href=
* "https://build.fhir.org/ig/HL7/davinci-ra/OperationDefinition-ra.approve-coding-gaps.html">$ra.approve-coding-gaps</a>
* operation found in the
* <a href="https://build.fhir.org/ig/HL7/davinci-ra/index.html">Da Vinci Risk
* Adjustment IG</a>.
*
* @param requestDetails metadata about the current request being processed.
* Generally auto-populated by the HAPI FHIR server
* framework.
* @param periodStart the start of the clinical evaluation period
* @param periodEnd the end of the clinical evaluation period
* @param subject a Patient or Patient Group
* @param measureId the id of a Measure resource
* @param measureIdentifier the identifier of a Measure resource
* @param measureUrl the url of a Measure resource
* @return a Parameters with <a href=
* "http://build.fhir.org/ig/HL7/davinci-ra/StructureDefinition-ra-coding-gap-bundle.html">Risk
* Adjustment Coding Gap Bundles</a>
*/
@Description(shortDefinition = "$ra.approve-coding-gaps operation", value = "Implements the <a href=\"https://build.fhir.org/ig/HL7/davinci-ra/OperationDefinition-ra.approve-coding-gaps.html\">$ra.approve-coding-gaps</a> operation found in the <a href=\"https://build.fhir.org/ig/HL7/davinci-ra/index.html\">Da Vinci Risk Adjustment IG</a>.")
@Operation(name = "$ra.approve-coding-gaps", idempotent = true, type = MeasureReport.class)
public Parameters approve(
RequestDetails requestDetails,
@OperationParam(name = RAConstants.PERIOD_START, typeName = "date") IPrimitiveType<Date> periodStart,
@OperationParam(name = RAConstants.PERIOD_END, typeName = "date") IPrimitiveType<Date> periodEnd,
@OperationParam(name = RAConstants.SUBJECT) String subject,
@OperationParam(name = RAConstants.MEASURE_ID) List<String> measureId,
@OperationParam(name = RAConstants.MEASURE_IDENTIFIER) List<String> measureIdentifier,
@OperationParam(name = RAConstants.MEASURE_URL) List<String> measureUrl) throws FHIRException {
try {
validateParameters(requestDetails);
} catch (Exception e) {
return parameters(part(RAConstants.INVALID_PARAMETERS_NAME,
generateIssue(RAConstants.INVALID_PARAMETERS_SEVERITY, e.getMessage())));
}

List<Bundle> codingGapReportBundles = new ArrayList<>();
getPatientListFromSubject(subject).forEach(
patient -> {
Bundle b = getMostRecentCodingGapReportBundle(subject, normalizeMeasureReference(
measureId, measureIdentifier, measureUrl), periodStart.getValue(), periodEnd.getValue());
MeasureReport mr = getReportFromBundle(b);
Composition composition = getCompositionFromBundle(b);
List<DetectedIssue> issues = getMostRecentIssuesFromBundle(b);
updateDetectedIssueStatusByCode(issues);
Resource author = getAuthorFromBundle(b, composition);
codingGapReportBundles.add(
buildCodingGapReportBundle(requestDetails.getFhirServerBase(), composition, issues, mr, author));
});

Parameters result = newResource(Parameters.class, RAConstants.APPROVE_ID_PREFIX + UUID.randomUUID());

for (Bundle codingGapReportBundle : codingGapReportBundles) {
result.addParameter(part(RAConstants.RETURN_PARAM_NAME, codingGapReportBundle));
}

return result;
}

@Override
public void validateParameters(RequestDetails requestDetails) {
Operations.validateCardinality(requestDetails, RAConstants.PERIOD_START, 1);
Operations.validateCardinality(requestDetails, RAConstants.PERIOD_END, 1);
Operations.validateCardinality(requestDetails, RAConstants.SUBJECT, 1);
Operations.validatePeriod(requestDetails, RAConstants.PERIOD_START, RAConstants.PERIOD_END);
Operations.validateSingularPattern(requestDetails, RAConstants.SUBJECT,
Operations.PATIENT_OR_GROUP_REFERENCE);
Operations.validateAtLeastOne(requestDetails, RAConstants.MEASURE_ID,
RAConstants.MEASURE_IDENTIFIER, RAConstants.MEASURE_URL);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.MeasureReport;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.Resource;
import org.opencds.cqf.ruler.behavior.ConfigurationUser;
import org.opencds.cqf.ruler.behavior.ResourceCreator;
import org.opencds.cqf.ruler.behavior.r4.ParameterUser;
Expand All @@ -41,6 +42,7 @@ public class RACodingGapsProvider extends DaoRegistryOperationProvider
private RAProperties raProperties;

private IdType compositionSectionAuthor;
private Resource author;

/**
* Implements the <a href=
Expand Down Expand Up @@ -91,13 +93,13 @@ public Parameters raCodingGaps(
if (reports.isEmpty()) {
result.addParameter(part(
RAConstants.RETURN_PARAM_NAME,
buildMissingMeasureReportCodingGapReportBundle(patient)));
buildMissingMeasureReportCodingGapReportBundle(requestDetails.getFhirServerBase(), patient)));
} else {
reports.forEach(report -> {
List<DetectedIssue> issues = buildOriginalIssues(report);
Composition composition = buildComposition(subject, report, issues,
compositionSectionAuthor);
Bundle bundle = buildCodingGapReportBundle(composition, issues, report);
Composition composition = buildComposition(subject, report, issues, compositionSectionAuthor);
Bundle bundle = buildCodingGapReportBundle(requestDetails.getFhirServerBase(), composition, issues,
report, author);
result.addParameter(part(RAConstants.RETURN_PARAM_NAME,
bundle.setId(UUID.randomUUID().toString())));
});
Expand All @@ -118,11 +120,13 @@ public void validateParameters(RequestDetails requestDetails) {

@Override
public void validateConfiguration(RequestDetails theRequestDetails) {
checkArgument(!Strings.isNullOrEmpty(raProperties.getComposition().getCompositionSectionAuthor()),
checkArgument(
raProperties.getComposition() != null
&& !Strings.isNullOrEmpty(raProperties.getComposition().getCompositionSectionAuthor()),
"The composition.ra_composition_section_author setting is required for the $ra.coding-gaps operation.");
compositionSectionAuthor = new IdType(raProperties.getComposition().getCompositionSectionAuthor());
// This will throw a ResourceNotFound exception if the Organization resource is
// not loaded in the server
read(compositionSectionAuthor);
author = read(compositionSectionAuthor);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.hl7.fhir.r4.model.DetectedIssue;
import org.hl7.fhir.r4.model.MeasureReport;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.Resource;
import org.opencds.cqf.ruler.behavior.r4.ParameterUser;
import org.opencds.cqf.ruler.provider.DaoRegistryOperationProvider;
import org.opencds.cqf.ruler.ra.RAConstants;
Expand Down Expand Up @@ -47,9 +48,11 @@ public Parameters remediate(
MeasureReport mr = getReportFromBundle(b);
Composition composition = getCompositionFromBundle(b);
List<DetectedIssue> issues = getIssuesFromBundle(b);
Resource author = getAuthorFromBundle(b, composition);
issues.addAll(getAssociatedIssues(mr.getIdElement().getValue()));
updateComposition(composition, mr, issues);
codingGapReportBundles.add(buildCodingGapReportBundle(composition, issues, mr));
codingGapReportBundles.add(
buildCodingGapReportBundle(requestDetails.getFhirServerBase(), composition, issues, mr, author));
});

Parameters result = new Parameters();
Expand Down
Loading