From 995718e1bc03920c57b9d7a200e5a24d62a6c912 Mon Sep 17 00:00:00 2001
From: Reinhold Kainhofer <reinhold@kainhofer.com>
Date: Wed, 8 Dec 2021 15:33:20 +0100
Subject: [PATCH] Generalize premium rebate calculation and administration cost
 reserves

Introduce hooks Hooks$premiumRebateCalculation and Hooks$adjustPVForReserves to modify the premium rebate calculation (e.g. a rebate relative to admin cost, or admin cost reserves that exclude a premium rebate).

The premiumRebateCalculation hook is called after the premiumRebate rate is evaluated and can be used to adjust the rate to some other value that e.g. includes reserves / future present values, etc. This way, one can have a varying rate, but always the same logic to apply the rebate rate. So parameter and calculation are separated cleanly.

The adjustPVForReserves hook is called before all present values are combined to the corresponding reserves. This allows modifying all present values that will be used in the reserves. E.g. one can reduce the admin cost present values by a premium rebate and the admin cost reserve properly discounts the rebate from the reserve. The PV modifications have NO influence on the actual present values stored for the contract or used for other purposes. Only the local copy of the present values in the reserve calculation funciton is modified.
---
 R/InsuranceParameters.R                    |  6 +++-
 R/InsuranceTarif.R                         | 38 +++++++++++++---------
 man/InsuranceContract.ParameterDefaults.Rd |  5 +++
 3 files changed, 32 insertions(+), 17 deletions(-)

diff --git a/R/InsuranceParameters.R b/R/InsuranceParameters.R
index 9caff53..1696f23 100644
--- a/R/InsuranceParameters.R
+++ b/R/InsuranceParameters.R
@@ -513,6 +513,8 @@ InsuranceContract.Values = list(
 #'     \item{\code{$adjustCosts}}{Function with signature \code{function(costs, params, values, ...)} to adjust the tariff costs after their setup (e.g. contract-specific conditions/waivers, etc.).}
 #'     \item{\code{$adjustMinCosts}}{Function with signature \code{function(minCosts, costs, params, values, ...)} to adjust the tariff minimum (unwaivable) costs after their setup (e.g. contract-specific conditions/waivers, etc.).}
 #'     \item{\code{$adjustPremiumCoefficients}}{Function with signature \code{function(coeff, type, premiums, params, values, premiumCalculationTime)} to adjust the coefficients for premium calculation after their default setup. Use cases are e.g. term-fix tariffs where the Zillmer premium term contains the administration cost over the whole contract, but not other gamma- or beta-costs.}
+#'     \item{\code{$adjustPVForReserves}}{Adjust the absolute present value vectors used to derive reserves (e.g. when a sum rebate is subtracted from the gamma-cost reserves without influencing the premium calculation). \code{function(absPV, params, values)}}
+#'     \item{\code{$premiumRebateCalculation}}{Calculate the actual premium rebate from the rebate rate (e.g. when the premium rate is given as a yearly cost reduction applied to a single-premium contract). \code{function(premiumRebateRate, params = params, values = values)}}
 #' }
 #'
 #'
@@ -626,7 +628,9 @@ InsuranceContract.ParameterDefaults = list(
       adjustCashFlowsCosts = NULL,
       adjustCosts = NULL,
       adjustMinCosts = NULL,
-      adjustPremiumCoefficients = NULL # function(coeff, type = type, premiums = premiums, params = params, values = values, premiumCalculationTime = premiumCalculationTime)
+      adjustPremiumCoefficients = NULL,  # function(coeff, type = type, premiums = premiums, params = params, values = values, premiumCalculationTime = premiumCalculationTime)
+      adjustPVForReserves = NULL,        # function(absPresentValues, params, values)
+      premiumRebateCalculation = NULL   # function(premiumRebateRate, params = params, values = values)
     )
 );
 
diff --git a/R/InsuranceTarif.R b/R/InsuranceTarif.R
index 52734eb..a5d791e 100644
--- a/R/InsuranceTarif.R
+++ b/R/InsuranceTarif.R
@@ -991,7 +991,9 @@ InsuranceTarif = R6Class(
       noMedicalExam.relative = valueOrFunction(loadings$noMedicalExamRelative,params = params, values = values);
       extraRebate   = valueOrFunction(loadings$extraRebate,  params = params, values = values);
       sumRebate     = valueOrFunction(loadings$sumRebate,    params = params, values = values);
-      premiumRebate = valueOrFunction(loadings$premiumRebate,params = params, values = values);
+      premiumRebateRate = valueOrFunction(loadings$premiumRebate,params = params, values = values);
+      premiumRebate = applyHook(params$Hooks$premiumRebateCalculation, premiumRebateRate, params = params, values = values);
+
       extraChargeGrossPremium = valueOrFunction(loadings$extraChargeGrossPremium, params = params, values = values);
       advanceProfitParticipation = 0;
       advanceProfitParticipationUnitCosts = 0;
@@ -1016,7 +1018,7 @@ InsuranceTarif = R6Class(
       values$premiums[["unitcost"]] = premium.unitcosts;
 
 
-            frequencyLoading = self$evaluateFrequencyLoading(loadings$premiumFrequencyLoading, params$ContractData$premiumFrequency, params = params, values = values)
+      frequencyLoading = self$evaluateFrequencyLoading(loadings$premiumFrequencyLoading, params$ContractData$premiumFrequency, params = params, values = values)
       premiumBeforeTax = (values$premiums[["unit.gross"]]*(1 + noMedicalExam.relative + extraChargeGrossPremium) + noMedicalExam - sumRebate - extraRebate) * sumInsured * (1 - advanceProfitParticipation);
       if (!params$Features$unitcostsInGross) {
         premiumBeforeTax = premiumBeforeTax + premium.unitcosts;
@@ -1045,20 +1047,22 @@ InsuranceTarif = R6Class(
       securityFactor = (1 + valueOrFunction(params$Loadings$security, params = params, values = values));
       ppScheme      = params$ProfitParticipation$profitParticipationScheme;
 
+      absPV = applyHook(params$Hooks$adjustPVForReserves, values$absPresentValues, params = params, values = values);
+
       # Net, Zillmer and Gross reserves
-      resNet = values$absPresentValues[,"benefitsAndRefund"] * securityFactor - values$premiums[["net"]] * values$absPresentValues[,"premiums.unit"];
-      BWZcorr = ifelse(values$absPresentValues[t, "premiums"] == 0, 0,
-                       values$absPresentValues[t, "Zillmer"] / values$absPresentValues[t, "premiums"]) * values$absPresentValues[,"premiums"];
+      resNet = absPV[,"benefitsAndRefund"] * securityFactor - values$premiums[["net"]] * absPV[,"premiums.unit"];
+      BWZcorr = ifelse(absPV[t, "premiums"] == 0, 0,
+                       absPV[t, "Zillmer"] / absPV[t, "premiums"]) * absPV[,"premiums"];
       resZ = resNet - BWZcorr;
 
-      resAdeq = values$absPresentValues[,"benefitsAndRefund"] * securityFactor +
-        values$absPresentValues[,"alpha"] + values$absPresentValues[,"beta"] + values$absPresentValues[,"gamma"] -
-        values$premiums[["gross"]] * values$absPresentValues[,"premiums.unit"];
+      resAdeq = absPV[,"benefitsAndRefund"] * securityFactor +
+          absPV[,"alpha"] + absPV[,"beta"] + absPV[,"gamma"] -
+        values$premiums[["gross"]] * absPV[,"premiums.unit"];
 
-      #values$premiums[["Zillmer"]] * values$absPresentValues[,"premiums"];
-      resGamma = values$absPresentValues[,"gamma"] -
-        ifelse(values$absPresentValues[t, "premiums"] == 0, 0,
-               values$absPresentValues[t, "gamma"] / values$absPresentValues[t, "premiums"]) * values$absPresentValues[,"premiums"]
+      #values$premiums[["Zillmer"]] * absPV[,"premiums"];
+      resGamma = absPV[,"gamma"] -
+        ifelse(absPV[t, "premiums"] == 0, 0,
+               absPV[t, "gamma"] / absPV[t, "premiums"]) * absPV[,"premiums"]
 
       advanceProfitParticipation = 0;
       if (!is.null(ppScheme)) {
@@ -1106,7 +1110,7 @@ InsuranceTarif = R6Class(
             "reduction"   = resReduction
             #, "Reserve.premiumfree"=res.premiumfree, "Reserve.gamma.premiumfree"=res.gamma.premiumfree);
       );
-      rownames(res) <- rownames(values$absPresentValues);
+      rownames(res) <- rownames(absPV);
       values$reserves = res;
 
       # The surrender value functions can have arbitrary form, so we store a function
@@ -1136,9 +1140,9 @@ InsuranceTarif = R6Class(
         premiumfreeValue = surrenderValue
       }
       Storno = 0; # TODO: Implement storno costs
-      premiumfreePV = (values$absPresentValues[, "benefits"] * securityFactor + values$absPresentValues[, "gamma_nopremiums"]); # PV of future premium free claims + costs
+      premiumfreePV = (absPV[, "benefits"] * securityFactor + absPV[, "gamma_nopremiums"]); # PV of future premium free claims + costs
       newSI = ifelse(premiumfreePV == 0, 0,
-        (premiumfreeValue - values$absPresentValues[,"death_Refund_past"] * securityFactor - c(Storno)) /
+        (premiumfreeValue - absPV[,"death_Refund_past"] * securityFactor - c(Storno)) /
         premiumfreePV * params$ContractData$sumInsured);
 
       cbind(res,
@@ -1348,7 +1352,9 @@ InsuranceTarif = R6Class(
       profits.advance  = profits.advance + afterProfit - afterUnitCosts;
 
       # premium rebate
-      premiumRebate    = valueOrFunction(loadings$premiumRebate,params = params, values = values);
+      premiumRebateRate = valueOrFunction(loadings$premiumRebate,params = params, values = values);
+      premiumRebate = applyHook(params$Hooks$premiumRebateCalculation, premiumRebateRate, params = params, values = values);
+
       afterPremiumRebate = afterUnitCosts * (1 - premiumRebate);
       rebate.premium   = afterPremiumRebate - afterUnitCosts;
 
diff --git a/man/InsuranceContract.ParameterDefaults.Rd b/man/InsuranceContract.ParameterDefaults.Rd
index 3026da8..6060c66 100644
--- a/man/InsuranceContract.ParameterDefaults.Rd
+++ b/man/InsuranceContract.ParameterDefaults.Rd
@@ -110,6 +110,9 @@ those cost components that are defined to be waivable, i.e. by
 defining a corresponding \code{$minCosts}). Linearly interpolates
 between \code{$Costs} and \code{$minCosts}, if the latter is set.
 Otherwise is has no effect.}
+\item{\code{$attributes}}{Additional custom attributes (as a named list),
+which can be used for particular behaviour of different contracts
+or contract slices.}
 }
 }
 
@@ -291,6 +294,8 @@ participation rates are defined at the level of profit classes.}
 \item{\code{$adjustCosts}}{Function with signature \code{function(costs, params, values, ...)} to adjust the tariff costs after their setup (e.g. contract-specific conditions/waivers, etc.).}
 \item{\code{$adjustMinCosts}}{Function with signature \code{function(minCosts, costs, params, values, ...)} to adjust the tariff minimum (unwaivable) costs after their setup (e.g. contract-specific conditions/waivers, etc.).}
 \item{\code{$adjustPremiumCoefficients}}{Function with signature \code{function(coeff, type, premiums, params, values, premiumCalculationTime)} to adjust the coefficients for premium calculation after their default setup. Use cases are e.g. term-fix tariffs where the Zillmer premium term contains the administration cost over the whole contract, but not other gamma- or beta-costs.}
+\item{\code{$adjustPVForReserves}}{Adjust the absolute present value vectors used to derive reserves (e.g. when a sum rebate is subtracted from the gamma-cost reserves without influencing the premium calculation). \code{function(absPV, params, values)}}
+\item{\code{$premiumRebateCalculation}}{Calculate the actual premium rebate from the rebate rate (e.g. when the premium rate is given as a yearly cost reduction applied to a single-premium contract). \code{function(premiumRebateRate, params = params, values = values)}}
 }
 }
 }
-- 
GitLab