fix(pricing): book daily cost to the day tokens were spent
token_usage holds a session's cumulative totals with no time, so /api/pricing/cost booked a multi-day session's whole cost to its start date and the status bar's Today read ~$0. TranscriptCache now also splits usage by each message's UTC hour (tokensByHour); the hook writer stores it in the new token_usage_hourly table (MAX per hour) and the server back-fills existing sessions at start. The daily split books each total over its hourly rows' local days and the rest to the start date, so the days still add up to the total.
This commit is contained in:
@@ -219,6 +219,29 @@ db.exec(`
|
||||
FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- The same buckets split by the UTC hour (YYYY-MM-DDTHH) the tokens were
|
||||
-- spent in, read from each transcript message's timestamp. Only the daily
|
||||
-- cost split reads it (GET /api/pricing/cost): token_usage stays the total,
|
||||
-- and whatever it holds beyond these rows is booked to the session's start.
|
||||
CREATE TABLE IF NOT EXISTS token_usage_hourly (
|
||||
session_id TEXT NOT NULL,
|
||||
hour TEXT NOT NULL,
|
||||
model TEXT NOT NULL,
|
||||
speed TEXT NOT NULL DEFAULT 'standard',
|
||||
inference_geo TEXT NOT NULL DEFAULT 'global',
|
||||
service_tier TEXT NOT NULL DEFAULT 'standard',
|
||||
input_tokens INTEGER NOT NULL DEFAULT 0,
|
||||
output_tokens INTEGER NOT NULL DEFAULT 0,
|
||||
cache_read_tokens INTEGER NOT NULL DEFAULT 0,
|
||||
cache_write_tokens INTEGER NOT NULL DEFAULT 0,
|
||||
cache_write_1h_tokens INTEGER NOT NULL DEFAULT 0,
|
||||
web_search_requests INTEGER NOT NULL DEFAULT 0,
|
||||
web_fetch_requests INTEGER NOT NULL DEFAULT 0,
|
||||
code_execution_requests INTEGER NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (session_id, hour, model, speed, inference_geo, service_tier),
|
||||
FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS model_pricing (
|
||||
model_pattern TEXT PRIMARY KEY,
|
||||
display_name TEXT NOT NULL,
|
||||
@@ -1774,6 +1797,27 @@ const stmts = {
|
||||
baseline_cache_write = 0, baseline_cache_write_1h = 0,
|
||||
baseline_web_search = 0, baseline_web_fetch = 0, baseline_code_execution = 0
|
||||
`),
|
||||
// One hour of one bucket, from a whole-transcript parse. MAX, not replace:
|
||||
// a transcript that shrank (rewritten) must not take back an hour already
|
||||
// counted. Args: session_id, hour, model, speed, inference_geo, service_tier,
|
||||
// input, output, cache_read, cache_write, cache_write_1h, web_search,
|
||||
// web_fetch, code_execution.
|
||||
upsertHourlyUsage: db.prepare(`
|
||||
INSERT INTO token_usage_hourly (session_id, hour, model, speed, inference_geo, service_tier,
|
||||
input_tokens, output_tokens, cache_read_tokens, cache_write_tokens,
|
||||
cache_write_1h_tokens, web_search_requests, web_fetch_requests,
|
||||
code_execution_requests)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(session_id, hour, model, speed, inference_geo, service_tier) DO UPDATE SET
|
||||
input_tokens = MAX(input_tokens, excluded.input_tokens),
|
||||
output_tokens = MAX(output_tokens, excluded.output_tokens),
|
||||
cache_read_tokens = MAX(cache_read_tokens, excluded.cache_read_tokens),
|
||||
cache_write_tokens = MAX(cache_write_tokens, excluded.cache_write_tokens),
|
||||
cache_write_1h_tokens = MAX(cache_write_1h_tokens, excluded.cache_write_1h_tokens),
|
||||
web_search_requests = MAX(web_search_requests, excluded.web_search_requests),
|
||||
web_fetch_requests = MAX(web_fetch_requests, excluded.web_fetch_requests),
|
||||
code_execution_requests = MAX(code_execution_requests, excluded.code_execution_requests)
|
||||
`),
|
||||
getTokenTotals: db.prepare(`
|
||||
SELECT
|
||||
COALESCE(SUM(input_tokens + baseline_input), 0) as total_input,
|
||||
|
||||
Reference in New Issue
Block a user