Proration and Premium Allocation

Note

This plugin will soon support an additional capability called holdbacks for cancellation scenarios. This will be useful for holding back fees or applying cancellation short rates. Until the availability of that feature, we do not recommend using this plugin for holdback scenarios because the plugin cannot handle all situations, and because holdback amounts will not be reapplied on reinstatement.

The Proration plugin controls how premiums, commissions, taxes, and fees are prorated when coverage periods are reduced by either endorsement or cancellation.

In an endorsement, existing peril characteristics are split and premium is prorated to determine the amount to be allocated to the segment before the split timestamp (this is called the “pre-split amount.”) For non-premium bearing endorsements, this also determines the post-split amount (equal to the original amount minus the pre-split amount).

For example, if a characteristics object that covers January 1 2021 to January 1 2022 and has $1000 of allocated premium is endorsed on July 1, approximately half of the premium should be allocated to the January 1 to July 1 segment. You may want to have more nuance:

  • Prorate by months: The premium will be 6 / 12 * 1000, or $500.00

  • Prorate by days: The premium will be 181 / 365 * 1000, $495.89

  • Prorate by milliseconds: The premium (in a daylight savings timezone in the northern hemisphere) will be 15634800000 / 31536000000 * 1000, or $495.78

The proration plugin can handle these and similar scenarios.

Cancellations work similarly but post-split characteristics will not be generated because the coverage is terminated.

Plugin Details

Enabling the plugin

To enable the plugin, turn on the Proration Plugin feature flag in config.json:

{
  "timezone": "America/Los_Angeles",
  "currency": "USD",
  "features" : { "property.proration.plugin.enabled": true }
}

If this feature flag is enabled, the plugin will be used; otherwise behavior will fall back to Legacy Proration.

Note

This feature flag is temporary and will soon be replaced with a configuration-based control system for plugin enablement.

The plugin script

The proration plugin requires a plugin script called prorater.js that exports a function called getProrationResult.

The data object

The data object passed to the plugin looks like this:

ProrationPluginData

{
  // Required
  items : [ProrationPluginItem]
  operation : string cancellation | endorsement
  paymentPlan : string
  segmentSplitTimestamp : timestamp
  tenantTimeZone : string

  // Optional
  cancellationType : string
}

Each proration item looks like this:

ProrationPluginItem

{
  // Required
  amount : number
  commissionRecipient : string
  feeLocator : string
  feeName : string
  followingAmount : number
  id : string
  perilCharacteristicsLocator : string
  perilLocator : string
  perilName : string
  segmentEndTimestamp : timestamp
  segmentStartTimestamp : timestamp
  taxLocator : string
  taxName : string
  type : string premium | tax | fee | commission | technicalPremium
}

Note

The id property of the proration item is a transient value used to key the plugin input to the response. It is not saved in the database nor is it consistant across calls.

Note

Numeric values sent to the plugin are passed as strings to ensure no loss of precision.

The Plugin Response Object

The plugin requires that the response from the plugin looks like this:

ProrationPluginResponse

{
  // Required
  items : [ProrationResponseItem]
}

Where each ProrationResponseItem looks like this:

ProrationResponseItem

{
  // Required
  holdbackAmount : number
  id : string
  proratedAmount : number

  // Optional
  holdbackMetadata : string
}

Note

Use the same id in the response as was used in the input ProrationPluginItem.

Warning

You may include holdbackMetadata only if you are also including a positive holdbackAmount in the ProrationResponseItem.

Sample Script (Simple)

This script implements the proration plugin using linear (millisecond-based) proration.

function getProrationResult(data)
{
  console.log("Hello simple proration world!!! Operation: " + data.operation);
  return { items: data.items.map(item => prorateItem(data, item)) };
}
function prorateItem(data, item)
{
  return {
    id: item.id,
    proratedAmount: round2(parseFloat(item.amount) * getLinearFraction(data, item)),
    holdbackAmount: 0
  };
}
function getLinearFraction(data, item)
{
  return (parseInt(data.segmentSplitTimestamp) - parseInt(item.segmentStartTimestamp))
  / (parseInt(item.segmentEndTimestamp) - parseInt(item.segmentStartTimestamp));
}
function round2(num)
{
  return Math.round(num * 100) / 100.0;
}

exports.getProrationResult = getProrationResult;

Sample Script (Advanced)

This script implements the proration plugin using linear proration for upfront, every_week, and every_two_weeks payment plans, and monthly proration for other plans.

function getProrationResult(data)
{
  console.log("Hello proration world!!! Operation: " + data.operation);

  return { items: data.items.map(item => prorateItem(data, item)) };
}

function prorateItem(data, item)
{
  let fraction;
  switch (data.paymentPlan)
  {
      case "total":
      case "every_week":
      case "every_two_weeks":
          fraction = getLinearFraction(data, item);
          break;
      default:
          fraction = getMonthlyFraction(data, item);
          break;
  }

  let amount = round2(fraction * parseFloat(item.amount));

  return {id: item.id, proratedAmount: amount, holdbackAmount: 0 };
}

function getMonthlyFraction(data, item)
{
  return Math.max(0,
                  Math.min(1.0,
                          monthCount(parseInt(item.segmentStartTimestamp), parseInt(data.segmentSplitTimestamp), data.tenantTimeZone)
                          / monthCount(parseInt(item.segmentStartTimestamp), parseInt(item.segmentEndTimestamp), data.tenantTimeZone)));
}

function getLinearFraction(data, item)
{
  return Math.max(0,
                  Math.min(1.0,
                           (parseInt(data.segmentSplitTimestamp) - parseInt(item.segmentStartTimestamp))
                           / (parseInt(item.segmentEndTimestamp) - parseInt(item.segmentStartTimestamp))));
}

function monthCount(startMs, endMs, timeZone)
{
  // This is a simple example; could use a javascript library for better timezone support

  let start = new Date(new Date(startMs).toLocaleString("en-US", {timeZone: timeZone }));
  let end = new Date(new Date(endMs).toLocaleString("en-US", {timeZone: timeZone }));
  let baseDayOfMonth = start.getDay();

  let count = 0.0;
  let cursor = start;

  while (cursor <= end)
  {
      let next = advanceMonth(cursor, baseDayOfMonth);
      if (next > end)
          count += (end.getTime() - cursor.getTime()) / (next.getTime() - cursor.getTime());
      else
          count++;
      cursor = next;
  }

  return count;
}

function advanceMonth(date, baseDayOfMonth)
{
  // avoid mutating date param
  let d = new Date(date);

  // Easy case, just add a month:
  if (baseDayOfMonth <= 28)
  {
      d.setMonth(d.getMonth() + 1);
      return d;
  }

  // Hard case, don't overflow past next month:

  // calc days in month
  let daysInMonth;
  switch (date.getMonth())
  {
      case 1: // Feb
          let year = date.getFullYear();
          let isLeapYear = ((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0);
          daysInMonth = isLeapYear ? 29 : 28;
          break;
      case 3: case 5: case 8: case 10:
          daysInMonth = 30;
          break;
      default:
          daysInMonth = 31;
          break;
  }

  // advance month
  d.setDate(1);
  d.setMonth(d.getMonth() + 1);
  d.setDate(Math.min(baseDayOfMonth, daysInMonth));

  return d;
}

function round2(num)
{
  return Math.round(num * 100) / 100.0;
}

exports.getProrationResult = getProrationResult;

Note

Socotra does not make any guarantee about how proration items are batched. If there are ten proration items to be processed, the plugin may be called once with the list of all ten, or it could be called multiple times with a subset processed on each call.

Legacy Proration

If the proration plugin is not implemented, the system will behave as follows:

During endorsements and cancellations, Socotra allocates premium to each coverage period using a single method, so all calculations are consistent.

  • If periods start and end at 00:00:00 (midnight), actual-millisecond proration is the same as actual-day

  • When allocating premium to billing periods, Socotra assigns the same amount to each whole billing period and then uses actual-millisecond proration for partial billing periods.

For Up-front, weekly, and every other week payment plans, Socotra will use milliseconds-based proration. For other plans, Socotra will use monthly-based proration based on the number of months (see below).

Month Counting

Consider the following scenario where a cancellation is processed before the end of the policy term:

  • The policy period is from June 13th for a one year term

  • The total premium for the year is $1200

  • The policyholder requests a cancellation effective September 19th

Resulting calculations:

  1. The remaining coverage period is from June 13th until September 19th, which is 3 months and 6 days.

  2. Since September 13th to October 13th is 30 days, the coverage period is 3 + 6/30 = 3.2 months long.

  3. 3.2 months will be used for monthly proration.