improve nickname matcher (#3628)

* improve nickname matcher

* change log

* review feedback and fix test
This commit is contained in:
Ken Stevens 2022-05-24 13:13:09 -04:00 committed by GitHub
parent 1c70e97087
commit df76b0f8f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 51 additions and 26 deletions

View File

@ -0,0 +1,5 @@
---
type: fix
issue: 3628
title: "Nickname matching only matched formal names to a nickname. This has been broadened to match nicknames to other
nicknames for the same formal name."

View File

@ -30,6 +30,7 @@ import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
@ -70,7 +71,7 @@ public class NicknameInterceptor {
toRemove.add(stringParam); toRemove.add(stringParam);
//First, attempt to expand as a formal name //First, attempt to expand as a formal name
String name = stringParam.getValue().toLowerCase(Locale.ROOT); String name = stringParam.getValue().toLowerCase(Locale.ROOT);
List<String> expansions = myNicknameSvc.getEquivalentNames(name); Collection<String> expansions = myNicknameSvc.getEquivalentNames(name);
if (expansions == null) { if (expansions == null) {
continue; continue;
} }

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.searchparam.nickname;
* #L% * #L%
*/ */
import javax.annotation.Nonnull;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.Reader; import java.io.Reader;
@ -57,11 +58,15 @@ class NicknameMap {
return myFormalToNick.size(); return myFormalToNick.size();
} }
List<String> getNicknamesFromFormalNameOrNull(String theName) { @Nonnull
return myFormalToNick.get(theName); List<String> getNicknamesFromFormalName(String theName) {
List<String> result = myFormalToNick.get(theName);
return result == null ? new ArrayList<>() : result;
} }
List<String> getFormalNamesFromNicknameOrNull(String theNickname) { @Nonnull
return myNicknameToFormal.get(theNickname); List<String> getFormalNamesFromNickname(String theNickname) {
List<String> result = myNicknameToFormal.get(theNickname);
return result == null ? new ArrayList<>() : result;
} }
} }

View File

@ -23,12 +23,15 @@ package ca.uhn.fhir.jpa.searchparam.nickname;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import javax.annotation.Nonnull;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.Reader; import java.io.Reader;
import java.util.ArrayList; import java.util.Collection;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
public class NicknameSvc { public class NicknameSvc {
private final NicknameMap myNicknameMap = new NicknameMap(); private final NicknameMap myNicknameMap = new NicknameMap();
@ -46,28 +49,27 @@ public class NicknameSvc {
return myNicknameMap.size(); return myNicknameMap.size();
} }
public List<String> getEquivalentNames(String theName) { public Collection<String> getEquivalentNames(String theName) {
List<String> retval = new ArrayList<>(); Set<String> retval = new HashSet<>(getNicknamesFromFormalName(theName));
retval.add(theName);
List<String> expansions; if (retval.isEmpty()) {
expansions = getNicknamesFromFormalNameOrNull(theName); List<String> formalNames = getFormalNamesFromNickname(theName);
if (expansions != null) { retval.addAll(formalNames);
retval.addAll(expansions); for (String formalName : formalNames) {
} else { retval.addAll(getNicknamesFromFormalName(formalName));
expansions = getFormalNamesFromNicknameOrNull(theName);
if (expansions != null) {
retval.addAll(expansions);
} }
} }
retval.add(theName);
return retval; return retval;
} }
List<String> getNicknamesFromFormalNameOrNull(String theName) { @Nonnull
return myNicknameMap.getNicknamesFromFormalNameOrNull(theName); List<String> getNicknamesFromFormalName(String theName) {
return myNicknameMap.getNicknamesFromFormalName(theName);
} }
List<String> getFormalNamesFromNicknameOrNull(String theNickname) { @Nonnull
return myNicknameMap.getFormalNamesFromNicknameOrNull(theNickname); List<String> getFormalNamesFromNickname(String theNickname) {
return myNicknameMap.getFormalNamesFromNickname(theNickname);
} }
} }

View File

@ -38,7 +38,7 @@ class NicknameInterceptorTest {
// verify // verify
String newSearch = sp.toNormalizedQueryString(null); String newSearch = sp.toNormalizedQueryString(null);
assertEquals("?name=ken,kendall,kendrick,kendrik,kenneth,kenny,kent,mckenna", newSearch); assertEquals("?name=ken,kendall,kendrick,kendrik,kenna,kenneth,kenny,kent,mckenna,meaka", newSearch);
} }
@Test @Test

View File

@ -24,7 +24,7 @@ kent,ken,kenny,kendrick
NicknameMap map = new NicknameMap(); NicknameMap map = new NicknameMap();
map.load(new StringReader(testData)); map.load(new StringReader(testData));
assertEquals(7, map.size()); assertEquals(7, map.size());
assertThat(map.getNicknamesFromFormalNameOrNull("kenneth"), containsInAnyOrder("ken", "kenny", "kendrick")); assertThat(map.getNicknamesFromFormalName("kenneth"), containsInAnyOrder("ken", "kenny", "kendrick"));
assertThat(map.getFormalNamesFromNicknameOrNull("ken"), containsInAnyOrder("kendall", "kendrick", "kendrik", "kenneth", "kenny", "kent")); assertThat(map.getFormalNamesFromNickname("ken"), containsInAnyOrder("kendall", "kendrick", "kendrik", "kenneth", "kenny", "kent"));
} }
} }

View File

@ -25,6 +25,7 @@ import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.jpa.searchparam.nickname.NicknameSvc; import ca.uhn.fhir.jpa.searchparam.nickname.NicknameSvc;
import java.io.IOException; import java.io.IOException;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@ -44,7 +45,12 @@ public class NicknameMatcher implements IMdmStringMatcher {
String leftString = theLeftString.toLowerCase(Locale.ROOT); String leftString = theLeftString.toLowerCase(Locale.ROOT);
String rightString = theRightString.toLowerCase(Locale.ROOT); String rightString = theRightString.toLowerCase(Locale.ROOT);
List<String> leftNames = myNicknameSvc.getEquivalentNames(leftString); Collection<String> leftNames = myNicknameSvc.getEquivalentNames(leftString);
return leftNames.contains(rightString); if (leftNames.contains(rightString)) {
return true;
}
Collection<String> rightNames = myNicknameSvc.getEquivalentNames(rightString);
return rightNames.contains(leftString);
} }
} }

View File

@ -17,6 +17,12 @@ class NicknameMatcherTest {
assertTrue(matcher.matches("Kenneth", "Kenny")); assertTrue(matcher.matches("Kenneth", "Kenny"));
assertTrue(matcher.matches("Ken", "Kenneth")); assertTrue(matcher.matches("Ken", "Kenneth"));
assertTrue(matcher.matches("Kenny", "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"));
assertFalse(matcher.matches("Ken", "Bob")); assertFalse(matcher.matches("Ken", "Bob"));
// These aren't nickname matches. If you want matches like these use a phonetic matcher // These aren't nickname matches. If you want matches like these use a phonetic matcher