getSections();
+
+ /**
+ * Returns the resource supplier for the given section. The resource supplier
+ * is used to supply the resources which will be used for a given
+ * section.
+ *
+ * @param theSection The section
+ */
+ @Nonnull
+ ISectionResourceSupplier getSectionResourceSupplier(@Nonnull Section theSection);
/**
* Provides a list of configuration property files for the IPS narrative generator.
@@ -53,7 +78,7 @@ public interface IIpsGenerationStrategy {
*
* If more than one file is provided, the files will be evaluated in order. Therefore you
* might choose to include a custom file, followed by
- * {@link ca.uhn.fhir.jpa.ips.strategy.DefaultIpsGenerationStrategy#DEFAULT_IPS_NARRATIVES_PROPERTIES}
+ * {@link BaseIpsGenerationStrategy#DEFAULT_IPS_NARRATIVES_PROPERTIES}
* in order to fall back to the default templates for any sections you have not
* provided an explicit template for.
*
@@ -85,7 +110,13 @@ public interface IIpsGenerationStrategy {
/**
* This method is used to determine the resource ID to assign to a resource that
* will be added to the IPS document Bundle. Implementations will probably either
- * return the resource ID as-is, or generate a placeholder UUID to replace it with.
+ * return null
to leave the resource ID as-is, or generate a
+ * placeholder UUID to replace it with.
+ *
+ * If you want to replace the native resource ID with a placeholder so as not
+ * to leak the server-generated IDs, the recommended way is to
+ * return IdType.newRandomUuid()
+ *
*
* @param theIpsContext The associated context for the specific IPS document being
* generated. Note that this will be null
when
@@ -93,43 +124,33 @@ public interface IIpsGenerationStrategy {
* be populated for all subsequent calls for a given IPS
* document generation.
* @param theResource The resource to massage the resource ID for
- * @return An ID to assign to the resource
+ * @return An ID to assign to the resource, or null
to leave the existing ID intact,
+ * meaning that the server-assigned IDs will be used in the bundle.
*/
+ @Nullable
IIdType massageResourceId(@Nullable IpsContext theIpsContext, @Nonnull IBaseResource theResource);
/**
- * This method can manipulate the {@link SearchParameterMap} that will
- * be used to find candidate resources for the given IPS section. The map will already have
- * a subject/patient parameter added to it. The map provided in {@literal theSearchParameterMap}
- * will contain a subject/patient reference, but no other parameters. This method can add other
- * parameters.
- *
- * For example, for a Vital Signs section, the implementation might add a parameter indicating
- * the parameter category=vital-signs
.
+ * Fetches and returns the patient to include in the generated IPS for the given patient ID.
*
- * @param theIpsSectionContext The context, which indicates the IPS section and the resource type
- * being searched for.
- * @param theSearchParameterMap The map to manipulate.
- */
- void massageResourceSearch(
- IpsContext.IpsSectionContext theIpsSectionContext, SearchParameterMap theSearchParameterMap);
-
- /**
- * Return a set of Include directives to be added to the resource search
- * for resources to include for a given IPS section. These include statements will
- * be added to the same {@link SearchParameterMap} provided to
- * {@link #massageResourceSearch(IpsContext.IpsSectionContext, SearchParameterMap)}.
- * This is a separate method in order to make subclassing easier.
- *
- * @param theIpsSectionContext The context, which indicates the IPS section and the resource type
- * being searched for.
+ * @throws ResourceNotFoundException If the ID is not known.
*/
@Nonnull
- Set provideResourceSearchIncludes(IpsContext.IpsSectionContext theIpsSectionContext);
+ IBaseResource fetchPatient(IIdType thePatientId, RequestDetails theRequestDetails) throws ResourceNotFoundException;
/**
- * This method will be called for each found resource candidate for inclusion in the
- * IPS document. The strategy can decide whether to include it or not.
+ * Fetches and returns the patient to include in the generated IPS for the given patient identifier.
+ *
+ * @throws ResourceNotFoundException If the ID is not known.
*/
- boolean shouldInclude(IpsContext.IpsSectionContext theIpsSectionContext, IBaseResource theCandidate);
+ @Nonnull
+ IBaseResource fetchPatient(TokenParam thePatientIdentifier, RequestDetails theRequestDetails);
+
+ /**
+ * This method is called once for each generated IPS document, after all other processing is complete. It can
+ * be used by the strategy to make direct manipulations prior to returning the document.
+ */
+ default void postManipulateIpsBundle(IBaseBundle theBundle) {
+ // nothing
+ }
}
diff --git a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/api/INoInfoGenerator.java b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/api/INoInfoGenerator.java
new file mode 100644
index 00000000000..cbb1c18375d
--- /dev/null
+++ b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/api/INoInfoGenerator.java
@@ -0,0 +1,39 @@
+/*-
+ * #%L
+ * HAPI FHIR JPA Server - International Patient Summary (IPS)
+ * %%
+ * Copyright (C) 2014 - 2024 Smile CDR, Inc.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package ca.uhn.fhir.jpa.ips.api;
+
+import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.hl7.fhir.instance.model.api.IIdType;
+
+/**
+ * This interface is invoked when a section has no resources found, and should generate
+ * a "stub" resource explaining why. Typically this would be content such as "no information
+ * is available for this section", and might indicate for example that the absence of
+ * AllergyIntolerance resources only indicates that the allergy status is not known, not that
+ * the patient has no allergies.
+ */
+public interface INoInfoGenerator {
+
+ /**
+ * Generate an appropriate no-info resource. The resource does not need to have an ID populated,
+ * although it can if it is a resource found in the repository.
+ */
+ IBaseResource generate(IIdType theSubjectId);
+}
diff --git a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/api/ISectionResourceSupplier.java b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/api/ISectionResourceSupplier.java
new file mode 100644
index 00000000000..63543eb749d
--- /dev/null
+++ b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/api/ISectionResourceSupplier.java
@@ -0,0 +1,125 @@
+/*-
+ * #%L
+ * HAPI FHIR JPA Server - International Patient Summary (IPS)
+ * %%
+ * Copyright (C) 2014 - 2024 Smile CDR, Inc.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package ca.uhn.fhir.jpa.ips.api;
+
+import ca.uhn.fhir.rest.api.server.RequestDetails;
+import jakarta.annotation.Nonnull;
+import jakarta.annotation.Nullable;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.thymeleaf.util.Validate;
+
+import java.util.List;
+
+/**
+ * This interface is invoked for each section of the IPS, and fetches/returns the
+ * resources which will be included in the IPS document for that section. This
+ * might be by performing a search in a local repository, but could also be
+ * done by calling a remote repository, performing a calculation, making
+ * JDBC database calls directly, etc.
+ *
+ * Note that you only need to implement this interface directly if you want to
+ * provide manual logic for gathering and preparing resources to include in
+ * an IPS document. If your resources can be collected by querying a JPS
+ * repository, you can use {@link ca.uhn.fhir.jpa.ips.jpa.JpaSectionResourceSupplier}
+ * as the implementation of this interface, and
+ * {@link ca.uhn.fhir.jpa.ips.jpa.IJpaSectionSearchStrategy} becomes the class
+ * that is used to define your searches.
+ *
+ *
+ * @since 7.2.0
+ * @see ca.uhn.fhir.jpa.ips.jpa.JpaSectionResourceSupplier
+ */
+public interface ISectionResourceSupplier {
+
+ /**
+ * This method will be called once for each section context (section and resource type combination),
+ * and will be used to supply the resources to include in the given IPS section. This method can
+ * be used if you wish to fetch resources for a given section from a source other than
+ * the repository. This could mean fetching resources using a FHIR REST client to an
+ * external server, or could even mean fetching data directly from a database using JDBC
+ * or similar.
+ *
+ * @param theIpsContext The IPS context, containing the identity of the patient whose IPS is being generated.
+ * @param theSectionContext The section context, containing the section name and resource type.
+ * @param theRequestDetails The RequestDetails object associated with the HTTP request associated with this generation.
+ * @return Returns a list of resources to add to the given section, or null
.
+ */
+ @Nullable
+ List fetchResourcesForSection(
+ IpsContext theIpsContext, IpsSectionContext theSectionContext, RequestDetails theRequestDetails);
+
+ /**
+ * This enum specifies how an individual {@link ResourceEntry resource entry} that
+ * is returned by {@link #fetchResourcesForSection(IpsContext, IpsSectionContext, RequestDetails)}
+ * should be included in the resulting IPS document bundle.
+ */
+ enum InclusionTypeEnum {
+
+ /**
+ * The resource should be included in the document bundle and linked to
+ * from the Composition via the Composition.section.entry
+ * reference.
+ */
+ PRIMARY_RESOURCE,
+
+ /**
+ * The resource should be included in the document bundle, but not directly
+ * linked from the composition. This typically means that it is referenced
+ * by at least one primary resource.
+ */
+ SECONDARY_RESOURCE,
+
+ /**
+ * Do not include this resource in the document
+ */
+ EXCLUDE
+ }
+
+ /**
+ * This class is the return type for {@link #fetchResourcesForSection(IpsContext, IpsSectionContext, RequestDetails)}.
+ */
+ class ResourceEntry {
+
+ private final IBaseResource myResource;
+
+ private final InclusionTypeEnum myInclusionType;
+
+ /**
+ * Constructor
+ *
+ * @param theResource The resource to include (must not be null)
+ * @param theInclusionType The inclusion type (must not be null)
+ */
+ public ResourceEntry(@Nonnull IBaseResource theResource, @Nonnull InclusionTypeEnum theInclusionType) {
+ Validate.notNull(theResource, "theResource must not be null");
+ Validate.notNull(theInclusionType, "theInclusionType must not be null");
+ myResource = theResource;
+ myInclusionType = theInclusionType;
+ }
+
+ public IBaseResource getResource() {
+ return myResource;
+ }
+
+ public InclusionTypeEnum getInclusionType() {
+ return myInclusionType;
+ }
+ }
+}
diff --git a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/api/IpsContext.java b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/api/IpsContext.java
index 5424d8791cc..f57d8c381ec 100644
--- a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/api/IpsContext.java
+++ b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/api/IpsContext.java
@@ -58,28 +58,8 @@ public class IpsContext {
return mySubjectId;
}
- public IpsSectionContext newSectionContext(IpsSectionEnum theSection, String theResourceType) {
- return new IpsSectionContext(mySubject, mySubjectId, theSection, theResourceType);
- }
-
- public static class IpsSectionContext extends IpsContext {
-
- private final IpsSectionEnum mySection;
- private final String myResourceType;
-
- private IpsSectionContext(
- IBaseResource theSubject, IIdType theSubjectId, IpsSectionEnum theSection, String theResourceType) {
- super(theSubject, theSubjectId);
- mySection = theSection;
- myResourceType = theResourceType;
- }
-
- public String getResourceType() {
- return myResourceType;
- }
-
- public IpsSectionEnum getSection() {
- return mySection;
- }
+ public IpsSectionContext newSectionContext(
+ Section theSection, Class theResourceType) {
+ return new IpsSectionContext<>(mySubject, mySubjectId, theSection, theResourceType);
}
}
diff --git a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/api/IpsSectionContext.java b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/api/IpsSectionContext.java
new file mode 100644
index 00000000000..669c8c5d389
--- /dev/null
+++ b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/api/IpsSectionContext.java
@@ -0,0 +1,43 @@
+/*-
+ * #%L
+ * HAPI FHIR JPA Server - International Patient Summary (IPS)
+ * %%
+ * Copyright (C) 2014 - 2024 Smile CDR, Inc.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package ca.uhn.fhir.jpa.ips.api;
+
+import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.hl7.fhir.instance.model.api.IIdType;
+
+public class IpsSectionContext extends IpsContext {
+
+ private final Section mySection;
+ private final Class myResourceType;
+
+ IpsSectionContext(IBaseResource theSubject, IIdType theSubjectId, Section theSection, Class theResourceType) {
+ super(theSubject, theSubjectId);
+ mySection = theSection;
+ myResourceType = theResourceType;
+ }
+
+ public Class getResourceType() {
+ return myResourceType;
+ }
+
+ public Section getSection() {
+ return mySection;
+ }
+}
diff --git a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/api/Section.java b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/api/Section.java
new file mode 100644
index 00000000000..7dfb47fcfc2
--- /dev/null
+++ b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/api/Section.java
@@ -0,0 +1,223 @@
+/*-
+ * #%L
+ * HAPI FHIR JPA Server - International Patient Summary (IPS)
+ * %%
+ * Copyright (C) 2014 - 2024 Smile CDR, Inc.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package ca.uhn.fhir.jpa.ips.api;
+
+import jakarta.annotation.Nullable;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.Validate;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Call {@link #newBuilder()} to create a new instance of this class.
+ */
+public class Section {
+
+ private final String myTitle;
+ private final String mySectionCode;
+ private final String mySectionDisplay;
+ private final List> myResourceTypes;
+ private final String myProfile;
+ private final INoInfoGenerator myNoInfoGenerator;
+
+ private final String mySectionSystem;
+
+ private Section(
+ String theTitle,
+ String theSectionSystem,
+ String theSectionCode,
+ String theSectionDisplay,
+ List> theResourceTypes,
+ String theProfile,
+ INoInfoGenerator theNoInfoGenerator) {
+ myTitle = theTitle;
+ mySectionSystem = theSectionSystem;
+ mySectionCode = theSectionCode;
+ mySectionDisplay = theSectionDisplay;
+ myResourceTypes = List.copyOf(theResourceTypes);
+ myProfile = theProfile;
+ myNoInfoGenerator = theNoInfoGenerator;
+ }
+
+ @Nullable
+ public INoInfoGenerator getNoInfoGenerator() {
+ return myNoInfoGenerator;
+ }
+
+ public List> getResourceTypes() {
+ return myResourceTypes;
+ }
+
+ public String getProfile() {
+ return myProfile;
+ }
+
+ public String getTitle() {
+ return myTitle;
+ }
+
+ public String getSectionSystem() {
+ return mySectionSystem;
+ }
+
+ public String getSectionCode() {
+ return mySectionCode;
+ }
+
+ public String getSectionDisplay() {
+ return mySectionDisplay;
+ }
+
+ @Override
+ public boolean equals(Object theO) {
+ if (theO instanceof Section) {
+ Section o = (Section) theO;
+ return StringUtils.equals(myProfile, o.myProfile);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return myProfile.hashCode();
+ }
+
+ /**
+ * Create a new empty section builder
+ */
+ public static SectionBuilder newBuilder() {
+ return new SectionBuilder();
+ }
+
+ /**
+ * Create a new section builder which is a clone of an existing section
+ */
+ public static SectionBuilder newBuilder(Section theSection) {
+ return new SectionBuilder(
+ theSection.myTitle,
+ theSection.mySectionSystem,
+ theSection.mySectionCode,
+ theSection.mySectionDisplay,
+ theSection.myProfile,
+ theSection.myNoInfoGenerator,
+ theSection.myResourceTypes);
+ }
+
+ public static class SectionBuilder {
+
+ private String myTitle;
+ private String mySectionSystem;
+ private String mySectionCode;
+ private String mySectionDisplay;
+ private List> myResourceTypes = new ArrayList<>();
+ private String myProfile;
+ private INoInfoGenerator myNoInfoGenerator;
+
+ private SectionBuilder() {
+ super();
+ }
+
+ public SectionBuilder(
+ String theTitle,
+ String theSectionSystem,
+ String theSectionCode,
+ String theSectionDisplay,
+ String theProfile,
+ INoInfoGenerator theNoInfoGenerator,
+ List> theResourceTypes) {
+ myTitle = theTitle;
+ mySectionSystem = theSectionSystem;
+ mySectionCode = theSectionCode;
+ mySectionDisplay = theSectionDisplay;
+ myNoInfoGenerator = theNoInfoGenerator;
+ myProfile = theProfile;
+ myResourceTypes = new ArrayList<>(theResourceTypes);
+ }
+
+ public SectionBuilder withTitle(String theTitle) {
+ Validate.notBlank(theTitle);
+ myTitle = theTitle;
+ return this;
+ }
+
+ public SectionBuilder withSectionSystem(String theSectionSystem) {
+ Validate.notBlank(theSectionSystem);
+ mySectionSystem = theSectionSystem;
+ return this;
+ }
+
+ public SectionBuilder withSectionCode(String theSectionCode) {
+ Validate.notBlank(theSectionCode);
+ mySectionCode = theSectionCode;
+ return this;
+ }
+
+ public SectionBuilder withSectionDisplay(String theSectionDisplay) {
+ Validate.notBlank(theSectionDisplay);
+ mySectionDisplay = theSectionDisplay;
+ return this;
+ }
+
+ /**
+ * This method may be called multiple times if the section will contain multiple resource types
+ */
+ public SectionBuilder withResourceType(Class extends IBaseResource> theResourceType) {
+ Validate.notNull(theResourceType, "theResourceType must not be null");
+ Validate.isTrue(!myResourceTypes.contains(theResourceType), "theResourceType has already been added");
+ myResourceTypes.add(theResourceType);
+ return this;
+ }
+
+ public SectionBuilder withProfile(String theProfile) {
+ Validate.notBlank(theProfile);
+ myProfile = theProfile;
+ return this;
+ }
+
+ /**
+ * Supplies a {@link INoInfoGenerator} which is used to create a stub resource
+ * to place in this section if no actual contents are found. This can be
+ * {@literal null} if you do not want any such stub to be included for this
+ * section.
+ */
+ @SuppressWarnings("UnusedReturnValue")
+ public SectionBuilder withNoInfoGenerator(@Nullable INoInfoGenerator theNoInfoGenerator) {
+ myNoInfoGenerator = theNoInfoGenerator;
+ return this;
+ }
+
+ public Section build() {
+ Validate.notBlank(mySectionSystem, "No section system has been defined for this section");
+ Validate.notBlank(mySectionCode, "No section code has been defined for this section");
+ Validate.notBlank(mySectionDisplay, "No section display has been defined for this section");
+
+ return new Section(
+ myTitle,
+ mySectionSystem,
+ mySectionCode,
+ mySectionDisplay,
+ myResourceTypes,
+ myProfile,
+ myNoInfoGenerator);
+ }
+ }
+}
diff --git a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/api/SectionRegistry.java b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/api/SectionRegistry.java
deleted file mode 100644
index e7940638d78..00000000000
--- a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/api/SectionRegistry.java
+++ /dev/null
@@ -1,470 +0,0 @@
-/*-
- * #%L
- * HAPI FHIR JPA Server - International Patient Summary (IPS)
- * %%
- * Copyright (C) 2014 - 2024 Smile CDR, Inc.
- * %%
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * #L%
- */
-package ca.uhn.fhir.jpa.ips.api;
-
-import jakarta.annotation.Nullable;
-import jakarta.annotation.PostConstruct;
-import org.apache.commons.lang3.Validate;
-import org.hl7.fhir.instance.model.api.IBaseResource;
-import org.hl7.fhir.instance.model.api.IIdType;
-import org.hl7.fhir.r4.model.AllergyIntolerance;
-import org.hl7.fhir.r4.model.CodeableConcept;
-import org.hl7.fhir.r4.model.Coding;
-import org.hl7.fhir.r4.model.Condition;
-import org.hl7.fhir.r4.model.MedicationStatement;
-import org.hl7.fhir.r4.model.Reference;
-import org.hl7.fhir.r4.model.ResourceType;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.function.Consumer;
-
-/**
- * This class is the registry for sections for the IPS document. It can be extended
- * and customized if you wish to add / remove / change sections.
- *
- * By default, all standard sections in the
- * base IPS specification IG
- * are included. You can customize this to remove sections, or to add new ones
- * as permitted by the IG.
- *
- *
- * To customize the sections, you may override the {@link #addSections()} method
- * in order to add new sections or remove them. You may also override individual
- * section methods such as {@link #addSectionAllergyIntolerance()} or
- * {@link #addSectionAdvanceDirectives()}.
- *
- */
-public class SectionRegistry {
-
- private final ArrayList mySections = new ArrayList<>();
- private List> myGlobalCustomizers = new ArrayList<>();
-
- /**
- * Constructor
- */
- public SectionRegistry() {
- super();
- }
-
- /**
- * This method should be automatically called by the Spring context. It initializes
- * the registry.
- */
- @PostConstruct
- public final void initialize() {
- Validate.isTrue(mySections.isEmpty(), "Sections are already initialized");
- addSections();
- }
-
- public boolean isInitialized() {
- return !mySections.isEmpty();
- }
-
- /**
- * Add the various sections to the registry in order. This method can be overridden for
- * customization.
- */
- protected void addSections() {
- addSectionAllergyIntolerance();
- addSectionMedicationSummary();
- addSectionProblemList();
- addSectionImmunizations();
- addSectionProcedures();
- addSectionMedicalDevices();
- addSectionDiagnosticResults();
- addSectionVitalSigns();
- addSectionPregnancy();
- addSectionSocialHistory();
- addSectionIllnessHistory();
- addSectionFunctionalStatus();
- addSectionPlanOfCare();
- addSectionAdvanceDirectives();
- }
-
- protected void addSectionAllergyIntolerance() {
- addSection(IpsSectionEnum.ALLERGY_INTOLERANCE)
- .withTitle("Allergies and Intolerances")
- .withSectionCode("48765-2")
- .withSectionDisplay("Allergies and adverse reactions Document")
- .withResourceTypes(ResourceType.AllergyIntolerance.name())
- .withProfile(
- "https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionAllergies")
- .withNoInfoGenerator(new AllergyIntoleranceNoInfoR4Generator())
- .build();
- }
-
- protected void addSectionMedicationSummary() {
- addSection(IpsSectionEnum.MEDICATION_SUMMARY)
- .withTitle("Medication List")
- .withSectionCode("10160-0")
- .withSectionDisplay("History of Medication use Narrative")
- .withResourceTypes(
- ResourceType.MedicationStatement.name(),
- ResourceType.MedicationRequest.name(),
- ResourceType.MedicationAdministration.name(),
- ResourceType.MedicationDispense.name())
- .withProfile(
- "https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionMedications")
- .withNoInfoGenerator(new MedicationNoInfoR4Generator())
- .build();
- }
-
- protected void addSectionProblemList() {
- addSection(IpsSectionEnum.PROBLEM_LIST)
- .withTitle("Problem List")
- .withSectionCode("11450-4")
- .withSectionDisplay("Problem list - Reported")
- .withResourceTypes(ResourceType.Condition.name())
- .withProfile(
- "https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionProblems")
- .withNoInfoGenerator(new ProblemNoInfoR4Generator())
- .build();
- }
-
- protected void addSectionImmunizations() {
- addSection(IpsSectionEnum.IMMUNIZATIONS)
- .withTitle("History of Immunizations")
- .withSectionCode("11369-6")
- .withSectionDisplay("History of Immunization Narrative")
- .withResourceTypes(ResourceType.Immunization.name())
- .withProfile(
- "https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionImmunizations")
- .build();
- }
-
- protected void addSectionProcedures() {
- addSection(IpsSectionEnum.PROCEDURES)
- .withTitle("History of Procedures")
- .withSectionCode("47519-4")
- .withSectionDisplay("History of Procedures Document")
- .withResourceTypes(ResourceType.Procedure.name())
- .withProfile(
- "https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionProceduresHx")
- .build();
- }
-
- protected void addSectionMedicalDevices() {
- addSection(IpsSectionEnum.MEDICAL_DEVICES)
- .withTitle("Medical Devices")
- .withSectionCode("46264-8")
- .withSectionDisplay("History of medical device use")
- .withResourceTypes(ResourceType.DeviceUseStatement.name())
- .withProfile(
- "https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionMedicalDevices")
- .build();
- }
-
- protected void addSectionDiagnosticResults() {
- addSection(IpsSectionEnum.DIAGNOSTIC_RESULTS)
- .withTitle("Diagnostic Results")
- .withSectionCode("30954-2")
- .withSectionDisplay("Relevant diagnostic tests/laboratory data Narrative")
- .withResourceTypes(ResourceType.DiagnosticReport.name(), ResourceType.Observation.name())
- .withProfile(
- "https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionResults")
- .build();
- }
-
- protected void addSectionVitalSigns() {
- addSection(IpsSectionEnum.VITAL_SIGNS)
- .withTitle("Vital Signs")
- .withSectionCode("8716-3")
- .withSectionDisplay("Vital signs")
- .withResourceTypes(ResourceType.Observation.name())
- .withProfile(
- "https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionVitalSigns")
- .build();
- }
-
- protected void addSectionPregnancy() {
- addSection(IpsSectionEnum.PREGNANCY)
- .withTitle("Pregnancy Information")
- .withSectionCode("10162-6")
- .withSectionDisplay("History of pregnancies Narrative")
- .withResourceTypes(ResourceType.Observation.name())
- .withProfile(
- "https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionPregnancyHx")
- .build();
- }
-
- protected void addSectionSocialHistory() {
- addSection(IpsSectionEnum.SOCIAL_HISTORY)
- .withTitle("Social History")
- .withSectionCode("29762-2")
- .withSectionDisplay("Social history Narrative")
- .withResourceTypes(ResourceType.Observation.name())
- .withProfile(
- "https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionSocialHistory")
- .build();
- }
-
- protected void addSectionIllnessHistory() {
- addSection(IpsSectionEnum.ILLNESS_HISTORY)
- .withTitle("History of Past Illness")
- .withSectionCode("11348-0")
- .withSectionDisplay("History of Past illness Narrative")
- .withResourceTypes(ResourceType.Condition.name())
- .withProfile(
- "https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionPastIllnessHx")
- .build();
- }
-
- protected void addSectionFunctionalStatus() {
- addSection(IpsSectionEnum.FUNCTIONAL_STATUS)
- .withTitle("Functional Status")
- .withSectionCode("47420-5")
- .withSectionDisplay("Functional status assessment note")
- .withResourceTypes(ResourceType.ClinicalImpression.name())
- .withProfile(
- "https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionFunctionalStatus")
- .build();
- }
-
- protected void addSectionPlanOfCare() {
- addSection(IpsSectionEnum.PLAN_OF_CARE)
- .withTitle("Plan of Care")
- .withSectionCode("18776-5")
- .withSectionDisplay("Plan of care note")
- .withResourceTypes(ResourceType.CarePlan.name())
- .withProfile(
- "https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionPlanOfCare")
- .build();
- }
-
- protected void addSectionAdvanceDirectives() {
- addSection(IpsSectionEnum.ADVANCE_DIRECTIVES)
- .withTitle("Advance Directives")
- .withSectionCode("42348-3")
- .withSectionDisplay("Advance directives")
- .withResourceTypes(ResourceType.Consent.name())
- .withProfile(
- "https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionAdvanceDirectives")
- .build();
- }
-
- private SectionBuilder addSection(IpsSectionEnum theSectionEnum) {
- return new SectionBuilder(theSectionEnum);
- }
-
- public SectionRegistry addGlobalCustomizer(Consumer theGlobalCustomizer) {
- Validate.notNull(theGlobalCustomizer, "theGlobalCustomizer must not be null");
- myGlobalCustomizers.add(theGlobalCustomizer);
- return this;
- }
-
- public List getSections() {
- Validate.isTrue(isInitialized(), "Section registry has not been initialized");
- return Collections.unmodifiableList(mySections);
- }
-
- public Section getSection(IpsSectionEnum theSectionEnum) {
- return getSections().stream()
- .filter(t -> t.getSectionEnum() == theSectionEnum)
- .findFirst()
- .orElseThrow(() -> new IllegalArgumentException("No section for type: " + theSectionEnum));
- }
-
- public interface INoInfoGenerator {
-
- /**
- * Generate an appropriate no-info resource. The resource does not need to have an ID populated,
- * although it can if it is a resource found in the repository.
- */
- IBaseResource generate(IIdType theSubjectId);
- }
-
- public class SectionBuilder {
-
- private final IpsSectionEnum mySectionEnum;
- private String myTitle;
- private String mySectionCode;
- private String mySectionDisplay;
- private List myResourceTypes;
- private String myProfile;
- private INoInfoGenerator myNoInfoGenerator;
-
- public SectionBuilder(IpsSectionEnum theSectionEnum) {
- mySectionEnum = theSectionEnum;
- }
-
- public SectionBuilder withTitle(String theTitle) {
- Validate.notBlank(theTitle);
- myTitle = theTitle;
- return this;
- }
-
- public SectionBuilder withSectionCode(String theSectionCode) {
- Validate.notBlank(theSectionCode);
- mySectionCode = theSectionCode;
- return this;
- }
-
- public SectionBuilder withSectionDisplay(String theSectionDisplay) {
- Validate.notBlank(theSectionDisplay);
- mySectionDisplay = theSectionDisplay;
- return this;
- }
-
- public SectionBuilder withResourceTypes(String... theResourceTypes) {
- Validate.isTrue(theResourceTypes.length > 0);
- myResourceTypes = Arrays.asList(theResourceTypes);
- return this;
- }
-
- public SectionBuilder withProfile(String theProfile) {
- Validate.notBlank(theProfile);
- myProfile = theProfile;
- return this;
- }
-
- public SectionBuilder withNoInfoGenerator(INoInfoGenerator theNoInfoGenerator) {
- myNoInfoGenerator = theNoInfoGenerator;
- return this;
- }
-
- public void build() {
- myGlobalCustomizers.forEach(t -> t.accept(this));
- mySections.add(new Section(
- mySectionEnum,
- myTitle,
- mySectionCode,
- mySectionDisplay,
- myResourceTypes,
- myProfile,
- myNoInfoGenerator));
- }
- }
-
- private static class AllergyIntoleranceNoInfoR4Generator implements INoInfoGenerator {
- @Override
- public IBaseResource generate(IIdType theSubjectId) {
- AllergyIntolerance allergy = new AllergyIntolerance();
- allergy.setCode(new CodeableConcept()
- .addCoding(new Coding()
- .setCode("no-allergy-info")
- .setSystem("http://hl7.org/fhir/uv/ips/CodeSystem/absent-unknown-uv-ips")
- .setDisplay("No information about allergies")))
- .setPatient(new Reference(theSubjectId))
- .setClinicalStatus(new CodeableConcept()
- .addCoding(new Coding()
- .setCode("active")
- .setSystem("http://terminology.hl7.org/CodeSystem/allergyintolerance-clinical")));
- return allergy;
- }
- }
-
- private static class MedicationNoInfoR4Generator implements INoInfoGenerator {
- @Override
- public IBaseResource generate(IIdType theSubjectId) {
- MedicationStatement medication = new MedicationStatement();
- // setMedicationCodeableConcept is not available
- medication
- .setMedication(new CodeableConcept()
- .addCoding(new Coding()
- .setCode("no-medication-info")
- .setSystem("http://hl7.org/fhir/uv/ips/CodeSystem/absent-unknown-uv-ips")
- .setDisplay("No information about medications")))
- .setSubject(new Reference(theSubjectId))
- .setStatus(MedicationStatement.MedicationStatementStatus.UNKNOWN);
- // .setEffective(new
- // Period().addExtension().setUrl("http://hl7.org/fhir/StructureDefinition/data-absent-reason").setValue((new Coding().setCode("not-applicable"))))
- return medication;
- }
- }
-
- private static class ProblemNoInfoR4Generator implements INoInfoGenerator {
- @Override
- public IBaseResource generate(IIdType theSubjectId) {
- Condition condition = new Condition();
- condition
- .setCode(new CodeableConcept()
- .addCoding(new Coding()
- .setCode("no-problem-info")
- .setSystem("http://hl7.org/fhir/uv/ips/CodeSystem/absent-unknown-uv-ips")
- .setDisplay("No information about problems")))
- .setSubject(new Reference(theSubjectId))
- .setClinicalStatus(new CodeableConcept()
- .addCoding(new Coding()
- .setCode("active")
- .setSystem("http://terminology.hl7.org/CodeSystem/condition-clinical")));
- return condition;
- }
- }
-
- public static class Section {
-
- private final IpsSectionEnum mySectionEnum;
- private final String myTitle;
- private final String mySectionCode;
- private final String mySectionDisplay;
- private final List myResourceTypes;
- private final String myProfile;
- private final INoInfoGenerator myNoInfoGenerator;
-
- public Section(
- IpsSectionEnum theSectionEnum,
- String theTitle,
- String theSectionCode,
- String theSectionDisplay,
- List theResourceTypes,
- String theProfile,
- INoInfoGenerator theNoInfoGenerator) {
- mySectionEnum = theSectionEnum;
- myTitle = theTitle;
- mySectionCode = theSectionCode;
- mySectionDisplay = theSectionDisplay;
- myResourceTypes = Collections.unmodifiableList(new ArrayList<>(theResourceTypes));
- myProfile = theProfile;
- myNoInfoGenerator = theNoInfoGenerator;
- }
-
- @Nullable
- public INoInfoGenerator getNoInfoGenerator() {
- return myNoInfoGenerator;
- }
-
- public List getResourceTypes() {
- return myResourceTypes;
- }
-
- public String getProfile() {
- return myProfile;
- }
-
- public IpsSectionEnum getSectionEnum() {
- return mySectionEnum;
- }
-
- public String getTitle() {
- return myTitle;
- }
-
- public String getSectionCode() {
- return mySectionCode;
- }
-
- public String getSectionDisplay() {
- return mySectionDisplay;
- }
- }
-}
diff --git a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/generator/IIpsGeneratorSvc.java b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/generator/IIpsGeneratorSvc.java
index 1f53fcebd51..fcd0330319c 100644
--- a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/generator/IIpsGeneratorSvc.java
+++ b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/generator/IIpsGeneratorSvc.java
@@ -30,11 +30,11 @@ public interface IIpsGeneratorSvc {
* Generates an IPS document and returns the complete document bundle
* for the given patient by ID
*/
- IBaseBundle generateIps(RequestDetails theRequestDetails, IIdType thePatientId);
+ IBaseBundle generateIps(RequestDetails theRequestDetails, IIdType thePatientId, String theProfile);
/**
* Generates an IPS document and returns the complete document bundle
* for the given patient by identifier
*/
- IBaseBundle generateIps(RequestDetails theRequestDetails, TokenParam thePatientIdentifier);
+ IBaseBundle generateIps(RequestDetails theRequestDetails, TokenParam thePatientIdentifier, String theProfile);
}
diff --git a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/generator/IpsGeneratorSvcImpl.java b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/generator/IpsGeneratorSvcImpl.java
index f3b837623c0..7562c5daf93 100644
--- a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/generator/IpsGeneratorSvcImpl.java
+++ b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/generator/IpsGeneratorSvcImpl.java
@@ -20,33 +20,23 @@
package ca.uhn.fhir.jpa.ips.generator;
import ca.uhn.fhir.context.FhirContext;
-import ca.uhn.fhir.context.RuntimeResourceDefinition;
-import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.fhirpath.IFhirPathEvaluationContext;
-import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
-import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.ips.api.IIpsGenerationStrategy;
+import ca.uhn.fhir.jpa.ips.api.ISectionResourceSupplier;
import ca.uhn.fhir.jpa.ips.api.IpsContext;
-import ca.uhn.fhir.jpa.ips.api.IpsSectionEnum;
-import ca.uhn.fhir.jpa.ips.api.SectionRegistry;
-import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
-import ca.uhn.fhir.model.api.Include;
-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.jpa.ips.api.IpsSectionContext;
+import ca.uhn.fhir.jpa.ips.api.Section;
import ca.uhn.fhir.narrative.CustomThymeleafNarrativeGenerator;
-import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails;
-import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.util.BundleBuilder;
import ca.uhn.fhir.util.CompositionBuilder;
import ca.uhn.fhir.util.ResourceReferenceInfo;
-import ca.uhn.fhir.util.ValidateUtil;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
+import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseExtension;
@@ -58,94 +48,100 @@ import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Composition;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.InstantType;
-import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Resource;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Set;
import java.util.UUID;
-import java.util.stream.Collectors;
-import static ca.uhn.fhir.jpa.term.api.ITermLoaderSvc.LOINC_URI;
+import static java.util.Objects.requireNonNull;
+import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc {
- public static final int CHUNK_SIZE = 10;
- private static final Logger ourLog = LoggerFactory.getLogger(IpsGeneratorSvcImpl.class);
- private final IIpsGenerationStrategy myGenerationStrategy;
- private final DaoRegistry myDaoRegistry;
+ public static final String RESOURCE_ENTRY_INCLUSION_TYPE = "RESOURCE_ENTRY_INCLUSION_TYPE";
+ public static final String URL_NARRATIVE_LINK = "http://hl7.org/fhir/StructureDefinition/narrativeLink";
+ private final List myGenerationStrategies;
private final FhirContext myFhirContext;
/**
* Constructor
*/
- public IpsGeneratorSvcImpl(
- FhirContext theFhirContext, IIpsGenerationStrategy theGenerationStrategy, DaoRegistry theDaoRegistry) {
- myGenerationStrategy = theGenerationStrategy;
- myDaoRegistry = theDaoRegistry;
+ public IpsGeneratorSvcImpl(FhirContext theFhirContext, IIpsGenerationStrategy theGenerationStrategy) {
+ this(theFhirContext, List.of(theGenerationStrategy));
+ }
+
+ public IpsGeneratorSvcImpl(FhirContext theFhirContext, List theIpsGenerationStrategies) {
+ myGenerationStrategies = theIpsGenerationStrategies;
myFhirContext = theFhirContext;
+
+ myGenerationStrategies.forEach(IIpsGenerationStrategy::initialize);
}
+ /**
+ * Generate an IPS using a patient ID
+ */
@Override
- public IBaseBundle generateIps(RequestDetails theRequestDetails, IIdType thePatientId) {
- IBaseResource patient = myDaoRegistry.getResourceDao("Patient").read(thePatientId, theRequestDetails);
-
- return generateIpsForPatient(theRequestDetails, patient);
+ public IBaseBundle generateIps(RequestDetails theRequestDetails, IIdType thePatientId, String theProfile) {
+ IIpsGenerationStrategy strategy = selectGenerationStrategy(theProfile);
+ IBaseResource patient = strategy.fetchPatient(thePatientId, theRequestDetails);
+ return generateIpsForPatient(strategy, theRequestDetails, patient);
}
+ /**
+ * Generate an IPS using a patient identifier
+ */
@Override
- public IBaseBundle generateIps(RequestDetails theRequestDetails, TokenParam thePatientIdentifier) {
- SearchParameterMap searchParameterMap =
- new SearchParameterMap().setLoadSynchronousUpTo(2).add(Patient.SP_IDENTIFIER, thePatientIdentifier);
- IBundleProvider searchResults =
- myDaoRegistry.getResourceDao("Patient").search(searchParameterMap, theRequestDetails);
-
- ValidateUtil.isTrueOrThrowInvalidRequest(
- searchResults.sizeOrThrowNpe() > 0, "No Patient could be found matching given identifier");
- ValidateUtil.isTrueOrThrowInvalidRequest(
- searchResults.sizeOrThrowNpe() == 1, "Multiple Patient resources were found matching given identifier");
-
- IBaseResource patient = searchResults.getResources(0, 1).get(0);
-
- return generateIpsForPatient(theRequestDetails, patient);
+ public IBaseBundle generateIps(
+ RequestDetails theRequestDetails, TokenParam thePatientIdentifier, String theProfile) {
+ IIpsGenerationStrategy strategy = selectGenerationStrategy(theProfile);
+ IBaseResource patient = strategy.fetchPatient(thePatientIdentifier, theRequestDetails);
+ return generateIpsForPatient(strategy, theRequestDetails, patient);
}
- private IBaseBundle generateIpsForPatient(RequestDetails theRequestDetails, IBaseResource thePatient) {
+ IIpsGenerationStrategy selectGenerationStrategy(@Nullable String theRequestedProfile) {
+ return myGenerationStrategies.stream()
+ .filter(t -> isBlank(theRequestedProfile) || theRequestedProfile.equals(t.getBundleProfile()))
+ .findFirst()
+ .orElse(myGenerationStrategies.get(0));
+ }
+
+ private IBaseBundle generateIpsForPatient(
+ IIpsGenerationStrategy theStrategy, RequestDetails theRequestDetails, IBaseResource thePatient) {
IIdType originalSubjectId = myFhirContext
.getVersion()
.newIdType()
.setValue(thePatient.getIdElement().getValue())
.toUnqualifiedVersionless();
- massageResourceId(null, thePatient);
+ massageResourceId(theStrategy, theRequestDetails, null, thePatient);
IpsContext context = new IpsContext(thePatient, originalSubjectId);
ResourceInclusionCollection globalResourcesToInclude = new ResourceInclusionCollection();
globalResourcesToInclude.addResourceIfNotAlreadyPresent(thePatient, originalSubjectId.getValue());
- IBaseResource author = myGenerationStrategy.createAuthor();
- massageResourceId(context, author);
+ IBaseResource author = theStrategy.createAuthor();
+ massageResourceId(theStrategy, theRequestDetails, context, author);
- CompositionBuilder compositionBuilder = createComposition(thePatient, context, author);
- determineInclusions(
- theRequestDetails, originalSubjectId, context, compositionBuilder, globalResourcesToInclude);
+ CompositionBuilder compositionBuilder = createComposition(theStrategy, thePatient, context, author);
+ determineInclusions(theStrategy, theRequestDetails, context, compositionBuilder, globalResourcesToInclude);
IBaseResource composition = compositionBuilder.getComposition();
// Create the narrative for the Composition itself
- CustomThymeleafNarrativeGenerator generator = newNarrativeGenerator(globalResourcesToInclude);
+ CustomThymeleafNarrativeGenerator generator = newNarrativeGenerator(theStrategy, globalResourcesToInclude);
generator.populateResourceNarrative(myFhirContext, composition);
- return createCompositionDocument(author, composition, globalResourcesToInclude);
+ return createDocumentBundleForComposition(theStrategy, author, composition, globalResourcesToInclude);
}
- private IBaseBundle createCompositionDocument(
- IBaseResource author, IBaseResource composition, ResourceInclusionCollection theResourcesToInclude) {
+ private IBaseBundle createDocumentBundleForComposition(
+ IIpsGenerationStrategy theStrategy,
+ IBaseResource author,
+ IBaseResource composition,
+ ResourceInclusionCollection theResourcesToInclude) {
BundleBuilder bundleBuilder = new BundleBuilder(myFhirContext);
bundleBuilder.setType(Bundle.BundleType.DOCUMENT.toCode());
bundleBuilder.setIdentifier("urn:ietf:rfc:4122", UUID.randomUUID().toString());
@@ -162,124 +158,51 @@ public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc {
// Add author to document
bundleBuilder.addDocumentEntry(author);
- return bundleBuilder.getBundle();
+ IBaseBundle retVal = bundleBuilder.getBundle();
+
+ theStrategy.postManipulateIpsBundle(retVal);
+
+ return retVal;
}
- @Nonnull
- private ResourceInclusionCollection determineInclusions(
+ private void determineInclusions(
+ IIpsGenerationStrategy theStrategy,
RequestDetails theRequestDetails,
- IIdType originalSubjectId,
- IpsContext context,
+ IpsContext theIpsContext,
CompositionBuilder theCompositionBuilder,
ResourceInclusionCollection theGlobalResourcesToInclude) {
- SectionRegistry sectionRegistry = myGenerationStrategy.getSectionRegistry();
- for (SectionRegistry.Section nextSection : sectionRegistry.getSections()) {
+ for (Section nextSection : theStrategy.getSections()) {
determineInclusionsForSection(
+ theStrategy,
theRequestDetails,
- originalSubjectId,
- context,
+ theIpsContext,
theCompositionBuilder,
theGlobalResourcesToInclude,
nextSection);
}
- return theGlobalResourcesToInclude;
}
private void determineInclusionsForSection(
+ IIpsGenerationStrategy theStrategy,
RequestDetails theRequestDetails,
- IIdType theOriginalSubjectId,
IpsContext theIpsContext,
CompositionBuilder theCompositionBuilder,
- ResourceInclusionCollection theGlobalResourcesToInclude,
- SectionRegistry.Section theSection) {
- ResourceInclusionCollection sectionResourcesToInclude = new ResourceInclusionCollection();
- for (String nextResourceType : theSection.getResourceTypes()) {
+ ResourceInclusionCollection theGlobalResourceCollectionToPopulate,
+ Section theSection) {
+ ResourceInclusionCollection sectionResourceCollectionToPopulate = new ResourceInclusionCollection();
+ ISectionResourceSupplier resourceSupplier = theStrategy.getSectionResourceSupplier(theSection);
- SearchParameterMap searchParameterMap = new SearchParameterMap();
- String subjectSp = determinePatientCompartmentSearchParameterName(nextResourceType);
- searchParameterMap.add(subjectSp, new ReferenceParam(theOriginalSubjectId));
+ determineInclusionsForSectionResourceTypes(
+ theStrategy,
+ theRequestDetails,
+ theIpsContext,
+ theGlobalResourceCollectionToPopulate,
+ theSection,
+ resourceSupplier,
+ sectionResourceCollectionToPopulate);
- IpsSectionEnum sectionEnum = theSection.getSectionEnum();
- IpsContext.IpsSectionContext ipsSectionContext =
- theIpsContext.newSectionContext(sectionEnum, nextResourceType);
- myGenerationStrategy.massageResourceSearch(ipsSectionContext, searchParameterMap);
-
- Set includes = myGenerationStrategy.provideResourceSearchIncludes(ipsSectionContext);
- includes.forEach(searchParameterMap::addInclude);
-
- IFhirResourceDao> dao = myDaoRegistry.getResourceDao(nextResourceType);
- IBundleProvider searchResult = dao.search(searchParameterMap, theRequestDetails);
- for (int startIndex = 0; ; startIndex += CHUNK_SIZE) {
- int endIndex = startIndex + CHUNK_SIZE;
- List resources = searchResult.getResources(startIndex, endIndex);
- if (resources.isEmpty()) {
- break;
- }
-
- for (IBaseResource nextCandidate : resources) {
-
- boolean candidateIsSearchInclude = ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.get(nextCandidate)
- == BundleEntrySearchModeEnum.INCLUDE;
- boolean addResourceToBundle;
- if (candidateIsSearchInclude) {
- addResourceToBundle = true;
- } else {
- addResourceToBundle = myGenerationStrategy.shouldInclude(ipsSectionContext, nextCandidate);
- }
-
- if (addResourceToBundle) {
-
- String originalResourceId = nextCandidate
- .getIdElement()
- .toUnqualifiedVersionless()
- .getValue();
-
- // Check if we already have this resource included so that we don't
- // include it twice
- IBaseResource previouslyExistingResource =
- theGlobalResourcesToInclude.getResourceByOriginalId(originalResourceId);
- if (previouslyExistingResource != null) {
- BundleEntrySearchModeEnum candidateSearchEntryMode =
- ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.get(nextCandidate);
- if (candidateSearchEntryMode == BundleEntrySearchModeEnum.MATCH) {
- ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.put(
- previouslyExistingResource, BundleEntrySearchModeEnum.MATCH);
- }
-
- nextCandidate = previouslyExistingResource;
- sectionResourcesToInclude.addResourceIfNotAlreadyPresent(nextCandidate, originalResourceId);
- } else if (theGlobalResourcesToInclude.hasResourceWithReplacementId(originalResourceId)) {
- if (!candidateIsSearchInclude) {
- sectionResourcesToInclude.addResourceIfNotAlreadyPresent(
- nextCandidate, originalResourceId);
- }
- } else {
- IIdType id = myGenerationStrategy.massageResourceId(theIpsContext, nextCandidate);
- nextCandidate.setId(id);
- theGlobalResourcesToInclude.addResourceIfNotAlreadyPresent(
- nextCandidate, originalResourceId);
- if (!candidateIsSearchInclude) {
- sectionResourcesToInclude.addResourceIfNotAlreadyPresent(
- nextCandidate, originalResourceId);
- }
- }
- }
- }
- }
- }
-
- if (sectionResourcesToInclude.isEmpty() && theSection.getNoInfoGenerator() != null) {
- IBaseResource noInfoResource = theSection.getNoInfoGenerator().generate(theIpsContext.getSubjectId());
- String id = IdType.newRandomUuid().getValue();
- if (noInfoResource.getIdElement().isEmpty()) {
- noInfoResource.setId(id);
- }
- ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.put(noInfoResource, BundleEntrySearchModeEnum.MATCH);
- theGlobalResourcesToInclude.addResourceIfNotAlreadyPresent(
- noInfoResource,
- noInfoResource.getIdElement().toUnqualifiedVersionless().getValue());
- sectionResourcesToInclude.addResourceIfNotAlreadyPresent(noInfoResource, id);
- }
+ generateSectionNoInfoResourceIfNoInclusionsFound(
+ theIpsContext, theGlobalResourceCollectionToPopulate, theSection, sectionResourceCollectionToPopulate);
/*
* Update any references within the added candidates - This is important
@@ -287,7 +210,23 @@ public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc {
* the summary, so we need to also update the references to those
* resources.
*/
- for (IBaseResource nextResource : theGlobalResourcesToInclude.getResources()) {
+ updateReferencesInInclusionsForSection(theGlobalResourceCollectionToPopulate);
+
+ if (sectionResourceCollectionToPopulate.isEmpty()) {
+ return;
+ }
+
+ addSection(
+ theStrategy,
+ theSection,
+ theCompositionBuilder,
+ sectionResourceCollectionToPopulate,
+ theGlobalResourceCollectionToPopulate);
+ }
+
+ private void updateReferencesInInclusionsForSection(
+ ResourceInclusionCollection theGlobalResourceCollectionToPopulate) {
+ for (IBaseResource nextResource : theGlobalResourceCollectionToPopulate.getResources()) {
List references = myFhirContext.newTerser().getAllResourceReferences(nextResource);
for (ResourceReferenceInfo nextReference : references) {
String existingReference = nextReference
@@ -298,12 +237,12 @@ public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc {
existingReference = new IdType(existingReference)
.toUnqualifiedVersionless()
.getValue();
- String replacement = theGlobalResourcesToInclude.getIdSubstitution(existingReference);
+ String replacement = theGlobalResourceCollectionToPopulate.getIdSubstitution(existingReference);
if (isNotBlank(replacement)) {
if (!replacement.equals(existingReference)) {
nextReference.getResourceReference().setReference(replacement);
}
- } else if (theGlobalResourcesToInclude.getResourceById(existingReference) == null) {
+ } else if (theGlobalResourceCollectionToPopulate.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);
@@ -312,17 +251,184 @@ public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc {
}
}
}
+ }
- if (sectionResourcesToInclude.isEmpty()) {
- return;
+ private static void generateSectionNoInfoResourceIfNoInclusionsFound(
+ IpsContext theIpsContext,
+ ResourceInclusionCollection theGlobalResourceCollectionToPopulate,
+ Section theSection,
+ ResourceInclusionCollection sectionResourceCollectionToPopulate) {
+ if (sectionResourceCollectionToPopulate.isEmpty() && theSection.getNoInfoGenerator() != null) {
+ IBaseResource noInfoResource = theSection.getNoInfoGenerator().generate(theIpsContext.getSubjectId());
+ String id = IdType.newRandomUuid().getValue();
+ if (noInfoResource.getIdElement().isEmpty()) {
+ noInfoResource.setId(id);
+ }
+ noInfoResource.setUserData(
+ RESOURCE_ENTRY_INCLUSION_TYPE, ISectionResourceSupplier.InclusionTypeEnum.PRIMARY_RESOURCE);
+ theGlobalResourceCollectionToPopulate.addResourceIfNotAlreadyPresent(
+ noInfoResource,
+ noInfoResource.getIdElement().toUnqualifiedVersionless().getValue());
+ sectionResourceCollectionToPopulate.addResourceIfNotAlreadyPresent(noInfoResource, id);
+ }
+ }
+
+ private void determineInclusionsForSectionResourceTypes(
+ IIpsGenerationStrategy theStrategy,
+ RequestDetails theRequestDetails,
+ IpsContext theIpsContext,
+ ResourceInclusionCollection theGlobalResourceCollectionToPopulate,
+ Section theSection,
+ ISectionResourceSupplier resourceSupplier,
+ ResourceInclusionCollection sectionResourceCollectionToPopulate) {
+ for (Class extends IBaseResource> nextResourceType : theSection.getResourceTypes()) {
+ determineInclusionsForSectionResourceType(
+ theStrategy,
+ theRequestDetails,
+ theIpsContext,
+ theGlobalResourceCollectionToPopulate,
+ theSection,
+ nextResourceType,
+ resourceSupplier,
+ sectionResourceCollectionToPopulate);
+ }
+ }
+
+ private void determineInclusionsForSectionResourceType(
+ IIpsGenerationStrategy theStrategy,
+ RequestDetails theRequestDetails,
+ IpsContext theIpsContext,
+ ResourceInclusionCollection theGlobalResourceCollectionToPopulate,
+ Section theSection,
+ Class nextResourceType,
+ ISectionResourceSupplier resourceSupplier,
+ ResourceInclusionCollection sectionResourceCollectionToPopulate) {
+ IpsSectionContext ipsSectionContext = theIpsContext.newSectionContext(theSection, nextResourceType);
+
+ List resources =
+ resourceSupplier.fetchResourcesForSection(theIpsContext, ipsSectionContext, theRequestDetails);
+ if (resources != null) {
+ for (ISectionResourceSupplier.ResourceEntry nextEntry : resources) {
+ IBaseResource resource = nextEntry.getResource();
+ Validate.isTrue(
+ resource.getIdElement().hasIdPart(),
+ "fetchResourcesForSection(..) returned resource(s) with no ID populated");
+ resource.setUserData(RESOURCE_ENTRY_INCLUSION_TYPE, nextEntry.getInclusionType());
+ }
+ addResourcesToIpsContents(
+ theStrategy,
+ theRequestDetails,
+ theIpsContext,
+ resources,
+ theGlobalResourceCollectionToPopulate,
+ sectionResourceCollectionToPopulate);
+ }
+ }
+
+ /**
+ * Given a collection of resources that have been fetched, analyze them and add them as appropriate
+ * to the collection that will be included in a given IPS section context.
+ *
+ * @param theStrategy The generation strategy
+ * @param theIpsContext The overall IPS generation context for this IPS.
+ * @param theCandidateResources The resources that have been fetched for inclusion in the IPS bundle
+ */
+ private void addResourcesToIpsContents(
+ IIpsGenerationStrategy theStrategy,
+ RequestDetails theRequestDetails,
+ IpsContext theIpsContext,
+ List theCandidateResources,
+ ResourceInclusionCollection theGlobalResourcesCollectionToPopulate,
+ ResourceInclusionCollection theSectionResourceCollectionToPopulate) {
+ for (ISectionResourceSupplier.ResourceEntry nextCandidateEntry : theCandidateResources) {
+ if (nextCandidateEntry.getInclusionType() == ISectionResourceSupplier.InclusionTypeEnum.EXCLUDE) {
+ continue;
+ }
+
+ IBaseResource nextCandidate = nextCandidateEntry.getResource();
+ boolean primaryResource = nextCandidateEntry.getInclusionType()
+ == ISectionResourceSupplier.InclusionTypeEnum.PRIMARY_RESOURCE;
+
+ String originalResourceId =
+ nextCandidate.getIdElement().toUnqualifiedVersionless().getValue();
+
+ // Check if we already have this resource included so that we don't
+ // include it twice
+ IBaseResource previouslyExistingResource =
+ theGlobalResourcesCollectionToPopulate.getResourceByOriginalId(originalResourceId);
+
+ if (previouslyExistingResource != null) {
+ reuseAlreadyIncludedGlobalResourceInSectionCollection(
+ theSectionResourceCollectionToPopulate,
+ previouslyExistingResource,
+ primaryResource,
+ originalResourceId);
+ } else if (theGlobalResourcesCollectionToPopulate.hasResourceWithReplacementId(originalResourceId)) {
+ addResourceToSectionCollectionOnlyIfPrimary(
+ theSectionResourceCollectionToPopulate, primaryResource, nextCandidate, originalResourceId);
+ } else {
+ addResourceToGlobalCollectionAndSectionCollection(
+ theStrategy,
+ theRequestDetails,
+ theIpsContext,
+ theGlobalResourcesCollectionToPopulate,
+ theSectionResourceCollectionToPopulate,
+ nextCandidate,
+ originalResourceId,
+ primaryResource);
+ }
+ }
+ }
+
+ private static void addResourceToSectionCollectionOnlyIfPrimary(
+ ResourceInclusionCollection theSectionResourceCollectionToPopulate,
+ boolean primaryResource,
+ IBaseResource nextCandidate,
+ String originalResourceId) {
+ if (primaryResource) {
+ theSectionResourceCollectionToPopulate.addResourceIfNotAlreadyPresent(nextCandidate, originalResourceId);
+ }
+ }
+
+ private void addResourceToGlobalCollectionAndSectionCollection(
+ IIpsGenerationStrategy theStrategy,
+ RequestDetails theRequestDetails,
+ IpsContext theIpsContext,
+ ResourceInclusionCollection theGlobalResourcesCollectionToPopulate,
+ ResourceInclusionCollection theSectionResourceCollectionToPopulate,
+ IBaseResource nextCandidate,
+ String originalResourceId,
+ boolean primaryResource) {
+ massageResourceId(theStrategy, theRequestDetails, theIpsContext, nextCandidate);
+ theGlobalResourcesCollectionToPopulate.addResourceIfNotAlreadyPresent(nextCandidate, originalResourceId);
+ addResourceToSectionCollectionOnlyIfPrimary(
+ theSectionResourceCollectionToPopulate, primaryResource, nextCandidate, originalResourceId);
+ }
+
+ private static void reuseAlreadyIncludedGlobalResourceInSectionCollection(
+ ResourceInclusionCollection theSectionResourceCollectionToPopulate,
+ IBaseResource previouslyExistingResource,
+ boolean primaryResource,
+ String originalResourceId) {
+ IBaseResource nextCandidate;
+ ISectionResourceSupplier.InclusionTypeEnum previouslyIncludedResourceInclusionType =
+ (ISectionResourceSupplier.InclusionTypeEnum)
+ previouslyExistingResource.getUserData(RESOURCE_ENTRY_INCLUSION_TYPE);
+ if (previouslyIncludedResourceInclusionType != ISectionResourceSupplier.InclusionTypeEnum.PRIMARY_RESOURCE) {
+ if (primaryResource) {
+ previouslyExistingResource.setUserData(
+ RESOURCE_ENTRY_INCLUSION_TYPE, ISectionResourceSupplier.InclusionTypeEnum.PRIMARY_RESOURCE);
+ }
}
- addSection(theSection, theCompositionBuilder, sectionResourcesToInclude, theGlobalResourcesToInclude);
+ nextCandidate = previouslyExistingResource;
+ theSectionResourceCollectionToPopulate.addResourceIfNotAlreadyPresent(nextCandidate, originalResourceId);
}
@SuppressWarnings("unchecked")
private void addSection(
- SectionRegistry.Section theSection,
+ IIpsGenerationStrategy theStrategy,
+ Section theSection,
CompositionBuilder theCompositionBuilder,
ResourceInclusionCollection theResourcesToInclude,
ResourceInclusionCollection theGlobalResourcesToInclude) {
@@ -330,34 +436,44 @@ public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc {
CompositionBuilder.SectionBuilder sectionBuilder = theCompositionBuilder.addSection();
sectionBuilder.setTitle(theSection.getTitle());
- sectionBuilder.addCodeCoding(LOINC_URI, theSection.getSectionCode(), theSection.getSectionDisplay());
+ sectionBuilder.addCodeCoding(
+ theSection.getSectionSystem(), theSection.getSectionCode(), theSection.getSectionDisplay());
for (IBaseResource next : theResourcesToInclude.getResources()) {
- if (ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.get(next) == BundleEntrySearchModeEnum.INCLUDE) {
+ ISectionResourceSupplier.InclusionTypeEnum inclusionType =
+ (ISectionResourceSupplier.InclusionTypeEnum) next.getUserData(RESOURCE_ENTRY_INCLUSION_TYPE);
+ if (inclusionType != ISectionResourceSupplier.InclusionTypeEnum.PRIMARY_RESOURCE) {
continue;
}
- IBaseExtension, ?> narrativeLink = ((IBaseHasExtensions) next).addExtension();
- narrativeLink.setUrl("http://hl7.org/fhir/StructureDefinition/narrativeLink");
- String narrativeLinkValue =
- theCompositionBuilder.getComposition().getIdElement().getValue()
- + "#"
- + myFhirContext.getResourceType(next)
- + "-"
- + next.getIdElement().getValue();
- IPrimitiveType narrativeLinkUri = (IPrimitiveType)
- myFhirContext.getElementDefinition("url").newInstance();
- narrativeLinkUri.setValueAsString(narrativeLinkValue);
- narrativeLink.setValue(narrativeLinkUri);
+ IBaseHasExtensions extensionHolder = (IBaseHasExtensions) next;
+ if (extensionHolder.getExtension().stream()
+ .noneMatch(t -> t.getUrl().equals(URL_NARRATIVE_LINK))) {
+ IBaseExtension, ?> narrativeLink = extensionHolder.addExtension();
+ narrativeLink.setUrl(URL_NARRATIVE_LINK);
+ String narrativeLinkValue =
+ theCompositionBuilder.getComposition().getIdElement().getValue()
+ + "#"
+ + myFhirContext.getResourceType(next)
+ + "-"
+ + next.getIdElement().getValue();
+ IPrimitiveType narrativeLinkUri =
+ (IPrimitiveType) requireNonNull(myFhirContext.getElementDefinition("url"))
+ .newInstance();
+ narrativeLinkUri.setValueAsString(narrativeLinkValue);
+ narrativeLink.setValue(narrativeLinkUri);
+ }
sectionBuilder.addEntry(next.getIdElement());
}
- String narrative = createSectionNarrative(theSection, theResourcesToInclude, theGlobalResourcesToInclude);
+ String narrative =
+ createSectionNarrative(theStrategy, theSection, theResourcesToInclude, theGlobalResourcesToInclude);
sectionBuilder.setText("generated", narrative);
}
- private CompositionBuilder createComposition(IBaseResource thePatient, IpsContext context, IBaseResource author) {
+ private CompositionBuilder createComposition(
+ IIpsGenerationStrategy theStrategy, IBaseResource thePatient, IpsContext context, IBaseResource author) {
CompositionBuilder compositionBuilder = new CompositionBuilder(myFhirContext);
compositionBuilder.setId(IdType.newRandomUuid());
@@ -365,43 +481,44 @@ public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc {
compositionBuilder.setSubject(thePatient.getIdElement().toUnqualifiedVersionless());
compositionBuilder.addTypeCoding("http://loinc.org", "60591-5", "Patient Summary Document");
compositionBuilder.setDate(InstantType.now());
- compositionBuilder.setTitle(myGenerationStrategy.createTitle(context));
- compositionBuilder.setConfidentiality(myGenerationStrategy.createConfidentiality(context));
+ compositionBuilder.setTitle(theStrategy.createTitle(context));
+ compositionBuilder.setConfidentiality(theStrategy.createConfidentiality(context));
compositionBuilder.addAuthor(author.getIdElement());
return compositionBuilder;
}
- private String determinePatientCompartmentSearchParameterName(String theResourceType) {
- RuntimeResourceDefinition resourceDef = myFhirContext.getResourceDefinition(theResourceType);
- Set 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(
+ IIpsGenerationStrategy theStrategy,
+ RequestDetails theRequestDetails,
+ IpsContext theIpsContext,
+ IBaseResource theResource) {
+ String base = theRequestDetails.getFhirServerBase();
- private void massageResourceId(IpsContext theIpsContext, IBaseResource theResource) {
- IIdType id = myGenerationStrategy.massageResourceId(theIpsContext, theResource);
- theResource.setId(id);
+ IIdType id = theResource.getIdElement();
+ if (!id.hasBaseUrl() && id.hasResourceType() && id.hasIdPart()) {
+ id = id.withServerBase(base, id.getResourceType());
+ theResource.setId(id);
+ }
+
+ id = theStrategy.massageResourceId(theIpsContext, theResource);
+ if (id != null) {
+ theResource.setId(id);
+ }
}
private String createSectionNarrative(
- SectionRegistry.Section theSection,
+ IIpsGenerationStrategy theStrategy,
+ Section theSection,
ResourceInclusionCollection theResources,
ResourceInclusionCollection theGlobalResourceCollection) {
- CustomThymeleafNarrativeGenerator generator = newNarrativeGenerator(theGlobalResourceCollection);
+ CustomThymeleafNarrativeGenerator generator = newNarrativeGenerator(theStrategy, theGlobalResourceCollection);
Bundle bundle = new Bundle();
for (IBaseResource resource : theResources.getResources()) {
- BundleEntrySearchModeEnum searchMode = ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.get(resource);
- if (searchMode == BundleEntrySearchModeEnum.MATCH) {
+ ISectionResourceSupplier.InclusionTypeEnum inclusionType =
+ (ISectionResourceSupplier.InclusionTypeEnum) resource.getUserData(RESOURCE_ENTRY_INCLUSION_TYPE);
+ if (inclusionType == ISectionResourceSupplier.InclusionTypeEnum.PRIMARY_RESOURCE) {
bundle.addEntry().setResource((Resource) resource);
}
}
@@ -414,14 +531,13 @@ public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc {
@Nonnull
private CustomThymeleafNarrativeGenerator newNarrativeGenerator(
- ResourceInclusionCollection theGlobalResourceCollection) {
- List narrativePropertyFiles = myGenerationStrategy.getNarrativePropertyFiles();
+ IIpsGenerationStrategy theStrategy, ResourceInclusionCollection theGlobalResourceCollection) {
+ List narrativePropertyFiles = theStrategy.getNarrativePropertyFiles();
CustomThymeleafNarrativeGenerator generator = new CustomThymeleafNarrativeGenerator(narrativePropertyFiles);
generator.setFhirPathEvaluationContext(new IFhirPathEvaluationContext() {
@Override
public IBase resolveReference(@Nonnull IIdType theReference, @Nullable IBase theContext) {
- IBaseResource resource = theGlobalResourceCollection.getResourceById(theReference);
- return resource;
+ return theGlobalResourceCollection.getResourceById(theReference);
}
});
return generator;
diff --git a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/DefaultJpaIpsGenerationStrategy.java b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/DefaultJpaIpsGenerationStrategy.java
new file mode 100644
index 00000000000..6a9368e052e
--- /dev/null
+++ b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/DefaultJpaIpsGenerationStrategy.java
@@ -0,0 +1,477 @@
+/*-
+ * #%L
+ * HAPI FHIR JPA Server - International Patient Summary (IPS)
+ * %%
+ * Copyright (C) 2014 - 2024 Smile CDR, Inc.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package ca.uhn.fhir.jpa.ips.jpa;
+
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
+import ca.uhn.fhir.jpa.ips.api.Section;
+import ca.uhn.fhir.jpa.ips.jpa.section.AdvanceDirectivesJpaSectionSearchStrategy;
+import ca.uhn.fhir.jpa.ips.jpa.section.AllergyIntoleranceJpaSectionSearchStrategy;
+import ca.uhn.fhir.jpa.ips.jpa.section.DiagnosticResultsJpaSectionSearchStrategyDiagnosticReport;
+import ca.uhn.fhir.jpa.ips.jpa.section.DiagnosticResultsJpaSectionSearchStrategyObservation;
+import ca.uhn.fhir.jpa.ips.jpa.section.FunctionalStatusJpaSectionSearchStrategy;
+import ca.uhn.fhir.jpa.ips.jpa.section.IllnessHistoryJpaSectionSearchStrategy;
+import ca.uhn.fhir.jpa.ips.jpa.section.ImmunizationsJpaSectionSearchStrategy;
+import ca.uhn.fhir.jpa.ips.jpa.section.MedicalDevicesJpaSectionSearchStrategy;
+import ca.uhn.fhir.jpa.ips.jpa.section.MedicationSummaryJpaSectionSearchStrategyMedicationAdministration;
+import ca.uhn.fhir.jpa.ips.jpa.section.MedicationSummaryJpaSectionSearchStrategyMedicationDispense;
+import ca.uhn.fhir.jpa.ips.jpa.section.MedicationSummaryJpaSectionSearchStrategyMedicationRequest;
+import ca.uhn.fhir.jpa.ips.jpa.section.MedicationSummaryJpaSectionSearchStrategyMedicationStatement;
+import ca.uhn.fhir.jpa.ips.jpa.section.PlanOfCareJpaSectionSearchStrategy;
+import ca.uhn.fhir.jpa.ips.jpa.section.PregnancyJpaSectionSearchStrategy;
+import ca.uhn.fhir.jpa.ips.jpa.section.ProblemListJpaSectionSearchStrategy;
+import ca.uhn.fhir.jpa.ips.jpa.section.ProceduresJpaSectionSearchStrategy;
+import ca.uhn.fhir.jpa.ips.jpa.section.SocialHistoryJpaSectionSearchStrategy;
+import ca.uhn.fhir.jpa.ips.jpa.section.VitalSignsJpaSectionSearchStrategy;
+import ca.uhn.fhir.jpa.ips.strategy.AllergyIntoleranceNoInfoR4Generator;
+import ca.uhn.fhir.jpa.ips.strategy.BaseIpsGenerationStrategy;
+import ca.uhn.fhir.jpa.ips.strategy.MedicationNoInfoR4Generator;
+import ca.uhn.fhir.jpa.ips.strategy.ProblemNoInfoR4Generator;
+import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
+import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc;
+import ca.uhn.fhir.rest.api.server.IBundleProvider;
+import ca.uhn.fhir.rest.api.server.RequestDetails;
+import ca.uhn.fhir.rest.param.TokenParam;
+import ca.uhn.fhir.util.ValidateUtil;
+import jakarta.annotation.Nonnull;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.hl7.fhir.instance.model.api.IIdType;
+import org.hl7.fhir.r4.model.AllergyIntolerance;
+import org.hl7.fhir.r4.model.CarePlan;
+import org.hl7.fhir.r4.model.ClinicalImpression;
+import org.hl7.fhir.r4.model.Condition;
+import org.hl7.fhir.r4.model.Consent;
+import org.hl7.fhir.r4.model.DeviceUseStatement;
+import org.hl7.fhir.r4.model.DiagnosticReport;
+import org.hl7.fhir.r4.model.Immunization;
+import org.hl7.fhir.r4.model.MedicationAdministration;
+import org.hl7.fhir.r4.model.MedicationDispense;
+import org.hl7.fhir.r4.model.MedicationRequest;
+import org.hl7.fhir.r4.model.MedicationStatement;
+import org.hl7.fhir.r4.model.Observation;
+import org.hl7.fhir.r4.model.Patient;
+import org.hl7.fhir.r4.model.Procedure;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.thymeleaf.util.Validate;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.function.Function;
+
+/**
+ * This {@link ca.uhn.fhir.jpa.ips.api.IIpsGenerationStrategy generation strategy} contains default rules for fetching
+ * IPS section contents for each of the base (universal realm) IPS definition sections. It fetches contents for each
+ * section from the JPA server repository.
+ *
+ * This class can be used directly, but it can also be subclassed and extended if you want to
+ * create an IPS strategy that is based on the defaults but add or change the inclusion rules or
+ * sections. If you are subclassing this class, the typical approach is to override the
+ * {@link #addSections()} method and replace it with your own implementation. You can include
+ * any of the same sections that are defined in the parent class, but you can also omit any
+ * you don't want to include, and add your own as well.
+ *
+ */
+public class DefaultJpaIpsGenerationStrategy extends BaseIpsGenerationStrategy {
+
+ public static final String SECTION_CODE_ALLERGY_INTOLERANCE = "48765-2";
+ public static final String SECTION_CODE_MEDICATION_SUMMARY = "10160-0";
+ public static final String SECTION_CODE_PROBLEM_LIST = "11450-4";
+ public static final String SECTION_CODE_IMMUNIZATIONS = "11369-6";
+ public static final String SECTION_CODE_PROCEDURES = "47519-4";
+ public static final String SECTION_CODE_MEDICAL_DEVICES = "46264-8";
+ public static final String SECTION_CODE_DIAGNOSTIC_RESULTS = "30954-2";
+ public static final String SECTION_CODE_VITAL_SIGNS = "8716-3";
+ public static final String SECTION_CODE_PREGNANCY = "10162-6";
+ public static final String SECTION_CODE_SOCIAL_HISTORY = "29762-2";
+ public static final String SECTION_CODE_ILLNESS_HISTORY = "11348-0";
+ public static final String SECTION_CODE_FUNCTIONAL_STATUS = "47420-5";
+ public static final String SECTION_CODE_PLAN_OF_CARE = "18776-5";
+ public static final String SECTION_CODE_ADVANCE_DIRECTIVES = "42348-3";
+ public static final String SECTION_SYSTEM_LOINC = ITermLoaderSvc.LOINC_URI;
+ private final List> myGlobalSectionCustomizers = new ArrayList<>();
+
+ @Autowired
+ private DaoRegistry myDaoRegistry;
+
+ @Autowired
+ private FhirContext myFhirContext;
+
+ private boolean myInitialized;
+
+ public void setDaoRegistry(DaoRegistry theDaoRegistry) {
+ myDaoRegistry = theDaoRegistry;
+ }
+
+ public void setFhirContext(FhirContext theFhirContext) {
+ myFhirContext = theFhirContext;
+ }
+
+ /**
+ * Subclasses may call this method to add customers that will customize every section
+ * added to the strategy.
+ */
+ public void addGlobalSectionCustomizer(@Nonnull Function theCustomizer) {
+ Validate.isTrue(!myInitialized, "This method must not be called after the strategy is initialized");
+ Validate.notNull(theCustomizer, "theCustomizer must not be null");
+ myGlobalSectionCustomizers.add(theCustomizer);
+ }
+
+ @Override
+ public final void initialize() {
+ Validate.isTrue(!myInitialized, "Strategy must not be initialized twice");
+ Validate.isTrue(myDaoRegistry != null, "No DaoRegistry has been supplied");
+ Validate.isTrue(myFhirContext != null, "No FhirContext has been supplied");
+ addSections();
+ myInitialized = true;
+ }
+
+ @Nonnull
+ @Override
+ public IBaseResource fetchPatient(IIdType thePatientId, RequestDetails theRequestDetails) {
+ return myDaoRegistry.getResourceDao("Patient").read(thePatientId, theRequestDetails);
+ }
+
+ @Nonnull
+ @Override
+ public IBaseResource fetchPatient(TokenParam thePatientIdentifier, RequestDetails theRequestDetails) {
+ SearchParameterMap searchParameterMap =
+ new SearchParameterMap().setLoadSynchronousUpTo(2).add(Patient.SP_IDENTIFIER, thePatientIdentifier);
+ IBundleProvider searchResults =
+ myDaoRegistry.getResourceDao("Patient").search(searchParameterMap, theRequestDetails);
+
+ ValidateUtil.isTrueOrThrowResourceNotFound(
+ searchResults.sizeOrThrowNpe() > 0, "No Patient could be found matching given identifier");
+ ValidateUtil.isTrueOrThrowInvalidRequest(
+ searchResults.sizeOrThrowNpe() == 1, "Multiple Patient resources were found matching given identifier");
+
+ return searchResults.getResources(0, 1).get(0);
+ }
+
+ /**
+ * Add the various sections to the registry in order. This method can be overridden for
+ * customization.
+ */
+ protected void addSections() {
+ addJpaSectionAllergyIntolerance();
+ addJpaSectionMedicationSummary();
+ addJpaSectionProblemList();
+ addJpaSectionImmunizations();
+ addJpaSectionProcedures();
+ addJpaSectionMedicalDevices();
+ addJpaSectionDiagnosticResults();
+ addJpaSectionVitalSigns();
+ addJpaSectionPregnancy();
+ addJpaSectionSocialHistory();
+ addJpaSectionIllnessHistory();
+ addJpaSectionFunctionalStatus();
+ addJpaSectionPlanOfCare();
+ addJpaSectionAdvanceDirectives();
+ }
+
+ protected void addJpaSectionAllergyIntolerance() {
+ Section section = Section.newBuilder()
+ .withTitle("Allergies and Intolerances")
+ .withSectionSystem(SECTION_SYSTEM_LOINC)
+ .withSectionSystem(SECTION_SYSTEM_LOINC)
+ .withSectionCode(SECTION_CODE_ALLERGY_INTOLERANCE)
+ .withSectionDisplay("Allergies and adverse reactions Document")
+ .withResourceType(AllergyIntolerance.class)
+ .withProfile(
+ "https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionAllergies")
+ .withNoInfoGenerator(new AllergyIntoleranceNoInfoR4Generator())
+ .build();
+
+ JpaSectionSearchStrategyCollection searchStrategyCollection = JpaSectionSearchStrategyCollection.newBuilder()
+ .addStrategy(AllergyIntolerance.class, new AllergyIntoleranceJpaSectionSearchStrategy())
+ .build();
+
+ addJpaSection(section, searchStrategyCollection);
+ }
+
+ protected void addJpaSectionMedicationSummary() {
+ Section section = Section.newBuilder()
+ .withTitle("Medication List")
+ .withSectionSystem(SECTION_SYSTEM_LOINC)
+ .withSectionCode(SECTION_CODE_MEDICATION_SUMMARY)
+ .withSectionDisplay("History of Medication use Narrative")
+ .withResourceType(MedicationStatement.class)
+ .withResourceType(MedicationRequest.class)
+ .withResourceType(MedicationAdministration.class)
+ .withResourceType(MedicationDispense.class)
+ .withProfile(
+ "https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionMedications")
+ .withNoInfoGenerator(new MedicationNoInfoR4Generator())
+ .build();
+
+ JpaSectionSearchStrategyCollection searchStrategyCollection = JpaSectionSearchStrategyCollection.newBuilder()
+ .addStrategy(
+ MedicationAdministration.class,
+ new MedicationSummaryJpaSectionSearchStrategyMedicationAdministration())
+ .addStrategy(
+ MedicationDispense.class, new MedicationSummaryJpaSectionSearchStrategyMedicationDispense())
+ .addStrategy(MedicationRequest.class, new MedicationSummaryJpaSectionSearchStrategyMedicationRequest())
+ .addStrategy(
+ MedicationStatement.class, new MedicationSummaryJpaSectionSearchStrategyMedicationStatement())
+ .build();
+
+ addJpaSection(section, searchStrategyCollection);
+ }
+
+ protected void addJpaSectionProblemList() {
+ Section section = Section.newBuilder()
+ .withTitle("Problem List")
+ .withSectionSystem(SECTION_SYSTEM_LOINC)
+ .withSectionCode(SECTION_CODE_PROBLEM_LIST)
+ .withSectionDisplay("Problem list - Reported")
+ .withResourceType(Condition.class)
+ .withProfile(
+ "https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionProblems")
+ .withNoInfoGenerator(new ProblemNoInfoR4Generator())
+ .build();
+
+ JpaSectionSearchStrategyCollection searchStrategyCollection = JpaSectionSearchStrategyCollection.newBuilder()
+ .addStrategy(Condition.class, new ProblemListJpaSectionSearchStrategy())
+ .build();
+
+ addJpaSection(section, searchStrategyCollection);
+ }
+
+ protected void addJpaSectionImmunizations() {
+ Section section = Section.newBuilder()
+ .withTitle("History of Immunizations")
+ .withSectionSystem(SECTION_SYSTEM_LOINC)
+ .withSectionCode(SECTION_CODE_IMMUNIZATIONS)
+ .withSectionDisplay("History of Immunization Narrative")
+ .withResourceType(Immunization.class)
+ .withProfile(
+ "https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionImmunizations")
+ .build();
+
+ JpaSectionSearchStrategyCollection searchStrategyCollection = JpaSectionSearchStrategyCollection.newBuilder()
+ .addStrategy(Immunization.class, new ImmunizationsJpaSectionSearchStrategy())
+ .build();
+
+ addJpaSection(section, searchStrategyCollection);
+ }
+
+ protected void addJpaSectionProcedures() {
+ Section section = Section.newBuilder()
+ .withTitle("History of Procedures")
+ .withSectionSystem(SECTION_SYSTEM_LOINC)
+ .withSectionCode(SECTION_CODE_PROCEDURES)
+ .withSectionDisplay("History of Procedures Document")
+ .withResourceType(Procedure.class)
+ .withProfile(
+ "https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionProceduresHx")
+ .build();
+
+ JpaSectionSearchStrategyCollection searchStrategyCollection = JpaSectionSearchStrategyCollection.newBuilder()
+ .addStrategy(Procedure.class, new ProceduresJpaSectionSearchStrategy())
+ .build();
+
+ addJpaSection(section, searchStrategyCollection);
+ }
+
+ protected void addJpaSectionMedicalDevices() {
+ Section section = Section.newBuilder()
+ .withTitle("Medical Devices")
+ .withSectionSystem(SECTION_SYSTEM_LOINC)
+ .withSectionCode(SECTION_CODE_MEDICAL_DEVICES)
+ .withSectionDisplay("History of medical device use")
+ .withResourceType(DeviceUseStatement.class)
+ .withProfile(
+ "https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionMedicalDevices")
+ .build();
+
+ JpaSectionSearchStrategyCollection searchStrategyCollection = JpaSectionSearchStrategyCollection.newBuilder()
+ .addStrategy(DeviceUseStatement.class, new MedicalDevicesJpaSectionSearchStrategy())
+ .build();
+
+ addJpaSection(section, searchStrategyCollection);
+ }
+
+ protected void addJpaSectionDiagnosticResults() {
+ Section section = Section.newBuilder()
+ .withTitle("Diagnostic Results")
+ .withSectionSystem(SECTION_SYSTEM_LOINC)
+ .withSectionCode(SECTION_CODE_DIAGNOSTIC_RESULTS)
+ .withSectionDisplay("Relevant diagnostic tests/laboratory data Narrative")
+ .withResourceType(DiagnosticReport.class)
+ .withResourceType(Observation.class)
+ .withProfile(
+ "https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionResults")
+ .build();
+
+ JpaSectionSearchStrategyCollection searchStrategyCollection = JpaSectionSearchStrategyCollection.newBuilder()
+ .addStrategy(DiagnosticReport.class, new DiagnosticResultsJpaSectionSearchStrategyDiagnosticReport())
+ .addStrategy(Observation.class, new DiagnosticResultsJpaSectionSearchStrategyObservation())
+ .build();
+
+ addJpaSection(section, searchStrategyCollection);
+ }
+
+ protected void addJpaSectionVitalSigns() {
+ Section section = Section.newBuilder()
+ .withTitle("Vital Signs")
+ .withSectionSystem(SECTION_SYSTEM_LOINC)
+ .withSectionCode(SECTION_CODE_VITAL_SIGNS)
+ .withSectionDisplay("Vital signs")
+ .withResourceType(Observation.class)
+ .withProfile(
+ "https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionVitalSigns")
+ .build();
+
+ JpaSectionSearchStrategyCollection searchStrategyCollection = JpaSectionSearchStrategyCollection.newBuilder()
+ .addStrategy(Observation.class, new VitalSignsJpaSectionSearchStrategy())
+ .build();
+
+ addJpaSection(section, searchStrategyCollection);
+ }
+
+ protected void addJpaSectionPregnancy() {
+ Section section = Section.newBuilder()
+ .withTitle("Pregnancy Information")
+ .withSectionSystem(SECTION_SYSTEM_LOINC)
+ .withSectionCode(SECTION_CODE_PREGNANCY)
+ .withSectionDisplay("History of pregnancies Narrative")
+ .withResourceType(Observation.class)
+ .withProfile(
+ "https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionPregnancyHx")
+ .build();
+
+ JpaSectionSearchStrategyCollection searchStrategyCollection = JpaSectionSearchStrategyCollection.newBuilder()
+ .addStrategy(Observation.class, new PregnancyJpaSectionSearchStrategy())
+ .build();
+
+ addJpaSection(section, searchStrategyCollection);
+ }
+
+ protected void addJpaSectionSocialHistory() {
+ Section section = Section.newBuilder()
+ .withTitle("Social History")
+ .withSectionSystem(SECTION_SYSTEM_LOINC)
+ .withSectionCode(SECTION_CODE_SOCIAL_HISTORY)
+ .withSectionDisplay("Social history Narrative")
+ .withResourceType(Observation.class)
+ .withProfile(
+ "https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionSocialHistory")
+ .build();
+
+ JpaSectionSearchStrategyCollection searchStrategyCollection = JpaSectionSearchStrategyCollection.newBuilder()
+ .addStrategy(Observation.class, new SocialHistoryJpaSectionSearchStrategy())
+ .build();
+
+ addJpaSection(section, searchStrategyCollection);
+ }
+
+ protected void addJpaSectionIllnessHistory() {
+ Section section = Section.newBuilder()
+ .withTitle("History of Past Illness")
+ .withSectionSystem(SECTION_SYSTEM_LOINC)
+ .withSectionCode(SECTION_CODE_ILLNESS_HISTORY)
+ .withSectionDisplay("History of Past illness Narrative")
+ .withResourceType(Condition.class)
+ .withProfile(
+ "https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionPastIllnessHx")
+ .build();
+
+ JpaSectionSearchStrategyCollection searchStrategyCollection = JpaSectionSearchStrategyCollection.newBuilder()
+ .addStrategy(Condition.class, new IllnessHistoryJpaSectionSearchStrategy())
+ .build();
+
+ addJpaSection(section, searchStrategyCollection);
+ }
+
+ protected void addJpaSectionFunctionalStatus() {
+ Section section = Section.newBuilder()
+ .withTitle("Functional Status")
+ .withSectionSystem(SECTION_SYSTEM_LOINC)
+ .withSectionCode(SECTION_CODE_FUNCTIONAL_STATUS)
+ .withSectionDisplay("Functional status assessment note")
+ .withResourceType(ClinicalImpression.class)
+ .withProfile(
+ "https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionFunctionalStatus")
+ .build();
+
+ JpaSectionSearchStrategyCollection searchStrategyCollection = JpaSectionSearchStrategyCollection.newBuilder()
+ .addStrategy(ClinicalImpression.class, new FunctionalStatusJpaSectionSearchStrategy())
+ .build();
+
+ addJpaSection(section, searchStrategyCollection);
+ }
+
+ protected void addJpaSectionPlanOfCare() {
+ Section section = Section.newBuilder()
+ .withTitle("Plan of Care")
+ .withSectionSystem(SECTION_SYSTEM_LOINC)
+ .withSectionCode(SECTION_CODE_PLAN_OF_CARE)
+ .withSectionDisplay("Plan of care note")
+ .withResourceType(CarePlan.class)
+ .withProfile(
+ "https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionPlanOfCare")
+ .build();
+
+ JpaSectionSearchStrategyCollection searchStrategyCollection = JpaSectionSearchStrategyCollection.newBuilder()
+ .addStrategy(CarePlan.class, new PlanOfCareJpaSectionSearchStrategy())
+ .build();
+
+ addJpaSection(section, searchStrategyCollection);
+ }
+
+ protected void addJpaSectionAdvanceDirectives() {
+ Section section = Section.newBuilder()
+ .withTitle("Advance Directives")
+ .withSectionSystem(SECTION_SYSTEM_LOINC)
+ .withSectionCode(SECTION_CODE_ADVANCE_DIRECTIVES)
+ .withSectionDisplay("Advance directives")
+ .withResourceType(Consent.class)
+ .withProfile(
+ "https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionAdvanceDirectives")
+ .build();
+
+ JpaSectionSearchStrategyCollection searchStrategyCollection = JpaSectionSearchStrategyCollection.newBuilder()
+ .addStrategy(Consent.class, new AdvanceDirectivesJpaSectionSearchStrategy())
+ .build();
+
+ addJpaSection(section, searchStrategyCollection);
+ }
+
+ protected void addJpaSection(
+ Section theSection, JpaSectionSearchStrategyCollection theSectionSearchStrategyCollection) {
+ Section section = theSection;
+ for (var next : myGlobalSectionCustomizers) {
+ section = next.apply(section);
+ }
+
+ Validate.isTrue(
+ theSection.getResourceTypes().size()
+ == theSectionSearchStrategyCollection.getResourceTypes().size(),
+ "Search strategy types does not match section types");
+ Validate.isTrue(
+ new HashSet<>(theSection.getResourceTypes())
+ .containsAll(theSectionSearchStrategyCollection.getResourceTypes()),
+ "Search strategy types does not match section types");
+
+ addSection(
+ section,
+ new JpaSectionResourceSupplier(theSectionSearchStrategyCollection, myDaoRegistry, myFhirContext));
+ }
+}
diff --git a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/IJpaSectionSearchStrategy.java b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/IJpaSectionSearchStrategy.java
new file mode 100644
index 00000000000..b6294bc499e
--- /dev/null
+++ b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/IJpaSectionSearchStrategy.java
@@ -0,0 +1,68 @@
+/*-
+ * #%L
+ * HAPI FHIR JPA Server - International Patient Summary (IPS)
+ * %%
+ * Copyright (C) 2014 - 2024 Smile CDR, Inc.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package ca.uhn.fhir.jpa.ips.jpa;
+
+import ca.uhn.fhir.jpa.ips.api.IpsSectionContext;
+import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
+import jakarta.annotation.Nonnull;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+
+/**
+ * Implementations of this interface are used to fetch resources to include
+ * for a given IPS section by performing a search in a local JPA repository.
+ *
+ * @since 7.2.0
+ */
+public interface IJpaSectionSearchStrategy {
+
+ /**
+ * This method can manipulate the {@link SearchParameterMap} that will
+ * be used to find candidate resources for the given IPS section. The map will already have
+ * a subject/patient parameter added to it. The map provided in {@literal theSearchParameterMap}
+ * will contain a subject/patient reference (e.g. ?patient=Patient/123
), but no
+ * other parameters. This method can add other parameters. The default implementation of this
+ * interface performs no action.
+ *
+ * For example, for a Vital Signs section, the implementation might add a parameter indicating
+ * the parameter category=vital-signs
.
+ *
+ *
+ * @param theIpsSectionContext The context, which indicates the IPS section and the resource type
+ * being searched for.
+ * @param theSearchParameterMap The map to manipulate.
+ */
+ default void massageResourceSearch(
+ @Nonnull IpsSectionContext theIpsSectionContext, @Nonnull SearchParameterMap theSearchParameterMap) {
+ // no action taken by default
+ }
+
+ /**
+ * This method will be called for each found resource candidate for inclusion in the
+ * IPS document. The strategy can decide whether to include it or not. Note that the
+ * default implementation will always return {@literal true}.
+ *
+ * This method is called once for every resource that is being considered for inclusion
+ * in an IPS section.
+ *
+ */
+ default boolean shouldInclude(@Nonnull IpsSectionContext theIpsSectionContext, @Nonnull T theCandidate) {
+ return true;
+ }
+}
diff --git a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/IpsGenerationCtxConfig.java b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/IpsGenerationCtxConfig.java
new file mode 100644
index 00000000000..2f365aae674
--- /dev/null
+++ b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/IpsGenerationCtxConfig.java
@@ -0,0 +1,25 @@
+/*-
+ * #%L
+ * HAPI FHIR JPA Server - International Patient Summary (IPS)
+ * %%
+ * Copyright (C) 2014 - 2024 Smile CDR, Inc.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package ca.uhn.fhir.jpa.ips.jpa;
+
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class IpsGenerationCtxConfig {}
diff --git a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/JpaSectionResourceSupplier.java b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/JpaSectionResourceSupplier.java
new file mode 100644
index 00000000000..e50bec7c1ac
--- /dev/null
+++ b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/JpaSectionResourceSupplier.java
@@ -0,0 +1,128 @@
+/*-
+ * #%L
+ * HAPI FHIR JPA Server - International Patient Summary (IPS)
+ * %%
+ * Copyright (C) 2014 - 2024 Smile CDR, Inc.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package ca.uhn.fhir.jpa.ips.jpa;
+
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.context.RuntimeResourceDefinition;
+import ca.uhn.fhir.context.RuntimeSearchParam;
+import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
+import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
+import ca.uhn.fhir.jpa.ips.api.ISectionResourceSupplier;
+import ca.uhn.fhir.jpa.ips.api.IpsContext;
+import ca.uhn.fhir.jpa.ips.api.IpsSectionContext;
+import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
+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.rest.api.server.IBundleProvider;
+import ca.uhn.fhir.rest.api.server.RequestDetails;
+import ca.uhn.fhir.rest.param.ReferenceParam;
+import jakarta.annotation.Nonnull;
+import jakarta.annotation.Nullable;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.hl7.fhir.r4.model.Coverage;
+import org.thymeleaf.util.Validate;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class JpaSectionResourceSupplier implements ISectionResourceSupplier {
+ public static final int CHUNK_SIZE = 10;
+
+ private final JpaSectionSearchStrategyCollection mySectionSearchStrategyCollection;
+ private final DaoRegistry myDaoRegistry;
+ private final FhirContext myFhirContext;
+
+ public JpaSectionResourceSupplier(
+ @Nonnull JpaSectionSearchStrategyCollection theSectionSearchStrategyCollection,
+ @Nonnull DaoRegistry theDaoRegistry,
+ @Nonnull FhirContext theFhirContext) {
+ Validate.notNull(theSectionSearchStrategyCollection, "theSectionSearchStrategyCollection must not be null");
+ Validate.notNull(theDaoRegistry, "theDaoRegistry must not be null");
+ Validate.notNull(theFhirContext, "theFhirContext must not be null");
+ mySectionSearchStrategyCollection = theSectionSearchStrategyCollection;
+ myDaoRegistry = theDaoRegistry;
+ myFhirContext = theFhirContext;
+ }
+
+ @Nullable
+ @Override
+ public List fetchResourcesForSection(
+ IpsContext theIpsContext, IpsSectionContext theIpsSectionContext, RequestDetails theRequestDetails) {
+
+ IJpaSectionSearchStrategy searchStrategy =
+ mySectionSearchStrategyCollection.getSearchStrategy(theIpsSectionContext.getResourceType());
+
+ SearchParameterMap searchParameterMap = new SearchParameterMap();
+
+ String subjectSp = determinePatientCompartmentSearchParameterName(theIpsSectionContext.getResourceType());
+ searchParameterMap.add(subjectSp, new ReferenceParam(theIpsContext.getSubjectId()));
+
+ searchStrategy.massageResourceSearch(theIpsSectionContext, searchParameterMap);
+
+ IFhirResourceDao dao = myDaoRegistry.getResourceDao(theIpsSectionContext.getResourceType());
+ IBundleProvider searchResult = dao.search(searchParameterMap, theRequestDetails);
+
+ List retVal = null;
+ for (int startIndex = 0; ; startIndex += CHUNK_SIZE) {
+ int endIndex = startIndex + CHUNK_SIZE;
+ List resources = searchResult.getResources(startIndex, endIndex);
+ if (resources.isEmpty()) {
+ break;
+ }
+
+ for (IBaseResource next : resources) {
+ if (!next.getClass().isAssignableFrom(theIpsSectionContext.getResourceType())
+ || searchStrategy.shouldInclude(theIpsSectionContext, (T) next)) {
+ if (retVal == null) {
+ retVal = new ArrayList<>();
+ }
+ InclusionTypeEnum inclusionType =
+ ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.get(next) == BundleEntrySearchModeEnum.INCLUDE
+ ? InclusionTypeEnum.SECONDARY_RESOURCE
+ : InclusionTypeEnum.PRIMARY_RESOURCE;
+ retVal.add(new ResourceEntry(next, inclusionType));
+ }
+ }
+ }
+
+ return retVal;
+ }
+
+ private String determinePatientCompartmentSearchParameterName(Class extends IBaseResource> theResourceType) {
+ RuntimeResourceDefinition resourceDef = myFhirContext.getResourceDefinition(theResourceType);
+ Set searchParams = resourceDef.getSearchParamsForCompartmentName("Patient").stream()
+ .map(RuntimeSearchParam::getName)
+ .collect(Collectors.toSet());
+ // A few we prefer
+ if (searchParams.contains(Observation.SP_PATIENT)) {
+ return Observation.SP_PATIENT;
+ }
+ if (searchParams.contains(Observation.SP_SUBJECT)) {
+ return Observation.SP_SUBJECT;
+ }
+ if (searchParams.contains(Coverage.SP_BENEFICIARY)) {
+ return Observation.SP_SUBJECT;
+ }
+ return searchParams.iterator().next();
+ }
+}
diff --git a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/api/IpsSectionEnum.java b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/JpaSectionSearchStrategy.java
similarity index 69%
rename from hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/api/IpsSectionEnum.java
rename to hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/JpaSectionSearchStrategy.java
index 7992dfefa1e..5eb41ab6f4c 100644
--- a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/api/IpsSectionEnum.java
+++ b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/JpaSectionSearchStrategy.java
@@ -17,21 +17,12 @@
* limitations under the License.
* #L%
*/
-package ca.uhn.fhir.jpa.ips.api;
+package ca.uhn.fhir.jpa.ips.jpa;
+
+import org.hl7.fhir.instance.model.api.IBaseResource;
+
+public class JpaSectionSearchStrategy implements IJpaSectionSearchStrategy {
+
+ // nothing for now, interface has default methods
-public enum IpsSectionEnum {
- ALLERGY_INTOLERANCE,
- MEDICATION_SUMMARY,
- PROBLEM_LIST,
- IMMUNIZATIONS,
- PROCEDURES,
- MEDICAL_DEVICES,
- DIAGNOSTIC_RESULTS,
- VITAL_SIGNS,
- ILLNESS_HISTORY,
- PREGNANCY,
- SOCIAL_HISTORY,
- FUNCTIONAL_STATUS,
- PLAN_OF_CARE,
- ADVANCE_DIRECTIVES
}
diff --git a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/JpaSectionSearchStrategyCollection.java b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/JpaSectionSearchStrategyCollection.java
new file mode 100644
index 00000000000..e281ae31844
--- /dev/null
+++ b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/JpaSectionSearchStrategyCollection.java
@@ -0,0 +1,62 @@
+/*-
+ * #%L
+ * HAPI FHIR JPA Server - International Patient Summary (IPS)
+ * %%
+ * Copyright (C) 2014 - 2024 Smile CDR, Inc.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package ca.uhn.fhir.jpa.ips.jpa;
+
+import org.hl7.fhir.instance.model.api.IBaseResource;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+public class JpaSectionSearchStrategyCollection {
+
+ private Map, Object> mySearchStrategies;
+
+ private JpaSectionSearchStrategyCollection(Map, Object> theSearchStrategies) {
+ mySearchStrategies = theSearchStrategies;
+ }
+
+ @SuppressWarnings("unchecked")
+ public IJpaSectionSearchStrategy getSearchStrategy(Class theClass) {
+ return (IJpaSectionSearchStrategy) mySearchStrategies.get(theClass);
+ }
+
+ public Collection> getResourceTypes() {
+ return mySearchStrategies.keySet();
+ }
+
+ public static JpaSectionSearchStrategyCollectionBuilder newBuilder() {
+ return new JpaSectionSearchStrategyCollectionBuilder();
+ }
+
+ public static class JpaSectionSearchStrategyCollectionBuilder {
+ private Map, Object> mySearchStrategies = new HashMap<>();
+
+ public JpaSectionSearchStrategyCollectionBuilder addStrategy(
+ Class theType, IJpaSectionSearchStrategy theSearchStrategy) {
+ mySearchStrategies.put(theType, theSearchStrategy);
+ return this;
+ }
+
+ public JpaSectionSearchStrategyCollection build() {
+ return new JpaSectionSearchStrategyCollection(mySearchStrategies);
+ }
+ }
+}
diff --git a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/AdvanceDirectivesJpaSectionSearchStrategy.java b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/AdvanceDirectivesJpaSectionSearchStrategy.java
new file mode 100644
index 00000000000..501f581b8c5
--- /dev/null
+++ b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/AdvanceDirectivesJpaSectionSearchStrategy.java
@@ -0,0 +1,41 @@
+/*-
+ * #%L
+ * HAPI FHIR JPA Server - International Patient Summary (IPS)
+ * %%
+ * Copyright (C) 2014 - 2024 Smile CDR, Inc.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package ca.uhn.fhir.jpa.ips.jpa.section;
+
+import ca.uhn.fhir.jpa.ips.api.IpsSectionContext;
+import ca.uhn.fhir.jpa.ips.jpa.JpaSectionSearchStrategy;
+import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
+import ca.uhn.fhir.rest.param.TokenOrListParam;
+import ca.uhn.fhir.rest.param.TokenParam;
+import jakarta.annotation.Nonnull;
+import org.hl7.fhir.r4.model.Consent;
+
+public class AdvanceDirectivesJpaSectionSearchStrategy extends JpaSectionSearchStrategy {
+
+ @Override
+ public void massageResourceSearch(
+ @Nonnull IpsSectionContext theIpsSectionContext, @Nonnull SearchParameterMap theSearchParameterMap) {
+ theSearchParameterMap.add(
+ Consent.SP_STATUS,
+ new TokenOrListParam()
+ .addOr(new TokenParam(
+ Consent.ConsentState.ACTIVE.getSystem(), Consent.ConsentState.ACTIVE.toCode())));
+ }
+}
diff --git a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/AllergyIntoleranceJpaSectionSearchStrategy.java b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/AllergyIntoleranceJpaSectionSearchStrategy.java
new file mode 100644
index 00000000000..f485091d80c
--- /dev/null
+++ b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/AllergyIntoleranceJpaSectionSearchStrategy.java
@@ -0,0 +1,44 @@
+/*-
+ * #%L
+ * HAPI FHIR JPA Server - International Patient Summary (IPS)
+ * %%
+ * Copyright (C) 2014 - 2024 Smile CDR, Inc.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package ca.uhn.fhir.jpa.ips.jpa.section;
+
+import ca.uhn.fhir.jpa.ips.api.IpsSectionContext;
+import ca.uhn.fhir.jpa.ips.jpa.JpaSectionSearchStrategy;
+import jakarta.annotation.Nonnull;
+import org.hl7.fhir.r4.model.AllergyIntolerance;
+
+public class AllergyIntoleranceJpaSectionSearchStrategy extends JpaSectionSearchStrategy {
+
+ @Override
+ public boolean shouldInclude(
+ @Nonnull IpsSectionContext theIpsSectionContext, @Nonnull AllergyIntolerance theCandidate) {
+ return !theCandidate
+ .getClinicalStatus()
+ .hasCoding("http://terminology.hl7.org/CodeSystem/allergyintolerance-clinical", "inactive")
+ && !theCandidate
+ .getClinicalStatus()
+ .hasCoding("http://terminology.hl7.org/CodeSystem/allergyintolerance-clinical", "resolved")
+ && !theCandidate
+ .getVerificationStatus()
+ .hasCoding(
+ "http://terminology.hl7.org/CodeSystem/allergyintolerance-verification",
+ "entered-in-error");
+ }
+}
diff --git a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/DiagnosticResultsJpaSectionSearchStrategyDiagnosticReport.java b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/DiagnosticResultsJpaSectionSearchStrategyDiagnosticReport.java
new file mode 100644
index 00000000000..8425d7cdbe3
--- /dev/null
+++ b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/DiagnosticResultsJpaSectionSearchStrategyDiagnosticReport.java
@@ -0,0 +1,42 @@
+/*-
+ * #%L
+ * HAPI FHIR JPA Server - International Patient Summary (IPS)
+ * %%
+ * Copyright (C) 2014 - 2024 Smile CDR, Inc.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package ca.uhn.fhir.jpa.ips.jpa.section;
+
+import ca.uhn.fhir.jpa.ips.api.IpsSectionContext;
+import ca.uhn.fhir.jpa.ips.jpa.JpaSectionSearchStrategy;
+import jakarta.annotation.Nonnull;
+import org.hl7.fhir.r4.model.DiagnosticReport;
+
+public class DiagnosticResultsJpaSectionSearchStrategyDiagnosticReport
+ extends JpaSectionSearchStrategy {
+
+ @SuppressWarnings("RedundantIfStatement")
+ @Override
+ public boolean shouldInclude(
+ @Nonnull IpsSectionContext theIpsSectionContext, @Nonnull DiagnosticReport theCandidate) {
+ if (theCandidate.getStatus() == DiagnosticReport.DiagnosticReportStatus.CANCELLED
+ || theCandidate.getStatus() == DiagnosticReport.DiagnosticReportStatus.ENTEREDINERROR
+ || theCandidate.getStatus() == DiagnosticReport.DiagnosticReportStatus.PRELIMINARY) {
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/DiagnosticResultsJpaSectionSearchStrategyObservation.java b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/DiagnosticResultsJpaSectionSearchStrategyObservation.java
new file mode 100644
index 00000000000..bb77621cef1
--- /dev/null
+++ b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/DiagnosticResultsJpaSectionSearchStrategyObservation.java
@@ -0,0 +1,56 @@
+/*-
+ * #%L
+ * HAPI FHIR JPA Server - International Patient Summary (IPS)
+ * %%
+ * Copyright (C) 2014 - 2024 Smile CDR, Inc.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package ca.uhn.fhir.jpa.ips.jpa.section;
+
+import ca.uhn.fhir.jpa.ips.api.IpsSectionContext;
+import ca.uhn.fhir.jpa.ips.jpa.JpaSectionSearchStrategy;
+import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
+import ca.uhn.fhir.rest.param.TokenOrListParam;
+import ca.uhn.fhir.rest.param.TokenParam;
+import jakarta.annotation.Nonnull;
+import org.hl7.fhir.r4.model.Observation;
+
+public class DiagnosticResultsJpaSectionSearchStrategyObservation extends JpaSectionSearchStrategy {
+
+ @Override
+ public void massageResourceSearch(
+ @Nonnull IpsSectionContext theIpsSectionContext,
+ @Nonnull SearchParameterMap theSearchParameterMap) {
+ theSearchParameterMap.add(
+ Observation.SP_CATEGORY,
+ new TokenOrListParam()
+ .addOr(new TokenParam(
+ "http://terminology.hl7.org/CodeSystem/observation-category", "laboratory")));
+ }
+
+ @SuppressWarnings("RedundantIfStatement")
+ @Override
+ public boolean shouldInclude(
+ @Nonnull IpsSectionContext theIpsSectionContext, @Nonnull Observation theCandidate) {
+ // code filtering not yet applied
+ if (theCandidate.getStatus() == Observation.ObservationStatus.CANCELLED
+ || theCandidate.getStatus() == Observation.ObservationStatus.ENTEREDINERROR
+ || theCandidate.getStatus() == Observation.ObservationStatus.PRELIMINARY) {
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/FunctionalStatusJpaSectionSearchStrategy.java b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/FunctionalStatusJpaSectionSearchStrategy.java
new file mode 100644
index 00000000000..6a5631dda0a
--- /dev/null
+++ b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/FunctionalStatusJpaSectionSearchStrategy.java
@@ -0,0 +1,40 @@
+/*-
+ * #%L
+ * HAPI FHIR JPA Server - International Patient Summary (IPS)
+ * %%
+ * Copyright (C) 2014 - 2024 Smile CDR, Inc.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package ca.uhn.fhir.jpa.ips.jpa.section;
+
+import ca.uhn.fhir.jpa.ips.api.IpsSectionContext;
+import ca.uhn.fhir.jpa.ips.jpa.JpaSectionSearchStrategy;
+import jakarta.annotation.Nonnull;
+import org.hl7.fhir.r4.model.ClinicalImpression;
+
+public class FunctionalStatusJpaSectionSearchStrategy extends JpaSectionSearchStrategy {
+
+ @SuppressWarnings("RedundantIfStatement")
+ @Override
+ public boolean shouldInclude(
+ @Nonnull IpsSectionContext theIpsSectionContext,
+ @Nonnull ClinicalImpression theCandidate) {
+ if (theCandidate.getStatus() == ClinicalImpression.ClinicalImpressionStatus.INPROGRESS
+ || theCandidate.getStatus() == ClinicalImpression.ClinicalImpressionStatus.ENTEREDINERROR) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/IllnessHistoryJpaSectionSearchStrategy.java b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/IllnessHistoryJpaSectionSearchStrategy.java
new file mode 100644
index 00000000000..feebd978b7d
--- /dev/null
+++ b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/IllnessHistoryJpaSectionSearchStrategy.java
@@ -0,0 +1,53 @@
+/*-
+ * #%L
+ * HAPI FHIR JPA Server - International Patient Summary (IPS)
+ * %%
+ * Copyright (C) 2014 - 2024 Smile CDR, Inc.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package ca.uhn.fhir.jpa.ips.jpa.section;
+
+import ca.uhn.fhir.jpa.ips.api.IpsSectionContext;
+import ca.uhn.fhir.jpa.ips.jpa.JpaSectionSearchStrategy;
+import jakarta.annotation.Nonnull;
+import org.hl7.fhir.r4.model.Condition;
+
+public class IllnessHistoryJpaSectionSearchStrategy extends JpaSectionSearchStrategy {
+
+ @SuppressWarnings("RedundantIfStatement")
+ @Override
+ public boolean shouldInclude(
+ @Nonnull IpsSectionContext theIpsSectionContext, @Nonnull Condition theCandidate) {
+ if (theCandidate
+ .getVerificationStatus()
+ .hasCoding("http://terminology.hl7.org/CodeSystem/condition-ver-status", "entered-in-error")) {
+ return false;
+ }
+
+ if (theCandidate
+ .getClinicalStatus()
+ .hasCoding("http://terminology.hl7.org/CodeSystem/condition-clinical", "inactive")
+ || theCandidate
+ .getClinicalStatus()
+ .hasCoding("http://terminology.hl7.org/CodeSystem/condition-clinical", "resolved")
+ || theCandidate
+ .getClinicalStatus()
+ .hasCoding("http://terminology.hl7.org/CodeSystem/condition-clinical", "remission")) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+}
diff --git a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/ImmunizationsJpaSectionSearchStrategy.java b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/ImmunizationsJpaSectionSearchStrategy.java
new file mode 100644
index 00000000000..7329bd038f0
--- /dev/null
+++ b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/ImmunizationsJpaSectionSearchStrategy.java
@@ -0,0 +1,50 @@
+/*-
+ * #%L
+ * HAPI FHIR JPA Server - International Patient Summary (IPS)
+ * %%
+ * Copyright (C) 2014 - 2024 Smile CDR, Inc.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package ca.uhn.fhir.jpa.ips.jpa.section;
+
+import ca.uhn.fhir.jpa.ips.api.IpsSectionContext;
+import ca.uhn.fhir.jpa.ips.jpa.JpaSectionSearchStrategy;
+import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
+import ca.uhn.fhir.rest.api.SortOrderEnum;
+import ca.uhn.fhir.rest.api.SortSpec;
+import jakarta.annotation.Nonnull;
+import org.hl7.fhir.r4.model.Immunization;
+
+public class ImmunizationsJpaSectionSearchStrategy extends JpaSectionSearchStrategy {
+
+ @Override
+ public void massageResourceSearch(
+ @Nonnull IpsSectionContext theIpsSectionContext,
+ @Nonnull SearchParameterMap theSearchParameterMap) {
+ theSearchParameterMap.setSort(new SortSpec(Immunization.SP_DATE).setOrder(SortOrderEnum.DESC));
+ theSearchParameterMap.addInclude(Immunization.INCLUDE_MANUFACTURER);
+ }
+
+ @SuppressWarnings("RedundantIfStatement")
+ @Override
+ public boolean shouldInclude(
+ @Nonnull IpsSectionContext theIpsSectionContext, @Nonnull Immunization theCandidate) {
+ if (theCandidate.getStatus() == Immunization.ImmunizationStatus.ENTEREDINERROR) {
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/MedicalDevicesJpaSectionSearchStrategy.java b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/MedicalDevicesJpaSectionSearchStrategy.java
new file mode 100644
index 00000000000..90c279a7414
--- /dev/null
+++ b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/MedicalDevicesJpaSectionSearchStrategy.java
@@ -0,0 +1,47 @@
+/*-
+ * #%L
+ * HAPI FHIR JPA Server - International Patient Summary (IPS)
+ * %%
+ * Copyright (C) 2014 - 2024 Smile CDR, Inc.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package ca.uhn.fhir.jpa.ips.jpa.section;
+
+import ca.uhn.fhir.jpa.ips.api.IpsSectionContext;
+import ca.uhn.fhir.jpa.ips.jpa.JpaSectionSearchStrategy;
+import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
+import jakarta.annotation.Nonnull;
+import org.hl7.fhir.r4.model.DeviceUseStatement;
+
+public class MedicalDevicesJpaSectionSearchStrategy extends JpaSectionSearchStrategy {
+
+ @Override
+ public void massageResourceSearch(
+ @Nonnull IpsSectionContext theIpsSectionContext,
+ @Nonnull SearchParameterMap theSearchParameterMap) {
+ theSearchParameterMap.addInclude(DeviceUseStatement.INCLUDE_DEVICE);
+ }
+
+ @SuppressWarnings("RedundantIfStatement")
+ @Override
+ public boolean shouldInclude(
+ @Nonnull IpsSectionContext theIpsSectionContext,
+ @Nonnull DeviceUseStatement theCandidate) {
+ if (theCandidate.getStatus() == DeviceUseStatement.DeviceUseStatementStatus.ENTEREDINERROR) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/MedicationSummaryJpaSectionSearchStrategyMedicationAdministration.java b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/MedicationSummaryJpaSectionSearchStrategyMedicationAdministration.java
new file mode 100644
index 00000000000..3cec15b99e1
--- /dev/null
+++ b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/MedicationSummaryJpaSectionSearchStrategyMedicationAdministration.java
@@ -0,0 +1,51 @@
+/*-
+ * #%L
+ * HAPI FHIR JPA Server - International Patient Summary (IPS)
+ * %%
+ * Copyright (C) 2014 - 2024 Smile CDR, Inc.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package ca.uhn.fhir.jpa.ips.jpa.section;
+
+import ca.uhn.fhir.jpa.ips.api.IpsSectionContext;
+import ca.uhn.fhir.jpa.ips.jpa.JpaSectionSearchStrategy;
+import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
+import ca.uhn.fhir.rest.param.TokenOrListParam;
+import ca.uhn.fhir.rest.param.TokenParam;
+import jakarta.annotation.Nonnull;
+import org.hl7.fhir.r4.model.MedicationAdministration;
+
+public class MedicationSummaryJpaSectionSearchStrategyMedicationAdministration
+ extends JpaSectionSearchStrategy {
+
+ @Override
+ public void massageResourceSearch(
+ @Nonnull IpsSectionContext theIpsSectionContext,
+ @Nonnull SearchParameterMap theSearchParameterMap) {
+ theSearchParameterMap.addInclude(MedicationAdministration.INCLUDE_MEDICATION);
+ theSearchParameterMap.add(
+ MedicationAdministration.SP_STATUS,
+ new TokenOrListParam()
+ .addOr(new TokenParam(
+ MedicationAdministration.MedicationAdministrationStatus.INPROGRESS.getSystem(),
+ MedicationAdministration.MedicationAdministrationStatus.INPROGRESS.toCode()))
+ .addOr(new TokenParam(
+ MedicationAdministration.MedicationAdministrationStatus.UNKNOWN.getSystem(),
+ MedicationAdministration.MedicationAdministrationStatus.UNKNOWN.toCode()))
+ .addOr(new TokenParam(
+ MedicationAdministration.MedicationAdministrationStatus.ONHOLD.getSystem(),
+ MedicationAdministration.MedicationAdministrationStatus.ONHOLD.toCode())));
+ }
+}
diff --git a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/MedicationSummaryJpaSectionSearchStrategyMedicationDispense.java b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/MedicationSummaryJpaSectionSearchStrategyMedicationDispense.java
new file mode 100644
index 00000000000..fc476cf3b3f
--- /dev/null
+++ b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/MedicationSummaryJpaSectionSearchStrategyMedicationDispense.java
@@ -0,0 +1,51 @@
+/*-
+ * #%L
+ * HAPI FHIR JPA Server - International Patient Summary (IPS)
+ * %%
+ * Copyright (C) 2014 - 2024 Smile CDR, Inc.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package ca.uhn.fhir.jpa.ips.jpa.section;
+
+import ca.uhn.fhir.jpa.ips.api.IpsSectionContext;
+import ca.uhn.fhir.jpa.ips.jpa.JpaSectionSearchStrategy;
+import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
+import ca.uhn.fhir.rest.param.TokenOrListParam;
+import ca.uhn.fhir.rest.param.TokenParam;
+import jakarta.annotation.Nonnull;
+import org.hl7.fhir.r4.model.MedicationDispense;
+
+public class MedicationSummaryJpaSectionSearchStrategyMedicationDispense
+ extends JpaSectionSearchStrategy {
+
+ @Override
+ public void massageResourceSearch(
+ @Nonnull IpsSectionContext theIpsSectionContext,
+ @Nonnull SearchParameterMap theSearchParameterMap) {
+ theSearchParameterMap.addInclude(MedicationDispense.INCLUDE_MEDICATION);
+ theSearchParameterMap.add(
+ MedicationDispense.SP_STATUS,
+ new TokenOrListParam()
+ .addOr(new TokenParam(
+ MedicationDispense.MedicationDispenseStatus.INPROGRESS.getSystem(),
+ MedicationDispense.MedicationDispenseStatus.INPROGRESS.toCode()))
+ .addOr(new TokenParam(
+ MedicationDispense.MedicationDispenseStatus.UNKNOWN.getSystem(),
+ MedicationDispense.MedicationDispenseStatus.UNKNOWN.toCode()))
+ .addOr(new TokenParam(
+ MedicationDispense.MedicationDispenseStatus.ONHOLD.getSystem(),
+ MedicationDispense.MedicationDispenseStatus.ONHOLD.toCode())));
+ }
+}
diff --git a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/MedicationSummaryJpaSectionSearchStrategyMedicationRequest.java b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/MedicationSummaryJpaSectionSearchStrategyMedicationRequest.java
new file mode 100644
index 00000000000..3d51ed17d6b
--- /dev/null
+++ b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/MedicationSummaryJpaSectionSearchStrategyMedicationRequest.java
@@ -0,0 +1,51 @@
+/*-
+ * #%L
+ * HAPI FHIR JPA Server - International Patient Summary (IPS)
+ * %%
+ * Copyright (C) 2014 - 2024 Smile CDR, Inc.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package ca.uhn.fhir.jpa.ips.jpa.section;
+
+import ca.uhn.fhir.jpa.ips.api.IpsSectionContext;
+import ca.uhn.fhir.jpa.ips.jpa.JpaSectionSearchStrategy;
+import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
+import ca.uhn.fhir.rest.param.TokenOrListParam;
+import ca.uhn.fhir.rest.param.TokenParam;
+import jakarta.annotation.Nonnull;
+import org.hl7.fhir.r4.model.MedicationRequest;
+
+public class MedicationSummaryJpaSectionSearchStrategyMedicationRequest
+ extends JpaSectionSearchStrategy {
+
+ @Override
+ public void massageResourceSearch(
+ @Nonnull IpsSectionContext theIpsSectionContext,
+ @Nonnull SearchParameterMap theSearchParameterMap) {
+ theSearchParameterMap.addInclude(MedicationRequest.INCLUDE_MEDICATION);
+ theSearchParameterMap.add(
+ MedicationRequest.SP_STATUS,
+ new TokenOrListParam()
+ .addOr(new TokenParam(
+ MedicationRequest.MedicationRequestStatus.ACTIVE.getSystem(),
+ MedicationRequest.MedicationRequestStatus.ACTIVE.toCode()))
+ .addOr(new TokenParam(
+ MedicationRequest.MedicationRequestStatus.UNKNOWN.getSystem(),
+ MedicationRequest.MedicationRequestStatus.UNKNOWN.toCode()))
+ .addOr(new TokenParam(
+ MedicationRequest.MedicationRequestStatus.ONHOLD.getSystem(),
+ MedicationRequest.MedicationRequestStatus.ONHOLD.toCode())));
+ }
+}
diff --git a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/MedicationSummaryJpaSectionSearchStrategyMedicationStatement.java b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/MedicationSummaryJpaSectionSearchStrategyMedicationStatement.java
new file mode 100644
index 00000000000..8089b76d1aa
--- /dev/null
+++ b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/MedicationSummaryJpaSectionSearchStrategyMedicationStatement.java
@@ -0,0 +1,54 @@
+/*-
+ * #%L
+ * HAPI FHIR JPA Server - International Patient Summary (IPS)
+ * %%
+ * Copyright (C) 2014 - 2024 Smile CDR, Inc.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package ca.uhn.fhir.jpa.ips.jpa.section;
+
+import ca.uhn.fhir.jpa.ips.api.IpsSectionContext;
+import ca.uhn.fhir.jpa.ips.jpa.JpaSectionSearchStrategy;
+import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
+import ca.uhn.fhir.rest.param.TokenOrListParam;
+import ca.uhn.fhir.rest.param.TokenParam;
+import jakarta.annotation.Nonnull;
+import org.hl7.fhir.r4.model.MedicationStatement;
+
+public class MedicationSummaryJpaSectionSearchStrategyMedicationStatement
+ extends JpaSectionSearchStrategy {
+
+ @Override
+ public void massageResourceSearch(
+ @Nonnull IpsSectionContext theIpsSectionContext,
+ @Nonnull SearchParameterMap theSearchParameterMap) {
+ theSearchParameterMap.addInclude(MedicationStatement.INCLUDE_MEDICATION);
+ theSearchParameterMap.add(
+ MedicationStatement.SP_STATUS,
+ new TokenOrListParam()
+ .addOr(new TokenParam(
+ MedicationStatement.MedicationStatementStatus.ACTIVE.getSystem(),
+ MedicationStatement.MedicationStatementStatus.ACTIVE.toCode()))
+ .addOr(new TokenParam(
+ MedicationStatement.MedicationStatementStatus.INTENDED.getSystem(),
+ MedicationStatement.MedicationStatementStatus.INTENDED.toCode()))
+ .addOr(new TokenParam(
+ MedicationStatement.MedicationStatementStatus.UNKNOWN.getSystem(),
+ MedicationStatement.MedicationStatementStatus.UNKNOWN.toCode()))
+ .addOr(new TokenParam(
+ MedicationStatement.MedicationStatementStatus.ONHOLD.getSystem(),
+ MedicationStatement.MedicationStatementStatus.ONHOLD.toCode())));
+ }
+}
diff --git a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/PlanOfCareJpaSectionSearchStrategy.java b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/PlanOfCareJpaSectionSearchStrategy.java
new file mode 100644
index 00000000000..4656eae3136
--- /dev/null
+++ b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/PlanOfCareJpaSectionSearchStrategy.java
@@ -0,0 +1,47 @@
+/*-
+ * #%L
+ * HAPI FHIR JPA Server - International Patient Summary (IPS)
+ * %%
+ * Copyright (C) 2014 - 2024 Smile CDR, Inc.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package ca.uhn.fhir.jpa.ips.jpa.section;
+
+import ca.uhn.fhir.jpa.ips.api.IpsSectionContext;
+import ca.uhn.fhir.jpa.ips.jpa.JpaSectionSearchStrategy;
+import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
+import ca.uhn.fhir.rest.param.TokenOrListParam;
+import ca.uhn.fhir.rest.param.TokenParam;
+import jakarta.annotation.Nonnull;
+import org.hl7.fhir.r4.model.CarePlan;
+
+public class PlanOfCareJpaSectionSearchStrategy extends JpaSectionSearchStrategy {
+
+ @Override
+ public void massageResourceSearch(
+ @Nonnull IpsSectionContext theIpsSectionContext,
+ @Nonnull SearchParameterMap theSearchParameterMap) {
+ theSearchParameterMap.add(
+ CarePlan.SP_STATUS,
+ new TokenOrListParam()
+ .addOr(new TokenParam(
+ CarePlan.CarePlanStatus.ACTIVE.getSystem(), CarePlan.CarePlanStatus.ACTIVE.toCode()))
+ .addOr(new TokenParam(
+ CarePlan.CarePlanStatus.ONHOLD.getSystem(), CarePlan.CarePlanStatus.ONHOLD.toCode()))
+ .addOr(new TokenParam(
+ CarePlan.CarePlanStatus.UNKNOWN.getSystem(),
+ CarePlan.CarePlanStatus.UNKNOWN.toCode())));
+ }
+}
diff --git a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/PregnancyJpaSectionSearchStrategy.java b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/PregnancyJpaSectionSearchStrategy.java
new file mode 100644
index 00000000000..20e6ee5d35f
--- /dev/null
+++ b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/PregnancyJpaSectionSearchStrategy.java
@@ -0,0 +1,75 @@
+/*-
+ * #%L
+ * HAPI FHIR JPA Server - International Patient Summary (IPS)
+ * %%
+ * Copyright (C) 2014 - 2024 Smile CDR, Inc.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package ca.uhn.fhir.jpa.ips.jpa.section;
+
+import ca.uhn.fhir.jpa.ips.api.IpsSectionContext;
+import ca.uhn.fhir.jpa.ips.jpa.JpaSectionSearchStrategy;
+import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
+import ca.uhn.fhir.rest.param.TokenOrListParam;
+import ca.uhn.fhir.rest.param.TokenParam;
+import jakarta.annotation.Nonnull;
+import org.hl7.fhir.r4.model.Observation;
+
+import static ca.uhn.fhir.jpa.term.api.ITermLoaderSvc.LOINC_URI;
+
+public class PregnancyJpaSectionSearchStrategy extends JpaSectionSearchStrategy {
+
+ public static final String LOINC_CODE_PREGNANCY_STATUS = "82810-3";
+ public static final String LOINC_CODE_NUMBER_BIRTHS_LIVE = "11636-8";
+ public static final String LOINC_CODE_NUMBER_BIRTHS_PRETERM = "11637-6";
+ public static final String LOINC_CODE_NUMBER_BIRTHS_STILL_LIVING = "11638-4";
+ public static final String LOINC_CODE_NUMBER_BIRTHS_TERM = "11639-2";
+ public static final String LOINC_CODE_NUMBER_BIRTHS_TOTAL = "11640-0";
+ public static final String LOINC_CODE_NUMBER_ABORTIONS = "11612-9";
+ public static final String LOINC_CODE_NUMBER_ABORTIONS_INDUCED = "11613-7";
+ public static final String LOINC_CODE_NUMBER_ABORTIONS_SPONTANEOUS = "11614-5";
+ public static final String LOINC_CODE_NUMBER_ECTOPIC_PREGNANCY = "33065-4";
+
+ @Override
+ public void massageResourceSearch(
+ @Nonnull IpsSectionContext theIpsSectionContext,
+ @Nonnull SearchParameterMap theSearchParameterMap) {
+ theSearchParameterMap.add(
+ Observation.SP_CODE,
+ new TokenOrListParam()
+ .addOr(new TokenParam(LOINC_URI, LOINC_CODE_PREGNANCY_STATUS))
+ .addOr(new TokenParam(LOINC_URI, LOINC_CODE_NUMBER_BIRTHS_LIVE))
+ .addOr(new TokenParam(LOINC_URI, LOINC_CODE_NUMBER_BIRTHS_PRETERM))
+ .addOr(new TokenParam(LOINC_URI, LOINC_CODE_NUMBER_BIRTHS_STILL_LIVING))
+ .addOr(new TokenParam(LOINC_URI, LOINC_CODE_NUMBER_BIRTHS_TERM))
+ .addOr(new TokenParam(LOINC_URI, LOINC_CODE_NUMBER_BIRTHS_TOTAL))
+ .addOr(new TokenParam(LOINC_URI, LOINC_CODE_NUMBER_ABORTIONS))
+ .addOr(new TokenParam(LOINC_URI, LOINC_CODE_NUMBER_ABORTIONS_INDUCED))
+ .addOr(new TokenParam(LOINC_URI, LOINC_CODE_NUMBER_ABORTIONS_SPONTANEOUS))
+ .addOr(new TokenParam(LOINC_URI, LOINC_CODE_NUMBER_ECTOPIC_PREGNANCY)));
+ }
+
+ @SuppressWarnings("RedundantIfStatement")
+ @Override
+ public boolean shouldInclude(
+ @Nonnull IpsSectionContext theIpsSectionContext, @Nonnull Observation theCandidate) {
+ // code filtering not yet applied
+ if (theCandidate.getStatus() == Observation.ObservationStatus.PRELIMINARY) {
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/ProblemListJpaSectionSearchStrategy.java b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/ProblemListJpaSectionSearchStrategy.java
new file mode 100644
index 00000000000..97e126049fc
--- /dev/null
+++ b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/ProblemListJpaSectionSearchStrategy.java
@@ -0,0 +1,47 @@
+/*-
+ * #%L
+ * HAPI FHIR JPA Server - International Patient Summary (IPS)
+ * %%
+ * Copyright (C) 2014 - 2024 Smile CDR, Inc.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package ca.uhn.fhir.jpa.ips.jpa.section;
+
+import ca.uhn.fhir.jpa.ips.api.IpsSectionContext;
+import ca.uhn.fhir.jpa.ips.jpa.JpaSectionSearchStrategy;
+import jakarta.annotation.Nonnull;
+import org.hl7.fhir.r4.model.Condition;
+
+public class ProblemListJpaSectionSearchStrategy extends JpaSectionSearchStrategy {
+
+ @SuppressWarnings("RedundantIfStatement")
+ @Override
+ public boolean shouldInclude(
+ @Nonnull IpsSectionContext theIpsSectionContext, @Nonnull Condition theCandidate) {
+ if (theCandidate
+ .getClinicalStatus()
+ .hasCoding("http://terminology.hl7.org/CodeSystem/condition-clinical", "inactive")
+ || theCandidate
+ .getClinicalStatus()
+ .hasCoding("http://terminology.hl7.org/CodeSystem/condition-clinical", "resolved")
+ || theCandidate
+ .getVerificationStatus()
+ .hasCoding("http://terminology.hl7.org/CodeSystem/condition-ver-status", "entered-in-error")) {
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/ProceduresJpaSectionSearchStrategy.java b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/ProceduresJpaSectionSearchStrategy.java
new file mode 100644
index 00000000000..3a76d79e6a8
--- /dev/null
+++ b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/ProceduresJpaSectionSearchStrategy.java
@@ -0,0 +1,40 @@
+/*-
+ * #%L
+ * HAPI FHIR JPA Server - International Patient Summary (IPS)
+ * %%
+ * Copyright (C) 2014 - 2024 Smile CDR, Inc.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package ca.uhn.fhir.jpa.ips.jpa.section;
+
+import ca.uhn.fhir.jpa.ips.api.IpsSectionContext;
+import ca.uhn.fhir.jpa.ips.jpa.JpaSectionSearchStrategy;
+import jakarta.annotation.Nonnull;
+import org.hl7.fhir.r4.model.Procedure;
+
+public class ProceduresJpaSectionSearchStrategy extends JpaSectionSearchStrategy {
+
+ @SuppressWarnings("RedundantIfStatement")
+ @Override
+ public boolean shouldInclude(
+ @Nonnull IpsSectionContext theIpsSectionContext, @Nonnull Procedure theCandidate) {
+ if (theCandidate.getStatus() == Procedure.ProcedureStatus.ENTEREDINERROR
+ || theCandidate.getStatus() == Procedure.ProcedureStatus.NOTDONE) {
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/SocialHistoryJpaSectionSearchStrategy.java b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/SocialHistoryJpaSectionSearchStrategy.java
new file mode 100644
index 00000000000..dc1fc63f6e9
--- /dev/null
+++ b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/SocialHistoryJpaSectionSearchStrategy.java
@@ -0,0 +1,54 @@
+/*-
+ * #%L
+ * HAPI FHIR JPA Server - International Patient Summary (IPS)
+ * %%
+ * Copyright (C) 2014 - 2024 Smile CDR, Inc.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package ca.uhn.fhir.jpa.ips.jpa.section;
+
+import ca.uhn.fhir.jpa.ips.api.IpsSectionContext;
+import ca.uhn.fhir.jpa.ips.jpa.JpaSectionSearchStrategy;
+import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
+import ca.uhn.fhir.rest.param.TokenOrListParam;
+import ca.uhn.fhir.rest.param.TokenParam;
+import jakarta.annotation.Nonnull;
+import org.hl7.fhir.r4.model.Observation;
+
+public class SocialHistoryJpaSectionSearchStrategy extends JpaSectionSearchStrategy {
+
+ @Override
+ public void massageResourceSearch(
+ @Nonnull IpsSectionContext theIpsSectionContext,
+ @Nonnull SearchParameterMap theSearchParameterMap) {
+ theSearchParameterMap.add(
+ Observation.SP_CATEGORY,
+ new TokenOrListParam()
+ .addOr(new TokenParam(
+ "http://terminology.hl7.org/CodeSystem/observation-category", "social-history")));
+ }
+
+ @SuppressWarnings("RedundantIfStatement")
+ @Override
+ public boolean shouldInclude(
+ @Nonnull IpsSectionContext theIpsSectionContext, @Nonnull Observation theCandidate) {
+ // code filtering not yet applied
+ if (theCandidate.getStatus() == Observation.ObservationStatus.PRELIMINARY) {
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/VitalSignsJpaSectionSearchStrategy.java b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/VitalSignsJpaSectionSearchStrategy.java
new file mode 100644
index 00000000000..cee888cda26
--- /dev/null
+++ b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/section/VitalSignsJpaSectionSearchStrategy.java
@@ -0,0 +1,51 @@
+/*-
+ * #%L
+ * HAPI FHIR JPA Server - International Patient Summary (IPS)
+ * %%
+ * Copyright (C) 2014 - 2024 Smile CDR, Inc.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package ca.uhn.fhir.jpa.ips.jpa.section;
+
+import ca.uhn.fhir.jpa.ips.api.IpsSectionContext;
+import ca.uhn.fhir.jpa.ips.jpa.JpaSectionSearchStrategy;
+import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
+import ca.uhn.fhir.rest.param.TokenOrListParam;
+import ca.uhn.fhir.rest.param.TokenParam;
+import jakarta.annotation.Nonnull;
+import org.hl7.fhir.r4.model.Observation;
+
+public class VitalSignsJpaSectionSearchStrategy extends JpaSectionSearchStrategy {
+
+ @Override
+ public void massageResourceSearch(
+ @Nonnull IpsSectionContext theIpsSectionContext,
+ @Nonnull SearchParameterMap theSearchParameterMap) {
+ theSearchParameterMap.add(
+ Observation.SP_CATEGORY,
+ new TokenOrListParam()
+ .addOr(new TokenParam(
+ "http://terminology.hl7.org/CodeSystem/observation-category", "vital-signs")));
+ }
+
+ @Override
+ public boolean shouldInclude(
+ @Nonnull IpsSectionContext theIpsSectionContext, @Nonnull Observation theCandidate) {
+ // code filtering not yet applied
+ return theCandidate.getStatus() != Observation.ObservationStatus.CANCELLED
+ && theCandidate.getStatus() != Observation.ObservationStatus.ENTEREDINERROR
+ && theCandidate.getStatus() != Observation.ObservationStatus.PRELIMINARY;
+ }
+}
diff --git a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/provider/IpsOperationProvider.java b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/provider/IpsOperationProvider.java
index 7ee56683b49..b946a9e5222 100644
--- a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/provider/IpsOperationProvider.java
+++ b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/provider/IpsOperationProvider.java
@@ -28,8 +28,14 @@ import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.TokenParam;
+import ca.uhn.fhir.util.FhirTerser;
+import ca.uhn.fhir.util.ValidateUtil;
+import jakarta.annotation.Nonnull;
+import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IIdType;
+import org.hl7.fhir.instance.model.api.IPrimitiveType;
+import org.thymeleaf.util.Validate;
public class IpsOperationProvider {
@@ -38,7 +44,8 @@ public class IpsOperationProvider {
/**
* Constructor
*/
- public IpsOperationProvider(IIpsGeneratorSvc theIpsGeneratorSvc) {
+ public IpsOperationProvider(@Nonnull IIpsGeneratorSvc theIpsGeneratorSvc) {
+ Validate.notNull(theIpsGeneratorSvc, "theIpsGeneratorSvc must not be null");
myIpsGeneratorSvc = theIpsGeneratorSvc;
}
@@ -54,9 +61,12 @@ public class IpsOperationProvider {
bundleType = BundleTypeEnum.DOCUMENT,
typeName = "Patient",
canonicalUrl = JpaConstants.SUMMARY_OPERATION_URL)
- public IBaseBundle patientInstanceSummary(@IdParam IIdType thePatientId, RequestDetails theRequestDetails) {
-
- return myIpsGeneratorSvc.generateIps(theRequestDetails, thePatientId);
+ public IBaseBundle patientInstanceSummary(
+ @IdParam IIdType thePatientId,
+ @OperationParam(name = "profile", min = 0, typeName = "uri") IPrimitiveType theProfile,
+ RequestDetails theRequestDetails) {
+ String profile = theProfile != null ? theProfile.getValueAsString() : null;
+ return myIpsGeneratorSvc.generateIps(theRequestDetails, thePatientId, profile);
}
/**
@@ -72,12 +82,20 @@ public class IpsOperationProvider {
typeName = "Patient",
canonicalUrl = JpaConstants.SUMMARY_OPERATION_URL)
public IBaseBundle patientTypeSummary(
+ @OperationParam(name = "profile", min = 0, typeName = "uri") IPrimitiveType theProfile,
@Description(
shortDefinition =
"When the logical id of the patient is not used, servers MAY choose to support patient selection based on provided identifier")
- @OperationParam(name = "identifier", min = 0, max = 1)
- TokenParam thePatientIdentifier,
+ @OperationParam(name = "identifier", min = 1, max = 1, typeName = "Identifier")
+ IBase thePatientIdentifier,
RequestDetails theRequestDetails) {
- return myIpsGeneratorSvc.generateIps(theRequestDetails, thePatientIdentifier);
+ String profile = theProfile != null ? theProfile.getValueAsString() : null;
+
+ ValidateUtil.isTrueOrThrowInvalidRequest(thePatientIdentifier != null, "No ID or identifier supplied");
+
+ FhirTerser terser = theRequestDetails.getFhirContext().newTerser();
+ String system = terser.getSinglePrimitiveValueOrNull(thePatientIdentifier, "system");
+ String value = terser.getSinglePrimitiveValueOrNull(thePatientIdentifier, "value");
+ return myIpsGeneratorSvc.generateIps(theRequestDetails, new TokenParam(system, value), profile);
}
}
diff --git a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/strategy/AllergyIntoleranceNoInfoR4Generator.java b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/strategy/AllergyIntoleranceNoInfoR4Generator.java
new file mode 100644
index 00000000000..23d048e264c
--- /dev/null
+++ b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/strategy/AllergyIntoleranceNoInfoR4Generator.java
@@ -0,0 +1,46 @@
+/*-
+ * #%L
+ * HAPI FHIR JPA Server - International Patient Summary (IPS)
+ * %%
+ * Copyright (C) 2014 - 2024 Smile CDR, Inc.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package ca.uhn.fhir.jpa.ips.strategy;
+
+import ca.uhn.fhir.jpa.ips.api.INoInfoGenerator;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.hl7.fhir.instance.model.api.IIdType;
+import org.hl7.fhir.r4.model.AllergyIntolerance;
+import org.hl7.fhir.r4.model.CodeableConcept;
+import org.hl7.fhir.r4.model.Coding;
+import org.hl7.fhir.r4.model.Reference;
+
+public class AllergyIntoleranceNoInfoR4Generator implements INoInfoGenerator {
+ @Override
+ public IBaseResource generate(IIdType theSubjectId) {
+ AllergyIntolerance allergy = new AllergyIntolerance();
+ allergy.setCode(new CodeableConcept()
+ .addCoding(new Coding()
+ .setCode("no-allergy-info")
+ .setSystem("http://hl7.org/fhir/uv/ips/CodeSystem/absent-unknown-uv-ips")
+ .setDisplay("No information about allergies")))
+ .setPatient(new Reference(theSubjectId))
+ .setClinicalStatus(new CodeableConcept()
+ .addCoding(new Coding()
+ .setCode("active")
+ .setSystem("http://terminology.hl7.org/CodeSystem/allergyintolerance-clinical")));
+ return allergy;
+ }
+}
diff --git a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/strategy/BaseIpsGenerationStrategy.java b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/strategy/BaseIpsGenerationStrategy.java
new file mode 100644
index 00000000000..12621839568
--- /dev/null
+++ b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/strategy/BaseIpsGenerationStrategy.java
@@ -0,0 +1,130 @@
+/*-
+ * #%L
+ * HAPI FHIR JPA Server - International Patient Summary (IPS)
+ * %%
+ * Copyright (C) 2014 - 2024 Smile CDR, Inc.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package ca.uhn.fhir.jpa.ips.strategy;
+
+import ca.uhn.fhir.jpa.ips.api.IIpsGenerationStrategy;
+import ca.uhn.fhir.jpa.ips.api.ISectionResourceSupplier;
+import ca.uhn.fhir.jpa.ips.api.IpsContext;
+import ca.uhn.fhir.jpa.ips.api.Section;
+import com.google.common.collect.Lists;
+import jakarta.annotation.Nonnull;
+import jakarta.annotation.Nullable;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.hl7.fhir.instance.model.api.IIdType;
+import org.hl7.fhir.r4.model.Address;
+import org.hl7.fhir.r4.model.Composition;
+import org.hl7.fhir.r4.model.IdType;
+import org.hl7.fhir.r4.model.Organization;
+import org.thymeleaf.util.Validate;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@SuppressWarnings({"HttpUrlsUsage"})
+public abstract class BaseIpsGenerationStrategy implements IIpsGenerationStrategy {
+
+ public static final String DEFAULT_IPS_NARRATIVES_PROPERTIES =
+ "classpath:ca/uhn/fhir/jpa/ips/narrative/ips-narratives.properties";
+ private final List mySections = new ArrayList<>();
+ private final Map mySectionToResourceSupplier = new HashMap<>();
+
+ /**
+ * Constructor
+ */
+ public BaseIpsGenerationStrategy() {
+ super();
+ }
+
+ @Override
+ public String getBundleProfile() {
+ return "http://hl7.org/fhir/uv/ips/StructureDefinition/Bundle-uv-ips";
+ }
+
+ @Nonnull
+ @Override
+ public final List getSections() {
+ return Collections.unmodifiableList(mySections);
+ }
+
+ @Nonnull
+ @Override
+ public ISectionResourceSupplier getSectionResourceSupplier(@Nonnull Section theSection) {
+ return mySectionToResourceSupplier.get(theSection);
+ }
+
+ /**
+ * This should be called once per section to add a section for inclusion in generated IPS documents.
+ * It should include a {@link Section} which contains static details about the section, and a {@link ISectionResourceSupplier}
+ * which is used to fetch resources for inclusion at runtime.
+ *
+ * @param theSection Contains static details about the section, such as the resource types it can contain, and a title.
+ * @param theSectionResourceSupplier The strategy object which will be used to supply content for this section at runtime.
+ */
+ public void addSection(Section theSection, ISectionResourceSupplier theSectionResourceSupplier) {
+ Validate.notNull(theSection, "theSection must not be null");
+ Validate.notNull(theSectionResourceSupplier, "theSectionResourceSupplier must not be null");
+ Validate.isTrue(
+ !mySectionToResourceSupplier.containsKey(theSection),
+ "A section with the given profile already exists");
+
+ mySections.add(theSection);
+ mySectionToResourceSupplier.put(theSection, theSectionResourceSupplier);
+ }
+
+ @Override
+ public List getNarrativePropertyFiles() {
+ return Lists.newArrayList(DEFAULT_IPS_NARRATIVES_PROPERTIES);
+ }
+
+ @Override
+ public IBaseResource createAuthor() {
+ Organization organization = new Organization();
+ organization
+ .setName("eHealthLab - University of Cyprus")
+ .addAddress(new Address()
+ .addLine("1 University Avenue")
+ .setCity("Nicosia")
+ .setPostalCode("2109")
+ .setCountry("CY"))
+ .setId(IdType.newRandomUuid());
+ return organization;
+ }
+
+ @Override
+ public String createTitle(IpsContext theContext) {
+ return "Patient Summary as of "
+ + DateTimeFormatter.ofPattern("MM/dd/yyyy").format(LocalDate.now());
+ }
+
+ @Override
+ public String createConfidentiality(IpsContext theIpsContext) {
+ return Composition.DocumentConfidentiality.N.toCode();
+ }
+
+ @Override
+ public IIdType massageResourceId(@Nullable IpsContext theIpsContext, @Nonnull IBaseResource theResource) {
+ return null;
+ }
+}
diff --git a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/strategy/DefaultIpsGenerationStrategy.java b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/strategy/DefaultIpsGenerationStrategy.java
deleted file mode 100644
index eccc26fd6f6..00000000000
--- a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/strategy/DefaultIpsGenerationStrategy.java
+++ /dev/null
@@ -1,446 +0,0 @@
-/*-
- * #%L
- * HAPI FHIR JPA Server - International Patient Summary (IPS)
- * %%
- * Copyright (C) 2014 - 2024 Smile CDR, Inc.
- * %%
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * #L%
- */
-package ca.uhn.fhir.jpa.ips.strategy;
-
-import ca.uhn.fhir.jpa.ips.api.IIpsGenerationStrategy;
-import ca.uhn.fhir.jpa.ips.api.IpsContext;
-import ca.uhn.fhir.jpa.ips.api.SectionRegistry;
-import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
-import ca.uhn.fhir.model.api.Include;
-import ca.uhn.fhir.rest.api.SortOrderEnum;
-import ca.uhn.fhir.rest.api.SortSpec;
-import ca.uhn.fhir.rest.param.TokenOrListParam;
-import ca.uhn.fhir.rest.param.TokenParam;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-import jakarta.annotation.Nonnull;
-import jakarta.annotation.Nullable;
-import org.hl7.fhir.instance.model.api.IBaseResource;
-import org.hl7.fhir.instance.model.api.IIdType;
-import org.hl7.fhir.r4.model.*;
-
-import java.time.LocalDate;
-import java.time.format.DateTimeFormatter;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-
-import static ca.uhn.fhir.jpa.term.api.ITermLoaderSvc.LOINC_URI;
-
-@SuppressWarnings({"EnhancedSwitchMigration", "HttpUrlsUsage"})
-public class DefaultIpsGenerationStrategy implements IIpsGenerationStrategy {
-
- public static final String DEFAULT_IPS_NARRATIVES_PROPERTIES =
- "classpath:ca/uhn/fhir/jpa/ips/narrative/ips-narratives.properties";
- private SectionRegistry mySectionRegistry;
-
- /**
- * Constructor
- */
- public DefaultIpsGenerationStrategy() {
- setSectionRegistry(new SectionRegistry());
- }
-
- @Override
- public SectionRegistry getSectionRegistry() {
- return mySectionRegistry;
- }
-
- public void setSectionRegistry(SectionRegistry theSectionRegistry) {
- if (!theSectionRegistry.isInitialized()) {
- theSectionRegistry.initialize();
- }
- mySectionRegistry = theSectionRegistry;
- }
-
- @Override
- public List getNarrativePropertyFiles() {
- return Lists.newArrayList(DEFAULT_IPS_NARRATIVES_PROPERTIES);
- }
-
- @Override
- public IBaseResource createAuthor() {
- Organization organization = new Organization();
- organization
- .setName("eHealthLab - University of Cyprus")
- .addAddress(new Address()
- .addLine("1 University Avenue")
- .setCity("Nicosia")
- .setPostalCode("2109")
- .setCountry("CY"))
- .setId(IdType.newRandomUuid());
- return organization;
- }
-
- @Override
- public String createTitle(IpsContext theContext) {
- return "Patient Summary as of "
- + DateTimeFormatter.ofPattern("MM/dd/yyyy").format(LocalDate.now());
- }
-
- @Override
- public String createConfidentiality(IpsContext theIpsContext) {
- return Composition.DocumentConfidentiality.N.toCode();
- }
-
- @Override
- public IIdType massageResourceId(@Nullable IpsContext theIpsContext, @Nonnull IBaseResource theResource) {
- return IdType.newRandomUuid();
- }
-
- @Override
- public void massageResourceSearch(
- IpsContext.IpsSectionContext theIpsSectionContext, SearchParameterMap theSearchParameterMap) {
- switch (theIpsSectionContext.getSection()) {
- case ALLERGY_INTOLERANCE:
- case PROBLEM_LIST:
- case PROCEDURES:
- case MEDICAL_DEVICES:
- case ILLNESS_HISTORY:
- case FUNCTIONAL_STATUS:
- return;
- case IMMUNIZATIONS:
- theSearchParameterMap.setSort(new SortSpec(Immunization.SP_DATE).setOrder(SortOrderEnum.DESC));
- return;
- case VITAL_SIGNS:
- if (theIpsSectionContext.getResourceType().equals(ResourceType.Observation.name())) {
- theSearchParameterMap.add(
- Observation.SP_CATEGORY,
- new TokenOrListParam()
- .addOr(new TokenParam(
- "http://terminology.hl7.org/CodeSystem/observation-category",
- "vital-signs")));
- return;
- }
- break;
- case SOCIAL_HISTORY:
- if (theIpsSectionContext.getResourceType().equals(ResourceType.Observation.name())) {
- theSearchParameterMap.add(
- Observation.SP_CATEGORY,
- new TokenOrListParam()
- .addOr(new TokenParam(
- "http://terminology.hl7.org/CodeSystem/observation-category",
- "social-history")));
- return;
- }
- break;
- case DIAGNOSTIC_RESULTS:
- if (theIpsSectionContext.getResourceType().equals(ResourceType.DiagnosticReport.name())) {
- return;
- } else if (theIpsSectionContext.getResourceType().equals(ResourceType.Observation.name())) {
- theSearchParameterMap.add(
- Observation.SP_CATEGORY,
- new TokenOrListParam()
- .addOr(new TokenParam(
- "http://terminology.hl7.org/CodeSystem/observation-category",
- "laboratory")));
- return;
- }
- break;
- case PREGNANCY:
- if (theIpsSectionContext.getResourceType().equals(ResourceType.Observation.name())) {
- theSearchParameterMap.add(
- Observation.SP_CODE,
- new TokenOrListParam()
- .addOr(new TokenParam(LOINC_URI, "82810-3"))
- .addOr(new TokenParam(LOINC_URI, "11636-8"))
- .addOr(new TokenParam(LOINC_URI, "11637-6"))
- .addOr(new TokenParam(LOINC_URI, "11638-4"))
- .addOr(new TokenParam(LOINC_URI, "11639-2"))
- .addOr(new TokenParam(LOINC_URI, "11640-0"))
- .addOr(new TokenParam(LOINC_URI, "11612-9"))
- .addOr(new TokenParam(LOINC_URI, "11613-7"))
- .addOr(new TokenParam(LOINC_URI, "11614-5"))
- .addOr(new TokenParam(LOINC_URI, "33065-4")));
- return;
- }
- break;
- case MEDICATION_SUMMARY:
- if (theIpsSectionContext.getResourceType().equals(ResourceType.MedicationStatement.name())) {
- theSearchParameterMap.add(
- MedicationStatement.SP_STATUS,
- new TokenOrListParam()
- .addOr(new TokenParam(
- MedicationStatement.MedicationStatementStatus.ACTIVE.getSystem(),
- MedicationStatement.MedicationStatementStatus.ACTIVE.toCode()))
- .addOr(new TokenParam(
- MedicationStatement.MedicationStatementStatus.INTENDED.getSystem(),
- MedicationStatement.MedicationStatementStatus.INTENDED.toCode()))
- .addOr(new TokenParam(
- MedicationStatement.MedicationStatementStatus.UNKNOWN.getSystem(),
- MedicationStatement.MedicationStatementStatus.UNKNOWN.toCode()))
- .addOr(new TokenParam(
- MedicationStatement.MedicationStatementStatus.ONHOLD.getSystem(),
- MedicationStatement.MedicationStatementStatus.ONHOLD.toCode())));
- return;
- } else if (theIpsSectionContext.getResourceType().equals(ResourceType.MedicationRequest.name())) {
- theSearchParameterMap.add(
- MedicationRequest.SP_STATUS,
- new TokenOrListParam()
- .addOr(new TokenParam(
- MedicationRequest.MedicationRequestStatus.ACTIVE.getSystem(),
- MedicationRequest.MedicationRequestStatus.ACTIVE.toCode()))
- .addOr(new TokenParam(
- MedicationRequest.MedicationRequestStatus.UNKNOWN.getSystem(),
- MedicationRequest.MedicationRequestStatus.UNKNOWN.toCode()))
- .addOr(new TokenParam(
- MedicationRequest.MedicationRequestStatus.ONHOLD.getSystem(),
- MedicationRequest.MedicationRequestStatus.ONHOLD.toCode())));
- return;
- } else if (theIpsSectionContext
- .getResourceType()
- .equals(ResourceType.MedicationAdministration.name())) {
- theSearchParameterMap.add(
- MedicationAdministration.SP_STATUS,
- new TokenOrListParam()
- .addOr(new TokenParam(
- MedicationAdministration.MedicationAdministrationStatus.INPROGRESS
- .getSystem(),
- MedicationAdministration.MedicationAdministrationStatus.INPROGRESS
- .toCode()))
- .addOr(new TokenParam(
- MedicationAdministration.MedicationAdministrationStatus.UNKNOWN.getSystem(),
- MedicationAdministration.MedicationAdministrationStatus.UNKNOWN.toCode()))
- .addOr(new TokenParam(
- MedicationAdministration.MedicationAdministrationStatus.ONHOLD.getSystem(),
- MedicationAdministration.MedicationAdministrationStatus.ONHOLD.toCode())));
- return;
- } else if (theIpsSectionContext.getResourceType().equals(ResourceType.MedicationDispense.name())) {
- theSearchParameterMap.add(
- MedicationDispense.SP_STATUS,
- new TokenOrListParam()
- .addOr(new TokenParam(
- MedicationDispense.MedicationDispenseStatus.INPROGRESS.getSystem(),
- MedicationDispense.MedicationDispenseStatus.INPROGRESS.toCode()))
- .addOr(new TokenParam(
- MedicationDispense.MedicationDispenseStatus.UNKNOWN.getSystem(),
- MedicationDispense.MedicationDispenseStatus.UNKNOWN.toCode()))
- .addOr(new TokenParam(
- MedicationDispense.MedicationDispenseStatus.ONHOLD.getSystem(),
- MedicationDispense.MedicationDispenseStatus.ONHOLD.toCode())));
- return;
- }
- break;
- case PLAN_OF_CARE:
- if (theIpsSectionContext.getResourceType().equals(ResourceType.CarePlan.name())) {
- theSearchParameterMap.add(
- CarePlan.SP_STATUS,
- new TokenOrListParam()
- .addOr(new TokenParam(
- CarePlan.CarePlanStatus.ACTIVE.getSystem(),
- CarePlan.CarePlanStatus.ACTIVE.toCode()))
- .addOr(new TokenParam(
- CarePlan.CarePlanStatus.ONHOLD.getSystem(),
- CarePlan.CarePlanStatus.ONHOLD.toCode()))
- .addOr(new TokenParam(
- CarePlan.CarePlanStatus.UNKNOWN.getSystem(),
- CarePlan.CarePlanStatus.UNKNOWN.toCode())));
- return;
- }
- break;
- case ADVANCE_DIRECTIVES:
- if (theIpsSectionContext.getResourceType().equals(ResourceType.Consent.name())) {
- theSearchParameterMap.add(
- Consent.SP_STATUS,
- new TokenOrListParam()
- .addOr(new TokenParam(
- Consent.ConsentState.ACTIVE.getSystem(),
- Consent.ConsentState.ACTIVE.toCode())));
- return;
- }
- break;
- }
-
- // Shouldn't happen: This means none of the above switches handled the Section+resourceType combination
- assert false
- : "Don't know how to handle " + theIpsSectionContext.getSection() + "/"
- + theIpsSectionContext.getResourceType();
- }
-
- @Nonnull
- @Override
- public Set provideResourceSearchIncludes(IpsContext.IpsSectionContext theIpsSectionContext) {
- switch (theIpsSectionContext.getSection()) {
- case MEDICATION_SUMMARY:
- if (ResourceType.MedicationStatement.name().equals(theIpsSectionContext.getResourceType())) {
- return Sets.newHashSet(MedicationStatement.INCLUDE_MEDICATION);
- }
- if (ResourceType.MedicationRequest.name().equals(theIpsSectionContext.getResourceType())) {
- return Sets.newHashSet(MedicationRequest.INCLUDE_MEDICATION);
- }
- if (ResourceType.MedicationAdministration.name().equals(theIpsSectionContext.getResourceType())) {
- return Sets.newHashSet(MedicationAdministration.INCLUDE_MEDICATION);
- }
- if (ResourceType.MedicationDispense.name().equals(theIpsSectionContext.getResourceType())) {
- return Sets.newHashSet(MedicationDispense.INCLUDE_MEDICATION);
- }
- break;
- case MEDICAL_DEVICES:
- if (ResourceType.DeviceUseStatement.name().equals(theIpsSectionContext.getResourceType())) {
- return Sets.newHashSet(DeviceUseStatement.INCLUDE_DEVICE);
- }
- break;
- case IMMUNIZATIONS:
- if (ResourceType.Immunization.name().equals(theIpsSectionContext.getResourceType())) {
- return Sets.newHashSet(Immunization.INCLUDE_MANUFACTURER);
- }
- break;
- case ALLERGY_INTOLERANCE:
- case PROBLEM_LIST:
- case PROCEDURES:
- case DIAGNOSTIC_RESULTS:
- case VITAL_SIGNS:
- case ILLNESS_HISTORY:
- case PREGNANCY:
- case SOCIAL_HISTORY:
- case FUNCTIONAL_STATUS:
- case PLAN_OF_CARE:
- case ADVANCE_DIRECTIVES:
- break;
- }
- return Collections.emptySet();
- }
-
- @SuppressWarnings("EnhancedSwitchMigration")
- @Override
- public boolean shouldInclude(IpsContext.IpsSectionContext theIpsSectionContext, IBaseResource theCandidate) {
-
- switch (theIpsSectionContext.getSection()) {
- case MEDICATION_SUMMARY:
- case PLAN_OF_CARE:
- case ADVANCE_DIRECTIVES:
- return true;
- case ALLERGY_INTOLERANCE:
- if (theIpsSectionContext.getResourceType().equals(ResourceType.AllergyIntolerance.name())) {
- AllergyIntolerance allergyIntolerance = (AllergyIntolerance) theCandidate;
- return !allergyIntolerance
- .getClinicalStatus()
- .hasCoding(
- "http://terminology.hl7.org/CodeSystem/allergyintolerance-clinical",
- "inactive")
- && !allergyIntolerance
- .getClinicalStatus()
- .hasCoding(
- "http://terminology.hl7.org/CodeSystem/allergyintolerance-clinical",
- "resolved")
- && !allergyIntolerance
- .getVerificationStatus()
- .hasCoding(
- "http://terminology.hl7.org/CodeSystem/allergyintolerance-verification",
- "entered-in-error");
- }
- break;
- case PROBLEM_LIST:
- if (theIpsSectionContext.getResourceType().equals(ResourceType.Condition.name())) {
- Condition prob = (Condition) theCandidate;
- return !prob.getClinicalStatus()
- .hasCoding("http://terminology.hl7.org/CodeSystem/condition-clinical", "inactive")
- && !prob.getClinicalStatus()
- .hasCoding("http://terminology.hl7.org/CodeSystem/condition-clinical", "resolved")
- && !prob.getVerificationStatus()
- .hasCoding(
- "http://terminology.hl7.org/CodeSystem/condition-ver-status",
- "entered-in-error");
- }
- break;
- case IMMUNIZATIONS:
- if (theIpsSectionContext.getResourceType().equals(ResourceType.Immunization.name())) {
- Immunization immunization = (Immunization) theCandidate;
- return immunization.getStatus() != Immunization.ImmunizationStatus.ENTEREDINERROR;
- }
- break;
- case PROCEDURES:
- if (theIpsSectionContext.getResourceType().equals(ResourceType.Procedure.name())) {
- Procedure proc = (Procedure) theCandidate;
- return proc.getStatus() != Procedure.ProcedureStatus.ENTEREDINERROR
- && proc.getStatus() != Procedure.ProcedureStatus.NOTDONE;
- }
- break;
- case MEDICAL_DEVICES:
- if (theIpsSectionContext.getResourceType().equals(ResourceType.DeviceUseStatement.name())) {
- DeviceUseStatement deviceUseStatement = (DeviceUseStatement) theCandidate;
- return deviceUseStatement.getStatus() != DeviceUseStatement.DeviceUseStatementStatus.ENTEREDINERROR;
- }
- return true;
- case DIAGNOSTIC_RESULTS:
- if (theIpsSectionContext.getResourceType().equals(ResourceType.DiagnosticReport.name())) {
- return true;
- }
- if (theIpsSectionContext.getResourceType().equals(ResourceType.Observation.name())) {
- // code filtering not yet applied
- Observation observation = (Observation) theCandidate;
- return (observation.getStatus() != Observation.ObservationStatus.PRELIMINARY);
- }
- break;
- case VITAL_SIGNS:
- if (theIpsSectionContext.getResourceType().equals(ResourceType.Observation.name())) {
- // code filtering not yet applied
- Observation observation = (Observation) theCandidate;
- return (observation.getStatus() != Observation.ObservationStatus.PRELIMINARY);
- }
- break;
- case ILLNESS_HISTORY:
- if (theIpsSectionContext.getResourceType().equals(ResourceType.Condition.name())) {
- Condition prob = (Condition) theCandidate;
- if (prob.getVerificationStatus()
- .hasCoding(
- "http://terminology.hl7.org/CodeSystem/condition-ver-status", "entered-in-error")) {
- return false;
- } else {
- return prob.getClinicalStatus()
- .hasCoding(
- "http://terminology.hl7.org/CodeSystem/condition-clinical", "inactive")
- || prob.getClinicalStatus()
- .hasCoding(
- "http://terminology.hl7.org/CodeSystem/condition-clinical", "resolved")
- || prob.getClinicalStatus()
- .hasCoding(
- "http://terminology.hl7.org/CodeSystem/condition-clinical",
- "remission");
- }
- }
- break;
- case PREGNANCY:
- if (theIpsSectionContext.getResourceType().equals(ResourceType.Observation.name())) {
- // code filtering not yet applied
- Observation observation = (Observation) theCandidate;
- return (observation.getStatus() != Observation.ObservationStatus.PRELIMINARY);
- }
- break;
- case SOCIAL_HISTORY:
- if (theIpsSectionContext.getResourceType().equals(ResourceType.Observation.name())) {
- // code filtering not yet applied
- Observation observation = (Observation) theCandidate;
- return (observation.getStatus() != Observation.ObservationStatus.PRELIMINARY);
- }
- break;
- case FUNCTIONAL_STATUS:
- if (theIpsSectionContext.getResourceType().equals(ResourceType.ClinicalImpression.name())) {
- ClinicalImpression clinicalImpression = (ClinicalImpression) theCandidate;
- return clinicalImpression.getStatus() != ClinicalImpression.ClinicalImpressionStatus.INPROGRESS
- && clinicalImpression.getStatus()
- != ClinicalImpression.ClinicalImpressionStatus.ENTEREDINERROR;
- }
- break;
- }
-
- return true;
- }
-}
diff --git a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/strategy/MedicationNoInfoR4Generator.java b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/strategy/MedicationNoInfoR4Generator.java
new file mode 100644
index 00000000000..9d6861a160d
--- /dev/null
+++ b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/strategy/MedicationNoInfoR4Generator.java
@@ -0,0 +1,45 @@
+/*-
+ * #%L
+ * HAPI FHIR JPA Server - International Patient Summary (IPS)
+ * %%
+ * Copyright (C) 2014 - 2024 Smile CDR, Inc.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package ca.uhn.fhir.jpa.ips.strategy;
+
+import ca.uhn.fhir.jpa.ips.api.INoInfoGenerator;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.hl7.fhir.instance.model.api.IIdType;
+import org.hl7.fhir.r4.model.CodeableConcept;
+import org.hl7.fhir.r4.model.Coding;
+import org.hl7.fhir.r4.model.MedicationStatement;
+import org.hl7.fhir.r4.model.Reference;
+
+public class MedicationNoInfoR4Generator implements INoInfoGenerator {
+ @Override
+ public IBaseResource generate(IIdType theSubjectId) {
+ MedicationStatement medication = new MedicationStatement();
+ // setMedicationCodeableConcept is not available
+ medication
+ .setMedication(new CodeableConcept()
+ .addCoding(new Coding()
+ .setCode("no-medication-info")
+ .setSystem("http://hl7.org/fhir/uv/ips/CodeSystem/absent-unknown-uv-ips")
+ .setDisplay("No information about medications")))
+ .setSubject(new Reference(theSubjectId))
+ .setStatus(MedicationStatement.MedicationStatementStatus.UNKNOWN);
+ return medication;
+ }
+}
diff --git a/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/strategy/ProblemNoInfoR4Generator.java b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/strategy/ProblemNoInfoR4Generator.java
new file mode 100644
index 00000000000..4d69eb1312a
--- /dev/null
+++ b/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/strategy/ProblemNoInfoR4Generator.java
@@ -0,0 +1,47 @@
+/*-
+ * #%L
+ * HAPI FHIR JPA Server - International Patient Summary (IPS)
+ * %%
+ * Copyright (C) 2014 - 2024 Smile CDR, Inc.
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package ca.uhn.fhir.jpa.ips.strategy;
+
+import ca.uhn.fhir.jpa.ips.api.INoInfoGenerator;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.hl7.fhir.instance.model.api.IIdType;
+import org.hl7.fhir.r4.model.CodeableConcept;
+import org.hl7.fhir.r4.model.Coding;
+import org.hl7.fhir.r4.model.Condition;
+import org.hl7.fhir.r4.model.Reference;
+
+public class ProblemNoInfoR4Generator implements INoInfoGenerator {
+ @Override
+ public IBaseResource generate(IIdType theSubjectId) {
+ Condition condition = new Condition();
+ condition
+ .setCode(new CodeableConcept()
+ .addCoding(new Coding()
+ .setCode("no-problem-info")
+ .setSystem("http://hl7.org/fhir/uv/ips/CodeSystem/absent-unknown-uv-ips")
+ .setDisplay("No information about problems")))
+ .setSubject(new Reference(theSubjectId))
+ .setClinicalStatus(new CodeableConcept()
+ .addCoding(new Coding()
+ .setCode("active")
+ .setSystem("http://terminology.hl7.org/CodeSystem/condition-clinical")));
+ return condition;
+ }
+}
diff --git a/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/advancedirectives.html b/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/advancedirectives.html
index c9bfed14888..1855612b60b 100644
--- a/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/advancedirectives.html
+++ b/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/advancedirectives.html
@@ -6,8 +6,8 @@ Action Controlled: Consent.provision.action[x].{ text || coding[x].display (sepa
Date: Consent.dateTime
*/-->
+
Advance Directives
- Advance Directives
Scope |
@@ -21,9 +21,9 @@ Date: Consent.dateTime
- Scope |
+ Scope |
Status |
- Action Controlled |
+ Action Controlled |
Date |
diff --git a/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/allergyintolerance.html b/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/allergyintolerance.html
index 5036bfc713d..728afbbd7a0 100644
--- a/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/allergyintolerance.html
+++ b/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/allergyintolerance.html
@@ -8,8 +8,8 @@ Severity: AllergyIntolerance.reaction.severity[x].code (separated by
)
Comments: AllergyIntolerance.note[x].text (separated by
)
*/-->
+
Allergies And Intolerances
- Allergies And Intolerances
Allergen |
@@ -27,10 +27,10 @@ Comments: AllergyIntolerance.note[x].text (separated by
)
Allergen |
Status |
- Category |
- Reaction |
- Severity |
- Comments |
+ Category |
+ Reaction |
+ Severity |
+ Comments |
Onset |
diff --git a/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/diagnosticresults.html b/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/diagnosticresults.html
index 79c6015430a..560007cd56d 100644
--- a/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/diagnosticresults.html
+++ b/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/diagnosticresults.html
@@ -14,61 +14,75 @@ Code: DiagnosticReport.code.text || DiagnosticReport.code.coding[x].display (sep
Date: DiagnosticReport.effectiveDateTime || DiagnosticReport.effectivePeriod.start
*/-->
-
- Diagnostic Results: Observations
-
-
- Code |
- Result |
- Unit |
- Interpretation |
- Reference Range |
- Comments |
- Date |
-
-
-
-
-
-
-
-
- Code |
- Result |
- Unit |
- Interpretation |
- Reference Range |
- Comments |
- Date |
-
-
-
-
-
-
-
+
+ Diagnostic Results: Observations
+
+
+
+ Code |
+ Result |
+ Unit |
+ Interpretation |
+ Reference Range |
+ Comments |
+ Date |
+
+
+
+
+
+
+
+
+ Code
+ |
+ Result |
+ Unit |
+
+ Interpretation
+ |
+
+ Reference
+ Range
+ |
+ Comments |
+ Date |
+
+
+
+
+
+
+
+
-
- Diagnostic Results: Diagnostic Reports
-
-
- Code |
- Date |
-
-
-
-
-
-
-
-
- Device |
- Date |
-
-
-
-
-
-
-
+
+ Diagnostic Results: Diagnostic Reports
+
+
+
+ Code |
+ Date |
+
+
+
+
+
+
+
+
+ Device
+ |
+ Date |
+
+
+
+
+
+
+
+
diff --git a/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/functionalstatus.html b/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/functionalstatus.html
index 30addeade65..a569efadf7b 100644
--- a/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/functionalstatus.html
+++ b/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/functionalstatus.html
@@ -7,8 +7,8 @@ Comments: ClinicalImpression.note[x].text (separated by
)
Date: ClinicalImpression.effectiveDateTime || ClinicalImpression.effectivePeriod.start
*/-->
+
Functional Status
- Functional Status
Assessment |
@@ -23,11 +23,11 @@ Date: ClinicalImpression.effectiveDateTime || ClinicalImpression.effectivePeriod
- Assessment |
+ Assessment |
Status |
Finding |
- Comments |
- Date |
+ Comments |
+ Date |
diff --git a/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/historyofprocedures.html b/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/historyofprocedures.html
index 6e1aa367549..9e4bfcc6a96 100644
--- a/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/historyofprocedures.html
+++ b/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/historyofprocedures.html
@@ -5,8 +5,8 @@ Comments: Procedure.note[x].text(separated by
)
Date: Procedure.performedDateTime || Procedure.performedPeriod.start && “-“ && Procedure.performedPeriod.end || Procedure.performedAge || Procedure.performedRange.low && “-“ && Procedure.performedRange.high || Procedure.performedString
*/-->
+
History Of Procedures
- History Of Procedures
Procedure |
@@ -19,9 +19,9 @@ Date: Procedure.performedDateTime || Procedure.performedPeriod.start && “-“
- Procedure |
- Comments |
- Date |
+ Procedure |
+ Comments |
+ Date |
diff --git a/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/immunizations.html b/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/immunizations.html
index 2386d815b38..e21ca2e1ae2 100644
--- a/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/immunizations.html
+++ b/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/immunizations.html
@@ -9,8 +9,8 @@ Comments: Immunization.note[x].text (separated by
)
Date: Immunization.occurrenceDateTime || Immunization.occurrenceString
*/-->
+
Immunizations
- Immunizations
Immunization |
@@ -27,13 +27,13 @@ Date: Immunization.occurrenceDateTime || Immunization.occurrenceString
- Immunization |
+ Immunization |
Status |
- Comments |
- Manufacturer |
+ Comments |
+ Manufacturer |
Lot Number |
- Comments |
- Date |
+ Comments |
+ Date |
diff --git a/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/medicaldevices.html b/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/medicaldevices.html
index fe23a21d5fd..73902b88bab 100644
--- a/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/medicaldevices.html
+++ b/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/medicaldevices.html
@@ -6,8 +6,8 @@ Comments: DeviceUseStatement.note[x].text (separated by
)
Date Recorded: DeviceUseStatement.recordedDateTime
*/-->
+
Medical Devices
- Medical Devices
Device |
@@ -21,10 +21,10 @@ Date Recorded: DeviceUseStatement.recordedDateTime
- Device |
+ Device |
Status |
- Comments |
- Date Recorded |
+ Comments |
+ Date Recorded |
diff --git a/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/medicationsummary.html b/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/medicationsummary.html
index ade250ed8ee..ed4a8f128d7 100644
--- a/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/medicationsummary.html
+++ b/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/medicationsummary.html
@@ -16,63 +16,78 @@ Sig: MedicationStatement.dosage[x].text (display all sigs separated by
)
Date: MedicationStatement.effectiveDateTime || MedicationStatement.effectivePeriod.start
*/-->
-
- Medication Summary: Medication Requests
-
-
- Medication |
- Status |
- Route |
- Sig |
- Comments |
- Authored Date |
-
-
-
-
-
-
-
-
- Medication |
- Status |
- Route |
- Sig |
- Comments |
- Authored Date |
-
-
-
-
-
-
-
-
- Medication Summary: Medication Statements
-
-
- Medication |
- Status |
- Route |
- Sig |
- Date |
-
-
-
-
-
-
-
- Medication |
- Status |
- Route |
- Sig |
- Date |
-
-
-
-
-
-
+
+ Medication Summary: Medication Requests
+
+
+
+ Medication |
+ Status |
+ Route |
+ Sig |
+ Comments |
+ Authored Date |
+
+
+
+
+
+
+
+
+
+ Medication
+ |
+ Status |
+
+ Route
+ |
+
+ Sig
+ |
+ Comments |
+ Authored Date |
+
+
+
+
+
+
+
+
+
+
+ Medication Summary: Medication Statements
+
+
+
+ Medication |
+ Status |
+ Route |
+ Sig |
+ Date |
+
+
+
+
+
+
+
+
+ Medication
+ |
+ Status |
+ Route |
+ Sig |
+ Date |
+
+
+
+
+
+
+
diff --git a/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/pasthistoryofillness.html b/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/pasthistoryofillness.html
index 0f3223999f7..11f06d54bf5 100644
--- a/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/pasthistoryofillness.html
+++ b/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/pasthistoryofillness.html
@@ -6,8 +6,8 @@ Comments: Condition.note[x].text (separated by
)
Onset Date: Condition.onsetDateTime || Condition.onsetPeriod.start && “-“ && Condition.onsetPeriod.end || Condition.onsetAge || Condition.onsetRange.low && “-“ && Condition.onsetRange.high || Condition.onsetString
*/-->
+
Past History of Illnesses
- Past History of Illnesses
Medical Problems |
@@ -21,10 +21,10 @@ Onset Date: Condition.onsetDateTime || Condition.onsetPeriod.start && “-“ &&
- Medical Problem |
+ Medical Problem |
Status |
- Comments |
- Onset Date |
+ Comments |
+ Onset Date |
diff --git a/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/planofcare.html b/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/planofcare.html
index 3b43317f4d3..d87170fac24 100644
--- a/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/planofcare.html
+++ b/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/planofcare.html
@@ -7,8 +7,8 @@ Planned Start: CarePlan.period.start
Planned End: CarePlan.period.end
*/-->
+
Plan of Care
- Plan of Care
Activity |
@@ -25,7 +25,7 @@ Planned End: CarePlan.period.end
Activity |
Intent |
- Comments |
+ Comments |
Planned Start |
Planned End |
diff --git a/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/pregnancy.html b/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/pregnancy.html
index b4fd5a3a3ae..d939053c3eb 100644
--- a/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/pregnancy.html
+++ b/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/pregnancy.html
@@ -6,8 +6,8 @@ Comments: Observation.note[x].text (separated by
)
Date: Observation.effectiveDateTime || Observation.effectivePeriod.start
*/-->
+
Pregnancy
- Pregnancy
Code |
@@ -21,10 +21,10 @@ Date: Observation.effectiveDateTime || Observation.effectivePeriod.start
- Code |
- Result |
- Comments |
- Date |
+ Code |
+ Result |
+ Comments |
+ Date |
diff --git a/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/problemlist.html b/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/problemlist.html
index 02a6ab4ed76..5c0e796bd5c 100644
--- a/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/problemlist.html
+++ b/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/problemlist.html
@@ -6,8 +6,8 @@ Comments: Condition.note[x].text (separated by
)
Onset Date: Condition.onsetDateTime || Condition.onsetPeriod.start && “-“ && Condition.onsetPeriod.end || Condition.onsetAge || Condition.onsetRange.low && “-“ && Condition.onsetRange.high || Condition.onsetString
*/-->
+
Problem List
- Problem List
Medical Problems |
@@ -21,10 +21,10 @@ Onset Date: Condition.onsetDateTime || Condition.onsetPeriod.start && “-“ &&
- Medical Problems |
+ Medical Problems |
Status |
- Comments |
- Onset Date |
+ Comments |
+ Onset Date |
diff --git a/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/socialhistory.html b/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/socialhistory.html
index 9472f17f9b4..de7ba86c8b8 100644
--- a/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/socialhistory.html
+++ b/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/socialhistory.html
@@ -7,8 +7,8 @@ Comments: Observation.note[x].text (separated by
)
Date: Observation.effectiveDateTime || Observation.effectivePeriod.start
*/-->
+
Social History
- Social History
Code |
@@ -23,11 +23,11 @@ Date: Observation.effectiveDateTime || Observation.effectivePeriod.start
- Code |
- Result |
- Unit |
- Comments |
- Date |
+ Code |
+ Result |
+ Unit |
+ Comments |
+ Date |
diff --git a/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/vitalsigns.html b/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/vitalsigns.html
index 89c01826383..f890ad6c26e 100644
--- a/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/vitalsigns.html
+++ b/hapi-fhir-jpaserver-ips/src/main/resources/ca/uhn/fhir/jpa/ips/narrative/vitalsigns.html
@@ -8,8 +8,8 @@ Comments: Observation.note[x].text (separated by
)
Date: Observation.effectiveDateTime || Observation.effectivePeriod.start
*/-->
+
Vital Signs
- Vital Signs
Code |
@@ -25,12 +25,12 @@ Date: Observation.effectiveDateTime || Observation.effectivePeriod.start
- Code |
- Result |
- Unit |
- Interpretation |
- Comments |
- Date |
+ Code |
+ Result |
+ Unit |
+ Interpretation |
+ Comments |
+ Date |
diff --git a/hapi-fhir-jpaserver-ips/src/test/java/ca/uhn/fhir/jpa/ips/generator/IpsGenerationR4Test.java b/hapi-fhir-jpaserver-ips/src/test/java/ca/uhn/fhir/jpa/ips/generator/IpsGenerationR4Test.java
index d075f0d138d..1a28350edce 100644
--- a/hapi-fhir-jpaserver-ips/src/test/java/ca/uhn/fhir/jpa/ips/generator/IpsGenerationR4Test.java
+++ b/hapi-fhir-jpaserver-ips/src/test/java/ca/uhn/fhir/jpa/ips/generator/IpsGenerationR4Test.java
@@ -7,14 +7,17 @@ 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;
-import ca.uhn.fhir.jpa.ips.strategy.DefaultIpsGenerationStrategy;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.provider.BaseResourceProviderR4Test;
import ca.uhn.fhir.util.ClasspathUtil;
-import ca.uhn.fhir.util.ResourceReferenceInfo;
import ca.uhn.fhir.validation.FhirValidator;
+import ca.uhn.fhir.validation.ResultSeverityEnum;
+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.ValidationSupportChain;
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
import org.hl7.fhir.instance.model.api.IBaseResource;
@@ -38,9 +41,8 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
-import jakarta.annotation.Nonnull;
-import jakarta.annotation.Nullable;
import java.util.List;
+import java.util.Optional;
import java.util.stream.Collectors;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -49,9 +51,9 @@ import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.matchesPattern;
import static org.hamcrest.Matchers.stringContainsInOrder;
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;
-import static org.junit.jupiter.api.Assertions.fail;
/**
* This test uses a complete R4 JPA server as a backend and wires the
@@ -99,8 +101,8 @@ public class IpsGenerationR4Test extends BaseResourceProviderR4Test {
// Verify
validateDocument(output);
assertEquals(117, output.getEntry().size());
- String patientId = findFirstEntryResource(output, Patient.class, 1).getId();
- assertThat(patientId, matchesPattern("urn:uuid:.*"));
+ String patientId = findFirstEntryResource(output, Patient.class, 1).getIdElement().toUnqualifiedVersionless().getValue();
+ assertEquals("Patient/f15d2419-fbff-464a-826d-0afe8f095771", patientId);
MedicationStatement medicationStatement = findFirstEntryResource(output, MedicationStatement.class, 2);
assertEquals(patientId, medicationStatement.getSubject().getReference());
assertNull(medicationStatement.getInformationSource().getReference());
@@ -186,8 +188,8 @@ public class IpsGenerationR4Test extends BaseResourceProviderR4Test {
// Verify
validateDocument(output);
assertEquals(7, output.getEntry().size());
- String patientId = findFirstEntryResource(output, Patient.class, 1).getId();
- assertThat(patientId, matchesPattern("urn:uuid:.*"));
+ String patientId = findFirstEntryResource(output, Patient.class, 1).getIdElement().toUnqualifiedVersionless().getValue();
+ assertEquals("Patient/5342998", patientId);
assertEquals(patientId, findEntryResource(output, Condition.class, 0, 2).getSubject().getReference());
assertEquals(patientId, findEntryResource(output, Condition.class, 1, 2).getSubject().getReference());
@@ -280,18 +282,9 @@ public class IpsGenerationR4Test extends BaseResourceProviderR4Test {
instanceValidator.setValidationSupport(new ValidationSupportChain(new IpsTerminologySvc(), myFhirContext.getValidationSupport()));
validator.registerValidatorModule(instanceValidator);
ValidationResult validation = validator.validateWithResult(theOutcome);
- assertTrue(validation.isSuccessful(), () -> myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(validation.toOperationOutcome()));
- // Make sure that all refs have been replaced with UUIDs
- List references = myFhirContext.newTerser().getAllResourceReferences(theOutcome);
- for (IBaseResource next : myFhirContext.newTerser().getAllEmbeddedResources(theOutcome, true)) {
- references.addAll(myFhirContext.newTerser().getAllResourceReferences(next));
- }
- for (ResourceReferenceInfo next : references) {
- if (!next.getResourceReference().getReferenceElement().getValue().startsWith("urn:uuid:")) {
- fail(next.getName());
- }
- }
+ Optional failure = validation.getMessages().stream().filter(t -> t.getSeverity().ordinal() >= ResultSeverityEnum.ERROR.ordinal()).findFirst();
+ assertFalse(failure.isPresent(), () -> failure.get().toString());
}
@Configuration
@@ -299,12 +292,12 @@ public class IpsGenerationR4Test extends BaseResourceProviderR4Test {
@Bean
public IIpsGenerationStrategy ipsGenerationStrategy() {
- return new DefaultIpsGenerationStrategy();
+ return new DefaultJpaIpsGenerationStrategy();
}
@Bean
public IIpsGeneratorSvc ipsGeneratorSvc(FhirContext theFhirContext, IIpsGenerationStrategy theGenerationStrategy, DaoRegistry theDaoRegistry) {
- return new IpsGeneratorSvcImpl(theFhirContext, theGenerationStrategy, theDaoRegistry);
+ return new IpsGeneratorSvcImpl(theFhirContext, theGenerationStrategy);
}
@Bean
@@ -315,7 +308,6 @@ public class IpsGenerationR4Test extends BaseResourceProviderR4Test {
}
- @SuppressWarnings("unchecked")
private static T findFirstEntryResource(Bundle theBundle, Class theType, int theExpectedCount) {
return findEntryResource(theBundle, theType, 0, theExpectedCount);
}
diff --git a/hapi-fhir-jpaserver-ips/src/test/java/ca/uhn/fhir/jpa/ips/generator/IpsGeneratorSvcImplTest.java b/hapi-fhir-jpaserver-ips/src/test/java/ca/uhn/fhir/jpa/ips/generator/IpsGeneratorSvcImplTest.java
index 0218697e19c..39278765825 100644
--- a/hapi-fhir-jpaserver-ips/src/test/java/ca/uhn/fhir/jpa/ips/generator/IpsGeneratorSvcImplTest.java
+++ b/hapi-fhir-jpaserver-ips/src/test/java/ca/uhn/fhir/jpa/ips/generator/IpsGeneratorSvcImplTest.java
@@ -3,9 +3,10 @@ package ca.uhn.fhir.jpa.ips.generator;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
-import ca.uhn.fhir.jpa.ips.api.IpsSectionEnum;
-import ca.uhn.fhir.jpa.ips.api.SectionRegistry;
-import ca.uhn.fhir.jpa.ips.strategy.DefaultIpsGenerationStrategy;
+import ca.uhn.fhir.jpa.ips.api.IIpsGenerationStrategy;
+import ca.uhn.fhir.jpa.ips.api.IpsContext;
+import ca.uhn.fhir.jpa.ips.api.Section;
+import ca.uhn.fhir.jpa.ips.jpa.DefaultJpaIpsGenerationStrategy;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
@@ -15,13 +16,11 @@ import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.SimpleBundleProvider;
import ca.uhn.fhir.test.utilities.HtmlUtil;
import ca.uhn.fhir.util.ClasspathUtil;
-import org.htmlunit.html.DomElement;
-import org.htmlunit.html.DomNodeList;
-import org.htmlunit.html.HtmlPage;
-import org.htmlunit.html.HtmlTable;
-import org.htmlunit.html.HtmlTableRow;
import com.google.common.collect.Lists;
+import jakarta.annotation.Nonnull;
+import jakarta.annotation.Nullable;
import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.AllergyIntolerance;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.CarePlan;
@@ -61,11 +60,11 @@ import org.mockito.junit.jupiter.MockitoExtension;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import jakarta.annotation.Nonnull;
import java.io.IOException;
import java.util.Collections;
import java.util.Date;
import java.util.List;
+import java.util.function.Function;
import java.util.stream.Collectors;
import static ca.uhn.fhir.jpa.ips.generator.IpsGenerationR4Test.findEntryResource;
@@ -76,6 +75,7 @@ import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
@@ -115,23 +115,44 @@ public class IpsGeneratorSvcImplTest {
private final FhirContext myFhirContext = FhirContext.forR4Cached();
private final DaoRegistry myDaoRegistry = new DaoRegistry(myFhirContext);
private IIpsGeneratorSvc mySvc;
- private DefaultIpsGenerationStrategy myStrategy;
+ private DefaultJpaIpsGenerationStrategy myStrategy;
@BeforeEach
public void beforeEach() {
myDaoRegistry.setResourceDaos(Collections.emptyList());
+ }
- myStrategy = new DefaultIpsGenerationStrategy();
- mySvc = new IpsGeneratorSvcImpl(myFhirContext, myStrategy, myDaoRegistry);
+ private void initializeGenerationStrategy() {
+ initializeGenerationStrategy(List.of());
+ }
+
+ private void initializeGenerationStrategy(List> theGlobalSectionCustomizers) {
+ myStrategy = new DefaultJpaIpsGenerationStrategy() {
+ @Override
+ public IIdType massageResourceId(@Nullable IpsContext theIpsContext, @javax.annotation.Nonnull IBaseResource theResource) {
+ return IdType.newRandomUuid();
+ }
+ };
+
+ myStrategy.setFhirContext(myFhirContext);
+ myStrategy.setDaoRegistry(myDaoRegistry);
+
+ if (theGlobalSectionCustomizers != null) {
+ for (var next : theGlobalSectionCustomizers) {
+ myStrategy.addGlobalSectionCustomizer(next);
+ }
+ }
+ mySvc = new IpsGeneratorSvcImpl(myFhirContext, myStrategy);
}
@Test
public void testGenerateIps() {
// Setup
+ initializeGenerationStrategy();
registerResourceDaosForSmallPatientSet();
// Test
- Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new TokenParam("http://foo", "bar"));
+ Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new TokenParam("http://foo", "bar"), null);
// Verify
ourLog.info("Generated IPS:\n{}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
@@ -168,6 +189,7 @@ public class IpsGeneratorSvcImplTest {
@Test
public void testAllergyIntolerance_OnsetTypes() throws IOException {
// Setup Patient
+ initializeGenerationStrategy();
registerPatientDaoWithRead();
AllergyIntolerance allergy1 = new AllergyIntolerance();
@@ -194,11 +216,11 @@ public class IpsGeneratorSvcImplTest {
registerRemainingResourceDaos();
// Test
- Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType(PATIENT_ID));
+ Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType(PATIENT_ID), null);
// Verify
Composition compositions = (Composition) outcome.getEntry().get(0).getResource();
- Composition.SectionComponent section = findSection(compositions, IpsSectionEnum.ALLERGY_INTOLERANCE);
+ Composition.SectionComponent section = findSection(compositions, DefaultJpaIpsGenerationStrategy.SECTION_CODE_ALLERGY_INTOLERANCE);
HtmlPage narrativeHtml = HtmlUtil.parseAsHtml(section.getText().getDivAsString());
ourLog.info("Narrative:\n{}", narrativeHtml.asXml());
@@ -216,6 +238,7 @@ public class IpsGeneratorSvcImplTest {
@Test
public void testAllergyIntolerance_MissingElements() throws IOException {
// Setup Patient
+ initializeGenerationStrategy();
registerPatientDaoWithRead();
AllergyIntolerance allergy = new AllergyIntolerance();
@@ -229,11 +252,11 @@ public class IpsGeneratorSvcImplTest {
registerRemainingResourceDaos();
// Test
- Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType(PATIENT_ID));
+ Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType(PATIENT_ID), null);
// Verify
Composition compositions = (Composition) outcome.getEntry().get(0).getResource();
- Composition.SectionComponent section = findSection(compositions, IpsSectionEnum.ALLERGY_INTOLERANCE);
+ Composition.SectionComponent section = findSection(compositions, DefaultJpaIpsGenerationStrategy.SECTION_CODE_ALLERGY_INTOLERANCE);
HtmlPage narrativeHtml = HtmlUtil.parseAsHtml(section.getText().getDivAsString());
ourLog.info("Narrative:\n{}", narrativeHtml.asXml());
@@ -245,6 +268,7 @@ public class IpsGeneratorSvcImplTest {
@Test
public void testMedicationSummary_MedicationStatementWithMedicationReference() throws IOException {
// Setup Patient
+ initializeGenerationStrategy();
registerPatientDaoWithRead();
// Setup Medication + MedicationStatement
@@ -256,7 +280,7 @@ public class IpsGeneratorSvcImplTest {
registerRemainingResourceDaos();
// Test
- Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType(PATIENT_ID));
+ Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType(PATIENT_ID), null);
// Verify Bundle Contents
List contentResourceTypes = toEntryResourceTypeStrings(outcome);
@@ -269,14 +293,14 @@ public class IpsGeneratorSvcImplTest {
// Verify
Composition compositions = (Composition) outcome.getEntry().get(0).getResource();
- Composition.SectionComponent section = findSection(compositions, IpsSectionEnum.MEDICATION_SUMMARY);
+ Composition.SectionComponent section = findSection(compositions, DefaultJpaIpsGenerationStrategy.SECTION_CODE_MEDICATION_SUMMARY);
HtmlPage narrativeHtml = HtmlUtil.parseAsHtml(section.getText().getDivAsString());
ourLog.info("Narrative:\n{}", narrativeHtml.asXml());
DomNodeList tables = narrativeHtml.getElementsByTagName("table");
- assertEquals(2, tables.size());
- HtmlTable table = (HtmlTable) tables.get(1);
+ assertEquals(1, tables.size());
+ HtmlTable table = (HtmlTable) tables.get(0);
HtmlTableRow row = table.getBodies().get(0).getRows().get(0);
assertEquals("Tylenol", row.getCell(0).asNormalizedText());
assertEquals("Active", row.getCell(1).asNormalizedText());
@@ -288,6 +312,7 @@ public class IpsGeneratorSvcImplTest {
@Test
public void testMedicationSummary_MedicationRequestWithNoMedication() throws IOException {
// Setup Patient
+ initializeGenerationStrategy();
registerPatientDaoWithRead();
// Setup Medication + MedicationStatement
@@ -301,17 +326,17 @@ public class IpsGeneratorSvcImplTest {
registerRemainingResourceDaos();
// Test
- Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType(PATIENT_ID));
+ Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType(PATIENT_ID), null);
// Verify
Composition compositions = (Composition) outcome.getEntry().get(0).getResource();
- Composition.SectionComponent section = findSection(compositions, IpsSectionEnum.MEDICATION_SUMMARY);
+ Composition.SectionComponent section = findSection(compositions, DefaultJpaIpsGenerationStrategy.SECTION_CODE_MEDICATION_SUMMARY);
HtmlPage narrativeHtml = HtmlUtil.parseAsHtml(section.getText().getDivAsString());
ourLog.info("Narrative:\n{}", narrativeHtml.asXml());
DomNodeList tables = narrativeHtml.getElementsByTagName("table");
- assertEquals(2, tables.size());
+ assertEquals(1, tables.size());
HtmlTable table = (HtmlTable) tables.get(0);
HtmlTableRow row = table.getBodies().get(0).getRows().get(0);
assertEquals("", row.getCell(0).asNormalizedText());
@@ -320,22 +345,12 @@ public class IpsGeneratorSvcImplTest {
assertEquals("", row.getCell(3).asNormalizedText());
}
- @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
public void testMedicationSummary_DuplicateSecondaryResources() {
- myStrategy.setSectionRegistry(new SectionRegistry().addGlobalCustomizer(t -> t.withNoInfoGenerator(null)));
-
// Setup Patient
+ initializeGenerationStrategy(
+ List.of(t->Section.newBuilder(t).withNoInfoGenerator(null).build())
+ );
registerPatientDaoWithRead();
// Setup Medication + MedicationStatement
@@ -349,7 +364,7 @@ public class IpsGeneratorSvcImplTest {
registerRemainingResourceDaos();
// Test
- Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType(PATIENT_ID));
+ Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType(PATIENT_ID), null);
// Verify Bundle Contents
List contentResourceTypes = toEntryResourceTypeStrings(outcome);
@@ -370,9 +385,10 @@ public class IpsGeneratorSvcImplTest {
*/
@Test
public void testMedicationSummary_ResourceAppearsAsSecondaryThenPrimary() throws IOException {
- myStrategy.setSectionRegistry(new SectionRegistry().addGlobalCustomizer(t -> t.withNoInfoGenerator(null)));
-
// Setup Patient
+ initializeGenerationStrategy(
+ List.of(t->Section.newBuilder(t).withNoInfoGenerator(null).build())
+ );
registerPatientDaoWithRead();
// Setup Medication + MedicationStatement
@@ -388,7 +404,7 @@ public class IpsGeneratorSvcImplTest {
registerRemainingResourceDaos();
// Test
- Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType(PATIENT_ID));
+ Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType(PATIENT_ID), null);
// Verify Bundle Contents
List contentResourceTypes = toEntryResourceTypeStrings(outcome);
@@ -403,20 +419,61 @@ public class IpsGeneratorSvcImplTest {
// Verify narrative - should have 2 rows (one for each primary MedicationStatement)
Composition compositions = (Composition) outcome.getEntry().get(0).getResource();
- Composition.SectionComponent section = findSection(compositions, IpsSectionEnum.MEDICATION_SUMMARY);
+ Composition.SectionComponent section = findSection(compositions, DefaultJpaIpsGenerationStrategy.SECTION_CODE_MEDICATION_SUMMARY);
HtmlPage narrativeHtml = HtmlUtil.parseAsHtml(section.getText().getDivAsString());
ourLog.info("Narrative:\n{}", narrativeHtml.asXml());
DomNodeList tables = narrativeHtml.getElementsByTagName("table");
- assertEquals(2, tables.size());
- HtmlTable table = (HtmlTable) tables.get(1);
+ assertEquals(1, tables.size());
+ HtmlTable table = (HtmlTable) tables.get(0);
+ assertEquals(2, table.getBodies().get(0).getRows().size());
+ }
+
+ /**
+ * If there is no contents in one of the 2 medication summary tables it should be
+ * omitted
+ */
+ @Test
+ public void testMedicationSummary_OmitMedicationRequestTable() throws IOException {
+ // Setup Patient
+ initializeGenerationStrategy(
+ List.of(t->Section.newBuilder(t).withNoInfoGenerator(null).build())
+ );
+ registerPatientDaoWithRead();
+
+ // Setup Medication + MedicationStatement
+ Medication medication = createSecondaryMedication(MEDICATION_ID);
+ MedicationStatement medicationStatement = createPrimaryMedicationStatement(MEDICATION_ID, MEDICATION_STATEMENT_ID);
+ medicationStatement.addDerivedFrom().setReference(MEDICATION_STATEMENT_ID2);
+ MedicationStatement medicationStatement2 = createPrimaryMedicationStatement(MEDICATION_ID, MEDICATION_STATEMENT_ID2);
+ ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.put(medicationStatement2, BundleEntrySearchModeEnum.INCLUDE);
+ MedicationStatement medicationStatement3 = createPrimaryMedicationStatement(MEDICATION_ID, MEDICATION_STATEMENT_ID2);
+ IFhirResourceDao medicationStatementDao = registerResourceDaoWithNoData(MedicationStatement.class);
+ when(medicationStatementDao.search(any(), any())).thenReturn(new SimpleBundleProvider(Lists.newArrayList(medicationStatement, medication, medicationStatement2, medicationStatement3)));
+
+ registerRemainingResourceDaos();
+
+ // Test
+ Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType(PATIENT_ID), null);
+
+ // Verify narrative - should have 2 rows (one for each primary MedicationStatement)
+ Composition compositions = (Composition) outcome.getEntry().get(0).getResource();
+ Composition.SectionComponent section = findSection(compositions, DefaultJpaIpsGenerationStrategy.SECTION_CODE_MEDICATION_SUMMARY);
+
+ HtmlPage narrativeHtml = HtmlUtil.parseAsHtml(section.getText().getDivAsString());
+ ourLog.info("Narrative:\n{}", narrativeHtml.asXml());
+
+ DomNodeList tables = narrativeHtml.getElementsByTagName("table");
+ assertEquals(1, tables.size());
+ HtmlTable table = (HtmlTable) tables.get(0);
assertEquals(2, table.getBodies().get(0).getRows().size());
}
@Test
public void testMedicalDevices_DeviceUseStatementWithDevice() throws IOException {
// Setup Patient
+ initializeGenerationStrategy();
registerPatientDaoWithRead();
// Setup Medication + MedicationStatement
@@ -439,11 +496,11 @@ public class IpsGeneratorSvcImplTest {
registerRemainingResourceDaos();
// Test
- Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType(PATIENT_ID));
+ Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType(PATIENT_ID), null);
// Verify
Composition compositions = (Composition) outcome.getEntry().get(0).getResource();
- Composition.SectionComponent section = findSection(compositions, IpsSectionEnum.MEDICAL_DEVICES);
+ Composition.SectionComponent section = findSection(compositions, DefaultJpaIpsGenerationStrategy.SECTION_CODE_MEDICAL_DEVICES);
HtmlPage narrativeHtml = HtmlUtil.parseAsHtml(section.getText().getDivAsString());
ourLog.info("Narrative:\n{}", narrativeHtml.asXml());
@@ -460,6 +517,7 @@ public class IpsGeneratorSvcImplTest {
@Test
public void testImmunizations() throws IOException {
// Setup Patient
+ initializeGenerationStrategy();
registerPatientDaoWithRead();
// Setup Medication + MedicationStatement
@@ -486,11 +544,11 @@ public class IpsGeneratorSvcImplTest {
registerRemainingResourceDaos();
// Test
- Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType(PATIENT_ID));
+ Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType(PATIENT_ID), null);
// Verify
Composition compositions = (Composition) outcome.getEntry().get(0).getResource();
- Composition.SectionComponent section = findSection(compositions, IpsSectionEnum.IMMUNIZATIONS);
+ Composition.SectionComponent section = findSection(compositions, DefaultJpaIpsGenerationStrategy.SECTION_CODE_IMMUNIZATIONS);
HtmlPage narrativeHtml = HtmlUtil.parseAsHtml(section.getText().getDivAsString());
ourLog.info("Narrative:\n{}", narrativeHtml.asXml());
@@ -511,6 +569,7 @@ public class IpsGeneratorSvcImplTest {
@Test
public void testReferencesUpdatedInSecondaryInclusions() {
// Setup Patient
+ initializeGenerationStrategy();
registerPatientDaoWithRead();
// Setup Medication + MedicationStatement
@@ -548,7 +607,7 @@ public class IpsGeneratorSvcImplTest {
registerRemainingResourceDaos();
// Test
- Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType(PATIENT_ID));
+ Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType(PATIENT_ID), null);
ourLog.info(myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
// Verify cross-references
@@ -572,10 +631,10 @@ public class IpsGeneratorSvcImplTest {
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);
+ Composition.SectionComponent problemListSection = findSection(composition, DefaultJpaIpsGenerationStrategy.SECTION_CODE_PROBLEM_LIST);
assertEquals(addedCondition.getId(), problemListSection.getEntry().get(0).getReference());
assertEquals(1, problemListSection.getEntry().size());
- Composition.SectionComponent illnessHistorySection = findSection(composition, IpsSectionEnum.ILLNESS_HISTORY);
+ Composition.SectionComponent illnessHistorySection = findSection(composition, DefaultJpaIpsGenerationStrategy.SECTION_CODE_ILLNESS_HISTORY);
assertEquals(addedCondition2.getId(), illnessHistorySection.getEntry().get(0).getReference());
assertEquals(1, illnessHistorySection.getEntry().size());
}
@@ -583,6 +642,7 @@ public class IpsGeneratorSvcImplTest {
@Test
public void testPatientIsReturnedAsAnIncludeResource() {
// Setup Patient
+ initializeGenerationStrategy();
registerPatientDaoWithRead();
// Setup Condition
@@ -608,7 +668,7 @@ public class IpsGeneratorSvcImplTest {
registerRemainingResourceDaos();
// Test
- Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType(PATIENT_ID));
+ Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType(PATIENT_ID), null);
List resources = outcome
.getEntry()
@@ -620,6 +680,30 @@ public class IpsGeneratorSvcImplTest {
));
}
+ @Test
+ public void testSelectGenerator() {
+ IIpsGenerationStrategy strategy1 = mock(IIpsGenerationStrategy.class);
+ when(strategy1.getBundleProfile()).thenReturn("http://1");
+ IIpsGenerationStrategy strategy2 = mock(IIpsGenerationStrategy.class);
+ when(strategy2.getBundleProfile()).thenReturn("http://2");
+ IpsGeneratorSvcImpl svc = new IpsGeneratorSvcImpl(myFhirContext, List.of(strategy1, strategy2));
+
+ assertSame(strategy1, svc.selectGenerationStrategy("http://1"));
+ assertSame(strategy1, svc.selectGenerationStrategy(null));
+ assertSame(strategy1, svc.selectGenerationStrategy("http://foo"));
+ assertSame(strategy2, svc.selectGenerationStrategy("http://2"));
+ }
+
+ @Nonnull
+ private Composition.SectionComponent findSection(Composition compositions, String theSectionCode) {
+ return compositions
+ .getSection()
+ .stream()
+ .filter(t -> t.getCode().getCodingFirstRep().getCode().equals(theSectionCode))
+ .findFirst()
+ .orElseThrow();
+ }
+
private void registerPatientDaoWithRead() {
IFhirResourceDao patientDao = registerResourceDaoWithNoData(Patient.class);
Patient patient = new Patient();
@@ -677,19 +761,19 @@ public class IpsGeneratorSvcImplTest {
}
@Nonnull
- private static Medication createSecondaryMedication(String medicationId) {
+ private static Medication createSecondaryMedication(String theMedicationId) {
Medication medication = new Medication();
- medication.setId(new IdType(medicationId));
+ medication.setId(new IdType(theMedicationId));
medication.getCode().addCoding().setDisplay("Tylenol");
ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.put(medication, BundleEntrySearchModeEnum.INCLUDE);
return medication;
}
@Nonnull
- private static MedicationStatement createPrimaryMedicationStatement(String medicationId, String medicationStatementId) {
+ private static MedicationStatement createPrimaryMedicationStatement(String theMedicationId, String medicationStatementId) {
MedicationStatement medicationStatement = new MedicationStatement();
medicationStatement.setId(medicationStatementId);
- medicationStatement.setMedication(new Reference(medicationId));
+ medicationStatement.setMedication(new Reference(theMedicationId));
medicationStatement.setStatus(MedicationStatement.MedicationStatementStatus.ACTIVE);
medicationStatement.getDosageFirstRep().getRoute().addCoding().setDisplay("Oral");
medicationStatement.getDosageFirstRep().setText("DAW");
diff --git a/hapi-fhir-jpaserver-ips/src/test/java/ca/uhn/fhir/jpa/ips/provider/IpsOperationProviderTest.java b/hapi-fhir-jpaserver-ips/src/test/java/ca/uhn/fhir/jpa/ips/provider/IpsOperationProviderTest.java
new file mode 100644
index 00000000000..96e6bd1c542
--- /dev/null
+++ b/hapi-fhir-jpaserver-ips/src/test/java/ca/uhn/fhir/jpa/ips/provider/IpsOperationProviderTest.java
@@ -0,0 +1,152 @@
+package ca.uhn.fhir.jpa.ips.provider;
+
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.jpa.ips.generator.IIpsGeneratorSvc;
+import ca.uhn.fhir.rest.param.TokenParam;
+import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
+import org.hl7.fhir.instance.model.api.IIdType;
+import org.hl7.fhir.r4.model.Bundle;
+import org.hl7.fhir.r4.model.IdType;
+import org.hl7.fhir.r4.model.Identifier;
+import org.hl7.fhir.r4.model.Parameters;
+import org.hl7.fhir.r4.model.UriType;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class IpsOperationProviderTest {
+
+ @Mock
+ private IIpsGeneratorSvc myIpsGeneratorSvc;
+
+ @RegisterExtension
+ private RestfulServerExtension myServer = new RestfulServerExtension(FhirContext.forR4Cached())
+ .withServer(t -> t.registerProviders(new IpsOperationProvider(myIpsGeneratorSvc)));
+
+ @Captor
+ private ArgumentCaptor myProfileCaptor;
+ @Captor
+ private ArgumentCaptor myIdTypeCaptor;
+ @Captor
+ private ArgumentCaptor