"""OCR backend interface + result type.""" from __future__ import annotations import time from dataclasses import dataclass, field from pathlib import Path @dataclass class OCRBackendResult: text: str = "" engine: str = "none" tier: str = "offline" # vlm | api | local | offline pages: int = 0 confidence: float = 0.0 latency_ms: float = 0.0 simulated: bool = False cost_usd: float = 0.0 error: str | None = None blocks: list = field(default_factory=list) @property def available(self) -> bool: return bool(self.text and self.text.strip()) and not self.error def to_channel_dict(self) -> dict: d = { "available": self.available, "chars": len(self.text.strip()), "engine": self.engine, "tier": self.tier, "confidence": round(self.confidence, 3), "latency_ms": round(self.latency_ms, 1), } if self.simulated: d["simulated"] = True if self.error: d["error"] = self.error if self.cost_usd: d["cost_usd"] = round(self.cost_usd, 6) return d class OCRBackend: name: str = "base" tier: str = "offline" requires: str = "" # human-readable description of what must be present def available(self) -> bool: # pragma: no cover - overridden return False def extract(self, file_path: str | Path) -> OCRBackendResult: # pragma: no cover raise NotImplementedError @staticmethod def _now() -> float: return time.perf_counter() def info(self) -> dict: return {"name": self.name, "tier": self.tier, "available": self.available(), "requires": self.requires}