diff --git a/plugin/cr/src/main/java/org/opencds/cqf/ruler/cr/KnowledgeArtifactProcessor.java b/plugin/cr/src/main/java/org/opencds/cqf/ruler/cr/KnowledgeArtifactProcessor.java index 79e9f609f..d5e57c8af 100644 --- a/plugin/cr/src/main/java/org/opencds/cqf/ruler/cr/KnowledgeArtifactProcessor.java +++ b/plugin/cr/src/main/java/org/opencds/cqf/ruler/cr/KnowledgeArtifactProcessor.java @@ -42,9 +42,14 @@ import org.hl7.fhir.r4.model.StringType; import org.hl7.fhir.r4.model.StructureDefinition; import org.hl7.fhir.r4.model.UsageContext; +import org.hl7.fhir.r4.model.ValueSet; import org.opencds.cqf.cql.evaluator.fhir.util.Canonicals; import org.opencds.cqf.ruler.cr.r4.ArtifactAssessment; import org.opencds.cqf.ruler.cr.r4.ArtifactAssessment.ArtifactAssessmentContentInformationType; +import org.opencds.cqf.ruler.cr.r4.CRMIReleaseExperimentalBehavior.CRMIReleaseExperimentalBehaviorCodes; +import org.opencds.cqf.ruler.cr.r4.CRMIReleaseVersionBehavior.CRMIReleaseVersionBehaviorCodes; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Configurable; import ca.uhn.fhir.model.api.IQueryParameterType; @@ -61,6 +66,7 @@ @Configurable // TODO: This belongs in the Evaluator. Only included in Ruler at dev time for shorter cycle. public class KnowledgeArtifactProcessor { + private Logger myLog = LoggerFactory.getLogger(KnowledgeArtifactProcessor.class); public static final String CPG_INFERENCEEXPRESSION = "http://hl7.org/fhir/uv/cpg/StructureDefinition/cpg-inferenceExpression"; public static final String CPG_ASSERTIONEXPRESSION = "http://hl7.org/fhir/uv/cpg/StructureDefinition/cpg-assertionExpression"; public static final String CPG_FEATUREEXPRESSION = "http://hl7.org/fhir/uv/cpg/StructureDefinition/cpg-featureExpression"; @@ -411,7 +417,7 @@ private void processReferencedResourceForDraft(FhirDal fhirDal, Bundle reference } } - private Optional getReleaseVersion(String version, CodeType versionBehavior, String existingVersion) throws UnprocessableEntityException { + private Optional getReleaseVersion(String version, CRMIReleaseVersionBehaviorCodes versionBehavior, String existingVersion) throws UnprocessableEntityException { Optional releaseVersion = Optional.ofNullable(null); // If no version exists use the version argument provided if (existingVersion == null || existingVersion.isEmpty() || existingVersion.isBlank()) { @@ -419,15 +425,15 @@ private Optional getReleaseVersion(String version, CodeType versionBehav } String replaceDraftInExisting = existingVersion.replace("-draft",""); - if (versionBehavior.getCode().equals("default")) { + if (CRMIReleaseVersionBehaviorCodes.DEFAULT == versionBehavior) { if(replaceDraftInExisting != null && !replaceDraftInExisting.isEmpty()){ releaseVersion = Optional.of(replaceDraftInExisting); } else { releaseVersion = Optional.ofNullable(version); } - } else if (versionBehavior.getCode().equals("force")) { + } else if (CRMIReleaseVersionBehaviorCodes.FORCE == versionBehavior) { releaseVersion = Optional.ofNullable(version); - } else if (versionBehavior.getCode().equals("check")) { + } else if (CRMIReleaseVersionBehaviorCodes.CHECK == versionBehavior) { if (!replaceDraftInExisting.equals(version)) { throw new UnprocessableEntityException(String.format("versionBehavior specified is 'check' and the version provided ('%s') does not match the version currently specified on the root artifact ('%s').",version,existingVersion)); } @@ -454,12 +460,12 @@ private Optional getReleaseVersion(String version, CodeType versionBehav * Links and references between Bundle resources are updated to point to * the new versions. */ - public Bundle createReleaseBundle(IdType idType, String version, CodeType versionBehavior, String releaseLabel, boolean latestFromTxServer, FhirDal fhirDal) throws UnprocessableEntityException, ResourceNotFoundException, PreconditionFailedException { + public Bundle createReleaseBundle(IdType idType, String releaseLabel, String version, CRMIReleaseVersionBehaviorCodes versionBehavior, boolean latestFromTxServer, CRMIReleaseExperimentalBehaviorCodes experimentalBehavior, FhirDal fhirDal) throws UnprocessableEntityException, ResourceNotFoundException, PreconditionFailedException { // TODO: This check is to avoid partial releases and should be removed once the argument is supported. if (latestFromTxServer) { throw new NotImplementedOperationException("Support for 'latestFromTxServer' is not yet implemented."); } - checkReleaseVersion(version,versionBehavior); + checkReleaseVersion(version, versionBehavior); MetadataResource rootArtifact = (MetadataResource) fhirDal.read(idType); KnowledgeArtifactAdapter rootArtifactAdapter = new KnowledgeArtifactAdapter<>(rootArtifact); Date currentApprovalDate = rootArtifactAdapter.getApprovalDate(); @@ -470,7 +476,11 @@ public Bundle createReleaseBundle(IdType idType, String version, CodeType versio String releaseVersion = getReleaseVersion(version, versionBehavior, existingVersion) .orElseThrow(() -> new UnprocessableEntityException("Could not resolve a version for the root artifact.")); Period rootEffectivePeriod = rootArtifactAdapter.getEffectivePeriod(); - List releasedResources = internalRelease(rootArtifactAdapter, releaseVersion, rootEffectivePeriod, versionBehavior, latestFromTxServer, fhirDal); + // if the root artifact is experimental then we don't need to check for experimental children + if (rootArtifact.getExperimental()) { + experimentalBehavior = CRMIReleaseExperimentalBehaviorCodes.NONE; + } + List releasedResources = internalRelease(rootArtifactAdapter, releaseVersion, rootEffectivePeriod, versionBehavior, latestFromTxServer, experimentalBehavior, fhirDal); if (releaseLabel != null) { Extension releaseLabelExtension = rootArtifact.getExtensionByUrl(releaseLabel); if (releaseLabelExtension == null) { @@ -575,21 +585,18 @@ public Bundle createReleaseBundle(IdType idType, String version, CodeType versio }); return transactionBundle; } - private void checkReleaseVersion(String version,CodeType versionBehavior) throws UnprocessableEntityException { - if (versionBehavior == null || versionBehavior.getCode() == null || versionBehavior.getCode().isEmpty()) { + private void checkReleaseVersion(String version,CRMIReleaseVersionBehaviorCodes versionBehavior) throws UnprocessableEntityException { + if (CRMIReleaseVersionBehaviorCodes.NULL == versionBehavior) { throw new UnprocessableEntityException("'versionBehavior' must be provided as an argument to the $release operation. Valid values are 'default', 'check', 'force'."); } - - if (!versionBehavior.getCode().equals("default") && !versionBehavior.getCode().equals("check") && !versionBehavior.getCode().equals("force")) { - throw new UnprocessableEntityException(String.format("'%s' is not a valid versionBehavior. Valid values are 'default', 'check', 'force'", versionBehavior.getCode())); - } checkVersionValidSemver(version); } private void checkReleasePreconditions(MetadataResource artifact, Date approvalDate) throws PreconditionFailedException { if (artifact == null) { throw new ResourceNotFoundException("Resource not found."); } - if(!artifact.getStatus().equals(Enumerations.PublicationStatus.DRAFT)){ + + if (Enumerations.PublicationStatus.DRAFT != artifact.getStatus()) { throw new PreconditionFailedException(String.format("Resource with ID: '%s' does not have a status of 'draft'.", artifact.getIdElement().getIdPart())); } if (approvalDate == null) { @@ -601,7 +608,7 @@ private void checkReleasePreconditions(MetadataResource artifact, Date approvalD } } private List internalRelease(KnowledgeArtifactAdapter artifactAdapter, String version, Period rootEffectivePeriod, - CodeType versionBehavior, boolean latestFromTxServer, FhirDal fhirDal) throws NotImplementedOperationException, ResourceNotFoundException { + CRMIReleaseVersionBehaviorCodes versionBehavior, boolean latestFromTxServer, CRMIReleaseExperimentalBehaviorCodes experimentalBehavior, FhirDal fhirDal) throws NotImplementedOperationException, ResourceNotFoundException { List resourcesToUpdate = new ArrayList(); // Step 1: Update the Date and the version @@ -643,7 +650,10 @@ private List internalRelease(KnowledgeArtifactAdapter searchResultAdapter = new KnowledgeArtifactAdapter<>(referencedResource); - resourcesToUpdate.addAll(internalRelease(searchResultAdapter, version, rootEffectivePeriod, versionBehavior, latestFromTxServer, fhirDal)); + if (CRMIReleaseExperimentalBehaviorCodes.NULL != experimentalBehavior && CRMIReleaseExperimentalBehaviorCodes.NONE != experimentalBehavior) { + checkNonExperimental(referencedResource, experimentalBehavior, fhirDal); + } + resourcesToUpdate.addAll(internalRelease(searchResultAdapter, version, rootEffectivePeriod, versionBehavior, latestFromTxServer, experimentalBehavior, fhirDal)); } } } @@ -694,6 +704,29 @@ private Optional checkIfReferenceInList(RelatedArtifact artifa } return updatedReference; } + private void checkNonExperimental(MetadataResource resource, CRMIReleaseExperimentalBehaviorCodes experimentalBehavior, FhirDal fhirDal) throws UnprocessableEntityException { + String nonExperimentalError = String.format("Root artifact is not Experimental, but references an Experimental resource with URL '%s'.", + resource.getUrl()); + if (CRMIReleaseExperimentalBehaviorCodes.WARN == experimentalBehavior && resource.getExperimental()) { + myLog.warn(nonExperimentalError); + } else if (CRMIReleaseExperimentalBehaviorCodes.ERROR == experimentalBehavior && resource.getExperimental()) { + throw new UnprocessableEntityException(nonExperimentalError); + } + // for ValueSets need to check recursively if any chldren are experimental + // since we don't own these + if (resource.getResourceType().equals(ResourceType.ValueSet)) { + ValueSet valueSet = (ValueSet) resource; + List valueSets = valueSet + .getCompose() + .getInclude() + .stream().flatMap(include -> include.getValueSet().stream()) + .collect(Collectors.toList()); + for (CanonicalType value: valueSets) { + KnowledgeArtifactAdapter.findLatestVersion(searchResourceByUrl(value.getValueAsString(), fhirDal)) + .ifPresent(childVs -> checkNonExperimental(childVs, experimentalBehavior, fhirDal)); + } + } + } /* $package */ public Bundle createPackageBundle(IdType id, FhirDal fhirDal, List capability, List include, List canonicalVersion, List checkCanonicalVersion, List forceCanonicalVersion, Integer count, Integer offset, Endpoint contentEndpoint, Endpoint terminologyEndpoint, Boolean packageOnly) throws NotImplementedOperationException, UnprocessableEntityException { if (contentEndpoint != null || terminologyEndpoint != null) { diff --git a/plugin/cr/src/main/java/org/opencds/cqf/ruler/cr/RepositoryService.java b/plugin/cr/src/main/java/org/opencds/cqf/ruler/cr/RepositoryService.java index a5f083d1e..50793b52a 100644 --- a/plugin/cr/src/main/java/org/opencds/cqf/ruler/cr/RepositoryService.java +++ b/plugin/cr/src/main/java/org/opencds/cqf/ruler/cr/RepositoryService.java @@ -19,6 +19,8 @@ import org.hl7.fhir.r4.model.Resource; import org.opencds.cqf.cql.evaluator.fhir.util.Canonicals; import org.opencds.cqf.ruler.cr.r4.ArtifactAssessment; +import org.opencds.cqf.ruler.cr.r4.CRMIReleaseExperimentalBehavior.CRMIReleaseExperimentalBehaviorCodes; +import org.opencds.cqf.ruler.cr.r4.CRMIReleaseVersionBehavior.CRMIReleaseVersionBehaviorCodes; import org.opencds.cqf.ruler.provider.DaoRegistryOperationProvider; import org.springframework.beans.factory.annotation.Autowired; @@ -149,12 +151,27 @@ public Bundle releaseOperation( @IdParam IdType theId, @OperationParam(name = "version") String version, @OperationParam(name = "versionBehavior") CodeType versionBehavior, - @OperationParam(name = "releaseLabel") String releaseLabel, - @OperationParam(name = "latestFromTxServer", typeName = "Boolean") IPrimitiveType latestFromTxServer) + @OperationParam(name = "latestFromTxServer", typeName = "Boolean") IPrimitiveType latestFromTxServer, + @OperationParam(name = "requireNonExperimental") CodeType requireNonExpermimental, + @OperationParam(name = "releaseLabel") String releaseLabel) throws FHIRException { - + CRMIReleaseVersionBehaviorCodes versionBehaviorCode; + CRMIReleaseExperimentalBehaviorCodes experimentalBehaviorCode; + try { + versionBehaviorCode = versionBehavior == null ? CRMIReleaseVersionBehaviorCodes.NULL : CRMIReleaseVersionBehaviorCodes.fromCode(versionBehavior.getCode()); + experimentalBehaviorCode = requireNonExpermimental == null ? CRMIReleaseExperimentalBehaviorCodes.NULL : CRMIReleaseExperimentalBehaviorCodes.fromCode(requireNonExpermimental.getCode()); + } catch (FHIRException e) { + throw new UnprocessableEntityException(e.getMessage()); + } FhirDal fhirDal = this.fhirDalFactory.create(requestDetails); - return transaction(this.artifactProcessor.createReleaseBundle(theId, version, versionBehavior, releaseLabel, latestFromTxServer != null && latestFromTxServer.getValue(), fhirDal)); + return transaction(this.artifactProcessor.createReleaseBundle( + theId, + releaseLabel, + version, + versionBehaviorCode, + latestFromTxServer != null && latestFromTxServer.getValue(), + experimentalBehaviorCode, + fhirDal)); } @Operation(name = "$crmi.package", idempotent = true, global = true, type = MetadataResource.class) diff --git a/plugin/cr/src/main/java/org/opencds/cqf/ruler/cr/r4/CRMIReleaseExperimentalBehavior.java b/plugin/cr/src/main/java/org/opencds/cqf/ruler/cr/r4/CRMIReleaseExperimentalBehavior.java new file mode 100644 index 000000000..44e574417 --- /dev/null +++ b/plugin/cr/src/main/java/org/opencds/cqf/ruler/cr/r4/CRMIReleaseExperimentalBehavior.java @@ -0,0 +1,153 @@ +package org.opencds.cqf.ruler.cr.r4; + +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.r4.model.Base; +import org.hl7.fhir.r4.model.Configuration; +import org.hl7.fhir.r4.model.EnumFactory; +import org.hl7.fhir.r4.model.Enumeration; +import org.hl7.fhir.r4.model.PrimitiveType; + +public class CRMIReleaseExperimentalBehavior { + public enum CRMIReleaseExperimentalBehaviorCodes { + /** + * The repository should throw an error if a specification which is not Experimental references Experimental components. + */ + ERROR, + /** + * The repository should warn if a specification which is not Experimental references Experimental components. + */ + WARN, + /** + * The repository does not need to consider the state of Experimental. + */ + NONE, + /** + * added to help the parsers with the generic types + */ + NULL; + + public static CRMIReleaseExperimentalBehaviorCodes fromCode(String codeString) throws FHIRException { + if (codeString == null || "".equals(codeString)) + return null; + if ("error".equals(codeString)) + return ERROR; + if ("warn".equals(codeString)) + return WARN; + if ("none".equals(codeString)) + return NONE; + if (Configuration.isAcceptInvalidEnums()) + return null; + else + throw new FHIRException("Unknown CRMIReleaseExperimentalBehaviorCode '" + codeString + "'"); + } + + public String toCode() { + switch (this) { + case ERROR: + return "error"; + case WARN: + return "warn"; + case NONE: + return "none"; + case NULL: + return null; + default: + return "?"; + } + } + + public String getSystem() { + switch (this) { + case ERROR: + return "http://hl7.org/fhir/uv/crmi/CodeSystem/crmi-release-experimental-behavior-codes"; + case WARN: + return "http://hl7.org/fhir/uv/crmi/CodeSystem/crmi-release-experimental-behavior-codes"; + case NONE: + return "http://hl7.org/fhir/uv/crmi/CodeSystem/crmi-release-experimental-behavior-codes"; + case NULL: + return null; + default: + return "?"; + } + } + + public String getDefinition() { + switch (this) { + case ERROR: + return "The repository should throw an error if a specification which is not Experimental references Experimental components."; + case WARN: + return "The repository should warn if a specification which is not Experimental references Experimental components."; + case NONE: + return "The repository does not need to consider the state of Experimental."; + case NULL: + return null; + default: + return "?"; + } + } + + public String getDisplay() { + switch (this) { + case ERROR: + return "Error"; + case WARN: + return "Warn"; + case NONE: + return "None"; + case NULL: + return null; + default: + return "?"; + } + } + + } + + public static class CRMIReleaseExperimentalBehaviorCodesEnumFactory implements EnumFactory { + public CRMIReleaseExperimentalBehaviorCodes fromCode(String codeString) throws IllegalArgumentException { + if (codeString == null || "".equals(codeString)) + if (codeString == null || "".equals(codeString)) + return null; + if ("error".equals(codeString)) + return CRMIReleaseExperimentalBehaviorCodes.ERROR; + if ("warn".equals(codeString)) + return CRMIReleaseExperimentalBehaviorCodes.WARN; + if ("none".equals(codeString)) + return CRMIReleaseExperimentalBehaviorCodes.NONE; + throw new IllegalArgumentException("Unknown CRMIReleaseExperimentalBehaviorCodes code '" + codeString + "'"); + } + + public Enumeration fromType(Base code) throws FHIRException { + if (code == null) + return null; + if (code.isEmpty()) + return new Enumeration(this); + String codeString = ((PrimitiveType) code).asStringValue(); + if (codeString == null || "".equals(codeString)) + return null; + if ("error".equals(codeString)) + return new Enumeration(this, CRMIReleaseExperimentalBehaviorCodes.ERROR); + if ("warn".equals(codeString)) + return new Enumeration(this, CRMIReleaseExperimentalBehaviorCodes.WARN); + if ("none".equals(codeString)) + return new Enumeration(this, CRMIReleaseExperimentalBehaviorCodes.NONE); + throw new FHIRException("Unknown CRMIReleaseExperimentalBehaviorCodes code '" + codeString + "'"); + } + + public String toCode(CRMIReleaseExperimentalBehaviorCodes code) { + if (code == CRMIReleaseExperimentalBehaviorCodes.ERROR) + return "error"; + if (code == CRMIReleaseExperimentalBehaviorCodes.WARN) + return "warn"; + if (code == CRMIReleaseExperimentalBehaviorCodes.NONE) + return "none"; + return "?"; + } + + public String toSystem(CRMIReleaseExperimentalBehaviorCodes code) { + return code.getSystem(); + } + } + + +} diff --git a/plugin/cr/src/main/java/org/opencds/cqf/ruler/cr/r4/CRMIReleaseVersionBehavior.java b/plugin/cr/src/main/java/org/opencds/cqf/ruler/cr/r4/CRMIReleaseVersionBehavior.java new file mode 100644 index 000000000..dea0bc795 --- /dev/null +++ b/plugin/cr/src/main/java/org/opencds/cqf/ruler/cr/r4/CRMIReleaseVersionBehavior.java @@ -0,0 +1,152 @@ +package org.opencds.cqf.ruler.cr.r4; + +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.r4.model.Base; +import org.hl7.fhir.r4.model.Configuration; +import org.hl7.fhir.r4.model.EnumFactory; +import org.hl7.fhir.r4.model.Enumeration; +import org.hl7.fhir.r4.model.PrimitiveType; + +public class CRMIReleaseVersionBehavior { + public enum CRMIReleaseVersionBehaviorCodes { + /** + * The version provided will be applied to the root artifact and all owned components if a version is not specified. + */ + DEFAULT, + /** + * If the root artifact has a specified version different from the version passed to the operation, an error will be returned. + */ + CHECK, + /** + * The version provided will be applied to the root artifact and all owned components, regardless of whether or not a version was already specified. + */ + FORCE, + /** + * added to help the parsers with the generic types + */ + NULL; + + public static CRMIReleaseVersionBehaviorCodes fromCode(String codeString) throws FHIRException { + if (codeString == null || "".equals(codeString)) + return null; + if ("default".equals(codeString)) + return DEFAULT; + if ("check".equals(codeString)) + return CHECK; + if ("force".equals(codeString)) + return FORCE; + if (Configuration.isAcceptInvalidEnums()) + return null; + else + throw new FHIRException("Unknown CRMIReleaseVersionBehaviorCodes '" + codeString + "'"); + } + + public String toCode() { + switch (this) { + case DEFAULT: + return "default"; + case CHECK: + return "check"; + case FORCE: + return "force"; + case NULL: + return null; + default: + return "?"; + } + } + + public String getSystem() { + switch (this) { + case DEFAULT: + return "http://hl7.org/fhir/uv/crmi/ValueSet/crmi-release-version-behavior"; + case CHECK: + return "http://hl7.org/fhir/uv/crmi/ValueSet/crmi-release-version-behavior"; + case FORCE: + return "http://hl7.org/fhir/uv/crmi/ValueSet/crmi-release-version-behavior"; + case NULL: + return null; + default: + return "?"; + } + } + + public String getDefinition() { + switch (this) { + case DEFAULT: + return "The version provided will be applied to the root artifact and all owned components if a version is not specified."; + case CHECK: + return "If the root artifact has a specified version different from the version passed to the operation, an error will be returned."; + case FORCE: + return "The version provided will be applied to the root artifact and all owned components, regardless of whether or not a version was already specified."; + case NULL: + return null; + default: + return "?"; + } + } + + public String getDisplay() { + switch (this) { + case DEFAULT: + return "Default"; + case CHECK: + return "Check"; + case FORCE: + return "Force"; + case NULL: + return null; + default: + return "?"; + } + } + + } + + public static class CRMIReleaseVersionBehaviorCodesEnumFactory implements EnumFactory { + public CRMIReleaseVersionBehaviorCodes fromCode(String codeString) throws IllegalArgumentException { + if (codeString == null || "".equals(codeString)) + if (codeString == null || "".equals(codeString)) + return null; + if ("default".equals(codeString)) + return CRMIReleaseVersionBehaviorCodes.DEFAULT; + if ("check".equals(codeString)) + return CRMIReleaseVersionBehaviorCodes.CHECK; + if ("force".equals(codeString)) + return CRMIReleaseVersionBehaviorCodes.FORCE; + throw new IllegalArgumentException("Unknown CRMIReleaseVersionBehaviorCodes code '" + codeString + "'"); + } + + public Enumeration fromType(Base code) throws FHIRException { + if (code == null) + return null; + if (code.isEmpty()) + return new Enumeration(this); + String codeString = ((PrimitiveType) code).asStringValue(); + if (codeString == null || "".equals(codeString)) + return null; + if ("default".equals(codeString)) + return new Enumeration(this, CRMIReleaseVersionBehaviorCodes.DEFAULT); + if ("check".equals(codeString)) + return new Enumeration(this, CRMIReleaseVersionBehaviorCodes.CHECK); + if ("force".equals(codeString)) + return new Enumeration(this, CRMIReleaseVersionBehaviorCodes.FORCE); + throw new FHIRException("Unknown CRMIReleaseVersionBehaviorCodes code '" + codeString + "'"); + } + + public String toCode(CRMIReleaseVersionBehaviorCodes code) { + if (code == CRMIReleaseVersionBehaviorCodes.DEFAULT) + return "error"; + if (code == CRMIReleaseVersionBehaviorCodes.CHECK) + return "check"; + if (code == CRMIReleaseVersionBehaviorCodes.FORCE) + return "force"; + return "?"; + } + + public String toSystem(CRMIReleaseVersionBehaviorCodes code) { + return code.getSystem(); + } + } + +} diff --git a/plugin/cr/src/test/java/org/opencds/cqf/ruler/cr/r4/RepositoryServiceTest.java b/plugin/cr/src/test/java/org/opencds/cqf/ruler/cr/r4/RepositoryServiceTest.java index a2831e18e..7ae55d330 100644 --- a/plugin/cr/src/test/java/org/opencds/cqf/ruler/cr/r4/RepositoryServiceTest.java +++ b/plugin/cr/src/test/java/org/opencds/cqf/ruler/cr/r4/RepositoryServiceTest.java @@ -3,9 +3,13 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; import static org.opencds.cqf.cql.evaluator.fhir.util.r4.Parameters.parameters; import static org.opencds.cqf.cql.evaluator.fhir.util.r4.Parameters.part; +import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Date; @@ -23,6 +27,7 @@ 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.CodeType; import org.hl7.fhir.r4.model.DateType; import org.hl7.fhir.r4.model.Enumerations; import org.hl7.fhir.r4.model.Extension; @@ -42,6 +47,7 @@ import org.opencds.cqf.ruler.cr.KnowledgeArtifactAdapter; import org.opencds.cqf.ruler.cr.KnowledgeArtifactProcessor; import org.opencds.cqf.ruler.test.RestIntegrationTest; +import org.slf4j.LoggerFactory; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Lazy; @@ -49,6 +55,9 @@ import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.Appender; @Lazy @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, @@ -190,12 +199,13 @@ void draftOperation_version_format_test() { @Test void releaseResource_test() { loadTransaction("ersd-release-bundle.json"); + loadResource("artifactAssessment-search-parameter.json"); String existingVersion = "1.2.3"; String versionData = "1.2.7.23"; Parameters params1 = parameters( part("version", new StringType(versionData)), - part("versionBehavior", new StringType("default")) + part("versionBehavior", new CodeType("default")) ); Bundle returnResource = getClient().operation() @@ -285,12 +295,13 @@ void releaseResource_test() { @Test void releaseResource_force_version() { loadTransaction("ersd-small-approved-draft-bundle.json"); + loadResource("artifactAssessment-search-parameter.json"); // Existing version should be "1.2.3"; String newVersionToForce = "1.2.7.23"; Parameters params = parameters( part("version", new StringType(newVersionToForce)), - part("versionBehavior", new StringType("force")) + part("versionBehavior", new CodeType("force")) ); Bundle returnResource = getClient().operation() @@ -308,14 +319,110 @@ void releaseResource_force_version() { assertTrue(releasedLibrary.getVersion().equals(newVersionToForce)); } + @Test + void releaseResource_require_non_experimental_error() { + loadResource("artifactAssessment-search-parameter.json"); + // SpecificationLibrary - root is experimentalbut HAS experimental children + loadTransaction("ersd-small-approved-draft-experimental-bundle.json"); + // SpecificationLibrary2 - root is NOT experimental but HAS experimental children + loadTransaction("ersd-small-approved-draft-non-experimental-with-experimental-comp-bundle.json"); + Parameters params = parameters( + part("version", new StringType("1.2.3")), + part("versionBehavior", new CodeType("default")), + part("requireNonExperimental", new CodeType("error")) + ); + Exception notExpectingAnyException = null; + // no Exception if root is experimental + try { + getClient().operation() + .onInstance(specificationLibReference) + .named("$crmi.release") + .withParameters(params) + .useHttpGet() + .returnResourceType(Bundle.class) + .execute(); + } catch (Exception e) { + notExpectingAnyException = e; + } + assertTrue(notExpectingAnyException == null); + + UnprocessableEntityException nonExperimentalChildException = null; + try { + getClient().operation() + .onInstance(specificationLibReference+"2") + .named("$crmi.release") + .withParameters(params) + .useHttpGet() + .returnResourceType(Bundle.class) + .execute(); + } catch (UnprocessableEntityException e) { + nonExperimentalChildException = e; + } + assertTrue(nonExperimentalChildException != null); + assertTrue(nonExperimentalChildException.getMessage().contains("not Experimental")); + } + + @Test + void releaseResource_require_non_experimental_warn() { + loadResource("artifactAssessment-search-parameter.json"); + // SpecificationLibrary - root is experimentalbut HAS experimental children + loadTransaction("ersd-small-approved-draft-experimental-bundle.json"); + // SpecificationLibrary2 - root is NOT experimental but HAS experimental children + loadTransaction("ersd-small-approved-draft-non-experimental-with-experimental-comp-bundle.json"); + Appender myMockAppender = mock(Appender.class); + List warningMessages = new ArrayList<>(); + doAnswer(t -> { + ILoggingEvent evt = (ILoggingEvent) t.getArguments()[0]; + // we only care about warning messages here + if (evt.getLevel().equals(Level.WARN)) { + // instead of appending to logs, we just add it to a list + warningMessages.add(evt.getFormattedMessage()); + } + return null; + }).when(myMockAppender).doAppend(any()); + org.slf4j.Logger logger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME); + ch.qos.logback.classic.Logger myLoggerRoot = (ch.qos.logback.classic.Logger) logger; + // add the mocked appender, make sure it is detached at the end + myLoggerRoot.addAppender(myMockAppender); + + Parameters params = parameters( + part("version", new StringType("1.2.3")), + part("versionBehavior", new CodeType("default")), + part("requireNonExperimental", new CodeType("warn")) + ); + getClient().operation() + .onInstance(specificationLibReference) + .named("$crmi.release") + .withParameters(params) + .useHttpGet() + .returnResourceType(Bundle.class) + .execute(); + // no warning if the root is Experimental + assertTrue(warningMessages.size()==0); + + getClient().operation() + .onInstance(specificationLibReference+"2") + .named("$crmi.release") + .withParameters(params) + .useHttpGet() + .returnResourceType(Bundle.class) + .execute(); + // SHOULD warn if the root is not experimental + assertTrue(warningMessages.stream().anyMatch(message -> message.contains("http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113762.1.4.1146.7"))); + assertTrue(warningMessages.stream().anyMatch(message -> message.contains("http://ersd.aimsplatform.org/fhir/Library/rctc2"))); + // cleanup + myLoggerRoot.detachAppender(myMockAppender); + } + @Test void releaseResource_propagate_effective_period() { loadTransaction("ersd-small-approved-draft-no-child-effective-period.json"); + loadResource("artifactAssessment-search-parameter.json"); String effectivePeriodToPropagate = "2020-12-11"; Parameters params = parameters( - part("version", new StringType("1.2.7.23")), - part("versionBehavior", new StringType("default")) + part("version", new StringType("1.2.7")), + part("versionBehavior", new CodeType("default")) ); Bundle returnResource = getClient().operation() @@ -369,8 +476,8 @@ void releaseResource_latestFromTx_NotSupported_test() { String actualErrorMessage = ""; Parameters params = parameters( - part("version", "1.2.3.23"), - part("versionBehavior", "default"), + part("version", "1.2.3"), + part("versionBehavior", new CodeType("default")), part("latestFromTxServer", new BooleanType(true)) ); @@ -396,7 +503,7 @@ void release_missing_approvalDate_validation_test() { Parameters params1 = parameters( part("version", versionData), - part("versionBehavior", "default") + part("versionBehavior", new CodeType("default")) ); try { @@ -415,11 +522,12 @@ void release_missing_approvalDate_validation_test() { @Test void release_version_format_test() { loadTransaction("ersd-small-approved-draft-bundle.json"); + loadResource("artifactAssessment-search-parameter.json"); for(String version:badVersionList){ UnprocessableEntityException maybeException = null; Parameters params = parameters( part("version", new StringType(version)), - part("versionBehavior", new StringType("force")) + part("versionBehavior", new CodeType("force")) ); try { getClient().operation() @@ -460,10 +568,11 @@ void release_releaseLabel_test() { @Test void release_version_active_test() { loadTransaction("ersd-small-active-bundle.json"); + loadResource("artifactAssessment-search-parameter.json"); PreconditionFailedException maybeException = null; Parameters params = parameters( - part("version", new StringType("1.2.3.23")), - part("versionBehavior", new StringType("force")) + part("version", new StringType("1.2.3")), + part("versionBehavior", new CodeType("force")) ); try { getClient().operation() @@ -481,8 +590,8 @@ void release_version_active_test() { void release_resource_not_found_test() { ResourceNotFoundException maybeException = null; Parameters params = parameters( - part("version", new StringType("1.2.3.23")), - part("versionBehavior", new StringType("force")) + part("version", new StringType("1.2.3")), + part("versionBehavior", new CodeType("force")) ); try { getClient().operation() @@ -499,6 +608,7 @@ void release_resource_not_found_test() { @Test void release_versionBehaviour_format_test() { loadTransaction("ersd-small-approved-draft-bundle.json"); + loadResource("artifactAssessment-search-parameter.json"); List badVersionBehaviors = Arrays.asList( "not-a-valid-option", null @@ -506,8 +616,8 @@ void release_versionBehaviour_format_test() { for(String versionBehaviour:badVersionBehaviors){ UnprocessableEntityException maybeException = null; Parameters params = parameters( - part("version", new StringType("1.2.3.23")), - part("versionBehavior", new StringType(versionBehaviour)) + part("version", new StringType("1.2.3")), + part("versionBehavior", new CodeType(versionBehaviour)) ); try { getClient().operation() @@ -543,7 +653,7 @@ void release_test_artifactComment_updated() { assertTrue(artifactAssessment.getDerivedFromContentRelatedArtifact().get().getResourceElement().getValue().equals("http://ersd.aimsplatform.org/fhir/Library/ReleaseSpecificationLibrary|1.2.3-draft")); Parameters releaseParams = parameters( part("version", versionData), - part("versionBehavior", "default") + part("versionBehavior", new CodeType("default")) ); Bundle releasedBundle = getClient().operation() .onInstance("Library/ReleaseSpecificationLibrary") diff --git a/plugin/cr/src/test/resources/ersd-small-approved-draft-experimental-bundle.json b/plugin/cr/src/test/resources/ersd-small-approved-draft-experimental-bundle.json new file mode 100644 index 000000000..e0564486c --- /dev/null +++ b/plugin/cr/src/test/resources/ersd-small-approved-draft-experimental-bundle.json @@ -0,0 +1,280 @@ +{ + "resourceType": "Bundle", + "id": "rctc-release-2022-10-19-Bundle-rctc", + "type": "transaction", + "timestamp": "2022-10-21T15:18:28.504-04:00", + "entry": [ + { + "fullUrl": "http://ersd.aimsplatform.org/fhir/Library/SpecificationLibrary", + "resource": { + "resourceType": "Library", + "id": "SpecificationLibrary", + "url": "http://ersd.aimsplatform.org/fhir/Library/SpecificationLibrary", + "version": "1.2.3-draft", + "status": "draft", + "date": "2023-06-30", + "approvalDate": "2023-06-30", + "experimental":true, + "relatedArtifact": [ + { + "type": "composed-of", + "resource": "http://ersd.aimsplatform.org/fhir/PlanDefinition/us-ecr-specification|1.2.3-draft", + "extension":[ + { + "url":"http://hl7.org/fhir/StructureDefinition/crmi-isOwned", + "valueBoolean": true + } + ] + }, + { + "type": "composed-of", + "resource": "http://ersd.aimsplatform.org/fhir/Library/rctc|1.2.3-draft", + "extension":[ + { + "url":"http://hl7.org/fhir/StructureDefinition/crmi-isOwned", + "valueBoolean": true + } + ] + } + ] + }, + "request": { + "method": "PUT", + "url": "Library/SpecificationLibrary" + } + }, + { + "fullUrl": "http://ersd.aimsplatform.org/fhir/PlanDefinition/us-ecr-specification", + "resource": { + "resourceType": "PlanDefinition", + "id": "us-ecr-specification", + "url": "http://ersd.aimsplatform.org/fhir/PlanDefinition/us-ecr-specification", + "version": "1.2.3-draft", + "status": "draft", + "relatedArtifact": [ + { + "type": "depends-on", + "label": "RCTC Value Set Library of Trigger Codes", + "resource": "http://ersd.aimsplatform.org/fhir/Library/rctc|1.2.3-draft" + } + ] + }, + "request": { + "method": "PUT", + "url": "PlanDefinition/us-ecr-specification" + } + }, + { + "fullUrl": "http://ersd.aimsplatform.org/fhir/Library/rctc", + "resource": { + "resourceType": "Library", + "id": "rctc", + "url": "http://ersd.aimsplatform.org/fhir/Library/rctc", + "version": "1.2.3-draft", + "status": "draft", + "relatedArtifact": [ + { + "type": "composed-of", + "resource": "http://ersd.aimsplatform.org/fhir/ValueSet/dxtc|1.2.3-draft", + "extension":[ + { + "url":"http://hl7.org/fhir/StructureDefinition/crmi-isOwned", + "valueBoolean": true + } + ] + } + ] + }, + "request": { + "method": "PUT", + "url": "Library/rctc" + } + }, + { + "fullUrl": "http://ersd.aimsplatform.org/fhir/ValueSet/dxtc", + "resource": { + "resourceType": "ValueSet", + "id": "dxtc", + "experimental":true, + "url": "http://ersd.aimsplatform.org/fhir/ValueSet/dxtc", + "version": "1.2.3-draft", + "status": "draft", + "compose": { + "include": [ + { + "valueSet": [ + "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113762.1.4.1146.6|20210526" + ] + } + ] + }, + "expansion": { + "timestamp": "2022-10-21T15:18:29-04:00", + "contains": [ + { + "system": "http://hl7.org/fhir/sid/icd-10-cm", + "version": "Provisional_2022-01-12", + "code": "T40.0X1A" + }, + { + "system": "http://hl7.org/fhir/sid/icd-10-cm", + "version": "Provisional_2022-01-12", + "code": "T40.0X2A" + }, + { + "system": "http://hl7.org/fhir/sid/icd-10-cm", + "version": "Provisional_2022-01-12", + "code": "T40.0X3A" + } + ] + } + }, + "request": { + "method": "PUT", + "url": "ValueSet/dxtc" + } + }, + { + "fullUrl": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113762.1.4.1146.6", + "resource": { + "resourceType": "ValueSet", + "id": "2.16.840.1.113762.1.4.1146.6", + "url": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113762.1.4.1146.6", + "identifier": [ + { + "system": "urn:ietf:rfc:3986", + "value": "urn:oid:2.16.840.1.113762.1.4.1146.6" + } + ], + "version": "20210526", + "status": "active", + "compose": { + "include": [ + { + "system": "http://snomed.info/sct", + "version": "Provisional_2022-04-25", + "concept": [ + { + "code": "1086051000119107", + "display": "Cardiomyopathy due to diphtheria (disorder)" + }, + { + "code": "1086061000119109", + "display": "Diphtheria radiculomyelitis (disorder)" + }, + { + "code": "1086071000119103", + "display": "Diphtheria tubulointerstitial nephropathy (disorder)" + }, + { + "code": "1090211000119102", + "display": "Pharyngeal diphtheria (disorder)" + }, + { + "code": "129667001", + "display": "Diphtheritic peripheral neuritis (disorder)" + }, + { + "code": "13596001", + "display": "Diphtheritic peritonitis (disorder)" + }, + { + "code": "15682004", + "display": "Anterior nasal diphtheria (disorder)" + }, + { + "code": "186347006", + "display": "Diphtheria of penis (disorder)" + }, + { + "code": "18901009", + "display": "Cutaneous diphtheria (disorder)" + }, + { + "code": "194945009", + "display": "Acute myocarditis - diphtheritic (disorder)" + }, + { + "code": "230596007", + "display": "Diphtheritic neuropathy (disorder)" + }, + { + "code": "240422004", + "display": "Tracheobronchial diphtheria (disorder)" + }, + { + "code": "26117009", + "display": "Diphtheritic myocarditis (disorder)" + }, + { + "code": "276197005", + "display": "Infection caused by Corynebacterium diphtheriae (disorder)" + }, + { + "code": "3419005", + "display": "Faucial diphtheria (disorder)" + }, + { + "code": "397428000", + "display": "Diphtheria (disorder)" + }, + { + "code": "397430003", + "display": "Diphtheria caused by Corynebacterium diphtheriae (disorder)" + }, + { + "code": "48278001", + "display": "Diphtheritic cystitis (disorder)" + }, + { + "code": "50215002", + "display": "Laryngeal diphtheria (disorder)" + }, + { + "code": "715659006", + "display": "Diphtheria of respiratory system (disorder)" + }, + { + "code": "75589004", + "display": "Nasopharyngeal diphtheria (disorder)" + }, + { + "code": "7773002", + "display": "Conjunctival diphtheria (disorder)" + }, + { + "code": "789005009", + "display": "Paralysis of uvula after diphtheria (disorder)" + } + ] + } + ] + }, + "expansion": { + "timestamp": "2022-10-21T15:18:29-04:00", + "contains": [ + { + "system": "http://snomed.info/sct", + "version": "Provisional_2022-04-25", + "code": "1086051000119107" + }, + { + "system": "http://snomed.info/sct", + "version": "Provisional_2022-04-25", + "code": "1086061000119109" + }, + { + "system": "http://snomed.info/sct", + "version": "Provisional_2022-04-25", + "code": "1086071000119103" + } + ] + } + }, + "request": { + "method": "PUT", + "url": "ValueSet/2.16.840.1.113762.1.4.1146.6" + } + } + ] +} \ No newline at end of file diff --git a/plugin/cr/src/test/resources/ersd-small-approved-draft-non-experimental-with-experimental-comp-bundle.json b/plugin/cr/src/test/resources/ersd-small-approved-draft-non-experimental-with-experimental-comp-bundle.json new file mode 100644 index 000000000..9936e52d7 --- /dev/null +++ b/plugin/cr/src/test/resources/ersd-small-approved-draft-non-experimental-with-experimental-comp-bundle.json @@ -0,0 +1,280 @@ +{ + "resourceType": "Bundle", + "id": "rctc2-release-2022-10-19-Bundle-rctc2", + "type": "transaction", + "timestamp": "2022-10-21T15:18:28.504-04:00", + "entry": [ + { + "fullUrl": "http://ersd.aimsplatform.org/fhir/Library/SpecificationLibrary2", + "resource": { + "resourceType": "Library", + "id": "SpecificationLibrary2", + "url": "http://ersd.aimsplatform.org/fhir/Library/SpecificationLibrary2", + "version": "1.2.3-draft", + "status": "draft", + "date": "2023-06-30", + "approvalDate": "2023-06-30", + "relatedArtifact": [ + { + "type": "composed-of", + "resource": "http://ersd.aimsplatform.org/fhir/PlanDefinition/us-ecr-specification2|1.2.3-draft", + "extension":[ + { + "url":"http://hl7.org/fhir/StructureDefinition/crmi-isOwned", + "valueBoolean": true + } + ] + }, + { + "type": "composed-of", + "resource": "http://ersd.aimsplatform.org/fhir/Library/rctc2|1.2.3-draft", + "extension":[ + { + "url":"http://hl7.org/fhir/StructureDefinition/crmi-isOwned", + "valueBoolean": true + } + ] + } + ] + }, + "request": { + "method": "PUT", + "url": "Library/SpecificationLibrary2" + } + }, + { + "fullUrl": "http://ersd.aimsplatform.org/fhir/PlanDefinition/us-ecr-specification2", + "resource": { + "resourceType": "PlanDefinition", + "id": "us-ecr-specification2", + "url": "http://ersd.aimsplatform.org/fhir/PlanDefinition/us-ecr-specification2", + "version": "1.2.3-draft", + "status": "draft", + "relatedArtifact": [ + { + "type": "depends-on", + "label": "rctc2 Value Set Library of Trigger Codes", + "resource": "http://ersd.aimsplatform.org/fhir/Library/rctc2|1.2.3-draft" + } + ] + }, + "request": { + "method": "PUT", + "url": "PlanDefinition/us-ecr-specification2" + } + }, + { + "fullUrl": "http://ersd.aimsplatform.org/fhir/Library/rctc2", + "resource": { + "resourceType": "Library", + "id": "rctc2", + "url": "http://ersd.aimsplatform.org/fhir/Library/rctc2", + "version": "1.2.3-draft", + "status": "draft", + "experimental":true, + "relatedArtifact": [ + { + "type": "composed-of", + "resource": "http://ersd.aimsplatform.org/fhir/ValueSet/dxtc2|1.2.3-draft", + "extension":[ + { + "url":"http://hl7.org/fhir/StructureDefinition/crmi-isOwned", + "valueBoolean": true + } + ] + } + ] + }, + "request": { + "method": "PUT", + "url": "Library/rctc2" + } + }, + { + "fullUrl": "http://ersd.aimsplatform.org/fhir/ValueSet/dxtc2", + "resource": { + "resourceType": "ValueSet", + "id": "dxtc2", + "url": "http://ersd.aimsplatform.org/fhir/ValueSet/dxtc2", + "version": "1.2.3-draft", + "status": "draft", + "compose": { + "include": [ + { + "valueSet": [ + "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113762.1.4.1146.7|20210526" + ] + } + ] + }, + "expansion": { + "timestamp": "2022-10-21T15:18:29-04:00", + "contains": [ + { + "system": "http://hl7.org/fhir/sid/icd-10-cm", + "version": "Provisional_2022-01-12", + "code": "T40.0X1A" + }, + { + "system": "http://hl7.org/fhir/sid/icd-10-cm", + "version": "Provisional_2022-01-12", + "code": "T40.0X2A" + }, + { + "system": "http://hl7.org/fhir/sid/icd-10-cm", + "version": "Provisional_2022-01-12", + "code": "T40.0X3A" + } + ] + } + }, + "request": { + "method": "PUT", + "url": "ValueSet/dxtc2" + } + }, + { + "fullUrl": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113762.1.4.1146.7", + "resource": { + "resourceType": "ValueSet", + "id": "2.16.840.1.113762.1.4.1146.7", + "url": "http://cts.nlm.nih.gov/fhir/ValueSet/2.16.840.1.113762.1.4.1146.7", + "identifier": [ + { + "system": "urn:ietf:rfc:3986", + "value": "urn:oid:2.16.840.1.113762.1.4.1146.7" + } + ], + "version": "20210526", + "status": "active", + "experimental":true, + "compose": { + "include": [ + { + "system": "http://snomed.info/sct", + "version": "Provisional_2022-04-25", + "concept": [ + { + "code": "1086051000119107", + "display": "Cardiomyopathy due to diphtheria (disorder)" + }, + { + "code": "1086061000119109", + "display": "Diphtheria radiculomyelitis (disorder)" + }, + { + "code": "1086071000119103", + "display": "Diphtheria tubulointerstitial nephropathy (disorder)" + }, + { + "code": "1090211000119102", + "display": "Pharyngeal diphtheria (disorder)" + }, + { + "code": "129667001", + "display": "Diphtheritic peripheral neuritis (disorder)" + }, + { + "code": "13596001", + "display": "Diphtheritic peritonitis (disorder)" + }, + { + "code": "15682004", + "display": "Anterior nasal diphtheria (disorder)" + }, + { + "code": "186347006", + "display": "Diphtheria of penis (disorder)" + }, + { + "code": "18901009", + "display": "Cutaneous diphtheria (disorder)" + }, + { + "code": "194945009", + "display": "Acute myocarditis - diphtheritic (disorder)" + }, + { + "code": "230596007", + "display": "Diphtheritic neuropathy (disorder)" + }, + { + "code": "240422004", + "display": "Tracheobronchial diphtheria (disorder)" + }, + { + "code": "26117009", + "display": "Diphtheritic myocarditis (disorder)" + }, + { + "code": "276197005", + "display": "Infection caused by Corynebacterium diphtheriae (disorder)" + }, + { + "code": "3419005", + "display": "Faucial diphtheria (disorder)" + }, + { + "code": "397428000", + "display": "Diphtheria (disorder)" + }, + { + "code": "397430003", + "display": "Diphtheria caused by Corynebacterium diphtheriae (disorder)" + }, + { + "code": "48278001", + "display": "Diphtheritic cystitis (disorder)" + }, + { + "code": "50215002", + "display": "Laryngeal diphtheria (disorder)" + }, + { + "code": "715659006", + "display": "Diphtheria of respiratory system (disorder)" + }, + { + "code": "75589004", + "display": "Nasopharyngeal diphtheria (disorder)" + }, + { + "code": "7773002", + "display": "Conjunctival diphtheria (disorder)" + }, + { + "code": "789005009", + "display": "Paralysis of uvula after diphtheria (disorder)" + } + ] + } + ] + }, + "expansion": { + "timestamp": "2022-10-21T15:18:29-04:00", + "contains": [ + { + "system": "http://snomed.info/sct", + "version": "Provisional_2022-04-25", + "code": "1086051000119107" + }, + { + "system": "http://snomed.info/sct", + "version": "Provisional_2022-04-25", + "code": "1086061000119109" + }, + { + "system": "http://snomed.info/sct", + "version": "Provisional_2022-04-25", + "code": "1086071000119103" + } + ] + } + }, + "request": { + "method": "PUT", + "url": "ValueSet/2.16.840.1.113762.1.4.1146.7" + } + } + ] +} \ No newline at end of file