Skip to content

Commit

Permalink
Resolve and Approve operation implementation (#675)
Browse files Browse the repository at this point in the history
* #641: added resolve operation, updated constants, updated ra.evaluate-measure operation and tests, updated evidence status date extension

* #641: Added closure request test and fixed MR group update logic

* #641: added invalidation and creation tests

* #641: Added creation error and precondition tests and logic - added ordering to tests and updated dates to ensure the correct bundle is being fetched

* RA Approve operation implementation (#669)

* Added Composition.author as a Bundle.entry to prevent error in building IG (document type Bundle requires Composition.author to be an entry)
Added fullUrl values for all Bundle.entry to prevent errors in building IG (document type Bundle requires fullUrl)
Removed http://hl7.org/fhir/us/davinci-ra/StructureDefinition/ra-groupReference extension from the DetectedIssue.detail to prevent errors in building IG (extension not valid for that element)
Updated profile for Composition to match IG

* Updated the id for remediate bundle
Fixed bug with getting remediate issues
Fixed bug where updating the DetectedIssue detail references to remove the group extension was removing the group extension from the MR
Updated DetectedIssue.code to be the HCC code

* Updated report profile to fix errors in IG (the Bundle profile was being used instead of the MR profile)
Added author to Bundle.entry for remediate and resolve to fix errors in IG (Composition.author is required to be in the Bundle when the type is document)
Updated evaluate to use the bundle profile instead of the mr profile

* #662: implemented approve operation logic and tests - fixed build

* Fixing build and tests

* Update tests to account for author Organization

* Add required Composition.author and Organization to fix tests

* Fix error message for missing Composition.author

* Protect the Composition author value

* Added protection for creating fullUrls
Increased test counts to account for updates to RA operations
Added ids to test resources to fix tests

* applied feedback

* applied feedback - fixed tests

* #670: fixed resolve operation precondition chek and updated tests

* Undo unintentional update of launch file

Co-authored-by: rob-reynolds <rob@alphora.com>

Co-authored-by: Jonathan Percival <jonathan.i.percival@gmail.com>
Co-authored-by: rob-reynolds <rob@alphora.com>
  • Loading branch information
3 people authored Nov 30, 2022
1 parent 96ce3ed commit e6cf0c1
Show file tree
Hide file tree
Showing 28 changed files with 6,165 additions and 110 deletions.
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

0 comments on commit e6cf0c1

Please sign in to comment.