Spaces:
Sleeping
Sleeping
File size: 6,811 Bytes
069dc30 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 | """
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
|