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 autonomedépendance partielle / dépendance totaledé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#

model_point()[source]#

The selected model point as a Series.

policy_id()[source]#

The policy identifier of the selected model point.

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 — status of partial, total or reduced — 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 at t = 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_mth is a model point column and not a rate looked up by sex.

cover_type()[source]#

total_and_partial or total_only: which states the contract covers.

The composite of the technical notes is total_and_partial and it is what the worked example runs. See the Space docstring for what total_only changes 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, avq6 or aggir: 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 reduced cell 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 a total_only cell.

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_option()[source]#

Whether the optional capital d’équipement is bought.

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.

premium_mth()[source]#

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.

premium_mode()[source]#

monthly, quarterly, half_yearly or annual; 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.

premium_months()[source]#

The number of months one premium instalment covers: 1, 3, 6 or 12.

premium_due(t)[source]#

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.

couple_factor()[source]#

The premium multiplier the couple discount applies, 0.90 or 1.00.

carence_accident_months()[source]#

The carence for accident, 0 months on every retrieved contract.

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, illness and neuro, the three keys of cause_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 month s is first paid at the end of month s + 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.

autonomous

the whole population is autonomous and premium-paying.

partial / total

a claim already in payment, seeded into the corresponding dependent ledger at claim_duration_months().

reduced

a 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 at z0 = franchise_months() is therefore paid in its very first month.

years_paid()[source]#

n: the completed years of premiums behind a reduced cell.

It selects the barème coefficient the frozen reduced rente carries, and it is ignored on every other kind of cell.

pols_if_init()[source]#

Initial number of policies; 1.0 on a single-policy model point.

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() - 1 and result_cf() has proj_len() rows, which is lifelib’s range(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_age is 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 duration is 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, since t is 0-based. The cells exists so that the monthly models in this library share one vocabulary.

policy_year(t)[source]#

y(t) = t // 12 + 1: the policy year containing month t; 1 for t = 0..11.

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 duration z0 + 1 reaches z0 + proj_len() in the last month t = proj_len() - 1, and one recognised in month 0 reaches proj_len(). The last element is therefore structurally zero, which is what makes the duration shift lossless.

seed_dur()[source]#

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

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 is proj_len() max_dur() floats where this is half that — and pols_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_guarantee on each policy anniversary, before claim. Once a rente is in payment it leaves this cells behind and moves at reval_rente instead — see rente_pay_pp().

rente_partial_pp(t)[source]#

rho G(y): the guaranteed rente partielle; zero on a total_only cell.

capital_pp(t)[source]#

CAP(y): the guaranteed capital d’équipement in the policy year containing t.

Indexed at reval_guarantee alongside 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 - z entered on the guarantee of its own policy year y_e and has been indexed at reval_rente at every anniversary since, so the amount is G(y_e) (1 + reval_rente)^(y - y_e). Two indexations, two ledgers: reval_guarantee set the amount at recognition and reval_rente has 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 = 0 on an in-force claim cell reads as policy year 1, which is the same [std] restart of the policy-year clock that age_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 of revision_table.csv to project a repricing.

premium_factor(y)[source]#

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.

premium_mth_pp(t)[source]#

P(y): the monthly premium in the policy year containing month t.

premium_mth() x couple_factor() x premium_factor(y). An instalment of premium_months() of these falls due whenever premium_due() is true.

The _mth_ is load-bearing: library-wide premium_pp is the annual premium per policy and premium_mth_pp the monthly one. Every recursion in this model works in months and so uses this cells, not premium_pp().

premium_pp(t)[source]#

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 are premiums(), built on premium_mth_pp() and premium_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 at t = 0 on every payment mode, so the first instalment always falls on the first row.

years_premiums_paid(t)[source]#

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.csv by 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() and mort_rate_total() — and reading a dependent’s mortality out of this cells is the largest single error available on this product.

mort_rate_mth(t)[source]#

q_H(t) = 1 - (1 - mort_rate)^(1/12): monthly healthy mortality [std].

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 makes 1 - (1 - q)^(k/12) the right monthly conversion for a dependent life and k q the 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)^k with k = mort_partial_mult, a proportional hazard on the force [std]. 0.10562 at age 85 against 0.06179 healthy. k has no anchor: it must exceed 1, because GIR 3-4 lives carry excess mortality, and sit well below mort_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)^k with k = 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. k is calibrated: see sojourn_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 through mort_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.

lapse_rate_mth(t)[source]#

w(t) monthly = 1 - (1 - lapse_rate)^(1/12) [std].

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 takes refunds_carence() with it.

aggravation_carence(t)[source]#

The share of aggravations recognised at month t.

1.0 on a total_and_partial cell, where the carence was already applied at first recognition into partielle. On a total_only cell 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_beta or prev_x_mid. The two slope parameters are pinned to sourced DREES rates; prev_ceil is [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() and inc_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.

severity_share(kind)[source]#

s_P or s_T: the share of APA prevalence read as one insured state.

"partial" or "total", keyed by the contract’s trigger_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.

prev_partial(t)[source]#

pi_P = s_P prev(x): the proportion of living lives in insured partielle.

prev_total(t)[source]#

pi_T = s_T prev(x): the proportion of living lives in insured totale.

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_H

with 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_H

and the - i_A pi_P term is why aggravation_rate and 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() at t = 0 on an autonomous cell 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 and auto(t + 1) does not depend on S(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 identity check_states() holds at the start of a month, and the last projected month, t = proj_len() - 1, has one. pols_red() and red_rente_value() carry the same guard for the same reason.

pols_surv(t)[source]#

surv(t): autonomous lives surviving the month’s mortality, before lapse.

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 of pols_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 a total_only cell 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 a total_and_partial cell 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_Tr on a total_and_partial cell, and the recognised aggravations in place of n_P on a total_only one. 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 a total_only cell, 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 under pols_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_rente only once it is in payment, which happens on the fourth vector of dep_cohorts().

Answers one month past the frame, at t = proj_len(), for the reason given under pols_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). Element z - 1 of each is the state at duration z, for z = 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 is proj_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 = 0 the vectors are the seeded state: all zeros on an autonomous or reduced cell, and pols_if_init() at cohort claim_duration_months() + 1 on 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_dur(t, z)[source]#

pols_totr(t, z): the reduced-rente totale population at duration z.

pols_part(t)[source]#

The whole population in dépendance partielle at the start of month t.

pols_tot(t)[source]#

The whole population in dépendance totale on a full rente, 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 from pols_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 to pols_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_rate is 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 of result_cf() reconciles the rest of the row against. It is not the weight on premium income, which is pols_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 a total_only cell 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_P on partielle, q_T on 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_dead_cum(t)[source]#

Cumulative deaths from every ledger before the start of month t.

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.

premiums(t)[source]#

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 on pols_prem() and never on pols_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 rho times 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 excludes pols_red().

The two per-event claim expenses are on claim_expenses(), published as a separate result_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-positive liability_cf companion. 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_mult and 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_mult and aggravation at aggravation_rate, since a life leaves partielle by dying or by deteriorating. Same continuous-age convention as sojourn_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_mult is 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 terminations must 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 runs t = 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) against pols_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 of q_T and 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_if is 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_carence has its own column because it is a return of premium and not a claim, and claim_expenses has its own because it is a per-event cost rather than a per-policy one. Nothing here is discounted.

result_states()[source]#

Result table of state movements and rates, indexed by policy month t.