from typing import Optional from .math_round_div import round_div from .money_amount import Money, assert_same_currency, money 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"), )