mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-11 03:13:24 +00:00
fix(usage): repair cost analytics and display precision - Automatic OpenRouter pricing sync with cost backfill for traces/snapshots/events - Live usage data merging for current-hour dashboard accuracy - 2-decimal API cost formatting across usage/overview pages - Comprehensive test coverage (PG, SQLite, HTTP, UI) Merged by github-maintain automation.
102 lines
5.2 KiB
Go
102 lines
5.2 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// UsageSnapshot represents one hourly aggregation row.
|
|
type UsageSnapshot struct {
|
|
ID uuid.UUID `json:"id" db:"id"`
|
|
BucketHour time.Time `json:"bucket_hour" db:"bucket_hour"`
|
|
AgentID *uuid.UUID `json:"agent_id,omitempty" db:"agent_id"`
|
|
Provider string `json:"provider" db:"provider"`
|
|
Model string `json:"model" db:"model"`
|
|
Channel string `json:"channel" db:"channel"`
|
|
InputTokens int64 `json:"input_tokens" db:"input_tokens"`
|
|
OutputTokens int64 `json:"output_tokens" db:"output_tokens"`
|
|
CacheReadTokens int64 `json:"cache_read_tokens" db:"cache_read_tokens"`
|
|
CacheCreateTokens int64 `json:"cache_create_tokens" db:"cache_create_tokens"`
|
|
ThinkingTokens int64 `json:"thinking_tokens" db:"thinking_tokens"`
|
|
TotalCost float64 `json:"total_cost" db:"total_cost"`
|
|
RequestCount int `json:"request_count" db:"request_count"`
|
|
LLMCallCount int `json:"llm_call_count" db:"llm_call_count"`
|
|
ToolCallCount int `json:"tool_call_count" db:"tool_call_count"`
|
|
ErrorCount int `json:"error_count" db:"error_count"`
|
|
UniqueUsers int `json:"unique_users" db:"unique_users"`
|
|
AvgDurationMS int `json:"avg_duration_ms" db:"avg_duration_ms"`
|
|
MemoryDocs int `json:"memory_docs" db:"memory_docs"`
|
|
MemoryChunks int `json:"memory_chunks" db:"memory_chunks"`
|
|
KGEntities int `json:"kg_entities" db:"kg_entities"`
|
|
KGRelations int `json:"kg_relations" db:"kg_relations"`
|
|
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
|
}
|
|
|
|
// SnapshotQuery filters for listing snapshots.
|
|
type SnapshotQuery struct {
|
|
From time.Time // required: start of time range (inclusive)
|
|
To time.Time // required: end of time range (exclusive)
|
|
AgentID *uuid.UUID // optional: filter by agent
|
|
Provider string // optional: cross-filter by provider
|
|
Model string // optional: cross-filter by model
|
|
Channel string // optional: cross-filter by channel
|
|
GroupBy string // "hour" (default), "day", "provider", "model", "provider_model", "channel", "agent"
|
|
}
|
|
|
|
// SnapshotTimeSeries is a single point in a time series response.
|
|
type SnapshotTimeSeries struct {
|
|
BucketTime time.Time `json:"bucket_time" db:"bucket_time"`
|
|
InputTokens int64 `json:"input_tokens" db:"input_tokens"`
|
|
OutputTokens int64 `json:"output_tokens" db:"output_tokens"`
|
|
CacheReadTokens int64 `json:"cache_read_tokens" db:"cache_read_tokens"`
|
|
CacheCreateTokens int64 `json:"cache_create_tokens" db:"cache_create_tokens"`
|
|
ThinkingTokens int64 `json:"thinking_tokens" db:"thinking_tokens"`
|
|
TotalCost float64 `json:"total_cost" db:"total_cost"`
|
|
RequestCount int `json:"request_count" db:"request_count"`
|
|
LLMCallCount int `json:"llm_call_count" db:"llm_call_count"`
|
|
ToolCallCount int `json:"tool_call_count" db:"tool_call_count"`
|
|
ErrorCount int `json:"error_count" db:"error_count"`
|
|
UniqueUsers int `json:"unique_users" db:"unique_users"`
|
|
AvgDurationMS int `json:"avg_duration_ms" db:"avg_duration_ms"`
|
|
MemoryDocs int `json:"memory_docs" db:"memory_docs"`
|
|
MemoryChunks int `json:"memory_chunks" db:"memory_chunks"`
|
|
KGEntities int `json:"kg_entities" db:"kg_entities"`
|
|
KGRelations int `json:"kg_relations" db:"kg_relations"`
|
|
}
|
|
|
|
// SnapshotBreakdown is a grouped aggregation row (by provider, model, etc.).
|
|
type SnapshotBreakdown struct {
|
|
Key string `json:"key" db:"key"`
|
|
InputTokens int64 `json:"input_tokens" db:"input_tokens"`
|
|
OutputTokens int64 `json:"output_tokens" db:"output_tokens"`
|
|
CacheReadTokens int64 `json:"cache_read_tokens" db:"cache_read_tokens"`
|
|
CacheCreateTokens int64 `json:"cache_create_tokens" db:"cache_create_tokens"`
|
|
TotalCost float64 `json:"total_cost" db:"total_cost"`
|
|
RequestCount int `json:"request_count" db:"request_count"`
|
|
LLMCallCount int `json:"llm_call_count" db:"llm_call_count"`
|
|
ToolCallCount int `json:"tool_call_count" db:"tool_call_count"`
|
|
ErrorCount int `json:"error_count" db:"error_count"`
|
|
AvgDurationMS int `json:"avg_duration_ms" db:"avg_duration_ms"`
|
|
}
|
|
|
|
type SnapshotCostBackfillStats struct {
|
|
SnapshotRowsUpdated int64
|
|
}
|
|
|
|
// SnapshotStore manages pre-computed usage snapshots.
|
|
type SnapshotStore interface {
|
|
// UpsertSnapshots inserts or updates (on conflict, replace) a batch of snapshots.
|
|
UpsertSnapshots(ctx context.Context, snapshots []UsageSnapshot) error
|
|
|
|
// GetTimeSeries returns hourly (or daily) aggregated time series.
|
|
GetTimeSeries(ctx context.Context, q SnapshotQuery) ([]SnapshotTimeSeries, error)
|
|
|
|
// GetBreakdown returns aggregated data grouped by a dimension (provider, model, provider_model, channel, agent).
|
|
GetBreakdown(ctx context.Context, q SnapshotQuery) ([]SnapshotBreakdown, error)
|
|
|
|
// GetLatestBucket returns the most recent bucket_hour, used by worker to know where to resume.
|
|
GetLatestBucket(ctx context.Context) (*time.Time, error)
|
|
}
|