Skip to main content

PricingEngine

The PricingEngine is the pure calculation core. It evaluates pricing expressions against usage metrics. No database dependency — can be used standalone.

Creating an Engine

from bursar import PricingEngine

# From a dict (testing, stateless)
engine = PricingEngine.from_dict({
"version": 1,
"models": {"_default": "input_tokens * 0.001 + output_tokens * 0.003"},
})

# From a config file — load it yourself, then pass the dict in
import yaml
with open("pricing.yaml") as f:
engine = PricingEngine.from_dict(yaml.safe_load(f))

Methods

calculate(metrics: UsageMetrics) -> CostBreakdown

Evaluate pricing expressions against usage metrics.

result = engine.calculate(
UsageMetrics(model="gpt-4", input_tokens=500, output_tokens=200)
)
print(f"Total: {result.total}")
print(f"Model credits: {result.model_credits}")

calculate_batch(metrics: list[UsageMetrics]) -> list[CostBreakdown]

Evaluate multiple metrics at once.

resolve_model(model_version: str) -> str | None

Resolve a model version string against configured model names.

has_model(model_name: str) -> bool

Check if a specific model is configured.

get_fixed_cost(job_name: str) -> Decimal | None

Get the fixed cost for a named job. Returns None for an unknown/unconfigured job (so callers can reject it rather than charge 0).

pricing_schema() -> PricingConfigData

Get the raw pricing config dict that the engine was initialized with.

min_balance: Decimal

Property — the configured minimum balance floor.

CostBreakdown

Returned by calculate(). All monetary fields are Decimal, quantized to 4 dp ROUND_HALF_UP; total is computed once by the engine, clamped to >= 0, and never re-derived (and never truncated to an integer):

FieldTypeDescription
totalDecimalTotal credits
model_creditsDecimalCredits from model expression
tool_creditsDecimalCredits from tool expressions
search_creditsDecimalCredits from search expressions
cache_savingsDecimalCache expression result (negative = savings)
fixed_creditsDecimalFixed job cost (0 when not applicable)
breakdowndictDebug info: model, token counts, tool count

UsageMetrics

Input to calculate():

FieldTypeDefault
modelstr"_default"
input_tokensint0
output_tokensint0
cache_read_tokensint0
cache_write_tokensint0
tool_callslist[ToolCall][]
search_queriesint0
search_resultsint0
web_search_callsint0
code_exec_callsint0
fixed_jobstr ` None`

Expression Safety

The engine uses Python's ast module with a strict allowlist:

  1. Parses ast.parse(expr, mode="eval")
  2. Walks the AST against a strict node allowlist; ** (ast.Pow) is excluded, so exponentiation is rejected
  3. Function calls whitelisted: ceil, floor, min, max, round, if, tier, clamp, percentile
  4. Rejects: attributes (x.__class__), subscripts (x[0]), lambdas, comprehensions, f-strings, imports
  5. All math is exact Decimal; division/modulo by zero raise ExpressionError; the result is asserted finite
  6. Evaluation namespace has __builtins__ emptied
  7. All expressions (and variable names) validated at config-load time

See the Expression Reference for the full operator/function table.