catagg 0.1.0.dev0__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.
- catagg-0.1.0.dev0/CHANGELOG.md +24 -0
- catagg-0.1.0.dev0/LICENSE +21 -0
- catagg-0.1.0.dev0/PKG-INFO +188 -0
- catagg-0.1.0.dev0/README.md +153 -0
- catagg-0.1.0.dev0/docs/api.md +3 -0
- catagg-0.1.0.dev0/docs/changelog.md +1 -0
- catagg-0.1.0.dev0/docs/img/.gitkeep +0 -0
- catagg-0.1.0.dev0/docs/index.md +23 -0
- catagg-0.1.0.dev0/docs/releasing.md +70 -0
- catagg-0.1.0.dev0/docs/validation.md +17 -0
- catagg-0.1.0.dev0/pyproject.toml +135 -0
- catagg-0.1.0.dev0/src/catagg/__init__.py +5 -0
- catagg-0.1.0.dev0/src/catagg/dependence/__init__.py +1 -0
- catagg-0.1.0.dev0/src/catagg/financial/__init__.py +1 -0
- catagg-0.1.0.dev0/src/catagg/frequency/__init__.py +1 -0
- catagg-0.1.0.dev0/src/catagg/io/__init__.py +1 -0
- catagg-0.1.0.dev0/src/catagg/metrics/__init__.py +1 -0
- catagg-0.1.0.dev0/src/catagg/py.typed +0 -0
- catagg-0.1.0.dev0/src/catagg/severity/__init__.py +1 -0
- catagg-0.1.0.dev0/src/catagg/simulate/__init__.py +1 -0
- catagg-0.1.0.dev0/tests/test_packaging.py +34 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here.
|
|
4
|
+
|
|
5
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Repository scaffolding generated from the shared template: hatchling build
|
|
13
|
+
with src layout, ruff and mypy configuration, pytest setup, GitHub Actions
|
|
14
|
+
CI (lint, type-check, five-way test matrix, docs build, distribution build),
|
|
15
|
+
MkDocs Material documentation site, MIT licence, issue and pull request
|
|
16
|
+
templates.
|
|
17
|
+
- Empty subpackages fixing the module layout: `io`, `frequency`, `severity`,
|
|
18
|
+
`dependence`, `simulate`, `financial`, `metrics`.
|
|
19
|
+
- Packaging tests: version/metadata drift and `py.typed` installation.
|
|
20
|
+
- Release workflow publishing to PyPI via Trusted Publishing, with a
|
|
21
|
+
tag-versus-package-version guard, and the release procedure documented in
|
|
22
|
+
`docs/releasing.md`.
|
|
23
|
+
|
|
24
|
+
[Unreleased]: https://github.com/John-Amal/catagg/commits/main
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Amal John
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: catagg
|
|
3
|
+
Version: 0.1.0.dev0
|
|
4
|
+
Summary: Catastrophe loss aggregation, from event loss tables to net exceedance probability curves.
|
|
5
|
+
Project-URL: Homepage, https://github.com/John-Amal/catagg
|
|
6
|
+
Project-URL: Documentation, https://john-amal.github.io/catagg/
|
|
7
|
+
Project-URL: Source, https://github.com/John-Amal/catagg
|
|
8
|
+
Project-URL: Changelog, https://github.com/John-Amal/catagg/blob/main/CHANGELOG.md
|
|
9
|
+
Project-URL: Issues, https://github.com/John-Amal/catagg/issues
|
|
10
|
+
Author: Amal John
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: actuarial,catastrophe-modelling,exceedance-probability,monte-carlo,reinsurance
|
|
14
|
+
Classifier: Development Status :: 3 - Alpha
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Requires-Dist: numpy>=1.26
|
|
23
|
+
Requires-Dist: pandas>=2.1
|
|
24
|
+
Requires-Dist: pyarrow>=15.0
|
|
25
|
+
Requires-Dist: scipy>=1.11
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: mypy>=1.11; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
30
|
+
Provides-Extra: docs
|
|
31
|
+
Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
|
|
32
|
+
Requires-Dist: mkdocs<2,>=1.6; extra == 'docs'
|
|
33
|
+
Requires-Dist: mkdocstrings[python]>=0.26; extra == 'docs'
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
# catagg
|
|
37
|
+
|
|
38
|
+
**Catastrophe loss aggregation, from event loss tables to net exceedance probability curves.**
|
|
39
|
+
|
|
40
|
+
[](https://github.com/John-Amal/catagg/actions)
|
|
41
|
+
[](https://pypi.org/project/catagg/)
|
|
42
|
+
[](https://john-amal.github.io/catagg/)
|
|
43
|
+
[](LICENSE)
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## The question this answers
|
|
48
|
+
|
|
49
|
+
A reinsurer holds a portfolio exposed to several perils. The catastrophe models produce an **event loss table** — for each modelled event, an annual rate and a loss distribution. Two questions follow immediately, and every pricing, capital and reinsurance-purchase decision depends on them:
|
|
50
|
+
|
|
51
|
+
**How much loss should we expect in a bad year, and how much of it do we actually keep after our reinsurance programme responds?**
|
|
52
|
+
|
|
53
|
+
`catagg` answers both. It simulates the year loss table, builds the gross exceedance probability curves, applies the reinsurance structures in the correct order, and returns gross, ceded and net views — with the validation checks that show the answer can be trusted.
|
|
54
|
+
|
|
55
|
+

|
|
56
|
+
|
|
57
|
+
*Example portfolio: three perils, 20% quota share, a 50m xs 50m per-occurrence layer with two reinstatements, and a 100m aggregate stop-loss. The programme removes roughly two thirds of the 200-year occurrence loss — and the reinstatement cap is visible as the point where the net curve stops flattening.*
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Install
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pip install catagg
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Python 3.11+. Runtime dependencies are numpy, pandas, pyarrow and scipy — nothing else.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Quickstart
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
import catagg as ca
|
|
75
|
+
|
|
76
|
+
# 1. Load an event loss table
|
|
77
|
+
elt = ca.read_elt("portfolio.parquet")
|
|
78
|
+
|
|
79
|
+
# 2. Simulate 100,000 years
|
|
80
|
+
yelt = ca.simulate(
|
|
81
|
+
elt,
|
|
82
|
+
n_years=100_000,
|
|
83
|
+
frequency="negative_binomial", # or "poisson"
|
|
84
|
+
secondary_uncertainty=True,
|
|
85
|
+
seed=42,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# 3. Gross view
|
|
89
|
+
gross = ca.ep_curve(yelt)
|
|
90
|
+
print(gross.at_return_period([10, 50, 100, 200, 250]))
|
|
91
|
+
|
|
92
|
+
# 4. Apply a reinsurance programme
|
|
93
|
+
programme = ca.Programme([
|
|
94
|
+
ca.QuotaShare(cession=0.20),
|
|
95
|
+
ca.ExcessOfLoss(attachment=50e6, limit=50e6, reinstatements=2, basis="occurrence"),
|
|
96
|
+
ca.AggregateStopLoss(attachment=100e6, limit=100e6),
|
|
97
|
+
])
|
|
98
|
+
|
|
99
|
+
result = programme.apply(yelt)
|
|
100
|
+
net = ca.ep_curve(result.net)
|
|
101
|
+
|
|
102
|
+
# 5. Compare
|
|
103
|
+
ca.plot.ep_comparison({"Gross": gross, "Net": net}, kind="OEP")
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Everything runs from a clean clone — the example portfolio is generated synthetically by a documented generator, so there are no data dependencies and no licensing questions.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Concepts
|
|
111
|
+
|
|
112
|
+
If you already work in cat modelling, skip this. If you're reading the code to learn, this is the map.
|
|
113
|
+
|
|
114
|
+
| Object | What it is |
|
|
115
|
+
|---|---|
|
|
116
|
+
| **ELT** | Event loss table. One row per modelled event: annual rate λ, mean loss, standard deviation, exposed value. |
|
|
117
|
+
| **YELT** | Year event loss table. One row per event *occurrence* per simulated year. The engine's central output. |
|
|
118
|
+
| **YLT** | Year loss table. One row per simulated year: aggregate loss and maximum occurrence loss. |
|
|
119
|
+
| **OEP** | Occurrence exceedance probability. The distribution of the *largest single event* in a year. Drives per-occurrence cover. |
|
|
120
|
+
| **AEP** | Aggregate exceedance probability. The distribution of *total annual* loss. Drives aggregate cover and capital. |
|
|
121
|
+
| **AAL** | Average annual loss. The mean of the AEP distribution, and the cleanest check on the whole pipeline. |
|
|
122
|
+
| **TVaR** | Tail value at risk. The mean loss given exceedance of a return period. What capital regimes actually ask for. |
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## What's in the engine
|
|
127
|
+
|
|
128
|
+
**Frequency.** Poisson, or negative binomial where event clustering matters — European windstorm and hurricane seasons are both over-dispersed, and a Poisson assumption understates the aggregate tail.
|
|
129
|
+
|
|
130
|
+
**Secondary uncertainty.** Loss given occurrence is sampled from a beta distribution on `[0, exposed_value]`, fitted by moments to the ELT's mean and standard deviation, with probability mass at zero and at full exposure. Degenerate cases — zero variance, or a variance the beta support cannot accommodate — are handled explicitly rather than silently clipped.
|
|
131
|
+
|
|
132
|
+
**Dependence.** Perils are correlated through a Gaussian or Student-t copula applied to annual losses. Independence is the default; the example shows what assuming it costs you in the tail.
|
|
133
|
+
|
|
134
|
+
**Financial structures.** Applied in programme order:
|
|
135
|
+
|
|
136
|
+
- Quota share, with optional ceding commission
|
|
137
|
+
- Per-occurrence excess of loss: attachment, limit, *n* reinstatements at stated percentages, pro-rata to amount, with the aggregate cap of `limit × (1 + n)` enforced
|
|
138
|
+
- Aggregate excess of loss and stop-loss
|
|
139
|
+
- Franchise deductible
|
|
140
|
+
|
|
141
|
+
**Metrics.** OEP, AEP, AAL, TVaR at any return period, full return-period tables, and Monte Carlo standard errors on every tail statistic.
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Validation
|
|
146
|
+
|
|
147
|
+
Numerical libraries in this domain live or die on whether you can show they're right. `catagg` ships a validation suite that runs in CI and as a readable notebook:
|
|
148
|
+
|
|
149
|
+
- Simulated AAL converges to the analytic `Σ λᵢ · μᵢ`
|
|
150
|
+
- Sampled frequency moments match the specified Poisson or negative binomial
|
|
151
|
+
- Sampled severity recovers the input mean and standard deviation
|
|
152
|
+
- Ceded loss never exceeds the aggregate reinstatement cap
|
|
153
|
+
- Net + ceded = gross, exactly, in every simulated year
|
|
154
|
+
- Tail-metric standard error as a function of simulation count
|
|
155
|
+
|
|
156
|
+
That last one matters more than it looks. A 200-year return period estimated from 10,000 simulation years carries a standard error large enough to change decisions, and most workflows never report it. `catagg` reports it by default.
|
|
157
|
+
|
|
158
|
+
See [docs/validation](https://john-amal.github.io/catagg/validation/).
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Scope
|
|
163
|
+
|
|
164
|
+
**In scope:** everything above.
|
|
165
|
+
|
|
166
|
+
**Deliberately out of scope for v0.1:** hazard modelling, vulnerability curves, location-level financial terms, exposure data management, a graphical interface, cloud orchestration.
|
|
167
|
+
|
|
168
|
+
`catagg` starts where the cat model stops. If you need the full modelling stack, use [Oasis LMF](https://oasislmf.org/). If you need to understand, audit or prototype the aggregation and financial layer, use this.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Roadmap
|
|
173
|
+
|
|
174
|
+
- **v0.2** — location and policy-level terms, surplus share, reinstatement premium calculation
|
|
175
|
+
- **v0.3** — OED-compatible ELT ingest, `oasislmf` interoperability
|
|
176
|
+
- **v0.4** — event-level correlation across sub-perils, seasonality in event timing
|
|
177
|
+
|
|
178
|
+
Issues and pull requests are welcome, particularly from anyone who has implemented reinstatement logic in production and disagrees with this one.
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Citing
|
|
183
|
+
|
|
184
|
+
If you use `catagg` in published work, cite the Zenodo record: *(DOI added on first release.)*
|
|
185
|
+
|
|
186
|
+
## Licence
|
|
187
|
+
|
|
188
|
+
MIT.
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# catagg
|
|
2
|
+
|
|
3
|
+
**Catastrophe loss aggregation, from event loss tables to net exceedance probability curves.**
|
|
4
|
+
|
|
5
|
+
[](https://github.com/John-Amal/catagg/actions)
|
|
6
|
+
[](https://pypi.org/project/catagg/)
|
|
7
|
+
[](https://john-amal.github.io/catagg/)
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## The question this answers
|
|
13
|
+
|
|
14
|
+
A reinsurer holds a portfolio exposed to several perils. The catastrophe models produce an **event loss table** — for each modelled event, an annual rate and a loss distribution. Two questions follow immediately, and every pricing, capital and reinsurance-purchase decision depends on them:
|
|
15
|
+
|
|
16
|
+
**How much loss should we expect in a bad year, and how much of it do we actually keep after our reinsurance programme responds?**
|
|
17
|
+
|
|
18
|
+
`catagg` answers both. It simulates the year loss table, builds the gross exceedance probability curves, applies the reinsurance structures in the correct order, and returns gross, ceded and net views — with the validation checks that show the answer can be trusted.
|
|
19
|
+
|
|
20
|
+

|
|
21
|
+
|
|
22
|
+
*Example portfolio: three perils, 20% quota share, a 50m xs 50m per-occurrence layer with two reinstatements, and a 100m aggregate stop-loss. The programme removes roughly two thirds of the 200-year occurrence loss — and the reinstatement cap is visible as the point where the net curve stops flattening.*
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install catagg
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Python 3.11+. Runtime dependencies are numpy, pandas, pyarrow and scipy — nothing else.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Quickstart
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
import catagg as ca
|
|
40
|
+
|
|
41
|
+
# 1. Load an event loss table
|
|
42
|
+
elt = ca.read_elt("portfolio.parquet")
|
|
43
|
+
|
|
44
|
+
# 2. Simulate 100,000 years
|
|
45
|
+
yelt = ca.simulate(
|
|
46
|
+
elt,
|
|
47
|
+
n_years=100_000,
|
|
48
|
+
frequency="negative_binomial", # or "poisson"
|
|
49
|
+
secondary_uncertainty=True,
|
|
50
|
+
seed=42,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# 3. Gross view
|
|
54
|
+
gross = ca.ep_curve(yelt)
|
|
55
|
+
print(gross.at_return_period([10, 50, 100, 200, 250]))
|
|
56
|
+
|
|
57
|
+
# 4. Apply a reinsurance programme
|
|
58
|
+
programme = ca.Programme([
|
|
59
|
+
ca.QuotaShare(cession=0.20),
|
|
60
|
+
ca.ExcessOfLoss(attachment=50e6, limit=50e6, reinstatements=2, basis="occurrence"),
|
|
61
|
+
ca.AggregateStopLoss(attachment=100e6, limit=100e6),
|
|
62
|
+
])
|
|
63
|
+
|
|
64
|
+
result = programme.apply(yelt)
|
|
65
|
+
net = ca.ep_curve(result.net)
|
|
66
|
+
|
|
67
|
+
# 5. Compare
|
|
68
|
+
ca.plot.ep_comparison({"Gross": gross, "Net": net}, kind="OEP")
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Everything runs from a clean clone — the example portfolio is generated synthetically by a documented generator, so there are no data dependencies and no licensing questions.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Concepts
|
|
76
|
+
|
|
77
|
+
If you already work in cat modelling, skip this. If you're reading the code to learn, this is the map.
|
|
78
|
+
|
|
79
|
+
| Object | What it is |
|
|
80
|
+
|---|---|
|
|
81
|
+
| **ELT** | Event loss table. One row per modelled event: annual rate λ, mean loss, standard deviation, exposed value. |
|
|
82
|
+
| **YELT** | Year event loss table. One row per event *occurrence* per simulated year. The engine's central output. |
|
|
83
|
+
| **YLT** | Year loss table. One row per simulated year: aggregate loss and maximum occurrence loss. |
|
|
84
|
+
| **OEP** | Occurrence exceedance probability. The distribution of the *largest single event* in a year. Drives per-occurrence cover. |
|
|
85
|
+
| **AEP** | Aggregate exceedance probability. The distribution of *total annual* loss. Drives aggregate cover and capital. |
|
|
86
|
+
| **AAL** | Average annual loss. The mean of the AEP distribution, and the cleanest check on the whole pipeline. |
|
|
87
|
+
| **TVaR** | Tail value at risk. The mean loss given exceedance of a return period. What capital regimes actually ask for. |
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## What's in the engine
|
|
92
|
+
|
|
93
|
+
**Frequency.** Poisson, or negative binomial where event clustering matters — European windstorm and hurricane seasons are both over-dispersed, and a Poisson assumption understates the aggregate tail.
|
|
94
|
+
|
|
95
|
+
**Secondary uncertainty.** Loss given occurrence is sampled from a beta distribution on `[0, exposed_value]`, fitted by moments to the ELT's mean and standard deviation, with probability mass at zero and at full exposure. Degenerate cases — zero variance, or a variance the beta support cannot accommodate — are handled explicitly rather than silently clipped.
|
|
96
|
+
|
|
97
|
+
**Dependence.** Perils are correlated through a Gaussian or Student-t copula applied to annual losses. Independence is the default; the example shows what assuming it costs you in the tail.
|
|
98
|
+
|
|
99
|
+
**Financial structures.** Applied in programme order:
|
|
100
|
+
|
|
101
|
+
- Quota share, with optional ceding commission
|
|
102
|
+
- Per-occurrence excess of loss: attachment, limit, *n* reinstatements at stated percentages, pro-rata to amount, with the aggregate cap of `limit × (1 + n)` enforced
|
|
103
|
+
- Aggregate excess of loss and stop-loss
|
|
104
|
+
- Franchise deductible
|
|
105
|
+
|
|
106
|
+
**Metrics.** OEP, AEP, AAL, TVaR at any return period, full return-period tables, and Monte Carlo standard errors on every tail statistic.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Validation
|
|
111
|
+
|
|
112
|
+
Numerical libraries in this domain live or die on whether you can show they're right. `catagg` ships a validation suite that runs in CI and as a readable notebook:
|
|
113
|
+
|
|
114
|
+
- Simulated AAL converges to the analytic `Σ λᵢ · μᵢ`
|
|
115
|
+
- Sampled frequency moments match the specified Poisson or negative binomial
|
|
116
|
+
- Sampled severity recovers the input mean and standard deviation
|
|
117
|
+
- Ceded loss never exceeds the aggregate reinstatement cap
|
|
118
|
+
- Net + ceded = gross, exactly, in every simulated year
|
|
119
|
+
- Tail-metric standard error as a function of simulation count
|
|
120
|
+
|
|
121
|
+
That last one matters more than it looks. A 200-year return period estimated from 10,000 simulation years carries a standard error large enough to change decisions, and most workflows never report it. `catagg` reports it by default.
|
|
122
|
+
|
|
123
|
+
See [docs/validation](https://john-amal.github.io/catagg/validation/).
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Scope
|
|
128
|
+
|
|
129
|
+
**In scope:** everything above.
|
|
130
|
+
|
|
131
|
+
**Deliberately out of scope for v0.1:** hazard modelling, vulnerability curves, location-level financial terms, exposure data management, a graphical interface, cloud orchestration.
|
|
132
|
+
|
|
133
|
+
`catagg` starts where the cat model stops. If you need the full modelling stack, use [Oasis LMF](https://oasislmf.org/). If you need to understand, audit or prototype the aggregation and financial layer, use this.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Roadmap
|
|
138
|
+
|
|
139
|
+
- **v0.2** — location and policy-level terms, surplus share, reinstatement premium calculation
|
|
140
|
+
- **v0.3** — OED-compatible ELT ingest, `oasislmf` interoperability
|
|
141
|
+
- **v0.4** — event-level correlation across sub-perils, seasonality in event timing
|
|
142
|
+
|
|
143
|
+
Issues and pull requests are welcome, particularly from anyone who has implemented reinstatement logic in production and disagrees with this one.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Citing
|
|
148
|
+
|
|
149
|
+
If you use `catagg` in published work, cite the Zenodo record: *(DOI added on first release.)*
|
|
150
|
+
|
|
151
|
+
## Licence
|
|
152
|
+
|
|
153
|
+
MIT.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
--8<-- "CHANGELOG.md"
|
|
File without changes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# catagg
|
|
2
|
+
|
|
3
|
+
Catastrophe loss aggregation, from event loss tables to net exceedance
|
|
4
|
+
probability curves.
|
|
5
|
+
|
|
6
|
+
!!! warning "Pre-release"
|
|
7
|
+
|
|
8
|
+
v0.1.0 is under development. The public API described in the README is the
|
|
9
|
+
specification being built against, not yet the shipped surface.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install catagg
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Python 3.11+. Runtime dependencies are numpy, pandas, pyarrow and scipy.
|
|
18
|
+
|
|
19
|
+
## Where to start
|
|
20
|
+
|
|
21
|
+
- **[Validation](validation.md)** — the checks that show the numbers can be trusted.
|
|
22
|
+
- **[API reference](api.md)** — the public surface.
|
|
23
|
+
- **[Changelog](changelog.md)** — what changed, and when.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Releasing
|
|
2
|
+
|
|
3
|
+
`catagg` publishes to PyPI from GitHub Actions using
|
|
4
|
+
[Trusted Publishing](https://docs.pypi.org/trusted-publishers/). No API token
|
|
5
|
+
is created, stored or rotated.
|
|
6
|
+
|
|
7
|
+
## One-off setup
|
|
8
|
+
|
|
9
|
+
Done once, before the project exists on PyPI.
|
|
10
|
+
|
|
11
|
+
### 1. PyPI account
|
|
12
|
+
|
|
13
|
+
Create an account on [pypi.org](https://pypi.org) and enable two-factor
|
|
14
|
+
authentication. Uploads are refused without it.
|
|
15
|
+
|
|
16
|
+
### 2. Register a pending publisher
|
|
17
|
+
|
|
18
|
+
PyPI lets you configure a publisher for a project that does not exist yet; the
|
|
19
|
+
first successful workflow run creates the project and claims the name.
|
|
20
|
+
|
|
21
|
+
Go to **Account settings → Publishing → Add a new pending publisher** and enter:
|
|
22
|
+
|
|
23
|
+
| Field | Value |
|
|
24
|
+
|---|---|
|
|
25
|
+
| PyPI project name | `catagg` |
|
|
26
|
+
| Owner | `John-Amal` |
|
|
27
|
+
| Repository name | `catagg` |
|
|
28
|
+
| Workflow name | `release.yml` |
|
|
29
|
+
| Environment name | `pypi` |
|
|
30
|
+
|
|
31
|
+
These must match exactly. A mismatch in any field makes the OIDC token invalid
|
|
32
|
+
and the upload fails with a permissions error rather than a helpful one.
|
|
33
|
+
|
|
34
|
+
### 3. Create the GitHub environment
|
|
35
|
+
|
|
36
|
+
In the repository: **Settings → Environments → New environment**, named `pypi`.
|
|
37
|
+
|
|
38
|
+
Optionally add yourself as a required reviewer. That turns every publish into a
|
|
39
|
+
manual approval — worth it, given that an upload can never be undone.
|
|
40
|
+
|
|
41
|
+
## Claiming the name
|
|
42
|
+
|
|
43
|
+
Run the workflow manually once, from **Actions → Release → Run workflow** on
|
|
44
|
+
`main`, while the version in `src/catagg/__init__.py` is still `0.1.0.dev0`.
|
|
45
|
+
|
|
46
|
+
This uploads a pre-release, which:
|
|
47
|
+
|
|
48
|
+
- claims the name `catagg` permanently;
|
|
49
|
+
- leaves `0.1.0` free for the real release;
|
|
50
|
+
- is invisible to `pip install catagg`, since pip ignores pre-releases unless
|
|
51
|
+
`--pre` is passed.
|
|
52
|
+
|
|
53
|
+
Confirm at <https://pypi.org/project/catagg/>.
|
|
54
|
+
|
|
55
|
+
## Normal release
|
|
56
|
+
|
|
57
|
+
1. Set the version in `src/catagg/__init__.py`. It is the single source; the
|
|
58
|
+
build reads it, and a test asserts it matches the distribution metadata.
|
|
59
|
+
2. Move the `[Unreleased]` entries in `CHANGELOG.md` under the new version
|
|
60
|
+
heading with its date.
|
|
61
|
+
3. Merge to `main` and wait for CI to pass.
|
|
62
|
+
4. Tag and push: `git tag v0.1.0 && git push origin v0.1.0`.
|
|
63
|
+
5. Publish the GitHub Release for that tag. The workflow refuses to run if the
|
|
64
|
+
tag and the package version disagree.
|
|
65
|
+
6. Mint the Zenodo DOI and add it to the README's citation section.
|
|
66
|
+
|
|
67
|
+
## Irreversibility
|
|
68
|
+
|
|
69
|
+
A version, once uploaded, can never be uploaded again — deleting a release does
|
|
70
|
+
not free its filename. Check the version number before publishing, not after.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Validation
|
|
2
|
+
|
|
3
|
+
!!! note "Placeholder"
|
|
4
|
+
|
|
5
|
+
This page is a stub. It is filled by the validation notebook built at the
|
|
6
|
+
end of the v0.1.0 milestone; no results are published here yet.
|
|
7
|
+
|
|
8
|
+
The suite this page will report on, each check running in CI:
|
|
9
|
+
|
|
10
|
+
| Check | What it proves |
|
|
11
|
+
|---|---|
|
|
12
|
+
| Simulated AAL against analytic `Σ λᵢ · μᵢ` | The frequency–severity loop is unbiased |
|
|
13
|
+
| Sampled frequency moments against Poisson / negative binomial | The sampler matches the specified distribution |
|
|
14
|
+
| Sampled severity moments against the ELT input | Method-of-moments beta fitting recovers its own inputs |
|
|
15
|
+
| Ceded loss against the aggregate reinstatement cap | Reinstatement accounting never over-recovers |
|
|
16
|
+
| Net + ceded = gross, every simulated year | The financial module conserves loss exactly |
|
|
17
|
+
| Tail-metric standard error against simulation count | How noisy a given return period estimate actually is |
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.27"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "catagg"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Catastrophe loss aggregation, from event loss tables to net exceedance probability curves."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{ name = "Amal John" }]
|
|
14
|
+
keywords = [
|
|
15
|
+
"catastrophe-modelling",
|
|
16
|
+
"reinsurance",
|
|
17
|
+
"exceedance-probability",
|
|
18
|
+
"actuarial",
|
|
19
|
+
"monte-carlo",
|
|
20
|
+
]
|
|
21
|
+
classifiers = [
|
|
22
|
+
"Development Status :: 3 - Alpha",
|
|
23
|
+
"Intended Audience :: Science/Research",
|
|
24
|
+
"Programming Language :: Python :: 3.11",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
26
|
+
"Programming Language :: Python :: 3.13",
|
|
27
|
+
"Typing :: Typed",
|
|
28
|
+
"Topic :: Scientific/Engineering :: Mathematics",
|
|
29
|
+
]
|
|
30
|
+
dependencies = [
|
|
31
|
+
"numpy>=1.26",
|
|
32
|
+
"pandas>=2.1",
|
|
33
|
+
"pyarrow>=15.0",
|
|
34
|
+
"scipy>=1.11",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.optional-dependencies]
|
|
38
|
+
dev = [
|
|
39
|
+
"mypy>=1.11",
|
|
40
|
+
"pytest>=8.0",
|
|
41
|
+
"ruff>=0.6",
|
|
42
|
+
]
|
|
43
|
+
docs = [
|
|
44
|
+
"mkdocs>=1.6,<2",
|
|
45
|
+
"mkdocs-material>=9.5",
|
|
46
|
+
"mkdocstrings[python]>=0.26",
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
[project.urls]
|
|
50
|
+
Homepage = "https://github.com/John-Amal/catagg"
|
|
51
|
+
Documentation = "https://john-amal.github.io/catagg/"
|
|
52
|
+
Source = "https://github.com/John-Amal/catagg"
|
|
53
|
+
Changelog = "https://github.com/John-Amal/catagg/blob/main/CHANGELOG.md"
|
|
54
|
+
Issues = "https://github.com/John-Amal/catagg/issues"
|
|
55
|
+
|
|
56
|
+
[tool.hatch.version]
|
|
57
|
+
path = "src/catagg/__init__.py"
|
|
58
|
+
|
|
59
|
+
[tool.hatch.build.targets.wheel]
|
|
60
|
+
packages = ["src/catagg"]
|
|
61
|
+
|
|
62
|
+
[tool.hatch.build.targets.sdist]
|
|
63
|
+
include = ["src", "tests", "docs", "CHANGELOG.md", "LICENSE", "README.md"]
|
|
64
|
+
|
|
65
|
+
# ---------------------------------------------------------------------------
|
|
66
|
+
# Lint / format
|
|
67
|
+
# ---------------------------------------------------------------------------
|
|
68
|
+
[tool.ruff]
|
|
69
|
+
line-length = 100
|
|
70
|
+
target-version = "py311"
|
|
71
|
+
src = ["src", "tests"]
|
|
72
|
+
# Ruff reformats Python blocks inside Markdown. The README is the project
|
|
73
|
+
# specification and is author-owned prose; a formatter should not rewrite it.
|
|
74
|
+
extend-exclude = ["*.md"]
|
|
75
|
+
|
|
76
|
+
[tool.ruff.lint]
|
|
77
|
+
select = [
|
|
78
|
+
"A", # builtin shadowing
|
|
79
|
+
"B", # bugbear
|
|
80
|
+
"C4", # comprehensions
|
|
81
|
+
"D", # docstrings
|
|
82
|
+
"E", # pycodestyle errors
|
|
83
|
+
"F", # pyflakes
|
|
84
|
+
"I", # import order
|
|
85
|
+
"N", # naming
|
|
86
|
+
"NPY", # numpy-specific
|
|
87
|
+
"PD", # pandas-vet
|
|
88
|
+
"PT", # pytest style
|
|
89
|
+
"RUF",
|
|
90
|
+
"SIM",
|
|
91
|
+
"UP", # pyupgrade
|
|
92
|
+
"W",
|
|
93
|
+
]
|
|
94
|
+
ignore = [
|
|
95
|
+
"D105", # docstring in magic method
|
|
96
|
+
"D107", # docstring in __init__
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
[tool.ruff.lint.pydocstyle]
|
|
100
|
+
convention = "numpy"
|
|
101
|
+
|
|
102
|
+
[tool.ruff.lint.per-file-ignores]
|
|
103
|
+
"tests/*" = ["D100", "D101", "D102", "D103", "D104"]
|
|
104
|
+
# `catagg.io` is the industry-legible name for the ingest layer and is only ever
|
|
105
|
+
# reached as an absolute import, so shadowing the stdlib module name is safe.
|
|
106
|
+
"src/catagg/io/*" = ["A005"]
|
|
107
|
+
"docs/*" = ["D"]
|
|
108
|
+
|
|
109
|
+
[tool.ruff.format]
|
|
110
|
+
docstring-code-format = true
|
|
111
|
+
|
|
112
|
+
# ---------------------------------------------------------------------------
|
|
113
|
+
# Types
|
|
114
|
+
# ---------------------------------------------------------------------------
|
|
115
|
+
[tool.mypy]
|
|
116
|
+
python_version = "3.11"
|
|
117
|
+
files = ["src", "tests"]
|
|
118
|
+
strict = true
|
|
119
|
+
warn_unreachable = true
|
|
120
|
+
pretty = true
|
|
121
|
+
|
|
122
|
+
# scipy and pyarrow ship incomplete type information; numpy and pandas are
|
|
123
|
+
# checked normally. Narrow these as the stubs improve rather than widening them.
|
|
124
|
+
[[tool.mypy.overrides]]
|
|
125
|
+
module = ["scipy.*", "pyarrow.*"]
|
|
126
|
+
ignore_missing_imports = true
|
|
127
|
+
|
|
128
|
+
# ---------------------------------------------------------------------------
|
|
129
|
+
# Tests
|
|
130
|
+
# ---------------------------------------------------------------------------
|
|
131
|
+
[tool.pytest.ini_options]
|
|
132
|
+
minversion = "8.0"
|
|
133
|
+
testpaths = ["tests"]
|
|
134
|
+
addopts = "-ra --strict-markers --strict-config"
|
|
135
|
+
xfail_strict = true
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Cross-peril dependence: Gaussian and Student-t copulas over annual losses."""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Reinsurance structures: quota share, excess of loss with reinstatements, stop-loss."""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Event-count samplers: Poisson and negative binomial."""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""ELT readers, schema validation, and the Parquet YELT writer."""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""EP curves, AAL, TVaR, return-period tables and Monte Carlo standard errors."""
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Secondary uncertainty: beta-with-point-mass sampler and moment fitting."""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""The year loop: ELT to YELT to YLT."""
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Checks that the distribution is assembled correctly.
|
|
2
|
+
|
|
3
|
+
These are not smoke tests: each one fails on a specific, real packaging
|
|
4
|
+
mistake that is otherwise silent until a user hits it.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import re
|
|
10
|
+
from importlib.metadata import version as dist_version
|
|
11
|
+
from importlib.resources import files
|
|
12
|
+
|
|
13
|
+
import catagg
|
|
14
|
+
|
|
15
|
+
PEP440 = re.compile(r"^\d+\.\d+\.\d+(?:(?:a|b|rc)\d+)?(?:\.post\d+)?(?:\.dev\d+)?$")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_version_is_pep440() -> None:
|
|
19
|
+
assert PEP440.match(catagg.__version__), catagg.__version__
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_module_version_matches_distribution_metadata() -> None:
|
|
23
|
+
"""The single source of truth must survive the build.
|
|
24
|
+
|
|
25
|
+
Fails if ``__init__.__version__`` and the built metadata drift apart --
|
|
26
|
+
the usual cause of a release tagged with one number and published under
|
|
27
|
+
another.
|
|
28
|
+
"""
|
|
29
|
+
assert catagg.__version__ == dist_version("catagg")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_py_typed_marker_is_installed() -> None:
|
|
33
|
+
"""Without this file downstream type checkers silently ignore the package."""
|
|
34
|
+
assert files("catagg").joinpath("py.typed").is_file()
|