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