Skip to content

Commit

Permalink
#377 - added PlanDefinition refresh processing and plugged into IGRef…
Browse files Browse the repository at this point in the history
…resh operation ... updated tests ... pretty print by default in IOUtils
  • Loading branch information
c-schuler committed Oct 3, 2022
1 parent a436a9f commit 92f0e1e
Show file tree
Hide file tree
Showing 15 changed files with 700 additions and 207 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import org.opencds.cqf.tooling.processor.CDSHooksProcessor;
import org.opencds.cqf.tooling.processor.IGBundleProcessor;
import org.opencds.cqf.tooling.processor.IGProcessor;
import org.opencds.cqf.tooling.processor.PlanDefinitionProcessor;
import org.opencds.cqf.tooling.plandefinition.PlanDefinitionProcessor;
import org.opencds.cqf.tooling.processor.argument.RefreshIGArgumentProcessor;
import org.opencds.cqf.tooling.questionnaire.QuestionnaireProcessor;

Expand Down Expand Up @@ -37,7 +37,7 @@ public void execute(String[] args) {
PlanDefinitionProcessor planDefinitionProcessor = new PlanDefinitionProcessor(libraryProcessor, cdsHooksProcessor);
QuestionnaireProcessor questionnaireProcessor = new QuestionnaireProcessor(libraryProcessor);
IGBundleProcessor igBundleProcessor = new IGBundleProcessor(measureProcessor, planDefinitionProcessor, questionnaireProcessor);
IGProcessor processor = new IGProcessor(igBundleProcessor, libraryProcessor, measureProcessor);
IGProcessor processor = new IGProcessor(igBundleProcessor, libraryProcessor, measureProcessor, planDefinitionProcessor);
processor.publishIG(params);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ public class RefreshIGParameters {
public ArrayList<String> resourceDirs;
public Boolean conformant;
public String measureToRefreshPath;
public String planDefinitionToRefreshPath;
public String libraryOutputPath;
public String measureOutputPath;
public String planDefinitionOutputPath;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package org.opencds.cqf.tooling.parameter;

import ca.uhn.fhir.context.FhirContext;
import org.opencds.cqf.tooling.processor.IProcessorContext;
import org.opencds.cqf.tooling.utilities.IOUtils;

public class RefreshPlanDefinitionParameters {
/*
The ig ini file
*/
public String ini;

/*
The canonical base URL of the ig
*/
public String igCanonicalBase;

/*
The path to CQL library content
*/
public String cqlContentPath;

/*
The fhirContext for the current process
*/
public FhirContext fhirContext;

/*
The target encoding for the output
*/
public IOUtils.Encoding encoding;

/*
Whether or not version is included in the name
*/
public Boolean versioned;

/*
The path to the measure resource(s)
*/
public String planDefinitionPath;

/*
An initialized processor context that can provide the IG context directly
*/
public IProcessorContext parentContext;

/*
Directory target for writing output
*/
public String planDefinitionOutputDirectory;
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package org.opencds.cqf.tooling.plandefinition;

import org.cqframework.cql.cql2elm.CqlTranslatorOptions;
import org.cqframework.cql.cql2elm.LibraryManager;
import org.cqframework.cql.cql2elm.model.CompiledLibrary;
import org.cqframework.cql.elm.requirements.fhir.DataRequirementsProcessor;
import org.hl7.fhir.r5.model.*;

import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class PlanDefinitionRefreshProcessor {

public PlanDefinition refreshPlanDefinition(PlanDefinition planToUse, LibraryManager libraryManager,
CompiledLibrary compiledLibrary, CqlTranslatorOptions options) {
planToUse.setDate(new Date());
Set<String> expressions = new HashSet<>();
if (planToUse.hasAction()) {
getExpressions(planToUse.getAction(), expressions);
}
DataRequirementsProcessor dqReqTrans = new DataRequirementsProcessor();
Library moduleDefinitionLibrary = dqReqTrans.gatherDataRequirements(libraryManager, compiledLibrary,
options, expressions, true);

planToUse.getExtension().removeAll(planToUse.getExtensionsByUrl(
"http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-parameter"));
planToUse.getExtension().removeAll(planToUse.getExtensionsByUrl(
"http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-dataRequirement"));
planToUse.getExtension().removeAll(planToUse.getExtensionsByUrl(
"http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-directReferenceCode"));
planToUse.getExtension().removeAll(planToUse.getExtensionsByUrl(
"http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-logicDefinition"));
planToUse.getExtension().removeAll(planToUse.getExtensionsByUrl(
"http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-effectiveDataRequirements"));

for (Extension extension : moduleDefinitionLibrary.getExtension()) {
if (extension.hasUrl()
&& extension.getUrl().equals(
"http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-directReferenceCode")) {
continue;
}
planToUse.addExtension(extension);
}

planToUse.getContained().removeIf(resource -> resource.getId().equalsIgnoreCase("effective-data-requirements"));
// planToUse.addContained(moduleDefinitionLibrary.setId("effective-data-requirements"));
// planToUse.addExtension().setUrl("http://hl7.org/fhir/us/cqfmeasures/StructureDefinition/cqfm-effectiveDataRequirements").setValue(new Reference("#effective-data-requirements")).setId("effective-data-requirements");
return planToUse;
}

private void getExpressions(List<PlanDefinition.PlanDefinitionActionComponent> actions, Set<String> expressions) {
for (PlanDefinition.PlanDefinitionActionComponent action : actions) {
if (action.hasCondition()) {
for (PlanDefinition.PlanDefinitionActionConditionComponent condition : action.getCondition()) {
if (condition.hasKind() && condition.getKind() == Enumerations.ActionConditionKind.APPLICABILITY
&& condition.hasExpression() && isExpressionIdentifier(condition.getExpression())) {
expressions.add(condition.getExpression().getExpression());
}
}
}
if (action.hasDynamicValue()) {
for (PlanDefinition.PlanDefinitionActionDynamicValueComponent dynamicValue : action.getDynamicValue()) {
if (dynamicValue.hasExpression() && isExpressionIdentifier(dynamicValue.getExpression())) {
expressions.add(dynamicValue.getExpression().getExpression());
}
}
}
if (action.hasAction()) {
getExpressions(action.getAction(), expressions);
}
}
}

private boolean isExpressionIdentifier(Expression expression) {
return expression.hasLanguage() && expression.hasExpression()
&& (expression.getLanguage().equalsIgnoreCase("text/cql.identifier")
|| expression.getLanguage().equalsIgnoreCase("text/cql"));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package org.opencds.cqf.tooling.plandefinition.r4;

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.opencds.cqf.tooling.common.r4.CqfmSoftwareSystemHelper;
import org.opencds.cqf.tooling.library.LibraryProcessor;
import org.opencds.cqf.tooling.parameter.RefreshPlanDefinitionParameters;
import org.opencds.cqf.tooling.plandefinition.PlanDefinitionProcessor;
import org.opencds.cqf.tooling.processor.CDSHooksProcessor;
import org.opencds.cqf.tooling.utilities.IOUtils;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

public class R4PlanDefinitionProcessor extends PlanDefinitionProcessor {

private static CqfmSoftwareSystemHelper cqfmHelper;

public R4PlanDefinitionProcessor(LibraryProcessor libraryProcessor, CDSHooksProcessor cdsHooksProcessor) {
super(libraryProcessor, cdsHooksProcessor);
}

private String getPlanDefinitionPath(String planDefinitionPath) {
File f = new File(planDefinitionPath);
if (!f.exists() && f.getParentFile().isDirectory() && f.getParentFile().exists()) {
return f.getParentFile().toString();
}
return planDefinitionPath;
}
/*
Refresh all PlanDefinition resources in the given planDefinitionPath
If the path is not specified, or is not a known directory, process
all known PlanDefinition resources overriding any currently existing files.
*/
protected List<String> refreshPlanDefinitions(String planDefinitionPath, IOUtils.Encoding encoding) {
return refreshPlanDefinitions(planDefinitionPath, null, encoding);
}

/*
Refresh all PlanDefinition resources in the given planDefinitionPath
If the path is not specified, or is not a known directory, process
all known PlanDefinition resources
*/
protected List<String> refreshPlanDefinitions(String planDefinitionPath, String planDefinitionOutputDirectory, IOUtils.Encoding encoding) {
File file = planDefinitionPath != null ? new File(planDefinitionPath) : null;
Map<String, String> fileMap = new HashMap<>();
List<org.hl7.fhir.r5.model.PlanDefinition> planDefinitions = new ArrayList<>();

if (file == null || !file.exists()) {
for (String path : IOUtils.getPlanDefinitionPaths(this.fhirContext)) {
loadPlanDefinition(fileMap, planDefinitions, new File(path));
}
}
else if (file.isDirectory()) {
for (File libraryFile : Objects.requireNonNull(file.listFiles())) {
if(IOUtils.isXMLOrJson(planDefinitionPath, libraryFile.getName())) {
loadPlanDefinition(fileMap, planDefinitions, libraryFile);
}
}
}
else {
loadPlanDefinition(fileMap, planDefinitions, file);
}

List<String> refreshedPlanDefinitionNames = new ArrayList<>();
List<org.hl7.fhir.r5.model.PlanDefinition> refreshedPlanDefinitions = super.refreshGeneratedContent(planDefinitions);
VersionConvertor_40_50 versionConvertor = new VersionConvertor_40_50(new BaseAdvisor_40_50());
for (org.hl7.fhir.r5.model.PlanDefinition refreshedPlanDefinition : refreshedPlanDefinitions) {
org.hl7.fhir.r4.model.PlanDefinition planDefinition = (org.hl7.fhir.r4.model.PlanDefinition) versionConvertor.convertResource(refreshedPlanDefinition);
if (planDefinition.hasIdentifier() && !planDefinition.getIdentifier().isEmpty()) {
this.getIdentifiers().addAll(planDefinition.getIdentifier());
}
String filePath;
IOUtils.Encoding fileEncoding;
if (fileMap.containsKey(refreshedPlanDefinition.getId()))
{
filePath = fileMap.get(refreshedPlanDefinition.getId());
fileEncoding = IOUtils.getEncoding(filePath);
} else {
filePath = getPlanDefinitionPath(planDefinitionPath);
fileEncoding = encoding;
}
cqfmHelper.ensureCQFToolingExtensionAndDevice(planDefinition, fhirContext);
// Issue 96
// Passing the includeVersion here to handle not using the version number in the filename
if (new File(filePath).exists()) {
// TODO: This prevents mangled names from being output
// It would be nice for the tooling to generate library shells, we have enough information to,
// but the tooling gets confused about the ID and the filename and what gets written is garbage
String outputPath = filePath;
if (planDefinitionOutputDirectory != null) {
File planDefinitionDirectory = new File(planDefinitionOutputDirectory);
if (!planDefinitionDirectory.exists()) {
//TODO: add logger and log non existant directory for writing
} else {
outputPath = planDefinitionDirectory.getAbsolutePath();
}
}
IOUtils.writeResource(planDefinition, outputPath, fileEncoding, fhirContext, this.versioned);
String refreshedPlanDefinitionName;
if (this.versioned && refreshedPlanDefinition.getVersion() != null) {
refreshedPlanDefinitionName = refreshedPlanDefinition.getName() + "-" + refreshedPlanDefinition.getVersion();
} else {
refreshedPlanDefinitionName = refreshedPlanDefinition.getName();
}
refreshedPlanDefinitionNames.add(refreshedPlanDefinitionName);
}
}

return refreshedPlanDefinitionNames;
}

private void loadPlanDefinition(Map<String, String> fileMap, List<org.hl7.fhir.r5.model.PlanDefinition> planDefinitions, File planDefinitionFile) {
try {
org.hl7.fhir.r4.model.Resource resource = FormatUtilities.loadFile(planDefinitionFile.getAbsolutePath());
VersionConvertor_40_50 versionConvertor = new VersionConvertor_40_50(new BaseAdvisor_40_50());
org.hl7.fhir.r5.model.PlanDefinition planDefinition = (org.hl7.fhir.r5.model.PlanDefinition) versionConvertor.convertResource(resource);
fileMap.put(planDefinition.getId(), planDefinitionFile.getAbsolutePath());
planDefinitions.add(planDefinition);
} catch (Exception ex) {
logMessage(String.format("Error reading PlanDefinition: %s. Error: %s", planDefinitionFile.getAbsolutePath(), ex.getMessage()));
}
}

@Override
public List<String> refreshPlanDefinitionContent(RefreshPlanDefinitionParameters params) {
if (params.parentContext != null) {
initialize(params.parentContext);
}
else {
initializeFromIni(params.ini);
}

String planDefinitionPath = params.planDefinitionPath;
String planDefinitionOutputDirectory = params.planDefinitionOutputDirectory;
fhirContext = params.fhirContext;
IOUtils.Encoding encoding = params.encoding;
versioned = params.versioned;

cqfmHelper = new CqfmSoftwareSystemHelper(rootDir);

if (planDefinitionOutputDirectory != null) {
return refreshPlanDefinitions(planDefinitionPath, planDefinitionOutputDirectory, encoding);
} else {
return refreshPlanDefinitions(planDefinitionPath, encoding);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.opencds.cqf.tooling.plandefinition.stu3;

import org.opencds.cqf.tooling.library.LibraryProcessor;
import org.opencds.cqf.tooling.plandefinition.PlanDefinitionProcessor;
import org.opencds.cqf.tooling.processor.CDSHooksProcessor;

public class STU3PlanDefinitionProcessor extends PlanDefinitionProcessor {
public STU3PlanDefinitionProcessor(LibraryProcessor libraryProcessor, CDSHooksProcessor cdsHooksProcessor) {
super(libraryProcessor, cdsHooksProcessor);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.util.List;

import org.opencds.cqf.tooling.measure.MeasureProcessor;
import org.opencds.cqf.tooling.plandefinition.PlanDefinitionProcessor;
import org.opencds.cqf.tooling.questionnaire.QuestionnaireProcessor;
import org.opencds.cqf.tooling.utilities.IOUtils.Encoding;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.opencds.cqf.tooling.library.LibraryProcessor;
import org.opencds.cqf.tooling.measure.MeasureProcessor;
import org.opencds.cqf.tooling.parameter.RefreshIGParameters;
import org.opencds.cqf.tooling.plandefinition.PlanDefinitionProcessor;
import org.opencds.cqf.tooling.utilities.IGUtils;
import org.opencds.cqf.tooling.utilities.IOUtils;
import org.opencds.cqf.tooling.utilities.IOUtils.Encoding;
Expand All @@ -26,11 +27,13 @@ public class IGProcessor extends BaseProcessor {
protected IGBundleProcessor igBundleProcessor;
protected LibraryProcessor libraryProcessor;
protected MeasureProcessor measureProcessor;
protected PlanDefinitionProcessor planDefinitionProcessor;

public IGProcessor(IGBundleProcessor igBundleProcessor, LibraryProcessor libraryProcessor, MeasureProcessor measureProcessor) {
public IGProcessor(IGBundleProcessor igBundleProcessor, LibraryProcessor libraryProcessor, MeasureProcessor measureProcessor, PlanDefinitionProcessor planDefinitionProcessor) {
this.igBundleProcessor = igBundleProcessor;
this.libraryProcessor = libraryProcessor;
this.measureProcessor = measureProcessor;
this.planDefinitionProcessor = planDefinitionProcessor;
}
//mega ig method
public void publishIG(RefreshIGParameters params) {
Expand Down Expand Up @@ -123,11 +126,13 @@ public void refreshIG(RefreshIGParameters params) {
// Boolean includeDependencies = params.includeDependencies;
String libraryOutputPath = params.libraryOutputPath;
String measureOutputPath = params.measureOutputPath;
String planDefinitionOutputPath = params.planDefinitionOutputPath;
Boolean includeTerminology = params.includeTerminology;
Boolean includePatientScenarios = params.includePatientScenarios;
Boolean versioned = params.versioned;
// String fhirUri = params.fhirUri;
String measureToRefreshPath = params.measureToRefreshPath;
String planDefinitionToRefreshPath = params.planDefinitionToRefreshPath;
ArrayList<String> resourceDirs = params.resourceDirs;
if (resourceDirs.size() == 0) {
try {
Expand Down Expand Up @@ -158,6 +163,14 @@ public void refreshIG(RefreshIGParameters params) {
}
refreshedResourcesNames.addAll(refreshedMeasureNames);

List<String> refreshedPlanDefinitionNames;
if (Strings.isNullOrEmpty(planDefinitionOutputPath)) {
refreshedPlanDefinitionNames = planDefinitionProcessor.refreshIgPlanDefinitionContent(this, encoding, versioned, fhirContext, planDefinitionToRefreshPath, params.shouldApplySoftwareSystemStamp);
} else {
refreshedPlanDefinitionNames = planDefinitionProcessor.refreshIgPlanDefinitionContent(this, encoding, planDefinitionOutputPath, versioned, fhirContext, planDefinitionToRefreshPath, params.shouldApplySoftwareSystemStamp);
}
refreshedResourcesNames.addAll(refreshedPlanDefinitionNames);

if (refreshedResourcesNames.isEmpty()) {
LogUtils.info("No resources successfully refreshed.");
return;
Expand Down
Loading

0 comments on commit 92f0e1e

Please sign in to comment.