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

Added support for population-level data requirement processing to IG refresh measure processor #536

Merged
merged 1 commit into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,25 @@
import java.util.concurrent.CopyOnWriteArrayList;

public class MeasureProcessor extends BaseProcessor {
public static String ResourcePrefix = "measure-";
public static final String RESOURCE_PREFIX = "measure-";
protected List<Object> identifiers;

public static String getId(String baseId) {
return ResourcePrefix + baseId;
return RESOURCE_PREFIX + baseId;
}

public List<String> refreshIgMeasureContent(BaseProcessor parentContext, Encoding outputEncoding, Boolean versioned, FhirContext fhirContext,
String measureToRefreshPath, Boolean shouldApplySoftwareSystemStamp) {
String measureToRefreshPath, Boolean shouldApplySoftwareSystemStamp, Boolean shouldIncludePopDataRequirements) {

return refreshIgMeasureContent(parentContext, outputEncoding, null, versioned, fhirContext, measureToRefreshPath,
shouldApplySoftwareSystemStamp);
shouldApplySoftwareSystemStamp, shouldIncludePopDataRequirements);
}



public List<String> refreshIgMeasureContent(BaseProcessor parentContext, Encoding outputEncoding, String measureOutputDirectory,
Boolean versioned, FhirContext fhirContext, String measureToRefreshPath,
Boolean shouldApplySoftwareSystemStamp) {
Boolean shouldApplySoftwareSystemStamp, Boolean shouldIncludePopDataRequirements) {

logger.info("[Refreshing Measures]");

Expand All @@ -61,6 +63,8 @@ public List<String> refreshIgMeasureContent(BaseProcessor parentContext, Encodin
params.encoding = outputEncoding;
params.versioned = versioned;
params.measureOutputDirectory = measureOutputDirectory;
params.shouldApplySoftwareSystemStamp = shouldApplySoftwareSystemStamp;
params.includePopulationDataRequirements = shouldIncludePopDataRequirements;
List<String> contentList = measureProcessor.refreshMeasureContent(params);

if (!measureProcessor.getIdentifiers().isEmpty()) {
Expand Down Expand Up @@ -111,15 +115,15 @@ private Measure refreshGeneratedContent(Measure measure, MeasureRefreshProcessor
VersionedIdentifier primaryLibraryIdentifier = CanonicalUtils.toVersionedIdentifier(libraryUrl);

List<CqlCompilerException> errors = new CopyOnWriteArrayList<>();
CompiledLibrary CompiledLibrary = libraryManager.resolveLibrary(primaryLibraryIdentifier, errors);
CompiledLibrary compiledLibrary = libraryManager.resolveLibrary(primaryLibraryIdentifier, errors);

logger.info(CqlProcessor.buildStatusMessage(errors, measure.getName(), verboseMessaging));

boolean hasSevereErrors = CqlProcessor.hasSevereErrors(errors);

//refresh measures without severe errors:
if (!hasSevereErrors) {
return processor.refreshMeasure(measure, libraryManager, CompiledLibrary, cqlTranslatorOptions.getCqlCompilerOptions());
return processor.refreshMeasure(measure, libraryManager, compiledLibrary, cqlTranslatorOptions.getCqlCompilerOptions());
}

return measure;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Collections;

import org.cqframework.cql.cql2elm.CqlCompilerOptions;
import org.cqframework.cql.cql2elm.LibraryManager;
Expand All @@ -17,19 +18,13 @@
import org.hl7.fhir.r5.model.Reference;
import org.hl7.fhir.r5.model.RelatedArtifact;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.StringType;

public class MeasureRefreshProcessor {
public Measure refreshMeasure(Measure measureToUse, LibraryManager libraryManager, CompiledLibrary CompiledLibrary, CqlCompilerOptions options) {

Library moduleDefinitionLibrary = getModuleDefinitionLibrary(measureToUse, libraryManager, CompiledLibrary, options);
public Boolean includePopulationDataRequirements = false;

measureToUse.setDate(new Date());
// http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/measure-cqfm
setMeta(measureToUse, moduleDefinitionLibrary);
// Don't need to do this... it is required information to perform this processing in the first place, should just be left alone
//setLibrary(measureToUse, CompiledLibrary);
// Don't need to do this... type isn't a computable attribute, it's just metadata and will come from the source measure
//setType(measureToUse);
public Measure refreshMeasure(Measure measureToUse, LibraryManager libraryManager, CompiledLibrary compiledLibrary, CqlCompilerOptions options) {

// Computable measure http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/computable-measure-cqfm
clearMeasureExtensions(measureToUse, "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-parameter");
Expand All @@ -39,29 +34,46 @@ public Measure refreshMeasure(Measure measureToUse, LibraryManager libraryManage
clearMeasureExtensions(measureToUse, "http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-effectiveDataRequirements");
clearRelatedArtifacts(measureToUse);

Library moduleDefinitionLibrary = getModuleDefinitionLibrary(measureToUse, libraryManager, compiledLibrary, options);
measureToUse.setDate(new Date());
// http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/measure-cqfm
setMeta(measureToUse, moduleDefinitionLibrary);
moduleDefinitionLibrary.setId("effective-data-requirements");
setEffectiveDataRequirements(measureToUse, moduleDefinitionLibrary);
setEffectiveDataRequirementsReference(measureToUse);
if (Boolean.TRUE.equals(includePopulationDataRequirements)) {
setPopulationDataRequirements(measureToUse, libraryManager, compiledLibrary, options);
}

return measureToUse;
}

private Library getModuleDefinitionLibrary(Measure measureToUse, LibraryManager libraryManager, CompiledLibrary CompiledLibrary, CqlCompilerOptions options){
private Library getModuleDefinitionLibrary(Measure measureToUse, LibraryManager libraryManager, CompiledLibrary compiledLibrary, CqlCompilerOptions options){
Set<String> expressionList = getExpressions(measureToUse);
DataRequirementsProcessor dqReqTrans = new DataRequirementsProcessor();
return dqReqTrans.gatherDataRequirements(libraryManager, CompiledLibrary, options, expressionList, true);
return dqReqTrans.gatherDataRequirements(libraryManager, compiledLibrary, options, expressionList, true);
}

private void setPopulationDataRequirements(Measure measureToUse, LibraryManager libraryManager, CompiledLibrary compiledLibrary, CqlCompilerOptions options) {
DataRequirementsProcessor dqReqTrans = new DataRequirementsProcessor();
measureToUse.getGroup().forEach(groupMember -> groupMember.getPopulation().forEach(population -> {
if (population.hasId()) { // Requirement for computable measures
var popMDL = dqReqTrans.gatherDataRequirements(libraryManager, compiledLibrary, options, Collections.singleton(population.getCriteria().getExpression()), false);
var mdlID = population.getId() + "-effectiveDataRequirements";
popMDL.setId(mdlID);
setEffectiveDataRequirements(measureToUse, popMDL);
population.getExtension().removeAll(population.getExtensionsByUrl("http://hl7.org/fhir/StructureDefinition/artifact-reference"));
population.addExtension("http://hl7.org/fhir/StructureDefinition/artifact-reference", new StringType("#" + mdlID));
}
}));
}

private Set<String> getExpressions(Measure measureToUse) {
Set<String> expressionSet = new HashSet<>();
measureToUse.getSupplementalData().forEach(supData->{
expressionSet.add(supData.getCriteria().getExpression());
});
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());
});
groupMember.getPopulation().forEach(population-> expressionSet.add(population.getCriteria().getExpression()));
groupMember.getStratifier().forEach(stratifier-> expressionSet.add(stratifier.getCriteria().getExpression()));
});
return expressionSet;
}
Expand All @@ -76,12 +88,9 @@ private void clearRelatedArtifacts(Measure measure) {
}

private void setEffectiveDataRequirements(Measure measureToUse, Library moduleDefinitionLibrary) {

moduleDefinitionLibrary.setId("effective-data-requirements");

int delIndex = -1;
for (Resource res : measureToUse.getContained()) {
if (res instanceof Library && ((Library)res).getId().equalsIgnoreCase("effective-data-requirements")) {
if (res instanceof Library && res.getId().equalsIgnoreCase(moduleDefinitionLibrary.getId())) {
delIndex = measureToUse.getContained().indexOf(res);
break;
}
Expand All @@ -92,9 +101,11 @@ private void setEffectiveDataRequirements(Measure measureToUse, Library moduleDe
}

measureToUse.getContained().add(moduleDefinitionLibrary);
}

Extension effDataReqExtension = new Extension();
effDataReqExtension.setUrl("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-effectiveDataRequirements");
private void setEffectiveDataRequirementsReference(Measure measureToUse) {
Extension effDataReqExtension = new Extension();
effDataReqExtension.setUrl("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-effectiveDataRequirements");
effDataReqExtension.setId("effective-data-requirements");
effDataReqExtension.setValue(new Reference().setReference("#effective-data-requirements"));
measureToUse.addExtension(effDataReqExtension);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,34 @@
package org.opencds.cqf.tooling.measure.r4;

import org.cqframework.cql.cql2elm.CqlCompilerException;
import org.cqframework.cql.cql2elm.CqlTranslatorOptions;
import org.cqframework.cql.cql2elm.LibraryManager;
import org.cqframework.cql.cql2elm.model.CompiledLibrary;
import org.hl7.elm.r1.VersionedIdentifier;
import org.hl7.fhir.convertors.advisors.impl.BaseAdvisor_40_50;
import org.hl7.fhir.convertors.conv40_50.VersionConvertor_40_50;
import org.hl7.fhir.r4.formats.FormatUtilities;
import org.hl7.fhir.r5.model.Measure;
import org.opencds.cqf.tooling.common.r4.CqfmSoftwareSystemHelper;
import org.opencds.cqf.tooling.measure.MeasureProcessor;
import org.opencds.cqf.tooling.measure.MeasureRefreshProcessor;
import org.opencds.cqf.tooling.parameter.RefreshMeasureParameters;
import org.opencds.cqf.tooling.processor.CqlProcessor;
import org.opencds.cqf.tooling.utilities.CanonicalUtils;
import org.opencds.cqf.tooling.utilities.IOUtils;
import org.opencds.cqf.tooling.utilities.ResourceUtils;

import java.io.File;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

public class R4MeasureProcessor extends MeasureProcessor {

private String measurePath;
private String measureOutputDirectory;
private IOUtils.Encoding encoding;
private Boolean shouldApplySoftwareSystemStamp;
private Boolean includePopulationDataRequirements;
private static CqfmSoftwareSystemHelper cqfmHelper;

private String getMeasurePath(String measurePath) {
Expand Down Expand Up @@ -60,11 +73,11 @@ else if (file.isDirectory()) {
loadMeasure(fileMap, measures, file);
}

List<String> refreshedMeasureNames = new ArrayList<String>();
List<org.hl7.fhir.r5.model.Measure> refreshedMeasures = super.refreshGeneratedContent(measures);
VersionConvertor_40_50 versionConvertor_40_50 = new VersionConvertor_40_50(new BaseAdvisor_40_50());
List<String> refreshedMeasureNames = new ArrayList<>();
List<org.hl7.fhir.r5.model.Measure> refreshedMeasures = refreshGeneratedContent(measures);
VersionConvertor_40_50 versionConvertor = new VersionConvertor_40_50(new BaseAdvisor_40_50());
for (org.hl7.fhir.r5.model.Measure refreshedMeasure : refreshedMeasures) {
org.hl7.fhir.r4.model.Measure measure = (org.hl7.fhir.r4.model.Measure) versionConvertor_40_50.convertResource(refreshedMeasure);
org.hl7.fhir.r4.model.Measure measure = (org.hl7.fhir.r4.model.Measure) versionConvertor.convertResource(refreshedMeasure);
if (measure.hasIdentifier() && !measure.getIdentifier().isEmpty()) {
this.getIdentifiers().addAll(measure.getIdentifier());
}
Expand Down Expand Up @@ -111,15 +124,61 @@ else if (file.isDirectory()) {
private void loadMeasure(Map<String, String> fileMap, List<org.hl7.fhir.r5.model.Measure> measures, File measureFile) {
try {
org.hl7.fhir.r4.model.Resource resource = FormatUtilities.loadFile(measureFile.getAbsolutePath());
VersionConvertor_40_50 versionConvertor_40_50 = new VersionConvertor_40_50(new BaseAdvisor_40_50());
org.hl7.fhir.r5.model.Measure measure = (org.hl7.fhir.r5.model.Measure) versionConvertor_40_50.convertResource(resource);
VersionConvertor_40_50 versionConvertor = new VersionConvertor_40_50(new BaseAdvisor_40_50());
org.hl7.fhir.r5.model.Measure measure = (org.hl7.fhir.r5.model.Measure) versionConvertor.convertResource(resource);
fileMap.put(measure.getId(), measureFile.getAbsolutePath());
measures.add(measure);
} catch (Exception ex) {
logMessage(String.format("Error reading measure: %s. Error: %s", measureFile.getAbsolutePath(), ex.getMessage()));
}
}

@Override
protected List<Measure> refreshGeneratedContent(List<Measure> sourceMeasures) {
return internalRefreshGeneratedContent(sourceMeasures);
}

private List<Measure> internalRefreshGeneratedContent(List<Measure> sourceMeasures) {
// for each Measure, refresh the measure based on the primary measure library
List<Measure> resources = new ArrayList<>();
MeasureRefreshProcessor processor = new MeasureRefreshProcessor();
LibraryManager libraryManager = getCqlProcessor().getLibraryManager();
CqlTranslatorOptions cqlTranslatorOptions = getCqlProcessor().getCqlTranslatorOptions();
for (Measure measure : sourceMeasures) {
// Do not attempt to refresh if the measure does not have a library
if (measure.hasLibrary()) {
resources.add(refreshGeneratedContent(measure, processor, libraryManager, cqlTranslatorOptions));
} else {
resources.add(measure);
}
}

return resources;
}

private Measure refreshGeneratedContent(Measure measure, MeasureRefreshProcessor processor, LibraryManager libraryManager, CqlTranslatorOptions cqlTranslatorOptions) {

String libraryUrl = ResourceUtils.getPrimaryLibraryUrl(measure, fhirContext);
VersionedIdentifier primaryLibraryIdentifier = CanonicalUtils.toVersionedIdentifier(libraryUrl);

List<CqlCompilerException> errors = new CopyOnWriteArrayList<>();
CompiledLibrary compiledLibrary = libraryManager.resolveLibrary(primaryLibraryIdentifier, errors);

logger.info(CqlProcessor.buildStatusMessage(errors, measure.getName(), verboseMessaging));

boolean hasSevereErrors = CqlProcessor.hasSevereErrors(errors);

//refresh measures without severe errors:
if (!hasSevereErrors) {
if (includePopulationDataRequirements != null) {
processor.includePopulationDataRequirements = includePopulationDataRequirements;
}
return processor.refreshMeasure(measure, libraryManager, compiledLibrary, cqlTranslatorOptions.getCqlCompilerOptions());
}

return measure;
}

@Override
public List<String> refreshMeasureContent(RefreshMeasureParameters params) {
if (params.parentContext != null) {
Expand All @@ -134,6 +193,8 @@ public List<String> refreshMeasureContent(RefreshMeasureParameters params) {
fhirContext = params.fhirContext;
encoding = params.encoding;
versioned = params.versioned;
shouldApplySoftwareSystemStamp = params.shouldApplySoftwareSystemStamp;
includePopulationDataRequirements = params.includePopulationDataRequirements;

R4MeasureProcessor.cqfmHelper = new CqfmSoftwareSystemHelper(rootDir);

Expand Down
Loading