Include profile URL in generated IPS (#5938)

* Include profile URL in generated IPS

* Add changelog

* Documentation tweak
This commit is contained in:
James Agnew 2024-05-16 05:02:06 -04:00 committed by GitHub
parent 446869b524
commit 14c364dffd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 39 additions and 12 deletions

View File

@ -626,6 +626,16 @@ public class BundleBuilder {
terser.setElement(myBundle, "Bundle.timestamp", theTimestamp.getValueAsString()); terser.setElement(myBundle, "Bundle.timestamp", theTimestamp.getValueAsString());
} }
/**
* Adds a profile URL to <code>Bundle.meta.profile</code>
*
* @since 7.4.0
*/
public void addProfile(String theProfile) {
FhirTerser terser = myContext.newTerser();
terser.addElement(myBundle, "Bundle.meta.profile", theProfile);
}
public class DeleteBuilder extends BaseOperationBuilder { public class DeleteBuilder extends BaseOperationBuilder {
// nothing yet // nothing yet

View File

@ -0,0 +1,4 @@
---
type: add
issue: 5938
title: "Generated IPS documents will now include a bundle profile declaration."

View File

@ -42,7 +42,10 @@ public interface IIpsGenerationStrategy {
/** /**
* This method returns the profile associated with the IPS document * This method returns the profile associated with the IPS document
* generated by this strategy. * generated by this strategy. This URL will be added to generated
* IPS Bundles in <code>Bundle.meta.profile</code>, and can also
* be used to support the <code>profile</code> parameter on the
* <code>$summary</code> operation.
*/ */
String getBundleProfile(); String getBundleProfile();

View File

@ -146,6 +146,7 @@ public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc {
bundleBuilder.setType(Bundle.BundleType.DOCUMENT.toCode()); bundleBuilder.setType(Bundle.BundleType.DOCUMENT.toCode());
bundleBuilder.setIdentifier("urn:ietf:rfc:4122", UUID.randomUUID().toString()); bundleBuilder.setIdentifier("urn:ietf:rfc:4122", UUID.randomUUID().toString());
bundleBuilder.setTimestamp(InstantType.now()); bundleBuilder.setTimestamp(InstantType.now());
bundleBuilder.addProfile(theStrategy.getBundleProfile());
// Add composition to document // Add composition to document
bundleBuilder.addDocumentEntry(composition); bundleBuilder.addDocumentEntry(composition);

View File

@ -5,7 +5,6 @@ import ca.uhn.fhir.context.support.ConceptValidationOptions;
import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport;
import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.context.support.ValidationSupportContext;
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings; import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.ips.api.IIpsGenerationStrategy; import ca.uhn.fhir.jpa.ips.api.IIpsGenerationStrategy;
import ca.uhn.fhir.jpa.ips.jpa.DefaultJpaIpsGenerationStrategy; import ca.uhn.fhir.jpa.ips.jpa.DefaultJpaIpsGenerationStrategy;
import ca.uhn.fhir.jpa.ips.provider.IpsOperationProvider; import ca.uhn.fhir.jpa.ips.provider.IpsOperationProvider;
@ -18,6 +17,7 @@ import ca.uhn.fhir.validation.SingleValidationMessage;
import ca.uhn.fhir.validation.ValidationResult; import ca.uhn.fhir.validation.ValidationResult;
import jakarta.annotation.Nonnull; import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable; import jakarta.annotation.Nullable;
import org.hl7.fhir.common.hapi.validation.support.NpmPackageValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain; import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain;
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
@ -31,6 +31,7 @@ import org.hl7.fhir.r4.model.Immunization;
import org.hl7.fhir.r4.model.MedicationStatement; import org.hl7.fhir.r4.model.MedicationStatement;
import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.Patient; import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.PrimitiveType;
import org.hl7.fhir.r4.model.Reference; import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.Resource; import org.hl7.fhir.r4.model.Resource;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
@ -41,6 +42,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfiguration;
import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -48,10 +50,10 @@ import java.util.stream.Collectors;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.stringContainsInOrder; import static org.hamcrest.Matchers.stringContainsInOrder;
import static org.hibernate.validator.internal.util.Contracts.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
/** /**
* This test uses a complete R4 JPA server as a backend and wires the * This test uses a complete R4 JPA server as a backend and wires the
@ -77,7 +79,7 @@ public class IpsGenerationR4Test extends BaseResourceProviderR4Test {
@Test @Test
public void testGenerateLargePatientSummary() { public void testGenerateLargePatientSummary() throws IOException {
Bundle sourceData = ClasspathUtil.loadCompressedResource(myFhirContext, Bundle.class, "/large-patient-everything.json.gz"); Bundle sourceData = ClasspathUtil.loadCompressedResource(myFhirContext, Bundle.class, "/large-patient-everything.json.gz");
sourceData.setType(Bundle.BundleType.TRANSACTION); sourceData.setType(Bundle.BundleType.TRANSACTION);
for (Bundle.BundleEntryComponent nextEntry : sourceData.getEntry()) { for (Bundle.BundleEntryComponent nextEntry : sourceData.getEntry()) {
@ -97,6 +99,9 @@ public class IpsGenerationR4Test extends BaseResourceProviderR4Test {
ourLog.info("Output: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(output)); ourLog.info("Output: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(output));
// Verify // Verify
assertThat(output.getMeta().getProfile().stream().map(PrimitiveType::getValue).toList(), contains(
"http://hl7.org/fhir/uv/ips/StructureDefinition/Bundle-uv-ips"
));
validateDocument(output); validateDocument(output);
assertEquals(117, output.getEntry().size()); assertEquals(117, output.getEntry().size());
String patientId = findFirstEntryResource(output, Patient.class, 1).getIdElement().toUnqualifiedVersionless().getValue(); String patientId = findFirstEntryResource(output, Patient.class, 1).getIdElement().toUnqualifiedVersionless().getValue();
@ -162,7 +167,7 @@ public class IpsGenerationR4Test extends BaseResourceProviderR4Test {
} }
@Test @Test
public void testGenerateTinyPatientSummary() { public void testGenerateTinyPatientSummary() throws IOException {
myStorageSettings.setResourceClientIdStrategy(JpaStorageSettings.ClientIdStrategyEnum.ANY); myStorageSettings.setResourceClientIdStrategy(JpaStorageSettings.ClientIdStrategyEnum.ANY);
Bundle sourceData = ClasspathUtil.loadCompressedResource(myFhirContext, Bundle.class, "/tiny-patient-everything.json.gz"); Bundle sourceData = ClasspathUtil.loadCompressedResource(myFhirContext, Bundle.class, "/tiny-patient-everything.json.gz");
@ -251,7 +256,7 @@ public class IpsGenerationR4Test extends BaseResourceProviderR4Test {
@Nonnull @Nonnull
private static Composition findCompositionSectionByDisplay(Bundle output, String theDisplay) { private static Composition findCompositionSectionByDisplay(Bundle output, @SuppressWarnings("SameParameterValue") String theDisplay) {
Composition composition = (Composition) output.getEntry().get(0).getResource(); Composition composition = (Composition) output.getEntry().get(0).getResource();
Composition.SectionComponent section = composition Composition.SectionComponent section = composition
.getSection() .getSection()
@ -259,6 +264,7 @@ public class IpsGenerationR4Test extends BaseResourceProviderR4Test {
.filter(t -> t.getCode().getCoding().get(0).getDisplay().equals(theDisplay)) .filter(t -> t.getCode().getCoding().get(0).getDisplay().equals(theDisplay))
.findFirst() .findFirst()
.orElseThrow(); .orElseThrow();
assertNotNull(section);
return composition; return composition;
} }
@ -266,23 +272,26 @@ public class IpsGenerationR4Test extends BaseResourceProviderR4Test {
@Nonnull @Nonnull
private static List<String> extractSectionTitles(Bundle outcome) { private static List<String> extractSectionTitles(Bundle outcome) {
Composition composition = (Composition) outcome.getEntry().get(0).getResource(); Composition composition = (Composition) outcome.getEntry().get(0).getResource();
List<String> sectionTitles = composition return composition
.getSection() .getSection()
.stream() .stream()
.map(Composition.SectionComponent::getTitle) .map(Composition.SectionComponent::getTitle)
.toList(); .toList();
return sectionTitles;
} }
private void validateDocument(Bundle theOutcome) { private void validateDocument(Bundle theOutcome) throws IOException {
FhirValidator validator = myFhirContext.newValidator(); FhirValidator validator = myFhirContext.newValidator();
FhirInstanceValidator instanceValidator = new FhirInstanceValidator(myFhirContext); FhirInstanceValidator instanceValidator = new FhirInstanceValidator(myFhirContext);
instanceValidator.setValidationSupport(new ValidationSupportChain(new IpsTerminologySvc(), myFhirContext.getValidationSupport()));
NpmPackageValidationSupport npmSupport = new NpmPackageValidationSupport(myFhirContext);
npmSupport.loadPackageFromClasspath("/ips-package-1.1.0.tgz");
instanceValidator.setValidationSupport(new ValidationSupportChain(npmSupport, new IpsTerminologySvc(), myFhirContext.getValidationSupport()));
validator.registerValidatorModule(instanceValidator); validator.registerValidatorModule(instanceValidator);
ValidationResult validation = validator.validateWithResult(theOutcome); ValidationResult validation = validator.validateWithResult(theOutcome);
Optional<SingleValidationMessage> failure = validation.getMessages().stream().filter(t -> t.getSeverity().ordinal() >= ResultSeverityEnum.ERROR.ordinal()).findFirst(); Optional<SingleValidationMessage> failure = validation.getMessages().stream().filter(t -> t.getSeverity().ordinal() >= ResultSeverityEnum.ERROR.ordinal()).findFirst();
assertFalse(failure.isPresent(), () -> failure.get().toString()); assertFalse(failure.isPresent(), () -> failure.orElseThrow().toString());
} }
@Configuration @Configuration
@ -294,7 +303,7 @@ public class IpsGenerationR4Test extends BaseResourceProviderR4Test {
} }
@Bean @Bean
public IIpsGeneratorSvc ipsGeneratorSvc(FhirContext theFhirContext, IIpsGenerationStrategy theGenerationStrategy, DaoRegistry theDaoRegistry) { public IIpsGeneratorSvc ipsGeneratorSvc(FhirContext theFhirContext, IIpsGenerationStrategy theGenerationStrategy) {
return new IpsGeneratorSvcImpl(theFhirContext, theGenerationStrategy); return new IpsGeneratorSvcImpl(theFhirContext, theGenerationStrategy);
} }