OPEN SOURCE
software/src/context/cross_references.py
このサイトで読み、必要なファイルを保存できます。
日常訳はAIを用いて作成された翻訳です。人間の聖書学・原語の専門家による全訳の検証は確認されていません。既存の翻訳や原語資料と照らし合わせてお読みください。
1"""Cross-reference system for intertextual awareness in translation.23Provides biblical cross-references to help translators understand4quotations, allusions, and thematic connections between passages.5"""67from __future__ import annotations89import json10from enum import Enum11from pathlib import Path12from typing import TYPE_CHECKING1314from pydantic import BaseModel, Field1516if TYPE_CHECKING:17from src.models.verse import Verse, VerseReference181920class ReferenceType(str, Enum):21"""Type of cross-reference relationship."""2223QUOTATION = "quotation" # Direct quote from another passage24ALLUSION = "allusion" # Indirect reference or echo25PARALLEL = "parallel" # Synoptic parallel (same event/teaching)26THEMATIC = "thematic" # Thematic connection27FULFILLMENT = "fulfillment" # Prophecy fulfillment28TYPOLOGY = "typology" # Type/antitype relationship293031class CrossReference(BaseModel):32"""A cross-reference between two biblical passages."""3334source_book: str = Field(..., description="Source book name")35source_chapter: int = Field(..., ge=1)36source_verse: int = Field(..., ge=1)37target_book: str = Field(..., description="Target book name")38target_chapter: int = Field(..., ge=1)39target_verse_start: int = Field(..., ge=1)40target_verse_end: int | None = Field(None, ge=1, description="End verse for ranges")41reference_type: ReferenceType = Field(42default=ReferenceType.THEMATIC,43description="Type of cross-reference",44)45note: str | None = Field(None, description="Explanation of the connection")46translation_hint: str | None = Field(47None,48description="Suggestion for maintaining consistency with target passage",49)5051@property52def source_ref(self) -> str:53"""Get source reference string."""54return f"{self.source_book} {self.source_chapter}:{self.source_verse}"5556@property57def target_ref(self) -> str:58"""Get target reference string."""59if self.target_verse_end and self.target_verse_end != self.target_verse_start:60return f"{self.target_book} {self.target_chapter}:{self.target_verse_start}-{self.target_verse_end}"61return f"{self.target_book} {self.target_chapter}:{self.target_verse_start}"626364class CrossReferenceMatch(BaseModel):65"""A matched cross-reference with additional context."""6667reference: CrossReference68target_text: str | None = Field(None, description="Text of the target passage")69target_translation: str | None = Field(70None, description="Existing translation of target (if available)"71)72relevance_score: float = Field(73default=1.0,74ge=0.0,75le=1.0,76description="Relevance score (1.0 = most relevant)",77)787980class CrossReferenceManager:81"""Manages biblical cross-references for translation context.8283Provides cross-reference lookup and formatting for inclusion in84translation prompts to ensure consistency across related passages.85"""8687def __init__(self, data_dir: Path | str | None = None) -> None:88"""Initialize the cross-reference manager.8990Args:91data_dir: Path to data directory containing cross-reference files.92"""93self._references: list[CrossReference] = []94self._by_source: dict[str, list[CrossReference]] = {}95self._by_target: dict[str, list[CrossReference]] = {}9697if data_dir:98self._data_dir = Path(data_dir)99self._load_cross_references()100else:101self._data_dir = None102103def _load_cross_references(self) -> None:104"""Load cross-references from data files."""105if not self._data_dir:106return107108xref_path = self._data_dir / "metadata" / "cross_references.json"109if not xref_path.exists():110return111112with xref_path.open("r", encoding="utf-8") as f:113data = json.load(f)114115for entry in data.get("references", []):116ref = CrossReference(**entry)117self.add_reference(ref)118119def add_reference(self, ref: CrossReference) -> None:120"""Add a cross-reference to the index.121122Args:123ref: The cross-reference to add.124"""125self._references.append(ref)126127# Index by source128source_key = f"{ref.source_book}:{ref.source_chapter}:{ref.source_verse}"129if source_key not in self._by_source:130self._by_source[source_key] = []131self._by_source[source_key].append(ref)132133# Index by target134target_key = f"{ref.target_book}:{ref.target_chapter}:{ref.target_verse_start}"135if target_key not in self._by_target:136self._by_target[target_key] = []137self._by_target[target_key].append(ref)138139def find_references_for_verse(140self,141book: str,142chapter: int,143verse: int,144) -> list[CrossReference]:145"""Find all cross-references for a given verse.146147Args:148book: Book name.149chapter: Chapter number.150verse: Verse number.151152Returns:153List of cross-references where this verse is the source.154"""155key = f"{book.lower()}:{chapter}:{verse}"156return list(self._by_source.get(key, []))157158def find_references_to_verse(159self,160book: str,161chapter: int,162verse: int,163) -> list[CrossReference]:164"""Find all references that point TO this verse.165166Args:167book: Book name.168chapter: Chapter number.169verse: Verse number.170171Returns:172List of cross-references where this verse is the target.173"""174key = f"{book.lower()}:{chapter}:{verse}"175return list(self._by_target.get(key, []))176177def find_references_by_type(178self,179book: str,180chapter: int,181verse: int,182ref_type: ReferenceType,183) -> list[CrossReference]:184"""Find cross-references of a specific type for a verse.185186Args:187book: Book name.188chapter: Chapter number.189verse: Verse number.190ref_type: Type of reference to filter by.191192Returns:193List of matching cross-references.194"""195refs = self.find_references_for_verse(book, chapter, verse)196return [r for r in refs if r.reference_type == ref_type]197198def get_quotation_sources(199self,200book: str,201chapter: int,202verse: int,203) -> list[CrossReference]:204"""Get OT quotation sources for a verse.205206Particularly useful for NT verses that quote the OT.207208Args:209book: Book name.210chapter: Chapter number.211verse: Verse number.212213Returns:214List of quotation cross-references.215"""216return self.find_references_by_type(217book, chapter, verse, ReferenceType.QUOTATION218)219220def get_parallel_passages(221self,222book: str,223chapter: int,224verse: int,225) -> list[CrossReference]:226"""Get synoptic parallel passages for a verse.227228Useful for ensuring consistency across Gospel parallels.229230Args:231book: Book name.232chapter: Chapter number.233verse: Verse number.234235Returns:236List of parallel passage cross-references.237"""238return self.find_references_by_type(239book, chapter, verse, ReferenceType.PARALLEL240)241242def format_for_prompt(243self,244references: list[CrossReference],245include_hints: bool = True,246max_references: int = 5,247) -> str:248"""Format cross-references for inclusion in a translation prompt.249250Args:251references: List of cross-references to format.252include_hints: Whether to include translation hints.253max_references: Maximum number of references to include.254255Returns:256Formatted string for prompt inclusion.257"""258if not references:259return ""260261lines = ["CROSS-REFERENCES:"]262263# Sort by relevance (quotations first, then type)264type_priority = {265ReferenceType.QUOTATION: 0,266ReferenceType.FULFILLMENT: 1,267ReferenceType.PARALLEL: 2,268ReferenceType.ALLUSION: 3,269ReferenceType.TYPOLOGY: 4,270ReferenceType.THEMATIC: 5,271}272sorted_refs = sorted(273references,274key=lambda r: type_priority.get(r.reference_type, 99),275)276277for ref in sorted_refs[:max_references]:278type_label = ref.reference_type.value.upper()279lines.append(f"\n[{type_label}] {ref.target_ref}")280281if ref.note:282lines.append(f" Note: {ref.note}")283284if include_hints and ref.translation_hint:285lines.append(f" Translation hint: {ref.translation_hint}")286287return "\n".join(lines)288289def get_translation_consistency_notes(290self,291book: str,292chapter: int,293verse: int,294) -> list[str]:295"""Get notes about translation consistency for related passages.296297Args:298book: Book name.299chapter: Chapter number.300verse: Verse number.301302Returns:303List of consistency notes.304"""305notes = []306307# Check for quotations308quotations = self.get_quotation_sources(book, chapter, verse)309if quotations:310for q in quotations:311notes.append(312f"This verse quotes {q.target_ref}. "313f"Consider maintaining consistency with the source passage."314)315if q.translation_hint:316notes.append(f"Hint: {q.translation_hint}")317318# Check for parallels319parallels = self.get_parallel_passages(book, chapter, verse)320if parallels:321parallel_refs = [p.target_ref for p in parallels]322notes.append(323f"Synoptic parallels: {', '.join(parallel_refs)}. "324f"Consider consistent phrasing for shared material."325)326327return notes328329def save(self, path: Path | None = None) -> None:330"""Save cross-references to file.331332Args:333path: Path to save to. Uses default if not provided.334"""335if path is None:336if self._data_dir is None:337raise ValueError("No data directory configured")338path = self._data_dir / "metadata" / "cross_references.json"339340path.parent.mkdir(parents=True, exist_ok=True)341342data = {343"version": "1.0",344"references": [ref.model_dump() for ref in self._references],345}346347with path.open("w", encoding="utf-8") as f:348json.dump(data, f, indent=2, ensure_ascii=False)349350def __len__(self) -> int:351"""Get the number of cross-references."""352return len(self._references)353354355# Well-known cross-references for John's Gospel (examples)356JOHN_CROSS_REFERENCES = [357CrossReference(358source_book="john",359source_chapter=1,360source_verse=1,361target_book="genesis",362target_chapter=1,363target_verse_start=1,364reference_type=ReferenceType.ALLUSION,365note="John's opening 'In the beginning' (Ἐν ἀρχῇ) deliberately echoes Genesis",366translation_hint="Consider using similar phrasing to Genesis 1:1 (初めに)",367),368CrossReference(369source_book="john",370source_chapter=1,371source_verse=14,372target_book="exodus",373target_chapter=33,374target_verse_start=7,375target_verse_end=11,376reference_type=ReferenceType.TYPOLOGY,377note="'Tabernacled among us' (ἐσκήνωσεν) echoes the tabernacle/tent of meeting",378translation_hint="The word 'dwelt' has tabernacle imagery",379),380CrossReference(381source_book="john",382source_chapter=1,383source_verse=23,384target_book="isaiah",385target_chapter=40,386target_verse_start=3,387reference_type=ReferenceType.QUOTATION,388note="John the Baptist quotes Isaiah 40:3",389translation_hint="Match LXX/Isaiah translation for 'voice crying in the wilderness'",390),391CrossReference(392source_book="john",393source_chapter=1,394source_verse=29,395target_book="exodus",396target_chapter=12,397target_verse_start=1,398target_verse_end=13,399reference_type=ReferenceType.TYPOLOGY,400note="'Lamb of God' connects to Passover lamb",401translation_hint="神の小羊 - maintain lamb imagery consistent with Passover",402),403CrossReference(404source_book="john",405source_chapter=3,406source_verse=14,407target_book="numbers",408target_chapter=21,409target_verse_start=8,410target_verse_end=9,411reference_type=ReferenceType.TYPOLOGY,412note="Bronze serpent lifted up prefigures Christ's crucifixion",413translation_hint="Use 上げる for 'lifted up' consistently",414),415]416417418def create_default_cross_references(data_dir: Path) -> CrossReferenceManager:419"""Create a CrossReferenceManager with default NT cross-references.420421Args:422data_dir: Data directory path.423424Returns:425CrossReferenceManager with pre-loaded references.426"""427manager = CrossReferenceManager(data_dir)428429# Add well-known references if not already loaded430if len(manager) == 0:431for ref in JOHN_CROSS_REFERENCES:432manager.add_reference(ref)433434return manager435