Source code for ratingmodels.renewal

r"""Renewal actions: turn an indicated rate into a charged renewal rate.

A renewal action applies the indicated change, then the filed constraints
(caps, floors, rounding), and reports the realised change. A row-level
helper re-rates a census or schedule under new relativities and rolls up to
a book total.
"""
from __future__ import annotations

from dataclasses import dataclass

import numpy as np
import pandas as pd

from ._utils import product, require_positive
from .constraints import apply_cap, round_rate


[docs] @dataclass class RenewalAction: """Result of :func:`renew`.""" current_rate: float indicated_rate: float proposed_rate: float # after caps/floors and rounding indicated_change: float proposed_change: float capped: bool def to_dict(self) -> dict: return { "current_rate": self.current_rate, "indicated_rate": self.indicated_rate, "proposed_rate": self.proposed_rate, "indicated_change": self.indicated_change, "proposed_change": self.proposed_change, "capped": self.capped, }
[docs] def renew( current_rate: float, indicated_rate: float, cap: float | None = None, floor: float | None = None, round_to: int | None = 2, ) -> RenewalAction: """Apply caps/floors (and optional rounding) to an indicated rate.""" require_positive(current_rate, "current_rate") require_positive(indicated_rate, "indicated_rate") indicated_change = indicated_rate / current_rate - 1.0 proposed = apply_cap(current_rate, indicated_rate, cap=cap, floor=floor) if round_to is not None: proposed = round_rate(proposed, round_to) proposed_change = proposed / current_rate - 1.0 capped = not np.isclose(proposed, indicated_rate, rtol=1e-9, atol=1e-9) return RenewalAction( current_rate=float(current_rate), indicated_rate=float(indicated_rate), proposed_rate=float(proposed), indicated_change=float(indicated_change), proposed_change=float(proposed_change), capped=bool(capped), )
[docs] def unit_level_renewal( census: pd.DataFrame, base_rate: float, factor_cols: list[str], count_col: str = "count", ) -> pd.DataFrame: """Re-rate each census row as ``base_rate * product(factor_cols)``. Returns the census with ``unit_rate`` and ``premium`` columns; the group total is the sum of ``premium``. """ require_positive(base_rate, "base_rate") out = census.copy() rates = [] for _, row in out.iterrows(): rates.append(base_rate * product(row[c] for c in factor_cols)) out["unit_rate"] = rates out["premium"] = out["unit_rate"] * out[count_col].astype(float) return out