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());
}
/**
* 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 {
// 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
* 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();

View File

@ -146,6 +146,7 @@ public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc {
bundleBuilder.setType(Bundle.BundleType.DOCUMENT.toCode());
bundleBuilder.setIdentifier("urn:ietf:rfc:4122", UUID.randomUUID().toString());
bundleBuilder.setTimestamp(InstantType.now());
bundleBuilder.addProfile(theStrategy.getBundleProfile());
// Add composition to document
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.ValidationSupportContext;
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.jpa.DefaultJpaIpsGenerationStrategy;
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 jakarta.annotation.Nonnull;
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.validator.FhirInstanceValidator;
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.Parameters;
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.Resource;
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.test.context.ContextConfiguration;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@ -48,10 +50,10 @@ import java.util.stream.Collectors;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
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.assertFalse;
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
@ -77,7 +79,7 @@ public class IpsGenerationR4Test extends BaseResourceProviderR4Test {
@Test
public void testGenerateLargePatientSummary() {
public void testGenerateLargePatientSummary() throws IOException {
Bundle sourceData = ClasspathUtil.loadCompressedResource(myFhirContext, Bundle.class, "/large-patient-everything.json.gz");
sourceData.setType(Bundle.BundleType.TRANSACTION);
for (Bundle.BundleEntryComponent nextEntry : sourceData.getEntry()) {
@ -97,6 +99,9 @@ public class IpsGenerationR4Test extends BaseResourceProviderR4Test {
ourLog.info("Output: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(output));
// Verify
assertThat(output.getMeta().getProfile().stream().map(PrimitiveType::getValue).toList(), contains(
"http://hl7.org/fhir/uv/ips/StructureDefinition/Bundle-uv-ips"
));
validateDocument(output);
assertEquals(117, output.getEntry().size());
String patientId = findFirstEntryResource(output, Patient.class, 1).getIdElement().toUnqualifiedVersionless().getValue();
@ -162,7 +167,7 @@ public class IpsGenerationR4Test extends BaseResourceProviderR4Test {
}
@Test
public void testGenerateTinyPatientSummary() {
public void testGenerateTinyPatientSummary() throws IOException {
myStorageSettings.setResourceClientIdStrategy(JpaStorageSettings.ClientIdStrategyEnum.ANY);
Bundle sourceData = ClasspathUtil.loadCompressedResource(myFhirContext, Bundle.class, "/tiny-patient-everything.json.gz");
@ -251,7 +256,7 @@ public class IpsGenerationR4Test extends BaseResourceProviderR4Test {
@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.SectionComponent section = composition
.getSection()
@ -259,6 +264,7 @@ public class IpsGenerationR4Test extends BaseResourceProviderR4Test {
.filter(t -> t.getCode().getCoding().get(0).getDisplay().equals(theDisplay))
.findFirst()
.orElseThrow();
assertNotNull(section);
return composition;
}
@ -266,23 +272,26 @@ public class IpsGenerationR4Test extends BaseResourceProviderR4Test {
@Nonnull
private static List<String> extractSectionTitles(Bundle outcome) {
Composition composition = (Composition) outcome.getEntry().get(0).getResource();
List<String> sectionTitles = composition
return composition
.getSection()
.stream()
.map(Composition.SectionComponent::getTitle)
.toList();
return sectionTitles;
}
private void validateDocument(Bundle theOutcome) {
private void validateDocument(Bundle theOutcome) throws IOException {
FhirValidator validator = myFhirContext.newValidator();
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);
ValidationResult validation = validator.validateWithResult(theOutcome);
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
@ -294,7 +303,7 @@ public class IpsGenerationR4Test extends BaseResourceProviderR4Test {
}
@Bean
public IIpsGeneratorSvc ipsGeneratorSvc(FhirContext theFhirContext, IIpsGenerationStrategy theGenerationStrategy, DaoRegistry theDaoRegistry) {
public IIpsGeneratorSvc ipsGeneratorSvc(FhirContext theFhirContext, IIpsGenerationStrategy theGenerationStrategy) {
return new IpsGeneratorSvcImpl(theFhirContext, theGenerationStrategy);
}