IPS API Refactor (#5682)
* IPS enhancements * API design complete * Work on section registry * Work on external fetch * IPS rewrite * Cleanup * Work * IPS refactor * Add changelog * Changelog updates * Spotless * Compile fix * Address review comments * Address review comments * License header * Revert narrative builder change * Address review comments * Addres review comments * Cleanup
This commit is contained in:
parent
d71736bf9d
commit
12eb2d6f35
|
@ -8,6 +8,11 @@ tab_width = 4
|
||||||
indent_size = 4
|
indent_size = 4
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
|
|
||||||
|
[*.html]
|
||||||
|
indent_style = tab
|
||||||
|
tab_width = 3
|
||||||
|
indent_size = 3
|
||||||
|
|
||||||
[*.xml]
|
[*.xml]
|
||||||
indent_style = tab
|
indent_style = tab
|
||||||
tab_width = 3
|
tab_width = 3
|
||||||
|
|
|
@ -25,6 +25,7 @@ import ca.uhn.fhir.fhirpath.IFhirPathEvaluationContext;
|
||||||
import ca.uhn.fhir.i18n.Msg;
|
import ca.uhn.fhir.i18n.Msg;
|
||||||
import ca.uhn.fhir.narrative2.BaseNarrativeGenerator;
|
import ca.uhn.fhir.narrative2.BaseNarrativeGenerator;
|
||||||
import ca.uhn.fhir.narrative2.INarrativeTemplate;
|
import ca.uhn.fhir.narrative2.INarrativeTemplate;
|
||||||
|
import ca.uhn.fhir.narrative2.NarrativeGeneratorTemplateUtils;
|
||||||
import ca.uhn.fhir.narrative2.TemplateTypeEnum;
|
import ca.uhn.fhir.narrative2.TemplateTypeEnum;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
|
@ -109,6 +110,7 @@ public abstract class BaseThymeleafNarrativeGenerator extends BaseNarrativeGener
|
||||||
Context context = new Context();
|
Context context = new Context();
|
||||||
context.setVariable("resource", theTargetContext);
|
context.setVariable("resource", theTargetContext);
|
||||||
context.setVariable("context", theTargetContext);
|
context.setVariable("context", theTargetContext);
|
||||||
|
context.setVariable("narrativeUtil", NarrativeGeneratorTemplateUtils.INSTANCE);
|
||||||
context.setVariable(
|
context.setVariable(
|
||||||
"fhirVersion", theFhirContext.getVersion().getVersion().name());
|
"fhirVersion", theFhirContext.getVersion().getVersion().name());
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR - Core Library
|
||||||
|
* %%
|
||||||
|
* 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.narrative2;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.util.BundleUtil;
|
||||||
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseBundle;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An instance of this class is added to the Thymeleaf context as a variable with
|
||||||
|
* name <code>"narrativeUtil"</code> and can be accessed from narrative templates.
|
||||||
|
*
|
||||||
|
* @since 7.0.0
|
||||||
|
*/
|
||||||
|
public class NarrativeGeneratorTemplateUtils {
|
||||||
|
|
||||||
|
public static final NarrativeGeneratorTemplateUtils INSTANCE = new NarrativeGeneratorTemplateUtils();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a Bundle as input, are any entries present with a given resource type
|
||||||
|
*/
|
||||||
|
public boolean bundleHasEntriesWithResourceType(IBaseBundle theBaseBundle, String theResourceType) {
|
||||||
|
FhirContext ctx = theBaseBundle.getStructureFhirVersionEnum().newContextCached();
|
||||||
|
List<Pair<String, IBaseResource>> entryResources =
|
||||||
|
BundleUtil.getBundleEntryUrlsAndResources(ctx, theBaseBundle);
|
||||||
|
return entryResources.stream()
|
||||||
|
.map(Pair::getValue)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.anyMatch(t -> ctx.getResourceType(t).equals(theResourceType));
|
||||||
|
}
|
||||||
|
}
|
|
@ -435,7 +435,7 @@ public class BundleBuilder {
|
||||||
*/
|
*/
|
||||||
public void addCollectionEntry(IBaseResource theResource) {
|
public void addCollectionEntry(IBaseResource theResource) {
|
||||||
setType("collection");
|
setType("collection");
|
||||||
addEntryAndReturnRequest(theResource, theResource.getIdElement().getValue());
|
addEntryAndReturnRequest(theResource);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -443,7 +443,7 @@ public class BundleBuilder {
|
||||||
*/
|
*/
|
||||||
public void addDocumentEntry(IBaseResource theResource) {
|
public void addDocumentEntry(IBaseResource theResource) {
|
||||||
setType("document");
|
setType("document");
|
||||||
addEntryAndReturnRequest(theResource, theResource.getIdElement().getValue());
|
addEntryAndReturnRequest(theResource);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -475,6 +475,14 @@ public class BundleBuilder {
|
||||||
return (IBaseBackboneElement) searchInstance;
|
return (IBaseBackboneElement) searchInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IBase addEntryAndReturnRequest(IBaseResource theResource) {
|
||||||
|
IIdType id = theResource.getIdElement();
|
||||||
|
if (id.hasVersionIdPart()) {
|
||||||
|
id = id.toVersionless();
|
||||||
|
}
|
||||||
|
return addEntryAndReturnRequest(theResource, id.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
private IBase addEntryAndReturnRequest(IBaseResource theResource, String theFullUrl) {
|
private IBase addEntryAndReturnRequest(IBaseResource theResource, String theFullUrl) {
|
||||||
Validate.notNull(theResource, "theResource must not be null");
|
Validate.notNull(theResource, "theResource must not be null");
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ package ca.uhn.fhir.util;
|
||||||
|
|
||||||
import ca.uhn.fhir.i18n.Msg;
|
import ca.uhn.fhir.i18n.Msg;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||||
|
@ -82,6 +83,12 @@ public class ValidateUtil {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void isTrueOrThrowResourceNotFound(boolean theSuccess, String theMessage, Object... theValues) {
|
||||||
|
if (!theSuccess) {
|
||||||
|
throw new ResourceNotFoundException(Msg.code(2494) + String.format(theMessage, theValues));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void exactlyOneNotNullOrThrowInvalidRequestException(Object[] theObjects, String theMessage) {
|
public static void exactlyOneNotNullOrThrowInvalidRequestException(Object[] theObjects, String theMessage) {
|
||||||
int count = 0;
|
int count = 0;
|
||||||
for (Object next : theObjects) {
|
for (Object next : theObjects) {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.fail;
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
import ca.uhn.fhir.i18n.Msg;
|
import ca.uhn.fhir.i18n.Msg;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
@ -12,7 +13,7 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
public class ValidateUtilTest {
|
public class ValidateUtilTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testValidate() {
|
public void testIsTrueOrThrowInvalidRequest() {
|
||||||
ValidateUtil.isTrueOrThrowInvalidRequest(true, "");
|
ValidateUtil.isTrueOrThrowInvalidRequest(true, "");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -23,6 +24,18 @@ public class ValidateUtilTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsTrueOrThrowResourceNotFound() {
|
||||||
|
ValidateUtil.isTrueOrThrowResourceNotFound(true, "");
|
||||||
|
|
||||||
|
try {
|
||||||
|
ValidateUtil.isTrueOrThrowResourceNotFound(false, "The message");
|
||||||
|
fail();
|
||||||
|
} catch (ResourceNotFoundException e) {
|
||||||
|
assertEquals(Msg.code(2494) + "The message", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testIsGreaterThan() {
|
public void testIsGreaterThan() {
|
||||||
ValidateUtil.isGreaterThan(2L, 1L, "");
|
ValidateUtil.isGreaterThan(2L, 1L, "");
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
type: fix
|
||||||
|
issue: 5682
|
||||||
|
title: "The BundleBuilder utility class will no longer include the `/_version/xxx` portion of the
|
||||||
|
resource ID in the `Bundle.entry.fullUrl` it generates, as the FHIR specification states that this
|
||||||
|
should be omitted."
|
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
type: change
|
||||||
|
issue: 5682
|
||||||
|
title: "The IPS $summary generation API has been overhauled to make it more flexible for
|
||||||
|
future use cases. Specifically, the section registry has been removed and folded into
|
||||||
|
the generation strategy, and support has been added for non-JPA sources of data. This is
|
||||||
|
a breaking change to the API, and implementers will need to update their code. This updated
|
||||||
|
API incorporates community feedback, and should now be considered a stable API for IPS
|
||||||
|
generation."
|
|
@ -0,0 +1,26 @@
|
||||||
|
---
|
||||||
|
type: add
|
||||||
|
issue: 5682
|
||||||
|
title: "Several enhancements have been made to the International Patient Summary generator based on
|
||||||
|
feedback from implementers:
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
New methods have been added to the <code>IIpsGenerationStrategy</code> allowing resources
|
||||||
|
for any or all sections to be fetched from a source other than the FHIR repository.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
The <code>IpsSectionEnum</code> class has been removed and replaced in any user-facing APIs
|
||||||
|
with references to <code>SectionRegistry.Section</code>. This makes it much easier to
|
||||||
|
extend or replace the section registry with custom sections not defined in the universal
|
||||||
|
IPS implementation guide.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Captions have been removed from narrative section tables, and replaced with H5 tags
|
||||||
|
directly above the table. This results in an easier to read display since the table
|
||||||
|
title will appear above the table instead of below it.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
The IPS narrative generator built in templates will now omit tables when the template
|
||||||
|
specified multiple tables and the specific table would have no resources.
|
||||||
|
</li>
|
||||||
|
</ul>"
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
type: fix
|
||||||
|
issue: 5682
|
||||||
|
title: "The IPS Generator will no longer replace resource IDs with placeholder IDs in the resulting
|
||||||
|
bundle by default, although this can be overridden in the generation strategy object."
|
|
@ -14,24 +14,17 @@ The IPS Generator uses FHIR resources stored in your repository as its input. Th
|
||||||
|
|
||||||
# Generation Strategy
|
# Generation Strategy
|
||||||
|
|
||||||
A user supplied strategy class is used to determine various properties of the IPS. This class must implement the `IIpsGenerationStrategy` interface. A default implementation called `DefaultIpsGenerationStrategy` is included. You may use this default implementation, use a subclassed version of it that adds additional logic, or use en entirely new implementation.
|
A user supplied strategy class is used to determine various properties of the IPS. This class must implement the `IIpsGenerationStrategy` interface. A default implementation called `DefaultJpaIpsGenerationStrategy` is included. You may use this default implementation, use a subclassed version of it that adds additional logic, or use en entirely new implementation.
|
||||||
|
|
||||||
The generation strategy also supplies the [Section Registry](#section-registry) and [Narrative Templates](#narrative-templates) implementations, so it can be considered the central part of your IPS configuration.
|
The generation strategy also supplies the [Narrative Templates](#narrative-templates) implementations, so it can be considered the central part of your IPS configuration.
|
||||||
|
|
||||||
* JavaDoc: [IIpsGenerationStrategy](/hapi-fhir/apidocs/hapi-fhir-jpaserver-ips/ca/uhn/fhir/jpa/ips/api/IIpsGenerationStrategy.html)
|
* JavaDoc: [IIpsGenerationStrategy](/hapi-fhir/apidocs/hapi-fhir-jpaserver-ips/ca/uhn/fhir/jpa/ips/api/IIpsGenerationStrategy.html)
|
||||||
* Source Code: [IIpsGenerationStrategy.java](https://github.com/hapifhir/hapi-fhir/blob/master/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/api/IIpsGenerationStrategy.java)
|
* Source Code: [IIpsGenerationStrategy.java](https://github.com/hapifhir/hapi-fhir/blob/master/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/api/IIpsGenerationStrategy.java)
|
||||||
* JavaDoc: [DefaultIpsGenerationStrategy](/hapi-fhir/apidocs/hapi-fhir-jpaserver-ips/ca/uhn/fhir/jpa/ips/strategy/DefaultIpsGenerationStrategy.html)
|
|
||||||
* Source Code: [DefaultIpsGenerationStrategy.java](https://github.com/hapifhir/hapi-fhir/blob/master/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/strategy/DefaultIpsGenerationStrategy.java)
|
|
||||||
|
|
||||||
|
The default generation strategy defines the sections that will be included in your IPS. Out of the box, the standard IPS sections are all included. See the [IG homepage](http://hl7.org/fhir/uv/ips/) for a list of the standard sections.
|
||||||
|
|
||||||
<a name="section-registry"/>
|
* JavaDoc: [DefaultJpaIpsGenerationStrategy](/hapi-fhir/apidocs/hapi-fhir-jpaserver-ips/ca/uhn/fhir/jpa/ips/jpa/DefaultJpaIpsGenerationStrategy.html)
|
||||||
|
* Source Code: [DefaultJpaIpsGenerationStrategy.java](https://github.com/hapifhir/hapi-fhir/blob/master/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/jpa/DefaultJpaIpsGenerationStrategy.java)
|
||||||
# Section Registry
|
|
||||||
|
|
||||||
The IPS SectionRegistry class defines the sections that will be included in your IPS. Out of the box, the standard IPS sections are all included. See the [IG homepage](http://hl7.org/fhir/uv/ips/) for a list of the standard sections.
|
|
||||||
|
|
||||||
* JavaDoc: [SectionRegistry](/hapi-fhir/apidocs/hapi-fhir-jpaserver-ips/ca/uhn/fhir/jpa/ips/api/SectionRegistry.html)
|
|
||||||
* Source Code: [SectionRegistry.java](https://github.com/hapifhir/hapi-fhir/blob/master/hapi-fhir-jpaserver-ips/src/main/java/ca/uhn/fhir/jpa/ips/api/SectionRegistry.java)
|
|
||||||
|
|
||||||
|
|
||||||
<a name="narrative-templates"/>
|
<a name="narrative-templates"/>
|
||||||
|
@ -44,7 +37,7 @@ The IPS generator uses HAPI FHIR [Narrative Generation](/hapi-fhir/docs/model/na
|
||||||
|
|
||||||
Narrative templates for individual sections will be supplied a Bundle resource containing only the matched resources for the individual section as entries (ie. the Composition itself will not be present and no other resources will be present). So, for example, when generating the _Allergies / Intolerances_ IPS section narrative, the input to the narrative generator will be a _Bundle_ resource containing only _AllergyIntolerance_ resources.
|
Narrative templates for individual sections will be supplied a Bundle resource containing only the matched resources for the individual section as entries (ie. the Composition itself will not be present and no other resources will be present). So, for example, when generating the _Allergies / Intolerances_ IPS section narrative, the input to the narrative generator will be a _Bundle_ resource containing only _AllergyIntolerance_ resources.
|
||||||
|
|
||||||
The narrative properties file should contain definitions using the profile URL of the individual section (as defined in the [section registry](#section-registry)) as the `.profile` qualifier. For example:
|
The narrative properties file should contain definitions using the profile URL of the individual section (as defined in the section definition within the generation strategy) as the `.profile` qualifier. For example:
|
||||||
|
|
||||||
```properties
|
```properties
|
||||||
ips-allergyintolerance.resourceType=Bundle
|
ips-allergyintolerance.resourceType=Bundle
|
||||||
|
|
|
@ -19,15 +19,17 @@
|
||||||
*/
|
*/
|
||||||
package ca.uhn.fhir.jpa.ips.api;
|
package ca.uhn.fhir.jpa.ips.api;
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
import ca.uhn.fhir.jpa.ips.strategy.BaseIpsGenerationStrategy;
|
||||||
import ca.uhn.fhir.model.api.Include;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
|
import ca.uhn.fhir.rest.param.TokenParam;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||||
import jakarta.annotation.Nonnull;
|
import jakarta.annotation.Nonnull;
|
||||||
import jakarta.annotation.Nullable;
|
import jakarta.annotation.Nullable;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseBundle;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface is the primary configuration and strategy provider for the
|
* This interface is the primary configuration and strategy provider for the
|
||||||
|
@ -39,11 +41,34 @@ import java.util.Set;
|
||||||
public interface IIpsGenerationStrategy {
|
public interface IIpsGenerationStrategy {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides a registry which defines the various sections that will be
|
* This method returns the profile associated with the IPS document
|
||||||
* included when generating an IPS. It can be subclassed and customized
|
* generated by this strategy.
|
||||||
* as needed in order to add, change, or remove sections.
|
|
||||||
*/
|
*/
|
||||||
SectionRegistry getSectionRegistry();
|
String getBundleProfile();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method will be called once by the framework. It can be
|
||||||
|
* used to perform any initialization.
|
||||||
|
*/
|
||||||
|
void initialize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method should return a list of the sections to include in the
|
||||||
|
* generated IPS. Note that each section must have a unique value for the
|
||||||
|
* {@link Section#getProfile()} value.
|
||||||
|
*/
|
||||||
|
@Nonnull
|
||||||
|
List<Section> 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.
|
* Provides a list of configuration property files for the IPS narrative generator.
|
||||||
|
@ -53,7 +78,7 @@ public interface IIpsGenerationStrategy {
|
||||||
* <p>
|
* <p>
|
||||||
* If more than one file is provided, the files will be evaluated in order. Therefore you
|
* 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
|
* 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
|
* in order to fall back to the default templates for any sections you have not
|
||||||
* provided an explicit template for.
|
* provided an explicit template for.
|
||||||
* </p>
|
* </p>
|
||||||
|
@ -85,7 +110,13 @@ public interface IIpsGenerationStrategy {
|
||||||
/**
|
/**
|
||||||
* This method is used to determine the resource ID to assign to a resource that
|
* 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
|
* 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 <code>null</code> to leave the resource ID as-is, or generate a
|
||||||
|
* placeholder UUID to replace it with.
|
||||||
|
* <p>
|
||||||
|
* 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 <code>IdType.newRandomUuid()</code>
|
||||||
|
* </p>
|
||||||
*
|
*
|
||||||
* @param theIpsContext The associated context for the specific IPS document being
|
* @param theIpsContext The associated context for the specific IPS document being
|
||||||
* generated. Note that this will be <code>null</code> when
|
* generated. Note that this will be <code>null</code> when
|
||||||
|
@ -93,43 +124,33 @@ public interface IIpsGenerationStrategy {
|
||||||
* be populated for all subsequent calls for a given IPS
|
* be populated for all subsequent calls for a given IPS
|
||||||
* document generation.
|
* document generation.
|
||||||
* @param theResource The resource to massage the resource ID for
|
* @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 <code>null</code> 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);
|
IIdType massageResourceId(@Nullable IpsContext theIpsContext, @Nonnull IBaseResource theResource);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method can manipulate the {@link SearchParameterMap} that will
|
* Fetches and returns the patient to include in the generated IPS for the given patient ID.
|
||||||
* 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.
|
|
||||||
* <p>
|
|
||||||
* For example, for a Vital Signs section, the implementation might add a parameter indicating
|
|
||||||
* the parameter <code>category=vital-signs</code>.
|
|
||||||
*
|
*
|
||||||
* @param theIpsSectionContext The context, which indicates the IPS section and the resource type
|
* @throws ResourceNotFoundException If the ID is not known.
|
||||||
* 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.
|
|
||||||
*/
|
*/
|
||||||
@Nonnull
|
@Nonnull
|
||||||
Set<Include> 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
|
* Fetches and returns the patient to include in the generated IPS for the given patient identifier.
|
||||||
* IPS document. The strategy can decide whether to include it or not.
|
*
|
||||||
|
* @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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -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.
|
||||||
|
* <p>
|
||||||
|
* 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.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @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 <code>null</code>.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
<T extends IBaseResource> List<ResourceEntry> fetchResourcesForSection(
|
||||||
|
IpsContext theIpsContext, IpsSectionContext<T> 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 <code>Composition.section.entry</code>
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -58,28 +58,8 @@ public class IpsContext {
|
||||||
return mySubjectId;
|
return mySubjectId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IpsSectionContext newSectionContext(IpsSectionEnum theSection, String theResourceType) {
|
public <T extends IBaseResource> IpsSectionContext<T> newSectionContext(
|
||||||
return new IpsSectionContext(mySubject, mySubjectId, theSection, theResourceType);
|
Section theSection, Class<T> 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<T extends IBaseResource> extends IpsContext {
|
||||||
|
|
||||||
|
private final Section mySection;
|
||||||
|
private final Class<T> myResourceType;
|
||||||
|
|
||||||
|
IpsSectionContext(IBaseResource theSubject, IIdType theSubjectId, Section theSection, Class<T> theResourceType) {
|
||||||
|
super(theSubject, theSubjectId);
|
||||||
|
mySection = theSection;
|
||||||
|
myResourceType = theResourceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Class<T> getResourceType() {
|
||||||
|
return myResourceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Section getSection() {
|
||||||
|
return mySection;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<Class<? extends IBaseResource>> myResourceTypes;
|
||||||
|
private final String myProfile;
|
||||||
|
private final INoInfoGenerator myNoInfoGenerator;
|
||||||
|
|
||||||
|
private final String mySectionSystem;
|
||||||
|
|
||||||
|
private Section(
|
||||||
|
String theTitle,
|
||||||
|
String theSectionSystem,
|
||||||
|
String theSectionCode,
|
||||||
|
String theSectionDisplay,
|
||||||
|
List<Class<? extends IBaseResource>> 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<Class<? extends IBaseResource>> 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<Class<? extends IBaseResource>> 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<Class<? extends IBaseResource>> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.
|
|
||||||
* <p>
|
|
||||||
* By default, all standard sections in the
|
|
||||||
* <a href="http://hl7.org/fhir/uv/ips/">base IPS specification IG</a>
|
|
||||||
* are included. You can customize this to remove sections, or to add new ones
|
|
||||||
* as permitted by the IG.
|
|
||||||
* </p>
|
|
||||||
* <p>
|
|
||||||
* 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()}.
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
public class SectionRegistry {
|
|
||||||
|
|
||||||
private final ArrayList<Section> mySections = new ArrayList<>();
|
|
||||||
private List<Consumer<SectionBuilder>> 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<SectionBuilder> theGlobalCustomizer) {
|
|
||||||
Validate.notNull(theGlobalCustomizer, "theGlobalCustomizer must not be null");
|
|
||||||
myGlobalCustomizers.add(theGlobalCustomizer);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Section> 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<String> 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<String> myResourceTypes;
|
|
||||||
private final String myProfile;
|
|
||||||
private final INoInfoGenerator myNoInfoGenerator;
|
|
||||||
|
|
||||||
public Section(
|
|
||||||
IpsSectionEnum theSectionEnum,
|
|
||||||
String theTitle,
|
|
||||||
String theSectionCode,
|
|
||||||
String theSectionDisplay,
|
|
||||||
List<String> 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<String> 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -30,11 +30,11 @@ public interface IIpsGeneratorSvc {
|
||||||
* Generates an IPS document and returns the complete document bundle
|
* Generates an IPS document and returns the complete document bundle
|
||||||
* for the given patient by ID
|
* 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
|
* Generates an IPS document and returns the complete document bundle
|
||||||
* for the given patient by identifier
|
* for the given patient by identifier
|
||||||
*/
|
*/
|
||||||
IBaseBundle generateIps(RequestDetails theRequestDetails, TokenParam thePatientIdentifier);
|
IBaseBundle generateIps(RequestDetails theRequestDetails, TokenParam thePatientIdentifier, String theProfile);
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,33 +20,23 @@
|
||||||
package ca.uhn.fhir.jpa.ips.generator;
|
package ca.uhn.fhir.jpa.ips.generator;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
|
||||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
|
||||||
import ca.uhn.fhir.fhirpath.IFhirPathEvaluationContext;
|
import ca.uhn.fhir.fhirpath.IFhirPathEvaluationContext;
|
||||||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
|
||||||
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
|
||||||
import ca.uhn.fhir.jpa.ips.api.IIpsGenerationStrategy;
|
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.IpsContext;
|
||||||
import ca.uhn.fhir.jpa.ips.api.IpsSectionEnum;
|
import ca.uhn.fhir.jpa.ips.api.IpsSectionContext;
|
||||||
import ca.uhn.fhir.jpa.ips.api.SectionRegistry;
|
import ca.uhn.fhir.jpa.ips.api.Section;
|
||||||
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.narrative.CustomThymeleafNarrativeGenerator;
|
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.api.server.RequestDetails;
|
||||||
import ca.uhn.fhir.rest.param.ReferenceParam;
|
|
||||||
import ca.uhn.fhir.rest.param.TokenParam;
|
import ca.uhn.fhir.rest.param.TokenParam;
|
||||||
import ca.uhn.fhir.util.BundleBuilder;
|
import ca.uhn.fhir.util.BundleBuilder;
|
||||||
import ca.uhn.fhir.util.CompositionBuilder;
|
import ca.uhn.fhir.util.CompositionBuilder;
|
||||||
import ca.uhn.fhir.util.ResourceReferenceInfo;
|
import ca.uhn.fhir.util.ResourceReferenceInfo;
|
||||||
import ca.uhn.fhir.util.ValidateUtil;
|
|
||||||
import com.google.common.collect.BiMap;
|
import com.google.common.collect.BiMap;
|
||||||
import com.google.common.collect.HashBiMap;
|
import com.google.common.collect.HashBiMap;
|
||||||
import jakarta.annotation.Nonnull;
|
import jakarta.annotation.Nonnull;
|
||||||
import jakarta.annotation.Nullable;
|
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.IBase;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseBundle;
|
import org.hl7.fhir.instance.model.api.IBaseBundle;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseExtension;
|
import org.hl7.fhir.instance.model.api.IBaseExtension;
|
||||||
|
@ -58,94 +48,100 @@ import org.hl7.fhir.r4.model.Bundle;
|
||||||
import org.hl7.fhir.r4.model.Composition;
|
import org.hl7.fhir.r4.model.Composition;
|
||||||
import org.hl7.fhir.r4.model.IdType;
|
import org.hl7.fhir.r4.model.IdType;
|
||||||
import org.hl7.fhir.r4.model.InstantType;
|
import org.hl7.fhir.r4.model.InstantType;
|
||||||
import org.hl7.fhir.r4.model.Patient;
|
|
||||||
import org.hl7.fhir.r4.model.Resource;
|
import org.hl7.fhir.r4.model.Resource;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import static ca.uhn.fhir.jpa.term.api.ITermLoaderSvc.LOINC_URI;
|
import static java.util.Objects.requireNonNull;
|
||||||
|
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
|
|
||||||
public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc {
|
public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc {
|
||||||
|
|
||||||
public static final int CHUNK_SIZE = 10;
|
public static final String RESOURCE_ENTRY_INCLUSION_TYPE = "RESOURCE_ENTRY_INCLUSION_TYPE";
|
||||||
private static final Logger ourLog = LoggerFactory.getLogger(IpsGeneratorSvcImpl.class);
|
public static final String URL_NARRATIVE_LINK = "http://hl7.org/fhir/StructureDefinition/narrativeLink";
|
||||||
private final IIpsGenerationStrategy myGenerationStrategy;
|
private final List<IIpsGenerationStrategy> myGenerationStrategies;
|
||||||
private final DaoRegistry myDaoRegistry;
|
|
||||||
private final FhirContext myFhirContext;
|
private final FhirContext myFhirContext;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*/
|
*/
|
||||||
public IpsGeneratorSvcImpl(
|
public IpsGeneratorSvcImpl(FhirContext theFhirContext, IIpsGenerationStrategy theGenerationStrategy) {
|
||||||
FhirContext theFhirContext, IIpsGenerationStrategy theGenerationStrategy, DaoRegistry theDaoRegistry) {
|
this(theFhirContext, List.of(theGenerationStrategy));
|
||||||
myGenerationStrategy = theGenerationStrategy;
|
}
|
||||||
myDaoRegistry = theDaoRegistry;
|
|
||||||
|
public IpsGeneratorSvcImpl(FhirContext theFhirContext, List<IIpsGenerationStrategy> theIpsGenerationStrategies) {
|
||||||
|
myGenerationStrategies = theIpsGenerationStrategies;
|
||||||
myFhirContext = theFhirContext;
|
myFhirContext = theFhirContext;
|
||||||
|
|
||||||
|
myGenerationStrategies.forEach(IIpsGenerationStrategy::initialize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate an IPS using a patient ID
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public IBaseBundle generateIps(RequestDetails theRequestDetails, IIdType thePatientId) {
|
public IBaseBundle generateIps(RequestDetails theRequestDetails, IIdType thePatientId, String theProfile) {
|
||||||
IBaseResource patient = myDaoRegistry.getResourceDao("Patient").read(thePatientId, theRequestDetails);
|
IIpsGenerationStrategy strategy = selectGenerationStrategy(theProfile);
|
||||||
|
IBaseResource patient = strategy.fetchPatient(thePatientId, theRequestDetails);
|
||||||
return generateIpsForPatient(theRequestDetails, patient);
|
return generateIpsForPatient(strategy, theRequestDetails, patient);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate an IPS using a patient identifier
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public IBaseBundle generateIps(RequestDetails theRequestDetails, TokenParam thePatientIdentifier) {
|
public IBaseBundle generateIps(
|
||||||
SearchParameterMap searchParameterMap =
|
RequestDetails theRequestDetails, TokenParam thePatientIdentifier, String theProfile) {
|
||||||
new SearchParameterMap().setLoadSynchronousUpTo(2).add(Patient.SP_IDENTIFIER, thePatientIdentifier);
|
IIpsGenerationStrategy strategy = selectGenerationStrategy(theProfile);
|
||||||
IBundleProvider searchResults =
|
IBaseResource patient = strategy.fetchPatient(thePatientIdentifier, theRequestDetails);
|
||||||
myDaoRegistry.getResourceDao("Patient").search(searchParameterMap, theRequestDetails);
|
return generateIpsForPatient(strategy, theRequestDetails, patient);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
IIdType originalSubjectId = myFhirContext
|
||||||
.getVersion()
|
.getVersion()
|
||||||
.newIdType()
|
.newIdType()
|
||||||
.setValue(thePatient.getIdElement().getValue())
|
.setValue(thePatient.getIdElement().getValue())
|
||||||
.toUnqualifiedVersionless();
|
.toUnqualifiedVersionless();
|
||||||
massageResourceId(null, thePatient);
|
massageResourceId(theStrategy, theRequestDetails, null, thePatient);
|
||||||
IpsContext context = new IpsContext(thePatient, originalSubjectId);
|
IpsContext context = new IpsContext(thePatient, originalSubjectId);
|
||||||
|
|
||||||
ResourceInclusionCollection globalResourcesToInclude = new ResourceInclusionCollection();
|
ResourceInclusionCollection globalResourcesToInclude = new ResourceInclusionCollection();
|
||||||
globalResourcesToInclude.addResourceIfNotAlreadyPresent(thePatient, originalSubjectId.getValue());
|
globalResourcesToInclude.addResourceIfNotAlreadyPresent(thePatient, originalSubjectId.getValue());
|
||||||
|
|
||||||
IBaseResource author = myGenerationStrategy.createAuthor();
|
IBaseResource author = theStrategy.createAuthor();
|
||||||
massageResourceId(context, author);
|
massageResourceId(theStrategy, theRequestDetails, context, author);
|
||||||
|
|
||||||
CompositionBuilder compositionBuilder = createComposition(thePatient, context, author);
|
CompositionBuilder compositionBuilder = createComposition(theStrategy, thePatient, context, author);
|
||||||
determineInclusions(
|
determineInclusions(theStrategy, theRequestDetails, context, compositionBuilder, globalResourcesToInclude);
|
||||||
theRequestDetails, originalSubjectId, context, compositionBuilder, globalResourcesToInclude);
|
|
||||||
|
|
||||||
IBaseResource composition = compositionBuilder.getComposition();
|
IBaseResource composition = compositionBuilder.getComposition();
|
||||||
|
|
||||||
// Create the narrative for the Composition itself
|
// Create the narrative for the Composition itself
|
||||||
CustomThymeleafNarrativeGenerator generator = newNarrativeGenerator(globalResourcesToInclude);
|
CustomThymeleafNarrativeGenerator generator = newNarrativeGenerator(theStrategy, globalResourcesToInclude);
|
||||||
generator.populateResourceNarrative(myFhirContext, composition);
|
generator.populateResourceNarrative(myFhirContext, composition);
|
||||||
|
|
||||||
return createCompositionDocument(author, composition, globalResourcesToInclude);
|
return createDocumentBundleForComposition(theStrategy, author, composition, globalResourcesToInclude);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IBaseBundle createCompositionDocument(
|
private IBaseBundle createDocumentBundleForComposition(
|
||||||
IBaseResource author, IBaseResource composition, ResourceInclusionCollection theResourcesToInclude) {
|
IIpsGenerationStrategy theStrategy,
|
||||||
|
IBaseResource author,
|
||||||
|
IBaseResource composition,
|
||||||
|
ResourceInclusionCollection theResourcesToInclude) {
|
||||||
BundleBuilder bundleBuilder = new BundleBuilder(myFhirContext);
|
BundleBuilder bundleBuilder = new BundleBuilder(myFhirContext);
|
||||||
bundleBuilder.setType(Bundle.BundleType.DOCUMENT.toCode());
|
bundleBuilder.setType(Bundle.BundleType.DOCUMENT.toCode());
|
||||||
bundleBuilder.setIdentifier("urn:ietf:rfc:4122", UUID.randomUUID().toString());
|
bundleBuilder.setIdentifier("urn:ietf:rfc:4122", UUID.randomUUID().toString());
|
||||||
|
@ -162,124 +158,51 @@ public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc {
|
||||||
// Add author to document
|
// Add author to document
|
||||||
bundleBuilder.addDocumentEntry(author);
|
bundleBuilder.addDocumentEntry(author);
|
||||||
|
|
||||||
return bundleBuilder.getBundle();
|
IBaseBundle retVal = bundleBuilder.getBundle();
|
||||||
|
|
||||||
|
theStrategy.postManipulateIpsBundle(retVal);
|
||||||
|
|
||||||
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
private void determineInclusions(
|
||||||
private ResourceInclusionCollection determineInclusions(
|
IIpsGenerationStrategy theStrategy,
|
||||||
RequestDetails theRequestDetails,
|
RequestDetails theRequestDetails,
|
||||||
IIdType originalSubjectId,
|
IpsContext theIpsContext,
|
||||||
IpsContext context,
|
|
||||||
CompositionBuilder theCompositionBuilder,
|
CompositionBuilder theCompositionBuilder,
|
||||||
ResourceInclusionCollection theGlobalResourcesToInclude) {
|
ResourceInclusionCollection theGlobalResourcesToInclude) {
|
||||||
SectionRegistry sectionRegistry = myGenerationStrategy.getSectionRegistry();
|
for (Section nextSection : theStrategy.getSections()) {
|
||||||
for (SectionRegistry.Section nextSection : sectionRegistry.getSections()) {
|
|
||||||
determineInclusionsForSection(
|
determineInclusionsForSection(
|
||||||
|
theStrategy,
|
||||||
theRequestDetails,
|
theRequestDetails,
|
||||||
originalSubjectId,
|
theIpsContext,
|
||||||
context,
|
|
||||||
theCompositionBuilder,
|
theCompositionBuilder,
|
||||||
theGlobalResourcesToInclude,
|
theGlobalResourcesToInclude,
|
||||||
nextSection);
|
nextSection);
|
||||||
}
|
}
|
||||||
return theGlobalResourcesToInclude;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void determineInclusionsForSection(
|
private void determineInclusionsForSection(
|
||||||
|
IIpsGenerationStrategy theStrategy,
|
||||||
RequestDetails theRequestDetails,
|
RequestDetails theRequestDetails,
|
||||||
IIdType theOriginalSubjectId,
|
|
||||||
IpsContext theIpsContext,
|
IpsContext theIpsContext,
|
||||||
CompositionBuilder theCompositionBuilder,
|
CompositionBuilder theCompositionBuilder,
|
||||||
ResourceInclusionCollection theGlobalResourcesToInclude,
|
ResourceInclusionCollection theGlobalResourceCollectionToPopulate,
|
||||||
SectionRegistry.Section theSection) {
|
Section theSection) {
|
||||||
ResourceInclusionCollection sectionResourcesToInclude = new ResourceInclusionCollection();
|
ResourceInclusionCollection sectionResourceCollectionToPopulate = new ResourceInclusionCollection();
|
||||||
for (String nextResourceType : theSection.getResourceTypes()) {
|
ISectionResourceSupplier resourceSupplier = theStrategy.getSectionResourceSupplier(theSection);
|
||||||
|
|
||||||
SearchParameterMap searchParameterMap = new SearchParameterMap();
|
determineInclusionsForSectionResourceTypes(
|
||||||
String subjectSp = determinePatientCompartmentSearchParameterName(nextResourceType);
|
theStrategy,
|
||||||
searchParameterMap.add(subjectSp, new ReferenceParam(theOriginalSubjectId));
|
theRequestDetails,
|
||||||
|
theIpsContext,
|
||||||
|
theGlobalResourceCollectionToPopulate,
|
||||||
|
theSection,
|
||||||
|
resourceSupplier,
|
||||||
|
sectionResourceCollectionToPopulate);
|
||||||
|
|
||||||
IpsSectionEnum sectionEnum = theSection.getSectionEnum();
|
generateSectionNoInfoResourceIfNoInclusionsFound(
|
||||||
IpsContext.IpsSectionContext ipsSectionContext =
|
theIpsContext, theGlobalResourceCollectionToPopulate, theSection, sectionResourceCollectionToPopulate);
|
||||||
theIpsContext.newSectionContext(sectionEnum, nextResourceType);
|
|
||||||
myGenerationStrategy.massageResourceSearch(ipsSectionContext, searchParameterMap);
|
|
||||||
|
|
||||||
Set<Include> 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<IBaseResource> 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Update any references within the added candidates - This is important
|
* 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
|
* the summary, so we need to also update the references to those
|
||||||
* resources.
|
* 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<ResourceReferenceInfo> references = myFhirContext.newTerser().getAllResourceReferences(nextResource);
|
List<ResourceReferenceInfo> references = myFhirContext.newTerser().getAllResourceReferences(nextResource);
|
||||||
for (ResourceReferenceInfo nextReference : references) {
|
for (ResourceReferenceInfo nextReference : references) {
|
||||||
String existingReference = nextReference
|
String existingReference = nextReference
|
||||||
|
@ -298,12 +237,12 @@ public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc {
|
||||||
existingReference = new IdType(existingReference)
|
existingReference = new IdType(existingReference)
|
||||||
.toUnqualifiedVersionless()
|
.toUnqualifiedVersionless()
|
||||||
.getValue();
|
.getValue();
|
||||||
String replacement = theGlobalResourcesToInclude.getIdSubstitution(existingReference);
|
String replacement = theGlobalResourceCollectionToPopulate.getIdSubstitution(existingReference);
|
||||||
if (isNotBlank(replacement)) {
|
if (isNotBlank(replacement)) {
|
||||||
if (!replacement.equals(existingReference)) {
|
if (!replacement.equals(existingReference)) {
|
||||||
nextReference.getResourceReference().setReference(replacement);
|
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
|
// If this reference doesn't point to something we have actually
|
||||||
// included in the bundle, clear the reference.
|
// included in the bundle, clear the reference.
|
||||||
nextReference.getResourceReference().setReference(null);
|
nextReference.getResourceReference().setReference(null);
|
||||||
|
@ -312,17 +251,184 @@ public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (sectionResourcesToInclude.isEmpty()) {
|
private static void generateSectionNoInfoResourceIfNoInclusionsFound(
|
||||||
return;
|
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 <T extends IBaseResource> void determineInclusionsForSectionResourceType(
|
||||||
|
IIpsGenerationStrategy theStrategy,
|
||||||
|
RequestDetails theRequestDetails,
|
||||||
|
IpsContext theIpsContext,
|
||||||
|
ResourceInclusionCollection theGlobalResourceCollectionToPopulate,
|
||||||
|
Section theSection,
|
||||||
|
Class<T> nextResourceType,
|
||||||
|
ISectionResourceSupplier resourceSupplier,
|
||||||
|
ResourceInclusionCollection sectionResourceCollectionToPopulate) {
|
||||||
|
IpsSectionContext<T> ipsSectionContext = theIpsContext.newSectionContext(theSection, nextResourceType);
|
||||||
|
|
||||||
|
List<ISectionResourceSupplier.ResourceEntry> 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<ISectionResourceSupplier.ResourceEntry> 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")
|
@SuppressWarnings("unchecked")
|
||||||
private void addSection(
|
private void addSection(
|
||||||
SectionRegistry.Section theSection,
|
IIpsGenerationStrategy theStrategy,
|
||||||
|
Section theSection,
|
||||||
CompositionBuilder theCompositionBuilder,
|
CompositionBuilder theCompositionBuilder,
|
||||||
ResourceInclusionCollection theResourcesToInclude,
|
ResourceInclusionCollection theResourcesToInclude,
|
||||||
ResourceInclusionCollection theGlobalResourcesToInclude) {
|
ResourceInclusionCollection theGlobalResourcesToInclude) {
|
||||||
|
@ -330,34 +436,44 @@ public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc {
|
||||||
CompositionBuilder.SectionBuilder sectionBuilder = theCompositionBuilder.addSection();
|
CompositionBuilder.SectionBuilder sectionBuilder = theCompositionBuilder.addSection();
|
||||||
|
|
||||||
sectionBuilder.setTitle(theSection.getTitle());
|
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()) {
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
IBaseExtension<?, ?> narrativeLink = ((IBaseHasExtensions) next).addExtension();
|
IBaseHasExtensions extensionHolder = (IBaseHasExtensions) next;
|
||||||
narrativeLink.setUrl("http://hl7.org/fhir/StructureDefinition/narrativeLink");
|
if (extensionHolder.getExtension().stream()
|
||||||
String narrativeLinkValue =
|
.noneMatch(t -> t.getUrl().equals(URL_NARRATIVE_LINK))) {
|
||||||
theCompositionBuilder.getComposition().getIdElement().getValue()
|
IBaseExtension<?, ?> narrativeLink = extensionHolder.addExtension();
|
||||||
+ "#"
|
narrativeLink.setUrl(URL_NARRATIVE_LINK);
|
||||||
+ myFhirContext.getResourceType(next)
|
String narrativeLinkValue =
|
||||||
+ "-"
|
theCompositionBuilder.getComposition().getIdElement().getValue()
|
||||||
+ next.getIdElement().getValue();
|
+ "#"
|
||||||
IPrimitiveType<String> narrativeLinkUri = (IPrimitiveType<String>)
|
+ myFhirContext.getResourceType(next)
|
||||||
myFhirContext.getElementDefinition("url").newInstance();
|
+ "-"
|
||||||
narrativeLinkUri.setValueAsString(narrativeLinkValue);
|
+ next.getIdElement().getValue();
|
||||||
narrativeLink.setValue(narrativeLinkUri);
|
IPrimitiveType<String> narrativeLinkUri =
|
||||||
|
(IPrimitiveType<String>) requireNonNull(myFhirContext.getElementDefinition("url"))
|
||||||
|
.newInstance();
|
||||||
|
narrativeLinkUri.setValueAsString(narrativeLinkValue);
|
||||||
|
narrativeLink.setValue(narrativeLinkUri);
|
||||||
|
}
|
||||||
|
|
||||||
sectionBuilder.addEntry(next.getIdElement());
|
sectionBuilder.addEntry(next.getIdElement());
|
||||||
}
|
}
|
||||||
|
|
||||||
String narrative = createSectionNarrative(theSection, theResourcesToInclude, theGlobalResourcesToInclude);
|
String narrative =
|
||||||
|
createSectionNarrative(theStrategy, theSection, theResourcesToInclude, theGlobalResourcesToInclude);
|
||||||
sectionBuilder.setText("generated", narrative);
|
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 compositionBuilder = new CompositionBuilder(myFhirContext);
|
||||||
compositionBuilder.setId(IdType.newRandomUuid());
|
compositionBuilder.setId(IdType.newRandomUuid());
|
||||||
|
|
||||||
|
@ -365,43 +481,44 @@ public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc {
|
||||||
compositionBuilder.setSubject(thePatient.getIdElement().toUnqualifiedVersionless());
|
compositionBuilder.setSubject(thePatient.getIdElement().toUnqualifiedVersionless());
|
||||||
compositionBuilder.addTypeCoding("http://loinc.org", "60591-5", "Patient Summary Document");
|
compositionBuilder.addTypeCoding("http://loinc.org", "60591-5", "Patient Summary Document");
|
||||||
compositionBuilder.setDate(InstantType.now());
|
compositionBuilder.setDate(InstantType.now());
|
||||||
compositionBuilder.setTitle(myGenerationStrategy.createTitle(context));
|
compositionBuilder.setTitle(theStrategy.createTitle(context));
|
||||||
compositionBuilder.setConfidentiality(myGenerationStrategy.createConfidentiality(context));
|
compositionBuilder.setConfidentiality(theStrategy.createConfidentiality(context));
|
||||||
compositionBuilder.addAuthor(author.getIdElement());
|
compositionBuilder.addAuthor(author.getIdElement());
|
||||||
|
|
||||||
return compositionBuilder;
|
return compositionBuilder;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String determinePatientCompartmentSearchParameterName(String theResourceType) {
|
private void massageResourceId(
|
||||||
RuntimeResourceDefinition resourceDef = myFhirContext.getResourceDefinition(theResourceType);
|
IIpsGenerationStrategy theStrategy,
|
||||||
Set<String> searchParams = resourceDef.getSearchParamsForCompartmentName("Patient").stream()
|
RequestDetails theRequestDetails,
|
||||||
.map(RuntimeSearchParam::getName)
|
IpsContext theIpsContext,
|
||||||
.collect(Collectors.toSet());
|
IBaseResource theResource) {
|
||||||
// Prefer "patient", then "subject" then anything else
|
String base = theRequestDetails.getFhirServerBase();
|
||||||
if (searchParams.contains(Observation.SP_PATIENT)) {
|
|
||||||
return Observation.SP_PATIENT;
|
|
||||||
}
|
|
||||||
if (searchParams.contains(Observation.SP_SUBJECT)) {
|
|
||||||
return Observation.SP_SUBJECT;
|
|
||||||
}
|
|
||||||
return searchParams.iterator().next();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void massageResourceId(IpsContext theIpsContext, IBaseResource theResource) {
|
IIdType id = theResource.getIdElement();
|
||||||
IIdType id = myGenerationStrategy.massageResourceId(theIpsContext, theResource);
|
if (!id.hasBaseUrl() && id.hasResourceType() && id.hasIdPart()) {
|
||||||
theResource.setId(id);
|
id = id.withServerBase(base, id.getResourceType());
|
||||||
|
theResource.setId(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
id = theStrategy.massageResourceId(theIpsContext, theResource);
|
||||||
|
if (id != null) {
|
||||||
|
theResource.setId(id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String createSectionNarrative(
|
private String createSectionNarrative(
|
||||||
SectionRegistry.Section theSection,
|
IIpsGenerationStrategy theStrategy,
|
||||||
|
Section theSection,
|
||||||
ResourceInclusionCollection theResources,
|
ResourceInclusionCollection theResources,
|
||||||
ResourceInclusionCollection theGlobalResourceCollection) {
|
ResourceInclusionCollection theGlobalResourceCollection) {
|
||||||
CustomThymeleafNarrativeGenerator generator = newNarrativeGenerator(theGlobalResourceCollection);
|
CustomThymeleafNarrativeGenerator generator = newNarrativeGenerator(theStrategy, theGlobalResourceCollection);
|
||||||
|
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
for (IBaseResource resource : theResources.getResources()) {
|
for (IBaseResource resource : theResources.getResources()) {
|
||||||
BundleEntrySearchModeEnum searchMode = ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.get(resource);
|
ISectionResourceSupplier.InclusionTypeEnum inclusionType =
|
||||||
if (searchMode == BundleEntrySearchModeEnum.MATCH) {
|
(ISectionResourceSupplier.InclusionTypeEnum) resource.getUserData(RESOURCE_ENTRY_INCLUSION_TYPE);
|
||||||
|
if (inclusionType == ISectionResourceSupplier.InclusionTypeEnum.PRIMARY_RESOURCE) {
|
||||||
bundle.addEntry().setResource((Resource) resource);
|
bundle.addEntry().setResource((Resource) resource);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -414,14 +531,13 @@ public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc {
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
private CustomThymeleafNarrativeGenerator newNarrativeGenerator(
|
private CustomThymeleafNarrativeGenerator newNarrativeGenerator(
|
||||||
ResourceInclusionCollection theGlobalResourceCollection) {
|
IIpsGenerationStrategy theStrategy, ResourceInclusionCollection theGlobalResourceCollection) {
|
||||||
List<String> narrativePropertyFiles = myGenerationStrategy.getNarrativePropertyFiles();
|
List<String> narrativePropertyFiles = theStrategy.getNarrativePropertyFiles();
|
||||||
CustomThymeleafNarrativeGenerator generator = new CustomThymeleafNarrativeGenerator(narrativePropertyFiles);
|
CustomThymeleafNarrativeGenerator generator = new CustomThymeleafNarrativeGenerator(narrativePropertyFiles);
|
||||||
generator.setFhirPathEvaluationContext(new IFhirPathEvaluationContext() {
|
generator.setFhirPathEvaluationContext(new IFhirPathEvaluationContext() {
|
||||||
@Override
|
@Override
|
||||||
public IBase resolveReference(@Nonnull IIdType theReference, @Nullable IBase theContext) {
|
public IBase resolveReference(@Nonnull IIdType theReference, @Nullable IBase theContext) {
|
||||||
IBaseResource resource = theGlobalResourceCollection.getResourceById(theReference);
|
return theGlobalResourceCollection.getResourceById(theReference);
|
||||||
return resource;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return generator;
|
return generator;
|
||||||
|
|
|
@ -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.
|
||||||
|
* <p>
|
||||||
|
* 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.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
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<Function<Section, Section>> 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<Section, Section> 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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<T extends IBaseResource> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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. <code>?patient=Patient/123</code>), but no
|
||||||
|
* other parameters. This method can add other parameters. The default implementation of this
|
||||||
|
* interface performs no action.
|
||||||
|
* <p>
|
||||||
|
* For example, for a Vital Signs section, the implementation might add a parameter indicating
|
||||||
|
* the parameter <code>category=vital-signs</code>.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @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<T> 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}.
|
||||||
|
* <p>
|
||||||
|
* This method is called once for every resource that is being considered for inclusion
|
||||||
|
* in an IPS section.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
default boolean shouldInclude(@Nonnull IpsSectionContext<T> theIpsSectionContext, @Nonnull T theCandidate) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 {}
|
|
@ -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 <T extends IBaseResource> List<ResourceEntry> fetchResourcesForSection(
|
||||||
|
IpsContext theIpsContext, IpsSectionContext<T> theIpsSectionContext, RequestDetails theRequestDetails) {
|
||||||
|
|
||||||
|
IJpaSectionSearchStrategy<T> 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<T> dao = myDaoRegistry.getResourceDao(theIpsSectionContext.getResourceType());
|
||||||
|
IBundleProvider searchResult = dao.search(searchParameterMap, theRequestDetails);
|
||||||
|
|
||||||
|
List<ResourceEntry> retVal = null;
|
||||||
|
for (int startIndex = 0; ; startIndex += CHUNK_SIZE) {
|
||||||
|
int endIndex = startIndex + CHUNK_SIZE;
|
||||||
|
List<IBaseResource> 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<String> 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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,21 +17,12 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
* #L%
|
* #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<T extends IBaseResource> implements IJpaSectionSearchStrategy<T> {
|
||||||
|
|
||||||
|
// 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
|
|
||||||
}
|
}
|
|
@ -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<Class<? extends IBaseResource>, Object> mySearchStrategies;
|
||||||
|
|
||||||
|
private JpaSectionSearchStrategyCollection(Map<Class<? extends IBaseResource>, Object> theSearchStrategies) {
|
||||||
|
mySearchStrategies = theSearchStrategies;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T extends IBaseResource> IJpaSectionSearchStrategy<T> getSearchStrategy(Class<T> theClass) {
|
||||||
|
return (IJpaSectionSearchStrategy<T>) mySearchStrategies.get(theClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<Class<? extends IBaseResource>> getResourceTypes() {
|
||||||
|
return mySearchStrategies.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JpaSectionSearchStrategyCollectionBuilder newBuilder() {
|
||||||
|
return new JpaSectionSearchStrategyCollectionBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class JpaSectionSearchStrategyCollectionBuilder {
|
||||||
|
private Map<Class<? extends IBaseResource>, Object> mySearchStrategies = new HashMap<>();
|
||||||
|
|
||||||
|
public <T extends IBaseResource> JpaSectionSearchStrategyCollectionBuilder addStrategy(
|
||||||
|
Class<T> theType, IJpaSectionSearchStrategy<T> theSearchStrategy) {
|
||||||
|
mySearchStrategies.put(theType, theSearchStrategy);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JpaSectionSearchStrategyCollection build() {
|
||||||
|
return new JpaSectionSearchStrategyCollection(mySearchStrategies);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<Consent> {
|
||||||
|
|
||||||
|
@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())));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<AllergyIntolerance> {
|
||||||
|
|
||||||
|
@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");
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<DiagnosticReport> {
|
||||||
|
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<Observation> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void massageResourceSearch(
|
||||||
|
@Nonnull IpsSectionContext<Observation> 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<Observation> 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<ClinicalImpression> {
|
||||||
|
|
||||||
|
@SuppressWarnings("RedundantIfStatement")
|
||||||
|
@Override
|
||||||
|
public boolean shouldInclude(
|
||||||
|
@Nonnull IpsSectionContext<ClinicalImpression> theIpsSectionContext,
|
||||||
|
@Nonnull ClinicalImpression theCandidate) {
|
||||||
|
if (theCandidate.getStatus() == ClinicalImpression.ClinicalImpressionStatus.INPROGRESS
|
||||||
|
|| theCandidate.getStatus() == ClinicalImpression.ClinicalImpressionStatus.ENTEREDINERROR) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<Condition> {
|
||||||
|
|
||||||
|
@SuppressWarnings("RedundantIfStatement")
|
||||||
|
@Override
|
||||||
|
public boolean shouldInclude(
|
||||||
|
@Nonnull IpsSectionContext<Condition> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<Immunization> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void massageResourceSearch(
|
||||||
|
@Nonnull IpsSectionContext<Immunization> 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<Immunization> theIpsSectionContext, @Nonnull Immunization theCandidate) {
|
||||||
|
if (theCandidate.getStatus() == Immunization.ImmunizationStatus.ENTEREDINERROR) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<DeviceUseStatement> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void massageResourceSearch(
|
||||||
|
@Nonnull IpsSectionContext<DeviceUseStatement> theIpsSectionContext,
|
||||||
|
@Nonnull SearchParameterMap theSearchParameterMap) {
|
||||||
|
theSearchParameterMap.addInclude(DeviceUseStatement.INCLUDE_DEVICE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("RedundantIfStatement")
|
||||||
|
@Override
|
||||||
|
public boolean shouldInclude(
|
||||||
|
@Nonnull IpsSectionContext<DeviceUseStatement> theIpsSectionContext,
|
||||||
|
@Nonnull DeviceUseStatement theCandidate) {
|
||||||
|
if (theCandidate.getStatus() == DeviceUseStatement.DeviceUseStatementStatus.ENTEREDINERROR) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<MedicationAdministration> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void massageResourceSearch(
|
||||||
|
@Nonnull IpsSectionContext<MedicationAdministration> 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())));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<MedicationDispense> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void massageResourceSearch(
|
||||||
|
@Nonnull IpsSectionContext<MedicationDispense> 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())));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<MedicationRequest> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void massageResourceSearch(
|
||||||
|
@Nonnull IpsSectionContext<MedicationRequest> 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())));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<MedicationStatement> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void massageResourceSearch(
|
||||||
|
@Nonnull IpsSectionContext<MedicationStatement> 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())));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<CarePlan> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void massageResourceSearch(
|
||||||
|
@Nonnull IpsSectionContext<CarePlan> 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())));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<Observation> {
|
||||||
|
|
||||||
|
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<Observation> 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<Observation> theIpsSectionContext, @Nonnull Observation theCandidate) {
|
||||||
|
// code filtering not yet applied
|
||||||
|
if (theCandidate.getStatus() == Observation.ObservationStatus.PRELIMINARY) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<Condition> {
|
||||||
|
|
||||||
|
@SuppressWarnings("RedundantIfStatement")
|
||||||
|
@Override
|
||||||
|
public boolean shouldInclude(
|
||||||
|
@Nonnull IpsSectionContext<Condition> 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<Procedure> {
|
||||||
|
|
||||||
|
@SuppressWarnings("RedundantIfStatement")
|
||||||
|
@Override
|
||||||
|
public boolean shouldInclude(
|
||||||
|
@Nonnull IpsSectionContext<Procedure> theIpsSectionContext, @Nonnull Procedure theCandidate) {
|
||||||
|
if (theCandidate.getStatus() == Procedure.ProcedureStatus.ENTEREDINERROR
|
||||||
|
|| theCandidate.getStatus() == Procedure.ProcedureStatus.NOTDONE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<Observation> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void massageResourceSearch(
|
||||||
|
@Nonnull IpsSectionContext<Observation> 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<Observation> theIpsSectionContext, @Nonnull Observation theCandidate) {
|
||||||
|
// code filtering not yet applied
|
||||||
|
if (theCandidate.getStatus() == Observation.ObservationStatus.PRELIMINARY) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<Observation> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void massageResourceSearch(
|
||||||
|
@Nonnull IpsSectionContext<Observation> 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<Observation> 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,8 +28,14 @@ import ca.uhn.fhir.rest.annotation.Operation;
|
||||||
import ca.uhn.fhir.rest.annotation.OperationParam;
|
import ca.uhn.fhir.rest.annotation.OperationParam;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
import ca.uhn.fhir.rest.param.TokenParam;
|
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.IBaseBundle;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||||
|
import org.thymeleaf.util.Validate;
|
||||||
|
|
||||||
public class IpsOperationProvider {
|
public class IpsOperationProvider {
|
||||||
|
|
||||||
|
@ -38,7 +44,8 @@ public class IpsOperationProvider {
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*/
|
*/
|
||||||
public IpsOperationProvider(IIpsGeneratorSvc theIpsGeneratorSvc) {
|
public IpsOperationProvider(@Nonnull IIpsGeneratorSvc theIpsGeneratorSvc) {
|
||||||
|
Validate.notNull(theIpsGeneratorSvc, "theIpsGeneratorSvc must not be null");
|
||||||
myIpsGeneratorSvc = theIpsGeneratorSvc;
|
myIpsGeneratorSvc = theIpsGeneratorSvc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,9 +61,12 @@ public class IpsOperationProvider {
|
||||||
bundleType = BundleTypeEnum.DOCUMENT,
|
bundleType = BundleTypeEnum.DOCUMENT,
|
||||||
typeName = "Patient",
|
typeName = "Patient",
|
||||||
canonicalUrl = JpaConstants.SUMMARY_OPERATION_URL)
|
canonicalUrl = JpaConstants.SUMMARY_OPERATION_URL)
|
||||||
public IBaseBundle patientInstanceSummary(@IdParam IIdType thePatientId, RequestDetails theRequestDetails) {
|
public IBaseBundle patientInstanceSummary(
|
||||||
|
@IdParam IIdType thePatientId,
|
||||||
return myIpsGeneratorSvc.generateIps(theRequestDetails, thePatientId);
|
@OperationParam(name = "profile", min = 0, typeName = "uri") IPrimitiveType<String> 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",
|
typeName = "Patient",
|
||||||
canonicalUrl = JpaConstants.SUMMARY_OPERATION_URL)
|
canonicalUrl = JpaConstants.SUMMARY_OPERATION_URL)
|
||||||
public IBaseBundle patientTypeSummary(
|
public IBaseBundle patientTypeSummary(
|
||||||
|
@OperationParam(name = "profile", min = 0, typeName = "uri") IPrimitiveType<String> theProfile,
|
||||||
@Description(
|
@Description(
|
||||||
shortDefinition =
|
shortDefinition =
|
||||||
"When the logical id of the patient is not used, servers MAY choose to support patient selection based on provided identifier")
|
"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)
|
@OperationParam(name = "identifier", min = 1, max = 1, typeName = "Identifier")
|
||||||
TokenParam thePatientIdentifier,
|
IBase thePatientIdentifier,
|
||||||
RequestDetails theRequestDetails) {
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<Section> mySections = new ArrayList<>();
|
||||||
|
private final Map<Section, ISectionResourceSupplier> 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<Section> 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<String> 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<String> 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<Include> 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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,8 +6,8 @@ Action Controlled: Consent.provision.action[x].{ text || coding[x].display (sepa
|
||||||
Date: Consent.dateTime
|
Date: Consent.dateTime
|
||||||
*/-->
|
*/-->
|
||||||
<div xmlns:th="http://www.thymeleaf.org">
|
<div xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<h5>Advance Directives</h5>
|
||||||
<table class="hapiPropertyTable">
|
<table class="hapiPropertyTable">
|
||||||
<caption>Advance Directives</caption>
|
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Scope</th>
|
<th>Scope</th>
|
||||||
|
@ -21,9 +21,9 @@ Date: Consent.dateTime
|
||||||
<th:block th:unless='*{getResourceType().name() == "Composition"}'>
|
<th:block th:unless='*{getResourceType().name() == "Composition"}'>
|
||||||
<th:block th:with="extension=${entry.getResource().getExtensionByUrl('http://hl7.org/fhir/StructureDefinition/narrativeLink').getValue().getValue()}">
|
<th:block th:with="extension=${entry.getResource().getExtensionByUrl('http://hl7.org/fhir/StructureDefinition/narrativeLink').getValue().getValue()}">
|
||||||
<tr th:id="${#strings.arraySplit(extension, '#')[1]}">
|
<tr th:id="${#strings.arraySplit(extension, '#')[1]}">
|
||||||
<td th:insert="IpsUtilityFragments :: codeableConcept (cc=*{getScope()},attr='display')">Scope</td>
|
<td th:insert="~{IpsUtilityFragments :: codeableConcept (cc=*{getScope()},attr='display')}">Scope</td>
|
||||||
<td th:text="*{getStatus().getDisplay()}">Status</td>
|
<td th:text="*{getStatus().getDisplay()}">Status</td>
|
||||||
<td th:insert="IpsUtilityFragments :: concatCodeableConcept (list=*{getProvision().getAction()})">Action Controlled</td>
|
<td th:insert="~{IpsUtilityFragments :: concatCodeableConcept (list=*{getProvision().getAction()})}">Action Controlled</td>
|
||||||
<td th:text="*{getDateTimeElement().getValue()}">Date</td>
|
<td th:text="*{getDateTimeElement().getValue()}">Date</td>
|
||||||
</tr>
|
</tr>
|
||||||
</th:block>
|
</th:block>
|
||||||
|
|
|
@ -8,8 +8,8 @@ Severity: AllergyIntolerance.reaction.severity[x].code (separated by <br />)
|
||||||
Comments: AllergyIntolerance.note[x].text (separated by <br />)
|
Comments: AllergyIntolerance.note[x].text (separated by <br />)
|
||||||
*/-->
|
*/-->
|
||||||
<div xmlns:th="http://www.thymeleaf.org">
|
<div xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<h5>Allergies And Intolerances</h5>
|
||||||
<table class="hapiPropertyTable">
|
<table class="hapiPropertyTable">
|
||||||
<caption>Allergies And Intolerances</caption>
|
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Allergen</th>
|
<th>Allergen</th>
|
||||||
|
@ -27,10 +27,10 @@ Comments: AllergyIntolerance.note[x].text (separated by <br />)
|
||||||
<tr th:id="${#strings.arraySplit(extension, '#')[1]}">
|
<tr th:id="${#strings.arraySplit(extension, '#')[1]}">
|
||||||
<td th:insert="IpsUtilityFragments :: codeableConcept (cc=*{getCode()},attr='display')">Allergen</td>
|
<td th:insert="IpsUtilityFragments :: codeableConcept (cc=*{getCode()},attr='display')">Allergen</td>
|
||||||
<td th:insert="~{IpsUtilityFragments :: codeableConcept (cc=*{getClinicalStatus()},attr='code')}">Status</td>
|
<td th:insert="~{IpsUtilityFragments :: codeableConcept (cc=*{getClinicalStatus()},attr='code')}">Status</td>
|
||||||
<td th:insert="IpsUtilityFragments :: concat (list=*{getCategory()},attr='value')">Category</td>
|
<td th:insert="~{IpsUtilityFragments :: concat (list=*{getCategory()},attr='value')}">Category</td>
|
||||||
<td th:insert="IpsUtilityFragments :: concatReactionManifestation (list=*{getReaction()})">Reaction</td>
|
<td th:insert="~{IpsUtilityFragments :: concatReactionManifestation (list=*{getReaction()})}">Reaction</td>
|
||||||
<td th:insert="IpsUtilityFragments :: concat (list=*{getReaction()},attr='severity')">Severity</td>
|
<td th:insert="~{IpsUtilityFragments :: concat (list=*{getReaction()},attr='severity')}">Severity</td>
|
||||||
<td th:insert="IpsUtilityFragments :: concat (list=*{getNote()},attr='text')">Comments</td>
|
<td th:insert="~{IpsUtilityFragments :: concat (list=*{getNote()},attr='text')}">Comments</td>
|
||||||
|
|
||||||
<th:block th:if="*{hasOnsetDateTimeType()}">
|
<th:block th:if="*{hasOnsetDateTimeType()}">
|
||||||
<td th:text="*{getOnsetDateTimeType().getValue()}">Onset</td>
|
<td th:text="*{getOnsetDateTimeType().getValue()}">Onset</td>
|
||||||
|
|
|
@ -14,61 +14,75 @@ Code: DiagnosticReport.code.text || DiagnosticReport.code.coding[x].display (sep
|
||||||
Date: DiagnosticReport.effectiveDateTime || DiagnosticReport.effectivePeriod.start
|
Date: DiagnosticReport.effectiveDateTime || DiagnosticReport.effectivePeriod.start
|
||||||
*/-->
|
*/-->
|
||||||
<div xmlns:th="http://www.thymeleaf.org">
|
<div xmlns:th="http://www.thymeleaf.org">
|
||||||
<table class="hapiPropertyTable">
|
<th:block th:if="${narrativeUtil.bundleHasEntriesWithResourceType(resource, 'Observation')}">
|
||||||
<caption>Diagnostic Results: Observations</caption>
|
<h5>Diagnostic Results: Observations</h5>
|
||||||
<thead>
|
<table class="hapiPropertyTable">
|
||||||
<tr>
|
<thead>
|
||||||
<th>Code</th>
|
<tr>
|
||||||
<th>Result</th>
|
<th>Code</th>
|
||||||
<th>Unit</th>
|
<th>Result</th>
|
||||||
<th>Interpretation</th>
|
<th>Unit</th>
|
||||||
<th>Reference Range</th>
|
<th>Interpretation</th>
|
||||||
<th>Comments</th>
|
<th>Reference Range</th>
|
||||||
<th>Date</th>
|
<th>Comments</th>
|
||||||
</tr>
|
<th>Date</th>
|
||||||
</thead>
|
</tr>
|
||||||
<tbody>
|
</thead>
|
||||||
<th:block th:each="entry : ${resource.entry}" th:object="${entry.getResource()}">
|
<tbody>
|
||||||
<th:block th:if='*{getResourceType().name() == "Observation"}'>
|
<th:block th:each="entry : ${resource.entry}" th:object="${entry.getResource()}">
|
||||||
<th:block th:unless='*{getResourceType().name() == "Composition"}'>
|
<th:block th:if='*{getResourceType().name() == "Observation"}'>
|
||||||
<th:block th:with="extension=${entry.getResource().getExtensionByUrl('http://hl7.org/fhir/StructureDefinition/narrativeLink').getValue().getValue()}">
|
<th:block th:unless='*{getResourceType().name() == "Composition"}'>
|
||||||
<tr th:id="${#strings.arraySplit(extension, '#')[1]}">
|
<th:block
|
||||||
<td th:insert="IpsUtilityFragments :: codeableConcept (cc=*{getCode()},attr='display')">Code</td>
|
th:with="extension=${entry.getResource().getExtensionByUrl('http://hl7.org/fhir/StructureDefinition/narrativeLink').getValue().getValue()}">
|
||||||
<td th:insert="IpsUtilityFragments :: renderValue (value=*{getValue()})">Result</td>
|
<tr th:id="${#strings.arraySplit(extension, '#')[1]}">
|
||||||
<td th:insert="IpsUtilityFragments :: renderValueUnit (value=*{getValue()})">Unit</td>
|
<td th:insert="~{IpsUtilityFragments :: codeableConcept (cc=*{getCode()},attr='display')}">Code
|
||||||
<td th:insert="IpsUtilityFragments :: firstFromCodeableConceptList (list=*{getInterpretation()})">Interpretation</td>
|
</td>
|
||||||
<td th:insert="IpsUtilityFragments :: concatReferenceRange (list=*{getReferenceRange()})">Reference Range</td>
|
<td th:insert="~{IpsUtilityFragments :: renderValue (value=*{getValue()})}">Result</td>
|
||||||
<td th:insert="IpsUtilityFragments :: concat (list=*{getNote()},attr='text')">Comments</td>
|
<td th:insert="~{IpsUtilityFragments :: renderValueUnit (value=*{getValue()})}">Unit</td>
|
||||||
<td th:insert="IpsUtilityFragments :: renderEffective (effective=*{getEffective()})">Date</td>
|
<td
|
||||||
</tr>
|
th:insert="~{IpsUtilityFragments :: firstFromCodeableConceptList (list=*{getInterpretation()})}">
|
||||||
</th:block>
|
Interpretation
|
||||||
</th:block>
|
</td>
|
||||||
</th:block>
|
<td th:insert="~{IpsUtilityFragments :: concatReferenceRange (list=*{getReferenceRange()})}">
|
||||||
</th:block>
|
Reference
|
||||||
</tbody>
|
Range
|
||||||
</table>
|
</td>
|
||||||
|
<td th:insert="~{IpsUtilityFragments :: concat (list=*{getNote()},attr='text')}">Comments</td>
|
||||||
|
<td th:insert="~{IpsUtilityFragments :: renderEffective (effective=*{getEffective()})}">Date</td>
|
||||||
|
</tr>
|
||||||
|
</th:block>
|
||||||
|
</th:block>
|
||||||
|
</th:block>
|
||||||
|
</th:block>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</th:block>
|
||||||
|
|
||||||
<table class="hapiPropertyTable">
|
<th:block th:if="${narrativeUtil.bundleHasEntriesWithResourceType(resource, 'DiagnosticReport')}">
|
||||||
<caption>Diagnostic Results: Diagnostic Reports</caption>
|
<h5>Diagnostic Results: Diagnostic Reports</h5>
|
||||||
<thead>
|
<table class="hapiPropertyTable">
|
||||||
<tr>
|
<thead>
|
||||||
<th>Code</th>
|
<tr>
|
||||||
<th>Date</th>
|
<th>Code</th>
|
||||||
</tr>
|
<th>Date</th>
|
||||||
</thead>
|
</tr>
|
||||||
<tbody>
|
</thead>
|
||||||
<th:block th:each="entry : ${resource.entry}" th:object="${entry.getResource()}">
|
<tbody>
|
||||||
<th:block th:if='*{getResourceType().name() == "DiagnosticReport"}'>
|
<th:block th:each="entry : ${resource.entry}" th:object="${entry.getResource()}">
|
||||||
<th:block th:unless='*{getResourceType().name() == "Composition"}'>
|
<th:block th:if='*{getResourceType().name() == "DiagnosticReport"}'>
|
||||||
<th:block th:with="extension=${entry.getResource().getExtensionByUrl('http://hl7.org/fhir/StructureDefinition/narrativeLink').getValue().getValue()}">
|
<th:block th:unless='*{getResourceType().name() == "Composition"}'>
|
||||||
<tr th:id="${#strings.arraySplit(extension, '#')[1]}">
|
<th:block
|
||||||
<td th:insert="IpsUtilityFragments :: codeableConcept (cc=*{getCode()},attr='display')">Device</td>
|
th:with="extension=${entry.getResource().getExtensionByUrl('http://hl7.org/fhir/StructureDefinition/narrativeLink').getValue().getValue()}">
|
||||||
<td th:insert="IpsUtilityFragments :: renderEffective (effective=*{getEffective()})">Date</td>
|
<tr th:id="${#strings.arraySplit(extension, '#')[1]}">
|
||||||
</tr>
|
<td th:insert="~{IpsUtilityFragments :: codeableConcept (cc=*{getCode()},attr='display')}">Device
|
||||||
</th:block>
|
</td>
|
||||||
</th:block>
|
<td th:insert="~{IpsUtilityFragments :: renderEffective (effective=*{getEffective()})}">Date</td>
|
||||||
</th:block>
|
</tr>
|
||||||
</th:block>
|
</th:block>
|
||||||
</tbody>
|
</th:block>
|
||||||
</table>
|
</th:block>
|
||||||
|
</th:block>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</th:block>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -7,8 +7,8 @@ Comments: ClinicalImpression.note[x].text (separated by <br />)
|
||||||
Date: ClinicalImpression.effectiveDateTime || ClinicalImpression.effectivePeriod.start
|
Date: ClinicalImpression.effectiveDateTime || ClinicalImpression.effectivePeriod.start
|
||||||
*/-->
|
*/-->
|
||||||
<div xmlns:th="http://www.thymeleaf.org">
|
<div xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<h5>Functional Status</h5>
|
||||||
<table class="hapiPropertyTable">
|
<table class="hapiPropertyTable">
|
||||||
<caption>Functional Status</caption>
|
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Assessment</th>
|
<th>Assessment</th>
|
||||||
|
@ -23,11 +23,11 @@ Date: ClinicalImpression.effectiveDateTime || ClinicalImpression.effectivePeriod
|
||||||
<th:block th:unless='*{getResourceType().name() == "Composition"}'>
|
<th:block th:unless='*{getResourceType().name() == "Composition"}'>
|
||||||
<th:block th:with="extension=${entry.getResource().getExtensionByUrl('http://hl7.org/fhir/StructureDefinition/narrativeLink').getValue().getValue()}">
|
<th:block th:with="extension=${entry.getResource().getExtensionByUrl('http://hl7.org/fhir/StructureDefinition/narrativeLink').getValue().getValue()}">
|
||||||
<tr th:id="${#strings.arraySplit(extension, '#')[1]}">
|
<tr th:id="${#strings.arraySplit(extension, '#')[1]}">
|
||||||
<td th:insert="IpsUtilityFragments :: codeableConcept (cc=*{getCode()},attr='display')">Assessment</td>
|
<td th:insert="~{IpsUtilityFragments :: codeableConcept (cc=*{getCode()},attr='display')}">Assessment</td>
|
||||||
<td th:text="*{getStatus().getCode()}">Status</td>
|
<td th:text="*{getStatus().getCode()}">Status</td>
|
||||||
<td th:text="*{getSummary()}">Finding</td>
|
<td th:text="*{getSummary()}">Finding</td>
|
||||||
<td th:insert="IpsUtilityFragments :: concat (list=*{getNote()},attr='text')">Comments</td>
|
<td th:insert="~{IpsUtilityFragments :: concat (list=*{getNote()},attr='text')}">Comments</td>
|
||||||
<td th:insert="IpsUtilityFragments :: renderEffective (effective=*{getEffective()})">Date</td>
|
<td th:insert="~{IpsUtilityFragments :: renderEffective (effective=*{getEffective()})}">Date</td>
|
||||||
</tr>
|
</tr>
|
||||||
</th:block>
|
</th:block>
|
||||||
</th:block>
|
</th:block>
|
||||||
|
|
|
@ -5,8 +5,8 @@ Comments: Procedure.note[x].text(separated by <br />)
|
||||||
Date: Procedure.performedDateTime || Procedure.performedPeriod.start && “-“ && Procedure.performedPeriod.end || Procedure.performedAge || Procedure.performedRange.low && “-“ && Procedure.performedRange.high || Procedure.performedString
|
Date: Procedure.performedDateTime || Procedure.performedPeriod.start && “-“ && Procedure.performedPeriod.end || Procedure.performedAge || Procedure.performedRange.low && “-“ && Procedure.performedRange.high || Procedure.performedString
|
||||||
*/-->
|
*/-->
|
||||||
<div xmlns:th="http://www.thymeleaf.org">
|
<div xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<h5>History Of Procedures</h5>
|
||||||
<table class="hapiPropertyTable">
|
<table class="hapiPropertyTable">
|
||||||
<caption>History Of Procedures</caption>
|
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Procedure</th>
|
<th>Procedure</th>
|
||||||
|
@ -19,9 +19,9 @@ Date: Procedure.performedDateTime || Procedure.performedPeriod.start && “-“
|
||||||
<th:block th:unless='*{getResourceType().name() == "Composition"}'>
|
<th:block th:unless='*{getResourceType().name() == "Composition"}'>
|
||||||
<th:block th:with="extension=${entry.getResource().getExtensionByUrl('http://hl7.org/fhir/StructureDefinition/narrativeLink').getValue().getValue()}">
|
<th:block th:with="extension=${entry.getResource().getExtensionByUrl('http://hl7.org/fhir/StructureDefinition/narrativeLink').getValue().getValue()}">
|
||||||
<tr th:id="${#strings.arraySplit(extension, '#')[1]}">
|
<tr th:id="${#strings.arraySplit(extension, '#')[1]}">
|
||||||
<td th:insert="IpsUtilityFragments :: codeableConcept (cc=*{getCode()},attr='display')">Procedure</td>
|
<td th:insert="~{IpsUtilityFragments :: codeableConcept (cc=*{getCode()},attr='display')}">Procedure</td>
|
||||||
<td th:insert="IpsUtilityFragments :: concat (list=*{getNote()},attr='text')">Comments</td>
|
<td th:insert="~{IpsUtilityFragments :: concat (list=*{getNote()},attr='text')}">Comments</td>
|
||||||
<td th:insert="IpsUtilityFragments :: renderPerformed (performed=*{getPerformed()})">Date</td>
|
<td th:insert="~{IpsUtilityFragments :: renderPerformed (performed=*{getPerformed()})}">Date</td>
|
||||||
</tr>
|
</tr>
|
||||||
</th:block>
|
</th:block>
|
||||||
</th:block>
|
</th:block>
|
||||||
|
|
|
@ -9,8 +9,8 @@ Comments: Immunization.note[x].text (separated by <br />)
|
||||||
Date: Immunization.occurrenceDateTime || Immunization.occurrenceString
|
Date: Immunization.occurrenceDateTime || Immunization.occurrenceString
|
||||||
*/-->
|
*/-->
|
||||||
<div xmlns:th="http://www.thymeleaf.org">
|
<div xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<h5>Immunizations</h5>
|
||||||
<table class="hapiPropertyTable">
|
<table class="hapiPropertyTable">
|
||||||
<caption>Immunizations</caption>
|
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Immunization</th>
|
<th>Immunization</th>
|
||||||
|
@ -27,13 +27,13 @@ Date: Immunization.occurrenceDateTime || Immunization.occurrenceString
|
||||||
<th:block th:if='*{getResourceType().name() == "Immunization"}'>
|
<th:block th:if='*{getResourceType().name() == "Immunization"}'>
|
||||||
<th:block th:with="extension=${entry.getResource().getExtensionByUrl('http://hl7.org/fhir/StructureDefinition/narrativeLink').getValue().getValue()}">
|
<th:block th:with="extension=${entry.getResource().getExtensionByUrl('http://hl7.org/fhir/StructureDefinition/narrativeLink').getValue().getValue()}">
|
||||||
<tr th:id="${#strings.arraySplit(extension, '#')[1]}">
|
<tr th:id="${#strings.arraySplit(extension, '#')[1]}">
|
||||||
<td th:insert="IpsUtilityFragments :: codeableConcept (cc=*{getVaccineCode()},attr='display')">Immunization</td>
|
<td th:insert="~{IpsUtilityFragments :: codeableConcept (cc=*{getVaccineCode()},attr='display')}">Immunization</td>
|
||||||
<td th:text="*{getStatusElement().value}">Status</td>
|
<td th:text="*{getStatusElement().value}">Status</td>
|
||||||
<td th:insert="IpsUtilityFragments :: concatDoseNumber (list=*{getProtocolApplied()})">Comments</td>
|
<td th:insert="~{IpsUtilityFragments :: concatDoseNumber (list=*{getProtocolApplied()})}">Comments</td>
|
||||||
<td th:insert="IpsUtilityFragments :: renderOrganization (orgRef=*{getManufacturer()})">Manufacturer</td>
|
<td th:insert="~{IpsUtilityFragments :: renderOrganization (orgRef=*{getManufacturer()})}">Manufacturer</td>
|
||||||
<td th:text="*{getLotNumber()}">Lot Number</td>
|
<td th:text="*{getLotNumber()}">Lot Number</td>
|
||||||
<td th:insert="IpsUtilityFragments :: concat (list=*{getNote()},attr='text')">Comments</td>
|
<td th:insert="~{IpsUtilityFragments :: concat (list=*{getNote()},attr='text')}">Comments</td>
|
||||||
<td th:insert="IpsUtilityFragments :: renderOccurrence (occurrence=*{getOccurrence()})">Date</td>
|
<td th:insert="~{IpsUtilityFragments :: renderOccurrence (occurrence=*{getOccurrence()})}">Date</td>
|
||||||
</tr>
|
</tr>
|
||||||
</th:block>
|
</th:block>
|
||||||
</th:block>
|
</th:block>
|
||||||
|
|
|
@ -6,8 +6,8 @@ Comments: DeviceUseStatement.note[x].text (separated by <br />)
|
||||||
Date Recorded: DeviceUseStatement.recordedDateTime
|
Date Recorded: DeviceUseStatement.recordedDateTime
|
||||||
*/-->
|
*/-->
|
||||||
<div xmlns:th="http://www.thymeleaf.org">
|
<div xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<h5>Medical Devices</h5>
|
||||||
<table class="hapiPropertyTable">
|
<table class="hapiPropertyTable">
|
||||||
<caption>Medical Devices</caption>
|
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Device</th>
|
<th>Device</th>
|
||||||
|
@ -21,10 +21,10 @@ Date Recorded: DeviceUseStatement.recordedDateTime
|
||||||
<th:block th:if='*{getResourceType().name() == "DeviceUseStatement"}'>
|
<th:block th:if='*{getResourceType().name() == "DeviceUseStatement"}'>
|
||||||
<th:block th:with="extension=${entry.getResource().getExtensionByUrl('http://hl7.org/fhir/StructureDefinition/narrativeLink').getValue().getValue()}">
|
<th:block th:with="extension=${entry.getResource().getExtensionByUrl('http://hl7.org/fhir/StructureDefinition/narrativeLink').getValue().getValue()}">
|
||||||
<tr th:id="${#strings.arraySplit(extension, '#')[1]}">
|
<tr th:id="${#strings.arraySplit(extension, '#')[1]}">
|
||||||
<td th:insert="IpsUtilityFragments :: renderDevice (deviceRef=*{getDevice()})">Device</td>
|
<td th:insert="~{IpsUtilityFragments :: renderDevice (deviceRef=*{getDevice()})}">Device</td>
|
||||||
<td th:text="*{getStatusElement().value}">Status</td>
|
<td th:text="*{getStatusElement().value}">Status</td>
|
||||||
<td th:insert="IpsUtilityFragments :: concat (list=*{getNote()},attr='text')">Comments</td>
|
<td th:insert="~{IpsUtilityFragments :: concat (list=*{getNote()},attr='text')}">Comments</td>
|
||||||
<td th:insert="IpsUtilityFragments :: renderRecorded (recorded=*{getRecordedOn()})">Date Recorded</td>
|
<td th:insert="~{IpsUtilityFragments :: renderRecorded (recorded=*{getRecordedOn()})}">Date Recorded</td>
|
||||||
</tr>
|
</tr>
|
||||||
</th:block>
|
</th:block>
|
||||||
</th:block>
|
</th:block>
|
||||||
|
|
|
@ -16,63 +16,78 @@ Sig: MedicationStatement.dosage[x].text (display all sigs separated by <br />)
|
||||||
Date: MedicationStatement.effectiveDateTime || MedicationStatement.effectivePeriod.start
|
Date: MedicationStatement.effectiveDateTime || MedicationStatement.effectivePeriod.start
|
||||||
*/-->
|
*/-->
|
||||||
<div xmlns:th="http://www.thymeleaf.org">
|
<div xmlns:th="http://www.thymeleaf.org">
|
||||||
<table class="hapiPropertyTable">
|
|
||||||
<caption>Medication Summary: Medication Requests</caption>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Medication</th>
|
|
||||||
<th>Status</th>
|
|
||||||
<th>Route</th>
|
|
||||||
<th>Sig</th>
|
|
||||||
<th>Comments</th>
|
|
||||||
<th>Authored Date</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<th:block th:each="entry : ${resource.entry}" th:object="${entry.getResource()}">
|
|
||||||
<th:block th:if='*{getResourceType().name() == "MedicationRequest"}'>
|
|
||||||
<th:block th:unless='*{getResourceType().name() == "Composition"}'>
|
|
||||||
<th:block th:with="extension=${entry.getResource().getExtensionByUrl('http://hl7.org/fhir/StructureDefinition/narrativeLink').getValue().getValue()}">
|
|
||||||
<tr th:id="${#strings.arraySplit(extension, '#')[1]}">
|
|
||||||
<td th:insert="IpsUtilityFragments :: renderMedication (medicationType=*{getMedication()})">Medication</td>
|
|
||||||
<td th:text="*{getStatus().getDisplay()}">Status</td>
|
|
||||||
<td th:insert="IpsUtilityFragments :: concatDosageRoute (list=*{getDosageInstruction()})">Route</td>
|
|
||||||
<td th:insert="IpsUtilityFragments :: concat (list=*{getDosageInstruction()},attr='text')">Sig</td>
|
|
||||||
<td th:insert="IpsUtilityFragments :: concat (list=*{getNote()},attr='text')">Comments</td>
|
|
||||||
<td th:text="*{getAuthoredOnElement().getValue()}">Authored Date</td>
|
|
||||||
</tr>
|
|
||||||
</th:block>
|
|
||||||
</th:block>
|
|
||||||
</th:block>
|
|
||||||
</th:block>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<table class="hapiPropertyTable">
|
<th:block th:if="${narrativeUtil.bundleHasEntriesWithResourceType(resource, 'MedicationRequest')}">
|
||||||
<caption>Medication Summary: Medication Statements</caption>
|
<h5>Medication Summary: Medication Requests</h5>
|
||||||
<thead>
|
<table class="hapiPropertyTable">
|
||||||
<tr>
|
<thead>
|
||||||
<th>Medication</th>
|
<tr>
|
||||||
<th>Status</th>
|
<th>Medication</th>
|
||||||
<th>Route</th>
|
<th>Status</th>
|
||||||
<th>Sig</th>
|
<th>Route</th>
|
||||||
<th>Date</th>
|
<th>Sig</th>
|
||||||
</tr>
|
<th>Comments</th>
|
||||||
</thead>
|
<th>Authored Date</th>
|
||||||
<tbody>
|
</tr>
|
||||||
<th:block th:each="entry : ${resource.entry}" th:object="${entry.getResource()}">
|
</thead>
|
||||||
<th:block th:if='*{getResourceType().name() == "MedicationStatement"}'>
|
<tbody>
|
||||||
<th:block th:with="extension=${entry.getResource().getExtensionByUrl('http://hl7.org/fhir/StructureDefinition/narrativeLink').getValue().getValue()}">
|
<th:block th:each="entry : ${resource.entry}" th:object="${entry.getResource()}">
|
||||||
<tr th:id="${#strings.arraySplit(extension, '#')[1]}">
|
<th:block th:if='*{getResourceType().name() == "MedicationRequest"}'>
|
||||||
<td th:insert="IpsUtilityFragments :: renderMedication (medicationType=*{getMedication()})">Medication</td>
|
<th:block th:unless='*{getResourceType().name() == "Composition"}'>
|
||||||
<td th:text="*{getStatus().getDisplay()}">Status</td>
|
<th:block
|
||||||
<td th:insert="IpsUtilityFragments :: concatDosageRoute (list=*{getDosage()})">Route</td>
|
th:with="extension=${entry.getResource().getExtensionByUrl('http://hl7.org/fhir/StructureDefinition/narrativeLink').getValue().getValue()}">
|
||||||
<td th:insert="IpsUtilityFragments :: concat (list=*{getDosage()},attr='text')">Sig</td>
|
<tr th:id="${#strings.arraySplit(extension, '#')[1]}">
|
||||||
<td th:insert="IpsUtilityFragments :: renderEffective (effective=*{getEffective()})">Date</td>
|
<td th:insert="~{IpsUtilityFragments :: renderMedication (medicationType=*{getMedication()})}">
|
||||||
</tr>
|
Medication
|
||||||
</th:block>
|
</td>
|
||||||
</th:block>
|
<td th:text="*{getStatus().getDisplay()}">Status</td>
|
||||||
</th:block>
|
<td th:insert="~{IpsUtilityFragments :: concatDosageRoute (list=*{getDosageInstruction()})}">
|
||||||
</tbody>
|
Route
|
||||||
</table>
|
</td>
|
||||||
|
<td th:insert="~{IpsUtilityFragments :: concat (list=*{getDosageInstruction()},attr='text')}">
|
||||||
|
Sig
|
||||||
|
</td>
|
||||||
|
<td th:insert="~{IpsUtilityFragments :: concat (list=*{getNote()},attr='text')}">Comments</td>
|
||||||
|
<td th:text="*{getAuthoredOnElement().getValue()}">Authored Date</td>
|
||||||
|
</tr>
|
||||||
|
</th:block>
|
||||||
|
</th:block>
|
||||||
|
</th:block>
|
||||||
|
</th:block>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</th:block>
|
||||||
|
|
||||||
|
<th:block th:if="${narrativeUtil.bundleHasEntriesWithResourceType(resource, 'MedicationStatement')}">
|
||||||
|
<h5>Medication Summary: Medication Statements</h5>
|
||||||
|
<table class="hapiPropertyTable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Medication</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Route</th>
|
||||||
|
<th>Sig</th>
|
||||||
|
<th>Date</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<th:block th:each="entry : ${resource.entry}" th:object="${entry.getResource()}">
|
||||||
|
<th:block th:if='*{getResourceType().name() == "MedicationStatement"}'>
|
||||||
|
<th:block
|
||||||
|
th:with="extension=${entry.getResource().getExtensionByUrl('http://hl7.org/fhir/StructureDefinition/narrativeLink').getValue().getValue()}">
|
||||||
|
<tr th:id="${#strings.arraySplit(extension, '#')[1]}">
|
||||||
|
<td th:insert="~{IpsUtilityFragments :: renderMedication (medicationType=*{getMedication()})}">
|
||||||
|
Medication
|
||||||
|
</td>
|
||||||
|
<td th:text="*{getStatus().getDisplay()}">Status</td>
|
||||||
|
<td th:insert="~{IpsUtilityFragments :: concatDosageRoute (list=*{getDosage()})}">Route</td>
|
||||||
|
<td th:insert="~{IpsUtilityFragments :: concat (list=*{getDosage()},attr='text')}">Sig</td>
|
||||||
|
<td th:insert="~{IpsUtilityFragments :: renderEffective (effective=*{getEffective()})}">Date</td>
|
||||||
|
</tr>
|
||||||
|
</th:block>
|
||||||
|
</th:block>
|
||||||
|
</th:block>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</th:block>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -6,8 +6,8 @@ Comments: Condition.note[x].text (separated by <br />)
|
||||||
Onset Date: Condition.onsetDateTime || Condition.onsetPeriod.start && “-“ && Condition.onsetPeriod.end || Condition.onsetAge || Condition.onsetRange.low && “-“ && Condition.onsetRange.high || Condition.onsetString
|
Onset Date: Condition.onsetDateTime || Condition.onsetPeriod.start && “-“ && Condition.onsetPeriod.end || Condition.onsetAge || Condition.onsetRange.low && “-“ && Condition.onsetRange.high || Condition.onsetString
|
||||||
*/-->
|
*/-->
|
||||||
<div xmlns:th="http://www.thymeleaf.org">
|
<div xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<h5>Past History of Illnesses</h5>
|
||||||
<table class="hapiPropertyTable">
|
<table class="hapiPropertyTable">
|
||||||
<caption>Past History of Illnesses</caption>
|
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Medical Problems</th>
|
<th>Medical Problems</th>
|
||||||
|
@ -21,10 +21,10 @@ Onset Date: Condition.onsetDateTime || Condition.onsetPeriod.start && “-“ &&
|
||||||
<th:block th:unless='*{getResourceType().name() == "Composition"}'>
|
<th:block th:unless='*{getResourceType().name() == "Composition"}'>
|
||||||
<th:block th:with="extension=${entry.getResource().getExtensionByUrl('http://hl7.org/fhir/StructureDefinition/narrativeLink').getValue().getValue()}">
|
<th:block th:with="extension=${entry.getResource().getExtensionByUrl('http://hl7.org/fhir/StructureDefinition/narrativeLink').getValue().getValue()}">
|
||||||
<tr th:id="${#strings.arraySplit(extension, '#')[1]}">
|
<tr th:id="${#strings.arraySplit(extension, '#')[1]}">
|
||||||
<td th:insert="IpsUtilityFragments :: codeableConcept (cc=*{getCode()},attr='display')">Medical Problem</td>
|
<td th:insert="~{IpsUtilityFragments :: codeableConcept (cc=*{getCode()},attr='display')}">Medical Problem</td>
|
||||||
<td th:insert="~{IpsUtilityFragments :: codeableConcept (cc=*{getClinicalStatus()},attr='code')}">Status</td>
|
<td th:insert="~{IpsUtilityFragments :: codeableConcept (cc=*{getClinicalStatus()},attr='code')}">Status</td>
|
||||||
<td th:insert="IpsUtilityFragments :: concat (list=*{getNote()},attr='text')">Comments</td>
|
<td th:insert="~{IpsUtilityFragments :: concat (list=*{getNote()},attr='text')}">Comments</td>
|
||||||
<td th:insert="IpsUtilityFragments :: renderOnset (onset=*{getOnset()})">Onset Date</td>
|
<td th:insert="~{IpsUtilityFragments :: renderOnset (onset=*{getOnset()})}">Onset Date</td>
|
||||||
</tr>
|
</tr>
|
||||||
</th:block>
|
</th:block>
|
||||||
</th:block>
|
</th:block>
|
||||||
|
|
|
@ -7,8 +7,8 @@ Planned Start: CarePlan.period.start
|
||||||
Planned End: CarePlan.period.end
|
Planned End: CarePlan.period.end
|
||||||
*/-->
|
*/-->
|
||||||
<div xmlns:th="http://www.thymeleaf.org">
|
<div xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<h5>Plan of Care</h5>
|
||||||
<table class="hapiPropertyTable">
|
<table class="hapiPropertyTable">
|
||||||
<caption>Plan of Care</caption>
|
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Activity</th>
|
<th>Activity</th>
|
||||||
|
@ -25,7 +25,7 @@ Planned End: CarePlan.period.end
|
||||||
<tr th:id="${#strings.arraySplit(extension, '#')[1]}">
|
<tr th:id="${#strings.arraySplit(extension, '#')[1]}">
|
||||||
<td th:text="*{getDescription()}">Activity</td>
|
<td th:text="*{getDescription()}">Activity</td>
|
||||||
<td th:text="*{getIntent().toCode()}">Intent</td>
|
<td th:text="*{getIntent().toCode()}">Intent</td>
|
||||||
<td th:insert="IpsUtilityFragments :: concat (list=*{getNote()},attr='text')">Comments</td>
|
<td th:insert="~{IpsUtilityFragments :: concat (list=*{getNote()},attr='text')}">Comments</td>
|
||||||
<td th:text="*{getPeriod().getStartElement().getValue()}">Planned Start</td>
|
<td th:text="*{getPeriod().getStartElement().getValue()}">Planned Start</td>
|
||||||
<td th:text="*{getPeriod().getEndElement().getValue()}">Planned End</td>
|
<td th:text="*{getPeriod().getEndElement().getValue()}">Planned End</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -6,8 +6,8 @@ Comments: Observation.note[x].text (separated by <br />)
|
||||||
Date: Observation.effectiveDateTime || Observation.effectivePeriod.start
|
Date: Observation.effectiveDateTime || Observation.effectivePeriod.start
|
||||||
*/-->
|
*/-->
|
||||||
<div xmlns:th="http://www.thymeleaf.org">
|
<div xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<h5>Pregnancy</h5>
|
||||||
<table class="hapiPropertyTable">
|
<table class="hapiPropertyTable">
|
||||||
<caption>Pregnancy</caption>
|
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Code</th>
|
<th>Code</th>
|
||||||
|
@ -21,10 +21,10 @@ Date: Observation.effectiveDateTime || Observation.effectivePeriod.start
|
||||||
<th:block th:unless='*{getResourceType().name() == "Composition"}'>
|
<th:block th:unless='*{getResourceType().name() == "Composition"}'>
|
||||||
<th:block th:with="extension=${entry.getResource().getExtensionByUrl('http://hl7.org/fhir/StructureDefinition/narrativeLink').getValue().getValue()}">
|
<th:block th:with="extension=${entry.getResource().getExtensionByUrl('http://hl7.org/fhir/StructureDefinition/narrativeLink').getValue().getValue()}">
|
||||||
<tr th:id="${#strings.arraySplit(extension, '#')[1]}">
|
<tr th:id="${#strings.arraySplit(extension, '#')[1]}">
|
||||||
<td th:insert="IpsUtilityFragments :: codeableConcept (cc=*{getCode()},attr='display')">Code</td>
|
<td th:insert="~{IpsUtilityFragments :: codeableConcept (cc=*{getCode()},attr='display')}">Code</td>
|
||||||
<td th:insert="IpsUtilityFragments :: renderValue (value=*{getValue()})">Result</td>
|
<td th:insert="~{IpsUtilityFragments :: renderValue (value=*{getValue()})}">Result</td>
|
||||||
<td th:insert="IpsUtilityFragments :: concat (list=*{getNote()},attr='text')">Comments</td>
|
<td th:insert="~{IpsUtilityFragments :: concat (list=*{getNote()},attr='text')}">Comments</td>
|
||||||
<td th:insert="IpsUtilityFragments :: renderEffective (effective=*{getEffective()})">Date</td>
|
<td th:insert="~{IpsUtilityFragments :: renderEffective (effective=*{getEffective()})}">Date</td>
|
||||||
</tr>
|
</tr>
|
||||||
</th:block>
|
</th:block>
|
||||||
</th:block>
|
</th:block>
|
||||||
|
|
|
@ -6,8 +6,8 @@ Comments: Condition.note[x].text (separated by <br />)
|
||||||
Onset Date: Condition.onsetDateTime || Condition.onsetPeriod.start && “-“ && Condition.onsetPeriod.end || Condition.onsetAge || Condition.onsetRange.low && “-“ && Condition.onsetRange.high || Condition.onsetString
|
Onset Date: Condition.onsetDateTime || Condition.onsetPeriod.start && “-“ && Condition.onsetPeriod.end || Condition.onsetAge || Condition.onsetRange.low && “-“ && Condition.onsetRange.high || Condition.onsetString
|
||||||
*/-->
|
*/-->
|
||||||
<div xmlns:th="http://www.thymeleaf.org">
|
<div xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<h5>Problem List</h5>
|
||||||
<table class="hapiPropertyTable">
|
<table class="hapiPropertyTable">
|
||||||
<caption>Problem List</caption>
|
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Medical Problems</th>
|
<th>Medical Problems</th>
|
||||||
|
@ -21,10 +21,10 @@ Onset Date: Condition.onsetDateTime || Condition.onsetPeriod.start && “-“ &&
|
||||||
<th:block th:unless='*{getResourceType().name() == "Composition"}'>
|
<th:block th:unless='*{getResourceType().name() == "Composition"}'>
|
||||||
<th:block th:with="extension=${entry.getResource().getExtensionByUrl('http://hl7.org/fhir/StructureDefinition/narrativeLink').getValue().getValue()}">
|
<th:block th:with="extension=${entry.getResource().getExtensionByUrl('http://hl7.org/fhir/StructureDefinition/narrativeLink').getValue().getValue()}">
|
||||||
<tr th:id="${#strings.arraySplit(extension, '#')[1]}">
|
<tr th:id="${#strings.arraySplit(extension, '#')[1]}">
|
||||||
<td th:insert="IpsUtilityFragments :: codeableConcept (cc=*{getCode()},attr='display')">Medical Problems</td>
|
<td th:insert="~{IpsUtilityFragments :: codeableConcept (cc=*{getCode()},attr='display')}">Medical Problems</td>
|
||||||
<td th:insert="~{IpsUtilityFragments :: codeableConcept (cc=*{getClinicalStatus()},attr='code')}">Status</td>
|
<td th:insert="~{IpsUtilityFragments :: codeableConcept (cc=*{getClinicalStatus()},attr='code')}">Status</td>
|
||||||
<td th:insert="IpsUtilityFragments :: concat (list=*{getNote()},attr='text')">Comments</td>
|
<td th:insert="~{IpsUtilityFragments :: concat (list=*{getNote()},attr='text')}">Comments</td>
|
||||||
<td th:insert="IpsUtilityFragments :: renderOnset (onset=*{getOnset()})">Onset Date</td>
|
<td th:insert="~{IpsUtilityFragments :: renderOnset (onset=*{getOnset()})}">Onset Date</td>
|
||||||
</tr>
|
</tr>
|
||||||
</th:block>
|
</th:block>
|
||||||
</th:block>
|
</th:block>
|
||||||
|
|
|
@ -7,8 +7,8 @@ Comments: Observation.note[x].text (separated by <br />)
|
||||||
Date: Observation.effectiveDateTime || Observation.effectivePeriod.start
|
Date: Observation.effectiveDateTime || Observation.effectivePeriod.start
|
||||||
*/-->
|
*/-->
|
||||||
<div xmlns:th="http://www.thymeleaf.org">
|
<div xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<h5>Social History</h5>
|
||||||
<table class="hapiPropertyTable">
|
<table class="hapiPropertyTable">
|
||||||
<caption>Social History</caption>
|
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Code</th>
|
<th>Code</th>
|
||||||
|
@ -23,11 +23,11 @@ Date: Observation.effectiveDateTime || Observation.effectivePeriod.start
|
||||||
<th:block th:unless='*{getResourceType().name() == "Composition"}'>
|
<th:block th:unless='*{getResourceType().name() == "Composition"}'>
|
||||||
<th:block th:with="extension=${entry.getResource().getExtensionByUrl('http://hl7.org/fhir/StructureDefinition/narrativeLink').getValue().getValue()}">
|
<th:block th:with="extension=${entry.getResource().getExtensionByUrl('http://hl7.org/fhir/StructureDefinition/narrativeLink').getValue().getValue()}">
|
||||||
<tr th:id="${#strings.arraySplit(extension, '#')[1]}">
|
<tr th:id="${#strings.arraySplit(extension, '#')[1]}">
|
||||||
<td th:insert="IpsUtilityFragments :: codeableConcept (cc=*{getCode()},attr='display')">Code</td>
|
<td th:insert="~{IpsUtilityFragments :: codeableConcept (cc=*{getCode()},attr='display')}">Code</td>
|
||||||
<td th:insert="IpsUtilityFragments :: renderValue (value=*{getValue()})">Result</td>
|
<td th:insert="~{IpsUtilityFragments :: renderValue (value=*{getValue()})}">Result</td>
|
||||||
<td th:insert="IpsUtilityFragments :: renderValueUnit (value=*{getValue()})">Unit</td>
|
<td th:insert="~{IpsUtilityFragments :: renderValueUnit (value=*{getValue()})}">Unit</td>
|
||||||
<td th:insert="IpsUtilityFragments :: concat (list=*{getNote()},attr='text')">Comments</td>
|
<td th:insert="~{IpsUtilityFragments :: concat (list=*{getNote()},attr='text')}">Comments</td>
|
||||||
<td th:insert="IpsUtilityFragments :: renderEffective (effective=*{getEffective()})">Date</td>
|
<td th:insert="~{IpsUtilityFragments :: renderEffective (effective=*{getEffective()})}">Date</td>
|
||||||
</tr>
|
</tr>
|
||||||
</th:block>
|
</th:block>
|
||||||
</th:block>
|
</th:block>
|
||||||
|
|
|
@ -8,8 +8,8 @@ Comments: Observation.note[x].text (separated by <br />)
|
||||||
Date: Observation.effectiveDateTime || Observation.effectivePeriod.start
|
Date: Observation.effectiveDateTime || Observation.effectivePeriod.start
|
||||||
*/-->
|
*/-->
|
||||||
<div xmlns:th="http://www.thymeleaf.org">
|
<div xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<h5>Vital Signs</h5>
|
||||||
<table class="hapiPropertyTable">
|
<table class="hapiPropertyTable">
|
||||||
<caption>Vital Signs</caption>
|
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Code</th>
|
<th>Code</th>
|
||||||
|
@ -25,12 +25,12 @@ Date: Observation.effectiveDateTime || Observation.effectivePeriod.start
|
||||||
<th:block th:unless='*{getResourceType().name() == "Composition"}'>
|
<th:block th:unless='*{getResourceType().name() == "Composition"}'>
|
||||||
<th:block th:with="extension=${entry.getResource().getExtensionByUrl('http://hl7.org/fhir/StructureDefinition/narrativeLink').getValue().getValue()}">
|
<th:block th:with="extension=${entry.getResource().getExtensionByUrl('http://hl7.org/fhir/StructureDefinition/narrativeLink').getValue().getValue()}">
|
||||||
<tr th:id="${#strings.arraySplit(extension, '#')[1]}">
|
<tr th:id="${#strings.arraySplit(extension, '#')[1]}">
|
||||||
<td th:insert="IpsUtilityFragments :: codeableConcept (cc=*{getCode()},attr='display')">Code</td>
|
<td th:insert="~{IpsUtilityFragments :: codeableConcept (cc=*{getCode()},attr='display')}">Code</td>
|
||||||
<td th:insert="IpsUtilityFragments :: renderValue (value=*{getValue()})">Result</td>
|
<td th:insert="~{IpsUtilityFragments :: renderValue (value=*{getValue()})}">Result</td>
|
||||||
<td th:insert="IpsUtilityFragments :: renderValueUnit (value=*{getValue()})">Unit</td>
|
<td th:insert="~{IpsUtilityFragments :: renderValueUnit (value=*{getValue()})}">Unit</td>
|
||||||
<td th:replace="IpsUtilityFragments :: firstFromCodeableConceptList (list=*{getInterpretation()})">Interpretation</td>
|
<td th:replace="~{IpsUtilityFragments :: firstFromCodeableConceptList (list=*{getInterpretation()})}">Interpretation</td>
|
||||||
<td th:insert="IpsUtilityFragments :: concat (list=*{getNote()},attr='text')">Comments</td>
|
<td th:insert="~{IpsUtilityFragments :: concat (list=*{getNote()},attr='text')}">Comments</td>
|
||||||
<td th:insert="IpsUtilityFragments :: renderEffective (effective=*{getEffective()})">Date</td>
|
<td th:insert="~{IpsUtilityFragments :: renderEffective (effective=*{getEffective()})}">Date</td>
|
||||||
</tr>
|
</tr>
|
||||||
</th:block>
|
</th:block>
|
||||||
</th:block>
|
</th:block>
|
||||||
|
|
|
@ -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.config.JpaStorageSettings;
|
||||||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||||
import ca.uhn.fhir.jpa.ips.api.IIpsGenerationStrategy;
|
import ca.uhn.fhir.jpa.ips.api.IIpsGenerationStrategy;
|
||||||
|
import ca.uhn.fhir.jpa.ips.jpa.DefaultJpaIpsGenerationStrategy;
|
||||||
import ca.uhn.fhir.jpa.ips.provider.IpsOperationProvider;
|
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.model.util.JpaConstants;
|
||||||
import ca.uhn.fhir.jpa.provider.BaseResourceProviderR4Test;
|
import ca.uhn.fhir.jpa.provider.BaseResourceProviderR4Test;
|
||||||
import ca.uhn.fhir.util.ClasspathUtil;
|
import ca.uhn.fhir.util.ClasspathUtil;
|
||||||
import ca.uhn.fhir.util.ResourceReferenceInfo;
|
|
||||||
import ca.uhn.fhir.validation.FhirValidator;
|
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 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.support.ValidationSupportChain;
|
||||||
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
|
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
@ -38,19 +41,17 @@ import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.test.context.ContextConfiguration;
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
|
|
||||||
import jakarta.annotation.Nonnull;
|
|
||||||
import jakarta.annotation.Nullable;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.Matchers.contains;
|
import static org.hamcrest.Matchers.contains;
|
||||||
import static org.hamcrest.Matchers.matchesPattern;
|
|
||||||
import static org.hamcrest.Matchers.stringContainsInOrder;
|
import static org.hamcrest.Matchers.stringContainsInOrder;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
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
|
* This test uses a complete R4 JPA server as a backend and wires the
|
||||||
|
@ -98,8 +99,8 @@ public class IpsGenerationR4Test extends BaseResourceProviderR4Test {
|
||||||
// Verify
|
// Verify
|
||||||
validateDocument(output);
|
validateDocument(output);
|
||||||
assertEquals(117, output.getEntry().size());
|
assertEquals(117, output.getEntry().size());
|
||||||
String patientId = findFirstEntryResource(output, Patient.class, 1).getId();
|
String patientId = findFirstEntryResource(output, Patient.class, 1).getIdElement().toUnqualifiedVersionless().getValue();
|
||||||
assertThat(patientId, matchesPattern("urn:uuid:.*"));
|
assertEquals("Patient/f15d2419-fbff-464a-826d-0afe8f095771", patientId);
|
||||||
MedicationStatement medicationStatement = findFirstEntryResource(output, MedicationStatement.class, 2);
|
MedicationStatement medicationStatement = findFirstEntryResource(output, MedicationStatement.class, 2);
|
||||||
assertEquals(patientId, medicationStatement.getSubject().getReference());
|
assertEquals(patientId, medicationStatement.getSubject().getReference());
|
||||||
assertNull(medicationStatement.getInformationSource().getReference());
|
assertNull(medicationStatement.getInformationSource().getReference());
|
||||||
|
@ -185,8 +186,8 @@ public class IpsGenerationR4Test extends BaseResourceProviderR4Test {
|
||||||
// Verify
|
// Verify
|
||||||
validateDocument(output);
|
validateDocument(output);
|
||||||
assertEquals(7, output.getEntry().size());
|
assertEquals(7, output.getEntry().size());
|
||||||
String patientId = findFirstEntryResource(output, Patient.class, 1).getId();
|
String patientId = findFirstEntryResource(output, Patient.class, 1).getIdElement().toUnqualifiedVersionless().getValue();
|
||||||
assertThat(patientId, matchesPattern("urn:uuid:.*"));
|
assertEquals("Patient/5342998", patientId);
|
||||||
assertEquals(patientId, findEntryResource(output, Condition.class, 0, 2).getSubject().getReference());
|
assertEquals(patientId, findEntryResource(output, Condition.class, 0, 2).getSubject().getReference());
|
||||||
assertEquals(patientId, findEntryResource(output, Condition.class, 1, 2).getSubject().getReference());
|
assertEquals(patientId, findEntryResource(output, Condition.class, 1, 2).getSubject().getReference());
|
||||||
|
|
||||||
|
@ -279,18 +280,9 @@ public class IpsGenerationR4Test extends BaseResourceProviderR4Test {
|
||||||
instanceValidator.setValidationSupport(new ValidationSupportChain(new IpsTerminologySvc(), myFhirContext.getValidationSupport()));
|
instanceValidator.setValidationSupport(new ValidationSupportChain(new IpsTerminologySvc(), myFhirContext.getValidationSupport()));
|
||||||
validator.registerValidatorModule(instanceValidator);
|
validator.registerValidatorModule(instanceValidator);
|
||||||
ValidationResult validation = validator.validateWithResult(theOutcome);
|
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
|
Optional<SingleValidationMessage> failure = validation.getMessages().stream().filter(t -> t.getSeverity().ordinal() >= ResultSeverityEnum.ERROR.ordinal()).findFirst();
|
||||||
List<ResourceReferenceInfo> references = myFhirContext.newTerser().getAllResourceReferences(theOutcome);
|
assertFalse(failure.isPresent(), () -> failure.get().toString());
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
|
@ -298,12 +290,12 @@ public class IpsGenerationR4Test extends BaseResourceProviderR4Test {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public IIpsGenerationStrategy ipsGenerationStrategy() {
|
public IIpsGenerationStrategy ipsGenerationStrategy() {
|
||||||
return new DefaultIpsGenerationStrategy();
|
return new DefaultJpaIpsGenerationStrategy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public IIpsGeneratorSvc ipsGeneratorSvc(FhirContext theFhirContext, IIpsGenerationStrategy theGenerationStrategy, DaoRegistry theDaoRegistry) {
|
public IIpsGeneratorSvc ipsGeneratorSvc(FhirContext theFhirContext, IIpsGenerationStrategy theGenerationStrategy, DaoRegistry theDaoRegistry) {
|
||||||
return new IpsGeneratorSvcImpl(theFhirContext, theGenerationStrategy, theDaoRegistry);
|
return new IpsGeneratorSvcImpl(theFhirContext, theGenerationStrategy);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
@ -314,7 +306,6 @@ public class IpsGenerationR4Test extends BaseResourceProviderR4Test {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private static <T extends IBaseResource> T findFirstEntryResource(Bundle theBundle, Class<T> theType, int theExpectedCount) {
|
private static <T extends IBaseResource> T findFirstEntryResource(Bundle theBundle, Class<T> theType, int theExpectedCount) {
|
||||||
return findEntryResource(theBundle, theType, 0, theExpectedCount);
|
return findEntryResource(theBundle, theType, 0, theExpectedCount);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,10 @@ package ca.uhn.fhir.jpa.ips.generator;
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||||
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
||||||
import ca.uhn.fhir.jpa.ips.api.IpsSectionEnum;
|
import ca.uhn.fhir.jpa.ips.api.IIpsGenerationStrategy;
|
||||||
import ca.uhn.fhir.jpa.ips.api.SectionRegistry;
|
import ca.uhn.fhir.jpa.ips.api.IpsContext;
|
||||||
import ca.uhn.fhir.jpa.ips.strategy.DefaultIpsGenerationStrategy;
|
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.api.ResourceMetadataKeyEnum;
|
||||||
import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum;
|
import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum;
|
||||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
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.rest.server.SimpleBundleProvider;
|
||||||
import ca.uhn.fhir.test.utilities.HtmlUtil;
|
import ca.uhn.fhir.test.utilities.HtmlUtil;
|
||||||
import ca.uhn.fhir.util.ClasspathUtil;
|
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 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.IBaseResource;
|
||||||
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.hl7.fhir.r4.model.AllergyIntolerance;
|
import org.hl7.fhir.r4.model.AllergyIntolerance;
|
||||||
import org.hl7.fhir.r4.model.Bundle;
|
import org.hl7.fhir.r4.model.Bundle;
|
||||||
import org.hl7.fhir.r4.model.CarePlan;
|
import org.hl7.fhir.r4.model.CarePlan;
|
||||||
|
@ -61,11 +60,11 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import jakarta.annotation.Nonnull;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static ca.uhn.fhir.jpa.ips.generator.IpsGenerationR4Test.findEntryResource;
|
import static ca.uhn.fhir.jpa.ips.generator.IpsGenerationR4Test.findEntryResource;
|
||||||
|
@ -75,6 +74,7 @@ import static org.hamcrest.Matchers.containsString;
|
||||||
import static org.hamcrest.Matchers.not;
|
import static org.hamcrest.Matchers.not;
|
||||||
import static org.hamcrest.Matchers.startsWith;
|
import static org.hamcrest.Matchers.startsWith;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
|
@ -114,23 +114,44 @@ public class IpsGeneratorSvcImplTest {
|
||||||
private final FhirContext myFhirContext = FhirContext.forR4Cached();
|
private final FhirContext myFhirContext = FhirContext.forR4Cached();
|
||||||
private final DaoRegistry myDaoRegistry = new DaoRegistry(myFhirContext);
|
private final DaoRegistry myDaoRegistry = new DaoRegistry(myFhirContext);
|
||||||
private IIpsGeneratorSvc mySvc;
|
private IIpsGeneratorSvc mySvc;
|
||||||
private DefaultIpsGenerationStrategy myStrategy;
|
private DefaultJpaIpsGenerationStrategy myStrategy;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void beforeEach() {
|
public void beforeEach() {
|
||||||
myDaoRegistry.setResourceDaos(Collections.emptyList());
|
myDaoRegistry.setResourceDaos(Collections.emptyList());
|
||||||
|
}
|
||||||
|
|
||||||
myStrategy = new DefaultIpsGenerationStrategy();
|
private void initializeGenerationStrategy() {
|
||||||
mySvc = new IpsGeneratorSvcImpl(myFhirContext, myStrategy, myDaoRegistry);
|
initializeGenerationStrategy(List.of());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeGenerationStrategy(List<Function<Section, Section>> 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
|
@Test
|
||||||
public void testGenerateIps() {
|
public void testGenerateIps() {
|
||||||
// Setup
|
// Setup
|
||||||
|
initializeGenerationStrategy();
|
||||||
registerResourceDaosForSmallPatientSet();
|
registerResourceDaosForSmallPatientSet();
|
||||||
|
|
||||||
// Test
|
// 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
|
// Verify
|
||||||
ourLog.info("Generated IPS:\n{}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
|
ourLog.info("Generated IPS:\n{}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
|
||||||
|
@ -165,6 +186,7 @@ public class IpsGeneratorSvcImplTest {
|
||||||
@Test
|
@Test
|
||||||
public void testAllergyIntolerance_OnsetTypes() throws IOException {
|
public void testAllergyIntolerance_OnsetTypes() throws IOException {
|
||||||
// Setup Patient
|
// Setup Patient
|
||||||
|
initializeGenerationStrategy();
|
||||||
registerPatientDaoWithRead();
|
registerPatientDaoWithRead();
|
||||||
|
|
||||||
AllergyIntolerance allergy1 = new AllergyIntolerance();
|
AllergyIntolerance allergy1 = new AllergyIntolerance();
|
||||||
|
@ -191,11 +213,11 @@ public class IpsGeneratorSvcImplTest {
|
||||||
registerRemainingResourceDaos();
|
registerRemainingResourceDaos();
|
||||||
|
|
||||||
// Test
|
// 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
|
// Verify
|
||||||
Composition compositions = (Composition) outcome.getEntry().get(0).getResource();
|
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());
|
HtmlPage narrativeHtml = HtmlUtil.parseAsHtml(section.getText().getDivAsString());
|
||||||
ourLog.info("Narrative:\n{}", narrativeHtml.asXml());
|
ourLog.info("Narrative:\n{}", narrativeHtml.asXml());
|
||||||
|
@ -213,6 +235,7 @@ public class IpsGeneratorSvcImplTest {
|
||||||
@Test
|
@Test
|
||||||
public void testAllergyIntolerance_MissingElements() throws IOException {
|
public void testAllergyIntolerance_MissingElements() throws IOException {
|
||||||
// Setup Patient
|
// Setup Patient
|
||||||
|
initializeGenerationStrategy();
|
||||||
registerPatientDaoWithRead();
|
registerPatientDaoWithRead();
|
||||||
|
|
||||||
AllergyIntolerance allergy = new AllergyIntolerance();
|
AllergyIntolerance allergy = new AllergyIntolerance();
|
||||||
|
@ -226,11 +249,11 @@ public class IpsGeneratorSvcImplTest {
|
||||||
registerRemainingResourceDaos();
|
registerRemainingResourceDaos();
|
||||||
|
|
||||||
// Test
|
// 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
|
// Verify
|
||||||
Composition compositions = (Composition) outcome.getEntry().get(0).getResource();
|
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());
|
HtmlPage narrativeHtml = HtmlUtil.parseAsHtml(section.getText().getDivAsString());
|
||||||
ourLog.info("Narrative:\n{}", narrativeHtml.asXml());
|
ourLog.info("Narrative:\n{}", narrativeHtml.asXml());
|
||||||
|
@ -242,6 +265,7 @@ public class IpsGeneratorSvcImplTest {
|
||||||
@Test
|
@Test
|
||||||
public void testMedicationSummary_MedicationStatementWithMedicationReference() throws IOException {
|
public void testMedicationSummary_MedicationStatementWithMedicationReference() throws IOException {
|
||||||
// Setup Patient
|
// Setup Patient
|
||||||
|
initializeGenerationStrategy();
|
||||||
registerPatientDaoWithRead();
|
registerPatientDaoWithRead();
|
||||||
|
|
||||||
// Setup Medication + MedicationStatement
|
// Setup Medication + MedicationStatement
|
||||||
|
@ -253,7 +277,7 @@ public class IpsGeneratorSvcImplTest {
|
||||||
registerRemainingResourceDaos();
|
registerRemainingResourceDaos();
|
||||||
|
|
||||||
// Test
|
// 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
|
// Verify Bundle Contents
|
||||||
List<String> contentResourceTypes = toEntryResourceTypeStrings(outcome);
|
List<String> contentResourceTypes = toEntryResourceTypeStrings(outcome);
|
||||||
|
@ -266,14 +290,14 @@ public class IpsGeneratorSvcImplTest {
|
||||||
|
|
||||||
// Verify
|
// Verify
|
||||||
Composition compositions = (Composition) outcome.getEntry().get(0).getResource();
|
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());
|
HtmlPage narrativeHtml = HtmlUtil.parseAsHtml(section.getText().getDivAsString());
|
||||||
ourLog.info("Narrative:\n{}", narrativeHtml.asXml());
|
ourLog.info("Narrative:\n{}", narrativeHtml.asXml());
|
||||||
|
|
||||||
DomNodeList<DomElement> tables = narrativeHtml.getElementsByTagName("table");
|
DomNodeList<DomElement> tables = narrativeHtml.getElementsByTagName("table");
|
||||||
assertEquals(2, tables.size());
|
assertEquals(1, tables.size());
|
||||||
HtmlTable table = (HtmlTable) tables.get(1);
|
HtmlTable table = (HtmlTable) tables.get(0);
|
||||||
HtmlTableRow row = table.getBodies().get(0).getRows().get(0);
|
HtmlTableRow row = table.getBodies().get(0).getRows().get(0);
|
||||||
assertEquals("Tylenol", row.getCell(0).asNormalizedText());
|
assertEquals("Tylenol", row.getCell(0).asNormalizedText());
|
||||||
assertEquals("Active", row.getCell(1).asNormalizedText());
|
assertEquals("Active", row.getCell(1).asNormalizedText());
|
||||||
|
@ -285,6 +309,7 @@ public class IpsGeneratorSvcImplTest {
|
||||||
@Test
|
@Test
|
||||||
public void testMedicationSummary_MedicationRequestWithNoMedication() throws IOException {
|
public void testMedicationSummary_MedicationRequestWithNoMedication() throws IOException {
|
||||||
// Setup Patient
|
// Setup Patient
|
||||||
|
initializeGenerationStrategy();
|
||||||
registerPatientDaoWithRead();
|
registerPatientDaoWithRead();
|
||||||
|
|
||||||
// Setup Medication + MedicationStatement
|
// Setup Medication + MedicationStatement
|
||||||
|
@ -298,17 +323,17 @@ public class IpsGeneratorSvcImplTest {
|
||||||
registerRemainingResourceDaos();
|
registerRemainingResourceDaos();
|
||||||
|
|
||||||
// Test
|
// 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
|
// Verify
|
||||||
Composition compositions = (Composition) outcome.getEntry().get(0).getResource();
|
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());
|
HtmlPage narrativeHtml = HtmlUtil.parseAsHtml(section.getText().getDivAsString());
|
||||||
ourLog.info("Narrative:\n{}", narrativeHtml.asXml());
|
ourLog.info("Narrative:\n{}", narrativeHtml.asXml());
|
||||||
|
|
||||||
DomNodeList<DomElement> tables = narrativeHtml.getElementsByTagName("table");
|
DomNodeList<DomElement> tables = narrativeHtml.getElementsByTagName("table");
|
||||||
assertEquals(2, tables.size());
|
assertEquals(1, tables.size());
|
||||||
HtmlTable table = (HtmlTable) tables.get(0);
|
HtmlTable table = (HtmlTable) tables.get(0);
|
||||||
HtmlTableRow row = table.getBodies().get(0).getRows().get(0);
|
HtmlTableRow row = table.getBodies().get(0).getRows().get(0);
|
||||||
assertEquals("", row.getCell(0).asNormalizedText());
|
assertEquals("", row.getCell(0).asNormalizedText());
|
||||||
|
@ -317,22 +342,12 @@ public class IpsGeneratorSvcImplTest {
|
||||||
assertEquals("", row.getCell(3).asNormalizedText());
|
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
|
@Test
|
||||||
public void testMedicationSummary_DuplicateSecondaryResources() {
|
public void testMedicationSummary_DuplicateSecondaryResources() {
|
||||||
myStrategy.setSectionRegistry(new SectionRegistry().addGlobalCustomizer(t -> t.withNoInfoGenerator(null)));
|
|
||||||
|
|
||||||
// Setup Patient
|
// Setup Patient
|
||||||
|
initializeGenerationStrategy(
|
||||||
|
List.of(t->Section.newBuilder(t).withNoInfoGenerator(null).build())
|
||||||
|
);
|
||||||
registerPatientDaoWithRead();
|
registerPatientDaoWithRead();
|
||||||
|
|
||||||
// Setup Medication + MedicationStatement
|
// Setup Medication + MedicationStatement
|
||||||
|
@ -346,7 +361,7 @@ public class IpsGeneratorSvcImplTest {
|
||||||
registerRemainingResourceDaos();
|
registerRemainingResourceDaos();
|
||||||
|
|
||||||
// Test
|
// 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
|
// Verify Bundle Contents
|
||||||
List<String> contentResourceTypes = toEntryResourceTypeStrings(outcome);
|
List<String> contentResourceTypes = toEntryResourceTypeStrings(outcome);
|
||||||
|
@ -367,9 +382,10 @@ public class IpsGeneratorSvcImplTest {
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testMedicationSummary_ResourceAppearsAsSecondaryThenPrimary() throws IOException {
|
public void testMedicationSummary_ResourceAppearsAsSecondaryThenPrimary() throws IOException {
|
||||||
myStrategy.setSectionRegistry(new SectionRegistry().addGlobalCustomizer(t -> t.withNoInfoGenerator(null)));
|
|
||||||
|
|
||||||
// Setup Patient
|
// Setup Patient
|
||||||
|
initializeGenerationStrategy(
|
||||||
|
List.of(t->Section.newBuilder(t).withNoInfoGenerator(null).build())
|
||||||
|
);
|
||||||
registerPatientDaoWithRead();
|
registerPatientDaoWithRead();
|
||||||
|
|
||||||
// Setup Medication + MedicationStatement
|
// Setup Medication + MedicationStatement
|
||||||
|
@ -385,7 +401,7 @@ public class IpsGeneratorSvcImplTest {
|
||||||
registerRemainingResourceDaos();
|
registerRemainingResourceDaos();
|
||||||
|
|
||||||
// Test
|
// 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
|
// Verify Bundle Contents
|
||||||
List<String> contentResourceTypes = toEntryResourceTypeStrings(outcome);
|
List<String> contentResourceTypes = toEntryResourceTypeStrings(outcome);
|
||||||
|
@ -400,20 +416,61 @@ public class IpsGeneratorSvcImplTest {
|
||||||
|
|
||||||
// Verify narrative - should have 2 rows (one for each primary MedicationStatement)
|
// Verify narrative - should have 2 rows (one for each primary MedicationStatement)
|
||||||
Composition compositions = (Composition) outcome.getEntry().get(0).getResource();
|
Composition compositions = (Composition) outcome.getEntry().get(0).getResource();
|
||||||
Composition.SectionComponent section = findSection(compositions, IpsSectionEnum.MEDICATION_SUMMARY);
|
Composition.SectionComponent section = findSection(compositions, DefaultJpaIpsGenerationStrategy.SECTION_CODE_MEDICATION_SUMMARY);
|
||||||
|
|
||||||
HtmlPage narrativeHtml = HtmlUtil.parseAsHtml(section.getText().getDivAsString());
|
HtmlPage narrativeHtml = HtmlUtil.parseAsHtml(section.getText().getDivAsString());
|
||||||
ourLog.info("Narrative:\n{}", narrativeHtml.asXml());
|
ourLog.info("Narrative:\n{}", narrativeHtml.asXml());
|
||||||
|
|
||||||
DomNodeList<DomElement> tables = narrativeHtml.getElementsByTagName("table");
|
DomNodeList<DomElement> tables = narrativeHtml.getElementsByTagName("table");
|
||||||
assertEquals(2, tables.size());
|
assertEquals(1, tables.size());
|
||||||
HtmlTable table = (HtmlTable) tables.get(1);
|
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<MedicationStatement> 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<DomElement> tables = narrativeHtml.getElementsByTagName("table");
|
||||||
|
assertEquals(1, tables.size());
|
||||||
|
HtmlTable table = (HtmlTable) tables.get(0);
|
||||||
assertEquals(2, table.getBodies().get(0).getRows().size());
|
assertEquals(2, table.getBodies().get(0).getRows().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMedicalDevices_DeviceUseStatementWithDevice() throws IOException {
|
public void testMedicalDevices_DeviceUseStatementWithDevice() throws IOException {
|
||||||
// Setup Patient
|
// Setup Patient
|
||||||
|
initializeGenerationStrategy();
|
||||||
registerPatientDaoWithRead();
|
registerPatientDaoWithRead();
|
||||||
|
|
||||||
// Setup Medication + MedicationStatement
|
// Setup Medication + MedicationStatement
|
||||||
|
@ -436,11 +493,11 @@ public class IpsGeneratorSvcImplTest {
|
||||||
registerRemainingResourceDaos();
|
registerRemainingResourceDaos();
|
||||||
|
|
||||||
// Test
|
// 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
|
// Verify
|
||||||
Composition compositions = (Composition) outcome.getEntry().get(0).getResource();
|
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());
|
HtmlPage narrativeHtml = HtmlUtil.parseAsHtml(section.getText().getDivAsString());
|
||||||
ourLog.info("Narrative:\n{}", narrativeHtml.asXml());
|
ourLog.info("Narrative:\n{}", narrativeHtml.asXml());
|
||||||
|
@ -457,6 +514,7 @@ public class IpsGeneratorSvcImplTest {
|
||||||
@Test
|
@Test
|
||||||
public void testImmunizations() throws IOException {
|
public void testImmunizations() throws IOException {
|
||||||
// Setup Patient
|
// Setup Patient
|
||||||
|
initializeGenerationStrategy();
|
||||||
registerPatientDaoWithRead();
|
registerPatientDaoWithRead();
|
||||||
|
|
||||||
// Setup Medication + MedicationStatement
|
// Setup Medication + MedicationStatement
|
||||||
|
@ -483,11 +541,11 @@ public class IpsGeneratorSvcImplTest {
|
||||||
registerRemainingResourceDaos();
|
registerRemainingResourceDaos();
|
||||||
|
|
||||||
// Test
|
// 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
|
// Verify
|
||||||
Composition compositions = (Composition) outcome.getEntry().get(0).getResource();
|
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());
|
HtmlPage narrativeHtml = HtmlUtil.parseAsHtml(section.getText().getDivAsString());
|
||||||
ourLog.info("Narrative:\n{}", narrativeHtml.asXml());
|
ourLog.info("Narrative:\n{}", narrativeHtml.asXml());
|
||||||
|
@ -508,6 +566,7 @@ public class IpsGeneratorSvcImplTest {
|
||||||
@Test
|
@Test
|
||||||
public void testReferencesUpdatedInSecondaryInclusions() {
|
public void testReferencesUpdatedInSecondaryInclusions() {
|
||||||
// Setup Patient
|
// Setup Patient
|
||||||
|
initializeGenerationStrategy();
|
||||||
registerPatientDaoWithRead();
|
registerPatientDaoWithRead();
|
||||||
|
|
||||||
// Setup Medication + MedicationStatement
|
// Setup Medication + MedicationStatement
|
||||||
|
@ -545,7 +604,7 @@ public class IpsGeneratorSvcImplTest {
|
||||||
registerRemainingResourceDaos();
|
registerRemainingResourceDaos();
|
||||||
|
|
||||||
// Test
|
// 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));
|
ourLog.info(myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
|
||||||
|
|
||||||
// Verify cross-references
|
// Verify cross-references
|
||||||
|
@ -569,10 +628,10 @@ public class IpsGeneratorSvcImplTest {
|
||||||
ourLog.info("Resource: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
|
ourLog.info("Resource: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
|
||||||
verify(conditionDao, times(2)).search(any(), any());
|
verify(conditionDao, times(2)).search(any(), any());
|
||||||
Composition composition = (Composition) outcome.getEntry().get(0).getResource();
|
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(addedCondition.getId(), problemListSection.getEntry().get(0).getReference());
|
||||||
assertEquals(1, problemListSection.getEntry().size());
|
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(addedCondition2.getId(), illnessHistorySection.getEntry().get(0).getReference());
|
||||||
assertEquals(1, illnessHistorySection.getEntry().size());
|
assertEquals(1, illnessHistorySection.getEntry().size());
|
||||||
}
|
}
|
||||||
|
@ -580,6 +639,7 @@ public class IpsGeneratorSvcImplTest {
|
||||||
@Test
|
@Test
|
||||||
public void testPatientIsReturnedAsAnIncludeResource() {
|
public void testPatientIsReturnedAsAnIncludeResource() {
|
||||||
// Setup Patient
|
// Setup Patient
|
||||||
|
initializeGenerationStrategy();
|
||||||
registerPatientDaoWithRead();
|
registerPatientDaoWithRead();
|
||||||
|
|
||||||
// Setup Condition
|
// Setup Condition
|
||||||
|
@ -605,7 +665,7 @@ public class IpsGeneratorSvcImplTest {
|
||||||
registerRemainingResourceDaos();
|
registerRemainingResourceDaos();
|
||||||
|
|
||||||
// Test
|
// 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<String> resources = outcome
|
List<String> resources = outcome
|
||||||
.getEntry()
|
.getEntry()
|
||||||
|
@ -617,6 +677,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() {
|
private void registerPatientDaoWithRead() {
|
||||||
IFhirResourceDao<Patient> patientDao = registerResourceDaoWithNoData(Patient.class);
|
IFhirResourceDao<Patient> patientDao = registerResourceDaoWithNoData(Patient.class);
|
||||||
Patient patient = new Patient();
|
Patient patient = new Patient();
|
||||||
|
@ -674,19 +758,19 @@ public class IpsGeneratorSvcImplTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
private static Medication createSecondaryMedication(String medicationId) {
|
private static Medication createSecondaryMedication(String theMedicationId) {
|
||||||
Medication medication = new Medication();
|
Medication medication = new Medication();
|
||||||
medication.setId(new IdType(medicationId));
|
medication.setId(new IdType(theMedicationId));
|
||||||
medication.getCode().addCoding().setDisplay("Tylenol");
|
medication.getCode().addCoding().setDisplay("Tylenol");
|
||||||
ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.put(medication, BundleEntrySearchModeEnum.INCLUDE);
|
ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.put(medication, BundleEntrySearchModeEnum.INCLUDE);
|
||||||
return medication;
|
return medication;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
private static MedicationStatement createPrimaryMedicationStatement(String medicationId, String medicationStatementId) {
|
private static MedicationStatement createPrimaryMedicationStatement(String theMedicationId, String medicationStatementId) {
|
||||||
MedicationStatement medicationStatement = new MedicationStatement();
|
MedicationStatement medicationStatement = new MedicationStatement();
|
||||||
medicationStatement.setId(medicationStatementId);
|
medicationStatement.setId(medicationStatementId);
|
||||||
medicationStatement.setMedication(new Reference(medicationId));
|
medicationStatement.setMedication(new Reference(theMedicationId));
|
||||||
medicationStatement.setStatus(MedicationStatement.MedicationStatementStatus.ACTIVE);
|
medicationStatement.setStatus(MedicationStatement.MedicationStatementStatus.ACTIVE);
|
||||||
medicationStatement.getDosageFirstRep().getRoute().addCoding().setDisplay("Oral");
|
medicationStatement.getDosageFirstRep().getRoute().addCoding().setDisplay("Oral");
|
||||||
medicationStatement.getDosageFirstRep().setText("DAW");
|
medicationStatement.getDosageFirstRep().setText("DAW");
|
||||||
|
|
|
@ -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<String> myProfileCaptor;
|
||||||
|
@Captor
|
||||||
|
private ArgumentCaptor<IIdType> myIdTypeCaptor;
|
||||||
|
@Captor
|
||||||
|
private ArgumentCaptor<TokenParam> myTokenCaptor;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGenerateById() {
|
||||||
|
// setup
|
||||||
|
|
||||||
|
Bundle expected = new Bundle();
|
||||||
|
expected.setType(Bundle.BundleType.DOCUMENT);
|
||||||
|
when(myIpsGeneratorSvc.generateIps(any(), any(IIdType.class), any())).thenReturn(expected);
|
||||||
|
|
||||||
|
// test
|
||||||
|
|
||||||
|
Bundle actual = myServer
|
||||||
|
.getFhirClient()
|
||||||
|
.operation()
|
||||||
|
.onInstance(new IdType("Patient/123"))
|
||||||
|
.named("$summary")
|
||||||
|
.withNoParameters(Parameters.class)
|
||||||
|
.returnResourceType(Bundle.class)
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
// verify
|
||||||
|
assertEquals(Bundle.BundleType.DOCUMENT, actual.getType());
|
||||||
|
verify(myIpsGeneratorSvc, times(1)).generateIps(any(), myIdTypeCaptor.capture(), myProfileCaptor.capture());
|
||||||
|
assertEquals("Patient/123", myIdTypeCaptor.getValue().getValue());
|
||||||
|
assertEquals(null, myProfileCaptor.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGenerateById_WithProfile() {
|
||||||
|
// setup
|
||||||
|
|
||||||
|
Bundle expected = new Bundle();
|
||||||
|
expected.setType(Bundle.BundleType.DOCUMENT);
|
||||||
|
when(myIpsGeneratorSvc.generateIps(any(), any(IIdType.class), any())).thenReturn(expected);
|
||||||
|
|
||||||
|
// test
|
||||||
|
|
||||||
|
Bundle actual = myServer
|
||||||
|
.getFhirClient()
|
||||||
|
.operation()
|
||||||
|
.onInstance(new IdType("Patient/123"))
|
||||||
|
.named("$summary")
|
||||||
|
.withParameter(Parameters.class, "profile", new UriType("http://foo"))
|
||||||
|
.returnResourceType(Bundle.class)
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
// verify
|
||||||
|
assertEquals(Bundle.BundleType.DOCUMENT, actual.getType());
|
||||||
|
verify(myIpsGeneratorSvc, times(1)).generateIps(any(), myIdTypeCaptor.capture(), myProfileCaptor.capture());
|
||||||
|
assertEquals("Patient/123", myIdTypeCaptor.getValue().getValue());
|
||||||
|
assertEquals("http://foo", myProfileCaptor.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGenerateByIdentifier() {
|
||||||
|
// setup
|
||||||
|
|
||||||
|
Bundle expected = new Bundle();
|
||||||
|
expected.setType(Bundle.BundleType.DOCUMENT);
|
||||||
|
when(myIpsGeneratorSvc.generateIps(any(), any(TokenParam.class), any())).thenReturn(expected);
|
||||||
|
|
||||||
|
// test
|
||||||
|
|
||||||
|
Bundle actual = myServer
|
||||||
|
.getFhirClient()
|
||||||
|
.operation()
|
||||||
|
.onType("Patient")
|
||||||
|
.named("$summary")
|
||||||
|
.withParameter(Parameters.class, "identifier", new Identifier().setSystem("http://system").setValue("value"))
|
||||||
|
.returnResourceType(Bundle.class)
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
// verify
|
||||||
|
assertEquals(Bundle.BundleType.DOCUMENT, actual.getType());
|
||||||
|
verify(myIpsGeneratorSvc, times(1)).generateIps(any(), myTokenCaptor.capture(), myProfileCaptor.capture());
|
||||||
|
assertEquals("http://system", myTokenCaptor.getValue().getSystem());
|
||||||
|
assertEquals("value", myTokenCaptor.getValue().getValue());
|
||||||
|
assertEquals(null, myProfileCaptor.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGenerateByIdentifier_WithProfile() {
|
||||||
|
// setup
|
||||||
|
|
||||||
|
Bundle expected = new Bundle();
|
||||||
|
expected.setType(Bundle.BundleType.DOCUMENT);
|
||||||
|
when(myIpsGeneratorSvc.generateIps(any(), any(TokenParam.class), any())).thenReturn(expected);
|
||||||
|
|
||||||
|
// test
|
||||||
|
|
||||||
|
Bundle actual = myServer
|
||||||
|
.getFhirClient()
|
||||||
|
.operation()
|
||||||
|
.onType("Patient")
|
||||||
|
.named("$summary")
|
||||||
|
.withParameter(Parameters.class, "identifier", new Identifier().setSystem("http://system").setValue("value"))
|
||||||
|
.andParameter("profile", new UriType("http://foo"))
|
||||||
|
.returnResourceType(Bundle.class)
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
// verify
|
||||||
|
|
||||||
|
assertEquals(Bundle.BundleType.DOCUMENT, actual.getType());
|
||||||
|
verify(myIpsGeneratorSvc, times(1)).generateIps(any(), myTokenCaptor.capture(), myProfileCaptor.capture());
|
||||||
|
assertEquals("http://system", myTokenCaptor.getValue().getSystem());
|
||||||
|
assertEquals("value", myTokenCaptor.getValue().getValue());
|
||||||
|
assertEquals("http://foo", myProfileCaptor.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -2,15 +2,14 @@ package ca.uhn.fhirtest.config;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
|
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
|
||||||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
|
||||||
import ca.uhn.fhir.jpa.config.HapiJpaConfig;
|
import ca.uhn.fhir.jpa.config.HapiJpaConfig;
|
||||||
import ca.uhn.fhir.jpa.config.r4.JpaR4Config;
|
import ca.uhn.fhir.jpa.config.r4.JpaR4Config;
|
||||||
import ca.uhn.fhir.jpa.config.util.HapiEntityManagerFactoryUtil;
|
import ca.uhn.fhir.jpa.config.util.HapiEntityManagerFactoryUtil;
|
||||||
import ca.uhn.fhir.jpa.ips.api.IIpsGenerationStrategy;
|
import ca.uhn.fhir.jpa.ips.api.IIpsGenerationStrategy;
|
||||||
import ca.uhn.fhir.jpa.ips.generator.IIpsGeneratorSvc;
|
import ca.uhn.fhir.jpa.ips.generator.IIpsGeneratorSvc;
|
||||||
import ca.uhn.fhir.jpa.ips.generator.IpsGeneratorSvcImpl;
|
import ca.uhn.fhir.jpa.ips.generator.IpsGeneratorSvcImpl;
|
||||||
|
import ca.uhn.fhir.jpa.ips.jpa.DefaultJpaIpsGenerationStrategy;
|
||||||
import ca.uhn.fhir.jpa.ips.provider.IpsOperationProvider;
|
import ca.uhn.fhir.jpa.ips.provider.IpsOperationProvider;
|
||||||
import ca.uhn.fhir.jpa.ips.strategy.DefaultIpsGenerationStrategy;
|
|
||||||
import ca.uhn.fhir.jpa.model.dialect.HapiFhirH2Dialect;
|
import ca.uhn.fhir.jpa.model.dialect.HapiFhirH2Dialect;
|
||||||
import ca.uhn.fhir.jpa.model.dialect.HapiFhirPostgres94Dialect;
|
import ca.uhn.fhir.jpa.model.dialect.HapiFhirPostgres94Dialect;
|
||||||
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
||||||
|
@ -200,13 +199,12 @@ public class TestR4Config {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public IIpsGenerationStrategy ipsGenerationStrategy() {
|
public IIpsGenerationStrategy ipsGenerationStrategy() {
|
||||||
return new DefaultIpsGenerationStrategy();
|
return new DefaultJpaIpsGenerationStrategy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public IIpsGeneratorSvc ipsGeneratorSvc(
|
public IIpsGeneratorSvc ipsGeneratorSvc(FhirContext theFhirContext, IIpsGenerationStrategy theGenerationStrategy) {
|
||||||
FhirContext theFhirContext, IIpsGenerationStrategy theGenerationStrategy, DaoRegistry theDaoRegistry) {
|
return new IpsGeneratorSvcImpl(theFhirContext, theGenerationStrategy);
|
||||||
return new IpsGeneratorSvcImpl(theFhirContext, theGenerationStrategy, theDaoRegistry);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
package ca.uhn.fhir.narrative2;
|
||||||
|
|
||||||
|
import org.hl7.fhir.dstu3.model.Bundle;
|
||||||
|
import org.hl7.fhir.dstu3.model.Medication;
|
||||||
|
import org.hl7.fhir.dstu3.model.Patient;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class NarrativeGeneratorTemplateUtilsTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBundleHasEntriesWithResourceType_True() {
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.addEntry().setResource(new Patient().setActive(true));
|
||||||
|
bundle.addEntry().setResource(new Medication().setIsBrand(true));
|
||||||
|
assertTrue(NarrativeGeneratorTemplateUtils.INSTANCE.bundleHasEntriesWithResourceType(bundle, "Patient"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBundleHasEntriesWithResourceType_False() {
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.addEntry().setResource(new Medication().setIsBrand(true));
|
||||||
|
assertFalse(NarrativeGeneratorTemplateUtils.INSTANCE.bundleHasEntriesWithResourceType(bundle, "Patient"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue