OPEN SOURCE
software/src/pipeline/morphology.py
Read on this site and download the files you need.
Nichijoyaku was created using AI. A complete review by human biblical-language and biblical-studies specialists has not been established. Read alongside established translations and original-language resources.
← Back to the open-source guide
1"""Selective morphology notes for translation prompts.23The MorphGNT (Greek) and OSHB/MorphHB (Hebrew) sources carry word-level4parse codes. Dumping them all would be noise; verbs are where parsing5actually disambiguates the Japanese rendering (tense/aspect, voice, mood,6participle constructions, wayyiqtol chains), so we decode verb forms only,7prioritize the forms most likely to be mis-rendered, and cap the count.8"""910from __future__ import annotations1112_GREEK_TENSE = {13"P": "present",14"I": "imperfect",15"F": "future",16"A": "aorist",17"X": "perfect",18"Y": "pluperfect",19}20_GREEK_VOICE = {"A": "active", "M": "middle", "P": "passive"}21_GREEK_MOOD = {22"I": "indicative",23"D": "imperative",24"S": "subjunctive",25"O": "optative",26"N": "infinitive",27"P": "participle",28}29_GREEK_CASE = {"N": "nominative", "G": "genitive", "D": "dative", "A": "accusative", "V": "vocative"}30_GREEK_NUMBER = {"S": "singular", "P": "plural"}3132# Non-indicative moods and marked tenses are where renderings go wrong.33_GREEK_PRIORITY_MOODS = {"P", "D", "S", "N", "O"}34_GREEK_PRIORITY_TENSES = {"I", "X", "Y", "F"}3536_HEBREW_STEM = {37"q": "qal",38"N": "niphal",39"p": "piel",40"P": "pual",41"h": "hiphil",42"H": "hophal",43"t": "hithpael",44}45_HEBREW_CONJ = {46"p": "perfect (qatal)",47"q": "sequential perfect (weqatal)",48"i": "imperfect (yiqtol)",49"w": "wayyiqtol (narrative past)",50"h": "cohortative",51"j": "jussive",52"v": "imperative",53"r": "active participle",54"s": "passive participle",55"a": "infinitive absolute",56"c": "infinitive construct",57}585960def decode_greek(word: str, lemma: str, pos: str, parsing: str) -> str | None:61"""Decode a MorphGNT verb entry to a short human-readable note.6263MorphGNT ``parsing`` has 8 slots: person, tense, voice, mood, case,64number, gender, degree.65"""66if pos.strip("-") != "V" or len(parsing) < 6:67return None68person, tense, voice, mood = parsing[0], parsing[1], parsing[2], parsing[3]69case, number = parsing[4], parsing[5]7071bits = []72if tense in _GREEK_TENSE:73bits.append(_GREEK_TENSE[tense])74if voice in _GREEK_VOICE:75bits.append(_GREEK_VOICE[voice])76if mood in _GREEK_MOOD:77bits.append(_GREEK_MOOD[mood])78if mood == "P": # participles: case/number matter for construal79if case in _GREEK_CASE:80bits.append(_GREEK_CASE[case])81if number in _GREEK_NUMBER:82bits.append(_GREEK_NUMBER[number])83elif person in "123" and number in _GREEK_NUMBER:84bits.append(f"{person}{'sg' if number == 'S' else 'pl'}")8586if not bits:87return None88return f"{word} (lemma {lemma}): {' '.join(bits)}"899091def _greek_priority(parsing: str) -> int:92mood = parsing[3] if len(parsing) > 3 else "-"93tense = parsing[1] if len(parsing) > 1 else "-"94if mood in _GREEK_PRIORITY_MOODS:95return 096if tense in _GREEK_PRIORITY_TENSES:97return 198return 299100101def decode_hebrew(word: str, morph: str) -> str | None:102"""Decode an OSHB verb entry (e.g. ``HVqw3ms`` or ``HVqrmsc/Sp1cs``)."""103segments = morph.split("/")104main = next((s.lstrip("H") for s in segments if s.lstrip("H").startswith("V")), None)105if main is None or len(main) < 3:106return None107stem, conj = main[1], main[2]108109bits = []110if stem in _HEBREW_STEM:111bits.append(_HEBREW_STEM[stem])112if conj in _HEBREW_CONJ:113bits.append(_HEBREW_CONJ[conj])114if not bits:115return None116rest = main[3:6]117if rest and rest[0] in "123":118bits.append(rest)119if any(s.startswith("Sp") for s in segments):120suffix = next(s for s in segments if s.startswith("Sp"))121bits.append(f"+ pronominal suffix {suffix[2:]}")122return f"{word}: {' '.join(bits)}"123124125def _hebrew_priority(morph: str) -> int:126main = next((s.lstrip("H") for s in morph.split("/") if s.lstrip("H").startswith("V")), "")127conj = main[2] if len(main) > 2 else "-"128if conj in "rsvhj": # participles, imperatives, volitives129return 0130return 1131132133def select_morphology_notes(134words: list[dict] | None, language: str, limit: int = 6135) -> list[str]:136"""Select and decode the most disambiguation-relevant verb forms.137138Args:139words: Word entries from the source JSON.140language: "greek" or "hebrew".141limit: Maximum number of notes.142143Returns:144Decoded notes, highest-priority first, capped at ``limit``.145"""146if not words:147return []148149candidates: list[tuple[int, str]] = []150for w in words:151if language == "greek":152note = decode_greek(153w.get("word", ""), w.get("lemma", ""), w.get("pos", ""), w.get("parsing", "")154)155if note:156candidates.append((_greek_priority(w.get("parsing", "")), note))157else:158note = decode_hebrew(w.get("word", ""), w.get("morph", ""))159if note:160candidates.append((_hebrew_priority(w.get("morph", "")), note))161162candidates.sort(key=lambda c: c[0])163return [note for _, note in candidates[:limit]]164