hsa-app/internal/classify/pricing.go

48 lines
2 KiB
Go
Raw Normal View History

package classify
// Pricing for receipt classification. Token counts come straight from each API
// response (exact, free); per-token prices are not available from any Anthropic
// API, so they live here as constants keyed by exact model id. A model id is
// priced once and never re-priced — Anthropic ships price changes as new ids — so
// this table only needs a new row when we adopt a new model (i.e. when we'd be
// changing CLASSIFY_MODEL anyway). An unknown id yields ok=false, and callers show
// the token counts without a (wrong) dollar figure rather than guessing.
// Usage is the token accounting from one Messages API response.
type Usage struct {
InputTokens int
OutputTokens int
CacheReadTokens int
CacheCreationTokens int
}
// rate holds a model's price in cents per one million tokens.
type rate struct {
inPerM float64 // cents per 1M input tokens
outPerM float64 // cents per 1M output tokens
}
// modelRates is the hand-maintained price table (cents per 1M tokens).
// $1/1M == 100 cents/1M.
var modelRates = map[string]rate{
"claude-haiku-4-5-20251001": {inPerM: 100, outPerM: 500},
"claude-haiku-4-5": {inPerM: 100, outPerM: 500},
"claude-sonnet-4-6": {inPerM: 300, outPerM: 1500},
"claude-opus-4-8": {inPerM: 500, outPerM: 2500},
"claude-opus-4-7": {inPerM: 500, outPerM: 2500},
}
// CostCents returns the cost of a classification call in cents, and whether the
// model's price is known. Cache tokens are billed at the input rate (a slight
// over-estimate — cache reads actually bill at ~0.1× input — which is fine for a
// "what did this scan cost" readout). Returns ok=false for an unpriced model.
func CostCents(model string, u Usage) (float64, bool) {
r, ok := modelRates[model]
if !ok {
return 0, false
}
inTokens := float64(u.InputTokens + u.CacheReadTokens + u.CacheCreationTokens)
cents := inTokens*r.inPerM/1_000_000 + float64(u.OutputTokens)*r.outPerM/1_000_000
return cents, true
}