Functional Weave
Code in Python

telecoms.roaming-charge@1.0.0

impl/python.py

3,228 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

from .dates_add_days import epoch_day_from_iso  ← from dates.add-days ^1.0.0 · built alongside by fune
from .money_amount import money  ← from money.amount ^1.0.0 · built alongside by fune
from .money_sum import sum_money  ← from money.sum ^1.0.0 · built alongside by fune
from .telecoms_roaming_charge_data import ROAMING_TARIFFS, ROAMING_TARIFFS_HISTORY, RoamingRate  ← this capability’s own data, compiled from data/roaming-tariffs.json into the same file by fune build
from .telecoms_roaming_charge_types import RoamingCharge, RoamingUsage

COUNTRY = re.compile(r"^[A-Z]{2}$")


def _is_count(value: object) -> bool:
    return not isinstance(value, bool) and isinstance(value, int) and value >= 0


def roaming_charge(tariff: str, country: str, on_date: str, usage: RoamingUsage) -> RoamingCharge:
    """Price roaming usage in one country on one date: find the tariff's zone
    for the country in force on that date, then multiply each kind of usage by
    its price. Exact integer arithmetic throughout.
    """
    if not isinstance(country, str) or not COUNTRY.match(country):
        raise ValueError('country must be an ISO 3166-1 alpha-2 code in capitals, received "%s"' % (country,))
    epoch_day_from_iso(on_date)
    counts = (usage.days, usage.data_mb, usage.call_out_minutes, usage.call_in_minutes, usage.sms_out)
    if not all(_is_count(n) for n in counts):
        raise ValueError("usage must be whole numbers of 0 or more")

    def covers(rule: RoamingRate) -> bool:
        return rule.tariff == tariff and country in rule.countries.split(" ")

    best: Optional[RoamingRate] = None
    for rule in ROAMING_TARIFFS:
        if not covers(rule):
            continue
        if on_date < rule.valid_from:
            continue
        if rule.valid_to is not None and on_date > rule.valid_to:
            continue
        if best is None or rule.valid_from > best.valid_from:
            best = rule

    if best is None:
        # A history=current build has dropped the rows that ended; answering an
        # older date with today's prices would be wrong, so say what happened.
        if ROAMING_TARIFFS_HISTORY != "full":
            earliest = None
            for rule in ROAMING_TARIFFS:
                if covers(rule) and (earliest is None or rule.valid_from < earliest):
                    earliest = rule.valid_from
            if earliest is not None and on_date < earliest:
                raise ValueError(
                    "no roaming zone for %s in tariff %s on %s: this build was installed with history=%s, "
                    "so it only carries rules from %s. Reinstall with history=full to price older usage."
                    % (country, tariff, on_date, ROAMING_TARIFFS_HISTORY, earliest)
                )
        raise ValueError("no roaming zone for %s in tariff %s on %s" % (country, tariff, on_date))

    c = best.currency
    daily_fees = money(usage.days * best.daily_fee_minor, c)
    data = money(usage.data_mb * best.data_per_mb_minor, c)
    calls = money(
        usage.call_out_minutes * best.call_out_per_minute_minor
        + usage.call_in_minutes * best.call_in_per_minute_minor,
        c,
    )
    sms = money(usage.sms_out * best.sms_out_minor, c)
    return RoamingCharge(
        tariff=tariff,
        zone=best.zone,
        daily_fees=daily_fees,
        data=data,
        calls=calls,
        sms=sms,
        total=sum_money([daily_fees, data, calls, sms], c),
    )