symfonic.services.cost¶
cost ¶
Cost and spend: port-registry row 5, and the adapter over the shipped ledger.
Distinct from :mod:symfonic.services.budget, which is the context-window
budget — token estimation, allocation and truncation. This package is about
money: whether a scope may incur another billable call, and what it has spent.
The two were never the same concern and sharing a word for them has cost more
than one reader an afternoon.
BudgetCheck ¶
Bases: Protocol
The admission read. One method, because it answers one question.
check
async
¶
May this scope incur another billable call?
is_admin is a policy input, not a caller-side branch (BUD-4): the
port decides whether an administrator bypasses, and the transport never
skips the call.
Source code in src/symfonic/services/cost/ports.py
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.
CostReadModel ¶
Bases: Protocol
The projection /metrics/* renders (BUD-6).
summarize
async
¶
Aggregated spend for a scope and window. Tenant-scoped, always.
SEC-TEN-2 makes isolation unconditional — never gated on a collector being enabled — so an implementation that cannot scope a summary must refuse it rather than widen it.
Source code in src/symfonic/services/cost/ports.py
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.
TrackerBudgetCheck ¶
BudgetCheck over TokenBudgetTracker.
The ledger is keyed by tenant id and stays that way: inventing a sub-tenant-granular key here would silently give every sub-tenant its own ceiling, which is a pricing change wearing a refactor's clothes. The subject's tenant segment is what the tracker sees, and that is recorded rather than assumed.