diff --git a/workflow/src/main/java/com/google/android/fhir/workflow/FhirEngineR4MeasureProcessor.java b/workflow/src/main/java/com/google/android/fhir/workflow/FhirEngineR4MeasureProcessor.java new file mode 100644 index 0000000000..a13540e501 --- /dev/null +++ b/workflow/src/main/java/com/google/android/fhir/workflow/FhirEngineR4MeasureProcessor.java @@ -0,0 +1,62 @@ +package com.google.android.fhir.workflow; + +import org.hl7.fhir.instance.model.api.IBaseBundle; +import org.hl7.fhir.r4.model.Measure; +import org.hl7.fhir.r4.model.MeasureReport; +import org.opencds.cqf.fhir.api.Repository; +import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions; +import org.opencds.cqf.fhir.cr.measure.common.MeasureEvalType; +import org.opencds.cqf.fhir.cr.measure.common.SubjectProvider; +import org.opencds.cqf.fhir.cr.measure.r4.R4MeasureProcessor; +import org.opencds.cqf.fhir.cr.measure.r4.R4RepositorySubjectProvider; +import org.opencds.cqf.fhir.utility.repository.FederatedRepository; +import org.opencds.cqf.fhir.utility.repository.InMemoryFhirRepository; + +import java.lang.reflect.Field; +import java.util.List; +import java.util.stream.Collectors; + +public class FhirEngineR4MeasureProcessor extends R4MeasureProcessor { + private static String FIELD_SUBJECT_PROVIDER="subjectProvider"; + private Repository repository; + + public FhirEngineR4MeasureProcessor(Repository repository, MeasureEvaluationOptions measureEvaluationOptions) { + super(repository, measureEvaluationOptions); + this.repository = repository; + } + + public FhirEngineR4MeasureProcessor(Repository repository, MeasureEvaluationOptions measureEvaluationOptions, SubjectProvider subjectProvider) { + super(repository, measureEvaluationOptions, subjectProvider); + this.repository = repository; + } + + @Override + public MeasureReport evaluateMeasure(Measure measure, String periodStart, String periodEnd, String reportType, List subjectIds, IBaseBundle additionalData, MeasureEvalType evalType) { + var actualRepo = this.repository; + if (additionalData != null) { + actualRepo = new FederatedRepository( + this.repository, new InMemoryFhirRepository(this.repository.fhirContext(), additionalData)); + } + + SubjectProvider subjectProvider = getSubjectProvider(); + var subjects = subjectProvider.getSubjects(actualRepo, evalType, subjectIds).collect(Collectors.toList()); + return super.evaluateMeasure( measure, periodStart, periodEnd, reportType, subjects, additionalData, evalType) ; + } + + + /*** + * We have two constructors that could result in different subject providers. So for this field we will use reflection + * @return [SubjectProvider] the SubjectProvider + */ + public SubjectProvider getSubjectProvider(){ + SubjectProvider subjectProvider; + try { + Field field = this.getClass().getSuperclass().getDeclaredField(FIELD_SUBJECT_PROVIDER); + field.setAccessible(true); + subjectProvider = (SubjectProvider) field.get(this); + }catch (Exception e){ + subjectProvider = null; + } + return subjectProvider; + } +} diff --git a/workflow/src/main/java/com/google/android/fhir/workflow/FhirOperator.kt b/workflow/src/main/java/com/google/android/fhir/workflow/FhirOperator.kt index 81ca998e40..7598a42e83 100644 --- a/workflow/src/main/java/com/google/android/fhir/workflow/FhirOperator.kt +++ b/workflow/src/main/java/com/google/android/fhir/workflow/FhirOperator.kt @@ -31,6 +31,7 @@ import org.hl7.fhir.instance.model.api.IBaseParameters import org.hl7.fhir.instance.model.api.IBaseResource import org.hl7.fhir.r4.model.CanonicalType import org.hl7.fhir.r4.model.IdType +import org.hl7.fhir.r4.model.Measure import org.hl7.fhir.r4.model.MeasureReport import org.hl7.fhir.r4.model.Parameters import org.hl7.fhir.r4.model.PlanDefinition @@ -38,8 +39,8 @@ import org.hl7.fhir.r4.model.Reference import org.opencds.cqf.fhir.cql.EvaluationSettings import org.opencds.cqf.fhir.cql.LibraryEngine import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions +import org.opencds.cqf.fhir.cr.measure.common.MeasureEvalType import org.opencds.cqf.fhir.cr.measure.common.MeasureReportType -import org.opencds.cqf.fhir.cr.measure.r4.R4MeasureProcessor import org.opencds.cqf.fhir.cr.plandefinition.r4.PlanDefinitionProcessor import org.opencds.cqf.fhir.utility.monad.Eithers import org.opencds.cqf.fhir.utility.repository.ProxyRepository @@ -68,7 +69,8 @@ internal constructor( private val libraryProcessor = LibraryEngine(repository, evaluationSettings) - private val measureProcessor = R4MeasureProcessor(repository, measureEvaluationOptions) + private val measureProcessor: FhirEngineR4MeasureProcessor = + FhirEngineR4MeasureProcessor(repository, measureEvaluationOptions) private val planDefinitionProcessor = PlanDefinitionProcessor(repository, evaluationSettings) /** @@ -209,6 +211,55 @@ internal constructor( return report } + /** + * Generates a [MeasureReport] based on the provided inputs. + * + * NOTE: The API may internally result in a blocking IO operation. The user should call the API + * from a worker thread or it may throw [BlockingMainThreadException] exception. + */ + @WorkerThread + fun evaluateMeasure( + measure: Measure, + start: String, + end: String, + reportType: String, + subjectId: String? = null, + practitioner: String? = null, + additionalData: IBaseBundle? = null, + ): MeasureReport { + val subject = + if (!practitioner.isNullOrBlank()) { + checkAndAddType(practitioner, "Practitioner") + } else if (!subjectId.isNullOrBlank()) { + checkAndAddType(subjectId, "Patient") + } else { + // List of null is required to run population-level measures + null + } + val evalType = + MeasureEvalType.fromCode(reportType) + .orElse( + if (!subjectId.isNullOrEmpty()) MeasureEvalType.SUBJECT else MeasureEvalType.POPULATION + ) as MeasureEvalType + + val report = + measureProcessor.evaluateMeasure( + /* measure = */ measure, + /* periodStart = */ start, + /* periodEnd = */ end, + /* reportType = */ reportType, + /* subjectIds = */ if (subject.isNullOrEmpty()) listOf() else listOf(subject), + /* additionalData = */ additionalData, + /* evalType = */ evalType, + ) + + // add subject reference for non-individual reportTypes + if (report.type.name == MeasureReportType.SUMMARY.name && !subject.isNullOrBlank()) { + report.setSubject(Reference(subject)) + } + return report + } + /** * Generates a [CarePlan] based on the provided inputs. *