"""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))