4360 bulk export questionnaireresponses should get picked when author is not empty (#4361)

* added failing test

* implemented solution

* added more tests

* added changelog

* changed the implementation, now extended to get all the patient based search params for a given resource instead of 2 in a fixed list of resources

* added test for patient bulk export for resources not in patient compartment, fixed implementation to pass test

Co-authored-by: Steven Li <steven@smilecdr.com>
This commit is contained in:
StevenXLi 2022-12-14 18:43:11 -05:00 committed by GitHub
parent 360f32f3e4
commit 29ebb950e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 155 additions and 12 deletions

View File

@ -59,7 +59,7 @@ public class SearchParameterUtil {
* Given the resource type, fetch its patient-based search parameter name
* 1. Attempt to find one called 'patient'
* 2. If that fails, find one called 'subject'
* 3. If that fails, find find by Patient Compartment.
* 3. If that fails, find one by Patient Compartment.
* 3.1 If that returns >1 result, throw an error
* 3.2 If that returns 1 result, return it
*/
@ -76,6 +76,29 @@ public class SearchParameterUtil {
return Optional.ofNullable(myPatientSearchParam);
}
/**
* Given the resource type, fetch all its patient-based search parameter name that's available
*/
public static Set<String> getPatientSearchParamsForResourceType(FhirContext theFhirContext, String theResourceType) {
RuntimeResourceDefinition runtimeResourceDefinition = theFhirContext.getResourceDefinition(theResourceType);
List<RuntimeSearchParam> searchParams = new ArrayList<>(runtimeResourceDefinition.getSearchParamsForCompartmentName("Patient"));
// add patient search parameter for resources that's not in the compartment
RuntimeSearchParam myPatientSearchParam = runtimeResourceDefinition.getSearchParam("patient");
if (myPatientSearchParam != null) {
searchParams.add(myPatientSearchParam);
}
RuntimeSearchParam mySubjectSearchParam = runtimeResourceDefinition.getSearchParam("subject");
if (mySubjectSearchParam != null) {
searchParams.add(mySubjectSearchParam);
}
if (searchParams == null || searchParams.size() == 0) {
String errorMessage = String.format("Resource type [%s] is not eligible for this type of export, as it contains no Patient compartment, and no `patient` or `subject` search parameter", runtimeResourceDefinition.getId());
throw new IllegalArgumentException(Msg.code(2222) + errorMessage);
}
// deduplicate list of searchParams and get their names
return searchParams.stream().map(RuntimeSearchParam::getName).collect(Collectors.toSet());
}
/**
* Search the resource definition for a compartment named 'patient' and return its related Search Parameter.

View File

@ -0,0 +1,5 @@
---
type: fix
issue: 4360
title: "Previously, Patient Bulk Export on certain resources with reference to patient only in author or performer did not get exported.
This has been fixed."

View File

@ -146,21 +146,23 @@ public class JpaBulkExportProcessor implements IBulkExportProcessor<JpaPid> {
throw new IllegalStateException(Msg.code(797) + errorMessage);
}
List<SearchParameterMap> maps = myBulkExportHelperSvc.createSearchParameterMapsForResourceType(def, theParams);
String patientSearchParam = getPatientSearchParamForCurrentResourceType(theParams.getResourceType()).getName();
Set<String> patientSearchParams = SearchParameterUtil.getPatientSearchParamsForResourceType(myContext, theParams.getResourceType());
for (SearchParameterMap map : maps) {
//Ensure users did not monkey with the patient compartment search parameter.
validateSearchParametersForPatient(map, theParams);
for (String patientSearchParam : patientSearchParams) {
List<SearchParameterMap> maps = myBulkExportHelperSvc.createSearchParameterMapsForResourceType(def, theParams);
for (SearchParameterMap map : maps) {
//Ensure users did not monkey with the patient compartment search parameter.
validateSearchParametersForPatient(map, theParams);
ISearchBuilder<JpaPid> searchBuilder = getSearchBuilderForResourceType(theParams.getResourceType());
ISearchBuilder<JpaPid> searchBuilder = getSearchBuilderForResourceType(theParams.getResourceType());
filterBySpecificPatient(theParams, resourceType, patientSearchParam, map);
filterBySpecificPatient(theParams, resourceType, patientSearchParam, map);
SearchRuntimeDetails searchRuntime = new SearchRuntimeDetails(null, jobId);
IResultIterator<JpaPid> resultIterator = searchBuilder.createQuery(map, searchRuntime, null, RequestPartitionId.allPartitions());
while (resultIterator.hasNext()) {
pids.add(resultIterator.next());
SearchRuntimeDetails searchRuntime = new SearchRuntimeDetails(null, jobId);
IResultIterator<JpaPid> resultIterator = searchBuilder.createQuery(map, searchRuntime, null, RequestPartitionId.allPartitions());
while (resultIterator.hasNext()) {
pids.add(resultIterator.next());
}
}
}
return pids;

View File

@ -13,19 +13,25 @@ import com.google.common.collect.Sets;
import org.apache.commons.io.LineIterator;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Basic;
import org.hl7.fhir.r4.model.Binary;
import org.hl7.fhir.r4.model.CarePlan;
import org.hl7.fhir.r4.model.Device;
import org.hl7.fhir.r4.model.DocumentReference;
import org.hl7.fhir.r4.model.Encounter;
import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.Group;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Location;
import org.hl7.fhir.r4.model.MedicationAdministration;
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.Practitioner;
import org.hl7.fhir.r4.model.Provenance;
import org.hl7.fhir.r4.model.QuestionnaireResponse;
import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.ServiceRequest;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
@ -460,6 +466,113 @@ public class BulkDataExportTest extends BaseResourceProviderR4Test {
verifyBulkExportResults(options, List.of("Patient/P1", obsId, provId, devId, devId2), List.of("Patient/P2", provId2, devId3));
}
@Test
public void testPatientBulkExportWithReferenceToAuthor_ShouldShowUp() {
myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.ENABLED);
// Create some resources
Patient patient = new Patient();
patient.setId("P1");
patient.setActive(true);
myClient.update().resource(patient).execute();
Basic basic = new Basic();
basic.setAuthor(new Reference("Patient/P1"));
String basicId = myClient.create().resource(basic).execute().getId().toUnqualifiedVersionless().getValue();
DocumentReference documentReference = new DocumentReference();
documentReference.setStatus(Enumerations.DocumentReferenceStatus.CURRENT);
documentReference.addAuthor(new Reference("Patient/P1"));
String docRefId = myClient.create().resource(documentReference).execute().getId().toUnqualifiedVersionless().getValue();
QuestionnaireResponse questionnaireResponseSub = new QuestionnaireResponse();
questionnaireResponseSub.setStatus(QuestionnaireResponse.QuestionnaireResponseStatus.COMPLETED);
questionnaireResponseSub.setSubject(new Reference("Patient/P1"));
String questRespSubId = myClient.create().resource(questionnaireResponseSub).execute().getId().toUnqualifiedVersionless().getValue();
QuestionnaireResponse questionnaireResponseAuth = new QuestionnaireResponse();
questionnaireResponseAuth.setStatus(QuestionnaireResponse.QuestionnaireResponseStatus.COMPLETED);
questionnaireResponseAuth.setAuthor(new Reference("Patient/P1"));
String questRespAuthId = myClient.create().resource(questionnaireResponseAuth).execute().getId().toUnqualifiedVersionless().getValue();
// set the export options
BulkDataExportOptions options = new BulkDataExportOptions();
options.setResourceTypes(Sets.newHashSet("Patient", "Basic", "DocumentReference", "QuestionnaireResponse"));
options.setExportStyle(BulkDataExportOptions.ExportStyle.PATIENT);
options.setOutputFormat(Constants.CT_FHIR_NDJSON);
verifyBulkExportResults(options, List.of("Patient/P1", basicId, docRefId, questRespAuthId, questRespSubId), Collections.emptyList());
}
@Test
public void testPatientBulkExportWithReferenceToPerformer_ShouldShowUp() {
myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.ENABLED);
// Create some resources
Patient patient = new Patient();
patient.setId("P1");
patient.setActive(true);
myClient.update().resource(patient).execute();
CarePlan carePlan = new CarePlan();
carePlan.setStatus(CarePlan.CarePlanStatus.COMPLETED);
CarePlan.CarePlanActivityComponent carePlanActivityComponent = new CarePlan.CarePlanActivityComponent();
CarePlan.CarePlanActivityDetailComponent carePlanActivityDetailComponent = new CarePlan.CarePlanActivityDetailComponent();
carePlanActivityDetailComponent.addPerformer(new Reference("Patient/P1"));
carePlanActivityComponent.setDetail(carePlanActivityDetailComponent);
carePlan.addActivity(carePlanActivityComponent);
String carePlanId = myClient.create().resource(carePlan).execute().getId().toUnqualifiedVersionless().getValue();
MedicationAdministration medicationAdministration = new MedicationAdministration();
medicationAdministration.setStatus(MedicationAdministration.MedicationAdministrationStatus.COMPLETED);
MedicationAdministration.MedicationAdministrationPerformerComponent medicationAdministrationPerformerComponent = new MedicationAdministration.MedicationAdministrationPerformerComponent();
medicationAdministrationPerformerComponent.setActor(new Reference("Patient/P1"));
medicationAdministration.addPerformer(medicationAdministrationPerformerComponent);
String medAdminId = myClient.create().resource(medicationAdministration).execute().getId().toUnqualifiedVersionless().getValue();
ServiceRequest serviceRequest = new ServiceRequest();
serviceRequest.setStatus(ServiceRequest.ServiceRequestStatus.COMPLETED);
serviceRequest.addPerformer(new Reference("Patient/P1"));
String sevReqId = myClient.create().resource(serviceRequest).execute().getId().toUnqualifiedVersionless().getValue();
Observation observationSub = new Observation();
observationSub.setStatus(Observation.ObservationStatus.AMENDED);
observationSub.setSubject(new Reference("Patient/P1"));
String obsSubId = myClient.create().resource(observationSub).execute().getId().toUnqualifiedVersionless().getValue();
Observation observationPer = new Observation();
observationPer.setStatus(Observation.ObservationStatus.AMENDED);
observationPer.addPerformer(new Reference("Patient/P1"));
String obsPerId = myClient.create().resource(observationPer).execute().getId().toUnqualifiedVersionless().getValue();
// set the export options
BulkDataExportOptions options = new BulkDataExportOptions();
options.setResourceTypes(Sets.newHashSet("Patient", "Observation", "CarePlan", "MedicationAdministration", "ServiceRequest"));
options.setExportStyle(BulkDataExportOptions.ExportStyle.PATIENT);
options.setOutputFormat(Constants.CT_FHIR_NDJSON);
verifyBulkExportResults(options, List.of("Patient/P1", carePlanId, medAdminId, sevReqId, obsSubId, obsPerId), Collections.emptyList());
}
@Test
public void testPatientBulkExportWithResourceNotInCompartment_ShouldShowUp() {
myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.ENABLED);
// Create some resources
Patient patient = new Patient();
patient.setId("P1");
patient.setActive(true);
myClient.update().resource(patient).execute();
Device device = new Device();
device.setStatus(Device.FHIRDeviceStatus.ACTIVE);
device.setPatient(new Reference("Patient/P1"));
String deviceId = myClient.create().resource(device).execute().getId().toUnqualifiedVersionless().getValue();
// set the export options
BulkDataExportOptions options = new BulkDataExportOptions();
options.setResourceTypes(Sets.newHashSet("Patient", "Device"));
options.setExportStyle(BulkDataExportOptions.ExportStyle.PATIENT);
options.setOutputFormat(Constants.CT_FHIR_NDJSON);
verifyBulkExportResults(options, List.of("Patient/P1", deviceId), Collections.emptyList());
}
private void verifyBulkExportResults(BulkDataExportOptions theOptions, List<String> theContainedList, List<String> theExcludedList) {
Batch2JobStartResponse startResponse = myJobRunner.startNewJob(BulkExportUtils.createBulkExportJobParametersFromExportOptions(theOptions));