ibnr 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ibnr-0.2.0/.dockerignore +13 -0
- ibnr-0.2.0/.gitattributes +3 -0
- ibnr-0.2.0/.github/workflows/docs.yml +112 -0
- ibnr-0.2.0/.github/workflows/release.yml +72 -0
- ibnr-0.2.0/.gitignore +27 -0
- ibnr-0.2.0/.python-version +1 -0
- ibnr-0.2.0/.vscode/settings.json +11 -0
- ibnr-0.2.0/CLAUDE.md +102 -0
- ibnr-0.2.0/Dockerfile +51 -0
- ibnr-0.2.0/LICENSE +373 -0
- ibnr-0.2.0/PKG-INFO +224 -0
- ibnr-0.2.0/README.md +187 -0
- ibnr-0.2.0/analysis/01_triangle_and_meyers_ccl.ipynb +1856 -0
- ibnr-0.2.0/analysis/02_transformer_vs_statistical.ipynb +2105 -0
- ibnr-0.2.0/analysis/README.md +32 -0
- ibnr-0.2.0/analysis/results/clark_growth_curve_validation.csv +201 -0
- ibnr-0.2.0/analysis/results/clark_validation.csv +201 -0
- ibnr-0.2.0/analysis/results/clark_validation_weibull.csv +201 -0
- ibnr-0.2.0/analysis/results/compare_gallery.csv +1209 -0
- ibnr-0.2.0/analysis/results/compare_gallery_reported_loss.csv +1153 -0
- ibnr-0.2.0/analysis/results/compare_gallery_reported_loss_v1_inherit_stats.csv +79 -0
- ibnr-0.2.0/analysis/results/compare_gallery_reported_loss_v2_sixcompany.csv +79 -0
- ibnr-0.2.0/analysis/results/compare_gallery_v1_inherit_stats.csv +85 -0
- ibnr-0.2.0/analysis/results/compare_gallery_v2_sixcompany.csv +85 -0
- ibnr-0.2.0/analysis/results/compartmental_validation.csv +201 -0
- ibnr-0.2.0/analysis/results/compartmental_validation_lognormal.csv +201 -0
- ibnr-0.2.0/analysis/results/convergence_meyers.csv +7 -0
- ibnr-0.2.0/analysis/results/convergence_meyers_ta08.csv +7 -0
- ibnr-0.2.0/analysis/results/england_verrall_odp_validation.csv +201 -0
- ibnr-0.2.0/analysis/results/experiment_exposure_sigma.csv +365 -0
- ibnr-0.2.0/analysis/results/experiment_v3_market.csv +25 -0
- ibnr-0.2.0/analysis/results/experiment_v3_multiline.csv +25 -0
- ibnr-0.2.0/analysis/results/experiment_v3_screened.csv +365 -0
- ibnr-0.2.0/analysis/results/meyers_ccl_validation.csv +201 -0
- ibnr-0.2.0/analysis/results/meyers_ccl_validation_netbulk.csv +201 -0
- ibnr-0.2.0/analysis/results/meyers_csr_validation.csv +201 -0
- ibnr-0.2.0/analysis/results/parity_meyers.csv +5 -0
- ibnr-0.2.0/docs/publishing.md +121 -0
- ibnr-0.2.0/docs/three-repo-workflow.md +139 -0
- ibnr-0.2.0/great-docs.yml +186 -0
- ibnr-0.2.0/pyproject.toml +95 -0
- ibnr-0.2.0/scripts/compare_gallery.py +801 -0
- ibnr-0.2.0/scripts/meyers_validation.py +448 -0
- ibnr-0.2.0/scripts/parity_meyers.py +336 -0
- ibnr-0.2.0/src/ibnr/__init__.py +7 -0
- ibnr-0.2.0/src/ibnr/data/__init__.py +3 -0
- ibnr-0.2.0/src/ibnr/data/schedule_p.py +297 -0
- ibnr-0.2.0/src/ibnr/gallery/__init__.py +29 -0
- ibnr-0.2.0/src/ibnr/gallery/bayesian/__init__.py +0 -0
- ibnr-0.2.0/src/ibnr/gallery/bayesian/_toolchain.py +70 -0
- ibnr-0.2.0/src/ibnr/gallery/bayesian/clark_growth_curve/__init__.py +3 -0
- ibnr-0.2.0/src/ibnr/gallery/bayesian/clark_growth_curve/card.md +70 -0
- ibnr-0.2.0/src/ibnr/gallery/bayesian/clark_growth_curve/model.py +279 -0
- ibnr-0.2.0/src/ibnr/gallery/bayesian/clark_growth_curve/model.stan +69 -0
- ibnr-0.2.0/src/ibnr/gallery/bayesian/compartmental/__init__.py +3 -0
- ibnr-0.2.0/src/ibnr/gallery/bayesian/compartmental/card.md +169 -0
- ibnr-0.2.0/src/ibnr/gallery/bayesian/compartmental/model.py +502 -0
- ibnr-0.2.0/src/ibnr/gallery/bayesian/compartmental/model.stan +160 -0
- ibnr-0.2.0/src/ibnr/gallery/bayesian/compartmental/model_lognormal.stan +184 -0
- ibnr-0.2.0/src/ibnr/gallery/bayesian/england_verrall_odp/__init__.py +3 -0
- ibnr-0.2.0/src/ibnr/gallery/bayesian/england_verrall_odp/card.md +99 -0
- ibnr-0.2.0/src/ibnr/gallery/bayesian/england_verrall_odp/model.py +294 -0
- ibnr-0.2.0/src/ibnr/gallery/bayesian/england_verrall_odp/model.stan +67 -0
- ibnr-0.2.0/src/ibnr/gallery/bayesian/meyers_ccl/__init__.py +3 -0
- ibnr-0.2.0/src/ibnr/gallery/bayesian/meyers_ccl/card.md +232 -0
- ibnr-0.2.0/src/ibnr/gallery/bayesian/meyers_ccl/model.py +390 -0
- ibnr-0.2.0/src/ibnr/gallery/bayesian/meyers_ccl/model.stan +76 -0
- ibnr-0.2.0/src/ibnr/gallery/bayesian/meyers_ccl/model_numpyro.py +140 -0
- ibnr-0.2.0/src/ibnr/gallery/bayesian/meyers_ccl/model_pymc.py +139 -0
- ibnr-0.2.0/src/ibnr/gallery/bayesian/meyers_csr/__init__.py +3 -0
- ibnr-0.2.0/src/ibnr/gallery/bayesian/meyers_csr/card.md +109 -0
- ibnr-0.2.0/src/ibnr/gallery/bayesian/meyers_csr/model.py +247 -0
- ibnr-0.2.0/src/ibnr/gallery/bayesian/meyers_csr/model.stan +76 -0
- ibnr-0.2.0/src/ibnr/gallery/entry.py +56 -0
- ibnr-0.2.0/src/ibnr/gallery/nn/__init__.py +2 -0
- ibnr-0.2.0/src/ibnr/gallery/nn/transformer/__init__.py +4 -0
- ibnr-0.2.0/src/ibnr/gallery/nn/transformer/card.md +138 -0
- ibnr-0.2.0/src/ibnr/gallery/nn/transformer/config.py +50 -0
- ibnr-0.2.0/src/ibnr/gallery/nn/transformer/model.py +451 -0
- ibnr-0.2.0/src/ibnr/gallery/nn/transformer/network.py +212 -0
- ibnr-0.2.0/src/ibnr/gallery/nn/transformer_ml/__init__.py +4 -0
- ibnr-0.2.0/src/ibnr/gallery/nn/transformer_ml/card.md +73 -0
- ibnr-0.2.0/src/ibnr/gallery/nn/transformer_ml/config.py +60 -0
- ibnr-0.2.0/src/ibnr/gallery/nn/transformer_ml/model.py +448 -0
- ibnr-0.2.0/src/ibnr/gallery/nn/transformer_ml/network.py +284 -0
- ibnr-0.2.0/src/ibnr/gallery/registry.py +42 -0
- ibnr-0.2.0/src/ibnr/gallery/statistical/__init__.py +4 -0
- ibnr-0.2.0/src/ibnr/gallery/statistical/clark/__init__.py +3 -0
- ibnr-0.2.0/src/ibnr/gallery/statistical/clark/card.md +96 -0
- ibnr-0.2.0/src/ibnr/gallery/statistical/clark/model.py +291 -0
- ibnr-0.2.0/src/ibnr/gallery/statistical/copula_glm/__init__.py +3 -0
- ibnr-0.2.0/src/ibnr/gallery/statistical/copula_glm/card.md +77 -0
- ibnr-0.2.0/src/ibnr/gallery/statistical/copula_glm/model.py +344 -0
- ibnr-0.2.0/src/ibnr/gallery/statistical/sur/__init__.py +3 -0
- ibnr-0.2.0/src/ibnr/gallery/statistical/sur/card.md +84 -0
- ibnr-0.2.0/src/ibnr/gallery/statistical/sur/model.py +366 -0
- ibnr-0.2.0/src/ibnr/kernels/__init__.py +4 -0
- ibnr-0.2.0/src/ibnr/kernels/calibration.py +71 -0
- ibnr-0.2.0/src/ibnr/kernels/contract.py +370 -0
- ibnr-0.2.0/src/ibnr/kernels/harness.py +332 -0
- ibnr-0.2.0/src/ibnr/kernels/multiline.py +351 -0
- ibnr-0.2.0/src/ibnr/kernels/nn_contract.py +441 -0
- ibnr-0.2.0/src/ibnr/kernels/parity.py +161 -0
- ibnr-0.2.0/src/ibnr/kernels/predictive.py +105 -0
- ibnr-0.2.0/src/ibnr/kernels/scores.py +39 -0
- ibnr-0.2.0/src/ibnr/triangle/__init__.py +3 -0
- ibnr-0.2.0/src/ibnr/triangle/core.py +239 -0
- ibnr-0.2.0/src/ibnr/triangle/io.py +340 -0
- ibnr-0.2.0/src/ibnr/triangle/transforms.py +189 -0
- ibnr-0.2.0/src/ibnr/triangle/validate.py +69 -0
- ibnr-0.2.0/tests/__init__.py +0 -0
- ibnr-0.2.0/tests/conftest.py +179 -0
- ibnr-0.2.0/tests/test_clark.py +134 -0
- ibnr-0.2.0/tests/test_clark_growth_curve.py +107 -0
- ibnr-0.2.0/tests/test_compartmental.py +339 -0
- ibnr-0.2.0/tests/test_contract.py +122 -0
- ibnr-0.2.0/tests/test_copula_glm.py +212 -0
- ibnr-0.2.0/tests/test_core.py +44 -0
- ibnr-0.2.0/tests/test_england_verrall_odp.py +119 -0
- ibnr-0.2.0/tests/test_gallery.py +128 -0
- ibnr-0.2.0/tests/test_harness.py +173 -0
- ibnr-0.2.0/tests/test_interop_bermuda.py +46 -0
- ibnr-0.2.0/tests/test_meyers_ccl.py +132 -0
- ibnr-0.2.0/tests/test_meyers_csr.py +125 -0
- ibnr-0.2.0/tests/test_multiline.py +198 -0
- ibnr-0.2.0/tests/test_nn_contract.py +262 -0
- ibnr-0.2.0/tests/test_nn_transformer.py +412 -0
- ibnr-0.2.0/tests/test_nn_transformer_ml.py +255 -0
- ibnr-0.2.0/tests/test_odp_contract.py +145 -0
- ibnr-0.2.0/tests/test_parity_meyers.py +235 -0
- ibnr-0.2.0/tests/test_predictive.py +132 -0
- ibnr-0.2.0/tests/test_schedule_p.py +156 -0
- ibnr-0.2.0/tests/test_schedule_p_github.py +213 -0
- ibnr-0.2.0/tests/test_scores.py +87 -0
- ibnr-0.2.0/tests/test_statistical_mart.py +212 -0
- ibnr-0.2.0/tests/test_sur.py +254 -0
- ibnr-0.2.0/tests/test_tieout_chainladder.py +204 -0
- ibnr-0.2.0/tests/test_transforms.py +183 -0
- ibnr-0.2.0/tests/test_validate.py +48 -0
- ibnr-0.2.0/uv.lock +3813 -0
ibnr-0.2.0/.dockerignore
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# keep the build context to what the image COPYs (pyproject/README/src/scripts)
|
|
2
|
+
.git
|
|
3
|
+
.venv
|
|
4
|
+
analysis
|
|
5
|
+
docs
|
|
6
|
+
tests
|
|
7
|
+
**/__pycache__
|
|
8
|
+
**/*.pyc
|
|
9
|
+
# locally compiled cmdstan artifacts next to the .stan sources (Windows dev
|
|
10
|
+
# box) — the image compiles its own Linux executables at build time
|
|
11
|
+
src/**/*.exe
|
|
12
|
+
src/**/*.o
|
|
13
|
+
src/**/*.d
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
name: CI Docs
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
pull_request:
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
build-docs:
|
|
11
|
+
name: "Build Docs"
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v6
|
|
15
|
+
with:
|
|
16
|
+
fetch-depth: 0 # Full history for accurate page timestamps
|
|
17
|
+
|
|
18
|
+
- uses: actions/setup-python@v6
|
|
19
|
+
with:
|
|
20
|
+
python-version: "3.11"
|
|
21
|
+
|
|
22
|
+
- name: Install package and dependencies
|
|
23
|
+
run: |
|
|
24
|
+
python -m pip install uv
|
|
25
|
+
# core deps + the `docs` group (great-docs), pinned by uv.lock.
|
|
26
|
+
# README code blocks render display-only, so the heavy `dev` group
|
|
27
|
+
# (chainladder, jupyter, ...) is not needed to build the site.
|
|
28
|
+
uv sync --no-default-groups --group docs
|
|
29
|
+
|
|
30
|
+
- name: Set up Quarto
|
|
31
|
+
uses: quarto-dev/quarto-actions/setup@v2
|
|
32
|
+
|
|
33
|
+
- name: Build docs
|
|
34
|
+
run: uv run great-docs build
|
|
35
|
+
|
|
36
|
+
- name: Save docs artifact
|
|
37
|
+
uses: actions/upload-artifact@v7
|
|
38
|
+
with:
|
|
39
|
+
name: docs-html
|
|
40
|
+
path: great-docs/_site
|
|
41
|
+
include-hidden-files: true
|
|
42
|
+
|
|
43
|
+
- name: Upload build timings
|
|
44
|
+
uses: actions/upload-artifact@v7
|
|
45
|
+
with:
|
|
46
|
+
name: build-timings
|
|
47
|
+
path: great-docs/_site/build-timings.json
|
|
48
|
+
|
|
49
|
+
publish-docs:
|
|
50
|
+
name: "Publish Docs"
|
|
51
|
+
runs-on: ubuntu-latest
|
|
52
|
+
needs: "build-docs"
|
|
53
|
+
if: github.ref == 'refs/heads/main'
|
|
54
|
+
permissions:
|
|
55
|
+
pages: write
|
|
56
|
+
id-token: write
|
|
57
|
+
environment:
|
|
58
|
+
name: github-pages
|
|
59
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
60
|
+
steps:
|
|
61
|
+
- uses: actions/download-artifact@v7
|
|
62
|
+
with:
|
|
63
|
+
name: docs-html
|
|
64
|
+
path: great-docs/_site
|
|
65
|
+
|
|
66
|
+
- name: Upload Pages artifact
|
|
67
|
+
uses: actions/upload-pages-artifact@v5
|
|
68
|
+
with:
|
|
69
|
+
path: great-docs/_site
|
|
70
|
+
include-hidden-files: true
|
|
71
|
+
|
|
72
|
+
- name: Deploy to GitHub Pages
|
|
73
|
+
id: deployment
|
|
74
|
+
uses: actions/deploy-pages@v5
|
|
75
|
+
|
|
76
|
+
preview-docs:
|
|
77
|
+
name: "Preview Docs"
|
|
78
|
+
runs-on: ubuntu-latest
|
|
79
|
+
needs: "build-docs"
|
|
80
|
+
if: github.event_name == 'pull_request'
|
|
81
|
+
permissions:
|
|
82
|
+
deployments: write
|
|
83
|
+
pull-requests: write
|
|
84
|
+
steps:
|
|
85
|
+
- uses: actions/download-artifact@v7
|
|
86
|
+
with:
|
|
87
|
+
name: docs-html
|
|
88
|
+
path: great-docs/_site
|
|
89
|
+
|
|
90
|
+
# Start deployment
|
|
91
|
+
- name: Configure pull release name
|
|
92
|
+
if: ${{ github.event_name == 'pull_request' }}
|
|
93
|
+
run: |
|
|
94
|
+
echo "RELEASE_NAME=pr-${{ github.event.number }}" >> $GITHUB_ENV
|
|
95
|
+
|
|
96
|
+
- name: Configure branch release name
|
|
97
|
+
if: ${{ github.event_name != 'pull_request' }}
|
|
98
|
+
run: |
|
|
99
|
+
# use branch name, but replace slashes. E.g. feat/a -> feat-a
|
|
100
|
+
echo "RELEASE_NAME=${GITHUB_REF_NAME//\//-}" >> $GITHUB_ENV
|
|
101
|
+
|
|
102
|
+
# Deploy
|
|
103
|
+
- name: Create Github Deployment
|
|
104
|
+
uses: bobheadxi/deployments@v1
|
|
105
|
+
id: deployment
|
|
106
|
+
if: ${{ !github.event.pull_request.head.repo.fork }}
|
|
107
|
+
with:
|
|
108
|
+
step: start
|
|
109
|
+
token: ${{ secrets.GITHUB_TOKEN }}
|
|
110
|
+
env: ${{ env.RELEASE_NAME }}
|
|
111
|
+
ref: ${{ github.head_ref }}
|
|
112
|
+
logs: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Publishes `ibnr` using PyPI Trusted Publishing (OIDC) — no API token is ever
|
|
4
|
+
# stored in this repository. PyPI verifies the identity of this workflow
|
|
5
|
+
# directly, so there is no long-lived secret to leak or rotate.
|
|
6
|
+
#
|
|
7
|
+
# Two ways to run it:
|
|
8
|
+
# * Dry run -> Actions > Release > "Run workflow". Builds and validates the
|
|
9
|
+
# distributions and uploads them as a downloadable artifact, but publishes
|
|
10
|
+
# NOTHING. Use this to rehearse a release.
|
|
11
|
+
# * Real release -> push a version tag: git tag v0.1.0 && git push origin v0.1.0
|
|
12
|
+
#
|
|
13
|
+
# Publishing happens ONLY on a tag push, so a stray "Run workflow" click can
|
|
14
|
+
# never release. Requires a matching "pending publisher" on PyPI (see
|
|
15
|
+
# docs/publishing.md); those values must match THIS file's name (release.yml)
|
|
16
|
+
# and the environment name below, or PyPI will refuse the upload.
|
|
17
|
+
|
|
18
|
+
on:
|
|
19
|
+
push:
|
|
20
|
+
tags:
|
|
21
|
+
- "v*"
|
|
22
|
+
workflow_dispatch: # dry run: build + validate only, never publishes
|
|
23
|
+
|
|
24
|
+
jobs:
|
|
25
|
+
build:
|
|
26
|
+
name: Build distributions
|
|
27
|
+
runs-on: ubuntu-latest
|
|
28
|
+
steps:
|
|
29
|
+
- uses: actions/checkout@v6
|
|
30
|
+
with:
|
|
31
|
+
fetch-depth: 0
|
|
32
|
+
|
|
33
|
+
- name: Install uv
|
|
34
|
+
uses: astral-sh/setup-uv@v5
|
|
35
|
+
with:
|
|
36
|
+
python-version: "3.12"
|
|
37
|
+
|
|
38
|
+
- name: Build sdist + wheel
|
|
39
|
+
run: uv build
|
|
40
|
+
|
|
41
|
+
# Fails the run before anything is uploaded if the metadata or the
|
|
42
|
+
# README (which becomes the PyPI project description) is malformed.
|
|
43
|
+
- name: Validate distribution metadata
|
|
44
|
+
run: uvx twine check --strict dist/*
|
|
45
|
+
|
|
46
|
+
- name: Show what will be published
|
|
47
|
+
run: ls -l dist/
|
|
48
|
+
|
|
49
|
+
- uses: actions/upload-artifact@v7
|
|
50
|
+
with:
|
|
51
|
+
name: dist
|
|
52
|
+
path: dist/
|
|
53
|
+
if-no-files-found: error
|
|
54
|
+
|
|
55
|
+
pypi:
|
|
56
|
+
name: Publish to PyPI
|
|
57
|
+
needs: build
|
|
58
|
+
# Tag pushes only — a manual "Run workflow" is always a dry run.
|
|
59
|
+
if: github.event_name == 'push'
|
|
60
|
+
runs-on: ubuntu-latest
|
|
61
|
+
environment:
|
|
62
|
+
name: pypi
|
|
63
|
+
url: https://pypi.org/project/ibnr/
|
|
64
|
+
permissions:
|
|
65
|
+
id-token: write # required for Trusted Publishing; nothing else is needed
|
|
66
|
+
steps:
|
|
67
|
+
- uses: actions/download-artifact@v7
|
|
68
|
+
with:
|
|
69
|
+
name: dist
|
|
70
|
+
path: dist/
|
|
71
|
+
|
|
72
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
ibnr-0.2.0/.gitignore
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# python / uv
|
|
2
|
+
.venv/
|
|
3
|
+
__pycache__/
|
|
4
|
+
*.py[cod]
|
|
5
|
+
*.egg-info/
|
|
6
|
+
dist/
|
|
7
|
+
build/
|
|
8
|
+
|
|
9
|
+
# tooling caches
|
|
10
|
+
.pytest_cache/
|
|
11
|
+
.ruff_cache/
|
|
12
|
+
.ipynb_checkpoints/
|
|
13
|
+
.coverage
|
|
14
|
+
|
|
15
|
+
# great-docs generated site (source of truth is great-docs.yml + docstrings)
|
|
16
|
+
great-docs/
|
|
17
|
+
|
|
18
|
+
# cmdstan compiled artifacts (model*.stan is the source of truth)
|
|
19
|
+
src/**/model*.exe
|
|
20
|
+
src/**/model
|
|
21
|
+
src/**/model_lognormal
|
|
22
|
+
src/**/model*.hpp
|
|
23
|
+
src/**/model*.o
|
|
24
|
+
|
|
25
|
+
# OS noise
|
|
26
|
+
Thumbs.db
|
|
27
|
+
.DS_Store
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"chat.agentSkillsLocations": {
|
|
3
|
+
".agents/skills": true,
|
|
4
|
+
".github/skills": true,
|
|
5
|
+
".claude/skills": true,
|
|
6
|
+
"~/.agents/skills": true,
|
|
7
|
+
"~/.copilot/skills": true,
|
|
8
|
+
"~/.claude/skills": true,
|
|
9
|
+
"~/.vscode/extensions/synapsevscode.synapse-1.25.0/copilot/skills": true
|
|
10
|
+
}
|
|
11
|
+
}
|
ibnr-0.2.0/CLAUDE.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
## What this project is
|
|
4
|
+
|
|
5
|
+
A gallery-centric actuarial reserving package: Bayesian MCMC and neural network loss reserving methods with mandatory evaluation, model stacking, and a long-format triangle data layer backed by duckdb and polars. Designed as a companion/extension to `chainladder-python`, not a fork of it.
|
|
6
|
+
|
|
7
|
+
Name: **ibnr** (PyPI availability verified 2026-06-11; import `ibnr`, repo dir name predates it). License: MPL-2.0 (deliberately matches chainladder).
|
|
8
|
+
|
|
9
|
+
## Dev commands
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
uv sync # core + dev deps (chainladder, bermuda-ledger for interop tests)
|
|
13
|
+
uv sync --extra bayesian # + cmdstanpy/numpyro/pymc/arviz/bayesblend (works on py3.12)
|
|
14
|
+
uv run pytest # fast suite (slow excluded via addopts); transforms run on BOTH backends
|
|
15
|
+
uv run pytest -m slow # cmdstan tests (compile + sample); needs cmdstan installed
|
|
16
|
+
uv run pytest "tests/test_transforms.py::test_as_of[polars]" # single test, one backend
|
|
17
|
+
uv run ruff check . && uv run ruff format .
|
|
18
|
+
uv run python scripts/meyers_validation.py --per-line 50 # Meyers retrospective validation
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Markers: `tieout` (chainladder comparisons), `mart` (needs the local Schedule P gold mart; auto-skips when absent), `parity`, `slow` (cmdstan; excluded by default via addopts).
|
|
22
|
+
|
|
23
|
+
cmdstan on this machine: installed at `~/.cmdstan` (2.39.0), built with the RTools toolchain at `C:\rtools45` (plain `make`, not `mingw32-make` — set `MAKE=make` and prepend `C:\rtools45\x86_64-w64-mingw32.static.posix\bin` and `C:\rtools45\usr\bin` to PATH; `gallery/bayesian/meyers_ccl/model.py:_ensure_windows_toolchain` does this automatically).
|
|
24
|
+
|
|
25
|
+
## Core design decisions — do not relitigate without asking
|
|
26
|
+
|
|
27
|
+
1. **Long-format 2D triangles only.** No 4D arrays. A triangle is a tidy table: `origin_period, dev_lag, eval_date, field, value` + arbitrary segment columns (lob, company, ...). `eval_date` is a first-class stored column, not derived — all backtesting slices on it. Each triangle carries metadata: grain, cumulative/incremental flag, units. Reference design: Ledger Investing's `bermuda`.
|
|
28
|
+
2. **ibis is the dataframe frontend.** Both duckdb and polars backends are supported — no debate, both. Transformations (incr↔cum via window functions, grain changes, pivots, `as_of()` slicing) are written once in ibis. The transformation test suite runs against BOTH backends; ibis's polars backend has weaker window-function coverage, so expect and document gaps.
|
|
29
|
+
3. **The Stan `data` block is the data contract.** `kernels/contract.py` maps Triangle → standardized dict. NumPyro and PyMC implementations consume the identical dict. Never let a backend grow its own data prep.
|
|
30
|
+
4. **`PredictiveDistribution` is the unifying output type** (`kernels/predictive.py`). Every gallery entry — Bayesian, NN, deterministic — must produce one. NN models require distributional heads (mixture/quantile heads or deep ensembles); point estimators cannot enter the gallery. Deterministic baselines (Mack, basic CL) are wrapped with bootstrap.
|
|
31
|
+
5. **Evaluation is a contract, not a feature.** `GalleryEntry` ABC requires `.fit()`, `.predict()`, `.evaluate()`, `.card()`. An entry that fails the eval harness does not register. Eval algorithms (ELPD/LOO/WAIC, PIT calibration, stacking via `bayesblend`, ArviZ-based diagnostics) are implemented ONCE in `kernels/` — gallery entries call them, never reimplement.
|
|
32
|
+
6. **Eject-pattern codegen, not a formula DSL.** Every gallery model is literal, readable source: `model.stan`, `model_numpyro.py`, `model_pymc.py` side by side. `gallery.scaffold()` copies source into the user's project for them to extend (copulas, priors, hyperparameters). Precedent: brms/bambi, but simpler.
|
|
33
|
+
7. **Cross-backend parity is a feature.** `kernels/parity.py` validates that NumPyro/PyMC ports match the Stan reference posterior before any convergence/speed comparisons. Parameterization (centered vs non-centered, truncation handling, init strategy) is an explicit documented attribute of each model card. Stan implementations from the literature are ground truth.
|
|
34
|
+
8. **Small public API:** `Triangle`, `gallery.list/fit/evaluate/stack/scaffold/leaderboard`.
|
|
35
|
+
|
|
36
|
+
## Repository layout
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
src/<pkg>/
|
|
40
|
+
triangle/ core.py (Triangle over ibis expr), transforms.py, io.py, validate.py
|
|
41
|
+
kernels/ contract.py, predictive.py, scores.py, calibration.py,
|
|
42
|
+
multiline.py (multi-LOB contract), nn_contract.py (NN grids/masks),
|
|
43
|
+
stacking.py, parity.py, tuning.py
|
|
44
|
+
gallery/ registry.py, entry.py (GalleryEntry ABC), scaffold.py
|
|
45
|
+
bayesian/ meyers_ccl/ meyers_csr/ england_verrall_odp/ compartmental/
|
|
46
|
+
nn/ transformer/ deeptriangle/ mdn/ resnet/
|
|
47
|
+
statistical/ sur/ copula_glm/ (frequentist stochastic dependence models)
|
|
48
|
+
deterministic/ mack/ ...
|
|
49
|
+
data/ schedule_p.py (gold mart adapter)
|
|
50
|
+
viz/ tidy.py, altair_.py, site.py (quarto gallery site)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Each gallery model dir contains: `card.md`, `model.stan`, `model_numpyro.py`, `model_pymc.py` (bayesian) or pytorch module + config (nn) or plain-numpy `model.py` (statistical).
|
|
54
|
+
|
|
55
|
+
## Tooling & conventions
|
|
56
|
+
|
|
57
|
+
* `src/` layout, `uv` for env + packaging, `ruff` for lint/format, `pytest`.
|
|
58
|
+
* Optional extras keep the core light: `[bayesian]` (cmdstanpy, numpyro, pymc, arviz, bayesblend), `[nn]` (torch), `[viz]` (altair, quarto tooling). Core deps ≈ ibis-framework[duckdb,polars] + scipy (scipy added 2026-07-06 for the statistical family; deliberate exception to "ibis only"). Torch must never be imported at module level — `ibnr.gallery` imports (and nn entries register) without the `[nn]` extra; `tests/test_gallery.py` enforces this in a subprocess.
|
|
59
|
+
* Test markers: `-m tieout` (results match chainladder-python on raa/clrd samples), `-m parity` (cross-backend posterior matching), `-m slow` (cmdstan compilation; separate CI job).
|
|
60
|
+
* CI matrix runs core and each extra in isolation to catch hidden imports.
|
|
61
|
+
* Interop is sacred: `Triangle.from_chainladder/to_chainladder`, `from_bermuda/to_bermuda` must round-trip losslessly.
|
|
62
|
+
|
|
63
|
+
## Related project (data source)
|
|
64
|
+
|
|
65
|
+
`https://github.com/EKtheSage/cas-schedule-p-data-model` (local checkout at `C:\Users\EthanKang\Projects\cas-schedule-p-data-model`) — Ethan's CAS Schedule P database (Data Vault warehouse, versioned gold publishes). **Default consumption is via GitHub releases**: each gold promote is an immutable release tagged with its publish_id; `load_schedule_p()` with no source resolves `github://EKtheSage/cas-schedule-p-data-model@latest` through the gh CLI, caches to `~/.cache/ibnr` (or `IBNR_CACHE_DIR`), sha256-verifies against the release manifest. Local dev works the same way — the warehouse checkout is only an override (`IBNR_SCHEDULE_P_WAREHOUSE` or explicit path), mainly for producer-side pipeline work. Experiment runs should pin a concrete `@publish_id`; the harness scripts stamp it into every results CSV.
|
|
66
|
+
|
|
67
|
+
Before writing `triangle/core.py` or `data/schedule_p.py`: inspect that repo. Determine storage format (duckdb file / parquet / warehouse), gold table names, column names, grains, and how realized ultimates are represented. Write the Triangle schema and the adapter against the real schema, not assumptions. The data pipeline stays in that repo; this package only consumes its gold mart via config (path/connection string). In-repo test fixtures use public samples (raa, clrd-style) so the package works without the mart.
|
|
68
|
+
|
|
69
|
+
## Milestones (in order; do not skip ahead)
|
|
70
|
+
|
|
71
|
+
1. **DONE (2026-06-11).** Triangle layer + transforms tie out to chainladder-python on raa/clrd, on both duckdb and polars backends. Also done: bermuda round-trip interop, `data/schedule_p.py` gold-mart adapter (ties to the published wide paid mart), `as_of()` backtest slicing. `dev_lag` is always months; eval_date convention: last day of the month `origin + dev_lag` lands in.
|
|
72
|
+
2. **DONE (2026-06-13).** `meyers_ccl` (Stan) reproduces Meyers monograph validation tables from the Schedule P gold mart (train on early diagonal, score realized ultimates, PIT uniformity). Built: `kernels/contract.py` (+ predictive, calibration), gallery registry/entry ABC, meyers_ccl entry + card, `scripts/meyers_validation.py`. Final 200-company run on **net-of-bulk incurred** (`reported_loss` = IncurLoss − BulkLoss; mart rebuilt to carry bulk_loss + direct premium): all four lines AND the combined test pass KS at 5% (combined D=6.8 vs crit 9.6; Meyers' own Fig 8.5 combined was 10.8*). Critical lesson: gross-of-bulk incurred fails WC at D=36.7 — the incurred definition is load-bearing; results in `analysis/results/meyers_ccl_validation_netbulk.csv` (gross run kept alongside as `meyers_ccl_validation.csv`). Explored in `analysis/01_triangle_and_meyers_ccl.ipynb`.
|
|
73
|
+
3. **DONE (2026-07-06; reordered ahead of the parity ports at Ethan's request.)** NN family opens + statistical dependence baselines, compared on the Schedule P backtest. Built: `nn_transformer` (masked-cell triangle transformer, MDN head + 5-seed deep ensemble, calendar-cutoff augmentation, eval_date validation split, autoregressive diagonal rollout; global fit → per-segment predict), `sur` (Zhang 2010 multivariate chain ladder via hand-rolled FGLS; collapses to volume-weighted CL per line at zero correlation), `copula_glm` (Shi & Frees 2011 lognormal marginals + Gaussian copula, parametric bootstrap), kernels `nn_contract.py` / `multiline.py` / `scores.py` (sample CRPS; now in `evaluate()`), new family string `"statistical"`, `scripts/compare_gallery.py` (headline field: paid_loss — lognormal marginals cannot take negative reported increments). **v2 transformer fix (2026-07-07):** pinned per-dev standardization (devs with <2 training-context values define standardized value 0, mean from observed cells at that dev, std 1; rollout draws there forced to the pooled dev mean) replaced v1's inherit-earlier-dev tail stats — the inheritance was the dominant defect (paid err +14%→+4%, rel-CRPS 0.092→0.050; reported err +148%→+1.7%, rel-CRPS 0.79→0.030, only model passing combined KS at 5%, D=26.7 vs crit 27.8). Training pool now multiline companies (≥2 screened lines; `--nn-pool market` reruns the full-mart pool). v1 results kept as `analysis/results/*_v1_inherit_stats.csv`; analysis in `analysis/02_transformer_vs_statistical.ipynb`. **v3 + multi-line study (2026-07-08):** relative calendar encoding (distance past cutoff) replaced absolute calendar embeddings, `nn_transformer_ml` opened (company cohorts, attention across lines; `config.dependence` = "ar" line-by-line AR sampling | "joint" multivariate Gaussian-mixture head with absent lines marginalized; predicts SUR layout incl. diversified totals), study widened to companies passing screens on ≥2 lines (60 companies, 152 cells; transformers train on exactly the scored pairs — looser pools measurably hurt), results CSVs carry anchor+premium, vw chain_ladder point benchmark + reserve-basis point metrics (Wilcoxon-paired) in `compare_gallery.py`. Headlines: paid calibration nn_transformer D=18.3 best; reported meyers_ccl passes KS outright (D=6.7); nn_ml_ar sharpest on 3/4 reported lines and first ML entry to edge CL on premium-normalized point reserves; implied cross-line correlation: joint 0.13/0.08 > ar 0.04/0.05 > SUR/copula 0.01-0.02; sur failed 2 companies, copula diverged on 7/152 cells, transformers 0 failures. NOTE: the single-line transformer's per-line draws are independent — only the `nn_ml_*` entries model cross-line dependence.
|
|
74
|
+
4. **DONE (2026-07-21).** Full Bayesian gallery in Stan before porting anything (reordered Stan-first at Ethan's request 2026-07-20): `meyers_csr` → `england_verrall_odp` → `clark_growth_curve` (NEW: both a deterministic Clark 2003 MLE entry tied out against chainladder's `ClarkLDF` + a Bayesian Stan version, loglogistic + Weibull curves) → `compartmental` (Gesmann & Morris). Every entry got the full Meyers-style 200-company Schedule P retrospective (KS/PIT on realized ultimates). **Checkpoint (2026-07-20):** meyers_ccl NumPyro + PyMC ports pass parity (kernels/parity.py gates mean/SD in MCSE units + marginal KS against the Stan reference; 2 WC triangles; convergence CSVs in analysis/results; open question: PyMC blow-up in the ta08 run on company 11347). **compartmental (2026-07-21):** two ablatable variants from the monograph's case study, priors verbatim from its appendix 7.2 brms code — `gaussian` (Model 1: OS + cumulative paid amounts, correlated (RLR,RRF) AY effects, LKJ) and `lognormal` (Model 2: OS + incremental paid loss ratios, AY+dev effects on all four parameters); new joint paid+outstanding contract `kernels.contract.compartmental_stan_data` (delta-stacked cells, t in years, OS = reported − paid). Retro (no clamp, zero failures both arms): gaussian combined **D=39.9*** (too sharp, CV≈2.5%, and the single-company priors bias OL to est/outcome 0.84); lognormal combined **D=16.1*** with CA passing outright (12.5) — the best-calibrated paid entry after CSR (paid panel: CSR 4.1 < comp-lognormal 16.1* < comp-gaussian 39.9* < ODP 47.9* < clark 49.6–63.4*). Monograph sampler settings (adapt_delta .99, treedepth 15) cost ~160–250 s/company sequential — addressed by the parallel harness (below). Open ablation: `hierarchical` variant with per-line mart-derived prior medians / cross-company pooling. **Parallel retro harness (2026-07-21):** `kernels/harness.py` — `RetroTask`/`SamplerSettings`/`ConvergenceGates`/`run_retro`: spawn-based process pool over companies (default = all cores − 1; `IBNR_MAX_WORKERS` in containers), staged sampler escalation (re-fit only fits failing the convergence gates), entries gained `parallel_chains`/`max_treedepth`/`precompile()`, workers consume a `schedule_p.pinned_source()` so they never resolve `@latest` or call gh. `meyers_validation.py` runs it by default (`--serial`/`--no-escalate` reproduce the published sequential behavior; compartmental stage 1 relaxed to 0.9/12 → ~4x/company, stage 2 = monograph settings). `Dockerfile` packages package + cmdstan 2.39.0 + precompiled gallery Stan models as the compute image other services call (build untested — no Docker on the dev box); long-term the same harness is the seam for a hosted scoring API.
|
|
75
|
+
5. NumPyro + PyMC ports for the rest of the family pass parity; cross-backend convergence comparison (R-hat/ESS/divergences/runtime) published in model cards.
|
|
76
|
+
6. Full Bayesian gallery with held-out-diagonal ELPD, PIT calibration, and bayesblend stacking. Leaderboard works.
|
|
77
|
+
7. Rest of the NN family joins the same leaderboard via PredictiveDistribution (deeptriangle, mdn, resnet; HPO in kernels/tuning.py).
|
|
78
|
+
8. (Secondary) viz/site.py renders the gallery as a quarto static site with interactive altair charts.
|
|
79
|
+
|
|
80
|
+
## Key references
|
|
81
|
+
|
|
82
|
+
* Meyers, Stochastic Loss Reserving Using Bayesian MCMC Models (CAS monograph; published Stan code = ground truth for CCL/CSR).
|
|
83
|
+
* England & Verrall, Stochastic Claims Reserving in General Insurance (ODP).
|
|
84
|
+
* Clark, LDF Curve-Fitting and Stochastic Reserving: A Maximum Likelihood Approach (CAS Forum 2003; loglogistic/Weibull growth curves, ODP likelihood — chainladder's `ClarkLDF` is the MLE reference).
|
|
85
|
+
* Gesmann & Morris, Hierarchical Compartmental Reserving Models (CAS; Stan/brms code; hardest parity case — ODEs).
|
|
86
|
+
* Kuo, DeepTriangle (original in Keras; reimplement in pytorch).
|
|
87
|
+
* Ledger Investing: `bermuda` (triangle design reference), `bayesblend` (stacking).
|
|
88
|
+
* chainladder-python: backends are numpy/cupy/sparse/dask 4D duck arrays; polars is input-only via dataframe interchange. We intentionally diverge.
|
|
89
|
+
|
|
90
|
+
## Gotchas
|
|
91
|
+
|
|
92
|
+
* ibis polars backend (ibis 10.x): **no window-function support at all** (`OperationNotDefinedError` for any `WindowFunction`), no `ScalarSubquery` either. Transforms use equi-join + group-by formulations instead (see `triangle/transforms.py`). Also: after `cross_join`, mutate derived expressions into real columns *before* filtering on them, or polars loses the joined column (`ColumnNotFoundError: 1__anchor`).
|
|
93
|
+
* chainladder semantics worth remembering (all encoded in the tieout tests):
|
|
94
|
+
* valuation timestamps are end-of-day, so `tri[tri.valuation <= "1985-12-31"]` *excludes* the 1985 diagonal; use `< "1986"`.
|
|
95
|
+
* triangle arithmetic treats missing cells as zero inside the observed region, so `cum_to_incr` fabricates increments at cells absent from the source and stores zero increments as NaN. Our long format means absent = unobserved, zero = explicit; we deliberately do NOT densify (it would fabricate data on `as_of` slices).
|
|
96
|
+
* `grain('OYDY')` anchors dev buckets to the latest diagonal (ages 3, 15, 27... when the latest valuation is Q1). `change_dev_grain` matches this.
|
|
97
|
+
* bermuda's `Cell.dev_lag` measures from period *end* (first diagonal = 0); ours is months from origin *start* (first diagonal = 12). `from_bermuda` recomputes; never copy bermuda's dev_lag.
|
|
98
|
+
* "Same model" across PPLs is only same if parameterization is held constant — otherwise convergence comparisons measure implementation accidents.
|
|
99
|
+
* Schedule P triangles are small; NN overfitting is the central risk. All NN training validates across many company×LOB triangles, never one.
|
|
100
|
+
* Windows dev machine: cmdstanpy needs a working toolchain (RTools/MSVC); keep cmdstan-dependent tests behind `-m slow` and runnable on CI Linux.
|
|
101
|
+
* Never name modules shadowing deps (`altair_.py` not `altair.py`).
|
|
102
|
+
* The Schedule P mart carries accident years beyond the Meyers study window (1988–2007, not just 1988–1997). Any outcome/aggregate computed from the full triangle must restrict to the origins present in the as_of training slice, or it silently includes post-study accident years (bit `scripts/compare_gallery.py::point_context` once — 2.4x inflated "outcomes").
|
ibnr-0.2.0/Dockerfile
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# ibnr compute image: the package + cmdstan + every gallery Stan model
|
|
2
|
+
# COMPILED IN, so a container start pays zero toolchain cost. This is the
|
|
3
|
+
# image other services call today (scripts/) and the base the hosted scoring
|
|
4
|
+
# API will run on — both go through kernels/harness.py, which parallelizes
|
|
5
|
+
# across all visible cores by default.
|
|
6
|
+
#
|
|
7
|
+
# docker build -t ibnr .
|
|
8
|
+
# docker run --rm -e GH_TOKEN=<token with repo read> \
|
|
9
|
+
# -e IBNR_MAX_WORKERS=8 --cpus 8 \
|
|
10
|
+
# -v ibnr-cache:/data/ibnr-cache -v "$PWD/results:/app/analysis/results" \
|
|
11
|
+
# ibnr python scripts/meyers_validation.py --model compartmental --per-line 50
|
|
12
|
+
#
|
|
13
|
+
# IBNR_MAX_WORKERS should match --cpus: inside a cpu-limited container,
|
|
14
|
+
# os.cpu_count() still reports the HOST's cores, so the env var is how the
|
|
15
|
+
# orchestrator tells the harness its real budget.
|
|
16
|
+
|
|
17
|
+
FROM python:3.12-slim
|
|
18
|
+
|
|
19
|
+
# build-essential: cmdstan is compiled into the image below (and gallery
|
|
20
|
+
# models compile against it). gh: fetches gold-mart releases at runtime from
|
|
21
|
+
# the private data repo (auth via GH_TOKEN).
|
|
22
|
+
RUN apt-get update \
|
|
23
|
+
&& apt-get install -y --no-install-recommends build-essential curl ca-certificates \
|
|
24
|
+
&& curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
|
|
25
|
+
-o /usr/share/keyrings/githubcli-archive-keyring.gpg \
|
|
26
|
+
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
|
|
27
|
+
> /etc/apt/sources.list.d/github-cli.list \
|
|
28
|
+
&& apt-get update && apt-get install -y --no-install-recommends gh \
|
|
29
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
30
|
+
|
|
31
|
+
WORKDIR /app
|
|
32
|
+
|
|
33
|
+
COPY pyproject.toml README.md ./
|
|
34
|
+
COPY src ./src
|
|
35
|
+
RUN pip install --no-cache-dir ".[bayesian]"
|
|
36
|
+
|
|
37
|
+
# cmdstan pinned to the version the reference results were produced with
|
|
38
|
+
# (CLAUDE.md: 2.39.0 on the dev machine)
|
|
39
|
+
RUN install_cmdstan --version 2.39.0 --cores 4
|
|
40
|
+
|
|
41
|
+
# compile every gallery Stan program into the image: containers start ready,
|
|
42
|
+
# and parallel workers never race the compiler (kernels.harness.precompile)
|
|
43
|
+
RUN python -c "from ibnr.kernels.harness import precompile; precompile()"
|
|
44
|
+
|
|
45
|
+
COPY scripts ./scripts
|
|
46
|
+
RUN mkdir -p analysis/results
|
|
47
|
+
|
|
48
|
+
# gold-mart release cache: mount a volume here so repeat runs skip the download
|
|
49
|
+
ENV IBNR_CACHE_DIR=/data/ibnr-cache
|
|
50
|
+
|
|
51
|
+
CMD ["python", "scripts/meyers_validation.py", "--help"]
|