Spaces:
Sleeping
Sleeping
| """ | |
| ClaimSense β Agent 11: Triage & Routing Agent | |
| =============================================== | |
| Deterministic rules engine. Makes final triage decision based on | |
| outputs of all prior agents. No ML β pure business logic. | |
| Outputs: | |
| AUTO_SETTLE β low fraud, covered, minor/moderate β auto pay | |
| ADJUSTER_REVIEW β standard review required | |
| SIU_REFERRAL β Special Investigation Unit β high fraud score | |
| DENY β excluded, invalid FNOL, or uncovered peril | |
| """ | |
| import logging | |
| from datetime import datetime | |
| log = logging.getLogger(__name__) | |
| # ββ Triage rules βββββββββββββββββββββββββββββββββββββββββββββ | |
| def _determine_outcome(fnol: dict, coverage: dict, | |
| fraud: dict, severity: dict) -> tuple: | |
| """ | |
| Returns (triage_outcome, priority, reasons) | |
| """ | |
| reasons = [] | |
| outcome = 'ADJUSTER_REVIEW' | |
| priority = 'MEDIUM' | |
| coverage_status = coverage.get('coverage_status', 'PENDING') | |
| fraud_score = int(fraud.get('fraud_score') or 0) | |
| fraud_band = fraud.get('fraud_band', 'LOW') | |
| severity_band = severity.get('severity_band', 'MODERATE') | |
| reserve = float(severity.get('reserve_estimate') or 0) | |
| fnol_status = fnol.get('status', 'FNOL_ACCEPTED') | |
| fraud_flags = fraud.get('fraud_flags', []) | |
| exclusions = coverage.get('exclusions', []) | |
| # ββ DENY conditions βββββββββββββββββββββββββββββββββββββββ | |
| if fnol_status == 'FNOL_INVALID': | |
| outcome = 'DENY' | |
| priority = 'LOW' | |
| reasons.append("FNOL is invalid β missing required fields") | |
| return outcome, priority, reasons | |
| if coverage_status == 'EXCLUDED': | |
| outcome = 'DENY' | |
| priority = 'LOW' | |
| reasons.append("Peril is excluded from coverage") | |
| reasons.extend(exclusions) | |
| return outcome, priority, reasons | |
| # ββ SIU Referral βββββββββββββββββββββββββββββββββββββββββ | |
| if fraud_score >= 70: | |
| outcome = 'SIU_REFERRAL' | |
| priority = 'HIGH' | |
| reasons.append(f"High fraud score: {fraud_score}/100") | |
| reasons.extend(fraud_flags[:3]) | |
| return outcome, priority, reasons | |
| # ββ Auto-settle conditions ββββββββββββββββββββββββββββββββ | |
| if ( | |
| coverage_status == 'COVERED' | |
| and fraud_band == 'LOW' | |
| and fraud_score < 25 | |
| and severity_band in ('MINOR', 'MODERATE') | |
| and reserve < 25000 | |
| and fnol_status == 'FNOL_ACCEPTED' | |
| ): | |
| outcome = 'AUTO_SETTLE' | |
| priority = 'LOW' | |
| reasons.append("All checks passed β eligible for automated settlement") | |
| reasons.append(f"Fraud score {fraud_score}/100 (LOW band)") | |
| reasons.append(f"Reserve estimate ${reserve:,.0f} below auto-settle threshold") | |
| return outcome, priority, reasons | |
| # ββ Adjuster review βββββββββββββββββββββββββββββββββββββββ | |
| if severity_band == 'CATASTROPHIC': | |
| priority = 'URGENT' | |
| reasons.append("CATASTROPHIC severity β executive escalation") | |
| elif severity_band == 'MAJOR': | |
| priority = 'HIGH' | |
| reasons.append("MAJOR severity β senior adjuster required") | |
| elif fraud_band == 'MEDIUM': | |
| priority = 'HIGH' | |
| reasons.append(f"Moderate fraud indicators detected (score: {fraud_score})") | |
| elif coverage_status == 'PARTIAL': | |
| reasons.append("Partial coverage β adjuster must determine applicable amount") | |
| else: | |
| reasons.append("Standard claim routed to adjuster queue") | |
| return outcome, priority, reasons | |
| def _assign_queue(outcome: str, severity_band: str, | |
| incident_type: str) -> str: | |
| """Map outcome + severity to adjuster queue name.""" | |
| if outcome == 'AUTO_SETTLE': | |
| return 'AUTOMATED_SETTLEMENT' | |
| if outcome == 'SIU_REFERRAL': | |
| return 'SPECIAL_INVESTIGATIONS_UNIT' | |
| if outcome == 'DENY': | |
| return 'CLAIMS_DENIAL_REVIEW' | |
| # ADJUSTER_REVIEW | |
| if severity_band == 'CATASTROPHIC': | |
| return 'MAJOR_LOSS_TEAM' | |
| if severity_band == 'MAJOR': | |
| return 'SENIOR_ADJUSTER_QUEUE' | |
| if incident_type in ('FIRE', 'STRUCTURAL', 'EARTHQUAKE', 'FLOOD'): | |
| return 'PROPERTY_DAMAGE_QUEUE' | |
| if incident_type in ('LIABILITY',): | |
| return 'LIABILITY_CLAIMS_QUEUE' | |
| return 'STANDARD_ADJUSTER_QUEUE' | |
| def _build_triage_summary(claim_id: str, outcome: str, priority: str, | |
| fraud: dict, severity: dict, | |
| coverage: dict, reasons: list) -> str: | |
| fraud_score = fraud.get('fraud_score', 0) | |
| severity_band = severity.get('severity_band', '') | |
| reserve = severity.get('reserve_estimate', 0) | |
| cov_status = coverage.get('coverage_status', '') | |
| outcome_text = { | |
| 'AUTO_SETTLE': 'approved for automated settlement', | |
| 'ADJUSTER_REVIEW': 'routed to adjuster review', | |
| 'SIU_REFERRAL': 'referred to the Special Investigation Unit', | |
| 'DENY': 'recommended for denial', | |
| }.get(outcome, outcome) | |
| return ( | |
| f"Claim {claim_id} has been {outcome_text}. " | |
| f"Coverage status is {cov_status}. " | |
| f"Fraud score: {fraud_score}/100. " | |
| f"Severity: {severity_band}. " | |
| f"Initial reserve estimate: ${float(reserve):,.0f}. " | |
| f"Priority: {priority}. " | |
| + (" ".join(reasons[:2]) if reasons else "") | |
| ) | |
| # ββ Main agent function βββββββββββββββββββββββββββββββββββββββ | |
| def run_triage_agent(fnol_result: dict, coverage_result: dict, | |
| fraud_result: dict, severity_result: dict) -> dict: | |
| claim_id = fnol_result.get('claim_id', '') | |
| log.info(f"[TRIAGE] Agent 11 running for {claim_id}") | |
| outcome, priority, reasons = _determine_outcome( | |
| fnol_result, coverage_result, fraud_result, severity_result | |
| ) | |
| incident_type = fnol_result.get('incident_type', 'OTHER') | |
| severity_band = severity_result.get('severity_band', 'MODERATE') | |
| adjuster_queue = _assign_queue(outcome, severity_band, incident_type) | |
| reserve = float(severity_result.get('reserve_estimate') or 0) | |
| fraud_score = int(fraud_result.get('fraud_score') or 0) | |
| overall_summary = _build_triage_summary( | |
| claim_id, outcome, priority, | |
| fraud_result, severity_result, coverage_result, reasons | |
| ) | |
| # ββ Decision factors for AXIOM audit βββββββββββββββββββββ | |
| decision_factors = [ | |
| { | |
| 'agent': 'FNOL Intake Agent', | |
| 'factor': 'FNOL Completeness', | |
| 'outcome': fnol_result.get('status', ''), | |
| 'detail': ( | |
| f"Completeness score: {fnol_result.get('completeness_score', 0)}%. " | |
| f"Days to report: {fnol_result.get('days_to_report', 0)}. " | |
| f"Issues: {len(fnol_result.get('validation_issues', []))}." | |
| ) | |
| }, | |
| { | |
| 'agent': 'Coverage Verification Agent', | |
| 'factor': 'Policy Coverage Check', | |
| 'outcome': coverage_result.get('coverage_status', ''), | |
| 'detail': coverage_result.get('summary', '') | |
| }, | |
| { | |
| 'agent': 'Fraud Signal Agent', | |
| 'factor': 'Fraud Risk Scoring', | |
| 'outcome': fraud_result.get('fraud_band', ''), | |
| 'detail': ( | |
| f"Fraud score: {fraud_score}/100. " | |
| f"Band: {fraud_result.get('fraud_band', '')}. " | |
| f"Flags: {len(fraud_result.get('fraud_flags', []))}." | |
| ) | |
| }, | |
| { | |
| 'agent': 'Severity & Reserve Agent', | |
| 'factor': 'Severity Classification', | |
| 'outcome': severity_band, | |
| 'detail': ( | |
| f"Severity: {severity_band}. " | |
| f"Reserve estimate: ${reserve:,.0f}. " | |
| f"{severity_result.get('reserve_note', '')}." | |
| ) | |
| }, | |
| { | |
| 'agent': 'Triage & Routing Agent', | |
| 'factor': 'Final Triage Decision', | |
| 'outcome': outcome, | |
| 'detail': " ".join(reasons) | |
| }, | |
| ] | |
| result = { | |
| 'claim_id': claim_id, | |
| 'status': 'TRIAGE_COMPLETE', | |
| 'triage_outcome': outcome, | |
| 'triage_priority': priority, | |
| 'adjuster_queue': adjuster_queue, | |
| 'triage_reasons': reasons, | |
| 'overall_summary': overall_summary, | |
| 'decision_factors': decision_factors, | |
| 'reserve_estimate': reserve, | |
| 'fraud_score': fraud_score, | |
| 'severity_band': severity_band, | |
| 'coverage_status': coverage_result.get('coverage_status', ''), | |
| 'triaged_at': datetime.utcnow().isoformat(), | |
| } | |
| log.info( | |
| f"[TRIAGE] {claim_id}: outcome={outcome} priority={priority} " | |
| f"queue={adjuster_queue}" | |
| ) | |
| return result | |