Merge pull request #2019 from jamesagnew/minimum-data-required-for-empi

Add a Minimum data requirement for EMPI processing
This commit is contained in:
Tadgh 2020-08-06 12:46:03 -07:00 committed by GitHub
commit 605c3d8f8a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 136 additions and 21 deletions

View File

@ -28,6 +28,7 @@ import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.empi.svc.EmpiMatchLinkSvc;
import ca.uhn.fhir.jpa.empi.svc.EmpiResourceFilteringSvc;
import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedJsonMessage;
import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage;
import ca.uhn.fhir.rest.server.TransactionLogMessages;
@ -50,6 +51,8 @@ public class EmpiMessageHandler implements MessageHandler {
private IInterceptorBroadcaster myInterceptorBroadcaster;
@Autowired
private FhirContext myFhirContext;
@Autowired
private EmpiResourceFilteringSvc myEmpiResourceFileringSvc;
@Override
public void handleMessage(Message<?> theMessage) throws MessagingException {
@ -62,13 +65,14 @@ public class EmpiMessageHandler implements MessageHandler {
ResourceModifiedMessage msg = ((ResourceModifiedJsonMessage) theMessage).getPayload();
try {
matchEmpiAndUpdateLinks(msg);
if (myEmpiResourceFileringSvc.shouldBeProcessed(getResourceFromPayload(msg))) {
matchEmpiAndUpdateLinks(msg);
}
} catch (Exception e) {
ourLog.error("Failed to handle EMPI Matching Resource:", e);
throw e;
}
}
public void matchEmpiAndUpdateLinks(ResourceModifiedMessage theMsg) {
String resourceType = theMsg.getId(myFhirContext).getResourceType();
validateResourceType(resourceType);

View File

@ -47,6 +47,7 @@ import ca.uhn.fhir.jpa.empi.svc.EmpiLinkSvcImpl;
import ca.uhn.fhir.jpa.empi.svc.EmpiLinkUpdaterSvcImpl;
import ca.uhn.fhir.jpa.empi.svc.EmpiMatchFinderSvcImpl;
import ca.uhn.fhir.jpa.empi.svc.EmpiMatchLinkSvc;
import ca.uhn.fhir.jpa.empi.svc.EmpiResourceFilteringSvc;
import ca.uhn.fhir.jpa.empi.svc.EmpiPersonDeletingSvc;
import ca.uhn.fhir.jpa.empi.svc.EmpiPersonMergerSvcImpl;
import ca.uhn.fhir.jpa.empi.svc.EmpiResetSvcImpl;
@ -211,4 +212,9 @@ public class EmpiConsumerConfig {
EmpiLinkDeleteSvc empiLinkDeleteSvc() {
return new EmpiLinkDeleteSvc();
}
@Bean
EmpiResourceFilteringSvc empiResourceFilteringSvc() {
return new EmpiResourceFilteringSvc();
}
}

View File

@ -0,0 +1,50 @@
package ca.uhn.fhir.jpa.empi.svc;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.empi.api.IEmpiSettings;
import ca.uhn.fhir.empi.log.Logs;
import ca.uhn.fhir.empi.rules.json.EmpiResourceSearchParamJson;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class EmpiResourceFilteringSvc {
private static final Logger ourLog = Logs.getEmpiTroubleshootingLog();
@Autowired
private IEmpiSettings empiSettings;
@Autowired
EmpiSearchParamSvc myEmpiSearchParamSvc;
@Autowired
FhirContext myFhirContext;
/**
* Given a resource from the EMPI Channel, determine whether or not EMPI processing should occur on it.
*
* EMPI processing should occur if for any {@link EmpiResourceSearchParamJson) Search Param, the resource contains a value.
*
* If the resource has no attributes that appear in the candidate search params, processing should be skipped, as there is not
* sufficient information to perform meaningful EMPI processing. (For example, how can EMPI processing occur on a patient that has _no_ attributes?)
*
* @param theResource the resource that you wish to check against EMPI rules.
*
* @return whether or not EMPI processing should proceed
*/
public boolean shouldBeProcessed(IAnyResource theResource) {
String resourceType = myFhirContext.getResourceType(theResource);
List<EmpiResourceSearchParamJson> candidateSearchParams = empiSettings.getEmpiRules().getCandidateSearchParams();
boolean containsValueForSomeSearchParam = candidateSearchParams.stream()
.filter(csp -> myEmpiSearchParamSvc.searchParamTypeIsValidForResourceType(csp.getResourceType(), resourceType))
.flatMap(csp -> csp.getSearchParams().stream())
.map(searchParam -> myEmpiSearchParamSvc.getValueFromResourceForSearchParam(theResource, searchParam))
.anyMatch(valueList -> !valueList.isEmpty());
ourLog.debug("Is {} suitable for EMPI processing? : {}", theResource.getId(), containsValueForSomeSearchParam);
return containsValueForSomeSearchParam;
}
}

View File

@ -87,10 +87,20 @@ public class EmpiSearchParamSvc implements ISearchParamRetriever {
spMap = mapFromCriteria(theTargetType, theCriteria);
}
return spMap;
}
}
public ISearchBuilder generateSearchBuilderForType(String theTargetType) {
public ISearchBuilder generateSearchBuilderForType(String theTargetType) {
IFhirResourceDao resourceDao = myDaoRegistry.getResourceDao(theTargetType);
return mySearchBuilderFactory.newSearchBuilder(resourceDao, theTargetType, resourceDao.getResourceType());
}
/**
* Will return true if the types match, or the search param type is '*', otherwise false.
* @param theSearchParamType
* @param theResourceType
* @return
*/
public boolean searchParamTypeIsValidForResourceType(String theSearchParamType, String theResourceType) {
return theSearchParamType.equalsIgnoreCase(theResourceType) || theSearchParamType.equalsIgnoreCase("*");
}
}

View File

@ -19,7 +19,6 @@ import org.hl7.fhir.r4.model.Enumerations;
import org.hl7.fhir.r4.model.Organization;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Person;
import org.hl7.fhir.r4.model.Practitioner;
import org.hl7.fhir.r4.model.SearchParameter;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -62,27 +61,21 @@ public class EmpiStorageInterceptorIT extends BaseEmpiR4Test {
super.loadEmpiSearchParameters();
}
@Test
public void testCreatePatient() throws InterruptedException {
myEmpiHelper.createWithLatch(new Patient());
assertLinkCount(1);
}
@Test
public void testCreatePractitioner() throws InterruptedException {
myEmpiHelper.createWithLatch(new Practitioner());
myEmpiHelper.createWithLatch(buildPractitionerWithNameAndId("somename", "some_id"));
assertLinkCount(1);
}
@Test
public void testCreatePerson() throws InterruptedException {
public void testCreatePerson() {
myPersonDao.create(new Person());
assertLinkCount(0);
}
@Test
public void testDeletePersonDeletesLinks() throws InterruptedException {
myEmpiHelper.createWithLatch(new Patient());
myEmpiHelper.createWithLatch(buildPaulPatient());
assertLinkCount(1);
Person person = getOnlyActivePerson();
myPersonDao.delete(person.getIdElement());
@ -102,6 +95,18 @@ public class EmpiStorageInterceptorIT extends BaseEmpiR4Test {
}
}
@Test
public void testCreatingPersonWithInsufficentEMPIAttributesIsNotEMPIProcessed() throws InterruptedException {
myEmpiHelper.doCreateResource(new Patient(), true);
assertLinkCount(0);
}
@Test
public void testCreatingPatientWithOneOrMoreMatchingAttributesIsEMPIProcessed() throws InterruptedException {
myEmpiHelper.createWithLatch(buildPaulPatient());
assertLinkCount(1);
}
@Test
public void testCreateOrganizationWithEmpiTagForbidden() throws InterruptedException {
//Creating a organization with the EMPI-MANAGED tag should fail
@ -163,7 +168,7 @@ public class EmpiStorageInterceptorIT extends BaseEmpiR4Test {
public void testEmpiManagedPersonCannotBeModifiedByPersonUpdateRequest() throws InterruptedException {
// When EMPI is enabled, only the EMPI system is allowed to modify Person links of Persons with the EMPI-MANAGED tag.
Patient patient = new Patient();
IIdType patientId = myEmpiHelper.createWithLatch(new Patient()).getDaoMethodOutcome().getId().toUnqualifiedVersionless();
IIdType patientId = myEmpiHelper.createWithLatch(buildPaulPatient()).getDaoMethodOutcome().getId().toUnqualifiedVersionless();
patient.setId(patientId);
@ -263,7 +268,7 @@ public class EmpiStorageInterceptorIT extends BaseEmpiR4Test {
@Test
public void testPatientsWithNoEIDCanBeUpdated() throws InterruptedException {
setPreventEidUpdates(true);
Patient p = new Patient();
Patient p = buildPaulPatient();
EmpiHelperR4.OutcomeAndLogMessageWrapper wrapper = myEmpiHelper.createWithLatch(p);
p.setId(wrapper.getDaoMethodOutcome().getId());
@ -275,7 +280,7 @@ public class EmpiStorageInterceptorIT extends BaseEmpiR4Test {
@Test
public void testPatientsCanHaveEIDAddedInStrictMode() throws InterruptedException {
setPreventEidUpdates(true);
Patient p = new Patient();
Patient p = buildPaulPatient();
EmpiHelperR4.OutcomeAndLogMessageWrapper messageWrapper = myEmpiHelper.createWithLatch(p);
p.setId(messageWrapper.getDaoMethodOutcome().getId());
addExternalEID(p, "external eid");

View File

@ -31,7 +31,7 @@ public abstract class BaseLinkR4Test extends BaseProviderR4Test {
public void before() {
super.before();
myPatient = createPatientAndUpdateLinks(new Patient());
myPatient = createPatientAndUpdateLinks(buildPaulPatient());
myPatientId = new StringType(myPatient.getIdElement().getValue());
myPerson = getPersonFromTarget(myPatient);

View File

@ -36,7 +36,7 @@ public class EmpiProviderBatchR4Test extends BaseLinkR4Test {
@BeforeEach
public void before() {
super.before();
myPractitioner = createPractitionerAndUpdateLinks(new Practitioner());
myPractitioner = createPractitionerAndUpdateLinks(buildPractitionerWithNameAndId("some_pract", "some_pract_id"));
myPractitionerId = new StringType(myPractitioner.getIdElement().getValue());
myPractitionerPerson = getPersonFromTarget(myPractitioner);
myPractitionerPersonId = new StringType(myPractitionerPerson.getIdElement().getValue());

View File

@ -17,7 +17,7 @@ import java.util.Date;
class EmpiBatchSvcImplTest extends BaseEmpiR4Test {
@Autowired
IEmpiBatchSvc myEmpiBatchSvc;
IEmpiBatchSvc myEmpiBatchSvc;
@Autowired
IInterceptorService myInterceptorService;

View File

@ -0,0 +1,39 @@
package ca.uhn.fhir.jpa.empi.svc;
import ca.uhn.fhir.jpa.empi.BaseEmpiR4Test;
import org.hl7.fhir.r4.model.BooleanType;
import org.hl7.fhir.r4.model.Patient;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
class EmpiResourceFilteringSvcTest extends BaseEmpiR4Test {
@Autowired
private EmpiResourceFilteringSvc myEmpiResourceFilteringSvc;
@Test
public void testFilterResourcesWhichHaveNoRelevantAttributes() {
Patient patient = new Patient();
patient.setDeceased(new BooleanType(true)); //EMPI rules defined do not care about the deceased attribute.
//SUT
boolean shouldBeProcessed = myEmpiResourceFilteringSvc.shouldBeProcessed(patient);
assertThat(shouldBeProcessed, is(equalTo(false)));
}
@Test
public void testDoNotFilterResourcesWithEMPIAttributes() {
Patient patient = new Patient();
patient.addIdentifier().setValue("Hey I'm an ID! rules defined in empi-rules.json care about me!");
//SUT
boolean shouldBeProcessed = myEmpiResourceFilteringSvc.shouldBeProcessed(patient);
assertThat(shouldBeProcessed, is(equalTo(true)));
}
}

View File

@ -399,6 +399,7 @@ public class SearchParamExtractorService {
myInterceptorBroadcaster = theJpaInterceptorBroadcaster;
}
@Nonnull
public List<String> extractParamValuesAsStrings(RuntimeSearchParam theActiveSearchParam, IBaseResource theResource) {
return mySearchParamExtractor.extractParamValuesAsStrings(theActiveSearchParam, theResource);
}

View File

@ -54,7 +54,7 @@ public class EmpiResourceSearchParamJson implements IModelJson, Iterable<String>
return this;
}
private List<String> getSearchParams() {
public List<String> getSearchParams() {
if (mySearchParams == null) {
mySearchParams = new ArrayList<>();
}