Functional Weave
Code in Python

property.deposit-cap@1.0.1

impl/python.py

2,990 bytes · the Python implementation · view raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

import re

from .money_amount import Money, money  ← from money.amount ^1.0.0 · built alongside by fune
from .property_deposit_cap_data import DEPOSIT_CAPS, DEPOSIT_CAPS_HISTORY, DepositCapRule  ← this capability’s own data, compiled from data/deposit-caps.json into the same file by fune build
from .property_deposit_cap_types import DepositCap, RentPeriod

_ISO_DATE = re.compile(r"[0-9]{4}-[0-9]{2}-[0-9]{2}")
# Keeps annual rent x 6 exact as a JavaScript number; the same limit here.
_MAX_MINOR = 1_000_000_000_000


def _find_rule(jurisdiction: str, on_date: str) -> DepositCapRule:
    best = None
    earliest = None
    for rule in DEPOSIT_CAPS:
        if rule.jurisdiction != jurisdiction:
            continue
        if earliest is None or rule.valid_from < earliest:
            earliest = rule.valid_from
        if on_date < rule.valid_from:
            continue
        if rule.valid_to is not None and on_date > rule.valid_to:
            continue
        if best is None or rule.valid_from > best.valid_from:
            best = rule
    if best is not None:
        return best
    # A build installed with a pruned history must not answer an old date with
    # today's rule, so it says what happened instead.
    if DEPOSIT_CAPS_HISTORY != "full" and earliest is not None and on_date < earliest:
        raise ValueError(
            "no deposit cap rule for %s on %s: this build was installed with history=%s, so it only carries rules from %s"
            % (jurisdiction, on_date, DEPOSIT_CAPS_HISTORY, earliest)
        )
    raise ValueError("no deposit cap rule for %s on %s" % (jurisdiction, on_date))


def deposit_cap(rent: Money, rent_period: RentPeriod, jurisdiction: str, on_date: str) -> DepositCap:
    """The maximum tenancy deposit: five weeks' rent below the annual-rent
    threshold, six at or above it. The threshold is tested on the exact annual
    rent, and the cap is annual rent x weeks / 52 rounded down once, so it can
    never sit a fraction of a penny over the statutory limit."""
    if not isinstance(on_date, str) or not _ISO_DATE.fullmatch(on_date):
        raise ValueError('onDate must be an ISO date (YYYY-MM-DD), received "%s"' % (on_date,))
    if rent.currency != "GBP":
        raise ValueError("rent must be in GBP, received %s" % (rent.currency,))
    if rent.minor < 0:
        raise ValueError("rent must not be negative, received %r" % (rent.minor,))
    if rent_period == "weekly":
        annual = rent.minor * 52
    elif rent_period == "monthly":
        annual = rent.minor * 12
    elif rent_period == "annual":
        annual = rent.minor
    else:
        raise ValueError('unknown rent period "%s"' % (rent_period,))
    if annual > _MAX_MINOR:
        raise ValueError("annual rent must be at most %d minor units" % (_MAX_MINOR,))
    rule = _find_rule(jurisdiction, on_date)
    weeks = rule.weeks_below if annual < rule.annual_rent_threshold_minor else rule.weeks_at_or_above
    return DepositCap(
        annual_rent=money(annual, "GBP"),
        weekly_rent=money(annual // 52, "GBP"),
        weeks=weeks,
        maximum=money(annual * weeks // 52, "GBP"),
    )