公開前の確認用サイト
日常訳NICHIJOYAKUEnglish ↗

OPEN SOURCE

software/scripts/report_terminology.py

このサイトで読み、必要なファイルを保存できます。

日常訳はAIを用いて作成された翻訳です。人間の聖書学・原語の専門家による全訳の検証は確認されていません。既存の翻訳や原語資料と照らし合わせてお読みください。

← オープンソースの案内へ

software/scripts/report_terminology.py138 行このファイルを保存 ↓

ZIPに含まれるものと同じ公開ソースです。行番号から、その行へのリンクを保存できます。

ファイルの確認情報SHA-256 ffcf74049f9aba8a6663d61c64428039c42acabe7e4ccda36fc81be8b4d9b8ca
1"""Cross-book terminology consistency report.23For every translated verse of a book, finds which glossary terms occur in the4source text and whether the translation uses the glossary's Japanese rendering5(or a discouraged one). Aggregates per-term so drift is visible at a glance —6e.g. λόγος rendered 言葉 in twelve verses but 言 in two.78Purely mechanical (no LLM calls). Usage:910    uv run python scripts/report_terminology.py <book> [<book> ...]11"""1213from __future__ import annotations1415import json16import sys17from pathlib import Path1819sys.path.insert(0, str(Path(__file__).parent.parent))2021from src.context.glossary import GlossaryManager  # noqa: E40222from src.data.loader import DataLoader  # noqa: E40223from src.models.verse import VerseReference  # noqa: E402242526_VERB_ENDINGS = "るすうくぐつぬぶむ"272829def _rendering_present(japanese: str, text: str) -> bool:30    """Check a glossary rendering against text, tolerating verb conjugation.3132    義と認める won't literally appear in 義と認められる and 罪を犯す won't33    appear in 罪を犯し — for renderings ending in a dictionary-form verb34    ending, also accept the stem (rendering minus the final kana).35    """36    if japanese in text:37        return True38    if len(japanese) > 2 and japanese[-1] in _VERB_ENDINGS and japanese[:-1] in text:39        return True40    return False414243def _accepted_renderings(term) -> list[str]:44    """All acceptable renderings: the primary plus semantic_range alternates."""45    return [term.japanese] + [46        alt for alt in getattr(term, "semantic_range", []) if alt47    ]484950def report_book(book: str, data_dir: Path) -> int:51    """Print the terminology report for one book. Returns issue count."""52    loader = DataLoader(data_dir)53    glossary = GlossaryManager(loader.load_glossary())5455    output_dir = data_dir / "output" / book56    if not output_dir.exists():57        print(f"[{book}] no translations found")58        return 05960    # term.original -> {"used": [refs], "missing": [refs], "avoided": [(ref, bad)]}61    stats: dict[str, dict] = {}6263    for chapter_file in sorted(output_dir.glob("chapter_*.json")):64        if chapter_file.stem.endswith("_eval"):65            continue66        data = json.loads(chapter_file.read_text(encoding="utf-8"))67        chapter = data["chapter"]68        for verse_num, vdata in data["verses"].items():69            text = vdata.get("text", "")70            if not text:71                continue72            ref = VerseReference(book=book, chapter=chapter, verse=int(verse_num))73            source_verse = loader.load_verse(ref)74            source = source_verse.greek_text or source_verse.hebrew_text75            if not source:76                continue77            for term in glossary.find_terms_in_text(source):78                entry = stats.setdefault(79                    term.original, {"used": [], "missing": [], "avoided": []}80                )81                label = f"{chapter}:{verse_num}"82                for bad in term.avoid_translations:83                    if bad and bad in text:84                        entry["avoided"].append((label, bad))85                if any(86                    _rendering_present(r, text) for r in _accepted_renderings(term)87                ):88                    entry["used"].append(label)89                else:90                    entry["missing"].append(label)9192    issues = 093    print(f"\n=== Terminology report: {book} ===")94    for original, entry in sorted(stats.items()):95        used, missing, avoided = entry["used"], entry["missing"], entry["avoided"]96        term = glossary.get_by_original(original)97        flag = ""98        if avoided:99            flag = "  << DISCOURAGED RENDERING USED"100            issues += len(avoided)101        elif used and missing:102            flag = "  << INCONSISTENT"103            issues += len(missing)104        elif missing and not used:105            # Never rendered with the glossary term — may be inflection or106            # legitimate paraphrase; informational only.107            flag = "  (glossary rendering not found — check manually)"108        print(109            f"  {original} → {term.japanese if term else '?'}: "110            f"used {len(used)}, without {len(missing)}{flag}"111        )112        for label in entry["missing"][:5]:113            if used:  # only detail actual inconsistencies114                print(f"      without glossary rendering: {label}")115        for label, bad in avoided[:5]:116            print(f"      uses discouraged '{bad}': {label}")117118    if not stats:119        print("  (no glossary terms matched)")120    return issues121122123def main() -> int:124    books = sys.argv[1:]125    if not books:126        print(__doc__)127        return 2128    data_dir = Path(__file__).parent.parent / "data"129    total = 0130    for book in books:131        total += report_book(book, data_dir)132    print(f"\nTotal potential issues: {total}")133    return 0134135136if __name__ == "__main__":137    main()138