Functional Weave
Code in TypeScript

telecoms.data-allowance@1.0.0

impl/python.py

1,992 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.

from typing import Optional

from .math_round_div import round_div  ← from math.round-div ^1.0.0 · built alongside by fune
from .money_amount import Money, assert_same_currency, money  ← from money.amount ^1.0.0 · built alongside by fune
from .telecoms_data_allowance_types import DataAllowance


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


def data_allowance(
    allowance: int, used: int, overage_block: int, price_per_block: Money, overage_cap: Optional[Money]
) -> DataAllowance:
    """Usage against a bundle: remaining allowance, overage in whole blocks
    priced and limited to an optional cap, and the share used, rounded down so
    a bundle with anything left never reads 100%.
    """
    if not _is_count(allowance) or not _is_count(used):
        raise ValueError(
            "allowance and usage must be whole numbers of 0 or more, received %r and %r" % (allowance, used)
        )
    if not _is_count(overage_block) or overage_block < 1:
        raise ValueError("overage block must be 1 or more, received %r" % (overage_block,))
    if price_per_block.minor < 0:
        raise ValueError("price per block must not be negative, received %d" % (price_per_block.minor,))
    if overage_cap is not None:
        assert_same_currency(price_per_block, overage_cap)
        if overage_cap.minor < 0:
            raise ValueError("overage cap must not be negative, received %d" % (overage_cap.minor,))
    overage = max(used - allowance, 0)
    overage_blocks = round_div(overage, overage_block, "up")
    uncapped = overage_blocks * price_per_block.minor
    capped = overage_cap is not None and uncapped > overage_cap.minor
    return DataAllowance(
        used=used,
        remaining=max(allowance - used, 0),
        overage=overage,
        overage_blocks=overage_blocks,
        overage_charge=money(overage_cap.minor if capped else uncapped, price_per_block.currency),
        capped=capped,
        percent_used_basis_points=None if allowance == 0 else round_div(used * 10000, allowance, "down"),
    )