Spaces:
Sleeping
Sleeping
| """ | |
| ClaimSense β Agent 7: FNOL Intake Agent | |
| ======================================== | |
| First Notice of Loss parser and validator. | |
| - Extracts and normalises incident fields from raw FNOL payload | |
| - Validates required fields are present | |
| - Checks policy_number format and incident_date is not future | |
| - Determines FNOL completeness score | |
| - No ML model needed β pure logic/rules | |
| """ | |
| import os, sys, json, logging | |
| from datetime import datetime, date | |
| log = logging.getLogger(__name__) | |
| # ββ Field validation helpers ββββββββββββββββββββββββββββββββββ | |
| VALID_INCIDENT_TYPES = { | |
| 'FIRE', 'WATER', 'WIND', 'HAIL', 'THEFT', 'VANDALISM', | |
| 'LIABILITY', 'EARTHQUAKE', 'FLOOD', 'VEHICLE_IMPACT', | |
| 'STRUCTURAL', 'OTHER' | |
| } | |
| REQUIRED_FIELDS = [ | |
| 'policy_number', 'claimant_name', 'incident_date', 'incident_type' | |
| ] | |
| def _parse_date(val): | |
| if not val: | |
| return None | |
| for fmt in ('%Y-%m-%d', '%m/%d/%Y', '%d-%m-%Y', '%Y/%m/%d'): | |
| try: | |
| return datetime.strptime(str(val).strip(), fmt).date() | |
| except ValueError: | |
| continue | |
| return None | |
| def _days_to_report(incident_dt, reported_dt=None): | |
| """Days between incident and FNOL submission β key fraud signal.""" | |
| if not incident_dt: | |
| return 0 | |
| ref = reported_dt or date.today() | |
| if isinstance(ref, datetime): | |
| ref = ref.date() | |
| delta = (ref - incident_dt).days | |
| return max(0, delta) | |
| def _completeness_score(fnol: dict) -> int: | |
| """0β100 completeness score based on fields present.""" | |
| bonus_fields = [ | |
| 'incident_description', 'incident_address', 'incident_city', | |
| 'claimant_email', 'claimant_phone', 'has_police_report', | |
| 'has_photos', 'estimated_damage' | |
| ] | |
| base = len([f for f in REQUIRED_FIELDS if fnol.get(f)]) / len(REQUIRED_FIELDS) | |
| bonus = sum(1 for f in bonus_fields if fnol.get(f)) / len(bonus_fields) | |
| return int((base * 70) + (bonus * 30)) | |
| # ββ Main agent function βββββββββββββββββββββββββββββββββββββββ | |
| def run_fnol_intake_agent(fnol_payload: dict) -> dict: | |
| """ | |
| Agent 7 β FNOL Intake. | |
| Args: | |
| fnol_payload: Raw FNOL submission dict | |
| Returns: | |
| result dict with status, normalised fields, validation issues | |
| """ | |
| claim_id = fnol_payload.get('claim_id', '') | |
| log.info(f"[FNOL] Agent 7 running for {claim_id}") | |
| issues = [] | |
| normalised = {} | |
| # ββ Required field checks βββββββββββββββββββββββββββββββββ | |
| for field in REQUIRED_FIELDS: | |
| if not fnol_payload.get(field): | |
| issues.append(f"Missing required field: {field}") | |
| # ββ Policy number βββββββββββββββββββββββββββββββββββββββββ | |
| policy_number = str(fnol_payload.get('policy_number', '')).strip() | |
| normalised['policy_number'] = policy_number | |
| # ββ Claimant ββββββββββββββββββββββββββββββββββββββββββββββ | |
| normalised['claimant_name'] = str(fnol_payload.get('claimant_name', '')).strip() | |
| normalised['claimant_email'] = fnol_payload.get('claimant_email') | |
| normalised['claimant_phone'] = fnol_payload.get('claimant_phone') | |
| normalised['claimant_relation'] = fnol_payload.get('claimant_relation', 'Named Insured') | |
| # ββ Incident type βββββββββββββββββββββββββββββββββββββββββ | |
| raw_type = str(fnol_payload.get('incident_type', '')).upper().strip() | |
| if raw_type and raw_type not in VALID_INCIDENT_TYPES: | |
| # Try to map common synonyms | |
| synonyms = { | |
| 'FLOOD': 'WATER', 'BURST PIPE': 'WATER', 'LEAK': 'WATER', | |
| 'BREAK-IN': 'THEFT', 'BURGLARY': 'THEFT', 'ROBBERY': 'THEFT', | |
| 'STORM': 'WIND', 'HURRICANE': 'WIND', 'TORNADO': 'WIND', | |
| 'ACCIDENT': 'LIABILITY', 'INJURY': 'LIABILITY', | |
| } | |
| mapped = synonyms.get(raw_type) | |
| if mapped: | |
| raw_type = mapped | |
| else: | |
| raw_type = 'OTHER' | |
| normalised['incident_type'] = raw_type or 'OTHER' | |
| # ββ Incident date βββββββββββββββββββββββββββββββββββββββββ | |
| incident_dt = _parse_date(fnol_payload.get('incident_date')) | |
| if incident_dt: | |
| if incident_dt > date.today(): | |
| issues.append("incident_date is in the future") | |
| incident_dt = None | |
| elif (date.today() - incident_dt).days > 1825: # 5 years | |
| issues.append("incident_date is more than 5 years ago β verify") | |
| else: | |
| if fnol_payload.get('incident_date'): | |
| issues.append(f"Could not parse incident_date: {fnol_payload.get('incident_date')}") | |
| normalised['incident_date'] = incident_dt.isoformat() if incident_dt else None | |
| normalised['days_to_report'] = _days_to_report(incident_dt) | |
| # ββ Late reporting flag βββββββββββββββββββββββββββββββββββ | |
| late_report = normalised['days_to_report'] > 30 | |
| if normalised['days_to_report'] > 90: | |
| issues.append( | |
| f"Very late FNOL: {normalised['days_to_report']} days after incident β fraud signal" | |
| ) | |
| # ββ Incident details ββββββββββββββββββββββββββββββββββββββ | |
| normalised['incident_description'] = fnol_payload.get('incident_description') | |
| normalised['incident_address'] = fnol_payload.get('incident_address') | |
| normalised['incident_city'] = fnol_payload.get('incident_city') | |
| normalised['incident_state'] = fnol_payload.get('incident_state') | |
| normalised['estimated_damage'] = fnol_payload.get('estimated_damage') | |
| normalised['has_police_report'] = bool(fnol_payload.get('has_police_report', False)) | |
| normalised['has_photos'] = bool(fnol_payload.get('has_photos', False)) | |
| # ββ Completeness βββββββββββββββββββββββββββββββββββββββββ | |
| completeness = _completeness_score({**fnol_payload, **normalised}) | |
| # ββ Status determination ββββββββββββββββββββββββββββββββββ | |
| critical_issues = [i for i in issues if 'Missing required' in i] | |
| if critical_issues: | |
| status = 'FNOL_INVALID' | |
| elif issues: | |
| status = 'FNOL_PARTIAL' | |
| else: | |
| status = 'FNOL_ACCEPTED' | |
| result = { | |
| 'claim_id': claim_id, | |
| 'status': status, | |
| 'normalised_fields': normalised, | |
| 'completeness_score': completeness, | |
| 'late_report': late_report, | |
| 'days_to_report': normalised['days_to_report'], | |
| 'validation_issues': issues, | |
| 'incident_type': normalised['incident_type'], | |
| 'incident_date': normalised['incident_date'], | |
| 'policy_number': policy_number, | |
| 'claimant_name': normalised['claimant_name'], | |
| } | |
| log.info( | |
| f"[FNOL] {claim_id}: {status} | " | |
| f"completeness={completeness} | " | |
| f"days_to_report={normalised['days_to_report']} | " | |
| f"issues={len(issues)}" | |
| ) | |
| return result | |