tags: - examples - business-rules - insurance - calculations - tutorial
Business Rules¶
This guide demonstrates complex, real-world business rule implementations in EverSharp, combining multiple language concepts to solve practical problems.
Insurance Premium Calculation¶
Basic Premium with Risk Factors¶
Calculate insurance premium based on multiple risk factors:
function calculateAutoPremium(driver, vehicle, coverage) {
// Base premium
basePremium =
coverage.liability + coverage.collision + coverage.comprehensive;
// Age factor
if (driver.age < 25) {
ageFactor = 1.5;
} else if (driver.age < 30) {
ageFactor = 1.2;
} else if (driver.age < 65) {
ageFactor = 1.0;
} else {
ageFactor = 1.3;
}
// Driving record factor
if (driver.accidents > 2) {
recordFactor = 1.8;
} else if (driver.accidents > 0) {
recordFactor = 1.3;
} else {
recordFactor = 0.9; // Clean record discount
}
// Vehicle factor
if (vehicle.year >= 2020) {
vehicleFactor = 1.1; // Newer cars cost more
} else if (vehicle.year >= 2010) {
vehicleFactor = 1.0;
} else {
vehicleFactor = 0.8; // Older cars cost less
}
// Calculate final premium
premium = basePremium * ageFactor * recordFactor * vehicleFactor;
return {
"basePremium": basePremium,
"ageFactor": ageFactor,
"recordFactor": recordFactor,
"vehicleFactor": vehicleFactor,
"totalPremium": $Round(premium * 100) / 100
};
}
// Example usage
driver = {
"age": 28,
"accidents": 0,
"yearsLicensed": 10
};
vehicle = {
"make": "Toyota",
"model": "Camry",
"year": 2022
};
coverage = {
"liability": 500,
"collision": 300,
"comprehensive": 200
};
result = calculateAutoPremium(driver, vehicle, coverage);
// Result: {
// basePremium: 1000,
// ageFactor: 1.2,
// recordFactor: 0.9,
// vehicleFactor: 1.1,
// totalPremium: 1188
// }
Multi-Policy Discount¶
Calculate discounts for multiple policies:
function calculateMultiPolicyDiscount(policies) {
// Count active policies
activeCount = policies.Filter((p) => p.status == "Active").Length;
// Determine discount rate
if (activeCount >= 4) {
discountRate = 0.2; // 20% for 4+ policies
} else if (activeCount >= 3) {
discountRate = 0.15; // 15% for 3 policies
} else if (activeCount >= 2) {
discountRate = 0.1; // 10% for 2 policies
} else {
discountRate = 0; // No discount for 1 policy
}
// Calculate total premium before and after discount
totalPremium = policies
.Filter((p) => p.status == "Active")
.Map((p) => p.premium)
.ReduceToNum((sum, p) => sum + p);
discountAmount = totalPremium * discountRate;
finalPremium = totalPremium - discountAmount;
return {
"policyCount": activeCount,
"totalPremium": totalPremium,
"discountRate": discountRate,
"discountAmount": discountAmount,
"finalPremium": finalPremium
};
}
policies = [
{ "id": 1, "type": "Auto", "premium": 1200, "status": "Active" },
{ "id": 2, "type": "Home", "premium": 1500, "status": "Active" },
{ "id": 3, "type": "Life", "premium": 800, "status": "Active" }
];
discount = calculateMultiPolicyDiscount(policies);
// Result: {
// policyCount: 3,
// totalPremium: 3500,
// discountRate: 0.15,
// discountAmount: 525,
// finalPremium: 2975
// }
Eligibility Rules¶
Age-Based Eligibility¶
Complex eligibility check with multiple criteria:
function checkPolicyEligibility(applicant, policyType) {
errors = [];
// Calculate age
age = $DifferenceInYears(applicant.birthDate, $Today());
// Age requirements vary by policy type
if (policyType == "Auto") {
if (age < 16) {
errors.Push("Must be at least 16 years old for auto insurance");
}
} else if (policyType == "Life") {
if (age < 18) {
errors.Push("Must be at least 18 years old for life insurance");
} else if (age > 80) {
errors.Push("Maximum age for new life insurance is 80");
}
} else if (policyType == "Home") {
if (age < 18) {
errors.Push("Must be at least 18 years old for home insurance");
}
}
// Credit score requirement
if (applicant.creditScore < 600) {
errors.Push("Credit score must be at least 600");
}
// Income requirement for high-value policies
if (policyType == "Life" && applicant.requestedCoverage > 1000000) {
if (applicant.annualIncome < 100000) {
errors.Push("Income requirement not met for coverage over $1M");
}
}
// Existing policy conflicts
if (applicant.hasActivePolicyOfType) {
errors.Push("Active policy of this type already exists");
}
return {
"eligible": errors.Length == 0,
"age": age,
"errors": errors
};
}
applicant = {
"name": "John Smith",
"birthDate": $ToDate("1990-05-15"),
"creditScore": 720,
"annualIncome": 85000,
"requestedCoverage": 500000,
"hasActivePolicyOfType": false
};
eligibility = checkPolicyEligibility(applicant, "Life");
// Result: { eligible: true, age: 34, errors: [] }
Geographic Risk Assessment¶
Determine risk level based on location:
function assessGeographicRisk(address, policyType) {
// High-risk zip codes (example data)
highRiskZips = ["90001", "90002", "90003"];
// Flood zones
floodZones = ["Zone A", "Zone AE", "Zone VE"];
riskFactors = [];
riskMultiplier = 1.0;
// Check zip code risk
isHighRiskZip = highRiskZips.Any((zip) => zip == address.zipCode);
if (isHighRiskZip) {
riskFactors.Push("High-risk zip code");
riskMultiplier = riskMultiplier * 1.3;
}
// Check flood zone
if (policyType == "Home") {
isFloodZone = floodZones.Any((zone) => zone == address.floodZone);
if (isFloodZone) {
riskFactors.Push("Flood zone");
riskMultiplier = riskMultiplier * 1.5;
}
}
// Urban vs rural
if (address.population > 100000) {
riskFactors.Push("Urban area");
if (policyType == "Auto") {
riskMultiplier = riskMultiplier * 1.2;
}
}
// Determine risk level
if (riskMultiplier >= 1.5) {
riskLevel = "High";
} else if (riskMultiplier >= 1.2) {
riskLevel = "Medium";
} else {
riskLevel = "Low";
}
return {
"riskLevel": riskLevel,
"riskMultiplier": riskMultiplier,
"riskFactors": riskFactors
};
}
address = {
"street": "123 Main St",
"city": "Los Angeles",
"zipCode": "90001",
"floodZone": "Zone X",
"population": 250000
};
risk = assessGeographicRisk(address, "Auto");
// Result: {
// riskLevel: "High",
// riskMultiplier: 1.56,
// riskFactors: ["High-risk zip code", "Urban area"]
// }
Claims Processing¶
Claim Validation¶
Validate claim against policy terms:
function validateClaim(claim, policy) {
errors = [];
warnings = [];
// Check policy is active
if (policy.status != "Active") {
errors.Push("Policy is not active");
}
// Check claim date is within policy period
isAfterStart =
$DifferenceInDays(policy.effectiveDate, claim.incidentDate) >= 0;
isBeforeEnd =
$DifferenceInDays(claim.incidentDate, policy.expirationDate) >= 0;
if (!isAfterStart || !isBeforeEnd) {
errors.Push("Incident date outside policy coverage period");
}
// Check claim amount against coverage limit
if (claim.amount > policy.coverageLimit) {
errors.Push("Claim amount exceeds coverage limit");
}
// Check deductible
if (claim.amount <= policy.deductible) {
warnings.Push("Claim amount does not exceed deductible");
}
// Check for duplicate claims
daysSinceLastClaim = $DifferenceInDays(
policy.lastClaimDate,
claim.incidentDate
);
if (daysSinceLastClaim < 30) {
warnings.Push("Multiple claims within 30 days - requires review");
}
// Calculate payout
if (errors.Length == 0) {
payableAmount = claim.amount - policy.deductible;
if (payableAmount < 0) {
payableAmount = 0;
}
if (payableAmount > policy.coverageLimit) {
payableAmount = policy.coverageLimit;
}
} else {
payableAmount = 0;
}
return {
"valid": errors.Length == 0,
"payableAmount": payableAmount,
"errors": errors,
"warnings": warnings
};
}
policy = {
"id": 12345,
"status": "Active",
"effectiveDate": $ToDate("2024-01-01"),
"expirationDate": $ToDate("2024-12-31"),
"coverageLimit": 50000,
"deductible": 1000,
"lastClaimDate": $ToDate("2024-03-15")
};
claim = {
"id": 67890,
"incidentDate": $ToDate("2024-06-15"),
"amount": 5000,
"description": "Vehicle collision"
};
validation = validateClaim(claim, policy);
// Result: {
// valid: true,
// payableAmount: 4000,
// errors: [],
// warnings: []
// }
Claims History Impact¶
Calculate rate increase based on claims history:
function calculateRateAdjustment(policy, claims) {
// Filter claims to last 3 years
cutoffDate = $AddYears($Today(), -3);
recentClaims = claims.Filter(
(c) => $DifferenceInDays(cutoffDate, c.date) >= 0
);
// Count claims
claimCount = recentClaims.Length;
// Sum claim amounts
totalClaimed = recentClaims
.Map((c) => c.amount)
.ReduceToNum((sum, amt) => sum + amt);
// Determine rate adjustment
if (claimCount == 0) {
adjustment = 0.95; // 5% discount for no claims
reason = "No claims discount";
} else if (claimCount == 1 && totalClaimed < 2000) {
adjustment = 1.0; // No change
reason = "Single small claim";
} else if (claimCount == 1) {
adjustment = 1.15; // 15% increase
reason = "One claim";
} else if (claimCount == 2) {
adjustment = 1.3; // 30% increase
reason = "Multiple claims";
} else {
adjustment = 1.5; // 50% increase
reason = "Excessive claims";
}
newPremium = policy.currentPremium * adjustment;
return {
"currentPremium": policy.currentPremium,
"adjustment": adjustment,
"newPremium": $Round(newPremium * 100) / 100,
"claimCount": claimCount,
"totalClaimed": totalClaimed,
"reason": reason
};
}
policy = {
"id": 12345,
"currentPremium": 1200
};
claims = [
{ "date": $ToDate("2023-06-15"), "amount": 3000 },
{ "date": $ToDate("2024-02-20"), "amount": 1500 }
];
adjustment = calculateRateAdjustment(policy, claims);
// Result: {
// currentPremium: 1200,
// adjustment: 1.30,
// newPremium: 1560,
// claimCount: 2,
// totalClaimed: 4500,
// reason: "Multiple claims"
// }
Payment Processing¶
Payment Schedule Calculation¶
Generate payment schedule for installments:
function calculatePaymentSchedule(totalAmount, startDate, frequency, term) {
// Calculate payment amount
if (frequency == "Monthly") {
paymentCount = term;
} else if (frequency == "Quarterly") {
paymentCount = term / 3;
} else if (frequency == "Semi-Annual") {
paymentCount = term / 6;
} else {
// Annual
paymentCount = 1;
}
paymentAmount = totalAmount / paymentCount;
paymentAmount = $Round(paymentAmount * 100) / 100;
// Calculate increment in months
if (frequency == "Monthly") {
increment = 1;
} else if (frequency == "Quarterly") {
increment = 3;
} else if (frequency == "Semi-Annual") {
increment = 6;
} else {
// Annual
increment = 12;
}
// Generate schedule
schedule = [];
currentDate = startDate;
index = 1;
while (index <= paymentCount) {
payment = {
"paymentNumber": index,
"dueDate": currentDate,
"amount": paymentAmount,
"status": "Pending"
};
schedule.Push(payment);
currentDate = $AddMonths(currentDate, increment);
index = index + 1;
}
// Adjust last payment for rounding
totalScheduled = paymentAmount * paymentCount;
difference = totalAmount - totalScheduled;
if (difference != 0) {
lastPayment = schedule[schedule.Length - 1];
lastPayment.amount = lastPayment.amount + difference;
}
return {
"totalAmount": totalAmount,
"frequency": frequency,
"paymentAmount": paymentAmount,
"paymentCount": paymentCount,
"schedule": schedule
};
}
result = calculatePaymentSchedule(1200, $ToDate("2024-01-01"), "Monthly", 12);
// Result: {
// totalAmount: 1200,
// frequency: "Monthly",
// paymentAmount: 100,
// paymentCount: 12,
// schedule: [
// { paymentNumber: 1, dueDate: 2024-01-01, amount: 100, status: "Pending" },
// { paymentNumber: 2, dueDate: 2024-02-01, amount: 100, status: "Pending" },
// ...
// ]
// }
Late Payment Calculation¶
Calculate penalties for late payments:
function calculateLateFee(payment, today) {
// Calculate days late
daysLate = $DifferenceInDays(payment.dueDate, today);
if (daysLate <= 0) {
// Not late
return {
"isLate": false,
"daysLate": 0,
"lateFee": 0,
"totalDue": payment.amount
};
}
// Calculate late fee
if (daysLate <= 15) {
// Grace period - no fee
lateFee = 0;
} else if (daysLate <= 30) {
// First 30 days: 5% fee
lateFee = payment.amount * 0.05;
} else {
// Over 30 days: 10% fee + $25
lateFee = payment.amount * 0.1 + 25;
}
lateFee = $Round(lateFee * 100) / 100;
totalDue = payment.amount + lateFee;
return {
"isLate": true,
"daysLate": daysLate,
"lateFee": lateFee,
"totalDue": totalDue
};
}
payment = {
"id": 1,
"dueDate": $ToDate("2024-10-01"),
"amount": 100
};
today = $ToDate("2024-11-15");
lateFeeInfo = calculateLateFee(payment, today);
// Result: {
// isLate: true,
// daysLate: 45,
// lateFee: 35,
// totalDue: 135
// }
Risk Scoring¶
Comprehensive Risk Score¶
Calculate overall risk score from multiple factors:
function calculateRiskScore(applicant) {
score = 100; // Start with perfect score
factors = [];
// Age factor
age = $DifferenceInYears(applicant.birthDate, $Today());
if (age < 25) {
score = score - 15;
factors.Push("Young driver");
} else if (age > 70) {
score = score - 10;
factors.Push("Senior driver");
}
// Driving history
if (applicant.accidents > 0) {
penaltyPoints = applicant.accidents * 10;
score = score - penaltyPoints;
factors.Push(applicant.accidents.ToString() + " accidents");
}
if (applicant.violations > 0) {
penaltyPoints = applicant.violations * 5;
score = score - penaltyPoints;
factors.Push(applicant.violations.ToString() + " violations");
}
// Years of experience
if (applicant.yearsLicensed < 3) {
score = score - 10;
factors.Push("Limited experience");
} else if (applicant.yearsLicensed >= 10) {
score = score + 5;
factors.Push("Experienced driver");
}
// Credit score
if (applicant.creditScore < 600) {
score = score - 20;
factors.Push("Poor credit");
} else if (applicant.creditScore >= 750) {
score = score + 10;
factors.Push("Excellent credit");
}
// Ensure score is between 0 and 100
if (score < 0) {
score = 0;
}
if (score > 100) {
score = 100;
}
// Determine risk category
if (score >= 80) {
category = "Low Risk";
} else if (score >= 60) {
category = "Medium Risk";
} else if (score >= 40) {
category = "High Risk";
} else {
category = "Very High Risk";
}
return {
"score": score,
"category": category,
"factors": factors
};
}
applicant = {
"name": "John Smith",
"birthDate": $ToDate("1990-05-15"),
"accidents": 1,
"violations": 0,
"yearsLicensed": 15,
"creditScore": 720
};
risk = calculateRiskScore(applicant);
// Result: {
// score: 85,
// category: "Low Risk",
// factors: ["1 accidents", "Experienced driver"]
// }
Policy Renewal¶
Renewal Pricing¶
Calculate renewal premium with loyalty discounts:
function calculateRenewalPremium(policy, claims) {
// Start with current premium
basePremium = policy.currentPremium;
// Calculate years with company
yearsWithCompany = $DifferenceInYears(policy.originalDate, $Today());
// Loyalty discount
if (yearsWithCompany >= 10) {
loyaltyDiscount = 0.15; // 15% off
} else if (yearsWithCompany >= 5) {
loyaltyDiscount = 0.1; // 10% off
} else if (yearsWithCompany >= 3) {
loyaltyDiscount = 0.05; // 5% off
} else {
loyaltyDiscount = 0;
}
// Claims adjustment (last 3 years)
cutoffDate = $AddYears($Today(), -3);
recentClaims = claims.Filter(
(c) => $DifferenceInDays(cutoffDate, c.date) >= 0
);
if (recentClaims.Length == 0) {
claimsAdjustment = 0.95; // 5% discount
} else if (recentClaims.Length == 1) {
claimsAdjustment = 1.1; // 10% increase
} else {
claimsAdjustment = 1.25; // 25% increase
}
// Market rate adjustment (inflation, etc.)
marketAdjustment = 1.03; // 3% increase
// Calculate renewal premium
adjustedPremium = basePremium * claimsAdjustment * marketAdjustment;
discountAmount = adjustedPremium * loyaltyDiscount;
renewalPremium = adjustedPremium - discountAmount;
return {
"currentPremium": basePremium,
"claimsAdjustment": claimsAdjustment,
"marketAdjustment": marketAdjustment,
"loyaltyDiscount": loyaltyDiscount,
"renewalPremium": $Round(renewalPremium * 100) / 100,
"yearsWithCompany": yearsWithCompany
};
}
policy = {
"id": 12345,
"currentPremium": 1200,
"originalDate": $ToDate("2015-01-01")
};
claims = []; // No claims
renewal = calculateRenewalPremium(policy, claims);
// Result: {
// currentPremium: 1200,
// claimsAdjustment: 0.95,
// marketAdjustment: 1.03,
// loyaltyDiscount: 0.15,
// renewalPremium: 999.27,
// yearsWithCompany: 9
// }
Portfolio Analysis¶
Book Roll Summary¶
Analyze portfolio of policies:
function analyzePortfolio(policies) {
// Initialize counters
totalPolicies = policies.Length;
activePolicies = 0;
totalPremium = 0;
averagePremium = 0;
// Count by status
statusCounts = { "active": 0, "activePremium": 0, "pending": 0, "lapsed": 0 };
policies.ForEach((p) => {
if (p.status == "Active") {
statusCounts.active = statusCounts.active + 1;
statusCounts.activePremium = statusCounts.activePremium + p.premium;
} else if (p.status == "Pending") {
statusCounts.pending = statusCounts.pending + 1;
} else if (p.status == "Lapsed") {
statusCounts.lapsed = statusCounts.lapsed + 1;
}
});
// Group by type
typeGroups = [];
policies.ForEach((p) => {
existing = typeGroups.Find((g) => g.type == p.type);
if (existing == null) {
typeGroups.Push({ "type": p.type, "count": 1, "premium": p.premium });
} else {
existing.count = existing.count + 1;
existing.premium = existing.premium + p.premium;
}
});
// Calculate averages
if (statusCounts.active > 0) {
averagePremium = statusCounts.activePremium / statusCounts.active;
}
// Find largest policy
largestPolicy = policies.ReduceToNum((max, p) => {
if (p.premium > max) {
return p.premium;
} else {
return max;
}
});
return {
"totalPolicies": totalPolicies,
"activeCount": statusCounts.active,
"pendingCount": statusCounts.pending,
"lapsedCount": statusCounts.lapsed,
"totalActivePremium": statusCounts.activePremium,
"averagePremium": $Round(averagePremium * 100) / 100,
"largestPolicy": largestPolicy,
"byType": typeGroups
};
}
policies = [
{ "id": 1, "type": "Auto", "premium": 1200, "status": "Active" },
{ "id": 2, "type": "Home", "premium": 1500, "status": "Active" },
{ "id": 3, "type": "Auto", "premium": 850, "status": "Lapsed" },
{ "id": 4, "type": "Life", "premium": 2000, "status": "Active" },
{ "id": 5, "type": "Auto", "premium": 950, "status": "Pending" }
];
analysis = analyzePortfolio(policies);
// Result: {
// totalPolicies: 5,
// activeCount: 3,
// pendingCount: 1,
// lapsedCount: 1,
// totalActivePremium: 4700,
// averagePremium: 1566.67,
// largestPolicy: 2000,
// byType: [
// { type: "Auto", count: 3, premium: 3000 },
// { type: "Home", count: 1, premium: 1500 },
// { type: "Life", count: 1, premium: 2000 }
// ]
// }
Best Practices for Complex Rules¶
Modular Functions¶
Break complex rules into smaller, testable functions:
// Good: Small, focused functions
function calculateAgeFactor(age) {
if (age < 25) {
return 1.5;
} else if (age < 65) {
return 1.0;
} else {
return 1.3;
}
}
function calculateRecordFactor(accidents) {
if (accidents > 2) {
return 1.8;
} else if (accidents > 0) {
return 1.3;
} else {
return 0.9;
}
}
function calculatePremium(base, age, accidents) {
ageFactor = calculateAgeFactor(age);
recordFactor = calculateRecordFactor(accidents);
return base * ageFactor * recordFactor;
}
Validation First¶
Validate inputs before processing:
function processApplication(application) {
// Validate first
validation = validateApplication(application);
if (!validation.isValid) {
return {
"success": false,
"errors": validation.errors
};
}
// Then process
result = calculatePremium(application);
return {
"success": true,
"premium": result
};
}
Return Detailed Results¶
Provide context with calculations:
// Good: Return breakdown
return {
"totalPremium": premium,
"basePremium": base,
"ageFactor": ageFactor,
"recordFactor": recordFactor,
"calculation": "base * ageFactor * recordFactor"
};
// Less helpful: Return only final value
// return premium;
Next Steps¶
- Basic Calculations - Foundation concepts
- Working with Dates - Date operations in rules
- Array Operations - Processing collections
- Object Manipulation - Working with data structures
See Also¶
- Control Flow - if/while statements
- Functions - Function patterns
- Native Functions - Built-in function reference