- templates/web/pricing.py: pricing table with source and as-of date; cost_for_call
(input rate + (output + thinking) at output rate, long-context above threshold;
unknown model raises naming the known models).
- Cost tab: coverage pill, total/this-month/tokens tiles, month-by-month table,
per-run tokens and cost ("not priced", never $0), by-hand panel with editable
assumptions labelled as such.
- Contract: RoiRun and GET /roi; optional GET /models and POST /runs/{id}/retry.
- SKILL.md: cost page wiring, model picker and retry-on-other-model as optional sections.
- Tests: test_pricing.py self-check, page_check_ai/test_ai_cost.py.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
51 lines
2.0 KiB
Python
51 lines
2.0 KiB
Python
"""Self-check for the AI cost function: rates per million tokens, thinking bills as output,
|
|
long-context rates above the threshold, and an unknown model is an error, never zero."""
|
|
import importlib.util
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
SRC = Path(__file__).resolve().parents[1] / "rpa-dashboard-ai" / "templates" / "web" / "pricing.py"
|
|
spec = importlib.util.spec_from_file_location("pricing", SRC)
|
|
pricing = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(pricing)
|
|
|
|
TABLE = {
|
|
"flat-model": {"input": 0.30, "output": 2.50},
|
|
"long-model": {"input": 1.25, "output": 10.00, "long_threshold": 200_000, "input_long": 2.50, "output_long": 15.00},
|
|
}
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def table(monkeypatch):
|
|
monkeypatch.setattr(pricing, "PRICING", TABLE)
|
|
|
|
|
|
def test_normal_call_prices_input_and_output_per_million():
|
|
# 1M in at 0.30 + 0.2M out at 2.50 = 0.30 + 0.50
|
|
assert pricing.cost_for_call("flat-model", 1_000_000, 200_000) == pytest.approx(0.80)
|
|
|
|
|
|
def test_thinking_tokens_bill_at_the_output_rate():
|
|
# 0.1M in at 0.30 + (0.1M out + 0.3M thinking) at 2.50 = 0.03 + 1.00
|
|
assert pricing.cost_for_call("flat-model", 100_000, 100_000, 300_000) == pytest.approx(1.03)
|
|
|
|
|
|
def test_long_context_rates_apply_above_the_threshold_only():
|
|
# at the threshold: 0.2M at 1.25 + 0.1M at 10 = 0.25 + 1.00
|
|
assert pricing.cost_for_call("long-model", 200_000, 100_000) == pytest.approx(1.25)
|
|
# above it: 0.4M at 2.50 + 0.1M at 15 = 1.00 + 1.50
|
|
assert pricing.cost_for_call("long-model", 400_000, 100_000) == pytest.approx(2.50)
|
|
|
|
|
|
def test_unknown_model_raises_and_names_the_known_models():
|
|
with pytest.raises(ValueError) as e:
|
|
pricing.cost_for_call("mystery-model", 1000, 1000)
|
|
assert "mystery-model" in str(e.value)
|
|
assert "flat-model" in str(e.value) and "long-model" in str(e.value)
|
|
|
|
|
|
def test_bundled_table_records_its_source_and_as_of_date():
|
|
assert pricing.PRICING_SOURCE.startswith("https://")
|
|
assert len(pricing.PRICING_AS_OF) == 10 and pricing.PRICING_AS_OF[4] == "-"
|