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.IPrimitiveType;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
@ -299,6 +300,20 @@ public class ParametersUtil {
|
|||
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) {
|
||||
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. 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 2: POSSIBLE_MATCH
|
||||
1. Level 3: AUTO MATCH
|
||||
1. Level 4: MANUAL MATCH
|
||||
1. Level 1: POSSIBLE_MATCH
|
||||
1. Level 2: AUTO MATCH
|
||||
1. Level 3: MANUAL MATCH
|
||||
1. Level 4: GOLDEN RECORD
|
||||
|
||||
### Possible rule match outcomes:
|
||||
|
||||
|
|
|
@ -92,6 +92,15 @@ This operation returns a `Parameters` resource that looks like the following:
|
|||
}, {
|
||||
"name": "linkSource",
|
||||
"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.jpa.model.entity.ResourceTable;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
|
@ -49,6 +48,7 @@ import java.util.Date;
|
|||
@UniqueConstraint(name = "IDX_EMPI_PERSON_TGT", columnNames = {"PERSON_PID", "TARGET_PID"}),
|
||||
})
|
||||
public class EmpiLink {
|
||||
public static final int VERSION_LENGTH = 16;
|
||||
private static final int MATCH_RESULT_LENGTH = 16;
|
||||
private static final int LINK_SOURCE_LENGTH = 16;
|
||||
|
||||
|
@ -88,6 +88,29 @@ public class EmpiLink {
|
|||
@Column(name = "UPDATED", nullable = false)
|
||||
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() {
|
||||
return myId;
|
||||
}
|
||||
|
@ -177,17 +200,6 @@ public class EmpiLink {
|
|||
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() {
|
||||
return myCreated;
|
||||
}
|
||||
|
@ -205,4 +217,70 @@ public class EmpiLink {
|
|||
myUpdated = theUpdated;
|
||||
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
|
||||
})
|
||||
public class TestJPAConfig {
|
||||
|
||||
@Bean
|
||||
public DaoConfig 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.util.EIDHelper;
|
||||
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.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.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.EmpiLinkQuerySvcImpl;
|
||||
import ca.uhn.fhir.jpa.empi.svc.EmpiLinkSvcImpl;
|
||||
import ca.uhn.fhir.jpa.empi.svc.EmpiLinkUpdaterSvcImpl;
|
||||
import ca.uhn.fhir.jpa.empi.svc.EmpiMatchFinderSvcImpl;
|
||||
import ca.uhn.fhir.jpa.empi.svc.EmpiMatchLinkSvc;
|
||||
import ca.uhn.fhir.jpa.empi.svc.EmpiPersonFindingSvc;
|
||||
import ca.uhn.fhir.jpa.empi.svc.EmpiPersonMergerSvcImpl;
|
||||
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 org.slf4j.Logger;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
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
|
||||
public class EmpiConsumerConfig {
|
||||
private static final Logger ourLog = Logs.getEmpiTroubleshootingLog();
|
||||
|
||||
@Autowired
|
||||
IEmpiSettings myEmpiProperties;
|
||||
@Autowired
|
||||
EmpiProviderLoader myEmpiProviderLoader;
|
||||
@Autowired
|
||||
EmpiSubscriptionLoader myEmpiSubscriptionLoader;
|
||||
@Autowired
|
||||
EmpiSearchParameterLoader myEmpiSearchParameterLoader;
|
||||
|
||||
@Bean
|
||||
IEmpiStorageInterceptor empiStorageInterceptor() {
|
||||
return new EmpiStorageInterceptor();
|
||||
|
@ -127,6 +118,21 @@ public class EmpiConsumerConfig {
|
|||
return new EmpiPersonFindingSvc();
|
||||
}
|
||||
|
||||
@Bean
|
||||
FindCandidateByEidSvc findCandidateByEidSvc() {
|
||||
return new FindCandidateByEidSvc();
|
||||
}
|
||||
|
||||
@Bean
|
||||
FindCandidateByLinkSvc findCandidateByLinkSvc() {
|
||||
return new FindCandidateByLinkSvc();
|
||||
}
|
||||
|
||||
@Bean
|
||||
FindCandidateByScoreSvc findCandidateByScoreSvc() {
|
||||
return new FindCandidateByScoreSvc();
|
||||
}
|
||||
|
||||
@Bean
|
||||
EmpiProviderLoader empiProviderLoader() {
|
||||
return new EmpiProviderLoader();
|
||||
|
@ -173,34 +179,28 @@ public class EmpiConsumerConfig {
|
|||
return new EIDHelper(theFhirContext, theEmpiConfig);
|
||||
}
|
||||
|
||||
@Bean
|
||||
EmpiLinkDaoSvc empiLinkDaoSvc() {
|
||||
return new EmpiLinkDaoSvc();
|
||||
}
|
||||
|
||||
@Bean
|
||||
EmpiLinkFactory empiLinkFactory(IEmpiSettings theEmpiSettings) {
|
||||
return new EmpiLinkFactory(theEmpiSettings);
|
||||
}
|
||||
|
||||
@Bean
|
||||
IEmpiLinkUpdaterSvc manualLinkUpdaterSvc() {
|
||||
return new EmpiLinkUpdaterSvcImpl();
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void registerInterceptorAndProvider() {
|
||||
if (!myEmpiProperties.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
@Bean
|
||||
EmpiLoader empiLoader() {
|
||||
return new EmpiLoader();
|
||||
}
|
||||
|
||||
@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");
|
||||
@Bean
|
||||
EmpiLinkDeleteSvc empiLinkDeleteSvc() {
|
||||
return new EmpiLinkDeleteSvc();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.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.svc.EmpiSearchParamSvc;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
@ -44,4 +45,9 @@ public class EmpiSubmitterConfig {
|
|||
EmpiRuleValidator empiRuleValidator(FhirContext theFhirContext) {
|
||||
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
|
||||
|
@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.dao;
|
|||
*/
|
||||
|
||||
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.log.Logs;
|
||||
import ca.uhn.fhir.empi.model.EmpiTransactionContext;
|
||||
|
@ -32,7 +33,6 @@ import org.hl7.fhir.r4.model.Patient;
|
|||
import org.slf4j.Logger;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Example;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
|
@ -43,25 +43,34 @@ import java.util.Date;
|
|||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
public class EmpiLinkDaoSvc {
|
||||
private static final Logger ourLog = Logs.getEmpiTroubleshootingLog();
|
||||
|
||||
@Autowired
|
||||
private IEmpiLinkDao myEmpiLinkDao;
|
||||
@Autowired
|
||||
private EmpiLinkFactory myEmpiLinkFactory;
|
||||
@Autowired
|
||||
private IdHelperService myIdHelperService;
|
||||
|
||||
@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 resourcePid = myIdHelperService.getPidOrNull(theTarget);
|
||||
|
||||
EmpiLink empiLink = getOrCreateEmpiLinkByPersonPidAndTargetPid(personPid, resourcePid);
|
||||
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);
|
||||
ourLog.debug(message);
|
||||
save(empiLink);
|
||||
|
@ -75,7 +84,7 @@ public class EmpiLinkDaoSvc {
|
|||
if (oExisting.isPresent()) {
|
||||
return oExisting.get();
|
||||
} else {
|
||||
EmpiLink empiLink = new EmpiLink();
|
||||
EmpiLink empiLink = myEmpiLinkFactory.newEmpiLink();
|
||||
empiLink.setPersonPid(thePersonPid);
|
||||
empiLink.setTargetPid(theResourcePid);
|
||||
return empiLink;
|
||||
|
@ -87,7 +96,7 @@ public class EmpiLinkDaoSvc {
|
|||
if (theTargetPid == null || thePersonPid == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
EmpiLink link = new EmpiLink();
|
||||
EmpiLink link = myEmpiLinkFactory.newEmpiLink();
|
||||
link.setTargetPid(theTargetPid);
|
||||
link.setPersonPid(thePersonPid);
|
||||
Example<EmpiLink> example = Example.of(link);
|
||||
|
@ -95,7 +104,7 @@ public class EmpiLinkDaoSvc {
|
|||
}
|
||||
|
||||
public List<EmpiLink> getEmpiLinksByTargetPidAndMatchResult(Long theTargetPid, EmpiMatchResultEnum theMatchResult) {
|
||||
EmpiLink exampleLink = new EmpiLink();
|
||||
EmpiLink exampleLink = myEmpiLinkFactory.newEmpiLink();
|
||||
exampleLink.setTargetPid(theTargetPid);
|
||||
exampleLink.setMatchResult(theMatchResult);
|
||||
Example<EmpiLink> example = Example.of(exampleLink);
|
||||
|
@ -103,7 +112,7 @@ public class EmpiLinkDaoSvc {
|
|||
}
|
||||
|
||||
public Optional<EmpiLink> getMatchedLinkForTargetPid(Long theTargetPid) {
|
||||
EmpiLink exampleLink = new EmpiLink();
|
||||
EmpiLink exampleLink = myEmpiLinkFactory.newEmpiLink();
|
||||
exampleLink.setTargetPid(theTargetPid);
|
||||
exampleLink.setMatchResult(EmpiMatchResultEnum.MATCH);
|
||||
Example<EmpiLink> example = Example.of(exampleLink);
|
||||
|
@ -116,7 +125,7 @@ public class EmpiLinkDaoSvc {
|
|||
return Optional.empty();
|
||||
}
|
||||
|
||||
EmpiLink exampleLink = new EmpiLink();
|
||||
EmpiLink exampleLink = myEmpiLinkFactory.newEmpiLink();
|
||||
exampleLink.setTargetPid(pid);
|
||||
exampleLink.setMatchResult(EmpiMatchResultEnum.MATCH);
|
||||
Example<EmpiLink> example = Example.of(exampleLink);
|
||||
|
@ -124,7 +133,7 @@ public class EmpiLinkDaoSvc {
|
|||
}
|
||||
|
||||
public Optional<EmpiLink> getEmpiLinksByPersonPidTargetPidAndMatchResult(Long thePersonPid, Long theTargetPid, EmpiMatchResultEnum theMatchResult) {
|
||||
EmpiLink exampleLink = new EmpiLink();
|
||||
EmpiLink exampleLink = myEmpiLinkFactory.newEmpiLink();
|
||||
exampleLink.setPersonPid(thePersonPid);
|
||||
exampleLink.setTargetPid(theTargetPid);
|
||||
exampleLink.setMatchResult(theMatchResult);
|
||||
|
@ -138,7 +147,7 @@ public class EmpiLinkDaoSvc {
|
|||
* @return A list of EmpiLinks that hold potential duplicate persons.
|
||||
*/
|
||||
public List<EmpiLink> getPossibleDuplicates() {
|
||||
EmpiLink exampleLink = new EmpiLink();
|
||||
EmpiLink exampleLink = myEmpiLinkFactory.newEmpiLink();
|
||||
exampleLink.setMatchResult(EmpiMatchResultEnum.POSSIBLE_DUPLICATE);
|
||||
Example<EmpiLink> example = Example.of(exampleLink);
|
||||
return myEmpiLinkDao.findAll(example);
|
||||
|
@ -149,7 +158,7 @@ public class EmpiLinkDaoSvc {
|
|||
if (pid == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
EmpiLink empiLink = new EmpiLink().setTargetPid(pid);
|
||||
EmpiLink empiLink = myEmpiLinkFactory.newEmpiLink().setTargetPid(pid);
|
||||
Example<EmpiLink> example = Example.of(empiLink);
|
||||
return myEmpiLinkDao.findOne(example);
|
||||
}
|
||||
|
@ -159,26 +168,12 @@ public class EmpiLinkDaoSvc {
|
|||
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) {
|
||||
Long pid = myIdHelperService.getPidOrNull(thePersonResource);
|
||||
if (pid == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
EmpiLink empiLink = new EmpiLink().setPersonPid(pid);
|
||||
EmpiLink empiLink = myEmpiLinkFactory.newEmpiLink().setPersonPid(pid);
|
||||
Example<EmpiLink> example = Example.of(empiLink);
|
||||
return myEmpiLinkDao.findAll(example);
|
||||
}
|
||||
|
@ -200,8 +195,12 @@ public class EmpiLinkDaoSvc {
|
|||
if (pid == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
EmpiLink empiLink = new EmpiLink().setTargetPid(pid);
|
||||
EmpiLink empiLink = myEmpiLinkFactory.newEmpiLink().setTargetPid(pid);
|
||||
Example<EmpiLink> example = Example.of(empiLink);
|
||||
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.interceptor.api.Hook;
|
||||
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.entity.EmpiLink;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
|
@ -50,7 +50,7 @@ public class EmpiStorageInterceptor implements IEmpiStorageInterceptor {
|
|||
@Autowired
|
||||
private ExpungeEverythingService myExpungeEverythingService;
|
||||
@Autowired
|
||||
private EmpiLinkDaoSvc myEmpiLinkDaoSvc;
|
||||
private EmpiLinkDeleteSvc myEmpiLinkDeleteSvc;
|
||||
@Autowired
|
||||
private FhirContext myFhirContext;
|
||||
@Autowired
|
||||
|
@ -87,7 +87,7 @@ public class EmpiStorageInterceptor implements IEmpiStorageInterceptor {
|
|||
if (EmpiUtil.isEmpiManagedPerson(myFhirContext, theNewResource) &&
|
||||
myPersonHelper.isDeactivated(theNewResource)) {
|
||||
ourLog.debug("Deleting empi links to deactivated Person {}", theNewResource.getIdElement().toUnqualifiedVersionless());
|
||||
myEmpiLinkDaoSvc.deleteWithAnyReferenceTo(theNewResource);
|
||||
myEmpiLinkDeleteSvc.deleteWithAnyReferenceTo(theNewResource);
|
||||
}
|
||||
|
||||
if (isInternalRequest(theRequestDetails)) {
|
||||
|
@ -106,7 +106,7 @@ public class EmpiStorageInterceptor implements IEmpiStorageInterceptor {
|
|||
if (!EmpiUtil.isEmpiResourceType(myFhirContext, theResource)) {
|
||||
return;
|
||||
}
|
||||
myEmpiLinkDaoSvc.deleteWithAnyReferenceTo(theResource);
|
||||
myEmpiLinkDeleteSvc.deleteWithAnyReferenceTo(theResource);
|
||||
}
|
||||
|
||||
private void forbidIfModifyingExternalEidOnTarget(IBaseResource theNewResource, IBaseResource theOldResource) {
|
||||
|
@ -181,6 +181,6 @@ public class EmpiStorageInterceptor implements IEmpiStorageInterceptor {
|
|||
@Hook(Pointcut.STORAGE_PRESTORAGE_EXPUNGE_RESOURCE)
|
||||
public void expungeAllMatchedEmpiLinks(AtomicInteger theCounter, IBaseResource theResource) {
|
||||
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.EmpiMatchResultEnum;
|
||||
import ca.uhn.fhir.empi.api.EmpiMatchOutcome;
|
||||
import ca.uhn.fhir.empi.api.IEmpiLinkSvc;
|
||||
import ca.uhn.fhir.empi.api.IEmpiSettings;
|
||||
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.util.EIDHelper;
|
||||
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.rest.api.server.storage.ResourcePersistentId;
|
||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||
|
@ -107,16 +109,16 @@ public class EmpiEidUpdateService {
|
|||
private void createNewPersonAndFlagAsDuplicate(IAnyResource theResource, EmpiTransactionContext theEmpiTransactionContext, IAnyResource theOldPerson) {
|
||||
log(theEmpiTransactionContext, "Duplicate detected based on the fact that both resources have different external EIDs.");
|
||||
IAnyResource newPerson = myPersonHelper.createPersonFromEmpiTarget(theResource);
|
||||
myEmpiLinkSvc.updateLink(newPerson, theResource, EmpiMatchResultEnum.MATCH, EmpiLinkSourceEnum.AUTO, theEmpiTransactionContext);
|
||||
myEmpiLinkSvc.updateLink(newPerson, theOldPerson, EmpiMatchResultEnum.POSSIBLE_DUPLICATE, EmpiLinkSourceEnum.AUTO, theEmpiTransactionContext);
|
||||
myEmpiLinkSvc.updateLink(newPerson, theResource, EmpiMatchOutcome.NEW_PERSON_MATCH, EmpiLinkSourceEnum.AUTO, theEmpiTransactionContext);
|
||||
myEmpiLinkSvc.updateLink(newPerson, theOldPerson, EmpiMatchOutcome.POSSIBLE_DUPLICATE, EmpiLinkSourceEnum.AUTO, theEmpiTransactionContext);
|
||||
}
|
||||
|
||||
private void linkToNewPersonAndFlagAsDuplicate(IAnyResource theResource, IAnyResource theOldPerson, IAnyResource theNewPerson, EmpiTransactionContext theEmpiTransactionContext) {
|
||||
log(theEmpiTransactionContext, "Changing a match link!");
|
||||
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.");
|
||||
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) {
|
||||
|
|
|
@ -25,8 +25,8 @@ import ca.uhn.fhir.empi.api.EmpiLinkSourceEnum;
|
|||
import ca.uhn.fhir.empi.api.EmpiMatchResultEnum;
|
||||
import ca.uhn.fhir.empi.api.IEmpiLinkQuerySvc;
|
||||
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.empi.dao.EmpiLinkDaoSvc;
|
||||
import ca.uhn.fhir.jpa.entity.EmpiLink;
|
||||
import ca.uhn.fhir.util.ParametersUtil;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
|
@ -82,13 +82,16 @@ public class EmpiLinkQuerySvcImpl implements IEmpiLinkQuerySvc {
|
|||
if (includeResultAndSource) {
|
||||
ParametersUtil.addPartString(myFhirContext, resultPart, "matchResult", empiLink.getMatchResult().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;
|
||||
}
|
||||
|
||||
private Example<EmpiLink> exampleLinkFromParameters(IIdType thePersonId, IIdType theTargetId, EmpiMatchResultEnum theMatchResult, EmpiLinkSourceEnum theLinkSource) {
|
||||
EmpiLink empiLink = new EmpiLink();
|
||||
EmpiLink empiLink = myEmpiLinkDaoSvc.newEmpiLink();
|
||||
if (thePersonId != null) {
|
||||
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.EmpiMatchOutcome;
|
||||
import ca.uhn.fhir.empi.api.EmpiMatchResultEnum;
|
||||
import ca.uhn.fhir.empi.api.IEmpiLinkSvc;
|
||||
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.util.AssuranceLevelUtil;
|
||||
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.empi.dao.EmpiLinkDaoSvc;
|
||||
import ca.uhn.fhir.jpa.entity.EmpiLink;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||
|
@ -63,24 +64,25 @@ public class EmpiLinkSvcImpl implements IEmpiLinkSvc {
|
|||
|
||||
@Override
|
||||
@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();
|
||||
|
||||
if (theMatchResult == EmpiMatchResultEnum.POSSIBLE_DUPLICATE && personsLinkedAsNoMatch(thePerson, theTarget)) {
|
||||
if (theMatchOutcome.isPossibleDuplicate() && personsLinkedAsNoMatch(thePerson, theTarget)) {
|
||||
log(theEmpiTransactionContext, thePerson.getIdElement().toUnqualifiedVersionless() +
|
||||
" is linked as NO_MATCH with " +
|
||||
theTarget.getIdElement().toUnqualifiedVersionless() +
|
||||
" not linking as POSSIBLE_DUPLICATE.");
|
||||
return;
|
||||
}
|
||||
validateRequestIsLegal(thePerson, theTarget, theMatchResult, theLinkSource);
|
||||
switch (theMatchResult) {
|
||||
EmpiMatchResultEnum matchResultEnum = theMatchOutcome.getMatchResultEnum();
|
||||
validateRequestIsLegal(thePerson, theTarget, matchResultEnum, theLinkSource);
|
||||
switch (matchResultEnum) {
|
||||
case MATCH:
|
||||
myPersonHelper.addOrUpdateLink(thePerson, resourceId, AssuranceLevelUtil.getAssuranceLevel(theMatchResult, theLinkSource), theEmpiTransactionContext);
|
||||
myPersonHelper.addOrUpdateLink(thePerson, resourceId, AssuranceLevelUtil.getAssuranceLevel(matchResultEnum, theLinkSource), theEmpiTransactionContext);
|
||||
myEmpiResourceDaoSvc.updatePerson(thePerson);
|
||||
break;
|
||||
case POSSIBLE_MATCH:
|
||||
myPersonHelper.addOrUpdateLink(thePerson, resourceId, AssuranceLevelUtil.getAssuranceLevel(theMatchResult, theLinkSource), theEmpiTransactionContext);
|
||||
myPersonHelper.addOrUpdateLink(thePerson, resourceId, AssuranceLevelUtil.getAssuranceLevel(matchResultEnum, theLinkSource), theEmpiTransactionContext);
|
||||
break;
|
||||
case NO_MATCH:
|
||||
myPersonHelper.removeLink(thePerson, resourceId, theEmpiTransactionContext);
|
||||
|
@ -89,7 +91,7 @@ public class EmpiLinkSvcImpl implements IEmpiLinkSvc {
|
|||
break;
|
||||
}
|
||||
myEmpiResourceDaoSvc.updatePerson(thePerson);
|
||||
createOrUpdateLinkEntity(thePerson, theTarget, theMatchResult, theLinkSource, theEmpiTransactionContext);
|
||||
createOrUpdateLinkEntity(thePerson, theTarget, theMatchOutcome, theLinkSource, theEmpiTransactionContext);
|
||||
}
|
||||
|
||||
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) {
|
||||
myEmpiLinkDaoSvc.createOrUpdateLinkEntity(thePerson, theResource, theMatchResult, theLinkSource, theEmpiTransactionContext);
|
||||
private void createOrUpdateLinkEntity(IBaseResource thePerson, IBaseResource theResource, EmpiMatchOutcome theMatchOutcome, EmpiLinkSourceEnum theLinkSource, EmpiTransactionContext theEmpiTransactionContext) {
|
||||
myEmpiLinkDaoSvc.createOrUpdateLinkEntity(thePerson, theResource, theMatchOutcome, theLinkSource, theEmpiTransactionContext);
|
||||
}
|
||||
|
||||
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.model.EmpiTransactionContext;
|
||||
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.empi.dao.EmpiLinkDaoSvc;
|
||||
import ca.uhn.fhir.jpa.entity.EmpiLink;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
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.MatchedTarget;
|
||||
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.springframework.beans.factory.annotation.Autowired;
|
||||
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.EmpiMatchResultEnum;
|
||||
import ca.uhn.fhir.empi.api.EmpiMatchOutcome;
|
||||
import ca.uhn.fhir.empi.api.IEmpiLinkSvc;
|
||||
import ca.uhn.fhir.empi.log.Logs;
|
||||
import ca.uhn.fhir.empi.model.EmpiTransactionContext;
|
||||
import ca.uhn.fhir.empi.util.EmpiUtil;
|
||||
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 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.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
List<MatchedPersonCandidate> personCandidates = myEmpiPersonFindingSvc.findPersonCandidates(theResource);
|
||||
if (personCandidates.isEmpty()) {
|
||||
CandidateList candidateList = myEmpiPersonFindingSvc.findPersonCandidates(theResource);
|
||||
if (candidateList.isEmpty()) {
|
||||
handleEmpiWithNoCandidates(theResource, theEmpiTransactionContext);
|
||||
} else if (personCandidates.size() == 1) {
|
||||
handleEmpiWithSingleCandidate(theResource, personCandidates, theEmpiTransactionContext);
|
||||
} else if (candidateList.exactlyOneMatch()) {
|
||||
handleEmpiWithSingleCandidate(theResource, candidateList.getOnlyMatch(), theEmpiTransactionContext);
|
||||
} else {
|
||||
handleEmpiWithMultipleCandidates(theResource, personCandidates, theEmpiTransactionContext);
|
||||
handleEmpiWithMultipleCandidates(theResource, candidateList, theEmpiTransactionContext);
|
||||
}
|
||||
return theEmpiTransactionContext;
|
||||
}
|
||||
|
||||
private void handleEmpiWithMultipleCandidates(IAnyResource theResource, List<MatchedPersonCandidate> thePersonCandidates, EmpiTransactionContext theEmpiTransactionContext) {
|
||||
Long samplePersonPid = thePersonCandidates.get(0).getCandidatePersonPid().getIdAsLong();
|
||||
boolean allSamePerson = thePersonCandidates.stream()
|
||||
private void handleEmpiWithMultipleCandidates(IAnyResource theResource, CandidateList theCandidateList, EmpiTransactionContext theEmpiTransactionContext) {
|
||||
MatchedPersonCandidate firstMatch = theCandidateList.getFirstMatch();
|
||||
Long samplePersonPid = firstMatch.getCandidatePersonPid().getIdAsLong();
|
||||
boolean allSamePerson = theCandidateList.stream()
|
||||
.allMatch(candidate -> candidate.getCandidatePersonPid().getIdAsLong().equals(samplePersonPid));
|
||||
|
||||
if (allSamePerson) {
|
||||
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 {
|
||||
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
|
||||
List<IAnyResource> persons = thePersonCandidates.stream().map((MatchedPersonCandidate matchedPersonCandidate) -> myEmpiPersonFindingSvc.getPersonFromMatchedPersonCandidate(matchedPersonCandidate)).collect(Collectors.toList());
|
||||
persons.forEach(person -> {
|
||||
myEmpiLinkSvc.updateLink(person, theResource, EmpiMatchResultEnum.POSSIBLE_MATCH, EmpiLinkSourceEnum.AUTO, theEmpiTransactionContext);
|
||||
});
|
||||
List<IAnyResource> persons = new ArrayList<>();
|
||||
for (MatchedPersonCandidate matchedPersonCandidate : theCandidateList.getCandidates()) {
|
||||
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.
|
||||
IAnyResource samplePerson = persons.get(0);
|
||||
persons.subList(1, persons.size()).stream()
|
||||
.forEach(possibleDuplicatePerson -> {
|
||||
myEmpiLinkSvc.updateLink(samplePerson, possibleDuplicatePerson, EmpiMatchResultEnum.POSSIBLE_DUPLICATE, EmpiLinkSourceEnum.AUTO, theEmpiTransactionContext);
|
||||
});
|
||||
//Set all Persons as POSSIBLE_DUPLICATE of the last person.
|
||||
IAnyResource firstPerson = persons.get(0);
|
||||
persons.subList(1, persons.size())
|
||||
.forEach(possibleDuplicatePerson -> myEmpiLinkSvc.updateLink(firstPerson, possibleDuplicatePerson, EmpiMatchOutcome.EID_POSSIBLE_DUPLICATE, EmpiLinkSourceEnum.AUTO, theEmpiTransactionContext));
|
||||
}
|
||||
}
|
||||
|
||||
private void handleEmpiWithNoCandidates(IAnyResource theResource, EmpiTransactionContext theEmpiTransactionContext) {
|
||||
log(theEmpiTransactionContext, "There were no matched candidates for EMPI, creating a new Person.");
|
||||
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) {
|
||||
|
@ -120,8 +124,8 @@ public class EmpiMatchLinkSvc {
|
|||
if (myPersonHelper.isPotentialDuplicate(person, theResource)) {
|
||||
log(theEmpiTransactionContext, "Duplicate detected based on the fact that both resources have different external EIDs.");
|
||||
IAnyResource newPerson = myPersonHelper.createPersonFromEmpiTarget(theResource);
|
||||
myEmpiLinkSvc.updateLink(newPerson, theResource, EmpiMatchResultEnum.MATCH, EmpiLinkSourceEnum.AUTO, theEmpiTransactionContext);
|
||||
myEmpiLinkSvc.updateLink(newPerson, person, EmpiMatchResultEnum.POSSIBLE_DUPLICATE, EmpiLinkSourceEnum.AUTO, theEmpiTransactionContext);
|
||||
myEmpiLinkSvc.updateLink(newPerson, theResource, EmpiMatchOutcome.NEW_PERSON_MATCH, EmpiLinkSourceEnum.AUTO, theEmpiTransactionContext);
|
||||
myEmpiLinkSvc.updateLink(newPerson, person, EmpiMatchOutcome.POSSIBLE_DUPLICATE, EmpiLinkSourceEnum.AUTO, theEmpiTransactionContext);
|
||||
} else {
|
||||
if (thePersonCandidate.isMatch()) {
|
||||
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.");
|
||||
MatchedPersonCandidate matchedPersonCandidate = thePersonCandidates.get(0);
|
||||
if (theEmpiTransactionContext.getRestOperation().equals(EmpiTransactionContext.OperationType.UPDATE)) {
|
||||
myEidUpdateService.handleEmpiUpdate(theResource, matchedPersonCandidate, theEmpiTransactionContext);
|
||||
myEidUpdateService.handleEmpiUpdate(theResource, thePersonCandidate, theEmpiTransactionContext);
|
||||
} 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.model.EmpiTransactionContext;
|
||||
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.empi.dao.EmpiLinkDaoSvc;
|
||||
import ca.uhn.fhir.jpa.entity.EmpiLink;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||
|
@ -76,7 +76,7 @@ public class EmpiPersonMergerSvcImpl implements IEmpiPersonMergerSvc {
|
|||
}
|
||||
|
||||
private void addMergeLink(Long theFromPersonPid, Long theToPersonPid) {
|
||||
EmpiLink empiLink = new EmpiLink()
|
||||
EmpiLink empiLink = myEmpiLinkDaoSvc.newEmpiLink()
|
||||
.setPersonPid(theFromPersonPid)
|
||||
.setTargetPid(theToPersonPid)
|
||||
.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
|
||||
|
@ -21,6 +21,7 @@ package ca.uhn.fhir.jpa.empi.svc;
|
|||
*/
|
||||
|
||||
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.springframework.beans.factory.annotation.Autowired;
|
||||
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
|
||||
|
@ -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.IFhirResourceDao;
|
||||
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.rest.api.server.IBundleProvider;
|
||||
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
|
||||
|
@ -20,28 +20,33 @@ package ca.uhn.fhir.jpa.empi.svc;
|
|||
* #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;
|
||||
|
||||
public class MatchedPersonCandidate {
|
||||
|
||||
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;
|
||||
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() {
|
||||
return myCandidatePersonPid;
|
||||
}
|
||||
|
||||
public EmpiMatchResultEnum getMatchResult() {
|
||||
return myEmpiMatchResult;
|
||||
public EmpiMatchOutcome getMatchResult() {
|
||||
return myEmpiMatchOutcome;
|
||||
}
|
||||
|
||||
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.jpa.api.dao.IFhirResourceDao;
|
||||
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.index.IdHelperService;
|
||||
import ca.uhn.fhir.jpa.empi.config.EmpiConsumerConfig;
|
||||
import ca.uhn.fhir.jpa.empi.config.EmpiSearchParameterLoader;
|
||||
import ca.uhn.fhir.jpa.empi.config.EmpiSubmitterConfig;
|
||||
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.IsMatchedToAPerson;
|
||||
import ca.uhn.fhir.jpa.empi.matcher.IsPossibleDuplicateOf;
|
||||
|
@ -61,13 +61,14 @@ import static org.slf4j.LoggerFactory.getLogger;
|
|||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration(classes = {EmpiSubmitterConfig.class, EmpiConsumerConfig.class, TestEmpiConfigR4.class, SubscriptionProcessorConfig.class})
|
||||
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_PAUL = "Paul";
|
||||
public static final String TEST_NAME_FAMILY = "Doe";
|
||||
protected static final String TEST_ID_SYSTEM = "http://a.tv/";
|
||||
protected static final String JANE_ID = "ID.JANE.123";
|
||||
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()
|
||||
.setSystem(ContactPoint.ContactPointSystem.PHONE)
|
||||
.setValue("555-555-5555");
|
||||
|
@ -360,7 +361,7 @@ abstract public class BaseEmpiR4Test extends BaseJpaR4Test {
|
|||
Person person = createPerson();
|
||||
Patient patient = createPatient();
|
||||
|
||||
EmpiLink empiLink = new EmpiLink();
|
||||
EmpiLink empiLink = myEmpiLinkDaoSvc.newEmpiLink();
|
||||
empiLink.setLinkSource(EmpiLinkSourceEnum.MANUAL);
|
||||
empiLink.setMatchResult(EmpiMatchResultEnum.MATCH);
|
||||
empiLink.setPersonPid(myIdHelperService.getPidOrNull(person));
|
||||
|
@ -372,4 +373,12 @@ abstract public class BaseEmpiR4Test extends BaseJpaR4Test {
|
|||
myEmpiSearchParameterLoader.daoUpdateEmpiSearchParameters();
|
||||
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;
|
||||
|
||||
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.entity.EmpiLink;
|
||||
import ca.uhn.fhir.jpa.util.TestUtil;
|
||||
|
@ -19,6 +20,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
|||
public class EmpiLinkDaoSvcTest extends BaseEmpiR4Test {
|
||||
@Autowired
|
||||
EmpiLinkDaoSvc myEmpiLinkDaoSvc;
|
||||
@Autowired
|
||||
IEmpiSettings myEmpiSettings;
|
||||
|
||||
@Test
|
||||
public void testCreate() {
|
||||
|
@ -41,4 +44,12 @@ public class EmpiLinkDaoSvcTest extends BaseEmpiR4Test {
|
|||
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() {
|
||||
// This test is here to enforce that new values in these enums are always added to the end
|
||||
|
||||
assertEquals(4, EmpiMatchResultEnum.values().length);
|
||||
assertEquals(EmpiMatchResultEnum.POSSIBLE_DUPLICATE, EmpiMatchResultEnum.values()[EmpiMatchResultEnum.values().length - 1]);
|
||||
assertEquals(5, EmpiMatchResultEnum.values().length);
|
||||
assertEquals(EmpiMatchResultEnum.GOLDEN_RECORD, EmpiMatchResultEnum.values()[EmpiMatchResultEnum.values().length - 1]);
|
||||
|
||||
assertEquals(2, EmpiLinkSourceEnum.values().length);
|
||||
assertEquals(EmpiLinkSourceEnum.MANUAL, EmpiLinkSourceEnum.values()[EmpiLinkSourceEnum.values().length - 1]);
|
||||
|
|
|
@ -45,7 +45,7 @@ public class EmpiExpungeTest extends BaseEmpiR4Test {
|
|||
myTargetId = myTargetEntity.getIdDt().toVersionless();
|
||||
myPersonEntity = (ResourceTable) myPersonDao.create(new Person()).getEntity();
|
||||
|
||||
EmpiLink empiLink = new EmpiLink();
|
||||
EmpiLink empiLink = myEmpiLinkDaoSvc.newEmpiLink();
|
||||
empiLink.setLinkSource(EmpiLinkSourceEnum.MANUAL);
|
||||
empiLink.setMatchResult(EmpiMatchResultEnum.MATCH);
|
||||
empiLink.setPersonPid(myPersonEntity.getId());
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package ca.uhn.fhir.jpa.empi.matcher;
|
||||
|
||||
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.empi.dao.EmpiLinkDaoSvc;
|
||||
import ca.uhn.fhir.jpa.entity.EmpiLink;
|
||||
import org.hamcrest.TypeSafeMatcher;
|
||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
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.empi.dao.EmpiLinkDaoSvc;
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
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.empi.dao.EmpiLinkDaoSvc;
|
||||
import ca.uhn.fhir.jpa.entity.EmpiLink;
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.Matcher;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package ca.uhn.fhir.jpa.empi.matcher;
|
||||
|
||||
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.empi.dao.EmpiLinkDaoSvc;
|
||||
import ca.uhn.fhir.jpa.entity.EmpiLink;
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.Matcher;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
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.empi.dao.EmpiLinkDaoSvc;
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package ca.uhn.fhir.jpa.empi.matcher;
|
||||
|
||||
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.empi.dao.EmpiLinkDaoSvc;
|
||||
import ca.uhn.fhir.jpa.entity.EmpiLink;
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.Matcher;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
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.empi.dao.EmpiLinkDaoSvc;
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||
|
|
|
@ -47,7 +47,8 @@ public class EmpiProviderQueryLinkR4Test extends BaseLinkR4Test {
|
|||
Person person2 = createPerson();
|
||||
myPerson2Id = new StringType(person2.getIdElement().toVersionless().getValue());
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -59,12 +60,12 @@ public class EmpiProviderQueryLinkR4Test extends BaseLinkR4Test {
|
|||
List<Parameters.ParametersParameterComponent> list = result.getParameter();
|
||||
assertThat(list, hasSize(1));
|
||||
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
|
||||
public void testQueryLinkThreeMatches() {
|
||||
// Add a second patient
|
||||
// Add a third patient
|
||||
Patient patient = createPatientAndUpdateLinks(buildJanePatient());
|
||||
IdType patientId = patient.getIdElement().toVersionless();
|
||||
Person person = getPersonFromTarget(patient);
|
||||
|
@ -75,7 +76,7 @@ public class EmpiProviderQueryLinkR4Test extends BaseLinkR4Test {
|
|||
List<Parameters.ParametersParameterComponent> list = result.getParameter();
|
||||
assertThat(list, hasSize(3));
|
||||
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
|
||||
|
@ -85,7 +86,7 @@ public class EmpiProviderQueryLinkR4Test extends BaseLinkR4Test {
|
|||
List<Parameters.ParametersParameterComponent> list = result.getParameter();
|
||||
assertThat(list, hasSize(1));
|
||||
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
|
||||
|
@ -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.get(0).getName(), is("personId"));
|
||||
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(3).getName(), is("linkSource"));
|
||||
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);
|
||||
List<Person.PersonLinkComponent> links = person.getLink();
|
||||
assertEquals(2, links.size());
|
||||
assertEquals(Person.IdentityAssuranceLevel.LEVEL3, links.get(0).getAssurance());
|
||||
assertEquals(Person.IdentityAssuranceLevel.LEVEL2, links.get(1).getAssurance());
|
||||
assertEquals(Person.IdentityAssuranceLevel.LEVEL2, links.get(0).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.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.Patient;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package ca.uhn.fhir.jpa.empi.svc;
|
||||
|
||||
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.r4.model.Patient;
|
||||
import org.hl7.fhir.r4.model.Practitioner;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package ca.uhn.fhir.jpa.empi.svc;
|
||||
|
||||
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.IEmpiLinkSvc;
|
||||
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;
|
||||
|
||||
public class EmpiLinkSvcTest extends BaseEmpiR4Test {
|
||||
private static final EmpiMatchOutcome POSSIBLE_MATCH = new EmpiMatchOutcome(null, null).setMatchResultEnum(EmpiMatchResultEnum.POSSIBLE_MATCH);
|
||||
@Autowired
|
||||
IEmpiLinkSvc myEmpiLinkSvc;
|
||||
|
||||
|
@ -36,7 +38,7 @@ public class EmpiLinkSvcTest extends BaseEmpiR4Test {
|
|||
public void compareEmptyPatients() {
|
||||
Patient patient = new Patient();
|
||||
patient.setId("Patient/1");
|
||||
EmpiMatchResultEnum result = myEmpiResourceMatcherSvc.getMatchResult(patient, patient);
|
||||
EmpiMatchResultEnum result = myEmpiResourceMatcherSvc.getMatchResult(patient, patient).getMatchResultEnum();
|
||||
assertEquals(EmpiMatchResultEnum.NO_MATCH, result);
|
||||
}
|
||||
|
||||
|
@ -49,14 +51,14 @@ public class EmpiLinkSvcTest extends BaseEmpiR4Test {
|
|||
Patient patient = createPatient();
|
||||
|
||||
{
|
||||
myEmpiLinkSvc.updateLink(person, patient, EmpiMatchResultEnum.POSSIBLE_MATCH, EmpiLinkSourceEnum.AUTO, createContextForCreate());
|
||||
myEmpiLinkSvc.updateLink(person, patient, POSSIBLE_MATCH, EmpiLinkSourceEnum.AUTO, createContextForCreate());
|
||||
assertLinkCount(1);
|
||||
Person newPerson = myPersonDao.read(personId);
|
||||
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);
|
||||
Person newPerson = myPersonDao.read(personId);
|
||||
assertEquals(0, newPerson.getLink().size());
|
||||
|
@ -70,7 +72,7 @@ public class EmpiLinkSvcTest extends BaseEmpiR4Test {
|
|||
Person person = 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);
|
||||
}
|
||||
|
||||
|
@ -87,7 +89,7 @@ public class EmpiLinkSvcTest extends BaseEmpiR4Test {
|
|||
|
||||
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());
|
||||
assertLinkCount(1);
|
||||
}
|
||||
|
@ -105,13 +107,13 @@ public class EmpiLinkSvcTest extends BaseEmpiR4Test {
|
|||
|
||||
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());
|
||||
assertLinkCount(1);
|
||||
}
|
||||
|
||||
private void saveNoMatchLink(Long thePersonPid, Long theTargetPid) {
|
||||
EmpiLink noMatchLink = new EmpiLink()
|
||||
EmpiLink noMatchLink = myEmpiLinkDaoSvc.newEmpiLink()
|
||||
.setPersonPid(thePersonPid)
|
||||
.setTargetPid(theTargetPid)
|
||||
.setLinkSource(EmpiLinkSourceEnum.MANUAL)
|
||||
|
@ -124,9 +126,9 @@ public class EmpiLinkSvcTest extends BaseEmpiR4Test {
|
|||
Person person = createPerson(buildJanePerson());
|
||||
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 {
|
||||
myEmpiLinkSvc.updateLink(person, patient, EmpiMatchResultEnum.MATCH, EmpiLinkSourceEnum.AUTO, null);
|
||||
myEmpiLinkSvc.updateLink(person, patient, EmpiMatchOutcome.NEW_PERSON_MATCH, EmpiLinkSourceEnum.AUTO, null);
|
||||
fail();
|
||||
} catch (InternalErrorException e) {
|
||||
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.
|
||||
try {
|
||||
myEmpiLinkSvc.updateLink(person, patient, EmpiMatchResultEnum.NO_MATCH, EmpiLinkSourceEnum.AUTO, null);
|
||||
myEmpiLinkSvc.updateLink(person, patient, EmpiMatchOutcome.NO_MATCH, EmpiLinkSourceEnum.AUTO, null);
|
||||
fail();
|
||||
} catch (InternalErrorException e) {
|
||||
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());
|
||||
assertEquals(0, myEmpiLinkDao.count());
|
||||
|
||||
myEmpiLinkDaoSvc.createOrUpdateLinkEntity(person, patient1, EmpiMatchResultEnum.MATCH, EmpiLinkSourceEnum.MANUAL, createContextForCreate());
|
||||
myEmpiLinkDaoSvc.createOrUpdateLinkEntity(person, patient2, EmpiMatchResultEnum.NO_MATCH, EmpiLinkSourceEnum.MANUAL, createContextForCreate());
|
||||
myEmpiLinkDaoSvc.createOrUpdateLinkEntity(person, patient1, EmpiMatchOutcome.NEW_PERSON_MATCH, EmpiLinkSourceEnum.MANUAL, createContextForCreate());
|
||||
myEmpiLinkDaoSvc.createOrUpdateLinkEntity(person, patient2, EmpiMatchOutcome.NO_MATCH, EmpiLinkSourceEnum.MANUAL, createContextForCreate());
|
||||
myEmpiLinkSvc.syncEmpiLinksToPersonLinks(person, createContextForCreate());
|
||||
assertTrue(person.hasLink());
|
||||
assertEquals(patient1.getIdElement().toVersionless().getValue(), person.getLinkFirstRep().getTarget().getReference());
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package ca.uhn.fhir.jpa.empi.svc;
|
||||
|
||||
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.util.EIDHelper;
|
||||
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 java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
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.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.in;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.slf4j.LoggerFactory.getLogger;
|
||||
|
||||
@TestPropertySource(properties = {
|
||||
|
@ -43,6 +49,9 @@ public class EmpiMatchLinkSvcMultipleEidModeTest extends BaseEmpiR4Test {
|
|||
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.
|
||||
Patient patient = createPatientAndUpdateLinks(buildJanePatient());
|
||||
assertLinksMatchResult(MATCH);
|
||||
assertLinksNewPerson(true);
|
||||
assertLinksMatchedByEid(false);
|
||||
|
||||
Person janePerson = getPersonFromTarget(patient);
|
||||
List<CanonicalEID> hapiEid = myEidHelper.getHapiEid(janePerson);
|
||||
|
@ -52,6 +61,9 @@ public class EmpiMatchLinkSvcMultipleEidModeTest extends BaseEmpiR4Test {
|
|||
addExternalEID(janePatient, "12345");
|
||||
addExternalEID(janePatient, "67890");
|
||||
createPatientAndUpdateLinks(janePatient);
|
||||
assertLinksMatchResult(MATCH, MATCH);
|
||||
assertLinksNewPerson(true, false);
|
||||
assertLinksMatchedByEid(false, false);
|
||||
|
||||
//We want to make sure the patients were linked to the same person.
|
||||
assertThat(patient, is(samePersonAs(janePatient)));
|
||||
|
@ -75,6 +87,25 @@ public class EmpiMatchLinkSvcMultipleEidModeTest extends BaseEmpiR4Test {
|
|||
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 Case #4
|
||||
|
@ -86,11 +117,17 @@ public class EmpiMatchLinkSvcMultipleEidModeTest extends BaseEmpiR4Test {
|
|||
addExternalEID(patient1, "id_3");
|
||||
addExternalEID(patient1, "id_4");
|
||||
createPatientAndUpdateLinks(patient1);
|
||||
assertLinksMatchResult(MATCH);
|
||||
assertLinksNewPerson(true);
|
||||
assertLinksMatchedByEid(false);
|
||||
|
||||
Patient patient2 = buildPaulPatient();
|
||||
addExternalEID(patient2, "id_5");
|
||||
addExternalEID(patient2, "id_1");
|
||||
patient2 = createPatientAndUpdateLinks(patient2);
|
||||
assertLinksMatchResult(MATCH, MATCH);
|
||||
assertLinksNewPerson(true, false);
|
||||
assertLinksMatchedByEid(false, true);
|
||||
|
||||
assertThat(patient1, is(samePersonAs(patient2)));
|
||||
|
||||
|
@ -102,13 +139,14 @@ public class EmpiMatchLinkSvcMultipleEidModeTest extends BaseEmpiR4Test {
|
|||
assertThat(personFromTarget.getIdentifier(), hasSize(5));
|
||||
|
||||
updatePatientAndUpdateLinks(patient2);
|
||||
assertLinksMatchResult(MATCH, MATCH);
|
||||
assertLinksNewPerson(true, false);
|
||||
assertLinksMatchedByEid(false, true);
|
||||
|
||||
assertThat(patient1, is(samePersonAs(patient2)));
|
||||
|
||||
|
||||
personFromTarget = getPersonFromTarget(patient2);
|
||||
assertThat(personFromTarget.getIdentifier(), hasSize(6));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -118,16 +156,21 @@ public class EmpiMatchLinkSvcMultipleEidModeTest extends BaseEmpiR4Test {
|
|||
addExternalEID(patient1, "eid-1");
|
||||
addExternalEID(patient1, "eid-11");
|
||||
patient1 = createPatientAndUpdateLinks(patient1);
|
||||
assertLinksMatchResult(MATCH);
|
||||
assertLinksNewPerson(true);
|
||||
assertLinksMatchedByEid(false);
|
||||
|
||||
Patient patient2 = buildJanePatient();
|
||||
addExternalEID(patient2, "eid-2");
|
||||
addExternalEID(patient2, "eid-22");
|
||||
patient2 = createPatientAndUpdateLinks(patient2);
|
||||
assertLinksMatchResult(MATCH, MATCH, POSSIBLE_DUPLICATE);
|
||||
assertLinksNewPerson(true, true, false);
|
||||
assertLinksMatchedByEid(false, false, false);
|
||||
|
||||
List<EmpiLink> possibleDuplicates = myEmpiLinkDaoSvc.getPossibleDuplicates();
|
||||
assertThat(possibleDuplicates, hasSize(1));
|
||||
|
||||
|
||||
List<Long> duplicatePids = Stream.of(patient1, patient2)
|
||||
.map(this::getPersonFromTarget)
|
||||
.map(myIdHelperService::getPidOrNull)
|
||||
|
@ -146,26 +189,39 @@ public class EmpiMatchLinkSvcMultipleEidModeTest extends BaseEmpiR4Test {
|
|||
addExternalEID(patient1, "eid-1");
|
||||
addExternalEID(patient1, "eid-11");
|
||||
patient1 = createPatientAndUpdateLinks(patient1);
|
||||
assertLinksMatchResult(MATCH);
|
||||
assertLinksNewPerson(true);
|
||||
assertLinksMatchedByEid(false);
|
||||
|
||||
Patient patient2 = buildPaulPatient();
|
||||
addExternalEID(patient2, "eid-2");
|
||||
addExternalEID(patient2, "eid-22");
|
||||
patient2 = createPatientAndUpdateLinks(patient2);
|
||||
assertLinksMatchResult(MATCH, MATCH);
|
||||
assertLinksNewPerson(true, true);
|
||||
assertLinksMatchedByEid(false, false);
|
||||
|
||||
Patient patient3 = buildPaulPatient();
|
||||
addExternalEID(patient3, "eid-22");
|
||||
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.
|
||||
assertThat(patient2, is(samePersonAs(patient3)));
|
||||
|
||||
//Now lets change one of the EIDs on an incoming 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
|
||||
//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_ different persons. In this case, we want to
|
||||
//set them all to possible_match, and set the two persons as possible duplicates.
|
||||
patient2.getIdentifier().clear();
|
||||
addExternalEID(patient2, "eid-11");
|
||||
addExternalEID(patient2, "eid-22");
|
||||
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(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.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.model.CanonicalEID;
|
||||
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.
|
||||
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.
|
||||
myEmpiMatchLinkSvc.updateEmpiLinksForEmpiTarget(unmatchedJane, createContextForCreate());
|
||||
|
@ -105,7 +105,7 @@ public class EmpiMatchLinkSvcTest extends BaseEmpiR4Test {
|
|||
Patient unmatchedPatient = createPatient(buildJanePatient());
|
||||
|
||||
//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.
|
||||
|
||||
//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
|
||||
//own individual Persons for the purpose of this test.
|
||||
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))));
|
||||
|
||||
//In theory, this will match both Persons!
|
||||
|
@ -386,20 +386,20 @@ public class EmpiMatchLinkSvcTest extends BaseEmpiR4Test {
|
|||
Person.PersonLinkComponent linkFirstRep = janePerson.getLinkFirstRep();
|
||||
|
||||
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
|
||||
public void testManualMatchesGenerateAssuranceLevel4() {
|
||||
Patient patient = createPatientAndUpdateLinks(buildJanePatient());
|
||||
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);
|
||||
Person.PersonLinkComponent linkFirstRep = janePerson.getLinkFirstRep();
|
||||
|
||||
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
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package ca.uhn.fhir.jpa.empi.svc;
|
||||
|
||||
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.IEmpiPersonMergerSvc;
|
||||
import ca.uhn.fhir.empi.model.EmpiTransactionContext;
|
||||
|
@ -28,8 +29,8 @@ import java.util.Collections;
|
|||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
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 POSTAL_CODE = "M6G 1B4";
|
||||
private static final String BAD_GIVEN_NAME = "Bob";
|
||||
private static final EmpiMatchOutcome POSSIBLE_MATCH = new EmpiMatchOutcome(null, null).setMatchResultEnum(EmpiMatchResultEnum.POSSIBLE_MATCH);
|
||||
|
||||
@Autowired
|
||||
IEmpiPersonMergerSvc myEmpiPersonMergerSvc;
|
||||
|
@ -107,7 +109,7 @@ public class EmpiPersonMergerSvcTest extends BaseEmpiR4Test {
|
|||
|
||||
@Test
|
||||
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);
|
||||
assertEquals(1, myEmpiLinkDao.count());
|
||||
mergePersons();
|
||||
|
@ -371,7 +373,7 @@ public class EmpiPersonMergerSvcTest extends BaseEmpiR4Test {
|
|||
|
||||
private EmpiLink createEmpiLink(Person thePerson, Patient 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) {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"version": "1",
|
||||
"candidateSearchParams": [
|
||||
{
|
||||
"resourceType": "Patient",
|
||||
|
|
|
@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.migrate.tasks;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.jpa.entity.EmpiLink;
|
||||
import ca.uhn.fhir.jpa.migrate.DriverTypeEnum;
|
||||
import ca.uhn.fhir.jpa.migrate.taskdef.ArbitrarySqlTask;
|
||||
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");
|
||||
pkgVerMod.modifyColumn("20200629.1", "PKG_DESC").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_20200706_to_20200714() {
|
||||
|
||||
}
|
||||
|
||||
private void init501() { //20200514 - present
|
||||
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.
|
||||
*/
|
||||
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!
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ public interface IEmpiLinkSvc {
|
|||
* @param theLinkSource MANUAL or AUTO: what caused the link.
|
||||
* @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
|
||||
|
|
|
@ -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 isPreventMultipleEids();
|
||||
|
||||
String getRuleVersion();
|
||||
}
|
||||
|
|
|
@ -25,9 +25,9 @@ import org.hl7.fhir.instance.model.api.IAnyResource;
|
|||
public class MatchedTarget {
|
||||
|
||||
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;
|
||||
myMatchResult = theMatchResult;
|
||||
}
|
||||
|
@ -36,15 +36,15 @@ public class MatchedTarget {
|
|||
return myTarget;
|
||||
}
|
||||
|
||||
public EmpiMatchResultEnum getMatchResult() {
|
||||
public EmpiMatchOutcome getMatchResult() {
|
||||
return myMatchResult;
|
||||
}
|
||||
|
||||
public boolean isMatch() {
|
||||
return myMatchResult == EmpiMatchResultEnum.MATCH;
|
||||
return myMatchResult.isMatch();
|
||||
}
|
||||
|
||||
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.FhirContext;
|
||||
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.EmpiFilterSearchParamJson;
|
||||
import ca.uhn.fhir.empi.rules.json.EmpiResourceSearchParamJson;
|
||||
|
@ -42,7 +43,7 @@ import java.util.HashSet;
|
|||
import java.util.Set;
|
||||
|
||||
@Service
|
||||
public class EmpiRuleValidator {
|
||||
public class EmpiRuleValidator implements IEmpiRuleValidator {
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(EmpiRuleValidator.class);
|
||||
|
||||
private final FhirContext myFhirContext;
|
||||
|
|
|
@ -20,6 +20,7 @@ package ca.uhn.fhir.empi.rules.config;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.empi.api.IEmpiRuleValidator;
|
||||
import ca.uhn.fhir.empi.api.IEmpiSettings;
|
||||
import ca.uhn.fhir.empi.rules.json.EmpiRulesJson;
|
||||
import ca.uhn.fhir.util.JsonUtil;
|
||||
|
@ -30,7 +31,7 @@ import java.io.IOException;
|
|||
|
||||
@Component
|
||||
public class EmpiSettings implements IEmpiSettings {
|
||||
private final EmpiRuleValidator myEmpiRuleValidator;
|
||||
private final IEmpiRuleValidator myEmpiRuleValidator;
|
||||
|
||||
private boolean myEnabled;
|
||||
private int myConcurrentConsumers = EMPI_DEFAULT_CONCURRENT_CONSUMERS;
|
||||
|
@ -48,7 +49,7 @@ public class EmpiSettings implements IEmpiSettings {
|
|||
private boolean myPreventMultipleEids;
|
||||
|
||||
@Autowired
|
||||
public EmpiSettings(EmpiRuleValidator theEmpiRuleValidator) {
|
||||
public EmpiSettings(IEmpiRuleValidator theEmpiRuleValidator) {
|
||||
myEmpiRuleValidator = theEmpiRuleValidator;
|
||||
}
|
||||
|
||||
|
@ -107,6 +108,11 @@ public class EmpiSettings implements IEmpiSettings {
|
|||
return myPreventMultipleEids;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRuleVersion() {
|
||||
return myEmpiRules.getVersion();
|
||||
}
|
||||
|
||||
public EmpiSettings setPreventMultipleEids(boolean thePreventMultipleEids) {
|
||||
myPreventMultipleEids = thePreventMultipleEids;
|
||||
return this;
|
||||
|
|
|
@ -26,6 +26,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
|||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.util.StdConverter;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
@ -35,6 +36,8 @@ import java.util.Map;
|
|||
|
||||
@JsonDeserialize(converter = EmpiRulesJson.EmpiRulesJsonConverter.class)
|
||||
public class EmpiRulesJson implements IModelJson {
|
||||
@JsonProperty(value = "version", required = true)
|
||||
String myVersion;
|
||||
@JsonProperty(value = "candidateSearchParams", required = true)
|
||||
List<EmpiResourceSearchParamJson> myCandidateSearchParams = new ArrayList<>();
|
||||
@JsonProperty(value = "candidateFilterSearchParams", required = true)
|
||||
|
@ -112,22 +115,17 @@ public class EmpiRulesJson implements IModelJson {
|
|||
myEnterpriseEIDSystem = theEnterpriseEIDSystem;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
public String getVersion() {
|
||||
return myVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EmpiRulesJson convert(EmpiRulesJson theEmpiRulesJson) {
|
||||
theEmpiRulesJson.initialize();
|
||||
return theEmpiRulesJson;
|
||||
public EmpiRulesJson setVersion(String theVersion) {
|
||||
myVersion = theVersion;
|
||||
return this;
|
||||
}
|
||||
|
||||
private void validate() {
|
||||
Validate.notBlank(myVersion, "version may not be blank");
|
||||
}
|
||||
|
||||
public String getSummary() {
|
||||
|
@ -157,4 +155,23 @@ public class EmpiRulesJson implements IModelJson {
|
|||
VectorMatchResultMap getVectorMatchResultMapForUnitTest() {
|
||||
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.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.HapiDateMatcher;
|
||||
import ca.uhn.fhir.empi.rules.metric.matcher.HapiStringMatcher;
|
||||
|
@ -77,14 +78,24 @@ public enum EmpiMetricEnum {
|
|||
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()) {
|
||||
return ((IEmpiFieldSimilarity) myEmpiFieldMetric).similarity(theFhirContext, theLeftBase, theRightBase, theExact) >= theThreshold;
|
||||
return matchBySimilarity((IEmpiFieldSimilarity) myEmpiFieldMetric, theFhirContext, theLeftBase, theRightBase, theExact, theThreshold);
|
||||
} else {
|
||||
return ((IEmpiFieldMatcher) myEmpiFieldMetric).matches(theFhirContext, theLeftBase, theRightBase, theExact);
|
||||
return matchByMatcher((IEmpiFieldMatcher) myEmpiFieldMetric, theFhirContext, theLeftBase, theRightBase, theExact);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ package ca.uhn.fhir.empi.rules.svc;
|
|||
*/
|
||||
|
||||
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.util.FhirTerser;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
@ -58,7 +59,7 @@ public class EmpiResourceFieldMatcher {
|
|||
* @return A boolean indicating whether they match.
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public boolean match(IBaseResource theLeftResource, IBaseResource theRightResource) {
|
||||
public EmpiMatchEvaluation match(IBaseResource theLeftResource, IBaseResource theRightResource) {
|
||||
validate(theLeftResource);
|
||||
validate(theRightResource);
|
||||
|
||||
|
@ -69,17 +70,18 @@ public class EmpiResourceFieldMatcher {
|
|||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private boolean match(List<IBase> theLeftValues, List<IBase> theRightValues) {
|
||||
boolean retval = false;
|
||||
private EmpiMatchEvaluation match(List<IBase> theLeftValues, List<IBase> theRightValues) {
|
||||
EmpiMatchEvaluation retval = new EmpiMatchEvaluation(false, 0.0);
|
||||
for (IBase leftValue : theLeftValues) {
|
||||
for (IBase rightValue : theRightValues) {
|
||||
retval |= match(leftValue, rightValue);
|
||||
EmpiMatchEvaluation nextMatch = match(leftValue, rightValue);
|
||||
retval = EmpiMatchEvaluation.max(retval, nextMatch);
|
||||
}
|
||||
}
|
||||
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());
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,8 @@ package ca.uhn.fhir.empi.rules.svc;
|
|||
|
||||
import ca.uhn.fhir.context.ConfigurationException;
|
||||
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.IEmpiSettings;
|
||||
import ca.uhn.fhir.empi.log.Logs;
|
||||
|
@ -47,19 +49,19 @@ public class EmpiResourceMatcherSvc {
|
|||
private static final Logger ourLog = Logs.getEmpiTroubleshootingLog();
|
||||
|
||||
private final FhirContext myFhirContext;
|
||||
private final IEmpiSettings myEmpiConfig;
|
||||
private final IEmpiSettings myEmpiSettings;
|
||||
private EmpiRulesJson myEmpiRulesJson;
|
||||
private final List<EmpiResourceFieldMatcher> myFieldMatchers = new ArrayList<>();
|
||||
|
||||
@Autowired
|
||||
public EmpiResourceMatcherSvc(FhirContext theFhirContext, IEmpiSettings theEmpiConfig) {
|
||||
public EmpiResourceMatcherSvc(FhirContext theFhirContext, IEmpiSettings theEmpiSettings) {
|
||||
myFhirContext = theFhirContext;
|
||||
myEmpiConfig = theEmpiConfig;
|
||||
myEmpiSettings = theEmpiSettings;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
myEmpiRulesJson = myEmpiConfig.getEmpiRules();
|
||||
myEmpiRulesJson = myEmpiSettings.getEmpiRules();
|
||||
if (myEmpiRulesJson == null) {
|
||||
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.
|
||||
*/
|
||||
public EmpiMatchResultEnum getMatchResult(IBaseResource theLeftResource, IBaseResource theRightResource) {
|
||||
public EmpiMatchOutcome getMatchResult(IBaseResource theLeftResource, IBaseResource theRightResource) {
|
||||
return match(theLeftResource, theRightResource);
|
||||
}
|
||||
|
||||
EmpiMatchResultEnum match(IBaseResource theLeftResource, IBaseResource theRightResource) {
|
||||
long matchVector = getMatchVector(theLeftResource, theRightResource);
|
||||
EmpiMatchResultEnum matchResult = myEmpiRulesJson.getMatchResult(matchVector);
|
||||
EmpiMatchOutcome match(IBaseResource theLeftResource, IBaseResource theRightResource) {
|
||||
EmpiMatchOutcome matchResult = getMatchOutcome(theLeftResource, theRightResource);
|
||||
EmpiMatchResultEnum matchResultEnum = myEmpiRulesJson.getMatchResult(matchResult.vector);
|
||||
matchResult.setMatchResultEnum(matchResultEnum);
|
||||
if (ourLog.isDebugEnabled()) {
|
||||
if (matchResult == EmpiMatchResultEnum.MATCH || matchResult == EmpiMatchResultEnum.POSSIBLE_MATCH) {
|
||||
ourLog.debug("{} {} with field matchers {}", matchResult, theRightResource.getIdElement().toUnqualifiedVersionless(), myEmpiRulesJson.getFieldMatchNamesForVector(matchVector));
|
||||
if (matchResult.isMatch() || matchResult.isPossibleMatch()) {
|
||||
ourLog.debug("{} {} with field matchers {}", matchResult, theRightResource.getIdElement().toUnqualifiedVersionless(), myEmpiRulesJson.getFieldMatchNamesForVector(matchResult.vector));
|
||||
} 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;
|
||||
|
@ -110,15 +113,18 @@ public class EmpiResourceMatcherSvc {
|
|||
* 0001|0010 = 0011
|
||||
* The binary string is now `0011`, which when you return it as a long becomes `3`.
|
||||
*/
|
||||
private long getMatchVector(IBaseResource theLeftResource, IBaseResource theRightResource) {
|
||||
long retval = 0;
|
||||
private EmpiMatchOutcome getMatchOutcome(IBaseResource theLeftResource, IBaseResource theRightResource) {
|
||||
long vector = 0;
|
||||
double score = 0.0;
|
||||
for (int i = 0; i < myFieldMatchers.size(); ++i) {
|
||||
//any that are not for the resourceType in question.
|
||||
EmpiResourceFieldMatcher fieldComparator = myFieldMatchers.get(i);
|
||||
if (fieldComparator.match(theLeftResource, theRightResource)) {
|
||||
retval |= (1 << i);
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
EmpiMatchEvaluation matchEvaluation = fieldComparator.match(theLeftResource, theRightResource);
|
||||
if (matchEvaluation.match) {
|
||||
vector |= (1 << i);
|
||||
}
|
||||
score += matchEvaluation.score;
|
||||
}
|
||||
return new EmpiMatchOutcome(vector, score);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,9 +47,9 @@ public final class AssuranceLevelUtil {
|
|||
private static CanonicalIdentityAssuranceLevel getAssuranceFromAutoResult(EmpiMatchResultEnum theMatchResult) {
|
||||
switch (theMatchResult) {
|
||||
case MATCH:
|
||||
return CanonicalIdentityAssuranceLevel.LEVEL3;
|
||||
case POSSIBLE_MATCH:
|
||||
return CanonicalIdentityAssuranceLevel.LEVEL2;
|
||||
case POSSIBLE_MATCH:
|
||||
return CanonicalIdentityAssuranceLevel.LEVEL1;
|
||||
case POSSIBLE_DUPLICATE:
|
||||
case NO_MATCH:
|
||||
default:
|
||||
|
@ -60,7 +60,7 @@ public final class AssuranceLevelUtil {
|
|||
private static CanonicalIdentityAssuranceLevel getAssuranceFromManualResult(EmpiMatchResultEnum theMatchResult) {
|
||||
switch (theMatchResult) {
|
||||
case MATCH:
|
||||
return CanonicalIdentityAssuranceLevel.LEVEL4;
|
||||
return CanonicalIdentityAssuranceLevel.LEVEL3;
|
||||
case NO_MATCH:
|
||||
case POSSIBLE_DUPLICATE:
|
||||
case POSSIBLE_MATCH:
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package ca.uhn.fhir.empi;
|
||||
|
||||
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.EmpiSettings;
|
||||
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 org.hl7.fhir.r4.model.Patient;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
|
@ -37,4 +39,16 @@ public abstract class BaseR4Test {
|
|||
retval.init();
|
||||
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 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.fail;
|
||||
|
||||
|
@ -27,6 +29,16 @@ public class EmpiRulesJsonR4Test extends BaseEmpiRulesR4Test {
|
|||
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
|
||||
public void testSerDeser() throws IOException {
|
||||
String json = JsonUtil.serialize(myRules);
|
||||
|
|
|
@ -51,6 +51,7 @@ public abstract class BaseEmpiRulesR4Test extends BaseR4Test {
|
|||
.setMatchThreshold(NAME_THRESHOLD);
|
||||
|
||||
EmpiRulesJson retval = new EmpiRulesJson();
|
||||
retval.setVersion("test version");
|
||||
retval.addResourceSearchParam(patientBirthdayBlocking);
|
||||
retval.addResourceSearchParam(patientIdentifierBlocking);
|
||||
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.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class CustomResourceMatcherR4Test extends BaseR4Test {
|
||||
|
||||
public static final String FIELD_EXACT_MATCH_NAME = EmpiMetricEnum.NAME_ANY_ORDER.name();
|
||||
|
@ -27,53 +25,54 @@ public class CustomResourceMatcherR4Test extends BaseR4Test {
|
|||
@Test
|
||||
public void testExactNameAnyOrder() {
|
||||
EmpiResourceMatcherSvc nameAnyOrderMatcher = buildMatcher(buildNameRules(EmpiMetricEnum.NAME_ANY_ORDER, true));
|
||||
assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnHenry));
|
||||
assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourHenryJohn));
|
||||
assertEquals(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourHenryJOHN));
|
||||
assertEquals(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnHENRY));
|
||||
assertEquals(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJaneHenry));
|
||||
assertEquals(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnSmith));
|
||||
assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnBillyHenry));
|
||||
assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourBillyJohnHenry));
|
||||
assertMatch(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnHenry));
|
||||
assertMatch(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourHenryJohn));
|
||||
assertMatch(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourHenryJOHN));
|
||||
assertMatch(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnHENRY));
|
||||
assertMatch(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJaneHenry));
|
||||
assertMatch(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnSmith));
|
||||
assertMatch(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnBillyHenry));
|
||||
assertMatch(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourBillyJohnHenry));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNormalizedNameAnyOrder() {
|
||||
EmpiResourceMatcherSvc nameAnyOrderMatcher = buildMatcher(buildNameRules(EmpiMetricEnum.NAME_ANY_ORDER, false));
|
||||
assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnHenry));
|
||||
assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourHenryJohn));
|
||||
assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourHenryJOHN));
|
||||
assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnHENRY));
|
||||
assertEquals(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJaneHenry));
|
||||
assertEquals(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnSmith));
|
||||
assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnBillyHenry));
|
||||
assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourBillyJohnHenry));
|
||||
assertMatch(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnHenry));
|
||||
assertMatch(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourHenryJohn));
|
||||
assertMatch(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourHenryJOHN));
|
||||
assertMatch(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnHENRY));
|
||||
assertMatch(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJaneHenry));
|
||||
assertMatch(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnSmith));
|
||||
assertMatch(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnBillyHenry));
|
||||
assertMatch(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourBillyJohnHenry));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExactNameFirstAndLast() {
|
||||
EmpiResourceMatcherSvc nameAnyOrderMatcher = buildMatcher(buildNameRules(EmpiMetricEnum.NAME_FIRST_AND_LAST, true));
|
||||
assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnHenry));
|
||||
assertEquals(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourHenryJohn));
|
||||
assertEquals(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourHenryJOHN));
|
||||
assertEquals(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnHENRY));
|
||||
assertEquals(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJaneHenry));
|
||||
assertEquals(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnSmith));
|
||||
assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnBillyHenry));
|
||||
assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourBillyJohnHenry));
|
||||
assertMatch(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnHenry));
|
||||
assertMatchResult(EmpiMatchResultEnum.MATCH, 1L, 1.0, false, false, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnHenry));
|
||||
assertMatch(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourHenryJohn));
|
||||
assertMatch(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourHenryJOHN));
|
||||
assertMatch(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnHENRY));
|
||||
assertMatch(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJaneHenry));
|
||||
assertMatch(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnSmith));
|
||||
assertMatch(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnBillyHenry));
|
||||
assertMatch(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourBillyJohnHenry));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNormalizedNameFirstAndLast() {
|
||||
EmpiResourceMatcherSvc nameAnyOrderMatcher = buildMatcher(buildNameRules(EmpiMetricEnum.NAME_FIRST_AND_LAST, false));
|
||||
assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnHenry));
|
||||
assertEquals(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourHenryJohn));
|
||||
assertEquals(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourHenryJOHN));
|
||||
assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnHENRY));
|
||||
assertEquals(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJaneHenry));
|
||||
assertEquals(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnSmith));
|
||||
assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnBillyHenry));
|
||||
assertEquals(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourBillyJohnHenry));
|
||||
assertMatch(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnHenry));
|
||||
assertMatch(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourHenryJohn));
|
||||
assertMatch(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourHenryJOHN));
|
||||
assertMatch(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnHENRY));
|
||||
assertMatch(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJaneHenry));
|
||||
assertMatch(EmpiMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnSmith));
|
||||
assertMatch(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnBillyHenry));
|
||||
assertMatch(EmpiMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourBillyJohnHenry));
|
||||
}
|
||||
|
||||
private EmpiRulesJson buildNameRules(EmpiMetricEnum theExactNameAnyOrder, boolean theExact) {
|
||||
|
|
|
@ -36,7 +36,7 @@ public class EmpiResourceFieldMatcherR4Test extends BaseEmpiRulesR4Test {
|
|||
Patient patient = new Patient();
|
||||
patient.setActive(true);
|
||||
|
||||
assertFalse(myComparator.match(patient, myJohny));
|
||||
assertFalse(myComparator.match(patient, myJohny).match);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -77,6 +77,6 @@ public class EmpiResourceFieldMatcherR4Test extends BaseEmpiRulesR4Test {
|
|||
|
||||
@Test
|
||||
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;
|
||||
|
||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||
import ca.uhn.fhir.empi.api.EmpiMatchOutcome;
|
||||
import ca.uhn.fhir.empi.api.EmpiMatchResultEnum;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
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.when;
|
||||
|
||||
public class EmpiResourceMatcherSvcR4Test extends BaseEmpiRulesR4Test {
|
||||
public static final double NAME_DELTA = 0.0001;
|
||||
private EmpiResourceMatcherSvc myEmpiResourceMatcherSvc;
|
||||
private Patient myJohn;
|
||||
private Patient myJohny;
|
||||
|
@ -33,27 +32,28 @@ public class EmpiResourceMatcherSvcR4Test extends BaseEmpiRulesR4Test {
|
|||
|
||||
@Test
|
||||
public void testCompareFirstNameMatch() {
|
||||
EmpiMatchResultEnum result = myEmpiResourceMatcherSvc.match(myJohn, myJohny);
|
||||
assertEquals(EmpiMatchResultEnum.POSSIBLE_MATCH, result);
|
||||
EmpiMatchOutcome result = myEmpiResourceMatcherSvc.match(myJohn, myJohny);
|
||||
assertMatchResult(EmpiMatchResultEnum.POSSIBLE_MATCH, 1L, 0.816, false, false, result);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareBothNamesMatch() {
|
||||
myJohn.addName().setFamily("Smith");
|
||||
myJohny.addName().setFamily("Smith");
|
||||
EmpiMatchResultEnum result = myEmpiResourceMatcherSvc.match(myJohn, myJohny);
|
||||
assertEquals(EmpiMatchResultEnum.MATCH, result);
|
||||
EmpiMatchOutcome result = myEmpiResourceMatcherSvc.match(myJohn, myJohny);
|
||||
assertMatchResult(EmpiMatchResultEnum.MATCH, 3L, 1.816, false, false, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
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");
|
||||
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();
|
||||
patient3.setId("Patient/3");
|
||||
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.POSSIBLE_DUPLICATE;
|
||||
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.LEVEL3;
|
||||
import static ca.uhn.fhir.empi.model.CanonicalIdentityAssuranceLevel.LEVEL4;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.core.Is.is;
|
||||
import static org.hamcrest.core.IsEqual.equalTo;
|
||||
|
@ -22,9 +22,9 @@ public class AssuranceLevelUtilTest {
|
|||
|
||||
@Test
|
||||
public void testValidPersonLinkLevels() {
|
||||
assertThat(AssuranceLevelUtil.getAssuranceLevel(POSSIBLE_MATCH, AUTO), is(equalTo(LEVEL2)));
|
||||
assertThat(AssuranceLevelUtil.getAssuranceLevel(MATCH, AUTO), is(equalTo(LEVEL3)));
|
||||
assertThat(AssuranceLevelUtil.getAssuranceLevel(MATCH, MANUAL), is(equalTo(LEVEL4)));
|
||||
assertThat(AssuranceLevelUtil.getAssuranceLevel(POSSIBLE_MATCH, AUTO), is(equalTo(LEVEL1)));
|
||||
assertThat(AssuranceLevelUtil.getAssuranceLevel(MATCH, AUTO), is(equalTo(LEVEL2)));
|
||||
assertThat(AssuranceLevelUtil.getAssuranceLevel(MATCH, MANUAL), is(equalTo(LEVEL3)));
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"version": "1",
|
||||
"candidateSearchParams" : [],
|
||||
"candidateFilterSearchParams" : [{
|
||||
"resourceType" : "Patient",
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"version": "1",
|
||||
"candidateSearchParams" : [],
|
||||
"candidateFilterSearchParams" : [],
|
||||
"matchFields" : [ {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"version": "1",
|
||||
"candidateSearchParams" : [{
|
||||
"resourceType" : "Patient",
|
||||
"searchParams" : ["foo"]
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"version": "1",
|
||||
"candidateSearchParams" : [],
|
||||
"candidateFilterSearchParams" : [],
|
||||
"matchFields" : [],
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"version": "1",
|
||||
"candidateSearchParams": [],
|
||||
"candidateFilterSearchParams": [],
|
||||
"matchFields": [
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"version": "1",
|
||||
"candidateSearchParams" : [],
|
||||
"candidateFilterSearchParams" : [],
|
||||
"matchFields" : [ {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"version": "1",
|
||||
"candidateSearchParams" : [],
|
||||
"candidateFilterSearchParams" : [],
|
||||
"matchFields" : [ {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"version": "1",
|
||||
"candidateSearchParams" : [],
|
||||
"candidateFilterSearchParams" : [],
|
||||
"matchFields" : [ {
|
||||
|
|
Loading…
Reference in New Issue