kenmandal's picture
Deploy ERP-DocIQ: agentic OCR + IDP (MiniCPM-V 8B, Tesseract)
32b00ed verified
Raw
History Blame Contribute Delete
1.76 kB
"""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}