From 3f76a544e05b657e1ccb01a012acb18554c607ec Mon Sep 17 00:00:00 2001 From: hobokenchicken Date: Thu, 19 Mar 2026 12:58:08 -0400 Subject: [PATCH] fix: improve analytics accuracy and cost calculation 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. --- internal/models/registry.go | 11 +++++++++++ internal/server/dashboard.go | 10 ++++++---- internal/server/server.go | 2 ++ internal/utils/registry.go | 13 ++++++++++++- 4 files changed, 31 insertions(+), 5 deletions(-) diff --git a/internal/models/registry.go b/internal/models/registry.go index 6cd2b313..fea0d02f 100644 --- a/internal/models/registry.go +++ b/internal/models/registry.go @@ -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 } diff --git a/internal/server/dashboard.go b/internal/server/dashboard.go index 9e2f44b0..296771ed 100644 --- a/internal/server/dashboard.go +++ b/internal/server/dashboard.go @@ -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, diff --git a/internal/server/server.go b/internal/server/server.go index 02a02c70..66882e01 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -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) diff --git a/internal/utils/registry.go b/internal/utils/registry.go index fb612558..ccf5d4c5 100644 --- a/internal/utils/registry.go +++ b/internal/utils/registry.go @@ -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 {