Functional Weave
Code in Python

property.service-charge-apportion@1.0.0

impl/python.py

2,628 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.

from typing import List, Sequence

from .math_gcd_lcm import lcm  ← from math.gcd-lcm ^1.0.0 · built alongside by fune
from .money_allocate import allocate  ← from money.allocate ^1.0.0 · built alongside by fune
from .money_amount import Money  ← from money.amount ^1.0.0 · built alongside by fune
from .property_service_charge_apportion_types import ApportionBasis, ServiceChargeLine, ServiceChargeUnit

_MAX_SAFE = 2**53 - 1


def _is_int(value: object) -> bool:
    return isinstance(value, int) and not isinstance(value, bool)


def apportion_service_charge(
    total: Money, units: Sequence[ServiceChargeUnit], basis: ApportionBasis
) -> List[ServiceChargeLine]:
    """Share a service charge between units by lease fraction, floor area or
    fixed percentage. Every share becomes an integer weight, and
    money.allocate does the one split, so the lines always add up to the
    total exactly."""
    if basis not in ("fraction", "floor-area", "percentage"):
        raise ValueError('unknown basis "%s": use fraction, floor-area or percentage' % (basis,))
    if len(units) == 0:
        raise ValueError("units must not be empty")
    for u in units:
        if not _is_int(u.share) or u.share < 0 or u.share > _MAX_SAFE:
            raise ValueError('unit "%s": share must be a whole number, zero or more, received %r' % (u.unit, u.share))
        if basis == "fraction":
            if u.of is None or not _is_int(u.of) or u.of <= 0 or u.of > _MAX_SAFE:
                raise ValueError('unit "%s": a fraction needs a denominator "of" greater than zero' % (u.unit,))
        elif u.of is not None:
            raise ValueError('unit "%s": "of" is only for the fraction basis' % (u.unit,))
    if basis == "fraction":
        common = 1
        for u in units:
            common = lcm(common, u.of)
        weights = [u.share * (common // u.of) for u in units]
        total_weight = sum(weights)
        if total_weight > _MAX_SAFE:
            raise ValueError("the fractions are too fine to apportion exactly")
        if total_weight != common:
            raise ValueError("the fractions add up to %d/%d, not exactly 1" % (total_weight, common))
    else:
        weights = [u.share for u in units]
        total_weight = sum(weights)
        if basis == "percentage" and total_weight != 10000:
            raise ValueError("the percentages add up to %d basis points, not 10000" % (total_weight,))
        if total_weight == 0:
            raise ValueError("the floor areas add up to zero")
    if total_weight > _MAX_SAFE or abs(total.minor) * total_weight > _MAX_SAFE:
        raise ValueError("the fractions are too fine to apportion exactly")
    parts = allocate(total, weights)
    return [ServiceChargeLine(unit=u.unit, charge=parts[i]) for i, u in enumerate(units)]