"""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