"""Job state models for fault-tolerant Bible translation orchestration. Provides dataclasses for tracking translation jobs at various granularities: - VerseJob: Individual verse translation tracking - BookJob: Book-level progress and status - BibleJob: Full Bible translation job with configuration snapshot """ from __future__ import annotations import uuid from dataclasses import dataclass, field from datetime import datetime from enum import Enum from typing import Any class JobStatus(str, Enum): """Status of a job at any level (verse, book, or bible).""" PENDING = "pending" IN_PROGRESS = "in_progress" COMPLETED = "completed" FAILED = "failed" CANCELLED = "cancelled" @dataclass class VerseJob: """Tracks the translation status of a single verse. Attributes: book: Book name (e.g., 'john', 'genesis'). chapter: Chapter number. verse: Verse number. status: Current job status. attempts: Number of translation attempts. last_error: Error message from most recent failed attempt. worker_pid: PID of worker process currently handling this verse. started_at: When translation started. completed_at: When translation completed (or failed). """ book: str chapter: int verse: int status: JobStatus = JobStatus.PENDING attempts: int = 0 last_error: str | None = None worker_pid: int | None = None started_at: datetime | None = None completed_at: datetime | None = None @property def reference_str(self) -> str: """Return verse reference as string (e.g., 'john:1:1').""" return f"{self.book}:{self.chapter}:{self.verse}" @dataclass class BookJob: """Tracks the translation status of an entire book. Attributes: book: Book name (e.g., 'john', 'genesis'). status: Current job status. total_verses: Total number of verses in the book. completed_verses: Number of successfully translated verses. failed_verses: Number of verses that failed translation. worker_pid: PID of worker process handling this book. started_at: When book translation started. completed_at: When book translation completed. error_message: Error message if book failed. """ book: str status: JobStatus = JobStatus.PENDING total_verses: int = 0 completed_verses: int = 0 failed_verses: int = 0 worker_pid: int | None = None started_at: datetime | None = None completed_at: datetime | None = None error_message: str | None = None @property def pending_verses(self) -> int: """Return number of pending verses.""" return self.total_verses - self.completed_verses - self.failed_verses @property def progress_percentage(self) -> float: """Return progress as a percentage.""" if self.total_verses == 0: return 0.0 return (self.completed_verses / self.total_verses) * 100 @dataclass class BibleJob: """Tracks a full Bible translation job. Attributes: job_id: Unique identifier for the job (UUID). books: List of books to translate. status: Current job status. config_snapshot: Frozen configuration at job creation. created_at: When job was created. started_at: When translation started. completed_at: When translation completed. total_verses: Total verses across all books. completed_verses: Total completed verses across all books. failed_verses: Total failed verses across all books. """ job_id: str = field(default_factory=lambda: str(uuid.uuid4())) books: list[str] = field(default_factory=list) status: JobStatus = JobStatus.PENDING config_snapshot: dict[str, Any] = field(default_factory=dict) created_at: datetime = field(default_factory=datetime.utcnow) started_at: datetime | None = None completed_at: datetime | None = None total_verses: int = 0 completed_verses: int = 0 failed_verses: int = 0 @property def pending_verses(self) -> int: """Return number of pending verses across all books.""" return self.total_verses - self.completed_verses - self.failed_verses @property def progress_percentage(self) -> float: """Return overall progress as a percentage.""" if self.total_verses == 0: return 0.0 return (self.completed_verses / self.total_verses) * 100 @dataclass class BookResult: """Result of translating a single book. Attributes: book: Book name. success: Whether translation succeeded. verses_translated: Number of verses successfully translated. verses_failed: Number of verses that failed. duration_seconds: Total time taken in seconds. error_message: Error message if failed. """ book: str success: bool verses_translated: int verses_failed: int duration_seconds: float error_message: str | None = None @dataclass class JobResult: """Result of a full Bible translation job. Attributes: job_id: Job identifier. success: Whether job completed successfully (all books translated). books_completed: Number of books fully translated. books_failed: Number of books that failed. total_verses_translated: Total verses across all books. total_verses_failed: Total failed verses across all books. duration_seconds: Total time taken in seconds. book_results: Individual results for each book. """ job_id: str success: bool books_completed: int books_failed: int total_verses_translated: int total_verses_failed: int duration_seconds: float book_results: list[BookResult] = field(default_factory=list) # All 66 books of the Bible in canonical order OLD_TESTAMENT_BOOKS = [ "genesis", "exodus", "leviticus", "numbers", "deuteronomy", "joshua", "judges", "ruth", "1samuel", "2samuel", "1kings", "2kings", "1chronicles", "2chronicles", "ezra", "nehemiah", "esther", "job", "psalms", "proverbs", "ecclesiastes", "song_of_solomon", "isaiah", "jeremiah", "lamentations", "ezekiel", "daniel", "hosea", "joel", "amos", "obadiah", "jonah", "micah", "nahum", "habakkuk", "zephaniah", "haggai", "zechariah", "malachi", ] NEW_TESTAMENT_BOOKS = [ "matthew", "mark", "luke", "john", "acts", "romans", "1corinthians", "2corinthians", "galatians", "ephesians", "philippians", "colossians", "1thessalonians", "2thessalonians", "1timothy", "2timothy", "titus", "philemon", "hebrews", "james", "1peter", "2peter", "1john", "2john", "3john", "jude", "revelation", ] ALL_BOOKS = OLD_TESTAMENT_BOOKS + NEW_TESTAMENT_BOOKS