refactoring nickname matching (#4918)
* refactoring 1 * refactoring step 2 * refactoring matches * cleanup * fixing some tests * fixing more tests * remove dbugcode * adding the nickname factory * code review fixes * review fixes * put default to prevent breaking * refactor review fixes * more review points * more review points * review points * review points * more review changes * review points * review points * adding bean --------- Co-authored-by: leif stawnyczy <leifstawnyczy@leifs-MacBook-Pro.local> Co-authored-by: leif stawnyczy <leifstawnyczy@leifs-mbp.home>
This commit is contained in:
parent
9838f8a19a
commit
7a18f17a01
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
type: fix
|
||||
issue: 4917
|
||||
title: "Matching algorithms have been refactored to allow
|
||||
greater flexibility in setting and defining nicknames
|
||||
as well as allowing bean injection into matcher classes.
|
||||
"
|
|
@ -17,7 +17,7 @@
|
|||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
package ca.uhn.fhir.jpa.searchparam.nickname;
|
||||
package ca.uhn.fhir.jpa.nickname;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.BufferedReader;
|
||||
|
@ -33,19 +33,30 @@ class NicknameMap {
|
|||
private final Map<String, List<String>> myFormalToNick = new HashMap<>();
|
||||
private final Map<String, List<String>> myNicknameToFormal = new HashMap<>();
|
||||
|
||||
private final List<String> myBadRows = new ArrayList<>();
|
||||
|
||||
void load(Reader theReader) throws IOException {
|
||||
try (BufferedReader reader = new BufferedReader(theReader)) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
String[] parts = line.split(",");
|
||||
String key = parts[0];
|
||||
List<String> values = new ArrayList<>(Arrays.asList(parts).subList(1, parts.length));
|
||||
add(key, values);
|
||||
if (parts.length > 1) {
|
||||
String key = parts[0];
|
||||
List<String> values = new ArrayList<>(Arrays.asList(parts).subList(1, parts.length));
|
||||
add(key, values);
|
||||
} else {
|
||||
myBadRows.add(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void add(String theKey, List<String> theValues) {
|
||||
void clear() {
|
||||
myFormalToNick.clear();
|
||||
myNicknameToFormal.clear();
|
||||
}
|
||||
|
||||
void add(String theKey, List<String> theValues) {
|
||||
myFormalToNick.put(theKey, theValues);
|
||||
for (String value : theValues) {
|
||||
myNicknameToFormal.putIfAbsent(value, new ArrayList<>());
|
||||
|
@ -57,14 +68,22 @@ class NicknameMap {
|
|||
return myFormalToNick.size();
|
||||
}
|
||||
|
||||
boolean isEmpty() {
|
||||
return size() == 0;
|
||||
}
|
||||
|
||||
List<String> getBadRows() {
|
||||
return myBadRows;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
List<String> getNicknamesFromFormalName(String theName) {
|
||||
public List<String> getNicknamesFromFormalName(String theName) {
|
||||
List<String> result = myFormalToNick.get(theName);
|
||||
return result == null ? new ArrayList<>() : result;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
List<String> getFormalNamesFromNickname(String theNickname) {
|
||||
public List<String> getFormalNamesFromNickname(String theNickname) {
|
||||
List<String> result = myNicknameToFormal.get(theNickname);
|
||||
return result == null ? new ArrayList<>() : result;
|
||||
}
|
|
@ -17,8 +17,12 @@
|
|||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
package ca.uhn.fhir.jpa.searchparam.nickname;
|
||||
package ca.uhn.fhir.jpa.nickname;
|
||||
|
||||
import ca.uhn.fhir.context.ConfigurationException;
|
||||
import ca.uhn.fhir.i18n.Msg;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
|
@ -32,22 +36,40 @@ import java.util.HashSet;
|
|||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Nickname service is used to load nicknames
|
||||
* via a file that contains rows of comma separated names that are
|
||||
* "similar" or nicknames of each other.
|
||||
* -
|
||||
* If no nickname resource is provided, nicknames/names.csv will be used instead.
|
||||
* -
|
||||
* If one is to be provided, it must be provided before nickname svc is invoked
|
||||
*/
|
||||
public class NicknameSvc {
|
||||
private final NicknameMap myNicknameMap = new NicknameMap();
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(NicknameSvc.class);
|
||||
|
||||
public NicknameSvc() throws IOException {
|
||||
Resource nicknameCsvResource = new ClassPathResource("/nickname/names.csv");
|
||||
try (InputStream inputStream = nicknameCsvResource.getInputStream()) {
|
||||
try (Reader reader = new InputStreamReader(inputStream)) {
|
||||
myNicknameMap.load(reader);
|
||||
}
|
||||
}
|
||||
private NicknameMap myNicknameMap;
|
||||
|
||||
private Resource myNicknameResource;
|
||||
|
||||
public NicknameSvc() {
|
||||
|
||||
}
|
||||
|
||||
public void setNicknameResource(Resource theNicknameResource) {
|
||||
myNicknameResource = theNicknameResource;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
ensureMapInitialized();
|
||||
return myNicknameMap.size();
|
||||
}
|
||||
|
||||
public List<String> getBadRows() {
|
||||
ensureMapInitialized();
|
||||
return myNicknameMap.getBadRows();
|
||||
}
|
||||
|
||||
public Collection<String> getEquivalentNames(String theName) {
|
||||
Set<String> retval = new HashSet<>(getNicknamesFromFormalName(theName));
|
||||
|
||||
|
@ -64,11 +86,34 @@ public class NicknameSvc {
|
|||
|
||||
@Nonnull
|
||||
List<String> getNicknamesFromFormalName(String theName) {
|
||||
ensureMapInitialized();
|
||||
return myNicknameMap.getNicknamesFromFormalName(theName);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
List<String> getFormalNamesFromNickname(String theNickname) {
|
||||
ensureMapInitialized();
|
||||
return myNicknameMap.getFormalNamesFromNickname(theNickname);
|
||||
}
|
||||
|
||||
private void ensureMapInitialized() {
|
||||
if (myNicknameResource == null) {
|
||||
myNicknameResource = new ClassPathResource("/nickname/names.csv");
|
||||
}
|
||||
|
||||
if (myNicknameMap == null) {
|
||||
myNicknameMap = new NicknameMap();
|
||||
}
|
||||
if (myNicknameMap.isEmpty()) {
|
||||
try {
|
||||
try (InputStream inputStream = myNicknameResource.getInputStream()) {
|
||||
try (Reader reader = new InputStreamReader(inputStream)) {
|
||||
myNicknameMap.load(reader);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new ConfigurationException(Msg.code(2234) + "Unable to load nicknames", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package ca.uhn.fhir.jpa.searchparam.nickname;
|
||||
package ca.uhn.fhir.jpa.nickname;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package ca.uhn.fhir.jpa.searchparam.nickname;
|
||||
package ca.uhn.fhir.jpa.nickname;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
|
@ -95,8 +95,8 @@ import ca.uhn.fhir.jpa.partition.PartitionLookupSvcImpl;
|
|||
import ca.uhn.fhir.jpa.partition.PartitionManagementProvider;
|
||||
import ca.uhn.fhir.jpa.partition.RequestPartitionHelperSvc;
|
||||
import ca.uhn.fhir.jpa.provider.DiffProvider;
|
||||
import ca.uhn.fhir.jpa.provider.ProcessMessageProvider;
|
||||
import ca.uhn.fhir.jpa.provider.InstanceReindexProvider;
|
||||
import ca.uhn.fhir.jpa.provider.ProcessMessageProvider;
|
||||
import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider;
|
||||
import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider;
|
||||
import ca.uhn.fhir.jpa.provider.ValueSetOperationProvider;
|
||||
|
@ -149,7 +149,6 @@ import ca.uhn.fhir.jpa.search.warm.ICacheWarmingSvc;
|
|||
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
|
||||
import ca.uhn.fhir.jpa.searchparam.config.SearchParamConfig;
|
||||
import ca.uhn.fhir.jpa.searchparam.extractor.IResourceLinkResolver;
|
||||
import ca.uhn.fhir.jpa.searchparam.nickname.NicknameInterceptor;
|
||||
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamProvider;
|
||||
import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
|
||||
import ca.uhn.fhir.jpa.sp.SearchParamPresenceSvcImpl;
|
||||
|
@ -198,7 +197,6 @@ import org.springframework.scheduling.concurrent.ScheduledExecutorFactoryBean;
|
|||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
|
||||
@Configuration
|
||||
|
@ -772,12 +770,6 @@ public class JpaConfig {
|
|||
return new UnknownCodeSystemWarningValidationSupport(theFhirContext);
|
||||
}
|
||||
|
||||
@Lazy
|
||||
@Bean
|
||||
public NicknameInterceptor nicknameInterceptor() throws IOException {
|
||||
return new NicknameInterceptor();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ISynchronousSearchSvc synchronousSearchSvc() {
|
||||
return new SynchronousSearchSvcImpl();
|
||||
|
|
|
@ -20,13 +20,11 @@
|
|||
package ca.uhn.fhir.jpa.dao.expunge;
|
||||
|
||||
import ca.uhn.fhir.mdm.api.IMdmSettings;
|
||||
import ca.uhn.fhir.mdm.rules.config.MdmSettings;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
|
|
|
@ -20,14 +20,21 @@
|
|||
package ca.uhn.fhir.jpa.mdm.config;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.nickname.NicknameSvc;
|
||||
import ca.uhn.fhir.jpa.searchparam.config.NicknameServiceConfig;
|
||||
import ca.uhn.fhir.mdm.api.IMdmSettings;
|
||||
import ca.uhn.fhir.mdm.interceptor.MdmSearchExpandingInterceptor;
|
||||
import ca.uhn.fhir.mdm.rules.config.MdmRuleValidator;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.IMatcherFactory;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.MdmMatcherFactory;
|
||||
import ca.uhn.fhir.mdm.svc.MdmLinkDeleteSvc;
|
||||
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
|
||||
@Import(NicknameServiceConfig.class)
|
||||
@Configuration
|
||||
public class MdmCommonConfig {
|
||||
@Bean
|
||||
|
@ -44,4 +51,17 @@ public class MdmCommonConfig {
|
|||
MdmLinkDeleteSvc mdmLinkDeleteSvc() {
|
||||
return new MdmLinkDeleteSvc();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public IMatcherFactory matcherFactory(
|
||||
FhirContext theFhirContext,
|
||||
IMdmSettings theSettings,
|
||||
NicknameSvc theNicknameSvc
|
||||
) {
|
||||
return new MdmMatcherFactory(
|
||||
theFhirContext,
|
||||
theSettings,
|
||||
theNicknameSvc
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,6 +48,7 @@ import ca.uhn.fhir.jpa.mdm.svc.candidate.FindCandidateByLinkSvc;
|
|||
import ca.uhn.fhir.jpa.mdm.svc.candidate.MdmCandidateSearchCriteriaBuilderSvc;
|
||||
import ca.uhn.fhir.jpa.mdm.svc.candidate.MdmCandidateSearchSvc;
|
||||
import ca.uhn.fhir.jpa.mdm.svc.candidate.MdmGoldenResourceFindingSvc;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.IMatcherFactory;
|
||||
import ca.uhn.fhir.mdm.util.MdmPartitionHelper;
|
||||
import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc;
|
||||
import ca.uhn.fhir.jpa.subscription.channel.api.IChannelFactory;
|
||||
|
@ -205,8 +206,12 @@ public class MdmConsumerConfig {
|
|||
}
|
||||
|
||||
@Bean
|
||||
MdmResourceMatcherSvc mdmResourceComparatorSvc(FhirContext theFhirContext, IMdmSettings theMdmSettings) {
|
||||
return new MdmResourceMatcherSvc(theFhirContext, theMdmSettings);
|
||||
MdmResourceMatcherSvc mdmResourceComparatorSvc(
|
||||
FhirContext theFhirContext,
|
||||
IMatcherFactory theIMatcherFactory,
|
||||
IMdmSettings theMdmSettings
|
||||
) {
|
||||
return new MdmResourceMatcherSvc(theFhirContext, theIMatcherFactory, theMdmSettings);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
|
|
@ -76,7 +76,12 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
|||
import static org.slf4j.LoggerFactory.getLogger;
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration(classes = {MdmSubmitterConfig.class, MdmConsumerConfig.class, TestMdmConfigR4.class, SubscriptionProcessorConfig.class})
|
||||
@ContextConfiguration(classes = {
|
||||
MdmSubmitterConfig.class,
|
||||
MdmConsumerConfig.class,
|
||||
TestMdmConfigR4.class,
|
||||
SubscriptionProcessorConfig.class
|
||||
})
|
||||
abstract public class BaseMdmR4Test extends BaseJpaR4Test {
|
||||
|
||||
protected static final String PARTITION_1 = "PART-1";
|
||||
|
|
|
@ -7,6 +7,7 @@ import ca.uhn.fhir.mdm.api.IMdmSubmitSvc;
|
|||
import ca.uhn.fhir.mdm.provider.MdmControllerHelper;
|
||||
import ca.uhn.fhir.mdm.provider.MdmProviderDstu3Plus;
|
||||
import ca.uhn.fhir.mdm.rules.config.MdmSettings;
|
||||
import ca.uhn.fhir.mdm.rules.svc.MdmResourceMatcherSvc;
|
||||
import ca.uhn.fhir.mdm.util.MessageHelper;
|
||||
import com.google.common.base.Charsets;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
@ -34,6 +35,8 @@ public abstract class BaseProviderR4Test extends BaseMdmR4Test {
|
|||
@Autowired
|
||||
protected MdmSettings myMdmSettings;
|
||||
@Autowired
|
||||
protected MdmResourceMatcherSvc myMdmResourceMatcherSvc;
|
||||
@Autowired
|
||||
private MdmControllerHelper myMdmHelper;
|
||||
@Autowired
|
||||
Batch2JobHelper myBatch2JobHelper;
|
||||
|
@ -48,7 +51,7 @@ public abstract class BaseProviderR4Test extends BaseMdmR4Test {
|
|||
String json = IOUtils.toString(resource.getInputStream(), Charsets.UTF_8);
|
||||
myMdmSettings.setEnabled(true);
|
||||
myMdmSettings.setScriptText(json);
|
||||
myMdmResourceMatcherSvc.setMdmSettings(myMdmSettings);
|
||||
myMdmResourceMatcherSvc.setMdmRulesJson(myMdmSettings.getMdmRules());
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
|
@ -62,7 +65,7 @@ public abstract class BaseProviderR4Test extends BaseMdmR4Test {
|
|||
public void after() throws IOException {
|
||||
super.after();
|
||||
myMdmSettings.setScriptText(defaultScript);
|
||||
myMdmResourceMatcherSvc.setMdmSettings(myMdmSettings);
|
||||
myMdmResourceMatcherSvc.setMdmRulesJson(myMdmSettings.getMdmRules());
|
||||
}
|
||||
|
||||
protected void clearMdmLinks() {
|
||||
|
|
|
@ -4,6 +4,7 @@ import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
|||
import ca.uhn.fhir.jpa.mdm.BaseMdmR4Test;
|
||||
import ca.uhn.fhir.jpa.mdm.svc.candidate.MdmCandidateSearchSvc;
|
||||
import ca.uhn.fhir.jpa.mdm.svc.candidate.TooManyCandidatesException;
|
||||
import ca.uhn.fhir.jpa.nickname.NicknameSvc;
|
||||
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.jpa.searchparam.nickname.NicknameInterceptor;
|
||||
|
@ -18,7 +19,6 @@ import org.junit.jupiter.api.BeforeEach;
|
|||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
|
@ -37,11 +37,15 @@ public class MdmCandidateSearchSvcIT extends BaseMdmR4Test {
|
|||
@Autowired
|
||||
MatchUrlService myMatchUrlService;
|
||||
|
||||
@Autowired
|
||||
NicknameSvc myNicknameSvc;
|
||||
|
||||
private NicknameInterceptor myNicknameInterceptor;
|
||||
|
||||
@BeforeEach
|
||||
public void before() throws IOException {
|
||||
myNicknameInterceptor = new NicknameInterceptor();
|
||||
public void before() throws Exception {
|
||||
super.before();
|
||||
myNicknameInterceptor = new NicknameInterceptor(myNicknameSvc);
|
||||
myInterceptorRegistry.registerInterceptor(myNicknameInterceptor);
|
||||
}
|
||||
|
||||
|
|
|
@ -165,8 +165,6 @@
|
|||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
|
||||
</dependencies>
|
||||
<build>
|
||||
<pluginManagement>
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
package ca.uhn.fhir.jpa.searchparam.config;
|
||||
|
||||
import ca.uhn.fhir.jpa.nickname.NicknameSvc;
|
||||
import ca.uhn.fhir.jpa.searchparam.nickname.NicknameInterceptor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
|
||||
@Configuration
|
||||
public class NicknameServiceConfig {
|
||||
|
||||
@Lazy
|
||||
@Bean
|
||||
public NicknameInterceptor nicknameInterceptor(NicknameSvc theNicknameSvc) {
|
||||
return new NicknameInterceptor(theNicknameSvc);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public NicknameSvc nicknameSvc() {
|
||||
return new NicknameSvc();
|
||||
}
|
||||
}
|
|
@ -47,9 +47,13 @@ import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
|
|||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
|
||||
@Import({
|
||||
NicknameServiceConfig.class
|
||||
})
|
||||
@Configuration
|
||||
public class SearchParamConfig {
|
||||
|
||||
|
|
|
@ -21,13 +21,13 @@ package ca.uhn.fhir.jpa.searchparam.nickname;
|
|||
|
||||
import ca.uhn.fhir.interceptor.api.Hook;
|
||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||
import ca.uhn.fhir.jpa.nickname.NicknameSvc;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||
import ca.uhn.fhir.rest.param.StringParam;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
@ -39,8 +39,8 @@ public class NicknameInterceptor {
|
|||
|
||||
private final NicknameSvc myNicknameSvc;
|
||||
|
||||
public NicknameInterceptor() throws IOException {
|
||||
myNicknameSvc = new NicknameSvc();
|
||||
public NicknameInterceptor(NicknameSvc theNicknameSvc) {
|
||||
myNicknameSvc = theNicknameSvc;
|
||||
}
|
||||
|
||||
@Hook(Pointcut.STORAGE_PRESEARCH_REGISTERED)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package ca.uhn.fhir.jpa.searchparam.nickname;
|
||||
|
||||
import ca.uhn.fhir.jpa.nickname.NicknameSvc;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.rest.param.StringParam;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -9,13 +10,18 @@ import java.io.IOException;
|
|||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class NicknameInterceptorTest {
|
||||
|
||||
private NicknameInterceptor createNicknameInterceptor() {
|
||||
return new NicknameInterceptor(new NicknameSvc());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExpandForward() throws IOException {
|
||||
// setup
|
||||
String formalName = "kenneth";
|
||||
SearchParameterMap sp = new SearchParameterMap();
|
||||
sp.add("name", new StringParam(formalName).setNicknameExpand(true));
|
||||
NicknameInterceptor svc = new NicknameInterceptor();
|
||||
NicknameInterceptor svc = createNicknameInterceptor();
|
||||
|
||||
// execute
|
||||
svc.expandNicknames(sp);
|
||||
|
@ -31,7 +37,7 @@ class NicknameInterceptorTest {
|
|||
String nickname = "ken";
|
||||
SearchParameterMap sp = new SearchParameterMap();
|
||||
sp.add("name", new StringParam(nickname).setNicknameExpand(true));
|
||||
NicknameInterceptor svc = new NicknameInterceptor();
|
||||
NicknameInterceptor svc = createNicknameInterceptor();
|
||||
|
||||
// execute
|
||||
svc.expandNicknames(sp);
|
||||
|
@ -47,7 +53,7 @@ class NicknameInterceptorTest {
|
|||
String unusualName = "X Æ A-12";
|
||||
SearchParameterMap sp = new SearchParameterMap();
|
||||
sp.add("name", new StringParam(unusualName).setNicknameExpand(true));
|
||||
NicknameInterceptor svc = new NicknameInterceptor();
|
||||
NicknameInterceptor svc = createNicknameInterceptor();
|
||||
|
||||
// execute
|
||||
svc.expandNicknames(sp);
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
*/
|
||||
package ca.uhn.fhir.mdm.api;
|
||||
|
||||
import ca.uhn.fhir.mdm.dao.IMdmLinkImplFactory;
|
||||
import ca.uhn.fhir.mdm.rules.json.MdmRulesJson;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
|
|
@ -139,7 +139,7 @@ public class MdmControllerHelper {
|
|||
public IBaseBundle getMatchesAndPossibleMatchesForResource(IAnyResource theResource, String theResourceType, RequestDetails theRequestDetails) {
|
||||
RequestPartitionId requestPartitionId;
|
||||
ReadPartitionIdRequestDetails details = ReadPartitionIdRequestDetails.forSearchType(theResourceType, null, null);
|
||||
if (myMdmSettings.getSearchAllPartitionForMatch()){
|
||||
if (myMdmSettings.getSearchAllPartitionForMatch()) {
|
||||
requestPartitionId = RequestPartitionId.allPartitions();
|
||||
} else {
|
||||
requestPartitionId = myRequestPartitionHelperSvc.determineReadPartitionForRequest(theRequestDetails, details);
|
||||
|
|
|
@ -19,21 +19,16 @@
|
|||
*/
|
||||
package ca.uhn.fhir.mdm.rules.json;
|
||||
|
||||
import ca.uhn.fhir.i18n.Msg;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.mdm.api.MdmMatchEvaluation;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.MdmMatcherEnum;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.models.MatchTypeEnum;
|
||||
import ca.uhn.fhir.model.api.IModelJson;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* Contains all business data for determining if a match exists on a particular field, given:
|
||||
* <p></p>
|
||||
* 1. A {@link MdmMatcherEnum} which determines the actual similarity values.
|
||||
* 1. A {@link MatchTypeEnum} which determines the actual similarity values.
|
||||
* 2. A given resource type (e.g. Patient)
|
||||
* 3. A given FHIRPath expression for finding the particular primitive to be used for comparison. (e.g. name.given)
|
||||
*/
|
||||
|
@ -87,10 +82,6 @@ public class MdmFieldMatchJson implements IModelJson {
|
|||
return myMatcher;
|
||||
}
|
||||
|
||||
public boolean isMatcherSupportingEmptyFields() {
|
||||
return (getMatcher() == null) ? false : getMatcher().isMatchingEmptyFields();
|
||||
}
|
||||
|
||||
public MdmFieldMatchJson setMatcher(MdmMatcherJson theMatcher) {
|
||||
myMatcher = theMatcher;
|
||||
return this;
|
||||
|
@ -105,17 +96,6 @@ public class MdmFieldMatchJson implements IModelJson {
|
|||
return this;
|
||||
}
|
||||
|
||||
public MdmMatchEvaluation match(FhirContext theFhirContext, IBase theLeftValue, IBase theRightValue) {
|
||||
if (myMatcher != null) {
|
||||
boolean result = myMatcher.match(theFhirContext, theLeftValue, theRightValue);
|
||||
return new MdmMatchEvaluation(result, result ? 1.0 : 0.0);
|
||||
}
|
||||
if (mySimilarity != null) {
|
||||
return mySimilarity.match(theFhirContext, theLeftValue, theRightValue);
|
||||
}
|
||||
throw new InternalErrorException(Msg.code(1522) + "Field Match " + myName + " has neither a matcher nor a similarity.");
|
||||
}
|
||||
|
||||
public String getFhirPath() {
|
||||
return myFhirPath;
|
||||
}
|
||||
|
|
|
@ -19,15 +19,13 @@
|
|||
*/
|
||||
package ca.uhn.fhir.mdm.rules.json;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.MdmMatcherEnum;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.models.MatchTypeEnum;
|
||||
import ca.uhn.fhir.model.api.IModelJson;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
|
||||
public class MdmMatcherJson implements IModelJson {
|
||||
@JsonProperty(value = "algorithm", required = true)
|
||||
MdmMatcherEnum myAlgorithm;
|
||||
MatchTypeEnum myAlgorithm;
|
||||
|
||||
@JsonProperty(value = "identifierSystem", required = false)
|
||||
String myIdentifierSystem;
|
||||
|
@ -38,11 +36,11 @@ public class MdmMatcherJson implements IModelJson {
|
|||
@JsonProperty(value = "exact")
|
||||
boolean myExact;
|
||||
|
||||
public MdmMatcherEnum getAlgorithm() {
|
||||
public MatchTypeEnum getAlgorithm() {
|
||||
return myAlgorithm;
|
||||
}
|
||||
|
||||
public MdmMatcherJson setAlgorithm(MdmMatcherEnum theAlgorithm) {
|
||||
public MdmMatcherJson setAlgorithm(MatchTypeEnum theAlgorithm) {
|
||||
myAlgorithm = theAlgorithm;
|
||||
return this;
|
||||
}
|
||||
|
@ -64,12 +62,4 @@ public class MdmMatcherJson implements IModelJson {
|
|||
myExact = theExact;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isMatchingEmptyFields() {
|
||||
return myAlgorithm.isMatchingEmptyFields();
|
||||
}
|
||||
|
||||
public boolean match(FhirContext theFhirContext, IBase theLeftValue, IBase theRightValue) {
|
||||
return myAlgorithm.match(theFhirContext, theLeftValue, theRightValue, myExact, myIdentifierSystem);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR - Master Data Management
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||
* %%
|
||||
* 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%
|
||||
*/
|
||||
package ca.uhn.fhir.mdm.rules.matcher;
|
||||
|
||||
import ca.uhn.fhir.i18n.Msg;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
|
||||
public class HapiDateMatcher implements IMdmFieldMatcher {
|
||||
private final HapiDateMatcherDstu3 myHapiDateMatcherDstu3 = new HapiDateMatcherDstu3();
|
||||
private final HapiDateMatcherR4 myHapiDateMatcherR4 = new HapiDateMatcherR4();
|
||||
|
||||
@Override
|
||||
public boolean matches(FhirContext theFhirContext, IBase theLeftBase, IBase theRightBase, boolean theExact, String theIdentifierSystem) {
|
||||
switch (theFhirContext.getVersion().getVersion()) {
|
||||
case DSTU3:
|
||||
return myHapiDateMatcherDstu3.match(theLeftBase, theRightBase);
|
||||
case R4:
|
||||
return myHapiDateMatcherR4.match(theLeftBase, theRightBase);
|
||||
default:
|
||||
throw new UnsupportedOperationException(Msg.code(1520) + "Version not supported: " + theFhirContext.getVersion().getVersion());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR - Master Data Management
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||
* %%
|
||||
* 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%
|
||||
*/
|
||||
package ca.uhn.fhir.mdm.rules.matcher;
|
||||
|
||||
import org.hl7.fhir.dstu3.model.BaseDateTimeType;
|
||||
import org.hl7.fhir.dstu3.model.DateTimeType;
|
||||
import org.hl7.fhir.dstu3.model.DateType;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
|
||||
public class HapiDateMatcherDstu3 {
|
||||
// TODO KHS code duplication (tried generalizing it with generics, but it got too convoluted)
|
||||
public boolean match(IBase theLeftBase, IBase theRightBase) {
|
||||
if (theLeftBase instanceof BaseDateTimeType && theRightBase instanceof BaseDateTimeType) {
|
||||
BaseDateTimeType leftDate = (BaseDateTimeType) theLeftBase;
|
||||
BaseDateTimeType rightDate = (BaseDateTimeType) theRightBase;
|
||||
int comparison = leftDate.getPrecision().compareTo(rightDate.getPrecision());
|
||||
if (comparison == 0) {
|
||||
return leftDate.getValueAsString().equals(rightDate.getValueAsString());
|
||||
}
|
||||
BaseDateTimeType leftPDate;
|
||||
BaseDateTimeType rightPDate;
|
||||
if (comparison > 0) {
|
||||
leftPDate = leftDate;
|
||||
if (rightDate instanceof DateType) {
|
||||
rightPDate = new DateType(rightDate.getValue(), leftDate.getPrecision());
|
||||
} else {
|
||||
rightPDate = new DateTimeType(rightDate.getValue(), leftDate.getPrecision());
|
||||
}
|
||||
} else {
|
||||
rightPDate = rightDate;
|
||||
if (leftDate instanceof DateType) {
|
||||
leftPDate = new DateType(leftDate.getValue(), rightDate.getPrecision());
|
||||
} else {
|
||||
leftPDate = new DateTimeType(leftDate.getValue(), rightDate.getPrecision());
|
||||
}
|
||||
}
|
||||
return leftPDate.getValueAsString().equals(rightPDate.getValueAsString());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR - Master Data Management
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||
* %%
|
||||
* 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%
|
||||
*/
|
||||
package ca.uhn.fhir.mdm.rules.matcher;
|
||||
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.r4.model.BaseDateTimeType;
|
||||
import org.hl7.fhir.r4.model.DateTimeType;
|
||||
import org.hl7.fhir.r4.model.DateType;
|
||||
|
||||
public class HapiDateMatcherR4 {
|
||||
// TODO KHS code duplication (tried generalizing it with generics, but it got too convoluted)
|
||||
public boolean match(IBase theLeftBase, IBase theRightBase) {
|
||||
if (theLeftBase instanceof BaseDateTimeType && theRightBase instanceof BaseDateTimeType) {
|
||||
BaseDateTimeType leftDate = (BaseDateTimeType) theLeftBase;
|
||||
BaseDateTimeType rightDate = (BaseDateTimeType) theRightBase;
|
||||
int comparison = leftDate.getPrecision().compareTo(rightDate.getPrecision());
|
||||
if (comparison == 0) {
|
||||
return leftDate.getValueAsString().equals(rightDate.getValueAsString());
|
||||
}
|
||||
BaseDateTimeType leftPDate;
|
||||
BaseDateTimeType rightPDate;
|
||||
//Left date is coarser
|
||||
if (comparison < 0) {
|
||||
leftPDate = leftDate;
|
||||
if (rightDate instanceof DateType) {
|
||||
rightPDate = new DateType(rightDate.getValue(), leftDate.getPrecision());
|
||||
} else {
|
||||
rightPDate = new DateTimeType(rightDate.getValue(), leftDate.getPrecision());
|
||||
}
|
||||
//Right date is coarser
|
||||
} else {
|
||||
rightPDate = rightDate;
|
||||
if (leftDate instanceof DateType) {
|
||||
leftPDate = new DateType(leftDate.getValue(), rightDate.getPrecision());
|
||||
} else {
|
||||
leftPDate = new DateTimeType(leftDate.getValue(), rightDate.getPrecision());
|
||||
}
|
||||
}
|
||||
return leftPDate.getValueAsString().equals(rightPDate.getValueAsString());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package ca.uhn.fhir.mdm.rules.matcher;
|
||||
|
||||
import ca.uhn.fhir.mdm.rules.matcher.models.IMdmFieldMatcher;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.models.MatchTypeEnum;
|
||||
|
||||
public interface IMatcherFactory {
|
||||
|
||||
/**
|
||||
* Retrieves the field matcher for the given MatchTypeEnum
|
||||
*/
|
||||
IMdmFieldMatcher getFieldMatcherForMatchType(MatchTypeEnum theMdmMatcherEnum);
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR - Master Data Management
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||
* %%
|
||||
* 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%
|
||||
*/
|
||||
package ca.uhn.fhir.mdm.rules.matcher;
|
||||
|
||||
public interface IMdmStringMatcher {
|
||||
boolean matches(String theLeftString, String theRightString);
|
||||
}
|
|
@ -1,86 +0,0 @@
|
|||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR - Master Data Management
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||
* %%
|
||||
* 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%
|
||||
*/
|
||||
package ca.uhn.fhir.mdm.rules.matcher;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.phonetic.PhoneticEncoderEnum;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
|
||||
/**
|
||||
* Enum for holding all the known FHIR Element matchers that we support in HAPI. The string matchers first
|
||||
* encode the string using an Apache Encoder before comparing them.
|
||||
* https://commons.apache.org/proper/commons-codec/userguide.html
|
||||
*/
|
||||
public enum MdmMatcherEnum {
|
||||
|
||||
CAVERPHONE1(new HapiStringMatcher(new PhoneticEncoderMatcher(PhoneticEncoderEnum.CAVERPHONE1))),
|
||||
CAVERPHONE2(new HapiStringMatcher(new PhoneticEncoderMatcher(PhoneticEncoderEnum.CAVERPHONE2))),
|
||||
COLOGNE(new HapiStringMatcher(new PhoneticEncoderMatcher(PhoneticEncoderEnum.COLOGNE))),
|
||||
DOUBLE_METAPHONE(new HapiStringMatcher(new PhoneticEncoderMatcher(PhoneticEncoderEnum.DOUBLE_METAPHONE))),
|
||||
MATCH_RATING_APPROACH(new HapiStringMatcher(new PhoneticEncoderMatcher(PhoneticEncoderEnum.MATCH_RATING_APPROACH))),
|
||||
METAPHONE(new HapiStringMatcher(new PhoneticEncoderMatcher(PhoneticEncoderEnum.METAPHONE))),
|
||||
NYSIIS(new HapiStringMatcher(new PhoneticEncoderMatcher(PhoneticEncoderEnum.NYSIIS))),
|
||||
REFINED_SOUNDEX(new HapiStringMatcher(new PhoneticEncoderMatcher(PhoneticEncoderEnum.REFINED_SOUNDEX))),
|
||||
SOUNDEX(new HapiStringMatcher(new PhoneticEncoderMatcher(PhoneticEncoderEnum.SOUNDEX))),
|
||||
NICKNAME(new HapiStringMatcher(new NicknameMatcher())),
|
||||
|
||||
STRING(new HapiStringMatcher()),
|
||||
SUBSTRING(new HapiStringMatcher(new SubstringStringMatcher())),
|
||||
|
||||
DATE(new HapiDateMatcher()),
|
||||
NAME_ANY_ORDER(new NameMatcher(MdmNameMatchModeEnum.ANY_ORDER)),
|
||||
NAME_FIRST_AND_LAST(new NameMatcher(MdmNameMatchModeEnum.FIRST_AND_LAST)),
|
||||
|
||||
IDENTIFIER(new IdentifierMatcher()),
|
||||
|
||||
EMPTY_FIELD(new EmptyFieldMatcher()),
|
||||
EXTENSION_ANY_ORDER(new ExtensionMatcher()),
|
||||
NUMERIC(new HapiStringMatcher(new NumericMatcher()));
|
||||
|
||||
private final IMdmFieldMatcher myMdmFieldMatcher;
|
||||
|
||||
MdmMatcherEnum(IMdmFieldMatcher theMdmFieldMatcher) {
|
||||
myMdmFieldMatcher = theMdmFieldMatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether two FHIR elements match according using the provided {@link IMdmFieldMatcher}
|
||||
*
|
||||
* @param theFhirContext
|
||||
* @param theLeftBase left FHIR element to compare
|
||||
* @param theRightBase right FHIR element to compare
|
||||
* @param theExact used by String matchers. If "false" then the string is normalized (case, accents) before comparing. If "true" then an exact string comparison is performed.
|
||||
* @param theIdentifierSystem used optionally by the IDENTIFIER matcher, when present, only matches the identifiers if they belong to this system.
|
||||
* @return
|
||||
*/
|
||||
public boolean match(FhirContext theFhirContext, IBase theLeftBase, IBase theRightBase, boolean theExact, String theIdentifierSystem) {
|
||||
return myMdmFieldMatcher.matches(theFhirContext, theLeftBase, theRightBase, theExact, theIdentifierSystem);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this matcher supports checks on empty fields
|
||||
*
|
||||
* @return
|
||||
* Returns true of this matcher supports empty fields and false otherwise
|
||||
*/
|
||||
public boolean isMatchingEmptyFields() {
|
||||
return this == EMPTY_FIELD;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
package ca.uhn.fhir.mdm.rules.matcher;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.phonetic.PhoneticEncoderEnum;
|
||||
import ca.uhn.fhir.jpa.nickname.NicknameSvc;
|
||||
import ca.uhn.fhir.mdm.api.IMdmSettings;
|
||||
import ca.uhn.fhir.mdm.log.Logs;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.fieldmatchers.EmptyFieldMatcher;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.fieldmatchers.ExtensionMatcher;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.fieldmatchers.HapiDateMatcher;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.fieldmatchers.HapiStringMatcher;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.fieldmatchers.IdentifierMatcher;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.fieldmatchers.MdmNameMatchModeEnum;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.fieldmatchers.NameMatcher;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.fieldmatchers.NicknameMatcher;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.fieldmatchers.NumericMatcher;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.fieldmatchers.PhoneticEncoderMatcher;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.fieldmatchers.SubstringStringMatcher;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.models.IMdmFieldMatcher;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.models.MatchTypeEnum;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
public class MdmMatcherFactory implements IMatcherFactory {
|
||||
private static final Logger ourLog = Logs.getMdmTroubleshootingLog();
|
||||
|
||||
private final FhirContext myFhirContext;
|
||||
private final IMdmSettings myMdmSettings;
|
||||
|
||||
private final NicknameSvc myNicknameSvc;
|
||||
|
||||
public MdmMatcherFactory(
|
||||
FhirContext theFhirContext,
|
||||
IMdmSettings theSettings,
|
||||
NicknameSvc theNicknameSvc
|
||||
) {
|
||||
myFhirContext = theFhirContext;
|
||||
myMdmSettings = theSettings;
|
||||
myNicknameSvc = theNicknameSvc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IMdmFieldMatcher getFieldMatcherForMatchType(MatchTypeEnum theMdmMatcherEnum) {
|
||||
String matchTypeName;
|
||||
if (theMdmMatcherEnum != null) {
|
||||
switch (theMdmMatcherEnum) {
|
||||
case CAVERPHONE1:
|
||||
return new PhoneticEncoderMatcher(PhoneticEncoderEnum.CAVERPHONE1);
|
||||
case CAVERPHONE2:
|
||||
return new PhoneticEncoderMatcher(PhoneticEncoderEnum.CAVERPHONE2);
|
||||
case COLOGNE:
|
||||
return new PhoneticEncoderMatcher(PhoneticEncoderEnum.COLOGNE);
|
||||
case DOUBLE_METAPHONE:
|
||||
return new PhoneticEncoderMatcher(PhoneticEncoderEnum.DOUBLE_METAPHONE);
|
||||
case MATCH_RATING_APPROACH:
|
||||
return new PhoneticEncoderMatcher(PhoneticEncoderEnum.MATCH_RATING_APPROACH);
|
||||
case METAPHONE:
|
||||
return new PhoneticEncoderMatcher(PhoneticEncoderEnum.METAPHONE);
|
||||
case NYSIIS:
|
||||
return new PhoneticEncoderMatcher(PhoneticEncoderEnum.NYSIIS);
|
||||
case REFINED_SOUNDEX:
|
||||
return new PhoneticEncoderMatcher(PhoneticEncoderEnum.REFINED_SOUNDEX);
|
||||
case SOUNDEX:
|
||||
return new PhoneticEncoderMatcher(PhoneticEncoderEnum.SOUNDEX);
|
||||
case NICKNAME:
|
||||
return new NicknameMatcher(myNicknameSvc);
|
||||
case STRING:
|
||||
return new HapiStringMatcher();
|
||||
case SUBSTRING:
|
||||
return new SubstringStringMatcher();
|
||||
case DATE:
|
||||
return new HapiDateMatcher(myFhirContext);
|
||||
case NAME_ANY_ORDER:
|
||||
return new NameMatcher(myFhirContext, MdmNameMatchModeEnum.ANY_ORDER);
|
||||
case NAME_FIRST_AND_LAST:
|
||||
return new NameMatcher(myFhirContext, MdmNameMatchModeEnum.FIRST_AND_LAST);
|
||||
case IDENTIFIER:
|
||||
return new IdentifierMatcher();
|
||||
case EXTENSION_ANY_ORDER:
|
||||
return new ExtensionMatcher();
|
||||
case NUMERIC:
|
||||
return new NumericMatcher();
|
||||
case EMPTY_FIELD:
|
||||
return new EmptyFieldMatcher();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
matchTypeName = theMdmMatcherEnum.name();
|
||||
} else {
|
||||
matchTypeName = "null";
|
||||
}
|
||||
|
||||
// This is odd, but it's a valid code path
|
||||
ourLog.warn("Unrecognized field type {}. Returning null", matchTypeName);
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR - Master Data Management
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||
* %%
|
||||
* 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%
|
||||
*/
|
||||
package ca.uhn.fhir.mdm.rules.matcher;
|
||||
|
||||
public class SubstringStringMatcher implements IMdmStringMatcher {
|
||||
@Override
|
||||
public boolean matches(String theLeftString, String theRightString) {
|
||||
return theLeftString.startsWith(theRightString) || theRightString.startsWith(theLeftString);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package ca.uhn.fhir.mdm.rules.matcher.fieldmatchers;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.i18n.Msg;
|
||||
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.r4.model.DateTimeType;
|
||||
import org.hl7.fhir.r4.model.DateType;
|
||||
|
||||
/**
|
||||
* A wrapper class for datetimes of ambiguous fhir version
|
||||
*/
|
||||
public class DateTimeWrapper {
|
||||
|
||||
/**
|
||||
* The precision of this datetime object
|
||||
*/
|
||||
private final TemporalPrecisionEnum myPrecision;
|
||||
|
||||
/**
|
||||
* The string value with current precision
|
||||
*/
|
||||
private final String myValueAsString;
|
||||
|
||||
public DateTimeWrapper(FhirContext theFhirContext, IBase theDate) {
|
||||
if (theDate instanceof org.hl7.fhir.dstu3.model.BaseDateTimeType) {
|
||||
org.hl7.fhir.dstu3.model.BaseDateTimeType dstu3Date = (org.hl7.fhir.dstu3.model.BaseDateTimeType) theDate;
|
||||
myPrecision = dstu3Date.getPrecision();
|
||||
myValueAsString = dstu3Date.getValueAsString();
|
||||
} else if (theDate instanceof org.hl7.fhir.r4.model.BaseDateTimeType) {
|
||||
org.hl7.fhir.r4.model.BaseDateTimeType r4Date = (org.hl7.fhir.r4.model.BaseDateTimeType) theDate;
|
||||
myPrecision = r4Date.getPrecision();
|
||||
myValueAsString = r4Date.getValueAsString();
|
||||
} else {
|
||||
// we should consider changing this error so we don't need the fhir context at all
|
||||
throw new UnsupportedOperationException(Msg.code(1520) + "Version not supported: " + theFhirContext.getVersion().getVersion());
|
||||
}
|
||||
}
|
||||
|
||||
public TemporalPrecisionEnum getPrecision() {
|
||||
return myPrecision;
|
||||
}
|
||||
|
||||
public String getValueAsStringWithPrecision(TemporalPrecisionEnum thePrecision) {
|
||||
// we are using an R4 DateTypes because all datetime strings are the same for all fhir versions
|
||||
// (and so it won't matter here)
|
||||
// we just want the string at a specific precision
|
||||
DateTimeType dateTimeType = new DateTimeType(myValueAsString);
|
||||
dateTimeType.setPrecision(thePrecision);
|
||||
return dateTimeType.getValueAsString();
|
||||
}
|
||||
|
||||
public String getValueAsString() {
|
||||
return myValueAsString;
|
||||
}
|
||||
}
|
|
@ -17,9 +17,10 @@
|
|||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
package ca.uhn.fhir.mdm.rules.matcher;
|
||||
package ca.uhn.fhir.mdm.rules.matcher.fieldmatchers;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.mdm.rules.json.MdmMatcherJson;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.models.IMdmFieldMatcher;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
|
||||
public class EmptyFieldMatcher implements IMdmFieldMatcher {
|
||||
|
@ -28,7 +29,7 @@ public class EmptyFieldMatcher implements IMdmFieldMatcher {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(FhirContext theFhirContext, IBase theLeftBase, IBase theRightBase, boolean theExact, String theIdentifierSystem) {
|
||||
public boolean matches(IBase theLeftBase, IBase theRightBase, MdmMatcherJson theParams) {
|
||||
for (IBase b : new IBase[] {theLeftBase, theRightBase}) {
|
||||
if (b != null && !b.isEmpty()) {
|
||||
return false;
|
||||
|
@ -36,4 +37,9 @@ public class EmptyFieldMatcher implements IMdmFieldMatcher {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMatchingEmptyFields() {
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -17,9 +17,10 @@
|
|||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
package ca.uhn.fhir.mdm.rules.matcher;
|
||||
package ca.uhn.fhir.mdm.rules.matcher.fieldmatchers;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.mdm.rules.json.MdmMatcherJson;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.models.IMdmFieldMatcher;
|
||||
import ca.uhn.fhir.util.ExtensionUtil;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.instance.model.api.IBaseExtension;
|
||||
|
@ -30,7 +31,7 @@ import java.util.List;
|
|||
public class ExtensionMatcher implements IMdmFieldMatcher {
|
||||
|
||||
@Override
|
||||
public boolean matches(FhirContext theFhirContext, IBase theLeftBase, IBase theRightBase, boolean theExact, String theIdentifierSystem) {
|
||||
public boolean matches(IBase theLeftBase, IBase theRightBase, MdmMatcherJson theParams) {
|
||||
if (!(theLeftBase instanceof IBaseHasExtensions && theRightBase instanceof IBaseHasExtensions)) {
|
||||
return false;
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR - Master Data Management
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||
* %%
|
||||
* 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%
|
||||
*/
|
||||
package ca.uhn.fhir.mdm.rules.matcher.fieldmatchers;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.mdm.rules.json.MdmMatcherJson;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.models.IMdmFieldMatcher;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
|
||||
public class HapiDateMatcher implements IMdmFieldMatcher {
|
||||
|
||||
private final FhirContext myFhirContext;
|
||||
|
||||
public HapiDateMatcher(FhirContext theFhirContext) {
|
||||
myFhirContext = theFhirContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(IBase theLeftBase, IBase theRightBase, MdmMatcherJson theParams) {
|
||||
DateTimeWrapper left = new DateTimeWrapper(myFhirContext, theLeftBase);
|
||||
DateTimeWrapper right = new DateTimeWrapper(myFhirContext, theRightBase);
|
||||
|
||||
/*
|
||||
* we use the precision to determine how we should equate.
|
||||
* We should use the same precision (the precision of the less
|
||||
* precise date)
|
||||
*/
|
||||
int comparison = left.getPrecision().compareTo(right.getPrecision());
|
||||
|
||||
String leftString;
|
||||
String rightString;
|
||||
if (comparison == 0) {
|
||||
// same precision
|
||||
leftString = left.getValueAsString();
|
||||
rightString = right.getValueAsString();
|
||||
} else if (comparison > 0) {
|
||||
// left date is more precise than right date
|
||||
leftString = left.getValueAsStringWithPrecision(right.getPrecision());
|
||||
rightString = right.getValueAsString();
|
||||
} else {
|
||||
// right date is more precise than left date
|
||||
rightString = right.getValueAsStringWithPrecision(left.getPrecision());
|
||||
leftString = left.getValueAsString();
|
||||
}
|
||||
|
||||
return leftString.equals(rightString);
|
||||
}
|
||||
}
|
|
@ -17,33 +17,26 @@
|
|||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
package ca.uhn.fhir.mdm.rules.matcher;
|
||||
package ca.uhn.fhir.mdm.rules.matcher.fieldmatchers;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.mdm.rules.json.MdmMatcherJson;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.models.IMdmFieldMatcher;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.util.StringMatcherUtils;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
|
||||
/**
|
||||
* Similarity measure for two IBase fields whose similarity can be measured by their String representations.
|
||||
*/
|
||||
public class HapiStringMatcher extends BaseHapiStringMetric implements IMdmFieldMatcher {
|
||||
private final IMdmStringMatcher myStringMatcher;
|
||||
|
||||
public HapiStringMatcher(IMdmStringMatcher theStringMatcher) {
|
||||
myStringMatcher = theStringMatcher;
|
||||
}
|
||||
|
||||
public HapiStringMatcher() {
|
||||
myStringMatcher = String::equals;
|
||||
}
|
||||
public class HapiStringMatcher implements IMdmFieldMatcher {
|
||||
|
||||
@Override
|
||||
public boolean matches(FhirContext theFhirContext, IBase theLeftBase, IBase theRightBase, boolean theExact, String theIdentifierSystem) {
|
||||
public boolean matches(IBase theLeftBase, IBase theRightBase, MdmMatcherJson theExtraMatchParams) {
|
||||
if (theLeftBase instanceof IPrimitiveType && theRightBase instanceof IPrimitiveType) {
|
||||
String leftString = extractString((IPrimitiveType<?>) theLeftBase, theExact);
|
||||
String rightString = extractString((IPrimitiveType<?>) theRightBase, theExact);
|
||||
String leftString = StringMatcherUtils.extractString((IPrimitiveType<?>) theLeftBase, theExtraMatchParams.getExact());
|
||||
String rightString = StringMatcherUtils.extractString((IPrimitiveType<?>) theRightBase, theExtraMatchParams.getExact());
|
||||
|
||||
return myStringMatcher.matches(leftString, rightString);
|
||||
return leftString.equals(rightString);
|
||||
}
|
||||
return false;
|
||||
}
|
|
@ -17,25 +17,34 @@
|
|||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
package ca.uhn.fhir.mdm.rules.matcher;
|
||||
package ca.uhn.fhir.mdm.rules.matcher.fieldmatchers;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.util.CanonicalIdentifier;
|
||||
import ca.uhn.fhir.mdm.rules.json.MdmMatcherJson;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.models.IMdmFieldMatcher;
|
||||
import ca.uhn.fhir.mdm.util.IdentifierUtil;
|
||||
import ca.uhn.fhir.model.primitive.StringDt;
|
||||
import ca.uhn.fhir.util.CanonicalIdentifier;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
|
||||
public class IdentifierMatcher implements IMdmFieldMatcher {
|
||||
|
||||
private boolean isEmpty(StringDt theValue) {
|
||||
if (theValue == null) {
|
||||
return true;
|
||||
}
|
||||
return theValue.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the two fhir identifiers are the same. If @param theIdentifierSystem is not null, then the
|
||||
* matcher only returns true if the identifier systems also match this system.
|
||||
* @throws UnsupportedOperationException if either Base is not an Identifier instance
|
||||
*/
|
||||
@Override
|
||||
public boolean matches(FhirContext theFhirContext, IBase theLeftBase, IBase theRightBase, boolean theExact, String theIdentifierSystem) {
|
||||
public boolean matches(IBase theLeftBase, IBase theRightBase, MdmMatcherJson theParams) {
|
||||
CanonicalIdentifier left = IdentifierUtil.identifierDtFromIdentifier(theLeftBase);
|
||||
if (theIdentifierSystem != null) {
|
||||
if (!theIdentifierSystem.equals(left.getSystemElement().getValueAsString())) {
|
||||
if (theParams.getIdentifierSystem() != null) {
|
||||
if (!theParams.getIdentifierSystem().equals(left.getSystemElement().getValueAsString())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -45,11 +54,4 @@ public class IdentifierMatcher implements IMdmFieldMatcher {
|
|||
}
|
||||
return left.equals(right);
|
||||
}
|
||||
|
||||
private boolean isEmpty(StringDt theValue) {
|
||||
if (theValue == null) {
|
||||
return true;
|
||||
}
|
||||
return theValue.isEmpty();
|
||||
}
|
||||
}
|
|
@ -17,7 +17,7 @@
|
|||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
package ca.uhn.fhir.mdm.rules.matcher;
|
||||
package ca.uhn.fhir.mdm.rules.matcher.fieldmatchers;
|
||||
|
||||
public enum MdmNameMatchModeEnum {
|
||||
ANY_ORDER,
|
|
@ -17,9 +17,11 @@
|
|||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
package ca.uhn.fhir.mdm.rules.matcher;
|
||||
package ca.uhn.fhir.mdm.rules.matcher.fieldmatchers;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.mdm.rules.json.MdmMatcherJson;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.models.IMdmFieldMatcher;
|
||||
import ca.uhn.fhir.mdm.util.NameUtil;
|
||||
import ca.uhn.fhir.util.StringUtil;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
@ -35,24 +37,27 @@ public class NameMatcher implements IMdmFieldMatcher {
|
|||
|
||||
private final MdmNameMatchModeEnum myMatchMode;
|
||||
|
||||
public NameMatcher(MdmNameMatchModeEnum theMatchMode) {
|
||||
private final FhirContext myFhirContext;
|
||||
|
||||
public NameMatcher(FhirContext theFhirContext, MdmNameMatchModeEnum theMatchMode) {
|
||||
myMatchMode = theMatchMode;
|
||||
myFhirContext = theFhirContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(FhirContext theFhirContext, IBase theLeftBase, IBase theRightBase, boolean theExact, String theIdentifierSystem) {
|
||||
String leftFamilyName = NameUtil.extractFamilyName(theFhirContext, theLeftBase);
|
||||
String rightFamilyName = NameUtil.extractFamilyName(theFhirContext, theRightBase);
|
||||
public boolean matches(IBase theLeftBase, IBase theRightBase, MdmMatcherJson theParams) {
|
||||
String leftFamilyName = NameUtil.extractFamilyName(myFhirContext, theLeftBase);
|
||||
String rightFamilyName = NameUtil.extractFamilyName(myFhirContext, theRightBase);
|
||||
if (StringUtils.isEmpty(leftFamilyName) || StringUtils.isEmpty(rightFamilyName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean match = false;
|
||||
|
||||
List<String> leftGivenNames = NameUtil.extractGivenNames(theFhirContext, theLeftBase);
|
||||
List<String> rightGivenNames = NameUtil.extractGivenNames(theFhirContext, theRightBase);
|
||||
List<String> leftGivenNames = NameUtil.extractGivenNames(myFhirContext, theLeftBase);
|
||||
List<String> rightGivenNames = NameUtil.extractGivenNames(myFhirContext, theRightBase);
|
||||
|
||||
if (!theExact) {
|
||||
if (!theParams.getExact()) {
|
||||
leftFamilyName = StringUtil.normalizeStringForSearchIndexing(leftFamilyName);
|
||||
rightFamilyName = StringUtil.normalizeStringForSearchIndexing(rightFamilyName);
|
||||
leftGivenNames = leftGivenNames.stream().map(StringUtil::normalizeStringForSearchIndexing).collect(Collectors.toList());
|
|
@ -17,28 +17,25 @@
|
|||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
package ca.uhn.fhir.mdm.rules.matcher;
|
||||
package ca.uhn.fhir.mdm.rules.matcher.fieldmatchers;
|
||||
|
||||
import ca.uhn.fhir.context.ConfigurationException;
|
||||
import ca.uhn.fhir.i18n.Msg;
|
||||
import ca.uhn.fhir.jpa.searchparam.nickname.NicknameSvc;
|
||||
import ca.uhn.fhir.mdm.rules.json.MdmMatcherJson;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.models.IMdmFieldMatcher;
|
||||
import ca.uhn.fhir.jpa.nickname.NicknameSvc;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.util.StringMatcherUtils;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Locale;
|
||||
|
||||
public class NicknameMatcher implements IMdmStringMatcher {
|
||||
public class NicknameMatcher implements IMdmFieldMatcher {
|
||||
private final NicknameSvc myNicknameSvc;
|
||||
|
||||
public NicknameMatcher() {
|
||||
try {
|
||||
myNicknameSvc = new NicknameSvc();
|
||||
} catch (IOException e) {
|
||||
throw new ConfigurationException(Msg.code(2234) + "Unable to load nicknames", e);
|
||||
}
|
||||
public NicknameMatcher(NicknameSvc theNicknameSvc) {
|
||||
myNicknameSvc = theNicknameSvc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(String theLeftString, String theRightString) {
|
||||
String leftString = theLeftString.toLowerCase(Locale.ROOT);
|
||||
String rightString = theRightString.toLowerCase(Locale.ROOT);
|
||||
|
@ -51,4 +48,15 @@ public class NicknameMatcher implements IMdmStringMatcher {
|
|||
Collection<String> rightNames = myNicknameSvc.getEquivalentNames(rightString);
|
||||
return rightNames.contains(leftString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(IBase theLeftBase, IBase theRightBase, MdmMatcherJson theParams) {
|
||||
if (theLeftBase instanceof IPrimitiveType && theRightBase instanceof IPrimitiveType) {
|
||||
String leftString = StringMatcherUtils.extractString((IPrimitiveType<?>) theLeftBase, theParams.getExact());
|
||||
String rightString = StringMatcherUtils.extractString((IPrimitiveType<?>) theRightBase, theParams.getExact());
|
||||
|
||||
return matches(leftString, rightString);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -17,19 +17,27 @@
|
|||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
package ca.uhn.fhir.mdm.rules.matcher;
|
||||
package ca.uhn.fhir.mdm.rules.matcher.fieldmatchers;
|
||||
|
||||
import ca.uhn.fhir.context.phonetic.NumericEncoder;
|
||||
import ca.uhn.fhir.mdm.rules.json.MdmMatcherJson;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.models.IMdmFieldMatcher;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.util.StringMatcherUtils;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
|
||||
// Useful for numerical identifiers like phone numbers, address parts etc.
|
||||
// This should not be used where decimals are important. A new "quantity matcher" should be added to handle cases like that.
|
||||
public class NumericMatcher implements IMdmStringMatcher {
|
||||
public class NumericMatcher implements IMdmFieldMatcher {
|
||||
private final NumericEncoder encoder = new NumericEncoder();
|
||||
|
||||
@Override
|
||||
public boolean matches(String theLeftString, String theRightString) {
|
||||
String left = encoder.encode(theLeftString);
|
||||
String right = encoder.encode(theRightString);
|
||||
return left.equals(right);
|
||||
public boolean matches(IBase theLeftBase, IBase theRightBase, MdmMatcherJson theParams) {
|
||||
if (theLeftBase instanceof IPrimitiveType && theRightBase instanceof IPrimitiveType) {
|
||||
String left = encoder.encode(StringMatcherUtils.extractString((IPrimitiveType<?>) theLeftBase, theParams.getExact()));
|
||||
String right = encoder.encode(StringMatcherUtils.extractString((IPrimitiveType<?>) theRightBase, theParams.getExact()));
|
||||
return left.equals(right);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -17,15 +17,20 @@
|
|||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
package ca.uhn.fhir.mdm.rules.matcher;
|
||||
package ca.uhn.fhir.mdm.rules.matcher.fieldmatchers;
|
||||
|
||||
import ca.uhn.fhir.context.phonetic.IPhoneticEncoder;
|
||||
import ca.uhn.fhir.context.phonetic.PhoneticEncoderEnum;
|
||||
import ca.uhn.fhir.mdm.rules.json.MdmMatcherJson;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.models.IMdmFieldMatcher;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.util.StringMatcherUtils;
|
||||
import ca.uhn.fhir.util.PhoneticEncoderUtil;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class PhoneticEncoderMatcher implements IMdmStringMatcher {
|
||||
public class PhoneticEncoderMatcher implements IMdmFieldMatcher {
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(PhoneticEncoderMatcher.class);
|
||||
|
||||
private final IPhoneticEncoder myStringEncoder;
|
||||
|
@ -34,8 +39,16 @@ public class PhoneticEncoderMatcher implements IMdmStringMatcher {
|
|||
myStringEncoder = PhoneticEncoderUtil.getEncoder(thePhoneticEnum.name());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(String theLeftString, String theRightString) {
|
||||
return myStringEncoder.encode(theLeftString).equals(myStringEncoder.encode(theRightString));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(IBase theLeftBase, IBase theRightBase, MdmMatcherJson theParams) {
|
||||
String leftString = StringMatcherUtils.extractString((IPrimitiveType<?>) theLeftBase, theParams.getExact());
|
||||
String rightString = StringMatcherUtils.extractString((IPrimitiveType<?>) theRightBase, theParams.getExact());
|
||||
|
||||
return matches(leftString, rightString);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR - Master Data Management
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||
* %%
|
||||
* 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%
|
||||
*/
|
||||
package ca.uhn.fhir.mdm.rules.matcher.fieldmatchers;
|
||||
|
||||
import ca.uhn.fhir.mdm.rules.json.MdmMatcherJson;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.models.IMdmFieldMatcher;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.util.StringMatcherUtils;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
|
||||
public class SubstringStringMatcher implements IMdmFieldMatcher {
|
||||
|
||||
@Override
|
||||
public boolean matches(IBase theLeftBase, IBase theRightBase, MdmMatcherJson theParams) {
|
||||
if (theLeftBase instanceof IPrimitiveType && theRightBase instanceof IPrimitiveType) {
|
||||
String left = StringMatcherUtils.extractString((IPrimitiveType<?>) theLeftBase, theParams.getExact());
|
||||
String right = StringMatcherUtils.extractString((IPrimitiveType<?>) theRightBase, theParams.getExact());
|
||||
return left.startsWith(right) || right.startsWith(left);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -17,14 +17,27 @@
|
|||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
package ca.uhn.fhir.mdm.rules.matcher;
|
||||
package ca.uhn.fhir.mdm.rules.matcher.models;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.mdm.rules.json.MdmMatcherJson;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
|
||||
/**
|
||||
* Measure how similar two IBase (resource fields) are to one another. 1.0 means identical. 0.0 means completely different.
|
||||
*/
|
||||
public interface IMdmFieldMatcher {
|
||||
boolean matches(FhirContext theFhirContext, IBase theLeftBase, IBase theRightBase, boolean theExact, String theIdentifierSystem);
|
||||
/**
|
||||
* Checks if theLeftBase and theRightBase match, returning true if they do
|
||||
* and false otherwise.
|
||||
*/
|
||||
boolean matches(IBase theLeftBase, IBase theRightBase, MdmMatcherJson theParams);
|
||||
|
||||
/**
|
||||
* True if matcher can/will match empty (null) fields,
|
||||
* false otherwise.
|
||||
*/
|
||||
default boolean isMatchingEmptyFields() {
|
||||
// false because most people are overriding this
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -17,17 +17,37 @@
|
|||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
package ca.uhn.fhir.mdm.rules.matcher;
|
||||
package ca.uhn.fhir.mdm.rules.matcher.models;
|
||||
|
||||
import ca.uhn.fhir.util.StringUtil;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
/**
|
||||
* Enum for holding all the known FHIR Element matchers that we support in HAPI. The string matchers first
|
||||
* encode the string using an Apache Encoder before comparing them.
|
||||
* https://commons.apache.org/proper/commons-codec/userguide.html
|
||||
*/
|
||||
public enum MatchTypeEnum {
|
||||
|
||||
CAVERPHONE1,
|
||||
CAVERPHONE2,
|
||||
COLOGNE,
|
||||
DOUBLE_METAPHONE,
|
||||
MATCH_RATING_APPROACH,
|
||||
METAPHONE,
|
||||
NYSIIS,
|
||||
REFINED_SOUNDEX,
|
||||
SOUNDEX,
|
||||
NICKNAME,
|
||||
|
||||
STRING,
|
||||
SUBSTRING,
|
||||
|
||||
DATE,
|
||||
NAME_ANY_ORDER,
|
||||
NAME_FIRST_AND_LAST,
|
||||
|
||||
IDENTIFIER,
|
||||
|
||||
EMPTY_FIELD,
|
||||
EXTENSION_ANY_ORDER,
|
||||
NUMERIC;
|
||||
|
||||
public abstract class BaseHapiStringMetric {
|
||||
protected String extractString(IPrimitiveType<?> thePrimitive, boolean theExact) {
|
||||
String theString = thePrimitive.getValueAsString();
|
||||
if (theExact) {
|
||||
return theString;
|
||||
}
|
||||
return StringUtil.normalizeStringForSearchIndexing(theString);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package ca.uhn.fhir.mdm.rules.matcher.util;
|
||||
|
||||
import ca.uhn.fhir.util.StringUtil;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
|
||||
public class StringMatcherUtils {
|
||||
public static String extractString(IPrimitiveType<?> thePrimitive, boolean theExact) {
|
||||
String theString = thePrimitive.getValueAsString();
|
||||
if (theExact) {
|
||||
return theString;
|
||||
}
|
||||
return StringUtil.normalizeStringForSearchIndexing(theString);
|
||||
}
|
||||
}
|
|
@ -20,7 +20,7 @@
|
|||
package ca.uhn.fhir.mdm.rules.similarity;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.BaseHapiStringMetric;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.util.StringMatcherUtils;
|
||||
import info.debatty.java.stringsimilarity.interfaces.NormalizedStringSimilarity;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
|
@ -28,7 +28,7 @@ import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
|||
/**
|
||||
* Similarity measure for two IBase fields whose similarity can be measured by their String representations.
|
||||
*/
|
||||
public class HapiStringSimilarity extends BaseHapiStringMetric implements IMdmFieldSimilarity {
|
||||
public class HapiStringSimilarity implements IMdmFieldSimilarity {
|
||||
private final NormalizedStringSimilarity myStringSimilarity;
|
||||
|
||||
public HapiStringSimilarity(NormalizedStringSimilarity theStringSimilarity) {
|
||||
|
@ -38,8 +38,8 @@ public class HapiStringSimilarity extends BaseHapiStringMetric implements IMdmFi
|
|||
@Override
|
||||
public double similarity(FhirContext theFhirContext, IBase theLeftBase, IBase theRightBase, boolean theExact) {
|
||||
if (theLeftBase instanceof IPrimitiveType && theRightBase instanceof IPrimitiveType) {
|
||||
String leftString = extractString((IPrimitiveType<?>) theLeftBase, theExact);
|
||||
String rightString = extractString((IPrimitiveType<?>) theRightBase, theExact);
|
||||
String leftString = StringMatcherUtils.extractString((IPrimitiveType<?>) theLeftBase, theExact);
|
||||
String rightString = StringMatcherUtils.extractString((IPrimitiveType<?>) theRightBase, theExact);
|
||||
|
||||
return myStringSimilarity.similarity(leftString, rightString);
|
||||
}
|
||||
|
|
|
@ -21,9 +21,16 @@ package ca.uhn.fhir.mdm.rules.svc;
|
|||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.fhirpath.IFhirPath;
|
||||
import ca.uhn.fhir.i18n.Msg;
|
||||
import ca.uhn.fhir.mdm.api.MdmMatchEvaluation;
|
||||
import ca.uhn.fhir.mdm.rules.json.MdmFieldMatchJson;
|
||||
import ca.uhn.fhir.mdm.rules.json.MdmMatcherJson;
|
||||
import ca.uhn.fhir.mdm.rules.json.MdmRulesJson;
|
||||
import ca.uhn.fhir.mdm.rules.json.MdmSimilarityJson;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.IMatcherFactory;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.models.IMdmFieldMatcher;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.models.MatchTypeEnum;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.util.FhirTerser;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
|
@ -48,7 +55,16 @@ public class MdmResourceFieldMatcher {
|
|||
private final String myName;
|
||||
private final boolean myIsFhirPathExpression;
|
||||
|
||||
public MdmResourceFieldMatcher(FhirContext theFhirContext, MdmFieldMatchJson theMdmFieldMatchJson, MdmRulesJson theMdmRulesJson) {
|
||||
private final IMatcherFactory myIMatcherFactory;
|
||||
|
||||
public MdmResourceFieldMatcher(
|
||||
FhirContext theFhirContext,
|
||||
IMatcherFactory theIMatcherFactory,
|
||||
MdmFieldMatchJson theMdmFieldMatchJson,
|
||||
MdmRulesJson theMdmRulesJson
|
||||
) {
|
||||
myIMatcherFactory = theIMatcherFactory;
|
||||
|
||||
myFhirContext = theFhirContext;
|
||||
myMdmFieldMatchJson = theMdmFieldMatchJson;
|
||||
myResourceType = theMdmFieldMatchJson.getResourceType();
|
||||
|
@ -70,7 +86,6 @@ public class MdmResourceFieldMatcher {
|
|||
* @param theRightResource the second {@link IBaseResource}
|
||||
* @return A boolean indicating whether they match.
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public MdmMatchEvaluation match(IBaseResource theLeftResource, IBaseResource theRightResource) {
|
||||
validate(theLeftResource);
|
||||
validate(theRightResource);
|
||||
|
@ -90,12 +105,12 @@ public class MdmResourceFieldMatcher {
|
|||
return match(leftValues, rightValues);
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private MdmMatchEvaluation match(List<IBase> theLeftValues, List<IBase> theRightValues) {
|
||||
MdmMatchEvaluation retval = new MdmMatchEvaluation(false, 0.0);
|
||||
|
||||
boolean isMatchingEmptyFieldValues = (theLeftValues.isEmpty() && theRightValues.isEmpty());
|
||||
if (isMatchingEmptyFieldValues && myMdmFieldMatchJson.isMatcherSupportingEmptyFields()) {
|
||||
IMdmFieldMatcher matcher = getFieldMatcher();
|
||||
if (isMatchingEmptyFieldValues && (matcher != null && matcher.isMatchingEmptyFields())) {
|
||||
return match((IBase) null, (IBase) null);
|
||||
}
|
||||
|
||||
|
@ -110,7 +125,18 @@ public class MdmResourceFieldMatcher {
|
|||
}
|
||||
|
||||
private MdmMatchEvaluation match(IBase theLeftValue, IBase theRightValue) {
|
||||
return myMdmFieldMatchJson.match(myFhirContext, theLeftValue, theRightValue);
|
||||
IMdmFieldMatcher matcher = getFieldMatcher();
|
||||
if (matcher != null) {
|
||||
boolean isMatches = matcher.matches(theLeftValue, theRightValue, myMdmFieldMatchJson.getMatcher());
|
||||
return new MdmMatchEvaluation(isMatches, isMatches ? 1.0 : 0.0);
|
||||
}
|
||||
|
||||
MdmSimilarityJson similarity = myMdmFieldMatchJson.getSimilarity();
|
||||
if (similarity != null) {
|
||||
return similarity.match(myFhirContext, theLeftValue, theRightValue);
|
||||
}
|
||||
|
||||
throw new InternalErrorException(Msg.code(1522) + "Field Match " + myName + " has neither a matcher nor a similarity.");
|
||||
}
|
||||
|
||||
private void validate(IBaseResource theResource) {
|
||||
|
@ -136,4 +162,17 @@ public class MdmResourceFieldMatcher {
|
|||
public String getName() {
|
||||
return myName;
|
||||
}
|
||||
|
||||
private IMdmFieldMatcher getFieldMatcher() {
|
||||
MdmMatcherJson matcherJson = myMdmFieldMatchJson.getMatcher();
|
||||
MatchTypeEnum matchTypeEnum = null;
|
||||
if (matcherJson != null) {
|
||||
matchTypeEnum = matcherJson.getAlgorithm();
|
||||
}
|
||||
if (matchTypeEnum == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return myIMatcherFactory.getFieldMatcherForMatchType(matchTypeEnum);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,8 @@ import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
|
|||
import ca.uhn.fhir.mdm.log.Logs;
|
||||
import ca.uhn.fhir.mdm.rules.json.MdmFieldMatchJson;
|
||||
import ca.uhn.fhir.mdm.rules.json.MdmRulesJson;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.IMatcherFactory;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.slf4j.Logger;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
@ -48,16 +50,18 @@ public class MdmResourceMatcherSvc {
|
|||
private static final Logger ourLog = Logs.getMdmTroubleshootingLog();
|
||||
|
||||
private final FhirContext myFhirContext;
|
||||
private MdmRulesJson myMdmRulesJson;
|
||||
private final IMatcherFactory myMatcherFactory;
|
||||
private final List<MdmResourceFieldMatcher> myFieldMatchers = new ArrayList<>();
|
||||
|
||||
public MdmResourceMatcherSvc(FhirContext theFhirContext, IMdmSettings theMdmSettings) {
|
||||
private MdmRulesJson myMdmRulesJson;
|
||||
|
||||
public MdmResourceMatcherSvc(
|
||||
FhirContext theFhirContext,
|
||||
IMatcherFactory theIMatcherFactory,
|
||||
IMdmSettings theMdmSettings
|
||||
) {
|
||||
myFhirContext = theFhirContext;
|
||||
|
||||
setMdmSettings(theMdmSettings);
|
||||
}
|
||||
|
||||
public void setMdmSettings(IMdmSettings theMdmSettings) {
|
||||
myMatcherFactory = theIMatcherFactory;
|
||||
myMdmRulesJson = theMdmSettings.getMdmRules();
|
||||
|
||||
addFieldMatchers();
|
||||
|
@ -69,7 +73,7 @@ public class MdmResourceMatcherSvc {
|
|||
}
|
||||
myFieldMatchers.clear();
|
||||
for (MdmFieldMatchJson matchFieldJson : myMdmRulesJson.getMatchFields()) {
|
||||
myFieldMatchers.add(new MdmResourceFieldMatcher( myFhirContext, matchFieldJson, myMdmRulesJson));
|
||||
myFieldMatchers.add(new MdmResourceFieldMatcher(myFhirContext, myMatcherFactory, matchFieldJson, myMdmRulesJson));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -77,9 +81,8 @@ public class MdmResourceMatcherSvc {
|
|||
* Given two {@link IBaseResource}s, perform all comparisons on them to determine an {@link MdmMatchResultEnum}, indicating
|
||||
* to what level the two resources are considered to be matching.
|
||||
*
|
||||
* @param theLeftResource The first {@link IBaseResource}.
|
||||
* @param theLeftResource The first {@link IBaseResource}.
|
||||
* @param theRightResource The second {@link IBaseResource}
|
||||
*
|
||||
* @return an {@link MdmMatchResultEnum} indicating the result of the comparison.
|
||||
*/
|
||||
public MdmMatchOutcome getMatchResult(IBaseResource theLeftResource, IBaseResource theRightResource) {
|
||||
|
@ -91,8 +94,8 @@ public class MdmResourceMatcherSvc {
|
|||
MdmMatchResultEnum matchResultEnum = myMdmRulesJson.getMatchResult(matchResult.getVector());
|
||||
matchResult.setMatchResultEnum(matchResultEnum);
|
||||
if (ourLog.isDebugEnabled()) {
|
||||
ourLog.debug("{} {}: {}", matchResult.getMatchResultEnum(), theRightResource.getIdElement().toUnqualifiedVersionless(), matchResult);
|
||||
if (ourLog.isTraceEnabled()) {
|
||||
ourLog.debug("{} {}: {}", matchResult.getMatchResultEnum(), theRightResource.getIdElement().toUnqualifiedVersionless(), matchResult);
|
||||
if (ourLog.isTraceEnabled()) {
|
||||
ourLog.trace("Field matcher results:\n{}", myMdmRulesJson.getDetailedFieldMatchResultWithSuccessInformation(matchResult.getVector()));
|
||||
}
|
||||
}
|
||||
|
@ -105,11 +108,11 @@ public class MdmResourceMatcherSvc {
|
|||
* start with a binary representation of the value 0 for long: 0000
|
||||
* first_name matches, so the value `1` is bitwise-ORed to the current value (0) in right-most position.
|
||||
* `0001`
|
||||
*
|
||||
* <p>
|
||||
* Next, we look at the second field comparator, and see if it matches. If it does, we left-shift 1 by the index
|
||||
* of the comparator, in this case also 1.
|
||||
* `0010`
|
||||
*
|
||||
* <p>
|
||||
* Then, we bitwise-or it with the current retval:
|
||||
* 0001|0010 = 0011
|
||||
* The binary string is now `0011`, which when you return it as a long becomes `3`.
|
||||
|
@ -146,11 +149,16 @@ public class MdmResourceMatcherSvc {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
|
||||
private boolean isValidResourceType(String theResourceType, String theFieldComparatorType) {
|
||||
private boolean isValidResourceType(String theResourceType, String theFieldComparatorType) {
|
||||
return (
|
||||
theFieldComparatorType.equalsIgnoreCase(MdmConstants.ALL_RESOURCE_SEARCH_PARAM_TYPE)
|
||||
|| theFieldComparatorType.equalsIgnoreCase(theResourceType)
|
||||
|| theFieldComparatorType.equalsIgnoreCase(theResourceType)
|
||||
);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setMdmRulesJson(MdmRulesJson theMdmRulesJson) {
|
||||
myMdmRulesJson = theMdmRulesJson;
|
||||
addFieldMatchers();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,19 @@
|
|||
package ca.uhn.fhir.mdm;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.nickname.NicknameSvc;
|
||||
import ca.uhn.fhir.mdm.api.IMdmSettings;
|
||||
import ca.uhn.fhir.mdm.api.MdmMatchOutcome;
|
||||
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
|
||||
import ca.uhn.fhir.mdm.rules.config.MdmRuleValidator;
|
||||
import ca.uhn.fhir.mdm.rules.config.MdmSettings;
|
||||
import ca.uhn.fhir.mdm.rules.json.MdmRulesJson;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.IMatcherFactory;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.MdmMatcherFactory;
|
||||
import ca.uhn.fhir.mdm.rules.svc.MdmResourceMatcherSvc;
|
||||
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
|
@ -20,6 +25,20 @@ public abstract class BaseR4Test {
|
|||
protected static final FhirContext ourFhirContext = FhirContext.forR4();
|
||||
protected ISearchParamRegistry mySearchParamRetriever = mock(ISearchParamRegistry.class);
|
||||
|
||||
protected IMatcherFactory myIMatcherFactory;
|
||||
|
||||
protected IMdmSettings myMdmSettings;
|
||||
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
myMdmSettings = mock(IMdmSettings.class);
|
||||
myIMatcherFactory = new MdmMatcherFactory(
|
||||
ourFhirContext,
|
||||
myMdmSettings,
|
||||
new NicknameSvc()
|
||||
);
|
||||
}
|
||||
|
||||
protected Patient buildJohn() {
|
||||
Patient patient = new Patient();
|
||||
patient.addName().addGiven("John");
|
||||
|
@ -35,7 +54,10 @@ public abstract class BaseR4Test {
|
|||
}
|
||||
|
||||
protected MdmResourceMatcherSvc buildMatcher(MdmRulesJson theMdmRulesJson) {
|
||||
return new MdmResourceMatcherSvc(ourFhirContext, new MdmSettings(new MdmRuleValidator(ourFhirContext, mySearchParamRetriever)).setMdmRules(theMdmRulesJson));
|
||||
return new MdmResourceMatcherSvc(ourFhirContext,
|
||||
myIMatcherFactory,
|
||||
new MdmSettings(new MdmRuleValidator(ourFhirContext, mySearchParamRetriever)).setMdmRules(theMdmRulesJson)
|
||||
);
|
||||
}
|
||||
|
||||
protected void assertMatch(MdmMatchResultEnum theExpectedMatchEnum, MdmMatchOutcome theMatchResult) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package ca.uhn.fhir.mdm.rules.json;
|
||||
|
||||
import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.MdmMatcherEnum;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.models.MatchTypeEnum;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
@ -27,7 +27,7 @@ public class VectorMatchResultMapTest {
|
|||
@Test
|
||||
public void testMatchBeforePossibleMatch() {
|
||||
MdmRulesJson mdmRulesJson = new MdmRulesJson();
|
||||
MdmMatcherJson matcherJson = new MdmMatcherJson().setAlgorithm(MdmMatcherEnum.STRING);
|
||||
MdmMatcherJson matcherJson = new MdmMatcherJson().setAlgorithm(MatchTypeEnum.STRING);
|
||||
mdmRulesJson.addMatchField(new MdmFieldMatchJson().setName("given").setResourceType("Patient").setResourcePath("name.given").setMatcher(matcherJson));
|
||||
mdmRulesJson.addMatchField(new MdmFieldMatchJson().setName("family").setResourceType("Patient").setResourcePath("name.family").setMatcher(matcherJson));
|
||||
mdmRulesJson.addMatchField(new MdmFieldMatchJson().setName("prefix").setResourceType("Patient").setResourcePath("name.prefix").setMatcher(matcherJson));
|
||||
|
|
|
@ -1,7 +1,16 @@
|
|||
package ca.uhn.fhir.mdm.rules.matcher;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.mdm.rules.json.MdmMatcherJson;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
||||
public abstract class BaseMatcherR4Test {
|
||||
protected static final FhirContext ourFhirContext = FhirContext.forR4();
|
||||
|
||||
protected MdmMatcherJson myMdmMatcherJson;
|
||||
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
myMdmMatcherJson = new MdmMatcherJson();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
package ca.uhn.fhir.mdm.rules.matcher;
|
||||
|
||||
import ca.uhn.fhir.mdm.rules.matcher.fieldmatchers.HapiDateMatcher;
|
||||
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
|
||||
import org.hl7.fhir.r4.model.DateTimeType;
|
||||
import org.hl7.fhir.r4.model.DateType;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Calendar;
|
||||
|
@ -14,6 +16,14 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
|||
|
||||
public class DateMatcherR4Test extends BaseMatcherR4Test {
|
||||
|
||||
private HapiDateMatcher myDateMatcher;
|
||||
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
super.before();
|
||||
myDateMatcher = new HapiDateMatcher(ourFhirContext);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExactDatePrecision() {
|
||||
Calendar cal = new GregorianCalendar(2020, 6, 15);
|
||||
|
@ -43,7 +53,8 @@ public class DateMatcherR4Test extends BaseMatcherR4Test {
|
|||
}
|
||||
|
||||
private boolean dateMatch(Date theDate, Date theSameMonth, TemporalPrecisionEnum theTheDay) {
|
||||
return MdmMatcherEnum.DATE.match(ourFhirContext, new DateType(theDate, theTheDay), new DateType(theSameMonth, theTheDay), true, null);
|
||||
myMdmMatcherJson.setExact(true);
|
||||
return myDateMatcher.matches(new DateType(theDate, theTheDay), new DateType(theSameMonth, theTheDay), myMdmMatcherJson);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -87,12 +98,11 @@ public class DateMatcherR4Test extends BaseMatcherR4Test {
|
|||
}
|
||||
|
||||
private boolean dateTimeMatch(Date theDate, Date theSecondDate, TemporalPrecisionEnum thePrecision, TemporalPrecisionEnum theSecondPrecision) {
|
||||
return MdmMatcherEnum.DATE.match(
|
||||
ourFhirContext,
|
||||
myMdmMatcherJson.setExact(true);
|
||||
return myDateMatcher.matches(
|
||||
new DateTimeType(theDate, thePrecision),
|
||||
new DateTimeType(theSecondDate, theSecondPrecision),
|
||||
true,
|
||||
null
|
||||
myMdmMatcherJson
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@ package ca.uhn.fhir.mdm.rules.matcher;
|
|||
|
||||
import ca.uhn.fhir.mdm.rules.json.MdmFieldMatchJson;
|
||||
import ca.uhn.fhir.mdm.rules.json.MdmMatcherJson;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.fieldmatchers.EmptyFieldMatcher;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.models.MatchTypeEnum;
|
||||
import ca.uhn.fhir.model.primitive.StringDt;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
@ -16,16 +18,16 @@ public class EmptyFieldMatcherTest extends BaseMatcherR4Test {
|
|||
StringDt leftEmpty = new StringDt("");
|
||||
StringDt rightEmpty = new StringDt("");
|
||||
StringDt right = new StringDt("a value");
|
||||
MdmMatcherJson matcher = new MdmMatcherJson().setAlgorithm(MdmMatcherEnum.EMPTY_FIELD);
|
||||
MdmFieldMatchJson fieldMatch = new MdmFieldMatchJson().setMatcher(matcher);
|
||||
|
||||
assertTrue(fieldMatch.match(ourFhirContext, null, null).match);
|
||||
assertTrue(fieldMatch.match(ourFhirContext, null, rightEmpty).match);
|
||||
assertTrue(fieldMatch.match(ourFhirContext, leftEmpty, null).match);
|
||||
assertTrue(fieldMatch.match(ourFhirContext, leftEmpty, rightEmpty).match);
|
||||
assertFalse(fieldMatch.match(ourFhirContext, null, right).match);
|
||||
assertFalse(fieldMatch.match(ourFhirContext, left, null).match);
|
||||
assertFalse(fieldMatch.match(ourFhirContext, left, right).match);
|
||||
EmptyFieldMatcher fieldMatch = new EmptyFieldMatcher();
|
||||
|
||||
assertTrue(fieldMatch.matches(null, null, null));
|
||||
assertTrue(fieldMatch.matches(null, rightEmpty, null));
|
||||
assertTrue(fieldMatch.matches(leftEmpty, null, null));
|
||||
assertTrue(fieldMatch.matches(leftEmpty, rightEmpty, null));
|
||||
assertFalse(fieldMatch.matches(null, right, null));
|
||||
assertFalse(fieldMatch.matches(left, null, null));
|
||||
assertFalse(fieldMatch.matches(left, right, null));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,16 +1,28 @@
|
|||
package ca.uhn.fhir.mdm.rules.matcher;
|
||||
|
||||
|
||||
import ca.uhn.fhir.mdm.rules.matcher.fieldmatchers.ExtensionMatcher;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.r4.model.IntegerType;
|
||||
import org.hl7.fhir.r4.model.Organization;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.hl7.fhir.r4.model.StringType;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class ExtensionMatcherR4Test extends BaseMatcherR4Test {
|
||||
|
||||
private ExtensionMatcher myExtensionMatcher;
|
||||
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
super.before();
|
||||
|
||||
myExtensionMatcher = new ExtensionMatcher();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPatientWithMatchingExtension(){
|
||||
Patient patient1 = new Patient();
|
||||
|
@ -19,7 +31,7 @@ public class ExtensionMatcherR4Test extends BaseMatcherR4Test {
|
|||
patient1.addExtension("asd",new StringType("Patient1"));
|
||||
patient2.addExtension("asd",new StringType("Patient1"));
|
||||
|
||||
assertTrue(MdmMatcherEnum.EXTENSION_ANY_ORDER.match(ourFhirContext, patient1, patient2, false, null));
|
||||
assertTrue(match(patient1, patient2));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -30,7 +42,7 @@ public class ExtensionMatcherR4Test extends BaseMatcherR4Test {
|
|||
patient1.addExtension("asd",new StringType("Patient1"));
|
||||
patient2.addExtension("asd",new StringType("Patient2"));
|
||||
|
||||
assertFalse(MdmMatcherEnum.EXTENSION_ANY_ORDER.match(ourFhirContext, patient1, patient2, false, null));
|
||||
assertFalse(match(patient1, patient2));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -41,7 +53,7 @@ public class ExtensionMatcherR4Test extends BaseMatcherR4Test {
|
|||
patient1.addExtension("asd",new StringType("Patient1"));
|
||||
patient2.addExtension("asd1",new StringType("Patient1"));
|
||||
|
||||
assertFalse(MdmMatcherEnum.EXTENSION_ANY_ORDER.match(ourFhirContext, patient1, patient2, false, null));
|
||||
assertFalse(match(patient1, patient2));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -54,7 +66,7 @@ public class ExtensionMatcherR4Test extends BaseMatcherR4Test {
|
|||
patient2.addExtension("asd",new StringType("Patient1"));
|
||||
patient2.addExtension("asdasd", new StringType("some value"));
|
||||
|
||||
assertTrue(MdmMatcherEnum.EXTENSION_ANY_ORDER.match(ourFhirContext, patient1, patient2, false, null));
|
||||
assertTrue(match(patient1, patient2));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -65,7 +77,7 @@ public class ExtensionMatcherR4Test extends BaseMatcherR4Test {
|
|||
patient1.addExtension("asd", new IntegerType(123));
|
||||
patient2.addExtension("asd", new IntegerType(123));
|
||||
|
||||
assertTrue(MdmMatcherEnum.EXTENSION_ANY_ORDER.match(ourFhirContext, patient1, patient2, false, null));
|
||||
assertTrue(match(patient1, patient2));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -73,7 +85,10 @@ public class ExtensionMatcherR4Test extends BaseMatcherR4Test {
|
|||
Patient patient1 = new Patient();
|
||||
Patient patient2 = new Patient();
|
||||
|
||||
assertFalse(MdmMatcherEnum.EXTENSION_ANY_ORDER.match(ourFhirContext, patient1, patient2, false, null));
|
||||
assertFalse(match(patient1, patient2));
|
||||
}
|
||||
|
||||
private boolean match(IBase theFirst, IBase theSecond) {
|
||||
return myExtensionMatcher.matches(theFirst, theSecond, myMdmMatcherJson);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
package ca.uhn.fhir.mdm.rules.matcher;
|
||||
|
||||
import ca.uhn.fhir.mdm.rules.json.MdmFieldMatchJson;
|
||||
import ca.uhn.fhir.mdm.rules.json.MdmMatcherJson;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.fieldmatchers.IdentifierMatcher;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.r4.model.Identifier;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
|
@ -14,15 +15,20 @@ public class IdentifierMatcherR4Test extends BaseMatcherR4Test {
|
|||
private static final String MATCHING_VALUE = "matchme";
|
||||
private static final String OTHER_VALUE = "strange";
|
||||
|
||||
private IdentifierMatcher myIdentifierMatcher;
|
||||
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
super.before();
|
||||
myIdentifierMatcher = new IdentifierMatcher();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIdentifierMatch() {
|
||||
Identifier left = new Identifier().setSystem(MATCHING_SYSTEM).setValue(MATCHING_VALUE);
|
||||
Identifier right = new Identifier().setSystem(MATCHING_SYSTEM).setValue(MATCHING_VALUE);
|
||||
|
||||
MdmMatcherJson matcher = new MdmMatcherJson().setAlgorithm(MdmMatcherEnum.IDENTIFIER);
|
||||
MdmFieldMatchJson fieldMatch = new MdmFieldMatchJson().setMatcher(matcher);
|
||||
|
||||
assertTrue(fieldMatch.match(ourFhirContext, left, right).match);
|
||||
assertTrue(match(left, right));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -33,17 +39,15 @@ public class IdentifierMatcherR4Test extends BaseMatcherR4Test {
|
|||
Identifier rightNoSystem = new Identifier().setValue(MATCHING_VALUE);
|
||||
Identifier rightNoValue = new Identifier().setSystem(MATCHING_SYSTEM);
|
||||
|
||||
MdmMatcherJson matcher = new MdmMatcherJson().setAlgorithm(MdmMatcherEnum.IDENTIFIER);
|
||||
MdmFieldMatchJson fieldMatch = new MdmFieldMatchJson().setMatcher(matcher);
|
||||
|
||||
assertFalse(fieldMatch.match(ourFhirContext, left, rightWrongSystem).match);
|
||||
assertFalse(fieldMatch.match(ourFhirContext, left, rightWrongValue).match);
|
||||
assertFalse(fieldMatch.match(ourFhirContext, left, rightNoSystem).match);
|
||||
assertFalse(fieldMatch.match(ourFhirContext, left, rightNoValue).match);
|
||||
assertFalse(fieldMatch.match(ourFhirContext, rightWrongSystem, left).match);
|
||||
assertFalse(fieldMatch.match(ourFhirContext, rightWrongValue, left).match);
|
||||
assertFalse(fieldMatch.match(ourFhirContext, rightNoSystem, left).match);
|
||||
assertFalse(fieldMatch.match(ourFhirContext, rightNoValue, left).match);
|
||||
assertFalse(match(left, rightWrongSystem));
|
||||
assertFalse(match(left, rightWrongValue));
|
||||
assertFalse(match(left, rightNoSystem));
|
||||
assertFalse(match(left, rightNoValue));
|
||||
assertFalse(match(rightWrongSystem, left));
|
||||
assertFalse(match(rightWrongValue, left));
|
||||
assertFalse(match(rightNoSystem, left));
|
||||
assertFalse(match(rightNoValue, left));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -51,10 +55,9 @@ public class IdentifierMatcherR4Test extends BaseMatcherR4Test {
|
|||
Identifier left = new Identifier().setSystem(MATCHING_SYSTEM);
|
||||
Identifier right = new Identifier().setSystem(MATCHING_SYSTEM);
|
||||
|
||||
MdmMatcherJson matcher = new MdmMatcherJson().setAlgorithm(MdmMatcherEnum.IDENTIFIER).setIdentifierSystem(MATCHING_SYSTEM);
|
||||
MdmFieldMatchJson fieldMatch = new MdmFieldMatchJson().setMatcher(matcher);
|
||||
myMdmMatcherJson.setIdentifierSystem(MATCHING_SYSTEM);
|
||||
|
||||
assertFalse(fieldMatch.match(ourFhirContext, left, right).match);
|
||||
assertFalse(match(left, right));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -62,10 +65,9 @@ public class IdentifierMatcherR4Test extends BaseMatcherR4Test {
|
|||
Identifier left = new Identifier().setSystem(MATCHING_SYSTEM).setValue(MATCHING_VALUE);
|
||||
Identifier right = new Identifier().setSystem(MATCHING_SYSTEM).setValue(MATCHING_VALUE);
|
||||
|
||||
MdmMatcherJson matcher = new MdmMatcherJson().setAlgorithm(MdmMatcherEnum.IDENTIFIER).setIdentifierSystem(MATCHING_SYSTEM);
|
||||
MdmFieldMatchJson fieldMatch = new MdmFieldMatchJson().setMatcher(matcher);
|
||||
myMdmMatcherJson.setIdentifierSystem(MATCHING_SYSTEM);
|
||||
|
||||
assertTrue(fieldMatch.match(ourFhirContext, left, right).match);
|
||||
assertTrue(match(left, right));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -73,9 +75,12 @@ public class IdentifierMatcherR4Test extends BaseMatcherR4Test {
|
|||
Identifier left = new Identifier().setSystem(OTHER_SYSTEM).setValue(MATCHING_VALUE);
|
||||
Identifier right = new Identifier().setSystem(OTHER_SYSTEM).setValue(MATCHING_VALUE);
|
||||
|
||||
MdmMatcherJson matcher = new MdmMatcherJson().setAlgorithm(MdmMatcherEnum.IDENTIFIER).setIdentifierSystem(MATCHING_SYSTEM);
|
||||
MdmFieldMatchJson fieldMatch = new MdmFieldMatchJson().setMatcher(matcher);
|
||||
myMdmMatcherJson.setIdentifierSystem(MATCHING_SYSTEM);
|
||||
|
||||
assertFalse(fieldMatch.match(ourFhirContext, left, right).match);
|
||||
assertFalse(match(left, right));
|
||||
}
|
||||
|
||||
private boolean match(IBase theFirst, IBase theSecond) {
|
||||
return myIdentifierMatcher.matches(theFirst, theSecond, myMdmMatcherJson);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,31 +1,48 @@
|
|||
package ca.uhn.fhir.mdm.rules.matcher;
|
||||
|
||||
import ca.uhn.fhir.jpa.nickname.NicknameSvc;
|
||||
import ca.uhn.fhir.mdm.rules.json.MdmMatcherJson;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.fieldmatchers.NicknameMatcher;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.models.IMdmFieldMatcher;
|
||||
import org.hl7.fhir.r4.model.StringType;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class NicknameMatcherTest {
|
||||
IMdmStringMatcher matcher = new NicknameMatcher();
|
||||
IMdmFieldMatcher matcher;
|
||||
|
||||
NicknameSvc myNicknameSvc = new NicknameSvc();
|
||||
|
||||
@BeforeEach
|
||||
public void begin() {
|
||||
matcher = new NicknameMatcher(myNicknameSvc);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatches() {
|
||||
assertTrue(matcher.matches("Ken", "ken"));
|
||||
assertTrue(matcher.matches("ken", "Ken"));
|
||||
assertTrue(matcher.matches("Ken", "Ken"));
|
||||
assertTrue(matcher.matches("Kenneth", "Ken"));
|
||||
assertTrue(matcher.matches("Kenneth", "Kenny"));
|
||||
assertTrue(matcher.matches("Ken", "Kenneth"));
|
||||
assertTrue(matcher.matches("Kenny", "Kenneth"));
|
||||
assertTrue(matcher.matches("Jim", "Jimmy"));
|
||||
assertTrue(matcher.matches("Jimmy", "Jim"));
|
||||
assertTrue(matcher.matches("Jim", "James"));
|
||||
assertTrue(matcher.matches("Jimmy", "James"));
|
||||
assertTrue(matcher.matches("James", "Jimmy"));
|
||||
assertTrue(matcher.matches("James", "Jim"));
|
||||
Assertions.assertTrue(match("Ken", "ken"));
|
||||
Assertions.assertTrue(match("ken", "Ken"));
|
||||
Assertions.assertTrue(match("Ken", "Ken"));
|
||||
Assertions.assertTrue(match("Kenneth", "Ken"));
|
||||
Assertions.assertTrue(match("Kenneth", "Kenny"));
|
||||
Assertions.assertTrue(match("Ken", "Kenneth"));
|
||||
Assertions.assertTrue(match("Kenny", "Kenneth"));
|
||||
Assertions.assertTrue(match("Jim", "Jimmy"));
|
||||
Assertions.assertTrue(match("Jimmy", "Jim"));
|
||||
Assertions.assertTrue(match("Jim", "James"));
|
||||
Assertions.assertTrue(match("Jimmy", "James"));
|
||||
Assertions.assertTrue(match("James", "Jimmy"));
|
||||
Assertions.assertTrue(match("James", "Jim"));
|
||||
|
||||
assertFalse(matcher.matches("Ken", "Bob"));
|
||||
Assertions.assertFalse(match("Ken", "Bob"));
|
||||
// These aren't nickname matches. If you want matches like these use a phonetic matcher
|
||||
assertFalse(matcher.matches("Allen", "Allan"));
|
||||
Assertions.assertFalse(match("Allen", "Allan"));
|
||||
}
|
||||
|
||||
private boolean match(String theFirst, String theSecond) {
|
||||
MdmMatcherJson json = new MdmMatcherJson();
|
||||
json.setExact(true);
|
||||
return matcher.matches(new StringType(theFirst), new StringType(theSecond), json);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,106 +1,140 @@
|
|||
package ca.uhn.fhir.mdm.rules.matcher;
|
||||
|
||||
import ca.uhn.fhir.jpa.nickname.NicknameSvc;
|
||||
import ca.uhn.fhir.mdm.api.IMdmSettings;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.models.IMdmFieldMatcher;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.models.MatchTypeEnum;
|
||||
import org.hl7.fhir.r4.model.BooleanType;
|
||||
import org.hl7.fhir.r4.model.DateType;
|
||||
import org.hl7.fhir.r4.model.Enumeration;
|
||||
import org.hl7.fhir.r4.model.Enumerations;
|
||||
import org.hl7.fhir.r4.model.StringType;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
public class StringMatcherR4Test extends BaseMatcherR4Test {
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(StringMatcherR4Test.class);
|
||||
public static final String LEFT_NAME = "namadega";
|
||||
public static final String RIGHT_NAME = "namaedga";
|
||||
|
||||
private IMatcherFactory myIMatcherFactory;
|
||||
|
||||
private IMdmSettings myMdmSettings;
|
||||
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
super.before();
|
||||
|
||||
myMdmSettings = mock(IMdmSettings.class);
|
||||
|
||||
myIMatcherFactory = new MdmMatcherFactory(
|
||||
ourFhirContext,
|
||||
myMdmSettings,
|
||||
new NicknameSvc()
|
||||
);
|
||||
}
|
||||
|
||||
private @Nonnull IMdmFieldMatcher getFieldMatcher(MatchTypeEnum theMatchTypeEnum) {
|
||||
return myIMatcherFactory.getFieldMatcherForMatchType(theMatchTypeEnum);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNamadega() {
|
||||
String left = LEFT_NAME;
|
||||
String right = RIGHT_NAME;
|
||||
assertTrue(match(MdmMatcherEnum.COLOGNE, left, right));
|
||||
assertTrue(match(MdmMatcherEnum.DOUBLE_METAPHONE, left, right));
|
||||
assertTrue(match(MdmMatcherEnum.MATCH_RATING_APPROACH, left, right));
|
||||
assertTrue(match(MdmMatcherEnum.METAPHONE, left, right));
|
||||
assertTrue(match(MdmMatcherEnum.SOUNDEX, left, right));
|
||||
assertTrue(match(MdmMatcherEnum.METAPHONE, left, right));
|
||||
assertTrue(match(MatchTypeEnum.COLOGNE, left, right));
|
||||
assertTrue(match(MatchTypeEnum.DOUBLE_METAPHONE, left, right));
|
||||
assertTrue(match(MatchTypeEnum.MATCH_RATING_APPROACH, left, right));
|
||||
assertTrue(match(MatchTypeEnum.METAPHONE, left, right));
|
||||
assertTrue(match(MatchTypeEnum.SOUNDEX, left, right));
|
||||
|
||||
assertFalse(match(MdmMatcherEnum.CAVERPHONE1, left, right));
|
||||
assertFalse(match(MdmMatcherEnum.CAVERPHONE2, left, right));
|
||||
assertFalse(match(MdmMatcherEnum.NYSIIS, left, right));
|
||||
assertFalse(match(MdmMatcherEnum.REFINED_SOUNDEX, left, right));
|
||||
assertFalse(match(MdmMatcherEnum.STRING, left, right));
|
||||
assertFalse(match(MdmMatcherEnum.SUBSTRING, left, right));
|
||||
assertFalse(match(MatchTypeEnum.CAVERPHONE1, left, right));
|
||||
assertFalse(match(MatchTypeEnum.CAVERPHONE2, left, right));
|
||||
assertFalse(match(MatchTypeEnum.NYSIIS, left, right));
|
||||
assertFalse(match(MatchTypeEnum.REFINED_SOUNDEX, left, right));
|
||||
assertFalse(match(MatchTypeEnum.STRING, left, right));
|
||||
assertFalse(match(MatchTypeEnum.SUBSTRING, left, right));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNumeric() {
|
||||
assertTrue(match(MdmMatcherEnum.NUMERIC, "4169671111", "(416) 967-1111"));
|
||||
assertFalse(match(MdmMatcherEnum.NUMERIC, "5169671111", "(416) 967-1111"));
|
||||
assertFalse(match(MdmMatcherEnum.NUMERIC, "4169671111", "(416) 967-1111x123"));
|
||||
assertTrue(match(MatchTypeEnum.NUMERIC, "4169671111", "(416) 967-1111"));
|
||||
assertFalse(match(MatchTypeEnum.NUMERIC, "5169671111", "(416) 967-1111"));
|
||||
assertFalse(match(MatchTypeEnum.NUMERIC, "4169671111", "(416) 967-1111x123"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMetaphone() {
|
||||
assertTrue(match(MdmMatcherEnum.METAPHONE, "Durie", "dury"));
|
||||
assertTrue(match(MdmMatcherEnum.METAPHONE, "Balo", "ballo"));
|
||||
assertTrue(match(MdmMatcherEnum.METAPHONE, "Hans Peter", "Hanspeter"));
|
||||
assertTrue(match(MdmMatcherEnum.METAPHONE, "Lawson", "Law son"));
|
||||
assertTrue(match(MatchTypeEnum.METAPHONE, "Durie", "dury"));
|
||||
assertTrue(match(MatchTypeEnum.METAPHONE, "Balo", "ballo"));
|
||||
assertTrue(match(MatchTypeEnum.METAPHONE, "Hans Peter", "Hanspeter"));
|
||||
assertTrue(match(MatchTypeEnum.METAPHONE, "Lawson", "Law son"));
|
||||
|
||||
assertFalse(match(MdmMatcherEnum.METAPHONE, "Allsop", "Allsob"));
|
||||
assertFalse(match(MdmMatcherEnum.METAPHONE, "Gevne", "Geve"));
|
||||
assertFalse(match(MdmMatcherEnum.METAPHONE, "Bruce", "Bruch"));
|
||||
assertFalse(match(MdmMatcherEnum.METAPHONE, "Smith", "Schmidt"));
|
||||
assertFalse(match(MdmMatcherEnum.METAPHONE, "Jyothi", "Jyoti"));
|
||||
assertFalse(match(MatchTypeEnum.METAPHONE, "Allsop", "Allsob"));
|
||||
assertFalse(match(MatchTypeEnum.METAPHONE, "Gevne", "Geve"));
|
||||
assertFalse(match(MatchTypeEnum.METAPHONE, "Bruce", "Bruch"));
|
||||
assertFalse(match(MatchTypeEnum.METAPHONE, "Smith", "Schmidt"));
|
||||
assertFalse(match(MatchTypeEnum.METAPHONE, "Jyothi", "Jyoti"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoubleMetaphone() {
|
||||
assertTrue(match(MdmMatcherEnum.DOUBLE_METAPHONE, "Durie", "dury"));
|
||||
assertTrue(match(MdmMatcherEnum.DOUBLE_METAPHONE, "Balo", "ballo"));
|
||||
assertTrue(match(MdmMatcherEnum.DOUBLE_METAPHONE, "Hans Peter", "Hanspeter"));
|
||||
assertTrue(match(MdmMatcherEnum.DOUBLE_METAPHONE, "Lawson", "Law son"));
|
||||
assertTrue(match(MdmMatcherEnum.DOUBLE_METAPHONE, "Allsop", "Allsob"));
|
||||
assertTrue(match(MatchTypeEnum.DOUBLE_METAPHONE, "Durie", "dury"));
|
||||
assertTrue(match(MatchTypeEnum.DOUBLE_METAPHONE, "Balo", "ballo"));
|
||||
assertTrue(match(MatchTypeEnum.DOUBLE_METAPHONE, "Hans Peter", "Hanspeter"));
|
||||
assertTrue(match(MatchTypeEnum.DOUBLE_METAPHONE, "Lawson", "Law son"));
|
||||
assertTrue(match(MatchTypeEnum.DOUBLE_METAPHONE, "Allsop", "Allsob"));
|
||||
|
||||
assertFalse(match(MdmMatcherEnum.DOUBLE_METAPHONE, "Gevne", "Geve"));
|
||||
assertFalse(match(MdmMatcherEnum.DOUBLE_METAPHONE, "Bruce", "Bruch"));
|
||||
assertFalse(match(MdmMatcherEnum.DOUBLE_METAPHONE, "Smith", "Schmidt"));
|
||||
assertFalse(match(MdmMatcherEnum.DOUBLE_METAPHONE, "Jyothi", "Jyoti"));
|
||||
assertFalse(match(MatchTypeEnum.DOUBLE_METAPHONE, "Gevne", "Geve"));
|
||||
assertFalse(match(MatchTypeEnum.DOUBLE_METAPHONE, "Bruce", "Bruch"));
|
||||
assertFalse(match(MatchTypeEnum.DOUBLE_METAPHONE, "Smith", "Schmidt"));
|
||||
assertFalse(match(MatchTypeEnum.DOUBLE_METAPHONE, "Jyothi", "Jyoti"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNormalizeCase() {
|
||||
assertTrue(match(MdmMatcherEnum.STRING, "joe", "JoE"));
|
||||
assertTrue(match(MdmMatcherEnum.STRING, "MCTAVISH", "McTavish"));
|
||||
assertTrue(match(MatchTypeEnum.STRING, "joe", "JoE"));
|
||||
assertTrue(match(MatchTypeEnum.STRING, "MCTAVISH", "McTavish"));
|
||||
|
||||
assertFalse(match(MdmMatcherEnum.STRING, "joey", "joe"));
|
||||
assertFalse(match(MdmMatcherEnum.STRING, "joe", "joey"));
|
||||
assertFalse(match(MatchTypeEnum.STRING, "joey", "joe"));
|
||||
assertFalse(match(MatchTypeEnum.STRING, "joe", "joey"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExactString() {
|
||||
assertTrue(MdmMatcherEnum.STRING.match(ourFhirContext, new StringType("Jilly"), new StringType("Jilly"), true, null));
|
||||
myMdmMatcherJson.setExact(true);
|
||||
|
||||
assertFalse(MdmMatcherEnum.STRING.match(ourFhirContext, new StringType("MCTAVISH"), new StringType("McTavish"), true, null));
|
||||
assertFalse(MdmMatcherEnum.STRING.match(ourFhirContext, new StringType("Durie"), new StringType("dury"), true, null));
|
||||
assertTrue(getFieldMatcher(MatchTypeEnum.STRING).matches(new StringType("Jilly"), new StringType("Jilly"), myMdmMatcherJson));
|
||||
|
||||
assertFalse(getFieldMatcher(MatchTypeEnum.STRING).matches(new StringType("MCTAVISH"), new StringType("McTavish"), myMdmMatcherJson));
|
||||
assertFalse(getFieldMatcher(MatchTypeEnum.STRING).matches(new StringType("Durie"), new StringType("dury"), myMdmMatcherJson));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExactBoolean() {
|
||||
assertTrue(MdmMatcherEnum.STRING.match(ourFhirContext, new BooleanType(true), new BooleanType(true), true, null));
|
||||
myMdmMatcherJson.setExact(true);
|
||||
|
||||
assertFalse(MdmMatcherEnum.STRING.match(ourFhirContext, new BooleanType(true), new BooleanType(false), true, null));
|
||||
assertFalse(MdmMatcherEnum.STRING.match(ourFhirContext, new BooleanType(false), new BooleanType(true), true, null));
|
||||
assertTrue(getFieldMatcher(MatchTypeEnum.STRING).matches(new BooleanType(true), new BooleanType(true), myMdmMatcherJson));
|
||||
|
||||
assertFalse(getFieldMatcher(MatchTypeEnum.STRING).matches(new BooleanType(true), new BooleanType(false), myMdmMatcherJson));
|
||||
assertFalse(getFieldMatcher(MatchTypeEnum.STRING).matches(new BooleanType(false), new BooleanType(true), myMdmMatcherJson));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExactDateString() {
|
||||
assertTrue(MdmMatcherEnum.STRING.match(ourFhirContext, new DateType("1965-08-09"), new DateType("1965-08-09"), true, null));
|
||||
myMdmMatcherJson.setExact(true);
|
||||
|
||||
assertFalse(MdmMatcherEnum.STRING.match(ourFhirContext, new DateType("1965-08-09"), new DateType("1965-09-08"), true, null));
|
||||
assertTrue(getFieldMatcher(MatchTypeEnum.STRING).matches(new DateType("1965-08-09"), new DateType("1965-08-09"), myMdmMatcherJson));
|
||||
|
||||
assertFalse(getFieldMatcher(MatchTypeEnum.STRING).matches(new DateType("1965-08-09"), new DateType("1965-09-08"), myMdmMatcherJson));
|
||||
}
|
||||
|
||||
|
||||
|
@ -112,52 +146,55 @@ public class StringMatcherR4Test extends BaseMatcherR4Test {
|
|||
Enumeration<Enumerations.AdministrativeGender> female = new Enumeration<Enumerations.AdministrativeGender>(new Enumerations.AdministrativeGenderEnumFactory());
|
||||
female.setValue(Enumerations.AdministrativeGender.FEMALE);
|
||||
|
||||
assertTrue(MdmMatcherEnum.STRING.match(ourFhirContext, male, male, true, null));
|
||||
myMdmMatcherJson.setExact(true);
|
||||
|
||||
assertFalse(MdmMatcherEnum.STRING.match(ourFhirContext, male, female, true, null));
|
||||
assertTrue(getFieldMatcher(MatchTypeEnum.STRING).matches(male, male, myMdmMatcherJson));
|
||||
|
||||
assertFalse(getFieldMatcher(MatchTypeEnum.STRING).matches(male, female, myMdmMatcherJson));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSoundex() {
|
||||
assertTrue(match(MdmMatcherEnum.SOUNDEX, "Gail", "Gale"));
|
||||
assertTrue(match(MdmMatcherEnum.SOUNDEX, "John", "Jon"));
|
||||
assertTrue(match(MdmMatcherEnum.SOUNDEX, "Thom", "Tom"));
|
||||
assertTrue(match(MatchTypeEnum.SOUNDEX, "Gail", "Gale"));
|
||||
assertTrue(match(MatchTypeEnum.SOUNDEX, "John", "Jon"));
|
||||
assertTrue(match(MatchTypeEnum.SOUNDEX, "Thom", "Tom"));
|
||||
|
||||
assertFalse(match(MdmMatcherEnum.SOUNDEX, "Fred", "Frank"));
|
||||
assertFalse(match(MdmMatcherEnum.SOUNDEX, "Thomas", "Tom"));
|
||||
assertFalse(match(MatchTypeEnum.SOUNDEX, "Fred", "Frank"));
|
||||
assertFalse(match(MatchTypeEnum.SOUNDEX, "Thomas", "Tom"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testCaverphone1() {
|
||||
assertTrue(match(MdmMatcherEnum.CAVERPHONE1, "Gail", "Gael"));
|
||||
assertTrue(match(MdmMatcherEnum.CAVERPHONE1, "John", "Jon"));
|
||||
assertTrue(match(MatchTypeEnum.CAVERPHONE1, "Gail", "Gael"));
|
||||
assertTrue(match(MatchTypeEnum.CAVERPHONE1, "John", "Jon"));
|
||||
|
||||
assertFalse(match(MdmMatcherEnum.CAVERPHONE1, "Gail", "Gale"));
|
||||
assertFalse(match(MdmMatcherEnum.CAVERPHONE1, "Fred", "Frank"));
|
||||
assertFalse(match(MdmMatcherEnum.CAVERPHONE1, "Thomas", "Tom"));
|
||||
assertFalse(match(MatchTypeEnum.CAVERPHONE1, "Gail", "Gale"));
|
||||
assertFalse(match(MatchTypeEnum.CAVERPHONE1, "Fred", "Frank"));
|
||||
assertFalse(match(MatchTypeEnum.CAVERPHONE1, "Thomas", "Tom"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCaverphone2() {
|
||||
assertTrue(match(MdmMatcherEnum.CAVERPHONE2, "Gail", "Gael"));
|
||||
assertTrue(match(MdmMatcherEnum.CAVERPHONE2, "John", "Jon"));
|
||||
assertTrue(match(MdmMatcherEnum.CAVERPHONE2, "Gail", "Gale"));
|
||||
assertTrue(match(MatchTypeEnum.CAVERPHONE2, "Gail", "Gael"));
|
||||
assertTrue(match(MatchTypeEnum.CAVERPHONE2, "John", "Jon"));
|
||||
assertTrue(match(MatchTypeEnum.CAVERPHONE2, "Gail", "Gale"));
|
||||
|
||||
assertFalse(match(MdmMatcherEnum.CAVERPHONE2, "Fred", "Frank"));
|
||||
assertFalse(match(MdmMatcherEnum.CAVERPHONE2, "Thomas", "Tom"));
|
||||
assertFalse(match(MatchTypeEnum.CAVERPHONE2, "Fred", "Frank"));
|
||||
assertFalse(match(MatchTypeEnum.CAVERPHONE2, "Thomas", "Tom"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNormalizeSubstring() {
|
||||
assertTrue(match(MdmMatcherEnum.SUBSTRING, "BILLY", "Bill"));
|
||||
assertTrue(match(MdmMatcherEnum.SUBSTRING, "Bill", "Billy"));
|
||||
assertTrue(match(MdmMatcherEnum.SUBSTRING, "FRED", "Frederik"));
|
||||
assertTrue(match(MatchTypeEnum.SUBSTRING, "BILLY", "Bill"));
|
||||
assertTrue(match(MatchTypeEnum.SUBSTRING, "Bill", "Billy"));
|
||||
assertTrue(match(MatchTypeEnum.SUBSTRING, "FRED", "Frederik"));
|
||||
|
||||
assertFalse(match(MdmMatcherEnum.SUBSTRING, "Fred", "Friederik"));
|
||||
assertFalse(match(MatchTypeEnum.SUBSTRING, "Fred", "Friederik"));
|
||||
}
|
||||
|
||||
private boolean match(MdmMatcherEnum theMatcher, String theLeft, String theRight) {
|
||||
return theMatcher.match(ourFhirContext, new StringType(theLeft), new StringType(theRight), false, null);
|
||||
private boolean match(MatchTypeEnum theMatcher, String theLeft, String theRight) {
|
||||
return getFieldMatcher(theMatcher)
|
||||
.matches(new StringType(theLeft), new StringType(theRight), myMdmMatcherJson);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ public abstract class BaseMdmRulesR4Test extends BaseR4Test {
|
|||
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
super.before();
|
||||
myMdmRulesJson = new MdmRulesJson();
|
||||
|
||||
ArrayList<String> myLegalMdmTypes = new ArrayList<>();
|
||||
|
|
|
@ -6,7 +6,7 @@ import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
|
|||
import ca.uhn.fhir.mdm.rules.json.MdmFieldMatchJson;
|
||||
import ca.uhn.fhir.mdm.rules.json.MdmMatcherJson;
|
||||
import ca.uhn.fhir.mdm.rules.json.MdmRulesJson;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.MdmMatcherEnum;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.models.MatchTypeEnum;
|
||||
import org.hl7.fhir.r4.model.HumanName;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
|
@ -20,7 +20,7 @@ import static org.mockito.Mockito.when;
|
|||
|
||||
public class CustomResourceMatcherR4Test extends BaseR4Test {
|
||||
|
||||
public static final String FIELD_EXACT_MATCH_NAME = MdmMatcherEnum.NAME_ANY_ORDER.name();
|
||||
public static final String FIELD_EXACT_MATCH_NAME = MatchTypeEnum.NAME_ANY_ORDER.name();
|
||||
private static Patient ourJohnHenry;
|
||||
private static Patient ourJohnHENRY;
|
||||
private static Patient ourJaneHenry;
|
||||
|
@ -32,6 +32,7 @@ public class CustomResourceMatcherR4Test extends BaseR4Test {
|
|||
|
||||
@BeforeEach
|
||||
public void before() {
|
||||
super.before();
|
||||
when(mySearchParamRetriever.getActiveSearchParam("Patient", "identifier")).thenReturn(mock(RuntimeSearchParam.class));
|
||||
when(mySearchParamRetriever.getActiveSearchParam("Practitioner", "identifier")).thenReturn(mock(RuntimeSearchParam.class));
|
||||
when(mySearchParamRetriever.getActiveSearchParam("Medication", "identifier")).thenReturn(mock(RuntimeSearchParam.class));
|
||||
|
@ -40,7 +41,8 @@ public class CustomResourceMatcherR4Test extends BaseR4Test {
|
|||
|
||||
@Test
|
||||
public void testExactNameAnyOrder() {
|
||||
MdmResourceMatcherSvc nameAnyOrderMatcher = buildMatcher(buildNameRules(MdmMatcherEnum.NAME_ANY_ORDER, true));
|
||||
MdmResourceMatcherSvc nameAnyOrderMatcher = buildMatcher(buildNameRules(MatchTypeEnum.NAME_ANY_ORDER, true));
|
||||
|
||||
assertMatch(MdmMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnHenry));
|
||||
assertMatch(MdmMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourHenryJohn));
|
||||
assertMatch(MdmMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourHenryJOHN));
|
||||
|
@ -53,7 +55,8 @@ public class CustomResourceMatcherR4Test extends BaseR4Test {
|
|||
|
||||
@Test
|
||||
public void testNormalizedNameAnyOrder() {
|
||||
MdmResourceMatcherSvc nameAnyOrderMatcher = buildMatcher(buildNameRules(MdmMatcherEnum.NAME_ANY_ORDER, false));
|
||||
MdmResourceMatcherSvc nameAnyOrderMatcher = buildMatcher(buildNameRules(MatchTypeEnum.NAME_ANY_ORDER, false));
|
||||
|
||||
assertMatch(MdmMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnHenry));
|
||||
assertMatch(MdmMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourHenryJohn));
|
||||
assertMatch(MdmMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourHenryJOHN));
|
||||
|
@ -66,7 +69,8 @@ public class CustomResourceMatcherR4Test extends BaseR4Test {
|
|||
|
||||
@Test
|
||||
public void testExactNameFirstAndLast() {
|
||||
MdmResourceMatcherSvc nameAnyOrderMatcher = buildMatcher(buildNameRules(MdmMatcherEnum.NAME_FIRST_AND_LAST, true));
|
||||
MdmResourceMatcherSvc nameAnyOrderMatcher = buildMatcher(buildNameRules(MatchTypeEnum.NAME_FIRST_AND_LAST, true));
|
||||
|
||||
assertMatch(MdmMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnHenry));
|
||||
assertMatchResult(MdmMatchResultEnum.MATCH, 1L, 1.0, false, false, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnHenry));
|
||||
assertMatch(MdmMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourHenryJohn));
|
||||
|
@ -80,7 +84,8 @@ public class CustomResourceMatcherR4Test extends BaseR4Test {
|
|||
|
||||
@Test
|
||||
public void testNormalizedNameFirstAndLast() {
|
||||
MdmResourceMatcherSvc nameAnyOrderMatcher = buildMatcher(buildNameRules(MdmMatcherEnum.NAME_FIRST_AND_LAST, false));
|
||||
MdmResourceMatcherSvc nameAnyOrderMatcher = buildMatcher(buildNameRules(MatchTypeEnum.NAME_FIRST_AND_LAST, false));
|
||||
|
||||
assertMatch(MdmMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourJohnHenry));
|
||||
assertMatch(MdmMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourHenryJohn));
|
||||
assertMatch(MdmMatchResultEnum.NO_MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourHenryJOHN));
|
||||
|
@ -91,7 +96,7 @@ public class CustomResourceMatcherR4Test extends BaseR4Test {
|
|||
assertMatch(MdmMatchResultEnum.MATCH, nameAnyOrderMatcher.match(ourJohnHenry, ourBillyJohnHenry));
|
||||
}
|
||||
|
||||
private MdmRulesJson buildNameRules(MdmMatcherEnum theAlgorithm, boolean theExact) {
|
||||
private MdmRulesJson buildNameRules(MatchTypeEnum theAlgorithm, boolean theExact) {
|
||||
MdmMatcherJson matcherJson = new MdmMatcherJson().setAlgorithm(theAlgorithm).setExact(theExact);
|
||||
MdmFieldMatchJson nameAnyOrderFieldMatch = new MdmFieldMatchJson()
|
||||
.setName(FIELD_EXACT_MATCH_NAME)
|
||||
|
|
|
@ -6,7 +6,7 @@ import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
|
|||
import ca.uhn.fhir.mdm.rules.json.MdmFieldMatchJson;
|
||||
import ca.uhn.fhir.mdm.rules.json.MdmMatcherJson;
|
||||
import ca.uhn.fhir.mdm.rules.json.MdmRulesJson;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.MdmMatcherEnum;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.models.MatchTypeEnum;
|
||||
import org.hl7.fhir.r4.model.HumanName;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
@ -46,10 +46,9 @@ public class FhirPathResourceMatcherR4Test extends BaseMdmRulesR4Test {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testFhirPathOrderedMatches() {
|
||||
MdmResourceMatcherSvc matcherSvc = buildMatcher(buildOrderedGivenNameRules(MdmMatcherEnum.STRING));
|
||||
MdmResourceMatcherSvc matcherSvc = buildMatcher(buildOrderedGivenNameRules(MatchTypeEnum.STRING));
|
||||
|
||||
myLeft = new Patient();
|
||||
HumanName name = myLeft.addName();
|
||||
|
@ -63,6 +62,7 @@ public class FhirPathResourceMatcherR4Test extends BaseMdmRulesR4Test {
|
|||
name2.addGiven("Gary");
|
||||
myRight.setId("Patient/2");
|
||||
|
||||
// test
|
||||
MdmMatchOutcome result = matcherSvc.match(myLeft, myRight);
|
||||
assertMatchResult(MdmMatchResultEnum.NO_MATCH, 0L, 0.0, false, false, result);
|
||||
|
||||
|
@ -85,12 +85,12 @@ public class FhirPathResourceMatcherR4Test extends BaseMdmRulesR4Test {
|
|||
|
||||
@Test
|
||||
public void testStringMatchResult() {
|
||||
MdmResourceMatcherSvc matcherSvc = buildMatcher(buildOrderedGivenNameRules(MdmMatcherEnum.STRING));
|
||||
MdmResourceMatcherSvc matcherSvc = buildMatcher(buildOrderedGivenNameRules(MatchTypeEnum.STRING));
|
||||
MdmMatchOutcome result = matcherSvc.match(myLeft, myRight);
|
||||
assertMatchResult(MdmMatchResultEnum.NO_MATCH, 0L, 0.0, false, false, result);
|
||||
}
|
||||
|
||||
protected MdmRulesJson buildOrderedGivenNameRules(MdmMatcherEnum theMatcherEnum) {
|
||||
protected MdmRulesJson buildOrderedGivenNameRules(MatchTypeEnum theMatcherEnum) {
|
||||
MdmFieldMatchJson firstGivenNameMatchField = new MdmFieldMatchJson()
|
||||
.setName(PATIENT_GIVEN_FIRST)
|
||||
.setResourceType("Patient")
|
||||
|
|
|
@ -3,10 +3,8 @@ package ca.uhn.fhir.mdm.rules.svc;
|
|||
import ca.uhn.fhir.mdm.rules.json.MdmFieldMatchJson;
|
||||
import ca.uhn.fhir.mdm.rules.json.MdmMatcherJson;
|
||||
import ca.uhn.fhir.mdm.rules.json.MdmRulesJson;
|
||||
import ca.uhn.fhir.mdm.rules.json.MdmSimilarityJson;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.MdmMatcherEnum;
|
||||
import ca.uhn.fhir.mdm.rules.similarity.MdmSimilarityEnum;
|
||||
import ca.uhn.fhir.parser.DataFormatException;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.fieldmatchers.EmptyFieldMatcher;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.models.MatchTypeEnum;
|
||||
import org.hl7.fhir.r4.model.Encounter;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
@ -14,12 +12,12 @@ import org.junit.jupiter.api.Test;
|
|||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.core.StringStartsWith.startsWith;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class MdmResourceFieldMatcherR4Test extends BaseMdmRulesR4Test {
|
||||
protected MdmResourceFieldMatcher myComparator;
|
||||
|
@ -30,7 +28,13 @@ public class MdmResourceFieldMatcherR4Test extends BaseMdmRulesR4Test {
|
|||
@BeforeEach
|
||||
public void before() {
|
||||
super.before();
|
||||
myComparator = new MdmResourceFieldMatcher(ourFhirContext, myGivenNameMatchField, myMdmRulesJson);
|
||||
|
||||
myComparator = new MdmResourceFieldMatcher(
|
||||
ourFhirContext,
|
||||
myIMatcherFactory,
|
||||
myGivenNameMatchField,
|
||||
myMdmRulesJson
|
||||
);
|
||||
myJohn = buildJohn();
|
||||
myJohny = buildJohny();
|
||||
}
|
||||
|
@ -44,8 +48,13 @@ public class MdmResourceFieldMatcherR4Test extends BaseMdmRulesR4Test {
|
|||
.setName("empty-given")
|
||||
.setResourceType("Patient")
|
||||
.setResourcePath("name.given")
|
||||
.setMatcher(new MdmMatcherJson().setAlgorithm(MdmMatcherEnum.EMPTY_FIELD));
|
||||
myComparator = new MdmResourceFieldMatcher(ourFhirContext, myGivenNameMatchField, myMdmRulesJson);
|
||||
.setMatcher(new MdmMatcherJson().setAlgorithm(MatchTypeEnum.EMPTY_FIELD));
|
||||
myComparator = new MdmResourceFieldMatcher(
|
||||
ourFhirContext,
|
||||
myIMatcherFactory,
|
||||
myGivenNameMatchField,
|
||||
myMdmRulesJson
|
||||
);
|
||||
|
||||
assertFalse(myComparator.match(myJohn, myJohny).match);
|
||||
|
||||
|
@ -90,6 +99,9 @@ public class MdmResourceFieldMatcherR4Test extends BaseMdmRulesR4Test {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO - what is this supposed to test?
|
||||
// it relies on matcher being null (is this a reasonable assumption?)
|
||||
// and falls through to similarity check
|
||||
@Test
|
||||
public void testMatch() {
|
||||
assertTrue(myComparator.match(myJohn, myJohny).match);
|
||||
|
|
|
@ -36,7 +36,6 @@ public class MdmResourceMatcherSvcR4Test extends BaseMdmRulesR4Test {
|
|||
public void testCompareFirstNameMatch() {
|
||||
MdmMatchOutcome result = myMdmResourceMatcherSvc.match(myJohn, myJohny);
|
||||
assertMatchResult(MdmMatchResultEnum.POSSIBLE_MATCH, 1L, 0.816, false, false, result);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -6,7 +6,7 @@ import ca.uhn.fhir.mdm.api.MdmMatchResultEnum;
|
|||
import ca.uhn.fhir.mdm.rules.json.MdmFieldMatchJson;
|
||||
import ca.uhn.fhir.mdm.rules.json.MdmMatcherJson;
|
||||
import ca.uhn.fhir.mdm.rules.json.MdmRulesJson;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.MdmMatcherEnum;
|
||||
import ca.uhn.fhir.mdm.rules.matcher.models.MatchTypeEnum;
|
||||
import org.hl7.fhir.r4.model.HumanName;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
@ -55,19 +55,21 @@ public class ResourceMatcherR4Test extends BaseMdmRulesR4Test {
|
|||
|
||||
@Test
|
||||
public void testMetaphoneMatchResult() {
|
||||
MdmResourceMatcherSvc matcherSvc = buildMatcher(buildNamePhoneRules(MdmMatcherEnum.METAPHONE));
|
||||
MdmMatchOutcome result = matcherSvc.match(myLeft, myRight);
|
||||
assertMatchResult(MdmMatchResultEnum.MATCH, 7L, 3.0, false, false, result);
|
||||
MdmResourceMatcherSvc matcherSvc = buildMatcher(buildNamePhoneRules(MatchTypeEnum.METAPHONE));
|
||||
|
||||
MdmMatchOutcome result = matcherSvc.match(myLeft, myRight);
|
||||
assertMatchResult(MdmMatchResultEnum.MATCH, 7L, 3.0, false, false, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStringMatchResult() {
|
||||
MdmResourceMatcherSvc matcherSvc = buildMatcher(buildNamePhoneRules(MdmMatcherEnum.STRING));
|
||||
MdmResourceMatcherSvc matcherSvc = buildMatcher(buildNamePhoneRules(MatchTypeEnum.STRING));
|
||||
|
||||
MdmMatchOutcome result = matcherSvc.match(myLeft, myRight);
|
||||
assertMatchResult(MdmMatchResultEnum.NO_MATCH, 5L, 2.0, false, false, result);
|
||||
}
|
||||
|
||||
protected MdmRulesJson buildNamePhoneRules(MdmMatcherEnum theMatcherEnum) {
|
||||
protected MdmRulesJson buildNamePhoneRules(MatchTypeEnum theMatcherEnum) {
|
||||
MdmFieldMatchJson lastNameMatchField = new MdmFieldMatchJson()
|
||||
.setName(PATIENT_FAMILY)
|
||||
.setResourceType("Patient")
|
||||
|
@ -84,7 +86,7 @@ public class ResourceMatcherR4Test extends BaseMdmRulesR4Test {
|
|||
.setName(PATIENT_PHONE)
|
||||
.setResourceType("Patient")
|
||||
.setResourcePath("telecom.value")
|
||||
.setMatcher(new MdmMatcherJson().setAlgorithm(MdmMatcherEnum.STRING));
|
||||
.setMatcher(new MdmMatcherJson().setAlgorithm(MatchTypeEnum.STRING));
|
||||
|
||||
MdmRulesJson retval = new MdmRulesJson();
|
||||
retval.setVersion("test version");
|
||||
|
|
|
@ -55,11 +55,6 @@
|
|||
<artifactId>junit-jupiter-params</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
|
|
Loading…
Reference in New Issue