""" Smart Explainer Agent (Enhanced + Gemini-powered) - Produces a detailed, human-readable explanation for a single InvoiceProcessingState. - Uses Gemini for natural summarization if API key is present. - Defensive, HTML-enhanced, and fully dashboard-ready. """ from state import InvoiceProcessingState, ValidationStatus, PaymentStatus, RiskLevel from datetime import datetime import google.generativeai as genai import json import os class SmartExplainerAgent: def __init__(self): # Configure Gemini only if available self.api_key = os.environ.get("GEMINI_API_KEY_4") self.use_gemini = bool(self.api_key) if self.use_gemini: genai.configure(api_key=self.api_key) self.model = genai.GenerativeModel("gemini-2.0-flash") # ---------- Helper functions ---------- def _safe_invoice_dict(self, state: InvoiceProcessingState) -> dict: if not state or not getattr(state, "invoice_data", None): return {} return ( state.invoice_data.model_dump(exclude_none=True) if hasattr(state.invoice_data, "model_dump") else state.invoice_data.dict() ) def _safe_validation(self, state: InvoiceProcessingState) -> dict: if not state or not getattr(state, "validation_result", None): return {} return ( state.validation_result.model_dump(exclude_none=True) if hasattr(state.validation_result, "model_dump") else state.validation_result.dict() ) def _safe_risk(self, state: InvoiceProcessingState) -> dict: if not state or not getattr(state, "risk_assessment", None): return {} return ( state.risk_assessment.model_dump(exclude_none=True) if hasattr(state.risk_assessment, "model_dump") else state.risk_assessment.dict() ) # ---------- Core explain logic ---------- def explain(self, state) -> str: """ Generate a detailed HTML + markdown explanation for a given invoice. Falls back gracefully if data or Gemini is unavailable. """ # --- Defensive normalization --- if state is None: return "
⚠️ No invoice state provided.
" if isinstance(state, dict): try: state = InvoiceProcessingState(**state) except Exception: pass # --- Extract fields safely --- invoice = self._safe_invoice_dict(state) or {} validation = self._safe_validation(state) or {} risk = self._safe_risk(state) or {} payment = ( state.payment_decision.model_dump(exclude_none=True) if getattr(state, "payment_decision", None) and hasattr(state.payment_decision, "model_dump") else getattr(state, "payment_decision", {}) or {} ) discrepancies = validation.get("discrepencies", []) # per schema inv_id = invoice.get("invoice_number") or invoice.get("file_name") or "Invoice: {inv_id}
", f"Vendor: {vendor}
", f"Amount: {_fmt(total)}
", f"Status: {status_val}
", "Validation: {val_status or 'unknown'}
", f"Risk Level: {risk_level or 'low'} ({risk_score})
", f"Payment: {payment.get('decision', 'N/A')} ({payment_status or 'pending'})
", ] if discrepancies: lines.append("Discrepancies Found:
{expected}, got {actual}Recommendation:
Gemini explanation failed: {e}
" return explanation_html