OPEN SOURCE
software/src/evaluation/storage.py
このサイトで読み、必要なファイルを保存できます。
日常訳はAIを用いて作成された翻訳です。人間の聖書学・原語の専門家による全訳の検証は確認されていません。既存の翻訳や原語資料と照らし合わせてお読みください。
1"""Storage utilities for translation evaluations."""23from __future__ import annotations45import json6from datetime import datetime7from pathlib import Path8from typing import Any910from src.models.evaluation import (11BookEvaluationSummary,12ChapterEvaluation,13EvaluationScore,14VerseEvaluation,15)16from src.models.verse import VerseReference171819class EvaluationStorage:20"""Manages persistence of translation evaluations."""2122def __init__(self, data_dir: Path | str) -> None:23"""Initialize the evaluation storage.2425Args:26data_dir: Path to the data directory.27"""28self.data_dir = Path(data_dir)29self.output_dir = self.data_dir / "output"3031def _get_eval_path(self, book: str, chapter: int) -> Path:32"""Get path to evaluation file for a chapter."""33return self.output_dir / book / f"chapter_{chapter:02d}_eval.json"3435def _serialize_datetime(self, obj: Any) -> Any:36"""Serialize datetime objects for JSON."""37if isinstance(obj, datetime):38return obj.isoformat()39raise TypeError(f"Object of type {type(obj)} is not JSON serializable")4041def save_chapter_evaluation(self, evaluation: ChapterEvaluation) -> Path:42"""Save a chapter evaluation to JSON.4344Args:45evaluation: ChapterEvaluation to save.4647Returns:48Path to the saved file.49"""50path = self._get_eval_path(evaluation.book, evaluation.chapter)51path.parent.mkdir(parents=True, exist_ok=True)5253# Convert to dict for JSON serialization54data = {55"book": evaluation.book,56"chapter": evaluation.chapter,57"evaluator_model": evaluation.evaluator_model,58"evaluated_at": evaluation.evaluated_at.isoformat(),59"verses": {},60"aggregate": {61"scores": evaluation.aggregate_scores,62"overall": evaluation.overall_score,63"flagged_count": evaluation.flagged_count,64"flagged_verses": evaluation.flagged_verses,65},66}6768for verse_num, verse_eval in evaluation.verses.items():69data["verses"][str(verse_num)] = {70"scores": {71dim: {72"score": score.score,73"confidence": score.confidence,74"reasoning": score.reasoning,75}76for dim, score in verse_eval.scores.items()77},78"overall_score": verse_eval.overall_score,79"flags": verse_eval.flags,80"notes": verse_eval.notes,81"timestamp": verse_eval.timestamp.isoformat(),82}8384with path.open("w", encoding="utf-8") as f:85json.dump(data, f, ensure_ascii=False, indent=2)8687return path8889def load_chapter_evaluation(90self, book: str, chapter: int91) -> ChapterEvaluation | None:92"""Load a chapter evaluation from JSON.9394Args:95book: Book name.96chapter: Chapter number.9798Returns:99ChapterEvaluation if found, None otherwise.100"""101path = self._get_eval_path(book, chapter)102if not path.exists():103return None104105with path.open("r", encoding="utf-8") as f:106data = json.load(f)107108verses = {}109for verse_num_str, verse_data in data.get("verses", {}).items():110verse_num = int(verse_num_str)111scores = {}112for dim, score_data in verse_data.get("scores", {}).items():113scores[dim] = EvaluationScore(114dimension=dim,115score=score_data["score"],116confidence=score_data["confidence"],117reasoning=score_data["reasoning"],118)119120verses[verse_num] = VerseEvaluation(121verse_number=verse_num,122evaluator_model=data.get("evaluator_model", "unknown"),123timestamp=datetime.fromisoformat(124verse_data.get("timestamp", datetime.utcnow().isoformat())125),126scores=scores,127flags=verse_data.get("flags", []),128notes=verse_data.get("notes"),129)130131return ChapterEvaluation(132book=data["book"],133chapter=data["chapter"],134evaluator_model=data.get("evaluator_model", "unknown"),135evaluated_at=datetime.fromisoformat(136data.get("evaluated_at", datetime.utcnow().isoformat())137),138verses=verses,139)140141def get_flagged_verses(self, book: str) -> list[VerseReference]:142"""Get all flagged verses for a book.143144Args:145book: Book name.146147Returns:148List of VerseReference objects for flagged verses.149"""150flagged = []151book_dir = self.output_dir / book152153if not book_dir.exists():154return flagged155156for eval_file in sorted(book_dir.glob("chapter_*_eval.json")):157chapter_eval = self.load_chapter_evaluation(158book, int(eval_file.stem.split("_")[1])159)160if chapter_eval:161for verse_num in chapter_eval.flagged_verses:162flagged.append(163VerseReference(164book=book, chapter=chapter_eval.chapter, verse=verse_num165)166)167168return flagged169170def get_book_summary(self, book: str) -> BookEvaluationSummary | None:171"""Generate a summary of evaluations for an entire book.172173Args:174book: Book name.175176Returns:177BookEvaluationSummary if evaluations exist, None otherwise.178"""179book_dir = self.output_dir / book180if not book_dir.exists():181return None182183eval_files = list(book_dir.glob("chapter_*_eval.json"))184if not eval_files:185return None186187all_scores: dict[str, list[float]] = {}188total_verses = 0189flagged_refs: list[str] = []190191for eval_file in sorted(eval_files):192chapter_num = int(eval_file.stem.split("_")[1])193chapter_eval = self.load_chapter_evaluation(book, chapter_num)194195if chapter_eval:196total_verses += len(chapter_eval.verses)197198# Aggregate dimension scores199for dim, score in chapter_eval.aggregate_scores.items():200if dim not in all_scores:201all_scores[dim] = []202all_scores[dim].append(score)203204# Collect flagged verses205for verse_num in chapter_eval.flagged_verses:206flagged_refs.append(f"{book}:{chapter_num}:{verse_num}")207208if not all_scores:209return None210211aggregate = {212dim: round(sum(scores) / len(scores), 1)213for dim, scores in all_scores.items()214}215overall = round(sum(aggregate.values()) / len(aggregate), 1)216217return BookEvaluationSummary(218book=book,219total_chapters=len(eval_files),220total_verses=total_verses,221aggregate_scores=aggregate,222overall_score=overall,223flagged_verses=flagged_refs,224evaluated_at=datetime.utcnow(),225)226227def is_chapter_evaluated(self, book: str, chapter: int) -> bool:228"""Check if a chapter has been evaluated.229230Args:231book: Book name.232chapter: Chapter number.233234Returns:235True if evaluation exists.236"""237return self._get_eval_path(book, chapter).exists()238239def save_verse_evaluation(240self, book: str, chapter: int, evaluation: VerseEvaluation241) -> Path:242"""Save a single verse evaluation.243244Loads existing chapter evaluation if it exists, updates the verse,245and saves back.246247Args:248book: Book name.249chapter: Chapter number.250evaluation: The verse evaluation to save.251252Returns:253Path to the saved file.254"""255# Load existing or create new chapter evaluation256chapter_eval = self.load_chapter_evaluation(book, chapter)257if chapter_eval is None:258chapter_eval = ChapterEvaluation(259book=book,260chapter=chapter,261evaluator_model=evaluation.evaluator_model,262verses={},263)264265# Update the verse266chapter_eval.verses[evaluation.verse_number] = evaluation267268# Save back269return self.save_chapter_evaluation(chapter_eval)270