"""Pericope management for chunk-based translation.""" from __future__ import annotations import json from pathlib import Path from src.models.pericope import Pericope, PericopeIndex class PericopeManager: """Manages pericope data for chunk-based translation. Loads pericope boundaries from JSON and provides lookup functionality to find the pericope containing any verse. """ def __init__(self, data_path: Path | str | None = None) -> None: """Initialize the pericope manager. Args: data_path: Path to pericopes.json. If None, uses default. """ if data_path is None: self._data_path = Path("data/metadata/pericopes.json") else: self._data_path = Path(data_path) self._indexes: dict[str, PericopeIndex] = {} self._loaded = False @property def data_path(self) -> Path: """Get the data file path.""" return self._data_path def _ensure_loaded(self) -> None: """Ensure pericope data is loaded.""" if not self._loaded: self.load() def load(self) -> None: """Load pericope data from JSON file.""" self._indexes.clear() self._loaded = True if not self._data_path.exists(): return with self._data_path.open("r", encoding="utf-8") as f: data = json.load(f) for book_name, book_data in data.get("books", {}).items(): pericopes = [] for p_data in book_data.get("pericopes", []): pericopes.append(Pericope(**p_data)) self._indexes[book_name] = PericopeIndex( book=book_name, name=book_data.get("name", book_name.title()), pericopes=pericopes, sblgnt_paragraphs=book_data.get("sblgnt_paragraphs"), ) def reload(self) -> None: """Force reload from file.""" self._loaded = False self.load() def get_pericope( self, book: str, chapter: int, verse: int ) -> Pericope | None: """Get the pericope containing a verse. Args: book: Book name. chapter: Chapter number. verse: Verse number. Returns: The Pericope containing the verse, or None if not found. """ self._ensure_loaded() index = self._indexes.get(book.lower()) if index is None: return None return index.find_pericope(chapter, verse) def get_pericope_by_id(self, pericope_id: str) -> tuple[str, Pericope] | None: """Get a pericope by its ID. Args: pericope_id: The pericope identifier. Returns: Tuple of (book_name, Pericope) if found, None otherwise. """ self._ensure_loaded() for book_name, index in self._indexes.items(): pericope = index.get_pericope_by_id(pericope_id) if pericope: return (book_name, pericope) return None def get_book_pericopes(self, book: str) -> list[Pericope]: """Get all pericopes for a book. Args: book: Book name. Returns: List of pericopes for the book. """ self._ensure_loaded() index = self._indexes.get(book.lower()) if index is None: return [] return index.pericopes def get_chapter_pericopes(self, book: str, chapter: int) -> list[Pericope]: """Get all pericopes that include verses from a chapter. Args: book: Book name. chapter: Chapter number. Returns: List of pericopes containing verses from the chapter. """ self._ensure_loaded() index = self._indexes.get(book.lower()) if index is None: return [] return index.get_chapter_pericopes(chapter) def get_available_books(self) -> list[str]: """Get list of books with pericope data. Returns: List of book names. """ self._ensure_loaded() return list(self._indexes.keys()) def has_pericope_data(self, book: str) -> bool: """Check if pericope data exists for a book. Args: book: Book name. Returns: True if pericope data exists. """ self._ensure_loaded() index = self._indexes.get(book.lower()) return index is not None and len(index.pericopes) > 0 def get_book_index(self, book: str) -> PericopeIndex | None: """Get the full pericope index for a book. Args: book: Book name. Returns: PericopeIndex or None if not found. """ self._ensure_loaded() return self._indexes.get(book.lower()) def get_statistics(self) -> dict: """Get statistics about loaded pericope data. Returns: Dictionary with statistics. """ self._ensure_loaded() total_pericopes = 0 total_verses = 0 books_with_data = [] for book_name, index in self._indexes.items(): if index.pericopes: books_with_data.append(book_name) total_pericopes += len(index.pericopes) for p in index.pericopes: total_verses += p.verse_count return { "books_with_data": len(books_with_data), "book_names": books_with_data, "total_pericopes": total_pericopes, "total_verses_covered": total_verses, } def __len__(self) -> int: """Get total number of pericopes across all books.""" self._ensure_loaded() return sum(len(idx.pericopes) for idx in self._indexes.values())