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

Add DataRequrementsProcessor from cqframework elm #340

Merged
merged 10 commits into from
Oct 26, 2021
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
<cqf-tooling.version>1.3.1-SNAPSHOT</cqf-tooling.version>
<cql-engine.version>1.5.2-SNAPSHOT</cql-engine.version>
<cql-evaluator.version>1.2.1-SNAPSHOT</cql-evaluator.version>
<cqframework.version>1.5.3</cqframework.version>
<cqframework.version>1.5.4-SNAPSHOT</cqframework.version>
<cds-hooks.version>1.3.1-SNAPSHOT</cds-hooks.version>
<slf4j.version>1.7.30</slf4j.version>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.HashSet;
import java.util.stream.Collectors;

import javax.inject.Inject;
Expand All @@ -32,8 +36,10 @@
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.lang3.tuple.Triple;
import org.cqframework.cql.cql2elm.CqlTranslator;
import org.cqframework.cql.cql2elm.CqlTranslatorOptions;
import org.cqframework.cql.cql2elm.LibraryManager;
import org.cqframework.cql.cql2elm.ModelManager;
import org.cqframework.cql.cql2elm.model.TranslatedLibrary;
import org.cqframework.cql.elm.execution.CodeDef;
import org.cqframework.cql.elm.execution.CodeSystemDef;
import org.cqframework.cql.elm.execution.ExpressionDef;
Expand All @@ -42,9 +48,11 @@
import org.cqframework.cql.elm.execution.Library;
import org.cqframework.cql.elm.execution.ValueSetDef;
import org.cqframework.cql.elm.execution.VersionedIdentifier;
import org.cqframework.cql.elm.requirements.fhir.DataRequirementsProcessor;
import org.cqframework.cql.tools.formatter.CqlFormatterVisitor;
import org.cqframework.cql.tools.formatter.CqlFormatterVisitor.FormatResult;
import org.hl7.elm.r1.ValueSetRef;
import org.hl7.fhir.convertors.VersionConvertor_40_50;
import org.hl7.fhir.r4.model.Attachment;
import org.hl7.fhir.r4.model.Base64BinaryType;
import org.hl7.fhir.r4.model.CodeableConcept;
Expand All @@ -56,12 +64,17 @@
import org.hl7.fhir.r4.model.ParameterDefinition;
import org.hl7.fhir.r4.model.RelatedArtifact;
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r5.model.CanonicalType;
import org.hl7.fhir.r5.model.Extension;
import org.hl7.fhir.r5.model.Meta;
import org.opencds.cqf.common.helpers.TranslatorHelper;
import org.opencds.cqf.cql.engine.execution.LibraryLoader;
import org.opencds.cqf.tooling.measure.r4.CodeTerminologyRef;
import org.opencds.cqf.tooling.measure.r4.CqfMeasure;
import org.opencds.cqf.tooling.measure.r4.TerminologyRef;
import org.opencds.cqf.tooling.measure.r4.TerminologyRef.TerminologyRefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import ca.uhn.fhir.cql.common.provider.LibraryResolutionProvider;
Expand All @@ -72,14 +85,128 @@

@Component
public class DataRequirementsProvider {
private static final Logger logger = LoggerFactory.getLogger(DataRequirementsProvider.class);

private LibraryHelper libraryHelper;

@Inject
public DataRequirementsProvider(LibraryHelper libraryHelper) {
this.libraryHelper = libraryHelper;
}

/*
public org.hl7.fhir.r5.model.Measure createMeasure(Measure r4Measure, LibraryManager libraryManager, TranslatedLibrary translatedLibrary, CqlTranslatorOptions options) {

org.hl7.fhir.r5.model.Measure measureToUse = (org.hl7.fhir.r5.model.Measure) VersionConvertor_40_50.convertResource(r4Measure);

org.hl7.fhir.r5.model.Library moduleDefinitionLibrary = getModuleDefinitionLibrary(measureToUse, libraryManager, translatedLibrary, options);
measureToUse.setDate(new Date());
setMeta(measureToUse, moduleDefinitionLibrary);
clearMeasureExtensions(measureToUse, "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-parameter");
clearMeasureExtensions(measureToUse, "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-dataRequirement");
clearMeasureExtensions(measureToUse, "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-directReferenceCode");
clearMeasureExtensions(measureToUse, "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-logicDefinition");
clearRelatedArtifacts(measureToUse);
setParameters(measureToUse, moduleDefinitionLibrary);
setDataRequirements(measureToUse, moduleDefinitionLibrary);
setDirectReferenceCode(measureToUse, moduleDefinitionLibrary);
setLogicDefinition(measureToUse, moduleDefinitionLibrary);
measureToUse.setRelatedArtifact(moduleDefinitionLibrary.getRelatedArtifact());

return measureToUse;
}
*/
public org.hl7.fhir.r4.model.Library getModuleDefinitionLibrary(Measure r4Measure, LibraryManager libraryManager, TranslatedLibrary translatedLibrary, CqlTranslatorOptions options){

org.hl7.fhir.r5.model.Measure measureToUse = (org.hl7.fhir.r5.model.Measure) VersionConvertor_40_50.convertResource(r4Measure);

Set<String> expressionList = getExpressions(measureToUse);
DataRequirementsProcessor dqReqTrans = new DataRequirementsProcessor();

org.hl7.fhir.r5.model.Library libraryR5 = dqReqTrans.gatherDataRequirements(libraryManager, translatedLibrary, options, expressionList, true);
org.hl7.fhir.r4.model.Library library = (org.hl7.fhir.r4.model.Library) VersionConvertor_40_50.convertResource(libraryR5);

return library;
}

private Set<String> getExpressions(org.hl7.fhir.r5.model.Measure measureToUse) {
Set<String> expressionSet = new HashSet<>();
measureToUse.getSupplementalData().forEach(supData->{
expressionSet.add(supData.getCriteria().getExpression());
});
measureToUse.getGroup().forEach(groupMember->{
groupMember.getPopulation().forEach(population->{
expressionSet.add(population.getCriteria().getExpression());
});
groupMember.getStratifier().forEach(stratifier->{
expressionSet.add(stratifier.getCriteria().getExpression());
});
});
return expressionSet;
}

private void clearMeasureExtensions(org.hl7.fhir.r5.model.Measure measure, String extensionUrl) {
List<Extension> extensionsToRemove = measure.getExtensionsByUrl(extensionUrl);
measure.getExtension().removeAll(extensionsToRemove);
}

private void clearRelatedArtifacts(org.hl7.fhir.r5.model.Measure measure) {
measure.getRelatedArtifact().removeIf(r -> r.getType() == org.hl7.fhir.r5.model.RelatedArtifact.RelatedArtifactType.DEPENDSON);
}

private void setLogicDefinition(org.hl7.fhir.r5.model.Measure measureToUse, org.hl7.fhir.r5.model.Library moduleDefinitionLibrary) {
moduleDefinitionLibrary.getExtension().forEach(extension -> {
if (extension.getUrl().equalsIgnoreCase("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-logicDefinition")) {
measureToUse.addExtension(extension);
}
});
}

private void setDirectReferenceCode(org.hl7.fhir.r5.model.Measure measureToUse, org.hl7.fhir.r5.model.Library moduleDefinitionLibrary) {
moduleDefinitionLibrary.getExtension().forEach(extension -> {
if(extension.getUrl().equalsIgnoreCase("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-directReferenceCode")) {
measureToUse.addExtension(extension);
}
});
}

private void setDataRequirements(org.hl7.fhir.r5.model.Measure measureToUse, org.hl7.fhir.r5.model.Library moduleDefinitionLibrary) {
moduleDefinitionLibrary.getDataRequirement().forEach(dataRequirement -> {
Extension dataReqExtension = new Extension();
dataReqExtension.setUrl("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-dataRequirement");
dataReqExtension.setValue(dataRequirement);
measureToUse.addExtension(dataReqExtension);
});
}

private void setParameters(org.hl7.fhir.r5.model.Measure measureToUse, org.hl7.fhir.r5.model.Library moduleDefinitionLibrary) {
Set<String> parameterName = new HashSet<>();
moduleDefinitionLibrary.getParameter().forEach(parameter->{
if(!parameterName.contains(parameter.getName())) {
Extension parameterExtension = new Extension();
parameterExtension.setUrl("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-parameter");
parameterExtension.setValue(parameter);
measureToUse.addExtension(parameterExtension);
parameterName.add(parameter.getName());
}
});
}

private void setMeta(org.hl7.fhir.r5.model.Measure measureToUse, org.hl7.fhir.r5.model.Library moduleDefinitionLibrary){
if (measureToUse.getMeta() == null) {
measureToUse.setMeta(new Meta());
}
boolean hasProfileMarker = false;
for (CanonicalType canonical : measureToUse.getMeta().getProfile()) {
if ("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/computable-measure-cqfm".equals(canonical.getValue())) {
hasProfileMarker = true;
}
}
if (!hasProfileMarker) {
measureToUse.getMeta().addProfile("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/computable-measure-cqfm");
}
}

// For creating the CQF measure we need to:
// 1. Find the Primary Library Resource
// 2. Load the Primary Library as ELM. This will recursively load the dependent
Expand All @@ -96,6 +223,23 @@ public CqfMeasure createCqfMeasure(Measure measure,
return this.createCqfMeasure(measure, libraryMap);
}

public org.hl7.fhir.r4.model.Library getLibraryFromMeasure(Measure measure,
LibraryResolutionProvider<org.hl7.fhir.r4.model.Library> libraryResourceProvider) {

org.hl7.fhir.r4.model.Library primaryLibrary = null;
Iterator var6 = measure.getLibrary().iterator();

String library= null;
while(var6.hasNext()) {
org.hl7.fhir.r4.model.CanonicalType ref = (org.hl7.fhir.r4.model.CanonicalType) var6.next();
library = (String) ref.getValue();
sliver007 marked this conversation as resolved.
Show resolved Hide resolved

}
primaryLibrary = libraryHelper.resolveLibraryReference(libraryResourceProvider, library);

return primaryLibrary;
}

private Map<VersionedIdentifier, Pair<Library, org.hl7.fhir.r4.model.Library>> createLibraryMap(Measure measure,
LibraryResolutionProvider<org.hl7.fhir.r4.model.Library> libraryResourceProvider) {
LibraryLoader libraryLoader = this.libraryHelper.createLibraryLoader(libraryResourceProvider);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package org.opencds.cqf.r4.providers;

import java.io.InputStream;
import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
Expand All @@ -13,14 +16,20 @@
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;

import ca.uhn.fhir.jpa.rp.r4.LibraryResourceProvider;
import com.google.common.base.Strings;

import org.cqframework.cql.cql2elm.CqlTranslator;
import org.cqframework.cql.cql2elm.CqlTranslatorOptions;
import org.cqframework.cql.cql2elm.LibraryManager;
import org.cqframework.cql.cql2elm.ModelManager;
import org.hibernate.cfg.NotYetImplementedException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Attachment;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.r4.model.CanonicalType;
Expand All @@ -32,6 +41,7 @@
import org.hl7.fhir.r4.model.Group;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Identifier;
import org.hl7.fhir.r4.model.Library;
import org.hl7.fhir.r4.model.ListResource;
import org.hl7.fhir.r4.model.Measure;
import org.hl7.fhir.r4.model.MeasureReport;
Expand All @@ -49,6 +59,8 @@
import org.opencds.cqf.common.config.HapiProperties;
import org.opencds.cqf.common.evaluation.EvaluationProviderFactory;
import org.opencds.cqf.common.helpers.DateHelper;
import org.opencds.cqf.common.helpers.TranslatorHelper;
import org.opencds.cqf.common.providers.LibraryContentProvider;
import org.opencds.cqf.cql.engine.execution.LibraryLoader;
import org.opencds.cqf.r4.evaluation.MeasureEvaluation;
import org.opencds.cqf.r4.evaluation.MeasureEvaluationSeed;
Expand Down Expand Up @@ -95,7 +107,7 @@ public class MeasureOperationsProvider {
private static final Logger logger = LoggerFactory.getLogger(MeasureOperationsProvider.class);

@Inject
public MeasureOperationsProvider(DaoRegistry registry, EvaluationProviderFactory factory,
public MeasureOperationsProvider(DaoRegistry registry, EvaluationProviderFactory factory, LibraryResourceProvider libraryResourceProvider,
NarrativeProvider narrativeProvider, HQMFProvider hqmfProvider,
LibraryResolutionProvider<org.hl7.fhir.r4.model.Library> libraryResolutionProvider,
MeasureResourceProvider measureResourceProvider, DataRequirementsProvider dataRequirementsProvider, LibraryHelper libraryHelper) {
Expand All @@ -110,6 +122,28 @@ public MeasureOperationsProvider(DaoRegistry registry, EvaluationProviderFactory
this.libraryHelper = libraryHelper;
}

private ModelManager getModelManager() {
sliver007 marked this conversation as resolved.
Show resolved Hide resolved
return new ModelManager();
}

private LibraryManager getLibraryManager(ModelManager modelManager) {
LibraryManager libraryManager = new LibraryManager(modelManager);
libraryManager.getLibrarySourceLoader().clearProviders();
libraryManager.getLibrarySourceLoader().registerProvider(getLibrarySourceProvider());

return libraryManager;
}

private LibraryContentProvider<org.hl7.fhir.r4.model.Library, org.hl7.fhir.r4.model.Attachment> librarySourceProvider;

private LibraryContentProvider<Library, Attachment> getLibrarySourceProvider() {
sliver007 marked this conversation as resolved.
Show resolved Hide resolved
if (librarySourceProvider == null) {
librarySourceProvider = new LibraryContentProvider<org.hl7.fhir.r4.model.Library, org.hl7.fhir.r4.model.Attachment>(
this.libraryResolutionProvider, x -> x.getContent(), x -> x.getContentType(), x -> x.getData());
}
return librarySourceProvider;
}

@Operation(name = "$hqmf", idempotent = true, type = Measure.class)
public Parameters hqmf(@IdParam IdType theId) {
Measure theResource = this.measureResourceProvider.getDao().read(theId);
Expand Down Expand Up @@ -880,7 +914,49 @@ public org.hl7.fhir.r4.model.Library dataRequirements(@IdParam IdType theId,
@OperationParam(name = "endPeriod") String endPeriod) throws InternalErrorException, FHIRException {

Measure measure = this.measureResourceProvider.getDao().read(theId);
return this.dataRequirementsProvider.getDataRequirements(measure, this.libraryResolutionProvider);

ModelManager modelManager = this.getModelManager();
LibraryManager libraryManager = this.getLibraryManager(modelManager);

Library library = dataRequirementsProvider.getLibraryFromMeasure(measure, libraryResolutionProvider);

if (library == null) {
throw new RuntimeException("Could not load measure library.");
}

CqlTranslator translator = TranslatorHelper.getTranslator(getContentStream(library), libraryManager, modelManager);

if (translator.getErrors().size() > 0) {
throw new RuntimeException("Errors during library compilation.");
}

return this.dataRequirementsProvider.getModuleDefinitionLibrary(measure, libraryManager, translator.getTranslatedLibrary(), getTranslatorOptions());
}


private CqlTranslatorOptions getTranslatorOptions() {
CqlTranslatorOptions cqlTranslatorOptions = new CqlTranslatorOptions();
sliver007 marked this conversation as resolved.
Show resolved Hide resolved
cqlTranslatorOptions.getFormats().add(CqlTranslator.Format.JSON);
cqlTranslatorOptions.getOptions().add(CqlTranslator.Options.EnableAnnotations);
cqlTranslatorOptions.setAnalyzeDataRequirements(true);
cqlTranslatorOptions.setCollapseDataRequirements(true);
return cqlTranslatorOptions;
}

private InputStream getContentStream(org.hl7.fhir.r4.model.Library library) {
Attachment cql = null;
for (Attachment a : library.getContent()) {
if (a.getContentType().equals("text/cql")) {
cql = a;
break;
}
}

if (cql == null) {
return null;
}

return new ByteArrayInputStream(Base64.getDecoder().decode(cql.getDataElement().getValueAsString()));
}

@SuppressWarnings("unchecked")
Expand Down