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

OPEN SOURCE

software/src/validation/checks.py

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

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

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

software/src/validation/checks.py223 行このファイルを保存 ↓

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

ファイルの確認情報SHA-256 eea5156adc33ce4a35af12324b5feac1a0c658356ae7ab37901916ce0a3afefc
1"""Validation checks for translations."""23from __future__ import annotations45from dataclasses import dataclass, field6from typing import TYPE_CHECKING78if TYPE_CHECKING:9    from src.context.glossary import GlossaryManager10    from src.models.verse import TranslationOutput111213@dataclass14class ValidationResult:15    """Result of a validation check."""1617    passed: bool18    check_name: str19    message: str20    details: dict = field(default_factory=dict)212223@dataclass24class ValidationReport:25    """Full validation report for a translation."""2627    results: list[ValidationResult] = field(default_factory=list)2829    @property30    def all_passed(self) -> bool:31        """Check if all validations passed."""32        return all(r.passed for r in self.results)3334    @property35    def failed_checks(self) -> list[ValidationResult]:36        """Get list of failed checks."""37        return [r for r in self.results if not r.passed]3839    def add_result(self, result: ValidationResult) -> None:40        """Add a validation result."""41        self.results.append(result)424344class TranslationValidator:45    """Validates translation outputs."""4647    def __init__(48        self,49        glossary: GlossaryManager | None = None,50        min_length: int = 5,51        max_length: int = 2000,52    ) -> None:53        """Initialize the validator.5455        Args:56            glossary: Glossary manager for term consistency checks.57            min_length: Minimum translation length.58            max_length: Maximum translation length.59        """60        self.glossary = glossary61        self.min_length = min_length62        self.max_length = max_length6364    def validate(self, output: TranslationOutput) -> ValidationReport:65        """Run all validation checks on a translation.6667        Args:68            output: Translation output to validate.6970        Returns:71            ValidationReport with all check results.72        """73        report = ValidationReport()7475        report.add_result(self._check_not_empty(output))76        report.add_result(self._check_length(output))77        report.add_result(self._check_no_english_only(output))78        report.add_result(self._check_has_japanese(output))7980        if self.glossary:81            report.add_result(self._check_glossary_consistency(output))8283        return report8485    def _check_not_empty(self, output: TranslationOutput) -> ValidationResult:86        """Check that translation is not empty."""87        if not output.text or not output.text.strip():88            return ValidationResult(89                passed=False,90                check_name="not_empty",91                message="Translation is empty",92            )93        return ValidationResult(94            passed=True,95            check_name="not_empty",96            message="Translation is not empty",97        )9899    def _check_length(self, output: TranslationOutput) -> ValidationResult:100        """Check translation length is within bounds."""101        length = len(output.text)102103        if length < self.min_length:104            return ValidationResult(105                passed=False,106                check_name="length",107                message=f"Translation too short ({length} < {self.min_length})",108                details={"length": length, "min": self.min_length},109            )110111        if length > self.max_length:112            return ValidationResult(113                passed=False,114                check_name="length",115                message=f"Translation too long ({length} > {self.max_length})",116                details={"length": length, "max": self.max_length},117            )118119        return ValidationResult(120            passed=True,121            check_name="length",122            message=f"Translation length OK ({length})",123            details={"length": length},124        )125126    def _check_no_english_only(self, output: TranslationOutput) -> ValidationResult:127        """Check that translation is not English-only."""128        text = output.text.strip()129130        # Check if text contains any Japanese characters131        has_japanese = any(132            "\u3040" <= char <= "\u309f"  # Hiragana133            or "\u30a0" <= char <= "\u30ff"  # Katakana134            or "\u4e00" <= char <= "\u9fff"  # CJK135            for char in text136        )137138        if not has_japanese:139            return ValidationResult(140                passed=False,141                check_name="no_english_only",142                message="Translation appears to be English only",143            )144145        return ValidationResult(146            passed=True,147            check_name="no_english_only",148            message="Translation contains Japanese characters",149        )150151    def _check_has_japanese(self, output: TranslationOutput) -> ValidationResult:152        """Check that translation has sufficient Japanese content."""153        text = output.text.strip()154155        # Count Japanese characters156        japanese_chars = sum(157            1158            for char in text159            if (160                "\u3040" <= char <= "\u309f"  # Hiragana161                or "\u30a0" <= char <= "\u30ff"  # Katakana162                or "\u4e00" <= char <= "\u9fff"  # CJK163            )164        )165166        total_chars = len(text)167        ratio = japanese_chars / total_chars if total_chars > 0 else 0168169        if ratio < 0.3:170            return ValidationResult(171                passed=False,172                check_name="has_japanese",173                message=f"Japanese character ratio too low ({ratio:.1%})",174                details={"ratio": ratio, "japanese_chars": japanese_chars},175            )176177        return ValidationResult(178            passed=True,179            check_name="has_japanese",180            message=f"Japanese character ratio OK ({ratio:.1%})",181            details={"ratio": ratio, "japanese_chars": japanese_chars},182        )183184    def _check_glossary_consistency(185        self, output: TranslationOutput186    ) -> ValidationResult:187        """Check that glossary terms are used consistently.188189        Note: This method should only be called when self.glossary is set.190        The validate() method handles this check.191        """192        assert self.glossary is not None193        # Get terms that were supposed to be applied194        applied_terms = output.glossary_terms_applied195196        if not applied_terms:197            return ValidationResult(198                passed=True,199                check_name="glossary_consistency",200                message="No glossary terms expected",201            )202203        # Check if Japanese translations are present204        missing = []205        for term_original in applied_terms:206            term = self.glossary.get_by_original(term_original)207            if term and term.japanese not in output.text:208                missing.append(term_original)209210        if missing:211            return ValidationResult(212                passed=False,213                check_name="glossary_consistency",214                message=f"Missing expected glossary terms: {', '.join(missing)}",215                details={"missing": missing},216            )217218        return ValidationResult(219            passed=True,220            check_name="glossary_consistency",221            message="All expected glossary terms present",222        )223