Files
palladium/calculators/Genesys_Token_Calculator/tests/test_billing.py

155 lines
5.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Tokens → dollars: the allowance, the rounding, and the commercial levers.
Hand-checked first, then pinned.
"""
from __future__ import annotations
import pytest
from genesyscalc.billing import (
MONTHS_PER_YEAR,
allowance_for,
billable_tokens_monthly,
cost_lines,
total_cost,
)
from genesyscalc.meters import LicenceModel, TokenPrice
NAMED = LicenceModel.NAMED
CONCURRENT = LicenceModel.CONCURRENT
# ── the free monthly allowance ───────────────────────────────────────────
def test_free_allowance_deducted_named() -> None:
"""1,000 consumption tokens, named org: 1,000 250 = 750 billable."""
assert billable_tokens_monthly(1_000, 0, NAMED) == 750
def test_free_allowance_deducted_concurrent() -> None:
"""Same consumption, concurrent org: 1,000 350 = 650."""
assert billable_tokens_monthly(1_000, 0, CONCURRENT) == 650
def test_allowance_floors_at_zero() -> None:
assert billable_tokens_monthly(120, 0, NAMED) == 0
def test_allowance_does_not_carry_over() -> None:
"""Two quiet months do not bank credit for a busy third.
120 → 0 billable. 120 → 0 billable. 400 → 150 billable, NOT 0: the
unused 130 + 130 is discarded, because the allowance "renew[s] each
month and do[es] not carry over to future months".
"""
assert billable_tokens_monthly(120, 0, NAMED) == 0
assert billable_tokens_monthly(120, 0, NAMED) == 0
assert billable_tokens_monthly(400, 0, NAMED) == 150
def test_allowance_can_be_switched_off() -> None:
"""So the gap is measurable on stage rather than merely asserted."""
assert billable_tokens_monthly(1_000, 0, NAMED, apply_allowance=False) == 1_000
assert allowance_for(NAMED, apply_allowance=False) == 0
assert allowance_for(NAMED) == 250
def test_allowance_applies_across_consumption_and_subscription() -> None:
"""It is an org-level deduction, not a per-line one.
44.12 consumption → ceil 45; + 20,000 subscription = 20,045; 250 =
19,795.
"""
assert billable_tokens_monthly(44.117647, 20_000, NAMED) == 19_795
# ── rounding ─────────────────────────────────────────────────────────────
def test_consumption_tokens_rounded_up_monthly() -> None:
"""44.117647 tokens of bot time bills as 45."""
assert billable_tokens_monthly(44.117647, 0, NAMED, apply_allowance=False) == 45
def test_per_user_tokens_are_exact_not_rounded() -> None:
"""500 named users × 40 = 20,000 exactly — no ceil on subscription."""
assert billable_tokens_monthly(0, 20_000, NAMED, apply_allowance=False) == 20_000
def test_negative_tokens_rejected() -> None:
with pytest.raises(ValueError, match="must not be negative"):
billable_tokens_monthly(-1, 0, NAMED)
# ── price, concession, currency ──────────────────────────────────────────
def test_concession_applies_to_msrp() -> None:
"""$1.00 list, 30% concession → $0.70/token; 1,000 tokens → $700/mo."""
price = TokenPrice(list_rate=1.00, concession_pct=0.30)
assert price.effective_rate() == pytest.approx(0.70)
totals = total_cost(1_000, 0, NAMED, price, apply_allowance=False)
assert totals.token_cost_monthly == pytest.approx(700.0)
assert totals.token_cost_annual == pytest.approx(8_400.0)
def test_msrp_walk_is_carried_alongside() -> None:
"""The deck-frame column: list beside effective, and the saving."""
price = TokenPrice(list_rate=1.00, concession_pct=0.30)
totals = total_cost(1_000, 0, NAMED, price, apply_allowance=False)
assert totals.list_cost_annual == pytest.approx(12_000.0)
assert totals.concession_saving_annual == pytest.approx(3_600.0)
assert totals.token_cost_annual <= totals.list_cost_annual
def test_contracted_rate_overrides_concession() -> None:
price = TokenPrice(list_rate=1.00, contracted_rate=0.55, concession_pct=0.30)
assert price.effective_rate() == pytest.approx(0.55)
def test_currency_carried_through() -> None:
price = TokenPrice(currency="JPY", list_rate=120.0)
totals = total_cost(1_000, 0, NAMED, price, apply_allowance=False)
assert totals.currency == "JPY"
assert totals.token_cost_monthly == pytest.approx(120_000.0)
def test_invalid_concession_rejected() -> None:
with pytest.raises(ValueError, match="concession_pct"):
TokenPrice(concession_pct=1.0)
# ── cost lines ───────────────────────────────────────────────────────────
def test_cost_lines_price_each_meter_at_the_effective_rate() -> None:
price = TokenPrice(list_rate=1.00, concession_pct=0.25)
lines = cost_lines(
{"ai_scoring": 100.0, "virtual_agent": 7_500.0},
{"ai_scoring": 2_000.0, "virtual_agent": 15_000.0},
price,
)
by_key = {line.key: line for line in lines}
assert by_key["ai_scoring"].cost_monthly == pytest.approx(75.0)
assert by_key["virtual_agent"].cost_monthly == pytest.approx(5_625.0)
assert by_key["virtual_agent"].cost_annual == pytest.approx(
5_625.0 * MONTHS_PER_YEAR
)
assert by_key["ai_scoring"].units_monthly == pytest.approx(2_000.0)
assert by_key["ai_scoring"].unit_label == "evaluations"
def test_cost_lines_are_ordered_by_feature_name() -> None:
lines = cost_lines(
{"virtual_agent": 1.0, "ai_scoring": 1.0}, {}, TokenPrice()
)
assert [line.feature for line in lines] == ["AI Scoring", "Virtual Agent"]
def test_totals_expose_the_allowance_that_was_applied() -> None:
totals = total_cost(1_000, 20_000, NAMED, TokenPrice())
assert totals.allowance_tokens_monthly == 250
assert totals.gross_tokens_monthly == pytest.approx(21_000.0)
assert totals.billable_tokens_monthly == 20_750