"""Policy data and attributes
This Space contains policy data and Cells associated with
policy attributes and policy values.
.. rubric:: References
Attributes:
    PolicyData: `PandasData`_ object holding model point data as a pandas
        DataFrame read from the model point file.
    ProductSpec: `ExcelRange`_ object which is a dict like object for associating
        product spec parameters to model point keys.
    LifeTable: Alias for :mod:`~fastlife.model.LifeTable`
    PremTerm: Alias for :func:`PolicyTerm`
.. _PandasData:
   https://docs.modelx.io/en/latest/reference/dataclient.html#pandasdata
.. _ExcelRange:
   https://docs.modelx.io/en/latest/reference/dataclient.html#excelrange
"""
from modelx.serialize.jsonvalues import *
_formula = None
_bases = []
_allow_none = True
_spaces = []
# ---------------------------------------------------------------------------
# Cells
[docs]def AnnPremRate():
    """Annualized Premium Rate per Sum Assured"""
    return GrossPremRate() * PremFreq().mask(PremFreq() == 0, other=1/10) 
[docs]def CashValueRate(t):
    """Cash Value Rate per Sum Assured"""
    return np.maximum(ReserveNLP_Rate('PREM', t) - SurrCharge(t), 0) 
[docs]def GrossPremRate():
    """Gross Premium Rate per Sum Assured per payment"""
    def get_value(pol):
        prod = pol['Product']
        alpha = pol['LoadAcqSA']
        beta = pol['LoadMaintPrem']
        delta = pol['LoadMaintPrem2']
        gamma = pol['LoadMaintSA']
        gamma2 = pol['LoadMaintSA2']
        freq = pol['PremFreq']
        x, n, m = pol['IssueAge'], pol['PolicyTerm'], pol['PolicyTerm']
        comf = LifeTable[pol['Sex'], pol['IntRate_PREM'], pol['TableID_PREM']]
        if prod == 'TERM' or prod == 'WL':
            return (comf.Axn(x, n) + alpha + gamma * comf.AnnDuenx(x, n, freq)
                    + gamma2 * comf.AnnDuenx(x, n-m, 1, m)) / (1-beta-delta) / freq / comf.AnnDuenx(x, m, freq)
        elif prod == 'ENDW':
            return (comf.Exn(x, n) + comf.Axn(x, n) + alpha + gamma * comf.AnnDuenx(x, n, freq)
                    + gamma2 * comf.AnnDuenx(x, n-m, 1, m)) / (1-beta-delta) / freq / comf.AnnDuenx(x, m, freq)
        else:
            raise ValueError('invalid product')
    result = PolicyDataExt1().apply(get_value, axis=1)
    result.name = 'GrossPremRate'
    return result 
[docs]def GrossPremTable():
    """Gross premium table"""
    return None 
[docs]def InitSurrCharge():
    """Initial Surrender Charge Rate"""
    def get_value(pol):
        prod, polt, gen = pol['Product'], pol['PolicyType'], pol['Gen']
        term = pol['PolicyTerm']
        param1 = SpecLookup.match("SurrChargeParam1", prod, polt, gen).value
        param2 = SpecLookup.match("SurrChargeParam2", prod, polt, gen).value
        if param1 is None or param2 is None:
            raise ValueError('SurrChargeParam not found')
        return param1 + param2 * min(term / 10, 1)
    result = PolicyData().apply(get_value, axis=1)
    result.name = 'InitSurrCharge'
    return result 
[docs]def IntRate(RateBasis):
    """Interest Rate"""
    if RateBasis == 'PREM':
        basis = 'IntRatePrem'
    elif RateBasis == 'VAL':
        basis = 'IntRateVal'
    else:
        raise ValueError('invalid RateBasis')
    def get_value(pol):
        result = SpecLookup.match(basis, 
                                  pol["Product"], 
                                  pol["PolicyType"],
                                  pol["Gen"]).value
        if result is not None:
            return result
        else:
            raise ValueError('lookup failed')
    result = PolicyData().apply(get_value, axis=1)
    result.name = 'IntRate_' + RateBasis
    return result 
[docs]def LoadAcqSA():
    """Acquisition Loading per Sum Assured"""
    param1 = Product().apply(lambda prod: SpecLookup("LoadAcqSAParam1", prod))
    param2 = Product().apply(lambda prod: SpecLookup("LoadAcqSAParam2", prod))
    result = param1 + param2 * np.minimum(PolicyTerm() / 10, 1)
    result.name = 'LoadAcqSA'
    return result 
[docs]def LoadMaintPrem():
    """Maintenance Loading per Gross Premium"""
    def get_value(pol):
        if SpecLookup("LoadMaintPremParam1", pol["Product"]) is not None:
            return SpecLookup("LoadMaintPremParam1", pol["Product"])
        elif SpecLookup("LoadMaintPremParam2", pol["Product"]) is not None:
            param = SpecLookup("LoadMaintPremParam2", pol["Product"])
            return (param + min(10, pol["PolicyTerm"])) / 100
        else:
            raise ValueError('LoadMaintPrem parameters not found')
    result = PolicyData().apply(get_value, axis=1)
    result.name = 'LoadMaintPrem'
    return result 
[docs]def LoadMaintSA():
    """Maintenance Loading per Sum Assured during Premium Payment"""
    def get_value(pol):
        result = SpecLookup.match("LoadMaintSA", 
                                  pol["Product"], 
                                  pol["PolicyType"],
                                  pol["Gen"]).value
        if result is not None:
            return result
        else:
            raise ValueError('lookup failed')
    result = PolicyData().apply(get_value, axis=1)
    result.name = 'LoadMaintSA'
    return result 
[docs]def LoadMaintSA2():
    """Maintenance Loading per Sum Assured after Premium Payment"""
    def get_value(pol):
        result = SpecLookup.match("LoadMaintSA2", 
                                  pol["Product"], 
                                  pol["PolicyType"],
                                  pol["Gen"]).value
        if result is not None:
            return result
        else:
            raise ValueError('lookup failed')
    result = PolicyData().apply(get_value, axis=1)
    result.name = 'LoadMaintSA2'
    return result 
[docs]def NetPremRate(basis):
    """Net Premium Rate"""
    def get_value(pol):
        prod = pol['Product']
        gamma2 = pol['LoadMaintSA2']
        x, n, m = pol['IssueAge'], pol['PolicyTerm'], pol['PolicyTerm']
        comf = LifeTable[pol['Sex'], pol['IntRate_' + basis], pol['TableID_' + basis]]
        if prod == 'TERM' or prod == 'WL':
            return (comf.Axn(x, n) + gamma2 * comf.AnnDuenx(x, n-m, 1, m)) / comf.AnnDuenx(x, n)
        elif prod == 'ENDW':
            return (comf.Axn(x, n) + gamma2 * comf.AnnDuenx(x, n-m, 1, m)) / comf.AnnDuenx(x, n)
        else:
            raise ValueError('invalid product')
    result = PolicyDataExt1().apply(get_value, axis=1)
    result.name = 'NetPremRate_' + basis
    return result 
[docs]def ReserveNLP_Rate(basis, t):
    """Net level premium reserve rate"""
    def get_value(pol):
        prod = pol['Product']
        gamma2 = pol['LoadMaintSA2']
        netp = pol['NetPremRate_' + basis]
        x, n, m = pol['IssueAge'], pol['PolicyTerm'], pol['PolicyTerm']
        lt = LifeTable[pol['Sex'], pol['IntRate_' + basis], pol['TableID_' + basis]]
        if t <= m:
            return lt.Axn(x+t, n-t) + (gamma2 * lt.AnnDuenx(x+t, n-m, 1, m-t)
                    - netp * lt.AnnDuenx(x+t, m-t))
        elif t <=n:
            return lt.Axn(x+t, n-t) + gamma2 * lt.AnnDuenx(x+t, n-m, 1, m-t)
        else:
            return 0
    result = PolicyDataExt2().apply(get_value, axis=1)
    result.name = 'ReserveNLP_Rate'
    return result 
[docs]def ReserveRate():
    """Valuation Reserve Rate per Sum Assured"""
    return None 
[docs]def SurrCharge(t):
    """Surrender Charge Rate per Sum Assured"""
    m = PremTerm()
    return InitSurrCharge * np.maximum((np.minimum(m, 10) - t) / np.minimum(m, 10), 0) 
[docs]def TableID(RateBasis):
    """Mortality Table ID"""
    if RateBasis == 'PREM':
        basis = "MortTablePrem"
    elif RateBasis == 'VAL':
        basis = "MortTableVal"
    else:
        raise ValueError('invalid RateBasis')
    def get_value(pol):
        result = SpecLookup.match(basis, 
                                  pol["Product"], 
                                  pol["PolicyType"],
                                  pol["Gen"]).value
        if result is not None:
            return result
        else:
            raise ValueError('lookup failed')
    result = PolicyData().apply(get_value, axis=1)
    result.name = 'TableID_' + RateBasis
    return result 
[docs]def UernPremRate():
    """Unearned Premium Rate"""
    return None 
Product = lambda: PolicyData()['Product']
PolicyType = lambda: PolicyData()['PolicyType']
Gen = lambda: PolicyData()['Gen']
Channel = lambda: PolicyData()['Channel']
Sex = lambda: PolicyData()['Sex']
Duration = lambda: PolicyData()['Duration']
IssueAge = lambda: PolicyData()['IssueAge']
PremFreq = lambda: PolicyData()['PremFreq']
PolicyTerm = lambda: PolicyData()['PolicyTerm']
PolicyCount = lambda: PolicyData()['PolicyCount']
SumAssured = lambda: PolicyData()['SumAssured']
[docs]def LoadMaintPrem2():
    """Maintenance Loading per Gross Premium for Premium Waiver"""
    result = pd.Series(0.002, index=PolicyData().index)
    result[PremTerm < 10] = 0.001
    result[PremTerm < 5] = 0.0005
    result.name = 'LoadMaintPrem2'
    return result 
[docs]def PolicyDataExt1():
    """Extended Poicy Data"""
    data = pd.concat([PolicyData(), 
                      LoadAcqSA(),
                      LoadMaintPrem(),
                      LoadMaintPrem2(),
                      LoadMaintSA(),
                      LoadMaintSA2(),
                      IntRate('PREM'),
                      TableID('PREM')], axis=1)
    return data 
[docs]def PolicyDataExt2():
    """Extended Poicy Data"""
    data = pd.concat([PolicyDataExt1(),
                      NetPremRate('PREM')], axis=1)
    return data 
[docs]def SpecLookup(spec, prod=None, polt=None, gen=None):
    """Look up product specs"""
    return ProductSpec.get((spec, prod, polt, gen), None) 
# ---------------------------------------------------------------------------
# References
LifeTable = ("Interface", ("...", "LifeTable"), "auto")
PremTerm = ("Interface", (".", "PolicyTerm"), "auto")
ProductSpec = ("Pickle", 3020542092744)
PolicyData = ("Pickle", 3020548337160)