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