fix: improve analytics accuracy and cost calculation
Some checks failed
CI / Lint (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Build (push) Has been cancelled

Refined CalculateCost to correctly handle cached token discounts. Added fuzzy matching to model lookup. Robustified SQL date extraction using SUBSTR and LIKE for better SQLite compatibility.
This commit is contained in:
2026-03-19 12:58:08 -04:00
parent e474549940
commit 3f76a544e0
4 changed files with 31 additions and 5 deletions

View File

@@ -1,5 +1,7 @@
package models
import "strings"
type ModelRegistry struct {
Providers map[string]ProviderInfo `json:"-"`
}
@@ -54,5 +56,14 @@ func (r *ModelRegistry) FindModel(modelID string) *ModelMetadata {
}
}
// Try fuzzy matching (e.g. gpt-4o-2024-05-13 matching gpt-4o)
for _, provider := range r.Providers {
for id, model := range provider.Models {
if strings.HasPrefix(modelID, id) {
return &model
}
}
}
return nil
}

View File

@@ -254,9 +254,10 @@ func (s *Server) handleUsageSummary(c *gin.Context) {
COUNT(*) as today_requests,
COALESCE(SUM(cost), 0.0) as today_cost
FROM llm_requests
WHERE strftime('%Y-%m-%d', timestamp) = ?
`, today)
WHERE timestamp LIKE ?
`, today+"%")
if err != nil {
fmt.Printf("[ERROR] Failed to fetch today stats: %v\n", err)
todayStats.TodayRequests = 0
todayStats.TodayCost = 0.0
}
@@ -305,7 +306,8 @@ func (s *Server) handleTimeSeries(c *gin.Context) {
query := fmt.Sprintf(`
SELECT
COALESCE(strftime('%%Y-%%m-%%d', timestamp), 'unknown') as bucket,
COALESCE(SUBSTR(timestamp, 1, 10), 'unknown') as bucket,
COUNT(*) as requests,
COALESCE(SUM(total_tokens), 0) as tokens,
COALESCE(SUM(cost), 0.0) as cost
@@ -468,7 +470,7 @@ func (s *Server) handleDetailedUsage(c *gin.Context) {
query := fmt.Sprintf(`
SELECT
COALESCE(strftime('%%Y-%%m-%%d', timestamp), 'unknown') as date,
COALESCE(SUBSTR(timestamp, 1, 10), 'unknown') as date,
COALESCE(client_id, 'unknown') as client,
COALESCE(provider, 'unknown') as provider,
COALESCE(model, 'unknown') as model,

View File

@@ -376,6 +376,8 @@ func (s *Server) logRequest(start time.Time, clientID, provider, model string, u
// Calculate cost using registry
entry.Cost = utils.CalculateCost(s.registry, model, entry.PromptTokens, entry.CompletionTokens, entry.CacheReadTokens, entry.CacheWriteTokens)
fmt.Printf("[DEBUG] Request logged: model=%s, prompt=%d, completion=%d, cache_read=%d, cost=%f\n",
model, entry.PromptTokens, entry.CompletionTokens, entry.CacheReadTokens, entry.Cost)
}
s.logger.LogRequest(entry)

View File

@@ -40,7 +40,18 @@ func CalculateCost(registry *models.ModelRegistry, modelID string, promptTokens,
return 0.0
}
cost := (float64(promptTokens) * meta.Cost.Input / 1000000.0) +
// promptTokens is usually the TOTAL prompt size.
// We subtract cacheRead from it to get the uncached part.
uncachedTokens := promptTokens
if cacheRead > 0 {
if cacheRead > promptTokens {
uncachedTokens = 0
} else {
uncachedTokens = promptTokens - cacheRead
}
}
cost := (float64(uncachedTokens) * meta.Cost.Input / 1000000.0) +
(float64(completionTokens) * meta.Cost.Output / 1000000.0)
if meta.Cost.CacheRead != nil {