Files
neural-hunt/internal/server/artifact_usage.go
jbergner 185ccf1101
All checks were successful
release-tag / release-image (push) Successful in 2m10s
RC-6
2026-08-10 19:44:17 +02:00

144 lines
6.1 KiB
Go

package server
import (
"database/sql"
"net/http"
"strconv"
"time"
)
type artifactUsageRow struct {
CreatedAt time.Time `json:"created_at"`
TaskID string `json:"task_id,omitempty"`
Kind string `json:"kind"`
Model string `json:"model"`
Endpoint string `json:"endpoint"`
Size string `json:"size"`
Quality string `json:"quality"`
RequestID string `json:"request_id,omitempty"`
InputTokens int64 `json:"input_tokens"`
InputTextTokens int64 `json:"input_text_tokens"`
InputImageTokens int64 `json:"input_image_tokens"`
OutputTokens int64 `json:"output_tokens"`
TotalTokens int64 `json:"total_tokens"`
EstimatedCostUSD *float64 `json:"estimated_cost_usd,omitempty"`
PricingBasis string `json:"pricing_basis,omitempty"`
}
func (s *Server) adminArtifactUsage(w http.ResponseWriter, r *http.Request) {
now := time.Now().UTC()
dayStartMS := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC).UnixMilli()
if raw := r.URL.Query().Get("day_start_ms"); raw != "" {
if v, err := strconv.ParseInt(raw, 10, 64); err == nil {
// Accept a browser-local midnight in a reasonable window. This lets the
// "today" KPI follow the admin's local calendar day without adding a
// timezone setting to the server.
candidate := time.UnixMilli(v).UTC()
if candidate.After(now.Add(-48*time.Hour)) && candidate.Before(now.Add(24*time.Hour)) {
dayStartMS = v
}
}
}
var todayCalls, todayCards, todayPriced int64
var todayCost float64
_ = s.store.DB.QueryRowContext(r.Context(), `SELECT count(*),
COALESCE(sum(CASE WHEN kind='artifact' THEN 1 ELSE 0 END),0),
COALESCE(sum(CASE WHEN estimated_cost_usd IS NOT NULL THEN 1 ELSE 0 END),0),
COALESCE(sum(estimated_cost_usd),0)
FROM artifact_api_usage WHERE created_at>=?`, dayStartMS).Scan(&todayCalls, &todayCards, &todayPriced, &todayCost)
var cardCalls, pricedCardCalls int64
var totalCardCost, avgCardCost float64
var inputTokens, inputTextTokens, inputImageTokens, outputTokens, totalTokens int64
_ = s.store.DB.QueryRowContext(r.Context(), `SELECT count(*),count(estimated_cost_usd),
COALESCE(sum(estimated_cost_usd),0),COALESCE(avg(estimated_cost_usd),0),
COALESCE(sum(input_tokens),0),COALESCE(sum(input_text_tokens),0),COALESCE(sum(input_image_tokens),0),
COALESCE(sum(output_tokens),0),COALESCE(sum(total_tokens),0)
FROM artifact_api_usage WHERE kind='artifact'`).Scan(
&cardCalls, &pricedCardCalls, &totalCardCost, &avgCardCost,
&inputTokens, &inputTextTokens, &inputImageTokens, &outputTokens, &totalTokens)
var anchorCalls int64
var anchorCost float64
_ = s.store.DB.QueryRowContext(r.Context(), `SELECT count(*),COALESCE(sum(estimated_cost_usd),0)
FROM artifact_api_usage WHERE kind='character_anchor'`).Scan(&anchorCalls, &anchorCost)
var rollingCalls1H, rollingCalls24H int64
var rollingCost24H float64
oneHourMS := now.Add(-time.Hour).UnixMilli()
twentyFourHoursMS := now.Add(-24 * time.Hour).UnixMilli()
_ = s.store.DB.QueryRowContext(r.Context(), `SELECT
COALESCE(sum(CASE WHEN created_at>=? THEN 1 ELSE 0 END),0),
count(*),COALESCE(sum(estimated_cost_usd),0)
FROM artifact_api_usage WHERE provider='openai' AND created_at>=?`, oneHourMS, twentyFourHoursMS).Scan(&rollingCalls1H, &rollingCalls24H, &rollingCost24H)
cfg := s.settings.Get()
circuitBlocked := (cfg.OpenAIMaxCalls1H > 0 && rollingCalls1H >= int64(cfg.OpenAIMaxCalls1H)) ||
(cfg.OpenAIMaxCalls24H > 0 && rollingCalls24H >= int64(cfg.OpenAIMaxCalls24H)) ||
(cfg.OpenAIMaxCost24HUSD > 0 && rollingCost24H+cfg.OpenAIBudgetReserveUSD > cfg.OpenAIMaxCost24HUSD)
recent := make([]artifactUsageRow, 0, 20)
rows, err := s.store.DB.QueryContext(r.Context(), `SELECT created_at,task_id,kind,model,endpoint,size,quality,request_id,
input_tokens,input_text_tokens,input_image_tokens,output_tokens,total_tokens,estimated_cost_usd,pricing_basis
FROM artifact_api_usage ORDER BY created_at DESC,id DESC LIMIT 20`)
if err == nil {
defer rows.Close()
for rows.Next() {
var createdMS int64
var taskID, requestID sql.NullString
var cost sql.NullFloat64
var row artifactUsageRow
if err := rows.Scan(&createdMS, &taskID, &row.Kind, &row.Model, &row.Endpoint, &row.Size, &row.Quality, &requestID,
&row.InputTokens, &row.InputTextTokens, &row.InputImageTokens, &row.OutputTokens, &row.TotalTokens, &cost, &row.PricingBasis); err != nil {
continue
}
row.CreatedAt = time.UnixMilli(createdMS).UTC()
if taskID.Valid {
row.TaskID = taskID.String
}
if requestID.Valid {
row.RequestID = requestID.String
}
if cost.Valid {
v := cost.Float64
row.EstimatedCostUSD = &v
}
recent = append(recent, row)
}
}
jsonOut(w, 200, map[string]any{
"day_start": time.UnixMilli(dayStartMS).UTC(),
"today_calls": todayCalls,
"today_cards": todayCards,
"today_priced_calls": todayPriced,
"today_cost_usd": todayCost,
"card_generations": cardCalls,
"priced_card_generations": pricedCardCalls,
"total_card_cost_usd": totalCardCost,
"avg_card_cost_usd": avgCardCost,
"cost_per_1000_usd": avgCardCost * 1000,
"anchor_generations": anchorCalls,
"anchor_cost_usd": anchorCost,
"card_usage": map[string]int64{
"input_tokens": inputTokens,
"input_text_tokens": inputTextTokens,
"input_image_tokens": inputImageTokens,
"output_tokens": outputTokens,
"total_tokens": totalTokens,
},
"circuit_breaker": map[string]any{
"blocked": circuitBlocked,
"calls_1h": rollingCalls1H,
"calls_24h": rollingCalls24H,
"cost_24h_usd": rollingCost24H,
"max_calls_1h": cfg.OpenAIMaxCalls1H,
"max_calls_24h": cfg.OpenAIMaxCalls24H,
"max_cost_24h_usd": cfg.OpenAIMaxCost24HUSD,
"reserve_per_call_usd": cfg.OpenAIBudgetReserveUSD,
},
"cost_method": "estimated from provider-reported token usage using pinned OpenAI standard public token rates; not invoice reconciliation",
"recent": recent,
})
}