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