IPS Connectathon Fixes (#4834)
* IPS Connectathon Fixes * Fix changelog * Cleanup
This commit is contained in:
parent
53ceb7cac4
commit
e14bbec83b
|
@ -0,0 +1,11 @@
|
||||||
|
---
|
||||||
|
type: change
|
||||||
|
issue: 4834
|
||||||
|
title: "Several enhancements were made to the IPS generator:<ul>
|
||||||
|
<li>Generated IPS documents will no longer include sections that contain no contents.</li>
|
||||||
|
<li>NarrativeLink extensions now use the correct datatype (url instead of uri)</li>
|
||||||
|
<li>Section profile URLs have been updated to no longer use an unknown URL</li>
|
||||||
|
<li>Some resources added to the generated IPS did not have their FHIR server IDs replaced with a placeholder UUID.</li>
|
||||||
|
<li>Immunization manufacturer was not fetched from the server</li>
|
||||||
|
</ul>
|
||||||
|
Thanks to Rio Bennin for all of the feedback!"
|
|
@ -48,7 +48,7 @@ The narrative properties file should contain definitions using the profile URL o
|
||||||
|
|
||||||
```properties
|
```properties
|
||||||
ips-allergyintolerance.resourceType=Bundle
|
ips-allergyintolerance.resourceType=Bundle
|
||||||
ips-allergyintolerance.profile=http://hl7.org/fhir/uv/ips/StructureDefinition/AllergiesAndIntolerances-uv-ips
|
ips-allergyintolerance.profile=https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionAllergies
|
||||||
ips-allergyintolerance.narrative=classpath:ca/uhn/fhir/jpa/ips/narrative/allergyintolerance.html
|
ips-allergyintolerance.narrative=classpath:ca/uhn/fhir/jpa/ips/narrative/allergyintolerance.html
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -107,7 +107,8 @@ public class SectionRegistry {
|
||||||
.withSectionCode("48765-2")
|
.withSectionCode("48765-2")
|
||||||
.withSectionDisplay("Allergies and Adverse Reactions")
|
.withSectionDisplay("Allergies and Adverse Reactions")
|
||||||
.withResourceTypes(ResourceType.AllergyIntolerance.name())
|
.withResourceTypes(ResourceType.AllergyIntolerance.name())
|
||||||
.withProfile("http://hl7.org/fhir/uv/ips/StructureDefinition/AllergiesAndIntolerances-uv-ips")
|
.withProfile(
|
||||||
|
"https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionAllergies")
|
||||||
.withNoInfoGenerator(new AllergyIntoleranceNoInfoR4Generator())
|
.withNoInfoGenerator(new AllergyIntoleranceNoInfoR4Generator())
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
@ -122,7 +123,8 @@ public class SectionRegistry {
|
||||||
ResourceType.MedicationRequest.name(),
|
ResourceType.MedicationRequest.name(),
|
||||||
ResourceType.MedicationAdministration.name(),
|
ResourceType.MedicationAdministration.name(),
|
||||||
ResourceType.MedicationDispense.name())
|
ResourceType.MedicationDispense.name())
|
||||||
.withProfile("http://hl7.org/fhir/uv/ips/StructureDefinition/MedicationSummary-uv-ips")
|
.withProfile(
|
||||||
|
"https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionMedications")
|
||||||
.withNoInfoGenerator(new MedicationNoInfoR4Generator())
|
.withNoInfoGenerator(new MedicationNoInfoR4Generator())
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
@ -133,7 +135,8 @@ public class SectionRegistry {
|
||||||
.withSectionCode("11450-4")
|
.withSectionCode("11450-4")
|
||||||
.withSectionDisplay("Problem List")
|
.withSectionDisplay("Problem List")
|
||||||
.withResourceTypes(ResourceType.Condition.name())
|
.withResourceTypes(ResourceType.Condition.name())
|
||||||
.withProfile("http://hl7.org/fhir/uv/ips/StructureDefinition/ProblemList-uv-ips")
|
.withProfile(
|
||||||
|
"https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionProblems")
|
||||||
.withNoInfoGenerator(new ProblemNoInfoR4Generator())
|
.withNoInfoGenerator(new ProblemNoInfoR4Generator())
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
@ -144,7 +147,8 @@ public class SectionRegistry {
|
||||||
.withSectionCode("11369-6")
|
.withSectionCode("11369-6")
|
||||||
.withSectionDisplay("History of Immunizations")
|
.withSectionDisplay("History of Immunizations")
|
||||||
.withResourceTypes(ResourceType.Immunization.name())
|
.withResourceTypes(ResourceType.Immunization.name())
|
||||||
.withProfile("http://hl7.org/fhir/uv/ips/StructureDefinition/Immunizations-uv-ips")
|
.withProfile(
|
||||||
|
"https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionImmunizations")
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,7 +158,8 @@ public class SectionRegistry {
|
||||||
.withSectionCode("47519-4")
|
.withSectionCode("47519-4")
|
||||||
.withSectionDisplay("History of Procedures")
|
.withSectionDisplay("History of Procedures")
|
||||||
.withResourceTypes(ResourceType.Procedure.name())
|
.withResourceTypes(ResourceType.Procedure.name())
|
||||||
.withProfile("http://hl7.org/fhir/uv/ips/StructureDefinition/HistoryOfProcedures-uv-ips")
|
.withProfile(
|
||||||
|
"https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionProceduresHx")
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,7 +169,8 @@ public class SectionRegistry {
|
||||||
.withSectionCode("46240-8")
|
.withSectionCode("46240-8")
|
||||||
.withSectionDisplay("Medical Devices")
|
.withSectionDisplay("Medical Devices")
|
||||||
.withResourceTypes(ResourceType.DeviceUseStatement.name())
|
.withResourceTypes(ResourceType.DeviceUseStatement.name())
|
||||||
.withProfile("http://hl7.org/fhir/uv/ips/StructureDefinition/MedicalDevices-uv-ips")
|
.withProfile(
|
||||||
|
"https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionMedicalDevices")
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,7 +180,8 @@ public class SectionRegistry {
|
||||||
.withSectionCode("30954-2")
|
.withSectionCode("30954-2")
|
||||||
.withSectionDisplay("Diagnostic Results")
|
.withSectionDisplay("Diagnostic Results")
|
||||||
.withResourceTypes(ResourceType.DiagnosticReport.name(), ResourceType.Observation.name())
|
.withResourceTypes(ResourceType.DiagnosticReport.name(), ResourceType.Observation.name())
|
||||||
.withProfile("http://hl7.org/fhir/uv/ips/StructureDefinition/DiagnosticResults-uv-ips")
|
.withProfile(
|
||||||
|
"https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionResults")
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,7 +191,8 @@ public class SectionRegistry {
|
||||||
.withSectionCode("8716-3")
|
.withSectionCode("8716-3")
|
||||||
.withSectionDisplay("Vital Signs")
|
.withSectionDisplay("Vital Signs")
|
||||||
.withResourceTypes(ResourceType.Observation.name())
|
.withResourceTypes(ResourceType.Observation.name())
|
||||||
.withProfile("http://hl7.org/fhir/uv/ips/StructureDefinition/VitalSigns-uv-ips")
|
.withProfile(
|
||||||
|
"https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionVitalSigns")
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,7 +202,8 @@ public class SectionRegistry {
|
||||||
.withSectionCode("10162-6")
|
.withSectionCode("10162-6")
|
||||||
.withSectionDisplay("Pregnancy Information")
|
.withSectionDisplay("Pregnancy Information")
|
||||||
.withResourceTypes(ResourceType.Observation.name())
|
.withResourceTypes(ResourceType.Observation.name())
|
||||||
.withProfile("http://hl7.org/fhir/uv/ips/StructureDefinition/Pregnancy-uv-ips")
|
.withProfile(
|
||||||
|
"https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionPregnancyHx")
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,7 +213,8 @@ public class SectionRegistry {
|
||||||
.withSectionCode("29762-2")
|
.withSectionCode("29762-2")
|
||||||
.withSectionDisplay("Social History")
|
.withSectionDisplay("Social History")
|
||||||
.withResourceTypes(ResourceType.Observation.name())
|
.withResourceTypes(ResourceType.Observation.name())
|
||||||
.withProfile("http://hl7.org/fhir/uv/ips/StructureDefinition/SocialHistory-uv-ips")
|
.withProfile(
|
||||||
|
"https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionSocialHistory")
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,7 +224,8 @@ public class SectionRegistry {
|
||||||
.withSectionCode("11348-0")
|
.withSectionCode("11348-0")
|
||||||
.withSectionDisplay("History of Past Illness")
|
.withSectionDisplay("History of Past Illness")
|
||||||
.withResourceTypes(ResourceType.Condition.name())
|
.withResourceTypes(ResourceType.Condition.name())
|
||||||
.withProfile("http://hl7.org/fhir/uv/ips/StructureDefinition/PastHistoryOfIllnesses-uv-ips")
|
.withProfile(
|
||||||
|
"https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionPastIllnessHx")
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,7 +235,8 @@ public class SectionRegistry {
|
||||||
.withSectionCode("47420-5")
|
.withSectionCode("47420-5")
|
||||||
.withSectionDisplay("Functional Status")
|
.withSectionDisplay("Functional Status")
|
||||||
.withResourceTypes(ResourceType.ClinicalImpression.name())
|
.withResourceTypes(ResourceType.ClinicalImpression.name())
|
||||||
.withProfile("http://hl7.org/fhir/uv/ips/StructureDefinition/FunctionalStatus-uv-ips")
|
.withProfile(
|
||||||
|
"https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionFunctionalStatus")
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -234,7 +246,8 @@ public class SectionRegistry {
|
||||||
.withSectionCode("18776-5")
|
.withSectionCode("18776-5")
|
||||||
.withSectionDisplay("Plan of Care")
|
.withSectionDisplay("Plan of Care")
|
||||||
.withResourceTypes(ResourceType.CarePlan.name())
|
.withResourceTypes(ResourceType.CarePlan.name())
|
||||||
.withProfile("http://hl7.org/fhir/uv/ips/StructureDefinition/PlanOfCare-uv-ips")
|
.withProfile(
|
||||||
|
"https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionPlanOfCare")
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -244,7 +257,8 @@ public class SectionRegistry {
|
||||||
.withSectionCode("42349-0")
|
.withSectionCode("42349-0")
|
||||||
.withSectionDisplay("Advance Directives")
|
.withSectionDisplay("Advance Directives")
|
||||||
.withResourceTypes(ResourceType.Consent.name())
|
.withResourceTypes(ResourceType.Consent.name())
|
||||||
.withProfile("http://hl7.org/fhir/uv/ips/StructureDefinition/AdvanceDirectives-uv-ips")
|
.withProfile(
|
||||||
|
"https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionAdvanceDirectives")
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -218,16 +218,16 @@ public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc {
|
||||||
|
|
||||||
for (IBaseResource nextCandidate : resources) {
|
for (IBaseResource nextCandidate : resources) {
|
||||||
|
|
||||||
boolean include;
|
boolean candidateIsSearchInclude = ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.get(nextCandidate)
|
||||||
|
== BundleEntrySearchModeEnum.INCLUDE;
|
||||||
if (ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.get(nextCandidate)
|
boolean addResourceToBundle;
|
||||||
== BundleEntrySearchModeEnum.INCLUDE) {
|
if (candidateIsSearchInclude) {
|
||||||
include = true;
|
addResourceToBundle = true;
|
||||||
} else {
|
} else {
|
||||||
include = myGenerationStrategy.shouldInclude(ipsSectionContext, nextCandidate);
|
addResourceToBundle = myGenerationStrategy.shouldInclude(ipsSectionContext, nextCandidate);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (include) {
|
if (addResourceToBundle) {
|
||||||
|
|
||||||
String originalResourceId = nextCandidate
|
String originalResourceId = nextCandidate
|
||||||
.getIdElement()
|
.getIdElement()
|
||||||
|
@ -249,13 +249,19 @@ public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc {
|
||||||
nextCandidate = previouslyExistingResource;
|
nextCandidate = previouslyExistingResource;
|
||||||
sectionResourcesToInclude.addResourceIfNotAlreadyPresent(nextCandidate, originalResourceId);
|
sectionResourcesToInclude.addResourceIfNotAlreadyPresent(nextCandidate, originalResourceId);
|
||||||
} else if (theGlobalResourcesToInclude.hasResourceWithReplacementId(originalResourceId)) {
|
} else if (theGlobalResourcesToInclude.hasResourceWithReplacementId(originalResourceId)) {
|
||||||
sectionResourcesToInclude.addResourceIfNotAlreadyPresent(nextCandidate, originalResourceId);
|
if (!candidateIsSearchInclude) {
|
||||||
|
sectionResourcesToInclude.addResourceIfNotAlreadyPresent(
|
||||||
|
nextCandidate, originalResourceId);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
IIdType id = myGenerationStrategy.massageResourceId(theIpsContext, nextCandidate);
|
IIdType id = myGenerationStrategy.massageResourceId(theIpsContext, nextCandidate);
|
||||||
nextCandidate.setId(id);
|
nextCandidate.setId(id);
|
||||||
theGlobalResourcesToInclude.addResourceIfNotAlreadyPresent(
|
theGlobalResourcesToInclude.addResourceIfNotAlreadyPresent(
|
||||||
nextCandidate, originalResourceId);
|
nextCandidate, originalResourceId);
|
||||||
sectionResourcesToInclude.addResourceIfNotAlreadyPresent(nextCandidate, originalResourceId);
|
if (!candidateIsSearchInclude) {
|
||||||
|
sectionResourcesToInclude.addResourceIfNotAlreadyPresent(
|
||||||
|
nextCandidate, originalResourceId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -281,7 +287,7 @@ 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 : sectionResourcesToInclude.getResources()) {
|
for (IBaseResource nextResource : theGlobalResourcesToInclude.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
|
||||||
|
@ -307,6 +313,10 @@ public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sectionResourcesToInclude.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
addSection(theSection, theCompositionBuilder, sectionResourcesToInclude, theGlobalResourcesToInclude);
|
addSection(theSection, theCompositionBuilder, sectionResourcesToInclude, theGlobalResourcesToInclude);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -336,7 +346,7 @@ public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc {
|
||||||
+ "-"
|
+ "-"
|
||||||
+ next.getIdElement().getValue();
|
+ next.getIdElement().getValue();
|
||||||
IPrimitiveType<String> narrativeLinkUri = (IPrimitiveType<String>)
|
IPrimitiveType<String> narrativeLinkUri = (IPrimitiveType<String>)
|
||||||
myFhirContext.getElementDefinition("uri").newInstance();
|
myFhirContext.getElementDefinition("url").newInstance();
|
||||||
narrativeLinkUri.setValueAsString(narrativeLinkValue);
|
narrativeLinkUri.setValueAsString(narrativeLinkValue);
|
||||||
narrativeLink.setValue(narrativeLinkUri);
|
narrativeLink.setValue(narrativeLinkUri);
|
||||||
|
|
||||||
|
@ -417,152 +427,6 @@ public class IpsGeneratorSvcImpl implements IIpsGeneratorSvc {
|
||||||
return generator;
|
return generator;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private static HashMap<PatientSummary.IPSSection, List<Resource>> hashPrimaries(List<Resource> resourceList) {
|
|
||||||
HashMap<PatientSummary.IPSSection, List<Resource>> iPSResourceMap = new HashMap<PatientSummary.IPSSection, List<Resource>>();
|
|
||||||
|
|
||||||
for (Resource resource : resourceList) {
|
|
||||||
for (PatientSummary.IPSSection iPSSection : PatientSummary.IPSSection.values()) {
|
|
||||||
if ( SectionTypes.get(iPSSection).contains(resource.getResourceType()) ) {
|
|
||||||
if ( !(resource.getResourceType() == ResourceType.Observation) || isObservationinSection(iPSSection, (Observation) resource)) {
|
|
||||||
if (iPSResourceMap.get(iPSSection) == null) {
|
|
||||||
iPSResourceMap.put(iPSSection, new ArrayList<Resource>());
|
|
||||||
}
|
|
||||||
iPSResourceMap.get(iPSSection).add(resource);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return iPSResourceMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private static HashMap<PatientSummary.IPSSection, List<Resource>> filterPrimaries(HashMap<PatientSummary.IPSSection, List<Resource>> sectionPrimaries) {
|
|
||||||
HashMap<PatientSummary.IPSSection, List<Resource>> filteredPrimaries = new HashMap<PatientSummary.IPSSection, List<Resource>>();
|
|
||||||
for ( PatientSummary.IPSSection section : sectionPrimaries.keySet() ) {
|
|
||||||
List<Resource> filteredList = new ArrayList<Resource>();
|
|
||||||
for (Resource resource : sectionPrimaries.get(section)) {
|
|
||||||
if (passesFilter(section, resource)) {
|
|
||||||
filteredList.add(resource);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (filteredList.size() > 0) {
|
|
||||||
filteredPrimaries.put(section, filteredList);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return filteredPrimaries;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<Resource> pruneResources(Patient patient, List<Resource> resources, HashMap<PatientSummary.IPSSection, List<Resource>> sectionPrimaries, FhirContext ctx) {
|
|
||||||
List<String> resourceIds = new ArrayList<String>();
|
|
||||||
List<String> followedIds = new ArrayList<String>();
|
|
||||||
|
|
||||||
HashMap<String, Resource> resourcesById = new HashMap<String, Resource>();
|
|
||||||
for (Resource resource : resources) {
|
|
||||||
resourcesById.put(resource.getIdElement().getIdPart(), resource);
|
|
||||||
}
|
|
||||||
String patientId = patient.getIdElement().getIdPart();
|
|
||||||
resourcesById.put(patientId, patient);
|
|
||||||
|
|
||||||
recursivePrune(patientId, resourceIds, followedIds, resourcesById, ctx);
|
|
||||||
|
|
||||||
for (PatientSummary.IPSSection section : sectionPrimaries.keySet()) {
|
|
||||||
for (Resource resource : sectionPrimaries.get(section)) {
|
|
||||||
String resourceId = resource.getIdElement().getIdPart();
|
|
||||||
recursivePrune(resourceId, resourceIds, followedIds, resourcesById, ctx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Resource> prunedResources = new ArrayList<Resource>();
|
|
||||||
|
|
||||||
for (Resource resource : resources) {
|
|
||||||
if (resourceIds.contains(resource.getIdElement().getIdPart())) {
|
|
||||||
prunedResources.add(resource);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return prunedResources;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Void recursivePrune(String resourceId, List<String> resourceIds, List<String> followedIds, HashMap<String, Resource> resourcesById, FhirContext ctx) {
|
|
||||||
if (!resourceIds.contains(resourceId)) {
|
|
||||||
resourceIds.add(resourceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
Resource resource = resourcesById.get(resourceId);
|
|
||||||
if (resource != null) {
|
|
||||||
ctx.newTerser().getAllResourceReferences(resource).stream()
|
|
||||||
.map( r -> r.getResourceReference().getReferenceElement().getIdPart() )
|
|
||||||
.forEach( id -> {
|
|
||||||
if (!followedIds.contains(id)) {
|
|
||||||
followedIds.add(id);
|
|
||||||
recursivePrune(id, resourceIds, followedIds, resourcesById, ctx);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<Resource> addLinkToResources(List<Resource> resources, HashMap<PatientSummary.IPSSection, List<Resource>> sectionPrimaries, Composition composition) {
|
|
||||||
List<Resource> linkedResources = new ArrayList<Resource>();
|
|
||||||
HashMap<String, String> valueUrls = new HashMap<String, String>();
|
|
||||||
|
|
||||||
String url = "http://hl7.org/fhir/StructureDefinition/narrativeLink";
|
|
||||||
String valueUrlBase = composition.getId() + "#";
|
|
||||||
|
|
||||||
for (PatientSummary.IPSSection section : sectionPrimaries.keySet()) {
|
|
||||||
String profile = SectionProfiles.get(section);
|
|
||||||
String[] arr = profile.split("/");
|
|
||||||
String profileName = arr[arr.length - 1];
|
|
||||||
String sectionValueUrlBase = valueUrlBase + profileName.split("-uv-")[0];
|
|
||||||
|
|
||||||
for (Resource resource : sectionPrimaries.get(section)) {
|
|
||||||
String valueUrl = sectionValueUrlBase + "-" + resource.getIdElement().getIdPart();
|
|
||||||
valueUrls.put(resource.getIdElement().getIdPart(), valueUrl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Resource resource : resources) {
|
|
||||||
if (valueUrls.containsKey(resource.getIdElement().getIdPart())) {
|
|
||||||
String valueUrl = valueUrls.get(resource.getIdElement().getIdPart());
|
|
||||||
Extension extension = new Extension();
|
|
||||||
extension.setUrl(url);
|
|
||||||
extension.setValue(new UriType(valueUrl));
|
|
||||||
DomainResource domainResource = (DomainResource) resource;
|
|
||||||
domainResource.addExtension(extension);
|
|
||||||
resource = (Resource) domainResource;
|
|
||||||
}
|
|
||||||
linkedResources.add(resource);
|
|
||||||
}
|
|
||||||
|
|
||||||
return linkedResources;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static HashMap<PatientSummary.IPSSection, String> createNarratives(HashMap<PatientSummary.IPSSection, List<Resource>> sectionPrimaries, List<Resource> resources, FhirContext ctx) {
|
|
||||||
HashMap<PatientSummary.IPSSection, String> hashedNarratives = new HashMap<PatientSummary.IPSSection, String>();
|
|
||||||
|
|
||||||
for (PatientSummary.IPSSection section : sectionPrimaries.keySet()) {
|
|
||||||
String narrative = createSectionNarrative(section, resources, ctx);
|
|
||||||
hashedNarratives.put(section, narrative);
|
|
||||||
}
|
|
||||||
|
|
||||||
return hashedNarratives;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
private static class ResourceInclusionCollection {
|
private static class ResourceInclusionCollection {
|
||||||
|
|
||||||
private final List<IBaseResource> myResources = new ArrayList<>();
|
private final List<IBaseResource> myResources = new ArrayList<>();
|
||||||
|
|
|
@ -44,6 +44,9 @@ public class IpsOperationProvider {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Patient/123/$summary
|
* Patient/123/$summary
|
||||||
|
* <p>
|
||||||
|
* Note that not all parameters from the official specification are yet supported. See
|
||||||
|
* <a href="http://build.fhir.org/ig/HL7/fhir-ips/OperationDefinition-summary.html>http://build.fhir.org/ig/HL7/fhir-ips/OperationDefinition-summary.html</a>
|
||||||
*/
|
*/
|
||||||
@Operation(
|
@Operation(
|
||||||
name = JpaConstants.OPERATION_SUMMARY,
|
name = JpaConstants.OPERATION_SUMMARY,
|
||||||
|
@ -58,6 +61,9 @@ public class IpsOperationProvider {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* /Patient/$summary?identifier=foo|bar
|
* /Patient/$summary?identifier=foo|bar
|
||||||
|
* <p>
|
||||||
|
* Note that not all parameters from the official specification are yet supported. See
|
||||||
|
* <a href="http://build.fhir.org/ig/HL7/fhir-ips/OperationDefinition-summary.html>http://build.fhir.org/ig/HL7/fhir-ips/OperationDefinition-summary.html</a>
|
||||||
*/
|
*/
|
||||||
@Operation(
|
@Operation(
|
||||||
name = JpaConstants.OPERATION_SUMMARY,
|
name = JpaConstants.OPERATION_SUMMARY,
|
||||||
|
|
|
@ -293,9 +293,13 @@ public class DefaultIpsGenerationStrategy implements IIpsGenerationStrategy {
|
||||||
return Sets.newHashSet(DeviceUseStatement.INCLUDE_DEVICE);
|
return Sets.newHashSet(DeviceUseStatement.INCLUDE_DEVICE);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case IMMUNIZATIONS:
|
||||||
|
if (ResourceType.Immunization.name().equals(theIpsSectionContext.getResourceType())) {
|
||||||
|
return Sets.newHashSet(Immunization.INCLUDE_MANUFACTURER);
|
||||||
|
}
|
||||||
|
break;
|
||||||
case ALLERGY_INTOLERANCE:
|
case ALLERGY_INTOLERANCE:
|
||||||
case PROBLEM_LIST:
|
case PROBLEM_LIST:
|
||||||
case IMMUNIZATIONS:
|
|
||||||
case PROCEDURES:
|
case PROCEDURES:
|
||||||
case DIAGNOSTIC_RESULTS:
|
case DIAGNOSTIC_RESULTS:
|
||||||
case VITAL_SIGNS:
|
case VITAL_SIGNS:
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<!--/* AdvanceDirectives -->
|
<!--/* AdvanceDirectives -->
|
||||||
<!--
|
<!--
|
||||||
Scope: Consent.scope.text || Consent.scope.coding[x].display
|
Scope: Consent.scope.text || Consent.scope.coding[x].display (separated by <br />)
|
||||||
Status: Consent.status.code
|
Status: Consent.status.code
|
||||||
Action Controlled: Consent.provision.action[x].coding[x].display (concatenate items separated by comma, e.g. x, y, z)
|
Action Controlled: Consent.provision.action[x].{ text || coding[x].display (separated by <br />)} (concatenate with comma, e.g. x, y, z)
|
||||||
Date: Consent.dateTime
|
Date: Consent.dateTime
|
||||||
*/-->
|
*/-->
|
||||||
<div xmlns:th="http://www.thymeleaf.org">
|
<div xmlns:th="http://www.thymeleaf.org">
|
||||||
|
@ -23,7 +23,6 @@ Date: Consent.dateTime
|
||||||
<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().getCode()}">Status</td>
|
<td th:text="*{getStatus().getCode()}">Status</td>
|
||||||
<td th:text="*{getdateTimeType().getValue()}">Action Controlled</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>
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
<!--/* AllergiesAndIntolerances -->
|
<!--/* AllergiesAndIntolerances -->
|
||||||
<!--
|
<!--
|
||||||
Allergen: AllergyIntolerance.code.text || AllergyIntolerance.code.coding[x].display
|
Allergen: AllergyIntolerance.code.text || AllergyIntolerance.code.coding[x].display (separated by <br />)
|
||||||
Status: AllergyIntolerance.clinicalStatus.coding[x].display
|
Status: AllergyIntolerance.clinicalStatus.text || AllergyIntolerance.clinicalStatus.coding[x].code (separated by <br />)
|
||||||
Category: AllergyIntolerance.code[x]
|
Category: AllergyIntolerance.category[x] (separated by <br />)
|
||||||
Reaction: AllergyIntolerance.reaction.manifestation.text || AllergyIntolerance.reaction.manifestation.coding[x].display *** What about getReaction().getDescription() ***
|
Reaction: AllergyIntolerance.reaction.manifestation.description || AllergyIntolerance.reaction.manifestation.text || AllergyIntolerance.reaction.manifestation.coding[x].display (separated by <br />)
|
||||||
Severity: AllergyIntolerance.reaction.severity.code
|
Severity: AllergyIntolerance.reaction.severity[x].code (separated by <br />)
|
||||||
Comments: AllergyIntolerance.note[x].text (display all notes separated by <br /> )
|
Comments: AllergyIntolerance.note[x].text (separated by <br />)
|
||||||
Onset: AllergyIntolerance.onsetDateTime
|
|
||||||
*/-->
|
*/-->
|
||||||
<div xmlns:th="http://www.thymeleaf.org">
|
<div xmlns:th="http://www.thymeleaf.org">
|
||||||
<table class="hapiPropertyTable">
|
<table class="hapiPropertyTable">
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
<!--/* DiagnosticResults -->
|
<!--/* DiagnosticResults -->
|
||||||
<!--
|
<!--
|
||||||
TABLE 1: Observation
|
TABLE 1: Observations
|
||||||
Code: Observation.code.text || Observation.code.coding[x].display
|
Code: Observation.code.text || Observation.code.coding[x].display (separated by <br />)
|
||||||
Result: Observation.valueQuantity.value || Observation.valueCodeableConcept.coding[x].display || Observation.valueString
|
Result: Observation.valueQuantity || Observation.valueDateTime || Observation.valueCodeableConcept.text || Observation.valueCodeableConcept.coding[x].display (separated by <br />) || Observation.valueString
|
||||||
Unit: Observation.valueQuantity.unit
|
Unit: Observation.valueQuantity.unit
|
||||||
Interpretation: Observation.interpretation.text || Observation. interpretation.coding[x].display
|
Interpretation: Observation.interpretation[0].text || Observation.interpretation[0].coding[x].display (separated by <br />)
|
||||||
Reference Range: Observation.referenceRange.low.value && “-“ && Observation.referenceRange.high.value
|
Reference Range: Observation.referenceRange[x]{ text || low.value && “-“ && high.value} (concatenate with comma, e.g. x, y, z)
|
||||||
Comments: Observation.note[x].text (display all notes separated by <br /> )
|
Comments: Observation.note[x].text (separated by <br />)
|
||||||
Date: Observation.effectiveDateTime
|
Date: Observation.effectiveDateTime || Observation.effectivePeriod.start
|
||||||
|
|
||||||
TABLE 2: DiagnosticReport
|
TABLE 2: Diagnostic Reports
|
||||||
Code: DiagnosticReport.code.text || DiagnosticReport.code.coding[x].display
|
Code: DiagnosticReport.code.text || DiagnosticReport.code.coding[x].display (separated by <br />)
|
||||||
Date: DiagnosticReport.effectiveDateTime
|
Date: DiagnosticReport.effectiveDateTime || DiagnosticReport.effectivePeriod.start
|
||||||
*/-->
|
*/-->
|
||||||
<div xmlns:th="http://www.thymeleaf.org">
|
<div xmlns:th="http://www.thymeleaf.org">
|
||||||
<table class="hapiPropertyTable">
|
<table class="hapiPropertyTable">
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<!--/* FunctionalStatus -->
|
<!--/* FunctionalStatus -->
|
||||||
<!--
|
<!--
|
||||||
Assessment: ClinicalImpression.code.text || ClinicalImpression.code[x].display
|
Assessment: ClinicalImpression.code.text || ClinicalImpression.code[x].display (separated by <br />)
|
||||||
Status: ClinicalImpression.status.code
|
Status: ClinicalImpression.status.code
|
||||||
Finding: ClinicalImpression.summary
|
Finding: ClinicalImpression.summary
|
||||||
Comments: ClinicalImpression.note[x].text (display all notes separated by <br /> )
|
Comments: ClinicalImpression.note[x].text (separated by <br />)
|
||||||
Date: ClinicalImpression.effectiveDateTime
|
Date: ClinicalImpression.effectiveDateTime || ClinicalImpression.effectivePeriod.start
|
||||||
*/-->
|
*/-->
|
||||||
<div xmlns:th="http://www.thymeleaf.org">
|
<div xmlns:th="http://www.thymeleaf.org">
|
||||||
<table class="hapiPropertyTable">
|
<table class="hapiPropertyTable">
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<!--/* HistoryOfProcedures -->
|
<!--/* HistoryOfProcedures -->
|
||||||
<!--
|
<!--
|
||||||
Procedure: Procedure.code.text || Procedure.code.coding[x].display
|
Procedure: Procedure.code.text || Procedure.code.coding[x].display (separated by <br />)
|
||||||
Comments: Procedure.note[x].text(display all notes separated by <br /> )
|
Comments: Procedure.note[x].text(separated by <br />)
|
||||||
Date: Procedure.performedDateTime
|
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">
|
||||||
<table class="hapiPropertyTable">
|
<table class="hapiPropertyTable">
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
<!--/* Immunizations -->
|
<!--/* Immunizations -->
|
||||||
<!--
|
<!--
|
||||||
Immunization: Immunization.vaccineCode.text || Immunization.vaccineCode.coding[x].display
|
Immunization: Immunization.vaccineCode.text || Immunization.vaccineCode.coding[x].display (separated by <br />)
|
||||||
Status: Immunization.status.code
|
Status: Immunization.status
|
||||||
Dose Number: Immunization.doseNumberPositiveInt || Immunization.doseNumberString
|
Dose Number: Immunization.protocolApplied[x]{doseNumberPositiveInt || doseNumberString} (concatenate with comma, e.g. x, y, z)
|
||||||
Manufacturer: Organization.name
|
Manufacturer: Organization.name
|
||||||
Lot Number: Immunization.lotNumber
|
Lot Number: Immunization.lotNumber
|
||||||
Comments: Immunization.note[x].text (display all notes separated by <br /> )
|
Comments: Immunization.note[x].text (separated by <br />)
|
||||||
Date: Immunization.occurrenceDateTime
|
Date: Immunization.occurrenceDateTime || Immunization.occurrenceString
|
||||||
*/-->
|
*/-->
|
||||||
<div xmlns:th="http://www.thymeleaf.org">
|
<div xmlns:th="http://www.thymeleaf.org">
|
||||||
<table class="hapiPropertyTable">
|
<table class="hapiPropertyTable">
|
||||||
|
|
|
@ -3,43 +3,43 @@
|
||||||
################################################
|
################################################
|
||||||
|
|
||||||
ips-allergyintolerance.resourceType=Bundle
|
ips-allergyintolerance.resourceType=Bundle
|
||||||
ips-allergyintolerance.profile=http://hl7.org/fhir/uv/ips/StructureDefinition/AllergiesAndIntolerances-uv-ips
|
ips-allergyintolerance.profile=https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionAllergies
|
||||||
ips-allergyintolerance.narrative=classpath:ca/uhn/fhir/jpa/ips/narrative/allergyintolerance.html
|
ips-allergyintolerance.narrative=classpath:ca/uhn/fhir/jpa/ips/narrative/allergyintolerance.html
|
||||||
|
|
||||||
ips-medicationsummary.resourceType=Bundle
|
ips-medicationsummary.resourceType=Bundle
|
||||||
ips-medicationsummary.profile=http://hl7.org/fhir/uv/ips/StructureDefinition/MedicationSummary-uv-ips
|
ips-medicationsummary.profile=https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionMedications
|
||||||
ips-medicationsummary.narrative=classpath:ca/uhn/fhir/jpa/ips/narrative/medicationsummary.html
|
ips-medicationsummary.narrative=classpath:ca/uhn/fhir/jpa/ips/narrative/medicationsummary.html
|
||||||
|
|
||||||
ips-problemlist.resourceType=Bundle
|
ips-problemlist.resourceType=Bundle
|
||||||
ips-problemlist.profile=http://hl7.org/fhir/uv/ips/StructureDefinition/ProblemList-uv-ips
|
ips-problemlist.profile=https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionProblems
|
||||||
ips-problemlist.narrative=classpath:ca/uhn/fhir/jpa/ips/narrative/problemlist.html
|
ips-problemlist.narrative=classpath:ca/uhn/fhir/jpa/ips/narrative/problemlist.html
|
||||||
|
|
||||||
ips-immunizations.resourceType=Bundle
|
ips-immunizations.resourceType=Bundle
|
||||||
ips-immunizations.profile=http://hl7.org/fhir/uv/ips/StructureDefinition/Immunizations-uv-ips
|
ips-immunizations.profile=https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionImmunizations
|
||||||
ips-immunizations.narrative=classpath:ca/uhn/fhir/jpa/ips/narrative/immunizations.html
|
ips-immunizations.narrative=classpath:ca/uhn/fhir/jpa/ips/narrative/immunizations.html
|
||||||
|
|
||||||
ips-historyofprocedures.resourceType=Bundle
|
ips-historyofprocedures.resourceType=Bundle
|
||||||
ips-historyofprocedures.profile=http://hl7.org/fhir/uv/ips/StructureDefinition/HistoryOfProcedures-uv-ips
|
ips-historyofprocedures.profile=https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionProceduresHx
|
||||||
ips-historyofprocedures.narrative=classpath:ca/uhn/fhir/jpa/ips/narrative/historyofprocedures.html
|
ips-historyofprocedures.narrative=classpath:ca/uhn/fhir/jpa/ips/narrative/historyofprocedures.html
|
||||||
|
|
||||||
ips-medicaldevices.resourceType=Bundle
|
ips-medicaldevices.resourceType=Bundle
|
||||||
ips-medicaldevices.profile=http://hl7.org/fhir/uv/ips/StructureDefinition/MedicalDevices-uv-ips
|
ips-medicaldevices.profile=https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionMedicalDevices
|
||||||
ips-medicaldevices.narrative=classpath:ca/uhn/fhir/jpa/ips/narrative/medicaldevices.html
|
ips-medicaldevices.narrative=classpath:ca/uhn/fhir/jpa/ips/narrative/medicaldevices.html
|
||||||
|
|
||||||
ips-diagnosticresults.resourceType=Bundle
|
ips-diagnosticresults.resourceType=Bundle
|
||||||
ips-diagnosticresults.profile=http://hl7.org/fhir/uv/ips/StructureDefinition/DiagnosticResults-uv-ips
|
ips-diagnosticresults.profile=https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionResults
|
||||||
ips-diagnosticresults.narrative=classpath:ca/uhn/fhir/jpa/ips/narrative/diagnosticresults.html
|
ips-diagnosticresults.narrative=classpath:ca/uhn/fhir/jpa/ips/narrative/diagnosticresults.html
|
||||||
|
|
||||||
ips-vitalsigns.resourceType=Bundle
|
ips-vitalsigns.resourceType=Bundle
|
||||||
ips-vitalsigns.profile=http://hl7.org/fhir/uv/ips/StructureDefinition/VitalSigns-uv-ips
|
ips-vitalsigns.profile=https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionVitalSigns
|
||||||
ips-vitalsigns.narrative=classpath:ca/uhn/fhir/jpa/ips/narrative/vitalsigns.html
|
ips-vitalsigns.narrative=classpath:ca/uhn/fhir/jpa/ips/narrative/vitalsigns.html
|
||||||
|
|
||||||
ips-pregnancy.resourceType=Bundle
|
ips-pregnancy.resourceType=Bundle
|
||||||
ips-pregnancy.profile=http://hl7.org/fhir/uv/ips/StructureDefinition/Pregnancy-uv-ips
|
ips-pregnancy.profile=https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionPregnancyHx
|
||||||
ips-pregnancy.narrative=classpath:ca/uhn/fhir/jpa/ips/narrative/pregnancy.html
|
ips-pregnancy.narrative=classpath:ca/uhn/fhir/jpa/ips/narrative/pregnancy.html
|
||||||
|
|
||||||
ips-socialhistory.resourceType=Bundle
|
ips-socialhistory.resourceType=Bundle
|
||||||
ips-socialhistory.profile=http://hl7.org/fhir/uv/ips/StructureDefinition/SocialHistory-uv-ips
|
ips-socialhistory.profile=https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionSocialHistory
|
||||||
ips-socialhistory.narrative=classpath:ca/uhn/fhir/jpa/ips/narrative/socialhistory.html
|
ips-socialhistory.narrative=classpath:ca/uhn/fhir/jpa/ips/narrative/socialhistory.html
|
||||||
|
|
||||||
ips-pasthistoryofillness.resourceType=Bundle
|
ips-pasthistoryofillness.resourceType=Bundle
|
||||||
|
@ -47,15 +47,15 @@ ips-pasthistoryofillness.profile=http://hl7.org/fhir/uv/ips/StructureDefinition/
|
||||||
ips-pasthistoryofillness.narrative=classpath:ca/uhn/fhir/jpa/ips/narrative/pasthistoryofillness.html
|
ips-pasthistoryofillness.narrative=classpath:ca/uhn/fhir/jpa/ips/narrative/pasthistoryofillness.html
|
||||||
|
|
||||||
ips-functionalstatus.resourceType=Bundle
|
ips-functionalstatus.resourceType=Bundle
|
||||||
ips-functionalstatus.profile=http://hl7.org/fhir/uv/ips/StructureDefinition/FunctionalStatus-uv-ips
|
ips-functionalstatus.profile=https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionFunctionalStatus
|
||||||
ips-functionalstatus.narrative=classpath:ca/uhn/fhir/jpa/ips/narrative/functionalstatus.html
|
ips-functionalstatus.narrative=classpath:ca/uhn/fhir/jpa/ips/narrative/functionalstatus.html
|
||||||
|
|
||||||
ips-planofcare.resourceType=Bundle
|
ips-planofcare.resourceType=Bundle
|
||||||
ips-planofcare.profile=http://hl7.org/fhir/uv/ips/StructureDefinition/PlanOfCare-uv-ips
|
ips-planofcare.profile=https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionPlanOfCare
|
||||||
ips-planofcare.narrative=classpath:ca/uhn/fhir/jpa/ips/narrative/planofcare.html
|
ips-planofcare.narrative=classpath:ca/uhn/fhir/jpa/ips/narrative/planofcare.html
|
||||||
|
|
||||||
ips-advancedirectives.resourceType=Bundle
|
ips-advancedirectives.resourceType=Bundle
|
||||||
ips-advancedirectives.profile=http://hl7.org/fhir/uv/ips/StructureDefinition/AdvanceDirectives-uv-ips
|
ips-advancedirectives.profile=https://hl7.org/fhir/uv/ips/StructureDefinition-Composition-uv-ips-definitions.html#Composition.section:sectionAdvanceDirectives
|
||||||
ips-advancedirectives.narrative=classpath:ca/uhn/fhir/jpa/ips/narrative/advancedirectives.html
|
ips-advancedirectives.narrative=classpath:ca/uhn/fhir/jpa/ips/narrative/advancedirectives.html
|
||||||
|
|
||||||
################################################
|
################################################
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<!--/* MedicalDevices -->
|
<!--/* MedicalDevices -->
|
||||||
<!--
|
<!--
|
||||||
Device: Device.type.coding.text || Device.type.coding[x].display
|
Device: Device.type.text || Device.type.coding[x].display (separated by <br />)
|
||||||
Status: DeviceUseStatement.status.code
|
Status: DeviceUseStatement.status
|
||||||
Comments: DeviceUseStatement.note[x].text (display all notes separated by <br /> )
|
Comments: DeviceUseStatement.note[x].text (separated by <br />)
|
||||||
Date Recorded: DeviceUseStatement.recordedOn
|
Date Recorded: DeviceUseStatement.recordedDateTime
|
||||||
*/-->
|
*/-->
|
||||||
<div xmlns:th="http://www.thymeleaf.org">
|
<div xmlns:th="http://www.thymeleaf.org">
|
||||||
<table class="hapiPropertyTable">
|
<table class="hapiPropertyTable">
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
<!--/* MedicationSummary -->
|
<!--/* MedicationSummary -->
|
||||||
<!--
|
<!--
|
||||||
Table 1 MedicationRequest
|
Table 1 Medication Requests
|
||||||
Medication: MedicationRequest.medicationCodeableConcept.coding[x].display || Medication.code.coding.text || Medication.code.coding.code[x].display
|
Medication: MedicationRequest.medicationCodeableConcept.text || MedicationRequest.medicationCodeableConcept.coding[x].display (separated by <br />) || Medication.code.text || Medication.code.coding[x].display (separated by <br />)
|
||||||
Status: MedicationRequest.status.display
|
Status: MedicationRequest.status.display
|
||||||
Route: MedicationRequest.dosageInstruction[x].route.coding[x].display
|
Route: MedicationRequest.dosageInstruction[x].{ route.text || route.coding[x].display (separated by <br />) } (concatenate with comma, e.g. x, y, z)
|
||||||
Sig: MedicationRequest.dosageInstruction[x].text (display all sigs separated by <br /> )
|
Sig: MedicationRequest.dosageInstruction[x].text (display all sigs separated by <br />)
|
||||||
Comments: MedicationRequest.note[x].text (display all notes separated by <br /> )
|
Comments: MedicationRequest.note[x].text (separated by <br />)
|
||||||
Authored Date: MedicationRequest.DateTime
|
Authored Date: MedicationRequest.authoredOn
|
||||||
|
|
||||||
Table 2 MedicationStatement
|
Table 2 Medication Statements
|
||||||
Medication: MedicationStatement.medicationCodeableConcept.coding[x].display || Medication.code.coding.text || Medication.code.coding.code[x].display
|
Medication: MedicationStatement.medicationCodeableConcept.text || MedicationStatement.medicationCodeableConcept.coding[x].display (separated by <br />) || Medication.code.text || Medication.code.coding[x].display (separated by <br />)
|
||||||
Status: MedicationStatement.status.display
|
Status: MedicationStatement.status.display
|
||||||
Route: MedicationStatement.dosage[x].route.coding[x].display
|
Route: MedicationStatement.dosage[x].{ route.text || route.coding[x].display (separated by <br />) } (concatenate with comma, e.g. x, y, z)
|
||||||
Sig: MedicationStatement.dosage[x].text (display all sigs separated by <br /> )
|
Sig: MedicationStatement.dosage[x].text (display all sigs separated by <br />)
|
||||||
Date: MedicationStatement.effectiveDateTime
|
Date: MedicationStatement.effectiveDateTime || MedicationStatement.effectivePeriod.start
|
||||||
*/-->
|
*/-->
|
||||||
<div xmlns:th="http://www.thymeleaf.org">
|
<div xmlns:th="http://www.thymeleaf.org">
|
||||||
<table class="hapiPropertyTable">
|
<table class="hapiPropertyTable">
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<!--/* PastHistoryOfIllnesses -->
|
<!--/* PastHistoryOfIllnesses -->
|
||||||
<!--
|
<!--
|
||||||
Medical Problem: Condition.code.text || Condition.code.coding[x].display
|
Medical Problems: Condition.code.text || Condition.code.coding[x].display (separated by <br />)
|
||||||
Status: Condition.clinicalStatus.coding[x].display
|
Status: Condition.clinicalStatus.text || Condition.clinicalStatus.coding[x].display (separated by <br />)
|
||||||
Comments: Condition.note[x].text (display all notes separated by <br /> )
|
Comments: Condition.note[x].text (separated by <br />)
|
||||||
Onset Date: Condition.onsetDateTime
|
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">
|
||||||
<table class="hapiPropertyTable">
|
<table class="hapiPropertyTable">
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<!--
|
<!--
|
||||||
Activity: CarePlan.description
|
Activity: CarePlan.description
|
||||||
Intent: CarePlan.intent.code
|
Intent: CarePlan.intent.code
|
||||||
Comments: CarePlan.dosage [x].text // Not dosaage but note... right?
|
Comments: CarePlan.note[x].text (separated by <br />)
|
||||||
Planned Start: CarePlan.period.start
|
Planned Start: CarePlan.period.start
|
||||||
Planned End: CarePlan.period.end
|
Planned End: CarePlan.period.end
|
||||||
*/-->
|
*/-->
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<!--/* Pregnancy -->
|
<!--/* Pregnancy -->
|
||||||
<!--
|
<!--
|
||||||
Code: Observation.code.text || Observation.code.coding[x].display
|
Code: Observation.code.text || Observation.code.coding[x].display (separated by <br />)
|
||||||
Result: Observation.valueQuantity.value || Observation.valueDateTime || Observation.valueCodeableConcept.coding[x].display || Observation.valueString
|
Result: Observation.valueQuantity || Observation.valueDateTime || Observation.valueCodeableConcept.text || Observation.valueCodeableConcept.coding[x].display (separated by <br />) || Observation.valueString
|
||||||
Comments: Observation.note[x].text (display all notes separated by <br /> )
|
Comments: Observation.note[x].text (separated by <br />)
|
||||||
Date: Observation.effectiveDateTime
|
Date: Observation.effectiveDateTime || Observation.effectivePeriod.start
|
||||||
*/-->
|
*/-->
|
||||||
<div xmlns:th="http://www.thymeleaf.org">
|
<div xmlns:th="http://www.thymeleaf.org">
|
||||||
<table class="hapiPropertyTable">
|
<table class="hapiPropertyTable">
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<!--/* ProblemList -->
|
<!--/* ProblemList -->
|
||||||
<!--
|
<!--
|
||||||
Medical Problem: Condition.code.text || Condition.code.coding[x].display
|
Medical Problems: Condition.code.text || Condition.code.coding[x].display (separated by <br />)
|
||||||
Status: Condition.clinicalStatus.coding[x].display
|
Status: Condition.clinicalStatus.text || Condition.clinicalStatus.coding[x].display (separated by <br />)
|
||||||
Comments: Condition.note[x].text (display all notes separated by <br /> )
|
Comments: Condition.note[x].text (separated by <br />)
|
||||||
Onset Date: Condition.onsetDateTime
|
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">
|
||||||
<table class="hapiPropertyTable">
|
<table class="hapiPropertyTable">
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<!--/* SocialHistory -->
|
<!--/* SocialHistory -->
|
||||||
<!--
|
<!--
|
||||||
Code: Observation.code.text || Observation.code.coding[x].display
|
Code: Observation.code.text || Observation.code.coding[x].display (separated by <br />)
|
||||||
Result: Observation.valueQuantity.value || Observation.valueCodeableConcept.coding[x].display || Observation.valueString
|
Result: Observation.valueQuantity || Observation.valueDateTime || Observation.valueCodeableConcept.text || Observation.valueCodeableConcept.coding[x].display (separated by <br />) || Observation.valueString
|
||||||
Unit: Observation.valueQuantity.unit
|
Unit: Observation.valueQuantity.unit
|
||||||
Comments: Observation.note[x].text (display all notes separated by <br /> )
|
Comments: Observation.note[x].text (separated by <br />)
|
||||||
Date: Observation.effectiveDateTime
|
Date: Observation.effectiveDateTime || Observation.effectivePeriod.start
|
||||||
*/-->
|
*/-->
|
||||||
<div xmlns:th="http://www.thymeleaf.org">
|
<div xmlns:th="http://www.thymeleaf.org">
|
||||||
<table class="hapiPropertyTable">
|
<table class="hapiPropertyTable">
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
<!--/* VitalSigns -->
|
<!--/* VitalSigns -->
|
||||||
<!--
|
<!--
|
||||||
Code: Observation.code.text || Observation.code.coding[x].display
|
Code: Observation.code.text || Observation.code.coding[x].display (separated by <br />)
|
||||||
Result: Observation.valueQuantity.value || Observation.valueCodeableConcept.coding[x].display || Observation.valueString
|
Result: Observation.valueQuantity || Observation.valueDateTime || Observation.valueCodeableConcept.text || Observation.valueCodeableConcept.coding[x].display (separated by <br />) || Observation.valueString
|
||||||
Unit: Observation.valueQuantity.unit
|
Unit: Observation.valueQuantity.unit
|
||||||
Interpretation: Observation.interpretation.text || Observation. interpretation.coding[x].display
|
Interpretation: Observation.interpretation[0].text || Observation.interpretation[0].coding[x].display (separated by <br />)
|
||||||
Comments: Observation.note[x].text (display all notes separated by <br /> )
|
Comments: Observation.note[x].text (separated by <br />)
|
||||||
Date: Observation.effectiveDateTime
|
Date: Observation.effectiveDateTime || Observation.effectivePeriod.start
|
||||||
*/-->
|
*/-->
|
||||||
<div xmlns:th="http://www.thymeleaf.org">
|
<div xmlns:th="http://www.thymeleaf.org">
|
||||||
<table class="hapiPropertyTable">
|
<table class="hapiPropertyTable">
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
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.support.ConceptValidationOptions;
|
||||||
|
import ca.uhn.fhir.context.support.IValidationSupport;
|
||||||
|
import ca.uhn.fhir.context.support.ValidationSupportContext;
|
||||||
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
|
import ca.uhn.fhir.jpa.api.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;
|
||||||
|
@ -9,11 +12,15 @@ 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.ValidationResult;
|
import ca.uhn.fhir.validation.ValidationResult;
|
||||||
|
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;
|
||||||
import org.hl7.fhir.r4.model.Bundle;
|
import org.hl7.fhir.r4.model.Bundle;
|
||||||
|
import org.hl7.fhir.r4.model.CodeSystem;
|
||||||
|
import org.hl7.fhir.r4.model.Composition;
|
||||||
import org.hl7.fhir.r4.model.Condition;
|
import org.hl7.fhir.r4.model.Condition;
|
||||||
import org.hl7.fhir.r4.model.MedicationStatement;
|
import org.hl7.fhir.r4.model.MedicationStatement;
|
||||||
import org.hl7.fhir.r4.model.Parameters;
|
import org.hl7.fhir.r4.model.Parameters;
|
||||||
|
@ -27,16 +34,25 @@ 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 javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.hamcrest.Matchers.contains;
|
||||||
import static org.hamcrest.Matchers.matchesPattern;
|
import static org.hamcrest.Matchers.matchesPattern;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
@ContextConfiguration(classes = {IpsGenerationTest.IpsConfig.class})
|
/**
|
||||||
public class IpsGenerationTest extends BaseResourceProviderR4Test {
|
* This test uses a complete R4 JPA server as a backend and wires the
|
||||||
|
* {@link IpsOperationProvider} into the REST server to test the end-to-end
|
||||||
|
* IPS generation flow.
|
||||||
|
*/
|
||||||
|
@ContextConfiguration(classes = {IpsGenerationR4Test.IpsConfig.class})
|
||||||
|
public class IpsGenerationR4Test extends BaseResourceProviderR4Test {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private IpsOperationProvider myIpsOperationProvider;
|
private IpsOperationProvider myIpsOperationProvider;
|
||||||
|
@ -74,13 +90,16 @@ public class IpsGenerationTest extends BaseResourceProviderR4Test {
|
||||||
ourLog.info("Output: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(output));
|
ourLog.info("Output: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(output));
|
||||||
|
|
||||||
// Verify
|
// Verify
|
||||||
validateDocument(outcome);
|
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).getId();
|
||||||
assertThat(patientId, matchesPattern("urn:uuid:.*"));
|
assertThat(patientId, matchesPattern("urn:uuid:.*"));
|
||||||
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());
|
||||||
|
|
||||||
|
List<String> sectionTitles = extractSectionTitles(output);
|
||||||
|
assertThat(sectionTitles.toString(), sectionTitles, contains("Allergies and Intolerances", "Medication List", "Problem List", "History of Immunizations", "Diagnostic Results"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -106,19 +125,46 @@ public class IpsGenerationTest extends BaseResourceProviderR4Test {
|
||||||
ourLog.info("Output: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(output));
|
ourLog.info("Output: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(output));
|
||||||
|
|
||||||
// Verify
|
// Verify
|
||||||
validateDocument(outcome);
|
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).getId();
|
||||||
assertThat(patientId, matchesPattern("urn:uuid:.*"));
|
assertThat(patientId, matchesPattern("urn:uuid:.*"));
|
||||||
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());
|
||||||
|
|
||||||
|
List<String> sectionTitles = extractSectionTitles(output);
|
||||||
|
assertThat(sectionTitles.toString(), sectionTitles, contains("Allergies and Intolerances", "Medication List", "Problem List"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private static List<String> extractSectionTitles(Bundle outcome) {
|
||||||
|
Composition composition = (Composition) outcome.getEntry().get(0).getResource();
|
||||||
|
List<String> sectionTitles = composition
|
||||||
|
.getSection()
|
||||||
|
.stream()
|
||||||
|
.map(Composition.SectionComponent::getTitle)
|
||||||
|
.toList();
|
||||||
|
return sectionTitles;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void validateDocument(Bundle theOutcome) {
|
private void validateDocument(Bundle theOutcome) {
|
||||||
FhirValidator validator = myFhirContext.newValidator();
|
FhirValidator validator = myFhirContext.newValidator();
|
||||||
validator.registerValidatorModule(new FhirInstanceValidator(myFhirContext));
|
FhirInstanceValidator instanceValidator = new FhirInstanceValidator(myFhirContext);
|
||||||
|
instanceValidator.setValidationSupport(new ValidationSupportChain(new IpsTerminologySvc(), myFhirContext.getValidationSupport()));
|
||||||
|
validator.registerValidatorModule(instanceValidator);
|
||||||
ValidationResult validation = validator.validateWithResult(theOutcome);
|
ValidationResult validation = validator.validateWithResult(theOutcome);
|
||||||
assertTrue(validation.isSuccessful(), () -> myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(validation.toOperationOutcome()));
|
assertTrue(validation.isSuccessful(), () -> myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(validation.toOperationOutcome()));
|
||||||
|
|
||||||
|
// Make sure that all refs have been replaced with UUIDs
|
||||||
|
List<ResourceReferenceInfo> references = myFhirContext.newTerser().getAllResourceReferences(theOutcome);
|
||||||
|
for (IBaseResource next : myFhirContext.newTerser().getAllEmbeddedResources(theOutcome, true)) {
|
||||||
|
references.addAll(myFhirContext.newTerser().getAllResourceReferences(next));
|
||||||
|
}
|
||||||
|
for (ResourceReferenceInfo next : references) {
|
||||||
|
if (!next.getResourceReference().getReferenceElement().getValue().startsWith("urn:uuid:")) {
|
||||||
|
fail(next.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
|
@ -159,4 +205,51 @@ public class IpsGenerationTest extends BaseResourceProviderR4Test {
|
||||||
return (T) resources.get(index);
|
return (T) resources.get(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a little fake terminology server that hardcodes the IPS terminology
|
||||||
|
* needed to validate these documents. This way we don't need to depend on a huge
|
||||||
|
* package.
|
||||||
|
*/
|
||||||
|
private class IpsTerminologySvc implements IValidationSupport {
|
||||||
|
@Override
|
||||||
|
public boolean isValueSetSupported(ValidationSupportContext theValidationSupportContext, String theValueSetUrl) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public CodeValidationResult validateCodeInValueSet(ValidationSupportContext theValidationSupportContext, ConceptValidationOptions theOptions, String theCodeSystem, String theCode, String theDisplay, @Nonnull IBaseResource theValueSet) {
|
||||||
|
if ("http://loinc.org".equals(theCodeSystem)) {
|
||||||
|
if ("60591-5".equals(theCode)) {
|
||||||
|
return new CodeValidationResult().setCode(theCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ("http://snomed.info/sct".equals(theCodeSystem)) {
|
||||||
|
if ("14657009".equals(theCode) || "255604002".equals(theCode)) {
|
||||||
|
return new CodeValidationResult().setCode(theCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public IBaseResource fetchCodeSystem(String theSystem) {
|
||||||
|
if ("http://hl7.org/fhir/uv/ips/CodeSystem/absent-unknown-uv-ips".equals(theSystem)) {
|
||||||
|
CodeSystem cs = new CodeSystem();
|
||||||
|
cs.setUrl("http://hl7.org/fhir/uv/ips/CodeSystem/absent-unknown-uv-ips");
|
||||||
|
cs.setContent(CodeSystem.CodeSystemContentMode.COMPLETE);
|
||||||
|
cs.addConcept().setCode("no-allergy-info");
|
||||||
|
cs.addConcept().setCode("no-medication-info");
|
||||||
|
cs.addConcept().setCode("no-known-allergies");
|
||||||
|
return cs;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FhirContext getFhirContext() {
|
||||||
|
return myFhirContext;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -14,40 +14,10 @@ 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 com.gargoylesoftware.htmlunit.html.DomElement;
|
import com.gargoylesoftware.htmlunit.html.*;
|
||||||
import com.gargoylesoftware.htmlunit.html.DomNodeList;
|
|
||||||
import com.gargoylesoftware.htmlunit.html.HtmlPage;
|
|
||||||
import com.gargoylesoftware.htmlunit.html.HtmlTable;
|
|
||||||
import com.gargoylesoftware.htmlunit.html.HtmlTableRow;
|
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import org.hamcrest.Matchers;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.hl7.fhir.r4.model.AllergyIntolerance;
|
import org.hl7.fhir.r4.model.*;
|
||||||
import org.hl7.fhir.r4.model.Bundle;
|
|
||||||
import org.hl7.fhir.r4.model.CarePlan;
|
|
||||||
import org.hl7.fhir.r4.model.ClinicalImpression;
|
|
||||||
import org.hl7.fhir.r4.model.Composition;
|
|
||||||
import org.hl7.fhir.r4.model.Condition;
|
|
||||||
import org.hl7.fhir.r4.model.Consent;
|
|
||||||
import org.hl7.fhir.r4.model.DateTimeType;
|
|
||||||
import org.hl7.fhir.r4.model.Device;
|
|
||||||
import org.hl7.fhir.r4.model.DeviceUseStatement;
|
|
||||||
import org.hl7.fhir.r4.model.DiagnosticReport;
|
|
||||||
import org.hl7.fhir.r4.model.Encounter;
|
|
||||||
import org.hl7.fhir.r4.model.IdType;
|
|
||||||
import org.hl7.fhir.r4.model.Immunization;
|
|
||||||
import org.hl7.fhir.r4.model.Medication;
|
|
||||||
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.Organization;
|
|
||||||
import org.hl7.fhir.r4.model.Patient;
|
|
||||||
import org.hl7.fhir.r4.model.PositiveIntType;
|
|
||||||
import org.hl7.fhir.r4.model.Procedure;
|
|
||||||
import org.hl7.fhir.r4.model.Reference;
|
|
||||||
import org.hl7.fhir.r4.model.Resource;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
@ -61,17 +31,19 @@ import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static ca.uhn.fhir.jpa.ips.generator.IpsGenerationTest.findEntryResource;
|
import static ca.uhn.fhir.jpa.ips.generator.IpsGenerationR4Test.findEntryResource;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.contains;
|
||||||
import static org.hamcrest.Matchers.startsWith;
|
import static org.hamcrest.Matchers.startsWith;
|
||||||
|
import static org.hamcrest.Matchers.*;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.*;
|
||||||
import static org.mockito.Mockito.times;
|
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This test verifies various IPS generation logic without using a full
|
||||||
|
* JPA backend.
|
||||||
|
*/
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
public class IpsGeneratorSvcImplTest {
|
public class IpsGeneratorSvcImplTest {
|
||||||
|
|
||||||
|
@ -103,6 +75,37 @@ public class IpsGeneratorSvcImplTest {
|
||||||
private IIpsGeneratorSvc mySvc;
|
private IIpsGeneratorSvc mySvc;
|
||||||
private DefaultIpsGenerationStrategy myStrategy;
|
private DefaultIpsGenerationStrategy myStrategy;
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private static List<String> toEntryResourceTypeStrings(Bundle outcome) {
|
||||||
|
return outcome
|
||||||
|
.getEntry()
|
||||||
|
.stream()
|
||||||
|
.map(t -> t.getResource().getResourceType().name())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private static Medication createSecondaryMedication(String medicationId) {
|
||||||
|
Medication medication = new Medication();
|
||||||
|
medication.setId(new IdType(medicationId));
|
||||||
|
medication.getCode().addCoding().setDisplay("Tylenol");
|
||||||
|
ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.put(medication, BundleEntrySearchModeEnum.INCLUDE);
|
||||||
|
return medication;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private static MedicationStatement createPrimaryMedicationStatement(String medicationId, String medicationStatementId) {
|
||||||
|
MedicationStatement medicationStatement = new MedicationStatement();
|
||||||
|
medicationStatement.setId(medicationStatementId);
|
||||||
|
medicationStatement.setMedication(new Reference(medicationId));
|
||||||
|
medicationStatement.setStatus(MedicationStatement.MedicationStatementStatus.ACTIVE);
|
||||||
|
medicationStatement.getDosageFirstRep().getRoute().addCoding().setDisplay("Oral");
|
||||||
|
medicationStatement.getDosageFirstRep().setText("DAW");
|
||||||
|
medicationStatement.setEffective(new DateTimeType("2023-01-01T11:22:33Z"));
|
||||||
|
ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.put(medicationStatement, BundleEntrySearchModeEnum.MATCH);
|
||||||
|
return medicationStatement;
|
||||||
|
}
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void beforeEach() {
|
public void beforeEach() {
|
||||||
myDaoRegistry.setResourceDaos(Collections.emptyList());
|
myDaoRegistry.setResourceDaos(Collections.emptyList());
|
||||||
|
@ -124,7 +127,7 @@ public class IpsGeneratorSvcImplTest {
|
||||||
|
|
||||||
List<String> contentResourceTypes = toEntryResourceTypeStrings(outcome);
|
List<String> contentResourceTypes = toEntryResourceTypeStrings(outcome);
|
||||||
assertThat(contentResourceTypes.toString(), contentResourceTypes,
|
assertThat(contentResourceTypes.toString(), contentResourceTypes,
|
||||||
Matchers.contains("Composition", "Patient", "AllergyIntolerance", "MedicationStatement", "MedicationStatement", "MedicationStatement", "Condition", "Condition", "Condition", "Organization"));
|
contains("Composition", "Patient", "AllergyIntolerance", "MedicationStatement", "MedicationStatement", "MedicationStatement", "Condition", "Condition", "Condition", "Organization"));
|
||||||
|
|
||||||
Composition composition = (Composition) outcome.getEntry().get(0).getResource();
|
Composition composition = (Composition) outcome.getEntry().get(0).getResource();
|
||||||
Composition.SectionComponent section;
|
Composition.SectionComponent section;
|
||||||
|
@ -145,7 +148,7 @@ public class IpsGeneratorSvcImplTest {
|
||||||
String compositionNarrative = composition.getText().getDivAsString();
|
String compositionNarrative = composition.getText().getDivAsString();
|
||||||
ourLog.info("Composition narrative: {}", compositionNarrative);
|
ourLog.info("Composition narrative: {}", compositionNarrative);
|
||||||
assertThat(compositionNarrative, containsString("Allergies and Intolerances"));
|
assertThat(compositionNarrative, containsString("Allergies and Intolerances"));
|
||||||
assertThat(compositionNarrative, containsString("Pregnancy"));
|
assertThat(compositionNarrative, not(containsString("Pregnancy")));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,7 +171,7 @@ public class IpsGeneratorSvcImplTest {
|
||||||
// Verify Bundle Contents
|
// Verify Bundle Contents
|
||||||
List<String> contentResourceTypes = toEntryResourceTypeStrings(outcome);
|
List<String> contentResourceTypes = toEntryResourceTypeStrings(outcome);
|
||||||
assertThat(contentResourceTypes.toString(), contentResourceTypes,
|
assertThat(contentResourceTypes.toString(), contentResourceTypes,
|
||||||
Matchers.contains("Composition", "Patient", "AllergyIntolerance", "MedicationStatement", "Medication", "Condition", "Organization"));
|
contains("Composition", "Patient", "AllergyIntolerance", "MedicationStatement", "Medication", "Condition", "Organization"));
|
||||||
MedicationStatement actualMedicationStatement = (MedicationStatement) outcome.getEntry().get(3).getResource();
|
MedicationStatement actualMedicationStatement = (MedicationStatement) outcome.getEntry().get(3).getResource();
|
||||||
Medication actualMedication = (Medication) outcome.getEntry().get(4).getResource();
|
Medication actualMedication = (Medication) outcome.getEntry().get(4).getResource();
|
||||||
assertThat(actualMedication.getId(), startsWith("urn:uuid:"));
|
assertThat(actualMedication.getId(), startsWith("urn:uuid:"));
|
||||||
|
@ -226,7 +229,7 @@ public class IpsGeneratorSvcImplTest {
|
||||||
// Verify Bundle Contents
|
// Verify Bundle Contents
|
||||||
List<String> contentResourceTypes = toEntryResourceTypeStrings(outcome);
|
List<String> contentResourceTypes = toEntryResourceTypeStrings(outcome);
|
||||||
assertThat(contentResourceTypes.toString(), contentResourceTypes,
|
assertThat(contentResourceTypes.toString(), contentResourceTypes,
|
||||||
Matchers.contains(
|
contains(
|
||||||
"Composition",
|
"Composition",
|
||||||
"Patient",
|
"Patient",
|
||||||
"MedicationStatement",
|
"MedicationStatement",
|
||||||
|
@ -265,7 +268,7 @@ public class IpsGeneratorSvcImplTest {
|
||||||
// Verify Bundle Contents
|
// Verify Bundle Contents
|
||||||
List<String> contentResourceTypes = toEntryResourceTypeStrings(outcome);
|
List<String> contentResourceTypes = toEntryResourceTypeStrings(outcome);
|
||||||
assertThat(contentResourceTypes.toString(), contentResourceTypes,
|
assertThat(contentResourceTypes.toString(), contentResourceTypes,
|
||||||
Matchers.contains(
|
contains(
|
||||||
"Composition",
|
"Composition",
|
||||||
"Patient",
|
"Patient",
|
||||||
"MedicationStatement",
|
"MedicationStatement",
|
||||||
|
@ -337,7 +340,7 @@ public class IpsGeneratorSvcImplTest {
|
||||||
// Setup Medication + MedicationStatement
|
// Setup Medication + MedicationStatement
|
||||||
Organization org = new Organization();
|
Organization org = new Organization();
|
||||||
org.setId(new IdType("Organization/pfizer"));
|
org.setId(new IdType("Organization/pfizer"));
|
||||||
org.setName("Pfizer");
|
org.setName("Pfizer Inc");
|
||||||
ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.put(org, BundleEntrySearchModeEnum.INCLUDE);
|
ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.put(org, BundleEntrySearchModeEnum.INCLUDE);
|
||||||
|
|
||||||
Immunization immunization = new Immunization();
|
Immunization immunization = new Immunization();
|
||||||
|
@ -374,13 +377,12 @@ public class IpsGeneratorSvcImplTest {
|
||||||
assertEquals("SpikeVax", row.getCell(0).asNormalizedText());
|
assertEquals("SpikeVax", row.getCell(0).asNormalizedText());
|
||||||
assertEquals("COMPLETED", row.getCell(1).asNormalizedText());
|
assertEquals("COMPLETED", row.getCell(1).asNormalizedText());
|
||||||
assertEquals("2 , 4", row.getCell(2).asNormalizedText());
|
assertEquals("2 , 4", row.getCell(2).asNormalizedText());
|
||||||
assertEquals("Pfizer", row.getCell(3).asNormalizedText());
|
assertEquals("Pfizer Inc", row.getCell(3).asNormalizedText());
|
||||||
assertEquals("35", row.getCell(4).asNormalizedText());
|
assertEquals("35", row.getCell(4).asNormalizedText());
|
||||||
assertEquals("Hello World", row.getCell(5).asNormalizedText());
|
assertEquals("Hello World", row.getCell(5).asNormalizedText());
|
||||||
assertThat(row.getCell(6).asNormalizedText(), containsString("2023"));
|
assertThat(row.getCell(6).asNormalizedText(), containsString("2023"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testReferencesUpdatedInSecondaryInclusions() {
|
public void testReferencesUpdatedInSecondaryInclusions() {
|
||||||
// Setup Patient
|
// Setup Patient
|
||||||
|
@ -422,6 +424,7 @@ public class IpsGeneratorSvcImplTest {
|
||||||
|
|
||||||
// Test
|
// Test
|
||||||
Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType(PATIENT_ID));
|
Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType(PATIENT_ID));
|
||||||
|
ourLog.info(myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
|
||||||
|
|
||||||
// Verify cross-references
|
// Verify cross-references
|
||||||
Patient addedPatient = findEntryResource(outcome, Patient.class, 0, 1);
|
Patient addedPatient = findEntryResource(outcome, Patient.class, 0, 1);
|
||||||
|
@ -452,6 +455,46 @@ public class IpsGeneratorSvcImplTest {
|
||||||
assertEquals(1, illnessHistorySection.getEntry().size());
|
assertEquals(1, illnessHistorySection.getEntry().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPatientIsReturnedAsAnIncludeResource() {
|
||||||
|
// Setup Patient
|
||||||
|
registerPatientDaoWithRead();
|
||||||
|
|
||||||
|
// Setup Condition
|
||||||
|
Condition conditionActive = new Condition();
|
||||||
|
conditionActive.setId("Condition/conditionActive");
|
||||||
|
conditionActive.getClinicalStatus().addCoding()
|
||||||
|
.setSystem("http://terminology.hl7.org/CodeSystem/condition-clinical")
|
||||||
|
.setCode("active");
|
||||||
|
conditionActive.setSubject(new Reference(PATIENT_ID));
|
||||||
|
conditionActive.setEncounter(new Reference(ENCOUNTER_ID));
|
||||||
|
ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.put(conditionActive, BundleEntrySearchModeEnum.MATCH);
|
||||||
|
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.setId(PATIENT_ID);
|
||||||
|
ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.put(patient, BundleEntrySearchModeEnum.INCLUDE);
|
||||||
|
|
||||||
|
IFhirResourceDao<Condition> conditionDao = registerResourceDaoWithNoData(Condition.class);
|
||||||
|
when(conditionDao.search(any(), any())).thenReturn(
|
||||||
|
new SimpleBundleProvider(Lists.newArrayList(conditionActive, patient)),
|
||||||
|
new SimpleBundleProvider()
|
||||||
|
);
|
||||||
|
|
||||||
|
registerRemainingResourceDaos();
|
||||||
|
|
||||||
|
// Test
|
||||||
|
Bundle outcome = (Bundle) mySvc.generateIps(new SystemRequestDetails(), new IdType(PATIENT_ID));
|
||||||
|
|
||||||
|
List<String> resources = outcome
|
||||||
|
.getEntry()
|
||||||
|
.stream()
|
||||||
|
.map(t -> t.getResource().getResourceType().name())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
assertThat(resources.toString(), resources, contains(
|
||||||
|
"Composition", "Patient", "AllergyIntolerance", "MedicationStatement", "Condition", "Organization"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
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();
|
||||||
|
@ -499,35 +542,4 @@ public class IpsGeneratorSvcImplTest {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
|
||||||
private static List<String> toEntryResourceTypeStrings(Bundle outcome) {
|
|
||||||
return outcome
|
|
||||||
.getEntry()
|
|
||||||
.stream()
|
|
||||||
.map(t -> t.getResource().getResourceType().name())
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nonnull
|
|
||||||
private static Medication createSecondaryMedication(String medicationId) {
|
|
||||||
Medication medication = new Medication();
|
|
||||||
medication.setId(new IdType(medicationId));
|
|
||||||
medication.getCode().addCoding().setDisplay("Tylenol");
|
|
||||||
ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.put(medication, BundleEntrySearchModeEnum.INCLUDE);
|
|
||||||
return medication;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nonnull
|
|
||||||
private static MedicationStatement createPrimaryMedicationStatement(String medicationId, String medicationStatementId) {
|
|
||||||
MedicationStatement medicationStatement = new MedicationStatement();
|
|
||||||
medicationStatement.setId(medicationStatementId);
|
|
||||||
medicationStatement.setMedication(new Reference(medicationId));
|
|
||||||
medicationStatement.setStatus(MedicationStatement.MedicationStatementStatus.ACTIVE);
|
|
||||||
medicationStatement.getDosageFirstRep().getRoute().addCoding().setDisplay("Oral");
|
|
||||||
medicationStatement.getDosageFirstRep().setText("DAW");
|
|
||||||
medicationStatement.setEffective(new DateTimeType("2023-01-01T11:22:33Z"));
|
|
||||||
ResourceMetadataKeyEnum.ENTRY_SEARCH_MODE.put(medicationStatement, BundleEntrySearchModeEnum.MATCH);
|
|
||||||
return medicationStatement;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
<configuration>
|
||||||
|
|
||||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||||
|
<level>INFO</level>
|
||||||
|
</filter>
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
|
||||||
|
</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<root level="info">
|
||||||
|
<appender-ref ref="STDOUT" />
|
||||||
|
</root>
|
||||||
|
|
||||||
|
</configuration>
|
Binary file not shown.
Loading…
Reference in New Issue