# modelx: pseudo-python
# This file is part of a modelx model.
# It can be imported as a Python module, but functions defined herein
# are model formulas and may not be executable as standard Python.
"""Policy attributes and policy values.
This Space holds policy attributes and policy-level values used by
:mod:`~annuallife.TradLife_A.Projection` and its base spaces.
The main role of this Space is to associate product specs sourced
from :mod:`~annuallife.TradLife_A.InputData` with the model points
held in
:func:`~annuallife.TradLife_A.InputData.policy_data`, and to expose
the resulting per-policy values as 1-D :mod:`numpy` arrays whose
layout matches the rows of ``policy_data``. Callers therefore index
into them with the integer policy index ``idx``.
The Cells fall into two groups by their data source:
* **Model point attributes**, such as :func:`product`,
:func:`issue_age` and :func:`sum_assured`, read a column directly
from :func:`~annuallife.TradLife_A.InputData.policy_data` and
convert it to a NumPy array.
* **Product-level values**, such as :func:`int_rate`,
:func:`table_id` and :func:`load_acq_sa_param1`, look up a column
in :func:`~annuallife.TradLife_A.InputData.product_spec` keyed by
product attributes (e.g. ``Product``, ``PolType``, ``Gen``) and
reindex it onto the rows of ``policy_data`` before converting it
to a NumPy array. These Cells are typically used to build the
loadings and rates consumed by
:func:`~annuallife.TradLife_A.BaseProj.gross_prem_rate`.
Both groups end with the same array-conversion step. The reindexing
and conversion are performed by helper Cells inherited from the
:mod:`~annuallife.TradLife_A.Utilities` base Space:
* :func:`~annuallife.TradLife_A.Utilities.map_to_policies` reindexes
a ``Series`` keyed by lookup columns onto the rows of
``policy_data``, so the result has one entry per policy.
* :func:`~annuallife.TradLife_A.Utilities.pandas_to_array` then
converts that ``Series`` into a NumPy array when
:attr:`~annuallife.TradLife_A.Utilities.return_array` is ``True``
(the default). When ``return_array`` is ``False`` the pandas
object is passed through unchanged, which is convenient for
inspection and debugging.
The two pipelines are illustrated below:
.. mermaid::
graph LR
A1["policy_data()['col']<br/>pandas Series<br/>indexed by Policy"]
A2["product_spec(name)<br/>pandas Series keyed by<br/>(Product, PolType, Gen)"]
A2 --> B["map_to_policies<br/>reindex onto<br/>policy_data rows"]
A1 --> C["pandas_to_array<br/>convert when<br/>return_array=True"]
B --> C
C --> D["1-D NumPy array<br/>indexed by policy idx"]
For example, :func:`issue_age` is implemented as
``pandas_to_array(input_data.policy_data()['IssueAge'])`` (top
branch), while :func:`load_acq_sa_param1` is implemented as
``pandas_to_array(map_to_policies(input_data.product_spec('LoadAcqSAParam1')))``
(bottom branch).
The steps can also be executed individually on a console, which is
useful for inspecting the intermediate pandas objects::
>>> pol = m.PolicyAttrs
>>> # Model-point attribute: pick a column from policy_data
>>> s = pol.input_data.policy_data()['IssueAge']
>>> s.head()
Policy
1 30
2 30
3 31
4 31
5 32
Name: IssueAge, dtype: int64
>>> len(s)
300
>>> # Convert to a 1-D NumPy array (return_array is True)
>>> pol.pandas_to_array(s)
array([30, 30, 31, ..., 78, 79, 79])
>>> # Product-level value: look up a column in product_spec
>>> ps = pol.input_data.product_spec('LoadAcqSAParam1')
>>> ps
Product
TERM 0.00
WL 0.02
ENDW 0.02
Name: LoadAcqSAParam1, dtype: float64
>>> # Reindex onto the rows of policy_data
>>> mapped = pol.map_to_policies(ps)
>>> mapped.head()
Policy
1 0.0
2 0.0
3 0.0
4 0.0
5 0.0
Name: LoadAcqSAParam1, dtype: float64
>>> len(mapped)
300
>>> # Convert to a 1-D NumPy array
>>> pol.pandas_to_array(mapped)
array([0. , 0. , 0. , ..., 0.02, 0.02, 0.02])
Composing these calls is exactly what :func:`issue_age` and
:func:`load_acq_sa_param1` return.
Parameters and References
-------------------------
Attributes:
input_data: Alias for :mod:`~annuallife.TradLife_A.InputData`.
Per-policy attributes are read from the ``PolicyData`` range
in *input.xlsx* via
:func:`~annuallife.TradLife_A.InputData.policy_data`,
and product specs from
:func:`~annuallife.TradLife_A.InputData.product_spec`.
prem_term: Alias for :func:`policy_term`.
.. rubric:: Inherited helpers
Inherited from :mod:`~annuallife.TradLife_A.Utilities`:
* :func:`~annuallife.TradLife_A.Utilities.pandas_to_array`
* :func:`~annuallife.TradLife_A.Utilities.map_to_policies`
* :attr:`~annuallife.TradLife_A.Utilities.return_array`
Cells Summary
-------------
Policy Attributes
^^^^^^^^^^^^^^^^^
Model point attributes read directly from
:func:`~annuallife.TradLife_A.InputData.policy_data` for each policy.
.. autosummary::
~product
~policy_type
~sex
~issue_age
~prem_freq
~policy_term
~policy_count
~sum_assured
~gen
~channel
~duration
Product Bases
^^^^^^^^^^^^^
Per-policy interest rate and mortality table identifiers, selected by
rate basis (premium or valuation).
.. autosummary::
~int_rate
~table_id
Loadings
^^^^^^^^
Per-policy acquisition and maintenance loadings used by
:func:`~annuallife.TradLife_A.BaseProj.gross_prem_rate`, together with
the raw ``ProductSpecTable`` parameters they are built from.
.. autosummary::
~load_acq_sa
~load_acq_sa_param1
~load_acq_sa_param2
~load_maint_prem
~load_maint_prem_param1
~load_maint_prem_param2
~load_maint_prem_waiver_prem
~load_maint_sa
~load_maint_sa2
Surrender Charges
^^^^^^^^^^^^^^^^^
The per-policy initial surrender charge rate and the raw
``ProductSpecTable`` parameters it is built from.
.. autosummary::
~init_surr_charge
~surr_charge_param1
~surr_charge_param2
Misc
^^^^
Placeholder cells reserved for future use.
.. warning::
:func:`gross_prem_table`, :func:`reserve_rate` and
:func:`uern_prem_rate` are placeholders that currently return
``None`` and are to be implemented.
.. autosummary::
~gross_prem_table
~reserve_rate
~uern_prem_rate
"""
from modelx.serialize.jsonvalues import *
_formula = None
_bases = [
".Utilities"
]
_allow_none = None
_spaces = []
# ---------------------------------------------------------------------------
# Cells
[docs]
def gross_prem_table():
"""Gross premium table"""
return None
[docs]
def init_surr_charge():
"""Initial Surrender Charge Rate"""
param1 = surr_charge_param1()
param2 = surr_charge_param2()
return param1 + param2 * np.minimum(policy_term() / 10, 1)
[docs]
def int_rate(basis):
"""Interest Rate"""
col = {
RateBasisID.PREM: 'IntRatePrem',
RateBasisID.VAL: 'IntRateVal'}.get(basis, None)
return pandas_to_array(
map_to_policies(input_data.product_spec(col)))
[docs]
def load_acq_sa():
"""Acquisition Loading per Sum Assured"""
param1 = load_acq_sa_param1()
param2 = load_acq_sa_param2()
return param1 + param2 * np.minimum(policy_term() / 10, 1)
[docs]
def load_maint_prem():
"""Maintenance Loading per Gross Premium"""
param1 = load_maint_prem_param1()
param2 = load_maint_prem_param2()
return np.where(np.isnan(param1), (param2 + np.minimum(10, policy_term())) / 100, param1)
[docs]
def load_maint_prem_waiver_prem():
"""Maintenance Loading per Gross Premium for Premium Waiver"""
table = input_data.prem_waiver_cost()
bins = [-np.inf] + list(table.keys())[:-1] + [np.inf]
vals = list(table.values())
return pandas_to_array(pd.cut(
input_data.policy_data()['PolicyTerm'],
bins=bins,
labels=vals,
right=False, # left-closed: [x, y)
))
[docs]
def load_maint_sa():
"""Maintenance Loading per Sum Assured during Premium Payment"""
return pandas_to_array(
map_to_policies(input_data.product_spec('LoadMaintSA')))
[docs]
def load_maint_sa2():
"""Maintenance Loading per Sum Assured during Premium Payment"""
return pandas_to_array(
map_to_policies(input_data.product_spec('LoadMaintSA2')))
[docs]
def reserve_rate():
"""Valuation Reserve Rate per Sum Assured"""
return None
[docs]
def table_id(basis):
"""Mortality Table ID"""
col = {
RateBasisID.PREM: 'MortTablePrem',
RateBasisID.VAL: 'MortTableVal'}.get(basis, None)
return pandas_to_array(
map_to_policies(input_data.product_spec(col)))
[docs]
def uern_prem_rate():
"""Unearned Premium Rate"""
return None
[docs]
def product():
"""Per-policy product type as a :mod:`~annuallife.TradLife_A.Enums.ProductID` code."""
return pandas_to_array(input_data.policy_data()['Product'].map(lambda s: getattr(ProductID, s)))
[docs]
def policy_type():
"""Per-policy policy type from the ``PolType`` column of :func:`~annuallife.TradLife_A.InputData.policy_data`."""
return pandas_to_array(input_data.policy_data()['PolType'])
[docs]
def gen():
"""Per-policy generation (cohort) identifier."""
return PolicyData[idx, 'Gen']
[docs]
def channel():
"""Per-policy distribution channel."""
return PolicyData[idx, 'Channel']
[docs]
def sex():
"""Per-policy sex as a :mod:`~annuallife.TradLife_A.Enums.SexID` code."""
return pandas_to_array(input_data.policy_data()['Sex'].map(lambda s: getattr(SexID, s)))
[docs]
def duration():
"""Per-policy elapsed policy duration in years."""
return PolicyData[idx, 'Duration']
[docs]
def issue_age():
"""Per-policy issue age in years."""
return pandas_to_array(input_data.policy_data()['IssueAge'])
[docs]
def prem_freq():
"""Per-policy premium payment frequency (number of payments per year)."""
return pandas_to_array(input_data.policy_data()['PremFreq'])
[docs]
def policy_term():
"""Per-policy policy term in years."""
return pandas_to_array(input_data.policy_data()['PolicyTerm'])
[docs]
def policy_count():
"""Per-policy number of policies in the model point."""
return pandas_to_array(input_data.policy_data()['PolicyCount'])
[docs]
def sum_assured():
"""Per-policy sum assured."""
return pandas_to_array(input_data.policy_data()['SumAssured'])
[docs]
def load_acq_sa_param1():
"""Per-policy ``LoadAcqSAParam1`` parameter from ``ProductSpecTable``."""
return pandas_to_array(
map_to_policies(input_data.product_spec('LoadAcqSAParam1')))
[docs]
def load_acq_sa_param2():
"""Per-policy ``LoadAcqSAParam2`` parameter from ``ProductSpecTable``."""
return pandas_to_array(
map_to_policies(input_data.product_spec('LoadAcqSAParam2')))
[docs]
def load_maint_prem_param1():
"""Per-policy ``LoadMaintPremParam1`` parameter from ``ProductSpecTable``."""
return pandas_to_array(
map_to_policies(input_data.product_spec('LoadMaintPremParam1')))
[docs]
def load_maint_prem_param2():
"""Per-policy ``LoadMaintPremParam2`` parameter from ``ProductSpecTable``."""
return pandas_to_array(
map_to_policies(input_data.product_spec('LoadMaintPremParam2')))
[docs]
def surr_charge_param1():
"""Per-policy ``SurrChargeParam1`` parameter from ``ProductSpecTable``."""
return pandas_to_array(
map_to_policies(input_data.product_spec('SurrChargeParam1')))
[docs]
def surr_charge_param2():
"""Per-policy ``SurrChargeParam2`` parameter from ``ProductSpecTable``."""
return pandas_to_array(
map_to_policies(input_data.product_spec('SurrChargeParam2')))
# ---------------------------------------------------------------------------
# References
input_data = ("Interface", ("..", "InputData"), "auto")
prem_term = ("Interface", (".", "policy_term"), "auto")