Skip to content

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

See Also