Pre-publication review draft
日常訳NICHIJOYAKU日本語 ↗

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

software/src/pipeline/morphology.py164 linesDownload this file ↓

This is the same source included in the download. Each line number is a link you can save or share.

File verification detailsSHA-256 8adf618bf62bc57c3d545a4ea47a5891f2d13e07bb4edf68900afb056c4889e4
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.6263    MorphGNT ``parsing`` has 8 slots: person, tense, voice, mood, case,64    number, gender, degree.65    """66    if pos.strip("-") != "V" or len(parsing) < 6:67        return None68    person, tense, voice, mood = parsing[0], parsing[1], parsing[2], parsing[3]69    case, number = parsing[4], parsing[5]7071    bits = []72    if tense in _GREEK_TENSE:73        bits.append(_GREEK_TENSE[tense])74    if voice in _GREEK_VOICE:75        bits.append(_GREEK_VOICE[voice])76    if mood in _GREEK_MOOD:77        bits.append(_GREEK_MOOD[mood])78    if mood == "P":  # participles: case/number matter for construal79        if case in _GREEK_CASE:80            bits.append(_GREEK_CASE[case])81        if number in _GREEK_NUMBER:82            bits.append(_GREEK_NUMBER[number])83    elif person in "123" and number in _GREEK_NUMBER:84        bits.append(f"{person}{'sg' if number == 'S' else 'pl'}")8586    if not bits:87        return None88    return f"{word} (lemma {lemma}): {' '.join(bits)}"899091def _greek_priority(parsing: str) -> int:92    mood = parsing[3] if len(parsing) > 3 else "-"93    tense = parsing[1] if len(parsing) > 1 else "-"94    if mood in _GREEK_PRIORITY_MOODS:95        return 096    if tense in _GREEK_PRIORITY_TENSES:97        return 198    return 299100101def decode_hebrew(word: str, morph: str) -> str | None:102    """Decode an OSHB verb entry (e.g. ``HVqw3ms`` or ``HVqrmsc/Sp1cs``)."""103    segments = morph.split("/")104    main = next((s.lstrip("H") for s in segments if s.lstrip("H").startswith("V")), None)105    if main is None or len(main) < 3:106        return None107    stem, conj = main[1], main[2]108109    bits = []110    if stem in _HEBREW_STEM:111        bits.append(_HEBREW_STEM[stem])112    if conj in _HEBREW_CONJ:113        bits.append(_HEBREW_CONJ[conj])114    if not bits:115        return None116    rest = main[3:6]117    if rest and rest[0] in "123":118        bits.append(rest)119    if any(s.startswith("Sp") for s in segments):120        suffix = next(s for s in segments if s.startswith("Sp"))121        bits.append(f"+ pronominal suffix {suffix[2:]}")122    return f"{word}: {' '.join(bits)}"123124125def _hebrew_priority(morph: str) -> int:126    main = next((s.lstrip("H") for s in morph.split("/") if s.lstrip("H").startswith("V")), "")127    conj = main[2] if len(main) > 2 else "-"128    if conj in "rsvhj":  # participles, imperatives, volitives129        return 0130    return 1131132133def select_morphology_notes(134    words: list[dict] | None, language: str, limit: int = 6135) -> list[str]:136    """Select and decode the most disambiguation-relevant verb forms.137138    Args:139        words: Word entries from the source JSON.140        language: "greek" or "hebrew".141        limit: Maximum number of notes.142143    Returns:144        Decoded notes, highest-priority first, capped at ``limit``.145    """146    if not words:147        return []148149    candidates: list[tuple[int, str]] = []150    for w in words:151        if language == "greek":152            note = decode_greek(153                w.get("word", ""), w.get("lemma", ""), w.get("pos", ""), w.get("parsing", "")154            )155            if note:156                candidates.append((_greek_priority(w.get("parsing", "")), note))157        else:158            note = decode_hebrew(w.get("word", ""), w.get("morph", ""))159            if note:160                candidates.append((_hebrew_priority(w.get("morph", "")), note))161162    candidates.sort(key=lambda c: c[0])163    return [note for _, note in candidates[:limit]]164