documentverification/verifier/engine/orchestrator.py
vickytechkey 71058e84c9
All checks were successful
Deploy Beta (NATIVE) / deploy (push) Successful in 37s
feat: add verification engine orchestrator and Codeplus vendor integration
2026-10-17 15:22:16 +05:30

143 lines
5 KiB
Python

import logging
import time
from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional
from .base import BaseVendor, DocumentType, VendorResult, VerificationStatus
from .registry import registry, VendorRegistry
logger = logging.getLogger(__name__)
@dataclass
class EngineResult:
"""Consolidated result returned by the VerificationEngine."""
is_success: bool
document_type: str
status: VerificationStatus
successful_vendor: Optional[str] = None
data: Dict[str, Any] = field(default_factory=dict)
attempts: List[Dict[str, Any]] = field(default_factory=list)
error_message: Optional[str] = None
total_latency_ms: float = 0.0
def to_dict(self) -> Dict[str, Any]:
return {
"is_success": self.is_success,
"document_type": self.document_type,
"status": self.status.value if isinstance(self.status, VerificationStatus) else str(self.status),
"successful_vendor": self.successful_vendor,
"data": self.data,
"attempts_count": len(self.attempts),
"attempts": self.attempts,
"error_message": self.error_message,
"total_latency_ms": round(self.total_latency_ms, 2),
}
class VerificationEngine:
"""
Core waterfall orchestrator.
Executes multiple vendors in a fallback chain.
If vendor A errors or fails, the engine falls back to vendor B, C, etc.
"""
def __init__(self, vendor_registry: Optional[VendorRegistry] = None):
self.registry = vendor_registry or registry
def verify(
self,
document_type: str,
data: Dict[str, Any],
files: Optional[Dict[str, Any]] = None,
preferred_vendor: Optional[str] = None,
) -> EngineResult:
"""
Executes document verification through the vendor waterfall.
"""
start_time = time.time()
doc_type_clean = document_type.lower()
vendors = self.registry.get_vendors_for_document(doc_type_clean, preferred_vendor=preferred_vendor)
if not vendors:
total_latency = (time.time() - start_time) * 1000
return EngineResult(
is_success=False,
document_type=doc_type_clean,
status=VerificationStatus.ERROR,
error_message=f"No active vendors configured for document type '{document_type}'",
total_latency_ms=total_latency,
)
attempts: List[Dict[str, Any]] = []
logger.info(
"Starting verification for '%s'. Candidate vendors in order: %s",
doc_type_clean,
[v.name for v in vendors],
)
for attempt_index, vendor in enumerate(vendors, start=1):
logger.info(
"Attempt #%d: Dispatching verification to vendor '%s'",
attempt_index,
vendor.name,
)
result: VendorResult = vendor.verify(doc_type_clean, data, files)
attempt_record = result.to_dict()
attempt_record["attempt_number"] = attempt_index
attempts.append(attempt_record)
if result.is_success:
total_latency = (time.time() - start_time) * 1000
logger.info(
"Verification SUCCEEDED via vendor '%s' on attempt #%d (latency=%.2f ms)",
vendor.name,
attempt_index,
result.latency_ms,
)
return EngineResult(
is_success=True,
document_type=doc_type_clean,
status=VerificationStatus.SUCCESS,
successful_vendor=vendor.name,
data=result.data,
attempts=attempts,
total_latency_ms=total_latency,
)
else:
logger.warning(
"Vendor '%s' failed on attempt #%d: status=%s, code=%s, error=%s. Triggering fallback to next vendor.",
vendor.name,
attempt_index,
result.status,
result.error_code,
result.error_message,
)
# If all vendors failed
total_latency = (time.time() - start_time) * 1000
last_error = attempts[-1].get("error_message") if attempts else "All vendors failed"
logger.error(
"All %d vendors failed verification for '%s'. Total time=%.2f ms",
len(vendors),
doc_type_clean,
total_latency,
)
last_data = attempts[-1].get("data") if attempts and isinstance(attempts[-1].get("data"), dict) else {}
return EngineResult(
is_success=False,
document_type=doc_type_clean,
status=VerificationStatus.FAILED,
successful_vendor=None,
data=last_data,
attempts=attempts,
error_message=last_error,
total_latency_ms=total_latency,
)
# Global default engine instance
engine = VerificationEngine()