Functional Weave
Code in Python

professional.utilisation@1.0.0

impl/python.py

1,937 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 Optional

from .math_round_div import round_div  ← from math.round-div ^1.0.0 · built alongside by fune
from .money_amount import Money  ← from money.amount ^1.0.0 · built alongside by fune
from .professional_utilisation_types import UtilisationRates


def _ratio(numerator: int, denominator: int) -> Optional[int]:
    # A ratio over nothing is not 0% or 100%: None says there was nothing to realise.
    return None if denominator == 0 else round_div(numerator * 10000, denominator, "half-up")


def _check_minutes(name: str, value: int) -> None:
    if isinstance(value, bool) or not isinstance(value, int) or value < 0:
        raise ValueError("%s must be a non-negative integer, received %s" % (name, value))


def utilisation_rates(
    billable_minutes: int,
    available_minutes: int,
    standard_value: Money,
    billed_value: Money,
    collected_value: Money,
) -> UtilisationRates:
    """Utilisation and billing, collection and overall realisation, in basis points."""
    _check_minutes("billableMinutes", billable_minutes)
    _check_minutes("availableMinutes", available_minutes)
    if available_minutes == 0:
        raise ValueError("availableMinutes must be greater than zero")
    for name, amount in (("standardValue", standard_value), ("billedValue", billed_value), ("collectedValue", collected_value)):
        if amount.currency != standard_value.currency:
            raise ValueError("currency mismatch: %s and %s" % (standard_value.currency, amount.currency))
        if amount.minor < 0:
            raise ValueError("%s must not be negative, received %s" % (name, amount.minor))
    return UtilisationRates(
        utilisation_basis_points=round_div(billable_minutes * 10000, available_minutes, "half-up"),
        billing_realisation_basis_points=_ratio(billed_value.minor, standard_value.minor),
        collection_realisation_basis_points=_ratio(collected_value.minor, billed_value.minor),
        overall_realisation_basis_points=_ratio(collected_value.minor, standard_value.minor),
    )