OPEN SOURCE
software/src/protos/converters.py
このサイトで読み、必要なファイルを保存できます。
日常訳はAIを用いて作成された翻訳です。人間の聖書学・原語の専門家による全訳の検証は確認されていません。既存の翻訳や原語資料と照らし合わせてお読みください。
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:21from src.models.glossary import GlossaryTerm as GlossaryTermModel22from src.models.glossary import TheologicalDomain as TheologicalDomainModel23from src.models.style import (24EquivalenceApproach as EquivalenceApproachModel,25FormalityLevel as FormalityLevelModel,26GenderLanguage as GenderLanguageModel,27ReadabilityLevel as ReadabilityLevelModel,28TermConsistency as TermConsistencyModel,29TheologicalTradition as TheologicalTraditionModel,30TranslationStyle as TranslationStyleModel,31)32from src.models.translation_memory import (33MatchType as MatchTypeModel,34SegmentLevel as SegmentLevelModel,35TMMatch as TMMatchModel,36TMSegment as TMSegmentModel,37)38from src.models.verse import (39BookMetadata as BookMetadataModel,40ChapterSummary as ChapterSummaryModel,41TranslationContext as TranslationContextModel,42TranslationOutput as TranslationOutputModel,43Verse as VerseModel,44VerseReference as VerseReferenceModel,45)46from src.orchestration.provenance import TranslationProvenance as TranslationProvenanceModel474849# =============================================================================50# Proto-like Enums (mirrors bible_translation.proto enums)51# =============================================================================525354class BiblicalTestament(IntEnum):55"""Biblical testament classification."""5657TESTAMENT_UNSPECIFIED = 058OLD_TESTAMENT = 159NEW_TESTAMENT = 2606162# Alias for backward compatibility63Testament = BiblicalTestament646566class TheologicalDomain(IntEnum):67"""Theological domain categories for terms."""6869THEOLOGICAL_DOMAIN_UNSPECIFIED = 070CHRISTOLOGY = 171SOTERIOLOGY = 272ESCHATOLOGY = 373PNEUMATOLOGY = 474ECCLESIOLOGY = 575HAMARTIOLOGY = 676THEOLOGY_PROPER = 777ANTHROPOLOGY = 878GENERAL = 9798081class EquivalenceApproach(IntEnum):82"""Translation equivalence approach."""8384EQUIVALENCE_APPROACH_UNSPECIFIED = 085FORMAL = 186DYNAMIC = 287OPTIMAL = 3888990class GenderLanguage(IntEnum):91"""Gender-inclusive language approach."""9293GENDER_LANGUAGE_UNSPECIFIED = 094TRADITIONAL = 195MODERATE_INCLUSIVE = 296FULLY_INCLUSIVE = 3979899class ReadabilityLevel(IntEnum):100"""Target audience readability level."""101102READABILITY_LEVEL_UNSPECIFIED = 0103SCHOLARLY = 1104GENERAL_PUBLIC = 2105CHILDREN = 3106WEB_NATIVE = 4107108109class TheologicalTradition(IntEnum):110"""Theological interpretive tradition."""111112THEOLOGICAL_TRADITION_UNSPECIFIED = 0113EVANGELICAL = 1114CATHOLIC = 2115ORTHODOX = 3116ECUMENICAL = 4117118119class TermConsistency(IntEnum):120"""Term translation consistency approach."""121122TERM_CONSISTENCY_UNSPECIFIED = 0123STRICT = 1124BALANCED = 2125CONTEXTUAL = 3126127128class FormalityLevel(IntEnum):129"""Japanese formality level."""130131FORMALITY_LEVEL_UNSPECIFIED = 0132CASUAL = 1133STANDARD = 2134FORMAL_LEVEL = 3135POLITE = 4136137138class MatchType(IntEnum):139"""Translation memory match type."""140141MATCH_TYPE_UNSPECIFIED = 0142EXACT = 1143FUZZY = 2144PHRASE = 3145146147class ConfidenceLevel(IntEnum):148"""Confidence level for translations."""149150CONFIDENCE_LEVEL_UNSPECIFIED = 0151LOW = 1152MEDIUM = 2153HIGH = 3154VERY_HIGH = 4155156157class SegmentLevel(IntEnum):158"""Level of granularity for a translation segment."""159160SEGMENT_LEVEL_UNSPECIFIED = 0161VERSE = 1162PHRASE = 2163164165# =============================================================================166# Proto-like Message Dataclasses167# =============================================================================168169170@dataclass171class VerseReferenceProto:172"""Proto representation of a verse reference."""173174book: str = ""175chapter: int = 0176verse: int = 0177178179@dataclass180class VerseProto:181"""Proto representation of a Bible verse."""182183reference: VerseReferenceProto = field(default_factory=VerseReferenceProto)184greek_text: str | None = None185greek_translit: str | None = None186hebrew_text: str | None = None187hebrew_translit: str | None = None188japanese_meiji: str | None = None189japanese_taisho: str | None = None190english_kjv: str | None = None191english_asv: str | None = None192193194@dataclass195class BookMetadataProto:196"""Proto representation of book metadata."""197198book: str = ""199name_japanese: str = ""200name_english: str = ""201testament: Testament = Testament.TESTAMENT_UNSPECIFIED202author: str = ""203themes: list[str] = field(default_factory=list)204description: str = ""205chapter_count: int = 0206verse_count: int = 0207208209@dataclass210class ChapterSummaryProto:211"""Proto representation of chapter summary."""212213book: str = ""214chapter: int = 0215summary: str = ""216key_events: list[str] = field(default_factory=list)217key_characters: list[str] = field(default_factory=list)218key_themes: list[str] = field(default_factory=list)219220221@dataclass222class GlossaryTermProto:223"""Proto representation of a glossary term."""224225original: str = ""226transliteration: str = ""227japanese: str = ""228english: str = ""229notes: str | None = None230lemma: str | None = None231inflected_forms: list[str] = field(default_factory=list)232strongs_number: str | None = None233definition: str | None = None234semantic_range: list[str] = field(default_factory=list)235domain: TheologicalDomain = TheologicalDomain.GENERAL236usage_examples: list[str] = field(default_factory=list)237frequency: int = 0238related_terms: list[str] = field(default_factory=list)239avoid_translations: list[str] = field(default_factory=list)240241242@dataclass243class TranslationStyleProto:244"""Proto representation of translation style configuration."""245246equivalence_approach: EquivalenceApproach = EquivalenceApproach.EQUIVALENCE_APPROACH_UNSPECIFIED247gender_language: GenderLanguage = GenderLanguage.GENDER_LANGUAGE_UNSPECIFIED248readability_level: ReadabilityLevel = ReadabilityLevel.READABILITY_LEVEL_UNSPECIFIED249theological_tradition: TheologicalTradition = (250TheologicalTradition.THEOLOGICAL_TRADITION_UNSPECIFIED251)252textual_basis: str = ""253term_consistency: TermConsistency = TermConsistency.TERM_CONSISTENCY_UNSPECIFIED254formality_level: FormalityLevel = FormalityLevel.FORMALITY_LEVEL_UNSPECIFIED255avoid_archaic: list[str] = field(default_factory=list)256target_style_reference: str = ""257additional_instructions: str | None = None258259260@dataclass261class FuriganaAnnotationProto:262"""Proto representation of furigana annotation."""263264start_offset: int = 0265end_offset: int = 0266base_text: str = ""267reading: str = ""268is_standard_reading: bool = True269reading_source: str = "dictionary"270271272@dataclass273class AnnotatedWordProto:274"""Proto representation of an annotated word."""275276surface_form: str = ""277lemma: str = ""278part_of_speech: str = ""279reading: str = ""280alternative_readings: list[str] = field(default_factory=list)281start_offset: int = 0282end_offset: int = 0283284285@dataclass286class TMSegmentProto:287"""Proto representation of a translation memory segment."""288289id: str = ""290source_text: str = ""291source_normalized: str = ""292target_text: str = ""293reference: VerseReferenceProto = field(default_factory=VerseReferenceProto)294level: SegmentLevel = SegmentLevel.VERSE295created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))296reuse_count: int = 0297metadata: dict = field(default_factory=dict)298299300@dataclass301class TranslationMemoryMatchProto:302"""Proto representation of a translation memory match."""303304segment: TMSegmentProto = field(default_factory=TMSegmentProto)305similarity: float = 0.0306match_type: MatchType = MatchType.MATCH_TYPE_UNSPECIFIED307308309@dataclass310class TranslationProvenanceProto:311"""Proto representation of translation provenance."""312313source_texts: dict[str, str] = field(default_factory=dict)314source_hashes: dict[str, str] = field(default_factory=dict)315preceding_verses: list[str] = field(default_factory=list)316following_verses: list[str] = field(default_factory=list)317pericope_id: str | None = None318style_config_hash: str = ""319glossary_hash: str = ""320prompt_version: str = "v1.0"321model: str = ""322system_prompt: str = ""323user_prompt: str = ""324tm_matches_used: list[dict] = field(default_factory=list)325attempt_number: int = 1326started_at: datetime | None = None327completed_at: datetime | None = None328evaluation_score: float | None = None329evaluation_flags: list[str] = field(default_factory=list)330chapter_summary: str | None = None331book_metadata: dict = field(default_factory=dict)332333334@dataclass335class TranslationResultProto:336"""Proto representation of translation result with furigana."""337338reference: VerseReferenceProto = field(default_factory=VerseReferenceProto)339text: str = ""340text_with_inline_furigana: str = ""341furigana_annotations: list[FuriganaAnnotationProto] = field(default_factory=list)342annotated_words: list[AnnotatedWordProto] = field(default_factory=list)343model: str = ""344timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc))345prompt_version: str = ""346sources_used: list[str] = field(default_factory=list)347glossary_terms_applied: list[str] = field(default_factory=list)348confidence: ConfidenceLevel = ConfidenceLevel.CONFIDENCE_LEVEL_UNSPECIFIED349confidence_score: float = 0.0350input_hash: str = ""351provenance: TranslationProvenanceProto | None = None352353354@dataclass355class TranslationPromptInputProto:356"""Proto representation of complete translation input."""357358target_verse: VerseProto = field(default_factory=VerseProto)359preceding_verses: list[VerseProto] = field(default_factory=list)360following_verses: list[VerseProto] = field(default_factory=list)361chapter_summary: ChapterSummaryProto | None = None362book_metadata: BookMetadataProto | None = None363glossary_terms: list[GlossaryTermProto] = field(default_factory=list)364style: TranslationStyleProto = field(default_factory=TranslationStyleProto)365tm_matches: list[TranslationMemoryMatchProto] = field(default_factory=list)366system_prompt: str = ""367user_prompt: str = ""368model: str = ""369prompt_version: str = ""370request_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."""380return VerseReferenceProto(381book=ref.book,382chapter=ref.chapter,383verse=ref.verse,384)385386387def verse_to_proto(verse: VerseModel) -> VerseProto:388"""Convert a Verse Pydantic model to proto representation."""389return VerseProto(390reference=verse_reference_to_proto(verse.reference),391greek_text=verse.greek_text,392greek_translit=verse.greek_translit,393hebrew_text=verse.hebrew_text,394hebrew_translit=verse.hebrew_translit,395japanese_meiji=verse.japanese_meiji,396japanese_taisho=verse.japanese_taisho,397english_kjv=verse.english_kjv,398english_asv=verse.english_asv,399)400401402def _theological_domain_to_proto(domain: TheologicalDomainModel) -> TheologicalDomain:403"""Convert TheologicalDomain enum to proto enum."""404domain_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}415return domain_map.get(domain.value, TheologicalDomain.GENERAL)416417418def book_metadata_to_proto(metadata: BookMetadataModel) -> BookMetadataProto:419"""Convert BookMetadata Pydantic model to proto representation."""420testament = Testament.NEW_TESTAMENT if metadata.testament == "NT" else Testament.OLD_TESTAMENT421return BookMetadataProto(422book=metadata.book,423name_japanese=metadata.name_japanese,424name_english=metadata.name_english,425testament=testament,426author=metadata.author,427themes=list(metadata.themes),428description=metadata.description,429chapter_count=metadata.chapter_count,430verse_count=metadata.verse_count,431)432433434def chapter_summary_to_proto(summary: ChapterSummaryModel) -> ChapterSummaryProto:435"""Convert ChapterSummary Pydantic model to proto representation."""436return ChapterSummaryProto(437book=summary.book,438chapter=summary.chapter,439summary=summary.summary,440key_events=list(summary.key_events),441key_characters=list(summary.key_characters),442key_themes=list(summary.key_themes),443)444445446def glossary_term_to_proto(term: GlossaryTermModel) -> GlossaryTermProto:447"""Convert GlossaryTerm Pydantic model to proto representation."""448return GlossaryTermProto(449original=term.original,450transliteration=term.transliteration,451japanese=term.japanese,452english=term.english,453notes=term.notes,454lemma=term.lemma,455inflected_forms=list(term.inflected_forms),456strongs_number=term.strongs_number,457definition=term.definition,458semantic_range=list(term.semantic_range),459domain=_theological_domain_to_proto(term.domain),460usage_examples=list(term.usage_examples),461frequency=term.frequency,462related_terms=list(term.related_terms),463avoid_translations=list(term.avoid_translations),464)465466467def _equivalence_approach_to_proto(approach: EquivalenceApproachModel) -> EquivalenceApproach:468"""Convert EquivalenceApproach enum to proto enum."""469approach_map = {470"formal": EquivalenceApproach.FORMAL,471"dynamic": EquivalenceApproach.DYNAMIC,472"optimal": EquivalenceApproach.OPTIMAL,473}474return approach_map.get(approach.value, EquivalenceApproach.EQUIVALENCE_APPROACH_UNSPECIFIED)475476477def _gender_language_to_proto(gender: GenderLanguageModel) -> GenderLanguage:478"""Convert GenderLanguage enum to proto enum."""479gender_map = {480"traditional": GenderLanguage.TRADITIONAL,481"moderate_inclusive": GenderLanguage.MODERATE_INCLUSIVE,482"fully_inclusive": GenderLanguage.FULLY_INCLUSIVE,483}484return gender_map.get(gender.value, GenderLanguage.GENDER_LANGUAGE_UNSPECIFIED)485486487def _readability_level_to_proto(level: ReadabilityLevelModel) -> ReadabilityLevel:488"""Convert ReadabilityLevel enum to proto enum."""489level_map = {490"scholarly": ReadabilityLevel.SCHOLARLY,491"general_public": ReadabilityLevel.GENERAL_PUBLIC,492"children": ReadabilityLevel.CHILDREN,493"web_native": ReadabilityLevel.WEB_NATIVE,494}495return level_map.get(level.value, ReadabilityLevel.READABILITY_LEVEL_UNSPECIFIED)496497498def _theological_tradition_to_proto(tradition: TheologicalTraditionModel) -> TheologicalTradition:499"""Convert TheologicalTradition enum to proto enum."""500tradition_map = {501"evangelical": TheologicalTradition.EVANGELICAL,502"catholic": TheologicalTradition.CATHOLIC,503"orthodox": TheologicalTradition.ORTHODOX,504"ecumenical": TheologicalTradition.ECUMENICAL,505}506return tradition_map.get(tradition.value, TheologicalTradition.THEOLOGICAL_TRADITION_UNSPECIFIED)507508509def _term_consistency_to_proto(consistency: TermConsistencyModel) -> TermConsistency:510"""Convert TermConsistency enum to proto enum."""511consistency_map = {512"strict": TermConsistency.STRICT,513"balanced": TermConsistency.BALANCED,514"contextual": TermConsistency.CONTEXTUAL,515}516return consistency_map.get(consistency.value, TermConsistency.TERM_CONSISTENCY_UNSPECIFIED)517518519def _formality_level_to_proto(level: FormalityLevelModel) -> FormalityLevel:520"""Convert FormalityLevel enum to proto enum."""521level_map = {522"casual": FormalityLevel.CASUAL,523"standard": FormalityLevel.STANDARD,524"polite": FormalityLevel.POLITE,525"formal": FormalityLevel.FORMAL_LEVEL,526}527return level_map.get(level.value, FormalityLevel.FORMALITY_LEVEL_UNSPECIFIED)528529530def translation_style_to_proto(style: TranslationStyleModel) -> TranslationStyleProto:531"""Convert TranslationStyle dataclass to proto representation."""532return TranslationStyleProto(533equivalence_approach=_equivalence_approach_to_proto(style.equivalence_approach),534gender_language=_gender_language_to_proto(style.gender_language),535readability_level=_readability_level_to_proto(style.readability_level),536theological_tradition=_theological_tradition_to_proto(style.theological_tradition),537textual_basis=style.textual_basis,538term_consistency=_term_consistency_to_proto(style.term_consistency),539formality_level=_formality_level_to_proto(style.formality_level),540avoid_archaic=list(style.avoid_archaic),541target_style_reference=style.target_style_reference,542additional_instructions=style.additional_instructions,543)544545546def furigana_annotation_to_proto(annotation: FuriganaAnnotation) -> FuriganaAnnotationProto:547"""Convert FuriganaAnnotation to proto representation."""548return FuriganaAnnotationProto(549start_offset=annotation.start_offset,550end_offset=annotation.end_offset,551base_text=annotation.base_text,552reading=annotation.reading,553is_standard_reading=annotation.is_standard_reading,554reading_source=annotation.reading_source,555)556557558def annotated_word_to_proto(559surface_form: str,560lemma: str,561part_of_speech: str,562reading: str,563start_offset: int,564end_offset: int,565alternative_readings: list[str] | None = None,566) -> AnnotatedWordProto:567"""Create an AnnotatedWordProto from components."""568return AnnotatedWordProto(569surface_form=surface_form,570lemma=lemma,571part_of_speech=part_of_speech,572reading=reading,573alternative_readings=alternative_readings or [],574start_offset=start_offset,575end_offset=end_offset,576)577578579def translation_context_to_prompt_input(580context: TranslationContextModel,581system_prompt: str = "",582user_prompt: str = "",583model: str = "",584prompt_version: str = "v1.0",585) -> TranslationPromptInputProto:586"""Convert TranslationContext to TranslationPromptInput proto.587588Args:589context: The translation context with verses, metadata, and glossary590system_prompt: The system prompt used for translation591user_prompt: The user prompt used for translation592model: The model name used for translation593prompt_version: Version of the prompt template594595Returns:596TranslationPromptInputProto capturing all inputs for reproducibility597"""598return TranslationPromptInputProto(599target_verse=verse_to_proto(context.target_verse),600preceding_verses=[verse_to_proto(v) for v in context.preceding_verses],601following_verses=[verse_to_proto(v) for v in context.following_verses],602chapter_summary=(603chapter_summary_to_proto(context.chapter_summary)604if context.chapter_summary605else None606),607book_metadata=(608book_metadata_to_proto(context.book_metadata)609if context.book_metadata610else None611),612glossary_terms=[glossary_term_to_proto(t) for t in context.glossary_terms],613system_prompt=system_prompt,614user_prompt=user_prompt,615model=model,616prompt_version=prompt_version,617request_time=datetime.now(timezone.utc),618)619620621def translation_result_to_proto(622output: TranslationOutputModel,623text_with_furigana: str = "",624furigana_annotations: list[FuriganaAnnotation] | None = None,625annotated_words: list[AnnotatedWordProto] | None = None,626confidence: ConfidenceLevel = ConfidenceLevel.CONFIDENCE_LEVEL_UNSPECIFIED,627confidence_score: float = 0.0,628input_hash: str = "",629) -> TranslationResultProto:630"""Convert TranslationOutput to TranslationResult proto with furigana.631632Args:633output: The translation output from the translator634text_with_furigana: Text with inline furigana notation635furigana_annotations: Parsed furigana annotations636annotated_words: Linguistic word annotations637confidence: Confidence level enum638confidence_score: Numeric confidence score (0-1)639input_hash: Hash of the input for reproducibility tracking640641Returns:642TranslationResultProto with rich annotation data643"""644return TranslationResultProto(645reference=verse_reference_to_proto(output.reference),646text=output.text,647text_with_inline_furigana=text_with_furigana or output.text,648furigana_annotations=[649furigana_annotation_to_proto(a) for a in (furigana_annotations or [])650],651annotated_words=annotated_words or [],652model=output.model,653timestamp=output.timestamp,654prompt_version=output.prompt_version,655sources_used=list(output.sources_used),656glossary_terms_applied=list(output.glossary_terms_applied),657confidence=confidence,658confidence_score=confidence_score,659input_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."""670from src.models.verse import VerseReference671672return VerseReference(673book=proto.book,674chapter=proto.chapter,675verse=proto.verse,676)677678679def proto_to_verse(proto: VerseProto) -> VerseModel:680"""Convert proto Verse to Pydantic model."""681from src.models.verse import Verse682683return Verse(684reference=proto_to_verse_reference(proto.reference),685greek_text=proto.greek_text,686greek_translit=proto.greek_translit,687hebrew_text=proto.hebrew_text,688hebrew_translit=proto.hebrew_translit,689japanese_meiji=proto.japanese_meiji,690japanese_taisho=proto.japanese_taisho,691english_kjv=proto.english_kjv,692english_asv=proto.english_asv,693)694695696def proto_to_translation_result(proto: TranslationResultProto) -> TranslationOutputModel:697"""Convert proto TranslationResult to Pydantic TranslationOutput.698699Note: This returns the basic TranslationOutput model. The furigana700annotations and other rich data are only available in the proto format.701"""702from src.models.verse import TranslationOutput703704return TranslationOutput(705reference=proto_to_verse_reference(proto.reference),706text=proto.text,707model=proto.model,708timestamp=proto.timestamp,709prompt_version=proto.prompt_version,710sources_used=list(proto.sources_used),711glossary_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."""722level_map = {723"verse": SegmentLevel.VERSE,724"phrase": SegmentLevel.PHRASE,725}726return 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."""731type_map = {732"exact": MatchType.EXACT,733"fuzzy": MatchType.FUZZY,734"phrase": MatchType.PHRASE,735}736return 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.741742Args:743segment: The TMSegment to convert.744745Returns:746TMSegmentProto representation.747"""748return TMSegmentProto(749id=segment.id,750source_text=segment.source_text,751source_normalized=segment.source_normalized,752target_text=segment.target_text,753reference=verse_reference_to_proto(segment.reference),754level=_segment_level_to_proto(segment.level),755created_at=segment.created_at,756reuse_count=segment.reuse_count,757metadata=dict(segment.metadata),758)759760761def tm_match_to_proto(match: TMMatchModel) -> TranslationMemoryMatchProto:762"""Convert TMMatch Pydantic model to proto representation.763764Args:765match: The TMMatch to convert.766767Returns:768TranslationMemoryMatchProto representation.769"""770return TranslationMemoryMatchProto(771segment=tm_segment_to_proto(match.segment),772similarity=match.similarity,773match_type=_match_type_to_proto(match.match_type),774)775776777def provenance_to_proto(provenance: TranslationProvenanceModel) -> TranslationProvenanceProto:778"""Convert TranslationProvenance to proto representation.779780Args:781provenance: The TranslationProvenance to convert.782783Returns:784TranslationProvenanceProto representation.785"""786return TranslationProvenanceProto(787source_texts=dict(provenance.source_texts),788source_hashes=dict(provenance.source_hashes),789preceding_verses=list(provenance.preceding_verses),790following_verses=list(provenance.following_verses),791pericope_id=provenance.pericope_id,792style_config_hash=provenance.style_config_hash,793glossary_hash=provenance.glossary_hash,794prompt_version=provenance.prompt_version,795model=provenance.model,796system_prompt=provenance.system_prompt,797user_prompt=provenance.user_prompt,798tm_matches_used=list(provenance.tm_matches_used),799attempt_number=provenance.attempt_number,800started_at=provenance.started_at,801completed_at=provenance.completed_at,802evaluation_score=provenance.evaluation_score,803evaluation_flags=list(provenance.evaluation_flags),804chapter_summary=provenance.chapter_summary,805book_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."""816from src.models.translation_memory import SegmentLevel as SegmentLevelModel817818level_map = {819SegmentLevel.VERSE: SegmentLevelModel.VERSE,820SegmentLevel.PHRASE: SegmentLevelModel.PHRASE,821}822return level_map.get(level, SegmentLevelModel.VERSE)823824825def _proto_to_match_type(match_type: MatchType) -> MatchTypeModel:826"""Convert proto MatchType to model enum."""827from src.models.translation_memory import MatchType as MatchTypeModel828829type_map = {830MatchType.EXACT: MatchTypeModel.EXACT,831MatchType.FUZZY: MatchTypeModel.FUZZY,832MatchType.PHRASE: MatchTypeModel.PHRASE,833}834return type_map.get(match_type, MatchTypeModel.EXACT)835836837def proto_to_tm_segment(proto: TMSegmentProto) -> TMSegmentModel:838"""Convert proto TMSegment to Pydantic model.839840Args:841proto: The TMSegmentProto to convert.842843Returns:844TMSegment Pydantic model.845"""846from src.models.translation_memory import TMSegment847848return TMSegment(849id=proto.id,850source_text=proto.source_text,851source_normalized=proto.source_normalized,852target_text=proto.target_text,853reference=proto_to_verse_reference(proto.reference),854level=_proto_to_segment_level(proto.level),855created_at=proto.created_at,856reuse_count=proto.reuse_count,857metadata=dict(proto.metadata),858)859860861def proto_to_tm_match(proto: TranslationMemoryMatchProto) -> TMMatchModel:862"""Convert proto TMMatch to Pydantic model.863864Args:865proto: The TranslationMemoryMatchProto to convert.866867Returns:868TMMatch Pydantic model.869"""870from src.models.translation_memory import TMMatch871872return TMMatch(873segment=proto_to_tm_segment(proto.segment),874similarity=proto.similarity,875match_type=_proto_to_match_type(proto.match_type),876)877878879def proto_to_provenance(proto: TranslationProvenanceProto) -> TranslationProvenanceModel:880"""Convert proto TranslationProvenance to model.881882Args:883proto: The TranslationProvenanceProto to convert.884885Returns:886TranslationProvenance model.887"""888from src.orchestration.provenance import TranslationProvenance889890return TranslationProvenance(891source_texts=dict(proto.source_texts),892source_hashes=dict(proto.source_hashes),893preceding_verses=list(proto.preceding_verses),894following_verses=list(proto.following_verses),895pericope_id=proto.pericope_id,896style_config_hash=proto.style_config_hash,897glossary_hash=proto.glossary_hash,898prompt_version=proto.prompt_version,899model=proto.model,900system_prompt=proto.system_prompt,901user_prompt=proto.user_prompt,902tm_matches_used=list(proto.tm_matches_used),903attempt_number=proto.attempt_number,904started_at=proto.started_at,905completed_at=proto.completed_at,906evaluation_score=proto.evaluation_score,907evaluation_flags=list(proto.evaluation_flags),908chapter_summary=proto.chapter_summary,909book_metadata=dict(proto.book_metadata),910)911