Skip to content

Commit

Permalink
catch cql exceptions per subject (#627)
Browse files Browse the repository at this point in the history
* catch cql exceptions per subject

* fix failing tests, adjust for error captured in contained OperationOutcome

* code review fixes
  • Loading branch information
Capt-Mac authored Jan 16, 2025
1 parent 0a9eeab commit fc70c53
Show file tree
Hide file tree
Showing 17 changed files with 448 additions and 424 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.opencds.cqf.fhir.cr.measure.common;

import java.util.ArrayList;
import java.util.List;
import org.opencds.cqf.cql.engine.runtime.Interval;

Expand All @@ -11,13 +12,16 @@ public class MeasureDef {
private Interval defaultMeasurementPeriod;
private final List<GroupDef> groups;
private final List<SdeDef> sdes;
private final List<String> errors;

public MeasureDef(String id, String url, String version, List<GroupDef> groups, List<SdeDef> sdes) {
this.id = id;
this.url = url;
this.version = version;
this.groups = groups;
this.sdes = sdes;

this.errors = new ArrayList<>();
}

public String id() {
Expand All @@ -43,4 +47,12 @@ public List<SdeDef> sdes() {
public List<GroupDef> groups() {
return this.groups;
}

public List<String> errors() {
return this.errors;
}

public void addError(String error) {
this.errors.add(error);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -305,11 +305,18 @@ protected MeasureDef evaluate(
String subjectTypePart = subjectInfo.getLeft();
String subjectIdPart = subjectInfo.getRight();
context.getState().setContextValue(subjectTypePart, subjectIdPart);

EvaluationResult result =
libraryEngine.getEvaluationResult(id, subjectId, null, null, null, null, zonedDateTime, context);

evaluateSubject(measureDef, subjectTypePart, subjectIdPart, subjectSize, type, result);
try {
EvaluationResult result = libraryEngine.getEvaluationResult(
id, subjectId, null, null, null, null, zonedDateTime, context);
evaluateSubject(measureDef, subjectTypePart, subjectIdPart, subjectSize, type, result);
} catch (Exception e) {
// Catch Exceptions from evaluation per subject, but allow rest of subjects to be processed (if
// applicable)
var error = String.format("Exception for subjectId: %s, Message: %s", subjectId, e.getMessage());
// Capture error for MeasureReportBuilder
measureDef.addError(error);
logger.error(error, e);
}
}

return measureDef;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,12 @@
import org.hl7.fhir.r4.model.MeasureReport.MeasureReportGroupComponent;
import org.hl7.fhir.r4.model.MeasureReport.MeasureReportGroupPopulationComponent;
import org.hl7.fhir.r4.model.MeasureReport.MeasureReportGroupStratifierComponent;
import org.hl7.fhir.r4.model.MeasureReport.MeasureReportStatus;
import org.hl7.fhir.r4.model.MeasureReport.StratifierGroupComponent;
import org.hl7.fhir.r4.model.MeasureReport.StratifierGroupPopulationComponent;
import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.OperationOutcome;
import org.hl7.fhir.r4.model.OperationOutcome.IssueType;
import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.Resource;
import org.hl7.fhir.r4.model.ResourceType;
Expand Down Expand Up @@ -187,6 +190,22 @@ private void validateReference(String reference) {
throw new InvalidRequestException("Invalid full reference: " + reference);
}
}

public void addOperationOutcomes() {
var errorMsgs = this.measureDef.errors();
for (var error : errorMsgs) {
addContained(createOperationOutcome(error));
}
}

private OperationOutcome createOperationOutcome(String errorMsg) {
OperationOutcome op = new OperationOutcome();
op.addIssue()
.setSeverity(OperationOutcome.IssueSeverity.ERROR)
.setCode(IssueType.EXCEPTION)
.setDiagnostics(errorMsg);
return op;
}
}

@Override
Expand All @@ -209,16 +228,26 @@ public MeasureReport build(

addEvaluatedResource(bc);
addSupplementalData(bc);
bc.addOperationOutcomes();

for (var r : bc.contained().values()) {
bc.report().addContained(r);
}

this.measureReportScorer.score(measure.getUrl(), measureDef, bc.report());

setReportStatus(bc);
return bc.report();
}

private void setReportStatus(BuilderContext bc) {
if (bc.report().hasContained()
&& bc.report().getContained().stream()
.anyMatch(t -> t.getResourceType().equals(ResourceType.OperationOutcome))) {
// Measure Reports that have encountered an error during evaluation will be set to status 'Error'
bc.report().setStatus(MeasureReportStatus.ERROR);
}
}

protected void addSupplementalData(BuilderContext bc) {
var report = bc.report();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,22 @@
import org.hl7.fhir.r4.model.DetectedIssue;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.MeasureReport;
import org.hl7.fhir.r4.model.MeasureReport.MeasureReportStatus;
import org.hl7.fhir.r4.model.MeasureReport.MeasureReportType;
import org.hl7.fhir.r4.model.OperationOutcome;
import org.hl7.fhir.r4.model.Organization;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.Resource;
import org.hl7.fhir.r4.model.ResourceType;
import org.opencds.cqf.fhir.api.Repository;
import org.opencds.cqf.fhir.cql.engine.retrieve.RetrieveSettings.SEARCH_FILTER_MODE;
import org.opencds.cqf.fhir.cql.engine.retrieve.RetrieveSettings.TERMINOLOGY_FILTER_MODE;
import org.opencds.cqf.fhir.cql.engine.terminology.TerminologySettings.VALUESET_EXPANSION_MODE;
import org.opencds.cqf.fhir.cr.measure.CareGapsProperties;
import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions;
import org.opencds.cqf.fhir.cr.measure.common.MeasurePeriodValidator;
import org.opencds.cqf.fhir.cr.measure.r4.Measure.SelectedReport;
import org.opencds.cqf.fhir.utility.repository.ig.IgRepository;

public class CareGaps {
Expand Down Expand Up @@ -539,5 +543,25 @@ public SelectedMeasureReport measureReportTypeIndividual() {
assertEquals(MeasureReportType.INDIVIDUAL, measureReport().getType());
return this;
}

public SelectedMeasureReport hasContainedOperationOutcome() {
assertTrue(measureReport().hasContained()
&& measureReport().getContained().stream()
.anyMatch(t -> t.getResourceType().equals(ResourceType.OperationOutcome)));
return this;
}

public SelectedMeasureReport hasContainedOperationOutcomeMsg(String msg) {
assertTrue(measureReport().getContained().stream()
.filter(t -> t.getResourceType().equals(ResourceType.OperationOutcome))
.map(y -> (OperationOutcome) y)
.anyMatch(x -> x.getIssueFirstRep().getDiagnostics().contains(msg)));
return this;
}

public SelectedMeasureReport hasStatus(MeasureReportStatus status) {
assertEquals(status, measureReport().getStatus());
return this;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import org.hl7.fhir.r4.model.MeasureReport.StratifierGroupComponent;
import org.hl7.fhir.r4.model.MeasureReport.StratifierGroupPopulationComponent;
import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.OperationOutcome;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.Period;
import org.hl7.fhir.r4.model.Reference;
Expand Down Expand Up @@ -677,6 +678,21 @@ private List<String> subjectResultReferences() {
}
return refs;
}

public SelectedReport hasContainedOperationOutcome() {
assertTrue(report().hasContained()
&& report().getContained().stream()
.anyMatch(t -> t.getResourceType().equals(ResourceType.OperationOutcome)));
return this;
}

public SelectedReport hasContainedOperationOutcomeMsg(String msg) {
assertTrue(report().getContained().stream()
.filter(t -> t.getResourceType().equals(ResourceType.OperationOutcome))
.map(y -> (OperationOutcome) y)
.anyMatch(x -> x.getIssueFirstRep().getDiagnostics().contains(msg)));
return this;
}
}

static class SelectedExtension extends Selected<Extension, SelectedReport> {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
package org.opencds.cqf.fhir.cr.measure.r4;

import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.opencds.cqf.fhir.test.Resources.getResourcePath;

import ca.uhn.fhir.context.FhirContext;
import java.nio.file.Paths;
import org.hl7.fhir.r4.model.DateTimeType;
import org.hl7.fhir.r4.model.MeasureReport.MeasureReportStatus;
import org.hl7.fhir.r4.model.Period;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -89,22 +88,15 @@ void cohortResourcePopulation() {

@Test
void cohortBooleanMissingRequiredPopulation() {
try {
given.when()
.measureId("CohortBooleanMissingReqdPopulation")
.evaluate()
.then()
.firstGroup()
.population("initial-population")
.hasCount(10)
.up()
.up()
.report();
fail("This should throw error");
} catch (UnsupportedOperationException e) {
assertTrue(e.getMessage()
.contains("MeasurePopulationType: numerator, is not a member of allowed 'cohort' populations"));
}
given.when()
.measureId("CohortBooleanMissingReqdPopulation")
.evaluate()
.then()
.hasStatus(MeasureReportStatus.ERROR)
.hasContainedOperationOutcome()
.hasContainedOperationOutcomeMsg(
"MeasurePopulationType: numerator, is not a member of allowed 'cohort' populations")
.report();
}

@Test
Expand All @@ -125,22 +117,14 @@ void cohortResourceIndividual() {

@Test
void cohortBooleanExtraInvalidPopulation() {
try {
given.when()
.measureId("CohortBooleanExtraInvalidPopulation")
.evaluate()
.then()
.firstGroup()
.population("initial-population")
.hasCount(10)
.up()
.up()
.report();
fail("This should throw error");
} catch (UnsupportedOperationException e) {
assertTrue(e.getMessage()
.contains("MeasurePopulationType: denominator, is not a member of allowed 'cohort' populations"));
}
given.when()
.measureId("CohortBooleanExtraInvalidPopulation")
.evaluate()
.then()
.hasContainedOperationOutcome()
.hasContainedOperationOutcomeMsg(
"MeasurePopulationType: denominator, is not a member of allowed 'cohort' populations")
.report();
}

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

import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.opencds.cqf.fhir.test.Resources.getResourcePath;

import ca.uhn.fhir.context.FhirContext;
import java.nio.file.Paths;
import org.hl7.fhir.r4.model.DateTimeType;
import org.hl7.fhir.r4.model.MeasureReport.MeasureReportStatus;
import org.hl7.fhir.r4.model.Period;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -108,28 +107,15 @@ void continuousVariableResourcePopulation() {

@Test
void continuousVariableBooleanMissingRequiredPopulation() {
try {
given.when()
.measureId("ContinuousVariableBooleanMissingReqdPopulation")
.evaluate()
.then()
.firstGroup()
.population("initial-population")
.hasCount(10)
.up()
.population("measure-population")
.hasCount(10)
.up()
.population("measure-population-exclusion")
.hasCount(10)
.up()
.up()
.report();
fail("This should throw error");
} catch (UnsupportedOperationException e) {
assertTrue(e.getMessage()
.contains("'continuous-variable' measure is missing required population: initial-population"));
}
given.when()
.measureId("ContinuousVariableBooleanMissingReqdPopulation")
.evaluate()
.then()
.hasStatus(MeasureReportStatus.ERROR)
.hasContainedOperationOutcome()
.hasContainedOperationOutcomeMsg(
"'continuous-variable' measure is missing required population: initial-population")
.report();
}

@Test
Expand All @@ -156,30 +142,15 @@ void continuousVariableResourceIndividual() {

@Test
void continuousVariableBooleanExtraInvalidPopulation() {
try {
given.when()
.measureId("ContinuousVariableBooleanExtraInvalidPopulation")
.evaluate()
.then()
.firstGroup()
.population("initial-population")
.hasCount(10)
.up()
.population("measure-population")
.hasCount(10)
.up()
.population("measure-population-exclusion")
.hasCount(10)
.up()
.up()
.report();
fail("This should throw error");
} catch (UnsupportedOperationException e) {
assertTrue(
e.getMessage()
.contains(
"MeasurePopulationType: denominator, is not a member of allowed 'continuous-variable' populations"));
}
given.when()
.measureId("ContinuousVariableBooleanExtraInvalidPopulation")
.evaluate()
.then()
.hasStatus(MeasureReportStatus.ERROR)
.hasContainedOperationOutcome()
.hasContainedOperationOutcomeMsg(
"MeasurePopulationType: denominator, is not a member of allowed 'continuous-variable' populations")
.report();
}

@Test
Expand Down
Loading

0 comments on commit fc70c53

Please sign in to comment.