Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
from typing import Optional, Sequence
from .dates_add_months import add_months ← from dates.add-months ^1.0.0 · built alongside by fune
from .dates_days_between import days_between ← from dates.days-between ^1.0.0 · built alongside by fune
from .finance_proration import prorate ← from finance.proration ^1.0.0 · built alongside by fune
from .insurance_premium_proration_types import CancellationBasis, CancellationRefund, ShortPeriodBand
from .money_amount import Money, assert_same_currency, money ← from money.amount ^1.0.0 · built alongside by fune
from .money_apply_rate import apply_rate ← from money.apply-rate ^1.0.0 · built alongside by fune
def _is_int(value: object) -> bool:
return isinstance(value, int) and not isinstance(value, bool)
def cancellation_return_premium(
premium: Money,
inception_date: str,
expiry_date: str,
cancellation_date: str,
basis: CancellationBasis,
short_period_scale: Sequence[ShortPeriodBand],
minimum_retained: Optional[Money],
) -> CancellationRefund:
"""The return premium when a policy is cancelled mid-term.
Pro rata keeps the premium for the days on cover. Short period keeps the
share the insurer's scale gives for how long cover ran ("not exceeding one
month: 20%"), and a period longer than every band keeps the whole premium.
A month on the scale is a calendar month from inception, not 30 days.
"""
if premium.minor < 0:
raise ValueError("premium must not be negative, received %d" % (premium.minor,))
total_days = days_between(inception_date, expiry_date)
if total_days <= 0:
raise ValueError(
"expiryDate must be after inceptionDate, received %s to %s" % (inception_date, expiry_date)
)
days_in_force = days_between(inception_date, cancellation_date)
if days_in_force < 0 or days_in_force > total_days:
raise ValueError(
"cancellationDate must be from inceptionDate to expiryDate, received %s" % (cancellation_date,)
)
if basis == "pro-rata":
retained = prorate(premium, total_days, days_in_force).used
elif basis == "short-period":
if len(short_period_scale) == 0:
raise ValueError("a short-period cancellation needs a scale")
share = 10000
found = False
previous = 0
for band in short_period_scale:
if not _is_int(band.up_to) or band.up_to < 1:
raise ValueError("upTo must be a whole number of at least 1, received %s" % (band.up_to,))
if band.unit not in ("days", "months"):
raise ValueError('unit must be days or months, received "%s"' % (band.unit,))
bp = band.retained_basis_points
if not _is_int(bp) or bp < 0 or bp > 10000:
raise ValueError("retainedBasisPoints must be from 0 to 10000, received %s" % (bp,))
if bp < previous:
raise ValueError("a short-period scale must not keep less for a longer period")
previous = bp
if found:
continue
if band.unit == "days":
within = days_in_force <= band.up_to
else:
within = cancellation_date <= add_months(inception_date, band.up_to)
if within:
share = bp
found = True
retained = apply_rate(premium, share, "half-up")
else:
raise ValueError('unknown basis "%s": use pro-rata or short-period' % (basis,))
minimum_applied = False
if minimum_retained is not None:
assert_same_currency(premium, minimum_retained)
if minimum_retained.minor < 0:
raise ValueError("minimumRetained must not be negative, received %d" % (minimum_retained.minor,))
# The insurer can never keep more than it was paid.
floor = min(minimum_retained.minor, premium.minor)
if floor > retained.minor:
retained = money(floor, premium.currency)
minimum_applied = True
return CancellationRefund(
days_in_force=days_in_force,
total_days=total_days,
retained=retained,
return_premium=money(premium.minor - retained.minor, premium.currency),
minimum_applied=minimum_applied,
)