from typing import Dict, Any, Tuple, List import re from datetime import datetime from pydantic import BaseModel, Field class ValidationResult(BaseModel): is_valid: bool errors: List[str] = Field(default_factory=list) confidence_penalty: float = 0.0 def validate_extracted_fields(doc_class: str, fields: Dict[str, Any]) -> ValidationResult: """ Validates extracted field values based on business rules: - total_amount: numeric, > 0 - date / invoice_date: valid date pattern - invoice_number: non-empty string Returns ValidationResult with pass/fail and penalty. """ errors = [] penalty = 0.0 if doc_class in ["invoice", "receipt"]: amount_val = fields.get("total_amount") if amount_val is not None: # Clean currency symbols and commas cleaned_str = re.sub(r'[^\d.-]', '', str(amount_val).replace(',', '')) try: amt = float(cleaned_str) if amt <= 0: errors.append(f"total_amount must be positive: got {amt}") penalty += 0.25 except ValueError: errors.append(f"total_amount cannot be parsed as float: {amount_val}") penalty += 0.35 date_val = fields.get("date") or fields.get("invoice_date") if date_val is not None: # Check standard date formats valid_date = False date_str = str(date_val).strip() date_formats = ["%Y-%m-%d", "%d/%m/%Y", "%m/%d/%Y", "%d-%m-%Y", "%Y/%m/%d", "%b %d, %Y", "%d %b %Y"] for fmt in date_formats: try: datetime.strptime(date_str, fmt) valid_date = True break except ValueError: pass # Also check with date regex if not valid_date and not re.search(r'\d{1,4}[/\-.]\d{1,2}[/\-.]\d{1,4}', date_str): errors.append(f"date format unrecognized: {date_val}") penalty += 0.2 if doc_class == "invoice": inv_num = fields.get("invoice_number") if inv_num is not None and not str(inv_num).strip(): errors.append("invoice_number is empty") penalty += 0.3 is_valid = len(errors) == 0 return ValidationResult(is_valid=is_valid, errors=errors, confidence_penalty=min(penalty, 0.5))