projectionmodels 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,50 @@
1
+ """
2
+ projectionmodels -- forward projection (budgeting) for a book of business
3
+ =========================================================================
4
+ Part of the OpenActuarial ecosystem. Sits beside `ratingmodels` and `lossmodels`
5
+ on top of the `actuarialpy` primitives layer, and depends only downward on
6
+ `actuarialpy` -- never sideways on another workflow package.
7
+
8
+ Rating builds a price from experience; projection takes the book AS IT IS -- stored
9
+ premiums, historical claims, membership assumptions -- and rolls it forward for
10
+ planning. The dividing line: the moment something computes what you *should* charge
11
+ rather than what the projection *is*, it belongs in `ratingmodels`, not here.
12
+
13
+ Layers
14
+ ------
15
+ PMPMProjection credibility-blended, trended, pooled claims PMPM
16
+ PremiumRollforward stored premium rolled forward by rate action / plan change
17
+ GroupProjection one group: premium + claims + renewal weighting (loop unit)
18
+ BookProjection aggregate in-force renewals + new business -> book budget
19
+
20
+ Each class computes on construction and exposes a frozen ``*Result`` dataclass via
21
+ ``.result``; a lowercase functional form (``project_group`` etc.) returns the result
22
+ directly.
23
+
24
+ Quick start
25
+ -----------
26
+ from projectionmodels import GroupProjection, BookProjection
27
+
28
+ g = GroupProjection(prospective_membership=E, seasonal_factors=s,
29
+ current_premium=..., current_member_months=...,
30
+ rate_action=0.06, plan_change=-0.02,
31
+ book_pmpm=..., claim_trend=0.06,
32
+ exp_midpoint=..., prosp_midpoint=...,
33
+ group_claims=..., group_member_months=..., group_claim_count=...,
34
+ pooling_pmpm=..., renewal_prob=0.90) # renewal likelihood from underwriting
35
+ book = BookProjection([g_1, g_2, new_biz], labels=[...])
36
+ book.loss_ratio
37
+ """
38
+ from .pmpm import PMPMProjection, PMPMResult, project_pmpm
39
+ from .premium import PremiumRollforward, PremiumResult, roll_forward_premium
40
+ from .group import (GroupProjection, GroupProjectionResult, project_group, new_business)
41
+ from .book import BookProjection, BookResult, project_book
42
+
43
+ __version__ = "0.1.0"
44
+
45
+ __all__ = [
46
+ "PMPMProjection", "PMPMResult", "project_pmpm",
47
+ "PremiumRollforward", "PremiumResult", "roll_forward_premium",
48
+ "GroupProjection", "GroupProjectionResult", "project_group", "new_business",
49
+ "BookProjection", "BookResult", "project_book",
50
+ ]
@@ -0,0 +1,65 @@
1
+ """
2
+ Book projection -- aggregate group projections into the book budget.
3
+ ====================================================================
4
+ Rolls up in-force renewals and new business into total premium, claims, and loss
5
+ ratio (by group and by month). Totals use the EXPECTED (renewal-weighted) figures,
6
+ so a group contributes in proportion to how likely it is to renew.
7
+ """
8
+ from __future__ import annotations
9
+ from dataclasses import dataclass
10
+ import numpy as np
11
+ import pandas as pd
12
+
13
+ from .group import GroupProjection, GroupProjectionResult
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class BookResult:
18
+ premium: float # expected (renewal-weighted) book premium
19
+ claims: float
20
+ loss_ratio: float
21
+ by_group: pd.DataFrame # per-group expected premium/claims/LR/renewal
22
+ monthly: pd.DataFrame # expected premium/claims by month, summed over the book
23
+
24
+
25
+ class BookProjection:
26
+ def __init__(self, projections, labels=None):
27
+ results = [p.result if isinstance(p, GroupProjection) else p for p in projections]
28
+ if not results:
29
+ raise ValueError("no projections supplied")
30
+ labels = list(labels) if labels is not None else [f"grp_{i}" for i in range(len(results))]
31
+
32
+ rows = []
33
+ prem_m = np.zeros(len(results[0].monthly))
34
+ clm_m = np.zeros_like(prem_m)
35
+ months = results[0].monthly["month"].to_numpy()
36
+ for lab, r in zip(labels, results):
37
+ rows.append({"group": lab, "premium": r.expected_premium, "claims": r.expected_claims,
38
+ "loss_ratio": (r.expected_claims / r.expected_premium
39
+ if r.expected_premium else np.nan),
40
+ "renewal_prob": r.renewal_prob})
41
+ prem_m += r.monthly["premium"].to_numpy() * r.renewal_prob
42
+ clm_m += r.monthly["claims"].to_numpy() * r.renewal_prob
43
+
44
+ by_group = pd.DataFrame(rows)
45
+ monthly = pd.DataFrame({"month": months, "premium": prem_m, "claims": clm_m})
46
+ monthly["loss_ratio"] = monthly["claims"] / monthly["premium"]
47
+ P, C = float(by_group["premium"].sum()), float(by_group["claims"].sum())
48
+ self.result = BookResult(premium=P, claims=C, loss_ratio=C / P,
49
+ by_group=by_group, monthly=monthly)
50
+
51
+ @property
52
+ def premium(self): return self.result.premium
53
+ @property
54
+ def claims(self): return self.result.claims
55
+ @property
56
+ def loss_ratio(self): return self.result.loss_ratio
57
+ @property
58
+ def by_group(self): return self.result.by_group
59
+ @property
60
+ def monthly(self): return self.result.monthly
61
+
62
+
63
+ def project_book(projections, labels=None) -> BookResult:
64
+ """Functional form: returns the :class:`BookResult`."""
65
+ return BookProjection(projections, labels=labels).result
@@ -0,0 +1,105 @@
1
+ """
2
+ Group projection -- one group's forward roll, premium and claims together.
3
+ ==========================================================================
4
+ Composes the premium roll-forward and the credibility-blended claims projection on
5
+ the given monthly membership, then weights both by the renewal probability. This is
6
+ the unit you loop over the in-force book (see :mod:`projectionmodels.book`).
7
+
8
+ Renewal probability weights premium and claims equally (a lapsed group books
9
+ neither), so the projected loss ratio is unaffected by it.
10
+ """
11
+ from __future__ import annotations
12
+ from dataclasses import dataclass
13
+ import numpy as np
14
+ import pandas as pd
15
+
16
+ from .pmpm import PMPMProjection, PMPMResult
17
+ from .premium import PremiumRollforward, PremiumResult
18
+
19
+
20
+ @dataclass(frozen=True)
21
+ class GroupProjectionResult:
22
+ monthly: pd.DataFrame # month, member_months, premium, claims (conditional on renewal)
23
+ premium: float # conditional on renewal
24
+ claims: float
25
+ loss_ratio: float
26
+ renewal_prob: float
27
+ expected_premium: float # renewal-weighted
28
+ expected_claims: float
29
+ pmpm: PMPMResult
30
+ premium_detail: PremiumResult
31
+
32
+
33
+ class GroupProjection:
34
+ def __init__(self, *, prospective_membership, seasonal_factors=None,
35
+ # premium (from DB)
36
+ current_premium, current_member_months, rate_action=0.0, plan_change=0.0,
37
+ # claims (group from DB + book)
38
+ book_pmpm, claim_trend, exp_midpoint, prosp_midpoint,
39
+ group_pmpm=None, group_claims=None, group_member_months=None,
40
+ group_claim_count=None, credibility=None, full_credibility_claims=1082.0,
41
+ pooling_pmpm=0.0, plan_affects_claims=True,
42
+ # renewal likelihood -- supplied (e.g. from underwriting), not modelled here
43
+ renewal_prob=1.0):
44
+ E = np.asarray(prospective_membership, float)
45
+ s = np.ones_like(E) if seasonal_factors is None else np.asarray(seasonal_factors, float)
46
+ plan_factor = (1.0 + plan_change) if plan_affects_claims else 1.0
47
+
48
+ prem = PremiumRollforward(current_premium=current_premium,
49
+ current_member_months=current_member_months,
50
+ rate_action=rate_action, plan_change=plan_change)
51
+ pmpm = PMPMProjection(book_pmpm=book_pmpm, claim_trend=claim_trend,
52
+ exp_midpoint=exp_midpoint, prosp_midpoint=prosp_midpoint,
53
+ group_pmpm=group_pmpm, group_claims=group_claims,
54
+ group_member_months=group_member_months,
55
+ group_claim_count=group_claim_count, credibility=credibility,
56
+ full_credibility_claims=full_credibility_claims,
57
+ plan_factor=plan_factor, pooling_pmpm=pooling_pmpm)
58
+
59
+ premium_m = prem.premium(E)
60
+ claims_m = pmpm.claims(E, s)
61
+ monthly = pd.DataFrame({"month": np.arange(1, len(E) + 1),
62
+ "member_months": E.round().astype(int),
63
+ "premium": premium_m, "claims": claims_m})
64
+ P, C = float(premium_m.sum()), float(claims_m.sum())
65
+ self.result = GroupProjectionResult(
66
+ monthly=monthly, premium=P, claims=C, loss_ratio=C / P, renewal_prob=renewal_prob,
67
+ expected_premium=renewal_prob * P, expected_claims=renewal_prob * C,
68
+ pmpm=pmpm.result, premium_detail=prem.result)
69
+
70
+ # convenient pass-through to the common result fields
71
+ @property
72
+ def monthly(self): return self.result.monthly
73
+ @property
74
+ def premium(self): return self.result.premium
75
+ @property
76
+ def claims(self): return self.result.claims
77
+ @property
78
+ def loss_ratio(self): return self.result.loss_ratio
79
+ @property
80
+ def expected_premium(self): return self.result.expected_premium
81
+ @property
82
+ def expected_claims(self): return self.result.expected_claims
83
+ @property
84
+ def renewal_prob(self): return self.result.renewal_prob
85
+
86
+
87
+ def project_group(**kwargs) -> GroupProjectionResult:
88
+ """Functional form: returns the :class:`GroupProjectionResult`."""
89
+ return GroupProjection(**kwargs).result
90
+
91
+
92
+ def new_business(*, book_pmpm, claim_trend, exp_midpoint, prosp_midpoint,
93
+ prospective_membership, manual_premium_pmpm, seasonal_factors=None,
94
+ close_ratio=1.0, plan_change=0.0, pooling_pmpm=0.0) -> GroupProjectionResult:
95
+ """A sold-but-new case: no experience, so claims are fully manual (credibility 0)
96
+ and premium is the manual/target rate on projected membership; `close_ratio`
97
+ plays the role of the renewal probability."""
98
+ E = np.asarray(prospective_membership, float)
99
+ return GroupProjection(
100
+ prospective_membership=E, seasonal_factors=seasonal_factors,
101
+ current_premium=manual_premium_pmpm * E.sum(), current_member_months=E.sum(),
102
+ rate_action=0.0, plan_change=plan_change,
103
+ book_pmpm=book_pmpm, claim_trend=claim_trend, exp_midpoint=exp_midpoint,
104
+ prosp_midpoint=prosp_midpoint, group_pmpm=book_pmpm, credibility=0.0,
105
+ pooling_pmpm=pooling_pmpm, renewal_prob=close_ratio).result
@@ -0,0 +1,70 @@
1
+ """
2
+ PMPM projection -- the claims engine.
3
+ =====================================
4
+ Credibility-blends the group's own PMPM with the book PMPM, trends and
5
+ plan-adjusts it, and adds a large-claim pooling load:
6
+
7
+ Z from the group's claim count (limited fluctuation) unless supplied
8
+ blended = Z * group_pmpm + (1 - Z) * book_pmpm
9
+ projected = blended * trend * plan_factor + pooling_pmpm * trend
10
+
11
+ `projected_pmpm` is a rate per member-month. Call `.claims(membership, seasonal)`
12
+ to turn it into projected claim dollars by month; seasonality (factors averaging 1)
13
+ redistributes across months without changing the annual total.
14
+ """
15
+ from __future__ import annotations
16
+ from dataclasses import dataclass
17
+ import numpy as np
18
+ import actuarialpy as ap
19
+ from actuarialpy import credibility_weighted_estimate
20
+
21
+
22
+ @dataclass(frozen=True)
23
+ class PMPMResult:
24
+ group_pmpm: float
25
+ book_pmpm: float
26
+ credibility: float
27
+ blended_pmpm: float
28
+ trend_factor: float
29
+ plan_factor: float
30
+ pooling_pmpm: float
31
+ projected_pmpm: float
32
+
33
+
34
+ class PMPMProjection:
35
+ def __init__(self, *, book_pmpm, claim_trend, exp_midpoint, prosp_midpoint,
36
+ group_pmpm=None, group_claims=None, group_member_months=None,
37
+ group_claim_count=None, credibility=None, full_credibility_claims=1082.0,
38
+ plan_factor=1.0, pooling_pmpm=0.0):
39
+ if group_pmpm is None:
40
+ if group_claims is None or group_member_months is None:
41
+ raise ValueError("supply group_pmpm, or group_claims and group_member_months")
42
+ group_pmpm = ap.pure_premium(group_claims, group_member_months)
43
+ if credibility is None:
44
+ if group_claim_count is None:
45
+ raise ValueError("supply credibility, or group_claim_count to derive it")
46
+ credibility = min(float(ap.limited_fluctuation_z(group_claim_count, full_credibility_claims)), 1.0)
47
+
48
+ tf = float(ap.midpoint_trend_factor(exp_midpoint, prosp_midpoint, claim_trend))
49
+ blended = float(credibility_weighted_estimate(group_pmpm, book_pmpm, credibility))
50
+ projected = blended * tf * plan_factor + pooling_pmpm * tf
51
+ self.result = PMPMResult(
52
+ group_pmpm=float(group_pmpm), book_pmpm=float(book_pmpm),
53
+ credibility=float(credibility), blended_pmpm=blended, trend_factor=tf,
54
+ plan_factor=float(plan_factor), pooling_pmpm=float(pooling_pmpm),
55
+ projected_pmpm=float(projected))
56
+
57
+ @property
58
+ def projected_pmpm(self) -> float:
59
+ return self.result.projected_pmpm
60
+
61
+ def claims(self, membership, seasonal_factors=None):
62
+ """Projected claim dollars by prospective month."""
63
+ E = np.asarray(membership, float)
64
+ s = np.ones_like(E) if seasonal_factors is None else np.asarray(seasonal_factors, float)
65
+ return self.result.projected_pmpm * E * s
66
+
67
+
68
+ def project_pmpm(**kwargs) -> PMPMResult:
69
+ """Functional form: returns the :class:`PMPMResult`."""
70
+ return PMPMProjection(**kwargs).result
@@ -0,0 +1,45 @@
1
+ """
2
+ Premium roll-forward -- the stored premium projected by known factors.
3
+ ======================================================================
4
+ Premiums come from the database; they are NOT rebuilt from loss experience (that is
5
+ `ratingmodels`). This just rolls the stored figure forward:
6
+
7
+ projected_pmpm = (current_premium / current_member_months)
8
+ * (1 + rate_action) * (1 + plan_change)
9
+
10
+ Premium is level per member-month (it earns evenly), so `.premium(membership)`
11
+ scales by membership with no seasonal shape -- unlike claims.
12
+ """
13
+ from __future__ import annotations
14
+ from dataclasses import dataclass
15
+ import numpy as np
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class PremiumResult:
20
+ current_pmpm: float
21
+ rate_action: float
22
+ plan_change: float
23
+ projected_pmpm: float
24
+
25
+
26
+ class PremiumRollforward:
27
+ def __init__(self, *, current_premium, current_member_months,
28
+ rate_action=0.0, plan_change=0.0):
29
+ current_pmpm = current_premium / current_member_months
30
+ projected = current_pmpm * (1.0 + rate_action) * (1.0 + plan_change)
31
+ self.result = PremiumResult(float(current_pmpm), float(rate_action),
32
+ float(plan_change), float(projected))
33
+
34
+ @property
35
+ def projected_pmpm(self) -> float:
36
+ return self.result.projected_pmpm
37
+
38
+ def premium(self, membership):
39
+ """Projected premium dollars by prospective month (level per member-month)."""
40
+ return self.result.projected_pmpm * np.asarray(membership, float)
41
+
42
+
43
+ def roll_forward_premium(**kwargs) -> PremiumResult:
44
+ """Functional form: returns the :class:`PremiumResult`."""
45
+ return PremiumRollforward(**kwargs).result
@@ -0,0 +1,127 @@
1
+ Metadata-Version: 2.4
2
+ Name: projectionmodels
3
+ Version: 0.1.0
4
+ Summary: Forward projection (budgeting) for a book of business: premium roll-forward and credibility-blended, trended, seasonalized claims. Part of the OpenActuarial ecosystem.
5
+ Project-URL: Homepage, https://github.com/OpenActuarial/projectionmodels
6
+ Project-URL: Documentation, https://openactuarial.org/projectionmodels.html
7
+ Project-URL: Repository, https://github.com/OpenActuarial/projectionmodels
8
+ Project-URL: Issues, https://github.com/OpenActuarial/projectionmodels/issues
9
+ Author: Michael Bryant
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: PMPM,actuarial,budgeting,forecasting,insurance,loss ratio,projection
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Financial and Insurance Industry
15
+ Classifier: Intended Audience :: Science/Research
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3 :: Only
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Topic :: Office/Business :: Financial
25
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
26
+ Requires-Python: >=3.10
27
+ Requires-Dist: actuarialpy>=0.40
28
+ Requires-Dist: numpy>=1.23
29
+ Requires-Dist: pandas>=1.5
30
+ Provides-Extra: dev
31
+ Requires-Dist: build>=1; extra == 'dev'
32
+ Requires-Dist: hypothesis>=6; extra == 'dev'
33
+ Requires-Dist: pytest>=7; extra == 'dev'
34
+ Requires-Dist: twine>=5; extra == 'dev'
35
+ Description-Content-Type: text/markdown
36
+
37
+ # projectionmodels
38
+
39
+ [![CI](https://github.com/OpenActuarial/projectionmodels/actions/workflows/ci.yml/badge.svg)](https://github.com/OpenActuarial/projectionmodels/actions/workflows/ci.yml) [![PyPI](https://img.shields.io/pypi/v/projectionmodels)](https://pypi.org/project/projectionmodels/)
40
+
41
+ Forward projection (budgeting) for a book of business — part of the
42
+ [OpenActuarial](https://openactuarial.org) ecosystem.
43
+
44
+ `ratingmodels` builds a price from experience. `projectionmodels` does the opposite
45
+ direction: it takes the book **as it is** — stored premiums, historical claims, and
46
+ membership assumptions — and rolls it forward for planning and budgeting. It sits
47
+ beside `ratingmodels` and `lossmodels` on top of the `actuarialpy` primitives layer,
48
+ and depends only **downward** on `actuarialpy` — never sideways on another workflow
49
+ package.
50
+
51
+ ## Install
52
+
53
+ ```bash
54
+ pip install projectionmodels
55
+ ```
56
+
57
+ ## Layers
58
+
59
+ | object | role |
60
+ | --- | --- |
61
+ | `PMPMProjection` | credibility-blended, trended, plan-adjusted claims PMPM with a pooling load |
62
+ | `PremiumRollforward` | stored premium rolled forward by rate action and plan change |
63
+ | `GroupProjection` | one group: premium + claims + renewal weighting — the unit you loop over the book |
64
+ | `BookProjection` | aggregate in-force renewals + new business into the book budget |
65
+
66
+ Each class computes on construction and exposes a frozen `*Result` dataclass via
67
+ `.result`; a lowercase functional form (`project_group`, `project_book`, …) returns
68
+ the result directly.
69
+
70
+ ## What it computes
71
+
72
+ **Premium** is a stored value rolled forward (not rebuilt from experience), level
73
+ per member-month:
74
+
75
+ ```
76
+ prem_pmpm* = (current_premium / current_member_months) · (1 + rate_action) · (1 + plan_change)
77
+ ```
78
+
79
+ **Claims** are the actuarial piece — a credibility blend of the group's own PMPM and
80
+ the book PMPM, trended, plan-adjusted, and seasonalised onto the given membership:
81
+
82
+ ```
83
+ Z = limited-fluctuation credibility from the group's claim count
84
+ blended = Z · group_pmpm + (1 − Z) · book_pmpm
85
+ projected = blended · trend · plan_factor + pooling_pmpm · trend
86
+ claims_m = projected · membership_m · seasonal_m
87
+ ```
88
+
89
+ **Renewal probability** is supplied per group (e.g. from underwriting) via
90
+ `renewal_prob` — it is an input, not something this package models. It weights
91
+ premium and claims equally (a lapsed group books neither), so the projected loss
92
+ ratio is unaffected. New business is the same `GroupProjection` with
93
+ `group_pmpm = book_pmpm`, `credibility = 0`, and `renewal_prob = close_ratio`.
94
+
95
+ ## Quick start
96
+
97
+ ```python
98
+ import numpy as np, pandas as pd
99
+ from projectionmodels import GroupProjection, BookProjection
100
+
101
+ g = GroupProjection(
102
+ prospective_membership=np.full(12, 1900.0), seasonal_factors=season_factors,
103
+ current_premium=4_500_000, current_member_months=21_600,
104
+ rate_action=0.06, plan_change=-0.02,
105
+ book_pmpm=180.0, claim_trend=0.06,
106
+ exp_midpoint=pd.Timestamp("2025-07-01"), prosp_midpoint=pd.Timestamp("2027-07-01"),
107
+ group_claims=3_800_000, group_member_months=21_600, group_claim_count=6_000,
108
+ pooling_pmpm=8.0, renewal_prob=0.90) # renewal likelihood from underwriting
109
+
110
+ book = BookProjection([g, ...], labels=["GroupA", ...])
111
+ book.loss_ratio # expected book loss ratio
112
+ book.by_group # per-group expected premium / claims / LR
113
+ book.monthly # book premium & claims by month
114
+ ```
115
+
116
+ ## Built on `actuarialpy`
117
+
118
+ This package adds no primitives of its own — it composes existing `actuarialpy`
119
+ primitives and depends only downward:
120
+
121
+ - `credibility_weighted_estimate` — the credibility blend, `Z·group + (1−Z)·book`
122
+ - `midpoint_trend_factor` — trend to the prospective midpoint
123
+ - `seasonality_factors` / `apply_seasonality` — monthly seasonality
124
+ - `pure_premium` — PMPM
125
+
126
+ `pool_losses` / `excess_over_threshold` in `actuarialpy` cap large claims when you
127
+ derive the pooling PMPM upstream.
@@ -0,0 +1,9 @@
1
+ projectionmodels/__init__.py,sha256=ZU3bormnPaifAbIDpKc_-3XhdovhpbdLJ6a690brwYk,2485
2
+ projectionmodels/book.py,sha256=eUSmccCLoHnUJr0roZr4bdrQTmyup6j74kaYMIKkbUM,2825
3
+ projectionmodels/group.py,sha256=B0PzQCJZvYlp9iCax8vJ2Z4ez3tRVVjIXUCFglAOPBY,5246
4
+ projectionmodels/pmpm.py,sha256=RCMnET4E-SnO-ofMT4V--vgxiunPcwfHvQjHV6_RnqY,3024
5
+ projectionmodels/premium.py,sha256=Hx3lVm5XnTqSKB9MQ9ljpACy-CI7CnJQQMw-uhXWiZo,1687
6
+ projectionmodels-0.1.0.dist-info/METADATA,sha256=SNoy0JitwJu50mwsK8bxKngtUC8WUnnHQnov0QQ1_8g,5631
7
+ projectionmodels-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
8
+ projectionmodels-0.1.0.dist-info/licenses/LICENSE,sha256=2hvamn9mVK3WmUG9GyH0XLwvFoEkowbxsu4AZeCgf-g,1070
9
+ projectionmodels-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 OpenActuarial
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.