Full Stack • Java • System Design • Cloud • AI Engineering

Insurance Claims System Design

Design a scalable enterprise Insurance Claims System — covering first notice of loss, multi-stage adjudication workflow, document management, fraud detection integration, reserve management, settlement and payment, regulatory compliance, and end-to-end audit trail.

1-Hour Interview Roadmap

Time Topic
0 – 5 min Requirements clarification
5 – 10 min Capacity estimation
10 – 18 min High-level architecture + core services
18 – 28 min Claim lifecycle — FNOL through settlement
28 – 36 min Adjudication workflow engine + business rules
36 – 43 min Document management + evidence handling
43 – 50 min Fraud detection integration + reserve management
50 – 56 min Database design + API design
56 – 60 min Trade-offs + common interview mistakes

What Are We Building?

An enterprise Insurance Claims System (ICS) that manages the complete lifecycle of an insurance claim — from the moment a policyholder reports an incident (First Notice of Loss) through investigation, adjudication, reserve setting, settlement approval, and payment disbursement.

An insurance claim is not a simple transaction. It is a multi-week, multi-party workflow involving policyholders, claims adjusters, medical/auto/property assessors, legal reviewers, fraud investigators, and payment approvers — all operating under strict regulatory deadlines and compliance obligations.

Scale reference: State Farm processes ~30,000 claims per day. Allstate processes ~15,000 claims per day. A mid-tier insurer processes 2,000–10,000 claims per day. Design for 10,000 claims per day across property, auto, and health lines of business.

Key unique challenges:

  • Long-running workflows — a claim can take days to months to resolve; state must be durable and recoverable after any failure
  • Multi-party coordination — adjusters, assessors, medical reviewers, and legal teams each own different stages; handoffs must be tracked
  • Regulatory compliance — every state/jurisdiction has mandatory acknowledgment and settlement deadlines (e.g., 15 days to acknowledge, 30 days to accept/deny); breaches incur fines
  • Fraud detection — 10–15% of claims have fraud indicators; detection must not delay legitimate claims while flagging suspicious ones
  • Immutable audit trail — every action, decision, and communication on a claim must be permanently recorded for regulatory examination and litigation

Step 1 — Requirements

Functional Requirements

# Requirement
1 Policyholder can file a First Notice of Loss (FNOL) via web, mobile, phone, or agent portal
2 System creates a claim record and assigns a unique claim number immediately
3 System routes the claim to the appropriate adjuster queue based on line of business, severity, and geography
4 Adjuster can review claim details, request additional documents, and update claim status
5 System supports multi-stage adjudication: intake → investigation → evaluation → decision → settlement
6 Policyholder and assessors can upload supporting documents (photos, repair estimates, medical bills)
7 System sets and tracks financial reserves (estimated claim cost) per claim
8 System integrates with fraud detection service; flags suspicious claims for investigation
9 Approved claims trigger payment disbursement to policyholder or third-party
10 System enforces regulatory deadlines and alerts adjusters when SLA windows are approaching
11 Supervisor can review and override adjuster decisions on high-value or disputed claims
12 Full audit trail of every action, decision, and communication on every claim

Non-Functional Requirements

# Requirement
1 FNOL submission must respond within 3 seconds (p99) — policyholder is in distress
2 Zero claim loss — no claim can be silently dropped; all failures must be retried or surfaced
3 Full immutable audit trail — every state change, decision, and document upload permanently logged
4 Regulatory deadline enforcement — system must alert and escalate when deadlines are at risk
5 High availability — 99.9% uptime for FNOL and claim status APIs
6 Document storage — support files up to 100MB per document; retain for 7 years minimum
7 HIPAA compliance for health insurance claims (PII/PHI encryption at rest and in transit)
8 Role-based access control — adjusters see only their assigned claims; supervisors see their team

Out of Scope

  • Policy underwriting and premium calculation
  • Policy issuance and renewal management
  • Reinsurance treaty management
  • Agent commission management
  • Customer self-service portal UI (assume API consumers build UI)

Step 2 — Capacity Estimation

Traffic Estimates

Daily claims filed (FNOL):      10,000
Average rate:                   10,000 / 86,400 = ~0.12 FNOL/sec
Peak rate (disaster event):     50× normal = ~6 FNOL/sec (CAT event — hurricane, earthquake)

Workflow actions per claim:     ~30 actions over claim lifetime (uploads, decisions, comms)
Daily workflow actions:         10,000 × 30 = 300,000 actions/day = ~3.5 actions/sec

Document uploads per claim:     ~8 documents avg (photos, estimates, records)
Daily uploads:                  10,000 × 8 = 80,000 uploads/day
Avg document size:              5 MB
Daily document storage:         80,000 × 5 MB = ~400 GB/day

Claim status reads:
  Policyholder checks × 5 per day per active claim
  Active claims at any time:    ~150,000 (avg 15-day lifecycle)
  Daily reads:                  150,000 × 5 = 750,000 reads/day = ~9 QPS avg

Storage Estimates

Per claim record:
  Claim header:               ~1 KB
  Workflow events (30 avg):   ~200 bytes each = 6 KB
  Reserve history:            ~500 bytes × 5 entries = 2.5 KB
  Total per claim (metadata): ~10 KB

Annual metadata storage:
  10,000 claims/day × 10 KB × 365 = ~36.5 GB/year  (trivial)

Document storage:
  400 GB/day × 365 = ~146 TB/year  (requires object storage — S3 / Azure Blob)
  7-year retention: 146 TB × 7 = ~1 PB total

Payment records:
  10,000 settlements/day × 500 bytes = ~5 MB/day

Key Insights

  • Metadata is tiny — 36.5 GB/year; fits in a well-indexed relational DB
  • Documents dominate storage — 1 PB over 7 years; requires cloud object storage with lifecycle policies
  • Normal load is modest — 3.5 actions/sec is low; CAT events create 50× spikes; queue-based architecture absorbs bursts
  • Long claim lifetimes — 150,000 claims active at any time; workflow state must be durable, not in-memory
  • Compliance is a hard requirement — 7-year document retention, deadline tracking, and audit log are not optional

Step 3 — High-Level Architecture

flowchart TD
    Channels["FNOL Channels\nWeb / Mobile / Phone / Agent"]
    AG["API Gateway\nAuth + Rate Limiting + TLS"]
    CS["Claims Service\n(Orchestrator + state machine)"]
    WE["Workflow Engine\n(Task routing + deadline tracking)"]
    DS["Document Service\n(Upload + retrieval + virus scan)"]
    FS["Fraud Service\n(Scoring + investigation queue)"]
    RS["Reserve Service\n(Financial reserve management)"]
    PS["Payment Service\n(Settlement disbursement)"]
    NS["Notification Service\n(Policyholder + adjuster alerts)"]
    AuditS["Audit Service\n(Append-only event log)"]

    ClaimDB[("PostgreSQL\nClaims DB")]
    DocStore[("S3 / Object Storage\nDocument Store")]
    KF["Kafka\nClaim Events Bus"]
    REDIS["Redis\nDeadline Cache + Session"]

    Channels --> AG
    AG --> CS
    CS --> WE
    CS --> DS
    CS --> RS
    CS --> ClaimDB
    CS --> KF
    CS --> REDIS

    KF --> FS
    KF --> NS
    KF --> AuditS
    KF --> PS

    DS --> DocStore
    FS --> ClaimDB
    RS --> ClaimDB
    PS --> ClaimDB
    AuditS --> ClaimDB

Component Responsibilities

Component Responsibility
Claims Service Central orchestrator — creates claims, drives state machine, coordinates all downstream services
Workflow Engine Routes tasks to adjuster queues; tracks deadlines; escalates approaching SLA breaches
Document Service Handles file uploads, virus scanning, metadata extraction, and secure retrieval with pre-signed URLs
Fraud Service Scores claims for fraud indicators; maintains investigation queue for flagged claims
Reserve Service Sets and adjusts financial reserves; tracks reserve history; reports to finance
Payment Service Processes approved settlement payments; integrates with ACH/wire/check payment rails
Notification Service Alerts policyholders and adjusters via email, SMS, and portal messages
Audit Service Subscribes to all claim events; writes append-only audit records; never updates or deletes
Claims DB PostgreSQL — all claim metadata, workflow state, reserves, payments (source of truth)
Document Store S3 / Azure Blob — all uploaded files; 7-year retention; WORM (write-once, read-many) for compliance
Kafka Event bus — decouples Claims Service from Fraud, Notifications, Audit, and Payments
Redis Deadline window cache; adjuster session state; claim lock (prevent concurrent edits)

Step 4 — Claim Lifecycle: FNOL to Settlement

The complete end-to-end journey of a claim from first report through payment.

sequenceDiagram
    participant P as Policyholder
    participant AG as API Gateway
    participant CS as Claims Service
    participant WE as Workflow Engine
    participant FS as Fraud Service
    participant KF as Kafka
    participant DB as Claims DB

    P->>AG: POST /v1/claims/fnol {policy_number, incident_date, type, description}
    AG->>CS: Forward authenticated request

    CS->>DB: Validate policy is active + in-force
    CS->>DB: INSERT claim {status: FNOL_RECEIVED, claim_number: auto}
    CS->>DB: INSERT claim_event {FNOL_SUBMITTED, actor: policyholder}
    CS->>KF: Publish claim.fnol_received {claim_id, type, severity_hint}

    CS->>WE: CreateWorkflow(claim_id, line_of_business, severity)
    WE->>DB: INSERT workflow_tasks {ACKNOWLEDGE_CLAIM, due: NOW() + 15 days}
    WE->>DB: INSERT workflow_tasks {ASSIGN_ADJUSTER, due: NOW() + 2 days}

    CS-->>P: 201 {claim_number, status: "received", next_steps}

    Note over KF,FS: Async — fraud scoring
    KF->>FS: Consume claim.fnol_received
    FS->>FS: Score claim for fraud indicators
    FS->>CS: POST /internal/claims/{id}/fraud-score {score: 0.12, flags: []}

    Note over WE: Deadline monitoring runs continuously
    WE->>DB: Alert adjuster if ACKNOWLEDGE task approaches due date

Step 5 — Claim State Machine

A claim moves through a precisely defined set of states. The system enforces valid transitions — an adjuster cannot approve a claim that is under fraud investigation, and a closed claim cannot be reopened without supervisor authorization.

stateDiagram-v2
    [*] --> FNOL_RECEIVED : Policyholder submits FNOL

    FNOL_RECEIVED --> ACKNOWLEDGED : Claims team acknowledges receipt\n(regulatory deadline: 15 days)
    FNOL_RECEIVED --> WITHDRAWN : Policyholder withdraws before acknowledgment

    ACKNOWLEDGED --> UNDER_INVESTIGATION : Adjuster assigned and begins investigation
    ACKNOWLEDGED --> DENIED : Clear exclusion — denied without investigation\n(e.g., policy lapsed)

    UNDER_INVESTIGATION --> FRAUD_REVIEW : Fraud score exceeds threshold\nor adjuster flags manually
    UNDER_INVESTIGATION --> PENDING_DOCUMENTS : Additional documents requested
    UNDER_INVESTIGATION --> EVALUATION : Investigation complete; moving to evaluation

    FRAUD_REVIEW --> UNDER_INVESTIGATION : Fraud cleared — return to normal flow
    FRAUD_REVIEW --> DENIED : Fraud confirmed — claim denied

    PENDING_DOCUMENTS --> UNDER_INVESTIGATION : Documents received
    PENDING_DOCUMENTS --> CLOSED_INCOMPLETE : Documents not received by deadline

    EVALUATION --> APPROVED : Adjuster approves claim and settlement amount
    EVALUATION --> DENIED : Adjuster denies claim (exclusion, no coverage, fraud)
    EVALUATION --> SUPERVISOR_REVIEW : Claim above adjuster authority limit\nor policyholder disputes decision

    SUPERVISOR_REVIEW --> APPROVED : Supervisor approves
    SUPERVISOR_REVIEW --> DENIED : Supervisor upholds denial

    APPROVED --> PAYMENT_PROCESSING : Settlement payment initiated
    PAYMENT_PROCESSING --> SETTLED : Payment confirmed delivered
    PAYMENT_PROCESSING --> PAYMENT_FAILED : Payment failed — retry or alternate method

    PAYMENT_FAILED --> PAYMENT_PROCESSING : Retry with corrected bank details
    SETTLED --> REOPENED : Supplemental claim or dispute within policy window
    REOPENED --> UNDER_INVESTIGATION : Supplemental investigation begins

    DENIED --> APPEALED : Policyholder files appeal within appeal window
    APPEALED --> SUPERVISOR_REVIEW : Appeal routed to senior adjuster

    SETTLED --> CLOSED : Claim fully resolved; final closure
    WITHDRAWN --> [*]
    CLOSED --> [*]

Valid Transition Enforcement

-- Application-level transition guard
VALID_TRANSITIONS = {
  'FNOL_RECEIVED':       ['ACKNOWLEDGED', 'WITHDRAWN'],
  'ACKNOWLEDGED':        ['UNDER_INVESTIGATION', 'DENIED'],
  'UNDER_INVESTIGATION': ['FRAUD_REVIEW', 'PENDING_DOCUMENTS', 'EVALUATION'],
  'FRAUD_REVIEW':        ['UNDER_INVESTIGATION', 'DENIED'],
  'PENDING_DOCUMENTS':   ['UNDER_INVESTIGATION', 'CLOSED_INCOMPLETE'],
  'EVALUATION':          ['APPROVED', 'DENIED', 'SUPERVISOR_REVIEW'],
  'SUPERVISOR_REVIEW':   ['APPROVED', 'DENIED'],
  'APPROVED':            ['PAYMENT_PROCESSING'],
  'PAYMENT_PROCESSING':  ['SETTLED', 'PAYMENT_FAILED'],
  'PAYMENT_FAILED':      ['PAYMENT_PROCESSING'],
  'SETTLED':             ['REOPENED', 'CLOSED'],
  'DENIED':              ['APPEALED'],
  'APPEALED':            ['SUPERVISOR_REVIEW'],
  'REOPENED':            ['UNDER_INVESTIGATION'],
}
-- Any other transition → raise InvalidClaimTransition exception

Step 6 — Workflow Engine and Task Management

The Workflow Engine is the scheduling brain of the claims system. Every claim generates a set of tasks with deadlines. The engine routes tasks to the right queues, monitors deadlines, and escalates when SLAs are at risk.

Task Types and Deadlines

Task Type Assigned To Regulatory Deadline Internal SLA Target
Acknowledge FNOL Claims team 15 days (most states) 2 business days
Assign adjuster Claims manager Internal only 1 business day
Contact policyholder Assigned adjuster Internal only 2 business days
Request additional docs Assigned adjuster Internal only Within 15 days
Complete field inspection Field assessor Internal only 7–14 business days
Complete medical review Medical reviewer Internal only 14 business days
Make coverage decision Adjuster 30–45 days (most states) 21 business days
Issue payment or denial Claims team 5 days after decision 2 business days
Respond to appeal Senior adjuster 30 days (most states) 21 business days

Deadline Tracking Architecture

flowchart LR
    Task["Workflow Task Created\n{task_type, claim_id, due_date}"]

    Task --> DB["Stored in workflow_tasks\n(PostgreSQL)"]
    Task --> REDIS["Deadline indexed in Redis\nSorted Set: ZADD deadlines {due_timestamp} {task_id}"]

    DeadlineMonitor["Deadline Monitor\n(runs every 5 minutes)"]
    DeadlineMonitor --> REDIS
    REDIS -->|"ZRANGEBYSCORE deadlines NOW() NOW()+24h"| Approaching["Tasks due in < 24 hours"]

    Approaching --> AlertAdjuster["Alert assigned adjuster\n(email + portal notification)"]
    Approaching --> CheckBreached["Tasks due NOW() or past due"]
    CheckBreached --> Escalate["Escalate to supervisor\n+ flag as SLA_AT_RISK"]
    CheckBreached --> RegAlert["Regulatory deadline breach?\n→ Compliance team alert\n+ incident record"]

Task Queue Design

Each line of business has separate adjuster queues. Routing considers adjuster workload, specialization, and geography:

flowchart TB
    Claim["New Claim\nType: AUTO / PROPERTY / HEALTH\nSeverity: LOW / MEDIUM / HIGH / CAT"]

    Router["Routing Engine"]

    subgraph Auto["Auto Claims Queues"]
        AutoLow["Auto — Low (<$5K)"]
        AutoMed["Auto — Medium ($5K–$25K)"]
        AutoHigh["Auto — High (>$25K)"]
    end

    subgraph Property["Property Claims Queues"]
        PropLow["Property — Low (<$10K)"]
        PropMed["Property — Medium"]
        PropHigh["Property — High (>$100K)"]
    end

    subgraph Health["Health Claims Queues"]
        HealthStd["Health — Standard"]
        HealthComplex["Health — Complex\n(surgery, chronic)"]
    end

    CAT["CAT Queue\n(Disaster events — all hands)"]

    Claim --> Router
    Router --> Auto
    Router --> Property
    Router --> Health
    Router --> CAT

Routing rules:

  • Severity determines queue; severity derived from loss description + estimated damage amount
  • CAT events (hurricane, tornado, earthquake) create a separate high-priority queue
  • Adjusters are pulled from their primary queue; supervisor can manually reassign
  • Max queue depth per adjuster: 40 open claims (configurable per jurisdiction)

Step 7 — Document Management

Claims generate a large volume of documents — incident photos, repair estimates, medical records, police reports, contractor invoices. The Document Service handles upload, virus scanning, metadata indexing, and secure retrieval.

Upload Flow

sequenceDiagram
    participant P as Policyholder / Adjuster
    participant CS as Claims Service
    participant DS as Document Service
    participant S3 as Object Storage (S3)
    participant VS as Virus Scanner

    P->>CS: Request upload URL for claim {claim_id, document_type, filename}
    CS->>DS: GenerateUploadURL(claim_id, document_type, filename, uploader_id)
    DS->>S3: Generate pre-signed PUT URL (expires in 15 minutes)
    DS-->>CS: {upload_url, document_id}
    CS-->>P: {upload_url, document_id}

    P->>S3: PUT file directly to S3 (bypasses backend — no size limit bottleneck)
    S3->>DS: S3 Event Notification: object created

    DS->>S3: Download file for scanning
    DS->>VS: ScanFile(file_bytes)
    VS-->>DS: {clean: true}  or  {infected: true, threat: "Trojan.XYZ"}

    alt File is clean
        DS->>DS: Extract metadata (EXIF, page count, file hash)
        DS->>ClaimDB: INSERT document {claim_id, document_id, status: AVAILABLE, s3_key, metadata}
        DS->>KF: Publish claim.document_uploaded {claim_id, document_id, type}
    else File is infected
        DS->>S3: Delete infected file
        DS->>ClaimDB: INSERT document {status: REJECTED_VIRUS}
        DS->>KF: Publish claim.document_rejected {claim_id, document_id, reason: VIRUS}
    end

Document Types and Retention

Document Type Examples Retention Access
Incident evidence Accident photos, property damage photos 7 years Adjuster, policyholder
Third-party estimates Auto repair estimate, contractor bid 7 years Adjuster, policyholder
Medical records Hospital bills, treatment summaries, Rx 10 years Medical reviewer, adjuster (need-to-know)
Legal documents Demand letters, court filings 10 years Legal team, senior adjuster
Internal assessments Adjuster notes, inspection reports 7 years Adjuster, supervisor
Payment records Settlement agreements, check copies 10 years Finance, compliance
Correspondence All emails and letters with policyholder 7 years Adjuster, customer service

Document Security

Storage:     S3 with server-side encryption (AES-256)
Access:      All retrieval via pre-signed URLs (15-min expiry)
             No direct S3 bucket access from client
PHI/PII:     Medical documents stored in separate S3 bucket with stricter IAM policy
             Access logged to CloudTrail for HIPAA compliance
WORM:        S3 Object Lock in COMPLIANCE mode — documents cannot be deleted by anyone
             including root account — for regulatory immutability requirement
Audit:       Every document access (view, download) logged to audit table

Step 8 — Fraud Detection Integration

Insurance fraud accounts for 10–15% of all claims, costing the industry $40+ billion annually. The fraud detection system runs asynchronously so it does not delay legitimate claim processing.

Fraud Scoring Flow

sequenceDiagram
    participant CS as Claims Service
    participant KF as Kafka
    participant FS as Fraud Service
    participant ML as ML Scoring Engine
    participant RulesDB as Rules Engine DB
    participant DB as Claims DB

    CS->>KF: Publish claim.fnol_received {claim_id, policy_data, incident_data}

    KF->>FS: Consume event
    FS->>ML: ScoreClaim(claim_features)
    ML-->>FS: {fraud_score: 0.82, contributing_factors: ["duplicate_incident_location", "policy_recently_increased", "third_claim_in_6_months"]}

    FS->>RulesDB: ApplyRules(claim_data, fraud_score)
    RulesDB-->>FS: {rule_hits: ["RULE_035: Multiple claims same address 90d", "RULE_078: Policy limit increase <30d before claim"]}

    alt Score < 0.3 (low risk)
        FS->>DB: UPDATE claim SET fraud_score = 0.12, fraud_status = 'CLEARED'
        FS->>KF: Publish claim.fraud_cleared {claim_id}
    else Score 0.3–0.7 (medium risk)
        FS->>DB: UPDATE claim SET fraud_score = 0.55, fraud_status = 'ELEVATED'
        FS->>DB: INSERT fraud_flag {notes: "Elevated score — monitor"}
        Note over FS: Claim continues normally but adjuster sees fraud flags
    else Score > 0.7 (high risk)
        FS->>DB: UPDATE claim SET fraud_score = 0.82, fraud_status = 'UNDER_REVIEW'
        FS->>CS: TransitionClaim(claim_id, FRAUD_REVIEW)
        FS->>KF: Publish claim.fraud_flagged {claim_id, score, factors}
    end

Fraud Indicators (Feature Inputs)

Category Indicators
Policy history Policy purchased < 30 days before claim, recent coverage increase, multiple policies same address
Claim history 3+ claims in 12 months, same incident type repeated, prior fraud flag on policyholder
Incident patterns Incident location matches prior claims, incident time/day is statistically anomalous
Third-party signals Repair shop or medical provider flagged for prior fraud, attorney involved from first FNOL
Behavioral signals Claim submitted immediately after accident (no delay), no police report for large theft claim
Network analysis Policyholder connected to known fraud ring (shared addresses, phone numbers, associates)

Fraud Investigation Queue

Flagged claims enter a special investigation queue visible only to the Special Investigations Unit (SIU):

SIU Queue:      Sorted by fraud_score DESC
SLA:            SIU must make decision within 30 days of flag
Outcomes:
  CLEARED       → Claim returns to UNDER_INVESTIGATION state; adjuster notified
  CONFIRMED     → Claim transitioned to DENIED; legal referral if criminal
  INCONCLUSIVE  → Claim continues with elevated monitoring; adjuster flagged

Step 9 — Reserve Management

A reserve is the financial estimate of what a claim will ultimately cost the insurer. Reserves are set when a claim is opened and revised as investigation proceeds. Accurate reserves are critical for financial reporting — insurers are legally required to maintain adequate reserves to pay claims.

Reserve Lifecycle

stateDiagram-v2
    [*] --> INITIAL_RESERVE : Set automatically at FNOL\nbased on coverage type + initial estimate

    INITIAL_RESERVE --> REVISED : Adjuster updates based on\ninspection or medical review

    REVISED --> REVISED : Multiple revisions allowed\nas investigation progresses

    REVISED --> FINAL_RESERVE : Adjuster sets final amount\nbefore approval

    FINAL_RESERVE --> SETTLED_RESERVE : Payment issued;\nreserve closed at actual payment amount

    SETTLED_RESERVE --> [*]

Reserve Setting Rules

At FNOL:
  Initial reserve = Coverage limit × expected_severity_factor
  Auto collision:  avg_repair_cost_by_vehicle_type
  Property:        sq_footage × damage_category_factor
  Health:          procedure_code_based estimate from medical coding table

Adjuster revision:
  Any adjuster can revise reserve up to their authority limit
  Revisions above authority → supervisor approval required

Authority limits:
  Junior adjuster:      Reserve up to $25,000
  Senior adjuster:      Reserve up to $100,000
  Claims supervisor:    Reserve up to $500,000
  VP Claims:            Reserve up to $2,000,000
  CFO approval:         Reserve > $2,000,000

Reserve history is never overwritten:
  Every revision is a new record with timestamp, actor, reason
  Finance reports use the current (latest) reserve; history is audit evidence

Reserve Schema

CREATE TABLE claim_reserves (
    reserve_id       UUID          NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
    claim_id         UUID          NOT NULL REFERENCES claims(claim_id),
    reserve_type     VARCHAR(20)   NOT NULL,   -- 'INDEMNITY' | 'EXPENSE' | 'MEDICAL'
    amount           BIGINT        NOT NULL,   -- in cents
    set_by           VARCHAR(100)  NOT NULL,   -- user_id or 'SYSTEM'
    reason           TEXT          NOT NULL,
    is_current       BOOLEAN       NOT NULL DEFAULT TRUE,
    superseded_at    TIMESTAMPTZ,              -- set when a newer reserve is created
    created_at       TIMESTAMPTZ   NOT NULL DEFAULT NOW()
);

-- Enforce only one current reserve per claim + type
CREATE UNIQUE INDEX idx_reserve_current
    ON claim_reserves(claim_id, reserve_type)
    WHERE is_current = TRUE;

Step 10 — Settlement and Payment

When a claim is approved, the settlement amount is finalized and payment is disbursed to the policyholder or a designated third party (repair shop, medical provider, attorney).

Settlement Approval Flow

sequenceDiagram
    participant Adj as Adjuster
    participant CS as Claims Service
    participant DB as Claims DB
    participant PS as Payment Service
    participant KF as Kafka
    participant NS as Notification Service

    Adj->>CS: POST /v1/claims/{id}/settle\n{settlement_amount, payee, payment_method, notes}

    CS->>CS: Validate: settlement_amount <= approved_reserve
    CS->>CS: Validate: adjuster authority >= settlement_amount

    CS->>DB: BEGIN TRANSACTION
    CS->>DB: INSERT settlement_record {amount, payee, method, approved_by}
    CS->>DB: UPDATE claim SET status = APPROVED, settlement_amount = :amount
    CS->>DB: INSERT claim_event {SETTLEMENT_APPROVED, actor: adjuster_id, amount}
    CS->>DB: COMMIT

    CS->>KF: Publish claim.settlement_approved {claim_id, amount, payee, method}

    KF->>PS: Consume event
    PS->>PS: Validate payee bank details / address
    PS->>PS: Select payment rail (ACH / wire / check / digital wallet)
    PS->>ExternalBank: Initiate payment via payment rail
    ExternalBank-->>PS: {payment_reference, estimated_arrival}

    PS->>DB: UPDATE claim SET status = PAYMENT_PROCESSING, payment_reference = :ref
    PS->>KF: Publish claim.payment_initiated {claim_id, payment_ref, estimated_arrival}

    KF->>NS: Send "Your settlement is on the way" to policyholder

Payment Rails

Payment Method Processing Time Use Case Notes
ACH (direct deposit) 1–3 business days Personal settlements Most common — lowest cost
Wire transfer Same day / next day Large amounts > $50K Higher cost; used for speed
Paper check 5–10 business days Policyholders without bank info Fraud risk; being deprecated
Digital wallet (Zelle / Venmo) Minutes Small personal settlements Age/eligibility restrictions
Direct-to-vendor (ACH) 1–3 business days Repair shop / medical provider Third-party payee

Payment Idempotency

Payments are financial operations. Every payment initiation carries an idempotency key to prevent double-payment on retries:

Idempotency key: payment:{claim_id}:{settlement_id}
Store:           PostgreSQL UNIQUE constraint on settlement_records.idempotency_key
Effect:          Retry of a payment initiation returns existing payment reference
                 No duplicate payment is ever sent

Step 11 — Regulatory Compliance Engine

Insurance is heavily regulated. Every U.S. state and many international jurisdictions have specific deadlines for claim acknowledgment, investigation, and settlement. Violation of these deadlines incurs fines and regulatory action.

Compliance Deadline Matrix

Jurisdiction Acknowledge Accept/Deny Pay After Acceptance
California 10 days 30 days 30 days
New York 15 days 15 days 5 days
Texas 15 days 15 days 5 days
Florida 14 days 30 days 20 days
Federal (ERISA health) 3 days urgent / 15 days standard 30 days 15 days

The system stores the policyholder's state of residence and applies the correct deadline matrix to every claim.

Compliance Monitoring

flowchart TB
    Claim["Claim Created\n(state: California)"]
    Claim --> DeadlineCalc["Compliance Engine\nCalculates all deadlines\nbased on state + claim type"]

    DeadlineCalc --> Deadlines["Deadlines stored:\n• Acknowledge by: Day 10\n• Decision by: Day 30\n• Payment by: Day 60"]

    Monitor["Deadline Monitor\n(runs every 15 minutes)"]
    Monitor --> D1{"Acknowledge\ndeadline"}
    Monitor --> D2{"Decision\ndeadline"}

    D1 -->|"< 48h remaining"| Warn["Alert adjuster:\nACKNOWLEDGE DEADLINE IN 48h"]
    D1 -->|"< 24h remaining"| Escalate["Alert supervisor\n+ compliance team"]
    D1 -->|"BREACHED"| Incident["Create compliance incident\nNotify legal + compliance VP\nPrepare regulatory response"]

    D2 -->|"< 72h remaining"| Warn2["Alert adjuster + supervisor"]
    D2 -->|"BREACHED"| Incident2["Compliance incident\n+ financial penalty estimate"]

Compliance Incident Record

CREATE TABLE compliance_incidents (
    incident_id      UUID          NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
    claim_id         UUID          NOT NULL REFERENCES claims(claim_id),
    deadline_type    VARCHAR(50)   NOT NULL,   -- 'ACKNOWLEDGE' | 'DECISION' | 'PAYMENT'
    deadline_date    DATE          NOT NULL,
    breached_at      TIMESTAMPTZ   NOT NULL,
    days_overdue     INT           NOT NULL,
    jurisdiction     VARCHAR(50)   NOT NULL,
    penalty_estimate BIGINT,                   -- in cents
    resolution_notes TEXT,
    resolved_at      TIMESTAMPTZ,
    created_at       TIMESTAMPTZ   NOT NULL DEFAULT NOW()
);

Step 12 — Database Design

Core Tables

-- Master claim record
CREATE TABLE claims (
    claim_id         UUID          NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
    claim_number     VARCHAR(20)   NOT NULL UNIQUE,   -- human-readable: CLM-2025-0042817
    policy_id        UUID          NOT NULL,
    policyholder_id  UUID          NOT NULL,
    line_of_business VARCHAR(20)   NOT NULL,   -- 'AUTO' | 'PROPERTY' | 'HEALTH' | 'LIABILITY'
    incident_date    DATE          NOT NULL,
    incident_type    VARCHAR(50)   NOT NULL,
    description      TEXT          NOT NULL,
    status           VARCHAR(30)   NOT NULL DEFAULT 'FNOL_RECEIVED',
    severity         VARCHAR(10)   NOT NULL DEFAULT 'MEDIUM',  -- LOW | MEDIUM | HIGH | CAT
    fraud_score      DECIMAL(4,3),
    fraud_status     VARCHAR(20)   NOT NULL DEFAULT 'PENDING',
    assigned_adjuster_id UUID,
    settlement_amount BIGINT,                  -- in cents; set on APPROVED
    jurisdiction     CHAR(2)       NOT NULL,   -- US state code
    idempotency_key  VARCHAR(255)  UNIQUE,
    created_at       TIMESTAMPTZ   NOT NULL DEFAULT NOW(),
    updated_at       TIMESTAMPTZ   NOT NULL DEFAULT NOW()
);

-- Immutable claim event log
CREATE TABLE claim_events (
    event_id         UUID          NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
    claim_id         UUID          NOT NULL REFERENCES claims(claim_id),
    event_type       VARCHAR(50)   NOT NULL,
    from_status      VARCHAR(30),
    to_status        VARCHAR(30),
    actor_type       VARCHAR(20)   NOT NULL,   -- 'POLICYHOLDER' | 'ADJUSTER' | 'SYSTEM' | 'SUPERVISOR'
    actor_id         VARCHAR(100)  NOT NULL,
    notes            TEXT,
    metadata         JSONB,
    occurred_at      TIMESTAMPTZ   NOT NULL DEFAULT NOW()
);

-- Workflow tasks with deadlines
CREATE TABLE workflow_tasks (
    task_id          UUID          NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
    claim_id         UUID          NOT NULL REFERENCES claims(claim_id),
    task_type        VARCHAR(50)   NOT NULL,
    assigned_to      UUID,                     -- user_id of adjuster / assessor
    queue_name       VARCHAR(50),
    status           VARCHAR(20)   NOT NULL DEFAULT 'OPEN',
    priority         INT           NOT NULL DEFAULT 5,
    due_date         TIMESTAMPTZ   NOT NULL,
    regulatory_deadline TIMESTAMPTZ,           -- hard deadline (regulatory vs internal SLA)
    completed_at     TIMESTAMPTZ,
    created_at       TIMESTAMPTZ   NOT NULL DEFAULT NOW()
);

-- Document metadata
CREATE TABLE claim_documents (
    document_id      UUID          NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
    claim_id         UUID          NOT NULL REFERENCES claims(claim_id),
    document_type    VARCHAR(50)   NOT NULL,
    filename         VARCHAR(255)  NOT NULL,
    s3_key           VARCHAR(500)  NOT NULL,
    file_size_bytes  BIGINT        NOT NULL,
    mime_type        VARCHAR(100),
    file_hash        VARCHAR(64),              -- SHA-256 for integrity verification
    uploaded_by      VARCHAR(100)  NOT NULL,
    uploader_type    VARCHAR(20)   NOT NULL,   -- 'POLICYHOLDER' | 'ADJUSTER' | 'ASSESSOR'
    virus_scan_status VARCHAR(20)  NOT NULL DEFAULT 'PENDING',
    status           VARCHAR(20)   NOT NULL DEFAULT 'PROCESSING',
    created_at       TIMESTAMPTZ   NOT NULL DEFAULT NOW()
);

-- Settlement records
CREATE TABLE settlements (
    settlement_id    UUID          NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY,
    claim_id         UUID          NOT NULL REFERENCES claims(claim_id),
    settlement_amount BIGINT       NOT NULL,   -- in cents
    payee_name       VARCHAR(200)  NOT NULL,
    payee_type       VARCHAR(20)   NOT NULL,   -- 'POLICYHOLDER' | 'REPAIR_SHOP' | 'MEDICAL_PROVIDER'
    payment_method   VARCHAR(20)   NOT NULL,
    payment_reference VARCHAR(100),
    payment_status   VARCHAR(20)   NOT NULL DEFAULT 'PENDING',
    idempotency_key  VARCHAR(255)  NOT NULL UNIQUE,
    approved_by      VARCHAR(100)  NOT NULL,
    approved_at      TIMESTAMPTZ   NOT NULL DEFAULT NOW(),
    paid_at          TIMESTAMPTZ
);

Key Indexes

-- Claims by policyholder (customer portal)
CREATE INDEX idx_claims_policyholder  ON claims(policyholder_id, created_at DESC);

-- Claims by adjuster (adjuster dashboard)
CREATE INDEX idx_claims_adjuster      ON claims(assigned_adjuster_id, status)
    WHERE assigned_adjuster_id IS NOT NULL;

-- Open tasks approaching deadline (workflow engine)
CREATE INDEX idx_tasks_deadline       ON workflow_tasks(due_date, status)
    WHERE status = 'OPEN';

-- Claims by status + line (queue management)
CREATE INDEX idx_claims_status_lob    ON claims(status, line_of_business, created_at);

-- Fraud review queue
CREATE INDEX idx_claims_fraud_review  ON claims(fraud_status, fraud_score DESC)
    WHERE fraud_status IN ('ELEVATED', 'UNDER_REVIEW');

-- Document lookup by claim
CREATE INDEX idx_documents_claim      ON claim_documents(claim_id, created_at DESC);

-- Events per claim (timeline view)
CREATE INDEX idx_events_claim         ON claim_events(claim_id, occurred_at DESC);

Step 13 — API Design

Submit FNOL

POST /v1/claims/fnol

{
  "idempotency_key":    "fnol-policy-P12345-2025-03-01T10:00:00Z",
  "policy_number":      "POL-P12345",
  "incident_date":      "2025-02-28",
  "incident_time":      "14:30",
  "incident_type":      "auto_collision",
  "incident_location":  "123 Main St, San Francisco, CA 94105",
  "description":        "Rear-ended at a red light. Significant trunk damage.",
  "at_fault":           false,
  "third_party_involved": true,
  "police_report_number": "SFPD-2025-00314"
}

201 Created response:

{
  "claim_id":       "clm_9f8e7d6c5b4a",
  "claim_number":   "CLM-2025-0042817",
  "status":         "fnol_received",
  "next_steps":     "A claims adjuster will contact you within 2 business days.",
  "acknowledgment_deadline": "2025-03-13",
  "contact_info":   { "phone": "1-800-CLAIMS-1", "email": "[email protected]" },
  "created_at":     "2025-03-01T10:00:02Z"
}

Get Claim Status

GET /v1/claims/{claim_id}

{
  "claim_id":       "clm_9f8e7d6c5b4a",
  "claim_number":   "CLM-2025-0042817",
  "status":         "under_investigation",
  "line_of_business": "auto",
  "incident_date":  "2025-02-28",
  "adjuster": {
    "name":   "Sarah Mitchell",
    "phone":  "415-555-0192",
    "email":  "[email protected]"
  },
  "current_reserve": 12500,
  "documents_requested": [
    { "type": "repair_estimate", "status": "pending", "due_date": "2025-03-15" }
  ],
  "timeline": [
    { "event": "FNOL submitted",       "date": "2025-03-01T10:00:02Z" },
    { "event": "Claim acknowledged",   "date": "2025-03-03T09:15:00Z" },
    { "event": "Adjuster assigned",    "date": "2025-03-03T09:15:00Z" },
    { "event": "Investigation started","date": "2025-03-04T11:00:00Z" }
  ]
}

Update Claim Status (Adjuster)

POST /v1/claims/{claim_id}/transitions

{
  "to_status":  "evaluation",
  "notes":      "Field inspection complete. Damage consistent with reported incident. Moving to evaluation.",
  "actor_id":   "adj_sarah_mitchell"
}

Request Documents

POST /v1/claims/{claim_id}/document-requests

{
  "document_type": "medical_records",
  "instructions":  "Please upload hospital records and itemized bills for the ER visit on 2025-02-28.",
  "due_date":      "2025-03-15",
  "required":      true
}

Step 14 — Kafka Event Architecture

flowchart TB
    CS["Claims Service"]

    CS --> T1["claim.fnol_received"]
    CS --> T2["claim.status_changed"]
    CS --> T3["claim.document_uploaded"]
    CS --> T4["claim.settlement_approved"]
    CS --> T5["claim.deadline_approaching"]
    CS --> T6["claim.deadline_breached"]

    T1 --> FS["Fraud Service\n(score new claims)"]
    T1 --> NS["Notification Service\n(FNOL confirmation)"]
    T1 --> AS["Audit Service\n(log FNOL event)"]

    T2 --> NS["Notification Service\n(status updates)"]
    T2 --> AS["Audit Service\n(all transitions)"]
    T2 --> Analytics["Analytics\n(claim pipeline metrics)"]

    T3 --> DS["Document Service\n(virus scan trigger)"]
    T3 --> WE["Workflow Engine\n(mark doc request fulfilled)"]

    T4 --> PS["Payment Service\n(initiate settlement)"]
    T4 --> RS["Reserve Service\n(close reserve)"]
    T4 --> AS["Audit Service"]

    T5 --> NS["Alert adjuster\n+ supervisor"]
    T6 --> Compliance["Compliance Team\n(incident creation)"]

Step 15 — Failure Handling and Observability

CAT Event Handling (Catastrophic Events)

A hurricane, wildfire, or earthquake can generate thousands of FNOLs in hours — 50× normal volume. The system must absorb this without losing any claim.

flowchart LR
    CAT["CAT Event\n(hurricane Ian — 50,000 claims in 24h)"]

    CAT --> KF["Kafka\nFNOL Topic\n(buffers surge)"]
    KF --> FNOL_Workers["FNOL Processing Workers\n(auto-scale on consumer lag)"]
    FNOL_Workers --> DB["PostgreSQL\n(writes — all claims persisted)"]

    CAT --> CAT_Flag["CAT Mode Enabled\n(operations manager toggles)"]
    CAT_Flag --> AdjRouting["Route all new claims\nto CAT Queue\n(not regular queues)"]
    CAT_Flag --> TempAdjusters["Enable CAT adjusters\n(on-call pool + contractors)"]
    CAT_Flag --> ExtendDeadlines["Regulatory deadline extension\n(most states allow CAT extensions)"]

Key Metrics

Metric Description Alert Threshold
claims.fnol_success_rate % FNOLs successfully created < 99.5%
claims.fnol_latency_p99 FNOL submission e2e latency > 5 seconds
claims.open_tasks_overdue Tasks past their internal SLA > 50 (escalate)
claims.regulatory_deadlines_at_risk Tasks within 48h of regulatory deadline Any
claims.regulatory_deadline_breaches Confirmed regulatory breaches Any — P0 alert
claims.fraud_review_backlog Claims in FRAUD_REVIEW > 30 days > 0
claims.adjuster_queue_depth Open claims per adjuster > 40 (overloaded)
documents.virus_scan_failure_rate Failed virus scans > 0.1%
payments.failure_rate Settlement payment failures > 1%
kafka.consumer_lag Lag on claim event topics > 10,000 messages

Claims Lifecycle Dashboard

The operations team monitors a real-time view of claim pipeline health:

Stage Count Avg Age % Past SLA
FNOL Received 842 0.3 days 0%
Acknowledged 1,204 1.2 days 0%
Under Investigation 48,320 8.4 days 2.1%
Fraud Review 387 14.2 days 12% ⚠️
Pending Documents 3,102 6.8 days 4.3%
Evaluation 8,940 18.1 days 1.8%
Approved / Payment 2,104 22.4 days 0.8%
Total Active 64,899 10.2 days avg 2.4% overall

Design Trade-offs

Trade-off 1: Workflow State — Database vs Dedicated Workflow Engine

Approach Pros Cons
Database-backed state machine (chosen) Simple; no additional infrastructure; state is durable by default Custom deadline tracking; no visual workflow designer
Dedicated workflow engine (Temporal / Camunda) Built-in deadline management; visual workflow; durable execution Operational complexity; vendor dependency; learning curve

Decision: Database-backed state machine for v1. The claim lifecycle is well-defined and stable — it does not change frequently. A bespoke state machine in PostgreSQL is simpler to operate, easier to debug, and has no external dependency. Temporal or Camunda becomes worth the complexity if the workflow needs frequent reconfiguration or branching logic exceeds ~15 states.

Trade-off 2: Synchronous vs Asynchronous Fraud Scoring

Approach Pros Cons
Asynchronous via Kafka (chosen) FNOL response is fast; fraud scoring does not block claim creation Small window where claim is in system before fraud score is applied
Synchronous in FNOL path Fraud score available before claim is acknowledged Adds 500ms–2s to FNOL response; ML model latency is unpredictable

Decision: Asynchronous. The policyholder submitting a FNOL is in distress — a 3-second response is already the limit. Fraud scoring typically completes in under 5 seconds via Kafka and catches all fraud before any human action is taken on the claim.

Trade-off 3: Object Storage for Documents vs Database BLOBs

Approach Pros Cons
Object storage — S3 (chosen) Unlimited scale; built-in WORM/compliance; pre-signed URLs for direct access Additional infrastructure; requires metadata in DB separately
Database BLOBs (PostgreSQL large objects) Single system; simpler architecture Massive DB size; backup complexity; no built-in WORM compliance

Decision: Object storage always. At 400 GB/day, storing files in the database would be catastrophically expensive and slow. S3's built-in Object Lock provides WORM compliance required for regulatory retention. Pre-signed URLs let clients download directly without routing through the backend.

Trade-off 4: Single Claims Service vs Line-of-Business Microservices

Approach Pros Cons
Single Claims Service (chosen) Shared workflow engine; unified audit log; simpler ops Auto/health/property have different fields and rules in same codebase
Per-LOB microservices Independent deployment per line; different schemas Shared audit trail becomes complex; routing logic split across services

Decision: Single Claims Service with line-of-business as a first-class field. The claim lifecycle is largely the same across lines (FNOL → investigation → decision → payment). LOB-specific logic lives in pluggable rules and field validation, not separate services. This avoids duplicating the workflow engine, compliance monitoring, and audit service.


Common Interview Mistakes

Mistake Why It Matters
Not modeling the claim as a state machine Without explicit valid transitions, adjusters can approve denied claims, settle fraud cases, and create invalid claim states
Storing documents in the claims database At 400 GB/day, the database becomes unusably large; backup and restore times become unacceptable
Missing regulatory deadline tracking Deadline breaches generate automatic fines; a system that cannot track its own deadlines is legally non-compliant
Synchronous fraud scoring in the FNOL path Adds unpredictable latency to the most time-critical API call; fraud can be scored asynchronously without risk
Not distinguishing reserve from settlement amount Reserve is an estimate; settlement is the final actual payment; conflating them corrupts financial reporting
Mutable audit log In litigation or regulatory examination, if the audit log can be edited, it has zero legal weight
Missing payment idempotency A payment service retry after a timeout can double-pay a policyholder — a significant financial and regulatory violation
Ignoring CAT event scaling Normal claim volume is low; a single weather event generates 50× volume; architecture must absorb this via queuing, not synchronous processing

Summary

The Insurance Claims System is one of the most regulated and operationally complex enterprise systems to design. It combines a long-lived workflow engine, financial reserve management, multi-party document exchange, fraud detection, and strict regulatory compliance — all under a mandate that no claim can be lost and no deadline can be missed.

flowchart TB
    Core["Insurance Claims System"]

    Core --> Lifecycle["Claim Lifecycle\nState machine\nFNOL → Settlement"]
    Core --> Workflow["Workflow Engine\nTask routing\nDeadline tracking"]
    Core --> Docs["Document Management\nS3 WORM storage\nPre-signed URLs"]
    Core --> Fraud["Fraud Detection\nAsync ML scoring\nSIU investigation queue"]
    Core --> Reserves["Reserve Management\nFinancial estimates\nAuthority limits"]
    Core --> Compliance["Regulatory Compliance\nJurisdiction deadlines\nBreached → P0 alert"]

The three design principles that make a claims system reliable:

  1. The audit log is the system's legal record — every action on every claim is permanently recorded with actor, timestamp, and reason. If it is not in the audit log, it did not happen. This is not a feature; it is a compliance obligation.

  2. Regulatory deadlines are P0 concerns — a single missed acknowledgment deadline can trigger a state regulatory investigation. Deadline monitoring must run continuously, alert early, and escalate aggressively. There is no acceptable failure mode.

  3. Decouple long-running workflows from synchronous APIs — the FNOL response must be fast because the policyholder is in distress. Fraud scoring, document processing, compliance checks, and payment processing all happen asynchronously. The claim exists and is tracked the moment the FNOL is submitted.