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):
| Field | Type | Description |
|---|---|---|
total | Decimal | Total credits |
model_credits | Decimal | Credits from model expression |
tool_credits | Decimal | Credits from tool expressions |
search_credits | Decimal | Credits from search expressions |
cache_savings | Decimal | Cache expression result (negative = savings) |
fixed_credits | Decimal | Fixed job cost (0 when not applicable) |
breakdown | dict | Debug info: model, token counts, tool count |
UsageMetrics
Input to calculate():
| Field | Type | Default |
|---|---|---|
model | str | "_default" |
input_tokens | int | 0 |
output_tokens | int | 0 |
cache_read_tokens | int | 0 |
cache_write_tokens | int | 0 |
tool_calls | list[ToolCall] | [] |
search_queries | int | 0 |
search_results | int | 0 |
web_search_calls | int | 0 |
code_exec_calls | int | 0 |
fixed_job | str ` | None` |
Expression Safety
The engine uses Python's ast module with a strict allowlist:
- Parses
ast.parse(expr, mode="eval") - Walks the AST against a strict node allowlist;
**(ast.Pow) is excluded, so exponentiation is rejected - Function calls whitelisted:
ceil,floor,min,max,round,if,tier,clamp,percentile - Rejects: attributes (
x.__class__), subscripts (x[0]), lambdas, comprehensions, f-strings, imports - All math is exact
Decimal; division/modulo by zero raiseExpressionError; the result is asserted finite - Evaluation namespace has
__builtins__emptied - All expressions (and variable names) validated at config-load time
See the Expression Reference for the full operator/function table.