Skip to content

Commit

Permalink
Merge pull request #49 from DBCG/fix-48-source-data-measure-evaluation
Browse files Browse the repository at this point in the history
#48 - source data measure evaluation
  • Loading branch information
c-schuler authored May 16, 2018
2 parents c9cf9db + 37a27fa commit 6ad1b8a
Show file tree
Hide file tree
Showing 7 changed files with 903 additions and 135 deletions.
159 changes: 159 additions & 0 deletions src/main/java/org/opencds/cqf/evaluation/MeasureEvaluation.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
package org.opencds.cqf.evaluation;

import ca.uhn.fhir.jpa.dao.SearchParameterMap;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.ReferenceParam;
import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.opencds.cqf.builders.MeasureReportBuilder;
import org.opencds.cqf.cql.data.DataProvider;
import org.opencds.cqf.cql.execution.Context;
import org.opencds.cqf.cql.runtime.Interval;
import org.opencds.cqf.helpers.FhirMeasureBundler;
import org.opencds.cqf.providers.JpaDataProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

public class MeasureEvaluation {

private static final Logger logger = LoggerFactory.getLogger(MeasureEvaluation.class);

private DataProvider provider;
private Interval measurementPeriod;

public MeasureEvaluation(DataProvider provider, Interval measurementPeriod) {
this.provider = provider;
this.measurementPeriod = measurementPeriod;
}

public MeasureReport evaluatePatientMeasure(Measure measure, Context context, String patientId) {
logger.info("Generating individual report");

if (patientId == null) {
return evaluatePopulationMeasure(measure, context);
}

Iterable<Object> patientRetrieve = provider.retrieve("Patient", patientId, "Patient", null, null, null, null, null, null, null, null);
Patient patient = null;
if (patientRetrieve.iterator().hasNext()) {
patient = (Patient) patientRetrieve.iterator().next();
}

return evaluate(measure, context, patient == null ? Collections.emptyList() : Collections.singletonList(patient), MeasureReport.MeasureReportType.INDIVIDUAL);
}

public MeasureReport evaluatePatientListMeasure(Measure measure, Context context, String practitionerRef)
{
logger.info("Generating patient-list report");

List<Patient> patients = new ArrayList<>();
if (practitionerRef != null) {
SearchParameterMap map = new SearchParameterMap();
map.add(
"general-practitioner",
new ReferenceParam(
practitionerRef.startsWith("Practitioner/")
? practitionerRef
: "Practitioner/" + practitionerRef
)
);

if (provider instanceof JpaDataProvider) {
IBundleProvider patientProvider = ((JpaDataProvider) provider).resolveResourceProvider("Patient").getDao().search(map);
List<IBaseResource> patientList = patientProvider.getResources(0, patientProvider.size());
patientList.forEach(x -> patients.add((Patient) x));
}
}
return evaluate(measure, context, patients, MeasureReport.MeasureReportType.PATIENTLIST);
}

public MeasureReport evaluatePopulationMeasure(Measure measure, Context context) {
logger.info("Generating summary report");

List<Patient> patients = new ArrayList<>();
if (provider instanceof JpaDataProvider) {
IBundleProvider patientProvider = ((JpaDataProvider) provider).resolveResourceProvider("Patient").getDao().search(new SearchParameterMap());
List<IBaseResource> patientList = patientProvider.getResources(0, patientProvider.size());
patientList.forEach(x -> patients.add((Patient) x));
}
return evaluate(measure, context, patients, MeasureReport.MeasureReportType.SUMMARY);
}

private MeasureReport evaluate(Measure measure, Context context, List<Patient> patients, MeasureReport.MeasureReportType type)
{
MeasureReportBuilder reportBuilder = new MeasureReportBuilder();
reportBuilder.buildStatus("complete");
reportBuilder.buildType(type);
reportBuilder.buildMeasureReference(measure.getIdElement().getValue());
if (type == MeasureReport.MeasureReportType.INDIVIDUAL && !patients.isEmpty()) {
reportBuilder.buildPatientReference(patients.get(0).getIdElement().getValue());
}
reportBuilder.buildPeriod(measurementPeriod);

MeasureReport report = reportBuilder.build();

List<Patient> initialPopulation = getInitalPopulation(measure, patients, context);
HashMap<String,Resource> resources = new HashMap<>();

for (Measure.MeasureGroupComponent group : measure.getGroup()) {
MeasureReport.MeasureReportGroupComponent reportGroup = new MeasureReport.MeasureReportGroupComponent();
reportGroup.setIdentifier(group.getIdentifier());
report.getGroup().add(reportGroup);

for (Measure.MeasureGroupPopulationComponent pop : group.getPopulation()) {
int count = 0;
// Worried about performance here with big populations...
for (Patient patient : initialPopulation) {
context.setContextValue("Patient", patient.getIdElement().getIdPart());
Object result = context.resolveExpressionRef(pop.getCriteria()).evaluate(context);
if (result instanceof Boolean) {
count += (Boolean) result ? 1 : 0;
}
else if (result instanceof Iterable) {
for (Object item : (Iterable) result) {
count++;
if (item instanceof Resource) {
resources.put(((Resource) item).getId(), (Resource) item);
}
}
}
}
MeasureReport.MeasureReportGroupPopulationComponent populationReport = new MeasureReport.MeasureReportGroupPopulationComponent();
populationReport.setCount(count);
populationReport.setCode(pop.getCode());
populationReport.setIdentifier(pop.getIdentifier());
reportGroup.getPopulation().add(populationReport);
}
}

FhirMeasureBundler bundler = new FhirMeasureBundler();
org.hl7.fhir.dstu3.model.Bundle evaluatedResources = bundler.bundle(resources.values());
evaluatedResources.setId(UUID.randomUUID().toString());
report.setEvaluatedResources(new Reference('#' + evaluatedResources.getId()));
report.addContained(evaluatedResources);
return report;
}

private List<Patient> getInitalPopulation(Measure measure, List<Patient> population, Context context) {
List<Patient> initalPop = new ArrayList<>();
for (Measure.MeasureGroupComponent group : measure.getGroup()) {
for (Measure.MeasureGroupPopulationComponent pop : group.getPopulation()) {
if (pop.getCode().getCodingFirstRep().getCode().equals("initial-population")) {
for (Patient patient : population) {
context.setContextValue("Patient", patient.getIdElement().getIdPart());
Object result = context.resolveExpressionRef(pop.getCriteria()).evaluate(context);
if (result == null) {
continue;
}
if ((Boolean) result) {
initalPop.add(patient);
}
}
}
}
}
return initalPop;
}
}
210 changes: 210 additions & 0 deletions src/main/java/org/opencds/cqf/helpers/DataProviderHelper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
package org.opencds.cqf.helpers;

import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt;
import ca.uhn.fhir.model.dstu2.composite.CodingDt;
import ca.uhn.fhir.model.dstu2.composite.PeriodDt;
import ca.uhn.fhir.model.primitive.CodeDt;
import ca.uhn.fhir.model.primitive.DateDt;
import ca.uhn.fhir.model.primitive.DateTimeDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import org.hl7.fhir.dstu3.model.*;
import org.opencds.cqf.cql.runtime.Code;
import org.opencds.cqf.cql.runtime.DateTime;
import org.opencds.cqf.cql.runtime.Interval;

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

public class DataProviderHelper {
// Get lowest precision from list of date objects
public static String getPrecision(List<Object> dateObjects) {
int precision = 7;
for (Object dateObject : dateObjects) {
if (dateObject instanceof Interval) {
DateTime start = (DateTime) ((Interval) dateObject).getStart();
DateTime end = (DateTime) ((Interval) dateObject).getEnd();

if (start != null) {
if (precision > start.getPartial().size()) {
precision = start.getPartial().size();
}
}
if (end != null) {
if (precision > end.getPartial().size()) {
precision = end.getPartial().size();
}
}
} else {
precision = ((DateTime) dateObject).getPartial().size();
}
}

switch (precision) {
case 1: return "year";
case 2: return "month";
case 3: return "day";
case 4: return "hour";
case 5: return "minute";
case 6: return "second";
default: return "millisecond";
}
}

public static Object getDstu2DateTime(Object dateObject) {
if (dateObject instanceof DateDt) {
return DateTime.fromJavaDate(((DateDt) dateObject).getValue());
} else if (dateObject instanceof DateTimeDt) {
return DateTime.fromJavaDate(((DateTimeDt) dateObject).getValue());
} else if (dateObject instanceof InstantDt) {
return DateTime.fromJavaDate(((InstantDt) dateObject).getValue());
} else if (dateObject instanceof PeriodDt) {
return new Interval(
DateTime.fromJavaDate(((PeriodDt) dateObject).getStart()), true,
DateTime.fromJavaDate(((PeriodDt) dateObject).getEnd()), true
);
}
return dateObject;
}

public static Object getDstu2Code(Object codeObject) {
if (codeObject instanceof CodeDt) {
return ((CodeDt) codeObject).getValue();
} else if (codeObject instanceof CodingDt) {
return new Code().withSystem(((CodingDt) codeObject).getSystem()).withCode(((CodingDt) codeObject).getCode());
}
else if (codeObject instanceof CodeableConceptDt) {
List<Code> codes = new ArrayList<>();
for (CodingDt coding : ((CodeableConceptDt) codeObject).getCoding()) {
codes.add((Code) getDstu2Code(coding));
}
return codes;
}
return codeObject;
}

public static Object getStu3DateTime(Object dateObject) {
if (dateObject instanceof Date) {
return DateTime.fromJavaDate((Date) dateObject);
} else if (dateObject instanceof DateTimeType) {
return DateTime.fromJavaDate(((DateTimeType) dateObject).getValue());
} else if (dateObject instanceof InstantType) {
return DateTime.fromJavaDate(((InstantType) dateObject).getValue());
} else if (dateObject instanceof Period) {
return new Interval(
DateTime.fromJavaDate(((Period) dateObject).getStart()), true,
DateTime.fromJavaDate(((Period) dateObject).getEnd()), true
);
}
return dateObject;
}

public static Object getStu3Code(Object codeObject) {
if (codeObject instanceof CodeType) {
return ((CodeType) codeObject).getValue();
} else if (codeObject instanceof Coding) {
return new Code().withSystem(((Coding) codeObject).getSystem()).withCode(((Coding) codeObject).getCode());
}
else if (codeObject instanceof CodeableConcept) {
List<Code> codes = new ArrayList<>();
for (Coding coding : ((CodeableConcept) codeObject).getCoding()) {
codes.add((Code) getStu3Code(coding));
}
return codes;
}
return codeObject;
}

public static boolean checkCodeMembership(Iterable<Code> codes, Object codeObject) {
// for now, just checking whether code values are equal... TODO - add intelligent checks for system and version
for (Code code : codes) {
if (codeObject instanceof String && code.getCode().equals(codeObject)) {
return true;
}
else if (codeObject instanceof Code && code.getCode().equals(((Code) codeObject).getCode())) {
return true;
}
else if (codeObject instanceof Iterable) {
for (Object obj : (Iterable) codeObject) {
if (code.getCode().equals(((Code) obj).getCode())) {
return true;
}
}
}
}
return false;
}

public static String convertStu3PathToSearchParam(String type, String path) {
path = path.replace(".value", "");
switch (type) {
case "AllergyIntolerance":
if (path.equals("clinicalStatus")) return "clinical-status";
else if (path.contains("substance")) return "code";
else if (path.equals("assertedDate")) return "date";
else if (path.equals("lastOccurrence")) return "last-date";
else if (path.startsWith("reaction")) {
if (path.endsWith("manifestation")) return "manifestation";
else if (path.endsWith("onset")) return "onset";
else if (path.endsWith("exposureRoute")) return "route";
else if (path.endsWith("severity")) return "severity";
}
else if (path.equals("verificationStatus")) return "verification-status";
break;
case "Claim":
if (path.contains("careTeam")) return "care-team";
else if (path.contains("payee")) return "payee";
break;
case "Condition":
if (path.equals("abatementDateTime")) return "abatement-date";
else if (path.equals("abatementPeriod")) return "abatement-date";
else if (path.equals("abatementRange")) return "abatement-age";
else if (path.equals("onsetDateTime")) return "onset-date";
else if (path.equals("onsetPeriod")) return "onset-date";
else if (path.equals("onsetRange")) return "onset-age";
break;
case "MedicationRequest":
if (path.equals("authoredOn")) return "authoredon";
else if (path.equals("medicationCodeableConcept")) return "code";
else if (path.equals("medicationReference")) return "medication";
else if (path.contains("event")) return "date";
else if (path.contains("performer")) return "intended-dispenser";
else if (path.contains("requester")) return "requester";
break;
case "NutritionOrder":
if (path.contains("additiveType")) return "additive";
else if (path.equals("dateTime")) return "datetime";
else if (path.contains("baseFormulaType")) return "formula";
else if (path.contains("oralDiet")) return "oraldiet";
else if (path.equals("orderer")) return "provider";
else if (path.contains("supplement")) return "supplement";
break;
case "ProcedureRequest":
if (path.equals("authoredOn")) return "authored";
else if (path.equals("basedOn")) return "based-on";
else if (path.equals("bodySite")) return "body-site";
else if (path.equals("context")) return "encounter";
else if (path.equals("performerType")) return "performer-type";
else if (path.contains("requester")) return "requester";
break;
case "ReferralRequest":
if (path.equals("authoredOn")) return "authored";
else if (path.equals("basedOn")) return "based-on";
else if (path.equals("context")) return "encounter";
else if (path.equals("groupIdentifier")) return "group-identifier";
else if (path.equals("occurrence")) return "occurrence-date";
else if (path.contains("requester")) return "requester";
else if (path.equals("serviceRequested")) return "service";
break;
case "VisionPrescription":
if (path.equals("dateWritten")) return "datewritten";
break;
default:
if (path.startsWith("effective")) return "date";
else if (path.equals("period")) return "date";
else if (path.equals("vaccineCode")) return "vaccine-code";
break;
}
return path.replace('.', '-').toLowerCase();
}
}
Loading

0 comments on commit 6ad1b8a

Please sign in to comment.