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/riester_rente/, 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
Riester_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_accum_file |
mort_table_accum() |
mort_table_accum.csv |
annuity_mort_file |
annuity_mort_table() |
annuity_mort_table.csv |
lapse_file |
lapse_table() |
lapse_table.csv |
zulage_file |
zulage_schedule() |
zulage_schedule.csv |
income_file |
income_schedule() |
income_schedule.csv |
surplus_file |
surplus_scenario() |
surplus_scenario.csv |
freq_loading_file |
freq_loading() |
freq_loading.csv |
Every file but ``model_point_table.csv`` carries a per-row ``provenance`` column,
which is delib’s second ruling: a model point is a configuration, every other row is an
assumption and says on the row where its number came from. The tags are the same
vocabulary the prose uses — [S#], [R#], [REG-R#], [std] with a rationale.
The two decrement tables are [std] proxies, and what a replacement must preserve
The German first-order tables are the property of the Deutsche Aktuarvereinigung, are not public, and are not redistributed here. They are cited by name and stood in for.
mort_table_accum.csv stands in for DAV 2008 T, the death-risk table. It is a
Gompertz proxy, qx = 0.001500 x 1.10^(age - 50) over ages 16 to 109 with qx = 1 at
110, and it carries no improvement projection at all, because on a death cover
improvement runs in the insurer’s favour and a first-order death basis does not anticipate
it. The projection applies mort_be_factor = 0.80 on top, which is the direction of
prudence for a death table: a first-order death basis assumes mortality higher than
expected, so the best estimate sits below it. The anchor a substitute must preserve
is the rate at age 50, ``qx = 0.001500``, so the notes’ worked example still closes;
the slope is a placeholder and nothing in the corpus fixes it.
annuity_mort_table.csv stands in for DAV 2004 R, the annuitant table, and the one
structural property that is not optional is that it is two-dimensional: a
generational basis in age and calendar year,
q(x, tau) = qx_base(x) x (1 - improvement(x))^(tau - annuity_base_year) with
annuity_base_year = 2027. A period-table proxy understates a twenty-year-deferred
annuitisation by a margin that dwarfs every other assumption in the model, so a
replacement may change the level and the improvement scale but must keep both arguments.
The shipped base is qx_base = 0.006000 x 1.115^(age - 65) over ages 55 to 109, capped
at 0.95, with qx_base = 1 at 110; the improvement is 1,8 % a year to age 65, tapering
by 0,045 pp per year of age to a floor of 0,2 %. It is applied at
annuity_mort_be_factor = 1.15 in the projection — the opposite direction, because a
first-order annuity table assumes mortality lower than expected — and at 1.00 inside
ann_factor(), which is the first-order basis the market’s
Rentenfaktor is struck on. The anchor a substitute must preserve is the first-order
annuity factor at age 67 in calendar 2044, ``ann_factor() = 20.87222879``, which is what
puts rentenfaktor_curr() at 27,947822 — below the anchor’s guaranteed 29,00 — so that
the guaranteed Rentenfaktor binds on the worked example and the notes’ conversion
table reproduces exactly.
The remaining six files are assumption or configuration tables rather than proxies for a
named instrument; each row says so in its own provenance cell.
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, one row per policy, twenty-six columns. This is the one input file with noprovenancecolumn, and the only exemption in the library: a model point is a configuration — one policy’s own terms — rather than an assumption, so a per-row tag would repeat the same provenance once per policy while saying nothing about any assumption. Where a column is an assumption in disguise (rechnungszins, the opening balances) the technical notes carry the tag.
- mort_table_accum()[source]#
Accumulation-phase death rates by attained age, from mort_table_accum.csv.
A [std] proxy for DAV 2008 T, which is proprietary and is not redistributed here; see the Space docstring for the anchor a replacement must preserve. Ages 16 to 110, no improvement dimension: a first-order death basis does not anticipate mortality improvement, because on a death cover improvement favours the insurer.
- annuity_mort_table()[source]#
Annuitant base rates and improvement scale by attained age.
Read from annuity_mort_table.csv, ages 55 to 110. A [std] generational proxy for DAV 2004 R:
qx_baseis the rate in the base calendar year andimprovementthe annual rate of decline, so the applied rate depends on both attained age and calendar year. That two-dimensional structure is the property a replacement may not drop.
- lapse_table()[source]#
Surrender and transfer-out rates by contract duration, from lapse_table.csv.
The
durationkey is the contract year, 1-based and contractual: rowkis contract yeark, and the projection reaches it throughduration(t) + 1 = duration_init() + t + 1rather than through the 0-basedt—duration(t)is itself the 0-based count of completed contract years.Two rates, not one.
lapse_rateis a Kündigung, which repays every Zulage and every § 10a relief and taxes the accumulated growth;transfer_rateis an Anbieterwechsel under the statutory Wechselrecht, which carries none of those consequences. The second is set above the first at every duration for that reason, and a model of this book carrying only a lapse rate has mis-specified it.
- zulage_schedule()[source]#
The Zulage entitlement drivers by schedule id and projection period
t.Read from zulage_schedule.csv. The
tkey is the model’s own 0-based time index, running0 ... 59, and is read directly att. Four drivers per row:unmittelbar, the indicator that the Grundzulage is drawn at all;n_kinder_pre2008andn_kinder_post2008, the counts of children for whom Kindergeld is drawn at the 185 € and the 300 € rate — a permanent birth-cohort split, not a transition, so a contract can carry both at once; andbonus, the once-in-a-lifetime Berufseinsteiger-Bonus. The schedule is exogenous because Kindergeld is a household fact the insurance contract does not observe, which is the most awkward feature of this product for a per-policy projection.
- income_schedule()[source]#
Contribution-liable earnings by schedule id and projection period
t.Read from income_schedule.csv. The
tkey is the model’s own 0-based time index, running0 ... 59.income(t)is the earnings of the calendar year of period t; the § 86 Mindesteigenbeitrag of periodtis struck on the previous calendar year, so the projection readsincome(t - 1)and takesincome_initfrom the model point fort = 0. Thezeropath encodes a mittelbar zulageberechtigt spouse, who has no contribution-liable earnings of their own and whose Mindesteigenbeitrag is therefore the 60 € Sockelbeitrag.
- surplus_scenario()[source]#
The declared laufende Verzinsung by scenario id and projection period
t.Read from surplus_scenario.csv. The
tkey is the model’s own 0-based time index, running0 ... 89, and is read directly att. Two paths ship:baseat 2,30 % level andlowat 0,50 % level. The declared rate includes the Rechnungszins — adding the two is the German arithmetic error this model is built to make visible — sodecl_rate - rechnungszinsis the laufende Zinsüberschussbeteiligung and is what accrues in the Überschussguthaben. It is the largest single lever in the model and the least supported: no declared rate at any carrier was established.
- freq_loading()[source]#
The Ratenzuschlag multiplier by payment frequency, from freq_loading.csv.
A charge and never a credit: the saver pays
eigenbeitrag_pp x loadwhile onlyeigenbeitrag_ppreaches the Sparbeitrag base and the Beitragsgarantie, so the loading enlarges the premium income and nothing else. Crediting it to the account is a listed modeling pitfall.