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 typing import Optional, Sequence
from .math_round_div import RoundingMode, round_div ← from math.round-div ^1.0.0 · built alongside by fune
from .money_amount import assert_same_currency, money ← from money.amount ^1.0.0 · built alongside by fune
from .telecoms_call_rating_types import CallRate, RatedCall
DIGITS = re.compile(r"[0-9]+")
def _is_increment(value: object) -> bool:
return not isinstance(value, bool) and isinstance(value, int) and value >= 1
def rate_call(dialled_number: str, duration_seconds: int, rates: Sequence[CallRate], mode: RoundingMode) -> RatedCall:
"""Rate one call: longest-prefix match on the dialled number, duration
rounded up to the rate's billing increments, per-minute charge rounded once
by ``mode``, connection fee added and the minimum charge applied.
"""
number = dialled_number[1:] if dialled_number.startswith("+") else dialled_number
if not DIGITS.fullmatch(number):
raise ValueError(
'dialled number must be international format digits, optionally with a leading +, received "%s"'
% (dialled_number,)
)
if isinstance(duration_seconds, bool) or not isinstance(duration_seconds, int) or duration_seconds < 0:
raise ValueError("duration must be whole seconds of 0 or more, received %r" % (duration_seconds,))
seen = set()
best: Optional[CallRate] = None
for rate in rates:
if not DIGITS.fullmatch(rate.prefix):
raise ValueError('rate prefix must be digits, received "%s"' % (rate.prefix,))
if rate.prefix in seen:
raise ValueError("duplicate prefix %s in the rate deck" % (rate.prefix,))
seen.add(rate.prefix)
if not _is_increment(rate.first_increment_seconds) or not _is_increment(rate.next_increment_seconds):
raise ValueError(
"billing increments must be 1 second or more, received %r/%r for prefix %s"
% (rate.first_increment_seconds, rate.next_increment_seconds, rate.prefix)
)
assert_same_currency(rates[0].per_minute, rate.per_minute)
assert_same_currency(rates[0].per_minute, rate.connection_fee)
assert_same_currency(rates[0].per_minute, rate.minimum_charge)
if number.startswith(rate.prefix) and (best is None or len(rate.prefix) > len(best.prefix)):
best = rate
if best is None:
raise ValueError("no rate for dialled number %s" % (number,))
currency = best.per_minute.currency
if duration_seconds == 0:
zero = money(0, currency)
return RatedCall(
prefix=best.prefix,
destination=best.destination,
billed_seconds=0,
usage_charge=zero,
connection_fee=zero,
total=zero,
)
first = best.first_increment_seconds
step = best.next_increment_seconds
if duration_seconds <= first:
billed_seconds = first
else:
billed_seconds = first + -(-(duration_seconds - first) // step) * step
usage = round_div(best.per_minute.minor * billed_seconds, 60, mode)
total = max(usage + best.connection_fee.minor, best.minimum_charge.minor)
return RatedCall(
prefix=best.prefix,
destination=best.destination,
billed_seconds=billed_seconds,
usage_charge=money(usage, currency),
connection_fee=best.connection_fee,
total=money(total, currency),
)