Functional Weave
Code in Rust

finance.proration@1.0.0

impl/python.py

1,754 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 .finance_proration_types import ProrationSplit
from .money_allocate import allocate  ← from money.allocate ^1.0.0 · built alongside by fune
from .money_amount import Money  ← from money.amount ^1.0.0 · built alongside by fune


def prorate(amount: Money, total_days: int, used_days: int) -> ProrationSplit:
    """Split a period's charge into the part used and the part not used.

    The mid-cycle upgrade, downgrade and cancellation calculation. Written the
    obvious way - one rounding for the charge and another for the credit - the
    two halves do not reliably add back up: 9.99 over two days is 4.995 each
    way, and two half-up roundings produce 5.00 and 5.00, a penny invented on a
    line the customer can see.

    One call to ``money.allocate`` does the whole split instead, so
    ``used + unused`` is the original amount for every input, credits included.
    """
    if isinstance(total_days, bool) or not isinstance(total_days, int) or total_days <= 0:
        raise ValueError("total days must be greater than zero, received %r" % (total_days,))
    if isinstance(used_days, bool) or not isinstance(used_days, int) or used_days < 0:
        raise ValueError("used days must not be negative, received %r" % (used_days,))
    if used_days > total_days:
        # Clamping here would turn a bug in the caller's period arithmetic into
        # a plausible invoice, which is far more expensive to find later.
        raise ValueError(
            "used days must not exceed total days, received %d of %d" % (used_days, total_days)
        )

    # total_days > 0 guarantees the ratios do not sum to zero, so allocate is
    # safe even when one side is zero: [0, n] and [n, 0] are both well defined.
    used, unused = allocate(amount, [used_days, total_days - used_days])

    return ProrationSplit(total=amount, used=used, unused=unused)