Functional Weave
Code in Rust

subscriptions.usage-tiered@1.0.0

impl/python.py

2,984 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 List, Sequence

from .money_amount import assert_same_currency, 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 .subscriptions_usage_tiered_types import TierCharge, TierMode, UsageCharge, UsageTier


def _charge(tier: int, quantity: int, t: UsageTier) -> TierCharge:
    amount = money(quantity * t.unit_price.minor + t.flat_fee.minor, t.unit_price.currency)
    return TierCharge(tier=tier, quantity=quantity, unit_price=t.unit_price, flat_fee=t.flat_fee, amount=amount)


def price_usage(quantity: int, tiers: Sequence[UsageTier], mode: TierMode) -> UsageCharge:
    """Price ``quantity`` units against ascending tiers. Graduated prices each
    unit in its own tier and charges the flat fee of every tier reached; volume
    prices every unit at the tier the quantity falls in, plus that tier's flat
    fee. The first tier is always reached, so its flat fee applies at zero.
    """
    if isinstance(quantity, bool) or not isinstance(quantity, int) or quantity < 0:
        raise ValueError("quantity must be a whole number of 0 or more, received %r" % (quantity,))
    if mode not in ("graduated", "volume"):
        raise ValueError('unknown tier mode "%s": expected graduated or volume' % (mode,))
    if len(tiers) == 0:
        raise ValueError("tiered pricing needs at least one tier")
    currency = tiers[0].unit_price.currency
    previous = 0
    for i, t in enumerate(tiers):
        assert_same_currency(tiers[0].unit_price, t.unit_price)
        assert_same_currency(tiers[0].unit_price, t.flat_fee)
        if t.up_to is None:
            if i != len(tiers) - 1:
                raise ValueError("only the last tier may be open-ended (upTo null)")
        else:
            if isinstance(t.up_to, bool) or not isinstance(t.up_to, int) or t.up_to <= previous:
                raise ValueError(
                    "tier upTo values must be positive and strictly increasing, received %r after %d"
                    % (t.up_to, previous)
                )
            previous = t.up_to
    last = tiers[-1]
    if last.up_to is not None and quantity > last.up_to:
        raise ValueError("quantity %d exceeds the last tier, which ends at %d" % (quantity, last.up_to))

    lines: List[TierCharge] = []
    if mode == "volume":
        index = next(i for i, t in enumerate(tiers) if t.up_to is None or quantity <= t.up_to)
        lines.append(_charge(index + 1, quantity, tiers[index]))
    else:
        floor = 0
        for i, t in enumerate(tiers):
            # Tier 1 is always reached; a later tier only once usage passes the
            # previous tier's last unit.
            if i > 0 and quantity <= floor:
                break
            top = quantity if t.up_to is None else min(quantity, t.up_to)
            lines.append(_charge(i + 1, top - floor, t))
            if t.up_to is None:
                break
            floor = t.up_to
    return UsageCharge(lines=lines, total=sum_money([line.amount for line in lines], currency))