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

v2 -> v1 plugin #748

Merged
merged 22 commits into from
Nov 25, 2023
Merged
Show file tree
Hide file tree
Changes from 9 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
1 change: 1 addition & 0 deletions cqf-ruler
Submodule cqf-ruler added at 602d23
66 changes: 66 additions & 0 deletions ersd-v2-v1-transform/pom.xml
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.transform</groupId>
<artifactId>cqf-ruler-ersd-v2-v1-transform-plugin</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>
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();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
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 usPHUsageContext = "http://hl7.org/fhir/us/ecr/CodeSystem/us-ph-usage-context";

public DaoRegistry getDaoRegistry() {
return myDaoRegistry;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
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.r4.model.Bundle;
import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.r4.model.CanonicalType;
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.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
* @return a greeting
*/
@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 = "resource") IBaseResource maybeBundle) throws UnprocessableEntityException {
if (maybeBundle == null) {
throw new UnprocessableEntityException("Resource is missing");
}
if (!(maybeBundle instanceof IBaseBundle )) {
throw new UnprocessableEntityException("Resource is not a bundle");
}
Bundle v2 = (Bundle) maybeBundle;
removeRootSpecificationLibrary(v2);
final PlanDefinition v1PlanDefinition = getV1PlanDefinition(requestDetails);
v2.getEntry().stream()
.forEach(entry -> {
if (entry.getResource() instanceof MetadataResource) {
MetadataResource resource = (MetadataResource) entry.getResource();
checkAndUpdateV2PlanDefinition(entry, v1PlanDefinition);
updateV2TriggeringValueSets(resource, v1PlanDefinition.getUrl());
updateV2TriggeringValueSetLibrary(resource);
resource.setExperimentalElement(null);
}
});
return v2;
}
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) {
if (entry.getResource().getResourceType() == ResourceType.PlanDefinition
&& entry.getResource().hasMeta()
&& entry.getResource().getMeta().getProfile().stream().anyMatch(canonical -> canonical.getValue().contains("/ersd-plandefinition"))) {
entry.setResource(v1PlanDefinition);
}
}
private void replaceProfile(Meta meta, String oldProfile, String newProfile) {
meta.getProfile().replaceAll(profile -> {
if (profile.getValue().equals(oldProfile)) {
return new CanonicalType(newProfile);
} else {
return profile;
}
});
}
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);
resource.getUseContext().stream().forEach(useContext -> {
if (useContext.getCode().getCode().equals("program")) {
useContext.setValue(new Reference(v1PlanDefinitionUrl));
}
});
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"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.transform.TransformConfig

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package com.transform;

import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.List;
import java.util.stream.Collectors;

import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.MetadataResource;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.ResourceType;
import org.junit.jupiter.api.Test;
import org.opencds.cqf.ruler.test.RestIntegrationTest;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {
TransformConfig.class }, properties = { "hapi.fhir.fhir_version=r4" })
class TransformProviderIT extends RestIntegrationTest {
@Test
void testTransformConfig() {
loadResource("ersd-v1-plandefinition-skeleton.json");
Bundle v2Bundle = (Bundle) loadResource("ersd-bundle-example.json");
Parameters v2BundleParams = new Parameters();
v2BundleParams.addParameter()
.setName("resource")
.setResource(v2Bundle);
Bundle v1Bundle = getClient()
.operation()
.onServer()
.named("$ersd-v2-to-v1-transform")
.withParameters(v2BundleParams)
.returnResourceType(Bundle.class)
.execute();

assertNotNull(v1Bundle);
List<MetadataResource> entries = v1Bundle.getEntry().stream().map(entry -> (MetadataResource) entry.getResource()).collect(Collectors.toList());
List<MetadataResource> ersdValueSets = entries.stream().filter(entry -> entry.getResourceType() == ResourceType.ValueSet
&& entry.hasMeta()
&& entry.getMeta().hasProfile(TransformProperties.ersdVSProfile)).collect(Collectors.toList());
List<MetadataResource> ersdValueSetLibrary = entries.stream().filter(entry -> entry.getResourceType() == ResourceType.Library
&& entry.hasMeta()
&& entry.getMeta().hasProfile(TransformProperties.ersdVSLibProfile)).collect(Collectors.toList());
List<MetadataResource> containsSpecificationLibrary = entries.stream().filter(entry -> entry.getResourceType() == ResourceType.Library && entry.hasMeta()
&& (entry.getMeta().hasProfile(TransformProperties.usPHSpecLibProfile) || entry.getUrl().equals("http://hl7.org/fhir/us/ecr/Library/SpecificationLibrary"))).collect(Collectors.toList());
List<MetadataResource> containsV2PlanDefinition = entries.stream().filter(entry -> entry.getResourceType() == ResourceType.PlanDefinition && entry.hasMeta()
&& entry.getMeta().hasProfile(TransformProperties.ersdPlanDefinitionProfile) && !entry.getName().equals("PlanDefinition_eRSD_Skeleton_Instance")).collect(Collectors.toList());
List<MetadataResource> VSTriggeringUseContextsMissingV1PlanDefinitionReference = entries.stream().filter(entry -> entry.getResourceType() == ResourceType.ValueSet && entry.hasMeta() && entry.getMeta().hasProfile(TransformProperties.ersdVSProfile)
&& entry.getUseContext().stream().anyMatch(useContext -> useContext.getCode().getCode().equals("program") && !useContext.getValueReference().getReference().equals("http://hl7.org/fhir/us/ecr/PlanDefinition/plandefinition-ersd-skeleton"))).collect(Collectors.toList());
List<MetadataResource> containsV1PlanDefinition = entries.stream().filter(entry -> entry.getResourceType() == ResourceType.PlanDefinition && entry.hasMeta()
&& entry.getMeta().hasProfile(TransformProperties.ersdPlanDefinitionProfile) && entry.getName().equals("PlanDefinition_eRSD_Skeleton_Instance")).collect(Collectors.toList());
List<MetadataResource> hasV2TriggeringVSLibUseContexts = entries.stream().filter(entry -> entry.getResourceType() == ResourceType.Library && entry.hasMeta() && entry.getMeta().hasProfile(TransformProperties.ersdVSLibProfile)
&& entry.getUseContext().stream().anyMatch(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());
List<MetadataResource> hasV2TriggeringVSUseContexts = entries.stream().filter(entry -> entry.getResourceType() == ResourceType.ValueSet && entry.hasMeta() && entry.getMeta().hasProfile(TransformProperties.ersdVSProfile)
&& entry.getUseContext().stream().anyMatch(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());
List<MetadataResource> hasUSPHProfiles = entries.stream().filter(entry -> entry.getResourceType() == ResourceType.PlanDefinition && entry.hasMeta() && entry.getMeta().hasProfile()
&& entry.getMeta().getProfile().stream().anyMatch(profile -> profile.getValueAsString().contains("us-ph"))).collect(Collectors.toList());
List<MetadataResource> hasExperimental = entries.stream().filter(entry -> entry.hasExperimental()).collect(Collectors.toList());
assertTrue(ersdValueSets.size() > 0);
assertTrue(ersdValueSetLibrary.size() > 0);
assertTrue(containsSpecificationLibrary.size() == 0);
assertTrue(containsV2PlanDefinition.size() == 0);
assertTrue(containsV1PlanDefinition.size() == 1);
assertTrue(VSTriggeringUseContextsMissingV1PlanDefinitionReference.size() == 0);
assertTrue(hasV2TriggeringVSLibUseContexts.size() == 0);
assertTrue(hasV2TriggeringVSUseContexts.size() == 0);
assertTrue(hasUSPHProfiles.size() == 0);
assertTrue(hasExperimental.size() == 0);
}
}
Loading
Loading