Files
2026-07-13 13:12:33 +08:00

294 lines
11 KiB
Python

"""Pin _reconcile_breakdown_to_row pro-rate semantics in rpc_usage.
Background: ``engine.usage.ModelUsage.cost`` ignores cache_read pricing
because ``engine.pricing.ModelPrice`` has no cache fields. For multi-model
billed sessions this caused the per-model breakdown sum to drift well above
the row's billed cost (3x for sessions with 90%+ cache hit). The reconcile
helper rescales each item so the breakdown sums to the row total while
preserving the *relative* share implied by the estimates.
"""
from __future__ import annotations
from opensquilla.gateway.rpc_usage import _reconcile_breakdown_to_row
def _make_row(*, cost_usd: float, cost_source: str, breakdown: list) -> dict:
return {
"session": "agent:main:webchat:test",
"cost_usd": cost_usd,
"costUsd": cost_usd,
"cost_source": cost_source,
"costSource": cost_source,
"modelBreakdown": breakdown,
}
def _make_item(model: str, cost: float) -> dict:
return {
"model": model,
"costUsd": cost,
"cost_usd": cost,
}
def test_billed_multi_model_prorates_to_row_total() -> None:
"""User-reported scenario reproduced exactly.
Real numbers from production session:
Row billed (with cache discount): $0.0607
Per-model estimates (no cache): $0.1024 + $0.0811 = $0.1835
Pre-fix breakdown sum drifted 3x above the row.
Post-fix expectation: items sum to row.cost (within float epsilon),
and relative proportion preserved (0.1024/0.1835 ≈ 55.8%).
"""
row = _make_row(
cost_usd=0.0607,
cost_source="provider_billed",
breakdown=[
_make_item("z-ai/glm-5.1-20260406", 0.1024),
_make_item("deepseek/deepseek-v4-flash-20260423", 0.0811),
],
)
_reconcile_breakdown_to_row(row)
items = row["modelBreakdown"]
total = sum(item["costUsd"] for item in items)
assert abs(total - 0.0607) < 1e-6, f"breakdown sum {total} drifts from row 0.0607"
# Relative proportion preserved (within rounding):
expected_share = 0.1024 / 0.1835
actual_share = items[0]["costUsd"] / total
assert abs(actual_share - expected_share) < 1e-3, actual_share
# Source rebadged for UI disclosure.
for item in items:
assert item["costSource"] == "provider_billed_prorated"
assert item["cost_source"] == "provider_billed_prorated"
# Original estimate is preserved alongside the pro-rated cost.
assert "estimatedCostUsd" in item
assert item["estimatedCostUsd"] > item["costUsd"]
def test_billed_single_model_no_change() -> None:
row = _make_row(
cost_usd=0.05,
cost_source="provider_billed",
breakdown=[_make_item("only-model", 0.07)],
)
_reconcile_breakdown_to_row(row)
items = row["modelBreakdown"]
# Single-item path: leave as-is (cost_rollup pipeline handles single-item parity)
assert items[0]["costUsd"] == 0.07
assert items[0].get("costSource") != "provider_billed_prorated"
def test_estimate_only_no_change() -> None:
row = _make_row(
cost_usd=0.05,
cost_source="opensquilla_estimate",
breakdown=[
_make_item("model-a", 0.03),
_make_item("model-b", 0.02),
],
)
_reconcile_breakdown_to_row(row)
items = row["modelBreakdown"]
# Estimate-only rows: row cost is also estimate, sums match by construction.
assert items[0]["costUsd"] == 0.03
assert items[1]["costUsd"] == 0.02
for item in items:
assert item.get("costSource") != "provider_billed_prorated"
def test_mixed_source_also_prorates() -> None:
row = _make_row(
cost_usd=0.10,
cost_source="mixed",
breakdown=[
_make_item("model-a", 0.18),
_make_item("model-b", 0.02),
],
)
_reconcile_breakdown_to_row(row)
items = row["modelBreakdown"]
assert abs(sum(i["costUsd"] for i in items) - 0.10) < 1e-6
for item in items:
assert item["costSource"] == "provider_billed_prorated"
def test_zero_estimate_falls_back_to_equal_split() -> None:
"""Defensive: tracker breakdown can land with all-zero costs (e.g. when
pricing lookup fails for unknown model ids). Without the equal-split
fallback, the breakdown would still show $0 across the board even though
the row recorded a real billed total — even worse than the original drift.
"""
row = _make_row(
cost_usd=0.04,
cost_source="provider_billed",
breakdown=[
_make_item("unknown-model-a", 0.0),
_make_item("unknown-model-b", 0.0),
_make_item("unknown-model-c", 0.0),
],
)
_reconcile_breakdown_to_row(row)
items = row["modelBreakdown"]
total = sum(item["costUsd"] for item in items)
assert abs(total - 0.04) < 1e-6
# All three get the equal share.
for item in items:
assert abs(item["costUsd"] - 0.04 / 3) < 1e-6
assert item["costSource"] == "provider_billed_prorated"
def test_zero_row_cost_no_change() -> None:
row = _make_row(
cost_usd=0.0,
cost_source="provider_billed",
breakdown=[
_make_item("a", 0.01),
_make_item("b", 0.01),
],
)
_reconcile_breakdown_to_row(row)
items = row["modelBreakdown"]
# No row total to spread → leave untouched.
assert items[0]["costUsd"] == 0.01
def test_unavailable_source_no_change() -> None:
row = _make_row(
cost_usd=0.05,
cost_source="unavailable",
breakdown=[
_make_item("a", 0.02),
_make_item("b", 0.03),
],
)
_reconcile_breakdown_to_row(row)
items = row["modelBreakdown"]
# unavailable source is not in {provider_billed, mixed} so no rebadge.
assert items[0].get("costSource") != "provider_billed_prorated"
assert items[0]["costUsd"] == 0.02
def test_reconcile_skips_when_items_already_billed_and_sum_matches_row() -> None:
"""When billed items already sum to the row total, reconcile is a no-op.
If the tracker has filled in real per-call billed costs for every item and
their sum already matches the row total, the reconcile must do nothing: no
rebadge to
'provider_billed_prorated', no value rewrite. Otherwise the UI would
falsely show the "split is estimated" disclosure for sessions where the
split is in fact the literal provider receipt.
"""
row = _make_row(
cost_usd=0.1365,
cost_source="provider_billed",
breakdown=[
{"model": "anthropic/claude-4.7-opus", "costUsd": 0.1254,
"cost_usd": 0.1254, "costSource": "provider_billed",
"cost_source": "provider_billed"},
{"model": "z-ai/glm-5.1", "costUsd": 0.0111,
"cost_usd": 0.0111, "costSource": "provider_billed",
"cost_source": "provider_billed"},
],
)
_reconcile_breakdown_to_row(row)
items = row["modelBreakdown"]
# Values untouched.
assert items[0]["costUsd"] == 0.1254
assert items[1]["costUsd"] == 0.0111
# Source untouched (NOT rebadged to provider_billed_prorated).
assert items[0]["costSource"] == "provider_billed"
assert items[1]["costSource"] == "provider_billed"
def test_reconcile_still_prorates_when_mixed_items_drift_from_row() -> None:
"""Mixed-source breakdown whose sum DRIFTS from row.cost_usd still
triggers the pro-rate path (drift = real correction needed). row=0.10
but breakdown sums to 0.20 → pro-rate scales each to half."""
row = _make_row(
cost_usd=0.10,
cost_source="provider_billed",
breakdown=[
# First item has real billed (but inflated for this scenario)
{"model": "a", "costUsd": 0.18, "cost_usd": 0.18,
"costSource": "provider_billed", "cost_source": "provider_billed"},
# Second item is estimate-only
{"model": "b", "costUsd": 0.02, "cost_usd": 0.02,
"costSource": "opensquilla_estimate", "cost_source": "opensquilla_estimate"},
],
)
_reconcile_breakdown_to_row(row)
items = row["modelBreakdown"]
# Drift detected → pro-rate fallback rebadges all items.
assert all(it["costSource"] == "provider_billed_prorated" for it in items)
assert abs(sum(it["costUsd"] for it in items) - 0.10) < 1e-6
def test_reconcile_skips_mixed_breakdown_when_sum_already_matches_row() -> None:
"""Mixed-source breakdowns are no-ops when they already sum to the row.
When the row uses SessionUsage.total_cost (billed where available, estimate
where not), the breakdown sum already equals the row total. The fast-path
must skip even though item sources are heterogeneous —
otherwise mixed rows get falsely rebadged as provider_billed_prorated.
"""
row = _make_row(
cost_usd=0.08, # = 0.05 billed + 0.03 estimate
cost_source="mixed",
breakdown=[
{"model": "claude", "costUsd": 0.05, "cost_usd": 0.05,
"costSource": "provider_billed", "cost_source": "provider_billed"},
{"model": "deepseek", "costUsd": 0.03, "cost_usd": 0.03,
"costSource": "opensquilla_estimate", "cost_source": "opensquilla_estimate"},
],
)
_reconcile_breakdown_to_row(row)
items = row["modelBreakdown"]
# No rebadge — each item keeps its honest source.
assert items[0]["costSource"] == "provider_billed"
assert items[1]["costSource"] == "opensquilla_estimate"
# Values untouched.
assert items[0]["costUsd"] == 0.05
assert items[1]["costUsd"] == 0.03
def test_reconcile_fast_path_tolerates_subcent_drift() -> None:
"""Float arithmetic in the cost rollup pipeline can leave a sub-cent
drift between the row total and the per-model billed sum. The
fast-path tolerance (0.001) must absorb this without falling back to
pro-rate (which would needlessly rebadge as provider_billed_prorated)."""
row = _make_row(
cost_usd=0.13649, # 0.0001 below the breakdown sum of 0.1365
cost_source="provider_billed",
breakdown=[
{"model": "a", "costUsd": 0.1254, "cost_usd": 0.1254,
"costSource": "provider_billed", "cost_source": "provider_billed"},
{"model": "b", "costUsd": 0.0111, "cost_usd": 0.0111,
"costSource": "provider_billed", "cost_source": "provider_billed"},
],
)
_reconcile_breakdown_to_row(row)
items = row["modelBreakdown"]
# No rebadge, no value mutation — within tolerance.
assert items[0]["costSource"] == "provider_billed"
def test_camel_case_only_row_works() -> None:
"""Tracker rows in some paths use camelCase keys only."""
row = {
"session": "agent:main:webchat:test",
"costUsd": 0.10,
"costSource": "provider_billed",
"modelBreakdown": [
{"model": "a", "costUsd": 0.15},
{"model": "b", "costUsd": 0.05},
],
}
_reconcile_breakdown_to_row(row)
total = sum(i["costUsd"] for i in row["modelBreakdown"])
assert abs(total - 0.10) < 1e-6