e93507a09c
Lockfile supply-chain audit / lockfile supply-chain audit (push) Has been cancelled
Windows Studio GGUF CI / GPU prebuilt resolves without Visual Studio (push) Has been cancelled
Windows Studio GGUF CI / setup.ps1 unit tests (VS 2026 / CMake guard) (push) Has been cancelled
Windows Studio GGUF CI / real-VS detection (VS 2022) (push) Has been cancelled
Windows Studio GGUF CI / real-VS detection (VS 2026) (push) Has been cancelled
Windows Studio GGUF CI / VC++ runtime detect + install round-trip (windows-2025-vs2026) (push) Has been cancelled
Windows Studio GGUF CI / VC++ runtime detect + install round-trip (windows-latest) (push) Has been cancelled
Windows Studio Update CI / Studio Updating Tests (push) Has been cancelled
Wheel CI / Wheel build + content sanity + import smoke (push) Has been cancelled
Lint CI / Source lint (Python + shell + YAML + JSON + safety nets) (push) Has been cancelled
MLX CI on Mac M1 / dispatch (push) Has been cancelled
Security audit / advisory audit (pip + npm + cargo) (push) Has been cancelled
Security audit / pip scan-packages :: extras (push) Has been cancelled
Security audit / pip scan-packages :: studio (push) Has been cancelled
Security audit / pip scan-packages :: hf-stack (push) Has been cancelled
Security audit / npm scan-packages (Studio frontend tarballs) (push) Has been cancelled
Security audit / workflow-trigger lint (pull_request_target / cache-poisoning) (push) Has been cancelled
Security audit / pytest tests/security (push) Has been cancelled
Security audit / npm provenance + new install-script diff (push) Has been cancelled
Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Backend CI / (Python 3.10) (push) Has been cancelled
Backend CI / (Python 3.11) (push) Has been cancelled
Backend CI / (Python 3.12) (push) Has been cancelled
Backend CI / (Python 3.13) (push) Has been cancelled
Backend CI / Repo tests (CPU) (push) Has been cancelled
Frontend CI / Frontend build + bundle sanity (push) Has been cancelled
Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Studio GGUF CI / JSON, images (push) Has been cancelled
Mac Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Mac Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Mac Studio GGUF CI / JSON, images (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-14) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-15) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-26) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-15-intel) (push) Has been cancelled
Mac Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-26-intel) (push) Has been cancelled
Mac Studio UI CI / Chat UI Tests (push) Has been cancelled
Studio Tauri CI / Tauri Linux debug build (no codesign) (push) Has been cancelled
Mac Studio Update CI / Studio Updating Tests (push) Has been cancelled
Studio UI CI / Chat UI Tests (push) Has been cancelled
Windows Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Windows Studio UI CI / Chat UI Tests (push) Has been cancelled
Studio Update CI / Studio Updating Tests (push) Has been cancelled
Core / Core (HF=default + TRL=default) (push) Has been cancelled
Core / Core (HF=4.57.6 + TRL<1) (push) Has been cancelled
Core / Core (HF=latest + TRL=latest) (push) Has been cancelled
Core / llama.cpp build + smoke (push) Has been cancelled
Windows Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Windows Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Windows Studio GGUF CI / JSON, images (push) Has been cancelled
Windows Studio GGUF CI / Studio install + inference without Visual Studio (push) Has been cancelled
Studio export capability / capability (macos-latest) (push) Has been cancelled
Studio export capability / capability (ubuntu-latest) (push) Has been cancelled
Studio export capability / capability (windows-latest) (push) Has been cancelled
Cross-platform parity / parity (macos-latest) (push) Has been cancelled
Cross-platform parity / parity (windows-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
Studio load-orchestrator CI / test (push) Has been cancelled
475 lines
16 KiB
Python
475 lines
16 KiB
Python
# SPDX-License-Identifier: AGPL-3.0-only
|
|
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
|
|
|
|
"""Adversarial edge cases for ``calculate_cost`` / ``_lookup``: prefix
|
|
boundary, negative tokens, chat vs raw parity, long-context crossover
|
|
on billable count, and malformed sub-objects."""
|
|
|
|
import math
|
|
|
|
from core.inference.pricing import (
|
|
ANTHROPIC_CACHE_5M_WRITE_MULT,
|
|
ANTHROPIC_CACHE_READ_MULT,
|
|
ANTHROPIC_PRICING,
|
|
OPENAI_CACHE_READ_MULT,
|
|
OPENAI_PRICING,
|
|
_lookup,
|
|
calculate_cost,
|
|
)
|
|
|
|
|
|
def _isclose(
|
|
a,
|
|
b,
|
|
tol = 1e-6,
|
|
):
|
|
return math.isclose(a, b, rel_tol = tol, abs_tol = tol)
|
|
|
|
|
|
# ── prefix-match boundary checks ────────────────────────────────────
|
|
|
|
|
|
def test_prefix_match_requires_dash_boundary_opus_variant():
|
|
# `claude-opus-4-15` must not inherit `claude-opus-4-1` pricing; the next
|
|
# char must be `-` or end-of-string.
|
|
assert _lookup("anthropic", "claude-opus-4-15") is None
|
|
out = calculate_cost(
|
|
"anthropic",
|
|
"claude-opus-4-15",
|
|
{"input_tokens": 1_000_000, "output_tokens": 0},
|
|
)
|
|
assert out["priced"] is False
|
|
assert out["total_usd"] == 0.0
|
|
|
|
|
|
def test_prefix_match_requires_dash_boundary_gpt_variant():
|
|
# Same dash-boundary invariant for OpenAI ids.
|
|
assert _lookup("openai", "gpt-5.55") is None
|
|
assert _lookup("openai", "gpt-5.55-2026-04-23") is None
|
|
out = calculate_cost(
|
|
"openai",
|
|
"gpt-5.55-2026-04-23",
|
|
{"input_tokens": 1_000_000, "output_tokens": 0},
|
|
)
|
|
assert out["priced"] is False
|
|
|
|
|
|
def test_prefix_match_requires_dash_boundary_pro_lookalike():
|
|
# `gpt-5.5-prod` must fall through `gpt-5.5-pro` (6x overcharge) and land on
|
|
# the canonical `gpt-5.5` row.
|
|
prices = _lookup("openai", "gpt-5.5-prod")
|
|
assert prices is not None
|
|
assert (
|
|
prices["input_per_mtok"] == OPENAI_PRICING["gpt-5.5"]["input_per_mtok"]
|
|
), "expected fallback to gpt-5.5 base ($5), not gpt-5.5-pro ($30)"
|
|
out = calculate_cost(
|
|
"openai",
|
|
"gpt-5.5-prod",
|
|
{"input_tokens": 100_000, "output_tokens": 0},
|
|
)
|
|
assert out["priced"] is True
|
|
assert _isclose(out["input_usd"], 100_000 / 1_000_000.0 * 5.0)
|
|
|
|
|
|
def test_prefix_match_still_resolves_legit_dated_snapshots():
|
|
# Boundary fix must not regress legit dated snapshots.
|
|
out = calculate_cost(
|
|
"openai",
|
|
"gpt-5.4-mini-2026-04-23",
|
|
{"input_tokens": 1_000_000, "output_tokens": 0},
|
|
)
|
|
assert out["priced"] is True
|
|
assert _isclose(out["input_usd"], 0.75)
|
|
|
|
# Anthropic dated snapshot still resolves to the canonical row.
|
|
out = calculate_cost(
|
|
"anthropic",
|
|
"claude-opus-4-7-20260414",
|
|
{"input_tokens": 1_000_000, "output_tokens": 0},
|
|
)
|
|
assert out["priced"] is True
|
|
assert _isclose(out["input_usd"], 5.0)
|
|
|
|
|
|
# ── precedence: input_tokens wins over prompt_tokens (and 0 is real) ──
|
|
|
|
|
|
def test_explicit_zero_input_tokens_wins_over_stale_prompt_tokens():
|
|
out = calculate_cost(
|
|
"openai",
|
|
"gpt-5.5",
|
|
{
|
|
"input_tokens": 0,
|
|
"prompt_tokens": 1_000_000, # stale chat-style mirror
|
|
"output_tokens": 100,
|
|
},
|
|
)
|
|
assert out["billable_input_tokens"] == 0
|
|
assert out["input_usd"] == 0.0
|
|
|
|
|
|
def test_none_input_tokens_falls_through_to_prompt_tokens():
|
|
# `None` means "key present but unset"; chat-style mirror wins.
|
|
out = calculate_cost(
|
|
"openai",
|
|
"gpt-5.5",
|
|
{
|
|
"input_tokens": None,
|
|
"prompt_tokens": 200_000,
|
|
"output_tokens": None,
|
|
"completion_tokens": 5_000,
|
|
},
|
|
)
|
|
assert out["billable_input_tokens"] == 200_000
|
|
assert out["billable_output_tokens"] == 5_000
|
|
assert _isclose(out["input_usd"], 200_000 / 1_000_000.0 * 5.0)
|
|
assert _isclose(out["output_usd"], 5_000 / 1_000_000.0 * 30.0)
|
|
|
|
|
|
# ── negative / corrupted upstream values clamp to zero ──────────────
|
|
|
|
|
|
def test_negative_tokens_clamp_to_zero_no_negative_bill():
|
|
out = calculate_cost(
|
|
"openai",
|
|
"gpt-5.5",
|
|
{"input_tokens": -100, "output_tokens": -50},
|
|
)
|
|
assert out["billable_input_tokens"] == 0
|
|
assert out["billable_output_tokens"] == 0
|
|
assert out["input_usd"] == 0.0
|
|
assert out["output_usd"] == 0.0
|
|
assert out["total_usd"] == 0.0
|
|
|
|
|
|
def test_negative_cache_buckets_clamp_to_zero():
|
|
# Negative cache_read on Anthropic would otherwise refund the bill.
|
|
out = calculate_cost(
|
|
"anthropic",
|
|
"claude-opus-4-7",
|
|
{
|
|
"input_tokens": 1_000,
|
|
"output_tokens": 0,
|
|
"cache_creation_input_tokens": -500,
|
|
"cache_read_input_tokens": -1_000,
|
|
},
|
|
)
|
|
assert out["cache_write_usd"] == 0.0
|
|
assert out["cache_read_usd"] == 0.0
|
|
assert out["billable_input_tokens"] == 1_000
|
|
assert out["total_usd"] >= 0.0
|
|
|
|
|
|
def test_negative_prompt_tokens_chat_style_clamp():
|
|
out = calculate_cost(
|
|
"openai",
|
|
"gpt-5.4-mini",
|
|
{"prompt_tokens": -100, "completion_tokens": -50},
|
|
)
|
|
assert out["billable_input_tokens"] == 0
|
|
assert out["billable_output_tokens"] == 0
|
|
assert out["total_usd"] == 0.0
|
|
|
|
|
|
# ── cache_read > prompt_tokens corruption: no negative billable ─────
|
|
|
|
|
|
def test_anthropic_chat_cache_read_exceeds_prompt_no_negative_billable():
|
|
# cache_read > prompt_tokens clamps uncached_input at 0; billable still
|
|
# reflects cache buckets (we charge for what we got).
|
|
out = calculate_cost(
|
|
"anthropic",
|
|
"claude-opus-4-7",
|
|
{
|
|
"prompt_tokens": 100,
|
|
"cache_creation_input_tokens": 0,
|
|
"cache_read_input_tokens": 500,
|
|
"completion_tokens": 0,
|
|
},
|
|
)
|
|
assert out["input_usd"] == 0.0 # uncached clamped to 0
|
|
assert out["billable_input_tokens"] == 500 # 0 uncached + 500 cache_read
|
|
# cache_read still priced at the discount rate.
|
|
base = ANTHROPIC_PRICING["claude-opus-4-7"]["input_per_mtok"]
|
|
assert _isclose(out["cache_read_usd"], 500 / 1_000_000.0 * base * ANTHROPIC_CACHE_READ_MULT)
|
|
|
|
|
|
def test_openai_raw_cached_tokens_exceeds_input_clamp_non_cached():
|
|
# OpenAI variant: cached > input must not produce negative input_usd.
|
|
base = OPENAI_PRICING["gpt-5.5"]["input_per_mtok"]
|
|
out = calculate_cost(
|
|
"openai",
|
|
"gpt-5.5",
|
|
{
|
|
"input_tokens": 100,
|
|
"output_tokens": 0,
|
|
"input_tokens_details": {"cached_tokens": 500},
|
|
},
|
|
)
|
|
assert out["input_usd"] == 0.0
|
|
# Cache read still priced (the 0.1x bucket).
|
|
assert _isclose(out["cache_read_usd"], 500 / 1_000_000.0 * base * OPENAI_CACHE_READ_MULT)
|
|
|
|
|
|
# ── long-context tier crosses on billable, including cache_creation ──
|
|
|
|
|
|
def test_openai_long_context_triggers_on_cache_creation_inflated_billable():
|
|
# cache_creation pushes billable past 272k -> long-context tier must fire to
|
|
# avoid undercounting.
|
|
out = calculate_cost(
|
|
"openai",
|
|
"gpt-5.5",
|
|
{
|
|
"input_tokens": 250_000,
|
|
"cache_creation_input_tokens": 50_000,
|
|
"output_tokens": 1_000,
|
|
},
|
|
)
|
|
assert out["billable_input_tokens"] == 300_000
|
|
assert "long-context" in out["model_priced"]
|
|
assert _isclose(out["input_usd"], 250_000 / 1_000_000.0 * 10.0)
|
|
assert _isclose(out["output_usd"], 1_000 / 1_000_000.0 * 45.0)
|
|
|
|
|
|
def test_openai_long_context_threshold_boundary_inclusive():
|
|
# Threshold is inclusive (>=).
|
|
out = calculate_cost(
|
|
"openai",
|
|
"gpt-5.5",
|
|
{"input_tokens": 272_000, "output_tokens": 1_000},
|
|
)
|
|
assert "long-context" in out["model_priced"]
|
|
out_lo = calculate_cost(
|
|
"openai",
|
|
"gpt-5.5",
|
|
{"input_tokens": 271_999, "output_tokens": 1_000},
|
|
)
|
|
assert "long-context" not in out_lo["model_priced"]
|
|
|
|
|
|
# ── chat-style vs raw envelope parity at OpenAI long-context tier ──
|
|
|
|
|
|
def test_openai_chat_envelope_long_context_parity_with_raw():
|
|
raw = calculate_cost(
|
|
"openai",
|
|
"gpt-5.5",
|
|
{"input_tokens": 300_000, "output_tokens": 10_000},
|
|
)
|
|
chat = calculate_cost(
|
|
"openai",
|
|
"gpt-5.5",
|
|
{"prompt_tokens": 300_000, "completion_tokens": 10_000},
|
|
)
|
|
assert _isclose(chat["total_usd"], raw["total_usd"])
|
|
assert "long-context" in chat["model_priced"]
|
|
assert "long-context" in raw["model_priced"]
|
|
|
|
|
|
# ── malformed sub-objects: no crash, no false bill ──────────────────
|
|
|
|
|
|
def test_cache_creation_as_int_does_not_crash():
|
|
# Proxies sometimes fold cache_creation to an int; tolerate it and fall back
|
|
# to the 5m default.
|
|
base = ANTHROPIC_PRICING["claude-opus-4-7"]["input_per_mtok"]
|
|
out = calculate_cost(
|
|
"anthropic",
|
|
"claude-opus-4-7",
|
|
{
|
|
"input_tokens": 0,
|
|
"output_tokens": 0,
|
|
"cache_creation_input_tokens": 1_000_000,
|
|
"cache_creation": 12345, # malformed; must not raise
|
|
},
|
|
)
|
|
# Falls back to 5m default for the whole bucket.
|
|
assert _isclose(
|
|
out["cache_write_usd"],
|
|
1_000_000 / 1_000_000.0 * base * ANTHROPIC_CACHE_5M_WRITE_MULT,
|
|
)
|
|
|
|
|
|
def test_non_dict_server_tool_use_is_ignored():
|
|
out = calculate_cost(
|
|
"anthropic",
|
|
"claude-opus-4-7",
|
|
{"input_tokens": 100, "output_tokens": 100, "server_tool_use": "garbage"},
|
|
)
|
|
assert out["server_tools_usd"] == 0.0
|
|
|
|
out = calculate_cost(
|
|
"openai",
|
|
"gpt-5.5",
|
|
{"input_tokens": 100, "output_tokens": 100, "openai_tool_use": [1, 2, 3]},
|
|
)
|
|
assert out["server_tools_usd"] == 0.0
|
|
|
|
|
|
def test_non_dict_input_tokens_details_is_ignored():
|
|
out = calculate_cost(
|
|
"openai",
|
|
"gpt-5.5",
|
|
{
|
|
"input_tokens": 100,
|
|
"output_tokens": 0,
|
|
"input_tokens_details": "nope",
|
|
"prompt_tokens_details": [1, 2, 3],
|
|
},
|
|
)
|
|
# No cached_tokens recovered -> no discount.
|
|
assert out["cache_read_usd"] == 0.0
|
|
|
|
|
|
# ── unknown provider degrades gracefully ────────────────────────────
|
|
|
|
|
|
def test_unknown_provider_priced_false_zero_bill():
|
|
out = calculate_cost(
|
|
"gemini",
|
|
"gemini-pro",
|
|
{"input_tokens": 1_000_000, "output_tokens": 1_000_000},
|
|
)
|
|
assert out["priced"] is False
|
|
assert out["total_usd"] == 0.0
|
|
# Tokens still report for the UI.
|
|
assert out["billable_input_tokens"] == 1_000_000
|
|
assert out["billable_output_tokens"] == 1_000_000
|
|
|
|
|
|
def test_anthropic_provider_with_openai_model_priced_false():
|
|
# OpenAI id against Anthropic table must not falsely match.
|
|
out = calculate_cost(
|
|
"anthropic",
|
|
"gpt-5.5",
|
|
{"input_tokens": 1_000_000, "output_tokens": 0},
|
|
)
|
|
assert out["priced"] is False
|
|
|
|
|
|
# ── all-zero / empty usage stays at zero ────────────────────────────
|
|
|
|
|
|
def test_empty_usage_dict_zero_bill():
|
|
out = calculate_cost("openai", "gpt-5.5", {})
|
|
assert out["priced"] is True # model is in the table
|
|
assert out["billable_input_tokens"] == 0
|
|
assert out["total_usd"] == 0.0
|
|
|
|
|
|
# ── Defense-in-depth: Anthropic prompt_tokens_details.cached_tokens ──
|
|
|
|
|
|
def test_anthropic_prompt_tokens_details_fallback_when_native_key_missing():
|
|
"""Chat-style envelope without `cache_read_input_tokens` but with mirrored
|
|
`prompt_tokens_details.cached_tokens` should still apply the cache_read
|
|
discount."""
|
|
r = calculate_cost(
|
|
provider = "anthropic",
|
|
model = "claude-opus-4-7",
|
|
usage = {
|
|
"prompt_tokens": 1_000_000,
|
|
"completion_tokens": 0,
|
|
# Mirrored shape only (no native key).
|
|
"prompt_tokens_details": {"cached_tokens": 1_000_000},
|
|
"cache_creation_input_tokens": 0,
|
|
},
|
|
)
|
|
assert r["billable_input_tokens"] == 1_000_000, r
|
|
# 1M cached at 0.1x of $5 (opus 4.7) = $0.50
|
|
assert math.isclose(r["cache_read_usd"], 0.5, rel_tol = 1e-3), r
|
|
|
|
|
|
def test_anthropic_native_key_takes_precedence_over_mirrored():
|
|
"""When both native and mirrored cache-read fields are present, the native
|
|
Anthropic field wins (mirror is fallback-only)."""
|
|
r = calculate_cost(
|
|
provider = "anthropic",
|
|
model = "claude-opus-4-7",
|
|
usage = {
|
|
"prompt_tokens": 1_000_000,
|
|
"cache_read_input_tokens": 800_000,
|
|
"prompt_tokens_details": {"cached_tokens": 1_000_000},
|
|
"cache_creation_input_tokens": 0,
|
|
},
|
|
)
|
|
# billable = uncached_input + cache_creation + cache_read
|
|
# = (1M - 0 - 800k) + 0 + 800k = 1M
|
|
assert r["billable_input_tokens"] == 1_000_000, r
|
|
# cache_read uses 800k (native), not 1M (mirrored).
|
|
assert math.isclose(r["cache_read_usd"], 0.4, rel_tol = 1e-3), r
|
|
|
|
|
|
def test_anthropic_native_zero_takes_precedence_over_mirrored():
|
|
"""Explicit `cache_read_input_tokens: 0` is authoritative; a stale mirrored
|
|
block from a proxy must not inflate cache_read past it."""
|
|
r = calculate_cost(
|
|
provider = "anthropic",
|
|
model = "claude-opus-4-7",
|
|
usage = {
|
|
"input_tokens": 1_000_000,
|
|
"output_tokens": 0,
|
|
"cache_read_input_tokens": 0,
|
|
# Stale mirror from a proxy; must be ignored (native present).
|
|
"prompt_tokens_details": {"cached_tokens": 1_000_000},
|
|
},
|
|
)
|
|
# Native is 0 -> cache_read stays 0.
|
|
assert r["cache_read_usd"] == 0.0, r
|
|
# billable = input + cache_creation + cache_read = 1M + 0 + 0
|
|
assert r["billable_input_tokens"] == 1_000_000, r
|
|
# 1M uncached at $5/M (no discount).
|
|
assert math.isclose(r["input_usd"], 5.0, rel_tol = 1e-3), r
|
|
assert math.isclose(r["total_usd"], 5.0, rel_tol = 1e-3), r
|
|
|
|
|
|
# ── _build_usage_chunk preserves cache_creation breakdown ──
|
|
|
|
|
|
def test_build_usage_chunk_forwards_anthropic_cache_creation_breakdown():
|
|
"""Chat-style envelope must carry the 5m/1h cache-write breakdown so
|
|
downstream cost calc applies the 2x 1h premium."""
|
|
import json
|
|
from core.inference.external_provider import _build_usage_chunk
|
|
|
|
chunk = _build_usage_chunk(
|
|
completion_id = "cmpl-x",
|
|
provider = "anthropic",
|
|
last_usage = {
|
|
"input_tokens": 10,
|
|
"output_tokens": 5,
|
|
"cache_creation_input_tokens": 1_000_000,
|
|
"cache_read_input_tokens": 0,
|
|
"cache_creation": {
|
|
"ephemeral_5m_input_tokens": 250_000,
|
|
"ephemeral_1h_input_tokens": 750_000,
|
|
},
|
|
},
|
|
)
|
|
assert chunk is not None
|
|
payload = json.loads(chunk.split("data: ", 1)[1])
|
|
cc = payload["usage"]["cache_creation"]
|
|
assert cc["ephemeral_1h_input_tokens"] == 750_000, cc
|
|
assert cc["ephemeral_5m_input_tokens"] == 250_000, cc
|
|
|
|
|
|
def test_calculate_cost_uses_forwarded_cache_creation_for_1h_premium():
|
|
"""Re-emitted chat envelope must price 1h cache writes at 2x base."""
|
|
r = calculate_cost(
|
|
provider = "anthropic",
|
|
model = "claude-opus-4-7",
|
|
usage = {
|
|
"prompt_tokens": 1_000_010,
|
|
"completion_tokens": 0,
|
|
"cache_creation_input_tokens": 1_000_000,
|
|
"cache_read_input_tokens": 0,
|
|
"cache_creation": {
|
|
"ephemeral_5m_input_tokens": 0,
|
|
"ephemeral_1h_input_tokens": 1_000_000,
|
|
},
|
|
},
|
|
)
|
|
# 1M at 1h-premium (2x of $5 = $10); 5m baseline would be $6.25.
|
|
assert math.isclose(r["cache_write_usd"], 10.0, rel_tol = 1e-2), r
|