Implementation Notes#
Status: Draft, 2026-08-26. Built from
products/eurocroissance/technical-notes.md;
the product it implements is specified in
product-spec.md.
This is a mechanics demonstration, not a pricing or reserving result. The mechanics are sourced, and unusually completely so — eurocroissance is a statutory construct rather than a market convention, so arts. L. 134-1 to L. 134-5 R1, R. 134-1 to R. 134-12 R2 R7 and A. 134-1 to A. 134-7 R3 fix the two provisions, the six permitted charge bases, the surrender and maturity values, the minimum part value, the 90 %-of-TEC discount ceiling and the provision pour garantie à terme. Every rate is a std standardization. No notice d’information, conditions générales or PRIIPs document d’information clé for any eurocroissance support could be retrieved [S10], so every insurer-level parameter is either a third-party fact-sheet figure [S8] or a level taken from the one published actuarial mémoire R13; the regulatory mortality tables are cited by arrêté and never shipped REG-R21 REG-R22 REG-R23; and no eurocroissance lapse experience is public R14 R21.
Run it#
python products/eurocroissance/run.py # Chassis A, the worked example
python products/eurocroissance/run.py 2 # Chassis B, same asset path
python products/eurocroissance/run.py 7 # an in-force Chassis A cell
Three lines to the same thing:
import modelx as mx
model = mx.read_model("products/eurocroissance/EC_FR_S")
model.Projection[1].result_provisions()
result_provisions() gives the provision machinery — the account’s assets against the
two provisions, the parts and their value, and the insurer’s own-funds items beside them.
Its anniversary rows, t = 11, 23, … , 119, are the two tables the notes’ worked
example prints; the other eleven rows of each policy year are the A. 134-5 intermediate
valuations, which the annual grid could not report at all. result_cf() gives the cash
flows, one row per policy month, and result_cf_annual() sums that frame into policy
years so it can be laid beside the annual-step model this one replaced.
The time index#
t is 0-based and counts policy months. Month t runs from time t to time
t + 1, so t = 0 is the issue month. proj_len() is the number of policy months
projected — 12 × policy_term() — and it is the exclusive end of the frame:
result_cf().index == range(proj_start(), proj_len()) last row = proj_len() - 1
len(result_cf()) == proj_len() - proj_start()
proj_len() == 12 * policy_term()
proj_start() == 12 * duration_inforce()
A new-business cell has proj_start() = 0 and 12n rows; the shipped worked example
(n = 10) has 120 rows, t = 0 … 119, and the last of them is the month that ends at
the échéance, where maturity_value, pols_maturity and claims_maturity live. An
in-force cell opens at proj_start() = 12 × duration_ifo. duration_ifo is an elapsed
count of completed policy years, so twelve times it is always the first month of a
policy year: shipped point 7 runs t = 48 … 119, 72 rows, and the frame opens on a
policy-year boundary on every model point.
Everything contractual about this product is annual, so the policy year is derived and used as a lookup key, never indexed by:
duration(t) == t // 12 completed policy years at the start of month t
policy_year(t) == duration(t) + 1 the contractual 1-based label
is_anniv(t) == t % 12 == 11 the last month of a policy year
anniv_mth(t) == 12 * duration(t) + 11
age(t) == issue_age() + duration(t) entering the policy year
age_anniv(t) == issue_age() + duration(t) + 1 attained at the anniversary
Policy year y is the months t = 12(y − 1) … 12y − 1, and its anniversary is
t = 12y − 1. That is where the notes’ worked-example tables are read.
The issue instant is not a row. The initial versement creates the rights, strikes
both provisions and draws the acquisition costs; all of that is the opening state of
the first projected month rather than a row of its own. The state is reached through the
"BOM" timing — own_assets_at(t, "BOM"), mg_at(t, "BOM"), pm_at(t, "BOM"),
prov_div_at(t, "BOM"), parts_at(t, "BOM"), part_value_at(t, "BOM"),
cum_prem_net_at(t, "BOM"), provision_value_at(t, "BOM") — and the flows (the
versement itself, its entry charge and the acquisition commission) are
beginning-of-month flows of month proj_start().
So the first row of result_cf() carries the initial versement and the first
month’s levy, return and striking, and no number is lost from any total.
Four cells are indexed by a month boundary m instead of by a month — rem_term(m),
tec_rate(m), i_pm(m), disc_factor(m) — with m = 0 at issue and m = 12n at the
échéance. Month t opens at m = t and is struck at m = t + 1, which is why
pm(t) = mg(t)·(1 + i_pm(t+1))^−rem_term(t+1) and why result_provisions() publishes
i_pm(t + 1) in its i_pm column: the rate that month’s provisions were struck at.
rem_term(m) = (proj_len() − m)/12 is the remaining term in years, fractional between
anniversaries. mort_rate(t) reads age_anniv(t), the âge atteint at the anniversary
that closes the policy year — the notes’ q(x + t + 1), kept on the annual assumption’s
own age rule.
Two speeds: the annual assumptions, and the month that applies them#
Annual — what the notes tabulate |
Monthly — what the recursion applies |
|---|---|
|
|
|
|
|
|
|
|
The unsuffixed name keeps the meaning the technical notes give it, which is the library
convention tests/test_model_conventions_fr.py asserts. Twelve of each monthly figure
compound back to exactly the annual one, which is what makes every anniversary value
identical to the annual-step model this one replaced.
Contractual events are not spread to match. The base 4° parts levy and a scheduled
versement fall in the first month of a policy year, t % 12 == 0; the striking of
the participation account, the base 5° performance levy on that year’s accumulated
performance, a free versement and the apport d’actifs fall on the anniversary,
is_anniv(t). The eleven other months of a year carry none of them, and
test_the_annual_contract_events_land_where_the_contract_puts_them sweeps every model
point to say so — an annual amount read by a per-t cash-flow cell would otherwise be
paid twelve times a year.
What the input files are keyed by#
No CSV value changed in the conversion; what changed is what the readers say the key means.
File |
Time-like column |
Decision |
|---|---|---|
|
|
An elapsed count of completed policy years, 0-based by nature. Unchanged; |
|
|
Contractual policy-year labels, 1-based, with 0 as the “none” sentinel. Unchanged; the model maps each to the single month |
|
|
Counts of years, not indices. Unchanged, and they stay in years: |
|
|
A contractual 1-based policy-year label, 1 to 40. File unchanged; |
|
|
The elapsed year end at which the return is credited: row |
|
|
One published curve per elapsed year. File unchanged; |
|
|
Attained age, not time. Unchanged; read at |
Two provisions, two state variables, one rebalancing a year — on a monthly grid#
That sentence is the model, and the qualifier is the conversion: the rebalancing is still annual, and only the grid underneath it is finer. The provision mathématique is the guaranteed amount discounted at the A. 134-1 rate; the provision de diversification takes whatever the account’s assets leave over, floored at the parts’ minimum value R2 R. 134-2, R. 134-4. Neither is a cash flow. The policy’s cash flows are versements in and surrender, death and maturity claims out; the two provisions reach them only through the R. 134-5 surrender and R. 134-6 maturity formulas.
Chassis A (1° engagement) |
Chassis B (2° engagement) |
|
|---|---|---|
|
|
identically 0 |
|
|
|
Surrender |
|
|
Death |
|
|
Maturity (at |
|
|
Insurer’s own funds |
|
|
The provision mathématique is re-struck, never accumulated#
pm(t) = mg(t)·(1 + i_pm(t+1))^−rem_term(t+1) i_pm(m) = max(0, 0.90 × TEC(rem_term(m)))
rem_term(m) = (proj_len() − m) / 12 years, fractional between anniversaries
i_pm is indexed by a month boundary m; month t closes at boundary t + 1, which
is rem_term(t + 1) years short of the échéance — a fractional number of them in eleven
months of twelve.
i_pm is A. 134-1’s ceiling R3: 90 % of the taux de l’échéance constante, interpolated
linearly between the bracketing maturities of the published curve, the longest rate held
beyond it, and a floor at zero. The article as retrieved gives the index maturity as the
holder’s guarantee maturity (method 1°) or the account’s 1°-engagement duration
(method 2°) and is silent on how it is re-read after inception; this model applies method 1°
and takes the remaining term at each valuation date — and, on this grid, at every
month boundary and at the fractional rem_term(m) — which is std and not the article.
technical-notes.md sets out the reasoning and is the source of truth for the value. The
curve row, by contrast, moves only at anniversaries: tec_curve.csv publishes one
curve per elapsed year and tec_rate(m) reads row m // 12, A. 134-1’s dernier TEC
publié, because interpolating a level between published years would invent data the file
does not contain. It is
not the
A. 132-1 maximum technical rate REG-R17 and not the A. 132-3 guaranteed-rate
ceiling REG-R18; those are different and stricter objects that apply to a tariff, while
this one is a valuation ceiling for a provision the saver has no right to withdraw at.
Rolling pm(t−1) forward at last year’s rate silently removes the rate effect. Over
the worked example’s policy year 6 — t = 59 to t = 71 — the move is +824.18, of which
the time effect on the
unchanged 2.25 % rate is only +236.74 and the re-strike at 0.90 % contributes
+587.44. An accumulating implementation lands pm(71) at
10,521.82 × 1.0225 = 10,758.56 against the 11,346.00 the re-strike gives, and every
later year inherits the gap.
The monthly re-strike splits that move in two, which the annual grid could not. The PM
accretes from pm(60) = 10,541.34 to pm(70) = 10,738.63 over eleven months at an
unchanged i_pm of 2.25 % — the time effect, arriving a twelfth at a time — and then
jumps to pm(71) = 11,346.00 in the twelfth month, where the TEC curve is re-published at
1.00 % — the rate effect, +607.37 in one month. Re-striking monthly is itself std
and is the same article read finer: R. 134-2 defines the PM at every instant and A. 134-5
requires an intermediate value at least monthly, which on Chassis A needs a PM for the
residual to be taken against. It is also what makes check_guarantee_funding() close
month by month rather than only at anniversaries.
In the last projected month, t = proj_len() − 1, the discount factor is 1, so pm
equals mg identically and the Chassis A
guarantee is pre-funded by construction. check_guarantee_funding() asserts it at every
t, and it is the model’s headline check.
The same rule is why an in-force model point carries no accumulated PM. pm_ifo is
shipped in the table and read by check_pm_restruck() alone: it compares what an extract
reports against what R. 134-2 requires, which is how a reader discovers an extract built
by accumulation. The projection itself re-derives the number.
The monthly re-striking, the annual participation account, and the insurer’s contribution#
L(t) = f_p·prov_div_open(t)·1{t % 12 == 0} A_a = A_open(t) − L(t) − W(t) + P_net(t)
I(t) = A_a·r_m(t) I_ytd(t) = I(t) + I_ytd(t−1) unless t opens the year
F(t) = f_perf·max(I_ytd(t), 0)·1{is_anniv(t)}
A(t) = A_a + I(t) − F(t) N(t) = N_open(t)(1 − f_p·1{t % 12 == 0})(1 − w_pm) + parts bought
prov_div(t) = max(A(t) − pm(t), N(t)·u_min) u(t) = prov_div(t)/N(t)
C(t) = max(pm(t) + prov_div(t) − A(t), 0)
X_open(t) is the "BOM" timing: X(t−1) in every month but the first projected one,
and in that one the state the initial versement — or the in-force extract — creates.
Two of the six R. 134-3 bases keep an annual rhythm, for two different reasons. L(t)
falls in the first month of each policy year because product-spec.md states it taken
there on the opening part value — a contract term, not an assumption; spreading it at
(1 − f_p)^(1/12) would compound back to the same parts count but would change the
cash, because the levy is valued on the then-current prov_div. F(t) falls on the
anniversary on I_ytd, the year’s accumulated performance, because R. 134-3 5° levies on
the balance of the participation account and R. 134-4 strikes that account at least
annually; a monthly levy would tax a positive month inside a losing year, which the
asymmetric max(·, 0) exists not to do, and would have taken money in policy year 6. A
policy exiting between anniversaries pays no performance levy for the part year, which is
what the article’s annual striking implies.
The diversification provision takes the residual and stops at the parts’ contractual
floor: R. 134-4 permits a debit balance to reduce the part value only within the limit
of its minimum R2. Where the floor binds, the two provisions together exceed the
assets, and the excess is exactly the L. 134-3 contribution R1. At the anniversary of
the notes’ policy year 6 — t = 71 — the
raw residual is 10,250.65 − 11,346.00 = −1,095.35, the floor binds at
207.7460 × 5.0000 = 1,038.73, and the contribution is 2,134.08 — so the contract
surrenders for 12,384.73 against account assets of 10,250.65.
The monthly grid says when that starts, which the annual grid could not. The part
value falls 13.4897 (t = 60) → 8.7493 (63) → 5.7614 (65) and then stops: the floor
first binds in month 66, where the contribution starts at 141.96, and it climbs
through 1,278.00 at month 70 to 2,134.08 at the anniversary. On Chassis B over the same
months the PGT is constituted the same way — first positive in month 68, at 61.48,
reaching 1,446.78 at month 71.
C(t) carries no return to the savers std. own_assets_at rolls forward from
A_open(t) and never from pm_open(t) + prov_div_open(t), so the policy-year-7 asset
roll — month t = 72 — starts from
10,250.65 and not from 12,384.73. Rolling the topped-up balance forward would manufacture
investment return out of the insurer’s capital.
The processing order is not free. R. 134-4 and R. 134-12 III both say that asset
affectations completing the account’s representation are made on the dates the
participation account is struck, after its balance has been allocated R2 R7, so
own_assets_at(t, timing) and parts_at(t, timing) expose the steps one at a time —
BOM, AFT_LEVY, AFT_EXIT, AFT_PREM, AFT_RETURN, AFT_PERF, AFT_TOP_UP — and
check_assets_roll_fwd() rebuilds the whole recursion in one expression so that a
mis-ordered step shows up rather than quietly shifting the answer. Because the recursion
starts from BOM rather than from X(t−1), the four roll-forward checks are live in
the first projected month too. Two of the three charges now enter that check by
name rather than being rebuilt from their rates, and that is the conversion showing
through: the base 4° levy falls only in the first month of a policy year and the base 5°
levy only on the anniversary, on the year’s accumulated performance, so neither can be
reconstructed from a month’s own balance. What the check still rebuilds independently is
the order and the base, and the companion identity
invest_income(t) == base × asset_return_mth(t) — asserted in the test module — pins the
balance the return accrues on.
A versement leaves the contribution unchanged, and that is worth knowing rather than
assuming: it adds P_net to the assets and g·P_net·v + (P_net − g·P_net·v)
to the two provisions, which is the same number. The order of steps 7 and 8 in the notes
is therefore immaterial, and the model strikes C(t) on the post-versement state.
The Chassis B surrender value is not guaranteed#
This is the single most important product fact, and the product’s central error is to
model it otherwise. Before the échéance a 2° engagement pays parts × part value and
nothing else R2 R. 134-5. At the anniversary of the worked example’s policy-year-6 shock, t = 71, that is 9,899.22
— 84.18 % of net versements — against a guarantee of 11,760.00 that does not apply.
A model that floors it at g × premiums, or at the discounted guarantee, is modelling a
contract that does not exist.
An exit is priced on the striking of the month it falls in, which is the forward part value A. 134-5 requires for a request made within that month. That is the one place this grid removes a documented simplification rather than adding one: a Chassis B saver surrendering in month 65 receives 11,430.63, not the 9,899.22 the anniversary at month 71 reports.
The shortfall is carried instead as the provision pour garantie à terme, on the
insurer’s balance sheet, outside the participation account, on the A. 132-18 tables at
a rate at most 90 % of the TEC and counting no cash flows other than guarantee
maturities and mortality R3 A. 134-2 R10. It is 1,446.78 at t = 71 and runs off to
zero by the échéance as the account recovers. A model must not “improve” that
deliberately narrow basis by adding lapses or expenses to it, and must not let it reach a
benefit: check_own_funds_not_paid() asserts that no benefit before the term exceeds
pm + prov_div, and the shipped Chassis B cells carry a positive PGT for four consecutive
years, so the check is live rather than decorative.
One of A. 134-2’s two admitted drivers is not implemented. pgt() discounts the
guaranteed amount to t and applies no survival factor, so the present value is the
amount for a guarantee certain to be reached. The simplification is std and prudent —
it overstates the provision — and it is invisible on the worked example, where mort_rate
is zero; it is live on every decrement-bearing cell. On model point 6 at t = 83 — the anniversary closing policy year 7 — the reported
pgt is 2,739.35, against 2,477.36 with the five-year survival factor 0.972660 the
shipped std table gives. In this single-policy model the mortality decrement reaches
the projection through pols_if in result_cf() rather than through the provision; a
fund-level implementation of A. 134-2 should carry the survival factor inside the present
value, over the account’s 2° engagements.
The max(·, mg) exists only in the last projected month, t = proj_len() − 1 and
only on Chassis B R2 R. 134-6. The échéance really is
one month, which is why that test stays on the month while the rachat exclusions moved to
the policy year — lapse_rate() and wd_rate() return zero over the whole of the last
policy year, duration(t) >= policy_term() − 1, exactly as the annual grid did. Restating
them on the last month would have opened eleven months the annual grid closed and moved
the whole maturity claim. On
Chassis A the maturity amount is pm + parts × part value there — more than the
guarantee whenever the parts retain any value, 12,765.89 against 11,760.00 here.
The death benefit is not the maturity guarantee#
Chapter IV contains no death valuation article, so the death benefit is the current
provision value R2 R13. Any garantie décès plancher [S1] [S2] is a complementary
guarantee provisioned outside the auxiliary account R2 R. 134-7: death_payout()
floors the payout at cumulative net versements where the model point elects it, and
rider_claim_pp() reports the difference separately — 1,860.78 on the policy-year-6
Chassis B death at t = 71, which is not the account’s money. cum_prem_net is kept as its own state
variable rather than read off mg, because the two coincide only while g is 100 %.
The charge bases are not interchangeable#
R. 134-3 permits six bases and no others, and base 3° — a levy on the encours of the
diversification provision — is available only in an auxiliary account holding no 1°
engagements. No base permits a levy on the provision mathématique at all R2. The
recurring charge here is therefore base 4°, a levy in number of parts: parts_levy()
is f_p × prov_div_at(t, "BOM"), taken in the first month of each policy year
(t % 12 == 0), and parts_at() cancels f_p of the parts there and nothing in the other
eleven months. On Chassis A
that is 15.64 opening policy year 1, t = 0; an encours levy on pm + prov_div
would have been 78.40, five times as much and unlawful in a 1° account.
The base 5° performance levy takes 10 % of the policy year’s positive financial performance
on the anniversary and nothing at all on a negative year, which is why both chassis
show a nil performance levy for policy year 6 at t = 71 — and nothing in the other eleven
months of any year.
Behaviour, and what is held at zero#
All dynamic shapes are std: no eurocroissance lapse experience is public and the product is too small and too young to have any R14 R21.
Overlay |
Factor |
When |
|---|---|---|
Guarantee-imminent suppression |
0.5 |
every month of the two policy years before the échéance — |
Duration-8 spike |
1.5 |
every month of policy year 8, |
Lock-up |
0 |
|
Last policy year |
0 |
|
Every factor is a multiplier on the annual rate, applied before the monthly conversion,
so each grades once a year as the behaviour it stands for does and not once a month. The
last two rows are read on duration(t) rather than on the month for the same reason: the
contract states both in years, and restating the last-year exclusion as the last month
would open eleven months the annual grid closed.
The gate on the first is the point. A saver who surrenders a 2° engagement gives up the entire guarantee R2 R. 134-5, so the deterrent exists precisely while the guarantee is in the money and is worth nothing otherwise. Applying it unconditionally would invent behaviour where there is none.
Held at zero or absent by design, and each for a stated reason:
the PCDD piloting rule — run the fund at 30 bp above the insurer’s own euro fund and carry the rest to the PCDD R13 — and the PCDD’s release back into the participation account.
pcdd()accumulates the R. 134-12 apport d’actifs R7 and nothing else. Holding it at zero understates the smoothing the real product delivers.the conversion of parts into PM under A. 134-4, whose 15 %-of-PM headroom
conversion_headroom()computes without exercising — 474.52 att= 59, on which the 0.50 % frais de conversion [S8] would be 2.37;the revaluation of guarantees out of the participation account, whose two A. 134-3 tests
gate_revalue_ok()evaluates without exercising, now every month rather than once a year: both pass att= 59 and the second fails att= 71, where the part-value floor has taken the headroom to nil;the rente viagère option at the échéance R2 R. 134-6, which
annuity_option_flag()rejects by name — it needs the TGH05 / TGF05 generational tables REG-R21, which are cited and never shipped, and a projection that runs past the term;and the statutory arbitrage into an SRI ≤ 2 support that A. 134-6 makes the maturity default R3 REG-R33.
Absent, and not by a switch: the commercial bonus devices the product specification
records — AXA’s Eurocroissance + adds +2 % to the base rate on 2026 payments subject to
a ≥ 45 % unit-linked condition [S3] [S4]. There is no uplift Reference, no cells, no
model-point column and no eligibility flag, because a marketing promotion is not a term of
the statutory mechanics this model demonstrates. Adding one would mean a rate overlay on
asset_return() and an eligibility field the composite has no basis to populate.
Inputs are external files#
The five input CSVs live in this directory, beside run.py — not inside the model
folder. EC_FR_S/ holds nothing but formulas:
products/eurocroissance/
model_point_table.csv <- inputs live here
mort_table.csv
lapse_table.csv
scenario_table.csv
tec_curve.csv
run.py
model.md
product-spec.md <- the documents this model implements
technical-notes.md
sources.md
EC_FR_S/ <- formulas only
__init__.py (model docstring)
_system.json
Data/__init__.py (reads the CSVs, once per model)
Projection/__init__.py (the by-policy projection)
This follows lifelib’s annuallife/TradLife_A. Projection is parameterized by
point_id, so the CSV readers live in an unparameterized Data Space and each file
is read once per model rather than once per model point; a test counts the reads.
Reference |
Cells |
File |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
File |
Contents |
Provenance |
|---|---|---|
|
Eleven model points. Points 1 and 2 are the worked example’s two chassis on one asset path — €10 000 gross at issue, €2 000 on the anniversary of policy year 3 ( |
anchor cells std, sizes and age from R13; guarantee level and term [S1] [S2] |
|
Base annual mortality by sex and âge atteint 18-120, capped at 1 |
std INSEE-shaped Makeham proxy REG-R24, anchored so that the 80 % best-estimate factor gives exactly 0.5000 % at male 57 — not TH 00-02 / TF 00-02, which are cited by arrêté and never shipped REG-R22 REG-R23 |
|
Annual rachat total 2.5 % level, and rachat partiel 6 % in policy years 1-2 then 3 %, keyed by the 1-based |
std; R13 observes 2 %-3 % and 6 % then 2 %-4 %, and no other eurocroissance lapse experience is public |
|
Gross asset return net of asset management fees, by scenario and elapsed year end. |
|
|
The TEC term structure at maturities 1, 2, 5, 10, 20 and 30, by scenario and elapsed time point |
std; the macro anchor is the 10-year OAT averaging 3.0 % in 2023 and 2024 R19 |
Two of these are scenario files rather than assumption files, and that is a product
statement. i_pm is 90 % of the TEC R3 A. 134-1, read at the remaining maturity std, so the level
and the slope of the curve drive the provision mathématique directly — a 150 bp fall
adds 587.44 to pm(5) in the worked example, more than twice the year’s time effect. A
model that carried a flat TEC in a Projection Reference would not be modelling this
product’s dominant risk, so the curve is a table with a maturity dimension and
tec_rate() interpolates across it exactly as the article requires.
Sign convention#
liability_cf(t) prints outgo positive, as the technical notes print it: claims and
rachats partiels and expenses out, versements in. net_cf(t) = −liability_cf(t) is
the library’s income-positive sign, so result_cf()["net_cf"] can be compared and
summed across products without checking which one it came from. The two are exact
negatives of each other by construction, and a test asserts it.
Neither the two provisions nor the insurer’s own-funds items appear in either. The
provisions are state variables and reach the flows only through the claim formulas; the
contribution and the PGT are capital, not benefit. charges_taken and rider_claims are
published in result_cf() as memo lines outside net_cf — the first is a transfer
inside the account from the savers’ provisions to the insurer, and the second is already
inside claims_death.
The first column, pols_if, is the start-of-month in-force count, which is the exposure
every flow on that same row is weighted by. result_cf()["pols_if"].iloc[0] is therefore
pols_if_init() on every model point, and a flow divided by its own row’s pols_if is the
per-policy amount. pols_if(12k) is also the count the annual-step model this one replaced
carried entering policy year k + 1, to floating point, because the monthly decrements
compound back to the annual ones. The technical notes’ l(t) — the count at the end of
month t, nil on the last projected row — is a different series and is reached as
pols_if_at(t, "AFT_DECR");
it is not published in result_cf(). It weights the maintenance expense, and nothing else.
Naming#
Notes symbol |
Cells |
Why it needed care |
|---|---|---|
|
|
|
|
(not an input) |
R. 134-2 makes the PM the guarantee discounted at the current rate, so an extract cannot supply it and a projection cannot roll it forward. The column is shipped and read by |
|
|
Two different events. A full surrender removes the policy; a partial one runs the guarantee, the parts and the assets down pro rata and leaves the contract in force. Sharing one name would have merged a decrement with an owner election. |
|
|
The unsuffixed name keeps the annual meaning the technical notes tabulate, and |
|
|
The age entering the policy year of month |
(the versement split) |
|
Named for where they land rather than for BOY and EOY, because they are two different rules: the first prices at the opening part value and splits at |
(the year’s performance) |
|
The month’s financial performance, and the same accumulated since the policy year opened. The second exists only because the base 5° levy is struck once a year on the participation account, so it needs the year’s balance and not the month’s. |
(exit charge) |
|
What leaves the provision and what reaches the saver. The provision run-down keys off the gross amount, the cash flow off the net one. |
|
|
One expression for both chassis, because |
|
|
The notes index the in-force probability at the end of the period, so the last projected row carries nil. The bare name |
— |
|
The initial versement and its net amount. They are separate from |
— |
|
The opening timings — on |
— |
|
Absent, deliberately. A eurocroissance engagement is not an account value: the saver’s rights are a number of parts whose value is common to the whole auxiliary account R2 R. 134-2, plus — on Chassis A — a share of a discounted promise. Naming either the library’s account value would assert something false about the contract. |
Standardizations used#
Everything in this list is std. The constant-force conversion of every annual
assumption to the month — 1 − (1 − q)^(1/12) for each decrement and (1 + r)^(1/12) − 1
for the return — since no retrieved French source states a conversion convention. Spreading
the rachat partiel at 1 − (1 − w_p)^(1/12) rather than taking it whole at the opening of
the policy year, which is the one decision that costs exact anniversary equivalence and is
quantified below. Re-striking the provision mathématique every month at the fractional
remaining term, which A. 134-5’s intermediate value requires on Chassis A but which the
article does not spell out. Holding the TEC curve at the most recently published row
between anniversaries while the maturity it is read at shortens monthly. Reading the
guarantee-imminent in-the-money test on the month’s own striking rather than on the
anniversary’s. The charge levels — entry
2.00 %, parts levy 0.80 % p.a., performance levy 10 %, exit 0 %, surrender indemnity 0 % —
and the routing of the recurring charge through base 4°; the levels come from R13 and
[S8], the routing does not. The minimum part value of €5.0000, nowhere published for any
insurer. The initial part value of €10.0000. The credit-balance route that raises the part
value rather than awarding parts. The reference TEC levels and the whole curve shape. The
asset-return paths. The insurer’s contribution earning nothing for the savers and being
released as soon as representation permits. The mortality proxy and the 80 % best-estimate
factor. The full and partial rachat levels and all three behavioural overlays. Expenses:
acquisition 5 % of versements, acquisition commission 2 % of the initial one, maintenance
0.20 % p.a. of the two provisions, all from R13. Death before surrender as the
processing order, 100 % of survivors taking the maturity amount, and âge atteint as the
age basis, since A. 335-1 fixes no model age basis REG-R23. The PCDD and the
apport d’actifs at zero in the base run. Reading A. 134-1’s index maturity as the
remaining term at each valuation date, which the article does not state. The
PGT without its survival factor, one of the two cash-flow drivers A. 134-2 admits.
Returning a zero surrender indemnity beyond ten years unconditionally, which takes up a
permission R. 132-5-3 grants rather than obeying a prohibition it does not impose.
Publishing pols_if as the start-of-period count, the library’s shared vocabulary, with
the notes’ end-of-period l(t) reached as pols_if_at(t, "AFT_DECR"). Accruing the
maintenance expense monthly, at one twelfth of 0.20 % p.a. on the month’s own provision
and in-force: twelve accruals cover exactly the year the annual grid charged once for at its
year-end striking, so the annual model’s extra opening-striking charge is dropped, and the
total maintenance over a contract moves by −0.2 % to −1.3 % on the shipped cells (and
+0.9 % on point 9, whose five annual versements step the provision up at the opening
of each of its first five policy years).
What the monthly grid reproduces, and what it deliberately does not#
Measured against a pre-conversion snapshot of the annual-step model over all eleven shipped
model points, every anniversary value is identical to 1.3 × 10⁻¹⁰ EUR — own_assets,
pm, prov_div, parts, part_value, mg, cum_prem_net, insurer_contribution,
pgt, pcdd, provision_value, surrender_value, death_value, death_payout,
maturity_value, conversion_headroom, gate_revalue_ok, parts_levy and perf_levy at
t = 12k + 11, and pols_if at t = 12k.
Annual grid |
Monthly grid |
|
|---|---|---|
|
10,250.65 |
10,250.65 |
|
11,346.00 |
11,346.00 |
Chassis B surrender, policy year 6 |
9,899.22 at the year end |
9,899.22 at |
Insurer’s contribution, policy year 6 |
2,134.08, arriving whole |
141.96 from month 66, 2,134.08 by month 71 |
Total |
1,040.52 |
1,038.34 (−0.21 %) |
Total |
1,376.56 |
1,388.49 (+0.87 %) |
Total |
3,869.49 |
3,878.27 (+0.23 %) |
Three things move, and each is the point of the finer grid rather than a defect. The cash
flows now fall where they happen — a claim at the end of the month of exit, a versement
in its own month. The maintenance expense accrues monthly, as above. And on the one
shipped cell that takes rachats partiels — point 5 — the asset-fed values sit
slightly above the annual grid’s, because wd_rate_mth spreads the exit cash over twelve
months so more capital earns return early: own_assets at the anniversaries runs +0.21 to
+3.05 EUR higher, at most 0.031 %, and part_value at maturity is 25.097 against
25.0768. What still matches exactly on that cell is everything whose run-down is purely
multiplicative — mg, cum_prem_net, pm, parts and pols_if — because
(1 − w_pm)^12 = 1 − w_p.
Tests#
tests/test_eurocroissance_fr.py asserts both chassis of the worked example row by row to
the cent — the opening state the initial versement creates, the asset roll, the parts
levy and its legal base, the performance levy and its
asymmetry, the re-strike of the PM and its rate/time decomposition, the minimum part value
and the insurer’s contribution it creates, the versement split on the anniversary of
policy year 3 on both chassis,
the PGT and its run-off, the A. 134-3 gates and the A. 134-4 headroom — plus one test per
modelling pitfall the notes list, each named for the failure it catches, and all nine
invariant checks on every model point.
It also asserts the claims the monthly grid itself makes, each with a named test:
test_the_monthly_rates_compound_back_to_the_annual_ones,
test_the_in_force_at_every_anniversary_is_what_an_annual_step_would_carry,
test_the_annual_contract_events_land_where_the_contract_puts_them (a sweep over every
model point and every month),
test_the_intermediate_value_is_struck_in_every_month,
test_the_maintenance_expense_accrues_monthly and
test_the_partial_rachat_is_spread_over_the_year.
The goldens are keyed by the anniversary month 12y − 1, so row t is the close of
policy year (t + 1)/12 and carries the number the annual-step model printed against that
policy year; the notes’ opening line is asserted separately against the "BOM" timings,
and the month-level table of policy year 6 has goldens of its own.
test_result_cf_shape_and_what_it_leaves_out pins the frame at
range(proj_start(), proj_len()) — 120 rows on the worked example — and
tests/test_model_conventions_fr.py pins the same rule across the library.
test_result_cf_annual_is_the_monthly_frame_summed pins result_cf_annual() as the frame
regrouped rather than a second projection, column by column.
python -m pytest tests -q