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:
calculateAutoPremium = function (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:
calculateMultiPolicyDiscount = function (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, 0);
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:
checkPolicyEligibility = function (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:
assessGeographicRisk = function (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:
validateClaim = function (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:
calculateRateAdjustment = function (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, 0);
// 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:
calculatePaymentSchedule = function (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:
calculateLateFee = function (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:
calculateRiskScore = function (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($ToString(applicant.accidents) + " accidents");
}
if (applicant.violations > 0) {
penaltyPoints = applicant.violations * 5;
score = score - penaltyPoints;
factors.Push($ToString(applicant.violations) + " 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:
calculateRenewalPremium = function (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:
analyzePortfolio = function (policies) {
// Initialize counters
totalPolicies = policies.Length;
activePolicies = 0;
totalPremium = 0;
averagePremium = 0;
// Count by status
statusCounts = policies.Reduce(
(acc, p) => {
// Count by status
if (p.status == "Active") {
acc.active = acc.active + 1;
acc.activePremium = acc.activePremium + p.premium;
} else if (p.status == "Pending") {
acc.pending = acc.pending + 1;
} else if (p.status == "Lapsed") {
acc.lapsed = acc.lapsed + 1;
}
return acc;
},
{ active: 0, activePremium: 0, pending: 0, lapsed: 0 }
);
// Group by type
typeGroups = policies.Reduce((acc, p) => {
existing = acc.Find((g) => g.type == p.type);
if (existing == null) {
acc.Push({
type: p.type,
count: 1,
premium: p.premium,
});
} else {
existing.count = existing.count + 1;
existing.premium = existing.premium + p.premium;
}
return acc;
}, []);
// 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;
}
}, 0);
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
calculateAgeFactor = function (age) {
if (age < 25) {
return 1.5;
} else if (age < 65) {
return 1.0;
} else {
return 1.3;
}
};
calculateRecordFactor = function (accidents) {
if (accidents > 2) {
return 1.8;
} else if (accidents > 0) {
return 1.3;
} else {
return 0.9;
}
};
calculatePremium = function (base, age, accidents) {
ageFactor = calculateAgeFactor(age);
recordFactor = calculateRecordFactor(accidents);
return base * ageFactor * recordFactor;
};
Validation First¶
Validate inputs before processing:
processApplication = function (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