fix
This commit is contained in:
@@ -38,6 +38,68 @@ when the call failed.
|
||||
log line, and the insert uses `context.WithoutCancel` so a cancelled job still
|
||||
records the call that was in flight.
|
||||
|
||||
## Cost (Admin → AI costs)
|
||||
|
||||
`/admin/ai-costs` answers "what does each user cost me": spend by user, by company
|
||||
and by model, with a grand total across all tenants.
|
||||
|
||||
It reads `ai_usage_daily`, a rollup written next to every captured call — one row
|
||||
per day / company / user / model / role. That is why it can be kept for
|
||||
`aiaudit.UsageRetentionDays` (**3 years**) while the prompt bodies expire in a week:
|
||||
a busy tenant adds tens of rows a day, not one per product.
|
||||
|
||||
Money is **micro-USD integers** end to end (`cost_micros`). Summing millions of
|
||||
fractional-cent calls as floats drifts; integers add up exactly. `cost_usd` in the
|
||||
API is a display convenience — never re-sum it.
|
||||
|
||||
Prices live in `ai_model_prices`, seeded with the OpenAI GPT-5.6 list prices
|
||||
effective 2026-07-30 (per 1M tokens):
|
||||
|
||||
| model | input | cached input | output |
|
||||
|---|---|---|---|
|
||||
| gpt-5.6-luna | $0.20 | $0.02 | $1.20 |
|
||||
| gpt-5.6-terra | $2.00 | $0.20 | $12.00 |
|
||||
| gpt-5.6-sol | $5.00 | $0.50 | $30.00 |
|
||||
|
||||
Update the table when list prices change (`UPDATE ai_model_prices …`). Recorded
|
||||
cost is never recalculated, so history keeps the rate that applied at the time.
|
||||
`aiaudit.defaultModelPrices` mirrors these as a code fallback for a model with no
|
||||
row; an unpriced model records usage at zero cost rather than guessing.
|
||||
|
||||
Cached prompt tokens are read from `usage.prompt_tokens_details.cached_tokens` and
|
||||
billed at the cached rate — they are a *subset* of prompt tokens, so they are
|
||||
discounted, never added on top.
|
||||
|
||||
```
|
||||
GET /api/admin/ai-costs?days=30&from=&to=&company_id=
|
||||
```
|
||||
|
||||
### History from before capture existed
|
||||
|
||||
`cmd/backfill-ai-usage` recovers cost for products processed before this shipped,
|
||||
reading the provider usage blocks still stored in `processed_products.gpt_response`.
|
||||
|
||||
```
|
||||
go run ./cmd/backfill-ai-usage # dry run
|
||||
go run ./cmd/backfill-ai-usage -apply
|
||||
```
|
||||
|
||||
It is best effort, and the limits are real:
|
||||
|
||||
- **Only rows that still carry a usage block.** `gpt_response` is overwritten on
|
||||
every reprocess and absent on older or failed rows.
|
||||
- **No per-user attribution** — `processed_products.user_id` is null on those rows,
|
||||
so recovered spend lands under "Unattributed" in the by-user view. By-company and
|
||||
the grand total are complete.
|
||||
- **The day is `updated_at`**, the last write to the row, not necessarily when the
|
||||
call was made.
|
||||
- **Cached-token discounts cannot be recovered**, so recovered input is priced as
|
||||
fully fresh — an over-estimate, never an under-estimate.
|
||||
|
||||
Rows are written with `source='backfill'`, so a re-run replaces its own rows and can
|
||||
never double-count live capture. By default the scan stops at the first day live
|
||||
capture recorded; `-before YYYY-MM-DD` overrides that.
|
||||
|
||||
## Retention
|
||||
|
||||
Rows are large and high volume — one per product per language, a few KB each. They
|
||||
|
||||
Reference in New Issue
Block a user