feat(person): add name-match tokenizer for direct matching (#763)

Lowercase, split on whitespace/hyphen/apostrophe, drop empties. Applied
symmetrically to query and candidate name components so "Anna-Maria" and
"Anna Maria" tokenize alike. Foundation for resolveByName direct matching.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-06-07 00:49:52 +02:00
committed by marcel
parent 0fe0ae5235
commit 9c616f9fb8
2 changed files with 54 additions and 0 deletions

View File

@@ -1,8 +1,12 @@
package org.raddatz.familienarchiv.person;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import org.springframework.lang.Nullable;
@@ -24,9 +28,11 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.server.ResponseStatusException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Service
@RequiredArgsConstructor
@Slf4j
public class PersonService {
private final PersonRepository personRepository;
@@ -103,6 +109,22 @@ public class PersonService {
return personRepository.searchByName(fragment);
}
// Name-match tokenizer (issue #763): lowercase, split on whitespace/hyphen/apostrophe,
// drop empties. Applied symmetrically to the query and to every candidate name component so
// that "Anna-Maria" and "Anna Maria" tokenize alike. Order-preserving for deterministic tests.
static Set<String> tokenize(String raw) {
if (raw == null || raw.isBlank()) {
return Set.of();
}
LinkedHashSet<String> tokens = new LinkedHashSet<>();
for (String part : raw.toLowerCase(Locale.ROOT).split("[\\s\\-']+")) {
if (!part.isEmpty()) {
tokens.add(part);
}
}
return tokens;
}
public List<Person> findAllFamilyMembers() {
return personRepository.findByFamilyMemberTrueOrderByLastNameAscFirstNameAsc();
}