insurance.no-claims-discount@1.0.0
impl/python.py
3,125 bytes · the Python implementation · view raw
from typing import Optional, Sequence
from .insurance_no_claims_discount_types import NcdLevel, NcdOutcome, NcdProtection
def _whole_at_least_zero(value: object, name: str) -> None:
if isinstance(value, bool) or not isinstance(value, int) or value < 0:
raise ValueError("%s must be a whole number of 0 or more, received %s" % (name, value))
def _level_for(scale: Sequence[NcdLevel], years: int) -> NcdLevel:
found = scale[0]
for level in scale:
if level.years <= years:
found = level
return found
def next_no_claims_discount(
scale: Sequence[NcdLevel], current_years: int, claims: int, protection: Optional[NcdProtection]
) -> NcdOutcome:
"""The no-claims discount held at renewal.
A claim-free year earns a year. A year with claims steps back to what the
scale says for the level held and the number of claims, unless protected
NCD covers them, in which case the years stay where they were (a year with
a claim earns nothing). Years keep counting past the top of the scale; the
discount is the highest level not above them.
"""
if len(scale) == 0:
raise ValueError("the NCD scale must not be empty")
if scale[0].years != 0:
raise ValueError("the NCD scale must start at 0 years")
for i, level in enumerate(scale):
_whole_at_least_zero(level.years, "years")
if i > 0 and level.years <= scale[i - 1].years:
raise ValueError("the NCD scale must be in ascending order of years")
bp = level.discount_basis_points
if isinstance(bp, bool) or not isinstance(bp, int) or bp < 0 or bp > 10000:
raise ValueError("discountBasisPoints must be from 0 to 10000, received %s" % (bp,))
previous = level.years
for back in level.step_back:
_whole_at_least_zero(back, "stepBack")
if back > previous:
raise ValueError(
"stepBack from %d years must not rise above the level or the step before it" % (level.years,)
)
previous = back
_whole_at_least_zero(current_years, "currentYears")
_whole_at_least_zero(claims, "claims")
protection_applied = False
if claims == 0:
years = current_years + 1
else:
covered = False
if protection is not None:
_whole_at_least_zero(protection.claim_limit, "claimLimit")
_whole_at_least_zero(protection.claims_in_window, "claimsInWindow")
if protection.claims_in_window < claims:
raise ValueError("claimsInWindow must include this year's claims")
covered = protection.claims_in_window <= protection.claim_limit
if covered:
years = current_years
protection_applied = True
else:
steps = _level_for(scale, current_years).step_back
years = 0 if len(steps) == 0 else steps[min(claims, len(steps)) - 1]
return NcdOutcome(
years=years,
discount_basis_points=_level_for(scale, years).discount_basis_points,
protection_applied=protection_applied,
)