documentverification/verifier/engine/base.py

138 lines
4.4 KiB
Python
Raw Permalink Normal View History

import time
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Dict, List, Optional
class DocumentType(str, Enum):
PAN = "pan"
PAN_V5 = "pan_v5"
PAN_VERIFY = "pan_verify"
PAN_ADVANCED = "pan_advanced"
PAN_DETAILED = "pan_detailed"
AADHAAR = "aadhaar"
DRIVING_LICENSE = "driving_license"
VOTER_ID = "voter_id"
PASSPORT = "passport"
BANK_ACCOUNT = "bank_account"
BANK_VERIFY_PD = "bank_verify_pd"
BANK_VERIFY_V5 = "bank_verify_v5"
BANK_PD = "bank_pd"
BANK_VERIFY_FULL = "bank_verify_full"
BANK_VERIFY_V4 = "bank_verify_v4"
GSTIN = "gstin"
GST = "gst"
GST_VERIFY = "gst_verify"
GST_BY_PAN = "gst_by_pan"
FETCH_GST = "fetch_gst"
MSME = "msme"
MSME_BY_PAN = "msme_by_pan"
UDYAM = "udyam"
CUSTOM = "custom"
class VerificationStatus(str, Enum):
SUCCESS = "SUCCESS"
FAILED = "FAILED"
ERROR = "ERROR"
SKIPPED = "SKIPPED"
@dataclass
class VendorResult:
"""Standardized result returned by every vendor verification adapter."""
vendor_name: str
is_success: bool
status: VerificationStatus
data: Dict[str, Any] = field(default_factory=dict)
raw_response: Any = None
error_code: Optional[str] = None
error_message: Optional[str] = None
latency_ms: float = 0.0
retryable: bool = False
def to_dict(self) -> Dict[str, Any]:
return {
"vendor_name": self.vendor_name,
"is_success": self.is_success,
"status": self.status.value if isinstance(self.status, VerificationStatus) else str(self.status),
"data": self.data,
"raw_response": self.raw_response,
"error_code": self.error_code,
"error_message": self.error_message,
"latency_ms": round(self.latency_ms, 2),
"retryable": self.retryable,
}
class BaseVendor:
"""
Abstract Base Class for all Verification Vendors (e.g. Codeplus, Cashfree, Karza, Surepass).
Subclasses implement vendor-specific logic while adhering to this unified contract.
"""
name: str = "base_vendor"
display_name: str = "Base Vendor"
priority: int = 100
is_active: bool = True
supported_documents: List[str] = []
def __init__(self, config: Optional[Dict[str, Any]] = None):
self.config = config or {}
def supports(self, document_type: str) -> bool:
"""Check if this vendor supports the requested document type."""
doc_key = document_type.lower()
return doc_key in [d.lower() for d in self.supported_documents] or "all" in self.supported_documents
def verify(
self,
document_type: str,
data: Dict[str, Any],
files: Optional[Dict[str, Any]] = None,
) -> VendorResult:
"""
Executes verification with latency tracking and universal exception handling.
Delegates actual execution to `execute_verification`.
"""
start_time = time.time()
doc_type_clean = document_type.lower()
if not self.supports(doc_type_clean):
latency = (time.time() - start_time) * 1000
return VendorResult(
vendor_name=self.name,
is_success=False,
status=VerificationStatus.SKIPPED,
error_code="DOC_NOT_SUPPORTED",
error_message=f"Vendor '{self.name}' does not support document type '{document_type}'",
latency_ms=latency,
retryable=True,
)
try:
result = self.execute_verification(doc_type_clean, data, files)
result.latency_ms = (time.time() - start_time) * 1000
result.vendor_name = self.name
return result
except Exception as exc:
latency = (time.time() - start_time) * 1000
return VendorResult(
vendor_name=self.name,
is_success=False,
status=VerificationStatus.ERROR,
error_code=exc.__class__.__name__,
error_message=str(exc),
latency_ms=latency,
retryable=True,
)
def execute_verification(
self,
document_type: str,
data: Dict[str, Any],
files: Optional[Dict[str, Any]] = None,
) -> VendorResult:
"""Must be implemented by subclasses."""
raise NotImplementedError("Subclasses must implement execute_verification()")