Empi 3 ruleset version (#1978)
* add rule version * add rule version * Rough in model for Golden Record. * Test Link Rule Version * add eid match boolean * added new fields to EmpiLink to provide more information about how the link was created * add logging to check an edge case * all tests pass * wip with failing tests * tests pass * FIXME * optimize imports * test score in provider output * FIXME * FIXME * Fix jpa test app context * fix migration string length * review feedback param name * review feedback javadoc * review feedback javadoc * bean config reorganization for cdr * add more tests
This commit is contained in:
parent
f5222f3105
commit
ebd6ca4b64
|
@ -34,6 +34,7 @@ import org.hl7.fhir.instance.model.api.IBaseReference;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -299,6 +300,20 @@ public class ParametersUtil {
|
||||||
addPart(theContext, theParameter, theName, value);
|
addPart(theContext, theParameter, theName, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void addPartBoolean(FhirContext theContext, IBase theParameter, String theName, Boolean theValue) {
|
||||||
|
IPrimitiveType<Boolean> value = (IPrimitiveType<Boolean>) theContext.getElementDefinition("boolean").newInstance();
|
||||||
|
value.setValue(theValue);
|
||||||
|
|
||||||
|
addPart(theContext, theParameter, theName, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void addPartDecimal(FhirContext theContext, IBase theParameter, String theName, Double theValue) {
|
||||||
|
IPrimitiveType<BigDecimal> value = (IPrimitiveType<BigDecimal>) theContext.getElementDefinition("decimal").newInstance();
|
||||||
|
value.setValue(theValue == null ? null : new BigDecimal(theValue));
|
||||||
|
|
||||||
|
addPart(theContext, theParameter, theName, value);
|
||||||
|
}
|
||||||
|
|
||||||
public static void addPartCoding(FhirContext theContext, IBase theParameter, String theName, String theSystem, String theCode, String theDisplay) {
|
public static void addPartCoding(FhirContext theContext, IBase theParameter, String theName, String theSystem, String theCode, String theDisplay) {
|
||||||
IBase coding = theContext.getElementDefinition("coding").newInstance();
|
IBase coding = theContext.getElementDefinition("coding").newInstance();
|
||||||
|
|
||||||
|
|
|
@ -52,10 +52,10 @@ Below are some simplifying principles HAPI EMPI enforces to reduce complexity an
|
||||||
1. HAPI EMPI stores these extra link details in a table called `MPI_LINK`.
|
1. HAPI EMPI stores these extra link details in a table called `MPI_LINK`.
|
||||||
|
|
||||||
1. Each record in the `MPI_LINK` table corresponds to a `link.target` entry on a Person resource unless it is a NO_MATCH record. HAPI EMPI uses the following convention for the Person.link.assurance level:
|
1. Each record in the `MPI_LINK` table corresponds to a `link.target` entry on a Person resource unless it is a NO_MATCH record. HAPI EMPI uses the following convention for the Person.link.assurance level:
|
||||||
1. Level 1: not used
|
1. Level 1: POSSIBLE_MATCH
|
||||||
1. Level 2: POSSIBLE_MATCH
|
1. Level 2: AUTO MATCH
|
||||||
1. Level 3: AUTO MATCH
|
1. Level 3: MANUAL MATCH
|
||||||
1. Level 4: MANUAL MATCH
|
1. Level 4: GOLDEN RECORD
|
||||||
|
|
||||||
### Possible rule match outcomes:
|
### Possible rule match outcomes:
|
||||||
|
|
||||||
|
|
|
@ -92,6 +92,15 @@ This operation returns a `Parameters` resource that looks like the following:
|
||||||
}, {
|
}, {
|
||||||
"name": "linkSource",
|
"name": "linkSource",
|
||||||
"valueString": "AUTO"
|
"valueString": "AUTO"
|
||||||
|
}, {
|
||||||
|
"name": "eidMatch",
|
||||||
|
"valueBoolean": false
|
||||||
|
}, {
|
||||||
|
"name": "newPerson",
|
||||||
|
"valueBoolean": false
|
||||||
|
}, {
|
||||||
|
"name": "score",
|
||||||
|
"valueDecimal": 1.8
|
||||||
} ]
|
} ]
|
||||||
} ]
|
} ]
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
package ca.uhn.fhir.jpa.dao.empi;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.jpa.dao.data.IEmpiLinkDao;
|
||||||
|
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class EmpiLinkDeleteSvc {
|
||||||
|
private static final Logger ourLog = LoggerFactory.getLogger(EmpiLinkDeleteSvc.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IEmpiLinkDao myEmpiLinkDao;
|
||||||
|
@Autowired
|
||||||
|
private IdHelperService myIdHelperService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete all EmpiLink records with any reference to this resource. (Used by Expunge.)
|
||||||
|
* @param theResource
|
||||||
|
* @return the number of records deleted
|
||||||
|
*/
|
||||||
|
public int deleteWithAnyReferenceTo(IBaseResource theResource) {
|
||||||
|
Long pid = myIdHelperService.getPidOrThrowException(theResource.getIdElement(), null);
|
||||||
|
int removed = myEmpiLinkDao.deleteWithAnyReferenceToPid(pid);
|
||||||
|
if (removed > 0) {
|
||||||
|
ourLog.info("Removed {} EMPI links with references to {}", removed, theResource.getIdElement().toVersionless());
|
||||||
|
}
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,7 +24,6 @@ import ca.uhn.fhir.empi.api.EmpiLinkSourceEnum;
|
||||||
import ca.uhn.fhir.empi.api.EmpiMatchResultEnum;
|
import ca.uhn.fhir.empi.api.EmpiMatchResultEnum;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
|
||||||
|
|
||||||
import javax.persistence.Column;
|
import javax.persistence.Column;
|
||||||
import javax.persistence.Entity;
|
import javax.persistence.Entity;
|
||||||
|
@ -49,6 +48,7 @@ import java.util.Date;
|
||||||
@UniqueConstraint(name = "IDX_EMPI_PERSON_TGT", columnNames = {"PERSON_PID", "TARGET_PID"}),
|
@UniqueConstraint(name = "IDX_EMPI_PERSON_TGT", columnNames = {"PERSON_PID", "TARGET_PID"}),
|
||||||
})
|
})
|
||||||
public class EmpiLink {
|
public class EmpiLink {
|
||||||
|
public static final int VERSION_LENGTH = 16;
|
||||||
private static final int MATCH_RESULT_LENGTH = 16;
|
private static final int MATCH_RESULT_LENGTH = 16;
|
||||||
private static final int LINK_SOURCE_LENGTH = 16;
|
private static final int LINK_SOURCE_LENGTH = 16;
|
||||||
|
|
||||||
|
@ -88,6 +88,29 @@ public class EmpiLink {
|
||||||
@Column(name = "UPDATED", nullable = false)
|
@Column(name = "UPDATED", nullable = false)
|
||||||
private Date myUpdated;
|
private Date myUpdated;
|
||||||
|
|
||||||
|
@Column(name = "VERSION", nullable = false, length = VERSION_LENGTH)
|
||||||
|
private String myVersion;
|
||||||
|
|
||||||
|
/** This link was created as a result of an eid match **/
|
||||||
|
@Column(name = "EID_MATCH")
|
||||||
|
private Boolean myEidMatch;
|
||||||
|
|
||||||
|
/** This link created a new person **/
|
||||||
|
@Column(name = "NEW_PERSON")
|
||||||
|
private Boolean myNewPerson;
|
||||||
|
|
||||||
|
@Column(name = "VECTOR")
|
||||||
|
private Long myVector;
|
||||||
|
|
||||||
|
@Column(name = "SCORE")
|
||||||
|
private Double myScore;
|
||||||
|
|
||||||
|
public EmpiLink() {}
|
||||||
|
|
||||||
|
public EmpiLink(String theVersion) {
|
||||||
|
myVersion = theVersion;
|
||||||
|
}
|
||||||
|
|
||||||
public Long getId() {
|
public Long getId() {
|
||||||
return myId;
|
return myId;
|
||||||
}
|
}
|
||||||
|
@ -177,17 +200,6 @@ public class EmpiLink {
|
||||||
return myLinkSource == EmpiLinkSourceEnum.MANUAL;
|
return myLinkSource == EmpiLinkSourceEnum.MANUAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
|
|
||||||
.append("myId", myId)
|
|
||||||
.append("myPersonPid", myPersonPid)
|
|
||||||
.append("myTargetPid", myTargetPid)
|
|
||||||
.append("myMatchResult", myMatchResult)
|
|
||||||
.append("myLinkSource", myLinkSource)
|
|
||||||
.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Date getCreated() {
|
public Date getCreated() {
|
||||||
return myCreated;
|
return myCreated;
|
||||||
}
|
}
|
||||||
|
@ -205,4 +217,70 @@ public class EmpiLink {
|
||||||
myUpdated = theUpdated;
|
myUpdated = theUpdated;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getVersion() {
|
||||||
|
return myVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EmpiLink setVersion(String theVersion) {
|
||||||
|
myVersion = theVersion;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getVector() {
|
||||||
|
return myVector;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EmpiLink setVector(Long theVector) {
|
||||||
|
myVector = theVector;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getScore() {
|
||||||
|
return myScore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EmpiLink setScore(Double theScore) {
|
||||||
|
myScore = theScore;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getEidMatch() {
|
||||||
|
return myEidMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEidMatch() {
|
||||||
|
return myEidMatch != null && myEidMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EmpiLink setEidMatch(Boolean theEidMatch) {
|
||||||
|
myEidMatch = theEidMatch;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getNewPerson() {
|
||||||
|
return myNewPerson;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNewPerson() {
|
||||||
|
return myNewPerson != null && myNewPerson;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EmpiLink setNewPerson(Boolean theNewPerson) {
|
||||||
|
myNewPerson = theNewPerson;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return new ToStringBuilder(this)
|
||||||
|
.append("myPersonPid", myPersonPid)
|
||||||
|
.append("myTargetPid", myTargetPid)
|
||||||
|
.append("myMatchResult", myMatchResult)
|
||||||
|
.append("myLinkSource", myLinkSource)
|
||||||
|
.append("myEidMatch", myEidMatch)
|
||||||
|
.append("myNewPerson", myNewPerson)
|
||||||
|
.append("myScore", myScore)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,6 @@ import javax.persistence.EntityManagerFactory;
|
||||||
SubscriptionChannelConfig.class
|
SubscriptionChannelConfig.class
|
||||||
})
|
})
|
||||||
public class TestJPAConfig {
|
public class TestJPAConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public DaoConfig daoConfig() {
|
public DaoConfig daoConfig() {
|
||||||
return new DaoConfig();
|
return new DaoConfig();
|
||||||
|
|
|
@ -33,45 +33,36 @@ import ca.uhn.fhir.empi.rules.config.EmpiRuleValidator;
|
||||||
import ca.uhn.fhir.empi.rules.svc.EmpiResourceMatcherSvc;
|
import ca.uhn.fhir.empi.rules.svc.EmpiResourceMatcherSvc;
|
||||||
import ca.uhn.fhir.empi.util.EIDHelper;
|
import ca.uhn.fhir.empi.util.EIDHelper;
|
||||||
import ca.uhn.fhir.empi.util.PersonHelper;
|
import ca.uhn.fhir.empi.util.PersonHelper;
|
||||||
|
import ca.uhn.fhir.jpa.dao.empi.EmpiLinkDeleteSvc;
|
||||||
import ca.uhn.fhir.jpa.empi.broker.EmpiMessageHandler;
|
import ca.uhn.fhir.jpa.empi.broker.EmpiMessageHandler;
|
||||||
import ca.uhn.fhir.jpa.empi.broker.EmpiQueueConsumerLoader;
|
import ca.uhn.fhir.jpa.empi.broker.EmpiQueueConsumerLoader;
|
||||||
|
import ca.uhn.fhir.jpa.empi.dao.EmpiLinkDaoSvc;
|
||||||
|
import ca.uhn.fhir.jpa.empi.dao.EmpiLinkFactory;
|
||||||
import ca.uhn.fhir.jpa.empi.interceptor.EmpiStorageInterceptor;
|
import ca.uhn.fhir.jpa.empi.interceptor.EmpiStorageInterceptor;
|
||||||
import ca.uhn.fhir.jpa.empi.interceptor.IEmpiStorageInterceptor;
|
import ca.uhn.fhir.jpa.empi.interceptor.IEmpiStorageInterceptor;
|
||||||
import ca.uhn.fhir.jpa.empi.svc.EmpiCandidateSearchCriteriaBuilderSvc;
|
|
||||||
import ca.uhn.fhir.jpa.empi.svc.EmpiCandidateSearchSvc;
|
|
||||||
import ca.uhn.fhir.jpa.empi.svc.EmpiEidUpdateService;
|
import ca.uhn.fhir.jpa.empi.svc.EmpiEidUpdateService;
|
||||||
import ca.uhn.fhir.jpa.empi.svc.EmpiLinkQuerySvcImpl;
|
import ca.uhn.fhir.jpa.empi.svc.EmpiLinkQuerySvcImpl;
|
||||||
import ca.uhn.fhir.jpa.empi.svc.EmpiLinkSvcImpl;
|
import ca.uhn.fhir.jpa.empi.svc.EmpiLinkSvcImpl;
|
||||||
import ca.uhn.fhir.jpa.empi.svc.EmpiLinkUpdaterSvcImpl;
|
import ca.uhn.fhir.jpa.empi.svc.EmpiLinkUpdaterSvcImpl;
|
||||||
import ca.uhn.fhir.jpa.empi.svc.EmpiMatchFinderSvcImpl;
|
import ca.uhn.fhir.jpa.empi.svc.EmpiMatchFinderSvcImpl;
|
||||||
import ca.uhn.fhir.jpa.empi.svc.EmpiMatchLinkSvc;
|
import ca.uhn.fhir.jpa.empi.svc.EmpiMatchLinkSvc;
|
||||||
import ca.uhn.fhir.jpa.empi.svc.EmpiPersonFindingSvc;
|
|
||||||
import ca.uhn.fhir.jpa.empi.svc.EmpiPersonMergerSvcImpl;
|
import ca.uhn.fhir.jpa.empi.svc.EmpiPersonMergerSvcImpl;
|
||||||
import ca.uhn.fhir.jpa.empi.svc.EmpiResourceDaoSvc;
|
import ca.uhn.fhir.jpa.empi.svc.EmpiResourceDaoSvc;
|
||||||
|
import ca.uhn.fhir.jpa.empi.svc.candidate.EmpiCandidateSearchCriteriaBuilderSvc;
|
||||||
|
import ca.uhn.fhir.jpa.empi.svc.candidate.EmpiCandidateSearchSvc;
|
||||||
|
import ca.uhn.fhir.jpa.empi.svc.candidate.EmpiPersonFindingSvc;
|
||||||
|
import ca.uhn.fhir.jpa.empi.svc.candidate.FindCandidateByEidSvc;
|
||||||
|
import ca.uhn.fhir.jpa.empi.svc.candidate.FindCandidateByLinkSvc;
|
||||||
|
import ca.uhn.fhir.jpa.empi.svc.candidate.FindCandidateByScoreSvc;
|
||||||
import ca.uhn.fhir.rest.server.util.ISearchParamRetriever;
|
import ca.uhn.fhir.rest.server.util.ISearchParamRetriever;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.event.ContextRefreshedEvent;
|
|
||||||
import org.springframework.context.event.EventListener;
|
|
||||||
import org.springframework.core.annotation.Order;
|
|
||||||
|
|
||||||
import javax.annotation.PostConstruct;
|
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
public class EmpiConsumerConfig {
|
public class EmpiConsumerConfig {
|
||||||
private static final Logger ourLog = Logs.getEmpiTroubleshootingLog();
|
private static final Logger ourLog = Logs.getEmpiTroubleshootingLog();
|
||||||
|
|
||||||
@Autowired
|
|
||||||
IEmpiSettings myEmpiProperties;
|
|
||||||
@Autowired
|
|
||||||
EmpiProviderLoader myEmpiProviderLoader;
|
|
||||||
@Autowired
|
|
||||||
EmpiSubscriptionLoader myEmpiSubscriptionLoader;
|
|
||||||
@Autowired
|
|
||||||
EmpiSearchParameterLoader myEmpiSearchParameterLoader;
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
IEmpiStorageInterceptor empiStorageInterceptor() {
|
IEmpiStorageInterceptor empiStorageInterceptor() {
|
||||||
return new EmpiStorageInterceptor();
|
return new EmpiStorageInterceptor();
|
||||||
|
@ -127,6 +118,21 @@ public class EmpiConsumerConfig {
|
||||||
return new EmpiPersonFindingSvc();
|
return new EmpiPersonFindingSvc();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
FindCandidateByEidSvc findCandidateByEidSvc() {
|
||||||
|
return new FindCandidateByEidSvc();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
FindCandidateByLinkSvc findCandidateByLinkSvc() {
|
||||||
|
return new FindCandidateByLinkSvc();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
FindCandidateByScoreSvc findCandidateByScoreSvc() {
|
||||||
|
return new FindCandidateByScoreSvc();
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
EmpiProviderLoader empiProviderLoader() {
|
EmpiProviderLoader empiProviderLoader() {
|
||||||
return new EmpiProviderLoader();
|
return new EmpiProviderLoader();
|
||||||
|
@ -173,34 +179,28 @@ public class EmpiConsumerConfig {
|
||||||
return new EIDHelper(theFhirContext, theEmpiConfig);
|
return new EIDHelper(theFhirContext, theEmpiConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
EmpiLinkDaoSvc empiLinkDaoSvc() {
|
||||||
|
return new EmpiLinkDaoSvc();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
EmpiLinkFactory empiLinkFactory(IEmpiSettings theEmpiSettings) {
|
||||||
|
return new EmpiLinkFactory(theEmpiSettings);
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
IEmpiLinkUpdaterSvc manualLinkUpdaterSvc() {
|
IEmpiLinkUpdaterSvc manualLinkUpdaterSvc() {
|
||||||
return new EmpiLinkUpdaterSvcImpl();
|
return new EmpiLinkUpdaterSvcImpl();
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostConstruct
|
@Bean
|
||||||
public void registerInterceptorAndProvider() {
|
EmpiLoader empiLoader() {
|
||||||
if (!myEmpiProperties.isEnabled()) {
|
return new EmpiLoader();
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventListener(classes = {ContextRefreshedEvent.class})
|
@Bean
|
||||||
// This @Order is here to ensure that MatchingQueueSubscriberLoader has initialized before we initialize this.
|
EmpiLinkDeleteSvc empiLinkDeleteSvc() {
|
||||||
// Otherwise the EMPI subscriptions won't get loaded into the SubscriptionRegistry
|
return new EmpiLinkDeleteSvc();
|
||||||
@Order
|
|
||||||
public void updateSubscriptions() {
|
|
||||||
if (!myEmpiProperties.isEnabled()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
myEmpiProviderLoader.loadProvider();
|
|
||||||
ourLog.info("EMPI provider registered");
|
|
||||||
|
|
||||||
myEmpiSubscriptionLoader.daoUpdateEmpiSubscriptions();
|
|
||||||
ourLog.info("EMPI subscriptions updated");
|
|
||||||
|
|
||||||
myEmpiSearchParameterLoader.daoUpdateEmpiSearchParameters();
|
|
||||||
ourLog.info("EMPI search parameters updated");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
package ca.uhn.fhir.jpa.empi.config;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.empi.api.IEmpiSettings;
|
||||||
|
import ca.uhn.fhir.empi.provider.EmpiProviderLoader;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.event.ContextRefreshedEvent;
|
||||||
|
import org.springframework.context.event.EventListener;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class EmpiLoader {
|
||||||
|
private static final Logger ourLog = LoggerFactory.getLogger(EmpiLoader.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
IEmpiSettings myEmpiProperties;
|
||||||
|
@Autowired
|
||||||
|
EmpiProviderLoader myEmpiProviderLoader;
|
||||||
|
@Autowired
|
||||||
|
EmpiSubscriptionLoader myEmpiSubscriptionLoader;
|
||||||
|
@Autowired
|
||||||
|
EmpiSearchParameterLoader myEmpiSearchParameterLoader;
|
||||||
|
|
||||||
|
@EventListener(classes = {ContextRefreshedEvent.class})
|
||||||
|
// This @Order is here to ensure that MatchingQueueSubscriberLoader has initialized before we initialize this.
|
||||||
|
// Otherwise the EMPI subscriptions won't get loaded into the SubscriptionRegistry
|
||||||
|
@Order
|
||||||
|
public void updateSubscriptions() {
|
||||||
|
if (!myEmpiProperties.isEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
myEmpiProviderLoader.loadProvider();
|
||||||
|
ourLog.info("EMPI provider registered");
|
||||||
|
|
||||||
|
myEmpiSubscriptionLoader.daoUpdateEmpiSubscriptions();
|
||||||
|
ourLog.info("EMPI subscriptions updated");
|
||||||
|
|
||||||
|
myEmpiSearchParameterLoader.daoUpdateEmpiSearchParameters();
|
||||||
|
ourLog.info("EMPI search parameters updated");
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,6 +22,7 @@ package ca.uhn.fhir.jpa.empi.config;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.empi.rules.config.EmpiRuleValidator;
|
import ca.uhn.fhir.empi.rules.config.EmpiRuleValidator;
|
||||||
|
import ca.uhn.fhir.jpa.dao.empi.EmpiLinkDeleteSvc;
|
||||||
import ca.uhn.fhir.jpa.empi.interceptor.EmpiSubmitterInterceptorLoader;
|
import ca.uhn.fhir.jpa.empi.interceptor.EmpiSubmitterInterceptorLoader;
|
||||||
import ca.uhn.fhir.jpa.empi.svc.EmpiSearchParamSvc;
|
import ca.uhn.fhir.jpa.empi.svc.EmpiSearchParamSvc;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
@ -44,4 +45,9 @@ public class EmpiSubmitterConfig {
|
||||||
EmpiRuleValidator empiRuleValidator(FhirContext theFhirContext) {
|
EmpiRuleValidator empiRuleValidator(FhirContext theFhirContext) {
|
||||||
return new EmpiRuleValidator(theFhirContext, empiSearchParamSvc());
|
return new EmpiRuleValidator(theFhirContext, empiSearchParamSvc());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
EmpiLinkDeleteSvc empiLinkDeleteSvc() {
|
||||||
|
return new EmpiLinkDeleteSvc();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package ca.uhn.fhir.jpa.dao;
|
package ca.uhn.fhir.jpa.empi.dao;
|
||||||
|
|
||||||
/*-
|
/*-
|
||||||
* #%L
|
* #%L
|
||||||
|
@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.dao;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ca.uhn.fhir.empi.api.EmpiLinkSourceEnum;
|
import ca.uhn.fhir.empi.api.EmpiLinkSourceEnum;
|
||||||
|
import ca.uhn.fhir.empi.api.EmpiMatchOutcome;
|
||||||
import ca.uhn.fhir.empi.api.EmpiMatchResultEnum;
|
import ca.uhn.fhir.empi.api.EmpiMatchResultEnum;
|
||||||
import ca.uhn.fhir.empi.log.Logs;
|
import ca.uhn.fhir.empi.log.Logs;
|
||||||
import ca.uhn.fhir.empi.model.EmpiTransactionContext;
|
import ca.uhn.fhir.empi.model.EmpiTransactionContext;
|
||||||
|
@ -32,7 +33,6 @@ import org.hl7.fhir.r4.model.Patient;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.data.domain.Example;
|
import org.springframework.data.domain.Example;
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.transaction.annotation.Propagation;
|
import org.springframework.transaction.annotation.Propagation;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
@ -43,25 +43,34 @@ import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@Service
|
|
||||||
public class EmpiLinkDaoSvc {
|
public class EmpiLinkDaoSvc {
|
||||||
private static final Logger ourLog = Logs.getEmpiTroubleshootingLog();
|
private static final Logger ourLog = Logs.getEmpiTroubleshootingLog();
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private IEmpiLinkDao myEmpiLinkDao;
|
private IEmpiLinkDao myEmpiLinkDao;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
private EmpiLinkFactory myEmpiLinkFactory;
|
||||||
|
@Autowired
|
||||||
private IdHelperService myIdHelperService;
|
private IdHelperService myIdHelperService;
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public EmpiLink createOrUpdateLinkEntity(IBaseResource thePerson, IBaseResource theTarget, EmpiMatchResultEnum theMatchResult, EmpiLinkSourceEnum theLinkSource, @Nullable EmpiTransactionContext theEmpiTransactionContext) {
|
public EmpiLink createOrUpdateLinkEntity(IBaseResource thePerson, IBaseResource theTarget, EmpiMatchOutcome theMatchOutcome, EmpiLinkSourceEnum theLinkSource, @Nullable EmpiTransactionContext theEmpiTransactionContext) {
|
||||||
Long personPid = myIdHelperService.getPidOrNull(thePerson);
|
Long personPid = myIdHelperService.getPidOrNull(thePerson);
|
||||||
Long resourcePid = myIdHelperService.getPidOrNull(theTarget);
|
Long resourcePid = myIdHelperService.getPidOrNull(theTarget);
|
||||||
|
|
||||||
EmpiLink empiLink = getOrCreateEmpiLinkByPersonPidAndTargetPid(personPid, resourcePid);
|
EmpiLink empiLink = getOrCreateEmpiLinkByPersonPidAndTargetPid(personPid, resourcePid);
|
||||||
empiLink.setLinkSource(theLinkSource);
|
empiLink.setLinkSource(theLinkSource);
|
||||||
empiLink.setMatchResult(theMatchResult);
|
empiLink.setMatchResult(theMatchOutcome.getMatchResultEnum());
|
||||||
|
// Preserve these flags for link updates
|
||||||
|
empiLink.setEidMatch(theMatchOutcome.isEidMatch() | empiLink.isEidMatch());
|
||||||
|
empiLink.setNewPerson(theMatchOutcome.isNewPerson() | empiLink.isNewPerson());
|
||||||
|
if (empiLink.getScore() != null) {
|
||||||
|
empiLink.setScore(Math.max(theMatchOutcome.score, empiLink.getScore()));
|
||||||
|
} else {
|
||||||
|
empiLink.setScore(theMatchOutcome.score);
|
||||||
|
}
|
||||||
|
|
||||||
String message = String.format("Creating EmpiLink from %s to %s -> %s", thePerson.getIdElement().toUnqualifiedVersionless(), theTarget.getIdElement().toUnqualifiedVersionless(), theMatchResult);
|
String message = String.format("Creating EmpiLink from %s to %s -> %s", thePerson.getIdElement().toUnqualifiedVersionless(), theTarget.getIdElement().toUnqualifiedVersionless(), theMatchOutcome);
|
||||||
theEmpiTransactionContext.addTransactionLogMessage(message);
|
theEmpiTransactionContext.addTransactionLogMessage(message);
|
||||||
ourLog.debug(message);
|
ourLog.debug(message);
|
||||||
save(empiLink);
|
save(empiLink);
|
||||||
|
@ -75,7 +84,7 @@ public class EmpiLinkDaoSvc {
|
||||||
if (oExisting.isPresent()) {
|
if (oExisting.isPresent()) {
|
||||||
return oExisting.get();
|
return oExisting.get();
|
||||||
} else {
|
} else {
|
||||||
EmpiLink empiLink = new EmpiLink();
|
EmpiLink empiLink = myEmpiLinkFactory.newEmpiLink();
|
||||||
empiLink.setPersonPid(thePersonPid);
|
empiLink.setPersonPid(thePersonPid);
|
||||||
empiLink.setTargetPid(theResourcePid);
|
empiLink.setTargetPid(theResourcePid);
|
||||||
return empiLink;
|
return empiLink;
|
||||||
|
@ -87,7 +96,7 @@ public class EmpiLinkDaoSvc {
|
||||||
if (theTargetPid == null || thePersonPid == null) {
|
if (theTargetPid == null || thePersonPid == null) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
EmpiLink link = new EmpiLink();
|
EmpiLink link = myEmpiLinkFactory.newEmpiLink();
|
||||||
link.setTargetPid(theTargetPid);
|
link.setTargetPid(theTargetPid);
|
||||||
link.setPersonPid(thePersonPid);
|
link.setPersonPid(thePersonPid);
|
||||||
Example<EmpiLink> example = Example.of(link);
|
Example<EmpiLink> example = Example.of(link);
|
||||||
|
@ -95,7 +104,7 @@ public class EmpiLinkDaoSvc {
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<EmpiLink> getEmpiLinksByTargetPidAndMatchResult(Long theTargetPid, EmpiMatchResultEnum theMatchResult) {
|
public List<EmpiLink> getEmpiLinksByTargetPidAndMatchResult(Long theTargetPid, EmpiMatchResultEnum theMatchResult) {
|
||||||
EmpiLink exampleLink = new EmpiLink();
|
EmpiLink exampleLink = myEmpiLinkFactory.newEmpiLink();
|
||||||
exampleLink.setTargetPid(theTargetPid);
|
exampleLink.setTargetPid(theTargetPid);
|
||||||
exampleLink.setMatchResult(theMatchResult);
|
exampleLink.setMatchResult(theMatchResult);
|
||||||
Example<EmpiLink> example = Example.of(exampleLink);
|
Example<EmpiLink> example = Example.of(exampleLink);
|
||||||
|
@ -103,7 +112,7 @@ public class EmpiLinkDaoSvc {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<EmpiLink> getMatchedLinkForTargetPid(Long theTargetPid) {
|
public Optional<EmpiLink> getMatchedLinkForTargetPid(Long theTargetPid) {
|
||||||
EmpiLink exampleLink = new EmpiLink();
|
EmpiLink exampleLink = myEmpiLinkFactory.newEmpiLink();
|
||||||
exampleLink.setTargetPid(theTargetPid);
|
exampleLink.setTargetPid(theTargetPid);
|
||||||
exampleLink.setMatchResult(EmpiMatchResultEnum.MATCH);
|
exampleLink.setMatchResult(EmpiMatchResultEnum.MATCH);
|
||||||
Example<EmpiLink> example = Example.of(exampleLink);
|
Example<EmpiLink> example = Example.of(exampleLink);
|
||||||
|
@ -116,7 +125,7 @@ public class EmpiLinkDaoSvc {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
EmpiLink exampleLink = new EmpiLink();
|
EmpiLink exampleLink = myEmpiLinkFactory.newEmpiLink();
|
||||||
exampleLink.setTargetPid(pid);
|
exampleLink.setTargetPid(pid);
|
||||||
exampleLink.setMatchResult(EmpiMatchResultEnum.MATCH);
|
exampleLink.setMatchResult(EmpiMatchResultEnum.MATCH);
|
||||||
Example<EmpiLink> example = Example.of(exampleLink);
|
Example<EmpiLink> example = Example.of(exampleLink);
|
||||||
|
@ -124,7 +133,7 @@ public class EmpiLinkDaoSvc {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<EmpiLink> getEmpiLinksByPersonPidTargetPidAndMatchResult(Long thePersonPid, Long theTargetPid, EmpiMatchResultEnum theMatchResult) {
|
public Optional<EmpiLink> getEmpiLinksByPersonPidTargetPidAndMatchResult(Long thePersonPid, Long theTargetPid, EmpiMatchResultEnum theMatchResult) {
|
||||||
EmpiLink exampleLink = new EmpiLink();
|
EmpiLink exampleLink = myEmpiLinkFactory.newEmpiLink();
|
||||||
exampleLink.setPersonPid(thePersonPid);
|
exampleLink.setPersonPid(thePersonPid);
|
||||||
exampleLink.setTargetPid(theTargetPid);
|
exampleLink.setTargetPid(theTargetPid);
|
||||||
exampleLink.setMatchResult(theMatchResult);
|
exampleLink.setMatchResult(theMatchResult);
|
||||||
|
@ -138,7 +147,7 @@ public class EmpiLinkDaoSvc {
|
||||||
* @return A list of EmpiLinks that hold potential duplicate persons.
|
* @return A list of EmpiLinks that hold potential duplicate persons.
|
||||||
*/
|
*/
|
||||||
public List<EmpiLink> getPossibleDuplicates() {
|
public List<EmpiLink> getPossibleDuplicates() {
|
||||||
EmpiLink exampleLink = new EmpiLink();
|
EmpiLink exampleLink = myEmpiLinkFactory.newEmpiLink();
|
||||||
exampleLink.setMatchResult(EmpiMatchResultEnum.POSSIBLE_DUPLICATE);
|
exampleLink.setMatchResult(EmpiMatchResultEnum.POSSIBLE_DUPLICATE);
|
||||||
Example<EmpiLink> example = Example.of(exampleLink);
|
Example<EmpiLink> example = Example.of(exampleLink);
|
||||||
return myEmpiLinkDao.findAll(example);
|
return myEmpiLinkDao.findAll(example);
|
||||||
|
@ -149,7 +158,7 @@ public class EmpiLinkDaoSvc {
|
||||||
if (pid == null) {
|
if (pid == null) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
EmpiLink empiLink = new EmpiLink().setTargetPid(pid);
|
EmpiLink empiLink = myEmpiLinkFactory.newEmpiLink().setTargetPid(pid);
|
||||||
Example<EmpiLink> example = Example.of(empiLink);
|
Example<EmpiLink> example = Example.of(empiLink);
|
||||||
return myEmpiLinkDao.findOne(example);
|
return myEmpiLinkDao.findOne(example);
|
||||||
}
|
}
|
||||||
|
@ -159,26 +168,12 @@ public class EmpiLinkDaoSvc {
|
||||||
myEmpiLinkDao.delete(theEmpiLink);
|
myEmpiLinkDao.delete(theEmpiLink);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete all EmpiLink records with any reference to this resource. (Used by Expunge.)
|
|
||||||
* @param theResource
|
|
||||||
* @return the number of records deleted
|
|
||||||
*/
|
|
||||||
public int deleteWithAnyReferenceTo(IBaseResource theResource) {
|
|
||||||
Long pid = myIdHelperService.getPidOrThrowException(theResource.getIdElement(), null);
|
|
||||||
int removed = myEmpiLinkDao.deleteWithAnyReferenceToPid(pid);
|
|
||||||
if (removed > 0) {
|
|
||||||
ourLog.info("Removed {} EMPI links with references to {}", removed, theResource.getIdElement().toVersionless());
|
|
||||||
}
|
|
||||||
return removed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<EmpiLink> findEmpiLinksByPersonId(IBaseResource thePersonResource) {
|
public List<EmpiLink> findEmpiLinksByPersonId(IBaseResource thePersonResource) {
|
||||||
Long pid = myIdHelperService.getPidOrNull(thePersonResource);
|
Long pid = myIdHelperService.getPidOrNull(thePersonResource);
|
||||||
if (pid == null) {
|
if (pid == null) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
EmpiLink empiLink = new EmpiLink().setPersonPid(pid);
|
EmpiLink empiLink = myEmpiLinkFactory.newEmpiLink().setPersonPid(pid);
|
||||||
Example<EmpiLink> example = Example.of(empiLink);
|
Example<EmpiLink> example = Example.of(empiLink);
|
||||||
return myEmpiLinkDao.findAll(example);
|
return myEmpiLinkDao.findAll(example);
|
||||||
}
|
}
|
||||||
|
@ -200,8 +195,12 @@ public class EmpiLinkDaoSvc {
|
||||||
if (pid == null) {
|
if (pid == null) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
EmpiLink empiLink = new EmpiLink().setTargetPid(pid);
|
EmpiLink empiLink = myEmpiLinkFactory.newEmpiLink().setTargetPid(pid);
|
||||||
Example<EmpiLink> example = Example.of(empiLink);
|
Example<EmpiLink> example = Example.of(empiLink);
|
||||||
return myEmpiLinkDao.findAll(example);
|
return myEmpiLinkDao.findAll(example);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public EmpiLink newEmpiLink() {
|
||||||
|
return myEmpiLinkFactory.newEmpiLink();
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package ca.uhn.fhir.jpa.empi.dao;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.empi.api.IEmpiSettings;
|
||||||
|
import ca.uhn.fhir.jpa.entity.EmpiLink;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
|
public class EmpiLinkFactory {
|
||||||
|
private final IEmpiSettings myEmpiSettings;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public EmpiLinkFactory(IEmpiSettings theEmpiSettings) {
|
||||||
|
myEmpiSettings = theEmpiSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EmpiLink newEmpiLink() {
|
||||||
|
return new EmpiLink(myEmpiSettings.getRuleVersion());
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,7 +29,7 @@ import ca.uhn.fhir.empi.util.EmpiUtil;
|
||||||
import ca.uhn.fhir.empi.util.PersonHelper;
|
import ca.uhn.fhir.empi.util.PersonHelper;
|
||||||
import ca.uhn.fhir.interceptor.api.Hook;
|
import ca.uhn.fhir.interceptor.api.Hook;
|
||||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||||
import ca.uhn.fhir.jpa.dao.EmpiLinkDaoSvc;
|
import ca.uhn.fhir.jpa.dao.empi.EmpiLinkDeleteSvc;
|
||||||
import ca.uhn.fhir.jpa.dao.expunge.ExpungeEverythingService;
|
import ca.uhn.fhir.jpa.dao.expunge.ExpungeEverythingService;
|
||||||
import ca.uhn.fhir.jpa.entity.EmpiLink;
|
import ca.uhn.fhir.jpa.entity.EmpiLink;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
|
@ -50,7 +50,7 @@ public class EmpiStorageInterceptor implements IEmpiStorageInterceptor {
|
||||||
@Autowired
|
@Autowired
|
||||||
private ExpungeEverythingService myExpungeEverythingService;
|
private ExpungeEverythingService myExpungeEverythingService;
|
||||||
@Autowired
|
@Autowired
|
||||||
private EmpiLinkDaoSvc myEmpiLinkDaoSvc;
|
private EmpiLinkDeleteSvc myEmpiLinkDeleteSvc;
|
||||||
@Autowired
|
@Autowired
|
||||||
private FhirContext myFhirContext;
|
private FhirContext myFhirContext;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
@ -87,7 +87,7 @@ public class EmpiStorageInterceptor implements IEmpiStorageInterceptor {
|
||||||
if (EmpiUtil.isEmpiManagedPerson(myFhirContext, theNewResource) &&
|
if (EmpiUtil.isEmpiManagedPerson(myFhirContext, theNewResource) &&
|
||||||
myPersonHelper.isDeactivated(theNewResource)) {
|
myPersonHelper.isDeactivated(theNewResource)) {
|
||||||
ourLog.debug("Deleting empi links to deactivated Person {}", theNewResource.getIdElement().toUnqualifiedVersionless());
|
ourLog.debug("Deleting empi links to deactivated Person {}", theNewResource.getIdElement().toUnqualifiedVersionless());
|
||||||
myEmpiLinkDaoSvc.deleteWithAnyReferenceTo(theNewResource);
|
myEmpiLinkDeleteSvc.deleteWithAnyReferenceTo(theNewResource);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isInternalRequest(theRequestDetails)) {
|
if (isInternalRequest(theRequestDetails)) {
|
||||||
|
@ -106,7 +106,7 @@ public class EmpiStorageInterceptor implements IEmpiStorageInterceptor {
|
||||||
if (!EmpiUtil.isEmpiResourceType(myFhirContext, theResource)) {
|
if (!EmpiUtil.isEmpiResourceType(myFhirContext, theResource)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
myEmpiLinkDaoSvc.deleteWithAnyReferenceTo(theResource);
|
myEmpiLinkDeleteSvc.deleteWithAnyReferenceTo(theResource);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void forbidIfModifyingExternalEidOnTarget(IBaseResource theNewResource, IBaseResource theOldResource) {
|
private void forbidIfModifyingExternalEidOnTarget(IBaseResource theNewResource, IBaseResource theOldResource) {
|
||||||
|
@ -181,6 +181,6 @@ public class EmpiStorageInterceptor implements IEmpiStorageInterceptor {
|
||||||
@Hook(Pointcut.STORAGE_PRESTORAGE_EXPUNGE_RESOURCE)
|
@Hook(Pointcut.STORAGE_PRESTORAGE_EXPUNGE_RESOURCE)
|
||||||
public void expungeAllMatchedEmpiLinks(AtomicInteger theCounter, IBaseResource theResource) {
|
public void expungeAllMatchedEmpiLinks(AtomicInteger theCounter, IBaseResource theResource) {
|
||||||
ourLog.debug("Expunging EmpiLink records with reference to {}", theResource.getIdElement());
|
ourLog.debug("Expunging EmpiLink records with reference to {}", theResource.getIdElement());
|
||||||
theCounter.addAndGet(myEmpiLinkDaoSvc.deleteWithAnyReferenceTo(theResource));
|
theCounter.addAndGet(myEmpiLinkDeleteSvc.deleteWithAnyReferenceTo(theResource));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ package ca.uhn.fhir.jpa.empi.svc;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ca.uhn.fhir.empi.api.EmpiLinkSourceEnum;
|
import ca.uhn.fhir.empi.api.EmpiLinkSourceEnum;
|
||||||
import ca.uhn.fhir.empi.api.EmpiMatchResultEnum;
|
import ca.uhn.fhir.empi.api.EmpiMatchOutcome;
|
||||||
import ca.uhn.fhir.empi.api.IEmpiLinkSvc;
|
import ca.uhn.fhir.empi.api.IEmpiLinkSvc;
|
||||||
import ca.uhn.fhir.empi.api.IEmpiSettings;
|
import ca.uhn.fhir.empi.api.IEmpiSettings;
|
||||||
import ca.uhn.fhir.empi.log.Logs;
|
import ca.uhn.fhir.empi.log.Logs;
|
||||||
|
@ -29,7 +29,9 @@ import ca.uhn.fhir.empi.model.CanonicalEID;
|
||||||
import ca.uhn.fhir.empi.model.EmpiTransactionContext;
|
import ca.uhn.fhir.empi.model.EmpiTransactionContext;
|
||||||
import ca.uhn.fhir.empi.util.EIDHelper;
|
import ca.uhn.fhir.empi.util.EIDHelper;
|
||||||
import ca.uhn.fhir.empi.util.PersonHelper;
|
import ca.uhn.fhir.empi.util.PersonHelper;
|
||||||
import ca.uhn.fhir.jpa.dao.EmpiLinkDaoSvc;
|
import ca.uhn.fhir.jpa.empi.dao.EmpiLinkDaoSvc;
|
||||||
|
import ca.uhn.fhir.jpa.empi.svc.candidate.EmpiPersonFindingSvc;
|
||||||
|
import ca.uhn.fhir.jpa.empi.svc.candidate.MatchedPersonCandidate;
|
||||||
import ca.uhn.fhir.jpa.entity.EmpiLink;
|
import ca.uhn.fhir.jpa.entity.EmpiLink;
|
||||||
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
|
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
|
||||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||||
|
@ -107,16 +109,16 @@ public class EmpiEidUpdateService {
|
||||||
private void createNewPersonAndFlagAsDuplicate(IAnyResource theResource, EmpiTransactionContext theEmpiTransactionContext, IAnyResource theOldPerson) {
|
private void createNewPersonAndFlagAsDuplicate(IAnyResource theResource, EmpiTransactionContext theEmpiTransactionContext, IAnyResource theOldPerson) {
|
||||||
log(theEmpiTransactionContext, "Duplicate detected based on the fact that both resources have different external EIDs.");
|
log(theEmpiTransactionContext, "Duplicate detected based on the fact that both resources have different external EIDs.");
|
||||||
IAnyResource newPerson = myPersonHelper.createPersonFromEmpiTarget(theResource);
|
IAnyResource newPerson = myPersonHelper.createPersonFromEmpiTarget(theResource);
|
||||||
myEmpiLinkSvc.updateLink(newPerson, theResource, EmpiMatchResultEnum.MATCH, EmpiLinkSourceEnum.AUTO, theEmpiTransactionContext);
|
myEmpiLinkSvc.updateLink(newPerson, theResource, EmpiMatchOutcome.NEW_PERSON_MATCH, EmpiLinkSourceEnum.AUTO, theEmpiTransactionContext);
|
||||||
myEmpiLinkSvc.updateLink(newPerson, theOldPerson, EmpiMatchResultEnum.POSSIBLE_DUPLICATE, EmpiLinkSourceEnum.AUTO, theEmpiTransactionContext);
|
myEmpiLinkSvc.updateLink(newPerson, theOldPerson, EmpiMatchOutcome.POSSIBLE_DUPLICATE, EmpiLinkSourceEnum.AUTO, theEmpiTransactionContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void linkToNewPersonAndFlagAsDuplicate(IAnyResource theResource, IAnyResource theOldPerson, IAnyResource theNewPerson, EmpiTransactionContext theEmpiTransactionContext) {
|
private void linkToNewPersonAndFlagAsDuplicate(IAnyResource theResource, IAnyResource theOldPerson, IAnyResource theNewPerson, EmpiTransactionContext theEmpiTransactionContext) {
|
||||||
log(theEmpiTransactionContext, "Changing a match link!");
|
log(theEmpiTransactionContext, "Changing a match link!");
|
||||||
myEmpiLinkSvc.deleteLink(theOldPerson, theResource, theEmpiTransactionContext);
|
myEmpiLinkSvc.deleteLink(theOldPerson, theResource, theEmpiTransactionContext);
|
||||||
myEmpiLinkSvc.updateLink(theNewPerson, theResource, EmpiMatchResultEnum.MATCH, EmpiLinkSourceEnum.AUTO, theEmpiTransactionContext);
|
myEmpiLinkSvc.updateLink(theNewPerson, theResource, EmpiMatchOutcome.NEW_PERSON_MATCH, EmpiLinkSourceEnum.AUTO, theEmpiTransactionContext);
|
||||||
log(theEmpiTransactionContext, "Duplicate detected based on the fact that both resources have different external EIDs.");
|
log(theEmpiTransactionContext, "Duplicate detected based on the fact that both resources have different external EIDs.");
|
||||||
myEmpiLinkSvc.updateLink(theNewPerson, theOldPerson, EmpiMatchResultEnum.POSSIBLE_DUPLICATE, EmpiLinkSourceEnum.AUTO, theEmpiTransactionContext);
|
myEmpiLinkSvc.updateLink(theNewPerson, theOldPerson, EmpiMatchOutcome.POSSIBLE_DUPLICATE, EmpiLinkSourceEnum.AUTO, theEmpiTransactionContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void log(EmpiTransactionContext theEmpiTransactionContext, String theMessage) {
|
private void log(EmpiTransactionContext theEmpiTransactionContext, String theMessage) {
|
||||||
|
|
|
@ -25,8 +25,8 @@ import ca.uhn.fhir.empi.api.EmpiLinkSourceEnum;
|
||||||
import ca.uhn.fhir.empi.api.EmpiMatchResultEnum;
|
import ca.uhn.fhir.empi.api.EmpiMatchResultEnum;
|
||||||
import ca.uhn.fhir.empi.api.IEmpiLinkQuerySvc;
|
import ca.uhn.fhir.empi.api.IEmpiLinkQuerySvc;
|
||||||
import ca.uhn.fhir.empi.model.EmpiTransactionContext;
|
import ca.uhn.fhir.empi.model.EmpiTransactionContext;
|
||||||
import ca.uhn.fhir.jpa.dao.EmpiLinkDaoSvc;
|
|
||||||
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
||||||
|
import ca.uhn.fhir.jpa.empi.dao.EmpiLinkDaoSvc;
|
||||||
import ca.uhn.fhir.jpa.entity.EmpiLink;
|
import ca.uhn.fhir.jpa.entity.EmpiLink;
|
||||||
import ca.uhn.fhir.util.ParametersUtil;
|
import ca.uhn.fhir.util.ParametersUtil;
|
||||||
import org.hl7.fhir.instance.model.api.IBase;
|
import org.hl7.fhir.instance.model.api.IBase;
|
||||||
|
@ -82,13 +82,16 @@ public class EmpiLinkQuerySvcImpl implements IEmpiLinkQuerySvc {
|
||||||
if (includeResultAndSource) {
|
if (includeResultAndSource) {
|
||||||
ParametersUtil.addPartString(myFhirContext, resultPart, "matchResult", empiLink.getMatchResult().name());
|
ParametersUtil.addPartString(myFhirContext, resultPart, "matchResult", empiLink.getMatchResult().name());
|
||||||
ParametersUtil.addPartString(myFhirContext, resultPart, "linkSource", empiLink.getLinkSource().name());
|
ParametersUtil.addPartString(myFhirContext, resultPart, "linkSource", empiLink.getLinkSource().name());
|
||||||
|
ParametersUtil.addPartBoolean(myFhirContext, resultPart, "eidMatch", empiLink.getEidMatch());
|
||||||
|
ParametersUtil.addPartBoolean(myFhirContext, resultPart, "newPerson", empiLink.getNewPerson());
|
||||||
|
ParametersUtil.addPartDecimal(myFhirContext, resultPart, "score", empiLink.getScore());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Example<EmpiLink> exampleLinkFromParameters(IIdType thePersonId, IIdType theTargetId, EmpiMatchResultEnum theMatchResult, EmpiLinkSourceEnum theLinkSource) {
|
private Example<EmpiLink> exampleLinkFromParameters(IIdType thePersonId, IIdType theTargetId, EmpiMatchResultEnum theMatchResult, EmpiLinkSourceEnum theLinkSource) {
|
||||||
EmpiLink empiLink = new EmpiLink();
|
EmpiLink empiLink = myEmpiLinkDaoSvc.newEmpiLink();
|
||||||
if (thePersonId != null) {
|
if (thePersonId != null) {
|
||||||
empiLink.setPersonPid(myIdHelperService.getPidOrThrowException(thePersonId));
|
empiLink.setPersonPid(myIdHelperService.getPidOrThrowException(thePersonId));
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.empi.svc;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ca.uhn.fhir.empi.api.EmpiLinkSourceEnum;
|
import ca.uhn.fhir.empi.api.EmpiLinkSourceEnum;
|
||||||
|
import ca.uhn.fhir.empi.api.EmpiMatchOutcome;
|
||||||
import ca.uhn.fhir.empi.api.EmpiMatchResultEnum;
|
import ca.uhn.fhir.empi.api.EmpiMatchResultEnum;
|
||||||
import ca.uhn.fhir.empi.api.IEmpiLinkSvc;
|
import ca.uhn.fhir.empi.api.IEmpiLinkSvc;
|
||||||
import ca.uhn.fhir.empi.log.Logs;
|
import ca.uhn.fhir.empi.log.Logs;
|
||||||
|
@ -28,8 +29,8 @@ import ca.uhn.fhir.empi.model.CanonicalIdentityAssuranceLevel;
|
||||||
import ca.uhn.fhir.empi.model.EmpiTransactionContext;
|
import ca.uhn.fhir.empi.model.EmpiTransactionContext;
|
||||||
import ca.uhn.fhir.empi.util.AssuranceLevelUtil;
|
import ca.uhn.fhir.empi.util.AssuranceLevelUtil;
|
||||||
import ca.uhn.fhir.empi.util.PersonHelper;
|
import ca.uhn.fhir.empi.util.PersonHelper;
|
||||||
import ca.uhn.fhir.jpa.dao.EmpiLinkDaoSvc;
|
|
||||||
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
||||||
|
import ca.uhn.fhir.jpa.empi.dao.EmpiLinkDaoSvc;
|
||||||
import ca.uhn.fhir.jpa.entity.EmpiLink;
|
import ca.uhn.fhir.jpa.entity.EmpiLink;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||||
|
@ -63,24 +64,25 @@ public class EmpiLinkSvcImpl implements IEmpiLinkSvc {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public void updateLink(IAnyResource thePerson, IAnyResource theTarget, EmpiMatchResultEnum theMatchResult, EmpiLinkSourceEnum theLinkSource, EmpiTransactionContext theEmpiTransactionContext) {
|
public void updateLink(IAnyResource thePerson, IAnyResource theTarget, EmpiMatchOutcome theMatchOutcome, EmpiLinkSourceEnum theLinkSource, EmpiTransactionContext theEmpiTransactionContext) {
|
||||||
IIdType resourceId = theTarget.getIdElement().toUnqualifiedVersionless();
|
IIdType resourceId = theTarget.getIdElement().toUnqualifiedVersionless();
|
||||||
|
|
||||||
if (theMatchResult == EmpiMatchResultEnum.POSSIBLE_DUPLICATE && personsLinkedAsNoMatch(thePerson, theTarget)) {
|
if (theMatchOutcome.isPossibleDuplicate() && personsLinkedAsNoMatch(thePerson, theTarget)) {
|
||||||
log(theEmpiTransactionContext, thePerson.getIdElement().toUnqualifiedVersionless() +
|
log(theEmpiTransactionContext, thePerson.getIdElement().toUnqualifiedVersionless() +
|
||||||
" is linked as NO_MATCH with " +
|
" is linked as NO_MATCH with " +
|
||||||
theTarget.getIdElement().toUnqualifiedVersionless() +
|
theTarget.getIdElement().toUnqualifiedVersionless() +
|
||||||
" not linking as POSSIBLE_DUPLICATE.");
|
" not linking as POSSIBLE_DUPLICATE.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
validateRequestIsLegal(thePerson, theTarget, theMatchResult, theLinkSource);
|
EmpiMatchResultEnum matchResultEnum = theMatchOutcome.getMatchResultEnum();
|
||||||
switch (theMatchResult) {
|
validateRequestIsLegal(thePerson, theTarget, matchResultEnum, theLinkSource);
|
||||||
|
switch (matchResultEnum) {
|
||||||
case MATCH:
|
case MATCH:
|
||||||
myPersonHelper.addOrUpdateLink(thePerson, resourceId, AssuranceLevelUtil.getAssuranceLevel(theMatchResult, theLinkSource), theEmpiTransactionContext);
|
myPersonHelper.addOrUpdateLink(thePerson, resourceId, AssuranceLevelUtil.getAssuranceLevel(matchResultEnum, theLinkSource), theEmpiTransactionContext);
|
||||||
myEmpiResourceDaoSvc.updatePerson(thePerson);
|
myEmpiResourceDaoSvc.updatePerson(thePerson);
|
||||||
break;
|
break;
|
||||||
case POSSIBLE_MATCH:
|
case POSSIBLE_MATCH:
|
||||||
myPersonHelper.addOrUpdateLink(thePerson, resourceId, AssuranceLevelUtil.getAssuranceLevel(theMatchResult, theLinkSource), theEmpiTransactionContext);
|
myPersonHelper.addOrUpdateLink(thePerson, resourceId, AssuranceLevelUtil.getAssuranceLevel(matchResultEnum, theLinkSource), theEmpiTransactionContext);
|
||||||
break;
|
break;
|
||||||
case NO_MATCH:
|
case NO_MATCH:
|
||||||
myPersonHelper.removeLink(thePerson, resourceId, theEmpiTransactionContext);
|
myPersonHelper.removeLink(thePerson, resourceId, theEmpiTransactionContext);
|
||||||
|
@ -89,7 +91,7 @@ public class EmpiLinkSvcImpl implements IEmpiLinkSvc {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
myEmpiResourceDaoSvc.updatePerson(thePerson);
|
myEmpiResourceDaoSvc.updatePerson(thePerson);
|
||||||
createOrUpdateLinkEntity(thePerson, theTarget, theMatchResult, theLinkSource, theEmpiTransactionContext);
|
createOrUpdateLinkEntity(thePerson, theTarget, theMatchOutcome, theLinkSource, theEmpiTransactionContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean personsLinkedAsNoMatch(IAnyResource thePerson, IAnyResource theTarget) {
|
private boolean personsLinkedAsNoMatch(IAnyResource thePerson, IAnyResource theTarget) {
|
||||||
|
@ -175,8 +177,8 @@ public class EmpiLinkSvcImpl implements IEmpiLinkSvc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createOrUpdateLinkEntity(IBaseResource thePerson, IBaseResource theResource, EmpiMatchResultEnum theMatchResult, EmpiLinkSourceEnum theLinkSource, EmpiTransactionContext theEmpiTransactionContext) {
|
private void createOrUpdateLinkEntity(IBaseResource thePerson, IBaseResource theResource, EmpiMatchOutcome theMatchOutcome, EmpiLinkSourceEnum theLinkSource, EmpiTransactionContext theEmpiTransactionContext) {
|
||||||
myEmpiLinkDaoSvc.createOrUpdateLinkEntity(thePerson, theResource, theMatchResult, theLinkSource, theEmpiTransactionContext);
|
myEmpiLinkDaoSvc.createOrUpdateLinkEntity(thePerson, theResource, theMatchOutcome, theLinkSource, theEmpiTransactionContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void log(EmpiTransactionContext theEmpiTransactionContext, String theMessage) {
|
private void log(EmpiTransactionContext theEmpiTransactionContext, String theMessage) {
|
||||||
|
|
|
@ -29,8 +29,8 @@ import ca.uhn.fhir.empi.api.IEmpiLinkUpdaterSvc;
|
||||||
import ca.uhn.fhir.empi.log.Logs;
|
import ca.uhn.fhir.empi.log.Logs;
|
||||||
import ca.uhn.fhir.empi.model.EmpiTransactionContext;
|
import ca.uhn.fhir.empi.model.EmpiTransactionContext;
|
||||||
import ca.uhn.fhir.empi.util.EmpiUtil;
|
import ca.uhn.fhir.empi.util.EmpiUtil;
|
||||||
import ca.uhn.fhir.jpa.dao.EmpiLinkDaoSvc;
|
|
||||||
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
||||||
|
import ca.uhn.fhir.jpa.empi.dao.EmpiLinkDaoSvc;
|
||||||
import ca.uhn.fhir.jpa.entity.EmpiLink;
|
import ca.uhn.fhir.jpa.entity.EmpiLink;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
import ca.uhn.fhir.rest.server.provider.ProviderConstants;
|
import ca.uhn.fhir.rest.server.provider.ProviderConstants;
|
||||||
|
|
|
@ -23,6 +23,7 @@ package ca.uhn.fhir.jpa.empi.svc;
|
||||||
import ca.uhn.fhir.empi.api.IEmpiMatchFinderSvc;
|
import ca.uhn.fhir.empi.api.IEmpiMatchFinderSvc;
|
||||||
import ca.uhn.fhir.empi.api.MatchedTarget;
|
import ca.uhn.fhir.empi.api.MatchedTarget;
|
||||||
import ca.uhn.fhir.empi.rules.svc.EmpiResourceMatcherSvc;
|
import ca.uhn.fhir.empi.rules.svc.EmpiResourceMatcherSvc;
|
||||||
|
import ca.uhn.fhir.jpa.empi.svc.candidate.EmpiCandidateSearchSvc;
|
||||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
|
@ -21,20 +21,23 @@ package ca.uhn.fhir.jpa.empi.svc;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ca.uhn.fhir.empi.api.EmpiLinkSourceEnum;
|
import ca.uhn.fhir.empi.api.EmpiLinkSourceEnum;
|
||||||
import ca.uhn.fhir.empi.api.EmpiMatchResultEnum;
|
import ca.uhn.fhir.empi.api.EmpiMatchOutcome;
|
||||||
import ca.uhn.fhir.empi.api.IEmpiLinkSvc;
|
import ca.uhn.fhir.empi.api.IEmpiLinkSvc;
|
||||||
import ca.uhn.fhir.empi.log.Logs;
|
import ca.uhn.fhir.empi.log.Logs;
|
||||||
import ca.uhn.fhir.empi.model.EmpiTransactionContext;
|
import ca.uhn.fhir.empi.model.EmpiTransactionContext;
|
||||||
import ca.uhn.fhir.empi.util.EmpiUtil;
|
import ca.uhn.fhir.empi.util.EmpiUtil;
|
||||||
import ca.uhn.fhir.empi.util.PersonHelper;
|
import ca.uhn.fhir.empi.util.PersonHelper;
|
||||||
|
import ca.uhn.fhir.jpa.empi.svc.candidate.CandidateList;
|
||||||
|
import ca.uhn.fhir.jpa.empi.svc.candidate.EmpiPersonFindingSvc;
|
||||||
|
import ca.uhn.fhir.jpa.empi.svc.candidate.MatchedPersonCandidate;
|
||||||
import ca.uhn.fhir.rest.server.TransactionLogMessages;
|
import ca.uhn.fhir.rest.server.TransactionLogMessages;
|
||||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* EmpiMatchLinkSvc is the entrypoint for HAPI's EMPI system. An incoming resource can call
|
* EmpiMatchLinkSvc is the entrypoint for HAPI's EMPI system. An incoming resource can call
|
||||||
|
@ -72,46 +75,47 @@ public class EmpiMatchLinkSvc {
|
||||||
}
|
}
|
||||||
|
|
||||||
private EmpiTransactionContext doEmpiUpdate(IAnyResource theResource, EmpiTransactionContext theEmpiTransactionContext) {
|
private EmpiTransactionContext doEmpiUpdate(IAnyResource theResource, EmpiTransactionContext theEmpiTransactionContext) {
|
||||||
List<MatchedPersonCandidate> personCandidates = myEmpiPersonFindingSvc.findPersonCandidates(theResource);
|
CandidateList candidateList = myEmpiPersonFindingSvc.findPersonCandidates(theResource);
|
||||||
if (personCandidates.isEmpty()) {
|
if (candidateList.isEmpty()) {
|
||||||
handleEmpiWithNoCandidates(theResource, theEmpiTransactionContext);
|
handleEmpiWithNoCandidates(theResource, theEmpiTransactionContext);
|
||||||
} else if (personCandidates.size() == 1) {
|
} else if (candidateList.exactlyOneMatch()) {
|
||||||
handleEmpiWithSingleCandidate(theResource, personCandidates, theEmpiTransactionContext);
|
handleEmpiWithSingleCandidate(theResource, candidateList.getOnlyMatch(), theEmpiTransactionContext);
|
||||||
} else {
|
} else {
|
||||||
handleEmpiWithMultipleCandidates(theResource, personCandidates, theEmpiTransactionContext);
|
handleEmpiWithMultipleCandidates(theResource, candidateList, theEmpiTransactionContext);
|
||||||
}
|
}
|
||||||
return theEmpiTransactionContext;
|
return theEmpiTransactionContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleEmpiWithMultipleCandidates(IAnyResource theResource, List<MatchedPersonCandidate> thePersonCandidates, EmpiTransactionContext theEmpiTransactionContext) {
|
private void handleEmpiWithMultipleCandidates(IAnyResource theResource, CandidateList theCandidateList, EmpiTransactionContext theEmpiTransactionContext) {
|
||||||
Long samplePersonPid = thePersonCandidates.get(0).getCandidatePersonPid().getIdAsLong();
|
MatchedPersonCandidate firstMatch = theCandidateList.getFirstMatch();
|
||||||
boolean allSamePerson = thePersonCandidates.stream()
|
Long samplePersonPid = firstMatch.getCandidatePersonPid().getIdAsLong();
|
||||||
|
boolean allSamePerson = theCandidateList.stream()
|
||||||
.allMatch(candidate -> candidate.getCandidatePersonPid().getIdAsLong().equals(samplePersonPid));
|
.allMatch(candidate -> candidate.getCandidatePersonPid().getIdAsLong().equals(samplePersonPid));
|
||||||
|
|
||||||
if (allSamePerson) {
|
if (allSamePerson) {
|
||||||
log(theEmpiTransactionContext, "EMPI received multiple match candidates, but they are all linked to the same person.");
|
log(theEmpiTransactionContext, "EMPI received multiple match candidates, but they are all linked to the same person.");
|
||||||
handleEmpiWithSingleCandidate(theResource, thePersonCandidates, theEmpiTransactionContext);
|
handleEmpiWithSingleCandidate(theResource, firstMatch, theEmpiTransactionContext);
|
||||||
} else {
|
} else {
|
||||||
log(theEmpiTransactionContext, "EMPI received multiple match candidates, that were linked to different Persons. Setting POSSIBLE_DUPLICATES and POSSIBLE_MATCHES.");
|
log(theEmpiTransactionContext, "EMPI received multiple match candidates, that were linked to different Persons. Setting POSSIBLE_DUPLICATES and POSSIBLE_MATCHES.");
|
||||||
//Set them all as POSSIBLE_MATCH
|
//Set them all as POSSIBLE_MATCH
|
||||||
List<IAnyResource> persons = thePersonCandidates.stream().map((MatchedPersonCandidate matchedPersonCandidate) -> myEmpiPersonFindingSvc.getPersonFromMatchedPersonCandidate(matchedPersonCandidate)).collect(Collectors.toList());
|
List<IAnyResource> persons = new ArrayList<>();
|
||||||
persons.forEach(person -> {
|
for (MatchedPersonCandidate matchedPersonCandidate : theCandidateList.getCandidates()) {
|
||||||
myEmpiLinkSvc.updateLink(person, theResource, EmpiMatchResultEnum.POSSIBLE_MATCH, EmpiLinkSourceEnum.AUTO, theEmpiTransactionContext);
|
IAnyResource person = myEmpiPersonFindingSvc.getPersonFromMatchedPersonCandidate(matchedPersonCandidate);
|
||||||
});
|
myEmpiLinkSvc.updateLink(person, theResource, EmpiMatchOutcome.EID_POSSIBLE_MATCH, EmpiLinkSourceEnum.AUTO, theEmpiTransactionContext);
|
||||||
|
persons.add(person);
|
||||||
|
}
|
||||||
|
|
||||||
//Set all Persons as POSSIBLE_DUPLICATE of the first person.
|
//Set all Persons as POSSIBLE_DUPLICATE of the last person.
|
||||||
IAnyResource samplePerson = persons.get(0);
|
IAnyResource firstPerson = persons.get(0);
|
||||||
persons.subList(1, persons.size()).stream()
|
persons.subList(1, persons.size())
|
||||||
.forEach(possibleDuplicatePerson -> {
|
.forEach(possibleDuplicatePerson -> myEmpiLinkSvc.updateLink(firstPerson, possibleDuplicatePerson, EmpiMatchOutcome.EID_POSSIBLE_DUPLICATE, EmpiLinkSourceEnum.AUTO, theEmpiTransactionContext));
|
||||||
myEmpiLinkSvc.updateLink(samplePerson, possibleDuplicatePerson, EmpiMatchResultEnum.POSSIBLE_DUPLICATE, EmpiLinkSourceEnum.AUTO, theEmpiTransactionContext);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleEmpiWithNoCandidates(IAnyResource theResource, EmpiTransactionContext theEmpiTransactionContext) {
|
private void handleEmpiWithNoCandidates(IAnyResource theResource, EmpiTransactionContext theEmpiTransactionContext) {
|
||||||
log(theEmpiTransactionContext, "There were no matched candidates for EMPI, creating a new Person.");
|
log(theEmpiTransactionContext, "There were no matched candidates for EMPI, creating a new Person.");
|
||||||
IAnyResource newPerson = myPersonHelper.createPersonFromEmpiTarget(theResource);
|
IAnyResource newPerson = myPersonHelper.createPersonFromEmpiTarget(theResource);
|
||||||
myEmpiLinkSvc.updateLink(newPerson, theResource, EmpiMatchResultEnum.MATCH, EmpiLinkSourceEnum.AUTO, theEmpiTransactionContext);
|
myEmpiLinkSvc.updateLink(newPerson, theResource, EmpiMatchOutcome.NEW_PERSON_MATCH, EmpiLinkSourceEnum.AUTO, theEmpiTransactionContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleEmpiCreate(IAnyResource theResource, MatchedPersonCandidate thePersonCandidate, EmpiTransactionContext theEmpiTransactionContext) {
|
private void handleEmpiCreate(IAnyResource theResource, MatchedPersonCandidate thePersonCandidate, EmpiTransactionContext theEmpiTransactionContext) {
|
||||||
|
@ -120,8 +124,8 @@ public class EmpiMatchLinkSvc {
|
||||||
if (myPersonHelper.isPotentialDuplicate(person, theResource)) {
|
if (myPersonHelper.isPotentialDuplicate(person, theResource)) {
|
||||||
log(theEmpiTransactionContext, "Duplicate detected based on the fact that both resources have different external EIDs.");
|
log(theEmpiTransactionContext, "Duplicate detected based on the fact that both resources have different external EIDs.");
|
||||||
IAnyResource newPerson = myPersonHelper.createPersonFromEmpiTarget(theResource);
|
IAnyResource newPerson = myPersonHelper.createPersonFromEmpiTarget(theResource);
|
||||||
myEmpiLinkSvc.updateLink(newPerson, theResource, EmpiMatchResultEnum.MATCH, EmpiLinkSourceEnum.AUTO, theEmpiTransactionContext);
|
myEmpiLinkSvc.updateLink(newPerson, theResource, EmpiMatchOutcome.NEW_PERSON_MATCH, EmpiLinkSourceEnum.AUTO, theEmpiTransactionContext);
|
||||||
myEmpiLinkSvc.updateLink(newPerson, person, EmpiMatchResultEnum.POSSIBLE_DUPLICATE, EmpiLinkSourceEnum.AUTO, theEmpiTransactionContext);
|
myEmpiLinkSvc.updateLink(newPerson, person, EmpiMatchOutcome.POSSIBLE_DUPLICATE, EmpiLinkSourceEnum.AUTO, theEmpiTransactionContext);
|
||||||
} else {
|
} else {
|
||||||
if (thePersonCandidate.isMatch()) {
|
if (thePersonCandidate.isMatch()) {
|
||||||
myPersonHelper.handleExternalEidAddition(person, theResource, theEmpiTransactionContext);
|
myPersonHelper.handleExternalEidAddition(person, theResource, theEmpiTransactionContext);
|
||||||
|
@ -131,13 +135,12 @@ public class EmpiMatchLinkSvc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleEmpiWithSingleCandidate(IAnyResource theResource, List<MatchedPersonCandidate> thePersonCandidates, EmpiTransactionContext theEmpiTransactionContext) {
|
private void handleEmpiWithSingleCandidate(IAnyResource theResource, MatchedPersonCandidate thePersonCandidate, EmpiTransactionContext theEmpiTransactionContext) {
|
||||||
log(theEmpiTransactionContext, "EMPI has narrowed down to one candidate for matching.");
|
log(theEmpiTransactionContext, "EMPI has narrowed down to one candidate for matching.");
|
||||||
MatchedPersonCandidate matchedPersonCandidate = thePersonCandidates.get(0);
|
|
||||||
if (theEmpiTransactionContext.getRestOperation().equals(EmpiTransactionContext.OperationType.UPDATE)) {
|
if (theEmpiTransactionContext.getRestOperation().equals(EmpiTransactionContext.OperationType.UPDATE)) {
|
||||||
myEidUpdateService.handleEmpiUpdate(theResource, matchedPersonCandidate, theEmpiTransactionContext);
|
myEidUpdateService.handleEmpiUpdate(theResource, thePersonCandidate, theEmpiTransactionContext);
|
||||||
} else {
|
} else {
|
||||||
handleEmpiCreate(theResource, matchedPersonCandidate, theEmpiTransactionContext);
|
handleEmpiCreate(theResource, thePersonCandidate, theEmpiTransactionContext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,183 +0,0 @@
|
||||||
package ca.uhn.fhir.jpa.empi.svc;
|
|
||||||
|
|
||||||
/*-
|
|
||||||
* #%L
|
|
||||||
* HAPI FHIR JPA Server - Enterprise Master Patient Index
|
|
||||||
* %%
|
|
||||||
* Copyright (C) 2014 - 2020 University Health Network
|
|
||||||
* %%
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
* #L%
|
|
||||||
*/
|
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
|
||||||
import ca.uhn.fhir.empi.api.EmpiMatchResultEnum;
|
|
||||||
import ca.uhn.fhir.empi.api.IEmpiMatchFinderSvc;
|
|
||||||
import ca.uhn.fhir.empi.api.MatchedTarget;
|
|
||||||
import ca.uhn.fhir.empi.log.Logs;
|
|
||||||
import ca.uhn.fhir.empi.model.CanonicalEID;
|
|
||||||
import ca.uhn.fhir.empi.util.EIDHelper;
|
|
||||||
import ca.uhn.fhir.jpa.dao.EmpiLinkDaoSvc;
|
|
||||||
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
|
||||||
import ca.uhn.fhir.jpa.entity.EmpiLink;
|
|
||||||
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
|
|
||||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class EmpiPersonFindingSvc {
|
|
||||||
private static final Logger ourLog = Logs.getEmpiTroubleshootingLog();
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private FhirContext myFhirContext;
|
|
||||||
@Autowired
|
|
||||||
IdHelperService myIdHelperService;
|
|
||||||
@Autowired
|
|
||||||
private EmpiLinkDaoSvc myEmpiLinkDaoSvc;
|
|
||||||
@Autowired
|
|
||||||
private EmpiResourceDaoSvc myEmpiResourceDaoSvc;
|
|
||||||
@Autowired
|
|
||||||
private IEmpiMatchFinderSvc myEmpiMatchFinderSvc;
|
|
||||||
@Autowired
|
|
||||||
private EIDHelper myEIDHelper;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given an incoming IBaseResource, limited to Patient/Practitioner, return a list of {@link MatchedPersonCandidate}
|
|
||||||
* indicating possible candidates for a matching Person. Uses several separate methods for finding candidates:
|
|
||||||
* <p>
|
|
||||||
* 0. First, check the incoming Resource for an EID. If it is present, and we can find a Person with this EID, it automatically matches.
|
|
||||||
* 1. First, check link table for any entries where this baseresource is the target of a person. If found, return.
|
|
||||||
* 2. If none are found, attempt to find Person Resources which link to this theResource.
|
|
||||||
* 3. If none are found, attempt to find Persons similar to our incoming resource based on the EMPI rules and similarity metrics.
|
|
||||||
* 4. If none are found, attempt to find Persons that are linked to Patients/Practitioners that are similar to our incoming resource based on the EMPI rules and
|
|
||||||
* similarity metrics.
|
|
||||||
*
|
|
||||||
* @param theResource the {@link IBaseResource} we are attempting to find matching candidate Persons for.
|
|
||||||
* @return A list of {@link MatchedPersonCandidate} indicating all potential Person matches.
|
|
||||||
*/
|
|
||||||
public List<MatchedPersonCandidate> findPersonCandidates(IAnyResource theResource) {
|
|
||||||
List<MatchedPersonCandidate> matchedPersonCandidates = attemptToFindPersonCandidateFromIncomingEID(theResource);
|
|
||||||
if (matchedPersonCandidates.isEmpty()) {
|
|
||||||
matchedPersonCandidates = attemptToFindPersonCandidateFromEmpiLinkTable(theResource);
|
|
||||||
}
|
|
||||||
if (matchedPersonCandidates.isEmpty()) {
|
|
||||||
//OK, so we have not found any links in the EmpiLink table with us as a target. Next, let's find possible Patient/Practitioner
|
|
||||||
//matches by following EMPI rules.
|
|
||||||
|
|
||||||
matchedPersonCandidates = attemptToFindPersonCandidateFromSimilarTargetResource(theResource);
|
|
||||||
}
|
|
||||||
return matchedPersonCandidates;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<MatchedPersonCandidate> attemptToFindPersonCandidateFromIncomingEID(IAnyResource theBaseResource) {
|
|
||||||
List<MatchedPersonCandidate> retval = new ArrayList<>();
|
|
||||||
|
|
||||||
List<CanonicalEID> eidFromResource = myEIDHelper.getExternalEid(theBaseResource);
|
|
||||||
if (!eidFromResource.isEmpty()) {
|
|
||||||
for (CanonicalEID eid : eidFromResource) {
|
|
||||||
Optional<IAnyResource> oFoundPerson = myEmpiResourceDaoSvc.searchPersonByEid(eid.getValue());
|
|
||||||
if (oFoundPerson.isPresent()) {
|
|
||||||
IAnyResource foundPerson = oFoundPerson.get();
|
|
||||||
Long pidOrNull = myIdHelperService.getPidOrNull(foundPerson);
|
|
||||||
MatchedPersonCandidate mpc = new MatchedPersonCandidate(new ResourcePersistentId(pidOrNull), EmpiMatchResultEnum.MATCH);
|
|
||||||
ourLog.debug("Matched {} by EID {}", foundPerson.getIdElement(), eid);
|
|
||||||
retval.add(mpc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempt to find a currently matching Person, based on the presence of an {@link EmpiLink} entity.
|
|
||||||
*
|
|
||||||
* @param theBaseResource the {@link IAnyResource} that we want to find candidate Persons for.
|
|
||||||
* @return an Optional list of {@link MatchedPersonCandidate} indicating matches.
|
|
||||||
*/
|
|
||||||
private List<MatchedPersonCandidate> attemptToFindPersonCandidateFromEmpiLinkTable(IAnyResource theBaseResource) {
|
|
||||||
List<MatchedPersonCandidate> retval = new ArrayList<>();
|
|
||||||
|
|
||||||
Long targetPid = myIdHelperService.getPidOrNull(theBaseResource);
|
|
||||||
if (targetPid != null) {
|
|
||||||
Optional<EmpiLink> oLink = myEmpiLinkDaoSvc.getMatchedLinkForTargetPid(targetPid);
|
|
||||||
if (oLink.isPresent()) {
|
|
||||||
ResourcePersistentId personPid = new ResourcePersistentId(oLink.get().getPersonPid());
|
|
||||||
ourLog.debug("Resource previously linked. Using existing link.");
|
|
||||||
retval.add(new MatchedPersonCandidate(personPid, oLink.get().getMatchResult()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempt to find matching Persons by resolving them from similar Matching target resources, where target resource
|
|
||||||
* can be either Patient or Practitioner. Runs EMPI logic over the existing Patient/Practitioners, then finds their
|
|
||||||
* entries in the EmpiLink table, and returns all the matches found therein.
|
|
||||||
*
|
|
||||||
* @param theBaseResource the {@link IBaseResource} which we want to find candidate Persons for.
|
|
||||||
* @return an Optional list of {@link MatchedPersonCandidate} indicating matches.
|
|
||||||
*/
|
|
||||||
private List<MatchedPersonCandidate> attemptToFindPersonCandidateFromSimilarTargetResource(IAnyResource theBaseResource) {
|
|
||||||
List<MatchedPersonCandidate> retval = new ArrayList<>();
|
|
||||||
|
|
||||||
List<Long> personPidsToExclude = getNoMatchPersonPids(theBaseResource);
|
|
||||||
List<MatchedTarget> matchedCandidates = myEmpiMatchFinderSvc.getMatchedTargets(myFhirContext.getResourceType(theBaseResource), theBaseResource);
|
|
||||||
|
|
||||||
//Convert all possible match targets to their equivalent Persons by looking up in the EmpiLink table,
|
|
||||||
//while ensuring that the matches aren't in our NO_MATCH list.
|
|
||||||
// The data flow is as follows ->
|
|
||||||
// MatchedTargetCandidate -> Person -> EmpiLink -> MatchedPersonCandidate
|
|
||||||
matchedCandidates = matchedCandidates.stream().filter(mc -> mc.isMatch() || mc.isPossibleMatch()).collect(Collectors.toList());
|
|
||||||
for (MatchedTarget match : matchedCandidates) {
|
|
||||||
Optional<EmpiLink> optMatchEmpiLink = myEmpiLinkDaoSvc.getMatchedLinkForTargetPid(myIdHelperService.getPidOrNull(match.getTarget()));
|
|
||||||
if (!optMatchEmpiLink.isPresent()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
EmpiLink matchEmpiLink = optMatchEmpiLink.get();
|
|
||||||
if (personPidsToExclude.contains(matchEmpiLink.getPersonPid())) {
|
|
||||||
ourLog.info("Skipping EMPI on candidate person with PID {} due to manual NO_MATCH", matchEmpiLink.getPersonPid());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
MatchedPersonCandidate candidate = new MatchedPersonCandidate(getResourcePersistentId(matchEmpiLink.getPersonPid()), match.getMatchResult());
|
|
||||||
retval.add(candidate);
|
|
||||||
}
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Long> getNoMatchPersonPids(IBaseResource theBaseResource) {
|
|
||||||
Long targetPid = myIdHelperService.getPidOrNull(theBaseResource);
|
|
||||||
return myEmpiLinkDaoSvc.getEmpiLinksByTargetPidAndMatchResult(targetPid, EmpiMatchResultEnum.NO_MATCH)
|
|
||||||
.stream()
|
|
||||||
.map(EmpiLink::getPersonPid)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
private ResourcePersistentId getResourcePersistentId(Long thePersonPid) {
|
|
||||||
return new ResourcePersistentId(thePersonPid);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IAnyResource getPersonFromMatchedPersonCandidate(MatchedPersonCandidate theMatchedPersonCandidate) {
|
|
||||||
ResourcePersistentId personPid = theMatchedPersonCandidate.getCandidatePersonPid();
|
|
||||||
return myEmpiResourceDaoSvc.readPersonByPid(personPid);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -27,8 +27,8 @@ import ca.uhn.fhir.empi.api.IEmpiPersonMergerSvc;
|
||||||
import ca.uhn.fhir.empi.log.Logs;
|
import ca.uhn.fhir.empi.log.Logs;
|
||||||
import ca.uhn.fhir.empi.model.EmpiTransactionContext;
|
import ca.uhn.fhir.empi.model.EmpiTransactionContext;
|
||||||
import ca.uhn.fhir.empi.util.PersonHelper;
|
import ca.uhn.fhir.empi.util.PersonHelper;
|
||||||
import ca.uhn.fhir.jpa.dao.EmpiLinkDaoSvc;
|
|
||||||
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
||||||
|
import ca.uhn.fhir.jpa.empi.dao.EmpiLinkDaoSvc;
|
||||||
import ca.uhn.fhir.jpa.entity.EmpiLink;
|
import ca.uhn.fhir.jpa.entity.EmpiLink;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||||
|
@ -76,7 +76,7 @@ public class EmpiPersonMergerSvcImpl implements IEmpiPersonMergerSvc {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addMergeLink(Long theFromPersonPid, Long theToPersonPid) {
|
private void addMergeLink(Long theFromPersonPid, Long theToPersonPid) {
|
||||||
EmpiLink empiLink = new EmpiLink()
|
EmpiLink empiLink = myEmpiLinkDaoSvc.newEmpiLink()
|
||||||
.setPersonPid(theFromPersonPid)
|
.setPersonPid(theFromPersonPid)
|
||||||
.setTargetPid(theToPersonPid)
|
.setTargetPid(theToPersonPid)
|
||||||
.setMatchResult(EmpiMatchResultEnum.MATCH)
|
.setMatchResult(EmpiMatchResultEnum.MATCH)
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
package ca.uhn.fhir.jpa.empi.svc.candidate;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
||||||
|
import ca.uhn.fhir.jpa.empi.dao.EmpiLinkDaoSvc;
|
||||||
|
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public abstract class BaseCandidateFinder {
|
||||||
|
@Autowired
|
||||||
|
IdHelperService myIdHelperService;
|
||||||
|
@Autowired
|
||||||
|
EmpiLinkDaoSvc myEmpiLinkDaoSvc;
|
||||||
|
|
||||||
|
CandidateList findCandidates(IAnyResource theTarget) {
|
||||||
|
CandidateList candidateList = new CandidateList(getStrategy());
|
||||||
|
candidateList.addAll(findMatchPersonCandidates(theTarget));
|
||||||
|
return candidateList;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract List<MatchedPersonCandidate> findMatchPersonCandidates(IAnyResource theTarget);
|
||||||
|
|
||||||
|
protected abstract CandidateStrategyEnum getStrategy();
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package ca.uhn.fhir.jpa.empi.svc.candidate;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
public class CandidateList {
|
||||||
|
private final CandidateStrategyEnum mySource;
|
||||||
|
private final List<MatchedPersonCandidate> myList = new ArrayList<>();
|
||||||
|
|
||||||
|
public CandidateList(CandidateStrategyEnum theSource) {
|
||||||
|
mySource = theSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CandidateStrategyEnum getSource() {
|
||||||
|
return mySource;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return myList.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addAll(List<MatchedPersonCandidate> theList) { myList.addAll(theList); }
|
||||||
|
|
||||||
|
public MatchedPersonCandidate getOnlyMatch() {
|
||||||
|
assert myList.size() == 1;
|
||||||
|
return myList.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean exactlyOneMatch() {
|
||||||
|
return myList.size()== 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stream<MatchedPersonCandidate> stream() {
|
||||||
|
return myList.stream();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<MatchedPersonCandidate> getCandidates() {
|
||||||
|
return Collections.unmodifiableList(myList);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MatchedPersonCandidate getFirstMatch() {
|
||||||
|
return myList.get(0);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package ca.uhn.fhir.jpa.empi.svc.candidate;
|
||||||
|
|
||||||
|
public enum CandidateStrategyEnum {
|
||||||
|
/** Find Person candidates based on matching EID */
|
||||||
|
EID,
|
||||||
|
/** Find Person candidates based on a link already existing for the target resource */
|
||||||
|
LINK,
|
||||||
|
/** Find Person candidates based on other targets that match the incoming target using the EMPI Matching rules */
|
||||||
|
SCORE
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package ca.uhn.fhir.jpa.empi.svc;
|
package ca.uhn.fhir.jpa.empi.svc.candidate;
|
||||||
|
|
||||||
/*-
|
/*-
|
||||||
* #%L
|
* #%L
|
||||||
|
@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.empi.svc;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ca.uhn.fhir.empi.rules.json.EmpiResourceSearchParamJson;
|
import ca.uhn.fhir.empi.rules.json.EmpiResourceSearchParamJson;
|
||||||
|
import ca.uhn.fhir.jpa.empi.svc.EmpiSearchParamSvc;
|
||||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
|
@ -1,4 +1,4 @@
|
||||||
package ca.uhn.fhir.jpa.empi.svc;
|
package ca.uhn.fhir.jpa.empi.svc.candidate;
|
||||||
|
|
||||||
/*-
|
/*-
|
||||||
* #%L
|
* #%L
|
||||||
|
@ -27,6 +27,7 @@ import ca.uhn.fhir.empi.rules.json.EmpiResourceSearchParamJson;
|
||||||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||||
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
||||||
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
||||||
|
import ca.uhn.fhir.jpa.empi.svc.EmpiSearchParamSvc;
|
||||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
import org.hl7.fhir.instance.model.api.IAnyResource;
|
|
@ -0,0 +1,79 @@
|
||||||
|
package ca.uhn.fhir.jpa.empi.svc.candidate;
|
||||||
|
|
||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR JPA Server - Enterprise Master Patient Index
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 - 2020 University Health Network
|
||||||
|
* %%
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
|
||||||
|
import ca.uhn.fhir.empi.log.Logs;
|
||||||
|
import ca.uhn.fhir.jpa.empi.svc.EmpiResourceDaoSvc;
|
||||||
|
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
|
||||||
|
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class EmpiPersonFindingSvc {
|
||||||
|
private static final Logger ourLog = Logs.getEmpiTroubleshootingLog();
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private EmpiResourceDaoSvc myEmpiResourceDaoSvc;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private FindCandidateByEidSvc myFindCandidateByEidSvc;
|
||||||
|
@Autowired
|
||||||
|
private FindCandidateByLinkSvc myFindCandidateByLinkSvc;
|
||||||
|
@Autowired
|
||||||
|
private FindCandidateByScoreSvc myFindCandidateByScoreSvc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an incoming IBaseResource, limited to Patient/Practitioner, return a list of {@link MatchedPersonCandidate}
|
||||||
|
* indicating possible candidates for a matching Person. Uses several separate methods for finding candidates:
|
||||||
|
* <p>
|
||||||
|
* 0. First, check the incoming Resource for an EID. If it is present, and we can find a Person with this EID, it automatically matches.
|
||||||
|
* 1. First, check link table for any entries where this baseresource is the target of a person. If found, return.
|
||||||
|
* 2. If none are found, attempt to find Person Resources which link to this theResource.
|
||||||
|
* 3. If none are found, attempt to find Persons similar to our incoming resource based on the EMPI rules and similarity metrics.
|
||||||
|
* 4. If none are found, attempt to find Persons that are linked to Patients/Practitioners that are similar to our incoming resource based on the EMPI rules and
|
||||||
|
* similarity metrics.
|
||||||
|
*
|
||||||
|
* @param theResource the {@link IBaseResource} we are attempting to find matching candidate Persons for.
|
||||||
|
* @return A list of {@link MatchedPersonCandidate} indicating all potential Person matches.
|
||||||
|
*/
|
||||||
|
public CandidateList findPersonCandidates(IAnyResource theResource) {
|
||||||
|
CandidateList matchedPersonCandidates = myFindCandidateByEidSvc.findCandidates(theResource);
|
||||||
|
|
||||||
|
if (matchedPersonCandidates.isEmpty()) {
|
||||||
|
matchedPersonCandidates = myFindCandidateByLinkSvc.findCandidates(theResource);
|
||||||
|
}
|
||||||
|
if (matchedPersonCandidates.isEmpty()) {
|
||||||
|
//OK, so we have not found any links in the EmpiLink table with us as a target. Next, let's find possible Patient/Practitioner
|
||||||
|
//matches by following EMPI rules.
|
||||||
|
|
||||||
|
matchedPersonCandidates = myFindCandidateByScoreSvc.findCandidates(theResource);
|
||||||
|
}
|
||||||
|
return matchedPersonCandidates;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IAnyResource getPersonFromMatchedPersonCandidate(MatchedPersonCandidate theMatchedPersonCandidate) {
|
||||||
|
ResourcePersistentId personPid = theMatchedPersonCandidate.getCandidatePersonPid();
|
||||||
|
return myEmpiResourceDaoSvc.readPersonByPid(personPid);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
package ca.uhn.fhir.jpa.empi.svc.candidate;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.empi.api.EmpiMatchOutcome;
|
||||||
|
import ca.uhn.fhir.empi.log.Logs;
|
||||||
|
import ca.uhn.fhir.empi.model.CanonicalEID;
|
||||||
|
import ca.uhn.fhir.empi.util.EIDHelper;
|
||||||
|
import ca.uhn.fhir.jpa.empi.svc.EmpiResourceDaoSvc;
|
||||||
|
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
|
||||||
|
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.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class FindCandidateByEidSvc extends BaseCandidateFinder {
|
||||||
|
private static final Logger ourLog = Logs.getEmpiTroubleshootingLog();
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private EIDHelper myEIDHelper;
|
||||||
|
@Autowired
|
||||||
|
private EmpiResourceDaoSvc myEmpiResourceDaoSvc;
|
||||||
|
|
||||||
|
protected List<MatchedPersonCandidate> findMatchPersonCandidates(IAnyResource theBaseResource) {
|
||||||
|
List<MatchedPersonCandidate> retval = new ArrayList<>();
|
||||||
|
|
||||||
|
List<CanonicalEID> eidFromResource = myEIDHelper.getExternalEid(theBaseResource);
|
||||||
|
if (!eidFromResource.isEmpty()) {
|
||||||
|
for (CanonicalEID eid : eidFromResource) {
|
||||||
|
Optional<IAnyResource> oFoundPerson = myEmpiResourceDaoSvc.searchPersonByEid(eid.getValue());
|
||||||
|
if (oFoundPerson.isPresent()) {
|
||||||
|
IAnyResource foundPerson = oFoundPerson.get();
|
||||||
|
Long pidOrNull = myIdHelperService.getPidOrNull(foundPerson);
|
||||||
|
MatchedPersonCandidate mpc = new MatchedPersonCandidate(new ResourcePersistentId(pidOrNull), EmpiMatchOutcome.EID_MATCH);
|
||||||
|
ourLog.debug("Matched {} by EID {}", foundPerson.getIdElement(), eid);
|
||||||
|
retval.add(mpc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CandidateStrategyEnum getStrategy() {
|
||||||
|
return CandidateStrategyEnum.EID;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
package ca.uhn.fhir.jpa.empi.svc.candidate;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.empi.log.Logs;
|
||||||
|
import ca.uhn.fhir.jpa.entity.EmpiLink;
|
||||||
|
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
|
||||||
|
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class FindCandidateByLinkSvc extends BaseCandidateFinder {
|
||||||
|
private static final Logger ourLog = Logs.getEmpiTroubleshootingLog();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to find a currently matching Person, based on the presence of an {@link EmpiLink} entity.
|
||||||
|
*
|
||||||
|
* @param theTarget the {@link IAnyResource} that we want to find candidate Persons for.
|
||||||
|
* @return an Optional list of {@link MatchedPersonCandidate} indicating matches.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected List<MatchedPersonCandidate> findMatchPersonCandidates(IAnyResource theTarget) {
|
||||||
|
List<MatchedPersonCandidate> retval = new ArrayList<>();
|
||||||
|
|
||||||
|
Long targetPid = myIdHelperService.getPidOrNull(theTarget);
|
||||||
|
if (targetPid != null) {
|
||||||
|
Optional<EmpiLink> oLink = myEmpiLinkDaoSvc.getMatchedLinkForTargetPid(targetPid);
|
||||||
|
if (oLink.isPresent()) {
|
||||||
|
ResourcePersistentId personPid = new ResourcePersistentId(oLink.get().getPersonPid());
|
||||||
|
ourLog.debug("Resource previously linked. Using existing link.");
|
||||||
|
retval.add(new MatchedPersonCandidate(personPid, oLink.get()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CandidateStrategyEnum getStrategy() {
|
||||||
|
return CandidateStrategyEnum.LINK;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
package ca.uhn.fhir.jpa.empi.svc.candidate;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.empi.api.EmpiMatchResultEnum;
|
||||||
|
import ca.uhn.fhir.empi.api.IEmpiMatchFinderSvc;
|
||||||
|
import ca.uhn.fhir.empi.api.MatchedTarget;
|
||||||
|
import ca.uhn.fhir.empi.log.Logs;
|
||||||
|
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
||||||
|
import ca.uhn.fhir.jpa.empi.dao.EmpiLinkDaoSvc;
|
||||||
|
import ca.uhn.fhir.jpa.entity.EmpiLink;
|
||||||
|
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
|
||||||
|
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class FindCandidateByScoreSvc extends BaseCandidateFinder {
|
||||||
|
private static final Logger ourLog = Logs.getEmpiTroubleshootingLog();
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private FhirContext myFhirContext;
|
||||||
|
@Autowired
|
||||||
|
IdHelperService myIdHelperService;
|
||||||
|
@Autowired
|
||||||
|
private EmpiLinkDaoSvc myEmpiLinkDaoSvc;
|
||||||
|
@Autowired
|
||||||
|
private IEmpiMatchFinderSvc myEmpiMatchFinderSvc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to find matching Persons by resolving them from similar Matching target resources, where target resource
|
||||||
|
* can be either Patient or Practitioner. Runs EMPI logic over the existing Patient/Practitioners, then finds their
|
||||||
|
* entries in the EmpiLink table, and returns all the matches found therein.
|
||||||
|
*
|
||||||
|
* @param theTarget the {@link IBaseResource} which we want to find candidate Persons for.
|
||||||
|
* @return an Optional list of {@link MatchedPersonCandidate} indicating matches.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected List<MatchedPersonCandidate> findMatchPersonCandidates(IAnyResource theTarget) {
|
||||||
|
List<MatchedPersonCandidate> retval = new ArrayList<>();
|
||||||
|
|
||||||
|
List<Long> personPidsToExclude = getNoMatchPersonPids(theTarget);
|
||||||
|
List<MatchedTarget> matchedCandidates = myEmpiMatchFinderSvc.getMatchedTargets(myFhirContext.getResourceType(theTarget), theTarget);
|
||||||
|
|
||||||
|
//Convert all possible match targets to their equivalent Persons by looking up in the EmpiLink table,
|
||||||
|
//while ensuring that the matches aren't in our NO_MATCH list.
|
||||||
|
// The data flow is as follows ->
|
||||||
|
// MatchedTargetCandidate -> Person -> EmpiLink -> MatchedPersonCandidate
|
||||||
|
matchedCandidates = matchedCandidates.stream().filter(mc -> mc.isMatch() || mc.isPossibleMatch()).collect(Collectors.toList());
|
||||||
|
for (MatchedTarget match : matchedCandidates) {
|
||||||
|
Optional<EmpiLink> optMatchEmpiLink = myEmpiLinkDaoSvc.getMatchedLinkForTargetPid(myIdHelperService.getPidOrNull(match.getTarget()));
|
||||||
|
if (!optMatchEmpiLink.isPresent()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
EmpiLink matchEmpiLink = optMatchEmpiLink.get();
|
||||||
|
if (personPidsToExclude.contains(matchEmpiLink.getPersonPid())) {
|
||||||
|
ourLog.info("Skipping EMPI on candidate person with PID {} due to manual NO_MATCH", matchEmpiLink.getPersonPid());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
MatchedPersonCandidate candidate = new MatchedPersonCandidate(getResourcePersistentId(matchEmpiLink.getPersonPid()), match.getMatchResult());
|
||||||
|
retval.add(candidate);
|
||||||
|
}
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Long> getNoMatchPersonPids(IBaseResource theBaseResource) {
|
||||||
|
Long targetPid = myIdHelperService.getPidOrNull(theBaseResource);
|
||||||
|
return myEmpiLinkDaoSvc.getEmpiLinksByTargetPidAndMatchResult(targetPid, EmpiMatchResultEnum.NO_MATCH)
|
||||||
|
.stream()
|
||||||
|
.map(EmpiLink::getPersonPid)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResourcePersistentId getResourcePersistentId(Long thePersonPid) {
|
||||||
|
return new ResourcePersistentId(thePersonPid);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected CandidateStrategyEnum getStrategy() {
|
||||||
|
return CandidateStrategyEnum.SCORE;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package ca.uhn.fhir.jpa.empi.svc;
|
package ca.uhn.fhir.jpa.empi.svc.candidate;
|
||||||
|
|
||||||
/*-
|
/*-
|
||||||
* #%L
|
* #%L
|
||||||
|
@ -20,28 +20,33 @@ package ca.uhn.fhir.jpa.empi.svc;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ca.uhn.fhir.empi.api.EmpiMatchResultEnum;
|
import ca.uhn.fhir.empi.api.EmpiMatchOutcome;
|
||||||
|
import ca.uhn.fhir.jpa.entity.EmpiLink;
|
||||||
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
|
import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId;
|
||||||
|
|
||||||
public class MatchedPersonCandidate {
|
public class MatchedPersonCandidate {
|
||||||
|
|
||||||
private final ResourcePersistentId myCandidatePersonPid;
|
private final ResourcePersistentId myCandidatePersonPid;
|
||||||
private final EmpiMatchResultEnum myEmpiMatchResult;
|
private final EmpiMatchOutcome myEmpiMatchOutcome;
|
||||||
|
|
||||||
public MatchedPersonCandidate(ResourcePersistentId theCandidate, EmpiMatchResultEnum theEmpiMatchResult) {
|
public MatchedPersonCandidate(ResourcePersistentId theCandidate, EmpiMatchOutcome theEmpiMatchOutcome) {
|
||||||
myCandidatePersonPid = theCandidate;
|
myCandidatePersonPid = theCandidate;
|
||||||
myEmpiMatchResult = theEmpiMatchResult;
|
myEmpiMatchOutcome = theEmpiMatchOutcome;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MatchedPersonCandidate(ResourcePersistentId thePersonPid, EmpiLink theEmpiLink) {
|
||||||
|
myCandidatePersonPid = thePersonPid;
|
||||||
|
myEmpiMatchOutcome = new EmpiMatchOutcome(theEmpiLink.getVector(), theEmpiLink.getScore()).setMatchResultEnum(theEmpiLink.getMatchResult());
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResourcePersistentId getCandidatePersonPid() {
|
public ResourcePersistentId getCandidatePersonPid() {
|
||||||
return myCandidatePersonPid;
|
return myCandidatePersonPid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public EmpiMatchResultEnum getMatchResult() {
|
public EmpiMatchOutcome getMatchResult() {
|
||||||
return myEmpiMatchResult;
|
return myEmpiMatchOutcome;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isMatch() {
|
public boolean isMatch() {
|
||||||
return myEmpiMatchResult == EmpiMatchResultEnum.MATCH;
|
return myEmpiMatchOutcome.isMatch();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -10,13 +10,13 @@ import ca.uhn.fhir.empi.rules.svc.EmpiResourceMatcherSvc;
|
||||||
import ca.uhn.fhir.empi.util.EIDHelper;
|
import ca.uhn.fhir.empi.util.EIDHelper;
|
||||||
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
||||||
import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
|
import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
|
||||||
import ca.uhn.fhir.jpa.dao.EmpiLinkDaoSvc;
|
|
||||||
import ca.uhn.fhir.jpa.dao.data.IEmpiLinkDao;
|
import ca.uhn.fhir.jpa.dao.data.IEmpiLinkDao;
|
||||||
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
||||||
import ca.uhn.fhir.jpa.empi.config.EmpiConsumerConfig;
|
import ca.uhn.fhir.jpa.empi.config.EmpiConsumerConfig;
|
||||||
import ca.uhn.fhir.jpa.empi.config.EmpiSearchParameterLoader;
|
import ca.uhn.fhir.jpa.empi.config.EmpiSearchParameterLoader;
|
||||||
import ca.uhn.fhir.jpa.empi.config.EmpiSubmitterConfig;
|
import ca.uhn.fhir.jpa.empi.config.EmpiSubmitterConfig;
|
||||||
import ca.uhn.fhir.jpa.empi.config.TestEmpiConfigR4;
|
import ca.uhn.fhir.jpa.empi.config.TestEmpiConfigR4;
|
||||||
|
import ca.uhn.fhir.jpa.empi.dao.EmpiLinkDaoSvc;
|
||||||
import ca.uhn.fhir.jpa.empi.matcher.IsLinkedTo;
|
import ca.uhn.fhir.jpa.empi.matcher.IsLinkedTo;
|
||||||
import ca.uhn.fhir.jpa.empi.matcher.IsMatchedToAPerson;
|
import ca.uhn.fhir.jpa.empi.matcher.IsMatchedToAPerson;
|
||||||
import ca.uhn.fhir.jpa.empi.matcher.IsPossibleDuplicateOf;
|
import ca.uhn.fhir.jpa.empi.matcher.IsPossibleDuplicateOf;
|
||||||
|
@ -61,13 +61,14 @@ import static org.slf4j.LoggerFactory.getLogger;
|
||||||
@ExtendWith(SpringExtension.class)
|
@ExtendWith(SpringExtension.class)
|
||||||
@ContextConfiguration(classes = {EmpiSubmitterConfig.class, EmpiConsumerConfig.class, TestEmpiConfigR4.class, SubscriptionProcessorConfig.class})
|
@ContextConfiguration(classes = {EmpiSubmitterConfig.class, EmpiConsumerConfig.class, TestEmpiConfigR4.class, SubscriptionProcessorConfig.class})
|
||||||
abstract public class BaseEmpiR4Test extends BaseJpaR4Test {
|
abstract public class BaseEmpiR4Test extends BaseJpaR4Test {
|
||||||
|
private static final Logger ourLog = getLogger(BaseEmpiR4Test.class);
|
||||||
|
|
||||||
public static final String NAME_GIVEN_JANE = "Jane";
|
public static final String NAME_GIVEN_JANE = "Jane";
|
||||||
public static final String NAME_GIVEN_PAUL = "Paul";
|
public static final String NAME_GIVEN_PAUL = "Paul";
|
||||||
public static final String TEST_NAME_FAMILY = "Doe";
|
public static final String TEST_NAME_FAMILY = "Doe";
|
||||||
protected static final String TEST_ID_SYSTEM = "http://a.tv/";
|
protected static final String TEST_ID_SYSTEM = "http://a.tv/";
|
||||||
protected static final String JANE_ID = "ID.JANE.123";
|
protected static final String JANE_ID = "ID.JANE.123";
|
||||||
protected static final String PAUL_ID = "ID.PAUL.456";
|
protected static final String PAUL_ID = "ID.PAUL.456";
|
||||||
private static final Logger ourLog = getLogger(BaseEmpiR4Test.class);
|
|
||||||
private static final ContactPoint TEST_TELECOM = new ContactPoint()
|
private static final ContactPoint TEST_TELECOM = new ContactPoint()
|
||||||
.setSystem(ContactPoint.ContactPointSystem.PHONE)
|
.setSystem(ContactPoint.ContactPointSystem.PHONE)
|
||||||
.setValue("555-555-5555");
|
.setValue("555-555-5555");
|
||||||
|
@ -360,7 +361,7 @@ abstract public class BaseEmpiR4Test extends BaseJpaR4Test {
|
||||||
Person person = createPerson();
|
Person person = createPerson();
|
||||||
Patient patient = createPatient();
|
Patient patient = createPatient();
|
||||||
|
|
||||||
EmpiLink empiLink = new EmpiLink();
|
EmpiLink empiLink = myEmpiLinkDaoSvc.newEmpiLink();
|
||||||
empiLink.setLinkSource(EmpiLinkSourceEnum.MANUAL);
|
empiLink.setLinkSource(EmpiLinkSourceEnum.MANUAL);
|
||||||
empiLink.setMatchResult(EmpiMatchResultEnum.MATCH);
|
empiLink.setMatchResult(EmpiMatchResultEnum.MATCH);
|
||||||
empiLink.setPersonPid(myIdHelperService.getPidOrNull(person));
|
empiLink.setPersonPid(myIdHelperService.getPidOrNull(person));
|
||||||
|
@ -372,4 +373,12 @@ abstract public class BaseEmpiR4Test extends BaseJpaR4Test {
|
||||||
myEmpiSearchParameterLoader.daoUpdateEmpiSearchParameters();
|
myEmpiSearchParameterLoader.daoUpdateEmpiSearchParameters();
|
||||||
mySearchParamRegistry.forceRefresh();
|
mySearchParamRegistry.forceRefresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void logAllLinks() {
|
||||||
|
ourLog.info("Logging all EMPI Links:");
|
||||||
|
List<EmpiLink> links = myEmpiLinkDao.findAll();
|
||||||
|
for (EmpiLink link : links) {
|
||||||
|
ourLog.info(link.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
package ca.uhn.fhir.jpa.empi.dao;
|
package ca.uhn.fhir.jpa.empi.dao;
|
||||||
|
|
||||||
import ca.uhn.fhir.empi.api.EmpiLinkSourceEnum;
|
import ca.uhn.fhir.empi.api.EmpiLinkSourceEnum;
|
||||||
import ca.uhn.fhir.jpa.dao.EmpiLinkDaoSvc;
|
import ca.uhn.fhir.empi.api.IEmpiSettings;
|
||||||
|
import ca.uhn.fhir.empi.rules.json.EmpiRulesJson;
|
||||||
import ca.uhn.fhir.jpa.empi.BaseEmpiR4Test;
|
import ca.uhn.fhir.jpa.empi.BaseEmpiR4Test;
|
||||||
import ca.uhn.fhir.jpa.entity.EmpiLink;
|
import ca.uhn.fhir.jpa.entity.EmpiLink;
|
||||||
import ca.uhn.fhir.jpa.util.TestUtil;
|
import ca.uhn.fhir.jpa.util.TestUtil;
|
||||||
|
@ -19,6 +20,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
public class EmpiLinkDaoSvcTest extends BaseEmpiR4Test {
|
public class EmpiLinkDaoSvcTest extends BaseEmpiR4Test {
|
||||||
@Autowired
|
@Autowired
|
||||||
EmpiLinkDaoSvc myEmpiLinkDaoSvc;
|
EmpiLinkDaoSvc myEmpiLinkDaoSvc;
|
||||||
|
@Autowired
|
||||||
|
IEmpiSettings myEmpiSettings;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreate() {
|
public void testCreate() {
|
||||||
|
@ -41,4 +44,12 @@ public class EmpiLinkDaoSvcTest extends BaseEmpiR4Test {
|
||||||
assertNotEquals(updatedLink.getCreated(), updatedLink.getUpdated());
|
assertNotEquals(updatedLink.getCreated(), updatedLink.getUpdated());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNew() {
|
||||||
|
EmpiLink newLink = myEmpiLinkDaoSvc.newEmpiLink();
|
||||||
|
EmpiRulesJson rules = myEmpiSettings.getEmpiRules();
|
||||||
|
assertEquals("1", rules.getVersion());
|
||||||
|
assertEquals(rules.getVersion(), newLink.getVersion());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,8 +11,8 @@ public class EmpiEnumTest {
|
||||||
public void empiEnumOrdinals() {
|
public void empiEnumOrdinals() {
|
||||||
// This test is here to enforce that new values in these enums are always added to the end
|
// This test is here to enforce that new values in these enums are always added to the end
|
||||||
|
|
||||||
assertEquals(4, EmpiMatchResultEnum.values().length);
|
assertEquals(5, EmpiMatchResultEnum.values().length);
|
||||||
assertEquals(EmpiMatchResultEnum.POSSIBLE_DUPLICATE, EmpiMatchResultEnum.values()[EmpiMatchResultEnum.values().length - 1]);
|
assertEquals(EmpiMatchResultEnum.GOLDEN_RECORD, EmpiMatchResultEnum.values()[EmpiMatchResultEnum.values().length - 1]);
|
||||||
|
|
||||||
assertEquals(2, EmpiLinkSourceEnum.values().length);
|
assertEquals(2, EmpiLinkSourceEnum.values().length);
|
||||||
assertEquals(EmpiLinkSourceEnum.MANUAL, EmpiLinkSourceEnum.values()[EmpiLinkSourceEnum.values().length - 1]);
|
assertEquals(EmpiLinkSourceEnum.MANUAL, EmpiLinkSourceEnum.values()[EmpiLinkSourceEnum.values().length - 1]);
|
||||||
|
|
|
@ -45,7 +45,7 @@ public class EmpiExpungeTest extends BaseEmpiR4Test {
|
||||||
myTargetId = myTargetEntity.getIdDt().toVersionless();
|
myTargetId = myTargetEntity.getIdDt().toVersionless();
|
||||||
myPersonEntity = (ResourceTable) myPersonDao.create(new Person()).getEntity();
|
myPersonEntity = (ResourceTable) myPersonDao.create(new Person()).getEntity();
|
||||||
|
|
||||||
EmpiLink empiLink = new EmpiLink();
|
EmpiLink empiLink = myEmpiLinkDaoSvc.newEmpiLink();
|
||||||
empiLink.setLinkSource(EmpiLinkSourceEnum.MANUAL);
|
empiLink.setLinkSource(EmpiLinkSourceEnum.MANUAL);
|
||||||
empiLink.setMatchResult(EmpiMatchResultEnum.MATCH);
|
empiLink.setMatchResult(EmpiMatchResultEnum.MATCH);
|
||||||
empiLink.setPersonPid(myPersonEntity.getId());
|
empiLink.setPersonPid(myPersonEntity.getId());
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package ca.uhn.fhir.jpa.empi.matcher;
|
package ca.uhn.fhir.jpa.empi.matcher;
|
||||||
|
|
||||||
import ca.uhn.fhir.empi.api.EmpiMatchResultEnum;
|
import ca.uhn.fhir.empi.api.EmpiMatchResultEnum;
|
||||||
import ca.uhn.fhir.jpa.dao.EmpiLinkDaoSvc;
|
|
||||||
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
||||||
|
import ca.uhn.fhir.jpa.empi.dao.EmpiLinkDaoSvc;
|
||||||
import ca.uhn.fhir.jpa.entity.EmpiLink;
|
import ca.uhn.fhir.jpa.entity.EmpiLink;
|
||||||
import org.hamcrest.TypeSafeMatcher;
|
import org.hamcrest.TypeSafeMatcher;
|
||||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package ca.uhn.fhir.jpa.empi.matcher;
|
package ca.uhn.fhir.jpa.empi.matcher;
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.dao.EmpiLinkDaoSvc;
|
|
||||||
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
||||||
|
import ca.uhn.fhir.jpa.empi.dao.EmpiLinkDaoSvc;
|
||||||
import org.hamcrest.Description;
|
import org.hamcrest.Description;
|
||||||
import org.hamcrest.Matcher;
|
import org.hamcrest.Matcher;
|
||||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package ca.uhn.fhir.jpa.empi.matcher;
|
package ca.uhn.fhir.jpa.empi.matcher;
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.dao.EmpiLinkDaoSvc;
|
|
||||||
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
||||||
|
import ca.uhn.fhir.jpa.empi.dao.EmpiLinkDaoSvc;
|
||||||
import ca.uhn.fhir.jpa.entity.EmpiLink;
|
import ca.uhn.fhir.jpa.entity.EmpiLink;
|
||||||
import org.hamcrest.Description;
|
import org.hamcrest.Description;
|
||||||
import org.hamcrest.Matcher;
|
import org.hamcrest.Matcher;
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package ca.uhn.fhir.jpa.empi.matcher;
|
package ca.uhn.fhir.jpa.empi.matcher;
|
||||||
|
|
||||||
import ca.uhn.fhir.empi.api.EmpiMatchResultEnum;
|
import ca.uhn.fhir.empi.api.EmpiMatchResultEnum;
|
||||||
import ca.uhn.fhir.jpa.dao.EmpiLinkDaoSvc;
|
|
||||||
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
||||||
|
import ca.uhn.fhir.jpa.empi.dao.EmpiLinkDaoSvc;
|
||||||
import ca.uhn.fhir.jpa.entity.EmpiLink;
|
import ca.uhn.fhir.jpa.entity.EmpiLink;
|
||||||
import org.hamcrest.Description;
|
import org.hamcrest.Description;
|
||||||
import org.hamcrest.Matcher;
|
import org.hamcrest.Matcher;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package ca.uhn.fhir.jpa.empi.matcher;
|
package ca.uhn.fhir.jpa.empi.matcher;
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.dao.EmpiLinkDaoSvc;
|
|
||||||
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
||||||
|
import ca.uhn.fhir.jpa.empi.dao.EmpiLinkDaoSvc;
|
||||||
import org.hamcrest.Description;
|
import org.hamcrest.Description;
|
||||||
import org.hamcrest.Matcher;
|
import org.hamcrest.Matcher;
|
||||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package ca.uhn.fhir.jpa.empi.matcher;
|
package ca.uhn.fhir.jpa.empi.matcher;
|
||||||
|
|
||||||
import ca.uhn.fhir.empi.api.EmpiMatchResultEnum;
|
import ca.uhn.fhir.empi.api.EmpiMatchResultEnum;
|
||||||
import ca.uhn.fhir.jpa.dao.EmpiLinkDaoSvc;
|
|
||||||
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
||||||
|
import ca.uhn.fhir.jpa.empi.dao.EmpiLinkDaoSvc;
|
||||||
import ca.uhn.fhir.jpa.entity.EmpiLink;
|
import ca.uhn.fhir.jpa.entity.EmpiLink;
|
||||||
import org.hamcrest.Description;
|
import org.hamcrest.Description;
|
||||||
import org.hamcrest.Matcher;
|
import org.hamcrest.Matcher;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package ca.uhn.fhir.jpa.empi.matcher;
|
package ca.uhn.fhir.jpa.empi.matcher;
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.dao.EmpiLinkDaoSvc;
|
|
||||||
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
||||||
|
import ca.uhn.fhir.jpa.empi.dao.EmpiLinkDaoSvc;
|
||||||
import org.hamcrest.Description;
|
import org.hamcrest.Description;
|
||||||
import org.hamcrest.Matcher;
|
import org.hamcrest.Matcher;
|
||||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||||
|
|
|
@ -47,7 +47,8 @@ public class EmpiProviderQueryLinkR4Test extends BaseLinkR4Test {
|
||||||
Person person2 = createPerson();
|
Person person2 = createPerson();
|
||||||
myPerson2Id = new StringType(person2.getIdElement().toVersionless().getValue());
|
myPerson2Id = new StringType(person2.getIdElement().toVersionless().getValue());
|
||||||
Long person2Pid = myIdHelperService.getPidOrNull(person2);
|
Long person2Pid = myIdHelperService.getPidOrNull(person2);
|
||||||
EmpiLink possibleDuplicateEmpiLink = new EmpiLink().setPersonPid(person1Pid).setTargetPid(person2Pid).setMatchResult(EmpiMatchResultEnum.POSSIBLE_DUPLICATE).setLinkSource(EmpiLinkSourceEnum.AUTO);
|
|
||||||
|
EmpiLink possibleDuplicateEmpiLink = myEmpiLinkDaoSvc.newEmpiLink().setPersonPid(person1Pid).setTargetPid(person2Pid).setMatchResult(EmpiMatchResultEnum.POSSIBLE_DUPLICATE).setLinkSource(EmpiLinkSourceEnum.AUTO);
|
||||||
saveLink(possibleDuplicateEmpiLink);
|
saveLink(possibleDuplicateEmpiLink);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,12 +60,12 @@ public class EmpiProviderQueryLinkR4Test extends BaseLinkR4Test {
|
||||||
List<Parameters.ParametersParameterComponent> list = result.getParameter();
|
List<Parameters.ParametersParameterComponent> list = result.getParameter();
|
||||||
assertThat(list, hasSize(1));
|
assertThat(list, hasSize(1));
|
||||||
List<Parameters.ParametersParameterComponent> part = list.get(0).getPart();
|
List<Parameters.ParametersParameterComponent> part = list.get(0).getPart();
|
||||||
assertEmpiLink(4, part, myPersonId.getValue(), myPatientId.getValue(), EmpiMatchResultEnum.POSSIBLE_MATCH);
|
assertEmpiLink(7, part, myPersonId.getValue(), myPatientId.getValue(), EmpiMatchResultEnum.POSSIBLE_MATCH, "false", "true", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testQueryLinkThreeMatches() {
|
public void testQueryLinkThreeMatches() {
|
||||||
// Add a second patient
|
// Add a third patient
|
||||||
Patient patient = createPatientAndUpdateLinks(buildJanePatient());
|
Patient patient = createPatientAndUpdateLinks(buildJanePatient());
|
||||||
IdType patientId = patient.getIdElement().toVersionless();
|
IdType patientId = patient.getIdElement().toVersionless();
|
||||||
Person person = getPersonFromTarget(patient);
|
Person person = getPersonFromTarget(patient);
|
||||||
|
@ -75,7 +76,7 @@ public class EmpiProviderQueryLinkR4Test extends BaseLinkR4Test {
|
||||||
List<Parameters.ParametersParameterComponent> list = result.getParameter();
|
List<Parameters.ParametersParameterComponent> list = result.getParameter();
|
||||||
assertThat(list, hasSize(3));
|
assertThat(list, hasSize(3));
|
||||||
List<Parameters.ParametersParameterComponent> part = list.get(2).getPart();
|
List<Parameters.ParametersParameterComponent> part = list.get(2).getPart();
|
||||||
assertEmpiLink(4, part, personId.getValue(), patientId.getValue(), EmpiMatchResultEnum.MATCH);
|
assertEmpiLink(7, part, personId.getValue(), patientId.getValue(), EmpiMatchResultEnum.MATCH, "false", "false", "2");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -85,7 +86,7 @@ public class EmpiProviderQueryLinkR4Test extends BaseLinkR4Test {
|
||||||
List<Parameters.ParametersParameterComponent> list = result.getParameter();
|
List<Parameters.ParametersParameterComponent> list = result.getParameter();
|
||||||
assertThat(list, hasSize(1));
|
assertThat(list, hasSize(1));
|
||||||
List<Parameters.ParametersParameterComponent> part = list.get(0).getPart();
|
List<Parameters.ParametersParameterComponent> part = list.get(0).getPart();
|
||||||
assertEmpiLink(2, part, myPerson1Id.getValue(), myPerson2Id.getValue(), EmpiMatchResultEnum.POSSIBLE_DUPLICATE);
|
assertEmpiLink(2, part, myPerson1Id.getValue(), myPerson2Id.getValue(), EmpiMatchResultEnum.POSSIBLE_DUPLICATE, "false", "false", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -116,7 +117,7 @@ public class EmpiProviderQueryLinkR4Test extends BaseLinkR4Test {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertEmpiLink(int theExpectedSize, List<Parameters.ParametersParameterComponent> thePart, String thePersonId, String theTargetId, EmpiMatchResultEnum theMatchResult) {
|
private void assertEmpiLink(int theExpectedSize, List<Parameters.ParametersParameterComponent> thePart, String thePersonId, String theTargetId, EmpiMatchResultEnum theMatchResult, String theEidMatch, String theNewPerson, String theScore) {
|
||||||
assertThat(thePart, hasSize(theExpectedSize));
|
assertThat(thePart, hasSize(theExpectedSize));
|
||||||
assertThat(thePart.get(0).getName(), is("personId"));
|
assertThat(thePart.get(0).getName(), is("personId"));
|
||||||
assertThat(thePart.get(0).getValue().toString(), is(removeVersion(thePersonId)));
|
assertThat(thePart.get(0).getValue().toString(), is(removeVersion(thePersonId)));
|
||||||
|
@ -127,6 +128,15 @@ public class EmpiProviderQueryLinkR4Test extends BaseLinkR4Test {
|
||||||
assertThat(thePart.get(2).getValue().toString(), is(theMatchResult.name()));
|
assertThat(thePart.get(2).getValue().toString(), is(theMatchResult.name()));
|
||||||
assertThat(thePart.get(3).getName(), is("linkSource"));
|
assertThat(thePart.get(3).getName(), is("linkSource"));
|
||||||
assertThat(thePart.get(3).getValue().toString(), is("AUTO"));
|
assertThat(thePart.get(3).getValue().toString(), is("AUTO"));
|
||||||
|
|
||||||
|
assertThat(thePart.get(4).getName(), is("eidMatch"));
|
||||||
|
assertThat(thePart.get(4).getValue().primitiveValue(), is(theEidMatch));
|
||||||
|
|
||||||
|
assertThat(thePart.get(5).getName(), is("newPerson"));
|
||||||
|
assertThat(thePart.get(5).getValue().primitiveValue(), is(theNewPerson));
|
||||||
|
|
||||||
|
assertThat(thePart.get(6).getName(), is("score"));
|
||||||
|
assertThat(thePart.get(6).getValue().primitiveValue(), is(theScore));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,7 @@ public class SearchParameterTest extends BaseEmpiR4Test {
|
||||||
ourLog.info("Search result: {}", encoded);
|
ourLog.info("Search result: {}", encoded);
|
||||||
List<Person.PersonLinkComponent> links = person.getLink();
|
List<Person.PersonLinkComponent> links = person.getLink();
|
||||||
assertEquals(2, links.size());
|
assertEquals(2, links.size());
|
||||||
assertEquals(Person.IdentityAssuranceLevel.LEVEL3, links.get(0).getAssurance());
|
assertEquals(Person.IdentityAssuranceLevel.LEVEL2, links.get(0).getAssurance());
|
||||||
assertEquals(Person.IdentityAssuranceLevel.LEVEL2, links.get(1).getAssurance());
|
assertEquals(Person.IdentityAssuranceLevel.LEVEL1, links.get(1).getAssurance());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package ca.uhn.fhir.jpa.empi.svc;
|
||||||
|
|
||||||
import ca.uhn.fhir.empi.rules.json.EmpiResourceSearchParamJson;
|
import ca.uhn.fhir.empi.rules.json.EmpiResourceSearchParamJson;
|
||||||
import ca.uhn.fhir.jpa.empi.BaseEmpiR4Test;
|
import ca.uhn.fhir.jpa.empi.BaseEmpiR4Test;
|
||||||
|
import ca.uhn.fhir.jpa.empi.svc.candidate.EmpiCandidateSearchCriteriaBuilderSvc;
|
||||||
import org.hl7.fhir.r4.model.HumanName;
|
import org.hl7.fhir.r4.model.HumanName;
|
||||||
import org.hl7.fhir.r4.model.Patient;
|
import org.hl7.fhir.r4.model.Patient;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
@ -19,7 +20,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
public class EmpiCandidateSearchCriteriaBuilderSvcTest extends BaseEmpiR4Test {
|
public class EmpiCandidateSearchCriteriaBuilderSvcTest extends BaseEmpiR4Test {
|
||||||
@Autowired
|
@Autowired
|
||||||
EmpiCandidateSearchCriteriaBuilderSvc myEmpiCandidateSearchCriteriaBuilderSvc;
|
EmpiCandidateSearchCriteriaBuilderSvc myEmpiCandidateSearchCriteriaBuilderSvc;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEmptyCase() {
|
public void testEmptyCase() {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package ca.uhn.fhir.jpa.empi.svc;
|
package ca.uhn.fhir.jpa.empi.svc;
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.empi.BaseEmpiR4Test;
|
import ca.uhn.fhir.jpa.empi.BaseEmpiR4Test;
|
||||||
|
import ca.uhn.fhir.jpa.empi.svc.candidate.EmpiCandidateSearchSvc;
|
||||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||||
import org.hl7.fhir.r4.model.Patient;
|
import org.hl7.fhir.r4.model.Patient;
|
||||||
import org.hl7.fhir.r4.model.Practitioner;
|
import org.hl7.fhir.r4.model.Practitioner;
|
||||||
|
@ -19,7 +20,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
public class EmpiCandidateSearchSvcTest extends BaseEmpiR4Test {
|
public class EmpiCandidateSearchSvcTest extends BaseEmpiR4Test {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
EmpiCandidateSearchSvc myEmpiCandidateSearchSvc;
|
EmpiCandidateSearchSvc myEmpiCandidateSearchSvc;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testFindCandidates() {
|
public void testFindCandidates() {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package ca.uhn.fhir.jpa.empi.svc;
|
package ca.uhn.fhir.jpa.empi.svc;
|
||||||
|
|
||||||
import ca.uhn.fhir.empi.api.EmpiLinkSourceEnum;
|
import ca.uhn.fhir.empi.api.EmpiLinkSourceEnum;
|
||||||
|
import ca.uhn.fhir.empi.api.EmpiMatchOutcome;
|
||||||
import ca.uhn.fhir.empi.api.EmpiMatchResultEnum;
|
import ca.uhn.fhir.empi.api.EmpiMatchResultEnum;
|
||||||
import ca.uhn.fhir.empi.api.IEmpiLinkSvc;
|
import ca.uhn.fhir.empi.api.IEmpiLinkSvc;
|
||||||
import ca.uhn.fhir.jpa.empi.BaseEmpiR4Test;
|
import ca.uhn.fhir.jpa.empi.BaseEmpiR4Test;
|
||||||
|
@ -22,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static org.junit.jupiter.api.Assertions.fail;
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
public class EmpiLinkSvcTest extends BaseEmpiR4Test {
|
public class EmpiLinkSvcTest extends BaseEmpiR4Test {
|
||||||
|
private static final EmpiMatchOutcome POSSIBLE_MATCH = new EmpiMatchOutcome(null, null).setMatchResultEnum(EmpiMatchResultEnum.POSSIBLE_MATCH);
|
||||||
@Autowired
|
@Autowired
|
||||||
IEmpiLinkSvc myEmpiLinkSvc;
|
IEmpiLinkSvc myEmpiLinkSvc;
|
||||||
|
|
||||||
|
@ -36,7 +38,7 @@ public class EmpiLinkSvcTest extends BaseEmpiR4Test {
|
||||||
public void compareEmptyPatients() {
|
public void compareEmptyPatients() {
|
||||||
Patient patient = new Patient();
|
Patient patient = new Patient();
|
||||||
patient.setId("Patient/1");
|
patient.setId("Patient/1");
|
||||||
EmpiMatchResultEnum result = myEmpiResourceMatcherSvc.getMatchResult(patient, patient);
|
EmpiMatchResultEnum result = myEmpiResourceMatcherSvc.getMatchResult(patient, patient).getMatchResultEnum();
|
||||||
assertEquals(EmpiMatchResultEnum.NO_MATCH, result);
|
assertEquals(EmpiMatchResultEnum.NO_MATCH, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,14 +51,14 @@ public class EmpiLinkSvcTest extends BaseEmpiR4Test {
|
||||||
Patient patient = createPatient();
|
Patient patient = createPatient();
|
||||||
|
|
||||||
{
|
{
|
||||||
myEmpiLinkSvc.updateLink(person, patient, EmpiMatchResultEnum.POSSIBLE_MATCH, EmpiLinkSourceEnum.AUTO, createContextForCreate());
|
myEmpiLinkSvc.updateLink(person, patient, POSSIBLE_MATCH, EmpiLinkSourceEnum.AUTO, createContextForCreate());
|
||||||
assertLinkCount(1);
|
assertLinkCount(1);
|
||||||
Person newPerson = myPersonDao.read(personId);
|
Person newPerson = myPersonDao.read(personId);
|
||||||
assertEquals(1, newPerson.getLink().size());
|
assertEquals(1, newPerson.getLink().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
myEmpiLinkSvc.updateLink(person, patient, EmpiMatchResultEnum.NO_MATCH, EmpiLinkSourceEnum.MANUAL, createContextForCreate());
|
myEmpiLinkSvc.updateLink(person, patient, EmpiMatchOutcome.NO_MATCH, EmpiLinkSourceEnum.MANUAL, createContextForCreate());
|
||||||
assertLinkCount(1);
|
assertLinkCount(1);
|
||||||
Person newPerson = myPersonDao.read(personId);
|
Person newPerson = myPersonDao.read(personId);
|
||||||
assertEquals(0, newPerson.getLink().size());
|
assertEquals(0, newPerson.getLink().size());
|
||||||
|
@ -70,7 +72,7 @@ public class EmpiLinkSvcTest extends BaseEmpiR4Test {
|
||||||
Person person = createPerson();
|
Person person = createPerson();
|
||||||
Person target = createPerson();
|
Person target = createPerson();
|
||||||
|
|
||||||
myEmpiLinkSvc.updateLink(person, target, EmpiMatchResultEnum.POSSIBLE_DUPLICATE, EmpiLinkSourceEnum.AUTO, createContextForCreate());
|
myEmpiLinkSvc.updateLink(person, target, EmpiMatchOutcome.POSSIBLE_DUPLICATE, EmpiLinkSourceEnum.AUTO, createContextForCreate());
|
||||||
assertLinkCount(1);
|
assertLinkCount(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,7 +89,7 @@ public class EmpiLinkSvcTest extends BaseEmpiR4Test {
|
||||||
|
|
||||||
saveNoMatchLink(personPid, targetPid);
|
saveNoMatchLink(personPid, targetPid);
|
||||||
|
|
||||||
myEmpiLinkSvc.updateLink(person, target, EmpiMatchResultEnum.POSSIBLE_DUPLICATE, EmpiLinkSourceEnum.AUTO, createContextForCreate());
|
myEmpiLinkSvc.updateLink(person, target, EmpiMatchOutcome.POSSIBLE_DUPLICATE, EmpiLinkSourceEnum.AUTO, createContextForCreate());
|
||||||
assertFalse(myEmpiLinkDaoSvc.getEmpiLinksByPersonPidTargetPidAndMatchResult(personPid, targetPid, EmpiMatchResultEnum.POSSIBLE_DUPLICATE).isPresent());
|
assertFalse(myEmpiLinkDaoSvc.getEmpiLinksByPersonPidTargetPidAndMatchResult(personPid, targetPid, EmpiMatchResultEnum.POSSIBLE_DUPLICATE).isPresent());
|
||||||
assertLinkCount(1);
|
assertLinkCount(1);
|
||||||
}
|
}
|
||||||
|
@ -105,13 +107,13 @@ public class EmpiLinkSvcTest extends BaseEmpiR4Test {
|
||||||
|
|
||||||
saveNoMatchLink(targetPid, personPid);
|
saveNoMatchLink(targetPid, personPid);
|
||||||
|
|
||||||
myEmpiLinkSvc.updateLink(person, target, EmpiMatchResultEnum.POSSIBLE_DUPLICATE, EmpiLinkSourceEnum.AUTO, createContextForCreate());
|
myEmpiLinkSvc.updateLink(person, target, EmpiMatchOutcome.POSSIBLE_DUPLICATE, EmpiLinkSourceEnum.AUTO, createContextForCreate());
|
||||||
assertFalse(myEmpiLinkDaoSvc.getEmpiLinksByPersonPidTargetPidAndMatchResult(personPid, targetPid, EmpiMatchResultEnum.POSSIBLE_DUPLICATE).isPresent());
|
assertFalse(myEmpiLinkDaoSvc.getEmpiLinksByPersonPidTargetPidAndMatchResult(personPid, targetPid, EmpiMatchResultEnum.POSSIBLE_DUPLICATE).isPresent());
|
||||||
assertLinkCount(1);
|
assertLinkCount(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveNoMatchLink(Long thePersonPid, Long theTargetPid) {
|
private void saveNoMatchLink(Long thePersonPid, Long theTargetPid) {
|
||||||
EmpiLink noMatchLink = new EmpiLink()
|
EmpiLink noMatchLink = myEmpiLinkDaoSvc.newEmpiLink()
|
||||||
.setPersonPid(thePersonPid)
|
.setPersonPid(thePersonPid)
|
||||||
.setTargetPid(theTargetPid)
|
.setTargetPid(theTargetPid)
|
||||||
.setLinkSource(EmpiLinkSourceEnum.MANUAL)
|
.setLinkSource(EmpiLinkSourceEnum.MANUAL)
|
||||||
|
@ -124,9 +126,9 @@ public class EmpiLinkSvcTest extends BaseEmpiR4Test {
|
||||||
Person person = createPerson(buildJanePerson());
|
Person person = createPerson(buildJanePerson());
|
||||||
Patient patient = createPatient(buildJanePatient());
|
Patient patient = createPatient(buildJanePatient());
|
||||||
|
|
||||||
myEmpiLinkSvc.updateLink(person, patient, EmpiMatchResultEnum.NO_MATCH, EmpiLinkSourceEnum.MANUAL, createContextForCreate());
|
myEmpiLinkSvc.updateLink(person, patient, EmpiMatchOutcome.NO_MATCH, EmpiLinkSourceEnum.MANUAL, createContextForCreate());
|
||||||
try {
|
try {
|
||||||
myEmpiLinkSvc.updateLink(person, patient, EmpiMatchResultEnum.MATCH, EmpiLinkSourceEnum.AUTO, null);
|
myEmpiLinkSvc.updateLink(person, patient, EmpiMatchOutcome.NEW_PERSON_MATCH, EmpiLinkSourceEnum.AUTO, null);
|
||||||
fail();
|
fail();
|
||||||
} catch (InternalErrorException e) {
|
} catch (InternalErrorException e) {
|
||||||
assertThat(e.getMessage(), is(equalTo("EMPI system is not allowed to modify links on manually created links")));
|
assertThat(e.getMessage(), is(equalTo("EMPI system is not allowed to modify links on manually created links")));
|
||||||
|
@ -140,7 +142,7 @@ public class EmpiLinkSvcTest extends BaseEmpiR4Test {
|
||||||
|
|
||||||
// Test: it should be impossible to have a AUTO NO_MATCH record. The only NO_MATCH records in the system must be MANUAL.
|
// Test: it should be impossible to have a AUTO NO_MATCH record. The only NO_MATCH records in the system must be MANUAL.
|
||||||
try {
|
try {
|
||||||
myEmpiLinkSvc.updateLink(person, patient, EmpiMatchResultEnum.NO_MATCH, EmpiLinkSourceEnum.AUTO, null);
|
myEmpiLinkSvc.updateLink(person, patient, EmpiMatchOutcome.NO_MATCH, EmpiLinkSourceEnum.AUTO, null);
|
||||||
fail();
|
fail();
|
||||||
} catch (InternalErrorException e) {
|
} catch (InternalErrorException e) {
|
||||||
assertThat(e.getMessage(), is(equalTo("EMPI system is not allowed to automatically NO_MATCH a resource")));
|
assertThat(e.getMessage(), is(equalTo("EMPI system is not allowed to automatically NO_MATCH a resource")));
|
||||||
|
@ -154,8 +156,8 @@ public class EmpiLinkSvcTest extends BaseEmpiR4Test {
|
||||||
Patient patient2 = createPatient(buildJanePatient());
|
Patient patient2 = createPatient(buildJanePatient());
|
||||||
assertEquals(0, myEmpiLinkDao.count());
|
assertEquals(0, myEmpiLinkDao.count());
|
||||||
|
|
||||||
myEmpiLinkDaoSvc.createOrUpdateLinkEntity(person, patient1, EmpiMatchResultEnum.MATCH, EmpiLinkSourceEnum.MANUAL, createContextForCreate());
|
myEmpiLinkDaoSvc.createOrUpdateLinkEntity(person, patient1, EmpiMatchOutcome.NEW_PERSON_MATCH, EmpiLinkSourceEnum.MANUAL, createContextForCreate());
|
||||||
myEmpiLinkDaoSvc.createOrUpdateLinkEntity(person, patient2, EmpiMatchResultEnum.NO_MATCH, EmpiLinkSourceEnum.MANUAL, createContextForCreate());
|
myEmpiLinkDaoSvc.createOrUpdateLinkEntity(person, patient2, EmpiMatchOutcome.NO_MATCH, EmpiLinkSourceEnum.MANUAL, createContextForCreate());
|
||||||
myEmpiLinkSvc.syncEmpiLinksToPersonLinks(person, createContextForCreate());
|
myEmpiLinkSvc.syncEmpiLinksToPersonLinks(person, createContextForCreate());
|
||||||
assertTrue(person.hasLink());
|
assertTrue(person.hasLink());
|
||||||
assertEquals(patient1.getIdElement().toVersionless().getValue(), person.getLinkFirstRep().getTarget().getReference());
|
assertEquals(patient1.getIdElement().toVersionless().getValue(), person.getLinkFirstRep().getTarget().getReference());
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package ca.uhn.fhir.jpa.empi.svc;
|
package ca.uhn.fhir.jpa.empi.svc;
|
||||||
|
|
||||||
import ca.uhn.fhir.empi.api.EmpiConstants;
|
import ca.uhn.fhir.empi.api.EmpiConstants;
|
||||||
|
import ca.uhn.fhir.empi.api.EmpiMatchResultEnum;
|
||||||
import ca.uhn.fhir.empi.model.CanonicalEID;
|
import ca.uhn.fhir.empi.model.CanonicalEID;
|
||||||
import ca.uhn.fhir.empi.util.EIDHelper;
|
import ca.uhn.fhir.empi.util.EIDHelper;
|
||||||
import ca.uhn.fhir.jpa.empi.BaseEmpiR4Test;
|
import ca.uhn.fhir.jpa.empi.BaseEmpiR4Test;
|
||||||
|
@ -15,15 +16,20 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.test.context.TestPropertySource;
|
import org.springframework.test.context.TestPropertySource;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static ca.uhn.fhir.empi.api.EmpiMatchResultEnum.MATCH;
|
||||||
|
import static ca.uhn.fhir.empi.api.EmpiMatchResultEnum.POSSIBLE_DUPLICATE;
|
||||||
|
import static ca.uhn.fhir.empi.api.EmpiMatchResultEnum.POSSIBLE_MATCH;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
import static org.hamcrest.Matchers.hasSize;
|
import static org.hamcrest.Matchers.hasSize;
|
||||||
import static org.hamcrest.Matchers.in;
|
import static org.hamcrest.Matchers.in;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
import static org.hamcrest.Matchers.not;
|
import static org.hamcrest.Matchers.not;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.slf4j.LoggerFactory.getLogger;
|
import static org.slf4j.LoggerFactory.getLogger;
|
||||||
|
|
||||||
@TestPropertySource(properties = {
|
@TestPropertySource(properties = {
|
||||||
|
@ -43,6 +49,9 @@ public class EmpiMatchLinkSvcMultipleEidModeTest extends BaseEmpiR4Test {
|
||||||
public void testIncomingPatientWithEIDThatMatchesPersonWithHapiEidAddsExternalEidsToPerson() {
|
public void testIncomingPatientWithEIDThatMatchesPersonWithHapiEidAddsExternalEidsToPerson() {
|
||||||
// Existing Person with system-assigned EID found linked from matched Patient. incoming Patient has EID. Replace Person system-assigned EID with Patient EID.
|
// Existing Person with system-assigned EID found linked from matched Patient. incoming Patient has EID. Replace Person system-assigned EID with Patient EID.
|
||||||
Patient patient = createPatientAndUpdateLinks(buildJanePatient());
|
Patient patient = createPatientAndUpdateLinks(buildJanePatient());
|
||||||
|
assertLinksMatchResult(MATCH);
|
||||||
|
assertLinksNewPerson(true);
|
||||||
|
assertLinksMatchedByEid(false);
|
||||||
|
|
||||||
Person janePerson = getPersonFromTarget(patient);
|
Person janePerson = getPersonFromTarget(patient);
|
||||||
List<CanonicalEID> hapiEid = myEidHelper.getHapiEid(janePerson);
|
List<CanonicalEID> hapiEid = myEidHelper.getHapiEid(janePerson);
|
||||||
|
@ -52,6 +61,9 @@ public class EmpiMatchLinkSvcMultipleEidModeTest extends BaseEmpiR4Test {
|
||||||
addExternalEID(janePatient, "12345");
|
addExternalEID(janePatient, "12345");
|
||||||
addExternalEID(janePatient, "67890");
|
addExternalEID(janePatient, "67890");
|
||||||
createPatientAndUpdateLinks(janePatient);
|
createPatientAndUpdateLinks(janePatient);
|
||||||
|
assertLinksMatchResult(MATCH, MATCH);
|
||||||
|
assertLinksNewPerson(true, false);
|
||||||
|
assertLinksMatchedByEid(false, false);
|
||||||
|
|
||||||
//We want to make sure the patients were linked to the same person.
|
//We want to make sure the patients were linked to the same person.
|
||||||
assertThat(patient, is(samePersonAs(janePatient)));
|
assertThat(patient, is(samePersonAs(janePatient)));
|
||||||
|
@ -75,6 +87,25 @@ public class EmpiMatchLinkSvcMultipleEidModeTest extends BaseEmpiR4Test {
|
||||||
assertThat(thirdIdentifier.getValue(), is(equalTo("67890")));
|
assertThat(thirdIdentifier.getValue(), is(equalTo("67890")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void assertLinksMatchResult(EmpiMatchResultEnum... theExpectedValues) {
|
||||||
|
assertFields(EmpiLink::getMatchResult, theExpectedValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertLinksNewPerson(Boolean... theExpectedValues) {
|
||||||
|
assertFields(EmpiLink::getNewPerson, theExpectedValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertLinksMatchedByEid(Boolean... theExpectedValues) {
|
||||||
|
assertFields(EmpiLink::getEidMatch, theExpectedValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> void assertFields(Function<EmpiLink, T> theAccessor, T... theExpectedValues) {
|
||||||
|
List<EmpiLink> links = myEmpiLinkDao.findAll();
|
||||||
|
assertEquals(theExpectedValues.length, links.size());
|
||||||
|
for (int i = 0; i < links.size(); ++i) {
|
||||||
|
assertEquals(theExpectedValues[i], theAccessor.apply(links.get(i)), "Value at index " + i + " was not equal");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
// Test Case #4
|
// Test Case #4
|
||||||
|
@ -86,11 +117,17 @@ public class EmpiMatchLinkSvcMultipleEidModeTest extends BaseEmpiR4Test {
|
||||||
addExternalEID(patient1, "id_3");
|
addExternalEID(patient1, "id_3");
|
||||||
addExternalEID(patient1, "id_4");
|
addExternalEID(patient1, "id_4");
|
||||||
createPatientAndUpdateLinks(patient1);
|
createPatientAndUpdateLinks(patient1);
|
||||||
|
assertLinksMatchResult(MATCH);
|
||||||
|
assertLinksNewPerson(true);
|
||||||
|
assertLinksMatchedByEid(false);
|
||||||
|
|
||||||
Patient patient2 = buildPaulPatient();
|
Patient patient2 = buildPaulPatient();
|
||||||
addExternalEID(patient2, "id_5");
|
addExternalEID(patient2, "id_5");
|
||||||
addExternalEID(patient2, "id_1");
|
addExternalEID(patient2, "id_1");
|
||||||
patient2 = createPatientAndUpdateLinks(patient2);
|
patient2 = createPatientAndUpdateLinks(patient2);
|
||||||
|
assertLinksMatchResult(MATCH, MATCH);
|
||||||
|
assertLinksNewPerson(true, false);
|
||||||
|
assertLinksMatchedByEid(false, true);
|
||||||
|
|
||||||
assertThat(patient1, is(samePersonAs(patient2)));
|
assertThat(patient1, is(samePersonAs(patient2)));
|
||||||
|
|
||||||
|
@ -102,13 +139,14 @@ public class EmpiMatchLinkSvcMultipleEidModeTest extends BaseEmpiR4Test {
|
||||||
assertThat(personFromTarget.getIdentifier(), hasSize(5));
|
assertThat(personFromTarget.getIdentifier(), hasSize(5));
|
||||||
|
|
||||||
updatePatientAndUpdateLinks(patient2);
|
updatePatientAndUpdateLinks(patient2);
|
||||||
|
assertLinksMatchResult(MATCH, MATCH);
|
||||||
|
assertLinksNewPerson(true, false);
|
||||||
|
assertLinksMatchedByEid(false, true);
|
||||||
|
|
||||||
assertThat(patient1, is(samePersonAs(patient2)));
|
assertThat(patient1, is(samePersonAs(patient2)));
|
||||||
|
|
||||||
|
|
||||||
personFromTarget = getPersonFromTarget(patient2);
|
personFromTarget = getPersonFromTarget(patient2);
|
||||||
assertThat(personFromTarget.getIdentifier(), hasSize(6));
|
assertThat(personFromTarget.getIdentifier(), hasSize(6));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -118,16 +156,21 @@ public class EmpiMatchLinkSvcMultipleEidModeTest extends BaseEmpiR4Test {
|
||||||
addExternalEID(patient1, "eid-1");
|
addExternalEID(patient1, "eid-1");
|
||||||
addExternalEID(patient1, "eid-11");
|
addExternalEID(patient1, "eid-11");
|
||||||
patient1 = createPatientAndUpdateLinks(patient1);
|
patient1 = createPatientAndUpdateLinks(patient1);
|
||||||
|
assertLinksMatchResult(MATCH);
|
||||||
|
assertLinksNewPerson(true);
|
||||||
|
assertLinksMatchedByEid(false);
|
||||||
|
|
||||||
Patient patient2 = buildJanePatient();
|
Patient patient2 = buildJanePatient();
|
||||||
addExternalEID(patient2, "eid-2");
|
addExternalEID(patient2, "eid-2");
|
||||||
addExternalEID(patient2, "eid-22");
|
addExternalEID(patient2, "eid-22");
|
||||||
patient2 = createPatientAndUpdateLinks(patient2);
|
patient2 = createPatientAndUpdateLinks(patient2);
|
||||||
|
assertLinksMatchResult(MATCH, MATCH, POSSIBLE_DUPLICATE);
|
||||||
|
assertLinksNewPerson(true, true, false);
|
||||||
|
assertLinksMatchedByEid(false, false, false);
|
||||||
|
|
||||||
List<EmpiLink> possibleDuplicates = myEmpiLinkDaoSvc.getPossibleDuplicates();
|
List<EmpiLink> possibleDuplicates = myEmpiLinkDaoSvc.getPossibleDuplicates();
|
||||||
assertThat(possibleDuplicates, hasSize(1));
|
assertThat(possibleDuplicates, hasSize(1));
|
||||||
|
|
||||||
|
|
||||||
List<Long> duplicatePids = Stream.of(patient1, patient2)
|
List<Long> duplicatePids = Stream.of(patient1, patient2)
|
||||||
.map(this::getPersonFromTarget)
|
.map(this::getPersonFromTarget)
|
||||||
.map(myIdHelperService::getPidOrNull)
|
.map(myIdHelperService::getPidOrNull)
|
||||||
|
@ -146,26 +189,39 @@ public class EmpiMatchLinkSvcMultipleEidModeTest extends BaseEmpiR4Test {
|
||||||
addExternalEID(patient1, "eid-1");
|
addExternalEID(patient1, "eid-1");
|
||||||
addExternalEID(patient1, "eid-11");
|
addExternalEID(patient1, "eid-11");
|
||||||
patient1 = createPatientAndUpdateLinks(patient1);
|
patient1 = createPatientAndUpdateLinks(patient1);
|
||||||
|
assertLinksMatchResult(MATCH);
|
||||||
|
assertLinksNewPerson(true);
|
||||||
|
assertLinksMatchedByEid(false);
|
||||||
|
|
||||||
Patient patient2 = buildPaulPatient();
|
Patient patient2 = buildPaulPatient();
|
||||||
addExternalEID(patient2, "eid-2");
|
addExternalEID(patient2, "eid-2");
|
||||||
addExternalEID(patient2, "eid-22");
|
addExternalEID(patient2, "eid-22");
|
||||||
patient2 = createPatientAndUpdateLinks(patient2);
|
patient2 = createPatientAndUpdateLinks(patient2);
|
||||||
|
assertLinksMatchResult(MATCH, MATCH);
|
||||||
|
assertLinksNewPerson(true, true);
|
||||||
|
assertLinksMatchedByEid(false, false);
|
||||||
|
|
||||||
Patient patient3 = buildPaulPatient();
|
Patient patient3 = buildPaulPatient();
|
||||||
addExternalEID(patient3, "eid-22");
|
addExternalEID(patient3, "eid-22");
|
||||||
patient3 = createPatientAndUpdateLinks(patient3);
|
patient3 = createPatientAndUpdateLinks(patient3);
|
||||||
|
assertLinksMatchResult(MATCH, MATCH, MATCH);
|
||||||
|
assertLinksNewPerson(true, true, false);
|
||||||
|
assertLinksMatchedByEid(false, false, true);
|
||||||
|
|
||||||
//Now, Patient 2 and 3 are linked, and the person has 2 eids.
|
//Now, Patient 2 and 3 are linked, and the person has 2 eids.
|
||||||
assertThat(patient2, is(samePersonAs(patient3)));
|
assertThat(patient2, is(samePersonAs(patient3)));
|
||||||
|
|
||||||
//Now lets change one of the EIDs on an incoming patient to one that matches our original patient.
|
//Now lets change one of the EIDs on the second patient to one that matches our original patient.
|
||||||
//This should create a situation in which the incoming EIDs are matched to _two_ unique patients. In this case, we want to
|
//This should create a situation in which the incoming EIDs are matched to _two_ different persons. In this case, we want to
|
||||||
//set them all to possible_match, and set the two persons as possible duplicates.
|
//set them all to possible_match, and set the two persons as possible duplicates.
|
||||||
patient2.getIdentifier().clear();
|
patient2.getIdentifier().clear();
|
||||||
addExternalEID(patient2, "eid-11");
|
addExternalEID(patient2, "eid-11");
|
||||||
addExternalEID(patient2, "eid-22");
|
addExternalEID(patient2, "eid-22");
|
||||||
patient2 = updatePatientAndUpdateLinks(patient2);
|
patient2 = updatePatientAndUpdateLinks(patient2);
|
||||||
|
logAllLinks();
|
||||||
|
assertLinksMatchResult(MATCH, POSSIBLE_MATCH, MATCH, POSSIBLE_MATCH, POSSIBLE_DUPLICATE);
|
||||||
|
assertLinksNewPerson(true, true, false, false, false);
|
||||||
|
assertLinksMatchedByEid(false, true, true, true, true);
|
||||||
|
|
||||||
assertThat(patient2, is(not(matchedToAPerson())));
|
assertThat(patient2, is(not(matchedToAPerson())));
|
||||||
assertThat(patient2, is(possibleMatchWith(patient1)));
|
assertThat(patient2, is(possibleMatchWith(patient1)));
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ca.uhn.fhir.jpa.empi.svc;
|
||||||
|
|
||||||
import ca.uhn.fhir.empi.api.EmpiConstants;
|
import ca.uhn.fhir.empi.api.EmpiConstants;
|
||||||
import ca.uhn.fhir.empi.api.EmpiLinkSourceEnum;
|
import ca.uhn.fhir.empi.api.EmpiLinkSourceEnum;
|
||||||
import ca.uhn.fhir.empi.api.EmpiMatchResultEnum;
|
import ca.uhn.fhir.empi.api.EmpiMatchOutcome;
|
||||||
import ca.uhn.fhir.empi.api.IEmpiLinkSvc;
|
import ca.uhn.fhir.empi.api.IEmpiLinkSvc;
|
||||||
import ca.uhn.fhir.empi.model.CanonicalEID;
|
import ca.uhn.fhir.empi.model.CanonicalEID;
|
||||||
import ca.uhn.fhir.empi.util.EIDHelper;
|
import ca.uhn.fhir.empi.util.EIDHelper;
|
||||||
|
@ -87,7 +87,7 @@ public class EmpiMatchLinkSvcTest extends BaseEmpiR4Test {
|
||||||
|
|
||||||
//Create a manual NO_MATCH between janePerson and unmatchedJane.
|
//Create a manual NO_MATCH between janePerson and unmatchedJane.
|
||||||
Patient unmatchedJane = createPatient(buildJanePatient());
|
Patient unmatchedJane = createPatient(buildJanePatient());
|
||||||
myEmpiLinkSvc.updateLink(janePerson, unmatchedJane, EmpiMatchResultEnum.NO_MATCH, EmpiLinkSourceEnum.MANUAL, createContextForCreate());
|
myEmpiLinkSvc.updateLink(janePerson, unmatchedJane, EmpiMatchOutcome.NO_MATCH, EmpiLinkSourceEnum.MANUAL, createContextForCreate());
|
||||||
|
|
||||||
//rerun EMPI rules against unmatchedJane.
|
//rerun EMPI rules against unmatchedJane.
|
||||||
myEmpiMatchLinkSvc.updateEmpiLinksForEmpiTarget(unmatchedJane, createContextForCreate());
|
myEmpiMatchLinkSvc.updateEmpiLinksForEmpiTarget(unmatchedJane, createContextForCreate());
|
||||||
|
@ -105,7 +105,7 @@ public class EmpiMatchLinkSvcTest extends BaseEmpiR4Test {
|
||||||
Patient unmatchedPatient = createPatient(buildJanePatient());
|
Patient unmatchedPatient = createPatient(buildJanePatient());
|
||||||
|
|
||||||
//This simulates an admin specifically saying that unmatchedPatient does NOT match janePerson.
|
//This simulates an admin specifically saying that unmatchedPatient does NOT match janePerson.
|
||||||
myEmpiLinkSvc.updateLink(janePerson, unmatchedPatient, EmpiMatchResultEnum.NO_MATCH, EmpiLinkSourceEnum.MANUAL, createContextForCreate());
|
myEmpiLinkSvc.updateLink(janePerson, unmatchedPatient, EmpiMatchOutcome.NO_MATCH, EmpiLinkSourceEnum.MANUAL, createContextForCreate());
|
||||||
//TODO change this so that it will only partially match.
|
//TODO change this so that it will only partially match.
|
||||||
|
|
||||||
//Now normally, when we run update links, it should link to janePerson. However, this manual NO_MATCH link
|
//Now normally, when we run update links, it should link to janePerson. However, this manual NO_MATCH link
|
||||||
|
@ -317,7 +317,7 @@ public class EmpiMatchLinkSvcTest extends BaseEmpiR4Test {
|
||||||
//In a normal situation, janePatient2 would just match to jane patient, but here we need to hack it so they are their
|
//In a normal situation, janePatient2 would just match to jane patient, but here we need to hack it so they are their
|
||||||
//own individual Persons for the purpose of this test.
|
//own individual Persons for the purpose of this test.
|
||||||
IAnyResource person = myPersonHelper.createPersonFromEmpiTarget(janePatient2);
|
IAnyResource person = myPersonHelper.createPersonFromEmpiTarget(janePatient2);
|
||||||
myEmpiLinkSvc.updateLink(person, janePatient2, EmpiMatchResultEnum.MATCH, EmpiLinkSourceEnum.AUTO, createContextForCreate());
|
myEmpiLinkSvc.updateLink(person, janePatient2, EmpiMatchOutcome.NEW_PERSON_MATCH, EmpiLinkSourceEnum.AUTO, createContextForCreate());
|
||||||
assertThat(janePatient, is(not(samePersonAs(janePatient2))));
|
assertThat(janePatient, is(not(samePersonAs(janePatient2))));
|
||||||
|
|
||||||
//In theory, this will match both Persons!
|
//In theory, this will match both Persons!
|
||||||
|
@ -386,20 +386,20 @@ public class EmpiMatchLinkSvcTest extends BaseEmpiR4Test {
|
||||||
Person.PersonLinkComponent linkFirstRep = janePerson.getLinkFirstRep();
|
Person.PersonLinkComponent linkFirstRep = janePerson.getLinkFirstRep();
|
||||||
|
|
||||||
assertThat(linkFirstRep.getTarget().getReference(), is(equalTo(patient.getIdElement().toVersionless().toString())));
|
assertThat(linkFirstRep.getTarget().getReference(), is(equalTo(patient.getIdElement().toVersionless().toString())));
|
||||||
assertThat(linkFirstRep.getAssurance(), is(equalTo(Person.IdentityAssuranceLevel.LEVEL3)));
|
assertThat(linkFirstRep.getAssurance(), is(equalTo(Person.IdentityAssuranceLevel.LEVEL2)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testManualMatchesGenerateAssuranceLevel4() {
|
public void testManualMatchesGenerateAssuranceLevel4() {
|
||||||
Patient patient = createPatientAndUpdateLinks(buildJanePatient());
|
Patient patient = createPatientAndUpdateLinks(buildJanePatient());
|
||||||
Person janePerson = getPersonFromTarget(patient);
|
Person janePerson = getPersonFromTarget(patient);
|
||||||
myEmpiLinkSvc.updateLink(janePerson, patient, EmpiMatchResultEnum.MATCH, EmpiLinkSourceEnum.MANUAL, createContextForCreate());
|
myEmpiLinkSvc.updateLink(janePerson, patient, EmpiMatchOutcome.NEW_PERSON_MATCH, EmpiLinkSourceEnum.MANUAL, createContextForCreate());
|
||||||
|
|
||||||
janePerson = getPersonFromTarget(patient);
|
janePerson = getPersonFromTarget(patient);
|
||||||
Person.PersonLinkComponent linkFirstRep = janePerson.getLinkFirstRep();
|
Person.PersonLinkComponent linkFirstRep = janePerson.getLinkFirstRep();
|
||||||
|
|
||||||
assertThat(linkFirstRep.getTarget().getReference(), is(equalTo(patient.getIdElement().toVersionless().toString())));
|
assertThat(linkFirstRep.getTarget().getReference(), is(equalTo(patient.getIdElement().toVersionless().toString())));
|
||||||
assertThat(linkFirstRep.getAssurance(), is(equalTo(Person.IdentityAssuranceLevel.LEVEL4)));
|
assertThat(linkFirstRep.getAssurance(), is(equalTo(Person.IdentityAssuranceLevel.LEVEL3)));
|
||||||
}
|
}
|
||||||
|
|
||||||
//Case #1
|
//Case #1
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package ca.uhn.fhir.jpa.empi.svc;
|
package ca.uhn.fhir.jpa.empi.svc;
|
||||||
|
|
||||||
import ca.uhn.fhir.empi.api.EmpiLinkSourceEnum;
|
import ca.uhn.fhir.empi.api.EmpiLinkSourceEnum;
|
||||||
|
import ca.uhn.fhir.empi.api.EmpiMatchOutcome;
|
||||||
import ca.uhn.fhir.empi.api.EmpiMatchResultEnum;
|
import ca.uhn.fhir.empi.api.EmpiMatchResultEnum;
|
||||||
import ca.uhn.fhir.empi.api.IEmpiPersonMergerSvc;
|
import ca.uhn.fhir.empi.api.IEmpiPersonMergerSvc;
|
||||||
import ca.uhn.fhir.empi.model.EmpiTransactionContext;
|
import ca.uhn.fhir.empi.model.EmpiTransactionContext;
|
||||||
|
@ -28,8 +29,8 @@ import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.hasSize;
|
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.hamcrest.Matchers.hasSize;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.fail;
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
@ -39,6 +40,7 @@ public class EmpiPersonMergerSvcTest extends BaseEmpiR4Test {
|
||||||
public static final String FAMILY_NAME = "Chan";
|
public static final String FAMILY_NAME = "Chan";
|
||||||
public static final String POSTAL_CODE = "M6G 1B4";
|
public static final String POSTAL_CODE = "M6G 1B4";
|
||||||
private static final String BAD_GIVEN_NAME = "Bob";
|
private static final String BAD_GIVEN_NAME = "Bob";
|
||||||
|
private static final EmpiMatchOutcome POSSIBLE_MATCH = new EmpiMatchOutcome(null, null).setMatchResultEnum(EmpiMatchResultEnum.POSSIBLE_MATCH);
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
IEmpiPersonMergerSvc myEmpiPersonMergerSvc;
|
IEmpiPersonMergerSvc myEmpiPersonMergerSvc;
|
||||||
|
@ -107,7 +109,7 @@ public class EmpiPersonMergerSvcTest extends BaseEmpiR4Test {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void mergeRemovesPossibleDuplicatesLink() {
|
public void mergeRemovesPossibleDuplicatesLink() {
|
||||||
EmpiLink empiLink = new EmpiLink().setPersonPid(myToPersonPid).setTargetPid(myFromPersonPid).setMatchResult(EmpiMatchResultEnum.POSSIBLE_DUPLICATE).setLinkSource(EmpiLinkSourceEnum.AUTO);
|
EmpiLink empiLink = myEmpiLinkDaoSvc.newEmpiLink().setPersonPid(myToPersonPid).setTargetPid(myFromPersonPid).setMatchResult(EmpiMatchResultEnum.POSSIBLE_DUPLICATE).setLinkSource(EmpiLinkSourceEnum.AUTO);
|
||||||
saveLink(empiLink);
|
saveLink(empiLink);
|
||||||
assertEquals(1, myEmpiLinkDao.count());
|
assertEquals(1, myEmpiLinkDao.count());
|
||||||
mergePersons();
|
mergePersons();
|
||||||
|
@ -371,7 +373,7 @@ public class EmpiPersonMergerSvcTest extends BaseEmpiR4Test {
|
||||||
|
|
||||||
private EmpiLink createEmpiLink(Person thePerson, Patient theTargetPatient) {
|
private EmpiLink createEmpiLink(Person thePerson, Patient theTargetPatient) {
|
||||||
thePerson.addLink().setTarget(new Reference(theTargetPatient));
|
thePerson.addLink().setTarget(new Reference(theTargetPatient));
|
||||||
return myEmpiLinkDaoSvc.createOrUpdateLinkEntity(thePerson, theTargetPatient, EmpiMatchResultEnum.POSSIBLE_MATCH, EmpiLinkSourceEnum.AUTO, createContextForCreate());
|
return myEmpiLinkDaoSvc.createOrUpdateLinkEntity(thePerson, theTargetPatient, POSSIBLE_MATCH, EmpiLinkSourceEnum.AUTO, createContextForCreate());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void populatePerson(Person thePerson) {
|
private void populatePerson(Person thePerson) {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
|
"version": "1",
|
||||||
"candidateSearchParams": [
|
"candidateSearchParams": [
|
||||||
{
|
{
|
||||||
"resourceType": "Patient",
|
"resourceType": "Patient",
|
||||||
|
|
|
@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.migrate.tasks;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import ca.uhn.fhir.jpa.entity.EmpiLink;
|
||||||
import ca.uhn.fhir.jpa.migrate.DriverTypeEnum;
|
import ca.uhn.fhir.jpa.migrate.DriverTypeEnum;
|
||||||
import ca.uhn.fhir.jpa.migrate.taskdef.ArbitrarySqlTask;
|
import ca.uhn.fhir.jpa.migrate.taskdef.ArbitrarySqlTask;
|
||||||
import ca.uhn.fhir.jpa.migrate.taskdef.CalculateHashesTask;
|
import ca.uhn.fhir.jpa.migrate.taskdef.CalculateHashesTask;
|
||||||
|
@ -126,11 +127,24 @@ public class HapiFhirJpaMigrationTasks extends BaseMigrationTasks<VersionEnum> {
|
||||||
Builder.BuilderWithTableName pkgVerMod = version.onTable("NPM_PACKAGE_VER");
|
Builder.BuilderWithTableName pkgVerMod = version.onTable("NPM_PACKAGE_VER");
|
||||||
pkgVerMod.modifyColumn("20200629.1", "PKG_DESC").nullable().withType(ColumnTypeEnum.STRING, 200);
|
pkgVerMod.modifyColumn("20200629.1", "PKG_DESC").nullable().withType(ColumnTypeEnum.STRING, 200);
|
||||||
pkgVerMod.modifyColumn("20200629.2", "DESC_UPPER").nullable().withType(ColumnTypeEnum.STRING, 200);
|
pkgVerMod.modifyColumn("20200629.2", "DESC_UPPER").nullable().withType(ColumnTypeEnum.STRING, 200);
|
||||||
|
|
||||||
|
init510_20200706_to_20200714();
|
||||||
|
|
||||||
|
Builder.BuilderWithTableName empiLink = version.onTable("MPI_LINK");
|
||||||
|
empiLink.addColumn("20200715.1", "VERSION").nonNullable().type(ColumnTypeEnum.STRING, EmpiLink.VERSION_LENGTH);
|
||||||
|
empiLink.addColumn("20200715.2", "EID_MATCH").nullable().type(ColumnTypeEnum.BOOLEAN);
|
||||||
|
empiLink.addColumn("20200715.3", "NEW_PERSON").nullable().type(ColumnTypeEnum.BOOLEAN);
|
||||||
|
empiLink.addColumn("20200715.4", "VECTOR").nullable().type(ColumnTypeEnum.LONG);
|
||||||
|
empiLink.addColumn("20200715.5", "SCORE").nullable().type(ColumnTypeEnum.FLOAT);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void init510_20200610() {
|
protected void init510_20200610() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void init510_20200706_to_20200714() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private void init501() { //20200514 - present
|
private void init501() { //20200514 - present
|
||||||
Builder version = forVersion(VersionEnum.V5_0_1);
|
Builder version = forVersion(VersionEnum.V5_0_1);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
package ca.uhn.fhir.empi.api;
|
||||||
|
|
||||||
|
public class EmpiMatchEvaluation {
|
||||||
|
public final boolean match;
|
||||||
|
public final double score;
|
||||||
|
|
||||||
|
public EmpiMatchEvaluation(boolean theMatch, double theScore) {
|
||||||
|
match = theMatch;
|
||||||
|
score = theScore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EmpiMatchEvaluation max(EmpiMatchEvaluation theLeft, EmpiMatchEvaluation theRight) {
|
||||||
|
return new EmpiMatchEvaluation(theLeft.match | theRight.match, Math.max(theLeft.score, theRight.score));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
package ca.uhn.fhir.empi.api;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This data object captures the final outcome of an EMPI match
|
||||||
|
*/
|
||||||
|
public final class EmpiMatchOutcome {
|
||||||
|
public static final EmpiMatchOutcome POSSIBLE_DUPLICATE = new EmpiMatchOutcome(null, null).setMatchResultEnum(EmpiMatchResultEnum.POSSIBLE_DUPLICATE);
|
||||||
|
public static final EmpiMatchOutcome NO_MATCH = new EmpiMatchOutcome(null, null).setMatchResultEnum(EmpiMatchResultEnum.NO_MATCH);
|
||||||
|
public static final EmpiMatchOutcome NEW_PERSON_MATCH = new EmpiMatchOutcome(null, null).setMatchResultEnum(EmpiMatchResultEnum.MATCH).setNewPerson(true);
|
||||||
|
public static final EmpiMatchOutcome EID_MATCH = new EmpiMatchOutcome(null, null).setMatchResultEnum(EmpiMatchResultEnum.MATCH).setEidMatch(true);
|
||||||
|
public static final EmpiMatchOutcome EID_POSSIBLE_MATCH = new EmpiMatchOutcome(null, null).setMatchResultEnum(EmpiMatchResultEnum.POSSIBLE_MATCH).setEidMatch(true);
|
||||||
|
public static final EmpiMatchOutcome EID_POSSIBLE_DUPLICATE = new EmpiMatchOutcome(null, null).setMatchResultEnum(EmpiMatchResultEnum.POSSIBLE_DUPLICATE).setEidMatch(true);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A bitmap that indicates which rules matched
|
||||||
|
*/
|
||||||
|
public final Long vector;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The sum of all scores for all rules evaluated. Similarity rules add the similarity score (between 0.0 and 1.0) whereas
|
||||||
|
* matcher rules add either a 0.0 or 1.0.
|
||||||
|
*/
|
||||||
|
public final Double score;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Did the EMPI match operation result in creating a new Person resource?
|
||||||
|
*/
|
||||||
|
private boolean myNewPerson;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Did the EMPI match occur as a result of EIDs matching?
|
||||||
|
*/
|
||||||
|
private boolean myEidMatch;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Based on the EMPI Rules, what was the final match result?
|
||||||
|
*/
|
||||||
|
private EmpiMatchResultEnum myMatchResultEnum;
|
||||||
|
|
||||||
|
public EmpiMatchOutcome(Long theVector, Double theScore) {
|
||||||
|
vector = theVector;
|
||||||
|
score = theScore;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMatch() {
|
||||||
|
return myMatchResultEnum == EmpiMatchResultEnum.MATCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPossibleMatch() {
|
||||||
|
return myMatchResultEnum == EmpiMatchResultEnum.POSSIBLE_MATCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean isPossibleDuplicate() {
|
||||||
|
return myMatchResultEnum == EmpiMatchResultEnum.POSSIBLE_DUPLICATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EmpiMatchResultEnum getMatchResultEnum() {
|
||||||
|
return myMatchResultEnum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EmpiMatchOutcome setMatchResultEnum(EmpiMatchResultEnum theMatchResultEnum) {
|
||||||
|
myMatchResultEnum = theMatchResultEnum;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNewPerson() {
|
||||||
|
return myNewPerson;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param theNewPerson this match is creating a new person */
|
||||||
|
public EmpiMatchOutcome setNewPerson(boolean theNewPerson) {
|
||||||
|
myNewPerson = theNewPerson;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEidMatch() {
|
||||||
|
return myEidMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param theEidMatch the link was established via a shared EID */
|
||||||
|
public EmpiMatchOutcome setEidMatch(boolean theEidMatch) {
|
||||||
|
myEidMatch = theEidMatch;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
|
@ -39,7 +39,12 @@ public enum EmpiMatchResultEnum {
|
||||||
/**
|
/**
|
||||||
* Link between two Person resources indicating they may be duplicates.
|
* Link between two Person resources indicating they may be duplicates.
|
||||||
*/
|
*/
|
||||||
POSSIBLE_DUPLICATE
|
POSSIBLE_DUPLICATE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Link between Person and Target pointing to the Golden Record for that Person
|
||||||
|
*/
|
||||||
|
|
||||||
|
GOLDEN_RECORD
|
||||||
// Stored in database as ORDINAL. Only add new values to bottom!
|
// Stored in database as ORDINAL. Only add new values to bottom!
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ public interface IEmpiLinkSvc {
|
||||||
* @param theLinkSource MANUAL or AUTO: what caused the link.
|
* @param theLinkSource MANUAL or AUTO: what caused the link.
|
||||||
* @param theEmpiTransactionContext
|
* @param theEmpiTransactionContext
|
||||||
*/
|
*/
|
||||||
void updateLink(IAnyResource thePerson, IAnyResource theTargetResource, EmpiMatchResultEnum theMatchResult, EmpiLinkSourceEnum theLinkSource, EmpiTransactionContext theEmpiTransactionContext);
|
void updateLink(IAnyResource thePerson, IAnyResource theTargetResource, EmpiMatchOutcome theMatchResult, EmpiLinkSourceEnum theLinkSource, EmpiTransactionContext theEmpiTransactionContext);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replace Person.link values from what they should be based on EmpiLink values
|
* Replace Person.link values from what they should be based on EmpiLink values
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
package ca.uhn.fhir.empi.api;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.empi.rules.json.EmpiRulesJson;
|
||||||
|
|
||||||
|
public interface IEmpiRuleValidator {
|
||||||
|
void validate(EmpiRulesJson theEmpiRules);
|
||||||
|
}
|
|
@ -35,4 +35,6 @@ public interface IEmpiSettings {
|
||||||
boolean isPreventEidUpdates();
|
boolean isPreventEidUpdates();
|
||||||
|
|
||||||
boolean isPreventMultipleEids();
|
boolean isPreventMultipleEids();
|
||||||
|
|
||||||
|
String getRuleVersion();
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,9 +25,9 @@ import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||||
public class MatchedTarget {
|
public class MatchedTarget {
|
||||||
|
|
||||||
private final IAnyResource myTarget;
|
private final IAnyResource myTarget;
|
||||||
private final EmpiMatchResultEnum myMatchResult;
|
private final EmpiMatchOutcome myMatchResult;
|
||||||
|
|
||||||
public MatchedTarget(IAnyResource theTarget, EmpiMatchResultEnum theMatchResult) {
|
public MatchedTarget(IAnyResource theTarget, EmpiMatchOutcome theMatchResult) {
|
||||||
myTarget = theTarget;
|
myTarget = theTarget;
|
||||||
myMatchResult = theMatchResult;
|
myMatchResult = theMatchResult;
|
||||||
}
|
}
|
||||||
|
@ -36,15 +36,15 @@ public class MatchedTarget {
|
||||||
return myTarget;
|
return myTarget;
|
||||||
}
|
}
|
||||||
|
|
||||||
public EmpiMatchResultEnum getMatchResult() {
|
public EmpiMatchOutcome getMatchResult() {
|
||||||
return myMatchResult;
|
return myMatchResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isMatch() {
|
public boolean isMatch() {
|
||||||
return myMatchResult == EmpiMatchResultEnum.MATCH;
|
return myMatchResult.isMatch();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isPossibleMatch() {
|
public boolean isPossibleMatch() {
|
||||||
return myMatchResult == EmpiMatchResultEnum.POSSIBLE_MATCH;
|
return myMatchResult.isPossibleMatch();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ package ca.uhn.fhir.empi.rules.config;
|
||||||
import ca.uhn.fhir.context.ConfigurationException;
|
import ca.uhn.fhir.context.ConfigurationException;
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.empi.api.EmpiConstants;
|
import ca.uhn.fhir.empi.api.EmpiConstants;
|
||||||
|
import ca.uhn.fhir.empi.api.IEmpiRuleValidator;
|
||||||
import ca.uhn.fhir.empi.rules.json.EmpiFieldMatchJson;
|
import ca.uhn.fhir.empi.rules.json.EmpiFieldMatchJson;
|
||||||
import ca.uhn.fhir.empi.rules.json.EmpiFilterSearchParamJson;
|
import ca.uhn.fhir.empi.rules.json.EmpiFilterSearchParamJson;
|
||||||
import ca.uhn.fhir.empi.rules.json.EmpiResourceSearchParamJson;
|
import ca.uhn.fhir.empi.rules.json.EmpiResourceSearchParamJson;
|
||||||
|
@ -42,7 +43,7 @@ import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class EmpiRuleValidator {
|
public class EmpiRuleValidator implements IEmpiRuleValidator {
|
||||||
private static final Logger ourLog = LoggerFactory.getLogger(EmpiRuleValidator.class);
|
private static final Logger ourLog = LoggerFactory.getLogger(EmpiRuleValidator.class);
|
||||||
|
|
||||||
private final FhirContext myFhirContext;
|
private final FhirContext myFhirContext;
|
||||||
|
|
|
@ -20,6 +20,7 @@ package ca.uhn.fhir.empi.rules.config;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import ca.uhn.fhir.empi.api.IEmpiRuleValidator;
|
||||||
import ca.uhn.fhir.empi.api.IEmpiSettings;
|
import ca.uhn.fhir.empi.api.IEmpiSettings;
|
||||||
import ca.uhn.fhir.empi.rules.json.EmpiRulesJson;
|
import ca.uhn.fhir.empi.rules.json.EmpiRulesJson;
|
||||||
import ca.uhn.fhir.util.JsonUtil;
|
import ca.uhn.fhir.util.JsonUtil;
|
||||||
|
@ -30,7 +31,7 @@ import java.io.IOException;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class EmpiSettings implements IEmpiSettings {
|
public class EmpiSettings implements IEmpiSettings {
|
||||||
private final EmpiRuleValidator myEmpiRuleValidator;
|
private final IEmpiRuleValidator myEmpiRuleValidator;
|
||||||
|
|
||||||
private boolean myEnabled;
|
private boolean myEnabled;
|
||||||
private int myConcurrentConsumers = EMPI_DEFAULT_CONCURRENT_CONSUMERS;
|
private int myConcurrentConsumers = EMPI_DEFAULT_CONCURRENT_CONSUMERS;
|
||||||
|
@ -48,7 +49,7 @@ public class EmpiSettings implements IEmpiSettings {
|
||||||
private boolean myPreventMultipleEids;
|
private boolean myPreventMultipleEids;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public EmpiSettings(EmpiRuleValidator theEmpiRuleValidator) {
|
public EmpiSettings(IEmpiRuleValidator theEmpiRuleValidator) {
|
||||||
myEmpiRuleValidator = theEmpiRuleValidator;
|
myEmpiRuleValidator = theEmpiRuleValidator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,6 +108,11 @@ public class EmpiSettings implements IEmpiSettings {
|
||||||
return myPreventMultipleEids;
|
return myPreventMultipleEids;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRuleVersion() {
|
||||||
|
return myEmpiRules.getVersion();
|
||||||
|
}
|
||||||
|
|
||||||
public EmpiSettings setPreventMultipleEids(boolean thePreventMultipleEids) {
|
public EmpiSettings setPreventMultipleEids(boolean thePreventMultipleEids) {
|
||||||
myPreventMultipleEids = thePreventMultipleEids;
|
myPreventMultipleEids = thePreventMultipleEids;
|
||||||
return this;
|
return this;
|
||||||
|
|
|
@ -26,6 +26,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||||
import com.fasterxml.jackson.databind.util.StdConverter;
|
import com.fasterxml.jackson.databind.util.StdConverter;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import org.apache.commons.lang3.Validate;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -35,6 +36,8 @@ import java.util.Map;
|
||||||
|
|
||||||
@JsonDeserialize(converter = EmpiRulesJson.EmpiRulesJsonConverter.class)
|
@JsonDeserialize(converter = EmpiRulesJson.EmpiRulesJsonConverter.class)
|
||||||
public class EmpiRulesJson implements IModelJson {
|
public class EmpiRulesJson implements IModelJson {
|
||||||
|
@JsonProperty(value = "version", required = true)
|
||||||
|
String myVersion;
|
||||||
@JsonProperty(value = "candidateSearchParams", required = true)
|
@JsonProperty(value = "candidateSearchParams", required = true)
|
||||||
List<EmpiResourceSearchParamJson> myCandidateSearchParams = new ArrayList<>();
|
List<EmpiResourceSearchParamJson> myCandidateSearchParams = new ArrayList<>();
|
||||||
@JsonProperty(value = "candidateFilterSearchParams", required = true)
|
@JsonProperty(value = "candidateFilterSearchParams", required = true)
|
||||||
|
@ -112,22 +115,17 @@ public class EmpiRulesJson implements IModelJson {
|
||||||
myEnterpriseEIDSystem = theEnterpriseEIDSystem;
|
myEnterpriseEIDSystem = theEnterpriseEIDSystem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public String getVersion() {
|
||||||
* Ensure the vector map is initialized after we deserialize
|
return myVersion;
|
||||||
*/
|
}
|
||||||
static class EmpiRulesJsonConverter extends StdConverter<EmpiRulesJson, EmpiRulesJson> {
|
|
||||||
|
|
||||||
/**
|
public EmpiRulesJson setVersion(String theVersion) {
|
||||||
* This empty constructor is required by Jackson
|
myVersion = theVersion;
|
||||||
*/
|
return this;
|
||||||
public EmpiRulesJsonConverter() {
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
private void validate() {
|
||||||
public EmpiRulesJson convert(EmpiRulesJson theEmpiRulesJson) {
|
Validate.notBlank(myVersion, "version may not be blank");
|
||||||
theEmpiRulesJson.initialize();
|
|
||||||
return theEmpiRulesJson;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getSummary() {
|
public String getSummary() {
|
||||||
|
@ -157,4 +155,23 @@ public class EmpiRulesJson implements IModelJson {
|
||||||
VectorMatchResultMap getVectorMatchResultMapForUnitTest() {
|
VectorMatchResultMap getVectorMatchResultMapForUnitTest() {
|
||||||
return myVectorMatchResultMap;
|
return myVectorMatchResultMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure the vector map is initialized after we deserialize
|
||||||
|
*/
|
||||||
|
static class EmpiRulesJsonConverter extends StdConverter<EmpiRulesJson, EmpiRulesJson> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This empty constructor is required by Jackson
|
||||||
|
*/
|
||||||
|
public EmpiRulesJsonConverter() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EmpiRulesJson convert(EmpiRulesJson theEmpiRulesJson) {
|
||||||
|
theEmpiRulesJson.validate();
|
||||||
|
theEmpiRulesJson.initialize();
|
||||||
|
return theEmpiRulesJson;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ package ca.uhn.fhir.empi.rules.metric;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.context.phonetic.PhoneticEncoderEnum;
|
import ca.uhn.fhir.context.phonetic.PhoneticEncoderEnum;
|
||||||
|
import ca.uhn.fhir.empi.api.EmpiMatchEvaluation;
|
||||||
import ca.uhn.fhir.empi.rules.metric.matcher.EmpiPersonNameMatchModeEnum;
|
import ca.uhn.fhir.empi.rules.metric.matcher.EmpiPersonNameMatchModeEnum;
|
||||||
import ca.uhn.fhir.empi.rules.metric.matcher.HapiDateMatcher;
|
import ca.uhn.fhir.empi.rules.metric.matcher.HapiDateMatcher;
|
||||||
import ca.uhn.fhir.empi.rules.metric.matcher.HapiStringMatcher;
|
import ca.uhn.fhir.empi.rules.metric.matcher.HapiStringMatcher;
|
||||||
|
@ -77,15 +78,25 @@ public enum EmpiMetricEnum {
|
||||||
return ((IEmpiFieldMatcher) myEmpiFieldMetric).matches(theFhirContext, theLeftBase, theRightBase, theExact);
|
return ((IEmpiFieldMatcher) myEmpiFieldMetric).matches(theFhirContext, theLeftBase, theRightBase, theExact);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean match(FhirContext theFhirContext, IBase theLeftBase, IBase theRightBase, boolean theExact, @Nullable Double theThreshold) {
|
public EmpiMatchEvaluation match(FhirContext theFhirContext, IBase theLeftBase, IBase theRightBase, boolean theExact, @Nullable Double theThreshold) {
|
||||||
if (isSimilarity()) {
|
if (isSimilarity()) {
|
||||||
return ((IEmpiFieldSimilarity) myEmpiFieldMetric).similarity(theFhirContext, theLeftBase, theRightBase, theExact) >= theThreshold;
|
return matchBySimilarity((IEmpiFieldSimilarity) myEmpiFieldMetric, theFhirContext, theLeftBase, theRightBase, theExact, theThreshold);
|
||||||
} else {
|
} else {
|
||||||
return ((IEmpiFieldMatcher) myEmpiFieldMetric).matches(theFhirContext, theLeftBase, theRightBase, theExact);
|
return matchByMatcher((IEmpiFieldMatcher) myEmpiFieldMetric, theFhirContext, theLeftBase, theRightBase, theExact);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSimilarity() {
|
private EmpiMatchEvaluation matchBySimilarity(IEmpiFieldSimilarity theSimilarity, FhirContext theFhirContext, IBase theLeftBase, IBase theRightBase, boolean theExact, Double theThreshold) {
|
||||||
|
double similarityResult = theSimilarity.similarity(theFhirContext, theLeftBase, theRightBase, theExact);
|
||||||
|
return new EmpiMatchEvaluation(similarityResult >= theThreshold, similarityResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
private EmpiMatchEvaluation matchByMatcher(IEmpiFieldMatcher theMatcher, FhirContext theFhirContext, IBase theLeftBase, IBase theRightBase, boolean theExact) {
|
||||||
|
boolean matcherResult = theMatcher.matches(theFhirContext, theLeftBase, theRightBase, theExact);
|
||||||
|
return new EmpiMatchEvaluation(matcherResult, matcherResult ? 1.0 : 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSimilarity() {
|
||||||
return myEmpiFieldMetric instanceof IEmpiFieldSimilarity;
|
return myEmpiFieldMetric instanceof IEmpiFieldSimilarity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ package ca.uhn.fhir.empi.rules.svc;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.empi.api.EmpiMatchEvaluation;
|
||||||
import ca.uhn.fhir.empi.rules.json.EmpiFieldMatchJson;
|
import ca.uhn.fhir.empi.rules.json.EmpiFieldMatchJson;
|
||||||
import ca.uhn.fhir.util.FhirTerser;
|
import ca.uhn.fhir.util.FhirTerser;
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
|
@ -58,7 +59,7 @@ public class EmpiResourceFieldMatcher {
|
||||||
* @return A boolean indicating whether they match.
|
* @return A boolean indicating whether they match.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
public boolean match(IBaseResource theLeftResource, IBaseResource theRightResource) {
|
public EmpiMatchEvaluation match(IBaseResource theLeftResource, IBaseResource theRightResource) {
|
||||||
validate(theLeftResource);
|
validate(theLeftResource);
|
||||||
validate(theRightResource);
|
validate(theRightResource);
|
||||||
|
|
||||||
|
@ -69,17 +70,18 @@ public class EmpiResourceFieldMatcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
private boolean match(List<IBase> theLeftValues, List<IBase> theRightValues) {
|
private EmpiMatchEvaluation match(List<IBase> theLeftValues, List<IBase> theRightValues) {
|
||||||
boolean retval = false;
|
EmpiMatchEvaluation retval = new EmpiMatchEvaluation(false, 0.0);
|
||||||
for (IBase leftValue : theLeftValues) {
|
for (IBase leftValue : theLeftValues) {
|
||||||
for (IBase rightValue : theRightValues) {
|
for (IBase rightValue : theRightValues) {
|
||||||
retval |= match(leftValue, rightValue);
|
EmpiMatchEvaluation nextMatch = match(leftValue, rightValue);
|
||||||
|
retval = EmpiMatchEvaluation.max(retval, nextMatch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean match(IBase theLeftValue, IBase theRightValue) {
|
private EmpiMatchEvaluation match(IBase theLeftValue, IBase theRightValue) {
|
||||||
return myEmpiFieldMatchJson.getMetric().match(myFhirContext, theLeftValue, theRightValue, myEmpiFieldMatchJson.getExact(), myEmpiFieldMatchJson.getMatchThreshold());
|
return myEmpiFieldMatchJson.getMetric().match(myFhirContext, theLeftValue, theRightValue, myEmpiFieldMatchJson.getExact(), myEmpiFieldMatchJson.getMatchThreshold());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,8 @@ package ca.uhn.fhir.empi.rules.svc;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.ConfigurationException;
|
import ca.uhn.fhir.context.ConfigurationException;
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.empi.api.EmpiMatchEvaluation;
|
||||||
|
import ca.uhn.fhir.empi.api.EmpiMatchOutcome;
|
||||||
import ca.uhn.fhir.empi.api.EmpiMatchResultEnum;
|
import ca.uhn.fhir.empi.api.EmpiMatchResultEnum;
|
||||||
import ca.uhn.fhir.empi.api.IEmpiSettings;
|
import ca.uhn.fhir.empi.api.IEmpiSettings;
|
||||||
import ca.uhn.fhir.empi.log.Logs;
|
import ca.uhn.fhir.empi.log.Logs;
|
||||||
|
@ -47,19 +49,19 @@ public class EmpiResourceMatcherSvc {
|
||||||
private static final Logger ourLog = Logs.getEmpiTroubleshootingLog();
|
private static final Logger ourLog = Logs.getEmpiTroubleshootingLog();
|
||||||
|
|
||||||
private final FhirContext myFhirContext;
|
private final FhirContext myFhirContext;
|
||||||
private final IEmpiSettings myEmpiConfig;
|
private final IEmpiSettings myEmpiSettings;
|
||||||
private EmpiRulesJson myEmpiRulesJson;
|
private EmpiRulesJson myEmpiRulesJson;
|
||||||
private final List<EmpiResourceFieldMatcher> myFieldMatchers = new ArrayList<>();
|
private final List<EmpiResourceFieldMatcher> myFieldMatchers = new ArrayList<>();
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public EmpiResourceMatcherSvc(FhirContext theFhirContext, IEmpiSettings theEmpiConfig) {
|
public EmpiResourceMatcherSvc(FhirContext theFhirContext, IEmpiSettings theEmpiSettings) {
|
||||||
myFhirContext = theFhirContext;
|
myFhirContext = theFhirContext;
|
||||||
myEmpiConfig = theEmpiConfig;
|
myEmpiSettings = theEmpiSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void init() {
|
public void init() {
|
||||||
myEmpiRulesJson = myEmpiConfig.getEmpiRules();
|
myEmpiRulesJson = myEmpiSettings.getEmpiRules();
|
||||||
if (myEmpiRulesJson == null) {
|
if (myEmpiRulesJson == null) {
|
||||||
throw new ConfigurationException("Failed to load EMPI Rules. If EMPI is enabled, then EMPI rules must be available in context.");
|
throw new ConfigurationException("Failed to load EMPI Rules. If EMPI is enabled, then EMPI rules must be available in context.");
|
||||||
}
|
}
|
||||||
|
@ -78,18 +80,19 @@ public class EmpiResourceMatcherSvc {
|
||||||
*
|
*
|
||||||
* @return an {@link EmpiMatchResultEnum} indicating the result of the comparison.
|
* @return an {@link EmpiMatchResultEnum} indicating the result of the comparison.
|
||||||
*/
|
*/
|
||||||
public EmpiMatchResultEnum getMatchResult(IBaseResource theLeftResource, IBaseResource theRightResource) {
|
public EmpiMatchOutcome getMatchResult(IBaseResource theLeftResource, IBaseResource theRightResource) {
|
||||||
return match(theLeftResource, theRightResource);
|
return match(theLeftResource, theRightResource);
|
||||||
}
|
}
|
||||||
|
|
||||||
EmpiMatchResultEnum match(IBaseResource theLeftResource, IBaseResource theRightResource) {
|
EmpiMatchOutcome match(IBaseResource theLeftResource, IBaseResource theRightResource) {
|
||||||
long matchVector = getMatchVector(theLeftResource, theRightResource);
|
EmpiMatchOutcome matchResult = getMatchOutcome(theLeftResource, theRightResource);
|
||||||
EmpiMatchResultEnum matchResult = myEmpiRulesJson.getMatchResult(matchVector);
|
EmpiMatchResultEnum matchResultEnum = myEmpiRulesJson.getMatchResult(matchResult.vector);
|
||||||
|
matchResult.setMatchResultEnum(matchResultEnum);
|
||||||
if (ourLog.isDebugEnabled()) {
|
if (ourLog.isDebugEnabled()) {
|
||||||
if (matchResult == EmpiMatchResultEnum.MATCH || matchResult == EmpiMatchResultEnum.POSSIBLE_MATCH) {
|
if (matchResult.isMatch() || matchResult.isPossibleMatch()) {
|
||||||
ourLog.debug("{} {} with field matchers {}", matchResult, theRightResource.getIdElement().toUnqualifiedVersionless(), myEmpiRulesJson.getFieldMatchNamesForVector(matchVector));
|
ourLog.debug("{} {} with field matchers {}", matchResult, theRightResource.getIdElement().toUnqualifiedVersionless(), myEmpiRulesJson.getFieldMatchNamesForVector(matchResult.vector));
|
||||||
} else if (ourLog.isTraceEnabled()) {
|
} else if (ourLog.isTraceEnabled()) {
|
||||||
ourLog.trace("{} {}. Field matcher results: {}", matchResult, theRightResource.getIdElement().toUnqualifiedVersionless(), myEmpiRulesJson.getDetailedFieldMatchResultForUnmatchedVector(matchVector));
|
ourLog.trace("{} {}. Field matcher results: {}", matchResult, theRightResource.getIdElement().toUnqualifiedVersionless(), myEmpiRulesJson.getDetailedFieldMatchResultForUnmatchedVector(matchResult.vector));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return matchResult;
|
return matchResult;
|
||||||
|
@ -110,15 +113,18 @@ public class EmpiResourceMatcherSvc {
|
||||||
* 0001|0010 = 0011
|
* 0001|0010 = 0011
|
||||||
* The binary string is now `0011`, which when you return it as a long becomes `3`.
|
* The binary string is now `0011`, which when you return it as a long becomes `3`.
|
||||||
*/
|
*/
|
||||||
private long getMatchVector(IBaseResource theLeftResource, IBaseResource theRightResource) {
|
private EmpiMatchOutcome getMatchOutcome(IBaseResource theLeftResource, IBaseResource theRightResource) {
|
||||||
long retval = 0;
|
long vector = 0;
|
||||||
|
double score = 0.0;
|
||||||
for (int i = 0; i < myFieldMatchers.size(); ++i) {
|
for (int i = 0; i < myFieldMatchers.size(); ++i) {
|
||||||
//any that are not for the resourceType in question.
|
//any that are not for the resourceType in question.
|
||||||
EmpiResourceFieldMatcher fieldComparator = myFieldMatchers.get(i);
|
EmpiResourceFieldMatcher fieldComparator = myFieldMatchers.get(i);
|
||||||
if (fieldComparator.match(theLeftResource, theRightResource)) {
|
EmpiMatchEvaluation matchEvaluation = fieldComparator.match(theLeftResource, theRightResource);
|
||||||
retval |= (1 << i);
|
if (matchEvaluation.match) {
|
||||||
|
vector |= (1 << i);
|
||||||
}
|
}
|
||||||
|
score += matchEvaluation.score;
|
||||||
}
|
}
|
||||||
return retval;
|
return new EmpiMatchOutcome(vector, score);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,9 +47,9 @@ public final class AssuranceLevelUtil {
|
||||||
private static CanonicalIdentityAssuranceLevel getAssuranceFromAutoResult(EmpiMatchResultEnum theMatchResult) {
|
private static CanonicalIdentityAssuranceLevel getAssuranceFromAutoResult(EmpiMatchResultEnum theMatchResult) {
|
||||||
switch (theMatchResult) {
|
switch (theMatchResult) {
|
||||||
case MATCH:
|
case MATCH:
|
||||||
return CanonicalIdentityAssuranceLevel.LEVEL3;
|
|
||||||
case POSSIBLE_MATCH:
|
|
||||||
return CanonicalIdentityAssuranceLevel.LEVEL2;
|
return CanonicalIdentityAssuranceLevel.LEVEL2;
|
||||||
|
case POSSIBLE_MATCH:
|
||||||
|
return CanonicalIdentityAssuranceLevel.LEVEL1;
|
||||||
case POSSIBLE_DUPLICATE:
|
case POSSIBLE_DUPLICATE:
|
||||||
case NO_MATCH:
|
case NO_MATCH:
|
||||||
default:
|
default:
|
||||||
|
@ -60,7 +60,7 @@ public final class AssuranceLevelUtil {
|
||||||
private static CanonicalIdentityAssuranceLevel getAssuranceFromManualResult(EmpiMatchResultEnum theMatchResult) {
|
private static CanonicalIdentityAssuranceLevel getAssuranceFromManualResult(EmpiMatchResultEnum theMatchResult) {
|
||||||
switch (theMatchResult) {
|
switch (theMatchResult) {
|
||||||
case MATCH:
|
case MATCH:
|
||||||
return CanonicalIdentityAssuranceLevel.LEVEL4;
|
return CanonicalIdentityAssuranceLevel.LEVEL3;
|
||||||
case NO_MATCH:
|
case NO_MATCH:
|
||||||
case POSSIBLE_DUPLICATE:
|
case POSSIBLE_DUPLICATE:
|
||||||
case POSSIBLE_MATCH:
|
case POSSIBLE_MATCH:
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package ca.uhn.fhir.empi;
|
package ca.uhn.fhir.empi;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.empi.api.EmpiMatchOutcome;
|
||||||
|
import ca.uhn.fhir.empi.api.EmpiMatchResultEnum;
|
||||||
import ca.uhn.fhir.empi.rules.config.EmpiRuleValidator;
|
import ca.uhn.fhir.empi.rules.config.EmpiRuleValidator;
|
||||||
import ca.uhn.fhir.empi.rules.config.EmpiSettings;
|
import ca.uhn.fhir.empi.rules.config.EmpiSettings;
|
||||||
import ca.uhn.fhir.empi.rules.json.EmpiRulesJson;
|
import ca.uhn.fhir.empi.rules.json.EmpiRulesJson;
|
||||||
|
@ -8,9 +10,9 @@ import ca.uhn.fhir.empi.rules.svc.EmpiResourceMatcherSvc;
|
||||||
import ca.uhn.fhir.rest.server.util.ISearchParamRetriever;
|
import ca.uhn.fhir.rest.server.util.ISearchParamRetriever;
|
||||||
import org.hl7.fhir.r4.model.Patient;
|
import org.hl7.fhir.r4.model.Patient;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.mockito.junit.MockitoJUnitRunner;
|
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
@ -37,4 +39,16 @@ public abstract class BaseR4Test {
|
||||||
retval.init();
|
retval.init();
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void assertMatch(EmpiMatchResultEnum theExpectedMatchEnum, EmpiMatchOutcome theMatchResult) {
|
||||||
|
assertEquals(theExpectedMatchEnum, theMatchResult.getMatchResultEnum());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void assertMatchResult(EmpiMatchResultEnum theExpectedMatchEnum, long theExpectedVector, double theExpectedScore, boolean theExpectedNewPerson, boolean theExpectedEidMatch, EmpiMatchOutcome theMatchResult) {
|
||||||
|
assertEquals(theExpectedScore, theMatchResult.score, 0.001);
|
||||||
|
assertEquals(theExpectedVector, theMatchResult.vector);
|
||||||
|
assertEquals(theExpectedEidMatch, theMatchResult.isEidMatch());
|
||||||
|
assertEquals(theExpectedNewPerson, theMatchResult.isNewPerson());
|
||||||
|
assertEquals(theExpectedMatchEnum, theMatchResult.getMatchResultEnum());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,8 @@ import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.hamcrest.Matchers.containsString;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.fail;
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
|
||||||
|
@ -27,6 +29,16 @@ public class EmpiRulesJsonR4Test extends BaseEmpiRulesR4Test {
|
||||||
myRules = buildActiveBirthdateIdRules();
|
myRules = buildActiveBirthdateIdRules();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testValidate() throws IOException {
|
||||||
|
EmpiRulesJson rules = new EmpiRulesJson();
|
||||||
|
try {
|
||||||
|
JsonUtil.serialize(rules);
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
assertThat(e.getMessage(), containsString("version may not be blank"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSerDeser() throws IOException {
|
public void testSerDeser() throws IOException {
|
||||||
String json = JsonUtil.serialize(myRules);
|
String json = JsonUtil.serialize(myRules);
|
||||||
|
|
|
@ -51,6 +51,7 @@ public abstract class BaseEmpiRulesR4Test extends BaseR4Test {
|
||||||
.setMatchThreshold(NAME_THRESHOLD);
|
.setMatchThreshold(NAME_THRESHOLD);
|
||||||
|
|
||||||
EmpiRulesJson retval = new EmpiRulesJson();
|
EmpiRulesJson retval = new EmpiRulesJson();
|
||||||
|
retval.setVersion("test version");
|
||||||
retval.addResourceSearchParam(patientBirthdayBlocking);
|
retval.addResourceSearchParam(patientBirthdayBlocking);
|
||||||
retval.addResourceSearchParam(patientIdentifierBlocking);
|
retval.addResourceSearchParam(patientIdentifierBlocking);
|
||||||
retval.addFilterSearchParam(activePatientsBlockingFilter);
|
retval.addFilterSearchParam(activePatientsBlockingFilter);
|
||||||
|
|
|
@ -10,8 +10,6 @@ import org.hl7.fhir.r4.model.Patient;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
|
|
||||||
public class CustomResourceMatcherR4Test extends BaseR4Test {
|
public class CustomResourceMatcherR4Test extends BaseR4Test {
|
||||||
|
|
||||||
public static final String FIELD_EXACT_MATCH_NAME = EmpiMetricEnum.NAME_ANY_ORDER.name();
|
public static final String FIELD_EXACT_MATCH_NAME = EmpiMetricEnum.NAME_ANY_ORDER.name();
|
||||||
|
@ -27,53 +25,54 @@ public class CustomResourceMatcherR4Test extends BaseR4Test {
|
||||||
@Test
|
@Test
|
||||||
public void testExactNameAnyOrder() {
|
public void testExactNameAnyOrder() {
|
||||||
EmpiResourceMatcherSvc nameAnyOrderMatcher = buildMatcher(buildNameRules(EmpiMetricEnum.NAME_ANY_ORDER, true));
|
EmpiResourceMatcherSvc nameAnyOrderMatcher = buildMatcher(buildNameRules(EmpiMetricEnum.NAME_ANY_ORDER, true));
|
||||||
assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnHenry));
|
assertMatch(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnHenry));
|
||||||
assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourHenryJohn));
|
assertMatch(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourHenryJohn));
|
||||||
assertEquals(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourHenryJOHN));
|
assertMatch(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourHenryJOHN));
|
||||||
assertEquals(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnHENRY));
|
assertMatch(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnHENRY));
|
||||||
assertEquals(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJaneHenry));
|
assertMatch(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJaneHenry));
|
||||||
assertEquals(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnSmith));
|
assertMatch(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnSmith));
|
||||||
assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnBillyHenry));
|
assertMatch(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnBillyHenry));
|
||||||
assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourBillyJohnHenry));
|
assertMatch(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourBillyJohnHenry));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNormalizedNameAnyOrder() {
|
public void testNormalizedNameAnyOrder() {
|
||||||
EmpiResourceMatcherSvc nameAnyOrderMatcher = buildMatcher(buildNameRules(EmpiMetricEnum.NAME_ANY_ORDER, false));
|
EmpiResourceMatcherSvc nameAnyOrderMatcher = buildMatcher(buildNameRules(EmpiMetricEnum.NAME_ANY_ORDER, false));
|
||||||
assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnHenry));
|
assertMatch(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnHenry));
|
||||||
assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourHenryJohn));
|
assertMatch(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourHenryJohn));
|
||||||
assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourHenryJOHN));
|
assertMatch(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourHenryJOHN));
|
||||||
assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnHENRY));
|
assertMatch(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnHENRY));
|
||||||
assertEquals(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJaneHenry));
|
assertMatch(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJaneHenry));
|
||||||
assertEquals(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnSmith));
|
assertMatch(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnSmith));
|
||||||
assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnBillyHenry));
|
assertMatch(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnBillyHenry));
|
||||||
assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourBillyJohnHenry));
|
assertMatch(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourBillyJohnHenry));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testExactNameFirstAndLast() {
|
public void testExactNameFirstAndLast() {
|
||||||
EmpiResourceMatcherSvc nameAnyOrderMatcher = buildMatcher(buildNameRules(EmpiMetricEnum.NAME_FIRST_AND_LAST, true));
|
EmpiResourceMatcherSvc nameAnyOrderMatcher = buildMatcher(buildNameRules(EmpiMetricEnum.NAME_FIRST_AND_LAST, true));
|
||||||
assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnHenry));
|
assertMatch(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnHenry));
|
||||||
assertEquals(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourHenryJohn));
|
assertMatchResult(EmpiMatchResultEnum.MATCH, 1L, 1.0, false, false, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnHenry));
|
||||||
assertEquals(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourHenryJOHN));
|
assertMatch(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourHenryJohn));
|
||||||
assertEquals(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnHENRY));
|
assertMatch(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourHenryJOHN));
|
||||||
assertEquals(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJaneHenry));
|
assertMatch(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnHENRY));
|
||||||
assertEquals(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnSmith));
|
assertMatch(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJaneHenry));
|
||||||
assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnBillyHenry));
|
assertMatch(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnSmith));
|
||||||
assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourBillyJohnHenry));
|
assertMatch(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnBillyHenry));
|
||||||
|
assertMatch(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourBillyJohnHenry));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNormalizedNameFirstAndLast() {
|
public void testNormalizedNameFirstAndLast() {
|
||||||
EmpiResourceMatcherSvc nameAnyOrderMatcher = buildMatcher(buildNameRules(EmpiMetricEnum.NAME_FIRST_AND_LAST, false));
|
EmpiResourceMatcherSvc nameAnyOrderMatcher = buildMatcher(buildNameRules(EmpiMetricEnum.NAME_FIRST_AND_LAST, false));
|
||||||
assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnHenry));
|
assertMatch(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnHenry));
|
||||||
assertEquals(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourHenryJohn));
|
assertMatch(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourHenryJohn));
|
||||||
assertEquals(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourHenryJOHN));
|
assertMatch(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourHenryJOHN));
|
||||||
assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnHENRY));
|
assertMatch(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnHENRY));
|
||||||
assertEquals(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJaneHenry));
|
assertMatch(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJaneHenry));
|
||||||
assertEquals(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnSmith));
|
assertMatch(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnSmith));
|
||||||
assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnBillyHenry));
|
assertMatch(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnBillyHenry));
|
||||||
assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourBillyJohnHenry));
|
assertMatch(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourBillyJohnHenry));
|
||||||
}
|
}
|
||||||
|
|
||||||
private EmpiRulesJson buildNameRules(EmpiMetricEnum theExactNameAnyOrder, boolean theExact) {
|
private EmpiRulesJson buildNameRules(EmpiMetricEnum theExactNameAnyOrder, boolean theExact) {
|
||||||
|
|
|
@ -36,7 +36,7 @@ public class EmpiResourceFieldMatcherR4Test extends BaseEmpiRulesR4Test {
|
||||||
Patient patient = new Patient();
|
Patient patient = new Patient();
|
||||||
patient.setActive(true);
|
patient.setActive(true);
|
||||||
|
|
||||||
assertFalse(myComparator.match(patient, myJohny));
|
assertFalse(myComparator.match(patient, myJohny).match);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -77,6 +77,6 @@ public class EmpiResourceFieldMatcherR4Test extends BaseEmpiRulesR4Test {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMatch() {
|
public void testMatch() {
|
||||||
assertTrue(myComparator.match(myJohn, myJohny));
|
assertTrue(myComparator.match(myJohn, myJohny).match);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
package ca.uhn.fhir.empi.rules.svc;
|
package ca.uhn.fhir.empi.rules.svc;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||||
|
import ca.uhn.fhir.empi.api.EmpiMatchOutcome;
|
||||||
import ca.uhn.fhir.empi.api.EmpiMatchResultEnum;
|
import ca.uhn.fhir.empi.api.EmpiMatchResultEnum;
|
||||||
import org.hl7.fhir.r4.model.Patient;
|
import org.hl7.fhir.r4.model.Patient;
|
||||||
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 static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
public class EmpiResourceMatcherSvcR4Test extends BaseEmpiRulesR4Test {
|
public class EmpiResourceMatcherSvcR4Test extends BaseEmpiRulesR4Test {
|
||||||
public static final double NAME_DELTA = 0.0001;
|
|
||||||
private EmpiResourceMatcherSvc myEmpiResourceMatcherSvc;
|
private EmpiResourceMatcherSvc myEmpiResourceMatcherSvc;
|
||||||
private Patient myJohn;
|
private Patient myJohn;
|
||||||
private Patient myJohny;
|
private Patient myJohny;
|
||||||
|
@ -33,27 +32,28 @@ public class EmpiResourceMatcherSvcR4Test extends BaseEmpiRulesR4Test {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCompareFirstNameMatch() {
|
public void testCompareFirstNameMatch() {
|
||||||
EmpiMatchResultEnum result = myEmpiResourceMatcherSvc.match(myJohn, myJohny);
|
EmpiMatchOutcome result = myEmpiResourceMatcherSvc.match(myJohn, myJohny);
|
||||||
assertEquals(EmpiMatchResultEnum.POSSIBLE_MATCH, result);
|
assertMatchResult(EmpiMatchResultEnum.POSSIBLE_MATCH, 1L, 0.816, false, false, result);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCompareBothNamesMatch() {
|
public void testCompareBothNamesMatch() {
|
||||||
myJohn.addName().setFamily("Smith");
|
myJohn.addName().setFamily("Smith");
|
||||||
myJohny.addName().setFamily("Smith");
|
myJohny.addName().setFamily("Smith");
|
||||||
EmpiMatchResultEnum result = myEmpiResourceMatcherSvc.match(myJohn, myJohny);
|
EmpiMatchOutcome result = myEmpiResourceMatcherSvc.match(myJohn, myJohny);
|
||||||
assertEquals(EmpiMatchResultEnum.MATCH, result);
|
assertMatchResult(EmpiMatchResultEnum.MATCH, 3L, 1.816, false, false, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMatchResult() {
|
public void testMatchResult() {
|
||||||
assertEquals(EmpiMatchResultEnum.POSSIBLE_MATCH, myEmpiResourceMatcherSvc.getMatchResult(myJohn, myJohny));
|
assertMatchResult(EmpiMatchResultEnum.POSSIBLE_MATCH, 1L, 0.816, false, false, myEmpiResourceMatcherSvc.getMatchResult(myJohn, myJohny));
|
||||||
myJohn.addName().setFamily("Smith");
|
myJohn.addName().setFamily("Smith");
|
||||||
myJohny.addName().setFamily("Smith");
|
myJohny.addName().setFamily("Smith");
|
||||||
assertEquals(EmpiMatchResultEnum.MATCH, myEmpiResourceMatcherSvc.getMatchResult(myJohn, myJohny));
|
assertMatchResult(EmpiMatchResultEnum.MATCH, 3L, 1.816, false, false, myEmpiResourceMatcherSvc.getMatchResult(myJohn, myJohny));
|
||||||
Patient patient3 = new Patient();
|
Patient patient3 = new Patient();
|
||||||
patient3.setId("Patient/3");
|
patient3.setId("Patient/3");
|
||||||
patient3.addName().addGiven("Henry");
|
patient3.addName().addGiven("Henry");
|
||||||
assertEquals(EmpiMatchResultEnum.NO_MATCH, myEmpiResourceMatcherSvc.getMatchResult(myJohn, patient3));
|
assertMatchResult(EmpiMatchResultEnum.NO_MATCH, 0L, 0.0, false, false, myEmpiResourceMatcherSvc.getMatchResult(myJohn, patient3));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,9 +9,9 @@ import static ca.uhn.fhir.empi.api.EmpiMatchResultEnum.MATCH;
|
||||||
import static ca.uhn.fhir.empi.api.EmpiMatchResultEnum.NO_MATCH;
|
import static ca.uhn.fhir.empi.api.EmpiMatchResultEnum.NO_MATCH;
|
||||||
import static ca.uhn.fhir.empi.api.EmpiMatchResultEnum.POSSIBLE_DUPLICATE;
|
import static ca.uhn.fhir.empi.api.EmpiMatchResultEnum.POSSIBLE_DUPLICATE;
|
||||||
import static ca.uhn.fhir.empi.api.EmpiMatchResultEnum.POSSIBLE_MATCH;
|
import static ca.uhn.fhir.empi.api.EmpiMatchResultEnum.POSSIBLE_MATCH;
|
||||||
|
import static ca.uhn.fhir.empi.model.CanonicalIdentityAssuranceLevel.LEVEL1;
|
||||||
import static ca.uhn.fhir.empi.model.CanonicalIdentityAssuranceLevel.LEVEL2;
|
import static ca.uhn.fhir.empi.model.CanonicalIdentityAssuranceLevel.LEVEL2;
|
||||||
import static ca.uhn.fhir.empi.model.CanonicalIdentityAssuranceLevel.LEVEL3;
|
import static ca.uhn.fhir.empi.model.CanonicalIdentityAssuranceLevel.LEVEL3;
|
||||||
import static ca.uhn.fhir.empi.model.CanonicalIdentityAssuranceLevel.LEVEL4;
|
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.core.Is.is;
|
import static org.hamcrest.core.Is.is;
|
||||||
import static org.hamcrest.core.IsEqual.equalTo;
|
import static org.hamcrest.core.IsEqual.equalTo;
|
||||||
|
@ -22,9 +22,9 @@ public class AssuranceLevelUtilTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testValidPersonLinkLevels() {
|
public void testValidPersonLinkLevels() {
|
||||||
assertThat(AssuranceLevelUtil.getAssuranceLevel(POSSIBLE_MATCH, AUTO), is(equalTo(LEVEL2)));
|
assertThat(AssuranceLevelUtil.getAssuranceLevel(POSSIBLE_MATCH, AUTO), is(equalTo(LEVEL1)));
|
||||||
assertThat(AssuranceLevelUtil.getAssuranceLevel(MATCH, AUTO), is(equalTo(LEVEL3)));
|
assertThat(AssuranceLevelUtil.getAssuranceLevel(MATCH, AUTO), is(equalTo(LEVEL2)));
|
||||||
assertThat(AssuranceLevelUtil.getAssuranceLevel(MATCH, MANUAL), is(equalTo(LEVEL4)));
|
assertThat(AssuranceLevelUtil.getAssuranceLevel(MATCH, MANUAL), is(equalTo(LEVEL3)));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
|
"version": "1",
|
||||||
"candidateSearchParams" : [],
|
"candidateSearchParams" : [],
|
||||||
"candidateFilterSearchParams" : [{
|
"candidateFilterSearchParams" : [{
|
||||||
"resourceType" : "Patient",
|
"resourceType" : "Patient",
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
|
"version": "1",
|
||||||
"candidateSearchParams" : [],
|
"candidateSearchParams" : [],
|
||||||
"candidateFilterSearchParams" : [],
|
"candidateFilterSearchParams" : [],
|
||||||
"matchFields" : [ {
|
"matchFields" : [ {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
|
"version": "1",
|
||||||
"candidateSearchParams" : [{
|
"candidateSearchParams" : [{
|
||||||
"resourceType" : "Patient",
|
"resourceType" : "Patient",
|
||||||
"searchParams" : ["foo"]
|
"searchParams" : ["foo"]
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
|
"version": "1",
|
||||||
"candidateSearchParams" : [],
|
"candidateSearchParams" : [],
|
||||||
"candidateFilterSearchParams" : [],
|
"candidateFilterSearchParams" : [],
|
||||||
"matchFields" : [],
|
"matchFields" : [],
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
|
"version": "1",
|
||||||
"candidateSearchParams": [],
|
"candidateSearchParams": [],
|
||||||
"candidateFilterSearchParams": [],
|
"candidateFilterSearchParams": [],
|
||||||
"matchFields": [
|
"matchFields": [
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
|
"version": "1",
|
||||||
"candidateSearchParams" : [],
|
"candidateSearchParams" : [],
|
||||||
"candidateFilterSearchParams" : [],
|
"candidateFilterSearchParams" : [],
|
||||||
"matchFields" : [ {
|
"matchFields" : [ {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
|
"version": "1",
|
||||||
"candidateSearchParams" : [],
|
"candidateSearchParams" : [],
|
||||||
"candidateFilterSearchParams" : [],
|
"candidateFilterSearchParams" : [],
|
||||||
"matchFields" : [ {
|
"matchFields" : [ {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
|
"version": "1",
|
||||||
"candidateSearchParams" : [],
|
"candidateSearchParams" : [],
|
||||||
"candidateFilterSearchParams" : [],
|
"candidateFilterSearchParams" : [],
|
||||||
"matchFields" : [ {
|
"matchFields" : [ {
|
||||||
|
|
Loading…
Reference in New Issue