Skip to content

symfonic.services.cost.values

values

The one budget decision shape (BUD-1).

Two enforcement points are deliberate โ€” the platform admits a request, the invocation re-checks before its own effects. Two decision shapes are not: the moment admission and re-check disagree about what "allowed" means, one of them is wrong and nobody can tell which.

BudgetDecision dataclass

BudgetDecision(allowed: bool, reason: str | None = None, code: str = 'budget_ok', daily_used_usd: float = 0.0, daily_limit_usd: float | None = None, monthly_used_usd: float = 0.0, monthly_limit_usd: float | None = None, retry_after_seconds: int = 3600)

Allowed or not, plus a stable code a transport can map on.

code is the field BUD-2 adds and the reason this type exists at all. The shipped path raises a plain HTTPException whose message begins "Budget exceeded:", and three routers branch on that prefix โ€” so a reworded or translated message turns a 429 into a 500. A caller that reads code cannot be broken by editing prose.

CostSummary dataclass

CostSummary(scope_key: str, window: str, cost_usd: float = 0.0, input_tokens: int = 0, output_tokens: int = 0, calls: int = 0)

A projection of runtime-service accounting (BUD-6).

A read model: it carries numbers somebody else computed. The platform performs no accounting arithmetic of its own, because two places that both know how to add up a bill will eventually disagree about a rounding rule.