From 0115e8e95437373ebe1cb40f10bc90de2fb8b953 Mon Sep 17 00:00:00 2001
From: Reinhold Kainhofer <reinhold@kainhofer.com>
Date: Thu, 19 Jan 2023 23:38:50 +0100
Subject: [PATCH] Implement sumInsured calculated from given premium

* Add parameter premium, which defines the prescribe premium (either written, written_beforetax or gross)
* The sumInsured will be derived from the premium by reversing the formulas (as far as possible)
* If both sumInsured and premium are given, the premium is ignored and sumInsured takes precedence
* The function calculatePremiums now also returns the sumInsured (in addition to the premiums and the coefficients for the premium calculation)

Implements #49, documents #63
---
 R/.gitignore                                  |   1 +
 R/InsuranceContract.R                         |   1 +
 R/InsuranceParameters.R                       |   4 +-
 R/InsuranceTarif.R                            | 106 +++++++++++++++++-
 tests/testthat/test-SumInsured-calculated.R   |  46 ++++++++
 ...ing-the-lifeinsurancecontracts-package.Rmd |  98 ++++++++++++++++
 6 files changed, 251 insertions(+), 5 deletions(-)
 create mode 100644 tests/testthat/test-SumInsured-calculated.R

diff --git a/R/.gitignore b/R/.gitignore
index d392185..f90d1a1 100644
--- a/R/.gitignore
+++ b/R/.gitignore
@@ -1 +1,2 @@
 dummy.R
+*.R.bak
diff --git a/R/InsuranceContract.R b/R/InsuranceContract.R
index 078c910..20bc205 100644
--- a/R/InsuranceContract.R
+++ b/R/InsuranceContract.R
@@ -630,6 +630,7 @@ InsuranceContract = R6Class(
             # so we have to extract the coefficients and store them in a separate variable
             if (recalculatePremiums) {
                 res = private$calculatePremiums(premiumCalculationTime = premiumCalculationTime);
+                self$Parameters$ContractData$sumInsured = res[["sumInsured"]]
                 self$Values$premiumCoefficients = res[["coefficients"]];
                 # TODO: Store premiums in a data.frame, including the time they are calculated???
                 self$Values$premiums = res[["premiums"]]
diff --git a/R/InsuranceParameters.R b/R/InsuranceParameters.R
index 3774dee..72c8f19 100644
--- a/R/InsuranceParameters.R
+++ b/R/InsuranceParameters.R
@@ -279,6 +279,7 @@ InsuranceContract.Values = list(
 #'               contracts with multiple parts, e.g. dynamic increases),
 #'               default = "Hauptvertrag"}
 #'     \item{\code{$sumInsured}}{Sum insured, default = 100,000}
+#'     \item{\code{$premium}}{Premium, given to determine the sumInsured (default: NULL)}
 #'     \item{\code{$initialCapital}}{Reserve/Capital that is already available
 #'               at contract inception, e.g. from a previous contract. No tax
 #'               or acquisition costs are applied to this capital.}
@@ -553,7 +554,8 @@ InsuranceContract.Values = list(
 InsuranceContract.ParameterDefaults = list(
     ContractData = list(
         id = "Hauptvertrag",
-        sumInsured = 100000,
+        sumInsured = NULL,
+        premium = NULL,
         birthDate = NULL,
         YOB = NULL,
         age = NULL,
diff --git a/R/InsuranceTarif.R b/R/InsuranceTarif.R
index 1991083..737dd05 100644
--- a/R/InsuranceTarif.R
+++ b/R/InsuranceTarif.R
@@ -580,7 +580,13 @@ InsuranceTarif = R6Class(
         row.names          = ages - age
       );
 
-      cf$additional_capital = pad0(params$ContractData$initialCapital / params$ContractData$sumInsured, cflen)
+      if (is.null(params$ContractData$sumInsured)) {
+        # No sumInsured given, determine SI from premium provided
+        #
+        cf$additional_capital = pad0(params$ContractData$initialCapital, cflen)
+      } else {
+        cf$additional_capital = pad0(params$ContractData$initialCapital / params$ContractData$sumInsured, cflen)
+      }
       # Premiums:
       if (!params$ContractState$premiumWaiver) {
         premiums = self$getPremiumCF(len = cflen, params = params, values = values)
@@ -993,12 +999,95 @@ InsuranceTarif = R6Class(
       pvCost = values$presentValuesCosts[t,,,]
 
       if (pv[["premiums"]] == 0) {
-        return(list("premiums" = values$premiums, "coefficients" = coefficients))
+        return(list("premiums" = values$premiums, "coefficients" = coefficients, "sumInsured" = params$ContractData$sumInsured))
+      }
+
+      #======================================================================== =
+      # Calculate sumInsured from Premium, if needed
+      # ======================================================================= =
+      if (is.null(sumInsured)) {
+        sumInsured = 1
+        params$ContractData$sumInsured = 1 # Temporarily set to 1!
+
+        # Premium type can be given using a named array, e.g. premium = c(gross = 1000)
+        premiumtype = names(params$ContractData$premium)
+        if (is.null(premiumtype)) premiumtype = "written";
+        premium = unname(params$ContractData$premium);
+        calculating = FALSE;
+
+
+        # Calculate unit gross premium (sumInsured=1)
+        values$premiums["additional_capital"] = values$cashFlows[t, "additional_capital"]
+        coeff = self$getPremiumCoefficients("gross", pv * 0, pvCost * 0, premiums = values$premiums, params = params, values = values, premiumCalculationTime = premiumCalculationTime)
+        enumerator  = sum(coeff[["SumInsured"]][["benefits"]] * pv) + sum(coeff[["SumInsured"]][["costs"]] * pvCost);
+        denominator = sum(coeff[["Premium"   ]][["benefits"]] * pv) + sum(coeff[["Premium"   ]][["costs"]] * pvCost);
+        values$premiums[["unit.gross"]] = enumerator/denominator * (1 + loadings$ongoingAlphaGrossPremium);
+
+        # Calculate other premium components:
+        # ATTENTION: This will not work if any of these depend on the absolute values of the premiums, or depend on net or Zillmer premium!
+        tax           = valueOrFunction(loadings$tax,          params = params, values = values);
+        unitCosts     = valueOrFunction(loadings$unitcosts,    params = params, values = values);
+        noMedicalExam = valueOrFunction(loadings$noMedicalExam,params = params, values = values);
+        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);
+        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;
+        ppScheme      = params$ProfitParticipation$profitParticipationScheme;
+        if (!is.null(ppScheme)) {
+          advanceProfitParticipation = ppScheme$getAdvanceProfitParticipation(params = params, values = values)
+          advanceProfitParticipationUnitCosts = ppScheme$getAdvanceProfitParticipationAfterUnitCosts(params = params, values = values)
+        }
+        if (is.null(advanceProfitParticipation)) advanceProfitParticipation = 0;
+        if (is.null(advanceProfitParticipationUnitCosts)) advanceProfitParticipationUnitCosts = 0;
+
+        partnerRebate = valueOrFunction(loadings$partnerRebate, params = params, values = values);
+
+        # Start from the given premium to derive the sumInsured step-by-step:
+        #
+        # Written premium after tax
+        calculating = calculating | (premiumtype == "written");
+        if (calculating) {
+          premium = premium / (1 + tax);
+        }
+        # Written premium before tax
+        calculating = calculating | (premiumtype == "written_beforetax");
+        if (calculating) {
+          premium = premium / (1 - premiumRebate - advanceProfitParticipationUnitCosts - partnerRebate);
+
+          pv.unitcosts = sum(
+            pvCost["unitcosts","SumInsured",] * sumInsured +
+              pvCost["unitcosts","SumPremiums",] * values$unitPremiumSum * values$premiums[["gross"]] +
+              pvCost["unitcosts","GrossPremium",] * values$premiums[["gross"]] +
+              pvCost["unitcosts","NetPremium",] * values$premiums[["net"]] +
+              pvCost["unitcosts","Constant",]
+          )
+          premium.unitcosts = pv.unitcosts / pv[["premiums"]] + valueOrFunction(loadings$unitcosts, params = params, values = values);
+          if (!params$Features$unitcostsInGross) {
+            premium = premium - premium.unitcosts;
+          }
+          premium = premium / (1 - advanceProfitParticipation)
+        }
+        calculating = calculating | (premiumtype == "gross");
+        if (calculating) {
+          sumInsured = premium /
+            (values$premiums[["unit.gross"]]*(1 + noMedicalExam.relative + extraChargeGrossPremium) + noMedicalExam - sumRebate - extraRebate);
+        }
+        params$ContractData$sumInsured = sumInsured
       }
 
+
       values$premiums["additional_capital"] = values$cashFlows[t, "additional_capital"] * sumInsured
 
+      #======================================================================== =
       # net, gross and Zillmer premiums are calculated from the present values using the coefficients on each present value as described in the formulas document
+      # ======================================================================= =
+      # GROSS Premium
+      # ----------------------------------------------------------------------- -
       coeff = self$getPremiumCoefficients("gross", pv * 0, pvCost * 0, premiums = values$premiums, params = params, values = values, premiumCalculationTime = premiumCalculationTime)
       enumerator  = sum(coeff[["SumInsured"]][["benefits"]] * pv) + sum(coeff[["SumInsured"]][["costs"]] * pvCost);
       denominator = sum(coeff[["Premium"   ]][["benefits"]] * pv) + sum(coeff[["Premium"   ]][["costs"]] * pvCost);
@@ -1006,6 +1095,9 @@ InsuranceTarif = R6Class(
       values$premiums[["gross"]] = values$premiums[["unit.gross"]] * sumInsured;
       coefficients[["gross"]] = coeff;
 
+      # ======================================================================= =
+      # NET Premium
+      # ----------------------------------------------------------------------- -
       coeff = self$getPremiumCoefficients("net", pv*0, pvCost*0, premiums = values$premiums, params = params, values = values, premiumCalculationTime = premiumCalculationTime)
       enumerator  = sum(coeff[["SumInsured"]][["benefits"]] * pv) + sum(coeff[["SumInsured"]][["costs"]] * pvCost);
       denominator = sum(coeff[["Premium"   ]][["benefits"]] * pv) + sum(coeff[["Premium"   ]][["costs"]] * pvCost);
@@ -1013,6 +1105,9 @@ InsuranceTarif = R6Class(
       values$premiums[["net"]] = values$premiums[["unit.net"]] * sumInsured;
       coefficients[["net"]] = coeff;
 
+      # ======================================================================= =
+      # ZILLMER Premium
+      # ----------------------------------------------------------------------- -
       coeff = self$getPremiumCoefficients("Zillmer", pv * 0, pvCost * 0, premiums = values$premiums, params = params, values = values, premiumCalculationTime = premiumCalculationTime);
       enumerator  = sum(coeff[["SumInsured"]][["benefits"]] * pv) + sum(coeff[["SumInsured"]][["costs"]] * pvCost);
       denominator = sum(coeff[["Premium"   ]][["benefits"]] * pv) + sum(coeff[["Premium"   ]][["costs"]] * pvCost);
@@ -1021,6 +1116,9 @@ InsuranceTarif = R6Class(
       coefficients[["Zillmer"]] = coeff;
 
 
+      # ======================================================================= =
+      # Additional premium components (after gross premium)
+      # ----------------------------------------------------------------------- -
       # The written premium is the gross premium with additional loadings, rebates, unit costs and taxes
       tax           = valueOrFunction(loadings$tax,          params = params, values = values);
       unitCosts     = valueOrFunction(loadings$unitcosts,    params = params, values = values);
@@ -1061,14 +1159,14 @@ InsuranceTarif = R6Class(
         premiumBeforeTax = premiumBeforeTax + premium.unitcosts;
       }
       premiumBeforeTax = premiumBeforeTax * (1 - premiumRebate - advanceProfitParticipationUnitCosts - partnerRebate);
-            premiumBeforeTax.y = premiumBeforeTax * (1 + frequencyLoading);
+      premiumBeforeTax.y = premiumBeforeTax * (1 + frequencyLoading);
       premiumBeforeTax = premiumBeforeTax.y / params$ContractData$premiumFrequency;
       values$premiums[["written_yearly"]] = premiumBeforeTax.y * (1 + tax)
       values$premiums[["written_beforetax"]] = premiumBeforeTax;
       values$premiums[["tax"]] = premiumBeforeTax * tax;
       values$premiums[["written"]] = premiumBeforeTax * (1 + tax);
 
-      list("premiums" = values$premiums, "coefficients" = coefficients)
+      list("premiums" = values$premiums, "coefficients" = coefficients, "sumInsured" = params$ContractData$sumInsured)
     },
 
     #' @description Calculate the reserves of the InsuranceContract given the
diff --git a/tests/testthat/test-SumInsured-calculated.R b/tests/testthat/test-SumInsured-calculated.R
new file mode 100644
index 0000000..92c8874
--- /dev/null
+++ b/tests/testthat/test-SumInsured-calculated.R
@@ -0,0 +1,46 @@
+test_that("Calculation of sumInsured from premium", {
+    library(MortalityTables)
+    mortalityTables.load("Austria_Census")
+
+    Tarif.EndowmentSI = InsuranceTarif$new(
+        name = "Example Tariff - Endowment",
+        type = "endowment",
+        tarif = "E1-RP",
+        desc = "An endowment with regular premiums (standard tariff)",
+
+        mortalityTable = mort.AT.census.2011.unisex,
+        cost = initializeCosts(alpha = 0.04, gamma.contract = 0.0005, unitcosts = 10),
+        i = 0.03
+    )
+    Contract.sumInsured = InsuranceContract$new(
+        tarif = Tarif.EndowmentSI,
+        age = 40, policyPeriod = 20,
+        sumInsured = 10000,
+        contractClosing = as.Date("2020-09-01")
+    )
+    Contract.premium = InsuranceContract$new(
+        tarif = Tarif.EndowmentSI,
+        age = 40, policyPeriod = 20,
+        premium = Contract.sumInsured$Values$premiums[["written"]],
+        contractClosing = as.Date("2020-09-01")
+    )
+    Contract.premium_beforetax = InsuranceContract$new(
+        tarif = Tarif.EndowmentSI,
+        age = 40, policyPeriod = 20,
+        premium = c(written_beforetax = Contract.sumInsured$Values$premiums[["written_beforetax"]]),
+        contractClosing = as.Date("2020-09-01")
+    )
+    Contract.premium_gross = InsuranceContract$new(
+        tarif = Tarif.EndowmentSI,
+        age = 40, policyPeriod = 20,
+        premium = c(gross = Contract.sumInsured$Values$premiums[["gross"]]),
+        contractClosing = as.Date("2020-09-01")
+    )
+
+    # All four contracts above should result in the same sumInsured:
+    expect_equal(Contract.premium$Parameters$ContractData$sumInsured, Contract.sumInsured$Parameters$ContractData$sumInsured)
+    expect_equal(Contract.premium_beforetax$Parameters$ContractData$sumInsured, Contract.sumInsured$Parameters$ContractData$sumInsured)
+    expect_equal(Contract.premium_gross$Parameters$ContractData$sumInsured, Contract.sumInsured$Parameters$ContractData$sumInsured)
+    expect_equal(Contract.sumInsured$Parameters$ContractData$sumInsured, Contract.premium$Parameters$ContractData$sumInsured)
+
+})
diff --git a/vignettes/using-the-lifeinsurancecontracts-package.Rmd b/vignettes/using-the-lifeinsurancecontracts-package.Rmd
index c2fae6e..f293d09 100644
--- a/vignettes/using-the-lifeinsurancecontracts-package.Rmd
+++ b/vignettes/using-the-lifeinsurancecontracts-package.Rmd
@@ -733,6 +733,104 @@ all_equal(contract.PureEnd.SP1$Values$reserves, contract.PureEnd.SP2$Values$rese
 ```
 
 
+## Determining Sum Insured from the premium
+
+By default, the insurance contract is created for a given sum insured and the 
+premiums are calculated accordingly. Sometimes, the reverse is needed: The 
+premium (either actuarial gross premium, written premium before or after taxes) 
+is given and the corresponding sum insured should be determined automatically.
+
+The `InsuranceContract` constructor / parameter set has an additional field 
+`premium` to indicate the desired premium (written premium after tax by default) 
+from which the sum insured is then calculated:
+```{r PrescribePremium}
+# Premium calculated from sumInsured
+contract.End = InsuranceContract$new(
+  Tarif.Endowment, age = 35, policyPeriod = 10,
+  contractClosing = as.Date("2020-08-18"), 
+  sumInsured = 10000);
+
+# sumInsured derived from written premium
+contract.End.premium = InsuranceContract$new(
+  Tarif.Endowment, age = 35, policyPeriod = 10,
+  contractClosing = as.Date("2020-08-18"), 
+  premium = 1139.06);
+
+contract.End.premiumBeforeTax = InsuranceContract$new(
+  Tarif.Endowment, age = 35, policyPeriod = 10,
+  contractClosing = as.Date("2020-08-18"), 
+  premium = c(written_beforetax = 1095.25));
+
+contract.End.premiumGross = InsuranceContract$new(
+  Tarif.Endowment, age = 35, policyPeriod = 10,
+  contractClosing = as.Date("2020-08-18"), 
+  premium = c(gross = 1085.25));
+
+```
+```{r PrescribePremiumOUTPUT,echo = FALSE}
+bind_rows(
+  c(Contract = "contract.End", contract.End$Values$premiums[c("net", "Zillmer", "gross", "written_beforetax", "written")], sumInsured = contract.End$Parameters$ContractData$sumInsured),
+  c(Contract = "contract.End.premium", contract.End.premium$Values$premiums[c("net", "Zillmer", "gross", "written_beforetax", "written")], sumInsured = contract.End.premium$Parameters$ContractData$sumInsured),
+  c(Contract = "contract.End.premiumBeforeTax", contract.End.premiumBeforeTax$Values$premiums[c("net", "Zillmer", "gross", "written_beforetax", "written")], sumInsured = contract.End.premiumBeforeTax$Parameters$ContractData$sumInsured),
+  c(Contract = "contract.End.premiumGross", contract.End.premiumGross$Values$premiums[c("net", "Zillmer", "gross", "written_beforetax", "written")], sumInsured = contract.End.premiumGross$Parameters$ContractData$sumInsured)
+)
+```
+
+The final written premium can be directly passed as the `premium` argument. Other
+types of premium must be passed as a named number (i.e. a one-element vector with
+name "gross", "written_beforetax" or "written").
+
+If a premium is prescribed, a `sumInsured` must not be given. If both are given, 
+the sumInsured is used and the prescribed premium is silently ignored.
+
+The are cases, when the sumInsured cannot be derived from a prescribed premium.
+One relevant case is when a premium rebate depends on the sum insured, since in 
+this case we need the sumInsured to calculate the rebate and thus the premium.
+All rebates, add-ons and other parameters will temporarily use a sumInsured=1 
+during the calculation of the actual sumInsured!
+
+
+
+## Providing additional capital at contract inception
+
+Often, when a contract is changed significantly, this is modelled as a totally 
+new contract, with the existing reserve provided as additional one-time payment
+for the follow-up contract. This is different from a single-premium contract, 
+because the new contract can have regular premiums, just the existing reserve
+is used as the initial reserve of the new contract.
+
+The package provides the argument `initialCapital` to provide initial capital
+that is also included in the calculation of the premium of the contract.
+
+```{r InitialCapital}
+# Contract with initial capital of 5.000 EUR
+contract.Endow.initialCapital = InsuranceContract$new(
+  tarif = Tarif.Endowment,
+  sumInsured = 10000,
+  initialCapital = 5000,
+  age = 40, policyPeriod = 10,
+  contractClosing = as.Date("2020-09-01")
+)
+# For comparison: Contract without initial capital of 5.000 EUR
+contract.Endow = InsuranceContract$new(
+  tarif = Tarif.Endowment,
+  sumInsured = 10000,
+  age = 40, policyPeriod = 10,
+  contractClosing = as.Date("2020-09-01")
+)
+```
+Comparing the reserves, one can clearly see the initial capital used as initial reserve:
+
+```{r InitialCapitalOUTPUT}
+data.frame(
+  `Premium with initialCapital`= contract.Endow.initialCapital$Values$premiumComposition[,"charged"], 
+  `Premium without initialCapital`= contract.Endow$Values$premiumComposition[,"charged"], 
+  `Res.with initialCapital`= contract.Endow.initialCapital$Values$reserves[,"contractual"], 
+  `Res.without initialCapital`= contract.Endow$Values$reserves[,"contractual"]
+)
+```
+
+
 ## Premium Waivers
 
 After a while, many customers do not want to pay premiums for the contract any 
-- 
GitLab