Example #1
0
class IntegerModel:
    """Integer model for testing."""

    a = def_parameter(dtype=int, description="A number A")
    b = def_parameter(dtype=int, description="A number B")
    c = def_parameter(dtype=int, description="A number C")
    d = def_parameter(dtype=int, description="A number D")
    ret_1 = def_intermediate(dtype=int, description="Results a + b")
    ret_2 = def_intermediate(dtype=int, description="Results c - d")
    ret_3 = def_return(dtype=int,
                       description="Result total of step_1 and step_2")

    @step(uses=["a", "b"], impacts=["ret_1"])
    def _step_1(self):
        """Add a and b together."""
        self.ret_1 = self.a + self.b

    @step(uses=["c", "d"], impacts=["ret_2"])
    def _step_2(self):
        """Subtract d from c"""
        self.ret_2 = self.c - self.d

    @step(uses=["ret_1", "ret_2"], impacts=["ret_3"])
    def _step_3(self):
        """Add total of steps 1 and 2."""
        self.ret_3 = self.ret_1 + self.ret_2
Example #2
0
    class TestAttributes:
        param1 = def_parameter()
        param2 = def_parameter()
        sensitivity1 = def_sensitivity(default=1)
        sensitivity2 = def_sensitivity(default=2)
        meta1 = def_meta(meta="meta1", dtype=str)
        meta2 = def_meta(meta="meta2", dtype=str)
        intermediate1 = def_intermediate()
        intermediate2 = def_intermediate()
        return1 = def_return()
        return2 = def_return()

        @step(uses=["param1", "param2"], impacts=["return1"])
        def _step1(self):
            pass
Example #3
0
        class ModelNoSteps:
            parameter = def_parameter(dtype=int)
            intermediate = def_intermediate(init_value=0)

            @step(uses=["parameter"], impacts=["intermediate"])
            def _add(self):
                self.intermediate = self.parameter
Example #4
0
class DValSisRPMD(DValBasePMD):
    """The disabled life reserve (DLR) valuation model for the cost of living adjustment
    (COLA) policy rider.

    This model is a child of the `DValBasePMD` with the only changes being the addition
    of a step `_get_sis_probability` to lookup the probability of the policy holder
    qualifying for SIS (i.e., sis_probability) and the monthly benefit amount is equal
    to the (1 - SIS prbability) x benefit amount.
    """

    sis_probability = def_intermediate(
        dtype=float,
        description=
        "The probability of policyholder qualifying for social insurance supplement benefit.",
    )
    coverage_id = def_meta(
        meta="SIS",
        dtype=str,
        description="The coverage id which recognizes base policy vs riders.",
    )

    @step(name="Get SIS Probability", uses=[], impacts=["sis_probability"])
    def _get_sis_probability(self):
        """Get SIS probability."""
        self.sis_probability = 0.7

    @step(
        name="Calculate Monthly Benefits",
        uses=[
            "frame",
            "valuation_dt",
            "start_pay_dt",
            "termination_dt",
            "sis_probability",
            "benefit_amount",
        ],
        impacts=["frame"],
    )
    def _calculate_benefit_cost(self):
        """Calculate the monthly benefit amount for each duration."""
        self.frame = frame_add_exposure(
            self.frame,
            begin_date=max(self.valuation_dt, self.start_pay_dt),
            end_date=self.termination_dt,
            exposure_name="EXPOSURE",
            begin_duration_col="DATE_BD",
            end_duration_col="DATE_ED",
        )
        self.frame["BENEFIT_AMOUNT"] = (self.frame["EXPOSURE"] *
                                        self.benefit_amount *
                                        (1 - self.sis_probability)).round(2)
Example #5
0
 class Test:
     ret = def_return()
     placeholder = def_intermediate()
     meta = def_meta(meta="meta")
     modifier = def_sensitivity(default=1)
     parameter = def_parameter()
class DisabledLivesValEMD:
    """Disabled lives deterministic valuation extract model.

    This model takes an extract of policies and runs them through the respective Policy Models
    based on the COVERAGE_ID column (i.e., whether it is base policy or rider).
    """

    # parameters
    extract_base = def_parameter(
        dtype=pd.DataFrame, description="The disabled lives base extract.")
    extract_riders = def_parameter(
        dtype=pd.DataFrame, description="The disabled lives rider extract.")
    valuation_dt = param_valuation_dt
    assumption_set = param_assumption_set

    # sensitivities
    modifier_ctr = modifier_ctr
    modifier_interest = modifier_interest

    # meta
    model_version = meta_model_version
    last_commit = meta_last_commit
    run_date_time = meta_run_date_time

    # intermediates
    records = def_intermediate(
        dtype=dict, description="The extract transformed to records.")

    # return
    projected = def_return(
        dtype=pd.DataFrame,
        description="The projected reserves for the policyholders.")
    time_0 = def_return(
        dtype=pd.DataFrame,
        description="The time 0 reserve for the policyholders.")
    errors = def_return(dtype=list, description="Any errors captured.")

    @step(
        name="Create Records from Extracts",
        uses=["extract_base", "extract_riders"],
        impacts=["records"],
    )
    def _create_records(self):
        """Turn extract into a list of records for each row in extract."""
        frame = self.extract_base.copy()
        del frame["IDI_MARKET"]
        del frame["TOBACCO_USAGE"]
        records = convert_to_records(frame, column_case="lower")
        rider_data = self.extract_riders.copy()
        rider_data.columns = [col.lower() for col in rider_data.columns]
        rider_data = rider_data.pivot(
            index=["policy_id", "claim_id", "coverage_id"],
            columns="rider_attribute",
            values="value",
        ).to_dict(orient="index")

        def update_record(record):
            key = (
                record["policy_id"],
                record["claim_id"],
                record["coverage_id"],
            )
            kwargs_add = rider_data.get(key, None)
            if kwargs_add is not None:
                return {**record, **kwargs_add}
            return record

        self.records = [
            record
            if record["coverage_id"] not in ["RES"] else update_record(record)
            for record in records
        ]

    @step(
        name="Run Records with Policy Models",
        uses=["records"] + list(FOREACH_PARAMS),
        impacts=["projected", "errors"],
    )
    def _run_foreach(self):
        """Foreach record run through respective policy model based on COVERAGE_ID value."""
        projected, errors = foreach_model(**get_kws(foreach_model, self))
        if isinstance(projected, list):
            projected = pd.DataFrame(
                columns=list(DisabledLivesValOutput.columns))
        self.projected = projected
        self.errors = errors

    @step(name="Get Time0 Values", uses=["projected"], impacts=["time_0"])
    def _get_time0(self):
        """Filter projected reserves frame down to time_0 reserve for each record."""
        cols = [
            "MODEL_VERSION",
            "LAST_COMMIT",
            "RUN_DATE_TIME",
            "SOURCE",
            "POLICY_ID",
            "CLAIM_ID",
            "COVERAGE_ID",
            "DATE_DLR",
            "DLR",
        ]
        self.time_0 = self.projected.groupby(cols[4:7],
                                             as_index=False).head(1)[cols]
class AValBasePMD(ALRBasePMD):
    """The active life reserve (ALR) valuation model for the base policy."""

    # meta
    claim_cost_model = def_meta(
        meta=claim_cost_model, dtype=callable, description="The claim cost model used.",
    )
    coverage_id = def_meta(
        meta="BASE",
        dtype=str,
        description="The coverage id which recognizes base policy vs riders.",
    )

    # intermediate objects
    age_issued = def_intermediate(
        dtype=date, description="The calculate age policy was issued."
    )
    lapse_rates = def_intermediate(
        dtype=pd.DataFrame, description="The placholder for lapse rates."
    )
    mortality_rates = def_intermediate(
        dtype=pd.DataFrame, description="The placholder for lapse rates."
    )
    incidence_rates = def_intermediate(
        dtype=pd.DataFrame, description="The placholder for incidence rates."
    )
    modeled_claim_cost = def_intermediate(
        dtype=dict, description="The placholder for modeled disabled lives."
    )

    # return object
    frame = def_return(dtype=pd.DataFrame, description="The frame of projected reserves.")

    #####################################################################################
    # Step: Calculate Issue Age
    #####################################################################################

    @step(
        name="Calculate Issue Age",
        uses=["birth_dt", "policy_start_dt"],
        impacts=["age_issued"],
    )
    def _calculate_age_issued(self):
        """Calculate the age policy issued."""
        self.age_issued = calculate_age(self.birth_dt, self.policy_start_dt, method="ALB")

    #####################################################################################
    # Step: Create Projectetd Frame
    #####################################################################################

    @step(
        name="Create Projectetd Frame",
        uses=["policy_start_dt", "policy_end_dt"],
        impacts=["frame"],
    )
    def _create_frame(self):
        """Create projected benefit frame from policy start date to policy end date by duration year."""
        kwargs_frame = {
            "frequency": "Y",
            "col_date_nm": "DATE_BD",
            "duration_year": "DURATION_YEAR",
        }
        kwargs_wt = {
            "as_of_dt": self.valuation_dt,
            "begin_duration_col": "DATE_BD",
            "end_duration_col": "DATE_ED",
            "wt_current_name": "WT_BD",
            "wt_next_name": "WT_ED",
        }
        self.frame = (
            create_frame(self.policy_start_dt, self.policy_end_dt, **kwargs_frame)
            .pipe(_assign_end_date)
            .pipe(frame_add_weights, **kwargs_wt)
            .query("DATE_BD <= @self.policy_end_dt")
        )[["DATE_BD", "DATE_ED", "DURATION_YEAR", "WT_BD", "WT_ED"]]

    #####################################################################################
    # Step: Calculate Age Attained
    #####################################################################################

    @step(name="Calculate Age Attained", uses=["frame", "birth_dt"], impacts=["frame"])
    def _calculate_age_attained(self):
        """Calculate age attained by policy duration on the frame."""
        self.frame["AGE_ATTAINED"] = calculate_age(
            self.birth_dt, self.frame["DATE_BD"], method="ALB"
        )

    #####################################################################################
    # Step: Calculate Benefit Term Date
    #####################################################################################

    @step(
        name="Calculate Benefit Term Date",
        uses=[
            "frame",
            "idi_benefit_period",
            "elimination_period",
            "birth_dt",
            "policy_end_dt",
        ],
        impacts=["frame"],
    )
    def _calculate_termination_dt(self):
        """Calculate benefit termination date if active individual were to become disabled for each policy duration."""
        if self.idi_benefit_period[-1] == "M":  # pylint: disable=E1136
            months = int(self.idi_benefit_period[:-1])  # pylint: disable=E1136
            self.frame["TERMINATION_DT"] = (
                self.frame["DATE_BD"]
                + pd.DateOffset(days=self.elimination_period)
                + pd.DateOffset(months=months)
            )
        elif self.idi_benefit_period[:2] == "TO":  # pylint: disable=E1136
            self.frame["TERMINATION_DT"] = self.policy_end_dt
        elif self.idi_benefit_period == "LIFE":
            self.frame["TERMINATION_DT"] = pd.to_datetime(
                date(
                    year=self.birth_dt.year + 120,
                    month=self.birth_dt.month,
                    day=self.birth_dt.day,
                )
            )

    #####################################################################################
    # Step: Model Claim Cost
    #####################################################################################

    @step(
        name="Model Claim Cost",
        uses=["frame", "claim_cost_model",],
        impacts=["modeled_claim_cost"],
    )
    def _model_claim_cost(self):
        """Model claim cost for active live if policy holder were to become disabled for each policy duration"""

        # create records
        record_cols = ["policy_id", "incurred_dt", "valuation_dt", "termination_dt"]
        frame = self.frame[["DATE_BD", "TERMINATION_DT"]].assign(
            policy_id=self.policy_id,
            incurred_dt=lambda df: df.DATE_BD,
            valuation_dt=lambda df: df.DATE_BD,
            termination_dt=lambda df: df.TERMINATION_DT,
        )[record_cols]
        records = convert_to_records(frame)

        # model disabled lives
        kwargs = {
            kw: getattr(self, kw)
            for kw in self.claim_cost_model.constant_params
            if kw not in record_cols + ["claim_id", "idi_diagnosis_grp"]
        }
        success, errors = self.claim_cost_model(
            records=records, claim_id="NA", idi_diagnosis_grp="AG", **kwargs,
        )
        if len(errors) == 0:
            self.modeled_claim_cost = {y: val for y, val in enumerate(success, start=1)}
        else:
            raise ModelRunError(str(errors))

    #####################################################################################
    # Step: Get Incidence Rate
    #####################################################################################

    @step(
        name="Get Incidence Rate",
        uses=[],  # idi_assumptions.uses("incidence_rate", "assumption_set"),
        impacts=["incidence_rates"],
    )
    def _get_incidence_rates(self):
        """Get incidence rates and multiply by incidence sensitivity to form final rate."""
        assumption_func = idi_assumptions.get(self.assumption_set, "incidence_rates")
        self.incidence_rates = assumption_func(**get_kws(assumption_func, self))

    #####################################################################################
    # Step: Get Mortality Rates
    #####################################################################################

    @step(
        name="Get Mortality Rates",
        uses=[],  # assumptions.uses("mortality_rate", "assumption_set"),
        impacts=["mortality_rates"],
    )
    def _get_mortality_rates(self):
        """Get lapse rates and multiply by incidence sensitivity to form final rate."""
        assumption_func = idi_assumptions.get(self.assumption_set, "mortality_rates")
        self.mortality_rates = assumption_func(**get_kws(assumption_func, self))

    #####################################################################################
    # Step: Get Lapse Rates
    #####################################################################################

    @step(
        name="Get Lapse Rates",
        uses=[],  # assumptions.uses("lapse_rate", "assumption_set"),
        impacts=["lapse_rates"],
    )
    def _get_lapse_rates(self):
        """Get lapse rates and multiply by incidence sensitivity to form final rate."""
        assumption_func = idi_assumptions.get(self.assumption_set, "lapse_rates")
        self.lapse_rates = assumption_func(**get_kws(assumption_func, self))

    #####################################################################################
    # Step: Calculate Premiums
    #####################################################################################

    @step(
        name="Calculate Premiums",
        uses=["frame", "gross_premium", "gross_premium_freq"],
        impacts=["frame"],
    )
    def _calculate_premiums(self):
        """Calculate premiums for each duration."""
        if self.gross_premium_freq == "MONTH" or self.gross_premium_freq == "M":
            premium = self.gross_premium * 12
        elif self.gross_premium_freq == "QUARTER" or self.gross_premium_freq == "Q":
            premium = self.gross_premium * 4
        elif self.gross_premium_freq == "SEMIANNUAL" or self.gross_premium_freq == "S":
            premium = self.gross_premium * 2
        elif self.gross_premium_freq == "ANNUAL" or self.gross_premium_freq == "A":
            premium = self.gross_premium

        self.frame["GROSS_PREMIUM"] = premium

    #####################################################################################
    # Step: Calculate Benefit Cost
    #####################################################################################

    @step(
        name="Calculate Benefit Cost",
        uses=["frame", "modeled_claim_cost", "incidence_rates"],
        impacts=["frame"],
    )
    def _calculate_benefit_cost(self):
        """Calculate benefit cost by multiplying disabled claim cost by final incidence
        rate for each duration.
        """
        # merge final incidence rate
        self.frame = self.frame.merge(
            self.incidence_rates[["AGE_ATTAINED", "INCIDENCE_RATE"]],
            how="left",
            on=["AGE_ATTAINED"],
        )

        # add modeled claim cost (i.e., DLR)
        self.frame["DLR"] = [df["DLR"].iat[0] for df in self.modeled_claim_cost.values()]

        # calculate benefit cost
        self.frame["BENEFIT_COST"] = self.frame["DLR"] * self.frame["INCIDENCE_RATE"]

    #####################################################################################
    # Step: Calculate Lives
    #####################################################################################

    @step(name="Calculate Lives", uses=["frame", "lapse_rates"], impacts=["frame"])
    def _calculate_lives(self):
        """Calculate the beginning, middle, and ending lives for each duration using lapse rates."""
        # merge mortality rates
        self.frame = self.frame.merge(
            self.mortality_rates[["AGE_ATTAINED", "MORTALITY_RATE"]],
            how="left",
            on=["AGE_ATTAINED"],
        )

        # merge lapse rates
        self.frame = self.frame.merge(
            self.lapse_rates[["DURATION_YEAR", "LAPSE_RATE"]],
            how="left",
            on=["DURATION_YEAR"],
        )
        self.frame["LAPSE_RATE"] = self.frame["LAPSE_RATE"].ffill()

        # calculate lives
        lives_ed = calc_continuance(
            self.frame["MORTALITY_RATE"], self.frame["LAPSE_RATE"]
        )
        lives_bd = lives_ed.shift(1, fill_value=1)
        lives_md = calc_interpolation(
            val_0=lives_bd,
            val_1=lives_ed,
            wt_0=self.frame["WT_BD"],
            wt_1=self.frame["WT_ED"],
            method="log",
        )

        # assign lives to frame
        self.frame["LIVES_BD"] = lives_bd
        self.frame["LIVES_MD"] = lives_md
        self.frame["LIVES_ED"] = lives_ed

    #####################################################################################
    # Step: Calculate Discount Factors
    #####################################################################################

    @step(
        name="Calculate Discount Factors", uses=["frame"], impacts=["frame"],
    )
    def _calculate_discount(self):
        """Calculate beginning, middle, and ending discount factors for each duration."""
        assumption_func = idi_assumptions.get(self.assumption_set, "interest_rate_al")
        base_int_rate = assumption_func(**get_kws(assumption_func, self))

        self.frame["INTEREST_RATE_BASE"] = base_int_rate
        self.frame["INTEREST_RATE_MODIFIER"] = self.modifier_interest
        self.frame["INTEREST_RATE"] = (
            self.frame["INTEREST_RATE_BASE"] * self.frame["INTEREST_RATE_MODIFIER"]
        )
        self.frame["DISCOUNT_BD"] = calc_discount(self.frame["INTEREST_RATE"], t_adj=0)
        self.frame["DISCOUNT_MD"] = calc_discount(self.frame["INTEREST_RATE"], t_adj=0.5)
        self.frame["DISCOUNT_ED"] = calc_discount(self.frame["INTEREST_RATE"])

    #####################################################################################
    # Step: Calculate Durational ALR
    #####################################################################################

    @step(
        name="Calculate Durational ALR",
        uses=["frame", "net_benefit_method"],
        impacts=["frame"],
    )
    def _calculate_durational_alr(self):
        """Calculate active life reserves (ALR) for each duration."""
        # calculate present value of future benefits
        benefit_cols = ["BENEFIT_COST", "LIVES_MD", "DISCOUNT_MD"]
        pvfb = calc_pv(self.frame[benefit_cols].prod(axis=1))

        # calculate present value of future premium
        premium_cols = ["GROSS_PREMIUM", "LIVES_BD", "DISCOUNT_BD"]
        pvfp = calc_pv(self.frame[premium_cols].prod(axis=1))

        # calculate present value of future net benefifts
        pvfnb = calc_pvfnb(pvfb, pvfp, net_benefit_method=self.net_benefit_method)

        # calculate alr at end of duration
        alr_bd = (
            (pvfb - pvfnb) / self.frame["LIVES_BD"] / self.frame["DISCOUNT_BD"]
        ).clip(lower=0)

        # assign values to frame
        self.frame["PVFB"] = pvfb
        self.frame["PVFP"] = pvfp
        self.frame["PVFNB"] = pvfnb
        self.frame["ALR_BD"] = alr_bd
        self.frame["ALR_ED"] = alr_bd.shift(-1, fill_value=0)

    #####################################################################################
    # Step: Calculate Valuation Date ALR
    #####################################################################################

    @step(
        name="Calculate Valuation Date ALR",
        uses=["frame", "valuation_dt"],
        impacts=["frame"],
    )
    def _calculate_valuation_dt_alr(self):
        """Calculate active life reserves (ALR) for each duration as of valuation date."""

        def alr_date(period):
            return self.valuation_dt + pd.DateOffset(years=period)

        # filter frame to valuation_dt starting in duration
        self.frame = self.frame[self.frame["DATE_ED"] >= self.valuation_dt].copy()

        # create projected alr date column
        self.frame["ALR_DATE"] = pd.to_datetime(
            [alr_date(period) for period in range(0, self.frame.shape[0])]
        )

        # calcualte interpolated alr
        self.frame["ALR"] = calc_interpolation(
            val_0=self.frame["ALR_BD"],
            val_1=self.frame["ALR_ED"],
            wt_0=self.frame["WT_BD"],
            wt_1=self.frame["WT_ED"],
            method="log",
        ).round(2)

    #####################################################################################
    # Step: Create Output Frame
    #####################################################################################

    @step(
        name="Create Output Frame",
        uses=[
            "frame",
            "policy_id",
            "model_version",
            "last_commit",
            "run_date_time",
            "coverage_id",
            "benefit_amount",
        ],
        impacts=["frame"],
    )
    def _to_output(self):
        """Reduce output to only needed columns."""
        self.frame = self.frame.assign(
            POLICY_ID=self.policy_id,
            MODEL_VERSION=self.model_version,
            LAST_COMMIT=self.last_commit,
            RUN_DATE_TIME=self.run_date_time,
            SOURCE=self.__class__.__qualname__,
            COVERAGE_ID=self.coverage_id,
            BENEFIT_AMOUNT=self.benefit_amount,
            # set column order
        )[list(ActiveLivesValOutput.columns)]