The Projection Space#

The by-policy projection of the BU_DE_S model.

The Space is parameterized by point_id, so Projection[1] is an ItemSpace projecting model point 1:

>>> Projection[1].result_cf()          # the worked example's anchor cell
>>> Projection.point_id = 5            # or switch the default

t counts policy months, 0-based: t = 0 is the first projected month — the month of inception for a new-business point, the valuation month for an in-force one — and proj_len() is the number of projected months, the exclusive end of the frame, so result_cf() runs t = 0 ... proj_len() - 1 and ends there. On the anchor cell that is proj_len() == 444, i.e. 444 monthly rows ending at t = 443. Nothing is payable at the end: cover ceases at attained age cover_end_age, there is no maturity value, no surrender value and no death benefit, and a claim still in payment at the horizon simply stops.

Input data

Inputs are external files: plain CSVs living in the model folder’s parent directory, products/berufsunfaehigkeit/, read at run time rather than stored inside the model. The model folder therefore holds nothing but formulas — no _data/, no IOSpec, no embedded values — so a diff of the model shows logic changes only, and an input can be edited or swapped without rewriting the model. This follows annuallife.TradLife_A; contrast basiclife.BasicTerm_S, which keeps its inputs inside the model through modelx’s IOSpec machinery.

The consequence worth knowing: the model is not portable on its own. Copying the BU_DE_S folder without its parent’s CSVs produces a model that reads and then fails on first evaluation.

Each table has a filename Reference and a reader Cells, both on Data, reached here through the data Reference:

Reference

Cells

File

model_point_file

data.model_point_table()

model_point_table.csv

inception_file

data.inception_table()

inception_table.csv

claim_duration_file

data.claim_duration_table()

claim_duration_table.csv

mortality_file

data.mortality_table()

mortality_table.csv

occupation_file

data.occupation_table()

occupation_table.csv

lapse_file

data.lapse_table()

lapse_table.csv

freq_loading_file

data.freq_loading_table()

freq_loading_table.csv

Naming

Cells names follow lifelib’s basiclife.BasicTerm_S and savings.CashValue_SE wherever those models have an analogue — pols_* for policy counts, plural nouns for cash flows, *_rate for annual rates with *_rate_mth for their monthly equivalents, *_pp for per-policy amounts, claims(t, kind) with an uppercase kind string, pols_if_at(t, timing) for the end-of-month read. The technical notes use compact actuarial symbols instead. The mapping is:

Notes symbol

Cells

Meaning

(none)

model_point()

The selected model point row

n

proj_len()

Number of projected months

(none)

first_len()

Months in the pricing run

u(t)

duration_mth(t)

Elapsed policy months at t

y(t)

policy_year(t)

Policy year containing t

x(t)

age(t)

Attained age, ALB

(none)

age_first(s)

Attained age in the pricing run

z

(the cohort index)

Months since onset of the BU

(none)

claim_year(z)

Claim year containing z

k

(the run-off slot)

Months into the § 174 run-off

R

bu_rente_mth()

Agreed monthly BU-Rente

R(t)

bu_rente_pp(t)

Insured BU-Rente at t

R_p(t, z)

rente_pay_pp(t, z)

BU-Rente in payment, cohort z

K

karenz_months()

Karenzzeit, months

g_L

leistungsdyn_rate()

Leistungsdynamik p.a.

g_B

beitragsdyn_rate()

Beitragsdynamik p.a.

(1 + g_B)^(y-1)

dyn_factor(t)

Beitragsdynamik factor at t

(1 + g_L)^((z-1)//12)

leistungsdyn_factor(z)

Leistungsdynamik factor at z

theta

beitragsverrechnung()

Zahlbeitrag / Bruttobeitrag

rho

risk_factor()

Risikozuschlag on the premium

phi

freq_load()

Ratenzahlungszuschlag

M

prem_mode_months()

Months between instalments

kappa

occ_factor()

Occupational loading

alpha

accept_factor

Anerkennungsquote factor

upsilon

au_uplift()

AU-Klausel inception uplift

P

prem_gross_level_pp()

Level annual Bruttobeitrag

(none)

prem_gross_ann_pp(t)

Annual Bruttobeitrag at t

(none)

prem_due(t)

1 in a payment month, else 0

P_b(t)

prem_gross_pp(t)

Bruttobeitrag instalment at t

P_z(t)

prem_zahl_pp(t)

Zahlbeitrag instalment at t

(1 - theta) P_b(t)

surplus_credit_pp(t)

Beitragsverrechnung instalment

BS_unit

beitragssumme_unit()

Beitragssumme per 1 EUR p.a.

i(x)

inc_rate_base(t)

Table inception rate at x(t)

i(x) kappa alpha upsilon

inc_rate(t)

Composed annual inception rate

i_m(t)

inc_rate_mth(t)

The same, monthly

r(z)

recov_rate(z)

Annual reactivation rate

r_m(z)

recov_rate_mth(z)

The same, monthly

q^a(x)

mort_rate(t)

Annual active-lives mortality

q^a_m(t)

mort_rate_mth(t)

The same, monthly

s(z)

mort_dis_sel_factor(z)

Disabled-mortality select factor

q^i(x, z)

mort_rate_dis(t, z)

Annual disabled-lives mortality

q^i_m(t, z)

mort_rate_dis_mth(t, z)

The same, monthly

w(y)

lapse_rate(t)

Annual Stornoquote

w_m(t)

lapse_rate_mth(t)

The same, monthly

lambda_i .. lambda_a

inc_load_first ..

The four first-order loads

v^t

disc_first(t)

Rechnungszins discount factor

l_a(t)

pols_actv(t)

Aktiv, start of month t

l_d(t, z)

pols_dis_dur(t, z)

Leistungspflichtig at duration z

(sum over z)

pols_dis(t)

The whole disabled ledger

l_r(t, k)

pols_runoff_slot(t, k)

§ 174 run-off slot k

V_r(t, k)

runoff_val(t, k)

The same, times its BU-Rente

(sum over k)

pols_runoff(t)

The whole run-off ledger

L(t)

pols_if(t)

In force at the start of t

(none)

pols_if_at(t, timing)

BEG / END of month t

L_p(t)

pols_prem(t)

Premium-paying count

(none)

pols_inception(t)

Aktiv -> leistungspflichtig

(none)

pols_recovery(t)

Claim terminations into run-off

(none)

pols_reactivation(t)

Run-off completions to aktiv

(none)

pols_death(t)

Deaths out of all three ledgers

(none)

pols_lapse(t)

Lapses, from pols_actv only

(the shadow ledgers)

*_first

The first-order pricing run

premiums(t)

premiums(t)

Gross Bruttobeitrag income

surplus_credit(t)

surplus_credit(t)

Beitragsverrechnung returned

claims_bu_rente etc.

claims(t, kind)

Benefit outgo by kind

expenses(t)

expenses(t)

Acquisition and administration

claim_expenses(t)

claim_expenses(t)

Leistungsbearbeitungskosten

net_cf(t)

net_cf(t)

Net cash flow, income positive

liability_cf(t)

liability_cf(t)

The same stream, outgo positive

Five names needed care.

occ_factor and risk_factor are not two spellings of the same thing. occ_factor() loads the inception rate and reaches the premium only through the equivalence, so it moves every claim and every decrement; risk_factor() loads the Bruttobeitrag alone and leaves the claims untouched. A Risikozuschlag prices an individually assessed impairment that the base table does not carry and this model does not carry either, so a loaded contract is priced above its own modelled cost. The direction is stated rather than corrected.

bu_rente_pp and rente_pay_pp run on different clocks. bu_rente_pp() is the insured monthly BU-Rente, escalating at beitragsdyn_rate on each policy anniversary before any claim; rente_pay_pp() is the amount in payment, escalating at leistungsdyn_rate on each anniversary of the onset. Escalating the BU-Rente in payment on the policy anniversary is a numbered pitfall.

pols_prem is not pols_if. It is pols_actv(t) plus the disabled cohorts still inside the Karenzzeit: the Beitragsbefreiung runs with the benefit, so a life inside the Karenzzeit is berufsunfaehig and still pays. Weighting the premium by pols_if charges premium to lives in claim and silently deletes the Beitragsbefreiung, which is core cover.

pols_recovery feeds the run-off, not the active ledger. A life whose claim ends in month t is still paid in t+1, t+2 and t+3 and only then rejoins pols_actv, because § 174 VVG leaves the insurer liable to the end of the third month after the notice reaches the policyholder. pols_reactivation() is the return arc and is three months behind pols_recovery().

The *_first cells are a second projection, not a variant of the first. They run the same four-ledger chain on Rechnungsgrundlagen erster Ordnung — inception x 1.30, reactivation x 0.70, disabled-lives mortality x 0.80, active-lives mortality x 0.80, no lapse — over the contract’s original term from entry_age, indexed by s rather than t. They exist only to fix prem_gross_level_pp() and never touch a published cash flow. Running them from inception rather than from the valuation date is what gives an in-force model point the Bruttobeitrag its contract was actually struck at: model point 6 is model point 1 fifteen years on, and the two must price the same.

The premium is derived, not read

No German BU rate card exists in this library’s source corpus, so the Bruttobeitrag is an output of a stated first-order basis. With d(s) = disc_first(s), the equivalence is

P x PV_prem = PV_rente + PV_wgh + PV_cost + PV_admin
  • acq_rate x P x BS_unit + admin_prem_rate x P x PV_prem

which is linear in P, because both the acquisition and the proportional administration loadings are proportional to it, so

P = (PV_rente + PV_wgh + PV_cost + PV_admin)

/ ( PV_prem x (1 - admin_prem_rate) - acq_rate x BS_unit )

and prem_gross_level_pp() is risk_factor() x P, or risk_factor() x gross_prem_ann() where the model point overrides it. The recursion is acyclic: no decrement in this model depends on the premium, so nothing in the pv_* cells depends on P. The equivalence is struck before the Risikozuschlag and without lapse, which is deliberate on both counts — German pricing does not anticipate lapse, because a lapse releases a liability and a prudent basis does not anticipate a favourable event.

PV_prem is struck on P / 12 in every month, so the Ratenzahlungszuschlag freq_load is a genuine loading on top of the tariff premium rather than a re-expression of it. That is the market’s own construction and it is why an annual payer and a monthly payer do not pay the same present value.

The four-ledger chain, and the two things it must not do

At the end of month t, in order: from the active ledger, deaths, then lapses on the survivors, then inceptions on the survivors of both; from each disabled cohort, deaths, then terminations on the survivors, the terminations entering run-off slot 1 carrying the BU-Rente they were on; from the run-off, deaths at active-lives mortality — these lives have recovered — then slot 1 to 2, slot 2 to 3, and the slot-3 survivors back to pols_actv with the Wiedereingliederungshilfe.

Death and lapse are the only exits, so

pols_if(t+1) = pols_if(t) - pols_death(t) - pols_lapse(t)

and inception, recovery and reactivation are internal transfers that must not appear in it. Putting them there is how a multi-state model silently loses mass, and it is why pols_if() is built by that roll-forward and check_states() then compares it against the three ledgers rather than restating a sum of them.

At the Leistungsendalter the benefit stops but the mass is held, not deleted: once age(t) >= benefit_end_age() every payment and every claim-maintenance cost is zero while the ledgers keep rolling, so both state identities still close. Those lives do not resume paying premium — they are still berufsunfaehig, and the Beitragsbefreiung is read here as keyed to the state rather than to the payment [std]. The alternative reading is defensible and is named so that a user who takes it knows what to change.

The run-off carries amounts as well as counts

runoff_val() is a value ledger: the run-off population times the monthly BU-Rente it is being paid. A cohort entering the run-off keeps the BU-Rente it was on at the Nachpruefung date and receives no further Leistungsdynamik [std] — three months is inside one anniversary of onset in every realistic case, so the simplification costs nothing and removes a second duration dimension from the run-off. The disabled ledger carries a value vector for the same reason, so that the month’s BU-Rente outgo is one sum over a list rather than a lookup per cohort.

Modules that are off in the base run

  • The *AU-Klausel*, au_klausel true on model point 10 with au_uplift 1.00. The machinery is present and demonstrably inert until a user supplies an uplift: no source quantifies what six months of certified Arbeitsunfaehigkeit adds to the incidence, so shipping a number would be an invention.

  • Lapse selection, not implemented. BU lapse is strongly selective — the healthy leave, the impaired cannot — so a non-selective rate understates the average inception rate of the surviving book, increasingly with duration. The direction is known and one-sided; the size is not, and stacking a selection loading on an already-[std] inception proxy would compound two unsourced choices.

  • Premium-shock lapse, not implemented. On the dynamik form the take-up of the increases is folded into the effective beitragsdyn_rate rather than modelled as a decision. That also keeps the equivalence acyclic: a shock-lapse module would make the lapse rate depend on the premium, which depends on the projection.

  • The *Zahlbeitrag* re-rating. beitragsverrechnung is held constant for the whole projection. It is the model’s single largest discretionary assumption and the one the product’s own consumer literature warns about; a user modelling the risk raises surplus_credit() toward zero over time, which raises collected premium toward the Bruttobeitrag and moves nothing else.

  • The *Nachversicherungsgarantie*, not modelled at all: it needs a take-up assumption and an anti-selection loading on the incremental cover, and neither is sourceable.

Four absences are product facts

There is no account value and no surrender value, so no av_pp_at exists and claims(t, "LAPSE") is structurally zero at every t — a lapse removes the policy and pays nothing. § 169 VVG through § 176 gives this contract a real Rueckkaufswert and § 165 a real beitragsfreie BU-Rente, but both are the release of a reserve this model deliberately does not compute, and the zero column states the scope rather than hiding it. There is no death benefit: an SBU pays nothing on death, before or during a claim, so pols_death() is a decrement and never a cash flow and there is no claims_death column for a reader arriving from a term-life model to find. There is no maturity benefit. And there is no acknowledged state: this model pays from onset and does not model the Leistungspruefung delay, so the Anerkenntnis is a timing event with no cash-flow consequence here — right in amount, early in timing.

Unisex

sex is a model-point attribute for reporting purposes only and must not enter the premium: sex-differentiated pricing has been unlawful in Germany for contracts written from 21 December 2012. The shipped decrement tables are unisex, so in the base parameterization sex moves nothing at all — model points 1 and 2 differ in it and in nothing else, and their frames are identical.

Sign convention

net_cf() is income positive — the Bruttobeitrag in, the Beitragsverrechnung, claims and expenses out — which is the library-wide sign. liability_cf() publishes the same stream outgo-positive, -net_cf(t) exactly, so a best-estimate liability is sum v(t) liability_cf(t) over whatever discount curve the valuation layer supplies. Both are columns of result_cf(), so the identity is verifiable in the frame rather than only in prose. The shape to expect is a large first-month strain — the whole acquisition charge falls in month 0, at 2.5 % of a 37-year Beitragssumme — then thin positive margins that thin further as the inception rate accelerates from the mid-forties.

Cells Descriptions#

model_point()[source]#

The selected model point as a Series.

status()[source]#

The state at t = 0: aktiv or leistung.

aktiv seeds the whole policy into pols_actv(); leistung seeds it into the disabled ledger at duration seed_claim_dur() instead, which is how an in-force claim is valued. It does not affect the first-order shadow used to price the contract: that always runs from inception as an active life, because the Bruttobeitrag was struck there.

entry_age()[source]#

The Eintrittsalter: age last birthday at inception.

Age last birthday advancing at the policy anniversary rather than the birthday [std] — the model carries no dates, so age() steps every twelfth month from here. An implementation on real dates carries a fractional offset of at most one year.

sex()[source]#

The insured’s sex, M or F. Reporting only — it must not price.

Sex-differentiated premiums and benefits have been unlawful in Germany for contracts written from 21 December 2012, and the shipped decrement tables are unisex, so nothing in this model reads this cells. The tension worth knowing is that the underlying Invalidisierungswahrscheinlichkeiten do differ by sex, so a unisex BU tariff embeds a mix assumption the insurer bears the risk of. Model points 1 and 2 differ in this attribute alone, and their frames are identical.

berufsgruppe()[source]#

The occupational rating class, BG1 - BG5; the key into occupation_table.csv.

occ_factor()[source]#

kappa: the occupational loading on the inception rate, from the table.

BG1 1.00 (the reference class) to BG5 4.50. It multiplies inc_rate_base() and therefore reaches the premium only through the equivalence — it is not the Risikozuschlag, which loads the premium alone and leaves the claims untouched. Because the flat administration and assessment costs do not scale with the risk, a model point three times the anchor’s inception rate carries a premium slightly below three times the anchor’s.

bu_rente_mth()[source]#

R: the agreed monthly BU-Rente at inception, in euros.

The product’s only substantive benefit. Subject in the market to an Angemessenheitsgrenze capping it at 60-70 % of gross income, which is a underwriting rule rather than a projection parameter and is not modelled.

cover_end_age()[source]#

The Endalter: the attained age at which the Versicherungsdauer ends.

Cover ceases here, so the last projected month is the last month of attained age cover_end_age() - 1. It is the market’s dominant premium lever, because the inception rate accelerates from the mid-forties and cutting the Endalter removes the most expensive years of cover.

benefit_end_age()[source]#

The Leistungsendalter: the attained age at which the Leistungsdauer ends.

A separate contractual term from cover_end_age() and not a synonym: model point 9 carries cover to 67 and benefit to 63, so a claim incepting at 62 is paid for one year while the premium runs for five more. From this age the BU-Rente and the claim-maintenance cost are zero and the disabled mass is held rather than deleted, so both state identities still close.

karenz_months()[source]#

K: the Karenzzeit — the agreed deferment of payment, in months.

Not the six-month Prognosezeitraum, which is part of the definition of Berufsunfaehigkeit; the Karenzzeit defers payment on a BU already established. Cohorts at duration z <= K are berufsunfaehig, are not paid, and still pay premium, because the Beitragsbefreiung runs with the benefit [std].

leistungsdyn_rate()[source]#

g_L: the Leistungsdynamik — annual escalation of the BU-Rente in payment.

Steps on each anniversary of the onset, not of the policy. Compounding 2 % over a claim that can run thirty years raises the final payment to about 1.70x the first.

premium_form()[source]#

The premium form: level or dynamik.

level is a Bruttobeitrag guaranteed flat for the whole term. dynamik is the Beitragsdynamik: premium and insured BU-Rente escalate together at beitragsdyn_rate() on each policy anniversary, and the whole escalating stream is priced by one equivalence at inception. That is internally consistent but is not the German market’s practice, which prices each increment at the attained age reached, so a given increase buys less than proportional cover; the direction is recorded rather than corrected.

beitragsdyn_rate()[source]#

g_B: the effective Beitragsdynamik rate, net of declined increases.

Zero on the level form. Folding take-up into an effective rate — a policyholder accepting two increases in three is represented by a lower rate — is the honest treatment of an option whose decline behaviour no source quantifies, and it keeps the equivalence acyclic.

prem_mode()[source]#

The payment frequency: annual, half_yearly, quarterly or monthly.

The key into freq_loading_table.csv, which supplies both the months between instalments and the Ratenzahlungszuschlag.

prem_mode_months()[source]#

M: the number of months between premium instalments — 12, 6, 3 or 1.

A premium falls in month t when duration_mth(t) is a multiple of it, so on the anchor cell one falls every month and on model point 4 one falls in months 0, 12, 24, … and is twelve times as large. Carrying the frequency as a parameter rather than smoothing it is the whole reason this model runs on a monthly grid.

freq_load()[source]#

phi: the Ratenzahlungszuschlag on the tariff premium [std].

1.00 annual, 1.02 half-yearly, 1.03 quarterly, 1.05 monthly. It loads the Bruttobeitrag and the Beitragsverrechnung together, so beitragsverrechnung() stays exactly the ratio the tariff quotes and the loading is invisible in the Brutto / Zahl split.

gross_prem_ann()[source]#

The annual Bruttobeitrag override, in euros; 0 means derive by equivalence.

Only model point 13 supplies one. A real tariff would read the premium off a rate card; no German BU rate card of any kind was obtained for this library, so the derivation is the default and the override is the exception.

beitragsverrechnung()[source]#

theta: the Zahlbeitrag / Bruttobeitrag ratio, held constant [std].

0.70 on twelve of the thirteen model points, against a recalled market range of 0.50 - 0.80. This is the Beitragsverrechnung: the anticipated Ueberschuss credited against the premium in advance under § 153 VVG through § 176, with the MindZV risk-result minimum allocation behind it. Holding it constant is the model’s single largest discretionary assumption; the insurer may reduce the credit as far as the Bruttobeitrag and no further, and across the recalled range collected premium moves by more than 40 % either way.

risk_factor()[source]#

rho: the Risikozuschlag — a multiplier on the Bruttobeitrag and nothing else.

It prices an individually assessed impairment that the base inception table does not carry, and which this model does not carry either, so the loaded contract is projected above its own modelled cost. Contrast occ_factor(), which loads the inception rate and so moves every claim and every decrement.

au_klausel()[source]#

Whether the AU-Klausel is elected.

The clause pays on a certificate of six months’ Arbeitsunfaehigkeit without a BU determination. Carried as machinery whose effect runs entirely through au_uplift(), which ships at 1.00 everywhere, so the switch is demonstrably inert until a user supplies a number.

au_uplift()[source]#

upsilon: the inception uplift when the AU-Klausel is on; 1.00 when it is off.

Shipped at 1.00 everywhere — no source in this library’s corpus quantifies what the clause adds to the incidence, and inventing a loading would be worse than leaving the machinery visibly inert. It is one of the three multipliers composed into inc_rate() and the only one that can be switched off by a model point column.

wiedereingliederung_months()[source]#

The Wiedereingliederungshilfe, expressed in monthly Renten; 0 switches it off.

Paid on the completion of the run-off, so a life that dies inside the run-off never returns to work and is paid nothing. Paying it on every recovery instead overstates it, and the difference is exactly the run-off’s own mortality.

duration_init_months()[source]#

Elapsed policy months at t = 0; 0 for a new-business point.

It shortens proj_len(), shifts age() and policy_year(), decides which months carry a premium instalment, and suppresses the acquisition charge: an in-force point has already incurred it, and charging it again at the valuation date is a numbered pitfall. It does not touch the first-order shadow, which always runs from inception.

claim_duration_init()[source]#

Months since the onset of the BU at t = 0, for a leistung model point.

Zero on every aktiv point. See seed_claim_dur() for the cohort it seeds.

pols_if_init()[source]#

The policy count at t = 0: 1.0 for every model point.

This is a per-policy probability projection, one model point at a time, so every ledger is a probability and every cash flow an expected amount per policy in force at the valuation date. result_cf()’s first pols_if value is this number exactly.

proj_len()[source]#

n: the number of projected months, so the frame runs t = 0 ... proj_len() - 1.

12 x (cover_end_age() - entry_age()) - duration_init_months(). Cover ceases at attained age cover_end_age(), so the last projected month is the last month of attained age cover_end_age() - 1, index proj_len() - 1. On the anchor cell that is 12 x (67 - 30) = 444 monthly rows, the last of them t = 443.

This is the library’s reading of proj_len() — the exclusive end of the frame, the row count rather than the last index — and result_cf().index[-1] == proj_len() - 1 is asserted for every model point.

first_len()[source]#

The number of months in the first-order pricing run, from inception.

12 x (cover_end_age() - entry_age()), which is proj_len() + duration_init_months(), so the run is s = 0 ... first_len() - 1. pols_actv_first() admits s == first_len() as well, the month past the end, exactly as the roll-forward checks read t == proj_len() on the main frame. The shadow ledgers run over the contract’s original term whatever duration the model point has already run, because the Bruttobeitrag was struck at inception and does not change afterwards.

duration_mth(t)[source]#

u(t): elapsed policy months at the start of month t.

duration_init_months() + t. Defined for negative t as well, because bu_rente_pp() reads it there to recover the insured BU-Rente at the onset of a claim that began before the valuation date.

policy_year(t)[source]#

y(t): the policy year containing month t, 1-based.

duration_mth(t) // 12 + 1, floored at 1 so that a negative t reaching back before inception reads as policy year 1 rather than as a year 0 the tariff never had.

age(t)[source]#

x(t): the attained age at month t, age last birthday.

entry_age() + duration_mth(t) // 12, so it steps at the policy anniversary rather than at a birthday the model does not carry [std]. Every rate lookup in the model is keyed by this age.

age_first(s)[source]#

The attained age at month s of the first-order pricing run.

entry_age() + s // 12: the shadow ledgers run from inception, so their age clock ignores duration_init_months().

policy_year_first(s)[source]#

The policy year containing month s of the first-order pricing run, 1-based.

claim_year(z)[source]#

The claim year containing claim duration z, 1-based and capped at the table’s last row.

(z - 1) // 12 + 1, capped at 11: rows 1-10 of claim_duration_table.csv are claim years 1-10 and row 11 is the ultimate. Duration z = 1 is the first month a claim can be paid, so z = 1 ... 12 is claim year 1 and z = 13 opens claim year 2.

seed_claim_dur()[source]#

The claim-duration cohort the initial population occupies, or 0 if it is not seeded.

claim_duration_init() + 1 on a leistung model point, 0 on an aktiv one: a claim whose onset was claim_duration_init() months before the valuation month is in its next month of duration at the start of it, exactly as a claim recognised at the end of month t sits at z = 1 at the start of month t + 1.

max_claim_dur()[source]#

The longest claim duration the cohort vectors have to carry.

proj_len() + seed_claim_dur() + 1. A cohort seeded at seed_claim_dur() reaches seed_claim_dur() + proj_len() at t = proj_len(), the month past the end of the frame that the roll-forward checks read, and the extra element is what makes the duration shift lossless — the last slot is structurally zero, so nothing falls off the end.

cohort_len(t)[source]#

The number of claim-duration cohorts that can be non-zero at the start of month t.

min(max_claim_dur(), t + seed_claim_dur() + 1). The vectors are truncated to this length rather than carried at full max_claim_dur() from month zero, which is purely a cost decision: pols_dis_dur() returns zero past the end of the list, so nothing about the two-dimensional view changes.

cohort_len_first(s)[source]#

The cohort-vector length in the first-order pricing run at month s.

min(first_len() + 1, s + 1): the shadow chain always starts as a new-business active life, so it carries no seeded claim and its first cohort appears at s = 1.

inc_rate_at_age(x)[source]#

i(x): the table Invalidisierungswahrscheinlichkeit at attained age x, per year.

A lookup into inception_table.csv, clamped to the table’s range 18-66. [std] and gross of declinature: accept_factor sits on top of it, and a replacement table already net of declinature must be used with that factor at 1.00 or the Anerkennungsquote is counted twice.

mort_rate_at_age(x)[source]#

q^a(x): the table active-lives mortality rate at attained age x, per year.

A lookup into the mort_rate_actv column of mortality_table.csv, clamped to the table’s range 18-70. It applies to the aktiv ledger and to the § 174 run-off, whose lives have recovered and are no longer impaired lives.

mort_rate_dis_at_age(x)[source]#

q^i(x): the table disabled-lives mortality rate at attained age x, per year.

The mort_rate_dis column of mortality_table.csv, before the claim-duration select factor, clamped to the table’s range. Four times the active rate at every age; with the first claim year’s select factor of 3.0 that is twelve times active mortality in the first year of a claim, falling to 4.8x ultimate. Using one rate for both states is a numbered pitfall, which is why the two live in separate columns.

recov_rates()[source]#

The eleven annual reactivation rates by claim year, as a tuple.

Read once from claim_duration_table.csv and indexed by claim_year(z) - 1. The table itself is the object the projection uses; this cells exists so that the file is turned into a tuple once rather than once per cohort, which on a 444-month projection with 445 cohorts is the difference between a lookup and a quarter of a million of them.

mort_dis_sel_factors()[source]#

The eleven disabled-mortality select factors by claim year, as a tuple.

3.0 / 2.0 / 1.6 / 1.4 / 1.3 and 1.2 from claim year 6, read once from claim_duration_table.csv. See recov_rates() for why it is a tuple.

inc_rate_base(t)[source]#

i(x(t)): the table inception rate at month t, before every multiplier.

Published separately from inc_rate() so that the composition inc_rate = inc_rate_base x occ_factor x accept_factor x au_uplift is visible and testable rather than buried in one formula.

inc_rate(t)[source]#

The composed annual inception rate at month t.

inc_rate_base(t) x occ_factor() x accept_factor x au_uplift() — and those are the only three multipliers on it. risk_factor is deliberately not among them: it loads the Bruttobeitrag and leaves every claim untouched. Publishing the composition explicitly is what makes a substituted table that is already net of declinature visible rather than silent.

inc_rate_mth(t)[source]#

i_m(t): the monthly inception rate, 1 - (1 - inc_rate(t))^(1/12) [std].

mort_rate(t)[source]#

q^a(x(t)): the annual active-lives mortality rate at month t.

The library’s convention is that mort_rate is annual and mort_rate_mth() monthly. It applies to the aktiv ledger and to the § 174 run-off.

mort_rate_mth(t)[source]#

q^a_m(t): the monthly active-lives mortality rate, 1 - (1 - mort_rate(t))^(1/12).

mort_dis_sel_factor(z)[source]#

s(z): the disabled-mortality select factor at claim duration z.

mort_dis_sel_factors()[claim_year(z) - 1]: 3.0 in the first claim year falling to 1.2 from the sixth. Disabled-lives mortality is select on claim duration, not on attained age alone, and a model that drops the duration dimension understates deaths in exactly the months where the claim reserve is largest.

mort_rate_dis(t, z)[source]#

q^i(x(t), z): the annual disabled-lives mortality rate at month t, duration z.

mort_rate_dis_at_age(age(t)) x mort_dis_sel_factor(z). Twelve times mort_rate() at duration 1 and 4.8 times it ultimately; it is never equal to it, at any age or duration.

mort_rate_dis_mth(t, z)[source]#

q^i_m(t, z): the monthly disabled-lives mortality rate at month t, duration z.

1 - (1 - mort_rate_dis(t, z))^(1/12). This is the published two-dimensional view; the ledger recursions read mort_rate_dis_mth_year() instead, which is the same eleven numbers held once per month rather than once per cohort.

mort_rate_dis_mth_year(t)[source]#

The eleven monthly disabled-lives mortality rates at month t, by claim year.

mort_rate_dis_mth(t, z) depends on z only through claim_year(z), so the whole cohort vector needs eleven numbers per month rather than one per cohort. This is what the disabled-ledger recursions read; mort_rate_dis_mth() is the addressable view of the same numbers and the two agree by construction.

recov_rate(z)[source]#

r(z): the annual Reaktivierungswahrscheinlichkeit at claim duration z.

recov_rates()[claim_year(z) - 1]: 0.250 in the first claim year, 0.130 in the second, and 0.006 from the eleventh. The front-loading is the point — a claim that survives its first two years is very likely to run to the Leistungsendalter — and a flat rate is a modelling error rather than a simplification, worth roughly a factor of two on projected benefit in either direction.

This one rate covers both recovery and konkrete Verweisung. They end the benefit the same way, through the same Nachpruefung, with the same three-month run-off, and no public data separates them, so the model publishes exactly one claim-termination-other-than-death rate.

recov_rate_mth(z)[source]#

r_m(z): the monthly reactivation rate, 1 - (1 - recov_rate(z))^(1/12) [std].

recov_rate_mth_year()[source]#

The eleven monthly reactivation rates by claim year, as a tuple.

Constant in t, because the shipped reactivation basis carries no age-at-disablement dimension — DAV 1997 RI does, and that absence is named rather than hidden. The disabled-ledger recursions read this; recov_rate_mth() is the addressable view.

lapse_rate(t)[source]#

w(y(t)): the annual Stornoquote in the policy year containing month t.

4.0 % in the first two policy years falling to a 2.0 % ultimate from the sixth [std], capped at the table’s last row. Low by the standards of every other delib product, and that is a product fact: once health has changed the cover cannot be replaced, so an insured with a claimable impairment cannot rationally lapse. Lapse applies to pols_actv() only — a life in claim pays no premium and so cannot lapse for non-payment.

lapse_rate_mth(t)[source]#

w_m(t): the monthly lapse rate, 1 - (1 - lapse_rate(t))^(1/12) [std].

Strictly below the annual rate wherever the annual rate is positive, which is the library’s convention for the pair.

inc_rate_first(s)[source]#

The annual first-order inception rate at month s of the pricing run.

inc_rate_at_age(age_first(s)) x occ_factor() x accept_factor x au_uplift() x inc_load_first. Prudence for a disability product means a higher incidence, so the load is above one: a claim that starts more often.

inc_rate_first_mth(s)[source]#

The monthly first-order inception rate at month s of the pricing run.

recov_rate_first(z)[source]#

The annual first-order reactivation rate at claim duration z.

recov_rate(z) x recov_load_first, and the load is below one: prudence means a claim that ends less often and therefore lasts longer.

recov_rate_first_mth(z)[source]#

The monthly first-order reactivation rate at claim duration z.

recov_rate_first_mth_year()[source]#

The eleven monthly first-order reactivation rates by claim year, as a tuple.

mort_rate_first_mth(s)[source]#

The monthly first-order active-lives mortality rate at month s of the pricing run.

mort_rate_at_age(age_first(s)) x mort_actv_load_first, and that load is below one: on this contract an active death releases a liability, so it is favourable to the insurer and a prudent basis does not anticipate it. The same reasoning is why the first-order basis carries no lapse at all.

mort_rate_dis_first_mth(s, z)[source]#

The monthly first-order disabled-lives mortality rate at month s, duration z.

mort_rate_dis_at_age(age_first(s)) x mort_dis_sel_factor(z) x mort_dis_load_first, the load again below one so that claims run longer on the pricing basis than on the best-estimate one.

mort_rate_dis_first_mth_year(s)[source]#

The eleven monthly first-order disabled-mortality rates at month s, by claim year.

disc_first(s)[source]#

v^s: the Rechnungszins discount factor at month s, (1 + rechnungszins)^(-s/12).

Used only inside the equivalence that fixes prem_gross_level_pp(), and never to discount a published cash flow: this library projects gross liability cash flows undiscounted, and the valuation layers that discount them are cited rather than reproduced. The rate is the Hoechstrechnungszins for contracts written from 1 January 2025, and both the figure and its effective date are [unverified].

dyn_factor(t)[source]#

(1 + g_B)^(y(t) - 1): the Beitragsdynamik factor in the policy year containing t.

1.0 throughout on the level form. It escalates the insured BU-Rente and the annual Bruttobeitrag together, on the policy anniversary — a different quantity and a different clock from leistungsdyn_factor().

dyn_factor_first(s)[source]#

The Beitragsdynamik factor at month s of the first-order pricing run.

leistungsdyn_factor(z)[source]#

(1 + g_L)^((z - 1) // 12): the Leistungsdynamik factor at claim duration z.

Steps on each anniversary of the onset: cohorts z = 1 ... 12 are paid the amount they came in on, z = 13 ... 24 are paid 1.02 times it, and so on. A model that steps this on the policy anniversary has the wrong clock.

bu_rente_pp(t)[source]#

R(t): the insured monthly BU-Rente at month t, in euros.

bu_rente_mth() x dyn_factor(t), so it is constant on the level form and escalates on the policy anniversary on the dynamik one. It is the amount a claim incepting at t comes into payment on; once in payment the amount leaves this cells behind and moves at leistungsdyn_factor() instead. Defined for negative t, where it returns the policy-year-1 amount, so that a claim seeded at t = 0 on an in-force point can recover the amount it came in on.

bu_rente_pp_first(s)[source]#

The insured monthly BU-Rente at month s of the first-order pricing run.

rente_pay_pp(t, z)[source]#

R_p(t, z): the monthly BU-Rente in payment at month t for the cohort at duration z.

bu_rente_pp(t - z) x leistungsdyn_factor(z): the insured amount at the moment of onset, which for a cohort at duration z in month t is month t - z, escalated on each anniversary of that onset.

The projection itself carries the product of this and the cohort population in the value vector of dis_cohorts(), so this cells is the addressable view rather than the hot path. The two agree by construction, and the identity pols_dis_dur(t, z) x rente_pay_pp(t, z) against that vector is a test.

beitragssumme_unit()[source]#

BS_unit: the Beitragssumme per 1 EUR p.a. of Bruttobeitrag.

sum over y = 1 .. (cover_end_age() - entry_age()) of (1 + g_B)^(y - 1) — 37 on the anchor cell, where the Beitragsdynamik is off. It is the base of the acquisition charge, which § 4 DeckRV caps at 25 per mille (2.5 %) of it, and it is the whole original term rather than the remaining one: the charge was incurred at inception.

prem_gross_level_pp()[source]#

P: the level annual Bruttobeitrag per policy, in euros.

risk_factor() times either the model point’s gross_prem_ann override or, where that is zero, the premium the equivalence produces:

P = (pv_rente_first() + pv_wiedereingl_first() + pv_claim_cost_first()
     + pv_admin_first())
    / (pv_prem_unit_first() x (1 - admin_prem_rate)
       - acq_rate x beitragssumme_unit())

struck on the first-order shadow ledgers, without lapse and before the Risikozuschlag. It is linear in P because both loadings are proportional to it, and it is acyclic because no decrement in this model depends on the premium.

This is the Bruttobeitrag: the contractually guaranteed maximum. What is actually charged is prem_zahl_pp(), beitragsverrechnung() times it.

prem_gross_ann_pp(t)[source]#

The annual Bruttobeitrag in force in the policy year containing month t.

prem_gross_level_pp() x dyn_factor(t) — level on the level form, growing by 1 + beitragsdyn_rate() each policy year on the dynamik one. It is an annual rate, not an instalment; prem_gross_pp() turns it into what is billed.

prem_due(t)[source]#

1 in a month a premium instalment falls due, 0 otherwise.

duration_mth(t) % prem_mode_months() == 0. On the anchor cell that is every month; on model point 4, months 0, 12, 24, …; and on model point 6, whose valuation date is 180 policy months in and whose mode is half-yearly, months 0, 6, 12, …

prem_gross_pp(t)[source]#

P_b(t): the Bruttobeitrag instalment due at the start of month t, per policy.

prem_due(t) x prem_gross_ann_pp(t) x freq_load() x prem_mode_months() / 12, so it is zero in a month that is not a payment month and carries the whole period’s premium in one that is. The Ratenzahlungszuschlag scales it, which is why the annual Bruttobeitrag of a monthly payer buys a 5 % larger bill than the tariff amount.

prem_zahl_pp(t)[source]#

P_z(t): the Zahlbeitrag instalment actually billed at month t, per policy.

beitragsverrechnung() x prem_gross_pp(t) — the Bruttobeitrag less the Beitragsverrechnung. This is the number a Produktinformationsblatt prints beside the Bruttobeitrag, and the one the policyholder pays; the gap between them is the anticipated Ueberschuss credited in advance, and it can be withdrawn as far as the Bruttobeitrag and no further.

surplus_credit_pp(t)[source]#

(1 - theta) P_b(t): the Beitragsverrechnung credited at month t, per policy.

Published as its own quantity rather than netted inside the premium, so that the Ueberschussbeteiligung is a visible line of the cash flow statement. A model carrying only the Zahlbeitrag silently assumes this credit is permanent.

pols_actv_first(s)[source]#

The aktiv population at the start of month s of the first-order pricing run.

Unit size at s = 0 whatever the model point’s own status, because the Bruttobeitrag was struck at inception on a new-business active life. Thereafter - deaths - inceptions + reactivations, with no lapse: a prudent German pricing basis does not anticipate a decrement that releases the liability.

pols_inception_first(s)[source]#

Transitions aktiv to leistungspflichtig at the end of month s, first-order basis.

Taken from the survivors of the month’s mortality; there is no lapse to survive.

dis_cohorts_first(s)[source]#

The first-order disabled ledger at the start of month s: (populations, values).

Element z - 1 of each list is the state at claim duration z. The second list is a population times the monthly *BU-Rente* it is paid, rolled with the same survival factors and stepped by 1 + leistungsdyn_rate() at each anniversary of onset, so the month’s benefit is one sum over a list. The shadow chain starts empty: at s <= 0 both lists are zeros, because the pricing run is always a new-business active life. See dis_cohorts() for the same construction on the best-estimate basis, which this mirrors exactly but for the loaded rates and the absent lapse.

dis_exits_first(s)[source]#

The first-order disabled ledger’s month-end exits: (deaths, recoveries, value).

One pass over the cohort vectors at month s, taking deaths first and terminations on the survivors. The third element is the recoveries times the *BU-Rente* they were on, which is what enters run-off slot 1 as a value.

runoff_cohorts_first(s)[source]#

The first-order § 174 run-off at the start of month s: (populations, values).

Three slots, both lists rolled at active-lives mortality because these lives have recovered. Slot 1 is last month’s claim terminations and the value they carried.

pols_dis_dur_first(s, z)[source]#

The first-order disabled population at claim duration z, start of month s.

pols_dis_first(s)[source]#

The whole first-order disabled ledger at the start of month s.

pols_runoff_first(s)[source]#

The whole first-order § 174 run-off ledger at the start of month s.

runoff_val_first(s, k)[source]#

The first-order run-off slot k times the monthly BU-Rente it is being paid.

pols_reactivation_first(s)[source]#

First-order run-off completions returning to aktiv at the end of month s.

The last run-off slot’s survivors of the month’s active-lives mortality. Three months behind dis_exits_first()’s recoveries, which is § 174 in arithmetic.

pols_prem_first(s)[source]#

The first-order premium-paying count at the start of month s.

pols_actv_first(s) plus the disabled cohorts still inside the Karenzzeit, on the same reading as pols_prem(): the Beitragsbefreiung runs with the benefit, so a life inside the Karenzzeit still pays.

pols_if_first(s)[source]#

The whole first-order in-force population at the start of month s.

The three shadow ledgers summed. It is the base of the flat administration charge in the equivalence and is read nowhere else.

pv_prem_unit_first()[source]#

PV_prem: the present value of 1 EUR p.a. of Bruttobeitrag, first-order basis.

sum over s of disc_first(s) x dyn_factor_first(s) x pols_prem_first(s) / 12: a twelfth of the annual premium in every month of the pricing run, weighted by the premium-paying population. The Ratenzahlungszuschlag is deliberately not in it, which is what makes freq_load a genuine loading on the tariff premium rather than a re-expression of it.

pv_rente_first()[source]#

PV_rente: the present value of the BU-Rente, first-order basis.

The disabled cohorts past the Karenzzeit plus all three run-off slots, each already carried as a population times its own BU-Rente, discounted at disc_first() and zero from the Leistungsendalter. Dropping the run-off from this sum understates the premium by the whole of the § 174 tail.

pv_wiedereingl_first()[source]#

PV_wgh: the present value of the Wiedereingliederungshilfe, first-order basis.

wiedereingliederung_months() monthly Renten paid on each completed run-off, so a life that dies inside the run-off is paid nothing. Zero on a model point with the benefit switched off.

pv_claim_cost_first()[source]#

PV_cost: the present value of the Leistungsbearbeitungskosten, first-order basis.

claim_assess_cost on each inception plus claim_maint_cost_mth on each month a claim is in payment — the disabled cohorts past the Karenzzeit and all three run-off slots, and nothing at all from the Leistungsendalter. These are flat euro amounts, which is why a heavier occupational class carries a premium slightly below the ratio of its inception rates.

pv_admin_first()[source]#

PV_admin: the present value of the flat Verwaltungskosten, first-order basis.

admin_flat_ann / 12 on every in-force life in every month, uninflated: a German Verwaltungskostenzuschlag is fixed in the tariff at conclusion. The proportional administration loading is not here — it is proportional to the premium, so it stays on the left-hand side of the equivalence and reduces the denominator instead.

pols_actv(t)[source]#

l_a(t): the aktiv population at the start of month t.

Premium-paying and exposed to inception, active-lives mortality and lapse. Seeded with pols_if_init() on an aktiv model point and with zero on a leistung one. Thereafter - deaths - lapses - inceptions + reactivations, the last of them being the § 174 return arc three months behind the recovery that produced it.

pols_death_actv(t)[source]#

Deaths out of the aktiv ledger at the end of month t.

Taken first, before lapses and inceptions, which is the model’s stated processing order [std]. A decrement and never a cash flow: an SBU pays nothing on death.

pols_lapse(t)[source]#

Lapses at the end of month t, from the aktiv ledger only.

Taken from the survivors of the month’s mortality. A lapse pays nothing here: § 169 VVG through § 176 gives this contract a real Rueckkaufswert and § 165 a real beitragsfreie BU-Rente, but both are the release of a reserve this model does not compute, so a lapse is a pure decrement and claims(t, "LAPSE") is zero.

pols_inception(t)[source]#

Transitions aktiv to leistungspflichtig at the end of month t.

Taken from the survivors of both the month’s mortality and its lapses, and weighted by the composed inc_rate_mth(), which already carries the occupational factor, the Anerkennungsquote and the AU-Klausel uplift. These lives enter claim duration z = 1 at the start of month t + 1, which is when their first BU-Rente falls due if there is no Karenzzeit. Each one costs claim_assess_cost.

dis_cohorts(t)[source]#

The disabled ledger at the start of month t: (populations, values).

Element z - 1 of each list is the state at claim duration z, for z = 1 ... cohort_len(t). The second list is a population times the monthly *BU-Rente* it is being paid rather than a population, so that the month’s benefit is one sum over a slice rather than a lookup per cohort; it rolls with the same survival factors and is stepped by 1 + leistungsdyn_rate() exactly when a cohort crosses an anniversary of its own onset, which is when z is a multiple of 12.

At t <= 0 the vectors are the seeded state: all zeros on an aktiv model point, and pols_if_init() at cohort seed_claim_dur() on a leistung one, whose value is the BU-Rente that claim came into payment on. Thereafter cohort 1 is the previous month’s inceptions and every other cohort is the previous cohort survived one month of disabled-lives mortality and one month of reactivation.

This is the model’s list-valued cells and the reason is cost: a two-argument recursion would be proj_len() x max_claim_dur() separate cells — nearly two hundred thousand on the anchor cell — where this is proj_len() cells with a loop inside. pols_dis_dur() reads elements out of it, so the notes’ two-dimensional object is still addressable by name. A new list is built on each step rather than the previous one mutated, so holding a returned list cannot corrupt the cache.

dis_exits(t)[source]#

The disabled ledger’s month-end exits at t: (deaths, recoveries, recovery value).

One pass over the cohort vectors, taking deaths at mort_rate_dis_mth_year() first and terminations at recov_rate_mth_year() on the survivors, which is the model’s stated processing order. The third element is the terminations times the *BU-Rente* they were on: that amount is frozen at the Nachpruefung date and is what the run-off goes on paying.

Published as one cells because the three totals come from the same pass and because dis_cohorts() would otherwise have to be walked three times a month.

runoff_cohorts(t)[source]#

The § 174 run-off at the start of month t: (populations, values).

runoff_months slots — three, the statutory run-off being to the end of the third month after the Einstellungsmitteilung reaches the policyholder. Slot 1 is last month’s claim terminations; slots 2 and 3 are the previous slots survived one month of active-lives mortality, because these lives have recovered and are no longer impaired lives. The value list is the same population times the BU-Rente it is still being paid, frozen at the Nachpruefung date and receiving no further Leistungsdynamik [std].

Empty at t <= 0 on every model point, including the in-force claim: a claim in payment is leistungspflichtig, not in run-off.

pols_dis_dur(t, z)[source]#

l_d(t, z): the leistungspflichtig population at claim duration z, start of month t.

Zero outside 1 <= z <= cohort_len(t), so the two-dimensional view is total.

pols_dis(t)[source]#

The whole leistungspflichtig ledger at the start of month t.

sum over z of pols_dis_dur(t, z). It includes cohorts still inside the Karenzzeit, which are berufsunfaehig, are not yet paid and are still paying premium.

pols_death_dis(t)[source]#

Deaths out of the disabled ledger at the end of month t.

At disabled-lives mortality, which is select on claim duration: twelve times the active rate in the first claim year, 4.8 times it ultimately.

pols_recovery(t)[source]#

Claim terminations other than death at the end of month t, entering the run-off.

Recovery and konkrete Verweisung together — one rate, because they end the benefit the same way, through the same Nachpruefung, with the same run-off, and no public data separates them. These lives do not rejoin :func:`pols_actv` here: they enter run-off slot 1 and return three months later as pols_reactivation().

runoff_value_in(t)[source]#

The recoveries at the end of month t times the BU-Rente they were on.

What enters run-off slot 1 as a value. Carried as an amount rather than recomputed from a count because the cohorts terminating in one month are on different BU-Renten — they incepted in different months and have crossed different numbers of onset anniversaries.

pols_runoff_slot(t, k)[source]#

l_r(t, k): the § 174 run-off population k months into the run-off, start of month t.

runoff_val(t, k)[source]#

V_r(t, k): run-off slot k times the monthly BU-Rente it is being paid.

A value ledger rather than a count, because a cohort entering the run-off keeps the BU-Rente it was on at the Nachpruefung date. It is what makes the run-off’s benefit and the Wiedereingliederungshilfe computable without a second duration dimension.

pols_runoff(t)[source]#

The whole § 174 run-off ledger at the start of month t.

The ledger a naive model omits, and omitting it is a first-order error: a recovery does not release the liability in the month it happens, it releases it three months later, and every one of those months carries a full BU-Rente.

pols_death_runoff(t)[source]#

Deaths out of the § 174 run-off at the end of month t, at active-lives mortality.

These lives have recovered, so they are no longer impaired lives. A death here also extinguishes the Wiedereingliederungshilfe the life would have been paid on completing the run-off.

pols_reactivation(t)[source]#

Run-off completions returning to aktiv at the end of month t.

The last run-off slot’s survivors of the month’s active-lives mortality. The Beitragsbefreiung stops with them, the premium resumes at the same Zahlbeitrag, and a fresh BU may be claimed later — so these lives re-enter pols_actv() fully exposed to inception again. They are also the population the Wiedereingliederungshilfe is paid on.

pols_death(t)[source]#

Deaths out of all three ledgers at the end of month t.

A decrement and never a cash flow. An SBU pays nothing on death, before or during a claim, so there is no claims_death column for a reader arriving from a term-life model to find. With pols_lapse() it is one of the only two exits from the model.

pols_if(t)[source]#

L(t): the policy count at the start of month t, and the weight on that row.

Built by its own roll-forward — pols_if(t-1) - pols_death(t-1) - pols_lapse(t-1), from pols_if_init() at t = 0 — rather than as the sum of the three ledgers, and that is deliberate. Death and lapse are the only exits from this model: inception, recovery and reactivation are internal transfers between the ledgers. Building pols_if from the exits and then comparing it against the ledgers in check_states() makes that structural claim a real test at every t, where defining it as the sum would make the check a restatement of its own definition.

It is the count at the start of the month, before any decrement, so it is the weight on that same result_cf() row’s cash flows and result_cf()’s first value is pols_if_init() exactly. End-of-month state goes through pols_if_at().

pols_if_at(t, timing)[source]#

The policy count at a point inside month t.

"BEG"

pols_if(t), the start of the month before any decrement — the same number as pols_if() and the weight on that month’s cash flows.

"END"

pols_if(t + 1), the end-of-month state after deaths and lapses. At t = proj_len() - 1 it is the population whose cover simply runs out, and nothing is payable to it.

pols_prem(t)[source]#

L_p(t): the premium-paying count at the start of month t.

pols_actv(t) plus the disabled cohorts still inside the Karenzzeit, sum over z <= karenz_months() of pols_dis_dur(t, z). A life inside the Karenzzeit is berufsunfaehig but is not yet being paid, so the Beitragsbefreiung has not started [std].

This, and not :func:`pols_if`, is the weight on the premium. Weighting the premium by the in-force count charges premium to lives in claim and so silently deletes the Beitragsbefreiung, which is core cover rather than an option — the classic German BU implementation error. On the anchor cell, where the Karenzzeit is zero, this equals pols_actv() at every t.

premiums(t)[source]#

The gross Bruttobeitrag income at the start of month t, an inflow.

prem_gross_pp(t) x pols_prem(t), zero in a month that is not a payment month. This is the gross stream; the Beitragsverrechnung returned out of it is surplus_credit(), and the cash actually collected is the difference. Publishing both is what keeps the Ueberschussbeteiligung a visible line rather than a netting hidden inside the premium.

surplus_credit(t)[source]#

The Beitragsverrechnung credited back at the start of month t, an outflow.

surplus_credit_pp(t) x pols_prem(t) — 30 % of every Bruttobeitrag at the shipped ratio. It is the Ueberschussbeteiligung of § 153 VVG through § 176, applied immediately as a reduction of the premium charged rather than accumulated, which is the standard Ueberschussverwendung in German BU and is why this model carries no surplus account, no RfB and no declaration mechanic.

claims(t, kind=None)[source]#

Benefit outgo at the start of month t, by kind; the total when kind is omitted.

"BU_RENTE"

the monthly annuity, paid in advance to the disabled cohorts past the Karenzzeit and to all three § 174 run-off slots, each already carried as a population times its own BU-Rente. Zero from the Leistungsendalter, where the mass is held rather than deleted.

"REINTEGRATION"

the Wiedereingliederungshilfe, wiedereingliederung_months() monthly Renten on each completed run-off, so a life that dies inside the run-off never returns to work and is paid nothing. It is not gated at the Leistungsendalter: a life may return to work after it while the cover still runs.

"LAPSE"

zero, always. § 169 VVG through § 176 gives this contract a real Rueckkaufswert and § 165 a real beitragsfreie BU-Rente, and this model prices neither, because both are the release of a reserve it deliberately does not compute. The kind exists so that the zero is a published scope statement rather than a missing column.

expenses(t)[source]#

Total administration expense at the start of month t [std].

Three components, all shipped as standardizations because no German insurer publishes a BU charge structure and a pure risk contract carries no Effektivkosten disclosure:

  • acquisition, acq_rate x prem_gross_level_pp() x beitragssumme_unit(), levied once, at t = 0, and only on a new-business point — an in-force point has already incurred it, and charging it again at the valuation date is a numbered pitfall. At 2.5 % of a 37-year Beitragssumme it is the largest single expense item in the model and it dominates the first month’s net_cf();

  • proportional administration, admin_prem_rate x premiums(t), in every month a premium is due;

  • flat administration, admin_flat_ann / 12 per in-force policy per month, uninflated, because a German Verwaltungskostenzuschlag is fixed in the tariff at conclusion.

Commission is not a separate line: it sits inside acq_rate, which is the German taxonomy. Claim handling is not here either — it is claim_expenses(), because it scales with claims rather than with policies.

claim_expenses(t)[source]#

The Leistungsbearbeitungskosten at month t [std].

claim_assess_cost on each inception at the end of the month — the Leistungspruefung of a German BU claim is expensive and is incurred once, when the claim is decided — plus claim_maint_cost_mth on each month a claim is in payment, on the same population the BU-Rente is paid to: the disabled cohorts past the Karenzzeit and all three run-off slots, and nothing from the Leistungsendalter.

Named separately from expenses() because it is the only expense line that scales with claims rather than with policies, and because both are flat euro amounts, which is what makes a heavier occupational class carry a premium slightly below the ratio of its inception rates.

net_cf(t)[source]#

The net liability cash flow of month t, income positive.

The gross Bruttobeitrag less the Beitragsverrechnung returned out of it, less the BU-Rente, the Wiedereingliederungshilfe and the (structurally zero) lapse benefit, less administration expense and Leistungsbearbeitungskosten. The library-wide sign; liability_cf() publishes the same stream outgo-positive.

The shape to expect is a large first-month strain — the whole acquisition charge falls in month 0 — then thin positive margins that thin further as the inception rate accelerates from the mid-forties, and turn negative in the last years before the Endalter, which is exactly the Deckungsrueckstellung this model does not compute being run down.

liability_cf(t)[source]#

The same stream as net_cf(), outgo positive: -net_cf(t) exactly.

The orientation a valuation layer consumes: a Solvency II best estimate is sum v(t) liability_cf(t) over whatever risk-free term structure is supplied, plus a risk margin. Published as a column beside net_cf() so the sign convention is verifiable in the frame rather than only in prose.

check_net_cf_resid(t)[source]#

The cash-flow-statement residual in month t; zero everywhere.

delib’s first ruling: every model reconstructs net_cf() from its own published parts, so the headline number of a cash flow model is not the one quantity nothing checks. The identity is

net_cf(t) = prem_zahl_pp(t) x pols_prem(t)
  • claims(t, “BU_RENTE”) - claims(t, “REINTEGRATION”)

  • claims(t, “LAPSE”) - expenses(t) - claim_expenses(t)

and the premium leg is deliberately rebuilt from the Zahlbeitrag actually billed times the premium-paying count rather than from premiums(t) - surplus_credit(t). That makes this a real reconciliation instead of a restatement of net_cf()’s own formula: it crosses the Brutto / Zahl split, and it fails if the premium is weighted by pols_if() instead of pols_prem() — which is this product’s classic implementation error, and one that leaves every total in the frame looking plausible.

check_net_cf()[source]#

True when the cash flow statement reconciles in every projected month.

No argument, one bool over all t, the library-wide shape; check_net_cf_resid() gives the signed residual of the month that failed.

check_states_resid(t)[source]#

The state-decomposition residual at the start of month t; zero everywhere.

pols_if(t) - pols_actv(t) - pols_dis(t) - pols_runoff(t).

This is the model’s structural check, and it is not trivially zero, because pols_if() is built by its own roll-forward off the two exits rather than as the sum of the three ledgers. What it catches is a life that leaves one ledger without arriving in another, or arrives in two: a recovery that both rejoins pols_actv and stays in the run-off, an inception counted in the disabled ledger and left in the active one, a run-off slot that falls off the end of its list. Every one of those is a first-order error in a multi-state model, and none of them shows in the cash flows as anything but a number that is slightly wrong.

check_states()[source]#

True when the three ledgers account for the whole in-force population at every t.

check_pols_roll_fwd_resid(t)[source]#

The in-force roll-forward residual in month t; zero everywhere.

pols_if(t) - pols_if(t+1) - pols_death(t) - pols_lapse(t). Death and lapse are the only exits, so inception, recovery and reactivation — which are internal transfers between the three ledgers — must not appear here. Putting them in is how a multi-state model silently loses mass.

Trivially zero by construction on this model, because pols_if() is defined by exactly this recursion. It is published because it is the notes’ own identity and because the substantive content of it — that the exits really are the only two — is asserted by check_states(), which compares that recursion against the ledgers. Read the two together: this one fixes the definition, that one tests it.

check_pols_roll_fwd()[source]#

True when the in-force roll-forward closes in every projected month.

check_dis_roll_fwd_resid(t)[source]#

The disabled-ledger roll-forward residual in month t; zero everywhere.

pols_dis(t+1) - pols_dis(t) + pols_death_dis(t) + pols_recovery(t) - pols_inception(t). The disabled ledger gains the month’s inceptions and loses its own deaths and its claim terminations, and nothing else — in particular it does not lose anything at the Leistungsendalter, where the benefit stops but the mass is held. It is built by direct comparison of the ledger total against the flows, so a cohort dropping off the end of the vector, a duration shift that loses a slot, or a Karenzzeit mistakenly applied to the population rather than to the payment all fail here.

check_dis_roll_fwd()[source]#

True when the disabled ledger rolls forward exactly in every projected month.

check_runoff_roll_fwd_resid(t)[source]#

The § 174 run-off roll-forward residual in month t; zero everywhere.

pols_runoff(t+1) - pols_runoff(t) + pols_death_runoff(t) + pols_reactivation(t) - pols_recovery(t). The run-off gains the month’s claim terminations and loses its own deaths and its completions, so a model that returns a recovery straight to pols_actv — the commonest way to forget § 174 — fails here immediately, and with it loses three monthly BU-Renten per recovery.

check_runoff_roll_fwd()[source]#

True when the § 174 run-off ledger rolls forward exactly in every projected month.

check_prem_split_resid(t)[source]#

The Brutto / Zahl split residual in month t; zero everywhere.

premiums(t) - surplus_credit(t) - prem_zahl_pp(t) x pols_prem(t). The two premium columns must reconcile to the Zahlbeitrag actually billed, so that a reader can recover the cash collected from the frame without knowing the ratio. It also fixes the Ratenzahlungszuschlag’s place: freq_load scales the Bruttobeitrag and the Beitragsverrechnung together, so it cancels out of this identity and beitragsverrechnung stays exactly the ratio the tariff quotes.

check_prem_split()[source]#

True when the two premium columns reconcile to the Zahlbeitrag in every month.

check_cover_end_resid(t)[source]#

The cover-cessation residual in month t; zero everywhere.

claims(t, "BU_RENTE") in every month whose attained age has reached benefit_end_age(), plus premiums(t) in every month whose attained age has reached cover_end_age(), and zero before both.

The second term is vacuous by construction of proj_len(), which stops the frame in the last month of attained age cover_end_age() - 1, and it is written anyway so that a change to the horizon cannot quietly extend the premium. The first is the live one: on model point 9 the Leistungsdauer ends four years before the Versicherungsdauer, so from attained age 63 the BU-Rente is zero while the premium runs on to 67 — and the disabled mass is held, not deleted, so the state identities still close across the boundary.

check_cover_end()[source]#

True when benefit and premium both stop exactly at their own contractual ages.

result_cf()[source]#

Result table of cash flows, indexed by policy month t.

pols_if is the start-of-month count, which is the weight applied to every cash flow on the same row, and its first value is pols_if_init() exactly. The three state columns beside it decompose it — aktiv, leistungspflichtig and the § 174 run-off — and pols_prem is the premium-paying count, which is below pols_if wherever anyone is in claim past the Karenzzeit.

premiums is the gross Bruttobeitrag and surplus_credit the Beitragsverrechnung returned out of it, so the cash actually collected is the difference of the two. claims_lapse is a column of zeros — there is no surrender or paid-up cash flow in this model — and is published rather than dropped so that the scope statement is made rather than inferred. liability_cf is net_cf outgo-positive.

The frame runs t = 0 ... proj_len() - 1 and stops: cover ceases at attained age cover_end_age() with nothing payable, and a claim still in payment at the horizon simply stops.

result_states()[source]#

Result table of transitions, rates and per-policy amounts, indexed by policy month t.

The transition columns are the flows at the end of month t, so they are the difference between one result_cf() row’s state columns and the next.

recov_rate is the only column that is not a function of t alone: reactivation depends on claim duration, not on the projection month. What is published here is recov_rate(t + 1) — the rate faced at month t by the cohort that entered claim duration 1 in month 0, which is exactly the duration profile laid out along t. Read it as the shape of the assumption, not as the rate applied in that month; the rate applied to a cohort is recov_rate(z) and belongs to the cohort.