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

OPEN SOURCE

software/src/protos/converters.py

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

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

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

software/src/protos/converters.py911 行このファイルを保存 ↓

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

ファイルの確認情報SHA-256 485ff2ccf99aaf015a706ddbae22112e57dbff934fe5049932775f7ae79ec8f4
1"""Converters between Pydantic models and Protocol Buffer messages.23This module provides bidirectional conversion between the Pydantic models used4internally and the Protocol Buffer messages used for serialization and I/O.56Since proto compilation requires optional dependencies, this module provides7dataclass-based representations that mirror the proto structure when the8compiled protos are not available.9"""1011from __future__ import annotations1213from dataclasses import dataclass, field14from datetime import datetime, timezone15from enum import IntEnum16from typing import TYPE_CHECKING1718from src.utils.furigana import FuriganaAnnotation1920if TYPE_CHECKING:21    from src.models.glossary import GlossaryTerm as GlossaryTermModel22    from src.models.glossary import TheologicalDomain as TheologicalDomainModel23    from src.models.style import (24        EquivalenceApproach as EquivalenceApproachModel,25        FormalityLevel as FormalityLevelModel,26        GenderLanguage as GenderLanguageModel,27        ReadabilityLevel as ReadabilityLevelModel,28        TermConsistency as TermConsistencyModel,29        TheologicalTradition as TheologicalTraditionModel,30        TranslationStyle as TranslationStyleModel,31    )32    from src.models.translation_memory import (33        MatchType as MatchTypeModel,34        SegmentLevel as SegmentLevelModel,35        TMMatch as TMMatchModel,36        TMSegment as TMSegmentModel,37    )38    from src.models.verse import (39        BookMetadata as BookMetadataModel,40        ChapterSummary as ChapterSummaryModel,41        TranslationContext as TranslationContextModel,42        TranslationOutput as TranslationOutputModel,43        Verse as VerseModel,44        VerseReference as VerseReferenceModel,45    )46    from src.orchestration.provenance import TranslationProvenance as TranslationProvenanceModel474849# =============================================================================50# Proto-like Enums (mirrors bible_translation.proto enums)51# =============================================================================525354class BiblicalTestament(IntEnum):55    """Biblical testament classification."""5657    TESTAMENT_UNSPECIFIED = 058    OLD_TESTAMENT = 159    NEW_TESTAMENT = 2606162# Alias for backward compatibility63Testament = BiblicalTestament646566class TheologicalDomain(IntEnum):67    """Theological domain categories for terms."""6869    THEOLOGICAL_DOMAIN_UNSPECIFIED = 070    CHRISTOLOGY = 171    SOTERIOLOGY = 272    ESCHATOLOGY = 373    PNEUMATOLOGY = 474    ECCLESIOLOGY = 575    HAMARTIOLOGY = 676    THEOLOGY_PROPER = 777    ANTHROPOLOGY = 878    GENERAL = 9798081class EquivalenceApproach(IntEnum):82    """Translation equivalence approach."""8384    EQUIVALENCE_APPROACH_UNSPECIFIED = 085    FORMAL = 186    DYNAMIC = 287    OPTIMAL = 3888990class GenderLanguage(IntEnum):91    """Gender-inclusive language approach."""9293    GENDER_LANGUAGE_UNSPECIFIED = 094    TRADITIONAL = 195    MODERATE_INCLUSIVE = 296    FULLY_INCLUSIVE = 3979899class ReadabilityLevel(IntEnum):100    """Target audience readability level."""101102    READABILITY_LEVEL_UNSPECIFIED = 0103    SCHOLARLY = 1104    GENERAL_PUBLIC = 2105    CHILDREN = 3106    WEB_NATIVE = 4107108109class TheologicalTradition(IntEnum):110    """Theological interpretive tradition."""111112    THEOLOGICAL_TRADITION_UNSPECIFIED = 0113    EVANGELICAL = 1114    CATHOLIC = 2115    ORTHODOX = 3116    ECUMENICAL = 4117118119class TermConsistency(IntEnum):120    """Term translation consistency approach."""121122    TERM_CONSISTENCY_UNSPECIFIED = 0123    STRICT = 1124    BALANCED = 2125    CONTEXTUAL = 3126127128class FormalityLevel(IntEnum):129    """Japanese formality level."""130131    FORMALITY_LEVEL_UNSPECIFIED = 0132    CASUAL = 1133    STANDARD = 2134    FORMAL_LEVEL = 3135    POLITE = 4136137138class MatchType(IntEnum):139    """Translation memory match type."""140141    MATCH_TYPE_UNSPECIFIED = 0142    EXACT = 1143    FUZZY = 2144    PHRASE = 3145146147class ConfidenceLevel(IntEnum):148    """Confidence level for translations."""149150    CONFIDENCE_LEVEL_UNSPECIFIED = 0151    LOW = 1152    MEDIUM = 2153    HIGH = 3154    VERY_HIGH = 4155156157class SegmentLevel(IntEnum):158    """Level of granularity for a translation segment."""159160    SEGMENT_LEVEL_UNSPECIFIED = 0161    VERSE = 1162    PHRASE = 2163164165# =============================================================================166# Proto-like Message Dataclasses167# =============================================================================168169170@dataclass171class VerseReferenceProto:172    """Proto representation of a verse reference."""173174    book: str = ""175    chapter: int = 0176    verse: int = 0177178179@dataclass180class VerseProto:181    """Proto representation of a Bible verse."""182183    reference: VerseReferenceProto = field(default_factory=VerseReferenceProto)184    greek_text: str | None = None185    greek_translit: str | None = None186    hebrew_text: str | None = None187    hebrew_translit: str | None = None188    japanese_meiji: str | None = None189    japanese_taisho: str | None = None190    english_kjv: str | None = None191    english_asv: str | None = None192193194@dataclass195class BookMetadataProto:196    """Proto representation of book metadata."""197198    book: str = ""199    name_japanese: str = ""200    name_english: str = ""201    testament: Testament = Testament.TESTAMENT_UNSPECIFIED202    author: str = ""203    themes: list[str] = field(default_factory=list)204    description: str = ""205    chapter_count: int = 0206    verse_count: int = 0207208209@dataclass210class ChapterSummaryProto:211    """Proto representation of chapter summary."""212213    book: str = ""214    chapter: int = 0215    summary: str = ""216    key_events: list[str] = field(default_factory=list)217    key_characters: list[str] = field(default_factory=list)218    key_themes: list[str] = field(default_factory=list)219220221@dataclass222class GlossaryTermProto:223    """Proto representation of a glossary term."""224225    original: str = ""226    transliteration: str = ""227    japanese: str = ""228    english: str = ""229    notes: str | None = None230    lemma: str | None = None231    inflected_forms: list[str] = field(default_factory=list)232    strongs_number: str | None = None233    definition: str | None = None234    semantic_range: list[str] = field(default_factory=list)235    domain: TheologicalDomain = TheologicalDomain.GENERAL236    usage_examples: list[str] = field(default_factory=list)237    frequency: int = 0238    related_terms: list[str] = field(default_factory=list)239    avoid_translations: list[str] = field(default_factory=list)240241242@dataclass243class TranslationStyleProto:244    """Proto representation of translation style configuration."""245246    equivalence_approach: EquivalenceApproach = EquivalenceApproach.EQUIVALENCE_APPROACH_UNSPECIFIED247    gender_language: GenderLanguage = GenderLanguage.GENDER_LANGUAGE_UNSPECIFIED248    readability_level: ReadabilityLevel = ReadabilityLevel.READABILITY_LEVEL_UNSPECIFIED249    theological_tradition: TheologicalTradition = (250        TheologicalTradition.THEOLOGICAL_TRADITION_UNSPECIFIED251    )252    textual_basis: str = ""253    term_consistency: TermConsistency = TermConsistency.TERM_CONSISTENCY_UNSPECIFIED254    formality_level: FormalityLevel = FormalityLevel.FORMALITY_LEVEL_UNSPECIFIED255    avoid_archaic: list[str] = field(default_factory=list)256    target_style_reference: str = ""257    additional_instructions: str | None = None258259260@dataclass261class FuriganaAnnotationProto:262    """Proto representation of furigana annotation."""263264    start_offset: int = 0265    end_offset: int = 0266    base_text: str = ""267    reading: str = ""268    is_standard_reading: bool = True269    reading_source: str = "dictionary"270271272@dataclass273class AnnotatedWordProto:274    """Proto representation of an annotated word."""275276    surface_form: str = ""277    lemma: str = ""278    part_of_speech: str = ""279    reading: str = ""280    alternative_readings: list[str] = field(default_factory=list)281    start_offset: int = 0282    end_offset: int = 0283284285@dataclass286class TMSegmentProto:287    """Proto representation of a translation memory segment."""288289    id: str = ""290    source_text: str = ""291    source_normalized: str = ""292    target_text: str = ""293    reference: VerseReferenceProto = field(default_factory=VerseReferenceProto)294    level: SegmentLevel = SegmentLevel.VERSE295    created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))296    reuse_count: int = 0297    metadata: dict = field(default_factory=dict)298299300@dataclass301class TranslationMemoryMatchProto:302    """Proto representation of a translation memory match."""303304    segment: TMSegmentProto = field(default_factory=TMSegmentProto)305    similarity: float = 0.0306    match_type: MatchType = MatchType.MATCH_TYPE_UNSPECIFIED307308309@dataclass310class TranslationProvenanceProto:311    """Proto representation of translation provenance."""312313    source_texts: dict[str, str] = field(default_factory=dict)314    source_hashes: dict[str, str] = field(default_factory=dict)315    preceding_verses: list[str] = field(default_factory=list)316    following_verses: list[str] = field(default_factory=list)317    pericope_id: str | None = None318    style_config_hash: str = ""319    glossary_hash: str = ""320    prompt_version: str = "v1.0"321    model: str = ""322    system_prompt: str = ""323    user_prompt: str = ""324    tm_matches_used: list[dict] = field(default_factory=list)325    attempt_number: int = 1326    started_at: datetime | None = None327    completed_at: datetime | None = None328    evaluation_score: float | None = None329    evaluation_flags: list[str] = field(default_factory=list)330    chapter_summary: str | None = None331    book_metadata: dict = field(default_factory=dict)332333334@dataclass335class TranslationResultProto:336    """Proto representation of translation result with furigana."""337338    reference: VerseReferenceProto = field(default_factory=VerseReferenceProto)339    text: str = ""340    text_with_inline_furigana: str = ""341    furigana_annotations: list[FuriganaAnnotationProto] = field(default_factory=list)342    annotated_words: list[AnnotatedWordProto] = field(default_factory=list)343    model: str = ""344    timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc))345    prompt_version: str = ""346    sources_used: list[str] = field(default_factory=list)347    glossary_terms_applied: list[str] = field(default_factory=list)348    confidence: ConfidenceLevel = ConfidenceLevel.CONFIDENCE_LEVEL_UNSPECIFIED349    confidence_score: float = 0.0350    input_hash: str = ""351    provenance: TranslationProvenanceProto | None = None352353354@dataclass355class TranslationPromptInputProto:356    """Proto representation of complete translation input."""357358    target_verse: VerseProto = field(default_factory=VerseProto)359    preceding_verses: list[VerseProto] = field(default_factory=list)360    following_verses: list[VerseProto] = field(default_factory=list)361    chapter_summary: ChapterSummaryProto | None = None362    book_metadata: BookMetadataProto | None = None363    glossary_terms: list[GlossaryTermProto] = field(default_factory=list)364    style: TranslationStyleProto = field(default_factory=TranslationStyleProto)365    tm_matches: list[TranslationMemoryMatchProto] = field(default_factory=list)366    system_prompt: str = ""367    user_prompt: str = ""368    model: str = ""369    prompt_version: str = ""370    request_time: datetime = field(default_factory=lambda: datetime.now(timezone.utc))371372373# =============================================================================374# Converter Functions: Pydantic -> Proto375# =============================================================================376377378def verse_reference_to_proto(ref: VerseReferenceModel) -> VerseReferenceProto:379    """Convert a VerseReference Pydantic model to proto representation."""380    return VerseReferenceProto(381        book=ref.book,382        chapter=ref.chapter,383        verse=ref.verse,384    )385386387def verse_to_proto(verse: VerseModel) -> VerseProto:388    """Convert a Verse Pydantic model to proto representation."""389    return VerseProto(390        reference=verse_reference_to_proto(verse.reference),391        greek_text=verse.greek_text,392        greek_translit=verse.greek_translit,393        hebrew_text=verse.hebrew_text,394        hebrew_translit=verse.hebrew_translit,395        japanese_meiji=verse.japanese_meiji,396        japanese_taisho=verse.japanese_taisho,397        english_kjv=verse.english_kjv,398        english_asv=verse.english_asv,399    )400401402def _theological_domain_to_proto(domain: TheologicalDomainModel) -> TheologicalDomain:403    """Convert TheologicalDomain enum to proto enum."""404    domain_map = {405        "christology": TheologicalDomain.CHRISTOLOGY,406        "soteriology": TheologicalDomain.SOTERIOLOGY,407        "eschatology": TheologicalDomain.ESCHATOLOGY,408        "pneumatology": TheologicalDomain.PNEUMATOLOGY,409        "ecclesiology": TheologicalDomain.ECCLESIOLOGY,410        "hamartiology": TheologicalDomain.HAMARTIOLOGY,411        "theology_proper": TheologicalDomain.THEOLOGY_PROPER,412        "anthropology": TheologicalDomain.ANTHROPOLOGY,413        "general": TheologicalDomain.GENERAL,414    }415    return domain_map.get(domain.value, TheologicalDomain.GENERAL)416417418def book_metadata_to_proto(metadata: BookMetadataModel) -> BookMetadataProto:419    """Convert BookMetadata Pydantic model to proto representation."""420    testament = Testament.NEW_TESTAMENT if metadata.testament == "NT" else Testament.OLD_TESTAMENT421    return BookMetadataProto(422        book=metadata.book,423        name_japanese=metadata.name_japanese,424        name_english=metadata.name_english,425        testament=testament,426        author=metadata.author,427        themes=list(metadata.themes),428        description=metadata.description,429        chapter_count=metadata.chapter_count,430        verse_count=metadata.verse_count,431    )432433434def chapter_summary_to_proto(summary: ChapterSummaryModel) -> ChapterSummaryProto:435    """Convert ChapterSummary Pydantic model to proto representation."""436    return ChapterSummaryProto(437        book=summary.book,438        chapter=summary.chapter,439        summary=summary.summary,440        key_events=list(summary.key_events),441        key_characters=list(summary.key_characters),442        key_themes=list(summary.key_themes),443    )444445446def glossary_term_to_proto(term: GlossaryTermModel) -> GlossaryTermProto:447    """Convert GlossaryTerm Pydantic model to proto representation."""448    return GlossaryTermProto(449        original=term.original,450        transliteration=term.transliteration,451        japanese=term.japanese,452        english=term.english,453        notes=term.notes,454        lemma=term.lemma,455        inflected_forms=list(term.inflected_forms),456        strongs_number=term.strongs_number,457        definition=term.definition,458        semantic_range=list(term.semantic_range),459        domain=_theological_domain_to_proto(term.domain),460        usage_examples=list(term.usage_examples),461        frequency=term.frequency,462        related_terms=list(term.related_terms),463        avoid_translations=list(term.avoid_translations),464    )465466467def _equivalence_approach_to_proto(approach: EquivalenceApproachModel) -> EquivalenceApproach:468    """Convert EquivalenceApproach enum to proto enum."""469    approach_map = {470        "formal": EquivalenceApproach.FORMAL,471        "dynamic": EquivalenceApproach.DYNAMIC,472        "optimal": EquivalenceApproach.OPTIMAL,473    }474    return approach_map.get(approach.value, EquivalenceApproach.EQUIVALENCE_APPROACH_UNSPECIFIED)475476477def _gender_language_to_proto(gender: GenderLanguageModel) -> GenderLanguage:478    """Convert GenderLanguage enum to proto enum."""479    gender_map = {480        "traditional": GenderLanguage.TRADITIONAL,481        "moderate_inclusive": GenderLanguage.MODERATE_INCLUSIVE,482        "fully_inclusive": GenderLanguage.FULLY_INCLUSIVE,483    }484    return gender_map.get(gender.value, GenderLanguage.GENDER_LANGUAGE_UNSPECIFIED)485486487def _readability_level_to_proto(level: ReadabilityLevelModel) -> ReadabilityLevel:488    """Convert ReadabilityLevel enum to proto enum."""489    level_map = {490        "scholarly": ReadabilityLevel.SCHOLARLY,491        "general_public": ReadabilityLevel.GENERAL_PUBLIC,492        "children": ReadabilityLevel.CHILDREN,493        "web_native": ReadabilityLevel.WEB_NATIVE,494    }495    return level_map.get(level.value, ReadabilityLevel.READABILITY_LEVEL_UNSPECIFIED)496497498def _theological_tradition_to_proto(tradition: TheologicalTraditionModel) -> TheologicalTradition:499    """Convert TheologicalTradition enum to proto enum."""500    tradition_map = {501        "evangelical": TheologicalTradition.EVANGELICAL,502        "catholic": TheologicalTradition.CATHOLIC,503        "orthodox": TheologicalTradition.ORTHODOX,504        "ecumenical": TheologicalTradition.ECUMENICAL,505    }506    return tradition_map.get(tradition.value, TheologicalTradition.THEOLOGICAL_TRADITION_UNSPECIFIED)507508509def _term_consistency_to_proto(consistency: TermConsistencyModel) -> TermConsistency:510    """Convert TermConsistency enum to proto enum."""511    consistency_map = {512        "strict": TermConsistency.STRICT,513        "balanced": TermConsistency.BALANCED,514        "contextual": TermConsistency.CONTEXTUAL,515    }516    return consistency_map.get(consistency.value, TermConsistency.TERM_CONSISTENCY_UNSPECIFIED)517518519def _formality_level_to_proto(level: FormalityLevelModel) -> FormalityLevel:520    """Convert FormalityLevel enum to proto enum."""521    level_map = {522        "casual": FormalityLevel.CASUAL,523        "standard": FormalityLevel.STANDARD,524        "polite": FormalityLevel.POLITE,525        "formal": FormalityLevel.FORMAL_LEVEL,526    }527    return level_map.get(level.value, FormalityLevel.FORMALITY_LEVEL_UNSPECIFIED)528529530def translation_style_to_proto(style: TranslationStyleModel) -> TranslationStyleProto:531    """Convert TranslationStyle dataclass to proto representation."""532    return TranslationStyleProto(533        equivalence_approach=_equivalence_approach_to_proto(style.equivalence_approach),534        gender_language=_gender_language_to_proto(style.gender_language),535        readability_level=_readability_level_to_proto(style.readability_level),536        theological_tradition=_theological_tradition_to_proto(style.theological_tradition),537        textual_basis=style.textual_basis,538        term_consistency=_term_consistency_to_proto(style.term_consistency),539        formality_level=_formality_level_to_proto(style.formality_level),540        avoid_archaic=list(style.avoid_archaic),541        target_style_reference=style.target_style_reference,542        additional_instructions=style.additional_instructions,543    )544545546def furigana_annotation_to_proto(annotation: FuriganaAnnotation) -> FuriganaAnnotationProto:547    """Convert FuriganaAnnotation to proto representation."""548    return FuriganaAnnotationProto(549        start_offset=annotation.start_offset,550        end_offset=annotation.end_offset,551        base_text=annotation.base_text,552        reading=annotation.reading,553        is_standard_reading=annotation.is_standard_reading,554        reading_source=annotation.reading_source,555    )556557558def annotated_word_to_proto(559    surface_form: str,560    lemma: str,561    part_of_speech: str,562    reading: str,563    start_offset: int,564    end_offset: int,565    alternative_readings: list[str] | None = None,566) -> AnnotatedWordProto:567    """Create an AnnotatedWordProto from components."""568    return AnnotatedWordProto(569        surface_form=surface_form,570        lemma=lemma,571        part_of_speech=part_of_speech,572        reading=reading,573        alternative_readings=alternative_readings or [],574        start_offset=start_offset,575        end_offset=end_offset,576    )577578579def translation_context_to_prompt_input(580    context: TranslationContextModel,581    system_prompt: str = "",582    user_prompt: str = "",583    model: str = "",584    prompt_version: str = "v1.0",585) -> TranslationPromptInputProto:586    """Convert TranslationContext to TranslationPromptInput proto.587588    Args:589        context: The translation context with verses, metadata, and glossary590        system_prompt: The system prompt used for translation591        user_prompt: The user prompt used for translation592        model: The model name used for translation593        prompt_version: Version of the prompt template594595    Returns:596        TranslationPromptInputProto capturing all inputs for reproducibility597    """598    return TranslationPromptInputProto(599        target_verse=verse_to_proto(context.target_verse),600        preceding_verses=[verse_to_proto(v) for v in context.preceding_verses],601        following_verses=[verse_to_proto(v) for v in context.following_verses],602        chapter_summary=(603            chapter_summary_to_proto(context.chapter_summary)604            if context.chapter_summary605            else None606        ),607        book_metadata=(608            book_metadata_to_proto(context.book_metadata)609            if context.book_metadata610            else None611        ),612        glossary_terms=[glossary_term_to_proto(t) for t in context.glossary_terms],613        system_prompt=system_prompt,614        user_prompt=user_prompt,615        model=model,616        prompt_version=prompt_version,617        request_time=datetime.now(timezone.utc),618    )619620621def translation_result_to_proto(622    output: TranslationOutputModel,623    text_with_furigana: str = "",624    furigana_annotations: list[FuriganaAnnotation] | None = None,625    annotated_words: list[AnnotatedWordProto] | None = None,626    confidence: ConfidenceLevel = ConfidenceLevel.CONFIDENCE_LEVEL_UNSPECIFIED,627    confidence_score: float = 0.0,628    input_hash: str = "",629) -> TranslationResultProto:630    """Convert TranslationOutput to TranslationResult proto with furigana.631632    Args:633        output: The translation output from the translator634        text_with_furigana: Text with inline furigana notation635        furigana_annotations: Parsed furigana annotations636        annotated_words: Linguistic word annotations637        confidence: Confidence level enum638        confidence_score: Numeric confidence score (0-1)639        input_hash: Hash of the input for reproducibility tracking640641    Returns:642        TranslationResultProto with rich annotation data643    """644    return TranslationResultProto(645        reference=verse_reference_to_proto(output.reference),646        text=output.text,647        text_with_inline_furigana=text_with_furigana or output.text,648        furigana_annotations=[649            furigana_annotation_to_proto(a) for a in (furigana_annotations or [])650        ],651        annotated_words=annotated_words or [],652        model=output.model,653        timestamp=output.timestamp,654        prompt_version=output.prompt_version,655        sources_used=list(output.sources_used),656        glossary_terms_applied=list(output.glossary_terms_applied),657        confidence=confidence,658        confidence_score=confidence_score,659        input_hash=input_hash,660    )661662663# =============================================================================664# Converter Functions: Proto -> Pydantic665# =============================================================================666667668def proto_to_verse_reference(proto: VerseReferenceProto) -> VerseReferenceModel:669    """Convert proto VerseReference to Pydantic model."""670    from src.models.verse import VerseReference671672    return VerseReference(673        book=proto.book,674        chapter=proto.chapter,675        verse=proto.verse,676    )677678679def proto_to_verse(proto: VerseProto) -> VerseModel:680    """Convert proto Verse to Pydantic model."""681    from src.models.verse import Verse682683    return Verse(684        reference=proto_to_verse_reference(proto.reference),685        greek_text=proto.greek_text,686        greek_translit=proto.greek_translit,687        hebrew_text=proto.hebrew_text,688        hebrew_translit=proto.hebrew_translit,689        japanese_meiji=proto.japanese_meiji,690        japanese_taisho=proto.japanese_taisho,691        english_kjv=proto.english_kjv,692        english_asv=proto.english_asv,693    )694695696def proto_to_translation_result(proto: TranslationResultProto) -> TranslationOutputModel:697    """Convert proto TranslationResult to Pydantic TranslationOutput.698699    Note: This returns the basic TranslationOutput model. The furigana700    annotations and other rich data are only available in the proto format.701    """702    from src.models.verse import TranslationOutput703704    return TranslationOutput(705        reference=proto_to_verse_reference(proto.reference),706        text=proto.text,707        model=proto.model,708        timestamp=proto.timestamp,709        prompt_version=proto.prompt_version,710        sources_used=list(proto.sources_used),711        glossary_terms_applied=list(proto.glossary_terms_applied),712    )713714715# =============================================================================716# Translation Memory Converters717# =============================================================================718719720def _segment_level_to_proto(level: SegmentLevelModel) -> SegmentLevel:721    """Convert SegmentLevel enum to proto enum."""722    level_map = {723        "verse": SegmentLevel.VERSE,724        "phrase": SegmentLevel.PHRASE,725    }726    return level_map.get(level.value, SegmentLevel.SEGMENT_LEVEL_UNSPECIFIED)727728729def _match_type_to_proto(match_type: MatchTypeModel) -> MatchType:730    """Convert MatchType enum to proto enum."""731    type_map = {732        "exact": MatchType.EXACT,733        "fuzzy": MatchType.FUZZY,734        "phrase": MatchType.PHRASE,735    }736    return type_map.get(match_type.value, MatchType.MATCH_TYPE_UNSPECIFIED)737738739def tm_segment_to_proto(segment: TMSegmentModel) -> TMSegmentProto:740    """Convert TMSegment Pydantic model to proto representation.741742    Args:743        segment: The TMSegment to convert.744745    Returns:746        TMSegmentProto representation.747    """748    return TMSegmentProto(749        id=segment.id,750        source_text=segment.source_text,751        source_normalized=segment.source_normalized,752        target_text=segment.target_text,753        reference=verse_reference_to_proto(segment.reference),754        level=_segment_level_to_proto(segment.level),755        created_at=segment.created_at,756        reuse_count=segment.reuse_count,757        metadata=dict(segment.metadata),758    )759760761def tm_match_to_proto(match: TMMatchModel) -> TranslationMemoryMatchProto:762    """Convert TMMatch Pydantic model to proto representation.763764    Args:765        match: The TMMatch to convert.766767    Returns:768        TranslationMemoryMatchProto representation.769    """770    return TranslationMemoryMatchProto(771        segment=tm_segment_to_proto(match.segment),772        similarity=match.similarity,773        match_type=_match_type_to_proto(match.match_type),774    )775776777def provenance_to_proto(provenance: TranslationProvenanceModel) -> TranslationProvenanceProto:778    """Convert TranslationProvenance to proto representation.779780    Args:781        provenance: The TranslationProvenance to convert.782783    Returns:784        TranslationProvenanceProto representation.785    """786    return TranslationProvenanceProto(787        source_texts=dict(provenance.source_texts),788        source_hashes=dict(provenance.source_hashes),789        preceding_verses=list(provenance.preceding_verses),790        following_verses=list(provenance.following_verses),791        pericope_id=provenance.pericope_id,792        style_config_hash=provenance.style_config_hash,793        glossary_hash=provenance.glossary_hash,794        prompt_version=provenance.prompt_version,795        model=provenance.model,796        system_prompt=provenance.system_prompt,797        user_prompt=provenance.user_prompt,798        tm_matches_used=list(provenance.tm_matches_used),799        attempt_number=provenance.attempt_number,800        started_at=provenance.started_at,801        completed_at=provenance.completed_at,802        evaluation_score=provenance.evaluation_score,803        evaluation_flags=list(provenance.evaluation_flags),804        chapter_summary=provenance.chapter_summary,805        book_metadata=dict(provenance.book_metadata),806    )807808809# =============================================================================810# Proto -> Model Converters for TM and Provenance811# =============================================================================812813814def _proto_to_segment_level(level: SegmentLevel) -> SegmentLevelModel:815    """Convert proto SegmentLevel to model enum."""816    from src.models.translation_memory import SegmentLevel as SegmentLevelModel817818    level_map = {819        SegmentLevel.VERSE: SegmentLevelModel.VERSE,820        SegmentLevel.PHRASE: SegmentLevelModel.PHRASE,821    }822    return level_map.get(level, SegmentLevelModel.VERSE)823824825def _proto_to_match_type(match_type: MatchType) -> MatchTypeModel:826    """Convert proto MatchType to model enum."""827    from src.models.translation_memory import MatchType as MatchTypeModel828829    type_map = {830        MatchType.EXACT: MatchTypeModel.EXACT,831        MatchType.FUZZY: MatchTypeModel.FUZZY,832        MatchType.PHRASE: MatchTypeModel.PHRASE,833    }834    return type_map.get(match_type, MatchTypeModel.EXACT)835836837def proto_to_tm_segment(proto: TMSegmentProto) -> TMSegmentModel:838    """Convert proto TMSegment to Pydantic model.839840    Args:841        proto: The TMSegmentProto to convert.842843    Returns:844        TMSegment Pydantic model.845    """846    from src.models.translation_memory import TMSegment847848    return TMSegment(849        id=proto.id,850        source_text=proto.source_text,851        source_normalized=proto.source_normalized,852        target_text=proto.target_text,853        reference=proto_to_verse_reference(proto.reference),854        level=_proto_to_segment_level(proto.level),855        created_at=proto.created_at,856        reuse_count=proto.reuse_count,857        metadata=dict(proto.metadata),858    )859860861def proto_to_tm_match(proto: TranslationMemoryMatchProto) -> TMMatchModel:862    """Convert proto TMMatch to Pydantic model.863864    Args:865        proto: The TranslationMemoryMatchProto to convert.866867    Returns:868        TMMatch Pydantic model.869    """870    from src.models.translation_memory import TMMatch871872    return TMMatch(873        segment=proto_to_tm_segment(proto.segment),874        similarity=proto.similarity,875        match_type=_proto_to_match_type(proto.match_type),876    )877878879def proto_to_provenance(proto: TranslationProvenanceProto) -> TranslationProvenanceModel:880    """Convert proto TranslationProvenance to model.881882    Args:883        proto: The TranslationProvenanceProto to convert.884885    Returns:886        TranslationProvenance model.887    """888    from src.orchestration.provenance import TranslationProvenance889890    return TranslationProvenance(891        source_texts=dict(proto.source_texts),892        source_hashes=dict(proto.source_hashes),893        preceding_verses=list(proto.preceding_verses),894        following_verses=list(proto.following_verses),895        pericope_id=proto.pericope_id,896        style_config_hash=proto.style_config_hash,897        glossary_hash=proto.glossary_hash,898        prompt_version=proto.prompt_version,899        model=proto.model,900        system_prompt=proto.system_prompt,901        user_prompt=proto.user_prompt,902        tm_matches_used=list(proto.tm_matches_used),903        attempt_number=proto.attempt_number,904        started_at=proto.started_at,905        completed_at=proto.completed_at,906        evaluation_score=proto.evaluation_score,907        evaluation_flags=list(proto.evaluation_flags),908        chapter_summary=proto.chapter_summary,909        book_metadata=dict(proto.book_metadata),910    )911