Tier 2 · Pricing

Traditional Life Pricing

M-071 · lifecycle: monitoring · RAT-071-v1.0.1

Intended Use

Traditional Life Pricing Price traditional life products (term, whole life, universal life) for new business.

Pricing harness for traditional life insurance products. Distinct from annuity suite by methodology (premium-driven vs. account-value mechanics).


Components

Inputs, processing, outputs

data sources
DS-001 · DS-008
assumptions
A-001, A-002, A-010, A-070, A-071
engines
insmodel.L4.traditional_life
insmodel.L4.mortality_engine
insmodel.L4.lapse_engine
insmodel.L4.investment_engine
insmodel.L4.expense_engine
insmodel.L4.reserve_engine_ldti
contracts
pricing_results_v1
dimensions
D6

Methodology & Mechanics

Methodology

M-071 is the traditional life pricing model (TraditionalLifeModel, engine id insmodel.L4.traditional_life, internal P-006). A single engine prices both Term Life and Whole Life off one parameter, product_subtype, because the two share an intended use — new-business premium adequacy and profit testing for mortality-driven protection products — and differ only in cash-value mechanics. It composes five engines declared in get_required_engines(): MortalityEngine (death cost), LapseEngine (surrender/lapse decrement), InvestmentEngine (portfolio yield on reserves), ExpenseEngine (operating cost), and ReserveEngine (GAAP FPB reserve).

calculate() projects a cohort monthly. For each month it derives the attained age and policy year, then:

  1. Mortality. Annual qx is built from a base rate, an age slope, and a multiplicative experience factor (_get_mortality_rate()), clipped to [0.0001, 1.0], then converted to a monthly decrement 1 − (1 − qx)^(1/12). Deaths drive death_benefit = deaths · face.
  2. Lapse. Annual lapse comes from a duration-graded base curve (_get_lapse_rate()). Term carries a shock lapse at the end of the level term (the renewal cliff) and an elevated post-term lapse (3× base, capped at 25%); Whole Life lapse is halved (cash-value retention). Converted to a monthly decrement.
  3. Premium. premium_inflow = in_force · annual_premium / 12 — level premium, the pricing lever the model solves around.
  4. Investment income. reserve · monthly_yield, where monthly_yield = (1 + portfolio_yield)^(1/12) − 1.
  5. Whole-Life-only cash mechanics. Cash surrender value (_get_cash_surrender_value(), linear growth from csv_start_year, capped at 95% of face) drives surrender outflow, policy-loan income (loan_balance · loan_rate / 12), and dividends (csv · dividend_rate / 12). Term sets all of these to zero.
  6. Expense. Per-policy maintenance (annualised /12) plus a percent-of-premium load.
  7. Net income and reserve roll-forward. net_income = premium + investment_income + loan_income − death_benefit − surrender − dividend − expense; the reserve rolls forward by the same flow and is floored at zero. DAC is set up at issue as in_force · annual_premium · dac_rate and amortised straight-line over dac_amortization_years.

In-force is decremented each month by deaths + lapses; projection stops at the term horizon (Term: term_years + 2) or when in-force falls below 1.

Profit testing (compute_profitability()) re-runs calculate() on a fresh pricing cohort and computes: PVFP (monthly net income discounted at the risk-adjusted rate), profit margin (PVFP / initial annual premium volume), an IRR approximation (total_profit / premium_volume / years — not a true cash-flow IRR solve), return on capital (PVFP over a capital charge of 3% of face for Term, 5% for Whole), breakeven year (first month cumulative net income turns positive), and loss ratio (total death benefits / total premiums). get_cash_flows() re-runs the projection across multiple interest-rate scenarios by averaging each scenario's yield.


Key Assumptions

Key Assumptions and Their Justification

The load-bearing assumptions for the canonical Term run. All are engine parameters with defaults in PRODUCT_CONTRACT["parameters"]; the snapshot uses the values asserted by tests/test_traditional_life_model.py::make_model.

Param Name Canonical value Derivation Justification
base_mortality_rate Base annual qx at issue 0.002 published_source Issue-age-40 male qx proxy; CSO-table order of magnitude. The model does not load a full CSO table — it uses base + slope (see Limitations).
mortality_age_slope qx increase per year of age 0.00015 assumption Linear age grading; a simplification of the convex CSO age curve.
mortality_experience_factor Experience multiplier on base qx 0.80 data_calibrated Underwriting selection: insured lives run below table; ASOP 25 credibility framing.
base_lapse_curve Annual lapse by policy year 8% Y1 → 2% Y10+ data_calibrated Duration-graded decay consistent with VM-20 §6 guardrails; early-duration lapse highest.
shock_lapse_rate Lapse at end of level term 30% data_calibrated The renewal cliff: premiums jump at term end, triggering mass lapse. First-order Term pricing risk.
portfolio_yield General-account yield on reserves 5% market Earned rate on backing assets; feeds investment income.
annual_premium Level gross premium $1,200 (Term) product_design The pricing lever; profit testing measures adequacy of this premium against mortality + expense + margin.
expense_per_policy / expense_pct_premium Maintenance + premium-load expense $75/policy + 8% of premium data_calibrated Industry expense-study range; per-policy plus acquisition-style load.
dac_rate / dac_amortization_years DAC setup and amortisation 50% of FY premium · 10 yr SL accounting Deferred acquisition cost; straight-line amortisation is a GAAP simplification (not interest-accreted EGP-based).
csv_growth_rate / dividend_rate / policy_loan_rate Whole-Life cash mechanics 3% · 2% · 5% product_design Engaged only for WHOLE_LIFE; Term zeroes them.

Operational notes. Mortality and lapse are the two behavioural drivers that move pricing: mortality sets the benefit cost, lapse sets how long premium and reserve persist. The crediting/cash-value parameters are product-design inputs (contractual), not calibrated experience. The reserve is not a statutory or VM-20 reserve — it is a simplified roll-forward seeded at in_force · face · 0.01 · duration and is a pricing artefact, not a held reserve (M-001 owns VM-20 statutory reserves).


Output Snapshot

Output Snapshot

Deterministic single-cohort run of TraditionalLifeModel (P-006) in its TERM configuration — reproducible, requires no live firm data (python scripts/model_snapshots.py M-071 in InsModel; mechanics asserted by tests/test_traditional_life_model.py, 32 tests passing). The provider is a MagicMock.

Input: issue age 40 · Male · face $500,000 · level premium $1,200/yr · 20-yr term · base qx 0.002 · experience factor 0.80 · base lapse 8% Y1 → 2% · shock lapse 30% · portfolio yield 5% · 10,000 in-force · profit test at discount 6% over 240 months. Cash-flow rows shown are month 1; pricing metrics are from compute_profitability().

output value meaning
product_subtype TERM pure protection, no cash value
gross_annual_premium 1,200.00 level premium per policy (the pricing lever)
month1_premium_inflow 1,000,000.00 10,000 in-force · $1,200 / 12
month1_death_benefit 717,232.26 deaths · $500,000 face, month 1
month1_investment_income 203,706.19 yield on the (seeded) reserve
month1_expense 142,500.00 per-policy maintenance + 8% premium load
month1_net_income 343,973.93 premium + NII − benefits − expense
driver_mortality_annual_qx 0.00172 annual qx at attained age 41 (base+slope)·0.80
driver_lapse_annual_y1 0.0800 year-1 annual lapse from the base curve
pvfp_at6pct 19,327,930.17 present value of future profit, 240mo @ 6%
profit_margin 1.61 PVFP / initial annual premium volume
irr_approx 0.09 approximate IRR (not a true cash-flow solve)
return_on_capital 0.13 PVFP / (3% of face · in-force) capital charge
breakeven_year 1 cumulative net income turns positive in year 1
loss_ratio 1.14 total death benefits / total premiums

The two pricing drivers are visible directly: annual qx of 0.00172 (the benefit cost) and year-1 lapse of 8% (the persistency assumption). The PVFP and margin are pricing diagnostics from compute_profitability(), not statutory profit. Two numbers deserve honest reading. First, loss ratio 1.14 (>1): total death benefits exceed total premiums over the horizon, which on a standalone premium basis would be inadequate — yet net income is positive because the model credits investment income on a large seeded reserve ($50M at issue = 10,000 · 500,000 · 0.01 · 1). Second, profit margin 1.61 is implausibly high for term pricing and is a direct artefact of that reserve seeding inflating investment income relative to a deliberately low illustrative premium. These figures are internally consistent engine outputs on the documented canonical inputs; they are not calibrated to a real product's priced premium and should be read as mechanics demonstrations, not a profit claim. See Limitations 1, 3, and 5.

Captured 2026-06-04 · deterministic, no live data.


Limitations

Limitations and Known Gaps

  1. Reserve is a pricing artefact, not a statutory reserve. The reserve is seeded at in_force · face · 0.01 · duration and rolled forward by net cash flow, floored at zero. It is neither a VM-20 net-premium/deterministic reserve (M-001) nor a GAAP FPB reserve (M-020), despite ReserveEngine being a declared dependency. The seeded magnitude drives investment income and therefore inflates PVFP and profit margin — the snapshot's margin of 1.61 is a direct consequence and must not be read as a profit claim.

  2. Mortality is base-plus-linear-slope, not a CSO table. _get_mortality_rate() applies (base_rate + age·slope)·experience with a single linear slope, clipped to [0.0001, 1.0]. It does not load the 2017 CSO select-and-ultimate structure, has no gender split beyond carrying a gender parameter that does not alter qx, and no smoker/non-smoker distinction. Acceptable as a pricing illustration; insufficient for a priced mortality basis.

  3. Premium is an input, not solved. The model profit-tests a given level premium; it does not solve for the premium that hits a target margin or IRR. "Premium adequacy" is assessed only by inspecting the resulting loss ratio / PVFP. The canonical $1,200 premium on $500K face produces loss ratio > 1, illustrating exactly this gap — the harness reports inadequacy rather than correcting it.

  4. Profitability metrics are approximations. IRR is total_profit / premium_volume / years, not a cash-flow IRR solve; return-on-capital uses a flat factor (3% face Term / 5% Whole) rather than a risk-based capital model; breakeven is read off cumulative undiscounted net income. Directional, not decision-grade.

  5. Deterministic, single-path projection. No stochastic mortality, no dynamic (moneyness/rate-dependent) lapse, no stochastic interest. get_cash_flows() only averages each scenario's yield to a scalar — it does not run a genuine multi-path projection.

  6. DAC is straight-line, not EGP-amortised. DAC amortises on a fixed straight-line schedule rather than in proportion to estimated gross profits/margins, diverging from LDTI/ ASC 944 acquisition-cost mechanics.

  7. Whole-Life cash mechanics are linear approximations. CSV grows linearly from csv_start_year (capped at 95% of face); dividends and policy-loan income are flat-rate functions of CSV. No guaranteed nonforfeiture-law minimum test, no dividend-scale interest crediting, no policy-loan utilisation dynamics.

  8. No live firm-data path (BV-032). All figures derive from documented canonical inputs and a MagicMock provider. Nothing here is a 10-K-reported or experience-calibrated value; the PRU calibration notes in the source docstring are illustrative targets, not validated outputs.

Tracked for ratification. Remediation status of the items noted in earlier passes: the tier-2 sensitivity suite on the top-3 load-bearing assumptions (mortality experience/base+slope, base lapse curve, shock lapse rate) is now DONE (tests/test_traditional_life_sensitivity.py, 13 tests; COND-071-02). The registry-vs-code engine-binding divergence is RESOLVED (COND-071-01; see Components). Still open: a Whole-Life-archetype deterministic snapshot (only TERM is snapshotted today); premium-adequacy / margin calibration (the engine profit-tests a supplied premium but has no premium-solver, and the canonical inputs produce loss ratio 1.14 / margin 1.61 inflated by a seeded reserve); the reserve-seeding pricing artefact (in_force·face·0.01·duration) that inflates investment income and PVFP; and the mortality basis itself (base+linear-slope clipped, not a CSO select-and-ultimate table; gender carried but inert; no smoker split; get_cash_flows() averages each scenario's yield to a scalar rather than running multi-path projection). None of these were changed in this pass.


Validation Evidence

Validation Packet

evidence status reference
Unit/behaviour test suite present tests/test_traditional_life_model.py — 32 tests passing (mortality, lapse decrement, shock lapse at renewal, CSV monotonicity, dividends-after-CSV, policy-loan income, premium inflow, DAC amortisation, profitability, termsheet Term vs Whole, multi-scenario).
Deterministic snapshot present scripts/model_snapshots.py M-071 (TERM configuration, this card).
No-lapse boundary invariant present test_no_lapse_boundary — zero lapse ⇒ surrender outflow 0, in-force declines from mortality only.
Shock-lapse invariant present test_shock_lapse_at_renewal — lapse at term_years equals the shock rate, exceeds normal-year lapse.
Whole-Life CSV monotonicity present test_whole_life_csv_monotonic — CSV non-decreasing in duration.
SR 11-7 governance check present test_governance_compliancecheck_product_compliance(P-006) passes; test_model_card_generation emits required sections.
Validation-evidence pack present modelling/validation_evidence/M-071/v1.0.0/README.md — Tier-2 scoped pack indexing conceptual soundness, behaviour suite, the COND-071-02 sensitivity suite, and the deterministic snapshot, each pointing at a reproducible source.
Whole-Life-archetype snapshot absent Only the TERM archetype is snapshotted; Whole Life is exercised by tests but not independently snapshotted here.
Independent challenge (2L) pending re-review Peer-permitted for tier-2. COND-071-01 (binding reconciled) and COND-071-02 (sensitivity suite) are remediated; the independent-challenge sign-off is 2L's to record on re-review (registry lifecycle.peer_review.status stays pending until 2L supersedes RAT-071-v1.0.0 with v1.0.1).
Sensitivity suite present Top-3 load-bearing assumptions sensitivity-tested: tests/test_traditional_life_sensitivity.py — 13 tests (mortality base+slope+experience, base lapse curve, shock lapse), shock-direction + bounded-magnitude, modelled on InsModel test_vm20_sensitivity.py. Indexed in modelling/validation_evidence/M-071/v1.0.0/README.md §3 (closes COND-071-02).
Back-test pending No realized-outcome reconciliation — BV-032 means no live-data path; needs a firm-calibrated outcome path before a backtest record can be opened.
Next revalidation scheduled 2028-05-01 (biennial, tier-2 — registry lifecycle.next_revalidation_due). last_validated_on: null (not yet validated).
Premium-adequacy / margin calibration absent No premium solver and no calibration of the canonical premium to a priced product (Limitations 1, 3).

References

References

Engine source: - ecosystem/InsModel/Models/firmmodel/products/traditional_life_model.pyTraditionalLifeModel (P-006), engine id insmodel.L4.traditional_life. - firmmodel/products/base_product.py — shared BaseProductModel contract (calculate, compute_profitability, get_termsheet, validate_input). - firmmodel/governance/legacy_metadata/TraditionalLifeModel.yaml — extracted component metadata.

Tests / snapshot: - tests/test_traditional_life_model.py (32 tests). - scripts/model_snapshots.py M-071.

Pricing / profit-testing literature: - ASOP No. 56 — Modeling (intended use, sensitivity, reliance, documentation). - ASOP No. 25 — Credibility Procedures (experience-factor application to base mortality). - ASOP No. 2 — Nonforfeiture and Policyholder Dividends (Whole-Life CSV / dividend context). - Atkinson & Dallas, Life Insurance Products and Finance (SOA) — asset-share / profit-testing methodology and PVFP construction for traditional life pricing. - Black & Skipper, Life Insurance — term vs whole-life premium structure, the renewal shock-lapse cliff, and persistency in pricing. - SOA 2017 Commissioners Standard Ordinary (CSO) Mortality Tables — the published mortality basis the model's base+slope approximates (not loaded; see Limitation 2).

Internal: - M-001 — VM-20 statutory reserves (engine-card depth exemplar; owns the held reserve this model only approximates). - M-070 — annuity pricing suite (sibling pricing card; shared BaseProductModel harness). - BV-032 — firm-data divergence (no live-data path).


Change Log

Change Log

Card change history. Code-side change history lives in git log of the component files.

  • 2026-05-08 — stub created from registry data per Decision 023 Phase 5 / B-07.
  • 2026-06-04 — hand-authored pass: Methodology, Key Assumptions, Output Snapshot, Limitations, Validation Packet, and References authored from traditional_life_model.py + TraditionalLifeModel.yaml legacy_metadata + bound A-NNN entries. Stub marker advanced to ``.
  • 2026-06-06 — code-grounded documentation-accuracy pass: added Standards Coverage (CSO_2017 / ASOP_56 / ASOP_25 / ASC_944-40-LDTI, pointers only) and Dependencies (upstream models: none — registry declares no upstream_models) sections to match the M-001 section shape; reconciled the Components processing-engine roster to get_required_engines() (Mortality/Lapse/Investment/Expense/Reserve — benefit_engine is registry-bound but not returned by the code) with a registry-vs-code divergence note; added honest Validation Packet governance-status rows (2L challenge pending, sensitivity suite pending, back-test pending, next revalidation 2028-05-01) from registry lifecycle; added a tracked-for-ratification note under Limitations. No model outputs, back-test numbers, or validation results were fabricated or changed.
  • 2026-06-06 — RAT-071-v1.0.0 remediation pass (1L, Decision 053 charter §2.2): COND-071-01 (binding) RESOLVEDmodel_registry.yaml components.processing.engines aligned to get_required_engines(): dropped benefit_engine (bound-but-unused), added investment_engine, expense_engine, reserve_engine_ldti (exercised-but-unbound); engine_registry.yaml model_membership lists made bidirectionally consistent; binding manifest regenerated; check_registry_integrity OK. COND-071-02 (sensitivity) DONE — authored tests/test_traditional_life_sensitivity.py (13 tests, shock-direction + bounded- magnitude on mortality / base lapse / shock lapse) and assembled the Tier-2 validation-evidence pack at modelling/validation_evidence/M-071/v1.0.0/README.md. Reconciled documentation_pack flags to disk reality (card/validation/change_log now present). Re-stamped doc-currency (fingerprint eb48b20cf61fbe10, current). COND-071-03 (independent sign-off) is left for 2L on re-review (1L may not record its own sign-off). The BV-032 back-test remains an honest pending item. No model outputs or validation results were fabricated.

2L Inventory Review

Open findings (1)

Independent 2nd-line review (INV-2026-06) — implemented capability vs registered scope. Each carries a recommended fix and is tracked in insightalm-mrm until closed.

HIGH INV-029 · P5 · validation-gap

Validation evidence + change logs missing across most of the inventory

Only M-001/M-020/M-050 carried full documentation packs before this pass. Most models record validation_evidence: missing and change_log: missing with peer_review: pending. Gold tests freeze behaviour but many assert only structural invariants (e.g. reserve>0), not correctness against external truth. The flagship T0-vs-10-K match is circular (BV-032).

Recommendation: For each Tier-1 model: produce a validation-evidence pack (back-test vs disclosed results once BV-032 re-calibration lands, sensitivity suite, challenger comparison), a change log, and a 2L ratification. Sequence behind BV-032 (firm-data) for anything needing 10-K reconciliation.


Validation Coverage

Per-tier expectations

Per MRM Framework §10.2 + §10.3, this model's regulatory_frameworks tag list activates the following overlays:

asop_56 internal
component tier-2 expectation status
Registry entry required present
Model card (§10.5 doc pack) required present
Validation evidence required present
Change log required present
Independent effective challenge (2L) required attested

Ratification

Ratified — RAT-071-v1.0.1

Latest ratification on file: RAT-071-v1.0.1. Authored by 2L (mrm-peer-reviewer) per Decision 028 charter §5 Pattern A.