AI cost/ROI page: pricing table, cost per call, Cost tab, ROI endpoint

- 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>
This commit is contained in:
VasanthandClaude Opus 5.5 committed 2026-10-16 15:23:52 +05:30
1 parent 8951ac05a5
commit 9dcdd8f572
11 files changed
+392 -6

No files matched your search

+85
View File
@@ -0,0 +1,85 @@
"""AI cost page: spend tiles, coverage pill, month breakdown and the by-hand assumptions.
ROI runs are set on the server stub (stub.ROI_RUNS); the stub prices them with pricing.py.
The test pins its own pricing table, so it holds on a generated dashboard whose table
lists only the project's models. See rpa-dashboard-ai/templates/contract.md.
"""
import sys
from datetime import datetime, timedelta, timezone
import pytest
from playwright.sync_api import expect
NOW = datetime.now(timezone.utc)
LAST = NOW.replace(day=1) - timedelta(days=1) # a day in last month
def roi_run(job_id, when, model, inp=None, out=None, thinking=0):
return {"job_id": job_id, "document": f"{job_id}.pdf", "created_at": when.strftime("%Y-%m-%dT%H:%M:%SZ"),
"model": model, "input_tokens": inp, "output_tokens": out, "thinking_tokens": thinking}
TABLE = {
"test-flash": {"input": 0.30, "output": 2.50},
"test-pro": {"input": 1.25, "output": 10.00, "long_threshold": 200_000, "input_long": 2.50, "output_long": 15.00},
"test-lite": {"input": 0.10, "output": 0.40},
}
AS_OF = "2030-01-31"
# Costs worked by hand from TABLE:
RUNS = [
roi_run("r1", NOW, "test-flash", 1_000_000, 200_000), # 0.30 + 0.50 = 0.80
roi_run("r2", NOW, "test-pro", 400_000, 60_000, 40_000), # long: 1.00 + 1.50 = 2.50
roi_run("r3", LAST, "test-lite", 1_000_000, 1_000_000), # 0.10 + 0.40 = 0.50
roi_run("r4", LAST, "test-flash"), # no token usage recorded
]
@pytest.fixture
def cost_page(page, stub, monkeypatch):
monkeypatch.setattr(sys.modules["pricing"], "PRICING", TABLE) # the stub's own pricing module
monkeypatch.setattr(stub, "PRICING_AS_OF", AS_OF)
stub.ROI_RUNS[:] = RUNS
page.get_by_role("navigation").get_by_role("button", name="Cost", exact=True).click()
return page
def tile(page, name):
return page.get_by_role("group", name=name, exact=True)
def test_spend_tiles_sum_the_priced_runs(cost_page):
expect(tile(cost_page, "Total spend")).to_contain_text("$3.80")
expect(tile(cost_page, "This month")).to_contain_text("$3.30")
def test_coverage_pill_counts_priced_of_all_documents(cost_page):
expect(cost_page.get_by_text("3 of 4 documents priced")).to_be_visible()
def test_month_breakdown_groups_runs_by_month(cost_page):
rows = cost_page.get_by_role("table", name="Spend by month").get_by_role("row")
expect(rows).to_have_count(3) # header + two months, newest first
expect(rows.nth(1)).to_contain_text(NOW.strftime("%b %Y"))
expect(rows.nth(1)).to_contain_text("2 of 2")
expect(rows.nth(1)).to_contain_text("$3.30")
expect(rows.nth(2)).to_contain_text(LAST.strftime("%b %Y"))
expect(rows.nth(2)).to_contain_text("1 of 2")
expect(rows.nth(2)).to_contain_text("$0.50")
def test_runs_show_tokens_and_cost_and_unpriced_is_not_zero(cost_page):
runs = cost_page.get_by_role("table", name="Cost per run")
r2 = runs.get_by_role("row").filter(has_text="r2.pdf")
for text in ("test-pro", "400,000", "60,000", "40,000", "$2.50"):
expect(r2).to_contain_text(text)
expect(runs.get_by_role("row").filter(has_text="r4.pdf")).to_contain_text("not priced")
def test_by_hand_panel_is_labelled_assumptions_and_recomputes(cost_page):
panel = cost_page.get_by_role("region", name="Against doing it by hand")
expect(panel).to_contain_text("assumptions")
# 3 priced documents x 10 min = 0.5 h at $30/h = $15.00
panel.get_by_label("Minutes per document").fill("10")
panel.get_by_label("Hourly rate").fill("30")
expect(panel).to_contain_text("$15.00")
expect(cost_page.get_by_text(AS_OF)).to_be_visible() # pricing as-of date shown