The Projection Space#
The by-policy projection of the Dep_FR_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 = 9 # or switch the default
t counts policy months, 0-based, exactly as the technical notes index them:
t = 0 is the first policy month and t = proj_len() - 1 the last, so
result_cf() runs 0 ... proj_len() - 1. Cover is viagère with no age limit, so
what ends the projection is a [std] terminal age of 110 rather than the contract:
proj_len() = 12 (terminal_age - entry_age) is the number of projected months,
480 on the base cell. The state ledgers are indexed at the start of the month, so
every cash flow on a result_cf() row is weighted by a state count on the same row.
Input data
Inputs are external files: plain CSVs living in the model folder’s parent
directory, products/dependance/, 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
Dep_FR_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 |
mort_table_file |
data.mort_table() |
mort_table.csv |
prevalence_file |
data.prevalence_table() |
prevalence_table.csv |
severity_share_file |
data.severity_share_table() |
severity_share_table.csv |
lapse_table_file |
data.lapse_table() |
lapse_table.csv |
cause_mix_file |
data.cause_mix_table() |
cause_mix_table.csv |
reduction_file |
data.reduction_table() |
reduction_table.csv |
revision_file |
data.revision_table() |
revision_table.csv |
Naming
Cells names follow lifelib’s basiclife.BasicTerm_S and savings.CashValue_SE
wherever those models have an analogue — pols_* for population counts, plural nouns
for cash flows, *_rate for annual rates and *_rate_mth for monthly ones,
*_pp for per-policy amounts, claims(t, kind) with an uppercase kind string.
The technical notes use compact actuarial symbols instead. The mapping is:
Notes symbol |
Cells |
Meaning |
|---|---|---|
(none) |
model_point() |
The selected model point |
entry_age |
age_at_entry() |
Entry age, différence de millésimes |
x = age(t) |
age(t) |
Attained age in month t |
y(t) |
policy_year(t) |
Policy year, t // 12 + 1 |
(none) |
duration(t) |
Completed policy years |
(none) |
duration_mth(t) |
Months elapsed, equal to t |
(none) |
terminal_age |
110, what ends the run |
proj_len |
proj_len() |
The number of projected months, exclusive end of t |
z |
(the duration argument) |
Months since first recognition |
fr |
franchise_months() |
Franchise, 3 months |
(none) |
max_dur() |
Longest duration tracked |
G(y) |
rente_total_pp(t) |
Guaranteed rente totale |
rho |
partial_ratio() |
Partial / total ratio, 0.50 |
rho G(y) |
rente_partial_pp(t) |
Guaranteed rente partielle |
G_pay(t, z) |
rente_pay_pp(t, z) |
Rente totale in payment for the cohort at duration z |
rho G_pay(t, z) |
rente_pay_partial_pp(t, z) |
The same, partial ledger |
CAP(y) |
capital_pp(t) |
Capital d’équipement |
P(y) |
premium_mth_pp(t) |
Monthly premium |
12 P(y) |
premium_pp(t) |
The same, annualised |
(none) |
premium_factor(y) |
Its compound index |
g_G |
reval_guarantee |
Revalorisation des garanties |
g_S |
reval_rente |
Revalorisation des rentes en service |
r(y) |
revision_rate(t) |
Scheduled tariff revision |
M_rev(y) |
revision_lapse_factor(t) |
Premium-shock lapse module |
cum_prem(t) |
cum_prem_pp(t) |
Premiums paid per policy |
c(n) |
reduction_coeff(n) |
Barème coefficient |
S(t) |
carence_factor(t) |
Share of causes covered |
(none) |
carence_months(cause) |
The three carence lengths |
mu_H(x) |
mort_force(t) |
Healthy force of mortality |
mubar |
mort_force_avg(t) |
Population average force |
(table) |
mort_rate(t) |
Healthy annual mortality |
q_H(t) |
mort_rate_mth(t) |
The same, monthly |
k_P, k_T |
mort_partial_mult, mort_total_mult |
State mortality multiples |
(none) |
mort_rate_partial(t) |
Partielle annual mortality |
q_P(t) |
mort_rate_partial_mth(t) |
The same, monthly |
(none) |
mort_rate_total(t) |
Totale annual mortality |
q_T(t) |
mort_rate_total_mth(t) |
The same, monthly |
prev(x) |
prev_rate(t) |
APA prevalence |
prev’(x) |
prev_slope(t) |
Its derivative in age |
s_P, s_T |
severity_share(kind) |
Public-to-insured shares |
pi_P, pi_T |
prev_partial(t), prev_total(t) |
Insured-state prevalence |
i_P(x) |
inc_rate_partial(t) |
Annual entry into partielle |
i_Pm(t) |
inc_rate_partial_mth(t) |
The same, monthly |
i_T(x) |
inc_rate_total(t) |
Annual entry into totale |
i_Tm(t) |
inc_rate_total_mth(t) |
The same, monthly |
i_A |
aggravation_rate |
Aggravation force |
i_Am |
aggravation_rate_mth() |
The same, monthly |
(held at zero) |
recovery_rate |
Return to autonomy |
w(t) |
lapse_rate(t) |
Annual lapse rate |
(table) |
lapse_rate_base(t) |
Table rate before the shock |
w(t) monthly |
lapse_rate_mth(t) |
Monthly lapse rate |
auto(t) |
pols_auto(t) |
Autonomous, premium-paying |
red(t) |
pols_red(t) |
Paid-up on a reduced rente |
(none) |
red_rente_pp(t) |
Its mean frozen rente |
pols_part(t, z) |
pols_part_dur(t, z) |
In partielle at duration z |
(sum over z) |
pols_part(t) |
In partielle |
pols_tot(t, z) |
pols_tot_dur(t, z) |
In totale at duration z |
(sum over z) |
pols_tot(t) |
In totale |
pols_totr(t, z) |
pols_totr_dur(t, z) |
In totale on a reduced rente |
(sum over z) |
pols_totr(t) |
The same, total |
(the four vectors) |
dep_cohorts(t) |
The cohort ledgers as lists |
pols_if(t) |
pols_if(t) |
Every ledger added |
(none) |
pols_if_at(t, timing) |
BEF_DECR / AFT_DECR |
(none) |
pols_prem(t) |
Population paying premium |
surv(t), base(t) |
pols_surv(t), pols_base(t) |
Autonomous survivors |
n_P(t) |
pols_entry_partial(t) |
Entrants into partielle |
n_T(t) |
pols_entry_total(t) |
Entrants into totale |
n_Tr(t) |
pols_entry_total_red(t) |
The same, from the reduced |
n_A(t) |
pols_aggravation(t) |
Aggravations partielle to totale |
(none) |
pols_aggravation_recog(t) |
Those of them recognised |
(none) |
pols_recognition(t) |
First recognitions |
(none) |
pols_death(t) |
Deaths, all five ledgers |
lapse(t) |
pols_lapse(t) |
Lapses out of autonomy |
(none) |
pols_lapse_exit(t) |
Those that leave outright |
(none) |
pols_reduction(t) |
Those that become paid-up |
carence_exit(t) |
pols_carence_exit(t) |
Memberships terminated by the carence |
(none) |
pols_recovery(t) |
Returns to autonomy; zero |
P x auto(t) |
premiums(t) |
Premium income |
claims_rente(t) |
claims(t, “RENTE”) |
Rente outgo |
claims_capital(t) |
claims(t, “CAPITAL”) |
Capital d’équipement outgo |
0 |
claims(t, “LAPSE”) |
Surrender outgo; always zero |
(none) |
instalments(t) |
Rente instalments paid |
refunds_carence(t) |
refunds_carence(t) |
Premiums returned |
e(y), a(y) |
expenses(t) |
Maintenance and assistance |
ec_adj, ec_ren |
claim_expenses(t) |
Adjudication and handling |
(none) |
inflation_factor(t) |
Expense inflation factor |
net_cf(t) |
net_cf(t) |
Net cash flow, income positive |
(the calibration) |
sojourn_total(x0) |
Expected sojourn in totale |
(the calibration) |
sojourn_partial(x0) |
Expected sojourn in partielle |
Four names needed care.
The notes write one symbol q with three subscripts for the three mortality rates.
mort_rate() is the healthy-life one, because that is what mort_rate means
in every other model in this library — the rate applying to the population the
projection starts with — and the two dependent-state rates are mort_rate_partial()
and mort_rate_total(). Reading a dependent’s mortality out of mort_rate is
precisely this product’s largest available error, and the naming is there to prevent it.
t is the policy month and z the months since first recognition. They are
different clocks and the model never mixes them: the carence takes t and the
franchise takes z.
The notes call the whole paid-up ledger red and the amount it carries a frozen
G(y) c(n). The ledger is pols_red() and the amount red_rente_pp(), a
probability-weighted mean over reduction cohorts. That is exact in expectation, because
incidence does not depend on the amount, and it is what the notes license an
implementation to do instead of carrying a per-reduction-cohort amount.
carence_exit(t) is spelled pols_carence_exit(). It is a policy count and every
other policy count in the library starts pols_; the cash flow it drives keeps the
notes’ own name, refunds_carence(), because it is a refund of premiums and not a
claim and belongs on its own line.
Five ledgers, and why the model needs all of them
The health chain is autonome → dépendance partielle / dépendance totale → décès, with lapse as a further exit from autonomy, and a fifth in-force but paid-up ledger, réduite, reached only by lapse from eight full years of premiums:
+------------- i_T -----------------+
| v
autonome ---- i_P + ---> partielle ---- i_A ---> totale ----> deces
| | |
| v v
| deces deces
|
+-- lapse before 8 years --> nothing at all
|
+-- lapse from 8 years ----> reduite --- i_T ---> totale (reduced rente)
Three absences are product facts, not gaps. There is no account value and no surrender
value, so no cv_pp exists and a lapse before eight years carries no cash flow at
all. There is no death benefit on this composite, so no claims_death exists.
And there is no maturity: the cover is viagère.
pols_red() is the one ledger a naive model omits, and omitting it is a first-order
error: lapse from year 8 does not release the liability, it converts it into a
smaller one that keeps running for life. On the base cell that ledger peaks at 8.27% of
the original policy at month 194, attained age 86 — the largest state in the model after
pols_auto() at that duration — and dropping it understates lifetime claims by
4.57%.
Recovery out of a covered state is a named input held at zero, not an omission:
recovery_rate is wired into the ledger roll and into pols_recovery(), and the
base run sets it to zero, as the only retrieved actuarial reference on this product
does. The direction of error is one-sided — claims are overstated — and no retrieved
source quantifies it.
The two dependent ledgers are two-dimensional
A cohort must be indexed by the months since first recognition, z, for two reasons
that have nothing to do with each other. The franchise drops the first three
instalments, so a cohort is paid only from z >= franchise_months() + 1. And the
rente in payment is the guarantee of the policy year in which the cohort was
recognised, indexed forward at reval_rente — a different rate from the one that
indexes the guarantee before claim — so the amount depends on the cohort’s vintage,
which is what z records.
dep_cohorts() holds all four vectors for one month and is the model’s only
list-valued cells. The alternative — four two-argument recursions — would be
4 proj_len() max_dur() separate cells, nearly a million on the base cell, each
with its own cache entry. Keeping them in one cells per month makes it proj_len()
cells with a loop inside, and pols_part_dur() and its siblings read elements out of
it so that the notes’ two-dimensional objects are still addressable by name. The lists
are rebuilt rather than mutated on each step, so a caller cannot corrupt the cache by
holding one.
The fourth vector is the value ledger of the reduced-rente claims: element z - 1 is
the population at duration z times the reduced rente it is being paid. It exists
because those amounts are frozen individually at each reduction date, not derivable from
the policy year the way the other two ledgers’ amounts are.
The carence and the franchise are different things
The carence runs from inception, is cause-specific, blocks the benefit and terminates the membership with a full refund of premiums. The franchise runs from recognition, is three months, and only delays payment. They are the two easiest things in this product to apply in each other’s place, and they cost different amounts: removing the carence raises lifetime claims by 3.99% and removing the franchise by 7.09%.
carence_factor() is the share of causes already covered at month t, read from
the [std] cause mix against the model point’s own three carence lengths: 0.10 in
policy year 1, 0.65 in years 2 and 3, 1.00 thereafter on the base cell. Note what it
does not touch: pols_auto() at t + 1 does not depend on it, because a
carence claim ends the membership rather than deferring it. The blocked lives leave
the in-force ledger exactly as the covered ones do, and they take a refund of every
premium paid with them — in policy year 1 of the base cell that refund is three quarters
of the year’s rente and capital claims combined.
The franchise is not a premium holiday. Exonération runs from recognition, so a life inside the three-month franchise pays no premium and receives no rente.
State-dependent mortality is the largest lever on this product
A dependent life’s mortality is far heavier than a healthy life’s at the same age.
mort_rate_partial() and mort_rate_total() apply proportional hazards on the
force, mort_partial_mult 1.75 and mort_total_mult 4.27, so the annual rates at
attained age 85 are 0.06179, 0.10562 and 0.23841. Applying healthy mortality to
dependent lives while leaving the incidence basis unchanged raises lifetime claims by
159.7%.
mort_total_mult is calibrated, not guessed: sojourn_total() returns 2.9989
years from exact age 84 at 4.27, against the mean duration of about three years the CCSF
reports for heavy dependents. At 2.75 the same calculation gives 4.19 years and at 3.50,
3.50 — the sojourn is far more sensitive to the multiple than a first look suggests.
mort_partial_mult has no such anchor: it must exceed 1 and sit well below
mort_total_mult, and at 1.75 sojourn_partial() gives 3.14 years from age 82,
the same order as the 29.2-month mean duration of APA receipt across all GIRs.
Prevalence is not incidence, and the identity that converts them
Every public French number about dependence measures receipt of the *allocation personnalisée d’autonomie*. It is a prevalence, not an incidence, and it is a public classification rather than the insurer’s. Both conversions are explicit steps here.
severity_share() is the first: the fractions of APA prevalence read as insured
partielle and totale, keyed by the contract’s trigger grid, [std] against two
indirect anchors. inc_rate_partial() and inc_rate_total() are the second, and
they are an identity rather than an approximation — differentiating the state
proportions along the age axis gives entry forces in terms of the prevalence slope, the
aggravation force and all three mortality forces.
Three properties of that identity an implementation must respect, and this one does. The
mortality terms are not refinements: a rising prevalence understates incidence
because the dependent population is simultaneously being drained by its own excess
mortality. aggravation_rate and inc_rate_total() are not independent
inputs: raising the aggravation force lowers the direct-to-totale incidence, because
the stock of totale lives is pinned by the assumed prevalence — consistently varying
the rate from 0 to 0.20 to 0.40 moves lifetime claims by only +0.54% / 0 / -0.52%, while
adding it without re-deriving the incidence raises them 0.84% and puts the lives in the
wrong state. And inc_rate_partial() can go negative at extreme ages, where the
prevalence slope flattens while excess mortality does not; both rates are floored at
zero [std], which never binds on the female base cell — inc_rate_partial() is
still 0.0040 at attained age 109 — and binds at attained age 109 on the male basis.
Two indexations, two ledgers
reval_guarantee moves the guarantee and the premium in the same proportion.
reval_rente moves every rente in payment, whatever its vintage. The reduced
guarantee moves with neither. Collapsing the two rates into one happens to work only
when they are equal, and the base configuration deliberately sets them different — 1.0%
against 1.5% — so that a test can tell.
Premium income rides on pols_auto, never on pols_if
Lives in a recognised state are exonerated and reduced lives are paid up, so neither band
pays anything. pols_prem() is the premium-paying population and it is
pols_auto() on every model point except a total_only one, where the partielle
ledger is not a recognised state and therefore keeps paying. Charging premium to the
whole in-force block overstates income by the whole reduced ledger plus the whole claim
ledger: on the base cell, at attained age 90 those two bands together are 44.6% of the
in-force block.
What cover_type does, and the one thing it standardizes
cover_type = total_and_partial is the composite the notes specify and the basis of
the worked example. cover_type = total_only buys the rente totale alone, and the
model reads that as: partielle is not a recognised state, so it pays no rente, it
carries no capital, it does not exonerate the premium — and the carence and the
franchise both attach at entry into totale, whether direct or by aggravation, since
that is then the first recognition. The health chain is untouched, which is what keeps
the prevalence identity intact.
One consequence is a [std] departure worth naming: on a total_only cell an
aggravating life starts a fresh duration cohort and therefore serves a franchise,
where on a total_and_partial cell the duration index runs from first recognition and
deterioration does not restart it. Both readings follow from the same principle — the
clock runs from first recognition of a covered state — and no retrieved document
addresses either directly.
The capital d’équipement is paid once per membership
It is paid on first entry into a covered state, never twice, and a reduced membership
has lost the option. So it rides on pols_recognition() less the entrants out of the
reduced ledger, and an aggravation produces no capital on a total_and_partial cell.
Paying it again on aggravation would inflate capital claims by the whole aggravation
flow.
Cells Descriptions#
- age_at_entry()[source]#
The entry age of the selected model point, différence de millésimes.
Sourced band 40 to 75 inclusive at signature. On an in-force cell —
statusofpartial,totalorreduced— this is the attained age at the valuation date and the policy-year clock restarts there [std]: the notes give no anniversary offset for an in-force membership, so the two indexations step att = 12, 24, ...from the valuation date rather than from the contractual anniversary.
- sex()[source]#
The sex (M / F) of the selected model point.
The decrements are sex-split — both the mortality proxy and the prevalence logistic have a row per sex — while the premium is unisex, compulsory since the 2004 EU directive, so
premium_mthis a model point column and not a rate looked up by sex.
- cover_type()[source]#
total_and_partialortotal_only: which states the contract covers.The composite of the technical notes is
total_and_partialand it is what the worked example runs. See the Space docstring for whattotal_onlychanges and for the one [std] departure it forces on the duration clock.
- cover_partial()[source]#
True when dépendance partielle is a covered, recognised state.
The single switch the rest of the model consults, so that the four consequences of
cover_type— the rente, the capital, the premium exonération and where the carence and the franchise attach — cannot drift apart.
- trigger_grid()[source]#
avq5,avq6oraggir: the grid the contract triggers on.Three alternative definitions of the same two states, and the reference model has to price all three because its decrement basis is built from public GIR-graded APA data. The grid enters through
severity_share()and nowhere else: a stricter grid is a smaller share of public prevalence, not a different chain.
- rente_total_mth()[source]#
G(1): the guaranteed rente totale at issue, EUR a month.
1,000 on the base cell, a [std] pick inside the sourced 500-3,000 band chosen because it is the cover for which the only age-graded French price point exists. On an in-force dependent cell it is the amount in payment at the valuation date, and on a
reducedcell it is the full guarantee before the barème coefficient is applied.
- partial_ratio()[source]#
rho: the rente partielle as a fraction of the rente totale.
0.50, modal across five retrieved providers. The two rentes are mutually exclusive: recognition of totale never opens partial rights.
- partial_ratio_paid()[source]#
The ratio actually paid:
partial_ratio(), or zero on atotal_onlycell.Kept separate from
partial_ratio()so that the model point still records the contractual ratio on a cell that does not buy the rente partielle.
- capital_amount()[source]#
CAP(1): the capital d’équipement at issue, EUR, 3,500 on the base cell.
Observed amounts run from 3,000 to 10,000 across the retrieved contracts; the base cell’s 3,500 is the AXA figure. Paid once per membership on first entry into a covered state, with no franchise [std], and the guarantee is extinguished on payment regardless of later deterioration.
P(1): the monthly premium at issue, EUR, an input and not a computed quantity.
No French insurer publishes a general individual LTC rate table. The base cell’s 75 EUR a month is the CCSF’s 2013 indicative price for exactly this cover at entry age 70 — dated and indicative, and used because inventing a premium would be worse. The premium is viagère: level for the entry age but payable for life, with no premium-paying term.
monthly,quarterly,half_yearlyorannual; the base cell is monthly.Payment is always in advance. No fractional-payment loading is applied [std]: no retrieved document discloses one, so an annual payer pays exactly twelve monthly premiums at the start of the policy year.
The number of months one premium instalment covers: 1, 3, 6 or 12.
True when a premium instalment falls due at the start of month t.
Instalments fall on months 0,
premium_months(),2 premium_months(), … of the projection, so an annual payer pays at each policy anniversary.
- couple_discount()[source]#
Whether the 10% réduction couple applies; off on the base cell.
Real and common — both spouses joining within three months, lost if either membership is resiliated or reduced — but a rating adjustment with no cash-flow mechanics beyond scaling the premium, conditional on facts about a second life the model point does not carry.
- carence_illness_months()[source]#
The carence for illness other than neurological or psychiatric, 12 months.
- carence_neuro_months()[source]#
The carence for neurological, neurodegenerative or psychiatric illness.
36 months, the longest of the three, on every retrieved contract — which is what an insurer does when a cause is both frequent and adversely selected, and every retrieved contract puts an MMSE overlay on exactly that cause.
- carence_months(cause)[source]#
The carence length in months for one of the three causes.
accident,illnessandneuro, the three keys ofcause_mix_table.csv. A membership on which dependence arises from a cause still inside its carence is terminated and every premium refunded; the benefit is not merely deferred.
- franchise_months()[source]#
fr: the franchise in months, 3 on the base cell.
Absolute and measured from recognition, so a cohort is paid from
z >= fr + 1: the cohort recognised at the end of monthsis first paid at the end of months + 4. This is a [std] monthly reading of “le 91e jour”, corroborated rather than assumed — one retrieved contract restores exactly three instalments at the first payment.
- reduction_qualifying_years()[source]#
The full consecutive years of premiums that qualify for mise en réduction.
Eight on the base cell; the observed range is five to eight across the retrieved contracts. Below it a lapsed membership ends with no value at all.
- status()[source]#
Which ledger the population starts in at
t = 0.autonomousthe whole population is autonomous and premium-paying.
partial/totala claim already in payment, seeded into the corresponding dependent ledger at
claim_duration_months().reduceda paid-up membership on a reduced rente totale, seeded with
years_paid()years of premiums behind it.
An in-force portfolio needs all four kinds of cell.
- claim_duration_months()[source]#
z0: the months since recognition already elapsed on an in-force claim cell.
The seeded population enters cohort
z0 + 1, since cohort 1 is a state recognised at the end of the month before the valuation date. A cell seeded atz0 = franchise_months()is therefore paid in its very first month.
- years_paid()[source]#
n: the completed years of premiums behind a
reducedcell.It selects the barème coefficient the frozen reduced rente carries, and it is ignored on every other kind of cell.
- proj_len()[source]#
The number of projected months:
12 (terminal_age - age_at_entry()).480 on the base cell, and it is the exclusive end of the frame: the projection runs
t = 0 ... proj_len() - 1andresult_cf()hasproj_len()rows, which is lifelib’srange(proj_len()). Cover is viagère with no age limit, so what ends the projection is the terminal age of the decrement basis, not the contract — there is no maturity, no expiry and no maturity benefit.terminal_ageis 110 [std], above which the mortality table forces the rate to 1.
- duration(t)[source]#
Completed policy years at the start of month t:
t // 12.0-based, as
durationis throughout lifelib: 0 through the first policy year.
- duration_mth(t)[source]#
Months elapsed from the start of the projection at the start of month t.
Equal to
t, sincetis 0-based. The cells exists so that the monthly models in this library share one vocabulary.
- age(t)[source]#
x: the attained age in the policy year containing month t.
age_at_entry() + t // 12, advancing at each policy anniversary [std] — the entry age is by différence de millésimes and the model advances it annually rather than on a birthday it does not carry.
- max_dur()[source]#
The longest claim duration the cohort vectors have to carry.
proj_len() + claim_duration_months() + 1, 481 on the base cell: a cohort seeded at durationz0 + 1reachesz0 + proj_len()in the last montht = proj_len() - 1, and one recognised in month 0 reachesproj_len(). The last element is therefore structurally zero, which is what makes the duration shift lossless.
- cohort_len(t)[source]#
The number of duration cohorts that can be non-zero at the start of month t.
The vectors are truncated to this length rather than carried at full
max_dur()from month zero. It is purely a cost decision — a full-length vector in every month isproj_len() max_dur()floats where this is half that — andpols_part_dur()returns zero past the end of the list, so nothing about the two-dimensional view changes.
- rente_total_pp(t)[source]#
G(y): the guaranteed rente totale in the policy year containing month t.
Indexed at
reval_guaranteeon each policy anniversary, before claim. Once a rente is in payment it leaves this cells behind and moves atreval_renteinstead — seerente_pay_pp().
- capital_pp(t)[source]#
CAP(y): the guaranteed capital d’équipement in the policy year containing t.
Indexed at
reval_guaranteealongside the rente, and zero when the option is not bought.
- rente_pay_pp(t, z)[source]#
G_pay(t, z): the rente totale in payment at month t for the cohort at duration z.
A cohort recognised at the end of month
t - zentered on the guarantee of its own policy yeary_eand has been indexed atreval_renteat every anniversary since, so the amount isG(y_e) (1 + reval_rente)^(y - y_e). Two indexations, two ledgers:reval_guaranteeset the amount at recognition andreval_rentehas moved it ever since. Collapsing them into one rate happens to work only when they are equal, and the base configuration sets them different so that a test can tell.A cohort seeded at
t = 0on an in-force claim cell reads as policy year 1, which is the same [std] restart of the policy-year clock thatage_at_entry()describes.
- rente_pay_partial_pp(t, z)[source]#
rho G_pay(t, z): the rente partielle in payment for the cohort at duration z.
- revision_rate(t)[source]#
r(y): the scheduled tariff revision in the policy year containing month t.
Applied to the premium on top of
reval_guarantee, and capped at 10% a year excluding revalorisation by the only retrieved contract that states a cap. Nil for five years then 1.5% a year on the shipped path, which is arbitrary inside that band: a real tariff revision is a management action, not a projected assumption, and it takes a deliberate substitution ofrevision_table.csvto project a repricing.
The compound premium index in policy year y; 1.0 in policy year 1.
(1 + reval_guarantee)(1 + r(y))per anniversary. The premium rises in the same proportion as the revalorisation of the guarantees, which is contractual, and the tariff revision multiplies on top of that, which is discretionary.
P(y): the monthly premium in the policy year containing month t.
premium_mth() x couple_factor() x premium_factor(y). An instalment ofpremium_months()of these falls due wheneverpremium_due()is true.The
_mth_is load-bearing: library-widepremium_ppis the annual premium per policy andpremium_mth_ppthe monthly one. Every recursion in this model works in months and so uses this cells, notpremium_pp().
12 P(y): the annual premium per policy in the policy year containing month t.
12 x premium_mth_pp(t). A reporting convenience, not a cash flow: this contract is projected monthly and the instalments actually falling due arepremiums(), built onpremium_mth_pp()andpremium_months(). A quarterly or annual payer pays the same annual amount in fewer, larger instalments, there being no fractional-payment loading [std].
- cum_prem_pp(t)[source]#
cum_prem(t): premiums paid per policy up to and including the start of month t.
The contre-assurance base: a membership terminated by the carence has every premium it ever paid refunded, and this is what is refunded. It is a per-policy amount and not a population-weighted one, which is why
refunds_carence()multiplies it by the terminating population rather than adding to it.The base case sits at
t = 0, the first projected month, and nothing is ever indexed below it:premium_due()is true att = 0on every payment mode, so the first instalment always falls on the first row.
n: the completed years of premiums at the end of month t,
(t + 1) // 12.Eight of them at
t = 95, which is the first month a lapse becomes a mise en réduction rather than an exit with nothing.
- reduction_coeff(n)[source]#
c(n): the barème coefficient at n completed years of premiums.
Zero below
reduction_qualifying_years(), then the CNP Banque de France scale — 25% at eight years rising about two points a year and capped at 70% from thirty. Years beyond the table take its last row.
- inflation_factor(t)[source]#
The expense inflation factor in month t:
(1 + inflation_rate)^(y - 1)[std].Steps on policy anniversaries, not monthly, which is how the notes write it. It applies to the maintenance and assistance levels and not to the two claim expenses, which are per-event amounts held flat [std].
- mort_rate(t)[source]#
The healthy-life annual mortality rate at the attained age in month t.
A [std] Gompertz proxy shaped like a French population table, read from
mort_table.csvby sex and age; not TH 00-02 / TF 00-02 or TGH05 / TGF05. The two dependent states carry heavier rates on different cells —mort_rate_partial()andmort_rate_total()— and reading a dependent’s mortality out of this cells is the largest single error available on this product.
- mort_force(t)[source]#
mu_H(x) = -ln(1 - mort_rate): the healthy force of mortality in month t.
The identity behind
inc_rate_partial()is written on forces, not on annual probabilities, and the two state multiples are proportional hazards on this force — which is what makes1 - (1 - q)^(k/12)the right monthly conversion for a dependent life andk qthe wrong one.
- mort_rate_partial(t)[source]#
The annual mortality of a life in dépendance partielle at the attained age.
1 - (1 - mort_rate)^kwithk = mort_partial_mult, a proportional hazard on the force [std]. 0.10562 at age 85 against 0.06179 healthy.khas no anchor: it must exceed 1, because GIR 3-4 lives carry excess mortality, and sit well belowmort_total_mult; at 1.75 the expected sojourn in partielle entered at age 82 is 3.14 years, the same order as the 29.2-month mean duration of APA receipt across all GIRs. No impaired-life table for either French dependence state exists in any retrieved source.
- mort_rate_partial_mth(t)[source]#
q_P(t) = 1 - (1 - mort_rate)^(k_P/12): monthly partielle mortality [std].
- mort_rate_total(t)[source]#
The annual mortality of a life in dépendance totale at the attained age.
1 - (1 - mort_rate)^kwithk = mort_total_mult[std]. 0.23841 at age 85 against 0.06179 healthy, and 0.216 at 84 against 0.055 — the gap that makes flat state mortality this product’s largest available error.kis calibrated: seesojourn_total().
- mort_rate_total_mth(t)[source]#
q_T(t) = 1 - (1 - mort_rate)^(k_T/12): monthly totale mortality [std].
- mort_force_at(x)[source]#
mu_H at an exact, possibly fractional, age x.
Log-linear in age between the integer ages of
mort_table.csv, which reproduces the shipped Gompertz force exactly, since a Gompertz force is exponential in age. Only the two sojourn calibrations use it: the projection itself reads the force at integer attained ages throughmort_force().
- lapse_rate_base(t)[source]#
The table annual lapse rate in month t [std], before the premium shock.
8 / 6 / 5 / 4 / 3 percent by policy band. No French LTC persistency study is public; the table’s only anchor is that the individual book fell 9.9% in 2024 on 28,400 new subscribers, so gross exits — deaths, claim entries and lapses together — ran at roughly 11% of the opening portfolio, and a 3-8% lapse table leaves the balance for mortality and incidence. Policy years beyond the table take its last row.
- revision_lapse_factor(t)[source]#
M_rev(y): the premium-shock lapse multiplier [std]; 1.0 in the base run.
1 + revision_lapse_slope x max(0, r(y) - revision_lapse_threshold). The member may refuse a tariff revision by resiliating within two months of notification, with a possible mise en réduction at the same date, so a revision at the 10% cap gives 1.24. It is off in the base run because the shipped revision path never exceeds 1.5%, and it is the only place a projected repricing feeds back into the block.
- lapse_rate(t)[source]#
w(t): the annual lapse rate out of the autonomous ledger in month t.
Applied to
pols_auto()only. A recognised life pays no premium and a reduced membership pays none either, so neither can lapse for non-payment, and with no surrender value there is nothing to surrender for — a lapse here is genuinely a decision to walk away from everything.
- aggravation_rate_mth()[source]#
i_Am = 1 - exp(-i_A/12): the monthly aggravation probability [std].
Flat in age, and not an independent input: the prevalence identity ties it to
inc_rate_total(), so raising it lowers the direct-to-totale incidence. There is no public transition law — the only retrieved actuarial reference models no such transition at all and prices two separate guarantees instead — and the contracts themselves do provide for deterioration, so this model carries it and states the cost of the missing law.
- recovery_rate_mth()[source]#
The monthly probability of returning to autonomy; zero in the base run.
Contractually the rente stops on improvement out of a covered state, and one retrieved notice lets the level move in either direction. The only retrieved actuarial reference nonetheless sets the probability of return to autonomy to zero, and so does this model — as a named input held at zero, wired into the ledger roll and into
pols_recovery(), not as an omission. Its direction of error is one-sided: claims are overstated, by an amount no retrieved source quantifies.
- carence_factor(t)[source]#
S(t): the share of causes whose carence has already expired at month t.
Read from the [std] cause mix against the model point’s own three carence lengths: 0.10 in policy year 1, 0.65 in policy years 2 and 3, 1.00 thereafter on the base cell — the
S1 <= S2 <= S3 <= S4 = 100%shape the actuarial reference asks for. What it multiplies is the claim, not the decrement: a life whose dependence arises from a cause still inside its carence leaves the in-force ledger exactly as a covered one does, and takesrefunds_carence()with it.
- aggravation_carence(t)[source]#
The share of aggravations recognised at month t.
1.0 on a
total_and_partialcell, where the carence was already applied at first recognition into partielle. On atotal_onlycell the aggravation is the first recognition of a covered state, so it carries the carence itself.
- prev_param(name)[source]#
One parameter of the APA-prevalence logistic for this model point’s sex.
prev_ceil,prev_betaorprev_x_mid. The two slope parameters are pinned to sourced DREES rates;prev_ceilis [std], unidentified by a two-anchor fit, and it governs the tail — where 65% of this product’s lifetime claims fall.
- prev_rate(t)[source]#
prev(x): APA prevalence at the attained age in month t.
prev_ceil / (1 + exp(-beta (x - x_mid))). This is a prevalence of receipt of a public allowance, not an incidence and not the insurer’s definition of dependence. Multiplying it by a rente amount as though it were an annual claim frequency is the error that dominates this product;severity_share()andinc_rate_partial()are the two explicit steps that stand between them.APA is not available below age 60, so the curve has no anchor at all under 60 and every entry age below 60 runs on pure extrapolation.
- prev_slope(t)[source]#
prev’(x) = beta prev (1 - prev / prev_ceil): the prevalence slope in age.
A rate per year, which is why it can be added to a prevalence times a force of mortality in the identity below. The dimensional check this enforces is the one that catches the product’s dominant error.
s_P or s_T: the share of APA prevalence read as one insured state.
"partial"or"total", keyed by the contract’strigger_grid(). Public prevalence is APA take-up on GIR 1-4 and insurer definitions are deliberately stricter — the notice says the insurer is not bound by the decisions of the public services. Two sourced anchors bound the haircut on the base grid and neither pins it: the GIR 1-2 share of APA beneficiaries, 34.9%, and the market’s own count of rentes in payment against lives covered, about 0.44 against the shipped 0.45.Holding the shares constant across ages is a standardization with a known direction of error: severity mix worsens with age, so the model understates totale prevalence at old ages and overstates it at young ones.
- mort_force_avg(t)[source]#
mubar: the mortality force averaged over the three living states.
mu_H pi_H + mu_P pi_P + mu_T pi_T. It appears in the incidence identity because the state proportions are proportions of a living population, which is itself being drained at this rate.
- inc_rate_partial(t)[source]#
i_P(x): the annual force of entry into dépendance partielle from autonomy.
Derived, not assumed. Differentiating the state proportions along the age axis gives the identity
i_P = [pi_P' + (i_A + mu_P) pi_P - pi_P mubar] / pi_Hwith
pi_P' = s_P beta prev (1 - prev / prev_ceil). The mortality terms are not refinements: dropping them understates incidence, because a rising prevalence is being fed against a dependent population that is simultaneously draining at its own excess mortality. Floored at zero [std] — the identity can go negative at extreme ages, where the prevalence slope flattens while excess mortality does not. The floor never binds on the female base cell — the rate is still 0.0040 at attained age 109 — and binds at attained age 109 on the male basis.
- inc_rate_partial_mth(t)[source]#
i_Pm(t) = 1 - exp(-i_P/12): the monthly entry probability into partielle.
- inc_rate_total(t)[source]#
i_T(x): the annual force of entry into dépendance totale direct from autonomy.
The second half of the same identity,
i_T = [pi_T' - i_A pi_P + mu_T pi_T - pi_T mubar] / pi_Hand the
- i_A pi_Pterm is whyaggravation_rateand this rate are not independent inputs: the stock of totale lives is pinned by the assumed prevalence, so aggravations arriving from partielle displace direct entries one for one. Adding an aggravation rate without re-deriving this one double-counts entries into totale.It overtakes
inc_rate_partial()between ages 80 and 85 — the severity mix worsening with age, arriving through the mortality terms of the identity rather than through the constant severity shares, which cannot produce it. Floored at zero [std].
- inc_rate_total_mth(t)[source]#
i_Tm(t) = 1 - exp(-i_T/12): the monthly entry probability into totale.
- pols_auto(t)[source]#
auto(t): the autonomous, premium-paying population at the start of month t.
pols_if_init()att = 0on anautonomouscell and zero on every other kind, then survivors of mortality, of lapse and of incidence among the survivors, plus any returns to autonomy.Note what is absent from the recursion:
carence_factor(). A carence claim terminates the membership rather than deferring it, so the blocked lives leave the in-force ledger exactly as the covered ones do andauto(t + 1)does not depend onS(t)at all.The guard lets the ledger answer one month past the frame, at
t = proj_len(), which is deliberate and not a leftover of a 1-based index: the population identitycheck_states()holds at the start of a month, and the last projected month,t = proj_len() - 1, has one.pols_red()andred_rente_value()carry the same guard for the same reason.
- pols_lapse(t)[source]#
lapse(t): lapses out of the autonomous ledger at the end of month t.
Taken from the survivors of mortality. Pays nothing: there is no surrender value at any duration and the design is fonds perdu, so a lapse before the qualifying period destroys the whole accumulated value. From the qualifying period the same flow becomes
pols_reduction()instead of an exit.
- pols_reduction(t)[source]#
The lapses of month t that become a mise en réduction rather than an exit.
Zero until the membership has
reduction_qualifying_years()full years of premiums behind it, and the whole ofpols_lapse()thereafter. It is the second decrement, not the absence of one.
- pols_lapse_exit(t)[source]#
The lapses of month t that leave the model outright, with no value at all.
- pols_base(t)[source]#
base(t): autonomous lives exposed to incidence, after mortality and lapse.
The notes’ order out of the autonomous state is mortality, then lapse, then incidence among the survivors [std].
- pols_entry_partial(t)[source]#
n_P(t): recognised entrants into dépendance partielle at the end of month t.
base(t) i_Pm(t) S(t). On atotal_onlycell the carence does not enter here, because partielle is not a recognised state on that cell: the whole incidence flow moves into the ledger and the carence attaches later, at the aggravation that first recognises a covered state.
- pols_entry_total(t)[source]#
n_T(t): recognised entrants into dépendance totale direct from autonomy.
base(t) i_Tm(t) S(t).
- pols_entry_total_red(t)[source]#
n_Tr(t): entrants into totale out of the reduced ledger.
The reduced cover is dépendance totale only, so there is no partial entry from it, and it carries no *carence*: eight full years of premiums have been paid. These lives take the reduced rente they froze at the reduction date and not the capital d’équipement, which a reduced membership has lost.
- pols_aggravation(t)[source]#
n_A(t): the gross flow partielle to totale at the end of month t.
pols_part(t) (1 - q_P(t)) (1 - recovery) i_Am, taken from the survivors of mortality and of recovery. On atotal_and_partialcell every one of them is recognised already, and the cohort keeps its duration index so it does not serve a second franchise.
- pols_aggravation_recog(t)[source]#
The aggravations of month t that are recognised, after the carence.
- pols_carence_exit(t)[source]#
carence_exit(t): memberships terminated because a carence was still running.
A carence claim is a decrement with a cash flow, not a suppressed claim: modelling the carence as a multiplier on incidence alone leaves the terminated membership in force and omits the refund, and both errors run the same way — they overstate the liability at the front end and the premium income behind it. See
refunds_carence()for the cash flow.
- pols_recognition(t)[source]#
First recognitions of a covered state at the end of month t.
n_P + n_T + n_Tron atotal_and_partialcell, and the recognised aggravations in place ofn_Pon atotal_onlyone. It is what the claim adjudication expense rides on — a real, medically supervised process with a 45-working-day deadline and an arbitration route.
- pols_capital_claims(t)[source]#
The recognitions of month t that carry the capital d’équipement.
pols_recognition()less the entrants out of the reduced ledger, which have lost the option. It is paid once per membership, not once per state: a life that takes it on entering partielle takes nothing further on aggravating, which is why an aggravation appears here only on atotal_onlycell, where it is the first recognition.
- pols_red(t)[source]#
red(t): the paid-up population on a reduced rente totale at the start of month t.
No premium, rente totale only, no capital, no assistance and no further revalorisation of the guarantee. It is fed by
pols_reduction()and drained by mortality and by entry into totale, and it never lapses, because there is no premium left to miss.This is the ledger a naive model omits, and omitting it turns every lapse from the qualifying period into a full release of liability.
Answers one month past the frame, at
t = proj_len(), for the reason given underpols_auto().
- red_rente_value(t)[source]#
The reduced ledger’s population times the frozen rente it carries.
Carried as a value rather than as a per-cohort amount: reductions happen in every month from the qualifying period and each freezes
G(y) c(n)at its own date, so the ledger holds a distribution of amounts. Tracking the probability-weighted total is exact in expectation, because incidence does not depend on the amount, and it is what the notes license an implementation to do instead of carrying a per-reduction-cohort amount.The frozen amount is never revalued before claim; it becomes a rente en service and starts moving at
reval_renteonly once it is in payment, which happens on the fourth vector ofdep_cohorts().Answers one month past the frame, at
t = proj_len(), for the reason given underpols_auto().
- red_rente_pp(t)[source]#
The mean frozen reduced rente carried by the reduced ledger at month t.
red_rente_value(t) / pols_red(t), and zero on an empty ledger. It is the amount a life entering totale out of the reduced ledger takes into payment.
- dep_cohorts(t)[source]#
The four dependent-ledger vectors at the start of month t, as lists.
(partielle, totale, totale-on-a-reduced-rente, the value of that third ledger). Elementz - 1of each is the state at durationz, forz = 1 ... cohort_len(t); the fourth is a population times amount rather than a population, because the reduced rentes are frozen individually at each reduction date and cannot be recovered from the policy year the way the other amounts can.The model’s only list-valued cells, and the reason is cost: four two-argument recursions would be
4 proj_len() max_dur()separate cells — nearly a million on the base cell — where this isproj_len()cells with a loop inside.pols_part_dur()and its siblings read elements out of it, so the notes’ two-dimensional objects are still addressable by name.At
t = 0the vectors are the seeded state: all zeros on anautonomousorreducedcell, andpols_if_init()at cohortclaim_duration_months() + 1on an in-force claim cell. Thereafter cohort 1 is the previous month’s recognitions and every other cohort is the previous cohort survived one month, aggravated and — at an anniversary — revalued. A new list is built on each step rather than the previous one mutated, so holding a returned list cannot corrupt the cache.
- pols_part_dur(t, z)[source]#
pols_part(t, z): the population in partielle at duration z at the start of t.
- pols_tot_dur(t, z)[source]#
pols_tot(t, z): the population in totale at duration z at the start of t.
- pols_totr(t)[source]#
The whole population in totale on a reduced rente, at the start of t.
A separate ledger from
pols_tot()because these lives entered frompols_red()and carry a frozen reduced amount rather than the policy year’s guarantee.
- totr_rente_value(t)[source]#
The reduced-rente totale ledger’s population times the amount it is paid.
- pols_recovery(t)[source]#
Returns to autonomy out of the two full-cover dependent ledgers; zero in the base.
Taken from the survivors of the month’s mortality. See
recovery_rate_mth()for why this is a named input held at zero rather than an omission.
- pols_recovery_red(t)[source]#
Returns out of the reduced-rente totale ledger; zero in the base run.
They go back to
pols_red()and not topols_auto(), because a paid-up membership that recovers is still paid up.
- red_value_recovered(t)[source]#
The frozen-rente value returning to the reduced ledger on recovery.
Zero in the base run. A recovering life takes back the amount it was being paid rather than the amount it originally froze [std]: the value ledger does not carry the two separately, and the difference is immaterial while
recovery_rateis zero.
- pols_if(t)[source]#
The number of policies in force at the start of month t: every ledger added.
pols_auto + pols_red + pols_part + pols_tot + pols_totr. It is the weight on maintenance expense and the count a reader ofresult_cf()reconciles the rest of the row against. It is not the weight on premium income, which ispols_prem().
- pols_prem(t)[source]#
The population actually paying premium at the start of month t.
pols_auto()on every cell whose partielle is a covered state, because a recognised life is exonerated and a reduced membership is paid up. On atotal_onlycell the partielle ledger is not recognised, so those lives keep paying and are added here.Exonération runs from recognition, not from the start of rente payment, so a life inside the three-month franchise pays no premium and receives no rente. Carrying the franchise the way an income-protection deferred period is carried — premium-paying, benefit-free — overstates premium income.
- pols_if_at(t, timing)[source]#
The number of policies in force at a point inside month t.
"BEF_DECR"the start of the month, before any transition; the same number as
pols_if()."AFT_DECR"the end of the month, once deaths, outright lapses and the carence terminations have been taken. Equal to
pols_if(t + 1).
The intermediate points of the other models have no single-population meaning here, because five ledgers are moving at once; the ledgers themselves expose them.
- pols_death(t)[source]#
Deaths at the end of month t, from all five ledgers.
Three different rates on the same clock: the healthy rate on the autonomous and reduced ledgers,
q_Pon partielle,q_Ton both totale ledgers. It is exact even though the dependent ledgers are cohort-indexed, because the mortality rates do not depend on the duration.
- pols_lapse_cum(t)[source]#
Cumulative outright lapses before the start of month t.
Lapses that became a mise en réduction are not here: they never left.
- pols_carence_cum(t)[source]#
Cumulative memberships terminated by the carence before the start of month t.
Premium income at the start of month t, an inflow.
P(y) x premium_months() x pols_prem(t)when an instalment falls due. Carried onpols_prem()and never onpols_if(): lives in a recognised state are exonerated and reduced lives are paid up, so charging premium to the whole in-force block overstates income by the whole of both bands.
- instalments(t)[source]#
The number of rente instalments paid at the end of month t.
The population of every ledger past its franchise that survived the month. It drives the per-instalment handling expense, which pays for the annual proof of life and of the persisting state.
- claims(t, kind=None)[source]#
Benefit outgo at the end of month t, by kind; the total when kind is omitted.
"RENTE"the monthly rente, paid in arrears to the cohorts past their franchise that survived the month. Three ledgers contribute at three amounts: partielle at
rhotimes its vintage’s indexed guarantee, totale at the whole of it, and the reduced-rente ledger at its own frozen amounts."CAPITAL"the capital d’équipement, paid once per membership on
pols_capital_claims()."LAPSE"zero, in every month of every model point. There is no surrender value at any duration, and that zero is a product fact worth publishing rather than leaving to be inferred from a missing column.
There is deliberately no
"DEATH"kind: this composite carries no death benefit at all, and the optional capital décès rider is out of scope.
- refunds_carence(t)[source]#
The premiums returned when a carence terminates a membership at month t.
pols_carence_exit(t) x cum_prem_pp(t). It is not a claim — it is a return of premium, and it belongs on its own line because it is the only cash flow that runs backwards through the carence. In policy year 1 of the base cell it is 0.6141 EUR, three quarters of the year’s rente and capital claims combined: during the carence the largest benefit-side cash flow is a premium refund.
- expenses(t)[source]#
Maintenance, assistance and acquisition expense at the start of month t [std].
3.00 EUR a month on every policy in force plus 1.20 EUR a month on every policy in force except the reduced ones, both inflating at 1.5% a year, plus 150 EUR of acquisition at
t = 0. There is no observed range for any expense level on this product: no retrieved document discloses an expense assumption, a loading or a commission rate. Only the structure is sourced — prestations d’assistance end on mise en réduction, which is why the second base excludespols_red().The two per-event claim expenses are on
claim_expenses(), published as a separateresult_cf()column.
- claim_expenses(t)[source]#
Claim adjudication and rente handling expense at the end of month t [std].
250 EUR per first recognition and 10 EUR per instalment paid, both flat rather than inflating [std]. The adjudication load is an order of magnitude above the per-instalment one because recognition is a real, medically supervised process — a medical attestation completed with the treating doctor, a médecin-conseil ruling within 45 working days of a complete file, and a medical arbitration route — while the handling load pays for an annual proof of life and of the persisting state.
- net_cf(t)[source]#
The net cash flow of month t, insurer perspective, income positive.
premiums - claims - refunds_carence - expenses - claim_expenses. The notes’ own sign and the library-wide one, so there is no outgo-positiveliability_cfcompanion. Undiscounted: a market-consistent valuation applies EIOPA’s monthly risk-free term structure to exactly this stream, and that is a layer above this model.
- sojourn_total(x0)[source]#
The expected sojourn in dépendance totale, in years, entered at exact age x0.
Mortality at
mort_total_multand no other decrement, in monthly steps, on a continuously advancing exact age — which is the calibration convention and not the projection’s own age basis, where the attained age steps once a policy year.This is what calibrates
mort_total_mult: 2.9989 years from exact age 84 at 4.27, against the mean duration of receipt of about three years the CCSF reports for heavy dependents at a mean age at onset of 84 for women. At 2.75 the same calculation gives 4.19 years and at 3.50, 3.50 — the sojourn is far more sensitive to the multiple than a first look suggests, which is why this is a calibration and not a pick.
- sojourn_partial(x0)[source]#
The expected sojourn in dépendance partielle, in years, entered at exact age x0.
Mortality at
mort_partial_multand aggravation ataggravation_rate, since a life leaves partielle by dying or by deteriorating. Same continuous-age convention assojourn_total().3.14 years from exact age 82 on the shipped basis — the same order of magnitude as the 29.2-month mean duration of APA receipt across all GIRs, which is the only comparator there is.
mort_partial_multis not calibrated to it: it has no anchor at all, and this is a sanity check rather than a fit.
- 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)less deaths from all five ledgers, less the lapses that left outright, less the memberships the carence terminated. Three flows are deliberately absent because they move lives between ledgers rather than out of the policy count: incidence, aggravation, and the mise en réduction — which is the whole point of running the check on the sum rather than on any one ledger. A model that treated a qualifying lapse as an exit would fail this check, not merely understate the liability.
- check_pols_roll_fwd()[source]#
True when the in-force roll-forward closes in every projected month.
The library-wide form of a roll-forward check: no argument, one bool over all t, so one test can call it across every model.
check_pols_roll_fwd_resid()gives the signed residual of the month that failed.
- check_states_resid(t)[source]#
The five-ledger population identity residual at the start of month t; zero.
pols_if + cumulative deaths + cumulative outright lapses + cumulative carence terminationsmust equal the starting population in every month. This is the check that catches a leak in the cohort machinery: a mis-indexed duration shift drops population out of a dependent ledger with no corresponding exit, and nothing else in the model would notice.
- check_states()[source]#
True when the five-ledger population identity holds in every projected month.
No argument, one bool over all t, the library-wide shape of a
check_*cells;check_states_resid()gives the signed residual of the month that failed. The sweep runst = 0 ... proj_len(), one point past the frame, because the identity holds at the start of a month and the last projected month,proj_len() - 1, still has one.
- check_part_roll_fwd_resid(t)[source]#
The partielle ledger’s aggregate roll-forward residual in month t; zero.
pols_part(t+1)againstpols_part(t) (1 - q_P)(1 - recovery)(1 - i_Am) + n_P, which is the same population computed without the cohort machinery. It is a real check and not an identity: the two sides are built differently, so a duration shift that dropped or duplicated a cohort would show up here even though the total policy count still closed.
- check_part_roll_fwd()[source]#
True when the partielle ledger closes against its aggregate recursion.
- check_tot_roll_fwd_resid(t)[source]#
The two totale ledgers’ aggregate roll-forward residual in month t; zero.
pols_tot(t+1) + pols_totr(t+1)against the same population rolled forward without the cohort machinery: survivors ofq_Tand of recovery, plus the direct entrants, plus the entrants out of the reduced ledger, plus the recognised aggravations. The aggravation term is what makes this check bite — an implementation that added aggravations to totale without removing them from partielle, or that recognised them twice, fails here.
- check_tot_roll_fwd()[source]#
True when the two totale ledgers close against their aggregate recursion.
- check_model_point()[source]#
True when the selected model point is one the contract could have written.
Unlike the three roll-forward checks this is a validation of the input rather than an identity of the projection: the rente inside its sourced 500-3,000 band, the entry age inside the sourced 40-75 band on a new-business cell, the three carences in the non-decreasing order the actuarial reference asks for, a reduced cell with enough years of premiums behind it to have qualified, and a cause mix that sums to one — without which
carence_factor()would silently scale every claim.
- result_cf()[source]#
Result table of cash flows, indexed by policy month t.
pols_ifis every ledger added at the start of the month, and the five ledgers are published beside it because the reader needs to know which of them is paying premium, which is receiving a rente and which is doing neither.refunds_carencehas its own column because it is a return of premium and not a claim, andclaim_expenseshas its own because it is a per-event cost rather than a per-policy one. Nothing here is discounted.