diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ParametersUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ParametersUtil.java index bc50d366b25..93780f3305d 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ParametersUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ParametersUtil.java @@ -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 value = (IPrimitiveType) 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 value = (IPrimitiveType) 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(); diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa_empi/empi_details.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa_empi/empi_details.md index 7859fab2f7b..c3c87ecc78d 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa_empi/empi_details.md +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa_empi/empi_details.md @@ -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: diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa_empi/empi_operations.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa_empi/empi_operations.md index c638a4c7d14..1e3068f0d5f 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa_empi/empi_operations.md +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa_empi/empi_operations.md @@ -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 } ] } ] } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/empi/EmpiLinkDeleteSvc.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/empi/EmpiLinkDeleteSvc.java new file mode 100644 index 00000000000..ba5eea15c04 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/empi/EmpiLinkDeleteSvc.java @@ -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; + } +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/EmpiLink.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/EmpiLink.java index a194026e18a..556fd0d9f50 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/EmpiLink.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/EmpiLink.java @@ -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(); + } } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestJPAConfig.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestJPAConfig.java index 1914951a3aa..b4d9f312862 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestJPAConfig.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestJPAConfig.java @@ -24,7 +24,6 @@ import javax.persistence.EntityManagerFactory; SubscriptionChannelConfig.class }) public class TestJPAConfig { - @Bean public DaoConfig daoConfig() { return new DaoConfig(); diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/config/EmpiConsumerConfig.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/config/EmpiConsumerConfig.java index 6ae308bb772..199e4a0bab0 100644 --- a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/config/EmpiConsumerConfig.java +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/config/EmpiConsumerConfig.java @@ -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(); } } diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/config/EmpiLoader.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/config/EmpiLoader.java new file mode 100644 index 00000000000..d266442c936 --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/config/EmpiLoader.java @@ -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"); + } +} diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/config/EmpiSubmitterConfig.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/config/EmpiSubmitterConfig.java index 3853a4dc878..a89f1cd23b2 100644 --- a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/config/EmpiSubmitterConfig.java +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/config/EmpiSubmitterConfig.java @@ -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(); + } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/EmpiLinkDaoSvc.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/dao/EmpiLinkDaoSvc.java similarity index 80% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/EmpiLinkDaoSvc.java rename to hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/dao/EmpiLinkDaoSvc.java index cd64a83c289..87938f40a6a 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/EmpiLinkDaoSvc.java +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/dao/EmpiLinkDaoSvc.java @@ -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 example = Example.of(link); @@ -95,7 +104,7 @@ public class EmpiLinkDaoSvc { } public List getEmpiLinksByTargetPidAndMatchResult(Long theTargetPid, EmpiMatchResultEnum theMatchResult) { - EmpiLink exampleLink = new EmpiLink(); + EmpiLink exampleLink = myEmpiLinkFactory.newEmpiLink(); exampleLink.setTargetPid(theTargetPid); exampleLink.setMatchResult(theMatchResult); Example example = Example.of(exampleLink); @@ -103,7 +112,7 @@ public class EmpiLinkDaoSvc { } public Optional getMatchedLinkForTargetPid(Long theTargetPid) { - EmpiLink exampleLink = new EmpiLink(); + EmpiLink exampleLink = myEmpiLinkFactory.newEmpiLink(); exampleLink.setTargetPid(theTargetPid); exampleLink.setMatchResult(EmpiMatchResultEnum.MATCH); Example 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 example = Example.of(exampleLink); @@ -124,7 +133,7 @@ public class EmpiLinkDaoSvc { } public Optional 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 getPossibleDuplicates() { - EmpiLink exampleLink = new EmpiLink(); + EmpiLink exampleLink = myEmpiLinkFactory.newEmpiLink(); exampleLink.setMatchResult(EmpiMatchResultEnum.POSSIBLE_DUPLICATE); Example 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 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 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 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 example = Example.of(empiLink); return myEmpiLinkDao.findAll(example); } + + public EmpiLink newEmpiLink() { + return myEmpiLinkFactory.newEmpiLink(); + } } diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/dao/EmpiLinkFactory.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/dao/EmpiLinkFactory.java new file mode 100644 index 00000000000..ae103cd735c --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/dao/EmpiLinkFactory.java @@ -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()); + } +} diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/interceptor/EmpiStorageInterceptor.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/interceptor/EmpiStorageInterceptor.java index f2b88662fa7..1860f9a8fc8 100644 --- a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/interceptor/EmpiStorageInterceptor.java +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/interceptor/EmpiStorageInterceptor.java @@ -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)); } } diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiEidUpdateService.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiEidUpdateService.java index 9ecfd1b5536..12af900192c 100644 --- a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiEidUpdateService.java +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiEidUpdateService.java @@ -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) { diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiLinkQuerySvcImpl.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiLinkQuerySvcImpl.java index 614700faf75..f520d1f722b 100644 --- a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiLinkQuerySvcImpl.java +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiLinkQuerySvcImpl.java @@ -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 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)); } diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiLinkSvcImpl.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiLinkSvcImpl.java index 641207873c1..43997ba75ff 100644 --- a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiLinkSvcImpl.java +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiLinkSvcImpl.java @@ -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) { diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiLinkUpdaterSvcImpl.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiLinkUpdaterSvcImpl.java index 9093ab62f64..b45f91e87bb 100644 --- a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiLinkUpdaterSvcImpl.java +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiLinkUpdaterSvcImpl.java @@ -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; diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiMatchFinderSvcImpl.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiMatchFinderSvcImpl.java index 00d221799a0..66286163891 100644 --- a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiMatchFinderSvcImpl.java +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiMatchFinderSvcImpl.java @@ -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; diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiMatchLinkSvc.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiMatchLinkSvc.java index 1f8d21ece2f..ef56e8e5161 100644 --- a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiMatchLinkSvc.java +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiMatchLinkSvc.java @@ -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 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 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 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 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 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); } } diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiPersonFindingSvc.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiPersonFindingSvc.java deleted file mode 100644 index c0b4cdd5eac..00000000000 --- a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiPersonFindingSvc.java +++ /dev/null @@ -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: - *

- * 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 findPersonCandidates(IAnyResource theResource) { - List 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 attemptToFindPersonCandidateFromIncomingEID(IAnyResource theBaseResource) { - List retval = new ArrayList<>(); - - List eidFromResource = myEIDHelper.getExternalEid(theBaseResource); - if (!eidFromResource.isEmpty()) { - for (CanonicalEID eid : eidFromResource) { - Optional 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 attemptToFindPersonCandidateFromEmpiLinkTable(IAnyResource theBaseResource) { - List retval = new ArrayList<>(); - - Long targetPid = myIdHelperService.getPidOrNull(theBaseResource); - if (targetPid != null) { - Optional 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 attemptToFindPersonCandidateFromSimilarTargetResource(IAnyResource theBaseResource) { - List retval = new ArrayList<>(); - - List personPidsToExclude = getNoMatchPersonPids(theBaseResource); - List 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 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 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); - } -} diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiPersonMergerSvcImpl.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiPersonMergerSvcImpl.java index 80c1ef05389..69347b7f3ab 100644 --- a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiPersonMergerSvcImpl.java +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiPersonMergerSvcImpl.java @@ -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) diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/candidate/BaseCandidateFinder.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/candidate/BaseCandidateFinder.java new file mode 100644 index 00000000000..47b2aa1b4a4 --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/candidate/BaseCandidateFinder.java @@ -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 findMatchPersonCandidates(IAnyResource theTarget); + + protected abstract CandidateStrategyEnum getStrategy(); +} diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/candidate/CandidateList.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/candidate/CandidateList.java new file mode 100644 index 00000000000..d319724570e --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/candidate/CandidateList.java @@ -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 myList = new ArrayList<>(); + + public CandidateList(CandidateStrategyEnum theSource) { + mySource = theSource; + } + + public CandidateStrategyEnum getSource() { + return mySource; + } + + public boolean isEmpty() { + return myList.isEmpty(); + } + + public void addAll(List theList) { myList.addAll(theList); } + + public MatchedPersonCandidate getOnlyMatch() { + assert myList.size() == 1; + return myList.get(0); + } + + public boolean exactlyOneMatch() { + return myList.size()== 1; + } + + public Stream stream() { + return myList.stream(); + } + + public List getCandidates() { + return Collections.unmodifiableList(myList); + } + + public MatchedPersonCandidate getFirstMatch() { + return myList.get(0); + } +} diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/candidate/CandidateStrategyEnum.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/candidate/CandidateStrategyEnum.java new file mode 100644 index 00000000000..631b5984519 --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/candidate/CandidateStrategyEnum.java @@ -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 +} diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiCandidateSearchCriteriaBuilderSvc.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/candidate/EmpiCandidateSearchCriteriaBuilderSvc.java similarity index 96% rename from hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiCandidateSearchCriteriaBuilderSvc.java rename to hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/candidate/EmpiCandidateSearchCriteriaBuilderSvc.java index e2ba0714119..00c93ce4a00 100644 --- a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiCandidateSearchCriteriaBuilderSvc.java +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/candidate/EmpiCandidateSearchCriteriaBuilderSvc.java @@ -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; diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiCandidateSearchSvc.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/candidate/EmpiCandidateSearchSvc.java similarity index 98% rename from hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiCandidateSearchSvc.java rename to hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/candidate/EmpiCandidateSearchSvc.java index 88d420df19f..e78ba51cb27 100644 --- a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/EmpiCandidateSearchSvc.java +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/candidate/EmpiCandidateSearchSvc.java @@ -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; diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/candidate/EmpiPersonFindingSvc.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/candidate/EmpiPersonFindingSvc.java new file mode 100644 index 00000000000..db1492b6aca --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/candidate/EmpiPersonFindingSvc.java @@ -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: + *

+ * 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); + } +} diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/candidate/FindCandidateByEidSvc.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/candidate/FindCandidateByEidSvc.java new file mode 100644 index 00000000000..338c8304f7b --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/candidate/FindCandidateByEidSvc.java @@ -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 findMatchPersonCandidates(IAnyResource theBaseResource) { + List retval = new ArrayList<>(); + + List eidFromResource = myEIDHelper.getExternalEid(theBaseResource); + if (!eidFromResource.isEmpty()) { + for (CanonicalEID eid : eidFromResource) { + Optional 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; + } +} diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/candidate/FindCandidateByLinkSvc.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/candidate/FindCandidateByLinkSvc.java new file mode 100644 index 00000000000..e73337842bc --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/candidate/FindCandidateByLinkSvc.java @@ -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 findMatchPersonCandidates(IAnyResource theTarget) { + List retval = new ArrayList<>(); + + Long targetPid = myIdHelperService.getPidOrNull(theTarget); + if (targetPid != null) { + Optional 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; + } +} diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/candidate/FindCandidateByScoreSvc.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/candidate/FindCandidateByScoreSvc.java new file mode 100644 index 00000000000..72c1adc15fe --- /dev/null +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/candidate/FindCandidateByScoreSvc.java @@ -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 findMatchPersonCandidates(IAnyResource theTarget) { + List retval = new ArrayList<>(); + + List personPidsToExclude = getNoMatchPersonPids(theTarget); + List 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 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 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; + } +} diff --git a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/MatchedPersonCandidate.java b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/candidate/MatchedPersonCandidate.java similarity index 62% rename from hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/MatchedPersonCandidate.java rename to hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/candidate/MatchedPersonCandidate.java index 537775fe2bd..dadd064633c 100644 --- a/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/MatchedPersonCandidate.java +++ b/hapi-fhir-jpaserver-empi/src/main/java/ca/uhn/fhir/jpa/empi/svc/candidate/MatchedPersonCandidate.java @@ -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(); } } diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/BaseEmpiR4Test.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/BaseEmpiR4Test.java index 876da1511f5..fc8b1db7685 100644 --- a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/BaseEmpiR4Test.java +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/BaseEmpiR4Test.java @@ -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 links = myEmpiLinkDao.findAll(); + for (EmpiLink link : links) { + ourLog.info(link.toString()); + } + } } diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/dao/EmpiLinkDaoSvcTest.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/dao/EmpiLinkDaoSvcTest.java index 2aa081486af..a2733bc18a3 100644 --- a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/dao/EmpiLinkDaoSvcTest.java +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/dao/EmpiLinkDaoSvcTest.java @@ -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()); + } + } diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/entity/EmpiEnumTest.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/entity/EmpiEnumTest.java index 935375b7e74..7137e481e78 100644 --- a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/entity/EmpiEnumTest.java +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/entity/EmpiEnumTest.java @@ -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]); diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/interceptor/EmpiExpungeTest.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/interceptor/EmpiExpungeTest.java index 9a67b0bcb50..415f80bfbe1 100644 --- a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/interceptor/EmpiExpungeTest.java +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/interceptor/EmpiExpungeTest.java @@ -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()); diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/BasePersonMatcher.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/BasePersonMatcher.java index 5fa8a42be96..e6ee7f961c8 100644 --- a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/BasePersonMatcher.java +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/BasePersonMatcher.java @@ -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; diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsLinkedTo.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsLinkedTo.java index d85b6fa5b49..4a18b40d2c1 100644 --- a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsLinkedTo.java +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsLinkedTo.java @@ -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; diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsMatchedToAPerson.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsMatchedToAPerson.java index c8fffcb6e67..4d4fcd9d1e5 100644 --- a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsMatchedToAPerson.java +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsMatchedToAPerson.java @@ -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; diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsPossibleDuplicateOf.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsPossibleDuplicateOf.java index cab1452e70b..4e42a78b57b 100644 --- a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsPossibleDuplicateOf.java +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsPossibleDuplicateOf.java @@ -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; diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsPossibleLinkedTo.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsPossibleLinkedTo.java index d2248ef4e3d..9e435d7d8a8 100644 --- a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsPossibleLinkedTo.java +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsPossibleLinkedTo.java @@ -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; diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsPossibleMatchWith.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsPossibleMatchWith.java index bd31637ed72..58796bd4239 100644 --- a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsPossibleMatchWith.java +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsPossibleMatchWith.java @@ -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; diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsSamePersonAs.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsSamePersonAs.java index 15db470f3ae..96d4f8b3ccd 100644 --- a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsSamePersonAs.java +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/matcher/IsSamePersonAs.java @@ -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; diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/EmpiProviderQueryLinkR4Test.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/EmpiProviderQueryLinkR4Test.java index 99a9181488a..276119ba1ab 100644 --- a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/EmpiProviderQueryLinkR4Test.java +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/provider/EmpiProviderQueryLinkR4Test.java @@ -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 list = result.getParameter(); assertThat(list, hasSize(1)); List 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 list = result.getParameter(); assertThat(list, hasSize(3)); List 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 list = result.getParameter(); assertThat(list, hasSize(1)); List 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 thePart, String thePersonId, String theTargetId, EmpiMatchResultEnum theMatchResult) { + private void assertEmpiLink(int theExpectedSize, List 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)); } } diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/searchparam/SearchParameterTest.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/searchparam/SearchParameterTest.java index 13a708594e5..5343272192a 100644 --- a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/searchparam/SearchParameterTest.java +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/searchparam/SearchParameterTest.java @@ -50,7 +50,7 @@ public class SearchParameterTest extends BaseEmpiR4Test { ourLog.info("Search result: {}", encoded); List 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()); } } diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiCandidateSearchCriteriaBuilderSvcTest.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiCandidateSearchCriteriaBuilderSvcTest.java index e9311c9bc33..8ca038b4127 100644 --- a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiCandidateSearchCriteriaBuilderSvcTest.java +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiCandidateSearchCriteriaBuilderSvcTest.java @@ -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; @@ -19,7 +20,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; public class EmpiCandidateSearchCriteriaBuilderSvcTest extends BaseEmpiR4Test { @Autowired - EmpiCandidateSearchCriteriaBuilderSvc myEmpiCandidateSearchCriteriaBuilderSvc; + EmpiCandidateSearchCriteriaBuilderSvc myEmpiCandidateSearchCriteriaBuilderSvc; @Test public void testEmptyCase() { diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiCandidateSearchSvcTest.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiCandidateSearchSvcTest.java index c8b908478d2..17c56fb94e7 100644 --- a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiCandidateSearchSvcTest.java +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiCandidateSearchSvcTest.java @@ -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; @@ -19,7 +20,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; public class EmpiCandidateSearchSvcTest extends BaseEmpiR4Test { @Autowired - EmpiCandidateSearchSvc myEmpiCandidateSearchSvc; + EmpiCandidateSearchSvc myEmpiCandidateSearchSvc; @Test public void testFindCandidates() { diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiLinkSvcTest.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiLinkSvcTest.java index 4bb1fd922a0..d249fa5849e 100644 --- a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiLinkSvcTest.java +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiLinkSvcTest.java @@ -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()); diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiMatchLinkSvcMultipleEidModeTest.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiMatchLinkSvcMultipleEidModeTest.java index efdaeb92edb..acb2c11a37d 100644 --- a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiMatchLinkSvcMultipleEidModeTest.java +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiMatchLinkSvcMultipleEidModeTest.java @@ -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 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 void assertFields(Function theAccessor, T... theExpectedValues) { + List 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 possibleDuplicates = myEmpiLinkDaoSvc.getPossibleDuplicates(); assertThat(possibleDuplicates, hasSize(1)); - List 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))); diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiMatchLinkSvcTest.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiMatchLinkSvcTest.java index 636ab804be2..e0474cec102 100644 --- a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiMatchLinkSvcTest.java +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiMatchLinkSvcTest.java @@ -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 diff --git a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiPersonMergerSvcTest.java b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiPersonMergerSvcTest.java index 7bba1f537cd..3ef04c84fa5 100644 --- a/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiPersonMergerSvcTest.java +++ b/hapi-fhir-jpaserver-empi/src/test/java/ca/uhn/fhir/jpa/empi/svc/EmpiPersonMergerSvcTest.java @@ -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) { diff --git a/hapi-fhir-jpaserver-empi/src/test/resources/empi/empi-rules.json b/hapi-fhir-jpaserver-empi/src/test/resources/empi/empi-rules.json index 7c1d675ad0c..24d8fcb0b5e 100644 --- a/hapi-fhir-jpaserver-empi/src/test/resources/empi/empi-rules.json +++ b/hapi-fhir-jpaserver-empi/src/test/resources/empi/empi-rules.json @@ -1,4 +1,5 @@ { + "version": "1", "candidateSearchParams": [ { "resourceType": "Patient", diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java index 2d44c2b84ce..d83fd015493 100644 --- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java +++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java @@ -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 { 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); diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/EmpiMatchEvaluation.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/EmpiMatchEvaluation.java new file mode 100644 index 00000000000..5723aa95f94 --- /dev/null +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/EmpiMatchEvaluation.java @@ -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)); + } +} diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/EmpiMatchOutcome.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/EmpiMatchOutcome.java new file mode 100644 index 00000000000..0aa5091a308 --- /dev/null +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/EmpiMatchOutcome.java @@ -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; + } +} diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/EmpiMatchResultEnum.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/EmpiMatchResultEnum.java index b58987118cd..381745e40aa 100644 --- a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/EmpiMatchResultEnum.java +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/EmpiMatchResultEnum.java @@ -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! } diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/IEmpiLinkSvc.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/IEmpiLinkSvc.java index 0289145c852..15c1c621f22 100644 --- a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/IEmpiLinkSvc.java +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/IEmpiLinkSvc.java @@ -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 diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/IEmpiRuleValidator.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/IEmpiRuleValidator.java new file mode 100644 index 00000000000..6619cb20b06 --- /dev/null +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/IEmpiRuleValidator.java @@ -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); +} diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/IEmpiSettings.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/IEmpiSettings.java index eb891da6980..397ff793950 100644 --- a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/IEmpiSettings.java +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/IEmpiSettings.java @@ -35,4 +35,6 @@ public interface IEmpiSettings { boolean isPreventEidUpdates(); boolean isPreventMultipleEids(); + + String getRuleVersion(); } diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/MatchedTarget.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/MatchedTarget.java index 1b36e61506b..c709280f6f7 100644 --- a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/MatchedTarget.java +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/api/MatchedTarget.java @@ -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(); } } diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/config/EmpiRuleValidator.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/config/EmpiRuleValidator.java index 1829bd50cbe..cc7d60be2ce 100644 --- a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/config/EmpiRuleValidator.java +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/config/EmpiRuleValidator.java @@ -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; diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/config/EmpiSettings.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/config/EmpiSettings.java index c7f778ca12f..db310c8933d 100644 --- a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/config/EmpiSettings.java +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/config/EmpiSettings.java @@ -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; diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/json/EmpiRulesJson.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/json/EmpiRulesJson.java index 6d3c04e6525..d3d459eb7d2 100644 --- a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/json/EmpiRulesJson.java +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/json/EmpiRulesJson.java @@ -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 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 { + public String getVersion() { + return myVersion; + } - /** - * This empty constructor is required by Jackson - */ - public EmpiRulesJsonConverter() { - } + public EmpiRulesJson setVersion(String theVersion) { + myVersion = theVersion; + return this; + } - @Override - public EmpiRulesJson convert(EmpiRulesJson theEmpiRulesJson) { - theEmpiRulesJson.initialize(); - return theEmpiRulesJson; - } + 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 { + + /** + * This empty constructor is required by Jackson + */ + public EmpiRulesJsonConverter() { + } + + @Override + public EmpiRulesJson convert(EmpiRulesJson theEmpiRulesJson) { + theEmpiRulesJson.validate(); + theEmpiRulesJson.initialize(); + return theEmpiRulesJson; + } + } } diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/metric/EmpiMetricEnum.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/metric/EmpiMetricEnum.java index 92d9780c323..e9e9b7d1696 100644 --- a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/metric/EmpiMetricEnum.java +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/metric/EmpiMetricEnum.java @@ -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,15 +78,25 @@ 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); } } - public boolean isSimilarity() { + private EmpiMatchEvaluation matchBySimilarity(IEmpiFieldSimilarity theSimilarity, FhirContext theFhirContext, IBase theLeftBase, IBase theRightBase, boolean theExact, Double theThreshold) { + double similarityResult = theSimilarity.similarity(theFhirContext, theLeftBase, theRightBase, theExact); + return new EmpiMatchEvaluation(similarityResult >= theThreshold, similarityResult); + } + + private EmpiMatchEvaluation matchByMatcher(IEmpiFieldMatcher theMatcher, FhirContext theFhirContext, IBase theLeftBase, IBase theRightBase, boolean theExact) { + boolean matcherResult = theMatcher.matches(theFhirContext, theLeftBase, theRightBase, theExact); + return new EmpiMatchEvaluation(matcherResult, matcherResult ? 1.0 : 0.0); + } + + public boolean isSimilarity() { return myEmpiFieldMetric instanceof IEmpiFieldSimilarity; } } diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/svc/EmpiResourceFieldMatcher.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/svc/EmpiResourceFieldMatcher.java index f3eb57c57b5..9eff49f933c 100644 --- a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/svc/EmpiResourceFieldMatcher.java +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/svc/EmpiResourceFieldMatcher.java @@ -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 theLeftValues, List theRightValues) { - boolean retval = false; + private EmpiMatchEvaluation match(List theLeftValues, List 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()); } diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/svc/EmpiResourceMatcherSvc.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/svc/EmpiResourceMatcherSvc.java index 46989304448..90afa38bffa 100644 --- a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/svc/EmpiResourceMatcherSvc.java +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/svc/EmpiResourceMatcherSvc.java @@ -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 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); + EmpiMatchEvaluation matchEvaluation = fieldComparator.match(theLeftResource, theRightResource); + if (matchEvaluation.match) { + vector |= (1 << i); } + score += matchEvaluation.score; } - return retval; + return new EmpiMatchOutcome(vector, score); } } diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/util/AssuranceLevelUtil.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/util/AssuranceLevelUtil.java index 746d1be31c1..324eedc6773 100644 --- a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/util/AssuranceLevelUtil.java +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/util/AssuranceLevelUtil.java @@ -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: diff --git a/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/BaseR4Test.java b/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/BaseR4Test.java index 7565982ba21..092225abfe2 100644 --- a/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/BaseR4Test.java +++ b/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/BaseR4Test.java @@ -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()); + } } diff --git a/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/rules/json/EmpiRulesJsonR4Test.java b/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/rules/json/EmpiRulesJsonR4Test.java index f3e910c6cbd..9f9b10dd870 100644 --- a/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/rules/json/EmpiRulesJsonR4Test.java +++ b/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/rules/json/EmpiRulesJsonR4Test.java @@ -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); diff --git a/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/rules/svc/BaseEmpiRulesR4Test.java b/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/rules/svc/BaseEmpiRulesR4Test.java index a924b8a1918..d917c1f00fc 100644 --- a/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/rules/svc/BaseEmpiRulesR4Test.java +++ b/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/rules/svc/BaseEmpiRulesR4Test.java @@ -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); diff --git a/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/rules/svc/CustomResourceMatcherR4Test.java b/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/rules/svc/CustomResourceMatcherR4Test.java index 93cd4d222b4..15f7325814b 100644 --- a/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/rules/svc/CustomResourceMatcherR4Test.java +++ b/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/rules/svc/CustomResourceMatcherR4Test.java @@ -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) { diff --git a/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/rules/svc/EmpiResourceFieldMatcherR4Test.java b/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/rules/svc/EmpiResourceFieldMatcherR4Test.java index 40e7aaaf91d..c969b272abe 100644 --- a/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/rules/svc/EmpiResourceFieldMatcherR4Test.java +++ b/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/rules/svc/EmpiResourceFieldMatcherR4Test.java @@ -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); } } diff --git a/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/rules/svc/EmpiResourceMatcherSvcR4Test.java b/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/rules/svc/EmpiResourceMatcherSvcR4Test.java index 89653602ec2..9e0c814c51b 100644 --- a/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/rules/svc/EmpiResourceMatcherSvcR4Test.java +++ b/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/rules/svc/EmpiResourceMatcherSvcR4Test.java @@ -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)); } } diff --git a/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/util/AssuranceLevelUtilTest.java b/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/util/AssuranceLevelUtilTest.java index 2ac5b507212..4f75eda8c28 100644 --- a/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/util/AssuranceLevelUtilTest.java +++ b/hapi-fhir-server-empi/src/test/java/ca/uhn/fhir/empi/util/AssuranceLevelUtilTest.java @@ -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))); } diff --git a/hapi-fhir-server-empi/src/test/resources/bad-rules-bad-filter.json b/hapi-fhir-server-empi/src/test/resources/bad-rules-bad-filter.json index 56cf810d2d7..671c8c1164b 100644 --- a/hapi-fhir-server-empi/src/test/resources/bad-rules-bad-filter.json +++ b/hapi-fhir-server-empi/src/test/resources/bad-rules-bad-filter.json @@ -1,4 +1,5 @@ { + "version": "1", "candidateSearchParams" : [], "candidateFilterSearchParams" : [{ "resourceType" : "Patient", diff --git a/hapi-fhir-server-empi/src/test/resources/bad-rules-bad-path.json b/hapi-fhir-server-empi/src/test/resources/bad-rules-bad-path.json index c26b88df3fc..9082015ed30 100644 --- a/hapi-fhir-server-empi/src/test/resources/bad-rules-bad-path.json +++ b/hapi-fhir-server-empi/src/test/resources/bad-rules-bad-path.json @@ -1,4 +1,5 @@ { + "version": "1", "candidateSearchParams" : [], "candidateFilterSearchParams" : [], "matchFields" : [ { diff --git a/hapi-fhir-server-empi/src/test/resources/bad-rules-bad-searchparam.json b/hapi-fhir-server-empi/src/test/resources/bad-rules-bad-searchparam.json index 888c704517b..8585752dcbf 100644 --- a/hapi-fhir-server-empi/src/test/resources/bad-rules-bad-searchparam.json +++ b/hapi-fhir-server-empi/src/test/resources/bad-rules-bad-searchparam.json @@ -1,4 +1,5 @@ { + "version": "1", "candidateSearchParams" : [{ "resourceType" : "Patient", "searchParams" : ["foo"] diff --git a/hapi-fhir-server-empi/src/test/resources/bad-rules-bad-url.json b/hapi-fhir-server-empi/src/test/resources/bad-rules-bad-url.json index ba386469223..d626556062d 100644 --- a/hapi-fhir-server-empi/src/test/resources/bad-rules-bad-url.json +++ b/hapi-fhir-server-empi/src/test/resources/bad-rules-bad-url.json @@ -1,4 +1,5 @@ { + "version": "1", "candidateSearchParams" : [], "candidateFilterSearchParams" : [], "matchFields" : [], diff --git a/hapi-fhir-server-empi/src/test/resources/bad-rules-duplicate-name.json b/hapi-fhir-server-empi/src/test/resources/bad-rules-duplicate-name.json index f5a6b78d362..6e85afe902f 100644 --- a/hapi-fhir-server-empi/src/test/resources/bad-rules-duplicate-name.json +++ b/hapi-fhir-server-empi/src/test/resources/bad-rules-duplicate-name.json @@ -1,4 +1,5 @@ { + "version": "1", "candidateSearchParams": [], "candidateFilterSearchParams": [], "matchFields": [ diff --git a/hapi-fhir-server-empi/src/test/resources/bad-rules-missing-name.json b/hapi-fhir-server-empi/src/test/resources/bad-rules-missing-name.json index 6859e9f4339..a4e4b913e38 100644 --- a/hapi-fhir-server-empi/src/test/resources/bad-rules-missing-name.json +++ b/hapi-fhir-server-empi/src/test/resources/bad-rules-missing-name.json @@ -1,4 +1,5 @@ { + "version": "1", "candidateSearchParams" : [], "candidateFilterSearchParams" : [], "matchFields" : [ { diff --git a/hapi-fhir-server-empi/src/test/resources/bad-rules-missing-threshold.json b/hapi-fhir-server-empi/src/test/resources/bad-rules-missing-threshold.json index f0f5c1762e3..727beb44abd 100644 --- a/hapi-fhir-server-empi/src/test/resources/bad-rules-missing-threshold.json +++ b/hapi-fhir-server-empi/src/test/resources/bad-rules-missing-threshold.json @@ -1,4 +1,5 @@ { + "version": "1", "candidateSearchParams" : [], "candidateFilterSearchParams" : [], "matchFields" : [ { diff --git a/hapi-fhir-server-empi/src/test/resources/bad-rules-unused-threshold.json b/hapi-fhir-server-empi/src/test/resources/bad-rules-unused-threshold.json index 719de741126..bfecf3bf6ad 100644 --- a/hapi-fhir-server-empi/src/test/resources/bad-rules-unused-threshold.json +++ b/hapi-fhir-server-empi/src/test/resources/bad-rules-unused-threshold.json @@ -1,4 +1,5 @@ { + "version": "1", "candidateSearchParams" : [], "candidateFilterSearchParams" : [], "matchFields" : [ {