Overview

The purpose of this report is to walk through the various allocation procedures utilized in the shiny application.

In order to provide knowledgeable insights from this report, we have supplied reasonable inputs that mimic the user-driven aspects of the allocation procedures.

Setup

The first step is to ingest all of the various user-driven inputs and demo-data to form a dataset to perform the allocation with.

Specify various ‘user-defined’ inputs:

  • Experience Period for Loss Run
  • Capping Threshold (%)
  • Budget guidance increase factor (a % applied to prior rates which are then used to allocate premiums)
# specify experience period based on loss run
min_year <- min(loss_run$year)
max_year <- max(loss_run$year)
experience_period <- c(min_year:max_year)

experience_period_display <- paste0(
  lubridate::ymd(paste0(min_year, "-01-01")) %>% format("%B %d, %Y"), " to ",
  lubridate::ymd(paste0(max_year, "-12-31")) %>% format("%B %d, %Y")
)

# specify percent change capping threshold
cap_threshold <- .25

# A 5% budget guidance increase factor
budget_guidance_percent <- 0.05

Costs to be Allocated

we need to extract the costs to be allocated in the model, specifically:

  1. The Total Cost Including Expenses
  2. The Risk Transfer Cost
  3. The All Risk Cost
  4. The Sum of Expenses

To do this, I utilize an internal, custom utility function extract_costs_for_allocation().

The resulting Costs for Allocation are:

# extract costs from renewal cost table
costs <- extract_costs(renewal_costs)

# derive current and prior overall rates and percent change
curr_rate <- costs$risk_transfer / sum(sov$tiv)
prior_rate <- sum(priors$prior_risk_transfer_premium) /
  sum(priors$prior_tiv)
pct_change <- (curr_rate / prior_rate) - 1

tibble::as_tibble(costs) %>%
  as.matrix() %>%
  t() %>%
  as.data.frame() %>%
  tibble::rownames_to_column(var = "Cost Type") %>%
  dplyr::rename("Dollar Amount" = V1) %>%
  tibble::as_tibble() %>%
  kkable(currency_cols = "Dollar Amount",
         proper_cols = "Cost Type",
         caption = "Extracted Costs for Allocation:")

Derive Relativity Adjusted TIVs

This step involves taking the user-defined relativity’s and multiplying them by the initial Total Insured Values defined in the Schedule of Values (SOV).

Specifically, we take the user-input Relativity Table and apply a custom utility function derive_relativity_adjusted_tivs() to derive our final relativity adjusted TIVs.

First, lets look at the input relativities:

rel_tables <- list(bu_rels, combustible_rels, sprinkler_tier_rels) %>%
  purrr::set_names(c("Business Unit Relativity",
                     "AOP Combustible Relativity",
                     "AOP Sprinkler Tier Relativity")) %>%
  purrr::map(apply_labels, dict = dictionary, dataset_name = "rels") %>%
  purrr::map(tibble::as_tibble)

purrr::map2(rel_tables, names(rel_tables), function(x, y) {
  kkable(data = x, caption = y, col_names = NULL, digits = 3, add_digits = TRUE)
})

Apply relativity factors to TIVs

Relativities are then applied directly to each entity’s TIV. For each relativity type, a separate relativity-adjusted TIV is calculated and used as required in eithe catastrophy allocation, All Other Peril allocation, or terrorism allocation.

rels_list <- list(
    relativity_data = list(
      bu_rels[, c(1, 2)], bu_rels[, c(1, 3)], bu_rels[, c(1, 4)], bu_rels[, c(1, 5)],
      bu_rels[, c(1, 6)], sprinkler_tier_rels, combustible_rels
    ),
    coverage = list(
      "aop", "cat_eq", "cat_wind", "cat_flood", "terrorism", "aop", "aop"
    ),
    sov_linker = list(
      "bu", "bu", "bu", "bu", "bu", "aop_sprinkler_tier", "aop_combustible"
    )
  )

rel_adjusted_tivs <- ingest_relativities(rels_list, sov = sov)

kkable(head(rel_adjusted_tivs),
       proper_cols = "entity_id", 
       currency_cols = names(rel_adjusted_tivs[2:ncol(rel_adjusted_tivs)]))

Prepare claim surcharges

For entities that have experienced claims, the client may wish to increase their allocated premium. The rules for doing so are limited in this application to: applying a % surcharge to the TIV for each claim made by the entity in a certain time period, or adding a $ surcharge to the allocated premium (at this stage in the calculation) for each claim made in a certain time period.

The demo data, count_buckets, sets out a typical specification for such functionality.

count_buckets %>%
  select(name:dollar_surcharge) %>%
  kkable(
    col_names = c("Label", "Minimum", "Maximum", "Percent Surcharge", "Dollar Surcharge"),
    caption = "User-Defined Claim Count Bucket Surcharges",
    currency_cols = c("min", "max", "dollar_surcharge"),
    percent_cols = c("percent_surcharge")
  )

Here, count_buckets are applied to the loss run ready for surcharging the premiums calculated in the main part of the model:

entity_loss_data <- entity_loss_summary(loss_run, count_buckets, experience_period)

col_names <- c("Entity ID", 
               count_buckets$name,
               "Total Counts", "Total Incurred")

kkable(entity_loss_data %>%
         # dplyr::mutate(entity_id = toproper(entity_id)) %>%
         dplyr::arrange(dplyr::desc(total_incurred)) %>%
         head(10), 
       col_names = col_names,
       proper_cols = "entity_id",
       currency_cols = length(col_names),
       caption = "Summarized Loss Data by Entity (Top 10 Entities by Total Incurred)")

Putting it all together

The steps involved are:

  1. merge_entity_data():
  • Apply rates to relativity_adjusted_tivs and also to priors (to simultaneously obtain an allocation of prior premiums)
  • Apply entity_loss_data to surcharge the resulting premiums pased on count_buckets and the loss run experience
  1. preliminary_allocation():
  • Determine a preliminary allocation of premiums (post claim surcharges) by calibrating the total allocated premium to match those indicated in costs
  • Also, apply budget_guidance_percent to prior premium rates to perform this allocation scenario
  1. apply_threshold():
  • Take the preliminary allocation of premiums and enforce a threshold upon them which prevents premiums allocated in the current period deviating too far from premium allocations of the prior period. For example, if the threshold is set to 25% (threshold <- 0.25), and if the prior premium allocated for a particular entity was 1m USD, then roughly the current premium allocation would be between 750k USD, and 1.25m USD.
  1. Perform final calculations to include expenses in the allocation.
# merge entity data (sov, rel adjusted tivs, loss data, market and model rates,
# and priors)

allocation_data <- merge_entity_data(
  sov,
  rel_adjusted_tivs,
  entity_loss_data,
  rates,
  priors
) %>%
  # perform initial preliminary allocation (CAT first, back into AOP, terror)
  # this is uncapped. before surcharges, and excluding expenses
  preliminary_allocation(costs, budget_guidance_percent) %>%
  # apply surcharges
  apply_surcharges(count_buckets) %>%
  # adjust column names for apply threshold function
  # TODO: add arguments to apply threshold functions for specifying column
  # so don't have to add this step.
  mutate(
    prior_allocated = prior_risk_transfer_premium,
    prior_allocated_rate = prior_allocated / prior_tiv,
    uncapped_allocated = surcharged_premium
  ) %>%
  # apply capping using a default 25% threshold
  # TODO: add argument to apply threshold for whether or not to net the
  # total pct change or not - currently it does this
  apply_threshold(
    total_pct_chg = pct_change,
    threshold = cap_threshold
  ) %>%
  # final rebalancing and allocate expenses
  allocate_expenses(costs, weight_variable = "tiv")

And the result is:

# declutter results and output
allocation_data %>%
  mutate(current_allocated_rate = rebalanced_allocated / tiv) %>% # excl expense
  select(
    entity_id,
    tiv,
    prior_tiv,
    aop_adj_tiv:terrorism_adj_tiv,
    model_aop_rate,
    model_cat_eq_rate,
    model_cat_wind_rate,
    model_cat_flood_rate,
    model_terrorism_rate,
    prior_risk_transfer_premium,
    preliminary_model_premium = total_model_premium_adj,
    surcharge,
    surcharged_premium,
    prior_allocated,
    prior_allocated_rate,
    uncapped_allocated,
    capped_allocated = allocated,
    final_allocated = rebalanced_allocated,
    current_allocated_rate,
    rate_percent_change = capped_rate_percent_change,
    allocated_expenses,
    final_allocated_w_expense
  ) %>%
  kkable()

Driver Summary

propalloc comes with functionality to split out the effects of each of the stages of premium allocation as they contribute to its change between prior and current premium allocations. This function uses argument filter_vector to show such a split for a subset of entities if desired.

driver_summary <- prepare_driver_summary(allocation_data, filter_vector = c())
driver_summary_bu_b_only <- prepare_driver_summary(allocation_data, filter_vector = c(bu = "bu_b"))