示例#1
0
class DocModel:
    """This is a model to test documentation."""

    param_1 = def_parameter(dtype=int, description="This is parameter 1.")
    param_2 = def_parameter(dtype=int, description="This is parameter 2.")
    sensit_1 = def_sensitivity(dtype=int,
                               default=1,
                               description="This is sensitivity 1.")
    sensit_2 = def_sensitivity(dtype=int,
                               default=2,
                               description="This is sensitivity 2.")
    meta_1 = def_meta(meta="meta_1", description="This is meta 1.")
    meta_2 = def_meta(meta="meta_2", description="This is meta 2.")
    return_1 = def_return(dtype=int, description="This is return 1.")
    return_2 = def_return(dtype=int, description="This is return 2.")

    @step(uses=["param_1", "sensit_1"], impacts=["return_1"])
    def _step_1(self):
        """Step 1 summary"""
        pass

    @step(uses=["param_2", "sensit_2"], impacts=["return_2"])
    def _step_2(self):
        """Step 2 summary"""
        pass
class AValResRPMD(AValBasePMD):
    """The active life reserve (ALR) valuation model for the cost of living adjustment
    (COLA) policy rider.

    This model is a child of the `AValBasePMD` with the only change being how the monthly
    benefit is calculated. The base model uses the benefit amount passed while this model
    calculates the benefit as the benefit amount x residual benefit percent.
    """

    # parameter
    residual_benefit_percent = def_parameter(
        dtype=float,
        description=
        "The residual benefit percent to multiply by the benefit amount.",
    )

    # meta
    claim_cost_model = def_meta(
        meta=claim_cost_model,
        dtype=callable,
        description="The claim cost model used.",
    )
    coverage_id = def_meta(
        meta="RES",
        dtype=str,
        description="The coverage id which recognizes base policy vs riders.",
    )
示例#3
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
class AValCatRPMD(AValBasePMD):
    """The active life reserve (ALR) valuation model for the catastrophic (CAT) policy rider.

    This model is a child of the `AValBasePMD` with the only change being the model mode is
    changed from ALR to ALRCAT. This is to notify the model to calculate a different set of
    claim termination rates.
    """

    claim_cost_model = def_meta(
        meta=claim_cost_model, dtype=callable, description="The claim cost model used.",
    )
    coverage_id = def_meta(
        meta="CAT",
        dtype=str,
        description="The coverage id which recognizes base policy vs riders.",
    )
class ActiveLifeCATClaimCostModel(DValCatRPMD):
    """Base model used to calculate claim cost for active lives."""

    model_mode = def_meta(
        meta="ALRCAT",
        dtype=str,
        description="Mode used in CTR calculation as it varies whether policy is active or disabled.",
    )
class AValSisRPMD(AValBasePMD):
    """The active life reserve (ALR) valuation model for the cost of living adjustment
    (COLA) policy rider.

    This model is a child of the `AValBasePMD` 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) when modeling claim cost and the
    benefit amount is equal to the (1 - SIS prbability) x benefit amount.
    """

    claim_cost_model = def_meta(
        meta=claim_cost_model, dtype=callable, description="The claim cost model used.",
    )
    coverage_id = def_meta(
        meta="SIS",
        dtype=str,
        description="The coverage id which recognizes base policy vs riders.",
    )
class AValColaRPMD(AValBasePMD):
    """The active life reserve (ALR) valuation model for the cost of living adjustment
    (COLA) policy rider.

    This model is a child of the `AValBasePMD` with the only change being how the monthly
    benefit is calculated. The base model uses the benefit amount passed while this model
    calculate the benefit with cola less the original benefit amount.
    """

    claim_cost_model = def_meta(
        meta=claim_cost_model,
        dtype=callable,
        description="The claim cost model used.",
    )
    coverage_id = def_meta(
        meta="COLA",
        dtype=str,
        description="The coverage id which recognizes base policy vs riders.",
    )
示例#8
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)
示例#9
0
    class Test:
        parameter = def_parameter(description="This is a parameter.")
        sensitivity = def_sensitivity(default=1,
                                      description="This is a sensitivity.")
        meta = def_meta(meta="meta", description="This is meta.")
        ret = def_return(dtype="int", description="This is a return.")

        @step(uses=["parameter"], impacts=["ret"])
        def _add(self):
            """Do addition."""
            pass

        @step(uses=["ret", "sensitivity"], impacts=["ret"])
        def _subtract(self):
            """Do subtraction."""
            pass
示例#10
0
class DValColaRPMD(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 change being how the monthly
    benefit is calculated. The base model uses the benefit amount passed while this model
    calculate the benefit with cola less the original benefit amount.
    """

    coverage_id = def_meta(
        meta="COLA",
        dtype=str,
        description="The coverage id which recognizes base policy vs riders.",
    )

    @step(
        name="Calculate Monthly Benefits",
        uses=[
            "frame",
            "valuation_dt",
            "start_pay_dt",
            "termination_dt",
            "cola_percent",
            "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",
        )
        max_duration = self.frame[self.frame["AGE_ATTAINED"] ==
                                  65]["DURATION_YEAR"]
        if max_duration.size > 0:
            upper = max_duration.iat[0]
            power = self.frame["DURATION_YEAR"].clip(upper=upper)
        else:
            power = self.frame["DURATION_YEAR"]
        cola = (1 + self.cola_percent)**(power - 1)
        self.frame["BENEFIT_AMOUNT"] = (self.frame["EXPOSURE"] * (
            (self.benefit_amount * cola) - self.benefit_amount)).round(2)
class DValResRPMD(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 change being how the monthly
    benefit is calculated. The base model uses the benefit amount passed while this model
    calculates the benefit as the benefit amount x residual benefit percent.
    """

    residual_benefit_percent = def_parameter(
        dtype=float,
        description="The residual benefit percent to multiply by the benefit amount.",
    )
    coverage_id = def_meta(
        meta="RES",
        dtype=str,
        description="The coverage id which recognizes base policy vs riders.",
    )

    @step(
        name="Calculate Monthly Benefits",
        uses=[
            "frame",
            "valuation_dt",
            "start_pay_dt",
            "termination_dt",
            "residual_benefit_percent",
            "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 * self.residual_benefit_percent
        ).round(2)
示例#12
0
class AValRopRPMD(AValBasePMD):
    """The active life reserve (ALR) valuation model for the return of premium (ROP)
    policy rider.

    This model is a child of the `AValBasePMD` and includes additional parameters for
    rop_return_freq, rop_return_percent and rop_claims_paid parameters.
    """

    # additional parameters
    rop_return_freq = def_parameter(
        dtype="int",
        description="The return of premium (ROP) frequency in years.")
    rop_return_percent = def_parameter(
        dtype="float", description="The return of premium (ROP) percentage.")
    rop_claims_paid = def_parameter(
        dtype="float",
        description="The return of premium (ROP) benefits paid.")

    # meta
    coverage_id = def_meta(
        meta="ROP",
        dtype=str,
        description="The coverage id which recognizes base policy vs riders.",
    )

    @step(
        name="Calculate Benefit Cost",
        uses=[
            "frame",
            "rop_return_freq",
            "rop_claims_paid",
            "rop_return_percent",
        ],
        impacts=["frame"],
    )
    def _calculate_benefit_cost(self):
        """Calculate benefit cost for each duration."""

        # set payment intervals
        self.frame["INCIDENCE_RATE"] = 0
        self.frame["ROP_INTERVAL"] = (
            self.frame["DURATION_YEAR"].subtract(1).div(
                self.rop_return_freq).astype(int))

        # add paid claims to frame
        self.frame["PAID_CLAIMS"] = 0
        rngs = self.frame.groupby(["ROP_INTERVAL"
                                   ]).agg(START_DT=("DATE_BD", "first"),
                                          END_DT=("DATE_ED", "last"))
        end_dt = rngs.query(
            "START_DT <= @self.valuation_dt <= END_DT")["END_DT"].iat[0]
        row = self.frame[self.frame["DATE_ED"] == end_dt].index[0]
        self.frame.at[row, "PAID_CLAIMS"] = self.rop_claims_paid

        # calculate expected total premium per ROP payment interval
        self.frame["ROP_PREMIUM"] = _calculate_rop_interval_premium(self.frame)

        # calculate ROP benefit cost
        self.frame["ROP_RETURN_PERCENTAGE"] = self.rop_return_percent
        self.frame["BENEFIT_COST"] = (
            self.frame["ROP_PREMIUM"] * self.frame["ROP_RETURN_PERCENTAGE"] -
            self.frame["PAID_CLAIMS"]).clip(lower=0)
示例#13
0
 class Test:
     ret = def_return()
     placeholder = def_intermediate()
     meta = def_meta(meta="meta")
     modifier = def_sensitivity(default=1)
     parameter = def_parameter()
示例#14
0
    """,
    dtype=str,
    validator=isin(["NLP", "PT1", "PT2"]),
)

param_volume_tbl = def_parameter(
    dtype=pd.DataFrame,
    description="The volume table to use with refence to the distribution of policies by attributes.",
)

param_as_of_dt = def_parameter(
    dtype=pd.Timestamp, description="The as of date which birth date will be based.",
)

meta_model_version = def_meta(
    meta=MOD_VERSION, dtype=str, description="The model version generated by versioneer."
)

meta_last_commit = def_meta(
    meta=GIT_REVISION, dtype=str, description="The last git commit."
)

meta_run_date_time = def_meta(
    meta=pd.to_datetime("now"), dtype=pd.Timestamp, description="The run date and time."
)

modifier_ctr = def_sensitivity(default=1.0, dtype=float, description="Modifier for CTR.")

modifier_interest = def_sensitivity(
    default=1.0, dtype=float, description="Interest rate modifier."
)
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)]