dp-zp-agent/tools/crzp/answering/names.py
2026-11-15 03:36:33 +01:00

60 lines
2.8 KiB
Python

"""Base forms of inflected Slovak names, used only without the model.
CRZP matches whole words, so "Jána Ptáka" cannot be found as written. Without
the model only the surname is searched, in a few candidate nominatives (most
likely first); the caller keeps the first candidate whose rows really contain
the asked person, because the row filter compares the stems of all asked
words (first name included). A wrong guess therefore finds nothing.
"""
from __future__ import annotations
from tools.crzp.source.parse import fold
# ending -> nominative replacements in order of preference (lowercase).
_RULES = [
("ovej", ["ová"]), ("ovou", ["ová"]), ("ovú", ["ová"]), # Novákovej -> Nováková
("skej", ["ská"]), ("ckej", ["cká"]), ("skou", ["ská"]), # Hronskej -> Hronská
("ského", ["ský"]), ("ckého", ["cký"]), ("ého", ["ý"]), ("ému", ["ý"]), ("ým", ["ý"]), # Malého -> Malý
("ovi", ["", "a"]), ("om", ["", "a"]), ("ou", ["a"]), # Ptákovi -> Pták, Plevom -> Pleva
("a", [""]), ("u", ["a", ""]), ("y", ["a"]), ("e", ["a", ""]), # Ptáka -> Pták, Galajdu -> Galajda
]
MAX_CANDIDATES = 3
def word_forms(word: str) -> list[str]:
"""Candidate nominatives of one name word, most likely first, the word itself last."""
low, forms = word.lower(), []
for ending, replacements in _RULES:
if low.endswith(ending) and len(low) - len(ending) >= 2:
for replacement in replacements:
base = low[: len(low) - len(ending)] + replacement
if replacement == "" and len(base) >= 3 and base[-1] in "kcrl" and base[-2] not in "aeiouyáéíóúýô":
forms += [base[:-1] + "e" + base[-1], base[:-1] + "o" + base[-1]] # Hládka -> Hládek, Pavla -> Pavol
forms.append(base)
break
forms.append(low)
seen, result = set(), []
for form in forms:
if len(form) >= 2 and fold(form) not in seen:
seen.add(fold(form))
result.append(form[:1].upper() + form[1:] if word[:1].isupper() else form)
return result
def surname_variants(name: str) -> list[str]:
"""Search candidates for the surname of an inflected name: "Daniela Hládka" -> ["Hládek", ...]."""
words = name.split()
return word_forms(words[-1])[:MAX_CANDIDATES] if words else []
def name_variants(name: str) -> list[str]:
"""Search texts for an inflected name, most likely first: the likeliest first
name with each surname candidate, then each surname candidate alone (the
first name is still checked in the rows, e.g. "Pavla" matches "Pavol")."""
words = name.split()
surnames = surname_variants(name)
if len(words) < 2:
return surnames
first = word_forms(words[0])[0]
return list(dict.fromkeys([f"{first} {s}" for s in surnames] + surnames))