T2: NameNormalizer testy + transliteratePolish, token-strip titles [checkpoint]
This commit is contained in:
+40
-8
@@ -20,12 +20,31 @@ public class NameNormalizer {
|
||||
public String makeSlug(String fullName) {
|
||||
String normalized = Normalizer.normalize(fullName, Normalizer.Form.NFKD);
|
||||
normalized = normalized.replaceAll("\\p{M}", "");
|
||||
normalized = normalized.replaceAll("[^a-zA-Z0-9\\s-]", " ");
|
||||
normalized = normalized.toLowerCase().replaceAll("\\s+", "-").replaceAll("-+", "-");
|
||||
normalized = normalized.replaceAll("[^\\p{L}\\s-]", "");
|
||||
normalized = transliteratePolish(normalized.toLowerCase());
|
||||
normalized = normalized.replaceAll("\\s+", "-").replaceAll("-+", "-");
|
||||
normalized = normalized.replaceAll("^-|-$", "");
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private String transliteratePolish(String input) {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map.put("ł", "l"); map.put("Ł", "l");
|
||||
map.put("ę", "e"); map.put("Ĕ", "e");
|
||||
map.put("ó", "o"); map.put("Ō", "o");
|
||||
map.put("ą", "a"); map.put("Ą", "a");
|
||||
map.put("ś", "s"); map.put("Ś", "s");
|
||||
map.put("ź", "z"); map.put("Ź", "z");
|
||||
map.put("ż", "z"); map.put("Ż", "z");
|
||||
map.put("ć", "c"); map.put("Ć", "c");
|
||||
map.put("ń", "n"); map.put("Ń", "n");
|
||||
String result = input;
|
||||
for (Map.Entry<String, String> entry : map.entrySet()) {
|
||||
result = result.replace(entry.getKey(), entry.getValue());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public String extractFullName(String rawName) {
|
||||
return extractGivenAndFamilyNames(rawName);
|
||||
}
|
||||
@@ -62,12 +81,25 @@ public class NameNormalizer {
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
static final List<String> TITLE_PREFIXES = List.of(
|
||||
"prof. dr hab.", "prof.dr hab.", "prof. dr.", "prof. dr",
|
||||
"dr hab.", "dr.hab.", "dr. hab.", "dr.hab",
|
||||
"mgr inż.", "mgr inż", "mgr.inż.", "mgr.inż",
|
||||
"prof.", "prof", "dr.", "dr",
|
||||
"mgr.", "mgr", "inż.", "inż",
|
||||
"lek.", "lek.med.", "n. med.", "n. o zdr.",
|
||||
"licencjat inż.", "licencjat inż"
|
||||
);
|
||||
|
||||
private String extractGivenAndFamilyNames(String rawName) {
|
||||
Pattern pattern = Pattern.compile(
|
||||
"(?i)(prof\\.\\s*|dr\\s*hab\\.\\s*|dr\\.\\s*|mgr\\s*|in\u017c\\.\\s*)",
|
||||
Pattern.MULTILINE | Pattern.DOTALL
|
||||
);
|
||||
Matcher matcher = pattern.matcher(rawName);
|
||||
return matcher.replaceAll("").trim();
|
||||
String name = rawName.trim();
|
||||
String lower = name.toLowerCase();
|
||||
for (String prefix : TITLE_PREFIXES) {
|
||||
if (lower.startsWith(prefix.toLowerCase())) {
|
||||
name = name.substring(prefix.length()).trim();
|
||||
lower = name.toLowerCase();
|
||||
}
|
||||
}
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
package com.developx.szpitale.ingest.normalize;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class NameNormalizerTest {
|
||||
|
||||
private final NameNormalizer normalizer = new NameNormalizer();
|
||||
|
||||
@Test
|
||||
void normalize_polishDiacritics_strippedToAsciiSlug() {
|
||||
String slug = normalizer.makeSlug("Łukasz Żółć");
|
||||
assertEquals("lukasz-zolc", slug);
|
||||
}
|
||||
|
||||
@Test
|
||||
void normalize_slug_lowercaseSeparator() {
|
||||
String slug = normalizer.makeSlug("Jan Kowalski");
|
||||
assertEquals("jan-kowalski", slug);
|
||||
}
|
||||
|
||||
@Test
|
||||
void normalize_slug_multipleSpacesToOne() {
|
||||
String slug = normalizer.makeSlug("Jan Kowalski");
|
||||
assertEquals("jan-kowalski", slug);
|
||||
}
|
||||
|
||||
@Test
|
||||
void normalize_slug_removesSpecialChars() {
|
||||
String slug = normalizer.makeSlug("Dr. Jan Kowalski Jr.");
|
||||
assertEquals("dr-jan-kowalski-jr", slug);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource({
|
||||
"'prof. dr hab. Jan Kochanowicz', 'Jan Kochanowicz'",
|
||||
"'dr Jan Nowak', 'Jan Nowak'",
|
||||
"'mgr inż. Anna Kowalska', 'Anna Kowalska'",
|
||||
"'Jan Wiśniewski', 'Jan Wiśniewski'",
|
||||
"'prof. Wojciech Łuczaj', 'Wojciech Łuczaj'"
|
||||
})
|
||||
void normalize_displayName_keepsOriginal(String rawName, String expectedFullName) {
|
||||
String fullName = normalizer.extractFullName(rawName);
|
||||
assertEquals(expectedFullName, fullName);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource({
|
||||
"'prof. dr hab. Jan Kochanowicz', 'prof.'",
|
||||
"'prof. dr hab. Jan Kochanowicz', 'dr hab.'",
|
||||
"'dr hab. n. med. Jan Wiśniewski', 'dr hab.'",
|
||||
"'dr hab. n. med. Jan Wiśniewski', 'n. med.'",
|
||||
"'mgr inż. Anna Kowalska', 'mgr inż.'"
|
||||
})
|
||||
void normalize_titlesContainExpected(String rawName, String expectedTitle) {
|
||||
List<String> titles = normalizer.extractTitles(rawName);
|
||||
assertTrue(titles.contains(expectedTitle), "Expected title '" + expectedTitle + "' not found in " + titles);
|
||||
}
|
||||
|
||||
@Test
|
||||
void normalize_titlesEmptyForPlainName() {
|
||||
List<String> titles = normalizer.extractTitles("Jan Nowak");
|
||||
assertTrue(titles.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void normalize_emptyTitlesWhenNoTitles() {
|
||||
List<String> titles = normalizer.extractTitles("Jan Kowalski");
|
||||
assertTrue(titles.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void makePersonId_createsPersonSlug() {
|
||||
String id = normalizer.makePersonId("Jan Kowalski");
|
||||
assertEquals("person:jan-kowalski", id);
|
||||
}
|
||||
|
||||
@Test
|
||||
void makePerson_idUsesSameSlugAsMakeSlug() {
|
||||
String id = normalizer.makePersonId("Łukasz Żółć");
|
||||
assertEquals("person:lukasz-zolc", id);
|
||||
}
|
||||
|
||||
@Test
|
||||
void makeHospitalId_createsHospitalSlug() {
|
||||
String id = normalizer.makeHospitalId("podlaskie", "Uniwersytecki Szpital Kliniczny");
|
||||
assertEquals("hosp:podlaskie:uniwersytecki-szpital-kliniczny", id);
|
||||
}
|
||||
|
||||
@Test
|
||||
void fuzzySimilarity_identicalStringsReturn1() {
|
||||
double similarity = normalizer.fuzzySimilarity("Jan Kowalski", "Jan Kowalski");
|
||||
assertEquals(1.0, similarity, 0.0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void fuzzySimilarity_typoReturnsHigh() {
|
||||
double similarity = normalizer.fuzzySimilarity("Jan Kowalski", "Jan Kowaski");
|
||||
assertTrue(similarity > 0.9, "Typo 'Kowalski' vs 'Kowaski' should have very high similarity: " + similarity);
|
||||
}
|
||||
|
||||
@Test
|
||||
void fuzzySimilarity_differentNamesReturnLower() {
|
||||
double similarity = normalizer.fuzzySimilarity("Jan Kowalski", "Anna Nowak");
|
||||
assertTrue(similarity < 0.7, "Two completely different names should have moderate similarity: " + similarity);
|
||||
}
|
||||
|
||||
@Test
|
||||
void fuzzySimilarity_handlesPolishCharacters() {
|
||||
double similarity = normalizer.fuzzySimilarity("Jan Kowalski", "Jan Kowalscy");
|
||||
assertTrue(similarity > 0.8, "Names differing only by suffix should have high similarity: " + similarity);
|
||||
}
|
||||
|
||||
@Test
|
||||
void findDuplicateCandidates_returnsCandidatesAboveThreshold() {
|
||||
List<String> names = List.of("Jan Kowalski", "Jan Kowaski", "Anna Nowak", "Jan Nowak");
|
||||
List<String> candidates = normalizer.findDuplicateCandidates(names, "Jan Kowalski", 0.7);
|
||||
assertTrue(candidates.contains("Jan Kowaski"), "Should contain similar name");
|
||||
}
|
||||
|
||||
@Test
|
||||
void findDuplicateCandidates_filtersTargetName() {
|
||||
List<String> names = List.of("Jan Kowalski");
|
||||
List<String> candidates = normalizer.findDuplicateCandidates(names, "Jan Kowalski", 0.7);
|
||||
assertTrue(candidates.isEmpty(), "Should not return the target name itself");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user