Implementation Notes#
Status: Draft, 2026-08-20. Built from technical-notes.md (the
product as a liability cash flow model on paper) and product-spec.md
(the representative contract those notes model). Source tags resolve against
sources.md.
This is a mechanics demonstration, not a pricing or reserving result. The contractual mechanics are sourced — the 死亡給付金 as cumulative premiums paid, the 解約返戻金 (kaiyaku-henreikin, surrender value) capped at that same amount, the unavailability of surrender from the 年金支払開始日, the unconditional 確定年金 instalments, the 年金の一括払 factor table, the 自動振替貸付 interest ceiling and the conditions of the 税制適格特約 (zeisei tekikaku tokuyaku), the tax-qualification rider (tokuyaku, 特約). Nearly everything quantitative is a standardization. The two assumed interest rates (yotei riritsu, 予定利率) are published [S5] [S8] but the 予定事業費率 and the 年金支払開始時費用 are calibrated std against one published specimen [S6], because the 算出方法書 is a 基礎書類 filed with the FSA and not published REG-R2; no carrier publishes an expense basis, a commission scale or a lapse curve by duration; and the mortality basis is not a published table. 生保標準生命表2018 and the 2007 年金開始後用 table are readable at stable public URLs but cannot be redistributed REG-R21, so this library ships a documented proxy anchored to quoted rates — the canonical library-wide 死亡保険用 table, and three spot rates on 年金開始後用. Replace the assumption tables with company data, and the mortality basis with a licensed one, before drawing any conclusion from the output.
Annuity_JP_S is the monthly-grid model of the fixed individual annuity insurance
(teigaku kojin nenkin hoken, 定額個人年金保険) composite with the 税制適格特約 attached. It is the
library’s payout chassis: the deferral phase is a savings accumulation and the payout
phase is the annuity machinery the other stream-benefit products point at.
Run it#
python products/individual_annuity/run.py # the worked example's anchor cell
python products/individual_annuity/run.py 4 # another model point
run.py prints the model point, the annuitisation quantities, the head and the tail of
result_cf(), the undiscounted total, and every check_*() cells. Its output is ASCII
only, so it prints on a Windows console under any code page: amounts are written “JPY” and
Japanese terms are romanized.
Three lines to the same thing:
import modelx as mx
model = mx.read_model("products/individual_annuity/Annuity_JP_S")
model.Projection[1].result_cf()
Projection takes a point_id; Projection[1] is the anchor cell of the notes’ worked
example. result_cf() returns a DataFrame indexed by the policy month t with
pols_if first and net_cf last. result_pols() publishes the decrement and value runs beside it — the
two in-force measures, the decrements that move them (each rate in both its annual and its
monthly form), and the fund, death benefit and surrender value that price them — because on
this product the relationship between those three values is the product, and printing them
only inside a cash flow is not enough. Its value columns are named av_at_m, db_at_m and
cv_at_m for the cells that produce them: they are monthly readings, and naming them
after the anniversary quantities they equal on one row in twelve would be wrong on the other
eleven.
Three clocks: the month t, the anniversary s, the elapsed month u#
t is 0-based and counts policy months, the library-wide convention: t = 0 is the
first policy month and month t runs from time t to time t + 1. proj_len() is the
number of projected months and the exclusive end of the frame, so both result frames run
t = 0 … proj_len() - 1 and have proj_len() rows — 540 on the anchor cell,
12 (n_y + k) = 12 × (35 + 10), with the last 年金支払日 at t = 528 and eleven further rows
closing the final policy year. Every sweep in the model is for t in range(proj_len()).
age(t) is x + t // 12, so age(0) is the 契約年齢 and it holds for twelve months;
pols_if(0) is 1. A contractual policy year is the 1-based label
policy_year(t) = 1 + t // 12 and is derived, never indexed by, with duration(t) = t // 12
beside it. The 年金支払開始日 is the month annuitisation_t() = 12 × annuitisation_y().
Beside t runs s, the anniversary index, in years. av_pp, db_pp, cv_pp,
surr_charge_pp, apl_bal, loan_pp and div_acc_pp are time-point values on that
clock — X(s) is the amount at anniversary s, with s = 0 at issue — and none of
their numbers moved when the projection index became a month. That is deliberate: the
保険料積立金 is a net-level-premium recursion at a 予定利率 credited on the 年単位の契約応当日, and the
whole payout phase is bought out of its value at one such date, so restating it monthly
would invent a within-year rule the 算出方法書 does not publish REG-R2 and would move the
年金原資, the 基本年金額 and the published calibration with it.
A value read between anniversaries uses a third index, u, an elapsed month, and the
flows of month t read it at u = t + 1: av_at_m(u), db_at_m(u), surr_charge_at_m(u),
cv_at_m(u), db_net_at_m(u) and cv_net_at_m(u). So the death and surrender claims of
month t are struck on db_net_at_m(t + 1) and cv_net_at_m(t + 1). At u = 12s each
reproduces its anniversary cells exactly, which is what makes the two grids agree wherever
they are both defined.
av_at_m is a declared linear interpolation std; db_at_m is not an interpolation
at all but the contract’s own clause, ρ (P/12) min(u, 12m) — 月払保険料 × 経過月数 [S2] [S4] —
which the annual grid could only approximate as the same schedule sampled at anniversaries.
That distinction is the point of keeping the two families separate: where the contract
publishes a within-year rule the model states it, and where it does not the model declares a
standardization.
On the 保証期間付終身年金 form proj_len() is 12 (ω − x) + 1: the frame stops at the first
month of the year the annuitant attains the payout table’s terminal age, where the table’s
q is 1 and so is its monthly equivalent.
What the conversion changed, and what it did not#
Survivorship at the anniversaries did not change: mort_rate and lapse_rate are the annual
rates the input tables carry, and mort_rate_mth and lapse_rate_mth convert them on the
effective convention r_m = 1 - (1 - r)^(1/12), so twelve months compound back to the annual
rate exactly and pols_if(12j) reproduces the annual model’s pols_if(j). Neither did the
premium income or the annuity instalments, both being annual and falling at the same
anniversaries. What changed:
result_cf()is a sawtooth. The 年払 premium falls in the monthst = 0, 12, …and the 年金年額 instalment att = n, n + 12, …, each zero in the eleven months between, with maintenance expense and — in deferral — death and surrender running every month. That is the shape of a 定額個人年金保険 and the annual grid could not draw it.The death benefit is the contract’s own monthly clause, not an annual approximation of it, so a death in the seventh month of a policy year is paid seven months’ premium.
Exits are valued at the month they happen.
cv_at_mprices a surrender between anniversaries instead of rounding it to a year end — which shows immediately at the anchor cell, where the surrender value is nil for the whole of policy year 1.The crossover has a month. The fund first exceeds the death benefit at the elapsed month 149, five months into policy year 13, where the annual grid could say only “at the anniversary 13”.
The product is two contracts joined at one date#
Before the annuity commencement date (nenkin shiharai kaishi bi, 年金支払開始日) the liability is a savings fund: a level office premium net of the 予定事業費率 accumulates at the deferral 予定利率 with a survivorship release, against a 死亡給付金 capped at cumulative premiums and a 解約返戻金 capped at the 死亡給付金. After that date the liability is a stream of instalments that does not depend on survival at all.
Everything switches at t = n, and the model puts each switch in exactly one cells:
What switches |
Where it lives |
|---|---|
The mortality table, 死亡保険用 to 年金開始後用 |
|
The best-estimate factor, 0.85 to 1.10 |
|
The availability of surrender |
|
The in-force rule, decrementing to unconditional |
|
The expense level, ¥4,000 to ¥2,000 p.a. |
|
n is read off the model point’s own 年金支払開始日 rather than summed from the 保険料払込期間 and
the 据置期間, and annuity_start_age() raises unless the two agree. That check has to sit on
the path every projection takes: reached only through tax_rider() it would validate the
base form’s model points never, and two spellings of one date is how a projection silently
annuitises on the wrong year.
Two in-force measures are carried, following SPIA_US_S. pols_if counts contracts with
an obligation open; lives_if counts annuitants alive. They separate in the deferral
phase because lapse removes a contract without removing a life, and in the payout phase for
the opposite reason: on a 確定年金 the instalments are unconditional, so pols_if is flat
through the certain period while lives_if runs down on the payout table. At the anchor
cell lives_if falls from 0.91268274 to 0.77848987 over the ten payout years — 14.70% of
the annuitants alive at 65 die — without moving a single yen of projected cash flow.
Collapsing the two is the notes’ second pitfall and the single most likely way to build this
product wrongly.
The fund and the surrender value are different quantities#
av_pp is the premium reserve fund (hokenryō tsumitatekin, 保険料積立金); cv_pp is the
解約返戻金. The recursion
V(s+1) = [ (V(s) + NP(s)) (1 + i_d) - q'(x+s) DB(s+1) ] / (1 - q'(x+s))
on the anniversary clock — one step a policy year, unchanged by the monthly grid —
divides by (1 - q') because the premiums of those who die are released to the survivors
net of the death benefit paid. Since DB is capped at cumulative premiums and V is not,
that release turns positive from the duration at which V first exceeds DB — the elapsed
month 149 at the anchor cell, five months into policy year 13 — and the excess of
av_pp over db_pp, ¥791,563.274447 at the anniversary 34, is precisely the survival
benefit the design buys. It is what pays for the annuity.
So the ceiling is applied to cv_pp and never to av_pp. Clipping the fund instead would
pass check_cv_cap() and destroy the 年金原資: annuitising ¥5,400,000 instead of
¥6,261,482.08 buys 13.7% less annuity. check_cv_cap() asserts cv_at_m(u) <= db_at_m(u)
at every deferral month, which is the sourced invariant [S2] [S4] and a stronger
statement than the annual grid could make, because between anniversaries the two sides move
on different clocks — DB by one 月払保険料 a month, CV by interpolation. The crossover is
where that residual reaches zero and stays there.
The recursion uses mort_rate_pricing, the 予定死亡率 at 100% of the 死亡保険用 table, and never
mort_rate, the best-estimate decrement — av_pp is a contractual quantity and not an
experience projection. Lapse does not appear in it at all: the surrender release is the
解約控除, which accrues to the insurer rather than to the surviving fund. check_fund()
catches both of the ways this recursion is usually built wrongly, since a model that had put
lapse into it, or that had used the best-estimate rate in place of q', fails there rather
than silently misstating the 年金原資.
The annuitisation transition#
At the month t = n — the anniversary n_y — three things happen in one step, and on this
grid the row before and the row after are one month apart rather than one year: the
年金原資 F = V(n_y) is struck net of any loan
balance; the 基本年金額 B is derived from it once and never recomputed; and the mortality
table and its best-estimate factor both switch. B is rounded down to the nearest ¥100
inside the model, because Japanese specimens are published at that granularity and it is a
contractual amount rather than a display convention [S3] [S5] [S6] [S10] — worth ¥15.281 a
year of annuity at the anchor cell, given up rather than rounded away on the screen.
The conversion rate is i_p = 0.65%, not the deferral rate: the payout phase is priced on
its own 予定利率, published separately and left unchanged when that carrier’s deferral rates
moved [S5]. Using i_d to buy the annuity overstates B by 1.55% at k = 10 — in the
direction a reader would not guess, since the payout rate is the lower one, so each yen of
年金原資 buys less annuity, not more.
Two mortality tables, and why the model ships anchors rather than coefficients#
生保標準生命表2018(死亡保険用)and 生保標準生命表2007(年金開始後用)are published by 日本アクチュアリー会 at stable public
URLs, free and in full — a real contrast with the CMI tables uklib cannot read at all. But
the publisher’s terms prohibit reproduction and transmission to third parties without
written consent REG-R21, so this library ships no copy of either. What it ships are two
std constructions, built differently because their anchor sets are.
death_cover_2018 is the canonical jplib table: one file, shared by every product in
the library that reads 生保標準生命表2018(死亡保険用), so that a given cell carries the same value and
the same provenance wherever it is shipped. Its anchor rows are rates read from the IAJ table
and quoted under attribution REG-R18; every age between two anchors is graduated
log-linearly in ln q, evaluated in double precision and rounded to five decimal places.
Nothing is extrapolated — both sexes run from an age-0 anchor to a terminal anchor — and both
sexes carry their own sourced anchors, so there is no age setback on this table.
annuity_payout_2007 is a Makeham law mu(x) = A + B c**x, q(x) = 1 - exp(-mu(x)), solved
in closed form from three published male spot rates, with those three reproduced exactly by
construction and the off-anchor residuals stated rather than hidden in technical-notes.md.
Only male spot rates were retrieved for it, so its female rows are the male construction with
a four-year age setback std — the setback the published terminal ages imply, 126
against 122. No number in the worked example depends on it, the anchor cell being male.
The implementation ships the anchors, not the Makeham coefficients the notes display.
Those coefficients are rounded for print and the payout factors are not reproducible from
them. mort_anchor_table.csv therefore carries, per table and sex, the anchor ages and rates
and the terminal age; mort_table.csv carries the rate the stated graduation produces at
every age, with a provenance column marking each row as a sourced anchor or a graduated
value; and check_mort_graduation() asserts that the two files still agree — log-linear on
死亡保険用, Makeham on 年金開始後用. Once a licensed or company table is dropped in, a False there
is the correct answer, which is why it reports a residual rather than raising.
One caution for a reader holding the notes: the 49% and 89% by which the death-cover table
overstates payout-phase mortality at ages 80 and 90 are comparisons of the two published
tables R3 REG-R18. The death-cover halves of that comparison are sourced anchors and come
back exactly from mort_table.csv; the payout halves do not, because that table is anchored
only at 60/80/100 and reads 0.077578 at age 90 against the published 0.08318, so the model’s
own tables give 49% and 103%. What holds in both is the direction and the materiality, which
is what the pitfall is about.
Inputs are external files#
Seven CSVs live beside run.py, not inside the model folder. This is the
annuallife/TradLife_A layout rather than basiclife/BasicTerm_S’s embedded IOSpec: the
model folder holds __init__.py and _system.json per Space and nothing else, so a diff of
the model shows logic changes only. The trade-off is that the model is not portable on its
own — copying Annuity_JP_S/ without its parent’s CSVs produces a model that reads and then
fails on first evaluation.
File |
Contents |
Provenance |
|---|---|---|
|
Nine model points, indexed by |
Point 1 is the notes’ anchor cell; its premium is the annualization of a published specimen at the identical model point [S6] |
|
The two std mortality tables, by table, sex and age |
死亡保険用 is the canonical library-wide table, log-linear between sourced anchors; 年金開始後用 is a Makeham construction. Not a copy of any 日本アクチュアリー会 file REG-R18 R3 REG-R19 |
|
The published rates each construction is anchored to, and the table’s terminal age |
Quoted rates REG-R18 R3 REG-R19; both sexes sourced on 死亡保険用, and the 年金開始後用 female rows a four-year age setback std |
|
The std 解約・失効 curve, in three phase segments |
Anchored to a market-wide 3.4% for FY2024 R15 REG-R31; the duration shape is a standardization |
|
The two 予定利率, |
[S8] [S5] [S11] [S4] and std where no document discloses the value |
|
Best-estimate cash expenses and commission |
[std, new here] throughout |
|
The 年金の一括払 factors for 1–14 remaining instalments |
One carrier’s published table, verbatim [S2] |
How each time-like input column is read. The model’s t is a 0-based month, while
every time-like input column is in years — and none of them was re-keyed when the grid
changed, because each is either a duration curve, an anniversary, or an elapsed count, and
all three are properly annual. Every such column in the input set:
File |
Column |
Read as |
Values |
|---|---|---|---|
|
|
A 0-based count of completed policy years, the first duration each rate applies from — |
0, 1, 2, 3, 10 on |
|
|
An anniversary: the 契約者貸付 is drawn at the start of the twenty-first policy year, the month |
20, so |
|
|
An elapsed count of years, the length of the 解約控除 run-off, not a point on the frame |
10, giving |
|
|
A count of remaining instalments |
1 … 14, the published table’s own range |
|
|
An attained age, reached through |
the tables’ own age ranges |
|
|
Elapsed counts of years — |
as issued |
|
|
An age, from which |
as issued |
No input column is a 1-based policy-year label, and no input column was re-keyed when the projection went monthly: the rates in them are annual observations and the durations in them are contractual anniversaries, so the model derives months from them rather than restating them.
Every assumption row carries a provenance column tagging it [std] … or with the source
it came from. The readers and every *_file Reference live on the Data Space, which takes
no parameters, so each file is read once per model however many model points are
projected. Data.input_dir() resolves to _model.path.parent at run time and is never
hard-coded, which is what lets a licensed table drop in as a same-schema CSV with no formula
change.
Modules that are off in the base run#
Six of the notes’ optional constructions are implemented and switched off at the anchor cell, so that the base run reproduces the worked example while the machinery stays visible and testable. Each is a model point column, so a non-anchor point exercises it.
Module |
Column |
Off value |
On at |
|---|---|---|---|
保証期間付終身年金 life-annuity election |
|
|
points 4 and 9 |
年金の一括払 commutation |
|
0.0 |
point 5, at 100% |
APL (自動振替貸付) |
|
false |
point 7 |
契約者貸付 policy loan |
|
false |
point 8 |
契約者配当 dividend |
|
0.000 |
point 8, at 0.2% |
Dynamic lapse |
|
0.0100 (= |
point 8, at 1.50% |
Three of them are worth stating in more than a table row.
保証期間付終身年金 is priced on a basis no model can know. The election is made at the
年金支払開始日 on the 基礎率 then in force, thirty-five years out at the anchor cell [S2] [S9].
Holding it at the issue basis is a std modeling choice and the reason base-run take-up
is zero rather than a guess. The module also changes proj_len(), which runs to the payout
table’s terminal age instead of n + k, and pols_if(), which is flat through the
guarantee and then runs off on the best-estimate payout basis. Model point 9 is the anchor
cell with nothing changed but the payout form, so the two B figures are directly
comparable: ¥281,300 against ¥638,100 out of the same ¥6,261,482.08, because the annuity-due
factor is 22.032668 against 9.714338. That ratio is the product fact the module exists to
show.
年金の一括払 switches on a composite artefact, not a product feature. The published factors
imply about 0.40% p.a. while the composite’s payout 予定利率 is 0.65%, and the two come from
different carriers [S2] [S5]. At t = n with ten instalments remaining the factor 9.921
returns ¥6,330,590.10 against a gross 年金原資 of ¥6,261,482.08, 1.1037% more. Nor is the table
an annuity-due at any positive rate: a single remaining instalment is factored at 1.010 and
two at 2.016, which would need v > 1. The model therefore uses the table verbatim over
1–14 and the 0.40% annuity-due only outside it, exactly as the notes prescribe, and leaves
base take-up at zero. A production model must re-derive the factors on its own payout basis.
自動振替貸付 is an election, not a no-lapse rule. With apl_on, the lapse decrement is
suppressed only while the 解約返戻金 is at least one premium and the outstanding balance has not
outgrown it; the balance compounds at the contractual cap of 8% p.a. [S4]; and the moment
principal and interest exceed the 解約返戻金 the whole in-force lapses. On point 7 the module engages at the premium anniversary 2, carries the contract for six
policy years, and terminates the whole in-force at the anniversary 8 — the month t = 96 —
where principal and interest outgrow the 解約返戻金. The test is made once a policy year, at the
premium date, because that is the only date on which a premium can go unpaid, and the balance
compounds annually for the same reason. Wiring the module on by default would remove lapse
from the model for the wrong reason — and one carrier’s product has no such facility at all
[S2].
Semantics the notes do not fix#
Four of the modules needed a reading the notes leave open. Each is resolved here in the formula’s docstring as well as in this list, because a reader who disagrees needs to find the choice, not infer it.
The APL premium is lent, not received. While the facility is running,
premiums(t)is zero in the premium months: the insurer lends the premium rather than collecting it std. And the 保険料積立金 recursion still creditsNP(s)in the policy year the facility fails, which is worth one year of fund accretion and follows from the fund’s annual construction rather than from the projection grid.The 契約者貸付 drawdown rule. Half the 解約返戻金 drawn at
t= 20 std, compounding at the sourced 2.40% [S11] [S8] and capped at the 解約返戻金. Both parameters are rows inpricing_table.csvand neither is sourced as a behaviour.The 契約者配当 declaration rule. The composite is a 5年ごと利差配当 design [S4]; the model declares
div_rateon the fund annually and accumulates it at the sourced 0.60% [S11], which is a std simplification of the five-year cycle. Zero in the base run, so no shipped figure depends on it — and under the 税制適格特約 the accumulation may never be paid in cash, so it appears inannuity_amount_pp()and nowhere in the cash flow [S1] R10.Dynamic lapse has no premium-shock driver. Premiums and the 予定利率 are both fixed at issue, so the multiplier keys off a rise in the new-business 予定利率 instead [S8]:
M(t) = min(2, max(1, 1 + 20 max(0, i_new - i_d)))std.
減額, 払済 and 復活 are not implemented [std scope]. A premium unpaid in a premium month
terminates the contract in that month: there is no 払込猶予期間 state and no reinstatement
re-entry. The monthly grid removed the arithmetic obstacle to modelling both — a grace window
of one or two months is now a representable length — and left the data obstacle, since grace
runs from a calendar 払込期月 the model point table carries no date for, and no retrieved
document gives a reinstatement rate. This model’s lapse_rate is therefore still a
net-of-復活 rate by construction, and a user substituting a gross experience rate will
over-decrement.
Sign convention#
The notes print CF(t) income positive, which is the library-wide sign of net_cf, so
this model publishes no liability_cf cells — that absence is a fact about which
orientation the notes chose, not an omission. A reader comparing the payout years with
SPIA_US_S, whose notes print outgo-positive, must flip the sign: this model’s payout rows
are eleven small negatives and one large one, twelve times over. The anchor cell runs
+¥77,665.70 at t = 0, has its first negative twelve-month block in policy year 29, and
sums undiscounted to −¥461,523.46.
Naming#
Cells names follow lifelib and the library’s settled vocabulary. pols_if(t) is the count
at the start of period t and is the weight on that same result_cf() row; pols_if_at(t, timing) gives the within-year reads and av_pp_at(t, timing) does the same for the fund;
prem_to_av_pp is the premium credited to it; claims(t, kind) produces
claims_annuity, claims_death, claims_lapse and claims_commutation, each named for
the kind that produces it; mort_rate and lapse_rate carry the annual rate applying
in month t, because that is what the input tables observe, and mort_rate_mth and
lapse_rate_mth are the monthly decrements derived from them — the library-wide _mth
suffix for exactly that conversion. The full mapping from the notes’ actuarial symbols to the cells names is
the table in the Projection docstring, headed Notes symbol. Twelve cases needed care:
Notes |
Cells |
Why |
|---|---|---|
|
|
Two quantities that are routinely collapsed into one “policy value”: the fund runs past the death benefit, the surrender value is capped at it. The library’s ruling puts the surrender quantity under |
|
|
The 予定死亡率 inside the fund and the best-estimate decrement on the in-force are different numbers in every year, only the first is contractual, and the two sit on different clocks — the fund’s is an anniversary and the decrement’s a month |
|
|
The anniversary schedule the fund recursion reads, and the contract’s own 月払保険料 × 経過月数 that a death between anniversaries is actually paid. They agree at every anniversary; between them only the second is the contract |
|
|
The |
|
|
Contracts with an obligation open against annuitants alive. On a 確定年金 they come apart completely, and the second weights nothing |
|
|
The applied rate, the table rate and the dynamic multiplier are three cells because the applied rate is also what 自動振替貸付 overrides — in both directions |
|
|
The amount struck once at |
|
|
Not |
|
|
|
(no symbol) |
|
The library-wide count whose cover ends at the scheduled end of the contract, whether or not anything is paid for reaching it — the meaning |
(annuity stream) |
|
Named for the benefit’s form — a stream rather than a lump sum — so the name alone does not say which contingency pays it. Here it is a living benefit, paid on the annuitant surviving to a payment date; the same column is a death benefit in IncomeTerm_JP_S and a living benefit in LTC_JP_S. The |
(APL balance) |
|
The 自動振替貸付 balance per policy, on the anniversary clock. The same concept as the savings chassis’s |
(table lookup) |
|
The library-wide split: |
(identities) |
|
The settled name for a per-step in-force roll-forward in both sister libraries, matching |
Three absences are product facts rather than gaps: there is no premium income after 払込満了 and
none at all once the annuity is in payment; there is no lapse decrement from the month
12 (n_y - 1) and no surrender value from t = n; and there is no maturity payment,
because the contract does not
mature into a lump sum — it pays its last instalment and ends. pols_maturity counts the
contracts reaching that scheduled end; claims(t, "MATURITY") does not exist.
Standardizations used#
Every quantitative parameter is either source-tagged in a CSV provenance column or marked
[std] there. The ones that move the answer most:
β= 6.5% andθ= 1.0% — one deferral loading and one payout loading, rather than an invented 新契約費 / 維持費 / 集金費 split that no source can confirm; the 算出方法書 is a 基礎書類 filed with the FSA and not published REG-R2. Calibrated at a single model point against a published specimen [S6], where they reproduce that carrier’s 年金原資 of approximately ¥6,260,000 as ¥6,261,482 and its 基本年金額 of ¥638,300 as ¥638,100, −0.031%. A production user should re-fit across all six of the specimen’s points rather than inherit a one-point calibration.The two mortality constructions and their best-estimate factors, 0.85 in deferral and 1.10 in payment. The direction of each is structural — one table is prudent against death and the other against longevity — while the size of 1.10 sits on an unverified margin, because the 作成概要 for the 2007 年金開始後用 table was not retrieved.
The 解約控除: one annual premium running off linearly over ten policy years. Both 約款 state the shape and not the parameters [S2] [S4]. The base amount is what makes the sourced invariant hold — at the anchor cell
cv_pp(1)is ¥7,976.18 against ¥180,000 of premium paid, nil-or-negligible as both 約款 require, whilecv_pp(0)is zero.The lapse curve. The only public figure is a market-wide 3.4% for FY2024 whose denominator is pre-annuitisation in-force 契約高, not policy count R15 REG-R31. On the anchor cell the shipped curve averages 3.4160% weighted by
pols_ifand 2.4754% weighted byav_pp, both read at the anniversaries of the deferral phase, since what they summarize is the annual rate curve in the file. The two weightings are not interchangeable and a calibration must say which one it used, which is whylapse_rate_mean(weighting)is a published cells and not a comment.The module semantics listed above — the lent APL premium, the loan drawdown rule, the annual dividend declaration and the dynamic-lapse driver. None of them moves a shipped figure, because all four are off in the base run; all four move a model point that is shipped, which is why they are parameters in
pricing_table.csvand not constants.
Tests#
tests/test_model_conventions_jp.py asserts the house style for every model in the library:
the folder layout, the Data / Projection split, the read-once property, the docstring
contract, the naming rules, result_cf()’s column conventions, that every model point
projects without NaN, and that read → write → re-read reproduces the same file set and the
same numbers. It also asserts the library-wide frame rule on every model point: the
index is named t, opens at a non-negative index, runs contiguously to proj_len() - 1
inclusive, and len(result_cf()) == proj_len() for a point projected from issue — which is
every point of this model.
tests/test_individual_annuity_jp.py asserts what this product owes on top of that. The
notes’ worked example is hard-coded there — the annuitisation quantities (F =
¥6,261,482.075674, ä(10, 0.65%) = 9.71433757, B raw ¥638,115.281 rounding to ¥638,100,
一括受取率 115.9534%, 年金受取率 118.1667% — none of which moved when the grid did), the deferral
rows, the anniversary value table and its elapsed-month readings, the crossover month, the
last deferral month, the payout rows and the eleven empty months after each instalment, the
month-by-month traces and both totals — so that a reviewer can check it against the notes by
eye. The conversion itself is asserted directly: that (1 - r_m)^12 = 1 - r on both
decrements, that pols_if(12j) reproduces the annual model’s pols_if(j), and that every
*_at_m(12s) returns its anniversary cells exactly. Every pitfall the notes
list earns a test named after it: the two tables and their opposite margins, instalments
that are certain rather than life-contingent, the cap that binds on cv_at_m and not on
av_at_m, the lapse decrement that stops a policy year before the 年金支払開始日, 払込満了 against
the 年金支払開始日, the two
予定利率, the death benefit that stops growing, the commutation factors that are not the payout
basis, the lapse rate whose denominator is 契約高, dividends that are zero rather than absent,
the APL that is an election, and the 基本年金額 that is struck once. Each of the six optional
modules is asserted in both positions, off and on, and the structural facts — no tail
states, a horizon that is the payout table’s terminal age on the life form, a zero 据置期間, the
0.70 tontine ratio, the payout table’s female setback, and the model points the model rejects
by name — are
asserted too. The frame itself is pinned there as well: result_cf() is indexed by t,
runs list(range(proj_len())) = 0 … 539 on the anchor cell, and ends on proj_len() - 1.
Seven check_*() cells assert the identities the notes imply, each taking no argument and
returning a bool, with the signed residual at check_*_resid. Five run on months and
two on anniversaries, because that is the clock each identity lives on: the in-force,
lives, surrender-cap, annuity-total and ledger identities are statements about the
projection, while the fund recursion and the mortality graduation are statements about the
annual construction underneath it. All seven return True on all nine shipped model points.
Check |
Identity |
|---|---|
|
|
|
|
|
|
|
|
|
the undiscounted guaranteed instalments sum to |
|
the published |
|
the shipped rates are still the graduation of the quoted anchors |
python -m pytest tests -q