Additional tweaks for #4509 (#4510)

* Additional tweaks for #4509

* More teaks

* format imports
This commit is contained in:
James Agnew 2023-02-09 09:35:33 -05:00 committed by GitHub
parent 26a7009277
commit 765fedfefa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 254 additions and 81 deletions

View File

@ -22,6 +22,7 @@ package ca.uhn.fhir.jpa.ips.generator;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.fhirpath.IFhirPathEvaluationContext; import ca.uhn.fhir.fhirpath.IFhirPathEvaluationContext;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
@ -32,6 +33,7 @@ import ca.uhn.fhir.jpa.ips.api.SectionRegistry;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.api.Include; import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.dstu2.resource.Observation;
import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum; import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum;
import ca.uhn.fhir.narrative.CustomThymeleafNarrativeGenerator; import ca.uhn.fhir.narrative.CustomThymeleafNarrativeGenerator;
import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.IBundleProvider;
@ -42,6 +44,10 @@ import ca.uhn.fhir.util.BundleBuilder;
import ca.uhn.fhir.util.CompositionBuilder; import ca.uhn.fhir.util.CompositionBuilder;
import ca.uhn.fhir.util.ResourceReferenceInfo; import ca.uhn.fhir.util.ResourceReferenceInfo;
import ca.uhn.fhir.util.ValidateUtil; import ca.uhn.fhir.util.ValidateUtil;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import org.apache.commons.collections4.BidiMap;
import org.apache.commons.collections4.bidimap.DualHashBidiMap;
import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseBundle; import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseExtension; import org.hl7.fhir.instance.model.api.IBaseExtension;
@ -66,6 +72,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors;
import static ca.uhn.fhir.jpa.term.api.ITermLoaderSvc.LOINC_URI; import static ca.uhn.fhir.jpa.term.api.ITermLoaderSvc.LOINC_URI;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -214,6 +221,8 @@ public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc {
nextCandidate = previouslyExistingResource; nextCandidate = previouslyExistingResource;
sectionResourcesToInclude.addResourceIfNotAlreadyPresent(nextCandidate, originalResourceId); sectionResourcesToInclude.addResourceIfNotAlreadyPresent(nextCandidate, originalResourceId);
} else if (theGlobalResourcesToInclude.hasResourceWithReplacementId(originalResourceId)) {
sectionResourcesToInclude.addResourceIfNotAlreadyPresent(nextCandidate, originalResourceId);
} else { } else {
IIdType id = myGenerationStrategy.massageResourceId(theIpsContext, nextCandidate); IIdType id = myGenerationStrategy.massageResourceId(theIpsContext, nextCandidate);
nextCandidate.setId(id); nextCandidate.setId(id);
@ -226,33 +235,6 @@ public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc {
} }
/*
* Update any references within the added candidates - This is important
* because we might be replacing resource IDs before including them in
* the summary, so we need to also update the references to those
* resources.
*/
for (IBaseResource nextResource : sectionResourcesToInclude.getResources()) {
List<ResourceReferenceInfo> references = myFhirContext.newTerser().getAllResourceReferences(nextResource);
for (ResourceReferenceInfo nextReference : references) {
String existingReference = nextReference.getResourceReference().getReferenceElement().getValue();
if (isNotBlank(existingReference)) {
existingReference = new IdType(existingReference).toUnqualifiedVersionless().getValue();
String replacement = theGlobalResourcesToInclude.getIdSubstitution(existingReference);
if (isNotBlank(replacement)) {
if (!replacement.equals(existingReference)) {
nextReference.getResourceReference().setReference(replacement);
}
} else if (theGlobalResourcesToInclude.getResourceById(existingReference) == null) {
// If this reference doesn't point to something we have actually
// included in the bundle, clear the reference.
nextReference.getResourceReference().setReference(null);
nextReference.getResourceReference().setResource(null);
}
}
}
}
} }
if (sectionResourcesToInclude.isEmpty() && theSection.getNoInfoGenerator() != null) { if (sectionResourcesToInclude.isEmpty() && theSection.getNoInfoGenerator() != null) {
@ -266,6 +248,33 @@ public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc {
sectionResourcesToInclude.addResourceIfNotAlreadyPresent(noInfoResource, id); sectionResourcesToInclude.addResourceIfNotAlreadyPresent(noInfoResource, id);
} }
/*
* Update any references within the added candidates - This is important
* because we might be replacing resource IDs before including them in
* the summary, so we need to also update the references to those
* resources.
*/
for (IBaseResource nextResource : sectionResourcesToInclude.getResources()) {
List<ResourceReferenceInfo> references = myFhirContext.newTerser().getAllResourceReferences(nextResource);
for (ResourceReferenceInfo nextReference : references) {
String existingReference = nextReference.getResourceReference().getReferenceElement().getValue();
if (isNotBlank(existingReference)) {
existingReference = new IdType(existingReference).toUnqualifiedVersionless().getValue();
String replacement = theGlobalResourcesToInclude.getIdSubstitution(existingReference);
if (isNotBlank(replacement)) {
if (!replacement.equals(existingReference)) {
nextReference.getResourceReference().setReference(replacement);
}
} else if (theGlobalResourcesToInclude.getResourceById(existingReference) == null) {
// If this reference doesn't point to something we have actually
// included in the bundle, clear the reference.
nextReference.getResourceReference().setReference(null);
nextReference.getResourceReference().setResource(null);
}
}
}
}
addSection(theSection, theCompositionBuilder, sectionResourcesToInclude, theGlobalResourcesToInclude); addSection(theSection, theCompositionBuilder, sectionResourcesToInclude, theGlobalResourcesToInclude);
} }
@ -278,6 +287,9 @@ public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc {
sectionBuilder.addCodeCoding(LOINC_URI, theSection.getSectionCode(), theSection.getSectionDisplay()); sectionBuilder.addCodeCoding(LOINC_URI, theSection.getSectionCode(), theSection.getSectionDisplay());
for (IBaseResource next : theResourcesToInclude.getResources()) { for (IBaseResource next : theResourcesToInclude.getResources()) {
if (ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.get(next) == BundleEntrySearchModeEnum.INCLUDE) {
continue;
}
IBaseExtension<?, ?> narrativeLink = ((IBaseHasExtensions) next).addExtension(); IBaseExtension<?, ?> narrativeLink = ((IBaseHasExtensions) next).addExtension();
narrativeLink.setUrl("http://hl7.org/fhir/StructureDefinition/narrativeLink"); narrativeLink.setUrl("http://hl7.org/fhir/StructureDefinition/narrativeLink");
@ -314,7 +326,18 @@ public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc {
private String determinePatientCompartmentSearchParameterName(String theResourceType) { private String determinePatientCompartmentSearchParameterName(String theResourceType) {
RuntimeResourceDefinition resourceDef = myFhirContext.getResourceDefinition(theResourceType); RuntimeResourceDefinition resourceDef = myFhirContext.getResourceDefinition(theResourceType);
return resourceDef.getSearchParamsForCompartmentName("Patient").get(0).getName(); Set<String> searchParams = resourceDef.getSearchParamsForCompartmentName("Patient")
.stream()
.map(RuntimeSearchParam::getName)
.collect(Collectors.toSet());
// Prefer "patient", then "subject" then anything else
if (searchParams.contains(Observation.SP_PATIENT)) {
return Observation.SP_PATIENT;
}
if (searchParams.contains(Observation.SP_SUBJECT)) {
return Observation.SP_SUBJECT;
}
return searchParams.iterator().next();
} }
private void massageResourceId(IpsContext theIpsContext, IBaseResource theResource) { private void massageResourceId(IpsContext theIpsContext, IBaseResource theResource) {
@ -519,7 +542,7 @@ public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc {
private final List<IBaseResource> myResources = new ArrayList<>(); private final List<IBaseResource> myResources = new ArrayList<>();
private final Map<String, IBaseResource> myIdToResource = new HashMap<>(); private final Map<String, IBaseResource> myIdToResource = new HashMap<>();
private final Map<String, String> myOriginalIdToNewId = new HashMap<>(); private final BiMap<String, String> myOriginalIdToNewId = HashBiMap.create();
public List<IBaseResource> getResources() { public List<IBaseResource> getResources() {
return myResources; return myResources;
@ -549,6 +572,10 @@ public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc {
return getResourceById(theReference.toUnqualifiedVersionless().getValue()); return getResourceById(theReference.toUnqualifiedVersionless().getValue());
} }
public boolean hasResourceWithReplacementId(String theReplacementId) {
return myOriginalIdToNewId.containsValue(theReplacementId);
}
public IBaseResource getResourceById(String theReference) { public IBaseResource getResourceById(String theReference) {
return myIdToResource.get(theReference); return myIdToResource.get(theReference);
} }

View File

@ -1,7 +1,7 @@
package ca.uhn.fhir.jpa.ips.generator; package ca.uhn.fhir.jpa.ips.generator;
import ca.uhn.fhir.batch2.jobs.models.BatchResourceId;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry; 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.provider.IpsOperationProvider; import ca.uhn.fhir.jpa.ips.provider.IpsOperationProvider;
@ -9,11 +9,16 @@ import ca.uhn.fhir.jpa.ips.strategy.DefaultIpsGenerationStrategy;
import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.provider.BaseResourceProviderR4Test; import ca.uhn.fhir.jpa.provider.BaseResourceProviderR4Test;
import ca.uhn.fhir.util.ClasspathUtil; import ca.uhn.fhir.util.ClasspathUtil;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.ValidationResult;
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;
import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Condition;
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.Resource;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -22,10 +27,13 @@ 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.util.List;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.matchesPattern; import static org.hamcrest.Matchers.matchesPattern;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ContextConfiguration(classes = {IpsGenerationTest.IpsConfig.class}) @ContextConfiguration(classes = {IpsGenerationTest.IpsConfig.class})
public class IpsGenerationTest extends BaseResourceProviderR4Test { public class IpsGenerationTest extends BaseResourceProviderR4Test {
@ -41,6 +49,7 @@ public class IpsGenerationTest extends BaseResourceProviderR4Test {
@AfterEach @AfterEach
public void afterEach() { public void afterEach() {
myServer.withServer(t -> t.unregisterProvider(myIpsOperationProvider)); myServer.withServer(t -> t.unregisterProvider(myIpsOperationProvider));
myDaoConfig.setResourceClientIdStrategy(DaoConfig.ClientIdStrategyEnum.ALPHANUMERIC);
} }
@ -65,26 +74,52 @@ public class IpsGenerationTest extends BaseResourceProviderR4Test {
ourLog.info("Output: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(output)); ourLog.info("Output: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(output));
// Verify // Verify
validateDocument(outcome);
assertEquals(37, output.getEntry().size()); assertEquals(117, output.getEntry().size());
String patientId = findFirstEntryResource(output, Patient.class).getId(); String patientId = findFirstEntryResource(output, Patient.class, 1).getId();
assertThat(patientId, matchesPattern("urn:uuid:.*")); assertThat(patientId, matchesPattern("urn:uuid:.*"));
MedicationStatement medicationStatement = findFirstEntryResource(output, MedicationStatement.class); MedicationStatement medicationStatement = findFirstEntryResource(output, MedicationStatement.class, 2);
assertEquals(patientId, medicationStatement.getSubject().getReference()); assertEquals(patientId, medicationStatement.getSubject().getReference());
assertNull(medicationStatement.getInformationSource().getReference()); assertNull(medicationStatement.getInformationSource().getReference());
} }
@SuppressWarnings("unchecked") @Test
private <T extends IBaseResource> T findFirstEntryResource(Bundle theBundle, Class<T> theType) { public void testGenerateTinyPatientSummary() {
return (T) theBundle myDaoConfig.setResourceClientIdStrategy(DaoConfig.ClientIdStrategyEnum.ANY);
.getEntry()
.stream() Bundle sourceData = ClasspathUtil.loadCompressedResource(myFhirContext, Bundle.class, "/tiny-patient-everything.json.gz");
.filter(t -> theType.isAssignableFrom(t.getResource().getClass())) sourceData.setType(Bundle.BundleType.TRANSACTION);
.findFirst() for (Bundle.BundleEntryComponent nextEntry : sourceData.getEntry()) {
.orElseThrow() nextEntry.getRequest().setMethod(Bundle.HTTPVerb.PUT);
.getResource(); nextEntry.getRequest().setUrl(nextEntry.getResource().getIdElement().toUnqualifiedVersionless().getValue());
}
Bundle outcome = mySystemDao.transaction(mySrd, sourceData);
ourLog.info("Created {} resources", outcome.getEntry().size());
Bundle output = myClient
.operation()
.onInstance("Patient/5342998")
.named(JpaConstants.OPERATION_SUMMARY)
.withNoParameters(Parameters.class)
.returnResourceType(Bundle.class)
.execute();
ourLog.info("Output: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(output));
// Verify
validateDocument(outcome);
assertEquals(7, output.getEntry().size());
String patientId = findFirstEntryResource(output, Patient.class, 1).getId();
assertThat(patientId, matchesPattern("urn:uuid:.*"));
assertEquals(patientId, findEntryResource(output, Condition.class, 0, 2).getSubject().getReference());
assertEquals(patientId, findEntryResource(output, Condition.class, 1, 2).getSubject().getReference());
} }
private void validateDocument(Bundle theOutcome) {
FhirValidator validator = myFhirContext.newValidator();
validator.registerValidatorModule(new FhirInstanceValidator(myFhirContext));
ValidationResult validation = validator.validateWithResult(theOutcome);
assertTrue(validation.isSuccessful(), () -> myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(validation.toOperationOutcome()));
}
@Configuration @Configuration
public static class IpsConfig { public static class IpsConfig {
@ -107,5 +142,21 @@ public class IpsGenerationTest extends BaseResourceProviderR4Test {
} }
@SuppressWarnings("unchecked")
private static <T extends IBaseResource> T findFirstEntryResource(Bundle theBundle, Class<T> theType, int theExpectedCount) {
return findEntryResource(theBundle, theType, 0, theExpectedCount);
}
@SuppressWarnings("unchecked")
static <T extends IBaseResource> T findEntryResource(Bundle theBundle, Class<T> theType, int index, int theExpectedCount) {
List<Resource> resources = theBundle
.getEntry()
.stream()
.map(Bundle.BundleEntryComponent::getResource)
.filter(r -> theType.isAssignableFrom(r.getClass()))
.toList();
assertEquals(theExpectedCount, resources.size());
return (T) resources.get(index);
}
} }

View File

@ -33,6 +33,7 @@ import org.hl7.fhir.r4.model.DateTimeType;
import org.hl7.fhir.r4.model.Device; import org.hl7.fhir.r4.model.Device;
import org.hl7.fhir.r4.model.DeviceUseStatement; import org.hl7.fhir.r4.model.DeviceUseStatement;
import org.hl7.fhir.r4.model.DiagnosticReport; import org.hl7.fhir.r4.model.DiagnosticReport;
import org.hl7.fhir.r4.model.Encounter;
import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Immunization; import org.hl7.fhir.r4.model.Immunization;
import org.hl7.fhir.r4.model.Medication; import org.hl7.fhir.r4.model.Medication;
@ -60,12 +61,15 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static ca.uhn.fhir.jpa.ips.generator.IpsGenerationTest.findEntryResource;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.startsWith; import static org.hamcrest.Matchers.startsWith;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
@ -74,6 +78,8 @@ public class IpsGeneratorSvcImplTest {
public static final String MEDICATION_ID = "Medication/tyl"; public static final String MEDICATION_ID = "Medication/tyl";
public static final String MEDICATION_STATEMENT_ID = "MedicationStatement/meds"; public static final String MEDICATION_STATEMENT_ID = "MedicationStatement/meds";
public static final String MEDICATION_STATEMENT_ID2 = "MedicationStatement/meds2"; public static final String MEDICATION_STATEMENT_ID2 = "MedicationStatement/meds2";
public static final String PATIENT_ID = "Patient/123";
public static final String ENCOUNTER_ID = "Encounter/encounter";
private static final List<Class<? extends IBaseResource>> RESOURCE_TYPES = Lists.newArrayList( private static final List<Class<? extends IBaseResource>> RESOURCE_TYPES = Lists.newArrayList(
AllergyIntolerance.class, AllergyIntolerance.class,
CarePlan.class, CarePlan.class,
@ -157,7 +163,7 @@ public class IpsGeneratorSvcImplTest {
registerRemainingResourceDaos(); registerRemainingResourceDaos();
// Test // Test
Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType("Patient/123")); Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType(PATIENT_ID));
// Verify Bundle Contents // Verify Bundle Contents
List<String> contentResourceTypes = toEntryResourceTypeStrings(outcome); List<String> contentResourceTypes = toEntryResourceTypeStrings(outcome);
@ -170,12 +176,7 @@ public class IpsGeneratorSvcImplTest {
// Verify // Verify
Composition compositions = (Composition) outcome.getEntry().get(0).getResource(); Composition compositions = (Composition) outcome.getEntry().get(0).getResource();
Composition.SectionComponent section = compositions Composition.SectionComponent section = findSection(compositions, IpsSectionEnum.MEDICATION_SUMMARY);
.getSection()
.stream()
.filter(t -> t.getTitle().equals(myStrategy.getSectionRegistry().getSection(IpsSectionEnum.MEDICATION_SUMMARY).getTitle()))
.findFirst()
.orElseThrow();
HtmlPage narrativeHtml = HtmlUtil.parseAsHtml(section.getText().getDivAsString()); HtmlPage narrativeHtml = HtmlUtil.parseAsHtml(section.getText().getDivAsString());
ourLog.info("Narrative:\n{}", narrativeHtml.asXml()); ourLog.info("Narrative:\n{}", narrativeHtml.asXml());
@ -191,6 +192,17 @@ public class IpsGeneratorSvcImplTest {
assertThat(row.getCell(4).asNormalizedText(), containsString("2023")); assertThat(row.getCell(4).asNormalizedText(), containsString("2023"));
} }
@Nonnull
private Composition.SectionComponent findSection(Composition compositions, IpsSectionEnum sectionEnum) {
Composition.SectionComponent section = compositions
.getSection()
.stream()
.filter(t -> t.getTitle().equals(myStrategy.getSectionRegistry().getSection(sectionEnum).getTitle()))
.findFirst()
.orElseThrow();
return section;
}
@Test @Test
public void testMedicationSummary_DuplicateSecondaryResources() { public void testMedicationSummary_DuplicateSecondaryResources() {
myStrategy.setSectionRegistry(new SectionRegistry().addGlobalCustomizer(t -> t.withNoInfoGenerator(null))); myStrategy.setSectionRegistry(new SectionRegistry().addGlobalCustomizer(t -> t.withNoInfoGenerator(null)));
@ -209,7 +221,7 @@ public class IpsGeneratorSvcImplTest {
registerRemainingResourceDaos(); registerRemainingResourceDaos();
// Test // Test
Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType("Patient/123")); Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType(PATIENT_ID));
// Verify Bundle Contents // Verify Bundle Contents
List<String> contentResourceTypes = toEntryResourceTypeStrings(outcome); List<String> contentResourceTypes = toEntryResourceTypeStrings(outcome);
@ -248,7 +260,7 @@ public class IpsGeneratorSvcImplTest {
registerRemainingResourceDaos(); registerRemainingResourceDaos();
// Test // Test
Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType("Patient/123")); Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType(PATIENT_ID));
// Verify Bundle Contents // Verify Bundle Contents
List<String> contentResourceTypes = toEntryResourceTypeStrings(outcome); List<String> contentResourceTypes = toEntryResourceTypeStrings(outcome);
@ -263,12 +275,7 @@ public class IpsGeneratorSvcImplTest {
// Verify narrative - should have 2 rows (one for each primary MedicationStatement) // Verify narrative - should have 2 rows (one for each primary MedicationStatement)
Composition compositions = (Composition) outcome.getEntry().get(0).getResource(); Composition compositions = (Composition) outcome.getEntry().get(0).getResource();
Composition.SectionComponent section = compositions Composition.SectionComponent section = findSection(compositions, IpsSectionEnum.MEDICATION_SUMMARY);
.getSection()
.stream()
.filter(t -> t.getTitle().equals(myStrategy.getSectionRegistry().getSection(IpsSectionEnum.MEDICATION_SUMMARY).getTitle()))
.findFirst()
.orElseThrow();
HtmlPage narrativeHtml = HtmlUtil.parseAsHtml(section.getText().getDivAsString()); HtmlPage narrativeHtml = HtmlUtil.parseAsHtml(section.getText().getDivAsString());
ourLog.info("Narrative:\n{}", narrativeHtml.asXml()); ourLog.info("Narrative:\n{}", narrativeHtml.asXml());
@ -304,16 +311,11 @@ public class IpsGeneratorSvcImplTest {
registerRemainingResourceDaos(); registerRemainingResourceDaos();
// Test // Test
Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType("Patient/123")); Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType(PATIENT_ID));
// Verify // Verify
Composition compositions = (Composition) outcome.getEntry().get(0).getResource(); Composition compositions = (Composition) outcome.getEntry().get(0).getResource();
Composition.SectionComponent section = compositions Composition.SectionComponent section = findSection(compositions, IpsSectionEnum.MEDICAL_DEVICES);
.getSection()
.stream()
.filter(t -> t.getTitle().equals(myStrategy.getSectionRegistry().getSection(IpsSectionEnum.MEDICAL_DEVICES).getTitle()))
.findFirst()
.orElseThrow();
HtmlPage narrativeHtml = HtmlUtil.parseAsHtml(section.getText().getDivAsString()); HtmlPage narrativeHtml = HtmlUtil.parseAsHtml(section.getText().getDivAsString());
ourLog.info("Narrative:\n{}", narrativeHtml.asXml()); ourLog.info("Narrative:\n{}", narrativeHtml.asXml());
@ -356,16 +358,11 @@ public class IpsGeneratorSvcImplTest {
registerRemainingResourceDaos(); registerRemainingResourceDaos();
// Test // Test
Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType("Patient/123")); Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType(PATIENT_ID));
// Verify // Verify
Composition compositions = (Composition) outcome.getEntry().get(0).getResource(); Composition compositions = (Composition) outcome.getEntry().get(0).getResource();
Composition.SectionComponent section = compositions Composition.SectionComponent section = findSection(compositions, IpsSectionEnum.IMMUNIZATIONS);
.getSection()
.stream()
.filter(t -> t.getTitle().equals(myStrategy.getSectionRegistry().getSection(IpsSectionEnum.IMMUNIZATIONS).getTitle()))
.findFirst()
.orElseThrow();
HtmlPage narrativeHtml = HtmlUtil.parseAsHtml(section.getText().getDivAsString()); HtmlPage narrativeHtml = HtmlUtil.parseAsHtml(section.getText().getDivAsString());
ourLog.info("Narrative:\n{}", narrativeHtml.asXml()); ourLog.info("Narrative:\n{}", narrativeHtml.asXml());
@ -383,10 +380,82 @@ public class IpsGeneratorSvcImplTest {
assertThat(row.getCell(6).asNormalizedText(), containsString("2023")); assertThat(row.getCell(6).asNormalizedText(), containsString("2023"));
} }
@Test
public void testReferencesUpdatedInSecondaryInclusions() {
// Setup Patient
registerPatientDaoWithRead();
// Setup Medication + MedicationStatement
Encounter encounter = new Encounter();
encounter.setId(new IdType(ENCOUNTER_ID));
encounter.setSubject(new Reference(PATIENT_ID));
ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.put(encounter, BundleEntrySearchModeEnum.INCLUDE);
Condition conditionActive = new Condition();
conditionActive.setId("Condition/conditionActive");
conditionActive.getClinicalStatus().addCoding()
.setSystem("http://terminology.hl7.org/CodeSystem/condition-clinical")
.setCode("active");
conditionActive.setSubject(new Reference(PATIENT_ID));
conditionActive.setEncounter(new Reference(ENCOUNTER_ID));
ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.put(conditionActive, BundleEntrySearchModeEnum.MATCH);
Condition conditionResolved = new Condition();
conditionResolved.setId("Condition/conditionResolved");
conditionResolved.getClinicalStatus().addCoding()
.setSystem("http://terminology.hl7.org/CodeSystem/condition-clinical")
.setCode("resolved");
conditionResolved.setSubject(new Reference(PATIENT_ID));
conditionResolved.setEncounter(new Reference(ENCOUNTER_ID));
ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.put(conditionResolved, BundleEntrySearchModeEnum.MATCH);
// Conditions will be loaded from two sections (problem list and illness history) so
// we return an active condition the first time and a resolved one the second
IFhirResourceDao<Condition> conditionDao = registerResourceDaoWithNoData(Condition.class);
when(conditionDao.search(any(), any())).thenReturn(
new SimpleBundleProvider(Lists.newArrayList(conditionActive, encounter)),
new SimpleBundleProvider(Lists.newArrayList(conditionResolved, encounter))
);
registerRemainingResourceDaos();
// Test
Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType(PATIENT_ID));
// Verify cross-references
Patient addedPatient = findEntryResource(outcome, Patient.class, 0, 1);
assertThat(addedPatient.getId(), startsWith("urn:uuid:"));
Condition addedCondition = findEntryResource(outcome, Condition.class, 0, 2);
assertThat(addedCondition.getId(), startsWith("urn:uuid:"));
Condition addedCondition2 = findEntryResource(outcome, Condition.class, 1, 2);
assertThat(addedCondition2.getId(), startsWith("urn:uuid:"));
Encounter addedEncounter = findEntryResource(outcome, Encounter.class, 0, 1);
assertThat(addedEncounter.getId(), startsWith("urn:uuid:"));
MedicationStatement addedMedicationStatement = findEntryResource(outcome, MedicationStatement.class, 0, 1);
assertThat(addedMedicationStatement.getId(), startsWith("urn:uuid:"));
assertEquals("no-medication-info", addedMedicationStatement.getMedicationCodeableConcept().getCodingFirstRep().getCode());
assertEquals(addedPatient.getId(), addedCondition.getSubject().getReference());
assertEquals(addedEncounter.getId(), addedCondition.getEncounter().getReference());
assertEquals(addedPatient.getId(), addedEncounter.getSubject().getReference());
assertEquals(addedPatient.getId(), addedMedicationStatement.getSubject().getReference());
// Verify sections
ourLog.info("Resource: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
verify(conditionDao, times(2)).search(any(), any());
Composition composition = (Composition) outcome.getEntry().get(0).getResource();
Composition.SectionComponent problemListSection = findSection(composition, IpsSectionEnum.PROBLEM_LIST);
assertEquals(addedCondition.getId(), problemListSection.getEntry().get(0).getReference());
assertEquals(1, problemListSection.getEntry().size());
Composition.SectionComponent illnessHistorySection = findSection(composition, IpsSectionEnum.ILLNESS_HISTORY);
assertEquals(addedCondition2.getId(), illnessHistorySection.getEntry().get(0).getReference());
assertEquals(1, illnessHistorySection.getEntry().size());
}
private void registerPatientDaoWithRead() { private void registerPatientDaoWithRead() {
IFhirResourceDao<Patient> patientDao = registerResourceDaoWithNoData(Patient.class); IFhirResourceDao<Patient> patientDao = registerResourceDaoWithNoData(Patient.class);
Patient patient = new Patient(); Patient patient = new Patient();
patient.setId("Patient/123"); patient.setId(PATIENT_ID);
when(patientDao.read(any(), any())).thenReturn(patient); when(patientDao.read(any(), any())).thenReturn(patient);
} }
@ -432,12 +501,11 @@ public class IpsGeneratorSvcImplTest {
@Nonnull @Nonnull
private static List<String> toEntryResourceTypeStrings(Bundle outcome) { private static List<String> toEntryResourceTypeStrings(Bundle outcome) {
List<String> contentResourceTypes = outcome return outcome
.getEntry() .getEntry()
.stream() .stream()
.map(t -> t.getResource().getResourceType().name()) .map(t -> t.getResource().getResourceType().name())
.collect(Collectors.toList()); .collect(Collectors.toList());
return contentResourceTypes;
} }
@Nonnull @Nonnull

View File

@ -1,5 +1,5 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <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"> 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> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
@ -48,11 +48,16 @@
<version>${project.version}</version> <version>${project.version}</version>
<classifier>classes</classifier> <classifier>classes</classifier>
</dependency> </dependency>
<dependency> <dependency>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-server-openapi</artifactId> <artifactId>hapi-fhir-server-openapi</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-jpaserver-ips</artifactId>
<version>${project.version}</version>
</dependency>
<dependency> <dependency>
<groupId>com.helger</groupId> <groupId>com.helger</groupId>

View File

@ -12,6 +12,7 @@ import ca.uhn.fhir.jpa.bulk.export.provider.BulkDataExportProvider;
import ca.uhn.fhir.jpa.delete.ThreadSafeResourceDeleterSvc; import ca.uhn.fhir.jpa.delete.ThreadSafeResourceDeleterSvc;
import ca.uhn.fhir.jpa.graphql.GraphQLProvider; import ca.uhn.fhir.jpa.graphql.GraphQLProvider;
import ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor; import ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor;
import ca.uhn.fhir.jpa.ips.provider.IpsOperationProvider;
import ca.uhn.fhir.jpa.provider.DiffProvider; import ca.uhn.fhir.jpa.provider.DiffProvider;
import ca.uhn.fhir.jpa.provider.JpaCapabilityStatementProvider; import ca.uhn.fhir.jpa.provider.JpaCapabilityStatementProvider;
import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu2; import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu2;
@ -152,6 +153,7 @@ public class TestRestfulServer extends RestfulServer {
setServerConformanceProvider(confProvider); setServerConformanceProvider(confProvider);
providers.add(myAppCtx.getBean(TerminologyUploaderProvider.class)); providers.add(myAppCtx.getBean(TerminologyUploaderProvider.class));
providers.add(myAppCtx.getBean(GraphQLProvider.class)); providers.add(myAppCtx.getBean(GraphQLProvider.class));
providers.add(myAppCtx.getBean(IpsOperationProvider.class));
break; break;
} }
case "R4B": { case "R4B": {

View File

@ -2,9 +2,15 @@ package ca.uhn.fhirtest.config;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.config.HapiJpaConfig; import ca.uhn.fhir.jpa.config.HapiJpaConfig;
import ca.uhn.fhir.jpa.config.r4.JpaR4Config; import ca.uhn.fhir.jpa.config.r4.JpaR4Config;
import ca.uhn.fhir.jpa.config.util.HapiEntityManagerFactoryUtil; import ca.uhn.fhir.jpa.config.util.HapiEntityManagerFactoryUtil;
import ca.uhn.fhir.jpa.ips.api.IIpsGenerationStrategy;
import ca.uhn.fhir.jpa.ips.generator.IIpsGeneratorSvc;
import ca.uhn.fhir.jpa.ips.generator.IpsGeneratorSvcImpl;
import ca.uhn.fhir.jpa.ips.provider.IpsOperationProvider;
import ca.uhn.fhir.jpa.ips.strategy.DefaultIpsGenerationStrategy;
import ca.uhn.fhir.jpa.model.dialect.HapiFhirH2Dialect; import ca.uhn.fhir.jpa.model.dialect.HapiFhirH2Dialect;
import ca.uhn.fhir.jpa.model.dialect.HapiFhirPostgres94Dialect; import ca.uhn.fhir.jpa.model.dialect.HapiFhirPostgres94Dialect;
import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.model.entity.ModelConfig;
@ -196,5 +202,19 @@ public class TestR4Config {
return new PropertySourcesPlaceholderConfigurer(); return new PropertySourcesPlaceholderConfigurer();
} }
@Bean
public IIpsGenerationStrategy ipsGenerationStrategy() {
return new DefaultIpsGenerationStrategy();
}
@Bean
public IIpsGeneratorSvc ipsGeneratorSvc(FhirContext theFhirContext, IIpsGenerationStrategy theGenerationStrategy, DaoRegistry theDaoRegistry) {
return new IpsGeneratorSvcImpl(theFhirContext, theGenerationStrategy, theDaoRegistry);
}
@Bean
public IpsOperationProvider ipsOperationProvider(IIpsGeneratorSvc theIpsGeneratorSvc) {
return new IpsOperationProvider(theIpsGeneratorSvc);
}
} }