Design a Digital Life Insurance Platform (LIC-Style)
Building a full-stack insurance system — quotes, underwriting, policies, claims, compliance, and multi-channel distribution at scale
Table of Contents
- Introduction — The Digital Life Insurance Landscape
- Life Insurance Fundamentals
- Requirements Gathering
- Capacity Estimation & Sizing
- Data Model & Storage Schema
- High-Level Architecture
- API Design
- Plan Catalog & Product Configuration
- Online Quote Engine
- Underwriting & Risk Assessment
- Policy Issuance Pipeline
- Premium Collection & Payment Gateway
- Policy Loan & Surrender
- Claim Settlement Process
- Beneficiary Management
- Agent Management System
- Customer Portal & Self-Service
- Document Vault
- Regulatory Compliance (IRDAI)
- Multi-Channel Distribution
- Renewal & Revival
- Analytics & Actuarial Reporting
- Cost Estimation
- Testing Strategy
- Interview Q&A
1. Introduction — The Digital Life Insurance Landscape
Life insurance is one of the oldest and most complex financial products in the world. In India alone, the Life Insurance Corporation (LIC) manages over 400 million policies with a total asset base exceeding ₹40 lakh crore. The global life insurance market is projected to reach $4.1 trillion by 2028, driven by growing awareness, digital adoption, and regulatory reforms that mandate digitization. Yet despite this massive scale, the technology stack underlying most life insurance operations remains decades old, built on COBOL mainframes and monolithic architectures that struggle to adapt to modern consumer expectations.
A digital life insurance platform must handle the entire lifecycle of a policy — from initial inquiry and quote generation, through underwriting and policy issuance, premium collection over decades, riders and endorsements, policy loans, surrenders, and ultimately claim settlement or maturity payout. This is fundamentally different from building an e-commerce site or a SaaS application because the temporal dimension spans decades, the regulatory requirements are extraordinarily strict, and the financial calculations involve actuarial science with probability distributions that must be precisely correct.
Consider the scale: a platform like LIC processes over 20 million premium payments per month, generates millions of policy documents, handles hundreds of thousands of death claims per year, and must maintain data integrity across all these operations for policies that may last 35 years or more. Every calculation must be auditable, every transaction must be reversible or compensatable, and every regulatory filing must be generated from a single source of truth.
The digital transformation of life insurance is not just about putting a mobile app on top of legacy systems. It requires a complete rethinking of the data model, the business process engine, the integration layer with payment gateways and KYC providers, the document management system, the agent management system, and the regulatory reporting infrastructure. This guide walks through designing such a platform from first principles, covering every major subsystem with production-grade architecture, C# code examples, and interview-level depth.
2. Life Insurance Fundamentals
Types of Life Insurance Products
Understanding the core product types is essential before designing the data model, because each product type has fundamentally different premium calculation logic, benefit structures, and regulatory requirements.
| Product Type | Description | Premium | Benefit | Example |
|---|---|---|---|---|
| Term Life | Pure protection for a fixed term | Low | Death benefit only | LIC Tech Term Plan |
| Whole Life | Coverage until age 99-100 | Moderate | Death benefit + guaranteed maturity | LIC Whole Life Plan |
| Endowment | Savings + protection hybrid | High | Guaranteed maturity + death benefit | LIC Jeevan Anand |
| ULIP | Market-linked investment | Variable | NAV-based fund value | LIC SIIP |
| Money Back | Periodic survival benefits | High | Survival payouts + maturity | LIC New Money Back |
| Pension/Annuity | Retirement income stream | Varies | Regular pension payments | LIC Jeevan Akshay |
| Group Term | Employer-sponsored coverage | Low per member | Death benefit for group | Corporate Group Term |
Key Financial Concepts
The system must accurately compute and store several financial concepts that form the backbone of every policy. Understanding mortality tables is critical because they define the probability of death at each age, which directly affects pricing. The Indian Mortality Table published by the Insurance Regulatory and Development Authority of India (IRDAI) is the standard reference for Indian life insurers. The mortality rate at age 30 might be 0.00106 (meaning approximately 1.06 deaths per 1,000 lives), while at age 60 it might be 0.01287 — these rates increase exponentially with age following a Gompertz-Makeham distribution.
Actuarial present value calculations discount future cash flows using an assumed interest rate (the investment return assumption). For a typical Indian endowment plan, the assumed investment return rate is between 4% and 6% per annum, published annually by the insurer and approved by IRDAI. The net level premium is the level annual premium that, when accumulated at the assumed interest rate, exactly equals the sum assured payable at the end of the term, adjusted for mortality. Loading factors cover acquisition costs (agent commissions, medical examination fees), administration costs, and a margin for contingencies.
The mathematical formula for a net single premium for a whole life policy at age x for a sum assured of S with interest rate i and mortality from the qx table is: NSPx = S times the sum of (tPx times qx+t divided by (1+i)^(t+1)) for all t from 0 to omega-x-1. The annual premium is then NSPx divided by the annuity-due factor a-double-dot-x. These calculations must be implemented with extreme precision using decimal arithmetic, not floating point, because even rounding errors of a few paise compounded over 30 years can result in significant discrepancies.
Policy Lifecycle States
A policy in a digital insurance platform traverses through many states over its lifetime. Understanding these states is critical for designing the state machine that governs all policy operations.
The state machine above represents the simplified version. In production, there are additional sub-states like "Grace Period," "Policy Loan Pending Approval," "Claim Under Investigation," "Claim Approved," "Claim Paid," "Maturity Claim Filed," and "Revival Under Review." Each state transition must be logged with a timestamp, the triggering user or system, and any associated financial transaction. The state machine must enforce valid transitions — for example, you cannot move a policy from "Terminated" back to "Active" under any circumstances.
3. Requirements Gathering
Functional Requirements
| # | Requirement | Priority | Details |
|---|---|---|---|
| F1 | Plan catalog browsing | Must | Customers browse available plans with details, benefits, illustrations |
| F2 | Online quote generation | Must | Instant premium calculation based on age, sum assured, term, riders |
| F3 | Online application | Must | Digital application with e-KYC, proposal form, medical declaration |
| F4 | E-KYC verification | Must | Aadhaar-based OTP, PAN verification, video KYC |
| F5 | Payment gateway integration | Must | UPI, net banking, credit/debit cards, NEFT, auto-debit |
| F6 | Policy document generation | Must | PDF policy bond, endorsement letters, premium receipts |
| F7 | Premium collection | Must | Single, annual, quarterly, monthly, semi-annual modes |
| F8 | Claim filing | Must | Death claim, maturity claim, rider claim initiation online |
| F9 | Beneficiary nomination | Must | Add, update, view nominees and their share percentages |
| F10 | Policy loan | Should | Apply for policy loan against surrender value |
| F11 | Surrender request | Should | Online surrender with instant calculation and payout |
| F12 | Agent portal | Should | Agent dashboard, lead management, commission tracking |
| F13 | Renewal reminders | Should | SMS, email, push notifications for upcoming premiums |
| F14 | Policy revival | Should | Revive lapsed policies with premium revival calculator |
| F15 | WhatsApp integration | Nice | Policy status, premium reminders via WhatsApp Business API |
| F16 | Chatbot assistance | Nice | AI-powered chatbot for FAQs and basic policy queries |
Non-Functional Requirements
| Requirement | Target | Rationale |
|---|---|---|
| Availability | 99.95% | Financial platform with regulatory uptime obligations |
| Latency (P99) | < 2s for quotes, < 5s for policy generation | Customers expect near-instant quotes; PDFs take time |
| Throughput | 50,000 quotes/day, 10,000 policies/day | Peak during campaign periods and year-end |
| Data retention | Minimum 25 years post policy maturity | Regulatory requirement for long-term policies |
| Consistency | Strong consistency for financial transactions | Premium payments and claim settlements must be exact |
| Security | SOC 2 Type II, IRDAI compliance | Sensitive financial and personal data |
| Encryption | AES-256 at rest, TLS 1.3 in transit | KYC documents, PAN, Aadhaar data protection |
4. Capacity Estimation & Sizing
Traffic Estimation
Let us estimate the scale for a mid-sized digital insurer targeting 5 million active policies. With 5 million policies, annual premium payments generate approximately 5 million annual premiums or 15 million quarterly premiums. Assuming a 60/40 split between annual and quarterly modes, we get roughly 3 million annual and 12 million quarterly premium payments per year. During peak months (March in India for tax-saving), premium collection traffic spikes 3x. This means approximately 4 million premium payment transactions per month at peak, or about 1,500 transactions per second during peak hours.
Quote generation is a lighter operation but still significant. If the website receives 500,000 unique visitors per month and 5% request a quote, that is 25,000 quotes per month or about 833 per day. However, during marketing campaigns this could spike to 10x, reaching 8,333 quotes per day. Claim submissions are much rarer — with 5 million policies and a crude mortality rate of 6 per 1,000 annually, we expect approximately 30,000 death claims per year or about 82 per day. Maturity claims depend on the policy mix but are similarly modest in frequency.
Storage Estimation
| Data Type | Record Size | Annual Records | Annual Storage | 25-Year Storage |
|---|---|---|---|---|
| Policy master | 2 KB | 500,000 new | 1 GB | 25 GB |
| Premium transactions | 0.5 KB | 15,000,000 | 7.5 GB | 188 GB |
| Claims | 5 KB | 30,000 | 150 MB | 3.75 GB |
| Documents (PDFs) | 500 KB avg | 2,000,000 | 1 TB | 25 TB |
| Agent data | 1 KB | 50,000 | 50 MB | 1.25 GB |
| Audit logs | 0.2 KB | 50,000,000 | 10 GB | 250 GB |
Total structured data for 25 years is approximately 470 GB. Documents dominate at 25 TB and must be stored in object storage like S3 or Azure Blob with lifecycle policies for tiered storage. The audit log is significant because every state change, every financial transaction, and every document access must be logged for regulatory compliance. We can compress audit logs after 3 years and archive them to cold storage, reducing the active storage requirement to about 60 GB for audit data.
Bandwidth Estimation
For the web and mobile applications, assuming 100,000 daily active users (DAUs) with an average session of 5 pages at 200 KB per page, the daily bandwidth is approximately 100 GB for serving content. API traffic for premium payments and quotes adds another 50 GB daily. Total daily bandwidth is approximately 150 GB, which is well within the capacity of a standard cloud deployment. Document downloads for policy bonds and claim forms could add another 200 GB daily during peak periods.
5. Data Model & Storage Schema
The data model for a life insurance platform is exceptionally complex because it must model products with configurable benefit structures, policies with multi-decade lifecycles, financial transactions with double-entry bookkeeping, and regulatory-mandated data retention. The design below uses a relational database (PostgreSQL) as the system of record with Redis for caching and MongoDB for document metadata.
Core Tables
SQL
-- Insurance Product / Plan definitions
CREATE TABLE insurance_products (
product_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
product_code VARCHAR(20) UNIQUE NOT NULL,
product_name VARCHAR(200) NOT NULL,
product_type VARCHAR(30) NOT NULL,
description TEXT,
min_sum_assured DECIMAL(15,2) NOT NULL,
max_sum_assured DECIMAL(15,2) NOT NULL,
min_entry_age INT NOT NULL,
max_entry_age INT NOT NULL,
min_term_years INT NOT NULL,
max_term_years INT NOT NULL,
premium_modes VARCHAR(50)[] NOT NULL,
assumed_interest DECIMAL(5,4) NOT NULL,
mortality_table VARCHAR(50) NOT NULL,
status VARCHAR(20) DEFAULT 'DRAFT',
approved_by_irdai BOOLEAN DEFAULT FALSE,
irdai_approval_no VARCHAR(50),
effective_date DATE NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Customer master
CREATE TABLE customers (
customer_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
customer_number VARCHAR(20) UNIQUE NOT NULL,
first_name VARCHAR(100) NOT NULL,
last_name VARCHAR(100) NOT NULL,
date_of_birth DATE NOT NULL,
gender VARCHAR(10) NOT NULL,
email VARCHAR(200),
phone VARCHAR(15) NOT NULL,
aadhaar_hash VARCHAR(64),
pan_number VARCHAR(10),
address_line1 VARCHAR(200),
address_line2 VARCHAR(200),
city VARCHAR(100),
state VARCHAR(100),
pincode VARCHAR(6),
occupation VARCHAR(100),
annual_income DECIMAL(15,2),
kyc_status VARCHAR(20) DEFAULT 'PENDING',
kyc_verified_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Policy master (the central entity)
CREATE TABLE policies (
policy_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
policy_number VARCHAR(20) UNIQUE NOT NULL,
customer_id UUID NOT NULL REFERENCES customers(customer_id),
product_id UUID NOT NULL REFERENCES insurance_products(product_id),
agent_id UUID REFERENCES agents(agent_id),
sum_assured DECIMAL(15,2) NOT NULL,
policy_term INT NOT NULL,
premium_term INT NOT NULL,
premium_amount DECIMAL(12,2) NOT NULL,
premium_mode VARCHAR(20) NOT NULL,
modal_loading DECIMAL(5,4) NOT NULL,
base_premium DECIMAL(12,2) NOT NULL,
gst_amount DECIMAL(12,2) NOT NULL,
total_premium DECIMAL(12,2) NOT NULL,
policy_status VARCHAR(30) NOT NULL DEFAULT 'ISSUED',
issue_date DATE NOT NULL,
inception_date DATE NOT NULL,
maturity_date DATE NOT NULL,
first_premium_date DATE NOT NULL,
last_premium_date DATE,
surrender_value DECIMAL(15,2) DEFAULT 0,
paid_up_sum_assured DECIMAL(15,2) DEFAULT 0,
revival_deadline DATE,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Premium transactions (double-entry bookkeeping)
CREATE TABLE premium_transactions (
transaction_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
policy_id UUID NOT NULL REFERENCES policies(policy_id),
transaction_type VARCHAR(30) NOT NULL,
amount DECIMAL(12,2) NOT NULL,
gst_amount DECIMAL(12,2) NOT NULL,
total_amount DECIMAL(12,2) NOT NULL,
payment_mode VARCHAR(30) NOT NULL,
payment_gateway_ref VARCHAR(100),
status VARCHAR(20) NOT NULL,
transaction_date TIMESTAMPTZ NOT NULL,
settlement_date TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Claims
CREATE TABLE claims (
claim_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
claim_number VARCHAR(20) UNIQUE NOT NULL,
policy_id UUID NOT NULL REFERENCES policies(policy_id),
claim_type VARCHAR(30) NOT NULL,
claim_status VARCHAR(30) NOT NULL DEFAULT 'FILED',
claim_amount DECIMAL(15,2),
settlement_amount DECIMAL(15,2),
filing_date DATE NOT NULL,
investigation_start DATE,
settlement_date DATE,
payment_date DATE,
cause_of_death TEXT,
hospital_details JSONB,
assignee_id UUID REFERENCES users(user_id),
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Beneficiaries / Nominees
CREATE TABLE beneficiaries (
beneficiary_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
policy_id UUID NOT NULL REFERENCES policies(policy_id),
customer_id UUID REFERENCES customers(customer_id),
name VARCHAR(200) NOT NULL,
relationship VARCHAR(50) NOT NULL,
date_of_birth DATE,
share_percentage DECIMAL(5,2) NOT NULL,
aadhaar_hash VARCHAR(64),
is_primary BOOLEAN DEFAULT FALSE,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Policy loans
CREATE TABLE policy_loans (
loan_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
policy_id UUID NOT NULL REFERENCES policies(policy_id),
loan_amount DECIMAL(15,2) NOT NULL,
outstanding_amount DECIMAL(15,2) NOT NULL,
interest_rate DECIMAL(5,4) NOT NULL,
disbursement_date DATE NOT NULL,
status VARCHAR(20) NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Document vault
CREATE TABLE documents (
document_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
entity_type VARCHAR(50) NOT NULL,
entity_id UUID NOT NULL,
document_type VARCHAR(50) NOT NULL,
file_name VARCHAR(200) NOT NULL,
file_size BIGINT NOT NULL,
mime_type VARCHAR(50) NOT NULL,
storage_key VARCHAR(500) NOT NULL,
checksum_sha256 VARCHAR(64) NOT NULL,
uploaded_by UUID NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Agents
CREATE TABLE agents (
agent_id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
agent_code VARCHAR(20) UNIQUE NOT NULL,
name VARCHAR(200) NOT NULL,
email VARCHAR(200) NOT NULL,
phone VARCHAR(15) NOT NULL,
agency_code VARCHAR(50),
branch_code VARCHAR(50),
commission_rate DECIMAL(5,4),
status VARCHAR(20) DEFAULT 'ACTIVE',
appointed_date DATE NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
Indexing Strategy
SQL
CREATE INDEX idx_policies_customer ON policies(customer_id);
CREATE INDEX idx_policies_status ON policies(policy_status) WHERE policy_status != 'TERMINATED';
CREATE INDEX idx_policies_maturity ON policies(maturity_date) WHERE policy_status = 'ACTIVE';
CREATE INDEX idx_premium_txn_policy ON premium_transactions(policy_id, transaction_date);
CREATE INDEX idx_premium_txn_date ON premium_transactions(transaction_date) WHERE status = 'SETTLED';
CREATE INDEX idx_claims_status ON claims(claim_status);
CREATE INDEX idx_claims_filing ON claims(filing_date);
CREATE INDEX idx_documents_entity ON documents(entity_type, entity_id);
CREATE INDEX idx_customers_phone ON customers(phone);
CREATE INDEX idx_policies_number_lookup ON policies(policy_number);
The indexing strategy prioritizes the most common lookup patterns: customer-to-policies (for customer service), policy status queries (for operational dashboards), maturity date range (for maturity claim processing), and premium transaction history (for financial reconciliation). Partial indexes on active policies and settled transactions keep index sizes manageable while covering the hot path queries efficiently.
6. High-Level Architecture
The architecture follows a microservices pattern where each bounded context in the insurance domain maps to a dedicated service. The policy service is the most critical and complex, as it manages the state machine for all policy lifecycle events. The premium service handles all financial transactions and integrates with multiple payment gateways. The claim service orchestrates the multi-step claim settlement workflow including document verification, investigation assignment, and settlement disbursement.
The communication between services uses a hybrid approach: synchronous REST for request-response patterns (quote generation, customer lookup) and asynchronous events via Kafka for eventual consistency patterns (premium payment confirmation triggers policy status update, claim filing triggers notification). This separation ensures that the premium collection path is optimized for low latency while claim processing can be asynchronous without impacting customer experience.
7. API Design
Core API Endpoints
REST
GET /api/v1/products -- List all products
GET /api/v1/products/{productId} -- Get product details
GET /api/v1/products/{productId}/brochure -- Download product brochure
POST /api/v1/quotes -- Generate quote
POST /api/v1/quotes/{quoteId}/apply -- Convert quote to application
POST /api/v1/applications -- Submit application
GET /api/v1/applications/{appId} -- Get application status
PUT /api/v1/applications/{appId}/medical -- Upload medical reports
POST /api/v1/policies -- Issue policy (admin)
GET /api/v1/policies/{policyNumber} -- Get policy details
GET /api/v1/policies/{policyNumber}/statements -- Premium statement history
POST /api/v1/policies/{policyNumber}/revive -- Revive lapsed policy
POST /api/v1/premiums/initiate -- Initiate premium payment
POST /api/v1/premiums/{txnId}/confirm -- Confirm payment
GET /api/v1/premiums/{policyNumber}/schedule -- Get payment schedule
POST /api/v1/claims/file -- File a claim
POST /api/v1/claims/{claimId}/documents -- Upload claim documents
GET /api/v1/claims/{claimId} -- Get claim status
POST /api/v1/loans/apply -- Apply for policy loan
POST /api/v1/loans/{loanId}/repay -- Repay loan
GET /api/v1/customers/{customerId}/policies -- Customer's policies
GET /api/v1/customers/{customerId}/nominees -- Customer's nominees
POST /api/v1/agents/{agentId}/leads -- Create lead
GET /api/v1/agents/{agentId}/dashboard -- Agent dashboard
Quote Request / Response
JSON
// POST /api/v1/quotes
{
"productCode": "JEEVAN_ANAND_815",
"sumAssured": 5000000,
"policyTerm": 25,
"premiumTerm": 20,
"premiumMode": "ANNUAL",
"dateOfBirth": "1990-06-15",
"gender": "MALE",
"smoker": false,
"riders": [
{ "riderCode": "TERM_RIDER", "sumAssured": 5000000 },
{ "riderCode": "ACCIDENT_RIDER", "sumAssured": 5000000 }
],
"state": "MAHARASHTRA"
}
// Response 200
{
"quoteId": "QTE-2026-07-13-ABCD1234",
"productCode": "JEEVAN_ANAND_815",
"basePremium": 297450.00,
"riderPremiums": [
{ "riderCode": "TERM_RIDER", "premium": 12500.00 },
{ "riderCode": "ACCIDENT_RIDER", "premium": 4500.00 }
],
"totalBasePremium": 314450.00,
"gstAmount": 56501.00,
"totalPayable": 370951.00,
"maturityBenefit": 5000000.00,
"deathBenefit": 5000000.00,
"bonusProjection": {
"conservative": 2100000,
"moderate": 3200000,
"optimistic": 4500000
},
"validUntil": "2026-07-20T23:59:59+05:30"
}
Policy Service Implementation
C#
public class PolicyService : IPolicyService
{
private readonly IPolicyRepository _policyRepo;
private readonly IPremiumRepository _premiumRepo;
private readonly IEventPublisher _eventPublisher;
private readonly ILogger<PolicyService> _logger;
public async Task<PolicyResponse> IssuePolicyAsync(
IssuePolicyRequest request,
CancellationToken ct)
{
await using var transaction = await _policyRepo
.BeginTransactionAsync(IsolationLevel.Serializable);
try
{
var policyNumber = await GeneratePolicyNumberAsync(ct);
var policy = new Policy
{
PolicyId = Guid.NewGuid(),
PolicyNumber = policyNumber,
CustomerId = request.CustomerId,
ProductId = request.ProductId,
AgentId = request.AgentId,
SumAssured = request.SumAssured,
PolicyTerm = request.PolicyTerm,
PremiumTerm = request.PremiumTerm,
PremiumAmount = request.BasePremium,
PremiumMode = request.PremiumMode,
ModalLoading = request.ModalLoading,
BasePremium = request.BasePremium,
GstAmount = request.GstAmount,
TotalPremium = request.TotalPremium,
PolicyStatus = PolicyStatus.Issued,
IssueDate = DateOnly.FromDateTime(DateTime.UtcNow),
InceptionDate = request.InceptionDate,
MaturityDate = CalculateMaturityDate(
request.InceptionDate, request.PolicyTerm),
FirstPremiumDate = request.InceptionDate,
CreatedAt = DateTime.UtcNow,
UpdatedAt = DateTime.UtcNow
};
await _policyRepo.AddAsync(policy, ct);
var premiumTxn = new PremiumTransaction
{
TransactionId = Guid.NewGuid(),
PolicyId = policy.PolicyId,
TransactionType = "FIRST_PREMIUM",
Amount = request.BasePremium,
GstAmount = request.GstAmount,
TotalAmount = request.TotalPremium,
PaymentMode = request.PaymentMode,
PaymentGatewayRef = request.PaymentRef,
Status = "SETTLED",
TransactionDate = DateTime.UtcNow,
SettlementDate = DateTime.UtcNow
};
await _premiumRepo.AddAsync(premiumTxn, ct);
await transaction.CommitAsync(ct);
await _eventPublisher.PublishAsync(new PolicyIssuedEvent
{
PolicyNumber = policyNumber,
CustomerId = request.CustomerId,
IssuedAt = DateTime.UtcNow
}, ct);
_logger.LogInformation(
"Policy {PolicyNumber} issued for customer {CustomerId}",
policyNumber, request.CustomerId);
return MapToResponse(policy);
}
catch (Exception ex)
{
await transaction.RollbackAsync(ct);
_logger.LogError(ex,
"Failed to issue policy for customer {CustomerId}",
request.CustomerId);
throw;
}
}
private async Task<string> GeneratePolicyNumberAsync(
CancellationToken ct)
{
var year = DateTime.UtcNow.Year;
var sequence = await _policyRepo
.GetNextSequenceAsync($"POL-{year}", ct);
return $"POL-{year}-{sequence:D7}";
}
private DateOnly CalculateMaturityDate(
DateOnly inceptionDate, int termYears)
{
return inceptionDate.AddYears(termYears);
}
}
8. Plan Catalog & Product Configuration
The plan catalog is the foundation of the entire insurance platform because it defines the rules, limits, and calculation parameters for every product. Rather than hardcoding product logic, a well-designed platform uses a configurable product engine where benefit structures, rider options, premium loading factors, and eligibility criteria are all stored as data. This allows product managers to launch new products or modify existing ones without code deployments, subject to regulatory approval.
Each product configuration includes mortality table references, interest rate assumptions, surrender value factors, paid-up value factors, and bonus declaration rules. The configuration must support multi-versioning because products approved by IRDAI may have different parameters for different policy years. For example, the bonus rate for LIC's Jeevan Anand might change from year to year based on the insurer's investment performance, and the system must calculate bonuses using the rate applicable to each policy year, not the current rate.
C#
public class ProductConfiguration
{
public string ProductCode { get; set; }
public string ProductName { get; set; }
public ProductType ProductType { get; set; }
public EligibilityCriteria Eligibility { get; set; }
public PremiumStructure Premium { get; set; }
public BenefitStructure Benefits { get; set; }
public List<RiderConfig> AvailableRiders { get; set; }
public SurrenderFactors Surrender { get; set; }
public List<BonusDeclaration> BonusHistory { get; set; }
}
public class EligibilityCriteria
{
public int MinEntryAge { get; set; }
public int MaxEntryAge { get; set; }
public int MinSumAssured { get; set; }
public int MaxSumAssured { get; set; }
public int MinTerm { get; set; }
public int MaxTerm { get; set; }
public decimal MinIncome { get; set; }
public List<string> AllowedPremiumModes { get; set; }
}
public class PremiumStructure
{
public decimal AssumedInterestRate { get; set; }
public string MortalityTable { get; set; }
public decimal ModalLoadingFactor { get; set; }
public decimal GSTRate { get; set; }
public Dictionary<string, decimal> ModeFactors { get; set; }
}
public class BenefitStructure
{
public bool HasMaturityBenefit { get; set; }
public bool HasDeathBenefit { get; set; }
public bool HasSurvivalBenefit { get; set; }
public decimal GuaranteedAdditionRate { get; set; }
public decimal LoyaltyAdditionRate { get; set; }
public List<SurvivalBenefitSchedule> SurvivalBenefits { get; set; }
public string BonusCalculationMethod { get; set; }
}
public class SurrenderFactors
{
public int MinPaidYearsForSurrender { get; set; }
public decimal FirstYearSurrenderFactor { get; set; }
public List<YearFactorPair> YearWiseFactors { get; set; }
}
public class BonusDeclaration
{
public int PolicyYear { get; set; }
public decimal ReversionaryBonusRate { get; set; }
public decimal TerminalBonusRate { get; set; }
public DateOnly DeclarationDate { get; set; }
}
public class RiderConfig
{
public string RiderCode { get; set; }
public string RiderName { get; set; }
public RiderType Type { get; set; }
public decimal PremiumRatePerThousand { get; set; }
public int MaxSumAssured { get; set; }
public bool RequiresMedicalExam { get; set; }
}
9. Online Quote Engine
The quote engine is the most computationally intensive real-time component of the platform. It must calculate premiums accurately using actuarial formulas, support multiple riders, project future benefits including bonus estimates, and return results within 500 milliseconds. The engine must handle the complexity of different mortality tables, interest rate assumptions, and modal loading factors for each product.
Premium Calculation Flow
C#
public class QuoteEngine : IQuoteEngine
{
private readonly IMortalityTableService _mortalityService;
private readonly IProductRepository _productRepo;
private readonly IBonusProjectionService _bonusService;
public async Task<QuoteResult> CalculateQuoteAsync(
QuoteRequest request,
CancellationToken ct)
{
var product = await _productRepo
.GetActiveVersionAsync(request.ProductCode, ct);
var mortalityTable = await _mortalityService
.GetTableAsync(product.Premium.MortalityTable, ct);
var age = CalculateAge(request.DateOfBirth);
ValidateEligibility(product, age, request.SumAssured,
request.PolicyTerm);
var netSinglePremium = CalculateNSP(
age,
request.SumAssured,
product.Premium.AssumedInterestRate,
mortalityTable,
request.PolicyTerm);
var loadedPremium = ApplyLoadingFactors(
netSinglePremium,
product.Premium.ModalLoadingFactor);
var annualPremium = loadedPremium / request.PremiumTerm;
var modalPremium = ApplyModalFactor(
annualPremium,
request.PremiumMode,
product.Premium.ModeFactors);
var gstAmount = modalPremium * product.Premium.GSTRate;
var riderPremiums = await CalculateRiderPremiumsAsync(
product.AvailableRiders,
request.Riders,
age,
request.SumAssured,
ct);
var maturityProjection = await _bonusService
.ProjectMaturityAsync(
product,
request.SumAssured,
request.PolicyTerm,
ct);
return new QuoteResult
{
QuoteId = GenerateQuoteId(),
BasePremium = Math.Round(annualPremium, 2),
ModalPremium = Math.Round(modalPremium, 2),
GstAmount = Math.Round(gstAmount, 2),
TotalPayable = Math.Round(modalPremium + gstAmount, 2),
RiderPremiums = riderPremiums,
MaturityBenefit = request.SumAssured,
BonusProjection = maturityProjection,
ValidUntil = DateTime.UtcNow.AddDays(7)
};
}
private decimal CalculateNSP(
int age,
decimal sumAssured,
decimal interestRate,
decimal[] mortalityTable,
int term)
{
decimal nsp = 0;
decimal discountFactor = 1m;
for (int t = 0; t < term; t++)
{
decimal mortalityRate = mortalityTable[age + t];
decimal survivalProb = 1m - mortalityRate;
decimal expectedPayout = sumAssured * mortalityRate;
nsp += expectedPayout * discountFactor;
discountFactor /= (1m + interestRate);
}
return nsp;
}
private decimal ApplyModalFactor(
decimal annualPremium,
string mode,
Dictionary<string, decimal> factors)
{
return annualPremium * factors[mode];
}
}
The quote engine uses Redis caching for product configurations and mortality tables since these change very infrequently (at most annually when new bonus rates are declared). A quote result is cached for 7 days with the quote ID as the key, allowing the application flow to reference the exact quote that was generated. The premium calculation uses decimal throughout to avoid floating-point precision issues that could result in incorrect premium amounts.
10. Underwriting & Risk Assessment
Underwriting is the process of evaluating the risk posed by a prospective policyholder and deciding whether to accept the risk, decline it, or accept it with modifications (such as a higher premium or an exclusion clause). In a digital platform, underwriting must balance speed (customers expect instant decisions for simple policies) with accuracy (errors in underwriting can result in massive losses for the insurer).
Underwriting Decision Matrix
| Risk Factor | Low Risk | Medium Risk | High Risk | Decision |
|---|---|---|---|---|
| Age | 18-35 | 36-50 | 51-65 | Age affects mortality rate |
| Sum Assured | < 25L | 25L - 1Cr | > 1Cr | Higher SA needs more scrutiny |
| BMI | 18.5-25 | 25-30 | <18.5 or >30 | Obesity increases mortality |
| Smoking | Non-smoker | Ex-smoker 3yr+ | Current smoker | Smoker loading 30-50% |
| Family History | No critical illness | One parent CVD | Hereditary conditions | Affects long-term risk |
| Income | > 5x premium | 3-5x premium | < 3x premium | Ensures affordability |
| Medical History | No conditions | Controlled hypertension | Diabetes, heart disease | May require extra medical |
C#
public class UnderwritingEngine : IUnderwritingEngine
{
public async Task<UnderwritingDecision> EvaluateAsync(
UnderwritingRequest request,
CancellationToken ct)
{
var score = 0;
var flags = new List<UnderwritingFlag>();
score += EvaluateAge(request.DateOfBirth);
score += EvaluateBMI(request.Weight, request.Height);
score += EvaluateSmokingStatus(request.Smoker);
score += EvaluateIncome(
request.AnnualIncome, request.ProposedPremium);
score += EvaluateFamilyHistory(request.FamilyHistory);
score += await EvaluateMedicalHistoryAsync(
request.CustomerId, ct);
if (request.SumAssured > 10_000_000m)
{
flags.Add(UnderwritingFlag.HIGH_SUM_ASSURED);
score += 15;
}
if (request.Age > 50)
{
flags.Add(UnderwritingFlag.SENIOR_ENTRY);
score += 10;
}
var decision = score switch
{
<= 20 => UnderwritingDecision.AutoApproved,
<= 40 => UnderwritingDecision.MedicalExamRequired,
<= 60 => UnderwritingDecision.ManualReview,
_ => UnderwritingDecision.Declined
};
if (decision == UnderwritingDecision.MedicalExamRequired)
{
await ScheduleMedicalExamAsync(request.ApplicationId, ct);
}
return new UnderwritingDecision
{
Decision = decision,
RiskScore = score,
Flags = flags,
EvaluatedAt = DateTime.UtcNow,
NextSteps = GetNextSteps(decision)
};
}
private int EvaluateAge(DateOnly dateOfBirth)
{
var age = CalculateAge(dateOfBirth);
return age switch
{
<= 25 => 5,
<= 35 => 10,
<= 45 => 15,
<= 55 => 25,
_ => 35
};
}
private int EvaluateBMI(decimal weightKg, decimal heightCm)
{
var heightM = heightCm / 100m;
var bmi = weightKg / (heightM * heightM);
return bmi switch
{
< 18.5m => 20,
<= 25m => 5,
<= 30m => 15,
_ => 25
};
}
}
The underwriting engine uses a rules-based scoring system that assigns weights to various risk factors. For policies below a certain sum assured threshold (typically ₹25 lakh), the engine can make auto-decisions. For higher sum assured policies, the engine flags cases for manual underwriter review. Medical examination requirements are triggered based on the risk score and the product's specific underwriting guidelines. The entire decision process must be auditable because IRDAI can audit underwriting decisions during inspections.
11. Policy Issuance Pipeline
Once an application is approved by underwriting, the policy issuance pipeline generates the policy bond, creates all necessary records, sets up the premium payment schedule, and delivers the policy documents to the customer. This pipeline must handle edge cases like backdated policies (where the inception date is in the past for premium payment purposes), single-premium policies, and group policies where one application generates multiple individual policies.
Issuance Pipeline Stages
C#
public class PolicyIssuancePipeline
{
private readonly IPolicyNumberGenerator _numberGen;
private readonly IPremiumScheduleBuilder _scheduleBuilder;
private readonly IPolicyBondPdfGenerator _pdfGen;
private readonly IDocumentVaultService _docVault;
private readonly INotificationService _notification;
private readonly ICommissionService _commission;
private readonly IRegulatoryService _regulatory;
public async Task<IssuanceResult> ExecuteAsync(
ApprovedApplication application,
CancellationToken ct)
{
var policyNumber = await _numberGen.GenerateAsync(ct);
var schedule = _scheduleBuilder.Build(
application.BasePremium,
application.PremiumMode,
application.PremiumTerm,
application.InceptionDate);
var pdfBytes = await _pdfGen.GenerateAsync(
new PolicyBondData
{
PolicyNumber = policyNumber,
CustomerName = application.CustomerName,
ProductName = application.ProductName,
SumAssured = application.SumAssured,
PremiumAmount = application.TotalPremium,
PremiumMode = application.PremiumMode,
PolicyTerm = application.PolicyTerm,
InceptionDate = application.InceptionDate,
MaturityDate = application.MaturityDate,
Nominees = application.Nominees,
TermsAndConditions = application.TermsAndConditions
}, ct);
var documentRef = await _docVault.UploadAsync(
new DocumentUpload
{
EntityType = "POLICY",
EntityId = policyNumber,
DocumentType = "POLICY_BOND",
FileName = $"{policyNumber}_bond.pdf",
Content = pdfBytes,
ContentType = "application/pdf"
}, ct);
await _notification.SendPolicyIssuanceAsync(
application.CustomerId,
policyNumber,
ct);
await _commission.SetupAsync(
application.AgentId,
policyNumber,
application.TotalPremium,
ct);
await _regulatory.RegisterPolicyAsync(
policyNumber,
application,
ct);
return new IssuanceResult
{
PolicyNumber = policyNumber,
DocumentId = documentRef.DocumentId,
IssuedAt = DateTime.UtcNow
};
}
}
13. Policy Loan & Surrender
Policy loans and surrenders are two of the most financially sensitive operations in the insurance platform. A policy loan allows the policyholder to borrow against the surrender value of their policy, typically up to 90% of the surrender value. Surrender is the premature termination of the policy where the policyholder receives the surrender value in exchange for giving up the policy benefits. Both operations require precise calculation of the surrender value based on actuarial factors that vary by product, policy year, and premium payment history.
Surrender Value Calculation
The guaranteed surrender value is typically calculated as a percentage of the total premiums paid, minus the cost of any benefits already enjoyed (such as mortality charges for term coverage). The formula varies by product and regulatory guidelines. For Indian life insurance products, IRDAI mandates a minimum guaranteed surrender value of 30% of total premiums paid (excluding the first year premium and any rider premiums) for policies that have been in force for at least 3 years. Special surrender value, if declared, may be higher and is based on the actuarial valuation of the policy's reserves.
C#
public class SurrenderService
{
private readonly IPolicyRepository _policyRepo;
private readonly IPremiumRepository _premiumRepo;
private readonly IProductRepository _productRepo;
public async Task<SurrenderQuote> CalculateSurrenderValueAsync(
string policyNumber,
CancellationToken ct)
{
var policy = await _policyRepo
.GetByNumberAsync(policyNumber, ct);
ValidateSurrenderEligibility(policy);
var product = await _productRepo
.GetActiveVersionAsync(policy.ProductCode, ct);
var totalPremiumsPaid = await _premiumRepo
.GetTotalPaidAsync(policy.PolicyId, ct);
var yearsInForce = CalculateYearsInForce(policy.InceptionDate);
var yearsPaid = CountPremiumYearsPaid(policy);
var guaranteedSurrenderValue = CalculateGSV(
totalPremiumsPaid,
policy.BasePremium,
policy.SumAssured,
yearsPaid,
product.Surrender);
var specialSurrenderValue = await CalculateSSVAsync(
policy, product, ct);
var applicableSurrenderValue = Math.Max(
guaranteedSurrenderValue,
specialSurrenderValue);
var outstandingLoan = await GetOutstandingLoanAsync(
policy.PolicyId, ct);
var netPayable = applicableSurrenderValue - outstandingLoan;
return new SurrenderQuote
{
PolicyNumber = policyNumber,
TotalPremiumsPaid = totalPremiumsPaid,
GuaranteedSurrenderValue = guaranteedSurrenderValue,
SpecialSurrenderValue = specialSurrenderValue,
ApplicableSurrenderValue = applicableSurrenderValue,
OutstandingLoan = outstandingLoan,
NetPayableAmount = Math.Max(netPayable, 0),
QuoteValidUntil = DateTime.UtcNow.AddDays(15),
SurrenderPenalty = applicableSurrenderValue - totalPremiumsPaid
};
}
private decimal CalculateGSV(
decimal totalPaid,
decimal basePremium,
decimal sumAssured,
int yearsPaid,
SurrenderFactors factors)
{
if (yearsPaid < factors.MinPaidYearsForSurrender)
{
throw new SurrenderNotAllowedException(
$"Minimum {factors.MinPaidYearsForSurrender} " +
$"years required");
}
var firstYearExcluded = basePremium;
var remainingPremiums = totalPaid - firstYearExcluded;
var factor = factors.YearWiseFactors
.Where(f => f.Year == Math.Min(yearsPaid, 20))
.Select(f => f.Factor)
.FirstOrDefault(factors.FirstYearSurrenderFactor);
var gsv = remainingPremiums * factor;
var minGSV = totalPaid * 0.30m;
return Math.Max(gsv, minGSV);
}
private void ValidateSurrenderEligibility(Policy policy)
{
if (policy.PolicyStatus == PolicyStatus.Lapsed)
throw new SurrenderNotAllowedException(
"Lapsed policy cannot be surrendered directly");
if (policy.PolicyStatus == PolicyStatus.Surrendered)
throw new SurrenderNotAllowedException(
"Policy already surrendered");
if (policy.PolicyStatus == PolicyStatus.Claimed)
throw new SurrenderNotAllowedException(
"Claimed policy cannot be surrendered");
}
}
14. Claim Settlement Process
Claim settlement is the ultimate purpose of life insurance — it is when the insurer fulfills its promise to the policyholder's family. The claim process must be handled with extreme care because it involves grieving families, large financial amounts, and strict regulatory timelines. IRDAI mandates that death claims must be settled within 30 days of receiving all required documents, or within 120 days if investigation is required.
Claim Settlement Workflow
C#
public class ClaimService : IClaimService
{
private readonly IClaimRepository _claimRepo;
private readonly IPolicyRepository _policyRepo;
private readonly IBeneficiaryRepository _beneficiaryRepo;
private readonly IDocumentVaultService _docVault;
private readonly IPaymentService _paymentService;
private readonly IEventPublisher _events;
public async Task<ClaimFilingResult> FileClaimAsync(
FileClaimRequest request,
CancellationToken ct)
{
var policy = await _policyRepo
.GetByNumberAsync(request.PolicyNumber, ct);
ValidateClaimFiling(policy, request.ClaimType);
var claim = new Claim
{
ClaimId = Guid.NewGuid(),
ClaimNumber = await GenerateClaimNumberAsync(ct),
PolicyId = policy.PolicyId,
ClaimType = request.ClaimType,
ClaimStatus = ClaimStatus.Filed,
FilingDate = DateOnly.FromDateTime(DateTime.UtcNow),
CauseOfDeath = request.CauseOfDeath,
HospitalDetails = request.HospitalDetails,
CreatedAt = DateTime.UtcNow
};
await _claimRepo.AddAsync(claim, ct);
await _events.PublishAsync(new ClaimFiledEvent
{
ClaimNumber = claim.ClaimNumber,
PolicyNumber = request.PolicyNumber,
ClaimType = request.ClaimType
}, ct);
return new ClaimFilingResult
{
ClaimNumber = claim.ClaimNumber,
RequiredDocuments = GetRequiredDocuments(
request.ClaimType),
FiledAt = DateTime.UtcNow
};
}
public async Task<ClaimSettlementResult> SettleClaimAsync(
string claimNumber,
SettlementApproval approval,
CancellationToken ct)
{
var claim = await _claimRepo
.GetByNumberAsync(claimNumber, ct);
if (claim.ClaimStatus != ClaimStatus.Approved)
throw new InvalidClaimStatusException(
claim.ClaimStatus, ClaimStatus.Approved);
var policy = await _policyRepo
.GetByIdAsync(claim.PolicyId, ct);
var beneficiaries = await _beneficiaryRepo
.GetByPolicyAsync(policy.PolicyId, ct);
var settlementAmount = approval.SettlementAmount;
var payments = new List<BeneficiaryPayment>();
foreach (var beneficiary in beneficiaries)
{
var shareAmount = settlementAmount *
(beneficiary.SharePercentage / 100m);
payments.Add(new BeneficiaryPayment
{
BeneficiaryId = beneficiary.BeneficiaryId,
Name = beneficiary.Name,
BankAccount = await GetBankAccountAsync(
beneficiary.BeneficiaryId, ct),
Amount = shareAmount
});
}
foreach (var payment in payments)
{
await _paymentService.InitiateNEFTAsync(
new NEFTRequest
{
BeneficiaryAccount = payment.BankAccount,
Amount = payment.Amount,
Reference = claim.ClaimNumber,
Remarks = $"Claim settlement - {claim.ClaimNumber}"
}, ct);
}
claim.ClaimStatus = ClaimStatus.Paid;
claim.SettlementAmount = settlementAmount;
claim.PaymentDate = DateOnly.FromDateTime(DateTime.UtcNow);
await _claimRepo.UpdateAsync(claim, ct);
policy.PolicyStatus = PolicyStatus.Claimed;
await _policyRepo.UpdateAsync(policy, ct);
await _events.PublishAsync(new ClaimSettledEvent
{
ClaimNumber = claim.ClaimNumber,
SettlementAmount = settlementAmount,
BeneficiaryCount = payments.Count
}, ct);
return new ClaimSettlementResult
{
ClaimNumber = claim.ClaimNumber,
TotalSettlementAmount = settlementAmount,
BeneficiaryPayments = payments,
SettledAt = DateTime.UtcNow
};
}
}
15. Beneficiary Management
Beneficiary management involves handling nominations, their shares, changes to nominations over the policy lifetime, and the legal complexities of claim distribution. Indian insurance law (Section 39 of the Insurance Act, 1938, as amended) allows policyholders to nominate one or more persons who shall receive the policy proceeds upon death. The nominee can be changed at any time during the policy lifetime by the policyholder, and the change takes effect upon communication to the insurer.
C#
public class BeneficiaryService
{
private readonly IBeneficiaryRepository _beneficiaryRepo;
private readonly IPolicyRepository _policyRepo;
private readonly IEventPublisher _events;
public async Task<BeneficiaryUpdateResult> UpdateNominationAsync(
string policyNumber,
UpdateNominationRequest request,
CancellationToken ct)
{
var policy = await _policyRepo
.GetByNumberAsync(policyNumber, ct);
if (policy.PolicyStatus == PolicyStatus.Claimed)
throw new CannotUpdateNominationException(
"Cannot update nomination on claimed policy");
var totalShare = request.Beneficiaries
.Sum(b => b.SharePercentage);
if (Math.Abs(totalShare - 100m) > 0.01m)
throw new InvalidSharePercentageException(
totalShare);
await using var txn = await _beneficiaryRepo
.BeginTransactionAsync(IsolationLevel.Serializable);
try
{
var existing = await _beneficiaryRepo
.GetByPolicyAsync(policy.PolicyId, ct);
foreach (var ben in existing)
{
ben.IsDeleted = true;
ben.UpdatedAt = DateTime.UtcNow;
}
await _beneficiaryRepo.UpdateRangeAsync(existing, ct);
var newBeneficiaries = request.Beneficiaries.Select(b =>
new Beneficiary
{
BeneficiaryId = Guid.NewGuid(),
PolicyId = policy.PolicyId,
Name = b.Name,
Relationship = b.Relationship,
DateOfBirth = b.DateOfBirth,
SharePercentage = b.SharePercentage,
AadhaarHash = HashAadhaar(b.AadhaarNumber),
IsPrimary = b.IsPrimary,
CreatedAt = DateTime.UtcNow
}).ToList();
await _beneficiaryRepo.AddRangeAsync(
newBeneficiaries, ct);
await txn.CommitAsync(ct);
await _events.PublishAsync(new NominationUpdatedEvent
{
PolicyNumber = policyNumber,
BeneficiaryCount = newBeneficiaries.Count,
UpdatedAt = DateTime.UtcNow
}, ct);
return new BeneficiaryUpdateResult
{
PolicyNumber = policyNumber,
Beneficiaries = newBeneficiaries,
UpdatedAt = DateTime.UtcNow
};
}
catch
{
await txn.RollbackAsync(ct);
throw;
}
}
}
16. Agent Management System
Insurance agents remain the primary distribution channel for life insurance products globally. In India, over 90% of life insurance policies are sold through agents. A comprehensive agent management system must handle agent recruitment and onboarding, commission calculation and disbursement, lead management, training and certification tracking, performance monitoring, and compliance with agency regulations.
Commission structures in life insurance are complex and regulated by IRDAI. First-year commission (FYC) is typically 25-40% of the first year premium, depending on the product type. Renewal commission (RYC) is 5-7.5% of renewal premiums for the duration the policy remains in force. The system must correctly calculate commissions considering clawback provisions (if the policy lapses within a specified period, the agent must return a portion of the FYC), vesting rules (commissions vest gradually over the first 3-5 years), and TDS deductions.
C#
public class CommissionService
{
private readonly ICommissionRepository _commissionRepo;
private readonly IAgentRepository _agentRepo;
private readonly IProductRepository _productRepo;
public async Task<CommissionCalculationResult>
CalculateCommissionAsync(
PremiumPaidEvent premiumEvent,
CancellationToken ct)
{
var policy = await _policyRepo
.GetByIdAsync(premiumEvent.PolicyId, ct);
var agent = await _agentRepo
.GetByIdAsync(policy.AgentId, ct);
var product = await _productRepo
.GetActiveVersionAsync(policy.ProductCode, ct);
var yearsPaid = await _premiumRepo
.GetYearsPaidCountAsync(policy.PolicyId, ct);
var premiumYear = premiumEvent.IsFirstPremium ? 1 : yearsPaid;
var commissionRate = premiumYear switch
{
1 => agent.CommissionRate,
2 => agent.CommissionRate * 0.75m,
3 => agent.CommissionRate * 0.50m,
_ when premiumYear <= 5 => agent.CommissionRate * 0.25m,
_ => product.ProductType == ProductType.ULIP
? 0.01m
: 0.05m
};
var commissionAmount = premiumEvent.Amount * commissionRate;
var tdsAmount = commissionAmount * 0.05m;
var netCommission = commissionAmount - tdsAmount;
if (policy.PolicyStatus == PolicyStatus.Lapsed &&
premiumYear <= 3)
{
var clawbackRate = premiumYear switch
{
1 => 1.0m,
2 => 0.75m,
3 => 0.50m,
_ => 0m
};
var clawbackAmount = commissionAmount * clawbackRate;
return new CommissionCalculationResult
{
AgentId = agent.AgentId,
PolicyNumber = policy.PolicyNumber,
GrossCommission = commissionAmount,
TdsDeduction = tdsAmount,
NetCommission = netCommission,
IsClawback = true,
ClawbackAmount = clawbackAmount,
EffectiveNet = netCommission - clawbackAmount
};
}
var commission = new CommissionRecord
{
AgentId = agent.AgentId,
PolicyId = policy.PolicyId,
PremiumYear = premiumYear,
GrossAmount = commissionAmount,
TdsAmount = tdsAmount,
NetAmount = netCommission,
Status = CommissionStatus.Accrued,
CalculatedAt = DateTime.UtcNow
};
await _commissionRepo.AddAsync(commission, ct);
return new CommissionCalculationResult
{
AgentId = agent.AgentId,
PolicyNumber = policy.PolicyNumber,
GrossCommission = commissionAmount,
TdsDeduction = tdsAmount,
NetCommission = netCommission,
IsClawback = false
};
}
}
17. Customer Portal & Self-Service
The customer portal is the digital front door to the insurance platform. It must provide a comprehensive self-service experience that reduces the need for customer service calls while maintaining security for sensitive financial operations. The portal must allow customers to view all their policies, download premium receipts and policy documents, initiate premium payments, file and track claims, update contact details, manage nominees, and apply for policy loans.
Security is paramount in the customer portal because it exposes financial data and enables transactions. Multi-factor authentication (MFA) is mandatory for all transactional operations. View-only operations (policy status, premium schedule) may use session-based authentication after initial login. Transactional operations (payments, surrender, claim filing) require re-authentication using OTP sent to the registered mobile number. Session tokens must expire after 15 minutes of inactivity for the web portal and 5 minutes for the mobile app.
The portal must also support accessibility requirements (WCAG 2.1 AA compliance) and be fully functional on low-bandwidth mobile connections common in tier-2 and tier-3 Indian cities. This means implementing progressive web app (PWA) patterns with offline capability for viewing cached policy data, lazy loading of images and documents, and compressed API responses using Brotli encoding.
Portal Features Matrix
| Feature | Auth Required | MFA Required | Available On |
|---|---|---|---|
| View policy status | Login | No | Web, Mobile |
| Download policy bond | Login | No | Web, Mobile |
| Download premium receipt | Login | No | Web, Mobile |
| Pay premium | Login | Yes (OTP) | Web, Mobile |
| Update contact details | Login | Yes (OTP) | Web, Mobile |
| Change nominee | Login | Yes (OTP) | Web, Mobile |
| File death claim | Login | Yes (OTP) | Web, Mobile |
| Apply for policy loan | Login | Yes (OTP + MFA) | Web only |
| Request surrender | Login | Yes (OTP + MFA) | Web only |
| Revive lapsed policy | Login | Yes (OTP) | Web, Mobile |
18. Document Vault
The document vault handles storage, retrieval, and lifecycle management of all documents generated and consumed by the insurance platform. This includes policy bonds, premium receipts, claim forms, medical reports, KYC documents, endorsement letters, maturity certificates, and regulatory filings. Documents must be stored with versioning, access control, audit trails, and compliance with IRDAI data retention requirements.
Storage architecture uses a tiered approach: hot storage (SSD-backed object storage) for documents accessed within the last 2 years, warm storage (standard object storage) for documents accessed within the last 5 years, and cold storage (glacier or archive class) for older documents. All documents are encrypted at rest using AES-256 with customer-specific encryption keys. Document checksums are verified on every retrieval to detect corruption.
C#
public class DocumentVaultService : IDocumentVaultService
{
private readonly IBlobStorage _blobStorage;
private readonly IDocumentRepository _docRepo;
private readonly IEncryptionService _encryption;
private readonly IAuditLogger _auditLogger;
public async Task<DocumentReference> UploadAsync(
DocumentUpload upload,
CancellationToken ct)
{
var checksum = ComputeSHA256(upload.Content);
var encryptedContent = await _encryption.EncryptAsync(
upload.Content,
GetCustomerKey(upload.EntityId),
ct);
var storageKey = GenerateStorageKey(
upload.EntityType,
upload.EntityId,
upload.DocumentType,
upload.FileName);
await _blobStorage.UploadAsync(
storageKey,
encryptedContent,
upload.ContentType,
ct);
var document = new DocumentRecord
{
DocumentId = Guid.NewGuid(),
EntityType = upload.EntityType,
EntityId = upload.EntityId,
DocumentType = upload.DocumentType,
FileName = upload.FileName,
FileSize = upload.Content.Length,
MimeType = upload.ContentType,
StorageKey = storageKey,
ChecksumSha256 = checksum,
UploadedBy = upload.UploadedBy,
CreatedAt = DateTime.UtcNow
};
await _docRepo.AddAsync(document, ct);
await _auditLogger.LogAsync(new AuditEntry
{
Action = "DOCUMENT_UPLOADED",
EntityType = upload.EntityType,
EntityId = upload.EntityId,
Details = new { document.DocumentId, upload.FileName }
}, ct);
return new DocumentReference
{
DocumentId = document.DocumentId,
FileName = upload.FileName,
UploadedAt = document.CreatedAt
};
}
public async Task<Stream> DownloadAsync(
Guid documentId,
string requestedBy,
CancellationToken ct)
{
var document = await _docRepo.GetByIdAsync(documentId, ct);
var content = await _blobStorage.DownloadAsync(
document.StorageKey, ct);
var decryptedContent = await _encryption.DecryptAsync(
content,
GetCustomerKey(document.EntityId),
ct);
var checksum = ComputeSHA256(decryptedContent);
if (checksum != document.ChecksumSha256)
{
throw new DocumentCorruptionException(
document.DocumentId);
}
await _auditLogger.LogAsync(new AuditEntry
{
Action = "DOCUMENT_DOWNLOADED",
EntityType = document.EntityType,
EntityId = document.EntityId,
UserId = requestedBy,
Details = new { document.DocumentId }
}, ct);
return new MemoryStream(decryptedContent);
}
}
19. Regulatory Compliance (IRDAI)
IRDAI compliance is not optional — it is a fundamental architectural constraint that must be designed into every subsystem. The platform must generate regulatory filings automatically, maintain auditable records for every transaction, enforce product approval workflows, and implement IRDAI-mandated customer protection features like free-look cancellation periods and guaranteed surrender values.
Key IRDAI Regulations
| Regulation | Requirement | System Implementation |
|---|---|---|
| Free Look Period | 15-30 days to cancel policy after issuance | Automatic free-look tracking, no-questions-asked cancellation |
| Claim Settlement | Within 30 days (simple) or 120 days (investigated) | SLA tracking, escalation alerts, auto-penalty calculation |
| Solvency Margin | Minimum 150% solvency ratio | Real-time solvency dashboard, reserve calculations |
| Policy Loan Interest | Maximum rate prescribed by IRDAI | Configurable interest rates per regulatory directive |
| Premium Payment Grace | 30 days grace period for annual/semi-annual | Grace period calculation, lapse prevention alerts |
| Mis-selling Prevention | Needs-based analysis before sale | Mandatory need analysis form in agent workflow |
| Data Localization | All data within Indian borders | India-only cloud regions, no cross-border data transfer |
| Annual Statements | Policy statement to every policyholder annually | Batch job for annual statement generation and delivery |
Automated Regulatory Reporting
C#
public class RegulatoryReportingService
{
private readonly IPolicyRepository _policyRepo;
private readonly IClaimRepository _claimRepo;
private readonly IPremiumRepository _premiumRepo;
private readonly IIRDAILFilingService _filingService;
public async Task<QuarterlyReturnResult>
GenerateQuarterlyReturnAsync(
int year, int quarter,
CancellationToken ct)
{
var startDate = GetQuarterStart(year, quarter);
var endDate = GetQuarterEnd(year, quarter);
var newPoliciesCount = await _policyRepo
.CountNewPoliciesAsync(startDate, endDate, ct);
var totalSumAssured = await _policyRepo
.GetTotalSumAssuredAsync(startDate, endDate, ct);
var claimsFiled = await _claimRepo
.CountClaimsAsync(startDate, endDate, ct);
var claimsSettled = await _claimRepo
.CountSettledClaimsAsync(startDate, endDate, ct);
var claimsRepudiated = await _claimRepo
.CountRepudiatedClaimsAsync(startDate, endDate, ct);
var claimSettlementAmount = await _claimRepo
.GetTotalSettlementAmountAsync(
startDate, endDate, ct);
var premiumsCollected = await _premiumRepo
.GetTotalCollectedAsync(startDate, endDate, ct);
var lapsedPolicies = await _policyRepo
.CountLapsedPoliciesAsync(startDate, endDate, ct);
var returnData = new QuarterlyReturn
{
ReportingPeriod = $"{year}-Q{quarter}",
InsurerCode = "INS001",
NewPoliciesIssued = newPoliciesCount,
TotalSumAssured = totalSumAssured,
ClaimsFiled = claimsFiled,
ClaimsSettled = claimsSettled,
ClaimsRepudiated = claimsRepudiated,
ClaimSettlementAmount = claimSettlementAmount,
PremiumsCollected = premiumsCollected,
PoliciesLapsed = lapsedPolicies,
ClaimSettlementRatio = claimsFiled > 0
? (decimal)claimsSettled / claimsFiled * 100
: 100m,
GeneratedAt = DateTime.UtcNow
};
await _filingService.SubmitQuarterlyReturnAsync(
returnData, ct);
return new QuarterlyReturnResult
{
ReturnData = returnData,
SubmittedAt = DateTime.UtcNow
};
}
}
20. Multi-Channel Distribution
Life insurance products are sold through multiple channels: individual agents, bancassurance (bank partners), corporate agents, brokers, online direct (website and mobile app), and telemarketing. Each channel has different commission structures, lead management requirements, and reporting needs. The platform must support a unified product catalog and pricing engine while maintaining channel-specific workflows and commission calculations.
Bancassurance partnerships require API integrations with partner banks where bank employees can generate quotes and submit applications through the bank's own interface. This means the insurance platform must expose a partner API with appropriate authentication (OAuth 2.0 with partner-specific client IDs), rate limiting per partner, and white-label capabilities where the insurance branding is replaced with the bank's branding on generated documents.
Channel Comparison
| Channel | Commission | Lead Source | API Requirements | Growth Rate |
|---|---|---|---|---|
| Individual Agent | 25-40% FYC | Agent's network | Agent portal, mobile app | Stable |
| Bancassurance | 15-25% FYC | Bank customers | Partner API, white-label | High |
| Corporate Agent | 15-30% FYC | Corporate employees | Group portal, bulk upload | Medium |
| Broker | 10-20% FYC | Broker's clients | Broker API, comparison tools | Medium |
| Online Direct | 0% (no middleman) | Digital marketing | Web, mobile app | Very High |
| Telemarketing | 10-15% FYC | Purchased leads | Call center integration | Declining |
The multi-channel architecture uses a channel abstraction layer that maps channel-specific operations to a common set of domain services. For example, a quote request from a bancassurance partner goes through the same QuoteEngine as a direct online request, but the channel context determines which product variants are available, which commission rates apply, and how the generated documents are branded.
21. Renewal & Revival
Renewal management is the process of collecting periodic premiums (quarterly, semi-annual, or monthly) after the first premium. The system must proactively remind customers of upcoming premiums, handle payment failures gracefully, manage grace periods, and process policy lapses when premiums remain unpaid beyond the grace period. Revival is the process of reinstating a lapsed policy by paying all overdue premiums along with interest and potentially providing evidence of continued insurability.
Renewal Timeline
C#
public class RenewalService
{
private readonly IPolicyRepository _policyRepo;
private readonly INotificationService _notification;
private readonly IPremiumScheduleRepository _scheduleRepo;
public async Task ProcessRenewalBatchAsync(
DateOnly processingDate,
CancellationToken ct)
{
var duePolicies = await _policyRepo
.GetPoliciesWithDuePremiumsAsync(processingDate, ct);
foreach (var policy in duePolicies)
{
var schedule = await _scheduleRepo
.GetNextDueScheduleAsync(
policy.PolicyId, processingDate, ct);
if (schedule == null) continue;
var daysSinceDue = processingDate
.DayNumber - schedule.DueDate.DayNumber;
switch (daysSinceDue)
{
case < 0:
await SendAdvanceReminderAsync(
policy, schedule, Math.Abs(daysSinceDue), ct);
break;
case 0:
await SendDueNotificationAsync(
policy, schedule, ct);
break;
case <= 30:
await SendGracePeriodReminderAsync(
policy, schedule, daysSinceDue, ct);
break;
case 31:
await ProcessLapseAsync(
policy, schedule, ct);
break;
}
}
}
private async Task ProcessLapseAsync(
Policy policy,
PremiumSchedule schedule,
CancellationToken ct)
{
policy.PolicyStatus = PolicyStatus.Lapsed;
policy.RevivalDeadline = DateOnly.FromDateTime(
DateTime.UtcNow.AddYears(2));
policy.UpdatedAt = DateTime.UtcNow;
policy.LastPremiumDate = schedule.PaidDate;
await _policyRepo.UpdateAsync(policy, ct);
await _notification.SendLapseNotificationAsync(
policy, ct);
}
public async Task<RevivalResult> RevivePolicyAsync(
string policyNumber,
CancellationToken ct)
{
var policy = await _policyRepo
.GetByNumberAsync(policyNumber, ct);
if (policy.PolicyStatus != PolicyStatus.Lapsed)
throw new InvalidPolicyStatusException(
"Only lapsed policies can be revived");
if (policy.RevivalDeadline < DateOnly.FromDateTime(
DateTime.UtcNow))
throw new RevivalDeadlineExpiredException(
policy.RevivalDeadline);
var overdueAmounts = await _scheduleRepo
.GetOverdueAmountsAsync(policy.PolicyId, ct);
var revivalCharge = overdueAmounts.TotalOverdue * 0.05m;
var gstOnCharge = revivalCharge * 0.18m;
var totalRevivalAmount = overdueAmounts.TotalOverdue +
revivalCharge + gstOnCharge;
return new RevivalResult
{
PolicyNumber = policyNumber,
OverduePremiums = overdueAmounts.TotalOverdue,
RevivalCharge = revivalCharge,
GST = gstOnCharge,
TotalAmount = totalRevivalAmount,
PaymentDeadline = DateOnly.FromDateTime(
DateTime.UtcNow.AddDays(15))
};
}
}
22. Analytics & Actuarial Reporting
The analytics platform must serve two distinct audiences: business analysts who need operational dashboards (daily premium collection, claim settlement ratios, agent performance) and actuaries who need deep statistical analysis (mortality experience studies, reserve adequacy testing, product profitability analysis). The data pipeline must maintain the integrity of financial data while providing real-time and batch analytics capabilities.
Key Metrics Dashboard
| Metric | Definition | Target | Alert Threshold |
|---|---|---|---|
| Claim Settlement Ratio | Claims settled / Claims filed | > 98% | < 95% |
| Persistency Ratio (13th month) | Policies still active after 13 months | > 75% | < 70% |
| Persistency Ratio (61st month) | Policies still active after 61 months | > 55% | < 50% |
| Average Claim Settlement Time | Days from filing to payment | < 15 days | > 25 days |
| Premium Collection Efficiency | Premiums collected / Premiums due | > 99% | < 97% |
| Digital Adoption Rate | Online policies / Total policies | > 40% | < 25% |
| Cost of Acquisition | Total acquisition cost / New premium | < 60% | > 75% |
| Solvency Ratio | Available solvency / Required | > 150% | < 160% |
Actuarial reporting requires a dedicated data warehouse separate from the operational database. The ETL pipeline extracts daily snapshots of policy data, premium transactions, and claim outcomes into a star schema optimized for actuarial queries. Mortality experience analysis requires joining policy data with claim data and computing age-specific death rates by product, gender, smoking status, and policy duration. These analyses directly inform future product pricing and reserve adequacy.
23. Cost Estimation
Monthly Cost Breakdown (AWS / Azure)
| Component | Specification | Monthly Cost (USD) | Notes |
|---|---|---|---|
| Application Servers | 8x m6i.xlarge (32 vCPU, 128GB) | $4,480 | Auto-scaling group |
| PostgreSQL (RDS) | db.r6g.2xlarge, Multi-AZ | $2,800 | With read replicas |
| Redis Cluster | 3x cache.r6g.xlarge | $1,800 | For quotes, sessions |
| MongoDB | M10 cluster, 3 nodes | $1,200 | Document metadata |
| S3 / Blob Storage | 5TB standard + 10TB IA | $450 | Policy documents |
| Kafka (MSK) | kafka.m5.2xlarge, 3 brokers | $2,400 | Event streaming |
| Elasticsearch | 3x m6i.xlarge.search | $1,500 | Logs, analytics |
| CDN (CloudFront) | 10TB transfer | $850 | Web assets, documents |
| Load Balancer | 2x ALB | $200 | Application load balancers |
| Monitoring | Datadog / CloudWatch | $600 | APM, logs, alerts |
| WAF | AWS WAF | $200 | API protection |
| Backup & DR | Cross-region replication | $800 | Disaster recovery |
| Total | $17,280 |
The infrastructure cost of approximately $17,000 per month supports 5 million active policies with 99.95% availability. The cost per policy per month is approximately $0.0034 or about ₹0.28, which is negligible compared to the average annual premium of ₹30,000-50,000 per policy. The most cost-effective optimization is aggressive caching of product configurations and premium calculations, which reduces database load by 60% and allows smaller database instances.
24. Testing Strategy
Actuarial Precision Testing
Testing a life insurance platform requires special attention to financial precision. Every premium calculation must match the expected value to the penny. Actuarial testing involves running the premium engine against known actuarial tables and expected results for hundreds of test cases across different products, ages, sum assured levels, and premium modes. Any deviation of more than ₹0.01 triggers an automatic failure.
C#
[TestClass]
public class QuoteEngineTests
{
private QuoteEngine _engine;
[TestInitialize]
public void Setup()
{
_engine = new QuoteEngine(
new MockMortalityTableService(),
new MockProductRepository(),
new MockBonusProjectionService());
}
[TestMethod]
public async Task CalculateQuote_JeevanAnand_30Male_ShouldMatchActuarialTable()
{
var request = new QuoteRequest
{
ProductCode = "JEEVAN_ANAND_815",
SumAssured = 5_000_000m,
PolicyTerm = 25,
PremiumTerm = 20,
PremiumMode = "ANNUAL",
DateOfBirth = new DateOnly(1996, 7, 13),
Gender = "MALE",
Smoker = false,
Riders = new List<RiderSelection>(),
State = "MAHARASHTRA"
};
var result = await _engine.CalculateQuoteAsync(
request, CancellationToken.None);
Assert.AreEqual(297450.00m, result.BasePremium,
"Base premium must match actuarial table");
Assert.AreEqual(370951.00m, result.TotalPayable,
"Total payable including GST must be exact");
}
[TestMethod]
public async Task CalculateQuote_ElderlyFemale_ShouldApplyCorrectMortalityRate()
{
var request = new QuoteRequest
{
ProductCode = "TERM_PLI_854",
SumAssured = 1_000_000m,
PolicyTerm = 10,
PremiumTerm = 10,
PremiumMode = "ANNUAL",
DateOfBirth = new DateOnly(1966, 1, 15),
Gender = "FEMALE",
Smoker = false,
Riders = new List<RiderSelection>(),
State = "DELHI"
};
var result = await _engine.CalculateQuoteAsync(
request, CancellationToken.None);
Assert.IsTrue(result.BasePremium > 0,
"Premium must be positive");
Assert.IsTrue(result.TotalPayable < result.BasePremium * 1.2m,
"GST should not exceed 20% of base");
}
[TestMethod]
public async Task CalculateQuote_WithSmoker_ShouldApplySmokerLoading()
{
var nonSmokerRequest = CreateBaseRequest(smoker: false);
var smokerRequest = CreateBaseRequest(smoker: true);
var nonSmokerResult = await _engine.CalculateQuoteAsync(
nonSmokerRequest, CancellationToken.None);
var smokerResult = await _engine.CalculateQuoteAsync(
smokerRequest, CancellationToken.None);
Assert.IsTrue(smokerResult.BasePremium >
nonSmokerResult.BasePremium,
"Smoker premium must be higher than non-smoker");
var loadingFactor = smokerResult.BasePremium /
nonSmokerResult.BasePremium;
Assert.IsTrue(loadingFactor >= 1.2m &&
loadingFactor <= 1.5m,
"Smoker loading should be between 20% and 50%");
}
[TestMethod]
[ExpectedException(typeof(EligibilityException))]
public async Task CalculateQuote_Underage_ShouldReject()
{
var request = new QuoteRequest
{
ProductCode = "JEEVAN_ANAND_815",
DateOfBirth = DateOnly.FromDateTime(
DateTime.Today.AddYears(-16)),
};
await _engine.CalculateQuoteAsync(
request, CancellationToken.None);
}
[TestMethod]
public async Task CalculateQuote_PremiumModes_ShouldMatchModalFactors()
{
var baseRequest = CreateBaseRequest();
var results = new Dictionary<string, decimal>();
foreach (var mode in new[] {
"ANNUAL", "SEMI_ANNUAL", "QUARTERLY", "MONTHLY" })
{
var request = baseRequest with
{ PremiumMode = mode };
var result = await _engine.CalculateQuoteAsync(
request, CancellationToken.None);
results[mode] = result.TotalPayable;
}
Assert.IsTrue(results["ANNUAL"] <
results["SEMI_ANNUAL"] * 2.1m,
"Annual should be cheaper than 2x semi-annual");
Assert.IsTrue(results["SEMI_ANNUAL"] <
results["QUARTERLY"] * 2.1m,
"Semi-annual should be cheaper than 2x quarterly");
}
}
Integration Test Coverage
| Test Category | Scope | Target Coverage | Key Scenarios |
|---|---|---|---|
| Unit Tests | Premium calculation, state machine | 90% | All product types, edge cases |
| Integration Tests | Service-to-service | 80% | End-to-end policy lifecycle |
| Actuarial Tests | Premium accuracy | 100% | 1000+ test cases per product |
| Payment Tests | Payment gateway | 100% | All payment modes, failures, retries |
| Security Tests | Authentication, authorization | 100% | MFA, session management, injection |
| Performance Tests | Load and stress testing | N/A | Peak premium collection load |
| Compliance Tests | IRDAI regulatory rules | 100% | All regulatory constraints |
25. Interview Q&A
Architecture & Design
Q1: How would you design the premium collection system to handle 1 million payments per day during peak season without data loss?
Answer: I would use a multi-layer approach. First, the API Gateway accepts payment initiation requests and persists them to a durable message queue (Kafka) before returning a session ID to the customer. This ensures that even if the payment service crashes after receiving the request, the payment intent is never lost. The payment service consumes from the Kafka topic, creates orders with the payment gateway, and persists the gateway order ID. When the payment gateway webhook arrives, it is processed idempotently against the persisted session. For reconciliation, a daily batch job compares the insurer's payment records with the gateway's settlement report to identify discrepancies. All financial records are written with serializable isolation to prevent double-crediting.
Q2: How would you handle the scenario where a policy was incorrectly priced due to a bug in the premium engine?
Answer: This is a critical scenario because insurance policies are legally binding contracts. The approach depends on the magnitude of the error and when it was discovered. For small rounding errors (a few paise), the insurer would typically honor the incorrectly calculated premium for the policy's lifetime as the cost of correction exceeds the error. For significant pricing errors, the insurer must follow IRDAI's dispute resolution process, which may involve adjusting the policy or offering a corrected premium with the policyholder's consent. The system must track the original quoted premium, any adjustments, and the reasoning for audit purposes. Implementing a versioned premium engine where the version used at policy inception is snapshot with the policy record ensures historical accuracy.
Q3: Design the claim settlement system to process claims within 30 days as mandated by IRDAI.
Answer: The key is designing a parallel processing pipeline rather than sequential. When a claim is filed, the system simultaneously initiates document verification, policy status verification, and beneficiary verification in parallel. A workflow engine (like Temporal or MassTransit) orchestrates the overall process, with escalation timers that trigger at day 15 (warning), day 25 (critical alert to manager), and day 28 (executive escalation). If any step requires additional information from the claimant, the system pauses that branch of the workflow while continuing others. The settlement payment is pre-approved for amounts below the underwriter's threshold, enabling straight-through processing for straightforward claims. Complex claims requiring investigation follow a separate path with a 120-day timeline.
Q4: How would you ensure data consistency across the policy, premium, and claim services?
Answer: Strong consistency within each service boundary is achieved using serializable transactions in PostgreSQL. Cross-service consistency uses the saga pattern with compensating transactions. For example, when processing a claim settlement, the claim service first reserves the settlement amount against the policy's reserve, then initiates the payment, and finally confirms the settlement. If the payment fails, a compensating transaction releases the reserved amount. The saga state machine is persisted in the event store, enabling automatic retry and recovery. For scenarios requiring absolute consistency (like premium payment crediting), the policy and premium services can use the same PostgreSQL database with cross-service foreign key references, accepting the tighter coupling for financial accuracy.
System Design
Q5: How would you design the system to support 100 different insurance products with varying benefit structures?
Answer: Rather than building product-specific logic for each product, I would design a product configuration engine where the benefit structure, premium calculation rules, and surrender factors are all data-driven. Each product has a configuration document (stored in MongoDB or PostgreSQL JSONB column) that defines the benefit type, riders, loading factors, and calculation formulas. The premium engine interprets this configuration at runtime. For products with non-standard benefit structures (like ULIPs with fund switching), the configuration includes custom calculation hooks that invoke product-specific C# classes. This approach allows product managers to configure new products through an admin interface, while developers only need to write code for genuinely new calculation patterns.
Q6: How would you handle the free-look period where a customer can cancel within 15-30 days?
Answer: When a policy is issued, the system records the issuance date and calculates the free-look end date (typically 15 days for online policies, 30 days for agent-sold policies as per IRDAI guidelines). During the free-look period, the policy status is FREE_LOOK rather than ACTIVE. If the customer initiates cancellation, the system refunds the entire premium paid minus a proportionate risk charge for the coverage period (typically the mortality charge for the days the policy was in force). The refund is processed to the original payment mode. If no cancellation request is received by the free-look end date, the system transitions the policy to ACTIVE status. The admin dashboard shows all policies currently in the free-look period for proactive customer engagement.
Q7: Design the system to handle policy revivals for millions of lapsed policies.
Answer: Revival is a batch-friendly operation because lapsed policies have a 2-year revival window and don't require real-time processing. The system maintains a revival queue (Kafka topic) that is populated when policies lapse. A revival processing service consumes from this queue and calculates revival amounts (overdue premiums + interest + GST). Customer outreach is automated: SMS and email reminders at 30, 90, 180, 365, and 730 days post-lapse. For self-service revival, the customer portal provides a revival calculator that shows the exact amount needed. The payment is processed through the same premium collection infrastructure. After the revival payment is confirmed, the policy status transitions from LAPSED back to ACTIVE, and the premium schedule is recalculated. Medical re-examination may be required for policies lapsed beyond 6 months, adding a workflow step before revival confirmation.
Q8: How would you design the document generation system to create thousands of policy bonds per day?
Answer: PDF generation is CPU-intensive, so I would use a distributed generation pipeline. Templates are stored in a versioned template repository and cached in Redis. The generation service pulls policy data from the policy service, merges it with the template, and produces the PDF using a library like QuestPDF or iTextSharp. To handle peak loads, I would use a worker pool with auto-scaling: a Kafka topic receives generation requests, and worker pods consume and process them in parallel. For efficiency, frequently used template sections are pre-rendered and cached. The generated PDFs are immediately uploaded to S3 and the document reference is stored in the document vault. The worker pods can scale from 2 to 50 instances based on queue depth, handling burst loads of 10,000 PDFs per hour.