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