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