system-design51 min read

How to Design an Insurance Claims Processing System — A Senior+ Guide | Ayodhyya

How to Design an Insurance Claims Processing System

Building a Production-Grade End-to-End Claims Platform — Lifecycle, Fraud Detection, Payments & Compliance

Senior+ System Design Guide 10,000+ Words 20 Deep-Dive Sections C# · Mermaid · Real-World Case Studies

1. Introduction & Why Claims Processing is Complex

The insurance claims process is one of the most complex transactional workflows in any industry. A single claim — from the moment a policyholder reports a loss to the moment they receive payment — can touch dozens of systems, involve multiple human actors (policyholders, agents, adjusters, medical providers, repair shops, legal counsel), span weeks or months, and must satisfy stringent regulatory requirements that vary by state, line of business, and claim type. Getting this wrong doesn't just frustrate customers — it can result in regulatory fines, litigation, and massive financial losses.

The core challenge is that claims processing is not a simple CRUD application. It is a long-running, stateful business process with branching logic, parallel activities, time-dependent deadlines, and complex domain rules. A property damage claim follows a different path than a personal injury claim. A claim involving suspected fraud triggers a completely different workflow than a straightforward fender-bender. A claim in California must comply with different regulations than the same claim in Florida. The system must handle all of these variations while maintaining a consistent, auditable, and auditable trail of every action taken.

At its heart, the claims lifecycle is a directed acyclic graph of states and transitions: FNOL (First Notice of Loss) → Coverage Verification → Adjuster Assignment → Investigation → Damage Assessment → Adjudication → Payment → (optionally) Subrogation → Recovery → Close. But within each of these high-level phases, there are sub-workflows, escalations, approvals, and exception paths that create a web of complexity. A single claim might involve 50+ state transitions, 20+ documents, 10+ system integrations, and 5+ human decision points.

Key Insight: An insurance claims system is not just a workflow engine — it is a distributed transaction processing system that must guarantee exactly-once payment, maintain regulatory audit trails across state boundaries, handle massive document volumes (photos, police reports, medical records), integrate with dozens of external systems, and process claims in real-time while detecting fraud patterns across millions of historical claims. The engineering challenges mirror those of financial trading systems combined with healthcare information systems.

The scale is enormous. State Farm processes over 2 million claims per year. Allstate handles roughly 1.5 million annually. A mid-size insurer might process 100,000-500,000 claims per year, with each claim averaging $5,000-$50,000 in payout. The total claims paid by US property and casualty insurers exceeded $800 billion in 2023. Every dollar of that money flowed through a claims processing system. The system must handle peak loads during catastrophe events — a single hurricane can generate 100,000+ claims in a week, overwhelming normal processing capacity. The system must scale elastically during these peaks while maintaining SLA compliance for non-catastrophe claims.

Real-World Case Studies

CompanySystemScaleKey Innovation
LemonadeAI-first claims200K+ claims/yearAI bot pays claims in 3 seconds, video FNOL
Root InsuranceUsage-based auto350K+ policiesTelematics-driven claims, AI damage assessment
Guidewire (Industry Platform)ClaimsCenter500+ insurersConfigurable workflow engine, rules-based adjudication
State FarmIn-house claims platform2M+ claims/yearCatastrophe auto-scaling, drone inspection integration
Hippo InsuranceSmart home claims150K+ policiesIoT sensor-driven FNOL, proactive claims prevention

2. Functional & Non-Functional Requirements

Functional Requirements

  • FNOL Intake: Accept claims via web portal, mobile app (photo upload), phone (IVR + agent), email parsing, and agent-submitted forms. Each intake channel normalizes to a single claim record.
  • Document Management: Upload, store, version, and retrieve photos, police reports, medical records, repair estimates, and correspondence. Support OCR extraction for structured data from unstructured documents.
  • Adjuster Assignment: Automatically assign claims to adjusters based on workload, expertise, geography, language, and claim type. Support round-robin and skill-based routing.
  • Coverage Verification: Lookup policy details, verify coverage applicability, check policy limits, deductibles, exclusions, and endorsements. Handle multi-policy and multi-line scenarios.
  • Damage Assessment: AI-powered photo analysis for initial damage estimation. Integration with repair shop estimates and third-party valuation tools.
  • Fraud Detection: Real-time and batch fraud scoring using rule engines, ML models, and external fraud databases (NICB, ISO ClaimSearch). Flag suspicious claims for SIU (Special Investigations Unit) review.
  • Reserve Calculation: Automatically calculate initial and running reserves based on claim type, severity indicators, historical patterns, and coverage limits.
  • Adjudication: Decision engine that evaluates coverage, liability, damages, and applies policy terms to determine coverage decision (approve, deny, partial) and payment amount.
  • Payment Processing: Issue payments via check, EFT (ACH), wire transfer, or split payments across multiple payees. Handle recoverable and non-recoverable deductibles.
  • Subrogation & Recovery: Identify subrogation potential, initiate recovery proceedings, track restitution payments.
  • Appeals Workflow: Handle claimant disputes, regulatory appeals, and internal re-review with escalation paths.
  • SLA Tracking: Monitor processing timelines against regulatory and internal SLA requirements. Auto-escalate approaching breaches.
  • Regulatory Compliance: State-specific rules for claim handling timelines, payment delays, bad faith penalties, and reporting requirements.
  • Notifications: Multi-channel notifications (email, SMS, push, mail) to policyholders, agents, vendors, and internal staff at every stage transition.
  • Audit Trail: Immutable, timestamped log of every action, decision, and state change for regulatory compliance and litigation support.

Non-Functional Requirements

RequirementTargetRationale
FNOL intake latency< 2 seconds (P99)Policyholders expect instant confirmation
Payment processing< 5 seconds for EFT authorizationRegulatory requirement in many states
Document upload< 10 seconds for 25MB photoMobile users on cellular connections
Fraud scoring< 500ms real-time, < 5 minutes batchMust not block FNOL intake
Availability99.99% (52 min downtime/year)Catastrophic events cannot wait for recovery
Throughput10,000 claims/hour peak (catastrophe)Hurricane season surge capacity
Data retention7 years (regulatory minimum)Litigation tail for bodily injury claims
Disaster recoveryRPO < 1 min, RTO < 15 minBusiness continuity for active catastrophes
Concurrent users5,000 adjusters + 50,000 portal usersLarge carrier with national footprint

3. Capacity Estimation & Cost Modeling

Understanding the data volumes and throughput requirements drives infrastructure sizing. A mid-size insurer processing 300,000 claims per year generates the following baseline:

MetricCalculationResult
Claims per year300,000300K
Claims per day (avg)300K / 365~820/day
Claims per second (avg)820 / 86,400~0.01/sec avg
Peak (catastrophe)100K claims/week ÷ 604,800 sec~0.17/sec peak
Documents per claimAverage 15 documents × 300K4.5M docs/year
Storage per documentAverage 2MB (photos, PDFs)9TB/year
State transitions per claimAverage 50 transitions × 300K15M transitions/year
Audit log entriesAverage 200 entries × 300K60M entries/year
Notification volumeAverage 8 per claim × 300K2.4M notifications/year
Catastrophe Scaling: During a major hurricane, the system must handle 100,000+ claims in a single week — approximately 50x normal daily volume. This requires elastic scaling of intake services, document processing, adjuster assignment, and payment pipelines. The system must maintain SLA compliance for non-catastrophe claims during these peaks, which means isolating catastrophe claims into a separate processing lane with dedicated resources.

Storage Breakdown

Storage TypeTechnologySizeGrowth/Year
Claim metadataPostgreSQL50 GB50 GB
Documents (photos, PDFs)S3 + CloudFront9 TB9 TB
Audit trailPostgreSQL → TimescaleDB200 GB200 GB
Search indexElasticsearch100 GB100 GB
Fraud feature storeRedis + S350 GB50 GB
Total Year 1~9.4 TB~9.4 TB/year

4. Data Model & Storage Schema

The data model must capture the full lifecycle of a claim, all associated documents and parties, every decision and adjustment, and every payment. The core entities form a hierarchy: Policy → Claim → ClaimActivity → Document → Payment. The design uses a combination of relational storage for transactional data and document storage for unstructured content.

C#
public class Claim
{
    public Guid ClaimId { get; set; }
    public string ClaimNumber { get; set; } // "CLM-2024-0012345"
    public Guid PolicyId { get; set; }
    public Guid InsuredId { get; set; }
    public ClaimType Type { get; set; } // Auto, Property, Liability, WorkersComp
    public ClaimStatus Status { get; set; }
    public ClaimSubStatus SubStatus { get; set; }
    public DateTime DateOfLoss { get; set; }
    public DateTime DateReported { get; set; }
    public string Description { get; set; }
    public string LossLocationAddress { get; set; }
    public decimal EstimatedReserve { get; set; }
    public decimal PaidReserve { get; set; }
    public decimal RemainingReserve { get; set; }
    public decimal? FinalSettlementAmount { get; set; }
    public Guid AssignedAdjusterId { get; set; }
    public string StateJurisdiction { get; set; }
    public bool IsCatastrophe { get; set; }
    public Guid? CatastropheId { get; set; }
    public FraudScore FraudScore { get; set; }
    public List<ClaimActivity> Activities { get; set; }
    public List<ClaimDocument> Documents { get; set; }
    public List<ClaimPayment> Payments { get; set; }
    public List<ClaimParty> Parties { get; set; }
    public DateTime CreatedAt { get; set; }
    public DateTime UpdatedAt { get; set; }
}

public enum ClaimStatus
{
    Submitted,
    UnderReview,
    InvestigationPending,
    AdjusterAssigned,
    DamageAssessmentPending,
    CoverageVerified,
    AdjudicationPending,
    Approved,
    PartiallyApproved,
    Denied,
    PaymentPending,
    PaymentIssued,
    PaymentComplete,
    SubrogationPending,
    SubrogationActive,
    RecoveryComplete,
    AppealPending,
    AppealResolved,
    Closed,
    Reopened
}

public enum ClaimSubStatus
{
    WaitingForDocuments,
    WaitingForInsuredContact,
    WaitingForThirdPartyReport,
    WaitingForRepairEstimate,
    WaitingForMedicalRecords,
    WaitingForPoliceReport,
    SIUReferralPending,
    SIUInvestigationActive,
    LegalReviewPending,
    ManagerApprovalRequired,
    RegulatoryHold
}

Claim Activity Log

C#
public class ClaimActivity
{
    public Guid ActivityId { get; set; }
    public Guid ClaimId { get; set; }
    public ActivityType Type { get; set; }
    public string Description { get; set; }
    public string ActorId { get; set; }
    public ActorRole ActorRole { get; set; } // Adjuster, System, SIU, Manager, Portal
    public string OldValue { get; set; }
    public string NewValue { get; set; }
    public Dictionary<string, string> Metadata { get; set; }
    public DateTime Timestamp { get; set; }
    public string IpAddress { get; set; }
    public string UserAgent { get; set; }
}

public enum ActivityType
{
    ClaimSubmitted,
    ClaimStatusChanged,
    AdjusterAssigned,
    AdjusterReassigned,
    DocumentUploaded,
    DocumentReviewed,
    CoverageVerified,
    ReserveUpdated,
    DamageAssessed,
    FraudScoreUpdated,
    FraudFlagRaised,
    SIUReferralCreated,
    AdjudicationDecisionMade,
    PaymentIssued,
    PaymentFailed,
    SubrogationInitiated,
    RecoveryReceived,
    AppealFiled,
    AppealDecided,
    ClaimReopened,
    ClaimClosed,
    NoteAdded,
    NoteAdded,
    ExternalSystemSynced,
    SLABreachWarning,
    SLABreachTriggered,
    RegulatoryHoldApplied,
    RegulatoryHoldReleased
}

Key Relationships

erDiagram POLICY ||--o{ CLAIM : has CLAIM ||--o{ CLAIM_ACTIVITY : generates CLAIM ||--o{ CLAIM_DOCUMENT : contains CLAIM ||--o{ CLAIM_PAYMENT : issues CLAIM ||--o{ CLAIM_PARTY : involves CLAIM ||--o{ SUBROGATION_CLAIM : subrogates CLAIM }o--|| ADJUDICATION_DECISION : receives CLAIM }o--|| RESERVE : maintains CLAIM }o--|| FRAUD_SCORE : scored_by CLAIM }o--o| CATASTROPHE : belongs_to ADJUSTER ||--o{ CLAIM : assigned_to

5. High-Level Architecture Overview

The system follows a microservices architecture with an event-driven backbone. Each phase of the claims lifecycle is handled by a dedicated service, and state transitions are propagated through a central event bus. This design provides independent scaling of each phase, clear ownership boundaries, and the ability to substitute or upgrade individual services without affecting the rest of the system.

graph TB subgraph Intake A[Web Portal] --> FNOL[FNOL Service] B[Mobile App] --> FNOL C[Phone IVR] --> FNOL D[Agent Portal] --> FNOL E[Email Parser] --> FNOL end subgraph CoreClaims FNOL --> EB[Event Bus - Kafka] EB --> CV[Coverage Verification Service] EB --> AA[Adjuster Assignment Service] EB --> DS[Damage Assessment Service] EB --> FD[Fraud Detection Service] EB --> ADJ[Adjudication Engine] end subgraph Supporting CV --> PDB[(Policy DB)] DS --> AIML[AI/ML Photo Analysis] FD --> FDB[(Fraud DB)] ADJ --> RES[Reserve Service] end subgraph Payments EB --> PAY[Payment Service] PAY --> EFT[EFT/ACH Gateway] PAY --> CHK[Check Printing Service] PAY --> WIRE[Wire Transfer] end subgraph Recovery EB --> SUB[Subrogation Service] EB --> REC[Recovery Service] EB --> APL[Appeals Service] end subgraph CrossCutting EB --> AUDIT[Audit Trail Service] EB --> NOTIFY[Notification Service] EB --> SLA[SLA Monitoring Service] EB --> ANALYTICS[Analytics Pipeline] end

Service Boundaries

ServiceResponsibilityData StoreScaling Profile
FNOL ServiceClaim intake, validation, deduplicationPostgreSQLCPU-bound (validation rules)
Coverage VerificationPolicy lookup, coverage determinationPostgreSQL + Redis cacheRead-heavy, cache-friendly
Adjuster AssignmentWorkload-based routingPostgreSQLLow volume, high importance
Damage AssessmentPhoto analysis, estimate generationPostgreSQL + S3GPU-bound (AI inference)
Fraud DetectionReal-time scoring, pattern matchingRedis + PostgreSQLCPU + memory bound
Adjudication EngineDecision logic, rule evaluationPostgreSQLCPU-bound (rules engine)
Reserve ServiceReserve calculation and updatesPostgreSQLLow volume
Payment ServicePayment orchestration, reconciliationPostgreSQL + payment gatewayI/O-bound (gateway calls)
Document ManagementUpload, OCR, retrievalS3 + ElasticsearchI/O-bound (storage)
Audit TrailImmutable event loggingTimescaleDBWrite-heavy, append-only
Notification ServiceEmail, SMS, push, mailPostgreSQLBursty (catastrophe events)
SLA MonitoringDeadline tracking, escalationPostgreSQL + RedisTimer-heavy (scheduled checks)
Event-Driven Design: All inter-service communication uses Kafka as the event bus. Each service publishes domain events (ClaimSubmitted, CoverageVerified, AdjusterAssigned, etc.) and subscribes to events from other services. This decouples services temporally — a service can process events at its own pace — and provides natural audit logging since every event is persisted in Kafka. The event schema is versioned using Avro with a schema registry to prevent breaking changes.

6. First Notice of Loss (FNOL) Intake

FNOL is the front door of the entire claims system. It is the first interaction a policyholder has with the claims process, and it sets the tone for their entire experience. A smooth, fast FNOL process builds trust; a clunky one creates anxiety and dissatisfaction. The FNOL service must accept claims from multiple channels, validate inputs, deduplicate against existing claims, extract structured data from unstructured inputs, and return an immediate confirmation with a claim number.

Intake Channels

ChannelInput TypeProcessingVolume
Web PortalStructured formDirect validation, instant creation35%
Mobile AppForm + photos + videoPhoto compression, GPS extraction, OCR40%
Phone (IVR + Agent)Verbal descriptionSpeech-to-text, agent data entry15%
EmailFree-text + attachmentsNLP parsing, attachment extraction5%
Agent PortalAgent-entered structured dataAgent-authenticated, expedited flow5%

FNOL Processing Pipeline

graph LR A[Raw Input] --> B[Validation] B --> C[Enrichment] C --> D[Deduplication] D --> E[Policy Lookup] E --> F[Fraud Pre-Screen] F --> G[Claim Creation] G --> H[Event Published]
C#
public class FnolService
{
    private readonly IClaimRepository _claimRepo;
    private readonly IPolicyService _policyService;
    private readonly IDeduplicationService _dedup;
    private readonly IFraudPreScreener _fraudScreener;
    private readonly IEventPublisher _events;
    private readonly IValidator<FnolRequest> _validator;

    public async Task<FnolResult> SubmitClaimAsync(
        FnolRequest request, CancellationToken ct)
    {
        // Step 1: Validate input
        var validation = await _validator.ValidateAsync(request);
        if (!validation.IsValid)
            throw new ValidationException(validation.Errors);

        // Step 2: Enrich with policy data
        var policy = await _policyService
            .LookupActivePolicyAsync(
                request.PolicyNumber, request.DateOfLoss);
        if (policy == null)
            throw new PolicyNotFoundException(request.PolicyNumber);

        // Step 3: Check for duplicate claims
        var existing = await _dedup
            .FindDuplicateAsync(request, policy.PolicyId);
        if (existing != null)
            return new FnolResult
            {
                ClaimNumber = existing.ClaimNumber,
                IsDuplicate = true,
                Message = "A similar claim already exists."
            };

        // Step 4: Create claim record
        var claim = new Claim
        {
            ClaimId = Guid.NewGuid(),
            ClaimNumber = GenerateClaimNumber(),
            PolicyId = policy.PolicyId,
            InsuredId = policy.InsuredId,
            Type = request.ClaimType,
            Status = ClaimStatus.Submitted,
            DateOfLoss = request.DateOfLoss,
            DateReported = DateTime.UtcNow,
            Description = request.Description,
            LossLocationAddress = request.LocationAddress,
            StateJurisdiction = DetermineJurisdiction(request),
            IsCatastrophe = await _catastropheService
                .IsCatastropheEventAsync(request.DateOfLoss, request.Location),
            EstimatedReserve = 0,
            CreatedAt = DateTime.UtcNow,
            UpdatedAt = DateTime.UtcNow
        };

        await _claimRepo.CreateAsync(claim);

        // Step 5: Pre-screen for fraud signals
        var fraudScore = await _fraudScreener
            .PreScreenAsync(claim, policy);
        claim.FraudScore = fraudScore;

        // Step 6: Publish domain event
        await _events.PublishAsync(new ClaimSubmittedEvent
        {
            ClaimId = claim.ClaimId,
            ClaimNumber = claim.ClaimNumber,
            PolicyId = policy.PolicyId,
            ClaimType = claim.Type,
            DateOfLoss = claim.DateOfLoss,
            FraudScore = fraudScore.Score,
            IsCatastrophe = claim.IsCatastrophe
        });

        return new FnolResult
        {
            ClaimNumber = claim.ClaimNumber,
            ClaimId = claim.ClaimId,
            Status = claim.Status,
            EstimatedProcessingDays = EstimateProcessingTime(claim)
        };
    }
}

Mobile Photo Upload Flow

The mobile app supports photo and video capture as part of FNOL. The upload flow is optimized for cellular connections with intermittent connectivity:

  • Capture: Photos are taken at native resolution (12MP+) and immediately compressed to 1920px on the longest edge with 85% JPEG quality, reducing a typical 5MB photo to ~300KB.
  • Metadata Extraction: EXIF data is preserved for GPS coordinates, timestamp, and device information. This metadata is critical for fraud investigation (verifying the photo was taken at the reported loss location at the reported time).
  • Offline Queue: If connectivity is unavailable, photos are queued locally with exponential backoff retry. The user sees a progress indicator and can continue filling out the claim form while photos upload in the background.
  • Deduplication: Perceptual hashing (pHash) compares uploaded photos against the claim's existing document set and against a global database of known fraudulent images to detect recycled or stock photos.
  • AI Pre-Analysis: Photos are immediately sent to the damage assessment AI for preliminary severity estimation, which feeds into reserve calculation and adjuster prioritization.
Mobile Connectivity: Design for the worst-case network scenario: 3G with 500ms latency and 10% packet loss. The mobile app should queue all uploads locally and sync when connectivity returns. Use background upload with exponential backoff to avoid blocking the UI. A claim can be fully submitted with placeholder photos — the system will merge the photo batch once all uploads complete.

Claim Deduplication

Claim duplication occurs when policyholders submit the same loss through multiple channels (web + phone), or when adjusters accidentally create duplicate records. The deduplication service uses a multi-signal approach:

  • Exact match: Same policy number + same date of loss + same loss location within 100 meters.
  • Fuzzy match: Same policy number + date of loss within 48 hours + similar description (cosine similarity > 0.8 on TF-IDF vectors).
  • Photo match: Perceptual hash similarity > 95% on uploaded photos (catches the same incident reported from different devices).
C#
public class DeduplicationService
{
    private readonly IClaimRepository _claimRepo;
    private readonly IPhotoHashService _photoHash;

    public async Task<Claim> FindDuplicateAsync(
        FnolRequest request, Guid policyId)
    {
        // Check 1: Exact match on policy + date + location
        var exactMatch = await _claimRepo.FindClaimAsync(
            policyId,
            request.DateOfLoss,
            request.LossLocationCoordinates,
            radiusMeters: 100);

        if (exactMatch != null) return exactMatch;

        // Check 2: Fuzzy match on description
        var recentClaims = await _claimRepo
            .GetRecentClaimsByPolicyAsync(policyId, days: 2);
        foreach (var claim in recentClaims)
        {
            var similarity = TextSimilarity
                .CosineSimilarity(
                    request.Description, claim.Description);
            if (similarity > 0.8)
                return claim;
        }

        // Check 3: Photo perceptual hash match
        if (request.Photos?.Any() == true)
        {
            var photoHashes = request.Photos
                .Select(p => _photoHash.ComputePerceptualHash(p));
            foreach (var hash in photoHashes)
            {
                var photoMatch = await _photoHash
                    .FindSimilarClaimByPhoto(hash, threshold: 0.95);
                if (photoMatch != null)
                    return photoMatch;
            }
        }

        return null; // No duplicate found
    }
}

Speech-to-Text for Phone FNOL

When policyholders call to report a claim, the IVR system captures the loss description using speech-to-text. The transcript is processed through NLP to extract structured fields: date of loss, location, type of damage, parties involved, and witness information. A confidence score is assigned to each extracted field — low-confidence fields are flagged for human review. The extracted data pre-populates the claim form for the agent, reducing call time by 40% on average. The system uses a fine-tuned language model trained on insurance call transcripts to achieve 94% field extraction accuracy.

7. Document Management & Evidence Handling

Claims generate massive document volumes. A single claim can accumulate 15-50 documents: incident photos, police reports, medical records, repair estimates, rental car receipts, tow bills, witness statements, correspondence letters, legal documents, and more. The document management service must handle upload, storage, versioning, OCR extraction, search, and retrieval with strict access controls and chain-of-custody tracking.

Document Categories

CategoryExamplesRetentionAccess Control
Photos/VideoDamage photos, dashcam footage, drone images7 yearsAdjuster, SIU, Manager
Police ReportsAccident reports, fire marshal reports7 yearsAdjuster, Legal
Medical RecordsMedical bills, treatment plans, IME reports10 yearsAdjuster, Medical Reviewer (HIPAA)
Repair EstimatesBody shop estimates, contractor bids7 yearsAdjuster, Appraiser
Legal DocumentsLawsuits, demand letters, settlement agreements10 yearsAdjuster, Legal, Manager
Financial RecordsInvoices, receipts, payroll records (WC)7 yearsAdjuster, Finance
CorrespondenceLetters, emails, SMS logs7 yearsAdjuster, Manager

Document Processing Pipeline

graph LR A[Upload] --> B[Virus Scan] B --> C[Dedup Check] C --> D[OCR Extraction] D --> E[Index & Store] E --> F[S3 + Metadata DB] D --> G[Structured Data → Claim Fields]
C#
public class DocumentProcessingService
{
    private readonly IVirusScanner _virusScanner;
    private readonly IOcrEngine _ocr;
    private readonly IDocumentRepository _docRepo;
    private readonly IIndexService _searchIndex;
    private readonly IPublisher _events;

    public async Task<DocumentResult> ProcessDocumentAsync(
        DocumentUpload upload, Guid claimId, string actorId)
    {
        // Step 1: Virus scan
        var scanResult = await _virusScanner.ScanAsync(upload.Stream);
        if (scanResult.IsInfected)
            throw new MalwareDetectedException(scanResult.ThreatName);

        // Step 2: Store original in S3
        var storageKey = $"claims/{claimId}/docs/{upload.FileName}";
        await _s3.PutObjectAsync(storageKey, upload.Stream);

        // Step 3: Run OCR for text-based documents
        string extractedText = null;
        Dictionary<string, string> structuredData = null;

        if (upload.ContentType.StartsWith("image/") ||
            upload.ContentType == "application/pdf")
        {
            extractedText = await _ocr.ExtractTextAsync(upload.Stream);
            structuredData = await _ocr.ExtractStructuredDataAsync(
                extractedText, upload.DocumentCategory);
        }

        // Step 4: Create document record
        var document = new ClaimDocument
        {
            DocumentId = Guid.NewGuid(),
            ClaimId = claimId,
            FileName = upload.FileName,
            ContentType = upload.ContentType,
            FileSizeBytes = upload.Stream.Length,
            StorageKey = storageKey,
            Category = upload.DocumentCategory,
            ExtractedText = extractedText,
            StructuredData = structuredData,
            UploadedBy = actorId,
            UploadedAt = DateTime.UtcNow,
            ChainOfCustody = new List<CustodyEntry>
            {
                new() { ActorId = actorId, Action = "Uploaded",
                    Timestamp = DateTime.UtcNow }
            }
        };

        await _docRepo.CreateAsync(document);

        // Step 5: Index for search
        await _searchIndex.IndexDocumentAsync(document);

        // Step 6: Apply extracted data to claim if high confidence
        if (structuredData?.ContainsKey("confidence") == true)
        {
            var confidence = double.Parse(
                structuredData["confidence"]);
            if (confidence > 0.9)
            {
                await _events.PublishAsync(new OcrDataExtractedEvent
                {
                    ClaimId = claimId,
                    DocumentId = document.DocumentId,
                    ExtractedData = structuredData
                });
            }
        }

        return new DocumentResult
        {
            DocumentId = document.DocumentId,
            Status = "Processed",
            ExtractedFieldsCount = structuredData?.Count ?? 0
        };
    }
}
HIPAA Compliance for Medical Documents: Medical records require special handling under HIPAA. They must be encrypted at rest and in transit, access must be logged and auditable, access must be limited to the minimum necessary for the claim, and the system must support patient right of access requests. Medical documents are stored in a separate S3 bucket with different lifecycle policies and access controls than general claim documents. All access to medical documents generates an audit log entry that is retained for 10 years.

8. Adjuster Assignment — Round-Robin & Skill-Based Routing

Assigning the right adjuster to a claim is critical for processing speed, accuracy, and customer satisfaction. A bodily injury claim assigned to a property adjuster will take twice as long and produce a worse outcome. A high-severity claim assigned to an adjuster already at capacity will miss SLA deadlines. The assignment engine must balance expertise, workload, geography, language, and claim characteristics.

Assignment Strategy

graph TD A[New Claim] --> B{Catastrophe?} B -->|Yes| C[Catastrophe Pool Assignment] B -->|No| D{Claim Type} D -->|Auto| E[Auto Adjuster Pool] D -->|Property| F[Property Adjuster Pool] D -->|Liability| G[Liability Adjuster Pool] D -->|Workers Comp| H[WC Adjuster Pool] E --> I[Skill-Based Selection] F --> I G --> I H --> I I --> J[Workload Balancing] J --> K[Geographic Proximity] K --> L[Language Match] L --> M[Final Assignment]
C#
public class AdjusterAssignmentService
{
    private readonly IAdjusterRepository _adjusterRepo;
    private readonly IWorkloadCalculator _workload;
    private readonly IEventPublisher _events;

    public async Task<AdjusterAssignment> AssignAdjusterAsync(
        Claim claim, CancellationToken ct)
    {
        // Step 1: Determine adjuster pool
        var pool = await _adjusterRepo.GetAvailableAdjustersAsync(
            claimType: claim.Type,
            jurisdiction: claim.StateJurisdiction,
            isCatastrophe: claim.IsCatastrophe);

        if (!pool.Any())
            throw new NoAdjustersAvailableException(claim.Type);

        // Step 2: Score each adjuster
        var scored = pool.Select(adj => new
        {
            Adjuster = adj,
            Score = CalculateAssignmentScore(adj, claim)
        })
        .OrderByDescending(x => x.Score)
        .ToList();

        // Step 3: Select top candidate
        var selected = scored.First().Adjuster;

        // Step 4: Create assignment
        var assignment = new AdjusterAssignment
        {
            ClaimId = claim.ClaimId,
            AdjusterId = selected.AdjusterId,
            AssignedAt = DateTime.UtcNow,
            AssignmentMethod = "skill-based",
            Score = scored.First().Score
        };

        // Step 5: Publish event
        await _events.PublishAsync(new AdjusterAssignedEvent
        {
            ClaimId = claim.ClaimId,
            AdjusterId = selected.AdjusterId,
            AssignmentScore = scored.First().Score
        });

        return assignment;
    }

    private double CalculateAssignmentScore(
        Adjuster adj, Claim claim)
    {
        double score = 0;

        // Skill match (0-40 points)
        if (adj.Specialties.Contains(claim.Type))
            score += 40;
        else if (adj.SecondarySpecialties.Contains(claim.Type))
            score += 20;

        // Workload (0-30 points, inversely proportional)
        var workloadRatio = adj.CurrentCaseload /
            (double)adj.MaxCaseload;
        score += (1.0 - workloadRatio) * 30;

        // Geographic proximity (0-15 points)
        var distance = GeoDistance.Calculate(
            adj.OfficeLocation, claim.LossLocationCoordinates);
        score += Math.Max(0, 15 - (distance / 10.0));

        // Language match (0-10 points)
        if (adj.Languages.Contains(
            claim.Insured.PreferredLanguage))
            score += 10;

        // Seniority/experience (0-5 points)
        score += Math.Min(5, adj.YearsOfExperience / 2.0);

        return score;
    }
}

Workload Management

Load LevelCaseload ThresholdAction
Green (Normal)< 70% of maxStandard assignment
Yellow (Elevated)70-85% of maxReduced priority, no new complex claims
Red (Capacity)85-100% of maxOnly simple/low-severity claims
Overloaded> 100% of maxNo new assignments, auto-rebalance
Reassignment on SLA Breach: If an adjuster fails to contact the insured within the regulatory timeframe (varies by state: 24 hours for initial contact in most states, up to 15 days for written acknowledgment in some), the system automatically reassigns the claim and notifies the adjuster's supervisor. The reassignment includes a full context transfer: all notes, documents, and activity history.

9. Damage Assessment & AI Photo Analysis

AI-powered damage assessment uses computer vision to analyze claim photos and generate initial severity estimates. This reduces the time between FNOL and initial reserve setting from days to minutes, enables faster payment for straightforward claims, and helps prioritize adjuster attention toward the most severe cases. The AI system analyzes photos for damage type, severity, affected components, and estimated repair/replacement costs.

AI Assessment Pipeline

graph LR A[Claim Photos] --> B[Object Detection] B --> C[Damage Classification] C --> D[Severity Scoring] D --> E[Cost Estimation] E --> F[Confidence Rating] F --> G{Confidence > 0.85?} G -->|Yes| H[Auto-Estimate Applied] G -->|No| I[Adjuster Review Required]
C#
public class AiDamageAssessmentService
{
    private readonly IObjectDetectionModel _objectDetector;
    private readonly IDamageClassifier _damageClassifier;
    private readonly ICostEstimator _costEstimator;
    private readonly IPhotoRepository _photoRepo;

    public async Task<DamageAssessment> AssessDamageAsync(
        Guid claimId, CancellationToken ct)
    {
        var photos = await _photoRepo.GetClaimPhotosAsync(claimId);
        if (!photos.Any())
            return new DamageAssessment
            {
                Status = "NoPhotosAvailable",
                RequiresManualReview = true
            };

        var detections = new List<ObjectDetection>();
        var damages = new List<DetectedDamage>();

        foreach (var photo in photos)
        {
            // Detect objects (vehicle, parts, building components)
            var objects = await _objectDetector.DetectAsync(
                photo.ImageData);
            detections.AddRange(objects);

            // Classify damage within detected regions
            foreach (var obj in objects)
            {
                var damage = await _damageClassifier.ClassifyAsync(
                    photo.ImageData, obj.BoundingBox);
                if (damage != null)
                {
                    damage.SourcePhotoId = photo.DocumentId;
                    damage.DetectedObject = obj.Label;
                    damages.Add(damage);
                }
            }
        }

        // Aggregate damages and estimate costs
        var assessment = new DamageAssessment
        {
            ClaimId = claimId,
            DetectedObjects = detections,
            IdentifiedDamages = damages,
            SeverityScore = CalculateOverallSeverity(damages),
            EstimatedRepairCost = await _costEstimator
                .EstimateRepairCostAsync(damages),
            EstimatedReplacementCost = await _costEstimator
                .EstimateReplacementCostAsync(damages),
            ConfidenceScore = CalculateConfidence(
                photos.Count, damages.Count),
            RequiresManualReview = CalculateConfidence(
                photos.Count, damages.Count) < 0.85,
            AssessedAt = DateTime.UtcNow
        };

        return assessment;
    }

    private double CalculateConfidence(
        int photoCount, int damageCount)
    {
        var photoFactor = Math.Min(1.0, photoCount / 8.0);
        var damageFactor = damageCount > 0 ? 1.0 : 0.0;
        return photoFactor * 0.6 + damageFactor * 0.4;
    }
}

Damage Classification Categories

CategoryAuto ClaimsProperty Claims
StructuralFrame, body panels, suspensionRoof, walls, foundation
MechanicalEngine, transmission, brakesHVAC, plumbing, electrical
CosmeticPaint, trim, interiorFlooring, paint, fixtures
SafetyAirbags, seatbelts, lightsSmoke detectors, handrails
Total LossExceeds threshold (70-80% value)Exceeds insured value
AI Confidence Threshold: The AI system must never be the sole decision-maker for claim payments. When confidence is below 0.85, the assessment is treated as a supplementary data point for the human adjuster, not a replacement. When confidence is above 0.85, the AI estimate can be used for initial reserves but must still be reviewed by an adjuster before final payment. This "human-in-the-loop" approach ensures accuracy while leveraging AI for speed.

10. Coverage Verification & Policy Lookup

Coverage verification determines whether the reported loss is covered under the policy, what limits and deductibles apply, and whether any exclusions or conditions affect the claim. This is one of the most legally sensitive parts of claims processing — an incorrect coverage determination can result in bad faith litigation. The coverage service must parse complex policy language, apply state-specific insurance regulations, and produce a clear, documented coverage determination.

C#
public class CoverageVerificationService
{
    private readonly IPolicyService _policyService;
    private readonly ICoverageRulesEngine _rulesEngine;
    private readonly IStateRegulationService _regulations;

    public async Task<CoverageDetermination> VerifyCoverageAsync(
        Claim claim, CancellationToken ct)
    {
        // Step 1: Retrieve the active policy
        var policy = await _policyService
            .GetPolicyAsync(claim.PolicyId);
        if (policy == null || policy.Status != PolicyStatus.Active)
            return new CoverageDetermination
            {
                IsCovered = false,
                DenialReason = "Policy is not active",
                DenialCode = "POLICY_INACTIVE"
            };

        // Step 2: Check if loss type is covered
        var coverageType = policy.Coverages
            .FirstOrDefault(c => c.Type == MapClaimToCoverageType(claim.Type));
        if (coverageType == null)
            return new CoverageDetermination
            {
                IsCovered = false,
                DenialReason = $"No {claim.Type} coverage on policy",
                DenialCode = "COVERAGE_NOT_FOUND"
            };

        // Step 3: Evaluate coverage rules
        var rulesResult = await _rulesEngine.EvaluateAsync(
            policy, claim, coverageType);

        // Step 4: Apply state-specific regulations
        var stateRules = await _regulations
            .GetStateRulesAsync(claim.StateJurisdiction);

        // Step 5: Calculate limits and deductibles
        var determination = new CoverageDetermination
        {
            IsCovered = rulesResult.IsCovered,
            CoverageId = coverageType.CoverageId,
            CoverageName = coverageType.Name,
            CoverageLimit = coverageType.Limit,
            Deductible = coverageType.Deductible,
            CopayPercentage = coverageType.Copay,
            WaitingPeriodDays = coverageType.WaitingPeriod,
            Exclusions = rulesResult.ApplicableExclusions,
            Conditions = rulesResult.ApplicableConditions,
            DenialReason = rulesResult.DenialReason,
            DenialCode = rulesResult.DenialCode,
            StateRegulationNotes = stateRules.ClaimHandlingNotes,
            VerifiedAt = DateTime.UtcNow,
            VerifiedBy = "System"
        };

        return determination;
    }
}

Common Coverage Scenarios

ScenarioCheckPotential Outcome
Auto collisionLiability coverage, collision coverage, limitsCovered up to policy limits minus deductible
Water damage (burst pipe)HO-3 sudden & accidental, exclude gradualCovered if sudden; denied if gradual/maintenance
Hurricane damageWind/hail coverage, separate hurricane deductibleSubject to named-storm deductible (1-5% of dwelling)
Work injuryWorkers comp, no-fault coverageCovered (workers comp is no-fault)
Liability (slip & fall)General liability, premises liabilityCovered defense + indemnity if applicable
Bad Faith Risk: An incorrect coverage denial can expose the insurer to bad faith litigation, which can result in damages far exceeding the original claim amount. The coverage determination must be thoroughly documented with the specific policy language, state regulation, and analysis that supports the decision. Every denial must be reviewed by a coverage specialist before issuance, and the denial letter must clearly explain the basis for the decision and the policyholder's right to appeal.

11. Reserve Calculation

Reserves represent the insurer's estimate of the total cost of a claim. They are set at multiple points during the claim lifecycle and directly impact the insurer's financial statements (loss reserves appear as liabilities on the balance sheet). Accurate reserves are critical for solvency, regulatory compliance, and reinsurance reporting. The reserve calculation engine uses statistical models, historical data, and claim-specific factors to generate initial, running, and final reserves.

Reserve Types

Reserve TypeWhen SetPurpose
IBNR (Incurred But Not Reported)At policy inceptionStatistical estimate of future claims from past policies
Initial ReserveAt FNOLFirst estimate based on claim type and severity indicators
Adjusted ReserveAfter investigationUpdated estimate with adjuster findings, AI assessment, vendor estimates
Payment ReserveBefore paymentFinal amount based on adjudication decision
Case ReserveOngoingRunning total of all reserves for the claim
C#
public class ReserveCalculationService
{
    private readonly IClaimRepository _claimRepo;
    private readonly IHistoricalDataStore _history;
    private readonly IAiDamageAssessment _aiAssessment;

    public async Task<ReserveEstimate> CalculateInitialReserveAsync(
        Claim claim, Policy policy)
    {
        // Get historical claims of similar type and severity
        var similarClaims = await _history
            .GetSimilarClaimsAsync(
                claim.Type,
                claim.StateJurisdiction,
                claim.FraudScore?.RiskLevel ?? "Low",
                sampleSize: 1000);

        // Statistical base estimate
        var medianPayout = similarClaims
            .Select(c => c.FinalPayout)
            .OrderBy(p => p)
            .ElementAt(similarClaims.Count / 2);
        var p90Payout = similarClaims
            .Select(c => c.FinalPayout)
            .OrderBy(p => p)
            .ElementAt((int)(similarClaims.Count * 0.9));

        // Apply severity multiplier from AI assessment
        double severityMultiplier = 1.0;
        if (claim.AiAssessment != null)
        {
            severityMultiplier = claim.Type switch
            {
                ClaimType.Auto when claim.AiAssessment.SeverityScore > 8 => 2.5,
                ClaimType.Auto when claim.AiAssessment.SeverityScore > 5 => 1.5,
                ClaimType.Auto => 1.0,
                ClaimType.Property when claim.AiAssessment.SeverityScore > 7 => 2.0,
                _ => 1.0
            };
        }

        // Apply catastrophe multiplier
        if (claim.IsCatastrophe)
            severityMultiplier *= 1.3;

        var estimatedReserve = medianPayout * severityMultiplier;

        // Cap at policy limit
        var coverageLimit = policy.Coverages
            .First(c => c.Type == MapClaimType(claim.Type)).Limit;
        estimatedReserve = Math.Min(estimatedReserve, coverageLimit);

        return new ReserveEstimate
        {
            ClaimId = claim.ClaimId,
            InitialReserve = estimatedReserve,
            StatisticalBasis = $"Median ${medianPayout:N0} from {similarClaims.Count} similar claims",
            SeverityMultiplier = severityMultiplier,
            CoverageLimit = coverageLimit,
            P90Estimate = p90Payout * severityMultiplier,
            ConfidenceInterval = CalculateConfidenceInterval(
                similarClaims),
            CalculatedAt = DateTime.UtcNow
        };
    }
}
Reserve Adequacy: Regulators monitor reserve adequacy closely. Under-reserving can lead to solvency issues and regulatory action. Over-reserving unnecessarily ties up capital. The system tracks reserve development over time — comparing initial estimates against final payouts — and recalibrates the statistical models quarterly. A "reserve development triangle" report shows whether the insurer is consistently over or under-estimating by claim type, state, and severity tier.

12. Fraud Detection Integration

Insurance fraud costs the industry over $80 billion per year in the US alone. Fraud detection is not a single system but a layered defense: pre-screening at FNOL, real-time scoring during investigation, and batch analysis across the entire claim portfolio. The fraud detection system uses rule engines, machine learning models, network analysis, and external databases to identify suspicious patterns.

Fraud Detection Layers

graph TB subgraph Real-Time A[FNOL Pre-Screen] --> B[Real-Time Scoring] B --> C[Alert Generation] end subgraph Batch D[Nightly Pattern Analysis] --> E[Network Analysis] E --> F[Anomaly Detection] F --> G[SIU Worklist] end subgraph External H[NICB Database] I[ISO ClaimSearch] J[State Fraud Bureau] end C --> H G --> H G --> I
C#
public class FraudDetectionService
{
    private readonly IFraudRuleEngine _ruleEngine;
    private readonly IFraudMlModel _mlModel;
    private readonly IExternalFraudDb _externalDb;
    private readonly IGraphAnalysis _graphAnalysis;

    public async Task<FraudScore> ScoreClaimAsync(
        Claim claim, Policy policy)
    {
        var signals = new List<FraudSignal>();

        // Layer 1: Rule-based pre-screening
        var ruleSignals = await _ruleEngine.EvaluateAsync(claim, policy);
        signals.AddRange(ruleSignals);

        // Layer 2: ML model scoring
        var features = ExtractFeatures(claim, policy);
        var mlScore = await _mlModel.PredictAsync(features);
        signals.Add(new FraudSignal
        {
            Source = "ML_MODEL",
            Score = mlScore.Probability,
            Explanation = mlScore.TopFeatures
        });

        // Layer 3: External database checks
        var externalMatches = await _externalDb
            .SearchAsync(claim.Insured, claim.LossLocation);
        if (externalMatches.Any())
        {
            signals.Add(new FraudSignal
            {
                Source = "EXTERNAL_DB",
                Score = 0.8,
                Explanation = $"Found {externalMatches.Count} matches in fraud databases"
            });
        }

        // Layer 4: Network analysis (collusion detection)
        var networkScore = await _graphAnalysis
            .AnalyzeNetworkAsync(
                claim.Insured,
                claim.Claimants,
                claim.RepairShop,
                claim.Attorney);
        if (networkScore > 0.5)
        {
            signals.Add(new FraudSignal
            {
                Source = "NETWORK_ANALYSIS",
                Score = networkScore,
                Explanation = "Suspicious network connections detected"
            });
        }

        // Aggregate scores
        var overallScore = AggregateScores(signals);
        var riskLevel = overallScore switch
        {
            > 0.8 => "High",
            > 0.5 => "Medium",
            > 0.3 => "Low",
            _ => "Minimal"
        };

        return new FraudScore
        {
            ClaimId = claim.ClaimId,
            OverallScore = overallScore,
            RiskLevel = riskLevel,
            Signals = signals,
            RecommendedAction = riskLevel switch
            {
                "High" => "Refer to SIU",
                "Medium" => "Enhanced investigation",
                _ => "Standard processing"
            },
            ScoredAt = DateTime.UtcNow
        };
    }
}

Common Fraud Indicators

IndicatorWeightDetection Method
Claim filed within 30 days of policy inceptionHighRule engine
Excessive documentation for simple claimMediumNLP analysis
Repair shop with high claim frequencyHighNetwork analysis
Photo metadata inconsistenciesHighEXIF analysis
Similar claim history (staged accidents)HighISO ClaimSearch + ML
Injuries inconsistent with accident typeMediumMedical AI analysis
Attorney involvement at FNOLLow-MediumRule engine
Policy coverage increased shortly before lossHighPolicy history analysis
False Positive Management: A fraud detection system with too many false positives wastes SIU resources and delays legitimate claims. The system must be calibrated to maintain a false positive rate below 5%. When a claim is flagged, the adjuster must be notified but must NOT be biased against the claimant — the investigation must remain objective. The system tracks SIU referral outcomes to continuously tune the detection models.

13. Adjudication & Decision Engine

Adjudication is the core decision-making step where the insurer determines coverage (approve, deny, partial), liability allocation, and payment amount. The adjudication engine combines rule-based logic with machine learning to handle straightforward claims automatically while escalating complex claims to human adjusters. The goal is to "auto-adjudicate" 60-70% of claims (simple, clear-cut cases) while handling the remaining 30-40% through human review.

Decision Flow

graph TD A[Claim Ready for Adjudication] --> B{AI Confidence > 0.9?} B -->|Yes| C{Coverage Verified?} B -->|No| D[Manual Adjuster Review] C -->|Yes| E{Liability Clear?} C -->|No| F[Coverage Denial] E -->|Yes| G{Reserve < Auto-Adjudicate Limit?} E -->|No| H[Shared Liability Calculation] G -->|Yes| I[Auto-Adjudicate: Approve] G -->|No| D I --> J[Payment Processing] D --> K[Adjuster Decision] K --> L{Decision} L -->|Approve| J L -->|Deny| F L -->|Partial| M[Partial Approval + Payment] F --> N[Denial Letter Generation] N --> O[Appeals Window Starts]
C#
public class AdjudicationEngine
{
    private readonly ICoverageService _coverage;
    private readonly IReserveService _reserve;
    private readonly IFraudService _fraud;
    private readonly IStateRulesService _stateRules;
    private readonly IEventPublisher _events;

    public async Task<AdjudicationResult> AdjudicateAsync(
        Claim claim, CancellationToken ct)
    {
        var result = new AdjudicationResult { ClaimId = claim.ClaimId };

        // Step 1: Verify coverage (must be complete)
        var coverage = await _coverage.VerifyCoverageAsync(claim);
        if (!coverage.IsCovered)
        {
            result.Decision = ClaimDecision.Denied;
            result.DenialReason = coverage.DenialReason;
            result.DenialCode = coverage.DenialCode;
            await PublishDecision(claim, result);
            return result;
        }

        // Step 2: Evaluate liability (if applicable)
        var liability = await EvaluateLiabilityAsync(claim);
        if (liability.PolicyholderPercentage == 0)
        {
            result.Decision = ClaimDecision.Denied;
            result.DenialReason = "No liability on the part of the insured";
            result.DenialCode = "NO_LIABILITY";
            await PublishDecision(claim, result);
            return result;
        }

        // Step 3: Calculate damages
        var damages = await CalculateDamagesAsync(
            claim, coverage, liability);

        // Step 4: Apply deductible and copay
        var netPayment = damages.TotalDamage
            * liability.PolicyholderPercentage / 100.0
            - coverage.Deductible;
        netPayment = Math.Max(0, netPayment);

        // Step 5: Check against policy limits
        netPayment = Math.Min(netPayment, coverage.CoverageLimit);

        // Step 6: Determine decision
        if (netPayment > 0)
        {
            if (netPayment == damages.TotalDamage *
                liability.PolicyholderPercentage / 100.0 - coverage.Deductible)
            {
                result.Decision = ClaimDecision.Approved;
            }
            else
            {
                result.Decision = ClaimDecision.PartiallyApproved;
            }
            result.ApprovedAmount = netPayment;
            result.PaymentBreakdown = new PaymentBreakdown
            {
                GrossDamages = damages.TotalDamage,
                LiabilityPercentage = liability.PolicyholderPercentage,
                Deductible = coverage.Deductible,
                CopayAmount = damages.TotalDamage * coverage.CopayPercentage / 100.0,
                SubrogationAmount = damages.TotalDamage *
                    (100 - liability.PolicyholderPercentage) / 100.0,
                NetPayment = netPayment
            };
        }
        else
        {
            result.Decision = ClaimDecision.Denied;
            result.DenialReason = "Damages below deductible";
            result.DenialCode = "BELOW_DEDUCTIBLE";
        }

        // Step 7: Check state-specific payment delay rules
        var stateDelay = await _stateRules
            .GetPaymentDelayAsync(claim.StateJurisdiction);
        result.PaymentDueDate = DateTime.UtcNow
            .AddDays(stateDelay.MaxDaysToPay);

        await PublishDecision(claim, result);
        return result;
    }
}
Auto-Adjudication: For straightforward claims (clear liability, AI confidence > 0.9, reserve under $5,000), the system can fully auto-adjudicate without human intervention. The policyholder receives a coverage decision and payment within 48 hours of FNOL — dramatically faster than the industry average of 30+ days. Auto-adjudicated claims are still audited by a human reviewer on a sampling basis (10% sample) to ensure quality.

14. Payment Processing

Payment processing is the culmination of the claims process — the moment the policyholder receives the financial benefit of their insurance. The payment service must support multiple payment methods (check, EFT/ACH, wire transfer, split payments), comply with state-specific payment timing regulations, handle payee verification, prevent duplicate payments, and maintain reconciliation with the general ledger.

Payment Methods

MethodProcessing TimeCostUse Case
EFT/ACH1-2 business days$0.50/transactionStandard payments under $100K
Check (printed)3-5 business days (mail)$2.50/checkPolicyholders without bank info
Wire TransferSame day$25/transferLarge settlements, legal disbursements
Split PaymentVaries by methodSum of methodsMultiple payees (insured + lien holder)
Direct Pay to Vendor1-3 business days$1.00Repair shops, medical providers
C#
public class PaymentService
{
    private readonly IPaymentGateway _gateway;
    private readonly ICheckPrinter _checkPrinter;
    private readonly IPaymentRepository _paymentRepo;
    private readonly IEventPublisher _events;
    private readonly ILedgerService _ledger;

    public async Task<PaymentResult> ProcessPaymentAsync(
        ClaimPayment payment, CancellationToken ct)
    {
        // Step 1: Validate payment (no duplicates, within limits)
        var validation = await ValidatePaymentAsync(payment);
        if (!validation.IsValid)
            throw new PaymentValidationException(validation.Errors);

        // Step 2: Determine payment method
        var method = DeterminePaymentMethod(payment);

        // Step 3: Process based on method
        PaymentResult result = method switch
        {
            PaymentMethod.EFT => await ProcessEftAsync(payment),
            PaymentMethod.Check => await ProcessCheckAsync(payment),
            PaymentMethod.Wire => await ProcessWireAsync(payment),
            PaymentMethod.SplitPayment => await ProcessSplitAsync(payment),
            _ => throw new UnsupportedPaymentMethodException(method)
        };

        // Step 4: Record payment
        payment.PaymentId = result.PaymentId;
        payment.Status = result.Status;
        payment.ProcessedAt = DateTime.UtcNow;
        await _paymentRepo.CreateAsync(payment);

        // Step 5: Update claim reserve
        await UpdateReserveAsync(payment.ClaimId, payment.Amount);

        // Step 6: Post to general ledger
        await _ledger.PostAsync(new LedgerEntry
        {
            ClaimId = payment.ClaimId,
            Amount = payment.Amount,
            Account = "Claims Paid",
            Reference = payment.PaymentId
        });

        // Step 7: Publish event
        await _events.PublishAsync(new PaymentIssuedEvent
        {
            ClaimId = payment.ClaimId,
            PaymentId = payment.PaymentId,
            Amount = payment.Amount,
            Method = method,
            Payees = payment.Payees
        });

        return result;
    }

    private async Task<PaymentResult> ProcessSplitAsync(
        ClaimPayment payment)
    {
        var results = new List<PaymentResult>();
        foreach (var payee in payment.Payees)
        {
            var partialPayment = new ClaimPayment
            {
                ClaimId = payment.ClaimId,
                Amount = payee.Amount,
                Payee = payee,
                Method = payee.PreferredMethod
            };
            var result = await ProcessPaymentAsync(partialPayment);
            results.Add(result);
        }

        return new PaymentResult
        {
            PaymentId = Guid.NewGuid(),
            Status = results.All(r => r.Status == PaymentStatus.Completed)
                ? PaymentStatus.Completed
                : PaymentStatus.PartiallyCompleted,
            SubPayments = results
        };
    }
}
Split Payment Scenarios: Split payments are common when there are lien holders (auto loans, mortgages), multiple insureds on a policy, or subrogation recoveries. The system must correctly allocate payments across all payees, ensure lien holders receive their contractual share first, and handle scenarios where one payee's payment fails while others succeed.

State Payment Regulations

StatePayment DeadlinePenalty for Late Payment
California30 days from decision2% per month interest
Florida20 days from decision12% per annum penalty
New York15 business days2% per month, up to policy limit
Texas5 business days for EFT18% per annum penalty
Federal (NFIP)30 days from adjuster reportInterest from date of loss

15. Subrogation, Salvage & Recovery

Subrogation is the insurer's right to recover payment from the responsible third party after paying a claim. If your insurer pays for your car repair because someone else caused the accident, your insurer then "steps into your shoes" and seeks reimbursement from the at-fault party's insurer. Salvage refers to the recovery value of damaged property (e.g., a totaled vehicle sold at auction). These recovery processes can recoup 15-25% of total claims paid, making them financially significant.

C#
public class SubrogationService
{
    private readonly ISubrogationRepository _repo;
    private readonly ILiabilityService _liability;
    private readonly IEventPublisher _events;

    public async Task<SubrogationAssessment> EvaluateSubrogationAsync(
        Claim claim, AdjudicationResult adjudication)
    {
        // Only evaluate if there's third-party liability
        if (adjudication.Liability?.ThirdPartyPercentage == 0)
            return new SubrogationAssessment
            {
                HasSubrogationPotential = false,
                Reason = "No third-party liability"
            };

        var thirdPartyShare = adjudication.PaymentBreakdown
            .SubrogationAmount;

        // Estimate recovery probability
        var recoveryProb = await EstimateRecoveryProbabilityAsync(
            claim, adjudication);

        var expectedRecovery = thirdPartyShare * recoveryProb;

        // Minimum threshold for pursuing subrogation
        if (expectedRecovery < 500)
            return new SubrogationAssessment
            {
                HasSubrogationPotential = true,
                RecommendedAction = "Write off",
                Reason = $"Expected recovery ${expectedRecovery:N0} below threshold"
            };

        return new SubrogationAssessment
        {
            HasSubrogationPotential = true,
            EstimatedRecovery = thirdPartyShare,
            RecoveryProbability = recoveryProb,
            ExpectedValue = expectedRecovery,
            AtFaultParty = claim.ThirdPartyInfo?.Name,
            AtFaultInsurer = claim.ThirdPartyInfo?.Insurer,
            RecommendedAction = recoveryProb > 0.7
                ? "Pursue subrogation"
                : "Evaluate cost-benefit",
            StatuteOfLimitations = await GetSubrogationDeadlineAsync(
                claim.StateJurisdiction)
        };
    }
}

Recovery Workflow

graph LR A[Claim Paid] --> B{Subrogation Assessment} B -->|High Potential| C[Subrogation Demand Letter] B -->|Low Potential| D[Write Off] C --> E{Third-Party Insurer Response} E -->|Accepts| F[Recovery Payment Received] E -->|Disputes| G[Negotiation / Mediation] G -->|Resolved| F G -->|Unresolved| H[Arbitration / Litigation] H --> F F --> I[Recovery Applied to Claim]

Salvage Processing

When a claim results in a total loss, the insurer takes possession of the damaged property and sells it through salvage channels. The salvage value reduces the net claim cost. The salvage service manages inventory, auction listing, buyer management, and title transfer. Typical salvage recovery is 10-30% of the pre-loss value for vehicles and 5-15% for property contents.

16. Appeals Workflow & Dispute Resolution

Policyholders have the right to dispute claim decisions. The appeals process must be fair, well-documented, and compliant with state regulations. Each appeal triggers a fresh review by a different adjuster or a panel, ensuring independence from the original decision-maker. The system tracks appeal outcomes to identify systemic issues with claim handling practices.

Appeals Workflow States

stateDiagram-v2 [*] --> AppealFiled AppealFiled --> UnderReview UnderReview --> PanelReview: Complex/Large Amount UnderReview --> ManagerReview: Standard PanelReview --> AppealDecision ManagerReview --> AppealDecision AppealDecision --> AppealUpheld: Original Decision Stands AppealDecision --> AppealOverturned: Decision Changed AppealDecision --> PartialAdjustment: Partial Change AppealOverturned --> PaymentProcessing PartialAdjustment --> PaymentProcessing AppealUpheld --> ExternalAppeal: Policyholder Requests ExternalAppeal --> RegulatoryReview RegulatoryReview --> FinalResolution FinalResolution --> [*]
C#
public class AppealsService
{
    private readonly IAppealRepository _repo;
    private readonly IClaimRepository _claimRepo;
    private readonly IAdjusterAssignmentService _assignment;

    public async Task<AppealResult> ProcessAppealAsync(
        AppealRequest request, CancellationToken ct)
    {
        // Create appeal record
        var appeal = new Appeal
        {
            AppealId = Guid.NewGuid(),
            ClaimId = request.ClaimId,
            OriginalDecision = request.OriginalDecision,
            AppealReason = request.Reason,
            FiledBy = request.PolicyholderId,
            FiledAt = DateTime.UtcNow,
            Status = AppealStatus.UnderReview
        };

        // Assign to a different adjuster than original
        var originalAdjuster = await _claimRepo
            .GetAssignedAdjusterAsync(request.ClaimId);
        var newAdjuster = await _assignment
            .AssignAppealReviewerAsync(
                request.ClaimId, originalAdjuster.AdjusterId);

        appeal.ReviewingAdjusterId = newAdjuster.AdjusterId;

        // Determine review type based on amount
        if (request.DisputedAmount > 50000)
        {
            appeal.ReviewType = ReviewType.PanelReview;
            appeal.PanelMembers = await SelectPanelMembersAsync();
        }
        else
        {
            appeal.ReviewType = ReviewType.ManagerReview;
        }

        await _repo.CreateAsync(appeal);

        // Check regulatory deadline
        var deadline = await GetAppealDeadlineAsync(
            request.ClaimId, request.StateJurisdiction);
        appeal.Deadline = deadline;

        return new AppealResult
        {
            AppealId = appeal.AppealId,
            Status = appeal.Status,
            EstimatedResolution = deadline,
            ReviewType = appeal.ReviewType
        };
    }
}
Regulatory Deadlines: Most states impose strict deadlines for resolving appeals (typically 30-60 days for internal appeals, 60-90 days for external regulatory appeals). The system must track these deadlines, send escalation alerts at 75% and 90% of deadline, and automatically escalate unresolved appeals to management. Missing an appeal deadline can result in the appeal being automatically granted or regulatory penalties.

17. SLA Tracking & Regulatory Compliance

Insurance claims processing is heavily regulated. Each state has specific requirements for how quickly an insurer must acknowledge a claim, investigate it, make a coverage decision, and issue payment. Violating these timelines can result in penalties, bad faith lawsuits, and regulatory action. The SLA tracking system monitors every claim against its applicable deadlines and triggers escalating actions as deadlines approach.

SLA Timeline by State

ActionCaliforniaFloridaNew YorkTexas
Initial Acknowledgment15 days14 days15 business days15 days
Investigation CompleteWithin reasonable time90 daysWithin reasonable time45 days
Coverage Decision40 days90 days15 business days15 business days
Payment After Decision30 days20 days15 business days5 business days (EFT)
Denial LetterWith decisionWithin decision timeframeWith decisionWithin decision timeframe
C#
public class SlaMonitoringService
{
    private readonly ISlaRepository _slaRepo;
    private readonly IClaimRepository _claimRepo;
    private readonly INotificationService _notifications;
    private readonly IEventPublisher _events;

    public async Task CheckSlaComplianceAsync(
        CancellationToken ct)
    {
        var activeClaims = await _claimRepo
            .GetActiveClaimsAsync();

        foreach (var claim in activeClaims)
        {
            var deadlines = await _slaRepo
                .GetApplicableDeadlinesAsync(
                    claim.StateJurisdiction,
                    claim.Type);

            foreach (var deadline in deadlines)
            {
                var elapsed = DateTime.UtcNow - claim.CreatedAt;
                var remaining = deadline.Value - elapsed;
                var percentage = elapsed.TotalSeconds /
                    deadline.Value.TotalSeconds;

                // Warning at 75%
                if (percentage >= 0.75 && percentage < 0.90)
                {
                    await _notifications.SendAsync(
                        new SlaWarningNotification
                        {
                            ClaimId = claim.ClaimId,
                            AdjusterId = claim.AssignedAdjusterId,
                            Deadline = deadline.Key,
                            RemainingTime = remaining,
                            Percentage = percentage
                        });
                }

                // Critical at 90%
                if (percentage >= 0.90 && percentage < 1.0)
                {
                    await _notifications.SendAsync(
                        new SlaCriticalNotification
                        {
                            ClaimId = claim.ClaimId,
                            AdjusterId = claim.AssignedAdjusterId,
                            SupervisorId = claim.SupervisorId,
                            Deadline = deadline.Key,
                            RemainingTime = remaining
                        });
                }

                // Breach
                if (percentage >= 1.0)
                {
                    await HandleSlaBreachAsync(
                        claim, deadline.Key, remaining);
                }
            }
        }
    }

    private async Task HandleSlaBreachAsync(
        Claim claim, string deadlineType, TimeSpan overdue)
    {
        // Log the breach
        await _events.PublishAsync(new SlaBreachEvent
        {
            ClaimId = claim.ClaimId,
            DeadlineType = deadlineType,
            OverdueDuration = overdue,
            Severity = overdue.TotalDays > 7
                ? "Critical" : "Warning"
        });

        // Escalate to supervisor and compliance
        await _notifications.SendAsync(new SlaBreachNotification
        {
            ClaimId = claim.ClaimId,
            AdjusterId = claim.AssignedAdjusterId,
            SupervisorId = claim.SupervisorId,
            ComplianceTeam = true,
            DeadlineType = deadlineType,
            OverdueDuration = overdue
        });

        // Auto-reassign if adjuster is overloaded
        if (overdue.TotalDays > 3)
        {
            await ReassignClaimAsync(claim);
        }
    }
}
Bad Faith Exposure: SLA breaches are the #1 source of bad faith lawsuits. A single bad faith judgment can exceed the original claim amount by 10-100x in punitive damages. The system must treat SLA compliance as a first-class concern, not an afterthought. Every SLA-related action (warning, escalation, breach) must be logged in the audit trail with timestamps.

18. API Design & Third-Party Integrations

The claims system integrates with numerous external systems: medical providers (for records and billing), repair shops (for estimates), body shops (for status updates), rental car companies (for loss-of-use), government agencies (for police reports, vital records), reinsurance carriers (for treaty claims), and regulatory bodies (for reporting). The API layer must be versioned, authenticated, rate-limited, and audited.

Core API Endpoints

HTTP
# FNOL Intake
POST   /api/v1/claims                          # Submit new claim
GET    /api/v1/claims/{claimNumber}            # Get claim details
PUT    /api/v1/claims/{claimId}                # Update claim
POST   /api/v1/claims/{claimId}/reopen         # Reopen closed claim

# Document Management
POST   /api/v1/claims/{claimId}/documents      # Upload document
GET    /api/v1/claims/{claimId}/documents      # List documents
GET    /api/v1/documents/{documentId}/download  # Download document
DELETE /api/v1/documents/{documentId}          # Remove document

# Coverage & Adjudication
POST   /api/v1/claims/{claimId}/coverage-check # Verify coverage
GET    /api/v1/claims/{claimId}/coverage       # Get coverage determination
POST   /api/v1/claims/{claimId}/adjudicate     # Trigger adjudication
GET    /api/v1/claims/{claimId}/decision       # Get decision

# Payments
POST   /api/v1/claims/{claimId}/payments       # Issue payment
GET    /api/v1/claims/{claimId}/payments       # List payments
GET    /api/v1/payments/{paymentId}/status     # Check payment status
POST   /api/v1/payments/{paymentId}/void       # Void payment

# Reserve
GET    /api/v1/claims/{claimId}/reserves       # Get reserve history
POST   /api/v1/claims/{claimId}/reserves       # Update reserve

# Fraud
GET    /api/v1/claims/{claimId}/fraud-score    # Get fraud assessment
POST   /api/v1/claims/{claimId}/fraud-referral # Refer to SIU

# Subrogation
GET    /api/v1/claims/{claimId}/subrogation    # Get subrogation status
POST   /api/v1/claims/{claimId}/subrogation    # Initiate subrogation

# Appeals
POST   /api/v1/claims/{claimId}/appeals        # File appeal
GET    /api/v1/claims/{claimId}/appeals        # List appeals
PUT    /api/v1/appeals/{appealId}             # Update appeal

# Analytics & Reporting
GET    /api/v1/reports/claims-summary          # Claims summary report
GET    /api/v1/reports/reserve-development     # Reserve development
GET    /api/v1/reports/sla-compliance          # SLA compliance report
GET    /api/v1/reports/fraud-metrics           # Fraud detection metrics

# Admin
GET    /api/v1/admin/adjusters                 # List adjusters
POST   /api/v1/admin/claims/{claimId}/assign  # Manual assignment
POST   /api/v1/admin/claims/{claimId}/escalate # Manual escalation
GET    /api/v1/admin/audit-trail/{claimId}     # Audit trail for claim

Third-Party Integration Patterns

SystemIntegration TypeProtocolData Flow
Medical Providers (EMR)FHIR R4 APIREST + OAuth 2.0Request medical records, submit bills
Repair ShopsREST API + WebhooksHTTPS + JWTSubmit estimates, track repair status
Rental CompaniesSOAP/XML (legacy)HTTPS + WS-SecurityAuthorize rental, track usage
Police DepartmentsState-specific portalsSFTP / APIDownload police reports
ISO ClaimSearchREST APIHTTPS + API KeyQuery claim history, fraud check
NICBSOAP APIHTTPS + CertificateSubmit/Query fraud referrals
ReinsurersBordereau uploadSFTP / APIReport large losses, treaty claims
State RegulatorsElectronic filingState portal / NAICStatutory reporting
Payment GatewaysREST APIHTTPS + OAuth 2.0EFT processing, check printing
Weather ServicesREST APIHTTPS + API KeyCatastrophe event data
C#
public class IntegrationHub
{
    private readonly HttpClient _httpClient;
    private readonly IResiliencePipeline _retryPipeline;
    private readonly ICircuitBreaker _circuitBreaker;
    private readonly IAuditLogger _auditLogger;

    public async Task<T> CallExternalSystemAsync<T>(
        string systemName,
        string endpoint,
        object requestBody,
        CancellationToken ct)
    {
        // Log the outbound call for audit
        await _auditLogger.LogExternalCallAsync(
            systemName, endpoint, requestBody);

        // Apply circuit breaker + retry
        return await _retryPipeline.ExecuteAsync(async token =>
        {
            if (_circuitBreaker.IsOpen(systemName))
                throw new CircuitBreakerOpenException(systemName);

            var response = await _httpClient.PostAsJsonAsync(
                endpoint, requestBody, token);

            if (response.StatusCode == HttpStatusCode.TooManyRequests)
            {
                var retryAfter = response.Headers
                    .RetryAfter?.Delta ?? TimeSpan.FromSeconds(30);
                await Task.Delay(retryAfter, token);
                throw new RetryableException("Rate limited");
            }

            response.EnsureSuccessStatusCode();
            return await response.Content
                .ReadFromJsonAsync<T>(token);
        }, ct);
    }
}
Integration Resilience: External system failures must never block claims processing. When a medical records provider is down, the claim continues processing with a "Waiting for Documents" sub-status. When the system comes back online, pending requests are automatically retried. The circuit breaker pattern prevents cascade failures — if a system returns 5 consecutive errors, all calls are short-circuited for 60 seconds to prevent resource exhaustion.

19. Customer Portal, Agent Portal & Notifications

Policyholders and agents need real-time visibility into claim status. The customer portal provides a self-service experience where policyholders can track their claim, upload documents, view payment status, and communicate with their adjuster. The agent portal provides a more detailed view with additional capabilities for managing multiple client claims, submitting FNOL, and accessing reports.

Portal Features

FeatureCustomer PortalAgent Portal
FNOL SubmissionWeb + MobileWeb (expedited)
Claim Status TrackingReal-time timelineDetailed status + SLA
Document UploadPhotos, receiptsAll document types
Adjuster CommunicationIn-app messagingDirect contact + notes
Payment TrackingStatus + historyDetailed + reconciliation
Reserve VisibilityEstimated vs paidFull reserve history
Appeal FilingGuided workflowSubmit on behalf
ReportingPersonal claim summaryPortfolio analytics
Multi-Claim ViewSingle claim onlyAll client claims

Notification System

C#
public class NotificationService
{
    private readonly IEmailService _email;
    private readonly ISmsService _sms;
    private readonly IPushService _push;
    private readonly IMailService _mail;
    private readonly INotificationPreferenceRepo _prefs;

    public async Task SendClaimNotificationAsync(
        Claim claim, NotificationType type,
        Dictionary<string, string> templateData)
    {
        var preferences = await _prefs
            .GetPreferencesAsync(claim.InsuredId);

        var notification = NotificationTemplates
            .GetTemplate(type, templateData);

        // Send via preferred channel(s)
        if (preferences.EmailEnabled)
        {
            await _email.SendAsync(
                claim.Insured.Email,
                notification.Subject,
                notification.EmailBody);
        }

        if (preferences.SmsEnabled && notification.IsUrgent)
        {
            await _sms.SendAsync(
                claim.Insured.Phone,
                notification.SmsBody);
        }

        if (preferences.PushEnabled)
        {
            await _push.SendAsync(
                claim.Insured.DeviceTokens,
                notification.PushTitle,
                notification.PushBody,
                new { claimId = claim.ClaimId });
        }

        // Regulatory-required mail notifications
        if (notification.RequiresPhysicalMail)
        {
            await _mail.SendAsync(
                claim.Insured.MailingAddress,
                notification.MailContent);
        }

        // Log notification
        await LogNotificationAsync(
            claim.ClaimId, type, preferences.PreferredChannel);
    }
}

Notification Events

EventChannelRecipient
Claim submitted confirmationEmail + PushPolicyholder
Adjuster assignedEmail + SMSPolicyholder
Document requestedEmail + PushPolicyholder
Document receivedEmailAdjuster
Coverage decision madeEmail + Push + MailPolicyholder
Payment issuedEmail + SMS + PushPolicyholder
SLA warning (internal)Email + SlackAdjuster + Supervisor
SIU referral (internal)Secure messageSIU Manager
Appeal filedEmail + PushPolicyholder
Claim closedEmail + Push + MailPolicyholder

20. Monitoring, Audit Trail & Analytics Dashboard

Comprehensive monitoring is essential for a claims system because delays directly translate to regulatory risk and customer dissatisfaction. The monitoring system must track claims processing velocity, SLA compliance, adjuster productivity, fraud detection effectiveness, payment accuracy, and financial reserves. Every action on a claim generates an immutable audit trail entry for regulatory compliance.

Key Metrics Dashboard

MetricTargetAlert ThresholdSeverity
FNOL processing time< 2 seconds> 5 seconds P99Warning
FNOL-to-adjuster-assignment< 4 hours> 8 hoursWarning
Initial contact by adjuster< 24 hours> 48 hoursCritical
SLA compliance rate> 98%< 95%Critical
Auto-adjudication rate> 60%< 50%Info
Average claim cycle time< 30 days> 45 daysWarning
Fraud detection rate> 5% flag rate< 2% or > 15%Warning
Payment processing errors< 0.1%> 0.5%Critical
Reserve accuracy (AE / PAE)< 10%> 20%Warning
Customer satisfaction (NPS)> 50< 30Warning
System availability99.99%< 99.95%Critical

Audit Trail Design

C#
public class AuditTrailService
{
    private readonly IAuditRepository _repo;
    private readonly IEventPublisher _events;

    public async Task LogActionAsync(AuditEntry entry)
    {
        entry.AuditId = Guid.NewGuid();
        entry.Timestamp = DateTime.UtcNow;
        entry.Hash = ComputeHash(entry); // Tamper detection

        // Write to immutable append-only store
        await _repo.AppendAsync(entry);

        // Also publish to Kafka for real-time monitoring
        await _events.PublishAsync(new AuditEvent
        {
            AuditId = entry.AuditId,
            ClaimId = entry.ClaimId,
            Action = entry.Action,
            Actor = entry.ActorId,
            Timestamp = entry.Timestamp
        });
    }

    private string ComputeHash(AuditEntry entry)
    {
        var data = $"{entry.ClaimId}|{entry.Action}|" +
            $"{entry.ActorId}|{entry.Timestamp:O}|" +
            $"{entry.Details}";
        using var sha256 = SHA256.Create();
        var bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(data));
        return Convert.ToBase64String(bytes);
    }
}
Immutable Audit Trail: The audit trail is append-only — no entry can be modified or deleted, even by administrators. Each entry includes a SHA-256 hash of its contents plus the previous entry's hash, forming a blockchain-like chain that makes tampering detectable. The audit store is backed up to cold storage daily and retained for the greater of 7 years or the state's regulatory requirement. In litigation, the audit trail serves as the authoritative record of every action taken on a claim.

Analytics Pipeline

graph LR A[Claim Events - Kafka] --> B[Spark Streaming] B --> C[Real-Time Dashboard] B --> D[Hourly Aggregations] D --> E[ClickHouse] E --> F[Analytics Dashboard] E --> G[Regulatory Reports] E --> H[ML Feature Store]

The analytics pipeline processes claim events in real-time using Spark Streaming, computes hourly aggregations (claim counts by status, adjuster productivity, SLA compliance rates, payment volumes), and stores results in ClickHouse for fast analytical queries. The dashboard provides four views: (1) Executive Summary — total claims, paid amount, reserve balance, NPS. (2) Operations — SLA compliance, adjuster workloads, processing velocity. (3) Financial — loss ratios, reserve development, payment trends. (4) Fraud — detection rates, SIU productivity, recovery rates.

21. Security, PII Protection & Testing Strategy

Insurance claims contain some of the most sensitive personal information: Social Security numbers, medical records, financial data, home addresses, and employment information. A data breach can expose millions of policyholders to identity theft and result in regulatory fines under HIPAA, state privacy laws (CCPA, SHIELD Act), and industry regulations (NAIC Model Laws). The security posture must be comprehensive, covering data encryption, access control, monitoring, and incident response.

Security Controls

ControlImplementationStandard
Encryption at restAES-256 for all data storesSOC 2, HIPAA
Encryption in transitTLS 1.3 for all connectionsPCI DSS, SOC 2
PII tokenizationSSN, DOB tokenized in non-productionHIPAA, CCPA
Role-based access controlRBAC with least-privilegeSOC 2
Multi-factor authenticationFIDO2/WebAuthn for all staffSOC 2
Data loss preventionDLP policies on all egress pointsHIPAA, CCPA
Audit loggingAll access to PII logged and monitoredHIPAA, SOC 2
Data retention enforcementAutomated purge after retention periodCCPA, GDPR
Vulnerability scanningWeekly automated + quarterly manualSOC 2
Penetration testingAnnual third-party assessmentSOC 2

Testing Strategy

The testing strategy for a claims system must cover unit tests for business rules, integration tests for external system interactions, end-to-end workflow tests, performance tests for catastrophe scaling, and compliance tests for regulatory requirements.

Test TypeCoverage TargetToolsExecution
Unit Tests90%+ for domain logicxUnit, FluentAssertionsEvery commit
Integration TestsAll external integrationsTestcontainers, WireMockEvery PR
Workflow TestsAll claim lifecycle pathsSpecFlow (BDD)Nightly
Performance TestsCatastrophe scaling scenariosk6, GatlingWeekly
Chaos TestsFailure mode coverageChaos Monkey, GremlinMonthly
Compliance TestsState-specific rulesCustom rule validatorsQuarterly
Security TestsOWASP Top 10SAST, DAST, dependency scanEvery build
C#
[Fact]
public async Task Adjudicate_SimpleAutoCollision_ApprovesAndCalculatesCorrectAmount()
{
    // Arrange
    var policy = CreatePolicy(
        coverageType: ClaimType.Auto,
        limit: 50000,
        deductible: 1000);
    var claim = CreateAutoClaim(
        damageAmount: 8000,
        liabilityPercentage: 100);
    var engine = CreateAdjudicationEngine();

    // Act
    var result = await engine.AdjudicateAsync(claim);

    // Assert
    Assert.Equal(ClaimDecision.Approved, result.Decision);
    Assert.Equal(7000, result.ApprovedAmount); // 8000 - 1000 deductible
    Assert.Equal(1000, result.PaymentBreakdown.Deductible);
    Assert.Equal(50000, result.PaymentBreakdown.CoverageLimit);
}

[Fact]
public async Task Adjudicate_ExceedsPolicyLimit_PartiallyApprovesAtLimit()
{
    var policy = CreatePolicy(limit: 50000, deductible: 2000);
    var claim = CreatePropertyClaim(damageAmount: 75000, liabilityPercentage: 100);
    var engine = CreateAdjudicationEngine();

    var result = await engine.AdjudicateAsync(claim);

    Assert.Equal(ClaimDecision.PartiallyApproved, result.Decision);
    Assert.Equal(48000, result.ApprovedAmount); // 75000 - 2000 deductible, capped at 50000
}

[Fact]
public async Task FraudDetection_SuspiciousPattern_ReturnsHighRisk()
{
    var claim = CreateClaimWithIndicators(
        newPolicy: true,
        attorneyAtFnol: true,
        inconsistentPhotos: true);
    var service = CreateFraudDetectionService();

    var score = await service.ScoreClaimAsync(claim);

    Assert.Equal("High", score.RiskLevel);
    Assert.Contains("Refer to SIU", score.RecommendedAction);
}
Test Data Management: All test environments use synthetic data — real PII is never used in testing. A data factory generates realistic but fake policyholder records, claims, and documents. Integration tests against external system mocks (WireMock) simulate all response scenarios including error cases, timeouts, and partial failures. Load tests use realistic claim volume patterns derived from production data (with all PII stripped).

PII Data Handling

C#
public class PiiProtectionService
{
    private readonly ITokenizationService _tokenizer;
    private readonly IPiiAccessLogger _accessLogger;

    public ClaimResponse SanitizeForApi(Claim claim, string callerRole)
    {
        var response = MapToResponse(claim);

        // Never expose SSN in API responses
        response.Ssn = null;

        // Mask sensitive fields based on caller role
        if (callerRole != "Adjuster" && callerRole != "SIU")
        {
            response.DateOfBirth = MaskDate(response.DateOfBirth);
            response.MedicalInfo = null;
        }

        // Log every PII access
        _accessLogger.LogAccess(new PiiAccessLog
        {
            ClaimId = claim.ClaimId,
            FieldsAccessed = GetAccessedPiiFields(callerRole),
            CallerRole = callerRole,
            Timestamp = DateTime.UtcNow
        });

        return response;
    }

    private string MaskDate(DateTime? date)
    {
        if (!date.HasValue) return null;
        return $"{date.Value.Year}-XX-XX"; // Year only
    }
}

Disaster Recovery & Business Continuity

The claims system operates in an active-active multi-region configuration. In a region failure, traffic is routed to the surviving region within 60 seconds via DNS failover. The database uses synchronous replication with RPO near zero. The system can sustain a complete region outage with zero claim data loss and minimal processing disruption. During catastrophic events, the system automatically enables a "catastrophe mode" that prioritizes FNOL intake over detailed investigation, accepting abbreviated claims and deferring document collection to later in the process. This ensures the system can absorb a surge of 100,000+ claims without blocking policyholders from reporting their losses.

22. Interview Q&A Deep Dive

Q1: How do you handle a catastrophe event that generates 100K claims in a week?

Answer: The system uses a "catastrophe lane" architecture. When the catastrophe detection service identifies a weather event (Hurricane, Tornado, Wildfire), it triggers the catastrophe mode. In this mode: (1) FNOL intake is prioritized — the simplified intake form collects minimal information (name, policy number, loss description, one photo) to maximize throughput. (2) All FNOL claims are batched to the catastrophe-specific adjuster pool, which is pre-expanded using on-demand staffing. (3) AI damage assessment runs on all uploaded photos immediately to prioritize severe claims. (4) Payment processing is pre-authorized for claims under $10,000 based on AI assessment, enabling rapid interim payments. (5) Non-catastrophe claims are processed normally in a separate lane with dedicated resources. The key insight is accepting imperfect data at FNOL (incomplete documents, estimated damage) to get money flowing quickly, then filling in the details as the catastrophe response stabilizes.

Q2: How do you ensure payment accuracy and prevent duplicate payments?

Answer: Multiple layers prevent duplicates: (1) Idempotency keys — every payment request includes a unique idempotency key (claim_id + payment_type + amount + date). The payment service checks for existing payments with the same key before processing. (2) Database constraints — the payment table has a unique constraint on (claim_id, idempotency_key). (3) Distributed locking — a Redis lock on the claim_id prevents concurrent payment processing for the same claim. (4) Reconciliation — a nightly job compares payment records against bank statements to catch any discrepancies. (5) Double-approval — payments over $10,000 require two authorized signers. The combination of these layers makes duplicate payments virtually impossible.

Q3: How do you handle state-specific regulatory requirements without duplicating logic?

Answer: A rules engine with state-specific rule sets. Each state's requirements are encoded as configuration (JSON/YAML), not code. The rule engine evaluates the claim against the applicable state's rules at each processing step. For example, California's requirement to acknowledge a claim within 15 days is a rule: { "state": "CA", "action": "acknowledge", "maxDays": 15 }. When a new state's requirements change, we update the configuration, not the code. The rule engine supports rule versioning (so historical claims use the rules that were in effect at the time of loss), rule inheritance (base rules with state-specific overrides), and rule auditing (every rule evaluation is logged with the inputs and result). This approach handles 50+ states with minimal code duplication.

Q4: How do you design the system for auditability and regulatory compliance?

Answer: Every state transition, every decision, every document access, and every external communication is logged to an immutable, append-only audit store. The audit entries are hash-chained (each entry includes a hash of the previous entry) to detect tampering. The audit store is separate from the operational database — even a database administrator cannot modify audit records. Audit entries include: claim_id, action, actor_id, actor_role, timestamp, old_value, new_value, ip_address, and a hash. The system retains audit data for the greater of 7 years or the state's regulatory requirement. For HIPAA-covered medical documents, access logs are retained for 10 years. The audit trail is queryable for regulatory examinations — "Show me all actions taken on claim X between dates Y and Z" returns results in seconds via Elasticsearch indexing.

Q5: How do you handle the integration with dozens of external systems with varying reliability?

Answer: Each external integration has its own resilience strategy. The integration hub applies circuit breakers, retries with exponential backoff, and timeout configurations per external system. When a system is down, the claim enters a "waiting" sub-status and the integration hub queues the request for retry. A background job processes the retry queue with increasing intervals (1min, 5min, 15min, 1hr). After 24 hours of failed retries, the system notifies the operations team. For critical integrations (payment gateways), we maintain hot-standby connections to backup providers. For non-critical integrations (repair shop status updates), we degrade gracefully — the claim continues processing, and the missing data is obtained later. All external calls are logged with request/response payloads for debugging.

Q6: How do you balance AI automation with human oversight?

Answer: The system uses a confidence-tiered approach. For claims where AI confidence exceeds 0.95 (simple, clear-cut cases), the system can auto-adjudicate with post-hoc human audit (10% sampling). For claims with confidence 0.85-0.95, the AI provides a recommendation that the adjuster reviews and can accept or override. For claims below 0.85, the AI provides supplementary data points but the adjuster makes the full decision. The system tracks the ratio of AI recommendations vs human overrides to continuously calibrate the confidence thresholds. If override rates rise, the threshold is raised. If AI accuracy improves, the threshold is lowered. This feedback loop ensures the system continuously improves while maintaining human oversight for complex or ambiguous claims.

Q7: How do you design the data model for multi-line insurance (auto + property + liability)?

Answer: The data model uses a polymorphic claim structure with a shared core (claim metadata, activities, documents, payments) and line-specific extensions (auto: vehicle info, collision details; property: building info, contents inventory; liability: plaintiff info, legal proceedings). The claim table stores common fields, and separate tables store line-specific data linked by claim_id. The adjudication engine loads the appropriate line-specific rules and processing logic based on the claim type. This avoids separate systems for each line while keeping line-specific logic isolated. A single policy can have multiple lines, and a single loss event can generate claims across multiple lines (e.g., a fire damages the building property and the vehicles inside — property claim + auto claim from one event).

Q8: How do you handle claim reopening after closure?

Answer: Claims can be reopened within the state's statutory period (typically 2-5 years from date of loss, depending on the state and line of business). When a claim is reopened, the system creates a new activity record linked to the original claim, re-evaluates coverage (policy must still be active or in the "extended reporting" period), and re-opens the reserve. The adjuster assignment is typically the same as the original claim (for institutional knowledge). All original documents, notes, and activities remain intact — the reopening is an addition, not a modification. The system tracks reopening rates by claim type and adjuster to identify systemic issues with initial claim handling.

Q9: How do you handle subrogation when multiple parties share liability?

Answer: The liability service calculates each party's percentage of responsibility. Subrogation is pursued against each third party proportional to their liability share. For example, if Party A is 60% liable and Party B is 40% liable, subrogation demands are issued to both parties' insurers proportionally. The system tracks each subrogation pursuit independently — one may settle while the other is still in negotiation. If the insured's state follows "comparative negligence" rules, subrogation is reduced by the insured's own liability percentage. If the state follows "contributory negligence" rules, any insured fault eliminates subrogation. The system encodes these state-specific rules in the rules engine.

Q10: How do you handle the appeals process when it involves regulatory bodies?

Answer: External regulatory appeals (Department of Insurance complaints) are tracked separately from internal appeals. When a regulatory complaint is received, the system: (1) Automatically freezes the claim from further processing until the regulatory review is complete. (2) Generates a comprehensive claim file (all documents, activities, decisions, and timelines) formatted for regulatory submission. (3) Assigns to a regulatory compliance specialist. (4) Tracks the regulatory deadline (typically 30-60 days for initial response). (5) Logs all communications with the regulatory body in the audit trail. (6) If the regulator rules against the insurer, the system automatically adjusts the claim, issues any required payments with penalty interest, and logs the outcome for actuarial analysis.

Pre-Interview Checklist

  • Understand the full claims lifecycle from FNOL to close
  • Know state-specific regulatory requirements and how to encode them as rules
  • Design a scalable FNOL intake that handles multiple channels and catastrophe surges
  • Understand coverage verification and the bad faith risk of incorrect denials
  • Know fraud detection techniques (rules, ML, network analysis, external databases)
  • Discuss payment processing accuracy and duplicate prevention
  • Understand subrogation, salvage, and recovery workflows
  • Know the appeals process and regulatory deadlines
  • Discuss audit trail design for regulatory compliance
  • Understand AI-human balance in claims adjudication
  • Know PII protection requirements (HIPAA, CCPA)
  • Discuss catastrophe scaling and business continuity

Key Numbers to Remember

MetricValue
US insurance fraud cost$80B+ per year
Average claim cycle time (industry)30+ days
Auto-adjudication target60-70% of claims
FNOL processing target< 2 seconds P99
Initial contact SLA24 hours (most states)
Payment processing (EFT)1-2 business days
Data retention minimum7 years
Catastrophe surge capacity50x normal daily volume
System availability target99.99% (52 min/year downtime)
Subrogation recovery rate15-25% of total claims paid
Monthly infrastructure cost (mid-size)~$45,000-60,000

Insurance Claims Processing System — Senior+ Guide | Ayodhyya