Functional Weave
Code in Python

insurance.no-claims-discount@1.0.1

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,
    )