-
Notifications
You must be signed in to change notification settings - Fork 49
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added new eCR module with eRSD V2 to V1 conversion operation --------- Co-authored-by: taha.attari@smilecdr.com <taha.attari@smilecdr.com> Co-authored-by: Adam Stevenson <stevenson_adam@yahoo.com>
- Loading branch information
1 parent
dd28edd
commit 5ef7709
Showing
16 changed files
with
11,644 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
FROM contentgroup/cqf-ruler:latest | ||
|
||
COPY ./target/cqf-ruler-*.jar plugin |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# Plugin | ||
|
||
This is a cqf-ruler plugin which takes an eRSD V2 Bundle and converts it to an eRSD V1 Bundle. | ||
|
||
## Build | ||
|
||
Use `mvn package` to build the jar files | ||
|
||
## Docker | ||
|
||
The Dockerfile builds on top of the base cqf-ruler image and simply copies the jar into the `plugin` directory of the image. | ||
|
||
## Setup | ||
|
||
A V1 PlanDefinition skeleton must be uploaded with ID : `plandefinition-ersd-skeleton` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<modelVersion>4.0.0</modelVersion> | ||
<parent> | ||
<groupId>org.opencds.cqf.ruler</groupId> | ||
<artifactId>cqf-ruler</artifactId> | ||
<version>0.15.0-SNAPSHOT</version> | ||
<relativePath>../</relativePath> | ||
</parent> | ||
|
||
<groupId>com.ecr</groupId> | ||
<artifactId>cqf-ruler-ecr</artifactId> | ||
<version>1.0.0</version> | ||
<dependencies> | ||
<dependency> | ||
<groupId>org.opencds.cqf.ruler</groupId> | ||
<artifactId>cqf-ruler-core</artifactId> | ||
<version>0.15.0-SNAPSHOT</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.junit.jupiter</groupId> | ||
<artifactId>junit-jupiter-api</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.junit.jupiter</groupId> | ||
<artifactId>junit-jupiter-engine</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.opencds.cqf.ruler</groupId> | ||
<artifactId>cqf-ruler-test</artifactId> | ||
<version>0.15.0-SNAPSHOT</version> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.springframework.boot</groupId> | ||
<artifactId>spring-boot-starter</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.springframework.boot</groupId> | ||
<artifactId>spring-boot-starter-web</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.springframework.boot</groupId> | ||
<artifactId>spring-boot-starter-test</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
</dependencies> | ||
<build> | ||
<pluginManagement> | ||
<plugins> | ||
<plugin> | ||
<groupId>org.sonatype.plugins</groupId> | ||
<artifactId>nexus-staging-maven-plugin</artifactId> | ||
<configuration> | ||
<skipNexusStagingDeployMojo>true</skipNexusStagingDeployMojo> | ||
<skip>true</skip> | ||
</configuration> | ||
</plugin> | ||
</plugins> | ||
</pluginManagement> | ||
</build> | ||
</project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package com.transform; | ||
|
||
import org.opencds.cqf.external.annotations.OnR4Condition; | ||
import org.opencds.cqf.ruler.api.OperationProvider; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Conditional; | ||
import org.springframework.context.annotation.Configuration; | ||
|
||
@Configuration | ||
public class TransformConfig { | ||
@Bean | ||
public TransformProperties transformProperties() { | ||
return new TransformProperties(); | ||
} | ||
|
||
@Bean | ||
@Conditional(OnR4Condition.class) | ||
public OperationProvider transformProvider() { | ||
return new TransformProvider(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package com.transform; | ||
import org.hl7.fhir.r5.model.IdType; | ||
import org.opencds.cqf.ruler.behavior.DaoRegistryUser; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.boot.context.properties.ConfigurationProperties; | ||
|
||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry; | ||
|
||
@ConfigurationProperties(prefix = "transform") | ||
public class TransformProperties implements DaoRegistryUser { | ||
@Autowired | ||
private DaoRegistry myDaoRegistry; | ||
|
||
public static final IdType v1PlanDefinitionId = new IdType("PlanDefinition", "plandefinition-ersd-skeleton"); | ||
public static final String usPHTriggeringVSProfile = "http://hl7.org/fhir/us/ecr/StructureDefinition/us-ph-triggering-valueset"; | ||
public static final String usPHTriggeringVSLibProfile = "http://hl7.org/fhir/us/ecr/StructureDefinition/us-ph-triggering-valueset-library"; | ||
public static final String ersdVSLibProfile = "http://hl7.org/fhir/us/ecr/StructureDefinition/ersd-valueset-library"; | ||
public static final String ersdVSProfile = "http://hl7.org/fhir/us/ecr/StructureDefinition/ersd-valueset"; | ||
public static final String ersdPlanDefinitionProfile = "http://hl7.org/fhir/us/ecr/StructureDefinition/ersd-plandefinition"; | ||
public static final String usPHSpecLibProfile = "http://hl7.org/fhir/us/ecr/StructureDefinition/us-ph-specification-library"; | ||
public static final String usPHUsageContextType = "http://hl7.org/fhir/us/ecr/CodeSystem/us-ph-usage-context-type"; | ||
public static final String hl7UsageContextType = "http://terminology.hl7.org/CodeSystem/usage-context-type"; | ||
public static final String usPHUsageContext = "http://hl7.org/fhir/us/ecr/CodeSystem/us-ph-usage-context"; | ||
|
||
public DaoRegistry getDaoRegistry() { | ||
return myDaoRegistry; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
package com.transform; | ||
|
||
import java.util.List; | ||
import java.util.Optional; | ||
import java.util.stream.Collectors; | ||
|
||
import org.hl7.fhir.instance.model.api.IBaseBundle; | ||
import org.hl7.fhir.instance.model.api.IBaseResource; | ||
import org.hl7.fhir.instance.model.api.IIdType; | ||
import org.hl7.fhir.r4.model.Bundle; | ||
import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; | ||
import org.hl7.fhir.r4.model.CanonicalType; | ||
import org.hl7.fhir.r4.model.Coding; | ||
import org.hl7.fhir.r4.model.Meta; | ||
import org.hl7.fhir.r4.model.MetadataResource; | ||
import org.hl7.fhir.r4.model.PlanDefinition; | ||
import org.hl7.fhir.r4.model.Reference; | ||
import org.hl7.fhir.r4.model.ResourceType; | ||
import org.hl7.fhir.r4.model.UsageContext; | ||
import org.hl7.fhir.r4.model.ValueSet; | ||
import org.opencds.cqf.ruler.api.OperationProvider; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
|
||
import ca.uhn.fhir.model.api.annotation.Description; | ||
import ca.uhn.fhir.rest.annotation.Operation; | ||
import ca.uhn.fhir.rest.annotation.OperationParam; | ||
import ca.uhn.fhir.rest.api.server.RequestDetails; | ||
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; | ||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; | ||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; | ||
|
||
public class TransformProvider implements OperationProvider { | ||
@Autowired | ||
TransformProperties transformProperties; | ||
|
||
/** | ||
* Implements the $ersd-v2-to-v1-transform operation which transforms an ersd v2 Bundle | ||
* into an ersd v1 compatible bundle | ||
* @param requestDetails the incoming request details | ||
* @param maybeBundle the v2 bundle to transform | ||
* @param maybePlanDefinition the v1 PlanDefinition to include | ||
* @return the v1 compatible bundle | ||
*/ | ||
@Description(shortDefinition = "Converts a v2 ERSD bundle into a v1 ERSD bundle", value = "Converts a v2 ERSD bundle into a v1 ERSD bundle") | ||
@Operation(idempotent = true, name = "$ersd-v2-to-v1-transform") | ||
public Bundle convert_v1( | ||
RequestDetails requestDetails, | ||
@OperationParam(name = "bundle") IBaseResource maybeBundle, | ||
@OperationParam(name = "planDefinition") IBaseResource maybePlanDefinition | ||
) throws UnprocessableEntityException { | ||
if (maybeBundle == null) { | ||
throw new UnprocessableEntityException("Resource is missing"); | ||
} | ||
if (!(maybeBundle instanceof IBaseBundle )) { | ||
throw new UnprocessableEntityException("Resource is not a bundle"); | ||
} | ||
if (maybePlanDefinition != null && !(maybePlanDefinition instanceof PlanDefinition)) { | ||
throw new UnprocessableEntityException("Provided v1 PlanDefinition is not a PlanDefinition resource"); | ||
} | ||
Bundle v2 = (Bundle) maybeBundle; | ||
removeRootSpecificationLibrary(v2); | ||
final PlanDefinition v1PlanDefinition = maybePlanDefinition != null ? (PlanDefinition) maybePlanDefinition : getV1PlanDefinition(requestDetails); | ||
v2.getEntry().stream() | ||
.forEach(entry -> { | ||
if (entry.getResource() instanceof MetadataResource) { | ||
MetadataResource resource = (MetadataResource) entry.getResource(); | ||
checkAndUpdateV2PlanDefinition(entry, v1PlanDefinition); | ||
updateV2GroupersUseContext(resource,v1PlanDefinition.getIdElement()); | ||
updateV2TriggeringValueSets(resource, v1PlanDefinition.getUrl()); | ||
updateV2TriggeringValueSetLibrary(resource); | ||
resource.setExperimentalElement(null); | ||
} | ||
}); | ||
return v2; | ||
} | ||
private void updateV2GroupersUseContext(MetadataResource resource, IIdType planDefinitionId) { | ||
// if resourc is a vs | ||
if (resource.getResourceType() == ResourceType.ValueSet) { | ||
ValueSet valueSet = (ValueSet) resource; | ||
// if vs is a grouper | ||
if (valueSet.hasCompose() | ||
&& valueSet.getCompose().getIncludeFirstRep().getValueSet().size() > 0) { | ||
List<UsageContext> usageContexts = valueSet.getUseContext(); | ||
UsageContext program = usageContexts.stream().filter(useContext -> useContext.getCode().getSystem().equals(TransformProperties.hl7UsageContextType) && useContext.getCode().getCode().equals("program")).findFirst().orElseGet(() -> { | ||
UsageContext retval = new UsageContext(new Coding(TransformProperties.hl7UsageContextType, "program", null), null); | ||
usageContexts.add(retval); | ||
return retval; | ||
}); | ||
program.setValue(new Reference(planDefinitionId)); | ||
} | ||
} | ||
} | ||
private void removeRootSpecificationLibrary(Bundle v2) { | ||
List<BundleEntryComponent> filteredRootLib = v2.getEntry().stream() | ||
.filter(entry -> entry.hasResource()) | ||
.filter(entry -> !(entry.getResource().hasMeta() && entry.getResource().getMeta().hasProfile(TransformProperties.usPHSpecLibProfile))).collect(Collectors.toList()); | ||
v2.setEntry(filteredRootLib); | ||
} | ||
private void checkAndUpdateV2PlanDefinition(BundleEntryComponent entry, PlanDefinition v1PlanDefinition) throws UnprocessableEntityException{ | ||
if (entry.getResource().getResourceType() == ResourceType.PlanDefinition | ||
&& entry.getResource().hasMeta() | ||
&& entry.getResource().getMeta().getProfile().stream().anyMatch(canonical -> canonical.getValue().contains("/ersd-plandefinition"))) { | ||
entry.setResource(v1PlanDefinition); | ||
String url = Optional.ofNullable(v1PlanDefinition.getUrl()).orElseThrow(() -> new UnprocessableEntityException("URL missing from PlanDefinition")); | ||
String version = Optional.ofNullable(v1PlanDefinition.getVersion()).orElseThrow(() -> new UnprocessableEntityException("Version missing from PlanDefinition")); | ||
entry.setFullUrl(url + "|" + version); | ||
} | ||
} | ||
/** | ||
* Remove all instances of an old profile and add one instance of a new profile | ||
* @param meta the meta object to update | ||
* @param oldProfile the profile URL to remove | ||
* @param newProfile the profile URL to add | ||
*/ | ||
private void replaceProfile(Meta meta, String oldProfile, String newProfile) { | ||
// remove all instances of old profile | ||
List<CanonicalType> updatedProfiles = meta.getProfile().stream() | ||
.filter(profile -> !profile.getValue().equals(oldProfile)).collect(Collectors.toList()); | ||
// add the new profile if it doesn't exist | ||
if (!updatedProfiles.stream().anyMatch(profile -> profile.getValue().equals(newProfile))) { | ||
updatedProfiles.add(new CanonicalType(newProfile)); | ||
} | ||
meta.setProfile(updatedProfiles); | ||
} | ||
private void updateV2TriggeringValueSetLibrary(MetadataResource resource) { | ||
if (resource.getResourceType() == ResourceType.Library | ||
&& resource.hasMeta() | ||
&& resource.getMeta().hasProfile(TransformProperties.usPHTriggeringVSLibProfile) | ||
) { | ||
replaceProfile(resource.getMeta(), TransformProperties.usPHTriggeringVSLibProfile, TransformProperties.ersdVSLibProfile); | ||
List<UsageContext> filteredUseContexts = resource.getUseContext().stream() | ||
.filter(useContext -> | ||
!(useContext.getCode().getCode().equals("reporting") | ||
&& useContext.getValueCodeableConcept().hasCoding(TransformProperties.usPHUsageContext, "triggering")) | ||
&& !(useContext.getCode().getCode().equals("specification-type") | ||
&& useContext.getValueCodeableConcept().hasCoding(TransformProperties.usPHUsageContext, "value-set-library"))) | ||
.collect(Collectors.toList()); | ||
resource.setUseContext(filteredUseContexts); | ||
} | ||
} | ||
private void updateV2TriggeringValueSets(MetadataResource resource, String v1PlanDefinitionUrl) { | ||
if (resource.getResourceType() == ResourceType.ValueSet | ||
&& resource.hasMeta() | ||
&& resource.getMeta().hasProfile(TransformProperties.usPHTriggeringVSProfile)) { | ||
replaceProfile(resource.getMeta(), TransformProperties.usPHTriggeringVSProfile, TransformProperties.ersdVSProfile); | ||
List<UsageContext> filteredUseContexts = resource.getUseContext().stream() | ||
.filter(useContext -> | ||
!(useContext.getCode().getCode().equals("reporting") | ||
&& useContext.getValueCodeableConcept().hasCoding(TransformProperties.usPHUsageContext, "triggering")) | ||
&& !(useContext.getCode().getCode().equals("priority") | ||
&& useContext.getValueCodeableConcept().hasCoding(TransformProperties.usPHUsageContext, "routine"))) | ||
.collect(Collectors.toList()); | ||
resource.setUseContext(filteredUseContexts); | ||
} | ||
} | ||
private PlanDefinition getV1PlanDefinition(RequestDetails requestDetails) throws ResourceNotFoundException { | ||
Optional<PlanDefinition> maybePlanDefinition = Optional.ofNullable(null); | ||
try { | ||
PlanDefinition v1PlanDefinition = (PlanDefinition) transformProperties | ||
.getDaoRegistry() | ||
.getResourceDao(TransformProperties.v1PlanDefinitionId.getResourceType()) | ||
.read(TransformProperties.v1PlanDefinitionId, requestDetails); | ||
maybePlanDefinition = Optional.of(v1PlanDefinition); | ||
} catch (ResourceNotFoundException | ResourceGoneException e) { | ||
throw new ResourceNotFoundException("Could not find V1 PlanDefinition"); | ||
} | ||
return maybePlanDefinition.orElseThrow(() -> new ResourceNotFoundException("Could not find V1 PlanDefinition")); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ | ||
com.transform.TransformConfig |
495 changes: 495 additions & 0 deletions
495
ecr/src/main/resources/ersd-v1-plandefinition-skeleton.json
Large diffs are not rendered by default.
Oops, something went wrong.
Oops, something went wrong.