Spaces:
Sleeping
Sleeping
| """ | |
| ClaimSense β Agent 8: Coverage Verification Agent | |
| =================================================== | |
| Verifies whether the claimed incident is covered under the policy. | |
| - Works for both linked (submission_id present) and standalone flows | |
| - Checks incident type vs coverage type eligibility matrix | |
| - Checks deductible, limits, exclusions | |
| - No ML β pure rules engine | |
| """ | |
| import logging | |
| from datetime import datetime, date | |
| log = logging.getLogger(__name__) | |
| # ββ Coverage eligibility matrix βββββββββββββββββββββββββββββββ | |
| # Maps coverage_type β set of covered incident types | |
| COVERAGE_MATRIX = { | |
| 'HO-3': { | |
| 'FIRE', 'WIND', 'HAIL', 'THEFT', 'VANDALISM', | |
| 'LIABILITY', 'VEHICLE_IMPACT', 'WATER', 'STRUCTURAL', 'OTHER' | |
| }, | |
| 'HO-5': { | |
| 'FIRE', 'WIND', 'HAIL', 'THEFT', 'VANDALISM', | |
| 'LIABILITY', 'VEHICLE_IMPACT', 'WATER', 'STRUCTURAL', | |
| 'EARTHQUAKE', 'OTHER' | |
| }, | |
| 'HO-4': { # Renters | |
| 'FIRE', 'THEFT', 'VANDALISM', 'LIABILITY', 'WATER', 'OTHER' | |
| }, | |
| 'HO-6': { # Condo | |
| 'FIRE', 'THEFT', 'VANDALISM', 'LIABILITY', 'WATER', 'STRUCTURAL', 'OTHER' | |
| }, | |
| 'DP-3': { # Dwelling | |
| 'FIRE', 'WIND', 'HAIL', 'VANDALISM', 'VEHICLE_IMPACT', 'OTHER' | |
| }, | |
| 'WC-3': { | |
| 'LIABILITY', 'OTHER' | |
| }, | |
| } | |
| # Incident types typically excluded (require endorsement) | |
| FLOOD_EXCLUDED = {'HO-3', 'HO-4', 'HO-5', 'HO-6', 'DP-3'} | |
| EARTHQUAKE_EXCLUDED = {'HO-3', 'HO-4', 'HO-6', 'DP-3'} | |
| def run_coverage_verify_agent(fnol_result: dict, submission: dict) -> dict: | |
| """ | |
| Agent 8 β Coverage Verification. | |
| Args: | |
| fnol_result: Output from Agent 7 (FNOL Intake) | |
| submission: Submission/policy data dict (may be empty for standalone) | |
| Returns: | |
| result dict with coverage_status, applicable_limit, deductible, exclusions | |
| """ | |
| claim_id = fnol_result.get('claim_id', '') | |
| incident_type = fnol_result.get('incident_type', 'OTHER') | |
| policy_number = fnol_result.get('policy_number', '') | |
| log.info(f"[COVERAGE] Agent 8 running for {claim_id}") | |
| # ββ Pull policy details βββββββββββββββββββββββββββββββββββ | |
| # Works for both linked (from DB via submission) and standalone | |
| coverage_type = ( | |
| submission.get('_coverage_type_code') or | |
| submission.get('coverage_type_code') or | |
| (submission.get('policy_request') or {}).get('coverage_type') or | |
| 'HO-3' # safe default | |
| ) | |
| coverage_limit = float( | |
| submission.get('_requested_coverage_limit') or | |
| (submission.get('policy_request') or {}).get('requested_coverage_limit') or | |
| 300000 | |
| ) | |
| deductible = float( | |
| submission.get('_requested_deductible') or | |
| (submission.get('policy_request') or {}).get('requested_deductible') or | |
| 2500 | |
| ) | |
| estimated_damage = float( | |
| fnol_result.get('normalised_fields', {}).get('estimated_damage') or 0 | |
| ) | |
| coverage_type = str(coverage_type).upper().strip() | |
| exclusions = [] | |
| partial_notes = [] | |
| # ββ Check eligibility matrix ββββββββββββββββββββββββββββββ | |
| covered_perils = COVERAGE_MATRIX.get(coverage_type, COVERAGE_MATRIX['HO-3']) | |
| if incident_type == 'FLOOD' and coverage_type in FLOOD_EXCLUDED: | |
| exclusions.append( | |
| f"FLOOD damage is excluded under {coverage_type}. " | |
| "Separate flood insurance (NFIP) required." | |
| ) | |
| elif incident_type == 'EARTHQUAKE' and coverage_type in EARTHQUAKE_EXCLUDED: | |
| exclusions.append( | |
| f"EARTHQUAKE is excluded under {coverage_type}. " | |
| "Earthquake endorsement required." | |
| ) | |
| elif incident_type not in covered_perils: | |
| exclusions.append( | |
| f"Incident type {incident_type} is not a covered peril under {coverage_type}." | |
| ) | |
| # ββ Limit checks βββββββββββββββββββββββββββββββββββββββββ | |
| applicable_limit = coverage_limit | |
| if estimated_damage > coverage_limit: | |
| partial_notes.append( | |
| f"Estimated damage ${estimated_damage:,.0f} exceeds " | |
| f"coverage limit ${coverage_limit:,.0f}. " | |
| f"Maximum payable: ${coverage_limit:,.0f}." | |
| ) | |
| # ββ Deductible check ββββββββββββββββββββββββββββββββββββββ | |
| net_payable = max(0, min(estimated_damage, coverage_limit) - deductible) | |
| if estimated_damage > 0 and estimated_damage <= deductible: | |
| partial_notes.append( | |
| f"Claimed amount ${estimated_damage:,.0f} does not exceed " | |
| f"deductible ${deductible:,.0f}. Claim may not result in payment." | |
| ) | |
| # ββ Final coverage status βββββββββββββββββββββββββββββββββ | |
| if exclusions: | |
| coverage_status = 'EXCLUDED' | |
| elif partial_notes: | |
| coverage_status = 'PARTIAL' | |
| else: | |
| coverage_status = 'COVERED' | |
| # ββ Summary βββββββββββββββββββββββββββββββββββββββββββββββ | |
| if coverage_status == 'COVERED': | |
| summary = ( | |
| f"Incident type {incident_type} is fully covered under {coverage_type}. " | |
| f"Coverage limit: ${coverage_limit:,.0f}. " | |
| f"Deductible: ${deductible:,.0f}. " | |
| f"Estimated net payable: ${net_payable:,.0f}." | |
| ) | |
| elif coverage_status == 'PARTIAL': | |
| summary = ( | |
| f"Incident {incident_type} is covered under {coverage_type} " | |
| f"but with limitations. " + " ".join(partial_notes) | |
| ) | |
| else: | |
| summary = ( | |
| f"Incident {incident_type} is NOT covered under {coverage_type}. " | |
| + " ".join(exclusions) | |
| ) | |
| result = { | |
| 'claim_id': claim_id, | |
| 'status': f'COVERAGE_{coverage_status}', | |
| 'coverage_status': coverage_status, | |
| 'coverage_type': coverage_type, | |
| 'policy_number': policy_number, | |
| 'applicable_limit': applicable_limit, | |
| 'deductible': deductible, | |
| 'estimated_damage': estimated_damage, | |
| 'net_payable_est': net_payable, | |
| 'exclusions': exclusions, | |
| 'partial_notes': partial_notes, | |
| 'summary': summary, | |
| } | |
| log.info( | |
| f"[COVERAGE] {claim_id}: {coverage_status} | " | |
| f"type={coverage_type} | limit=${coverage_limit:,.0f} | " | |
| f"exclusions={len(exclusions)}" | |
| ) | |
| return result | |