Features (see spec.md v2): - Wire receipt classification into the upload flow; cheap model (Haiku 4.5) is now the default, shown as a footnote with per-scan cost in cents. - Skip-AI toggle to enter fields by hand. - Duplicate-transaction warning: live check on date+amount, gated submit. - Tally tab: person x year totals with margins and grand total. - Recent uploads / recent receipts tabs with paging and file serving. - People reconcile on startup: merge stray partial names (e.g. "Jude" -> "Jude Tremblay"), reassigning receipts; idempotent seeding. - scripts/build.sh builds the binary; scripts/run.sh builds and runs with .env. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
47 lines
2 KiB
Go
47 lines
2 KiB
Go
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
|
||
}
|