Skip to content

Commit 955f07e

Browse files
authored
Added support for population-level data requirement processing to IG refresh measure processor (#536)
- Updated Refresh IG params to include flag for population level data requirement processing - Added the population level data requirement processing logic to the MeasureRefreshProcessor - Tested locally with the NHSN measure repository measures
1 parent eb2ec44 commit 955f07e

9 files changed

Lines changed: 152 additions & 106 deletions

File tree

tooling/src/main/java/org/opencds/cqf/tooling/measure/MeasureProcessor.java

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,23 +20,25 @@
2020
import java.util.concurrent.CopyOnWriteArrayList;
2121

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

2626
public static String getId(String baseId) {
27-
return ResourcePrefix + baseId;
27+
return RESOURCE_PREFIX + baseId;
2828
}
2929

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

3333
return refreshIgMeasureContent(parentContext, outputEncoding, null, versioned, fhirContext, measureToRefreshPath,
34-
shouldApplySoftwareSystemStamp);
34+
shouldApplySoftwareSystemStamp, shouldIncludePopDataRequirements);
3535
}
3636

37+
38+
3739
public List<String> refreshIgMeasureContent(BaseProcessor parentContext, Encoding outputEncoding, String measureOutputDirectory,
3840
Boolean versioned, FhirContext fhirContext, String measureToRefreshPath,
39-
Boolean shouldApplySoftwareSystemStamp) {
41+
Boolean shouldApplySoftwareSystemStamp, Boolean shouldIncludePopDataRequirements) {
4042

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

@@ -61,6 +63,8 @@ public List<String> refreshIgMeasureContent(BaseProcessor parentContext, Encodin
6163
params.encoding = outputEncoding;
6264
params.versioned = versioned;
6365
params.measureOutputDirectory = measureOutputDirectory;
66+
params.shouldApplySoftwareSystemStamp = shouldApplySoftwareSystemStamp;
67+
params.includePopulationDataRequirements = shouldIncludePopDataRequirements;
6468
List<String> contentList = measureProcessor.refreshMeasureContent(params);
6569

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

113117
List<CqlCompilerException> errors = new CopyOnWriteArrayList<>();
114-
CompiledLibrary CompiledLibrary = libraryManager.resolveLibrary(primaryLibraryIdentifier, errors);
118+
CompiledLibrary compiledLibrary = libraryManager.resolveLibrary(primaryLibraryIdentifier, errors);
115119

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

118122
boolean hasSevereErrors = CqlProcessor.hasSevereErrors(errors);
119123

120124
//refresh measures without severe errors:
121125
if (!hasSevereErrors) {
122-
return processor.refreshMeasure(measure, libraryManager, CompiledLibrary, cqlTranslatorOptions.getCqlCompilerOptions());
126+
return processor.refreshMeasure(measure, libraryManager, compiledLibrary, cqlTranslatorOptions.getCqlCompilerOptions());
123127
}
124128

125129
return measure;

tooling/src/main/java/org/opencds/cqf/tooling/measure/MeasureRefreshProcessor.java

Lines changed: 37 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import java.util.HashSet;
55
import java.util.List;
66
import java.util.Set;
7+
import java.util.Collections;
78

89
import org.cqframework.cql.cql2elm.CqlCompilerOptions;
910
import org.cqframework.cql.cql2elm.LibraryManager;
@@ -17,19 +18,13 @@
1718
import org.hl7.fhir.r5.model.Reference;
1819
import org.hl7.fhir.r5.model.RelatedArtifact;
1920
import org.hl7.fhir.r5.model.Resource;
21+
import org.hl7.fhir.r5.model.StringType;
2022

2123
public class MeasureRefreshProcessor {
22-
public Measure refreshMeasure(Measure measureToUse, LibraryManager libraryManager, CompiledLibrary CompiledLibrary, CqlCompilerOptions options) {
2324

24-
Library moduleDefinitionLibrary = getModuleDefinitionLibrary(measureToUse, libraryManager, CompiledLibrary, options);
25+
public Boolean includePopulationDataRequirements = false;
2526

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

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

37+
Library moduleDefinitionLibrary = getModuleDefinitionLibrary(measureToUse, libraryManager, compiledLibrary, options);
38+
measureToUse.setDate(new Date());
39+
// http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/measure-cqfm
40+
setMeta(measureToUse, moduleDefinitionLibrary);
41+
moduleDefinitionLibrary.setId("effective-data-requirements");
4242
setEffectiveDataRequirements(measureToUse, moduleDefinitionLibrary);
43+
setEffectiveDataRequirementsReference(measureToUse);
44+
if (Boolean.TRUE.equals(includePopulationDataRequirements)) {
45+
setPopulationDataRequirements(measureToUse, libraryManager, compiledLibrary, options);
46+
}
4347

4448
return measureToUse;
4549
}
4650

47-
private Library getModuleDefinitionLibrary(Measure measureToUse, LibraryManager libraryManager, CompiledLibrary CompiledLibrary, CqlCompilerOptions options){
51+
private Library getModuleDefinitionLibrary(Measure measureToUse, LibraryManager libraryManager, CompiledLibrary compiledLibrary, CqlCompilerOptions options){
4852
Set<String> expressionList = getExpressions(measureToUse);
4953
DataRequirementsProcessor dqReqTrans = new DataRequirementsProcessor();
50-
return dqReqTrans.gatherDataRequirements(libraryManager, CompiledLibrary, options, expressionList, true);
54+
return dqReqTrans.gatherDataRequirements(libraryManager, compiledLibrary, options, expressionList, true);
55+
}
56+
57+
private void setPopulationDataRequirements(Measure measureToUse, LibraryManager libraryManager, CompiledLibrary compiledLibrary, CqlCompilerOptions options) {
58+
DataRequirementsProcessor dqReqTrans = new DataRequirementsProcessor();
59+
measureToUse.getGroup().forEach(groupMember -> groupMember.getPopulation().forEach(population -> {
60+
if (population.hasId()) { // Requirement for computable measures
61+
var popMDL = dqReqTrans.gatherDataRequirements(libraryManager, compiledLibrary, options, Collections.singleton(population.getCriteria().getExpression()), false);
62+
var mdlID = population.getId() + "-effectiveDataRequirements";
63+
popMDL.setId(mdlID);
64+
setEffectiveDataRequirements(measureToUse, popMDL);
65+
population.getExtension().removeAll(population.getExtensionsByUrl("http://hl7.org/fhir/StructureDefinition/artifact-reference"));
66+
population.addExtension("http://hl7.org/fhir/StructureDefinition/artifact-reference", new StringType("#" + mdlID));
67+
}
68+
}));
5169
}
5270

5371
private Set<String> getExpressions(Measure measureToUse) {
5472
Set<String> expressionSet = new HashSet<>();
55-
measureToUse.getSupplementalData().forEach(supData->{
56-
expressionSet.add(supData.getCriteria().getExpression());
57-
});
73+
measureToUse.getSupplementalData().forEach(supData-> expressionSet.add(supData.getCriteria().getExpression()));
5874
measureToUse.getGroup().forEach(groupMember->{
59-
groupMember.getPopulation().forEach(population->{
60-
expressionSet.add(population.getCriteria().getExpression());
61-
});
62-
groupMember.getStratifier().forEach(stratifier->{
63-
expressionSet.add(stratifier.getCriteria().getExpression());
64-
});
75+
groupMember.getPopulation().forEach(population-> expressionSet.add(population.getCriteria().getExpression()));
76+
groupMember.getStratifier().forEach(stratifier-> expressionSet.add(stratifier.getCriteria().getExpression()));
6577
});
6678
return expressionSet;
6779
}
@@ -76,12 +88,9 @@ private void clearRelatedArtifacts(Measure measure) {
7688
}
7789

7890
private void setEffectiveDataRequirements(Measure measureToUse, Library moduleDefinitionLibrary) {
79-
80-
moduleDefinitionLibrary.setId("effective-data-requirements");
81-
8291
int delIndex = -1;
8392
for (Resource res : measureToUse.getContained()) {
84-
if (res instanceof Library && ((Library)res).getId().equalsIgnoreCase("effective-data-requirements")) {
93+
if (res instanceof Library && res.getId().equalsIgnoreCase(moduleDefinitionLibrary.getId())) {
8594
delIndex = measureToUse.getContained().indexOf(res);
8695
break;
8796
}
@@ -92,9 +101,11 @@ private void setEffectiveDataRequirements(Measure measureToUse, Library moduleDe
92101
}
93102

94103
measureToUse.getContained().add(moduleDefinitionLibrary);
104+
}
95105

96-
Extension effDataReqExtension = new Extension();
97-
effDataReqExtension.setUrl("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-effectiveDataRequirements");
106+
private void setEffectiveDataRequirementsReference(Measure measureToUse) {
107+
Extension effDataReqExtension = new Extension();
108+
effDataReqExtension.setUrl("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-effectiveDataRequirements");
98109
effDataReqExtension.setId("effective-data-requirements");
99110
effDataReqExtension.setValue(new Reference().setReference("#effective-data-requirements"));
100111
measureToUse.addExtension(effDataReqExtension);

tooling/src/main/java/org/opencds/cqf/tooling/measure/r4/R4MeasureProcessor.java

Lines changed: 67 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,34 @@
11
package org.opencds.cqf.tooling.measure.r4;
22

3+
import org.cqframework.cql.cql2elm.CqlCompilerException;
4+
import org.cqframework.cql.cql2elm.CqlTranslatorOptions;
5+
import org.cqframework.cql.cql2elm.LibraryManager;
6+
import org.cqframework.cql.cql2elm.model.CompiledLibrary;
7+
import org.hl7.elm.r1.VersionedIdentifier;
38
import org.hl7.fhir.convertors.advisors.impl.BaseAdvisor_40_50;
49
import org.hl7.fhir.convertors.conv40_50.VersionConvertor_40_50;
510
import org.hl7.fhir.r4.formats.FormatUtilities;
11+
import org.hl7.fhir.r5.model.Measure;
612
import org.opencds.cqf.tooling.common.r4.CqfmSoftwareSystemHelper;
713
import org.opencds.cqf.tooling.measure.MeasureProcessor;
14+
import org.opencds.cqf.tooling.measure.MeasureRefreshProcessor;
815
import org.opencds.cqf.tooling.parameter.RefreshMeasureParameters;
16+
import org.opencds.cqf.tooling.processor.CqlProcessor;
17+
import org.opencds.cqf.tooling.utilities.CanonicalUtils;
918
import org.opencds.cqf.tooling.utilities.IOUtils;
19+
import org.opencds.cqf.tooling.utilities.ResourceUtils;
1020

1121
import java.io.File;
1222
import java.util.*;
23+
import java.util.concurrent.CopyOnWriteArrayList;
1324

1425
public class R4MeasureProcessor extends MeasureProcessor {
1526

1627
private String measurePath;
1728
private String measureOutputDirectory;
1829
private IOUtils.Encoding encoding;
30+
private Boolean shouldApplySoftwareSystemStamp;
31+
private Boolean includePopulationDataRequirements;
1932
private static CqfmSoftwareSystemHelper cqfmHelper;
2033

2134
private String getMeasurePath(String measurePath) {
@@ -60,11 +73,11 @@ else if (file.isDirectory()) {
6073
loadMeasure(fileMap, measures, file);
6174
}
6275

63-
List<String> refreshedMeasureNames = new ArrayList<String>();
64-
List<org.hl7.fhir.r5.model.Measure> refreshedMeasures = super.refreshGeneratedContent(measures);
65-
VersionConvertor_40_50 versionConvertor_40_50 = new VersionConvertor_40_50(new BaseAdvisor_40_50());
76+
List<String> refreshedMeasureNames = new ArrayList<>();
77+
List<org.hl7.fhir.r5.model.Measure> refreshedMeasures = refreshGeneratedContent(measures);
78+
VersionConvertor_40_50 versionConvertor = new VersionConvertor_40_50(new BaseAdvisor_40_50());
6679
for (org.hl7.fhir.r5.model.Measure refreshedMeasure : refreshedMeasures) {
67-
org.hl7.fhir.r4.model.Measure measure = (org.hl7.fhir.r4.model.Measure) versionConvertor_40_50.convertResource(refreshedMeasure);
80+
org.hl7.fhir.r4.model.Measure measure = (org.hl7.fhir.r4.model.Measure) versionConvertor.convertResource(refreshedMeasure);
6881
if (measure.hasIdentifier() && !measure.getIdentifier().isEmpty()) {
6982
this.getIdentifiers().addAll(measure.getIdentifier());
7083
}
@@ -111,15 +124,61 @@ else if (file.isDirectory()) {
111124
private void loadMeasure(Map<String, String> fileMap, List<org.hl7.fhir.r5.model.Measure> measures, File measureFile) {
112125
try {
113126
org.hl7.fhir.r4.model.Resource resource = FormatUtilities.loadFile(measureFile.getAbsolutePath());
114-
VersionConvertor_40_50 versionConvertor_40_50 = new VersionConvertor_40_50(new BaseAdvisor_40_50());
115-
org.hl7.fhir.r5.model.Measure measure = (org.hl7.fhir.r5.model.Measure) versionConvertor_40_50.convertResource(resource);
127+
VersionConvertor_40_50 versionConvertor = new VersionConvertor_40_50(new BaseAdvisor_40_50());
128+
org.hl7.fhir.r5.model.Measure measure = (org.hl7.fhir.r5.model.Measure) versionConvertor.convertResource(resource);
116129
fileMap.put(measure.getId(), measureFile.getAbsolutePath());
117130
measures.add(measure);
118131
} catch (Exception ex) {
119132
logMessage(String.format("Error reading measure: %s. Error: %s", measureFile.getAbsolutePath(), ex.getMessage()));
120133
}
121134
}
122135

136+
@Override
137+
protected List<Measure> refreshGeneratedContent(List<Measure> sourceMeasures) {
138+
return internalRefreshGeneratedContent(sourceMeasures);
139+
}
140+
141+
private List<Measure> internalRefreshGeneratedContent(List<Measure> sourceMeasures) {
142+
// for each Measure, refresh the measure based on the primary measure library
143+
List<Measure> resources = new ArrayList<>();
144+
MeasureRefreshProcessor processor = new MeasureRefreshProcessor();
145+
LibraryManager libraryManager = getCqlProcessor().getLibraryManager();
146+
CqlTranslatorOptions cqlTranslatorOptions = getCqlProcessor().getCqlTranslatorOptions();
147+
for (Measure measure : sourceMeasures) {
148+
// Do not attempt to refresh if the measure does not have a library
149+
if (measure.hasLibrary()) {
150+
resources.add(refreshGeneratedContent(measure, processor, libraryManager, cqlTranslatorOptions));
151+
} else {
152+
resources.add(measure);
153+
}
154+
}
155+
156+
return resources;
157+
}
158+
159+
private Measure refreshGeneratedContent(Measure measure, MeasureRefreshProcessor processor, LibraryManager libraryManager, CqlTranslatorOptions cqlTranslatorOptions) {
160+
161+
String libraryUrl = ResourceUtils.getPrimaryLibraryUrl(measure, fhirContext);
162+
VersionedIdentifier primaryLibraryIdentifier = CanonicalUtils.toVersionedIdentifier(libraryUrl);
163+
164+
List<CqlCompilerException> errors = new CopyOnWriteArrayList<>();
165+
CompiledLibrary compiledLibrary = libraryManager.resolveLibrary(primaryLibraryIdentifier, errors);
166+
167+
logger.info(CqlProcessor.buildStatusMessage(errors, measure.getName(), verboseMessaging));
168+
169+
boolean hasSevereErrors = CqlProcessor.hasSevereErrors(errors);
170+
171+
//refresh measures without severe errors:
172+
if (!hasSevereErrors) {
173+
if (includePopulationDataRequirements != null) {
174+
processor.includePopulationDataRequirements = includePopulationDataRequirements;
175+
}
176+
return processor.refreshMeasure(measure, libraryManager, compiledLibrary, cqlTranslatorOptions.getCqlCompilerOptions());
177+
}
178+
179+
return measure;
180+
}
181+
123182
@Override
124183
public List<String> refreshMeasureContent(RefreshMeasureParameters params) {
125184
if (params.parentContext != null) {
@@ -134,6 +193,8 @@ public List<String> refreshMeasureContent(RefreshMeasureParameters params) {
134193
fhirContext = params.fhirContext;
135194
encoding = params.encoding;
136195
versioned = params.versioned;
196+
shouldApplySoftwareSystemStamp = params.shouldApplySoftwareSystemStamp;
197+
includePopulationDataRequirements = params.includePopulationDataRequirements;
137198

138199
R4MeasureProcessor.cqfmHelper = new CqfmSoftwareSystemHelper(rootDir);
139200

0 commit comments

Comments
 (0)