The Data Space#

Input data shared by every by-policy projection.

The eight input CSVs are read here, once per model, and referenced from Projection as data. Projection is parameterized by point_id, so each Projection[N] is a separate ItemSpace with its own cells cache; if the readers lived there, every model point would re-read every file. Holding them in an unparameterized Space reads each file once no matter how many policies are projected.

Inputs are external files: plain CSVs in the model folder’s parent directory, products/klassische_rentenversicherung/, rather than data 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. 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 RV_DE_S folder without its parent’s CSVs produces a model that reads and then fails on first evaluation.

input_dir() resolves the directory from _model.path.parent at run time, so the model works wherever the repository is checked out. Each table has a filename Reference and a reader Cells:

Reference

Cells

File

model_point_file

model_point_table()

model_point_table.csv

mort_file

mort_table()

mort_table.csv

decl_rate_file

decl_rate_table()

decl_rate_table.csv

rentenfaktor_file

rentenfaktor_table()

rentenfaktor_table.csv

charge_file

charge_table()

charge_table.csv

lapse_file

lapse_table()

lapse_table.csv

freq_load_file

freq_load_table()

freq_load_table.csv

param_file

param_table()

param_table.csv

Every file but model_point_table.csv carries a per-row provenance column, this library’s second ruling: an assumption says on its own row where it came from. The model point table is exempt because a model point is a configuration — one policy’s own terms — rather than an assumption.

What is a proxy here, what it is anchored on, and what a replacement must preserve

Three of the seven assumption files are [std] proxies for tables that exist and are not redistributable, and one is a scenario path standing in for declarations that were never established. Each is anchored so that the technical notes’ worked example reproduces exactly, and the anchor is the thing a substitute must preserve.

mort_table.csv — the first-order basis of this product is DAV 2004 R, a Generationentafel which is the property of the Deutsche Aktuarvereinigung, is not public and is not shipped here. What ships is a proxy with the structure the real table has and none of its values: a sex-distinct base table q_base(sex, age) for base year 2005 — the year DAV 2004 R was intended for new business — and an age-dependent annual improvement rate improve(age), combined generationally as q_base x (1 - improve)^(calendar_year - 2005). The base table is Gompertz, 0.002000 x 1.09^(age - 50) for males and 0.001300 x 1.09^(age - 50) for females, closed with q = 1 at age 120 so the projection ends with no survivors. The improvement is 1,5 % a year below age 60, grading linearly to 0,5 % at age 100 and to zero at 110 — a deliberate simplification of the Starttrend / Zieltrend structure the German construction actually uses, documented as one rather than presented as a replication. The anchor a substitute table must preserve is ``q_base(M, 50) = 0.002000`` exactly, together with the 2005 base year and the terminal q = 1 at age 120; with those three the worked example still closes. Dropping a licensed or company generational table in place of this one changes the basis with no formula change — but note that the table and the Rentenfaktor below are not calibrated to each other, and the Rentenfaktor is authoritative, so a substitution must either re-strike the factors or accept the inconsistency.

rentenfaktor_table.csv — the aktueller Rentenfaktor is the carrier’s then-current immediate-annuity tariff, and no German market level was established for any carrier in any year. Three scenario paths ship, rising 2,5 % per year of age at Rentenbeginn: base anchored at 32,00 € per month per 10 000 € at age 67, low at 25,50 € and high at 35,00 €. The anchors are chosen so both branches of the max(garantierter, aktueller) rule are exercised by the shipped model points — the current factor wins on point 1, the guarantee binds on point 13 — because a rule with one branch never exercised is a rule no test covers. A replacement must keep base at age 67 equal to 32,00 € for the worked example to close.

decl_rate_table.csv — the declared laufende Verzinsung by calendar year and scenario. The declaration instrument exists and its 2026 vintage is evidenced; nothing inside it is. The base path is level at 2,55 % p.a., which sits inside the only public pair the library has (2,53 % Klassik / 2,58 % Neue Klassik for 2025) and is a market average rather than a carrier’s declaration; low is level at 1,50 %. The path is a scenario, not a forecast. It covers calendar years 2005 to 2060 and the reader in Projection clamps to that range, so a projection running past 2060 holds the last declared year flat.

charge_table.csv, lapse_table.csv, freq_load_table.csv and param_table.csv carry levels that are [std] throughout with two exceptions that are cited rather than chosen: the Höchstzillmersatz of 25 ‰ of the Beitragssumme (40 ‰ before 1 January 2015), and the five-year spread of acquisition costs that § 169 Abs. 3 VVG imposes on the surrender floor. The lapse table’s one shaped feature is the duration-12 step, at the twelve-year threshold § 20 Abs. 1 Nr. 6 EStG puts on the halving of the taxable gain: the shape is argued, every level is a placeholder.

Cells Descriptions#

input_dir()[source]#

The directory holding the input CSVs: the model folder’s parent.

Inputs are external files, not data stored inside the model, so the model folder is pure formulas. The path is resolved at run time from where the model was read, following annuallife.TradLife_A.

model_point_table()[source]#

The model point table, read from model_point_table.csv, indexed by point_id.

Fourteen policies, thirty columns. The one input file exempt from the provenance rule: a model point is a configuration — one policy’s own terms — rather than an assumption. Point 1 is the anchor cell of the technical notes’ worked example.

The two in-force points, 6 and 14, carry opening balances (av_pp_init, av_sur_pp_init, prem_cum_pp_init, alpha_amort_pp_init) that are the balances this model’s own recursion produces for the same contract run from inception, so an in-force cell is the continuation of a projectable contract rather than a free-standing guess.

mort_table()[source]#

The generational mortality proxy by (sex, age), from mort_table.csv.

Two columns: q_base, the first-order annual death rate in the 2005 base year, and improve, the annual mortality improvement rate applied from that base year. A [std] proxy, not DAV 2004 R; see the Space docstring for what it is, what it is anchored on and what a replacement must preserve.

decl_rate_table()[source]#

The declared laufende Verzinsung by (scenario_id, calendar_year).

Read from decl_rate_table.csv. The declared rate contains the guarantee: it is the Garantieverzinsung plus the laufende Zinsüberschussbeteiligung, never a surplus on top of the guarantee, which is what the max(0, .) in bonus_rate() implements. Levels are [std].

rentenfaktor_table()[source]#

The aktueller Rentenfaktor by (rf_scenario_id, age), from rentenfaktor_table.csv.

annuity_rate_curr is the monthly annuity per 10 000 € of conversion capital at the annuitant’s attained age at Rentenbeginn, on the carrier’s then-current immediate-annuity tariff. Three [std] scenario paths — base, low, high — anchored at age 67 on 32,00 €, 25,50 € and 35,00 €.

charge_table()[source]#

The tariff charge set by (charge_id, item), from charge_table.csv.

Two charge sets: zillmer_25 for contracts concluded from 1 January 2015 and zillmer_40 for the earlier Höchstzillmersatz. Eight items each — alpha_rate, alpha_spread_years, beta_rate, gamma_rate, gamma_pup_rate, stornoabzug_rate, min_annuity_mth and annuity_admin_rate, the last of which is recorded and deliberately not applied. Keyed per item so each number carries its own provenance tag.

A charge is a deduction the tariff makes from the premium or the Deckungskapital: it moves money inside the contract and produces no cash flow. An expense is the insurer’s own outgo and is a cash flow. The two are different things and confusing them is a listed pitfall; the expense levels live in param_table().

lapse_table()[source]#

The annual surrender rates by policy duration, from lapse_table.csv.

Durations 1 to 40, the contractual policy year and so 1-based: the projection’s 0-based t reads the table at t + 1, and a projection running longer holds the last row. Every level is [std]. The one shaped feature is the duration-12 step, at the twelve-year threshold § 20 Abs. 1 Nr. 6 EStG puts on the halving of the taxable gain: German Schicht-3 surrenders are suppressed approaching duration 12 and spike at it. Surrender is zero from the Rentenbeginn, which is imposed in lapse_rate() rather than carried in the table.

freq_load_table()[source]#

The Ratenzahlungszuschlag by payment frequency, from freq_load_table.csv.

freq_load is the multiplier on the annual gross premium and n_instalments the number of instalments a year, carried for documentation: the annual grid charges the loaded annual amount at the start of the year and does not model the instalments themselves. Levels are [std]; no German carrier’s loading was established.

param_table()[source]#

The scalar assumptions by item, from param_table.csv.

Every scalar that is neither a charge nor a rate table: the four expense levels and their inflation, mort_be_factor and mort_base_year, omega_age, val_reserve_rate, the three Überschussrente parameters and roll_fwd_tol. They live in a file rather than in Projection References so that each one carries its own provenance tag, which a Reference cannot.