Functional Weave
Code in Python

telecoms.call-rating@1.0.1

impl/python.py

3,341 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.

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