Individual neural claims reserving — per-claim RBNS reserve estimates with bootstrap uncertainty
Project description
insurance-reserving-neural
Individual neural claims reserving for UK insurance teams. Per-claim RBNS reserve estimates with bootstrap uncertainty, benchmarked against chain-ladder.
The problem
Chain-ladder treats your whole portfolio as one blended triangle. A bodily injury claim that has been through two failed settlement offers and three case estimate revisions gets the same development factor as a straightforward property claim that settled in six months. The method discards everything your claims handlers actually know.
The PRA's 2023 thematic review found this directly: insurers using aggregate triangle methods were systematically failing to capture heterogeneous claims inflation. Bodily injury running at 15% and property at 3% — blended triangles under-reserve one and over-reserve the other.
Individual neural reserving predicts each claim's outstanding liability using its own payment history, case estimate revision pattern, claim type, litigation status, and development stage. Sum the predictions over open claims to get the RBNS reserve. The approach has been in the academic literature since 2022 and has demonstrably better performance metrics than chain-ladder on every dataset tested.
There was no Python library for it. Until now.
What this library does
- Generates realistic synthetic claims data (SPLICE-inspired) for testing and onboarding
- Extracts per-claim features from panel data: payment summaries, case estimate history, development timing
- Trains feedforward neural networks (FNN+) and LSTMs on individual claim development histories
- Applies Duan (1983) smearing bias correction to log-space predictions
- Produces per-claim outstanding predictions and aggregate portfolio RBNS reserves
- Quantifies reserve uncertainty via residual bootstrap (P10/P50/P75/P90/P99.5 for Solvency II)
- Computes an aggregate chain-ladder reserve from the same data for regulatory benchmarking
Installation
pip install insurance-reserving-neural # core (no PyTorch)
pip install insurance-reserving-neural[torch] # with PyTorch for neural models
Quick start
import polars as pl
from insurance_reserving_neural.synthetic import SyntheticClaimsGenerator
from insurance_reserving_neural.models import FNNReserver
from insurance_reserving_neural.metrics import mean_absolute_log_error, ocl_error, chain_ladder_reserve
from insurance_reserving_neural.bootstrap import BootstrapReserver
# Generate synthetic data — replace with your own panel DataFrame
gen = SyntheticClaimsGenerator(n_claims=5000, random_state=42)
df = gen.generate()
train_df = df.filter(pl.col("split") == "train")
test_df = df.filter(pl.col("split") == "test")
# Train FNN+ (feedforward with case estimate features)
model = FNNReserver(use_case_estimates=True, max_epochs=100)
model.fit(train_df)
# Per-claim predictions
preds = model.predict(test_df)
print(f"MALE: {mean_absolute_log_error(preds):.4f}")
print(f"OCLerr: {ocl_error(preds):.4f}")
# Portfolio RBNS reserve
reserve = model.reserve(test_df.filter(pl.col("is_open") == True))
print(f"RBNS reserve: £{reserve:,.0f}")
# Chain-ladder benchmark
from insurance_reserving_neural.data import prepare_features
cl_result = chain_ladder_reserve(prepare_features(df))
print(f"Chain-ladder reserve: £{cl_result['reserve']:,.0f}")
# Bootstrap uncertainty (Solvency II percentiles)
boot = BootstrapReserver(model, n_boot=1000)
boot.fit(train_df)
dist = boot.reserve_distribution(test_df.filter(pl.col("is_open") == True))
print(f"P50: £{dist['P50']:,.0f} P90: £{dist['P90']:,.0f} P99.5: £{dist['P99_5']:,.0f}")
Data format
Your data needs to be in panel format: one row per claim per valuation date. Each row contains the claim's history as of that date.
Required columns:
| Column | Type | Description |
|---|---|---|
claim_id |
str | Unique claim identifier |
accident_date |
date | Date of loss |
valuation_date |
date | As-at date for this observation |
cumulative_paid |
float | Total payments to valuation_date |
case_estimate |
float | Current case reserve at valuation_date |
is_open |
bool | True if claim still open |
ultimate |
float | Final settlement amount (null for open claims) |
Case estimate history columns (needed for FNN+, the recommended model):
| Column | Description |
|---|---|
n_case_revisions |
Number of case estimate changes to date |
case_estimate_mean |
Mean case estimate over revision history |
case_estimate_std |
Std dev of revision history |
case_estimate_trend |
Linear trend of case estimates |
largest_case_revision |
Largest single absolute revision |
prop_upward_revisions |
Fraction of revisions that were upward |
User covariates: any columns starting with feat_ are treated as model inputs. Standard examples: feat_claim_type (int), feat_litigation (0/1), feat_fault (float).
Architecture
FNN+ is the default. Avanzi, Lambrianidis, Taylor, Wong (2025) tested four architectures on SPLICE-simulated data and found that case estimate summaries reduce MALE by 15–25% compared to payment history alone. The LSTM adds only marginal improvement over FNN+ at 4–8× the training cost. For UK portfolios with moderate development tails, FNN+ is the right default.
Model architecture:
Input (features + case estimate summaries)
→ Linear(n_features, 64) → BatchNorm → ReLU → Dropout(0.1)
→ Linear(64, 32) → BatchNorm → ReLU → Dropout(0.1)
→ Linear(32, 16) → BatchNorm → ReLU → Dropout(0.1)
→ Linear(16, 1)
→ log-outstanding prediction
→ exp(ŷ) × Duan smearing factor → outstanding prediction
Loss function: MSE on log(outstanding). Log-space training is not optional — raw MSE on payment amounts is dominated by the top 1% of large claims and produces unstable gradients.
Bias correction: Duan (1983) smearing estimator. E[exp(Y)] ≠ exp(E[Y]) — naive back-transformation understates the mean. The smearing factor b = mean(exp(residuals)) corrects this.
Metrics
MALE (Mean Absolute Log Error): per-claim accuracy in log space. Lower is better. 0.3 is reasonable for a first model; published results on SPLICE data show FNN+ achieving 0.25–0.35.
OCLerr (Outstanding Claims Liability Error): portfolio-level signed bias. (predicted_total - actual_total) / actual_total. What the finance director looks at. Under-reserving (negative) is the regulatory risk.
Chain-ladder reserve: aggregate benchmark computed from the same individual data. Required by PRA SS8/24 for model validation documentation.
Regulatory context
PRA SS8/24 (effective December 2024) states that relying solely on historical triangle extrapolation "is unlikely to satisfy the Directive requirement for a probability-weighted average of future cash-flows." This library provides individual-data methods that directly address this requirement.
The built-in chain-ladder comparison and bootstrap uncertainty quantification produce the evidence an internal model validation team needs for sign-off.
References
- Avanzi, Lambrianidis, Taylor, Wong (2025). On the use of case estimate and transactional payment data in neural networks for individual loss reserving. arXiv:2601.05274
- Richman, Wüthrich (2026). From Chain-Ladder to Individual Claims Reserving. arXiv:2602.15385
- Schneider, Schwab (2025). Advancing Loss Reserving: A Hybrid Neural Network Approach. JRI / SSRN:4769020
- Duan N (1983). Smearing estimate: A nonparametric retransformation method. JASA 78(383), 605–610
- PRA SS8/24: Solvency II Calculation of Technical Provisions (November 2024)
License
MIT. Built by Burning Cost.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file insurance_reserving_neural-0.1.1.tar.gz.
File metadata
- Download URL: insurance_reserving_neural-0.1.1.tar.gz
- Upload date:
- Size: 38.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.8 {"installer":{"name":"uv","version":"0.10.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b0144cec122b6ba64e0b8f2fdb6904e2935aee43f931d7a6e954f5bcd0c2384f
|
|
| MD5 |
fff1a9c4710f92ee7966664fbd9536d7
|
|
| BLAKE2b-256 |
ce54fd24a8d20c24e678500455094c3941261355af51b6b274894ee861c957f6
|
File details
Details for the file insurance_reserving_neural-0.1.1-py3-none-any.whl.
File metadata
- Download URL: insurance_reserving_neural-0.1.1-py3-none-any.whl
- Upload date:
- Size: 29.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.8 {"installer":{"name":"uv","version":"0.10.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d160d049dbfe3cff2ab23bd52b7215ac22ab83e980a4082680d8d722fefa63f4
|
|
| MD5 |
e37c29ca7bc45241fca0bdda464ec67e
|
|
| BLAKE2b-256 |
eaa42ebc91fad02a5de191b89f25dbaa4f6da42284ea242a0b774d47d0f7e6d1
|