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.", )
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.", )
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)
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
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)
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)
class Test: ret = def_return() placeholder = def_intermediate() meta = def_meta(meta="meta") modifier = def_sensitivity(default=1) parameter = def_parameter()
""", 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)]