Functional Weave
Code in Python

telecoms.roaming-charge@1.0.0

impl/typescript.ts

2,877 bytes · the TypeScript 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 { epochDayFromIso } from "./dates_add_days.ts";  ← from dates.add-days ^1.0.0 · built alongside by fune
import { money } from "./money_amount.ts";  ← from money.amount ^1.0.0 · built alongside by fune
import { sumMoney } from "./money_sum.ts";  ← from money.sum ^1.0.0 · built alongside by fune
import { ROAMING_TARIFFS, ROAMING_TARIFFS_HISTORY } from "./telecoms_roaming_charge_data.ts";  ← this capability’s own data, compiled from data/roaming-tariffs.json into the same file by fune build
import { type RoamingCharge, type RoamingUsage } from "./telecoms_roaming_charge_types.ts";

const COUNTRY = /^[A-Z]{2}$/;

/**
 * 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.
 */
export function roamingCharge(tariff: string, country: string, onDate: string, usage: RoamingUsage): RoamingCharge {
  if (!COUNTRY.test(country)) {
    throw new RangeError(`country must be an ISO 3166-1 alpha-2 code in capitals, received "${country}"`);
  }
  epochDayFromIso(onDate);
  const counts = [usage.days, usage.dataMb, usage.callOutMinutes, usage.callInMinutes, usage.smsOut];
  if (counts.some((n) => !Number.isInteger(n) || n < 0)) {
    throw new RangeError("usage must be whole numbers of 0 or more");
  }

  const covers = (rule: (typeof ROAMING_TARIFFS)[number]) => rule.tariff === tariff && rule.countries.split(" ").includes(country);
  let best: (typeof ROAMING_TARIFFS)[number] | null = null;
  for (const rule of ROAMING_TARIFFS) {
    if (!covers(rule)) continue;
    if (onDate < rule.validFrom) continue;
    if (rule.validTo !== null && onDate > rule.validTo) continue;
    if (best === null || rule.validFrom > best.validFrom) best = rule;
  }

  if (best === null) {
    // 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") {
      let earliest: string | null = null;
      for (const rule of ROAMING_TARIFFS) {
        if (covers(rule) && (earliest === null || rule.validFrom < earliest)) earliest = rule.validFrom;
      }
      if (earliest !== null && onDate < earliest) {
        throw new RangeError(
          `no roaming zone for ${country} in tariff ${tariff} on ${onDate}: this build was installed with history=${ROAMING_TARIFFS_HISTORY}, ` +
            `so it only carries rules from ${earliest}. Reinstall with history=full to price older usage.`
        );
      }
    }
    throw new RangeError(`no roaming zone for ${country} in tariff ${tariff} on ${onDate}`);
  }

  const c = best.currency;
  const dailyFees = money(usage.days * best.dailyFeeMinor, c);
  const data = money(usage.dataMb * best.dataPerMbMinor, c);
  const calls = money(usage.callOutMinutes * best.callOutPerMinuteMinor + usage.callInMinutes * best.callInPerMinuteMinor, c);
  const sms = money(usage.smsOut * best.smsOutMinor, c);
  return { tariff, zone: best.zone, dailyFees, data, calls, sms, total: sumMoney([dailyFees, data, calls, sms], c) };
}